KCSAN: data-race in __d_drop / retain_dentry

Jianzhou Zhao posted 1 patch 4 weeks ago
KCSAN: data-race in __d_drop / retain_dentry
Posted by Jianzhou Zhao 4 weeks ago


Subject: [BUG] fs: KCSAN: data-race in __d_drop / retain_dentry

Dear Maintainers,

We are writing to report a KCSAN-detected data race vulnerability within the VFS dcache subsystem (`fs/dcache.c` and `include/linux/list_bl.h`). This bug was found by our custom fuzzing tool, RacePilot. The race occurs when `__d_drop()` unhashes a dentry and maliciously clears `dentry->d_hash.pprev` concurrently against a lockless RCU reader executing `retain_dentry()` which calls `hlist_bl_unhashed()` to inspect `pprev`. We observed this bug on the Linux kernel version 6.18.0-08691-g2061f18ad76e-dirty.

Call Trace & Context
==================================================================
BUG: KCSAN: data-race in __d_drop / retain_dentry

write to 0xffff888013db3010 of 8 bytes by task 3021 on cpu 1:
 __d_drop fs/dcache.c:607 [inline]
 __d_drop+0x8c/0xd0 fs/dcache.c:601
 __dentry_kill+0xbd/0x3e0 fs/dcache.c:715
 dput fs/dcache.c:977 [inline]
 dput+0x123/0x220 fs/dcache.c:964
 handle_mounts fs/namei.c:1722 [inline]
 step_into_slowpath+0x688/0x960 fs/namei.c:2081
 ...
 __x64_sys_readlinkat+0x6f/0xa0 fs/stat.c:624

read to 0xffff888013db3010 of 8 bytes by task 4584 on cpu 0:
 hlist_bl_unhashed include/linux/list_bl.h:57 [inline]
 d_unhashed include/linux/dcache.h:374 [inline]
 retain_dentry+0x79/0x320 fs/dcache.c:809
 fast_dput fs/dcache.c:913 [inline]
 dput+0x97/0x220 fs/dcache.c:971
 end_dirop fs/namei.c:2939 [inline]
 do_unlinkat+0x332/0x540 fs/namei.c:5483
 ...
 __x64_sys_unlink+0x7d/0xa0 fs/namei.c:5513

value changed: 0xffff88807dbb55f8 -> 0x0000000000000000

Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 4584 Comm: systemd-udevd Not tainted 6.18.0-08691-g2061f18ad76e-dirty #50 PREEMPT(voluntary) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
==================================================================

Execution Flow & Code Context
When a dentry receives its final decrement during a path operation (e.g., inside `dput`), its lifecycle might traverse `__dentry_kill()` leading to `__d_drop()`. Here, VFS manually eradicates the dentry from the hash list by assigning `NULL` to the internal double-linked list pointer tracker `pprev`:
```c
// fs/dcache.c
void __d_drop(struct dentry *dentry)
{
	if (!d_unhashed(dentry)) {
		___d_drop(dentry);
		...
		dentry->d_hash.pprev = NULL; // <-- Plain concurrent write
		write_seqcount_invalidate(&dentry->d_seq);
	}
}
```

Simultaneously, another thread undergoing an optimistic lockless `dput` (e.g., `fast_dput` resolving symbolic links or unlinking) probes whether the unreferenced dentry should be retained via `retain_dentry()`. `retain_dentry` verifies `d_unhashed()` relying on `hlist_bl_unhashed()`:
```c
// include/linux/list_bl.h
static inline bool hlist_bl_unhashed(const struct hlist_bl_node *h)
{
	return !h->pprev; // <-- Plain concurrent read
}
```

Root Cause Analysis
A KCSAN data race materializes because `__d_drop` resets `dentry->d_hash.pprev` using standard assignments, while `retain_dentry` inspects `pprev` outside the dentry's lock protection. The Linux kernel explicitly permits optimistic RCU verification of `d_unhashed` within `retain_dentry` given that transient inaccuracies gracefully fall through to a strictly-locked verification slow-path (`locked: if (dentry->d_lockref.count || retain_dentry(dentry, true))`). This lockless access is an architectural optimization; however, reading and writing the 8-byte `pprev` pointer without safe `READ_ONCE()` and `WRITE_ONCE()` directives violates the Memory Model constraints under KCSAN and exposes potential tearing risks across compiler transformations. 
Unfortunately, we were unable to generate a reproducer for this bug.

Potential Impact
This data race technically threatens architectures or compilers prone to load/store tearing resulting in a garbled non-NULL `pprev` pointer value snapshot. Nonetheless, functionally, the impact is minuscule: since `retain_dentry` simply falls back to the properly synchronized path if it wrongly presumes the dentry is not unhashed, there is no direct vulnerability. The data race nevertheless generates false-positive diagnostic spam obscuring more pertinent memory corruption flaws.

Proposed Fix
To codify the lockless RCU access paradigm for `hlist_bl_unhashed` reliably across VFS and properly placate KCSAN, we apply `WRITE_ONCE` to the hash decoupling and `READ_ONCE` for the state examination.

```diff
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -604,7 +604,7 @@ void __d_drop(struct dentry *dentry)
 		___d_drop(dentry);
 		__sanitizer_obj_cov_trace_pc(268);
 		__sanitizer_obj_cov_trace_pc(436);
-		dentry->d_hash.pprev = NULL;
+		WRITE_ONCE(dentry->d_hash.pprev, NULL);
 		write_seqcount_invalidate(&dentry->d_seq);
 	}
 }
--- a/include/linux/list_bl.h
+++ b/include/linux/list_bl.h
@@ -54,7 +54,7 @@ static inline bool hlist_bl_unhashed(const struct hlist_bl_node *h)
 	__sanitizer_obj_cov_trace_pc(386);
 	__sanitizer_obj_cov_trace_pc(1106);
 	__sanitizer_obj_cov_trace_pc(1107);
-	return !h->pprev;
+	return !READ_ONCE(h->pprev);
 }
 
 static inline struct hlist_bl_node *hlist_bl_first(struct hlist_bl_head *h)
```

We would be highly honored if this could be of any help.

Best regards,
RacePilot Team
Re: KCSAN: data-race in __d_drop / retain_dentry
Posted by Al Viro 3 hours ago
On Wed, Mar 11, 2026 at 04:02:41PM +0800, Jianzhou Zhao wrote:

> Execution Flow & Code Context
> When a dentry receives its final decrement during a path operation (e.g., inside `dput`), its lifecycle might traverse `__dentry_kill()` leading to `__d_drop()`. Here, VFS manually eradicates the dentry from the hash list by assigning `NULL` to the internal double-linked list pointer tracker `pprev`:
> ```c
> // fs/dcache.c
> void __d_drop(struct dentry *dentry)
> {
> 	if (!d_unhashed(dentry)) {
> 		___d_drop(dentry);
> 		...
> 		dentry->d_hash.pprev = NULL; // <-- Plain concurrent write
> 		write_seqcount_invalidate(&dentry->d_seq);
> 	}
> }
> ```
> 
> Simultaneously, another thread undergoing an optimistic lockless `dput`

Without having held the reference it's dropping?
Re: KCSAN: data-race in __d_drop / retain_dentry
Posted by Al Viro 2 hours ago
On Thu, Apr 09, 2026 at 12:12:40AM +0100, Al Viro wrote:
> On Wed, Mar 11, 2026 at 04:02:41PM +0800, Jianzhou Zhao wrote:
> 
> > Execution Flow & Code Context
> > When a dentry receives its final decrement during a path operation (e.g., inside `dput`), its lifecycle might traverse `__dentry_kill()` leading to `__d_drop()`. Here, VFS manually eradicates the dentry from the hash list by assigning `NULL` to the internal double-linked list pointer tracker `pprev`:
> > ```c
> > // fs/dcache.c
> > void __d_drop(struct dentry *dentry)
> > {
> > 	if (!d_unhashed(dentry)) {
> > 		___d_drop(dentry);
> > 		...
> > 		dentry->d_hash.pprev = NULL; // <-- Plain concurrent write
> > 		write_seqcount_invalidate(&dentry->d_seq);
> > 	}
> > }
> > ```
> > 
> > Simultaneously, another thread undergoing an optimistic lockless `dput`
> 
> Without having held the reference it's dropping?

Note that if the sequence is
A:	fast_dput(): count 1->0
B:	grab reference, count 0->1
B:	drop, reference, count 1->0, grab ->d_lock and proceedi to __dentry_kill()
B:	in __dentry_kill() set count negative
B:	in __dentry_kill() clear ->d_hash.pprev
A:	call retain_dentry()
which is legitimate, not noticing d_unhashed() in retain_dentry() is fine -
fast_dput() will proceed to
	spin_lock(&dentry->d_lock);
	if (dentry->d_lockref.count || retain_dentry(dentry, true)) {
notice that ->d_lockref.count is negative and bugger off to
		spin_unlock(&dentry->d_lock);
		return true;
with rcu_read_lock() still held, same as it would if retain_dentry()
had returned true.

See the comments in fast_dput(), specifically
	/*
	 * Did somebody else grab a reference to it in the meantime, and
	 * we're no longer the last user after all? Alternatively, somebody
	 * else could have killed it and marked it dead. Either way, we
	 * don't need to do anything else.
	 */
Re: KCSAN: data-race in __d_drop / retain_dentry
Posted by Christian Brauner 4 weeks ago
On Wed, Mar 11, 2026 at 04:02:41PM +0800, Jianzhou Zhao wrote:
> 
> 
> Subject: [BUG] fs: KCSAN: data-race in __d_drop / retain_dentry
> Subject: [BUG] fs: KCSAN: data-race in step_into_slowpath / vfs_unlink
> Subject: [BUG] fs/buffer: KCSAN: data-race in __remove_assoc_queue / mark_buffer_dirty_inode
> Subject: [BUG] fs/namei: KCSAN: data-race in path_lookupat / vfs_rename (`d_flags`)

Propose a patch series with fixes and appropriate arguments for adding
these annotations, please. All of the races indicated seem benign.