Re: [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does
From: Josef Bacik
Date: Thu Nov 05 2026 - 11:08:29 EST
Hey Christian,
This is a summary of my Claude review of v2. v2 went out before my mail
on the RFC [1] did, so the first part is what's left of that one with
the new patch numbers and the second part is what's new in v2. What
says "run" was run in a VM on top of v2, the rest is from reading the
code.
3 (selftests Makefile), 4 (pde_seq_next()), 5 (sysctl readdir ending
early) and the allocinfo deadlock from that mail are gone in v2. These
still apply:
- 1 is bugfixes 32/51 ("proc: resume readdir after the last name it
returned") now and the patch is unchanged. Run again, readdir(2)
still returns 1 without a dirent at the end of /proc/fs, bus, driver,
irq, tty, net and self/net, for all of the 100000 calls.
- 2 is cleanup 5/6 ("proc: drop the _buf suffix from the new methods")
now. Run again, the module with .proc_read = seq_read builds without
a warning with W=1, loads without a warning, and every read() fails
with -EFAULT. There's still nothing in porting.rst.
- 6 is bugfixes 47/51, 48/51 and 50/51 now. 47/51 has no Fixes: or
stable tag, 48/51 is tagged for stable and git apply takes it on top
of 46/51 with two hunks offset by 6 lines. 50/51 has the same Fixes:
7e0e953bb0cf.
- All of the smaller stuff. The cursor is allocated in one place since
core 03/23 and it's still GFP_KERNEL, copy_to_user_fromio() loses its
last caller in drivers 24/26, the sentence about ntfs_set_label() is
in fs 07/30, and snd_info_text_entry_write() still drops what was
written before it grows the buffer.
New in v2:
1) bugfixes 01/51 ("alloc_tag: don't remove /proc/allocinfo under the
codetag module lock")
shutdown_mem_profiling_async() is defined outside of #ifdef
CONFIG_MODULES and its three callers are inside of it. Run with
CONFIG_MODULES=n, CONFIG_MEM_ALLOC_PROFILING=y and CONFIG_WERROR=y,
v7.4-rc1 builds mm/alloc_tag.o and with 01/51 it's
mm/alloc_tag.c:620:13: error: 'shutdown_mem_profiling_async' defined but not used [-Werror=unused-function]
This one is tagged for stable.
2) drivers 07/26 ("md: let procfs own the wait queue of /proc/mdstat")
md_exit() used to remove /proc/mdstat first, which waited for the
readers. It now does that after destroy_workqueue(md_misc_wq), so that
nothing can reach proc_poll_notify() on a removed entry. But a reader
can be in here while md_exit() runs:
mddev = list_entry(v, struct mddev, all_mddevs);
if (!mddev_get(mddev))
return 0;
spin_unlock(&all_mddevs_lock);
...
spin_lock(&all_mddevs_lock);
...
mddev_put_locked(mddev);
md_exit() clears the array, but its mddev_put_locked() isn't the last
one anymore, so nothing gets queued and destroy_workqueue() has nothing
to wait for. The reader's put is the last one and ends up in
queue_work(md_misc_wq, &mddev->del_work);
on the destroyed workqueue. It takes an rmmod with an inactive array
left over and somebody reading /proc/mdstat, and this wasn't run.
The two orders each fix one side. If proc_poll_notify() was safe to
call on an entry that's been removed the removal could stay where it
was.
3) core 21/23 ("proc: let lockdep check the pin")
The class is keyed on the proc_ops, and every ALSA text entry uses
snd_info_text_entry_ops, so they're all one class. Run with
PROVE_LOCKING and snd-dummy built in, cat /proc/asound/pcm and then
unbind snd_dummy.0:
WARNING: possible circular locking dependency detected
init/1 is trying to acquire lock:
(pde->in_use){++++}-{0:0}, at: __remove_proc_subtree
but task is already holding lock:
(register_mutex#3){+.+.}-{4:4}, at: snd_pcm_dev_disconnect
-> #1 (register_mutex#3){+.+.}-{4:4}:
snd_pcm_proc_read
snd_info_seq_show
seq_read_iter
-> #0 (pde->in_use){++++}-{0:0}:
proc_entry_rundown
__remove_proc_subtree
snd_info_disconnect_entry
snd_pcm_dev_disconnect
snd_pcm_proc_read() is the only proc callback that takes register_mutex
and it only serves /proc/asound/pcm. What snd_pcm_dev_disconnect()
removes under the mutex is pcmNp and pcmNc, and nothing below those
takes it. So it's a false positive, and it turns lockdep off for
whoever unplugs a sound card. The removal under register_mutex is
already there at your base, only the annotation is new.
proc_create_single_data() gets a key per registration site, but there's
no way for a proc_create_data() user that multiplexes callbacks behind
one proc_ops to say so. sysfs has the per attribute key and
ignore_lockdep for this.
4) core 20/23 ("selftests/proc: check that a mapped entry can be
removed")
The core cover letter says this one "fails without the mmap patch and
passes with it". Run against a virtio-rng device, it passes on core
18/23, which has no proc_vm_ops in System.map, and it passes on the
whole series. The test checks that the removal doesn't wait, that the
entry is gone and that pread() fails with -EIO, and it doesn't touch the
mapping after the removal, so none of the new wrappers run on a removed
entry. These kernels had neither KASAN nor lockdep, so if what fails
for you is a splat then that's the difference, but the test itself
doesn't notice.
5) core 18/23 ("proc: let procfs copy ioctl arguments in and out")
No Signed-off-by in the mail. It's the only one of the 172, and the RFC
version had it.
Smaller stuff:
- The 0/7 mail still says "the xt_recent deadlock fixed in (1) below".
The part 1 cover letter has it right, it goes through netfilter now.
- cleanup 03/06 says mmap() and write() are all that changes for kcore,
kmsg and the kpage files. proc_reg_poll() answered for every regular
file, and kcore_fops and the three kpage ones have no ->poll(), so
EPOLL_CTL_ADD on them goes from working to -EPERM. I doubt anybody
cares, it's only the changelog.
One more old one for part 1, and this is the lockdep patch doing its
job. devm_rtc_unregister_device() holds rtc->ops_lock over
rtc_proc_del_device(), and rtc_proc_show() -> rtc_read_time() takes
ops_lock while it's pinned. Run with PROVE_LOCKING and rtc-test as
rtc0, cat /proc/driver/rtc and then unbind rtc-test.0:
WARNING: possible circular locking dependency detected
init/1 is trying to acquire lock:
(pde->in_use){++++}-{0:0}, at: remove_proc_entry
but task is already holding lock:
(&rtc->ops_lock){+.+.}-{4:4}, at: devm_rtc_unregister_device
-> #1 (&rtc->ops_lock){+.+.}-{4:4}:
rtc_read_time
rtc_proc_show
seq_read_iter
That one is the same entry on both sides, so it's real.
rtc_read_time() uses mutex_lock_interruptible(), so a signal to the
reader gets both of them out of it.
The end state still checks out. Every proc_ops initializer matches the
final struct, nothing copies to or from userspace while pinned, and it
didn't find anything wrong with the new seq_file, open node, poll or
vm_ops code itself.
[1] https://lore.kernel.org/r/464ef986eab97abf226791c7e19ad111.josef@xxxxxxxxxxxxxx
Thanks,
Josef