[PATCH v2 3/4] kernfs: don't hold kernfs_rwsem across dir_emit()

From: Shakeel Butt

Date: Fri Sep 11 2026 - 14:35:43 EST


kernfs_fop_readdir() holds kernfs_rwsem for reading across the whole
listing, dir_emit() included. dir_emit() copies to userspace, so it can
fault into reclaim while holding the lock that every create, remove and
rename in the hierarchy needs. sysfs and cgroupfs have one per machine.

Under memory pressure the monitoring daemons fault on their own
getdents(2) buffer with it held:

below: page allocation stall for 120 secs: order:0,
mode:0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO|__GFP_COMP)
nodemask=(null),cpuset=hostcritical.slice,mems_allowed=0
Call Trace:
<TASK>
dump_stack_lvl+0x5d/0x80
__alloc_frozen_pages_noprof+0x5f4d/0x6300
? memcg_list_lru_alloc+0x73/0x320
? ima_file_check+0xd0/0x7d0
vma_alloc_folio_noprof+0x145/0x560
handle_mm_fault+0x17c9/0x2720
? find_vma+0x27/0x30
do_user_addr_fault+0x39f/0x6e0
exc_page_fault+0x8f/0x110
asm_exc_page_fault+0x22/0x30
RIP: 0010:filldir64+0xd7/0x1a0
[Code:/RSP:/RAX:..R15: register block elided]
kernfs_fop_readdir+0x2de/0x420
iterate_dir+0x8c/0x1f0
__se_sys_getdents64+0x61/0xe0
? copy_page_from_iter+0x860/0x860
do_syscall_64+0x6a/0x250
entry_SYSCALL_64_after_hwframe+0x4b/0x53
</TASK>

Commit 9aab10a0249e ("kernfs: Don't re-lock kernfs_root::kernfs_rwsem in
kernfs_fop_readdir().") took the lock drop out because dir_emit() was
handed kernfs_node::name, which a rename can free. The previous patch
emits a copy instead, so drop the lock around dir_emit().

A listing is no longer atomic within one getdents(2) call, which for
most sysfs and cgroup directories is all of it. POSIX leaves that
unspecified for an entry added or removed since opendir(3).

Fixes: 9aab10a0249e ("kernfs: Don't re-lock kernfs_root::kernfs_rwsem in kernfs_fop_readdir().")
Assisted-by: LLM
Signed-off-by: Shakeel Butt <shakeel.butt@xxxxxxxxx>
---
fs/kernfs/dir.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c
index 6d7d9c9ba33a..cc6288d5b4cc 100644
--- a/fs/kernfs/dir.c
+++ b/fs/kernfs/dir.c
@@ -2027,10 +2027,14 @@ static int kernfs_fop_readdir(struct file *file, struct dir_context *ctx)
file->private_data = pos;
kernfs_get(pos);

- if (!dir_emit(ctx, name, len, ino, type)) {
- up_read(&root->kernfs_rwsem);
+ /*
+ * dir_emit() can fault, so run it unlocked. @pos is pinned
+ * above and kernfs_dir_pos() rechecks it on the way back.
+ */
+ up_read(&root->kernfs_rwsem);
+ if (!dir_emit(ctx, name, len, ino, type))
return 0;
- }
+ down_read(&root->kernfs_rwsem);
}
up_read(&root->kernfs_rwsem);
file->private_data = NULL;
--
2.53.0-Meta