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
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?
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.
*/
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.
© 2016 - 2026 Red Hat, Inc.