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

Shakeel Butt posted 4 patches 1 week, 6 days ago
fs/kernfs/dir.c                               |  85 ++++++--
.../selftests/filesystems/kernfs_test.c       | 200 ++++++++++++++++++
2 files changed, 263 insertions(+), 22 deletions(-)
[PATCH v2 0/4] kernfs: don't hold kernfs_rwsem across dir_emit()
Posted by Shakeel Butt 1 week, 6 days ago
kernfs_fop_readdir() takes kernfs_rwsem for reading and holds it for the
whole listing, dir_emit() included.  dir_emit() copies into a userspace
buffer, so it can fault, and under memory pressure that fault goes to
reclaim.

We are hitting this case on the Meta fleet very regularly.  Many times it
is below[1], a monitoring daemon.  It walks the cgroup tree and faults on
its own getdents(2) buffer with the lock held for reading.

	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>

kernfs_rwsem is the lock every create, remove and rename in the hierarchy
needs, and sysfs and cgroupfs have one per machine.  More importantly, the
userspace OOM killers such as systemd-oomd also traverse the cgroupfs
hierarchy and can get stuck behind below, which is stuck in reclaim.  They
are supposed to relieve memory pressure on the system, but can get stuck
themselves.

The fix is to not hold the lock across dir_emit().  Copy the name while the
lock is held, drop the lock, emit, and take it again.

That opens a window.  With the lock dropped, the entry the listing stopped
on can be removed or renamed before the listing picks up again, and readdir
only remembers its place as a name hash.  Two entries in one directory can
share a hash, so the hash alone does not say where the listing stopped.

Patch 1 makes the fallback search land after the missing entry rather than
on either side of it.  Patch 2 keeps the name of the entry the listing is
on and resumes on the full key.  Patch 3 is the fix.  Patch 4 adds tests.

Patch 1 is worth having on its own.  Without the rest, the same search can
land on the entry before the missing one and report it a second time,
between two getdents(2) calls.

The series applies to the vfs-7.4.kernfs branch of the vfs tree.

Changes since v1 [2]:
 - Split the old patch 2 in two, per Tejun.  Patch 2 adds the name copy and
   the full-key resume with the locking unchanged; patch 3 only drops the
   lock.

[1] https://github.com/facebookincubator/below
[2] https://lore.kernel.org/all/20260910003650.1680854-1-shakeel.butt@linux.dev/

Shakeel Butt (4):
  kernfs: don't repeat or skip an entry when readdir resumes
  kernfs: resume readdir on the full entry key
  kernfs: don't hold kernfs_rwsem across dir_emit()
  selftests: cover readdir resuming at a removed entry

 fs/kernfs/dir.c                               |  85 ++++++--
 .../selftests/filesystems/kernfs_test.c       | 200 ++++++++++++++++++
 2 files changed, 263 insertions(+), 22 deletions(-)


base-commit: 7f24d0f60b1087dbc28d68398957c18774e5a7d8
-- 
2.53.0-Meta
Re: [PATCH v2 0/4] kernfs: don't hold kernfs_rwsem across dir_emit()
Posted by Christian Brauner 1 week, 1 day ago
> kernfs_fop_readdir() takes kernfs_rwsem for reading and holds it for the
> whole listing, dir_emit() included.  dir_emit() copies into a userspace
> buffer, so it can fault, and under memory pressure that fault goes to
> reclaim.
> 
> We are hitting this case on the Meta fleet very regularly.  Many times it
> is below[1], a monitoring daemon.  It walks the cgroup tree and faults on
> its own getdents(2) buffer with the lock held for reading.
> 
> 	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>
> 
> kernfs_rwsem is the lock every create, remove and rename in the hierarchy
> needs, and sysfs and cgroupfs have one per machine.  More importantly, the
> userspace OOM killers such as systemd-oomd also traverse the cgroupfs
> hierarchy and can get stuck behind below, which is stuck in reclaim.  They
> are supposed to relieve memory pressure on the system, but can get stuck
> themselves.
> 
> The fix is to not hold the lock across dir_emit().  Copy the name while the
> lock is held, drop the lock, emit, and take it again.
> 
> That opens a window.  With the lock dropped, the entry the listing stopped
> on can be removed or renamed before the listing picks up again, and readdir
> only remembers its place as a name hash.  Two entries in one directory can
> share a hash, so the hash alone does not say where the listing stopped.
> 
> Patch 1 makes the fallback search land after the missing entry rather than
> on either side of it.  Patch 2 keeps the name of the entry the listing is
> on and resumes on the full key.  Patch 3 is the fix.  Patch 4 adds tests.
> 
> Patch 1 is worth having on its own.  Without the rest, the same search can
> land on the entry before the missing one and report it a second time,
> between two getdents(2) calls.
> 
> The series applies to the vfs-7.4.kernfs branch of the vfs tree.

Looks fine to me although it spaghettifies the code quite a bit.
I'll pull it but wait for Tejun to give it a nod.

--
Re: [PATCH v2 0/4] kernfs: don't hold kernfs_rwsem across dir_emit()
Posted by Christian Brauner 1 week, 1 day ago
On Fri, 11 Sep 2026 11:28:11 -0700, Shakeel Butt wrote:
> kernfs: don't hold kernfs_rwsem across dir_emit()
> 
> kernfs_fop_readdir() takes kernfs_rwsem for reading and holds it for the
> whole listing, dir_emit() included.  dir_emit() copies into a userspace
> buffer, so it can fault, and under memory pressure that fault goes to
> reclaim.
> 
> [...]

Applied to the vfs-7.4.kernfs branch of the vfs/vfs.git tree.
Patches in the vfs-7.4.kernfs branch should appear in linux-next soon.

Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.

It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.

Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs-7.4.kernfs

[1/4] kernfs: don't repeat or skip an entry when readdir resumes
      https://git.kernel.org/vfs/vfs/c/5ebcb578adee
[2/4] kernfs: resume readdir on the full entry key
      https://git.kernel.org/vfs/vfs/c/45126ed80bb1
[3/4] kernfs: don't hold kernfs_rwsem across dir_emit()
      https://git.kernel.org/vfs/vfs/c/341c65579de5
[4/4] selftests: cover readdir resuming at a removed entry
      https://git.kernel.org/vfs/vfs/c/a4354906005d