VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-46052

CVE-2026-46052

Description

In the Linux kernel, the following vulnerability has been resolved:

ceph: only d_add() negative dentries when they are unhashed

Ceph can call d_add(dentry, NULL) on a negative dentry that is already present in the primary dcache hash.

In the current VFS that is not safe. d_add() goes through __d_add() to __d_rehash(), which unconditionally reinserts dentry->d_hash into the hlist_bl bucket. If the dentry is already hashed, reinserting the same node can corrupt the bucket, including creating a self-loop. Once that happens, __d_lookup() can spin forever in the hlist_bl walk, typically looping only on the d_name.hash mismatch check and eventually triggering RCU stall reports like this one:

rcu: INFO: rcu_sched self-detected stall on CPU rcu: 87-....: (2100 ticks this GP) idle=3a4c/1/0x4000000000000000 softirq=25003319/25003319 fqs=829 rcu: (t=2101 jiffies g=79058445 q=698988 ncpus=192) CPU: 87 UID: 2952868916 PID: 3933303 Comm: php-cgi8.3 Not tainted 6.18.17-i1-amd #950 NONE Hardware name: Dell Inc. PowerEdge R7615/0G9DHV, BIOS 1.6.6 09/22/2023 RIP: 0010:__d_lookup+0x46/0xb0 Code: c1 e8 07 48 8d 04 c2 48 8b 00 49 89 fc 49 89 f5 48 89 c3 48 83 e3 fe 48 83 f8 01 77 0f eb 2d 0f 1f 44 00 00 48 8b 1b 48 85 db <74> 20 39 6b 18 75 f3 48 8d 7b 78 e8 ba 85 d0 00 4c 39 63 10 74 1f RSP: 0018:ff745a70c8253898 EFLAGS: 00000282 RAX: ff26e470054cb208 RBX: ff26e470054cb208 RCX: 000000006e958966 RDX: ff26e48267340000 RSI: ff745a70c82539b0 RDI: ff26e458f74655c0 RBP: 000000006e958966 R08: 0000000000000180 R09: 9cd08d909b919a89 R10: ff26e458f74655c0 R11: 0000000000000000 R12: ff26e458f74655c0 R13: ff745a70c82539b0 R14: d0d0d0d0d0d0d0d0 R15: 2f2f2f2f2f2f2f2f FS: 00007f5770896980(0000) GS:ff26e482c5d88000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f5764de50c0 CR3: 000000a72abb5001 CR4: 0000000000771ef0 PKRU: 55555554 Call Trace:

lookup_fast+0x9f/0x100 walk_component+0x1f/0x150 link_path_walk+0x20e/0x3d0 path_lookupat+0x68/0x180 filename_lookup+0xdc/0x1e0 vfs_statx+0x6c/0x140 vfs_fstatat+0x67/0xa0 __do_sys_newfstatat+0x24/0x60 do_syscall_64+0x6a/0x230 entry_SYSCALL_64_after_hwframe+0x76/0x7e

This is reachable with reused cached negative dentries. A Ceph lookup or atomic_open can be handed a negative dentry that is already hashed, and fs/ceph/dir.c then hits one of two paths that incorrectly assume "negative" also means "unhashed":

- ceph_finish_lookup(): MDS reply is -ENOENT with no trace -> d_add(dentry, NULL)

- ceph_lookup(): local ENOENT fast path for a complete directory with shared caps -> d_add(dentry, NULL)

Both paths can therefore re-add an already-hashed negative dentry.

Ceph already uses the correct pattern elsewhere: ceph_fill_trace() only calls d_add(dn, NULL) for a negative null-dentry reply when d_unhashed(dn) is true.

Fix both fs/ceph/dir.c sites the same way: only call d_add() for a negative dentry when it is actually unhashed. If the negative dentry is already hashed, leave it in place and reuse it as-is.

This preserves the existing behavior for unhashed dentries while avoiding d_hash list corruption for reused hashed negatives.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A race condition in Ceph's handling of negative dentries via `d_add()` can corrupt dcache hash buckets, leading to kernel hangs and RCU stall crashes.

Vulnerability

In the Linux kernel’s Ceph filesystem, calling d_add(dentry, NULL) on a negative dentry that is already present in the primary dcache hash can corrupt the hash bucket. The function __d_rehash() unconditionally reinserts the dentry into the hlist_bl bucket, creating a self-loop and destabilizing the dentry cache. This bug affects all kernels where the Ceph client uses d_add() on already-hashed negative dentries; the referenced fix targets kernel versions from 6.18 onward, including 6.18.17-i1-amd [1].

Exploitation

An attacker would require the ability to trigger a Ceph filesystem operation that creates a negative dentry (e.g., a failed lookup) that remains hashed, and then cause a subsequent operation that calls d_add() on that same dentry. No special kernel privileges are needed beyond access to a Ceph mount point. The corruption occurs within the VFS layer during normal path lookup, meaning any user-level process performing file lookups on the Ceph filesystem can trigger the vulnerability.

Impact

Successful exploitation results in a severe denial of service: the system becomes unresponsive as __d_lookup() enters an infinite loop, eventually causing an RCU stall and a kernel panic. The attacker does not gain code execution or data access, but can crash the kernel, affecting all processes on the system. The observed crash shows a CPU stall with the call trace ending in lookup_fast and walk_component [1].

Mitigation

The fix is merged into the Linux kernel stable tree as commit 803447f93d75 [1]. Users should apply this patch or update to a kernel version containing it. No workaround is available other than avoiding Ceph mounts or restricting access to Ceph filesystems until the patched kernel is deployed. The vulnerability is not listed on CISA’s Known Exploited Vulnerabilities (KEV) catalog.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

10
2010cb06b9df

ceph: only d_add() negative dentries when they are unhashed

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMax KellermannMar 27, 2026Fixed in 7.0.4via kernel-cna
2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
b91e535f208c

ceph: only d_add() negative dentries when they are unhashed

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMax KellermannMar 27, 2026Fixed in 6.18.27via kernel-cna
2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index 45e5edecc0cbc4..66e592c47e57a9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index 45e5edecc0cbc4..66e592c47e57a9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
4179cc390dac

ceph: only d_add() negative dentries when they are unhashed

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMax KellermannMar 27, 2026Fixed in 6.12.86via kernel-cna
2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index a0850916d3530f..181ac356ca53b3 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index a0850916d3530f..181ac356ca53b3 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
803447f93d75

ceph: only d_add() negative dentries when they are unhashed

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMax KellermannMar 27, 2026Fixed in 7.1-rc1via kernel-cna
2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
83ce43a21bb7

ceph: only d_add() negative dentries when they are unhashed

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitMax KellermannFixed in 6.6.140via kernel-cna
2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index cc448470fd9b9b..d3bb28939194d9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -745,7 +745,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -813,7 +814,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			__ceph_touch_fmode(ci, mdsc, CEPH_FILE_MODE_RD);
     			spin_unlock(&ci->i_ceph_lock);
     			dout(" dir %p complete, -ENOENT\n", dir);
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index cc448470fd9b9b..d3bb28939194d9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -745,7 +745,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -813,7 +814,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			__ceph_touch_fmode(ci, mdsc, CEPH_FILE_MODE_RD);
     			spin_unlock(&ci->i_ceph_lock);
     			dout(" dir %p complete, -ENOENT\n", dir);
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
b91e535f208c

ceph: only d_add() negative dentries when they are unhashed

2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index 45e5edecc0cbc4..66e592c47e57a9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index 45e5edecc0cbc4..66e592c47e57a9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
803447f93d75

ceph: only d_add() negative dentries when they are unhashed

2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
4179cc390dac

ceph: only d_add() negative dentries when they are unhashed

2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index a0850916d3530f..181ac356ca53b3 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index a0850916d3530f..181ac356ca53b3 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
2010cb06b9df

ceph: only d_add() negative dentries when they are unhashed

2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index bac9cfb6b982f2..27ce9e55e94768 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -769,7 +769,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -840,7 +841,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			spin_unlock(&ci->i_ceph_lock);
     			doutc(cl, " dir %llx.%llx complete, -ENOENT\n",
     			      ceph_vinop(dir));
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
83ce43a21bb7

ceph: only d_add() negative dentries when they are unhashed

2 files changed · +8 6
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index cc448470fd9b9b..d3bb28939194d9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -745,7 +745,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -813,7 +814,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			__ceph_touch_fmode(ci, mdsc, CEPH_FILE_MODE_RD);
     			spin_unlock(&ci->i_ceph_lock);
     			dout(" dir %p complete, -ENOENT\n", dir);
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/dir.c+4 3 modified
    diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
    index cc448470fd9b9b..d3bb28939194d9 100644
    --- a/fs/ceph/dir.c
    +++ b/fs/ceph/dir.c
    @@ -745,7 +745,8 @@ struct dentry *ceph_finish_lookup(struct ceph_mds_request *req,
     				d_drop(dentry);
     				err = -ENOENT;
     			} else {
    -				d_add(dentry, NULL);
    +				if (d_unhashed(dentry))
    +					d_add(dentry, NULL);
     			}
     		}
     	}
    @@ -813,7 +814,8 @@ static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
     			__ceph_touch_fmode(ci, mdsc, CEPH_FILE_MODE_RD);
     			spin_unlock(&ci->i_ceph_lock);
     			dout(" dir %p complete, -ENOENT\n", dir);
    -			d_add(dentry, NULL);
    +			if (d_unhashed(dentry))
    +				d_add(dentry, NULL);
     			di->lease_shared_gen = atomic_read(&ci->i_shared_gen);
     			return NULL;
     		}
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing d_unhashed() check before calling d_add(dentry, NULL) on negative dentries that may already be hashed in the dcache, causing hash bucket corruption via unconditional rehashing."

Attack vector

An attacker can trigger this bug by causing the Ceph filesystem to reuse a cached negative dentry that is already present in the primary dcache hash. When a Ceph lookup or atomic_open operation receives a negative dentry that is already hashed, the functions ceph_finish_lookup() and ceph_lookup() in fs/ceph/dir.c call d_add(dentry, NULL) without first checking d_unhashed(). The d_add() call goes through __d_rehash(), which unconditionally reinserts the dentry's d_hash node into the hlist_bl bucket. If the dentry is already hashed, this reinsertion corrupts the bucket — potentially creating a self-loop — causing __d_lookup() to spin forever and trigger RCU stall warnings, leading to a denial of service on the system.

Affected code

The vulnerability is in fs/ceph/dir.c in two functions: ceph_finish_lookup() (around line 769) and ceph_lookup() (around line 840) [patch_id=2660109]. Both functions unconditionally called d_add(dentry, NULL) on negative dentries without first checking whether the dentry was already hashed.

What the fix does

The patch adds a d_unhashed(dentry) guard before each d_add(dentry, NULL) call in both ceph_finish_lookup() and ceph_lookup() in fs/ceph/dir.c [patch_id=2660109]. If the negative dentry is already hashed, the code now skips the d_add() call entirely and reuses the dentry as-is, avoiding the unsafe rehashing. This matches the correct pattern already used elsewhere in the Ceph codebase (ceph_fill_trace()), which only calls d_add(dn, NULL) when d_unhashed(dn) is true.

Preconditions

  • configSystem must be using the Ceph filesystem (ceph.ko).
  • inputA cached negative dentry that is already hashed in the primary dcache must be reused during a Ceph lookup or atomic_open operation.

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.