[PATCH] fs: stat: Mark accesses in generic_fillattr
From: Can Peng
Date: Fri Jul 10 2026 - 03:16:14 EST
I got the following KCSAN report during syzbot testing:
==================================================================
write to 0xffff9585c0413028 of 4 bytes by task 1047 on cpu 16:
inc_nlink+0x38/0xb0
ramfs_mkdir+0x8e/0xb0
vfs_mkdir+0x147/0x260
do_mkdirat+0x200/0x240
__x64_sys_mkdirat+0x52/0x60
x64_sys_call+0x1bdf/0x1d90
do_syscall_64+0x5b/0x1d0
entry_SYSCALL_64_after_hwframe+0x76/0xe0
read to 0xffff9585c0413028 of 4 bytes by task 1060 on cpu 4:
generic_fillattr+0xeb/0x2d0
vfs_getattr_nosec+0x1f0/0x200
vfs_statx_path+0x19d/0x200
vfs_statx+0xc2/0x130
vfs_fstatat+0x73/0x110
__do_sys_newfstatat+0x52/0xb0
__x64_sys_newfstatat+0x57/0x70
x64_sys_call+0x141a/0x1d90
do_syscall_64+0x5b/0x1d0
entry_SYSCALL_64_after_hwframe+0x76/0xe0
value changed: 0x00000023 -> 0x00000024
==================================================================
KCSAN reports a data race between generic_fillattr() reading
inode->i_nlink while filling st_nlink and inc_nlink() updating
the link count during ramfs_mkdir().
getattr does not take the inode's i_rwsem, so st_nlink
is a snapshot and may observe either the old or new link count
when it races with directory updates. This is expected and
does not require serializing the stat path with link count updates.
Mark the read with data_race(READ_ONCE()) to document
the intentional lockless snapshot and to keep KCSAN from reporting
this benign race.
Signed-off-by: Can Peng <pengcan@xxxxxxxxxx>
---
fs/stat.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/fs/stat.c b/fs/stat.c
index 89909746bed1..5847f10dd20b 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -88,7 +88,11 @@ void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask,
stat->dev = inode->i_sb->s_dev;
stat->ino = inode->i_ino;
stat->mode = inode->i_mode;
- stat->nlink = inode->i_nlink;
+ /*
+ * st_nlink is a snapshot. Link count updates are serialized by
+ * inode->i_rwsem, but getattr does not take that lock.
+ */
+ stat->nlink = data_race(READ_ONCE(inode->i_nlink));
stat->uid = vfsuid_into_kuid(vfsuid);
stat->gid = vfsgid_into_kgid(vfsgid);
stat->rdev = inode->i_rdev;
--
2.53.0