proc: move procfs to what kernfs does
Subsystems (76)
Christian Brauner
maintainercover proc: move procfs to what kernfs does
Ok, as mentioned off-list I have an RFC version of getting rid of page fault induced stalls in procfs when removing entry which plagues networking devices among others.
There was a hackish very narrow AI solution to a part of this bug class that was floated off-list. It used a second buffering mechanism on top of seqfiles which was really nasty. Plus, it didn't fix the whole bug class, it only eliminated a small portion. If we want to get rid of the whole page fault issue we need a bigger hammer. So I tried to work through that over the last couple of weeks. The series in a shape where it's not totally insane to have someone else look at it. So here it is.
I understand it's big but I thought it's probably nicer to look at the end result than just making a promise of what it might end up looking like.
It's not super polished yet and I haven't gotten into sorting out all the warts as there were some really gnarly bugs in there like the whole readdir mess that repeated entries or terminated early. I think we could go a lot further there if we wanted to but I think that blows this out of proportion and at some point it becomes difficult to grasp the scope. So I'd rather delay that until I have a sense of how acceptable this all is.
remove_proc_entry() waits until every call into the entry has returned, and most of those calls copy to or from userspace with the entry pinned.
A reader or writer can make a page fault in that copy take as long as it wants, with userfaultfd or by punching holes into a memfd over and over. And the callers of remove_proc_entry() like to hold locks while they wait. addrconf holds RTNL when it removes /proc/net/dev_snmp6/<dev> and xt_hashlimit holds a mutex that all network namespaces share.
Every network device with IPv6 gets a /proc/net/dev_snmp6/<dev> that everyone can read, and addrconf_ifdown() removes it with RTNL held. So any local user can keep RTNL for as long as their read stays stalled. Until then no device can be registered, removed or renamed and no network namespace can be created or torn down.
Without privileges a FUSE mount in a user namespace whose daemon never answers is enough to stall the copy. Killing the reader ends it, but until then whoever removes the file sits in D state.
xt_hashlimit removes the proc file of a table with hashlimit_mutex held. Every rule insert in every network namespace takes that mutex and so does network namespace cleanup. cleanup_net() runs on a single workqueue with pernet_ops_rwsem held, so a stalled reader also stops every later network namespace teardown and every module load that registers pernet operations. xt_recent does the same with recent_mutex.
And it's not specific to networking. remove_proc_entry(), proc_remove() and remove_proc_subtree() have over 200 callers and plenty of them hold locks. A stalled reader of /proc/fs/ext4/<dev>/options holds up the unmount of that filesystem with s_umount held, and every sync(2) with it. The same goes for PCI hot-removal, for freeing IRQ descriptors, which CPU bring-up waits on, and for unplugging a USB sound card. syzbot has hit removal under locks before (cf. [1]), and the xt_recent deadlock fixed in (1) below is another one.
Stalling the kernel in a copy to or from userspace is a well-known exploitation primitive. It's why unprivileged userfaultfd can't do it by default anymore since v5.11 (d0d4730ac2e4), and FUSE has been used the same way.
kernfs doesn't have that problem. The code of a kernfs file only ever runs with the node pinned and never touches userspace memory, kernfs copies in and out itself. This moves procfs over to the same scheme.
sysfs has only held its active reference around ->show() and ->store() since v2.6.23, the same release that made remove_proc_entry() wait for the entry's callers. A copy to userspace couldn't be stalled for long back then. userfaultfd only came in v4.3 and FUSE in user namespaces in v4.18. configfs moved its copies out of its active reference in v5.3 and DRM copies ioctl arguments in and out around the driver's handler the same way. debugfs still holds its reference across the copies, ran into a deadlock with wifi drivers because of it and grew an API to cancel operations instead (cf. [2]).
That touches just about every ->proc_read(), ->proc_write() and ->proc_ioctl() in the tree, so I've split it up into seven series. They go out as replies to this mail:
(1) proc: fix a few old bugs in /proc handlers (38 patches)
Bugs in the handlers and in procfs itself that are a lot older than the conversion and that reviewing it turned up. Most of them are for stable. They come first so they can go there on their own and the conversion doesn't have to bother with that crap.
(2) proc: let procfs own the data path (16 patches)
The procfs side. procfs owns the seq_file of an entry, copies in writes, copies out reads and copies ioctl arguments in and out, and only calls into an entry with it pinned. proc_create_seq() and friends and the plain seq_file users are switched over here too.
(3) proc: convert the arch and platform entries (22 patches)
(4) proc: convert the driver entries (23 patches)
(5) proc: convert the fs, kernel and mm entries (27 patches)
(6) proc: convert the net entries and the kfifo samples (11 patches)
(7) proc: drop the __user methods (5 patches)
Drops the old methods for every entry that can be removed and gives
the new ones the names kernfs uses, ->proc_read(), ->proc_write()
and ->proc_ioctl(). This needs all four conversion series.The series should all indicate cleanly what dependencies they have so b4 should be able to pull it for review. But I could be wrong.
With all of it on a KASAN and lockdep kernel the removal selftests of every part pass, the proc, pktgen and allocinfo selftests behave like without it and the bugs from (1) that reproduce in a VM are gone. Every patch builds on its own on x86_64 and i386, and with W=1 on the architectures it touches.
[1]: https://lore.kernel.org/r/[email protected]
[2]: https://lore.kernel.org/r/[email protected]
Signature · 1 line
Christian Brauner <[email protected]>
Christian Brauner
maintainercover proc: fix a few old bugs in /proc handlers
Moving procfs to what kernfs does touches just about every ->proc_read(), ->proc_write() and ->proc_ioctl() in the tree. Reviewing that turned up a bunch of bugs that have nothing to do with the conversion and are a lot older. They're fixed here first so they can go to stable on their own and the conversion doesn't have to carry them.
Two of them matter for the conversion itself:
- proc_bus_pci_write() truncates the file position to an int. A pwrite() at an offset like 0xfffffc00 makes it negative and the loop then writes a lot more than it was handed into config space at negative offsets. Once procfs hands it a kernel copy of the write that's a heap out-of-bounds read, so this has to be fixed before the PCI conversion:
BUG: KASAN: slab-out-of-bounds in proc_bus_pci_write+0x528/0x5c0
Read of size 4 at addr ff11000112103b20 by task pci-check/241
Call Trace:
proc_bus_pci_write+0x528/0x5c0
proc_reg_write+0x2d7/0x730
vfs_write+0x1cd/0xfa0
__x64_sys_pwrite64+0x191/0x250isapnp has the same bug on the read side.
- xt_recent removes the proc entries of its tables with a spinlock held when a network namespace goes away. remove_proc_entry() waits for the readers and a reader takes that very lock. That's a deadlock.
/proc/sys readdir resumes a listing at a counted position. The entries are sorted by name, so a device that comes or goes in front of that position shifts them and the next getdents64() repeats an entry or skips one. Even ls needs a second call to find the end of a directory. It resumes after the name of the last entry it returned now, the way kernfs resumes after its last node, and a selftest lists a directory in two pieces around a device coming and going. That one is first, it goes to stable on its own.
/proc/sys also has the bug the conversion fixes for the rest of /proc. proc_sys_call_handler() copies what is read and written and proc_sys_readdir() copies the names of the entries to and from userspace while the sysctl tables are in use. Unregistering a table waits until nobody uses it anymore. So a read(), write() or getdents64() whose buffer faults for as long as userspace wants keeps unregister_sysctl_table() waiting. addrconf unregisters the ipv6 sysctls of a device under RTNL, so that stalls every RTNL user. The handler copies in before it grabs the table and out after letting go of it. readdir doesn't take the tables into use at all anymore. It pins the memory of the entry it is at, which unregistering doesn't wait for, finds the next one under sysctl_lock and emits with nothing held, the way kernfs_fop_readdir() and proc_readdir_de() work. That's the same one trip through sysctl_lock per entry as before. A selftest covers all three.
/proc symlinks have it as well. proc_get_link() pins the entry and vfs_readlink() copies the link to userspace before the delayed call unpins it. ALSA removes /proc/asound/<id> with snd_card_mutex and info_mutex held when a card changes its id, so a faulting readlink() holds those up. The pin keeps nothing alive, the inode holds the entry, so it goes. A selftest covers that too.
Chris' review of the conversion turned up more old bugs, in procfs itself and in its users. Those are here as well:
- __xlate_proc_name() walks through whatever it finds for the components of a name, regular files included, and pktgen hands it the names that the writer of "add_device" picks. So an unprivileged user in namespaces of its own can hang an entry below another file and get it freed behind pktgen's back. procfs doesn't walk through anything but directories anymore and pktgen refuses names with a '/'.
- SUNRPC moved the waitqueue that poll() and epoll use for its channel files into the cache_detail of a network namespace. That's freed when the namespace goes away, but procfs leaves the waiters of a removed file queued. That's a use-after-free an unprivileged user can trigger too. It goes back to a single static waitqueue. procfs should really own the waitqueues of its files like kernfs does. I'll do that separately, it's nothing for stable.
- proc_register() WARNs when a name is already registered, and pktgen lets userspace pick names that collide with its own files. It reports the duplicate with pr_warn() and a stack dump now, like sysfs does.
- proc_readdir_de() resumes at a counted position just like /proc/sys and on top of that ends a listing early when the entry it is emitting goes away. It resumes after the last name now too, and a selftest covers both.
- proc_remove() removes by name, so removing an entry that is already gone can take out a different one. It removes the entry it's given now. And procfs warns about entries that a dying network namespace leaves in /proc/net instead of silently freeing their parent.
- On NOMMU every /proc file can be mapped privately, which reads it with nommu_region_sem held, and reading /proc/maps takes that lock too. Any user can hang the machine with that. procfs files can't be mapped on NOMMU anymore.
- esas2r takes the lengths of its HBA, SMP and firmware ioctls from userspace without checking them against the buffer they're copied into, through a file everyone can open.
- vmcore_cleanup() can run twice at once. powerpc's copy_oldmem_page() doesn't check ioremap_cache() and only recovers from a machine check for read(), not for splice() or vmcore's own reads. The pseries ofdt parser can read past its input and lparcfg reports a failed hcall as a huge write.
The rest is small: dasd looks in front of its buffer on an empty write, toshiba_acpi's input checks can't fail, simdisk reads the file name without the lock that protects it, rtas_flash can read past its message buffer, rtasd hands out whatever vmalloc() left in its log buffer, ALSA's info layer doesn't clamp writes past the end of a data entry, BusLogic, ips and libipw return 0 from writes so stdio writers spin forever, the scsi host files and um's exitcode parse what is written without NUL-terminating it, and ntfs3 warns when a huge label is written.
The PCI, xt_recent, sysctl, readdir, readlink, pktgen, SUNRPC, duplicate name and /proc readdir bugs reproduce on a KASAN and lockdep kernel in a VM and are gone with the series. The rest is build tested only.
The procfs conversion is on top of this, starting with "proc: let procfs own the data path".
---
Christian Brauner (38):
PCI/proc: don't truncate the position of config space accesses
PNP: isapnp: don't read past the end of the buffer on a large pread()
netfilter: xt_recent: don't remove the proc entries under a spinlock s390/dasd: don't look in front of the buffer on an empty write platform/x86: toshiba_acpi: reject malformed brightness and fan writes
xtensa: simdisk: don't read the file name without the lock powerpc/rtas_flash: don't read past the validate message powerpc/rtasd: zero the error log buffer
ALSA: info: don't write past the end of a data entry
scsi: BusLogic: don't return 0 from writes to the proc file
scsi: ips: don't return 0 from writes to the proc file
wifi: libipw: don't return 0 from writes to debug_level
scsi: core: NUL-terminate what is written to the host proc files
um: NUL-terminate what is written to /proc/exitcode
ntfs3: don't warn when a huge label is written
proc: don't walk through a non-directory when resolving a name
net: pktgen: reject device names with a '/'
proc: don't WARN about a name that is already registered
proc: resume readdir after the last name it returned selftests/proc: check a /proc listing while entries come and go
proc: let proc_remove() remove the entry it was given
proc: warn about entries left in /proc/net of a dying namespace
proc: don't let NOMMU map procfs files
proc/vmcore: don't let vmcore_cleanup() run twice at once powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() powerpc/crash_dump: recover from a machine check in copy_oldmem_page()
sunrpc: make queue_wait a global wait queue again
scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls powerpc/pseries: bound the lengths in the ofdt property parser powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long
proc: sysctl: resume readdir after the last name it returned selftests/proc: check a sysctl listing while entries come and go
proc: sysctl: copy to and from userspace without holding the table
proc: sysctl: look at the table under sysctl_lock when creating an inode
proc: sysctl: don't hold the tables while readdir copies to userspace selftests/proc: check that faulting sysctl accesses don't hold up removal
proc: don't pin a symlink's entry while readlink() copies it out selftests/proc: check that a faulting readlink() doesn't hold up removal
arch/powerpc/kernel/crash_dump.c | 6 +- arch/powerpc/kernel/rtas_flash.c | 2 +- arch/powerpc/kernel/rtasd.c | 2 +- arch/powerpc/platforms/pseries/lparcfg.c | 2 +- arch/powerpc/platforms/pseries/reconfig.c | 18 +- arch/um/kernel/exitcode.c | 3 +- arch/xtensa/platforms/iss/simdisk.c | 25 +- drivers/net/wireless/intel/ipw2x00/libipw_module.c | 2 +- drivers/pci/proc.c | 15 +- drivers/platform/x86/toshiba_acpi.c | 8 +- drivers/pnp/isapnp/proc.c | 6 +- drivers/s390/block/dasd.c | 2 +- drivers/scsi/BusLogic.c | 2 +- drivers/scsi/esas2r/esas2r.h | 2 +- drivers/scsi/esas2r/esas2r_ioctl.c | 41 ++- drivers/scsi/esas2r/esas2r_main.c | 3 +- drivers/scsi/ips.c | 2 +- drivers/scsi/scsi_proc.c | 1 + fs/ntfs3/super.c | 11 +- fs/proc/generic.c | 174 ++++++--- fs/proc/inode.c | 38 +- fs/proc/internal.h | 1 + fs/proc/proc_net.c | 7 + fs/proc/proc_sysctl.c | 389 ++++++++++++++------- fs/proc/root.c | 1 + fs/proc/vmcore.c | 5 + include/linux/sunrpc/cache.h | 1 - net/core/pktgen.c | 4 + net/netfilter/xt_recent.c | 4 +- net/sunrpc/cache.c | 12 +- sound/core/info.c | 2 + tools/testing/selftests/proc/.gitignore | 5 + tools/testing/selftests/proc/Makefile | 5 + tools/testing/selftests/proc/config | 6 + tools/testing/selftests/proc/proc-readdir-churn.c | 236 +++++++++++++ .../selftests/proc/proc-remove-slow-readlink.c | 191 ++++++++++ .../selftests/proc/proc-remove-slow-sysctl.c | 213 +++++++++++ .../selftests/proc/proc-sysctl-readdir-churn.c | 147 ++++++++ 38 files changed, 1342 insertions(+), 252 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261101-work-procfs-bugfixes-054d00028491
Christian Brauner
maintainerRFC PATCH 1/38 PCI/proc: don't truncate the position of config space accesses
proc_bus_pci_write() truncates the file position to an int. A pwrite() at an offset like 0xfffff000 turns it negative, the check against the size of the config space passes and the count it clamps to ends up larger than what was written. The loop then reads past the end of the caller's buffer and writes all of it into config space at negative offsets, which pci_user_write_config_*() pass on to the config accessors unchecked. The write also returns more than it was asked to write.
An offset above 4G that truncates to a small positive value writes to the start of config space instead. proc_bus_pci_read() keeps the position in an unsigned int, so a read at such an offset returns the start of config space instead of hitting EOF.
Check the position against the size of the config space before narrowing it.
--- drivers/pci/proc.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-)
drivers/pci/proc.c +8 -7
@@ -32,8 +32,7 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) { struct pci_dev *dev = pde_data(file_inode(file)); -unsigned int pos = *ppos; -unsigned int cnt, size; +unsigned int pos, cnt, size;/* * Normal users can read only the standardized portion of the
@@ -51,8 +50,9 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,if (!nbytes)return 0 ; -if (pos >= size) +if (*ppos >= size)return 0 ; + pos = *ppos;if (nbytes >= size) nbytes = size;if (pos + nbytes > size)
@@ -119,9 +119,8 @@ static ssize_t proc_bus_pci_write(struct file *file, const char __user *buf, {struct inode *ino =file_inode (file);struct pci_dev *dev =pde_data (ino); -int pos = *ppos;int size = dev->cfg_size; -int cnt, ret; +int pos, cnt, ret; ret =security_locked_down (LOCKDOWN_PCI_ACCESS );if (ret)
@@ -130,14 +129,16 @@ static ssize_t proc_bus_pci_write(struct file *file, const char __user *buf,if (!nbytes)return 0 ; +if (*ppos >= size) +return 0 ; + pos = *ppos; +if (resource_is_exclusive (&dev->driver_exclusive_resource, pos, nbytes)) {pci_warn_once (dev,"%s: Unexpected write to kernel-exclusive config offset %x" , current->comm, pos);add_taint (TAINT_USER ,LOCKDEP_STILL_OK ); } -if (pos >= size) -return 0 ;if (nbytes >= size) nbytes = size;if (pos + nbytes > size)
Signature · 1 line
2.53.0
Re: [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses
Quoted 3 lines · Christian Brauner
proc_bus_pci_write()truncates the file position to an int. Apwrite()at an offset like0xfffff000turns it negative [..]
I think this would actually be better with rw_verify_area() just checking a per-file maxbytes.
We already have that logic for lseek and friends - using that fairly hacky combination of s_maxbytes and !unsigned_offsets().
We should combine s_maxbytes and unsigned_offsets() at file open time into f_maxbytes and check that not just at lseek but in rw_verify_area.
And then things like proc_bus_pci could set it's size limit in one single place.
Hmm?
Linus
Christian Brauner
maintainerRe: [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses
Quoted 11 lines · Linus Torvalds
I think this would actually be better with
rw_verify_area()just checking a per-file maxbytes.We already have that logic for lseek and friends - using that fairly hacky combination of
s_maxbytesand !unsigned_offsets().We should combine
s_maxbytesandunsigned_offsets()at file open time intof_maxbytesand check that not just at lseek but inrw_verify_area.And then things like
proc_bus_pcicould set it's size limit in one single place.
Yeah, I like that.
Hm, maybe we should keep this and the isapnp thing small and do that change on top.
This also might mean a change in behavior. Because a read past the end returns 0 for proc_bus_pci and for a regular file past s_maxbytes. So that would potentially need some more changes.
[email protected]
AI reviewRe: [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses
In reply to Christian BraunerSashiko has reviewed this patch and found no issues.
Quoted 14 lines
proc_bus_pci_write()truncates the file position to an int. Apwrite()at an offset like0xfffff000turns it negative, the check against the size of the config space passes and the count it clamps to ends up larger than what was written. The loop then reads past the end of the caller's buffer and writes all of it into config space at negative offsets, which pci_user_write_config_*() pass on to the config accessors unchecked. The write also returns more than it was asked to write.An offset above 4G that truncates to a small positive value writes to the start of config space instead.
proc_bus_pci_read()keeps the position in an unsigned int, so a read at such an offset returns the start of config space instead of hitting EOF.
Quoted 6 lines
Check the position against the size of the config space before narrowing it.
Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Christian Brauner
maintainerRFC PATCH 2/38 PNP: isapnp: don't read past the end of the buffer on a large pread()
In reply to Christian Braunerisapnp_proc_bus_read() truncates the position to an int. A pread() at an offset like 0xfffffc00 turns it negative, the check against the size of the configuration space passes, and the loop then puts everything from that position up to byte 256 into the reader's buffer, no matter how few bytes were asked for.
Keep the position as a loff_t and clamp the count against what is left of the configuration space.
--- drivers/pnp/isapnp/proc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
drivers/pnp/isapnp/proc.c +2 -4
@@ -23,14 +23,12 @@ static ssize_t isapnp_proc_bus_read(struct file *file, char __user * buf, size_t nbytes, loff_t * ppos) { struct pnp_dev *dev = pde_data(file_inode(file)); -int pos = *ppos; +loff_t pos = *ppos;int cnt, size =256 ;if (pos >= size)return 0 ; -if (nbytes >= size) - nbytes = size; -if (pos + nbytes > size) +if (nbytes > size - pos) nbytes = size - pos; cnt = nbytes;
Signature · 1 line
2.53.0
Re: [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread()
Quoted 7 lines · Christian Brauner
isapnp_proc_bus_read()truncates the position to an int. Apread()at an offset like0xfffffc00turns it negative, the check against the size of the configuration space passes, and the loop then puts everything from that position up to byte 256 into the reader's buffer, no matter how few bytes were asked for.
Quoted 6 lines
Keep the position as a
loff_tand clamp the count against what is left of the configuration space.Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Quoted 30 lines
--- drivers/pnp/isapnp/proc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)drivers/pnp/isapnp/proc.c +2
-5@@ -23,14 +23,12 @@ static ssize_t isapnp_proc_bus_read(struct file *file, char __user * buf, size_t nbytes, loff_t * ppos) { struct pnp_dev *dev = pde_data(file_inode(file)); -int pos = *ppos; +loff_t pos = *ppos;int cnt, size =256 ;if (pos >= size)return 0 ; -if (nbytes >= size) - nbytes = size; -if (pos + nbytes > size) +if (nbytes > size - pos) nbytes = size - pos; cnt = nbytes; --2.53.0
[email protected]
AI reviewRe: [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread()
In reply to Christian BraunerSashiko has reviewed this patch and found no issues.
Quoted 6 lines
isapnp_proc_bus_read()truncates the position to an int. Apread()at an offset like0xfffffc00turns it negative, the check against the size of the configuration space passes, and the loop then puts everything from that position up to byte 256 into the reader's buffer, no matter how few bytes were asked for.
Quoted 6 lines
Keep the position as a
loff_tand clamp the count against what is left of the configuration space.Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Christian Brauner
maintainerRFC PATCH 3/38 netfilter: xt_recent: don't remove the proc entries under a spinlock
In reply to Christian Braunerrecent_proc_net_exit() removes the proc entries of all tables with recent_lock held. remove_proc_entry() waits until nobody calls into the entry anymore, and reading one of them takes recent_lock in recent_seq_start() while procfs has the entry pinned. So when a reader gets there while the network namespace goes away, the exit path sleeps with a spinlock held and bh disabled, waiting for a reader that spins on that very lock:
BUG: sleeping function called from invalid context at kernel/sched/completion.c:116
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 12, name: kworker/u16:0
locks held by kworker/u16:0/12: 4, last CPU#2:
#2: (pernet_ops_rwsem){++++}-{4:4}, at: cleanup_net+0xbf/0x860
#3: (recent_lock){+...}-{3:3}, at: recent_net_exit+0x3d/0x170 [xt_recent]
Workqueue: netns cleanup_net
Call Trace:
wait_for_completion+0x8e/0x400
proc_entry_rundown+0x1ab/0x1f0
remove_proc_entry+0x1ab/0x530
recent_net_exit+0x9f/0x170 [xt_recent]
ops_undo_list+0x271/0x8d0
cleanup_net+0x3f3/0x860
process_one_work+0x946/0x1820
BUG: scheduling while atomic: kworker/u16:0/12/0x00000202
watchdog: BUG: soft lockup - CPU#0 stuck for 26s! [rloop:252]
Call Trace:
_raw_spin_lock_bh+0x7a/0x80
recent_seq_start+0x86/0x1e0 [xt_recent]
seq_read_iter+0x26d/0x1230
seq_read+0x2a9/0x560
proc_reg_read+0x1c2/0x3c0
vfs_read+0x176/0xb40
__x64_sys_pread64+0x191/0x250Tables are added and removed with recent_mutex held and that's also what recent_mt_destroy() looks at ->xt_recent under. Nothing that runs with a proc entry pinned takes it. So take recent_mutex instead.
--- net/netfilter/xt_recent.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
net/netfilter/xt_recent.c +2 -2
@@ -652,12 +652,12 @@ static void __net_exit recent_proc_net_exit(struct net *net)* that the parent xt_recent proc entry is empty before trying to * remove it. */ -spin_lock_bh (&recent_lock); +mutex_lock (&recent_mutex);list_for_each_entry (t, &recent_net->tables, list)remove_proc_entry (t->name, recent_net->xt_recent); recent_net->xt_recent =NULL ; -spin_unlock_bh (&recent_lock); +mutex_unlock (&recent_mutex);remove_proc_entry ("xt_recent" , net->proc_net); }
Signature · 1 line
2.53.0
Florian Westphal
maintainerRe: [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock
Quoted 7 lines · Christian Brauner
recent_proc_net_exit()removes the proc entries of all tables withrecent_lockheld.remove_proc_entry()waits until nobody calls into the entry anymore, and reading one of them takesrecent_lockinrecent_seq_start()while procfs has the entry pinned. So when a reader gets there while the network namespace goes away, the exit path sleeps with a spinlock held and bh disabled, waiting for a reader that spins on that very lock:
Yes, but note that this change is already part of a pending nf -> net merge request.
Christian Brauner
maintainerRe: [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock
Quoted 2 lines · Florian Westphal
Yes, but note that this change is already part of a pending nf -> net merge request.
Dropped.
[email protected]
AI reviewRe: [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock
In reply to Christian BraunerSashiko has reviewed this patch and found no issues.
Quoted 15 lines
recent_proc_net_exit()removes the proc entries of all tables withrecent_lockheld.remove_proc_entry()waits until nobody calls into the entry anymore, and reading one of them takesrecent_lockinrecent_seq_start()while procfs has the entry pinned. So when a reader gets there while the network namespace goes away, the exit path sleeps with a spinlock held and bh disabled, waiting for a reader that spins on that very lock:BUG: sleeping function called from invalid context at kernel/sched/completion.c:116 in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 12, name: kworker/u16:0 locks held by kworker/u16:0/12: 4, last CPU#2: #2: (pernet_ops_rwsem){++++}-{4:4}, at: cleanup_net+0xbf/0x860 #3: (recent_lock){+...}-{3:3}, at: recent_net_exit+0x3d/0x170 [xt_recent] Workqueue: netns cleanup_net Call Trace:
Quoted 6 lines
[ ... ]
with a proc entry pinned takes it. So takerecent_mutexinstead.Fixes 665e205c16c1 ("netfilter: xt_recent: fix namespace destroy path")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Christian Brauner
maintainerRFC PATCH 4/38 s390/dasd: don't look in front of the buffer on an empty write
In reply to Christian Braunerdasd_get_user_string() strips a trailing newline by looking at the last byte that was written. For an empty write that's the byte in front of the buffer it just allocated. Only look if something was written.
--- drivers/s390/block/dasd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/s390/block/dasd.c +1 -1
@@ -933,7 +933,7 @@ char *dasd_get_user_string(const char __user *user_buf, size_t user_len)return ERR_PTR (-EFAULT ); }/* got the string, now strip linefeed. */ -if (buffer[user_len -1 ] =='\n' ) +if (user_len && buffer[user_len -1 ] =='\n' ) buffer[user_len -1 ] =0 ;else buffer[user_len] =0 ;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 04/38] s390/dasd: don't look in front of the buffer on an empty write
Sashiko has reviewed this patch and found no issues.
Quoted 7 lines
dasd_get_user_string()strips a trailing newline by looking at the last byte that was written. For an empty write that's the byte in front of the buffer it just allocated. Only look if something was written.Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Christian Brauner
maintainerRFC PATCH 5/38 platform/x86: toshiba_acpi: reject malformed brightness and fan writes
In reply to Christian BraunerWhen lcd_proc_write() and fan_proc_write() were refactored their checks were inverted but the && stayed. They only fail if sscanf() didn't parse anything and the value is out of range at the same time, and for the brightness value < 0 && value > levels can't be true at all. So an out of range brightness or fan setting is passed on to the hardware, and so is the uninitialized value if nothing could be parsed.
Restore the checks as they were before the refactoring.
--- drivers/platform/x86/toshiba_acpi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
drivers/platform/x86/toshiba_acpi.c +4 -4
@@ -1498,8 +1498,8 @@ static ssize_t lcd_proc_write(struct file *file, const char __user *buf, cmd[len] ='\0' ; levels = dev->backlight_dev->props.max_brightness +1 ; -if (sscanf (cmd," brightness : %i" , &value) !=1 && - value <0 && value > levels) +if (sscanf (cmd," brightness : %i" , &value) !=1 || + value <0 || value >= levels)return -EINVAL ;if (set_lcd_brightness (dev, value))
@@ -1678,8 +1678,8 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf,return -EFAULT ; cmd[len] ='\0' ; -if (sscanf (cmd," force_on : %i" , &value) !=1 && - value !=0 && value !=1 ) +if (sscanf (cmd," force_on : %i" , &value) !=1 || + value <0 || value >1 )return -EINVAL ;if (set_fan_status (dev, value))
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 05/38] platform/x86: toshiba_acpi: reject malformed brightness and fan writes
Sashiko has reviewed this patch and found no issues.
Quoted 7 lines
When
lcd_proc_write()andfan_proc_write()were refactored their checks were inverted but the && stayed. They only fail ifsscanf()didn't parse anything and the value is out of range at the same time, and for the brightness value < 0 && value > levels can't be true at all. So an out of range brightness or fan setting is passed on to the hardware, and so is the uninitialized value if nothing could be parsed.
Quoted 6 lines
Restore the checks as they were before the refactoring.
Fixes e1a949c1b988 ("toshiba_acpi: Refactor *{get, set} functions return value")Fixes 3e07e5baa2af ("toshiba_acpi: Add set_fan_status function")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Christian Brauner
maintainerRFC PATCH 6/38 xtensa: simdisk: don't read the file name without the lock
In reply to Christian Braunerproc_read_simdisk() looks at dev->filename without holding dev->lock. A write to the same file detaches the disk and frees the name under the lock, so a concurrent reader can copy out freed memory. Copy the name with the lock held.
--- arch/xtensa/platforms/iss/simdisk.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-)
arch/xtensa/platforms/iss/simdisk.c +12 -13
@@ -238,22 +238,21 @@ static ssize_t proc_read_simdisk(struct file *file, char __user *buf, size_t size, loff_t *ppos) { struct simdisk *dev = pde_data(file_inode(file)); -const char *s = dev->filename; -if (s) { -ssize_t len =strlen (s); -char *temp =kmalloc (len +2 ,GFP_KERNEL ); +ssize_t len; +char *temp; -if (!temp) -return -ENOMEM ; +/* a write sets at most a page, see proc_write_simdisk() */ + temp =kmalloc (PAGE_SIZE +2 ,GFP_KERNEL ); +if (!temp) +return -ENOMEM ; - len =scnprintf (temp, len +2 ,"%s\n" , s); - len =simple_read_from_buffer (buf, size, ppos, - temp, len); +spin_lock (&dev->lock); + len =scnprintf (temp,PAGE_SIZE +2 ,"%s\n" , dev->filename ?:"" ); +spin_unlock (&dev->lock); -kfree (temp); -return len; - } -return simple_read_from_buffer (buf, size, ppos,"\n" ,1 ); + len =simple_read_from_buffer (buf, size, ppos, temp, len); +kfree (temp); +return len; } static ssize_t proc_write_simdisk(struct file *file, const char __user *buf,
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 06/38] xtensa: simdisk: don't read the file name without the lock
Sashiko has reviewed this patch and found no issues.
Quoted 8 lines
proc_read_simdisk()looks atdev->filenamewithout holdingdev->lock. A write to the same file detaches the disk and frees the name under the lock, so a concurrent reader can copy out freed memory. Copy the name with the lock held.Fixes b6c7e873daf7 ("xtensa: ISS: add host file-based simulated disk")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
Christian Brauner
maintainerRFC PATCH 7/38 powerpc/rtas_flash: don't read past the validate message
In reply to Christian Braunerget_validate_flash_msg() adds up what snprintf() returns, which is the length of the whole string and not what fit into msg. If the update results the firmware hands back don't fit into what is left of the 256 bytes, the length ends up larger than msg and validate_flash_read() copies whatever follows it on the stack to userspace. Use scnprintf().
--- arch/powerpc/kernel/rtas_flash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/powerpc/kernel/rtas_flash.c +1 -1
@@ -448,7 +448,7 @@ static int get_validate_flash_msg(struct rtas_validate_flash_t *args_buf, n =sprintf (msg,"%d\n" , args_buf->update_results);if ((args_buf->update_results >=VALIDATE_CUR_UNKNOWN ) || (args_buf->update_results ==VALIDATE_TMP_UPDATE )) - n +=snprintf (msg + n, msglen - n,"%s\n" , + n +=scnprintf (msg + n, msglen - n,"%s\n" , args_buf->buf); }else { n =sprintf (msg,"%d\n" , args_buf->status);
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 07/38] powerpc/rtas_flash: don't read past the validate message
Sashiko has reviewed this patch and found no issues.
Quoted 9 lines
get_validate_flash_msg()adds up whatsnprintf()returns, which is the length of the whole string and not what fit into msg. If the update results the firmware hands back don't fit into what is left of the 256 bytes, the length ends up larger than msg andvalidate_flash_read()copies whatever follows it on the stack to userspace. Usescnprintf().Fixes a94a14720eaf ("powerpc/rtas_flash: Fix validate_flash buffer overflow issue")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
Christian Brauner
maintainerRFC PATCH 8/38 powerpc/rtasd: zero the error log buffer
In reply to Christian Braunerrtas_log_read() hands out a whole slot of the error log but pSeries_log_error() only fills in the sequence number and the event, which is usually a lot shorter. The rest of the slot is whatever vmalloc() left there. Use vzalloc().
--- arch/powerpc/kernel/rtasd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/powerpc/kernel/rtasd.c +1 -1
@@ -523,7 +523,7 @@ static int __init rtas_event_scan_init(void) rtas_error_log_max =rtas_get_error_log_max (); rtas_error_log_buffer_max = rtas_error_log_max +sizeof (int ); - rtas_log_buf =vmalloc (array_size (LOG_NUMBER , + rtas_log_buf =vzalloc (array_size (LOG_NUMBER , rtas_error_log_buffer_max));if (!rtas_log_buf) {printk (KERN_ERR "rtasd: no memory\n" );
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 08/38] powerpc/rtasd: zero the error log buffer
Sashiko has reviewed this patch and found no issues.
Quoted 8 lines
rtas_log_read()hands out a whole slot of the error log butpSeries_log_error()only fills in the sequence number and the event, which is usually a lot shorter. The rest of the slot is whatevervmalloc()left there. Usevzalloc().Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
Christian Brauner
maintainerRFC PATCH 9/38 ALSA: info: don't write past the end of a data entry
In reply to Christian Braunersnd_info_entry_write() clamps the count to what is left of the entry by computing entry->size - pos as a size_t. For a position past the end that wraps and the write goes to the driver unclamped, for opl4 into sample memory outside of what the entry covers. At the end of the entry the driver gets a count of 0 and vmalloc(0) warns. Before the clamp moved into the common code the driver ignored such writes. Fail them with -ENOSPC instead, like a block device does at the end of the device. Returning 0 would make writers that use stdio retry forever.
--- sound/core/info.c | 2 ++ 1 file changed, 2 insertions(+)
sound/core/info.c +2 -0
@@ -171,6 +171,8 @@ static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer pos = *offset;if (!valid_pos (pos, count))return -EIO ; +if (pos >= entry->size) +return count ? -ENOSPC :0 ;if (count >0 ) {size_t maxsize = entry->size - pos; count =min (count, maxsize);
Signature · 1 line
2.53.0
Re: [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry
Quoted 10 lines · Christian Brauner
snd_info_entry_write()clamps the count to what is left of the entry by computingentry->size- pos as asize_t. For a position past the end that wraps and the write goes to the driver unclamped, for opl4 into sample memory outside of what the entry covers. At the end of the entry the driver gets a count of 0 and vmalloc(0) warns. Before the clamp moved into the common code the driver ignored such writes. Fail them with-ENOSPCinstead, like a block device does at the end of the device. Returning 0 would make writers that use stdio retry forever.
Quoted 3 lines
Fixes d97e1b78239c ("ALSA: info - Check file position validity in common layer")Signed-off-by Christian Brauner (Amutable) <[email protected]>
thanks,
Takashi
Quoted 21 lines
--- sound/core/info.c | 2 ++ 1 file changed, 2 insertions(+)sound/core/info.c +2
-0@@ -171,6 +171,8 @@ static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer pos = *offset;if (!valid_pos (pos, count))return -EIO ; +if (pos >= entry->size) +return count ? -ENOSPC :0 ;if (count >0 ) {size_t maxsize = entry->size - pos; count =min (count, maxsize);Signature · 2 lines
-- 2.53.0
[email protected]
AI reviewRe: [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry
In reply to Christian BraunerSashiko has reviewed this patch and found no issues.
Quoted 9 lines
snd_info_entry_write()clamps the count to what is left of the entry by computingentry->size- pos as asize_t. For a position past the end that wraps and the write goes to the driver unclamped, for opl4 into sample memory outside of what the entry covers. At the end of the entry the driver gets a count of 0 and vmalloc(0) warns. Before the clamp moved into the common code the driver ignored such writes. Fail them with-ENOSPCinstead, like a block device does at the end of the device. Returning 0 would make writers that use stdio retry forever.
Quoted 3 lines
Fixes d97e1b78239c ("ALSA: info - Check file position validity in common layer")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Christian Brauner
maintainerRFC PATCH 10/38 scsi: BusLogic: don't return 0 from writes to the proc file
In reply to Christian BraunerA write to /proc/scsi/BusLogic/<n> resets the statistics and returns 0. Writers that use stdio, echo for example, take that as no progress and retry forever. Return the number of bytes that were written.
--- drivers/scsi/BusLogic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/scsi/BusLogic.c +1 -1
@@ -3328,7 +3328,7 @@ static int blogic_write_info(struct Scsi_Host *shost, char *procbuf, adapter->ext_resets =0 ; adapter->adapter_intern_errors =0 ;memset (tgt_stats,0 ,BLOGIC_MAXDEV *sizeof (struct blogic_tgt_stats )); -return 0 ; +return bytes_avail; }static int blogic_show_info (struct seq_file *m,struct Scsi_Host *shost)
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 10/38] scsi: BusLogic: don't return 0 from writes to the proc file
Sashiko has reviewed this patch and found no issues.
Quoted 6 lines
A write to /proc/scsi/BusLogic/<n> resets the statistics and returns 0. Writers that use stdio, echo for example, take that as no progress and retry forever. Return the number of bytes that were written.
Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Christian Brauner
maintainerRFC PATCH 11/38 scsi: ips: don't return 0 from writes to the proc file
In reply to Christian Braunerips_write_info() ignores what is written to /proc/scsi/ips/<n> and returns 0. Writers that use stdio, echo for example, take that as no progress and retry forever. Return the number of bytes that were written.
--- drivers/scsi/ips.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/scsi/ips.c +1 -1
@@ -1428,7 +1428,7 @@ ips_write_info(struct Scsi_Host *host, char *buffer, int length)if (!ha)return (-EINVAL ); -return 0 ; +return length; } static int
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 11/38] scsi: ips: don't return 0 from writes to the proc file
Sashiko has reviewed this patch and found no issues.
Quoted 7 lines
ips_write_info()ignores what is written to /proc/scsi/ips/<n> and returns 0. Writers that use stdio, echo for example, take that as no progress and retry forever. Return the number of bytes that were written.Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
Christian Brauner
maintainerRFC PATCH 12/38 wifi: libipw: don't return 0 from writes to debug_level
In reply to Christian Braunerdebug_level_proc_write() returns the length of the string it parsed. A write that starts with a NUL byte thus returns 0 and writers that use stdio retry it forever. Return the number of bytes that were written instead, which also stops writes longer than the 11 bytes it looks at from coming up short.
--- drivers/net/wireless/intel/ipw2x00/libipw_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/net/wireless/intel/ipw2x00/libipw_module.c +1 -1
@@ -236,7 +236,7 @@ static ssize_t debug_level_proc_write(struct file *file,else libipw_debug_level = val; -return strnlen (buf, len); +return count; }static const struct proc_ops debug_level_proc_ops = {
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 12/38] wifi: libipw: don't return 0 from writes to debug_level
Sashiko has reviewed this patch and found no issues.
Quoted 8 lines
debug_level_proc_write()returns the length of the string it parsed. A write that starts with a NUL byte thus returns 0 and writers that use stdio retry it forever. Return the number of bytes that were written instead, which also stops writes longer than the 11 bytes it looks at from coming up short.Fixes b453872c35cf ("[NET] ieee80211 subsystem")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
Christian Brauner
maintainerRFC PATCH 13/38 scsi: core: NUL-terminate what is written to the host proc files
In reply to Christian Braunerproc_scsi_host_write() copies what is written into a page and hands it to ->write_info() without terminating it. ppa and imm parse it with strncmp() and simple_strtoul(), which read on past what was written into whatever the page held before. Terminate the copy. A write is at most PROC_BLOCK_SIZE bytes, so the page always has room for that.
--- drivers/scsi/scsi_proc.c | 1 + 1 file changed, 1 insertion(+)
drivers/scsi/scsi_proc.c +1 -0
@@ -80,6 +80,7 @@ static ssize_t proc_scsi_host_write(struct file *file, const char __user *buf, ret = -EFAULT ;if (copy_from_user (page, buf, count)) goto out; + page[count] ='\0' ; ret = shost->hostt->write_info (shost, page, count); } out:
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 13/38] scsi: core: NUL-terminate what is written to the host proc files
Sashiko has reviewed this patch and found no issues.
Quoted 9 lines
proc_scsi_host_write()copies what is written into a page and hands it to ->write_info() without terminating it. ppa and imm parse it withstrncmp()andsimple_strtoul(), which read on past what was written into whatever the page held before. Terminate the copy. A write is at mostPROC_BLOCK_SIZEbytes, so the page always has room for that.Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
Christian Brauner
maintainerRFC PATCH 14/38 um: NUL-terminate what is written to /proc/exitcode
In reply to Christian Braunerexitcode_proc_write() copies up to sizeof(buf) bytes of the write into buf and hands it to simple_strtol() without terminating it. So "echo -n 5" parses whatever the stack held after the '5', and a write of seven bytes or more reads past the end of buf. Terminate the copy.
--- arch/um/kernel/exitcode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
arch/um/kernel/exitcode.c +2 -1
@@ -43,9 +43,10 @@ static ssize_t exitcode_proc_write(struct file *file,size_t size;int tmp; - size =min (count,sizeof (buf)); + size =min (count,sizeof (buf) -1 );if (copy_from_user (buf, buffer, size))return -EFAULT ; + buf[size] ='\0' ; tmp =simple_strtol (buf, &end,0 );if ((*end !='\0' ) && !isspace (*end))
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 14/38] um: NUL-terminate what is written to /proc/exitcode
Sashiko has reviewed this patch and found no issues.
Quoted 8 lines
exitcode_proc_write()copies up to sizeof(buf) bytes of the write into buf and hands it tosimple_strtol()without terminating it. So "echo -n 5" parses whatever the stack held after the '5', and a write of seven bytes or more reads past the end of buf. Terminate the copy.Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
Christian Brauner
maintainerRFC PATCH 15/38 ntfs3: don't warn when a huge label is written
In reply to Christian Braunerntfs3_label_write() allocates as many bytes as are written with plain kmalloc(). A write that is larger than what kmalloc() can hand out then trips the page allocator's warning, which panics a panic_on_warn kernel. Use memdup_user(), which fails such an allocation quietly with -ENOMEM like before.
--- fs/ntfs3/super.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-)
fs/ntfs3/super.c +3 -8
@@ -552,15 +552,10 @@ static ssize_t ntfs3_label_write(struct file *file, const char __user *buffer,if (sb_rdonly (sb))return -EROFS ; - label =kmalloc (count,GFP_NOFS ); + label =memdup_user (buffer, count); +if (IS_ERR (label)) +return PTR_ERR (label); -if (!label) -return -ENOMEM ; - -if (copy_from_user (label, buffer, ret)) { - ret = -EFAULT ; - goto out; - }while (ret >0 && label[ret -1 ] =='\n' ) ret -=1 ;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 15/38] ntfs3: don't warn when a huge label is written
Sashiko has reviewed this patch and found no issues.
Quoted 9 lines
ntfs3_label_write()allocates as many bytes as are written with plainkmalloc(). A write that is larger than whatkmalloc()can hand out then trips the page allocator's warning, which panics apanic_on_warnkernel. Usememdup_user(), which fails such an allocation quietly with-ENOMEMlike before.Fixes 7832e123490a ("fs/ntfs3: Add support /proc/fs/ntfs3/<dev>/volinfo and /proc/fs/ntfs3/<dev>/label")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
Christian Brauner
maintainerRFC PATCH 16/38 proc: don't walk through a non-directory when resolving a name
In reply to Christian Brauner__xlate_proc_name() walks the components of a name like "a/b/c" and takes whatever it finds for "a" and "b" as the directory to go on in. It never checks that they actually are directories. So proc_create("file/x") hangs x into the subdir tree of the regular file "file". Nobody can look x up there, but remove_proc_subtree() on the file removes and frees x along with it, behind the back of whoever registered x.
pktgen passes the name the writer of "add_device" picks to proc_create_data(), and only the part in front of the '@' has to name a network device:
echo "add_device lo@" > /proc/net/pktgen/kpktgend_0
echo "add_device lo@/x" > /proc/net/pktgen/kpktgend_1
echo "rem_device_all" > /proc/net/pktgen/kpktgend_0
echo "rem_device_all" > /proc/net/pktgen/kpktgend_1
The third write frees both entries and the fourth one makes kpktgend_1 call proc_remove() on the freed second one:
BUG: KASAN: slab-use-after-free in proc_remove+0x8f/0xa0 Read of size 8 at addr ff110001134c8e70 by task kpktgend_1/243 Call Trace: proc_remove+0x8f/0xa0 pktgen_remove_device.isra.0+0x118/0x500 pktgen_rem_all_ifs+0xeb/0x1a0 pktgen_thread_worker+0x1b42/0x7280
The files of a network namespace belong to the root of its user namespace and these writes don't require a capability. So once pktgen is loaded an unprivileged user can do this in namespaces of its own.
Treat a component that isn't a directory like one that doesn't exist. All names with a '/' that the tree passes in as literals only walk through directories. pktgen is fixed separately to not pass such names in the first place.
--- fs/proc/generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/proc/generic.c +1 -1
@@ -170,7 +170,7 @@ static int __xlate_proc_name(const char *name, struct proc_dir_entry **ret, de = *ret ?: &proc_root;while ((next =strchr (cp,'/' )) !=NULL ) { de =pde_subdir_find (de, cp, next - cp); -if (!de) { +if (!de || !S_ISDIR (de->mode)) {WARN (1 ,"name '%s'\n" , name);return -ENOENT ; }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name
Sashiko has reviewed this patch and found no issues.
Quoted 17 lines
__xlate_proc_name()walks the components of a name like "a/b/c" and takes whatever it finds for "a" and "b" as the directory to go on in. It never checks that they actually are directories. Soproc_create("file/x")hangs x into the subdir tree of the regular file "file". Nobody can look x up there, butremove_proc_subtree()on the file removes and frees x along with it, behind the back of whoever registered x.pktgen passes the name the writer of "
add_device" picks toproc_create_data(), and only the part in front of the '@' has to name a network device:echo "
add_devicelo@" >/proc/net/pktgen/kpktgend_0
echo "add_devicelo@/x" >/proc/net/pktgen/kpktgend_1
echo "rem_device_all" >/proc/net/pktgen/kpktgend_0
[ ... ]
Quoted 4 lines
Reported-by Chris Mason <[email protected]>Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
Re: [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name
In reply to Christian BraunerQuoted 37 lines · Christian Brauner
__xlate_proc_name()walks the components of a name like "a/b/c" and takes whatever it finds for "a" and "b" as the directory to go on in. It never checks that they actually are directories. Soproc_create("file/x")hangs x into the subdir tree of the regular file "file". Nobody can look x up there, butremove_proc_subtree()on the file removes and frees x along with it, behind the back of whoever registered x.pktgen passes the name the writer of "
add_device" picks toproc_create_data(), and only the part in front of the '@' has to name a network device:echo "
add_devicelo@" >/proc/net/pktgen/kpktgend_0
echo "add_devicelo@/x" >/proc/net/pktgen/kpktgend_1
echo "rem_device_all" >/proc/net/pktgen/kpktgend_0
echo "rem_device_all" >/proc/net/pktgen/kpktgend_1The third write frees both entries and the fourth one makes
kpktgend_1callproc_remove()on the freed second one:BUG: KASAN: slab-use-after-free in proc_remove+0x8f/0xa0 Read of size 8 at addr ff110001134c8e70 by task kpktgend_1/243 Call Trace: proc_remove+0x8f/0xa0 pktgen_remove_device.isra.0+0x118/0x500 pktgen_rem_all_ifs+0xeb/0x1a0 pktgen_thread_worker+0x1b42/0x7280The files of a network namespace belong to the root of its user namespace and these writes don't require a capability. So once pktgen is loaded an unprivileged user can do this in namespaces of its own.
Treat a component that isn't a directory like one that doesn't exist. All names with a '/' that the tree passes in as literals only walk through directories. pktgen is fixed separately to not pass such names in the first place.
Quoted 4 lines
Reported-by Chris Mason <[email protected]>Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Looks good. Feel free to add:
Honza
Quoted 21 lines
--- fs/proc/generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)fs/proc/generic.c +1
-1@@ -170,7 +170,7 @@ static int __xlate_proc_name(const char *name, struct proc_dir_entry **ret, de = *ret ?: &proc_root;while ((next =strchr (cp,'/' )) !=NULL ) { de =pde_subdir_find (de, cp, next - cp); -if (!de) { +if (!de || !S_ISDIR (de->mode)) {WARN (1 ,"name '%s'\n" , name);return -ENOENT ; }Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 17/38 net: pktgen: reject device names with a '/'
In reply to Christian Brauner"add_device" takes a name like eth0@1. Only the part in front of the '@' has to name a network device. The rest is free text and all of it becomes the name of a file in /proc/net/pktgen, where procfs takes a '/' as a directory separator. So "add_device lo@a/b" makes procfs look for a directory "lo@a" that doesn't exist and warn about it. Any user can do that in a network namespace of its own:
name 'lo@a/b'
WARNING: fs/proc/generic.c:174 at __xlate_proc_name+0xb1/0xf0, CPU#3: bash/241 Call Trace: __proc_create+0xd0/0x9b0 proc_create_reg+0x45/0xf0 proc_create_data+0x77/0xf0 pktgen_thread_write+0xd8d/0x1750
With panic_on_warn that takes the machine down. And if "lo@a" exists because somebody added a device of that name, procfs used to register the new entry below that regular file, and removing "lo@a" freed both.
Reject names with a '/'.
--- net/core/pktgen.c | 4 ++++ 1 file changed, 4 insertions(+)
net/core/pktgen.c +4 -0
@@ -3831,6 +3831,10 @@ static int pktgen_add_device(struct pktgen_thread *t, const char *ifname)int err;int node =cpu_to_node (t->cpu); +/* The name becomes the name of a file in /proc/net/pktgen. */ +if (strchr (ifname,'/' )) +return -EINVAL ; +/* We don't allow a device to be on several threads */ if (__pktgen_NN_threads (t->net, ifname,FIND )) {
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 17/38] net: pktgen: reject device names with a '/'
Sashiko has reviewed this patch and found no issues.
Quoted 19 lines
"
add_device" takes a name like eth0@1. Only the part in front of the '@' has to name a network device. The rest is free text and all of it becomes the name of a file in/proc/net/pktgen, where procfs takes a '/' as a directory separator. So "add_devicelo@a/b" makes procfs look for a directory "lo@a" that doesn't exist and warn about it. Any user can do that in a network namespace of its own:name 'lo@a/b'
WARNING: fs/proc/generic.c:174 at __xlate_proc_name+0xb1/0xf0, CPU#3: bash/241 Call Trace: __proc_create+0xd0/0x9b0 proc_create_reg+0x45/0xf0 proc_create_data+0x77/0xf0 pktgen_thread_write+0xd8d/0x1750With
panic_on_warnthat takes the machine down. And if "lo@a" exists because somebody added a device of that name, procfs used to register the new entry below that regular file, and removing "lo@a" freed both.
Quoted 6 lines
Reject names with a '/'.
Reported-by Chris Mason <[email protected]>Fixes e6fce5b916cd ("pktgen: multiqueue etc.")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
Christian Brauner
maintainerRFC PATCH 18/38 proc: don't WARN about a name that is already registered
In reply to Christian Braunerproc_register() WARNs when the directory already has an entry of the same name and fails the registration. Callers that pick their names themselves should never get there but some take names from userspace. pktgen creates a file for each device a writer of "add_device" adds, named after the device. A device can have any name, including "pgctrl" or "kpktgend_0", the names of pktgen's own files:
ip link set lo name pgctrl
ip link set pgctrl up
echo "add_device pgctrl" > /proc/net/pktgen/kpktgend_0
proc_dir_entry 'pktgen/pgctrl' already registered
WARNING: fs/proc/generic.c:404 at proc_register+0x555/0x8b0, CPU#0: bash/241 Call Trace: proc_create_data+0x9f/0xf0 pktgen_thread_write+0xd9b/0x1780 proc_reg_write+0x1c2/0x3c0 vfs_write+0x1cd/0xfa0
The files of a network namespace belong to the root of its user namespace and none of this requires a capability. So once pktgen is loaded an unprivileged user can taint the kernel, or crash it with panic_on_warn, from namespaces of its own. pktgen itself copes with the failed registration just fine.
So go back to what proc_register() did before commit 6c2f91e077f1 ("proc: use WARN() rather than printk+backtrace") and what sysfs_warn_dup() does for sysfs: report the name with pr_warn() and dump the stack. While at it, do that after dropping proc_subdir_lock instead of printing a backtrace while holding the lock that every lookup and readdir in procfs needs.
--- fs/proc/generic.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
fs/proc/generic.c +3 -2
@@ -401,9 +401,10 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,write_lock (&proc_subdir_lock); dp->parent = dir;if (pde_subdir_insert (dir, dp) == false) { -WARN (1 ,"proc_dir_entry '%s/%s' already registered\n" , - dir->name, dp->name);write_unlock (&proc_subdir_lock); +pr_warn ("proc_dir_entry '%s/%s' already registered\n" , + dir->name, dp->name); +dump_stack (); goto out_free_inum; }write_unlock (&proc_subdir_lock);
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 18/38] proc: don't WARN about a name that is already registered
Sashiko has reviewed this patch and found no issues.
Quoted 15 lines
proc_register()WARNs when the directory already has an entry of the same name and fails the registration. Callers that pick their names themselves should never get there but some take names from userspace. pktgen creates a file for each device a writer of "add_device" adds, named after the device. A device can have any name, including "pgctrl" or "kpktgend_0", the names of pktgen's own files:ip link set lo name pgctrl
ip link set pgctrl up
echo "add_devicepgctrl" >/proc/net/pktgen/kpktgend_0
proc_dir_entry'pktgen/pgctrl' already registeredWARNING: fs/proc/generic.c:404 at proc_register+0x555/0x8b0, CPU#0: bash/241 Call Trace: proc_create_data+0x9f/0xf0
Quoted 6 lines
[ ... ]
Reported-by Chris Mason <[email protected]>Fixes 6c2f91e077f1 ("proc: use WARN() rather than printk+backtrace")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
Re: [PATCH RFC 18/38] proc: don't WARN about a name that is already registered
In reply to Christian BraunerQuoted 32 lines · Christian Brauner
proc_register()WARNs when the directory already has an entry of the same name and fails the registration. Callers that pick their names themselves should never get there but some take names from userspace. pktgen creates a file for each device a writer of "add_device" adds, named after the device. A device can have any name, including "pgctrl" or "kpktgend_0", the names of pktgen's own files:ip link set lo name pgctrl
ip link set pgctrl up
echo "add_devicepgctrl" >/proc/net/pktgen/kpktgend_0
proc_dir_entry'pktgen/pgctrl' already registeredWARNING: fs/proc/generic.c:404 at proc_register+0x555/0x8b0, CPU#0: bash/241 Call Trace: proc_create_data+0x9f/0xf0 pktgen_thread_write+0xd9b/0x1780 proc_reg_write+0x1c2/0x3c0 vfs_write+0x1cd/0xfa0The files of a network namespace belong to the root of its user namespace and none of this requires a capability. So once pktgen is loaded an unprivileged user can taint the kernel, or crash it with
panic_on_warn, from namespaces of its own. pktgen itself copes with the failed registration just fine.So go back to what
proc_register()did before commit6c2f91e077f1("proc: useWARN()rather than printk+backtrace") and whatsysfs_warn_dup()does for sysfs: report the name withpr_warn()and dump the stack. While at it, do that after droppingproc_subdir_lockinstead of printing a backtrace while holding the lock that every lookup and readdir in procfs needs.
Quoted 4 lines
Reported-by Chris Mason <[email protected]>Fixes 6c2f91e077f1 ("proc: use WARN() rather than printk+backtrace")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Makes sense. Feel free to add:
Honza
Quoted 25 lines
--- fs/proc/generic.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)fs/proc/generic.c +3
-2@@ -401,9 +401,10 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,write_lock (&proc_subdir_lock); dp->parent = dir;if (pde_subdir_insert (dir, dp) == false) { -WARN (1 ,"proc_dir_entry '%s/%s' already registered\n" , - dir->name, dp->name);write_unlock (&proc_subdir_lock); +pr_warn ("proc_dir_entry '%s/%s' already registered\n" , + dir->name, dp->name); +dump_stack (); goto out_free_inum; }write_unlock (&proc_subdir_lock);Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 19/38 proc: resume readdir after the last name it returned
In reply to Christian Braunerproc_readdir_de() counts the entries of a directory and resumes a listing at the number getdents64() stopped at. The entries are sorted by name, so one that is added or removed in between shifts everything behind it by one. Listing /proc/net/dev_snmp6 with a buffer that holds ".", ".." and "lo" and adding a device whose name sorts before "lo" makes the next call return "lo" a second time. Removing one that sorted before the position makes the next call skip an entry instead.
Since commit 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()") an entry that is removed while it is being emitted also ends the listing right there. pde_subdir_next() finds nothing after an erased entry, so proc_readdir_de() reports the end of the directory and the next call resumes by number, skipping another entry. For /proc itself proc_root_readdir() takes that as the end of the static entries and moves on to the pids, so the rest of the static entries is missing. And if the position is behind the last entry when a call starts proc_readdir_de() returns 0, so /proc then lists no pids at all.
So remember the name of the last entry a call emitted in the file and resume at the first entry after it, like kernfs resumes after the node it stopped at. An entry that was removed while it was emitted is continued after by its name as well. If the position was moved by a seek in between the listing counts from the start like before.
--- fs/proc/generic.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++-------- fs/proc/internal.h | 1 + fs/proc/proc_net.c | 1 + fs/proc/root.c | 1 + 4 files changed, 80 insertions(+), 12 deletions(-)
fs/proc/generic.c +77 -12
@@ -87,6 +87,28 @@ static struct proc_dir_entry *pde_subdir_find(struct proc_dir_entry *dir,return NULL ; } +/* The first entry of @dir that sorts after @name. */ +static struct proc_dir_entry *pde_subdir_after (struct proc_dir_entry *dir, +const char *name, +unsigned int len) +{ +struct rb_node *node = dir->subdir.rb_node, *after =NULL ; + +while (node) { +struct proc_dir_entry *de =rb_entry (node, + struct proc_dir_entry, + subdir_node); + +if (proc_match (name, de, len) <0 ) { + after = node; + node = node->rb_left; + }else { + node = node->rb_right; + } + } + return rb_entry_safe(after, struct proc_dir_entry, subdir_node); +} +static bool pde_subdir_insert (struct proc_dir_entry *dir,struct proc_dir_entry *de) {
@@ -277,6 +299,40 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,return proc_lookup_de (dir, dentry,PDE (dir)); } +/* +* Where the next readdir resumes. The entries are counted, so one that is +* added or removed in front of the position would shift what the next call +* returns. The name of the last entry emitted doesn't move. +*/ +struct proc_dir_cursor { +loff_t pos;/* the position behind the entry */ +unsigned int len; +char name[NAME_MAX +1 ]; +}; + +/* If this can't remember the entry the next call counts from the start. */ +static void proc_dir_cursor_set (struct file *file,struct proc_dir_entry *de, +loff_t pos) +{ +struct proc_dir_cursor *cursor = file->private_data; + +if (!cursor) { + cursor =kmalloc (sizeof (*cursor),GFP_KERNEL ); +if (!cursor) +return ; + file->private_data = cursor; + } + cursor->pos = pos; + cursor->len = de->namelen; +memcpy (cursor->name, de->name, de->namelen); +} + +int proc_dir_release (struct inode *inode,struct file *file) +{ +kfree (file->private_data); +return 0 ; +} +/* * This returns non-zero if at EOF, so that the /proc * root directory can use this and check if it should
@@ -289,23 +345,26 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,int proc_readdir_de (struct file *file,struct dir_context *ctx,struct proc_dir_entry *de) { +struct proc_dir_cursor *cursor = file->private_data; +struct proc_dir_entry *dir = de;int i;if (!dir_emit_dots (file, ctx))return 0 ; - i = ctx->pos -2 ;read_lock (&proc_subdir_lock); - de =pde_subdir_first (de); -for (;;) { -if (!de) { -read_unlock (&proc_subdir_lock); -return 0 ; - } -if (!i) -break ; - de =pde_subdir_next (de); - i--; +if (cursor && cursor->pos == ctx->pos) { +/* Resume after the last entry emitted, wherever it is now. */ + de =pde_subdir_after (dir, cursor->name, cursor->len); + }else { +/* Moved by a seek, count from the start like always. */ + de =pde_subdir_first (dir); +for (i = ctx->pos -2 ; de && i; i--) + de =pde_subdir_next (de); + } +if (!de) { +read_unlock (&proc_subdir_lock); +return 1 ; } do {
@@ -318,8 +377,13 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,return 0 ; } ctx->pos++; +proc_dir_cursor_set (file, de, ctx->pos);read_lock (&proc_subdir_lock); - next =pde_subdir_next (de); +/* Removed in the meantime, go on after its name. */ +if (RB_EMPTY_NODE (&de->subdir_node)) + next =pde_subdir_after (dir, de->name, de->namelen); +else + next =pde_subdir_next (de);pde_put (de); de = next; }while (de);
@@ -347,6 +411,7 @@ static const struct file_operations proc_dir_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate_shared = proc_readdir, + .release = proc_dir_release, }; static int proc_net_d_revalidate(struct inode *dir, const struct qstr *name,
fs/proc/internal.h +1 -0
@@ -287,6 +287,7 @@ extern struct dentry *proc_lookup(struct inode *, struct dentry *, unsigned int)struct dentry *proc_lookup_de (struct inode *,struct dentry *,struct proc_dir_entry *);extern int proc_readdir (struct file *,struct dir_context *);int proc_readdir_de (struct file *,struct dir_context *,struct proc_dir_entry *); +int proc_dir_release (struct inode *,struct file *);static inline void pde_get (struct proc_dir_entry *pde) {
fs/proc/proc_net.c +1 -0
@@ -351,6 +351,7 @@ const struct file_operations proc_net_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate_shared = proc_tgid_net_readdir, + .release = proc_dir_release, };static __net_init int proc_net_ns_init (struct net *net)
fs/proc/root.c +1 -0
@@ -441,6 +441,7 @@ static const struct file_operations proc_root_operations = { .read = generic_read_dir, .iterate_shared = proc_root_readdir, .llseek = generic_file_llseek, + .release = proc_dir_release, };/*
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
Sashiko has reviewed this patch and found no issues.
Quoted 24 lines
proc_readdir_de()counts the entries of a directory and resumes a listing at the numbergetdents64()stopped at. The entries are sorted by name, so one that is added or removed in between shifts everything behind it by one. Listing/proc/net/dev_snmp6with a buffer that holds ".", ".." and "lo" and adding a device whose name sorts before "lo" makes the next call return "lo" a second time. Removing one that sorted before the position makes the next call skip an entry instead.Since commit
895b4c0c79b0("fs/proc: fix uaf inproc_readdir_de()") an entry that is removed while it is being emitted also ends the listing right there.pde_subdir_next()finds nothing after an erased entry, soproc_readdir_de()reports the end of the directory and the next call resumes by number, skipping another entry. For /proc itselfproc_root_readdir()takes that as the end of the static entries and moves on to the pids, so the rest of the static entries is missing. And if the position is behind the last entry when a call startsproc_readdir_de()returns 0, so /proc then lists no pids at all.So remember the name of the last entry a call emitted in the file and resume at the first entry after it, like kernfs resumes after the node it stopped at. An entry that was removed while it was emitted is continued after by its name as well. If the position was moved by a seek in between the listing counts from the start like before.
Quoted 4 lines
Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Fixes 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
Re: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
In reply to Christian BraunerQuoted 24 lines · Christian Brauner
proc_readdir_de()counts the entries of a directory and resumes a listing at the numbergetdents64()stopped at. The entries are sorted by name, so one that is added or removed in between shifts everything behind it by one. Listing/proc/net/dev_snmp6with a buffer that holds ".", ".." and "lo" and adding a device whose name sorts before "lo" makes the next call return "lo" a second time. Removing one that sorted before the position makes the next call skip an entry instead.Since commit
895b4c0c79b0("fs/proc: fix uaf inproc_readdir_de()") an entry that is removed while it is being emitted also ends the listing right there.pde_subdir_next()finds nothing after an erased entry, soproc_readdir_de()reports the end of the directory and the next call resumes by number, skipping another entry. For /proc itselfproc_root_readdir()takes that as the end of the static entries and moves on to the pids, so the rest of the static entries is missing. And if the position is behind the last entry when a call startsproc_readdir_de()returns 0, so /proc then lists no pids at all.So remember the name of the last entry a call emitted in the file and resume at the first entry after it, like kernfs resumes after the node it stopped at. An entry that was removed while it was emitted is continued after by its name as well. If the position was moved by a seek in between the listing counts from the start like before.
Quoted 4 lines
Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Fixes 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Looks good. Feel free to add:
Honza
Quoted 182 lines
--- fs/proc/generic.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++-------- fs/proc/internal.h | 1 + fs/proc/proc_net.c | 1 + fs/proc/root.c | 1 + 4 files changed, 80 insertions(+), 12 deletions(-)fs/proc/generic.c +77
-12@@ -87,6 +87,28 @@ static struct proc_dir_entry *pde_subdir_find(struct proc_dir_entry *dir,return NULL ; } +/* The first entry of @dir that sorts after @name. */ +static struct proc_dir_entry *pde_subdir_after (struct proc_dir_entry *dir, +const char *name, +unsigned int len) +{ +struct rb_node *node = dir->subdir.rb_node, *after =NULL ; + +while (node) { +struct proc_dir_entry *de =rb_entry (node, + struct proc_dir_entry, + subdir_node); + +if (proc_match (name, de, len) <0 ) { + after = node; + node = node->rb_left; + }else { + node = node->rb_right; + } + } + return rb_entry_safe(after, struct proc_dir_entry, subdir_node); +} +static bool pde_subdir_insert (struct proc_dir_entry *dir,struct proc_dir_entry *de) {@@ -277,6 +299,40 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,return proc_lookup_de (dir, dentry,PDE (dir)); } +/* +* Where the next readdir resumes. The entries are counted, so one that is +* added or removed in front of the position would shift what the next call +* returns. The name of the last entry emitted doesn't move. +*/ +struct proc_dir_cursor { +loff_t pos;/* the position behind the entry */ +unsigned int len; +char name[NAME_MAX +1 ]; +}; + +/* If this can't remember the entry the next call counts from the start. */ +static void proc_dir_cursor_set (struct file *file,struct proc_dir_entry *de, +loff_t pos) +{ +struct proc_dir_cursor *cursor = file->private_data; + +if (!cursor) { + cursor =kmalloc (sizeof (*cursor),GFP_KERNEL ); +if (!cursor) +return ; + file->private_data = cursor; + } + cursor->pos = pos; + cursor->len = de->namelen; +memcpy (cursor->name, de->name, de->namelen); +} + +int proc_dir_release (struct inode *inode,struct file *file) +{ +kfree (file->private_data); +return 0 ; +} +/* * This returns non-zero if at EOF, so that the /proc * root directory can use this and check if it should @@ -289,23 +345,26 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,int proc_readdir_de (struct file *file,struct dir_context *ctx,struct proc_dir_entry *de) { +struct proc_dir_cursor *cursor = file->private_data; +struct proc_dir_entry *dir = de;int i;if (!dir_emit_dots (file, ctx))return 0 ; - i = ctx->pos -2 ;read_lock (&proc_subdir_lock); - de =pde_subdir_first (de); -for (;;) { -if (!de) { -read_unlock (&proc_subdir_lock); -return 0 ; - } -if (!i) -break ; - de =pde_subdir_next (de); - i--; +if (cursor && cursor->pos == ctx->pos) { +/* Resume after the last entry emitted, wherever it is now. */ + de =pde_subdir_after (dir, cursor->name, cursor->len); + }else { +/* Moved by a seek, count from the start like always. */ + de =pde_subdir_first (dir); +for (i = ctx->pos -2 ; de && i; i--) + de =pde_subdir_next (de); + } +if (!de) { +read_unlock (&proc_subdir_lock); +return 1 ; } do {@@ -318,8 +377,13 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,return 0 ; } ctx->pos++; +proc_dir_cursor_set (file, de, ctx->pos);read_lock (&proc_subdir_lock); - next =pde_subdir_next (de); +/* Removed in the meantime, go on after its name. */ +if (RB_EMPTY_NODE (&de->subdir_node)) + next =pde_subdir_after (dir, de->name, de->namelen); +else + next =pde_subdir_next (de);pde_put (de); de = next; }while (de);@@ -347,6 +411,7 @@ static const struct file_operations proc_dir_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate_shared = proc_readdir, + .release = proc_dir_release, }; static int proc_net_d_revalidate(struct inode *dir, const struct qstr *name,fs/proc/internal.h +1
-0@@ -287,6 +287,7 @@ extern struct dentry *proc_lookup(struct inode *, struct dentry *, unsigned int)struct dentry *proc_lookup_de (struct inode *,struct dentry *,struct proc_dir_entry *);extern int proc_readdir (struct file *,struct dir_context *);int proc_readdir_de (struct file *,struct dir_context *,struct proc_dir_entry *); +int proc_dir_release (struct inode *,struct file *);static inline void pde_get (struct proc_dir_entry *pde) {fs/proc/proc_net.c +1
-0@@ -351,6 +351,7 @@ const struct file_operations proc_net_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate_shared = proc_tgid_net_readdir, + .release = proc_dir_release, };static __net_init int proc_net_ns_init (struct net *net)fs/proc/root.c +1
-0@@ -441,6 +441,7 @@ static const struct file_operations proc_root_operations = { .read = generic_read_dir, .iterate_shared = proc_root_readdir, .llseek = generic_file_llseek, + .release = proc_dir_release, };/* Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Re: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
In reply to Christian BraunerQuoted 24 lines · Christian Brauner
proc_readdir_de()counts the entries of a directory and resumes a listing at the numbergetdents64()stopped at. The entries are sorted by name, so one that is added or removed in between shifts everything behind it by one. Listing/proc/net/dev_snmp6with a buffer that holds ".", ".." and "lo" and adding a device whose name sorts before "lo" makes the next call return "lo" a second time. Removing one that sorted before the position makes the next call skip an entry instead.Since commit
895b4c0c79b0("fs/proc: fix uaf inproc_readdir_de()") an entry that is removed while it is being emitted also ends the listing right there.pde_subdir_next()finds nothing after an erased entry, soproc_readdir_de()reports the end of the directory and the next call resumes by number, skipping another entry. For /proc itselfproc_root_readdir()takes that as the end of the static entries and moves on to the pids, so the rest of the static entries is missing. And if the position is behind the last entry when a call startsproc_readdir_de()returns 0, so /proc then lists no pids at all.So remember the name of the last entry a call emitted in the file and resume at the first entry after it, like kernfs resumes after the node it stopped at. An entry that was removed while it was emitted is continued after by its name as well. If the position was moved by a seek in between the listing counts from the start like before.
Quoted 4 lines
Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Fixes 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()")Signed-off-by Christian Brauner (Amutable) <[email protected]>
fs/proc/generic.c struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry, · line 322
Quoted 9 lines
fs/proc/generic.c +9
-0+static void proc_dir_cursor_set (struct file *file,struct proc_dir_entry *de, +loff_t pos) +{ +struct proc_dir_cursor *cursor = file->private_data; + +if (!cursor) { + cursor =kmalloc (sizeof (*cursor),GFP_KERNEL ); +if (!cursor) +return ;
(For both this and the proc_sys version, you could instead do the cursor allocation at the start of readdir() so that you can return a proper error if it fails, but I guess it doesn't really matter and that would complicate the code a bit more...)
Quoted 6 lines
fs/proc/generic.c +6
-0+ file->private_data = cursor; + } + cursor->pos = pos; + cursor->len = de->namelen; +memcpy (cursor->name, de->name, de->namelen); +}
Christian Brauner
maintainerRe: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
Quoted 11 lines · Jann Horn
Quoted 1 line · Christian Brauner
[...]
Reviewed-by Jann Horn <[email protected]>Quoted 1 line
[...]
(For both this and the
proc_sysversion, you could instead do the cursor allocation at the start ofreaddir()so that you can return a proper error if it fails, but I guess it doesn't really matter and that would complicate the code a bit more...)
I did consider this and didn't think it was worth it. If we can allocate a cursor we just start from the beginning.
Christian Brauner
maintainerRFC PATCH 20/38 selftests/proc: check a /proc listing while entries come and go
In reply to Christian BraunerList /proc/net/dev_snmp6 of a new network namespace while a tun device comes and goes:
- with a buffer that holds the dots and "lo", add a device whose name sorts first, finish the listing: "lo" is listed once
- with that device added and a buffer that holds the dots and it, remove it, finish the listing: "lo" is still listed
- with that device added, stall getdents64() on a userfaultfd page while it writes the device's entry, remove the device, finish the
listing: "lo" is still listed
All three fail without the fix, the first lists "lo" twice, the other two not at all.
--- tools/testing/selftests/proc/.gitignore | 1 + tools/testing/selftests/proc/Makefile | 1 + tools/testing/selftests/proc/proc-readdir-churn.c | 236 ++++++++++++++++++++++ 3 files changed, 238 insertions(+)
tools/testing/selftests/proc/.gitignore +1 -0
@@ -8,6 +8,7 @@ /proc-maps-race /proc-multiple-procfs /proc-net-dev-lseek +/proc-readdir-churn /proc-empty-vm /proc-pid-vm /proc-self-map-files-001
tools/testing/selftests/proc/Makefile +1 -0
@@ -11,6 +11,7 @@ TEST_GEN_PROGS += proc-2-is-kthread TEST_GEN_PROGS += proc-loadavg-001 TEST_GEN_PROGS += proc-maps-race TEST_GEN_PROGS += proc-net-dev-lseek +TEST_GEN_PROGS += proc-readdir-churn TEST_GEN_PROGS += proc-empty-vm TEST_GEN_PROGS += proc-pid-vm TEST_GEN_PROGS += proc-self-map-files-001
tools/testing/selftests/proc/proc-readdir-churn.c new file +236 -0
236 lines not on this page · raw message
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 20/38] selftests/proc: check a /proc listing while entries come and go
Sashiko has reviewed this patch and found no issues.
Quoted 9 lines
List
/proc/net/dev_snmp6of a new network namespace while a tun device comes and goes:- with a buffer that holds the dots and "lo", add a device whose name sorts first, finish the listing: "lo" is listed once
- with that device added and a buffer that holds the dots and it, remove it, finish the listing: "lo" is still listed
Quoted 8 lines
- with that device added, stall
getdents64()on a userfaultfd page while it writes the device's entry, remove the device, finish the
listing: "lo" is still listedAll three fail without the fix, the first lists "lo" twice, the other two not at all.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=20
Christian Brauner
maintainerRFC PATCH 21/38 proc: let proc_remove() remove the entry it was given
In reply to Christian Braunerproc_remove() looks the entry up by its name in its parent and removes whatever it finds there:
remove_proc_subtree(de->name, de->parent);
If the entry was removed already, say along with its parent, that is either nothing or a different entry that got the same name in the meantime, and de->parent may well be gone. Two removals of the same entry are a bug in the caller, but going by the name turns it into removing an entry that belongs to somebody else.
Since commit 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()") an entry that was unlinked has an empty rb node. So check for that under proc_subdir_lock and otherwise remove the entry itself, with the loop of remove_proc_subtree() moved into a helper both of them use. Nothing can help a caller that passes an entry that has been freed already.
--- fs/proc/generic.c | 78 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 27 deletions(-)
fs/proc/generic.c +51 -27
@@ -817,33 +817,14 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent) }EXPORT_SYMBOL (remove_proc_entry); -int remove_proc_subtree (const char *name,struct proc_dir_entry *parent) +/* +* Remove @root, already erased from its parent, and everything below it. +* Called with proc_subdir_lock held for writing, returns with it dropped. +*/ +static int __remove_proc_subtree (struct proc_dir_entry *root) { -struct proc_dir_entry *root =NULL , *de, *next; -const char *fn = name; -unsigned int len; - -write_lock (&proc_subdir_lock); -if (__xlate_proc_name (name, &parent, &fn) !=0 ) { -write_unlock (&proc_subdir_lock); -return -ENOENT ; - } - len =strlen (fn); +struct proc_dir_entry *de = root, *next; - root =pde_subdir_find (parent, fn, len); -if (!root) { -write_unlock (&proc_subdir_lock); -return -ENOENT ; - } -if (unlikely (pde_is_permanent (root))) { -write_unlock (&proc_subdir_lock); -WARN (1 ,"removing permanent /proc entry '%s/%s'" , - root->parent->name, root->name); -return -EINVAL ; - } -pde_erase (root, parent); - - de = root;while (1 ) { next =pde_subdir_first (de);if (next) {
@@ -871,6 +852,34 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)pde_put (root);return 0 ; } + +int remove_proc_subtree (const char *name,struct proc_dir_entry *parent) +{ +struct proc_dir_entry *root =NULL ; +const char *fn = name; +unsigned int len; + +write_lock (&proc_subdir_lock); +if (__xlate_proc_name (name, &parent, &fn) !=0 ) { +write_unlock (&proc_subdir_lock); +return -ENOENT ; + } + len =strlen (fn); + + root =pde_subdir_find (parent, fn, len); +if (!root) { +write_unlock (&proc_subdir_lock); +return -ENOENT ; + } +if (unlikely (pde_is_permanent (root))) { +write_unlock (&proc_subdir_lock); +WARN (1 ,"removing permanent /proc entry '%s/%s'" , + root->parent->name, root->name); +return -EINVAL ; + } +pde_erase (root, parent); +return __remove_proc_subtree (root); +}EXPORT_SYMBOL (remove_proc_subtree);void *proc_get_parent_data (const struct inode *inode)
@@ -882,8 +891,23 @@ EXPORT_SYMBOL_GPL(proc_get_parent_data);void proc_remove (struct proc_dir_entry *de) { -if (de) -remove_proc_subtree (de->name, de->parent); +if (!de) +return ; + +write_lock (&proc_subdir_lock); +/* Removed already, maybe along with its parent. */ +if (RB_EMPTY_NODE (&de->subdir_node)) { +write_unlock (&proc_subdir_lock); +return ; + } +if (unlikely (pde_is_permanent (de))) { +write_unlock (&proc_subdir_lock); +WARN (1 ,"removing permanent /proc entry '%s/%s'" , + de->parent->name, de->name); +return ; + } +pde_erase (de, de->parent); +__remove_proc_subtree (de); }EXPORT_SYMBOL (proc_remove);
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 21/38] proc: let proc_remove() remove the entry it was given
Sashiko has reviewed this patch and found no issues.
Quoted 18 lines
proc_remove()looks the entry up by its name in its parent and removes whatever it finds there:remove_proc_subtree(de->name, de->parent);If the entry was removed already, say along with its parent, that is either nothing or a different entry that got the same name in the meantime, and
de->parentmay well be gone. Two removals of the same entry are a bug in the caller, but going by the name turns it into removing an entry that belongs to somebody else.Since commit
895b4c0c79b0("fs/proc: fix uaf inproc_readdir_de()") an entry that was unlinked has an empty rb node. So check for that underproc_subdir_lockand otherwise remove the entry itself, with the loop ofremove_proc_subtree()moved into a helper both of them use. Nothing can help a caller that passes an entry that has been freed already.
Quoted 3 lines
Reported-by Chris Mason <[email protected]>Fixes a8ca16ea7b0a ("proc: Supply a function to remove a proc entry by PDE")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
Re: [PATCH RFC 21/38] proc: let proc_remove() remove the entry it was given
In reply to Christian BraunerQuoted 18 lines · Christian Brauner
proc_remove()looks the entry up by its name in its parent and removes whatever it finds there:remove_proc_subtree(de->name, de->parent);If the entry was removed already, say along with its parent, that is either nothing or a different entry that got the same name in the meantime, and
de->parentmay well be gone. Two removals of the same entry are a bug in the caller, but going by the name turns it into removing an entry that belongs to somebody else.Since commit
895b4c0c79b0("fs/proc: fix uaf inproc_readdir_de()") an entry that was unlinked has an empty rb node. So check for that underproc_subdir_lockand otherwise remove the entry itself, with the loop ofremove_proc_subtree()moved into a helper both of them use. Nothing can help a caller that passes an entry that has been freed already.
Quoted 3 lines
Reported-by Chris Mason <[email protected]>Fixes a8ca16ea7b0a ("proc: Supply a function to remove a proc entry by PDE")Signed-off-by Christian Brauner (Amutable) <[email protected]>
One nitpick below. Otherwise feel free to add:
Quoted 84 lines
fs/proc/generic.c +41
-27-int remove_proc_subtree (const char *name,struct proc_dir_entry *parent) +/* +* Remove @root, already erased from its parent, and everything below it. +* Called with proc_subdir_lock held for writing, returns with it dropped. +*/ +static int __remove_proc_subtree (struct proc_dir_entry *root) { -struct proc_dir_entry *root =NULL , *de, *next; -const char *fn = name; -unsigned int len; - -write_lock (&proc_subdir_lock); -if (__xlate_proc_name (name, &parent, &fn) !=0 ) { -write_unlock (&proc_subdir_lock); -return -ENOENT ; - } - len =strlen (fn); +struct proc_dir_entry *de = root, *next; - root =pde_subdir_find (parent, fn, len); -if (!root) { -write_unlock (&proc_subdir_lock); -return -ENOENT ; - } -if (unlikely (pde_is_permanent (root))) { -write_unlock (&proc_subdir_lock); -WARN (1 ,"removing permanent /proc entry '%s/%s'" , - root->parent->name, root->name); -return -EINVAL ; - } -pde_erase (root, parent); - - de = root;while (1 ) { next =pde_subdir_first (de);if (next) {@@ -871,6 +852,34 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)pde_put (root);return 0 ; } + +int remove_proc_subtree (const char *name,struct proc_dir_entry *parent) +{ +struct proc_dir_entry *root =NULL ; +const char *fn = name; +unsigned int len; + +write_lock (&proc_subdir_lock); +if (__xlate_proc_name (name, &parent, &fn) !=0 ) { +write_unlock (&proc_subdir_lock); +return -ENOENT ; + } + len =strlen (fn); + + root =pde_subdir_find (parent, fn, len); +if (!root) { +write_unlock (&proc_subdir_lock); +return -ENOENT ; + } +if (unlikely (pde_is_permanent (root))) { +write_unlock (&proc_subdir_lock); +WARN (1 ,"removing permanent /proc entry '%s/%s'" , + root->parent->name, root->name); +return -EINVAL ; + } +pde_erase (root, parent); +return __remove_proc_subtree (root); +}EXPORT_SYMBOL (remove_proc_subtree);void *proc_get_parent_data (const struct inode *inode)@@ -882,8 +891,23 @@ EXPORT_SYMBOL_GPL(proc_get_parent_data);void proc_remove (struct proc_dir_entry *de) { -if (de) -remove_proc_subtree (de->name, de->parent); +if (!de) +return ; + +write_lock (&proc_subdir_lock); +/* Removed already, maybe along with its parent. */ +if (RB_EMPTY_NODE (&de->subdir_node)) { +write_unlock (&proc_subdir_lock);
fs/proc/generic.c EXPORT_SYMBOL_GPL(proc_get_parent_data); · line 910
Quoted 10 lines
fs/proc/generic.c +10
-0+return ; + } +if (unlikely (pde_is_permanent (de))) { +write_unlock (&proc_subdir_lock); +WARN (1 ,"removing permanent /proc entry '%s/%s'" , + de->parent->name, de->name); +return ; + } +pde_erase (de, de->parent); +__remove_proc_subtree (de);
I'd move the RB_EMPTY_NODE() and pde_is_permanent() along with pde_erase() call back to __remove_proc_subtree(). Both callers need them (so it is unnecessary duplication) and also logically it makes sense there.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 22/38 proc: warn about entries left in /proc/net of a dying namespace
In reply to Christian Braunerproc_net_ns_exit() runs after all the other pernet exits and frees net->proc_net right after removing "stat" from it. An entry that some pernet exit forgot to remove is left with a freed ->parent, and so PDE_NET() hands seq_open_net() and single_open_net() a freed struct net when the entry is opened through a file descriptor that someone kept.
There is no such entry in the tree that we know of, but nothing would tell us about one either. So warn about it like remove_proc_entry() does for a directory that isn't empty.
--- fs/proc/proc_net.c | 6 ++++++ 1 file changed, 6 insertions(+)
fs/proc/proc_net.c +6 -0
@@ -410,7 +410,13 @@ static __net_init int proc_net_ns_init(struct net *net) static __net_exit void proc_net_ns_exit(struct net *net) { +struct proc_dir_entry *left; +remove_proc_entry ("stat" , net->proc_net); +/* Whatever is left still points at net->proc_net and so at net. */ + left =rb_entry_safe (rb_first (&net->proc_net->subdir), + struct proc_dir_entry, subdir_node); +WARN (left,"/proc/net: leaking at least '%s'\n" , left->name);pde_free (net->proc_net); }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 22/38] proc: warn about entries left in /proc/net of a dying namespace
Sashiko has reviewed this patch and found no issues.
Quoted 6 lines
proc_net_ns_exit()runs after all the other pernet exits and freesnet->proc_netright after removing "stat" from it. An entry that some pernet exit forgot to remove is left with a freed ->parent, and soPDE_NET()handsseq_open_net()andsingle_open_net()a freed struct net when the entry is opened through a file descriptor that someone kept.
Quoted 7 lines
There is no such entry in the tree that we know of, but nothing would tell us about one either. So warn about it like
remove_proc_entry()does for a directory that isn't empty.Reported-by Chris Mason <[email protected]>Fixes e9720acd728a ("[NET]: Make /proc/net a symlink on /proc/self/net (v3)")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=22
Re: [PATCH RFC 22/38] proc: warn about entries left in /proc/net of a dying namespace
In reply to Christian BraunerQuoted 6 lines · Christian Brauner
proc_net_ns_exit()runs after all the other pernet exits and freesnet->proc_netright after removing "stat" from it. An entry that some pernet exit forgot to remove is left with a freed ->parent, and soPDE_NET()handsseq_open_net()andsingle_open_net()a freed struct net when the entry is opened through a file descriptor that someone kept.
Quoted 7 lines
There is no such entry in the tree that we know of, but nothing would tell us about one either. So warn about it like
remove_proc_entry()does for a directory that isn't empty.Reported-by Chris Mason <[email protected]>Fixes e9720acd728a ("[NET]: Make /proc/net a symlink on /proc/self/net (v3)")Signed-off-by Christian Brauner (Amutable) <[email protected]>
OK, why not. Feel free to add:
Honza
Quoted 26 lines
--- fs/proc/proc_net.c | 6 ++++++ 1 file changed, 6 insertions(+)fs/proc/proc_net.c +6
-0@@ -410,7 +410,13 @@ static __net_init int proc_net_ns_init(struct net *net) static __net_exit void proc_net_ns_exit(struct net *net) { +struct proc_dir_entry *left; +remove_proc_entry ("stat" , net->proc_net); +/* Whatever is left still points at net->proc_net and so at net. */ + left =rb_entry_safe (rb_first (&net->proc_net->subdir), + struct proc_dir_entry, subdir_node); +WARN (left,"/proc/net: leaking at least '%s'\n" , left->name);pde_free (net->proc_net); }Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 23/38 proc: don't let NOMMU map procfs files
In reply to Christian BraunerEvery procfs file has a ->mmap since commit 786d7e1612f0 ("Fix rmmod/read/write races in /proc entries"), whether the entry has a ->proc_mmap or not. On NOMMU validate_mmap_request() takes that to mean the file can be mapped. procfs has no ->mmap_capabilities so a regular file gets NOMMU_MAP_COPY, and a private mapping never calls ->mmap at all. Instead do_mmap() takes nommu_region_sem for writing and do_mmap_private() reads the file into the copy with kernel_read().
Reading /proc/maps takes nommu_region_sem as well:
mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, open("/proc/maps", O_RDONLY), 0)
do_mmap() // down_write(&nommu_region_sem)
-> do_mmap_private()
-> kernel_read()
-> seq_read_iter()
-> nommu_region_list_start() // down_read()The task hangs for good, holding nommu_region_sem and its own mmap_lock, and every mmap(), munmap() and exit after that hangs behind it. /proc/maps is 0444 so any user can do that.
Give the procfs files a ->mmap_capabilities on NOMMU that allows nothing. Mapping a procfs file then fails with -ENODEV, like mapping a file without ->mmap did before 786d7e1612f0. NOMMU never called ->proc_mmap() for a regular file anyway, procfs never offered NOMMU_MAP_DIRECT.
--- fs/proc/inode.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
fs/proc/inode.c +24 -0
@@ -466,6 +466,18 @@ proc_reg_get_unmapped_area(struct file *file, unsigned long orig_addr,return rv; } +#ifndef CONFIG_MMU +/* +* NOMMU never calls ->proc_mmap() for a regular file. It reads the file into +* a private copy instead, with nommu_region_sem held for writing, which +* reading /proc/maps takes as well. Don't map procfs files at all. +*/ +static unsigned int proc_reg_mmap_capabilities (struct file *file) +{ +return 0 ; +} +#endif +static int proc_reg_open (struct inode *inode,struct file *file) {struct proc_dir_entry *pde =PDE (inode);
@@ -559,6 +571,9 @@ static const struct file_operations proc_reg_file_ops = { .unlocked_ioctl = proc_reg_unlocked_ioctl, .mmap = proc_reg_mmap, .get_unmapped_area = proc_reg_get_unmapped_area, +#ifndef CONFIG_MMU + .mmap_capabilities = proc_reg_mmap_capabilities, +#endif .open = proc_reg_open, .release = proc_reg_release, };
@@ -572,6 +587,9 @@ static const struct file_operations proc_iter_file_ops = { .unlocked_ioctl = proc_reg_unlocked_ioctl, .mmap = proc_reg_mmap, .get_unmapped_area = proc_reg_get_unmapped_area, +#ifndef CONFIG_MMU + .mmap_capabilities = proc_reg_mmap_capabilities, +#endif .open = proc_reg_open, .release = proc_reg_release, };
@@ -586,6 +604,9 @@ static const struct file_operations proc_reg_file_ops_compat = { .compat_ioctl = proc_reg_compat_ioctl, .mmap = proc_reg_mmap, .get_unmapped_area = proc_reg_get_unmapped_area, +#ifndef CONFIG_MMU + .mmap_capabilities = proc_reg_mmap_capabilities, +#endif .open = proc_reg_open, .release = proc_reg_release, };
@@ -600,6 +621,9 @@ static const struct file_operations proc_iter_file_ops_compat = { .compat_ioctl = proc_reg_compat_ioctl, .mmap = proc_reg_mmap, .get_unmapped_area = proc_reg_get_unmapped_area, +#ifndef CONFIG_MMU + .mmap_capabilities = proc_reg_mmap_capabilities, +#endif .open = proc_reg_open, .release = proc_reg_release, };
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 23/38] proc: don't let NOMMU map procfs files
Sashiko has reviewed this patch and found no issues.
Quoted 14 lines
Every procfs file has a ->mmap since commit
786d7e1612f0("Fix rmmod/read/write races in /proc entries"), whether the entry has a ->proc_mmap or not. On NOMMUvalidate_mmap_request()takes that to mean the file can be mapped. procfs has no ->mmap_capabilities so a regular file getsNOMMU_MAP_COPY, and a private mapping never calls ->mmap at all. Insteaddo_mmap()takesnommu_region_semfor writing anddo_mmap_private()reads the file into the copy withkernel_read().Reading
/proc/mapstakesnommu_region_semas well:
mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, open("/proc/maps", O_RDONLY), 0)
do_mmap()//down_write(&nommu_region_sem)
->do_mmap_private()
Quoted 7 lines
-> kernel_read()[ ... ]
Reported-by Chris Mason <[email protected]>Fixes 786d7e1612f0 ("Fix rmmod/read/write races in /proc entries")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=23
Christian Brauner
maintainerRFC PATCH 24/38 proc/vmcore: don't let vmcore_cleanup() run twice at once
In reply to Christian Braunervmcore_cleanup() has a single caller, fadump's release_mem_store() on powerpc. That checks fw_dump.dump_active without a lock and calls vmcore_cleanup() without one, and kernfs only serializes the writers of one open file. So two tasks that write to /sys/kernel/fadump/release_mem through different files run vmcore_cleanup() at the same time.
The first one removes /proc/vmcore and waits for its readers in proc_remove(). The second one finds the entry already unlinked, returns from proc_remove() right away and frees vmcore_list, the ELF headers and the device dumps while those readers still walk them. Or both of them free the same lists at once.
Serialize vmcore_cleanup() with a mutex of its own. It can't be vmcore_mutex, removing /proc/vmcore calls release_vmcore() which takes it. The second caller then finds nothing left to free.
--- fs/proc/vmcore.c | 5 +++++ 1 file changed, 5 insertions(+)
fs/proc/vmcore.c +5 -0
@@ -1764,6 +1764,11 @@ fs_initcall(vmcore_init);/* Cleanup function for vmcore module. */ void vmcore_cleanup (void ) { +/* Not vmcore_mutex, removing /proc/vmcore calls release_vmcore(). */ +static DEFINE_MUTEX (vmcore_cleanup_mutex); + +/* Two writers of fadump's release_mem can get here at once. */ +guard (mutex)(&vmcore_cleanup_mutex);if (proc_vmcore) {proc_remove (proc_vmcore); proc_vmcore =NULL ;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once
Sashiko has reviewed this patch and found no issues.
Quoted 13 lines
vmcore_cleanup()has a single caller, fadump'srelease_mem_store()on powerpc. That checks fw_dump.dump_active without a lock and callsvmcore_cleanup()without one, and kernfs only serializes the writers of one open file. So two tasks that write to/sys/kernel/fadump/release_memthrough different files runvmcore_cleanup()at the same time.The first one removes
/proc/vmcoreand waits for its readers inproc_remove(). The second one finds the entry already unlinked, returns fromproc_remove()right away and freesvmcore_list, the ELF headers and the device dumps while those readers still walk them. Or both of them free the same lists at once.
Quoted 8 lines
Serialize
vmcore_cleanup()with a mutex of its own. It can't bevmcore_mutex, removing/proc/vmcorecallsrelease_vmcore()which takes it. The second caller then finds nothing left to free.Reported-by Chris Mason <[email protected]>Fixes b500afff11f6 ("fadump: Invalidate registration and release reserved memory for general use.")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=24
Re: [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once
In reply to Christian BraunerQuoted 13 lines · Christian Brauner
vmcore_cleanup()has a single caller, fadump'srelease_mem_store()on powerpc. That checks fw_dump.dump_active without a lock and callsvmcore_cleanup()without one, and kernfs only serializes the writers of one open file. So two tasks that write to/sys/kernel/fadump/release_memthrough different files runvmcore_cleanup()at the same time.The first one removes
/proc/vmcoreand waits for its readers inproc_remove(). The second one finds the entry already unlinked, returns fromproc_remove()right away and freesvmcore_list, the ELF headers and the device dumps while those readers still walk them. Or both of them free the same lists at once.
Quoted 8 lines
Serialize
vmcore_cleanup()with a mutex of its own. It can't bevmcore_mutex, removing/proc/vmcorecallsrelease_vmcore()which takes it. The second caller then finds nothing left to free.Reported-by Chris Mason <[email protected]>Fixes b500afff11f6 ("fadump: Invalidate registration and release reserved memory for general use.")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Looks good. Feel free to add:
Honza
Quoted 24 lines
--- fs/proc/vmcore.c | 5 +++++ 1 file changed, 5 insertions(+)fs/proc/vmcore.c +5
-0@@ -1764,6 +1764,11 @@ fs_initcall(vmcore_init);/* Cleanup function for vmcore module. */ void vmcore_cleanup (void ) { +/* Not vmcore_mutex, removing /proc/vmcore calls release_vmcore(). */ +static DEFINE_MUTEX (vmcore_cleanup_mutex); + +/* Two writers of fadump's release_mem can get here at once. */ +guard (mutex)(&vmcore_cleanup_mutex);if (proc_vmcore) {proc_remove (proc_vmcore); proc_vmcore =NULL ;Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 25/38 powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page()
In reply to Christian Braunercopy_oldmem_page() maps a page of the old kernel that isn't in the linear mapping with ioremap_cache() and copies from it without checking whether the mapping worked. If it didn't, the copy reads from an offset into the NULL page. That oopses unless the copy happens to be a user copy, whose exception table entries turn the fault into a short copy.
Return -ENOMEM like the other architectures do. read_from_oldmem() turns that into -EFAULT like any other short copy.
--- arch/powerpc/kernel/crash_dump.c | 2 ++ 1 file changed, 2 insertions(+)
arch/powerpc/kernel/crash_dump.c +2 -0
@@ -86,6 +86,8 @@ ssize_t copy_oldmem_page(struct iov_iter *iter, unsigned long pfn, csize =copy_to_iter (vaddr + offset, csize, iter); }else { vaddr =ioremap_cache (paddr,PAGE_SIZE ); +if (!vaddr) +return -ENOMEM ; csize =copy_to_iter (vaddr + offset, csize, iter);iounmap (vaddr); }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page()
Sashiko has reviewed this patch and found no issues.
Quoted 6 lines
copy_oldmem_page()maps a page of the old kernel that isn't in the linear mapping withioremap_cache()and copies from it without checking whether the mapping worked. If it didn't, the copy reads from an offset into the NULL page. That oopses unless the copy happens to be a user copy, whose exception table entries turn the fault into a short copy.
Quoted 7 lines
Return
-ENOMEMlike the other architectures do.read_from_oldmem()turns that into-EFAULTlike any other short copy.Reported-by Chris Mason <[email protected]>Fixes 54c32021eb6f ("[PATCH] powerpc: Add arch-dependent copy_oldmem_page")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=25
Re: [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page()
In reply to Christian BraunerQuoted 6 lines · Christian Brauner
copy_oldmem_page()maps a page of the old kernel that isn't in the linear mapping withioremap_cache()and copies from it without checking whether the mapping worked. If it didn't, the copy reads from an offset into the NULL page. That oopses unless the copy happens to be a user copy, whose exception table entries turn the fault into a short copy.
Quoted 7 lines
Return
-ENOMEMlike the other architectures do.read_from_oldmem()turns that into-EFAULTlike any other short copy.Reported-by Chris Mason <[email protected]>Fixes 54c32021eb6f ("[PATCH] powerpc: Add arch-dependent copy_oldmem_page")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Christian Brauner
maintainerRFC PATCH 26/38 powerpc/crash_dump: recover from a machine check in copy_oldmem_page()
In reply to Christian BraunerA capture kernel reads the memory of the crashed kernel, and that memory may well have uncorrectable errors. On powerpc a machine check in the kernel is only recovered from if the instruction that took it has an exception table entry (cf. mce_common_process_ue()). copy_oldmem_page() copies with copy_to_iter(). For a read() of /proc/vmcore that is a user copy, all of whose loads have exception table entries, so the machine check turns into a short copy and read_from_oldmem() returns -EFAULT.
But since commit 4a22fd20379c ("vmcore: convert __read_vmcore to use an iov_iter") /proc/vmcore can be spliced too, and splice(), sendfile() and copy_file_range() copy into kernel pages with memcpy(), which has no exception table entries. A machine check there panics the capture kernel. The same goes for the reads of the ELF headers and notes that vmcore does itself.
Copy with _copy_mc_to_iter() instead. PPC64 selects ARCH_HAS_COPY_MC and there it uses copy_mc_generic() for kernel and user destinations alike, which has the exception table entries. Everywhere else it is _copy_to_iter().
--- arch/powerpc/kernel/crash_dump.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
arch/powerpc/kernel/crash_dump.c +2 -2
@@ -83,12 +83,12 @@ ssize_t copy_oldmem_page(struct iov_iter *iter, unsigned long pfn,if (memblock_is_region_memory (paddr, csize)) { vaddr =__va (paddr); - csize =copy_to_iter (vaddr + offset, csize, iter); + csize =_copy_mc_to_iter (vaddr + offset, csize, iter); }else { vaddr =ioremap_cache (paddr,PAGE_SIZE );if (!vaddr)return -ENOMEM ; - csize =copy_to_iter (vaddr + offset, csize, iter); + csize =_copy_mc_to_iter (vaddr + offset, csize, iter);iounmap (vaddr); }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 26/38] powerpc/crash_dump: recover from a machine check in copy_oldmem_page()
Sashiko has reviewed this patch and found no issues.
Quoted 20 lines
A capture kernel reads the memory of the crashed kernel, and that memory may well have uncorrectable errors. On powerpc a machine check in the kernel is only recovered from if the instruction that took it has an exception table entry (cf.
mce_common_process_ue()).copy_oldmem_page()copies withcopy_to_iter(). For aread()of/proc/vmcorethat is a user copy, all of whose loads have exception table entries, so the machine check turns into a short copy andread_from_oldmem()returns-EFAULT.But since commit
4a22fd20379c("vmcore: convert__read_vmcoreto use aniov_iter")/proc/vmcorecan be spliced too, andsplice(),sendfile()andcopy_file_range()copy into kernel pages withmemcpy(), which has no exception table entries. A machine check there panics the capture kernel. The same goes for the reads of the ELF headers and notes that vmcore does itself.Copy with
_copy_mc_to_iter()instead. PPC64 selectsARCH_HAS_COPY_MCand there it usescopy_mc_generic()for kernel and user destinations alike, which has the exception table entries. Everywhere else it is_copy_to_iter().
Quoted 4 lines
Reported-by Chris Mason <[email protected]>Fixes 4a22fd20379c ("vmcore: convert __read_vmcore to use an iov_iter")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=26
Christian Brauner
maintainerRFC PATCH 27/38 sunrpc: make queue_wait a global wait queue again
In reply to Christian BraunerSince commit 552d0e17ea04 ("sunrpc: convert queue_wait from global to per-cache-detail waitqueue") poll() and epoll queue their waiters for a channel file on the queue_wait head in struct cache_detail. But the cache_detail can be freed while its channel file is still open and waited on.
The procfs channel files of a network namespace are removed when the namespace goes away. Removing a procfs file waits for the callers that are in one of its methods right now and then calls ->release on every file that is still open (cf. close_pdeo()). The waiters that poll() and epoll left on the head stay queued until their file is really closed. So:
sunrpc_exit_net()
-> ip_map_cache_destroy()
-> cache_unregister_net()
-> remove_cache_proc_entries()
-> proc_remove() // ->release, the waiters stay queued
-> cache_destroy_net() // kfree(cd)and when poll() returns remove_wait_queue() writes to the freed cache_detail:
BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x4b/0xa0 Read of size 1 at addr ff110001131bb570 by task sunrpc-poll-uaf/242 Call Trace: remove_wait_queue+0x22/0x1c0 poll_freewait+0xc2/0x230 do_sys_poll+0x9ba/0xd50 __x64_sys_poll+0x165/0x470 Freed by task 12: kfree+0x267/0x620 sunrpc_exit_net+0x34/0x80 ops_undo_list+0x271/0x8d0 cleanup_net+0x3f3/0x860
Closing an epoll instance that watches the file gets there through ep_unregister_pollwait() instead.
The channel files of a network namespace belong to the root of its user namespace and cache_open() doesn't check for a capability. So an unprivileged user can open the channel file of a namespace of its own from the outside, poll it and let the namespace die.
Go back to a single static wait queue that never goes away. A wakeup for one cache wakes the waiters of all caches again but cache_poll() reports per cache_detail so that's just a spurious wakeup. procfs should really own the wait queue of its files like kernfs does but that's nothing for stable.
--- include/linux/sunrpc/cache.h | 1 - net/sunrpc/cache.c | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-)
include/linux/sunrpc/cache.h +0 -1
@@ -119,7 +119,6 @@ struct cache_detail {struct list_head requests;struct list_head readers;spinlock_t queue_lock; -wait_queue_head_t queue_wait;u64 next_seqno;atomic_t writers;/* how many time is /channel open */
net/sunrpc/cache.c +9 -3
@@ -404,7 +404,6 @@ void sunrpc_init_cache_detail(struct cache_detail *cd)INIT_LIST_HEAD (&cd->requests);INIT_LIST_HEAD (&cd->readers);spin_lock_init (&cd->queue_lock); -init_waitqueue_head (&cd->queue_wait); cd->next_seqno =1 ;spin_lock (&cache_list_lock); cd->nextcheck =0 ;
@@ -965,13 +964,20 @@ static ssize_t cache_write(struct file *filp, const char __user *buf,return ret; } +/* +* Not in struct cache_detail: poll and epoll waiters stay queued until +* their file is closed, but a channel file in procfs can stay open after +* the cache_detail of its dying network namespace has been freed. +*/ +static DECLARE_WAIT_QUEUE_HEAD (queue_wait); +static __poll_t cache_poll (struct file *filp,poll_table *wait,struct cache_detail *cd) {__poll_t mask;struct cache_reader *rp = filp->private_data; -poll_wait (filp, &cd->queue_wait, wait); +poll_wait (filp, &queue_wait, wait);/* alway allow write */ mask =EPOLLOUT |EPOLLWRNORM ;
@@ -1236,7 +1242,7 @@ static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h)spin_unlock (&detail->queue_lock);if (ret != -EAGAIN && detail->cache_notify) detail->cache_notify (detail, h); -wake_up (&detail->queue_wait); +wake_up (&queue_wait);if (ret == -EAGAIN ) {kfree (buf);kfree (crq);
Signature · 1 line
2.53.0
Re: [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again
Quoted 50 lines · Christian Brauner
Since commit
552d0e17ea04("sunrpc: convertqueue_waitfrom global to per-cache-detail waitqueue")poll()and epoll queue their waiters for a channel file on thequeue_waithead in structcache_detail. But thecache_detailcan be freed while its channel file is still open and waited on.The procfs channel files of a network namespace are removed when the namespace goes away. Removing a procfs file waits for the callers that are in one of its methods right now and then calls ->release on every file that is still open (cf.
close_pdeo()). The waiters thatpoll()and epoll left on the head stay queued until their file is really closed. So:
sunrpc_exit_net()
->ip_map_cache_destroy()-> cache_unregister_net() -> remove_cache_proc_entries() -> proc_remove() // ->release, the waiters stay queued -> cache_destroy_net() // kfree(cd)and when
poll()returnsremove_wait_queue()writes to the freedcache_detail:BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x4b/0xa0 Read of size 1 at addr ff110001131bb570 by task sunrpc-poll-uaf/242 Call Trace: remove_wait_queue+0x22/0x1c0 poll_freewait+0xc2/0x230 do_sys_poll+0x9ba/0xd50 __x64_sys_poll+0x165/0x470 Freed by task 12: kfree+0x267/0x620 sunrpc_exit_net+0x34/0x80 ops_undo_list+0x271/0x8d0 cleanup_net+0x3f3/0x860Closing an epoll instance that watches the file gets there through
ep_unregister_pollwait()instead.The channel files of a network namespace belong to the root of its user namespace and
cache_open()doesn't check for a capability. So an unprivileged user can open the channel file of a namespace of its own from the outside, poll it and let the namespace die.Go back to a single static wait queue that never goes away. A wakeup for one cache wakes the waiters of all caches again but
cache_poll()reports percache_detailso that's just a spurious wakeup. procfs should really own the wait queue of its files like kernfs does but that's nothing for stable.
Quoted 5 lines
Reported-by Chris Mason <[email protected]>Fixes 552d0e17ea04 ("sunrpc: convert queue_wait from global toper-cache-detail waitqueue")
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Signature · 1 line
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
Christian Brauner
maintainerRe: [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again
Quoted 53 lines · Chuck Lever
Quoted 50 lines · Christian Brauner
Since commit
552d0e17ea04("sunrpc: convertqueue_waitfrom global to per-cache-detail waitqueue")poll()and epoll queue their waiters for a channel file on thequeue_waithead in structcache_detail. But thecache_detailcan be freed while its channel file is still open and waited on.The procfs channel files of a network namespace are removed when the namespace goes away. Removing a procfs file waits for the callers that are in one of its methods right now and then calls ->release on every file that is still open (cf.
close_pdeo()). The waiters thatpoll()and epoll left on the head stay queued until their file is really closed. So:
sunrpc_exit_net()
->ip_map_cache_destroy()-> cache_unregister_net() -> remove_cache_proc_entries() -> proc_remove() // ->release, the waiters stay queued -> cache_destroy_net() // kfree(cd)and when
poll()returnsremove_wait_queue()writes to the freedcache_detail:BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x4b/0xa0 Read of size 1 at addr ff110001131bb570 by task sunrpc-poll-uaf/242 Call Trace: remove_wait_queue+0x22/0x1c0 poll_freewait+0xc2/0x230 do_sys_poll+0x9ba/0xd50 __x64_sys_poll+0x165/0x470 Freed by task 12: kfree+0x267/0x620 sunrpc_exit_net+0x34/0x80 ops_undo_list+0x271/0x8d0 cleanup_net+0x3f3/0x860Closing an epoll instance that watches the file gets there through
ep_unregister_pollwait()instead.The channel files of a network namespace belong to the root of its user namespace and
cache_open()doesn't check for a capability. So an unprivileged user can open the channel file of a namespace of its own from the outside, poll it and let the namespace die.Go back to a single static wait queue that never goes away. A wakeup for one cache wakes the waiters of all caches again but
cache_poll()reports percache_detailso that's just a spurious wakeup. procfs should really own the wait queue of its files like kernfs does but that's nothing for stable.
Quoted 7 lines
Quoted 5 lines
Reported-by Chris Mason <[email protected]>Fixes 552d0e17ea04 ("sunrpc: convert queue_wait from global toper-cache-detail waitqueue")
Signed-off-by Christian Brauner (Amutable) <[email protected]>Acked-by Chuck Lever <[email protected]>
Fwiw, I have a patch series on top of this that makes procfs own the waitqueue and then this whole UAF issues goes away structurally and you get to elide that global thing here.
[email protected]
AI reviewRe: [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again
In reply to Christian BraunerSashiko has reviewed this patch and found no issues.
Quoted 13 lines
Since commit
552d0e17ea04("sunrpc: convertqueue_waitfrom global to per-cache-detail waitqueue")poll()and epoll queue their waiters for a channel file on thequeue_waithead in structcache_detail. But thecache_detailcan be freed while its channel file is still open and waited on.The procfs channel files of a network namespace are removed when the namespace goes away. Removing a procfs file waits for the callers that are in one of its methods right now and then calls ->release on every file that is still open (cf.
close_pdeo()). The waiters thatpoll()and epoll left on the head stay queued until their file is really closed. So:
Quoted 8 lines
sunrpc_exit_net()
->ip_map_cache_destroy()
[ ... ]Reported-by Chris Mason <[email protected]>Fixes 552d0e17ea04 ("sunrpc: convert queue_wait from global to per-cache-detail waitqueue")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=27
Christian Brauner
maintainerRFC PATCH 28/38 scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
In reply to Christian Brauneresas2r_ioctl_handler() copies in a struct atto_express_ioctl, a bit over 512k, and hands the union member for the command to the handlers. For EXPRESS_IOCTL_HBA and EXPRESS_IOCTL_SMP the length of the buffered ioctl is the size of the member plus lengths that come straight from userspace:
bi.length = sizeof(struct atto_ioctl) + ioctl_hba->data_length;
bi.length = sizeof(struct atto_ioctl_smp)
+ le32_to_cpu(si->req_length)
+ le32_to_cpu(si->rsp_length);
handle_buffered_ioctl() copies bi.length bytes from the member into a DMA buffer and, once the adapter is done, copies the DMA buffer back over the member. Nothing checks that the lengths fit into the struct that was copied in. So a large data_length makes it read past the struct and then write the DMA buffer back over the memory behind it.
The firmware commands pass sizeof(struct atto_express_ioctl) to esas2r_{read,write}_{fw,fs}() as the length of data.fwrw.image, which starts 28 bytes into the struct. So they can read and write 28 bytes past it as well.
The handler is reachable through /proc/scsi/esas2r/ATTONode. It is created with mode 0, which procfs turns into 0444, and esas2r doesn't check for a capability anywhere. So any local user can do this on a machine with such an adapter.
Bound all of these by the room from the member to the end of the struct. read_hw() passes handle_hba_ioctl() a single struct atto_ioctl and only ever returns that, so there the data length has to be zero.
--- drivers/scsi/esas2r/esas2r.h | 2 +- drivers/scsi/esas2r/esas2r_ioctl.c | 41 ++++++++++++++++++++++++-------------- drivers/scsi/esas2r/esas2r_main.c | 3 ++- 3 files changed, 29 insertions(+), 17 deletions(-)
drivers/scsi/esas2r/esas2r.h +1 -1
@@ -967,7 +967,7 @@ int esas2r_write_params(struct esas2r_adapter *a, struct esas2r_request *rq,int esas2r_ioctl_handler (void *hostdata,unsigned int cmd,void __user *arg);int esas2r_ioctl (struct scsi_device *dev,unsigned int cmd,void __user *arg);u8 handle_hba_ioctl (struct esas2r_adapter *a, -struct atto_ioctl *ioctl_hba); +struct atto_ioctl *ioctl_hba,size_t room);enum scsi_qc_status esas2r_queuecommand (struct Scsi_Host *host,struct scsi_cmnd *cmd);int esas2r_show_info (struct seq_file *m,struct Scsi_Host *sh);
drivers/scsi/esas2r/esas2r_ioctl.c +26 -15
94 lines not on this page · raw message
drivers/scsi/esas2r/esas2r_main.c +2 -1
@@ -178,7 +178,8 @@ static ssize_t read_hw(struct file *file, struct kobject *kobj,if (!a->local_atto_ioctl)return -ENOMEM ; -if (handle_hba_ioctl (a, a->local_atto_ioctl) !=IOCTL_SUCCESS ) +if (handle_hba_ioctl (a, a->local_atto_ioctl, +sizeof (struct atto_ioctl )) !=IOCTL_SUCCESS )return -ENOMEM ;memcpy (buf, a->local_atto_ioctl, length);
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 28/38] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] scsi: esas2r: TOCTOU race on data_length in handle_hba_ioctl() via read_hw() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] scsi: esas2r: TOCTOU race on data_length in handle_hba_ioctl() via read_hw() --
commit 8af21917bc01e9991b2ab872e7a4c38c91a6e388
Author: Christian Brauner <[email protected]>
scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
This commit adds bounds checks on user-supplied lengths for the HBA, SMP, and firmware ioctls in esas2r to prevent out-of-bounds reads and writes past the end of struct atto_express_ioctl and a->local_atto_ioctl.
Quoted 4 lines
drivers/scsi/esas2r/esas2r_ioctl.c +0
-0
[ ... ]
Quoted 8 lines
drivers/scsi/esas2r/esas2r_ioctl.c +2
-1@@ -1221,11 +1231,16 @@ static void hba_ioctl_done_callback(struct esas2r_adapter *a, } } +/* @room is the size of the buffer that starts with @ioctl_hba. */ u8 handle_hba_ioctl (struct esas2r_adapter *a, -struct atto_ioctl *ioctl_hba) +struct atto_ioctl *ioctl_hba,size_t room)
drivers/scsi/esas2r/esas2r_ioctl.c static void hba_ioctl_done_callback(struct esas2r_adapter *a, · line 1246
Quoted 10 lines
drivers/scsi/esas2r/esas2r_ioctl.c +4
-0{struct esas2r_buffered_ioctl bi; +/* The data follows @ioctl_hba in the same buffer. */ +if (ioctl_hba->data_length > room -sizeof (struct atto_ioctl )) +return IOCTL_INVALID_PARAM ; +memset (&bi,0 ,sizeof (bi)); bi.a = a;
[Severity: High]
Can a concurrent write_hw() bypass this bounds check when handle_hba_ioctl() is called from read_hw()?
In drivers/scsi/esas2r/esas2r_main.c, read_hw() and write_hw() both operate on the shared a->local_atto_ioctl buffer without holding a driver lock (and kernfs only serializes per open file description via of->mutex, not across separate file descriptors):
drivers/scsi/esas2r/esas2r_main.c:read_hw() {
...
if (handle_hba_ioctl(a, a->local_atto_ioctl,
sizeof(struct atto_ioctl)) != IOCTL_SUCCESS)
return -ENOMEM;
...
}drivers/scsi/esas2r/esas2r_main.c:write_hw() {
...
memset(a->local_atto_ioctl, 0, sizeof(struct atto_ioctl));
memcpy(a->local_atto_ioctl, buf, length);
...
}Inside handle_hba_ioctl(), ioctl_hba->data_length is read twice without synchronization:
drivers/scsi/esas2r/esas2r_ioctl.c:handle_hba_ioctl() {
if (ioctl_hba->data_length > room - sizeof(struct atto_ioctl))
return IOCTL_INVALID_PARAM;
memset(&bi, 0, sizeof(bi));
bi.a = a;
bi.ioctl = ioctl_hba;
bi.length = sizeof(struct atto_ioctl) + ioctl_hba->data_length;
...
return handle_buffered_ioctl(&bi);
}If write_hw() overwrites a->local_atto_ioctl->data_length with a non-zero value between the bounds check and the assignment to bi.length, wouldn't handle_buffered_ioctl() still read and write bi.length bytes past the end of a->local_atto_ioctl?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=28
Christian Brauner
maintainerRe: [PATCH RFC 28/38] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
Quoted 37 lines · On Tue, Nov 03, 2026 at 02:48:22PM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] scsi: esas2r: TOCTOU race ondata_lengthinhandle_hba_ioctl()viaread_hw()--commit
8af21917bc01e9991b2ab872e7a4c38c91a6e388
Author: Christian Brauner <[email protected]>scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
This commit adds bounds checks on user-supplied lengths for the HBA, SMP, and firmware ioctls in esas2r to prevent out-of-bounds reads and writes past the end of struct
atto_express_ioctlanda->local_atto_ioctl.Quoted 4 lines
drivers/scsi/esas2r/esas2r_ioctl.c +0
-0[ ... ]
Quoted 17 lines
drivers/scsi/esas2r/esas2r_ioctl.c +6
-1@@ -1221,11 +1231,16 @@ static void hba_ioctl_done_callback(struct esas2r_adapter *a, } } +/* @room is the size of the buffer that starts with @ioctl_hba. */ u8 handle_hba_ioctl (struct esas2r_adapter *a, -struct atto_ioctl *ioctl_hba) +struct atto_ioctl *ioctl_hba,size_t room) {struct esas2r_buffered_ioctl bi; +/* The data follows @ioctl_hba in the same buffer. */ +if (ioctl_hba->data_length > room -sizeof (struct atto_ioctl )) +return IOCTL_INVALID_PARAM ; +memset (&bi,0 ,sizeof (bi));
Quoted 5 lines
drivers/scsi/esas2r/esas2r_ioctl.c static void hba_ioctl_done_callback(struct esas2r_adapter *a, · line 1246
Quoted 1 line
drivers/scsi/esas2r/esas2r_ioctl.c +0
-0bi.a = a;[Severity: High]
Can a concurrentwrite_hw()bypass this bounds check whenhandle_hba_ioctl()is called fromread_hw()?
This whole codepath as currently implemented is structurally fscked. So it's not something fixable in this series apart from the glaringly obvious.
But for the record:
- The ATTONode entry thing is created as a side effect of reading another file.
So esas2r_info() registers a character device and creates /proc/scsi/esas2r/ATTONode the first time it runs. That pins a global pointer to whichever host triggered it. So the control node only exists after someone looked at the host info. So it is bound to the first host and not to the adapter it operates on.
The lifeteime is then tied to the module and not to the host. The entry is removed in esas2r_exit() on module exit.
But the directory it lives in belongs to SCSI. That removes it when the last host of the template goes away.
So unbind or hotunplug of the last adapter removes a non-empty directory... the dir is unlinked and the ATTONode leaks and stays reachable through a cached dentry.
And the global host pointer dangles.
So every ioctl on the node dereferences it.
It's like non-empty directory removal basically. Fun.
- No privilege check anywhere. The node is created with mode 0 so 0444 with procfs the the driver never calls capable().
Hence, any local user can read and write firmware and NVRAM and issue VDA commands.
- The ioctl protocol itself (where to begin):
- Plain-number commands
- one struct copied in whole
- unbounded lengths taken straight from userspace (well, that's fixed here)
Sorry, I'm not going to redesign esas2r.
And from what I can tell that ATTO stuff is legacy anyway so good riddance as far as I'm concerned.
Christian Brauner
maintainerRFC PATCH 29/38 powerpc/pseries: bound the lengths in the ofdt property parser
In reply to Christian BraunerThe commands for /proc/powerpc/ofdt that carry properties, "add_node", "add_property" and "update_property", can make the parser read past the buffer they were copied into:
(1) do_update_property() computes the end of the buffer after parse_node() moved buf past the phandle. So the end is off by the length of the phandle string, which can be padded with zeroes, and parse_next_property() accepts a value that reaches that far past the command. new_property() then copies those bytes into the property, which everyone can read in /proc/device-tree.
(2) parse_next_property() parses the length with simple_strtoul() into an int and only rejects -1. A length above INT_MAX turns negative, tmp += *length moves backwards and passes the check against the end, so *tmp is read up to 2G in front of the buffer.
(3) Updating "slb-size" or "ibm,slb-size" reads an int from the value whatever its length.
Compute the end before parsing the phandle, reject lengths that don't fit into an int or into what is left of the buffer, and only take the slb size from a value that holds one. The file is root-only and refused under lockdown but the parser still shouldn't read past its input.
--- arch/powerpc/platforms/pseries/reconfig.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-)
arch/powerpc/platforms/pseries/reconfig.c +10 -8
@@ -114,6 +114,7 @@ static void release_prop_list(const struct property *prop)static char *parse_next_property (char *buf,char *end,char **name,int *length,unsigned char **value) { +unsigned long len;char *tmp; *name = buf;
@@ -133,13 +134,13 @@ static char * parse_next_property(char *buf, char *end, char **name, int *length }/* now we're on the length */ - *length =-1 ; - *length =simple_strtoul (tmp, &tmp,10 ); -if (*length ==-1 ) { + len =simple_strtoul (tmp, &tmp,10 ); +if (len >INT_MAX ) {printk (KERN_ERR "property parse failed in %s at line %d\n" , __func__, __LINE__);return NULL ; } + *length = len;if (*tmp !=' ' || ++tmp >= end) {printk (KERN_ERR "property parse failed in %s at line %d\n" , __func__, __LINE__);
@@ -148,13 +149,13 @@ static char * parse_next_property(char *buf, char *end, char **name, int *length/* now we're on the value */ *value = tmp; - tmp += *length; -if (tmp > end) { +if (len > end - tmp) {printk (KERN_ERR "property parse failed in %s at line %d\n" , __func__, __LINE__);return NULL ; } -else if (tmp < end && *tmp !=' ' && *tmp !='\0' ) { + tmp += len; +if (tmp < end && *tmp !=' ' && *tmp !='\0' ) {printk (KERN_ERR "property parse failed in %s at line %d\n" , __func__, __LINE__);return NULL ;
@@ -320,8 +321,8 @@ static int do_update_property(char *buf, size_t bufsize)char *name, *end, *next_prop;int length;struct property *newprop; - buf =parse_node (buf, bufsize, &np); end = buf + bufsize; + buf =parse_node (buf, bufsize, &np);if (!np)return -ENODEV ;
@@ -337,7 +338,8 @@ static int do_update_property(char *buf, size_t bufsize)if (!newprop)return -ENOMEM ; -if (!strcmp (name,"slb-size" ) || !strcmp (name,"ibm,slb-size" )) +if ((!strcmp (name,"slb-size" ) || !strcmp (name,"ibm,slb-size" )) && + length >= (int )sizeof (int ))slb_set_size (*(int *)value);return of_update_property (np, newprop);
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 29/38] powerpc/pseries: bound the lengths in the ofdt property parser
Sashiko has reviewed this patch and found no issues.
Quoted 15 lines
The commands for
/proc/powerpc/ofdtthat carry properties, "add_node", "add_property" and "update_property", can make the parser read past the buffer they were copied into:(1)
do_update_property()computes the end of the buffer afterparse_node()moved buf past the phandle. So the end is off by the length of the phandle string, which can be padded with zeroes, andparse_next_property()accepts a value that reaches that far past the command.new_property()then copies those bytes into the property, which everyone can read in/proc/device-tree.(2)
parse_next_property()parses the length withsimple_strtoul()into an int and only rejects -1. A length aboveINT_MAXturns negative, tmp += *length moves backwards and passes the check against the end, so *tmp is read up to 2G in front of the buffer.
Quoted 6 lines
[ ... ]Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Fixes 610d91511f99 ("[PATCH] powerpc: Add support for changing properties from userspace")Fixes 46db2f86a3b2 ("powerpc/pseries: Fix to handle slb resize across migration")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=29
Christian Brauner
maintainerRFC PATCH 30/38 powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long
In reply to Christian Braunerh_get_ppp() returns the status of the H_GET_PPP hcall as an unsigned int. update_ppp() hands it on as its ssize_t result, and lparcfg_write() turns the hcall statuses it knows into an errno and returns anything else as it is. So when H_GET_PPP fails, say with H_PARAMETER (-4), the status comes back as 4294967292 and a write to /proc/powerpc/lparcfg reports that many bytes written.
Return the status as the long it is. lparcfg_write() then treats it like the status of H_SET_PPP and an unknown one is at least negative. Before commit 11529396ea31 ("powerpc/pseries: Split processor entitlement retrieval and gathering to helper routines") a failed H_GET_PPP turned into -EIO.
--- arch/powerpc/platforms/pseries/lparcfg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/powerpc/platforms/pseries/lparcfg.c +1 -1
@@ -113,7 +113,7 @@ struct hvcall_ppp_data {* XXXXXX - Entitled procs capacity % available to the * partitions pool. */ -static unsigned int h_get_ppp (struct hvcall_ppp_data *ppp_data) +static long h_get_ppp (struct hvcall_ppp_data *ppp_data) {unsigned long retbuf[PLPAR_HCALL9_BUFSIZE ] = {0 };long rc;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 30/38] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long
Sashiko has reviewed this patch and found no issues.
Quoted 13 lines
h_get_ppp()returns the status of theH_GET_PPPhcall as an unsigned int.update_ppp()hands it on as itsssize_tresult, andlparcfg_write()turns the hcall statuses it knows into an errno and returns anything else as it is. So whenH_GET_PPPfails, say withH_PARAMETER(-4), the status comes back as 4294967292 and a write to/proc/powerpc/lparcfgreports that many bytes written.Return the status as the long it is.
lparcfg_write()then treats it like the status ofH_SET_PPPand an unknown one is at least negative. Before commit11529396ea31("powerpc/pseries: Split processor entitlement retrieval and gathering to helper routines") a failedH_GET_PPPturned into-EIO.
Quoted 4 lines
Reported-by Chris Mason <[email protected]>Fixes 11529396ea31 ("powerpc/pseries: Split processor entitlement retrieval and gathering to helper routines")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=30
Christian Brauner
maintainerRFC PATCH 31/38 proc: sysctl: resume readdir after the last name it returned
In reply to Christian Braunerproc_sys_readdir() counts the entries of a directory and resumes a listing at the number getdents64() stopped at. The entries are sorted by name, so one that is registered or unregistered in between shifts everything behind it by one. Listing /proc/sys/net/ipv4/conf with a buffer that holds ".", ".." and "all" and registering a device whose name sorts before "all" makes the next call return "all" a second time. Unregistering one that sorted before the position makes it skip an entry instead. So a listing that takes more than one call shows a name twice, or not at all. Even the 64k buffer of ls needs a second call to find the end of the directory, so a device that comes up right then repeats the last entry.
So remember the name of the last entry a call emitted in the file and resume at the first usable entry after it, like kernfs resumes after the node it stopped at. The entry itself can't be pinned across calls, unregistering waits for that. If the position was moved by a seek in between the listing counts from the start like before, seeking to an arbitrary position has never been better than that.
That also takes one walk down the tree to resume instead of a walk over every entry in front of the position with a round trip through sysctl_lock for each of them.
--- fs/proc/proc_sysctl.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 3 deletions(-)
fs/proc/proc_sysctl.c +103 -3
@@ -417,6 +417,54 @@ static void next_entry(struct ctl_table_header **phead, const struct ctl_table * *pentry = entry; } +/* +* The first usable entry of @dir after @name, or the first one at all for a +* NULL @name. The entries are sorted by name, so that is where a listing +* resumes when entries in front of it came or went in between. -ENOENT if +* the directory is being unregistered. +*/ +static int entry_after (struct ctl_dir *dir,const char *name,int namelen, +struct ctl_table_header **phead, +const struct ctl_table **pentry) +{ +struct ctl_table_header *head =NULL ; +const struct ctl_table *entry =NULL ; +struct rb_node *node, *after =NULL ; +struct ctl_node *ctl_node; + +spin_lock (&sysctl_lock); +if (unlikely (dir->header.unregistering)) { +spin_unlock (&sysctl_lock); +return -ENOENT ; + } +if (!name) { + after =rb_first (&dir->root); + }else for (node = dir->root.rb_node; node;) { +const struct ctl_table *table; +struct ctl_table_header *h; + + ctl_node = rb_entry(node, struct ctl_node, node); + h = ctl_node->header; + table = &h->ctl_table[ctl_node - h->node]; +if (namecmp (table->procname,strlen (table->procname), + name, namelen) >0 ) { + after = node; + node = node->rb_left; + }else { + node = node->rb_right; + } + } + ctl_node =first_usable_entry (after); +spin_unlock (&sysctl_lock); +if (ctl_node) { + head = ctl_node->header; + entry = &head->ctl_table[ctl_node - head->node]; + } + *phead = head; + *pentry = entry; +return 0 ; +} +/* * sysctl_perm does NOT grant the superuser all rights automatically, because * some sysctl variables are readonly even to root.
@@ -675,6 +723,36 @@ static __poll_t proc_sys_poll(struct file *filp, poll_table *wait)return ret; } +/* +* Where the next readdir resumes. The entries are counted, so one that is +* registered or unregistered in front of the position would shift what +* the next call returns. The name of the last entry emitted doesn't move. +*/ +struct sysctl_cursor { +loff_t pos;/* the position behind the entry */ +int len; +char name[NAME_MAX +1 ]; +}; + +/* If this can't remember the entry the next call counts from the start. */ +static void sysctl_cursor_set (struct file *file,const char *name,int len, +loff_t pos) +{ +struct sysctl_cursor *cursor = file->private_data; + +if (len >NAME_MAX ) +return ; +if (!cursor) { + cursor =kmalloc (sizeof (*cursor),GFP_KERNEL ); +if (!cursor) +return ; + file->private_data = cursor; + } + cursor->pos = pos; + cursor->len = len; +memcpy (cursor->name, name, len); +} + static bool proc_sys_fill_cache(struct file *file, struct dir_context *ctx, struct ctl_table_header *head,
@@ -753,8 +831,11 @@ static int scan(struct ctl_table_header *head, const struct ctl_table *table,else res =proc_sys_fill_cache (file, ctx, head, table); -if (res) +if (res) { ctx->pos = *pos; +sysctl_cursor_set (file, table->procname,strlen (table->procname), + ctx->pos); + }return res; }
@@ -762,6 +843,7 @@ static int scan(struct ctl_table_header *head, const struct ctl_table *table,static int proc_sys_readdir (struct file *file,struct dir_context *ctx) {struct ctl_table_header *head =grab_header (file_inode (file)); +struct sysctl_cursor *cursor = file->private_data;struct ctl_table_header *h =NULL ;const struct ctl_table *entry;struct ctl_dir *ctl_dir;
@@ -775,9 +857,20 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)if (!dir_emit_dots (file, ctx)) goto out; - pos =2 ; +/* +* Resume after the last entry the previous call emitted. Only a seek +* that moved the position away from it counts from the start, like +* seeking to an arbitrary position always did. +*/ +if (cursor && cursor->pos == ctx->pos) { +entry_after (ctl_dir, cursor->name, cursor->len, &h, &entry); + pos = ctx->pos; + }else { +first_entry (ctl_dir, &h, &entry); + pos =2 ; + } -for (first_entry (ctl_dir, &h, &entry); h;next_entry (&h, &entry)) { +for (; h;next_entry (&h, &entry)) {if (!scan (h, entry, &pos, file, ctx)) {sysctl_head_finish (h);break ;
@@ -788,6 +881,12 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)return 0 ; } +static int proc_sys_dir_release (struct inode *inode,struct file *file) +{ +kfree (file->private_data); +return 0 ; +} +static int proc_sys_permission (const struct mnt_idmap *idmap,struct inode *inode,int mask) {
@@ -867,6 +966,7 @@ static const struct file_operations proc_sys_dir_file_operations = { .read = generic_read_dir, .iterate_shared = proc_sys_readdir, .llseek = generic_file_llseek, + .release = proc_sys_dir_release, };static const struct inode_operations proc_sys_inode_operations = {
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned
Sashiko has reviewed this patch and found no issues.
Quoted 19 lines
proc_sys_readdir()counts the entries of a directory and resumes a listing at the numbergetdents64()stopped at. The entries are sorted by name, so one that is registered or unregistered in between shifts everything behind it by one. Listing/proc/sys/net/ipv4/confwith a buffer that holds ".", ".." and "all" and registering a device whose name sorts before "all" makes the next call return "all" a second time. Unregistering one that sorted before the position makes it skip an entry instead. So a listing that takes more than one call shows a name twice, or not at all. Even the 64k buffer of ls needs a second call to find the end of the directory, so a device that comes up right then repeats the last entry.So remember the name of the last entry a call emitted in the file and resume at the first usable entry after it, like kernfs resumes after the node it stopped at. The entry itself can't be pinned across calls, unregistering waits for that. If the position was moved by a seek in between the listing counts from the start like before, seeking to an arbitrary position has never been better than that.
Quoted 7 lines
That also takes one walk down the tree to resume instead of a walk over every entry in front of the position with a round trip through
sysctl_lockfor each of them.Fixes 77b14db502cb ("[PATCH] sysctl: reimplement the sysctl proc support")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=31
Re: [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned
In reply to Christian BraunerQuoted 19 lines · Christian Brauner
proc_sys_readdir()counts the entries of a directory and resumes a listing at the numbergetdents64()stopped at. The entries are sorted by name, so one that is registered or unregistered in between shifts everything behind it by one. Listing/proc/sys/net/ipv4/confwith a buffer that holds ".", ".." and "all" and registering a device whose name sorts before "all" makes the next call return "all" a second time. Unregistering one that sorted before the position makes it skip an entry instead. So a listing that takes more than one call shows a name twice, or not at all. Even the 64k buffer of ls needs a second call to find the end of the directory, so a device that comes up right then repeats the last entry.So remember the name of the last entry a call emitted in the file and resume at the first usable entry after it, like kernfs resumes after the node it stopped at. The entry itself can't be pinned across calls, unregistering waits for that. If the position was moved by a seek in between the listing counts from the start like before, seeking to an arbitrary position has never been better than that.
Quoted 7 lines
That also takes one walk down the tree to resume instead of a walk over every entry in front of the position with a round trip through
sysctl_lockfor each of them.Fixes 77b14db502cb ("[PATCH] sysctl: reimplement the sysctl proc support")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Just one coding style nit below. Otherwise feel free to add:
fs/proc/proc_sysctl.c static void next_entry(struct ctl_table_header **phead, const struct ctl_table * · line 442
Quoted 3 lines
fs/proc/proc_sysctl.c +3
-0+if (!name) { + after =rb_first (&dir->root); + }else for (node = dir->root.rb_node; node;) {
This indentation looks too inventive to me :) I'd just use
} else {
for (...) {
HonzaSignature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 32/38 selftests/proc: check a sysctl listing while entries come and go
In reply to Christian BraunerList /proc/sys/net/ipv4/conf of a new network namespace in two pieces:
- with a buffer that holds the dots and "all", register a tun device whose name sorts first, finish the listing: "all" is listed once
- with that device registered and a buffer that holds the dots and it, unregister it, finish the listing: "all" is still listed
Both fail without the fix, the first lists "all" twice, the second not at all.
--- tools/testing/selftests/proc/.gitignore | 1 + tools/testing/selftests/proc/Makefile | 1 + .../selftests/proc/proc-sysctl-readdir-churn.c | 147 +++++++++++++++++++++ 3 files changed, 149 insertions(+)
tools/testing/selftests/proc/.gitignore +1 -0
@@ -25,4 +25,5 @@ /self /setns-dcache /setns-sysvipc +/proc-sysctl-readdir-churn /thread-self
tools/testing/selftests/proc/Makefile +1 -0
@@ -30,6 +30,7 @@ TEST_GEN_PROGS += setns-sysvipc TEST_GEN_PROGS += thread-self TEST_GEN_PROGS += proc-multiple-procfs TEST_GEN_PROGS += proc-fsconfig-hidepid +TEST_GEN_PROGS += proc-sysctl-readdir-churn TEST_GEN_PROGS += proc-pidns include ../lib.mk
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c new file +147 -0
147 lines not on this page · raw message
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 32/38] selftests/proc: check a sysctl listing while entries come and go
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] selftests/proc: missing prerequisite skips in sysctl_readdir_setup() - [Low] selftests/proc…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] selftests/proc: missing prerequisite skips in sysctl_readdir_setup()
- [Low] selftests/proc: proc-sysctl-readdir-churn duplicates proc-readdir-churn --
commit 9b75f87bfb2fb1adbf5b6ac83f3bfdac725df7f5
Author: Christian Brauner <[email protected]>
selftests/proc: check a sysctl listing while entries come and go
This commit adds a new kselftest binary, proc-sysctl-readdir-churn, to verify that multi-call getdents64() listings of /proc/sys/net/ipv4/conf do not repeat or skip entries when a tun network device is registered or unregistered between calls.
Quoted 6 lines
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c new file +0
-0@@ -0,0 +1,147 @@
[ ... ]
Quoted 50 lines
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c new file +50
-0+#define CONF_DIR "/proc/sys/net/ipv4/conf" +#define TUN_NAME "a0"/* sorts before "all" */ +#define TUN_PATH CONF_DIR "/" TUN_NAME +#define SMALL_BUF 80/* ".", ".." and one short name */ +#define NAMES 64 + +static int tun_open (const char *name) +{ +struct ifreq ifr = {}; +int fd; + + fd =open ("/dev/net/tun" ,O_RDWR |O_CLOEXEC ); +if (fd <0 ) +return -1 ; +strncpy (ifr.ifr_name, name,IFNAMSIZ -1 ); + ifr.ifr_flags =IFF_TUN |IFF_NO_PI ; +if (ioctl (fd,TUNSETIFF , &ifr)) { +close (fd); +return -1 ; + } +return fd; +} + +/* one getdents64() into @buf, the names appended to @names */ +static int list (int fd,char *buf,size_t size,char names[][NAME_MAX +1 ], +int *n) +{ +long ret, off; +int got =0 ; + + ret =syscall (__NR_getdents64, fd, buf, size); +if (ret <0 ) +return -errno; +for (off =0 ; off < ret;) { +struct dirent64 *d = (void *)(buf + off); + +if (*n <NAMES ) +strcpy (names[(*n)++], d->d_name); + got++; + off += d->d_reclen; + } +return got; +} + +static int count (char names[][NAME_MAX +1 ],int n,const char *name) +{ +int i, c =0 ; + +for (i =0 ; i < n; i++) +if (!strcmp (names[i], name))
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c line 82
Quoted 10 lines
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c new file +10
-0+ c++; +return c; +} + +FIXTURE (sysctl_readdir) { +int dir; +int n; +char names[NAMES ][NAME_MAX +1 ]; +char buf[65536 ]; +};
[Severity: Low]
This isn't a bug, but tun_open(), list(), count(), the fixture state in FIXTURE(sysctl_readdir), and the register_in_front and unregister_in_front test flows in tools/testing/selftests/proc/proc-sysctl-readdir-churn.c duplicate the helpers, FIXTURE(proc_readdir), and the add_in_front and remove_in_front test cases in tools/testing/selftests/proc/proc-readdir-churn.c, differing only in the target directory (/proc/sys/net/ipv4/conf vs /proc/net/dev_snmp6) and expected baseline entries (all, default, and lo vs lo).
Could this be parameterized in proc-readdir-churn.c using FIXTURE_VARIANT and FIXTURE_VARIANT_ADD instead of introducing a second standalone test file with duplicated helpers and test logic?
Quoted 18 lines
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c new file +18
-0+ +FIXTURE_SETUP (sysctl_readdir) +{ +if (unshare (CLONE_NEWNET )) +SKIP (return,"unshare(CLONE_NEWNET): %s" ,strerror (errno)); +if (access ("/dev/net/tun" ,F_OK )) +SKIP (return,"/dev/net/tun: %s" ,strerror (errno)); + self->dir =open (CONF_DIR ,O_RDONLY |O_DIRECTORY |O_CLOEXEC ); +ASSERT_GE (self->dir,0 ); + self->n =0 ; +} + +FIXTURE_TEARDOWN (sysctl_readdir) +{ +close (self->dir); +} + +/* a device registered in front of the position doesn't repeat an entry */
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c line 110
Quoted 10 lines
tools/testing/selftests/proc/proc-sysctl-readdir-churn.c new file +10
-0+TEST_F(sysctl_readdir, register_in_front) +{ +int tun; + +/* ".", ".." and "all", "default" doesn't fit */ +ASSERT_EQ (list (self->dir, self->buf,SMALL_BUF , self->names, &self->n),3 ); +ASSERT_EQ (count (self->names, self->n,"all" ),1 ); + + tun =tun_open (TUN_NAME ); +ASSERT_GE (tun,0 );
[Severity: Medium]
In FIXTURE_SETUP(sysctl_readdir), opening CONF_DIR (/proc/sys/net/ipv4/conf) is checked with ASSERT_GE(self->dir, 0), whereas proc-readdir-churn.c skips when opening SNMP6_DIR fails:
tools/testing/selftests/proc/proc-readdir-churn.c:FIXTURE_SETUP(proc_readdir) {
...
self->dir = open(SNMP6_DIR, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (self->dir < 0)
SKIP(return, SNMP6_DIR ": %s", strerror(errno));
}In addition, FIXTURE_SETUP(sysctl_readdir) only checks access("/dev/net/tun", F_OK). On systems where /dev/net/tun exists as a static device node (for example, created by udev or kmod static-nodes) while CONFIG_TUN is disabled, access("/dev/net/tun", F_OK) succeeds, but open("/dev/net/tun", ...) inside tun_open(TUN_NAME) fails with ENODEV or ENXIO and triggers ASSERT_GE(tun, 0) in register_in_front and unregister_in_front.
Since tools/testing/selftests/proc/config only enables CONFIG_PROC_FS=y at this point, will running this test on a kernel without CONFIG_SYSCTL, CONFIG_INET, or CONFIG_TUN fail the test instead of skipping when those prerequisites are unavailable?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=32
Christian Brauner
maintainerRe: [PATCH RFC 32/38] selftests/proc: check a sysctl listing while entries come and go
Quoted 1 line · On Tue, Nov 03, 2026 at 02:48:31PM +0000, [email protected] wrote:
- [Low] selftests/proc: proc-sysctl-readdir-churn duplicates proc-readdir-churn
My appetite for churn due to "low" issues in tests is low.
Christian Brauner
maintainerRFC PATCH 33/38 proc: sysctl: copy to and from userspace without holding the table
In reply to Christian Braunerproc_sys_call_handler() grabs the table, copies in what is written, calls the handler and copies what it produced out to userspace before it lets go of the table again. Unregistering a table waits until nobody uses it anymore. So a reader or writer whose copy faults for as long as it wants, with userfaultfd or a stalled FUSE mapping, keeps unregister_sysctl_table() waiting, and its callers like to hold locks while they do that. addrconf unregisters the ipv6 sysctls of a device with RTNL held, so a stalled read of /proc/sys/net/ipv6/conf/<dev>/mtu holds up every RTNL user until the reader gives up.
Copy in before grabbing the table and copy out after letting go of it, like procfs and kernfs do for their files. The buffer is ours, nothing needs the table for the copies.
That moves the allocation and the copy in before the permission check. A write that can't be copied in, or a request that is too large, now fails with -EFAULT or -ENOMEM where it used to fail with -EPERM.
--- fs/proc/proc_sysctl.c | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-)
fs/proc/proc_sysctl.c +28 -22
@@ -602,14 +602,36 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter, int write) {struct inode *inode =file_inode (iocb->ki_filp); -struct ctl_table_header *head =grab_header (inode);const struct ctl_table *table =PROC_I (inode)->sysctl_entry; +struct ctl_table_header *head;size_t count =iov_iter_count (iter);char *kbuf;ssize_t error; -if (IS_ERR (head)) -return PTR_ERR (head); +/* don't even try if the size is too large */ +if (count >=KMALLOC_MAX_SIZE ) +return -ENOMEM ; + kbuf =kvzalloc (count +1 ,GFP_KERNEL ); +if (!kbuf) +return -ENOMEM ; + +/* +* Copy in before grabbing the table and copy out after letting go of +* it. Unregistering the table waits for us and a copy that faults can +* take as long as userspace wants. +*/ +if (write) { + error = -EFAULT ; +if (!copy_from_iter_full (kbuf, count, iter)) + goto out_free_buf; + kbuf[count] ='\0' ; + } + + head =grab_header (inode); +if (IS_ERR (head)) { + error =PTR_ERR (head); + goto out_free_buf; + }/* * At this point we know that the sysctl was not unregistered
@@ -624,28 +646,15 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,if (!table->proc_handler) goto out; -/* don't even try if the size is too large */ - error = -ENOMEM ; -if (count >=KMALLOC_MAX_SIZE ) - goto out; - kbuf =kvzalloc (count +1 ,GFP_KERNEL ); -if (!kbuf) - goto out; - -if (write) { - error = -EFAULT ; -if (!copy_from_iter_full (kbuf, count, iter)) - goto out_free_buf; - kbuf[count] ='\0' ; - } - error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, &kbuf, &count, &iocb->ki_pos);if (error) - goto out_free_buf; + goto out;/* careful: calling conventions are nasty here */ error = table->proc_handler (table, write, kbuf, &count, &iocb->ki_pos); +out: +sysctl_head_finish (head);if (error) goto out_free_buf;
@@ -658,9 +667,6 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter, error = count; out_free_buf:kvfree (kbuf); -out: -sysctl_head_finish (head); -return error; }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table
Sashiko has reviewed this patch and found no issues.
Quoted 14 lines
proc_sys_call_handler()grabs the table, copies in what is written, calls the handler and copies what it produced out to userspace before it lets go of the table again. Unregistering a table waits until nobody uses it anymore. So a reader or writer whose copy faults for as long as it wants, with userfaultfd or a stalled FUSE mapping, keepsunregister_sysctl_table()waiting, and its callers like to hold locks while they do that. addrconf unregisters the ipv6 sysctls of a device with RTNL held, so a stalled read of /proc/sys/net/ipv6/conf/<dev>/mtu holds up every RTNL user until the reader gives up.Copy in before grabbing the table and copy out after letting go of it, like procfs and kernfs do for their files. The buffer is ours, nothing needs the table for the copies.
Quoted 7 lines
That moves the allocation and the copy in before the permission check. A write that can't be copied in, or a request that is too large, now fails with
-EFAULTor-ENOMEMwhere it used to fail with-EPERM.Fixes 330d57fb98a9 ("[PATCH] Fix sysctl unregistration oops (CVE-2005-2709)")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=33
Re: [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table
In reply to Christian BraunerQuoted 14 lines · Christian Brauner
proc_sys_call_handler()grabs the table, copies in what is written, calls the handler and copies what it produced out to userspace before it lets go of the table again. Unregistering a table waits until nobody uses it anymore. So a reader or writer whose copy faults for as long as it wants, with userfaultfd or a stalled FUSE mapping, keepsunregister_sysctl_table()waiting, and its callers like to hold locks while they do that. addrconf unregisters the ipv6 sysctls of a device with RTNL held, so a stalled read of /proc/sys/net/ipv6/conf/<dev>/mtu holds up every RTNL user until the reader gives up.Copy in before grabbing the table and copy out after letting go of it, like procfs and kernfs do for their files. The buffer is ours, nothing needs the table for the copies.
Quoted 7 lines
That moves the allocation and the copy in before the permission check. A write that can't be copied in, or a request that is too large, now fails with
-EFAULTor-ENOMEMwhere it used to fail with-EPERM.Fixes 330d57fb98a9 ("[PATCH] Fix sysctl unregistration oops (CVE-2005-2709)")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Looks good. Feel free to add:
Honza
Quoted 94 lines
--- fs/proc/proc_sysctl.c | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-)fs/proc/proc_sysctl.c +28
-22@@ -602,14 +602,36 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter, int write) {struct inode *inode =file_inode (iocb->ki_filp); -struct ctl_table_header *head =grab_header (inode);const struct ctl_table *table =PROC_I (inode)->sysctl_entry; +struct ctl_table_header *head;size_t count =iov_iter_count (iter);char *kbuf;ssize_t error; -if (IS_ERR (head)) -return PTR_ERR (head); +/* don't even try if the size is too large */ +if (count >=KMALLOC_MAX_SIZE ) +return -ENOMEM ; + kbuf =kvzalloc (count +1 ,GFP_KERNEL ); +if (!kbuf) +return -ENOMEM ; + +/* +* Copy in before grabbing the table and copy out after letting go of +* it. Unregistering the table waits for us and a copy that faults can +* take as long as userspace wants. +*/ +if (write) { + error = -EFAULT ; +if (!copy_from_iter_full (kbuf, count, iter)) + goto out_free_buf; + kbuf[count] ='\0' ; + } + + head =grab_header (inode); +if (IS_ERR (head)) { + error =PTR_ERR (head); + goto out_free_buf; + }/* * At this point we know that the sysctl was not unregistered @@ -624,28 +646,15 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,if (!table->proc_handler) goto out; -/* don't even try if the size is too large */ - error = -ENOMEM ; -if (count >=KMALLOC_MAX_SIZE ) - goto out; - kbuf =kvzalloc (count +1 ,GFP_KERNEL ); -if (!kbuf) - goto out; - -if (write) { - error = -EFAULT ; -if (!copy_from_iter_full (kbuf, count, iter)) - goto out_free_buf; - kbuf[count] ='\0' ; - } - error =BPF_CGROUP_RUN_PROG_SYSCTL (head, table, write, &kbuf, &count, &iocb->ki_pos);if (error) - goto out_free_buf; + goto out;/* careful: calling conventions are nasty here */ error = table->proc_handler (table, write, kbuf, &count, &iocb->ki_pos); +out: +sysctl_head_finish (head);if (error) goto out_free_buf;@@ -658,9 +667,6 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter, error = count; out_free_buf:kvfree (kbuf); -out: -sysctl_head_finish (head); -return error; }Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 34/38 proc: sysctl: look at the table under sysctl_lock when creating an inode
In reply to Christian Braunerproc_sys_make_inode() checks under sysctl_lock that @head isn't being unregistered, lets go of the lock, and then reads the mode of @table and calls ->set_ownership() with @head. That's fine as long as the caller holds @head in use, which keeps unregistering from getting past that check. The next patch has a caller that only pins the memory of @head. For that one the table, and whatever the owner of the header keeps behind it, is only there for as long as nobody unregisters the header, which happens under sysctl_lock. So read the mode and set the ownership before letting go of the lock. The ->set_ownership() callbacks only look up ids in a user namespace.
No functional changes.
--- fs/proc/proc_sysctl.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)
fs/proc/proc_sysctl.c +13 -7
@@ -500,6 +500,7 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,struct ctl_table_root *root = head->root;struct inode *inode;struct proc_inode *ei; +umode_t mode; inode =new_inode (sb);if (!inode)
@@ -509,12 +510,22 @@ static struct inode *proc_sys_make_inode(struct super_block *sb, ei =PROC_I (inode); + inode->i_uid =GLOBAL_ROOT_UID ; + inode->i_gid =GLOBAL_ROOT_GID ;spin_lock (&sysctl_lock);if (unlikely (head->unregistering)) {spin_unlock (&sysctl_lock);iput (inode);return ERR_PTR (-ENOENT ); } +/* +* The table, and whatever the owner of the header keeps behind it, +* is only there for as long as nobody unregisters the header, which +* happens under sysctl_lock. So look at them before letting go of it. +*/ + mode = table->mode; +if (root->set_ownership) + root->set_ownership (head, &inode->i_uid, &inode->i_gid); ei->sysctl = head; ei->sysctl_entry = table;hlist_add_head_rcu (&ei->sibling_inodes, &head->inodes);
@@ -522,8 +533,8 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,spin_unlock (&sysctl_lock);simple_inode_init_ts (inode); - inode->i_mode = table->mode; -if (!S_ISDIR (table->mode)) { + inode->i_mode = mode; +if (!S_ISDIR (mode)) { inode->i_mode |=S_IFREG ; inode->i_op = &proc_sys_inode_operations; inode->i_fop = &proc_sys_file_operations;
@@ -535,11 +546,6 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,make_empty_dir_inode (inode); } - inode->i_uid =GLOBAL_ROOT_UID ; - inode->i_gid =GLOBAL_ROOT_GID ; -if (root->set_ownership) - root->set_ownership (head, &inode->i_uid, &inode->i_gid); -return inode; }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 34/38] proc: sysctl: look at the table under sysctl_lock when creating an inode
Sashiko has reviewed this patch and found no issues.
Quoted 11 lines
proc_sys_make_inode()checks undersysctl_lockthat @head isn't being unregistered, lets go of the lock, and then reads the mode of @table and calls ->set_ownership() with @head. That's fine as long as the caller holds @head in use, which keeps unregistering from getting past that check. The next patch has a caller that only pins the memory of @head. For that one the table, and whatever the owner of the header keeps behind it, is only there for as long as nobody unregisters the header, which happens undersysctl_lock. So read the mode and set the ownership before letting go of the lock. The ->set_ownership() callbacks only look up ids in a user namespace.
Quoted 3 lines
No functional changes.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=34
Re: [PATCH RFC 34/38] proc: sysctl: look at the table under sysctl_lock when creating an inode
In reply to Christian BraunerQuoted 11 lines · Christian Brauner
proc_sys_make_inode()checks undersysctl_lockthat @head isn't being unregistered, lets go of the lock, and then reads the mode of @table and calls ->set_ownership() with @head. That's fine as long as the caller holds @head in use, which keeps unregistering from getting past that check. The next patch has a caller that only pins the memory of @head. For that one the table, and whatever the owner of the header keeps behind it, is only there for as long as nobody unregisters the header, which happens undersysctl_lock. So read the mode and set the ownership before letting go of the lock. The ->set_ownership() callbacks only look up ids in a user namespace.
Quoted 3 lines
No functional changes.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Looks good to me. Feel free to add:
Honza
Quoted 66 lines
--- fs/proc/proc_sysctl.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)fs/proc/proc_sysctl.c +13
-7@@ -500,6 +500,7 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,struct ctl_table_root *root = head->root;struct inode *inode;struct proc_inode *ei; +umode_t mode; inode =new_inode (sb);if (!inode)@@ -509,12 +510,22 @@ static struct inode *proc_sys_make_inode(struct super_block *sb, ei =PROC_I (inode); + inode->i_uid =GLOBAL_ROOT_UID ; + inode->i_gid =GLOBAL_ROOT_GID ;spin_lock (&sysctl_lock);if (unlikely (head->unregistering)) {spin_unlock (&sysctl_lock);iput (inode);return ERR_PTR (-ENOENT ); } +/* +* The table, and whatever the owner of the header keeps behind it, +* is only there for as long as nobody unregisters the header, which +* happens under sysctl_lock. So look at them before letting go of it. +*/ + mode = table->mode; +if (root->set_ownership) + root->set_ownership (head, &inode->i_uid, &inode->i_gid); ei->sysctl = head; ei->sysctl_entry = table;hlist_add_head_rcu (&ei->sibling_inodes, &head->inodes);@@ -522,8 +533,8 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,spin_unlock (&sysctl_lock);simple_inode_init_ts (inode); - inode->i_mode = table->mode; -if (!S_ISDIR (table->mode)) { + inode->i_mode = mode; +if (!S_ISDIR (mode)) { inode->i_mode |=S_IFREG ; inode->i_op = &proc_sys_inode_operations; inode->i_fop = &proc_sys_file_operations;@@ -535,11 +546,6 @@ static struct inode *proc_sys_make_inode(struct super_block *sb,make_empty_dir_inode (inode); } - inode->i_uid =GLOBAL_ROOT_UID ; - inode->i_gid =GLOBAL_ROOT_GID ; -if (root->set_ownership) - root->set_ownership (head, &inode->i_uid, &inode->i_gid); -return inode; }Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 35/38 proc: sysctl: don't hold the tables while readdir copies to userspace
In reply to Christian Braunerproc_sys_readdir() holds the directory's table for the whole readdir and the table of each entry while dir_emit() copies its name to userspace. Unregistering a table waits until nobody uses it anymore. So a getdents64() whose buffer faults for as long as it wants keeps unregister_sysctl_table() waiting, just like a stalled read() does. Listing /proc/sys/net/ipv6/conf/<dev>/ that way holds up the removal of the device under RTNL.
So don't take any table into use for a listing. A header counts what refers to its memory, the inodes made for it, and unregistering doesn't wait for those. readdir takes one of those references on the entry it is at instead, which keeps the header around and nothing else. Under sysctl_lock, where unregistering erases an entry, it finds the entry, copies its name and mode, follows a link to the entry it stands for and pins that. Then it looks the dentry up, creates the inode if there is none, which checks for itself under sysctl_lock that the table is still registered, and emits the entry with nothing held at all. The next entry is the first live one after that name, which is where a listing resumes between calls since "proc: sysctl: resume readdir after the last name it returned" anyway. kernfs_fop_readdir() and proc_readdir_de() work the same way, a reference on the entry and the lock only to find the next one.
While the entry is still registered its node is still in the tree and the next one is right behind it, like kernfs_dir_next_pos() continues from the node it holds. Only once it went away, and to resume, is the next entry found by name. That's one trip through sysctl_lock per entry like before, and the link entries of /proc/sys/net no longer take two more. The directory isn't
taken into use either, an entry keeps its directory registered while it is used and readdir looks at the directory under the lock. So the dots don't wait for anything, a directory that went away lists them and then fails with -ENOENT. A seek to an arbitrary position counts the entries from the start like before, one trip per entry.
--- fs/proc/proc_sysctl.c | 307 +++++++++++++++++++++++++++----------------------- 1 file changed, 166 insertions(+), 141 deletions(-)
fs/proc/proc_sysctl.c +166 -141
@@ -91,6 +91,8 @@ static void drop_sysctl_table(struct ctl_table_header *header);static int sysctl_follow_link (struct ctl_table_header **phead,const struct ctl_table **pentry);static int insert_links (struct ctl_table_header *head); +static struct ctl_table_set *lookup_header_set (struct ctl_table_root *root); +static struct ctl_dir *xlate_dir (struct ctl_table_set *set,struct ctl_dir *dir);static void put_links (struct ctl_table_header *header);static void sysctl_print_dir (struct ctl_dir *dir)
@@ -367,86 +369,97 @@ static const struct ctl_table *lookup_entry(struct ctl_table_header **phead,return entry; } -static struct ctl_node *first_usable_entry (struct rb_node *node) -{ -struct ctl_node *ctl_node; +/* +* What readdir takes along for an entry between the trips through +* sysctl_lock: the header pinned, which keeps its memory and nothing else, +* the entry, its name and mode. For a link the header and entry are the +* ones it stands for in the caller's set. +*/ +struct sysctl_dirent { +struct ctl_table_header *head; +const struct ctl_table *table; +struct ctl_node *node;/* the entry's node in the directory, NULL for a link */ +ino_t ino; +unsigned int type; +umode_t mode; +bool skip; +int len; +char name[NAME_MAX +1 ]; +}; -for (;node; node =rb_next (node)) { - ctl_node = rb_entry(node, struct ctl_node, node); -if (use_table (ctl_node->header)) -return ctl_node; - } -return NULL ; +/* A reference on the memory of @head, like an inode made for it holds. */ +static void sysctl_pin (struct ctl_table_header *head) +{ +lockdep_assert_held (&sysctl_lock); + head->count++; } -static void first_entry (struct ctl_dir *dir, -struct ctl_table_header **phead,const struct ctl_table **pentry) +static void sysctl_unpin (struct ctl_table_header *head) { -struct ctl_table_header *head =NULL ; -const struct ctl_table *entry =NULL ; -struct ctl_node *ctl_node; - -spin_lock (&sysctl_lock); - ctl_node =first_usable_entry (rb_first (&dir->root)); -spin_unlock (&sysctl_lock); -if (ctl_node) { - head = ctl_node->header; - entry = &head->ctl_table[ctl_node - head->node]; - } - *phead = head; - *pentry = entry; +lockdep_assert_held (&sysctl_lock); +if (!--head->count) +kfree_rcu (head, rcu); } -static void next_entry (struct ctl_table_header **phead,const struct ctl_table **pentry) +/* the first entry from @node on whose table isn't being unregistered */ +static struct ctl_node *first_live_entry (struct rb_node *node) { -struct ctl_table_header *head = *phead; -const struct ctl_table *entry = *pentry; -struct ctl_node *ctl_node = &head->node[entry - head->ctl_table]; - -spin_lock (&sysctl_lock); -unuse_table (head); +struct ctl_node *ctl_node; - ctl_node =first_usable_entry (rb_next (&ctl_node->node)); -spin_unlock (&sysctl_lock); - head =NULL ; -if (ctl_node) { - head = ctl_node->header; - entry = &head->ctl_table[ctl_node - head->node]; +lockdep_assert_held (&sysctl_lock); +for (; node; node =rb_next (node)) { + ctl_node = rb_entry(node, struct ctl_node, node); +if (!ctl_node->header->unregistering) +return ctl_node; } - *phead = head; - *pentry = entry; +return NULL ; }/* -* The first usable entry of @dir after @name, or the first one at all for a -* NULL @name. The entries are sorted by name, so that is where a listing -* resumes when entries in front of it came or went in between. -ENOENT if -* the directory is being unregistered. +* Fill @d with the entry of @dir behind the one it holds, letting go of that +* one, or with the first live entry after @name, or the first one at all for +* a NULL @name. While the entry @d holds is still registered its node is +* still in the tree and the next one is right behind it. Once it is gone the +* entries are found by name, they are sorted by it, so that is also where a +* listing resumes when entries in front of it came or went in between. +* 1 with the next entry pinned in @d, 0 at the end of the directory and +* -ENOENT once @dir itself is being unregistered. +* +* Everything that reads the tables happens under sysctl_lock, where an entry +* is erased when it is unregistered, since nothing holds the tables in use. */ -static int entry_after (struct ctl_dir *dir,const char *name,int namelen, -struct ctl_table_header **phead, -const struct ctl_table **pentry) +static int sysctl_dir_next (struct ctl_dir *dir,struct sysctl_dirent *d, +const char *name,int namelen) { -struct ctl_table_header *head =NULL ; -const struct ctl_table *entry =NULL ;struct rb_node *node, *after =NULL ; +struct ctl_table_header *head; +const struct ctl_table *entry;struct ctl_node *ctl_node; +bool by_name = true; +int ret =0 ;spin_lock (&sysctl_lock); +if (d->head) { +if (d->node && !d->head->unregistering) { + after =rb_next (&d->node->node); + by_name = false; + } +sysctl_unpin (d->head); + d->head =NULL ; + }if (unlikely (dir->header.unregistering)) { -spin_unlock (&sysctl_lock); -return -ENOENT ; + ret = -ENOENT ; + goto out; } -if (!name) { +if (!by_name) { +/* behind the entry @d held */ + }else if (!name) { after =rb_first (&dir->root); }else for (node = dir->root.rb_node; node;) { -const struct ctl_table *table; -struct ctl_table_header *h; - ctl_node = rb_entry(node, struct ctl_node, node); - h = ctl_node->header; - table = &h->ctl_table[ctl_node - h->node]; -if (namecmp (table->procname,strlen (table->procname), + head = ctl_node->header; + entry = &head->ctl_table[ctl_node - head->node]; +if (namecmp (entry->procname,strlen (entry->procname), name, namelen) >0 ) { after = node; node = node->rb_left;
@@ -454,15 +467,56 @@ static int entry_after(struct ctl_dir *dir, const char *name, int namelen, node = node->rb_right; } } - ctl_node =first_usable_entry (after); -spin_unlock (&sysctl_lock); -if (ctl_node) { - head = ctl_node->header; - entry = &head->ctl_table[ctl_node - head->node]; + ctl_node =first_live_entry (after); +if (!ctl_node) + goto out; + head = ctl_node->header; + entry = &head->ctl_table[ctl_node - head->node]; + + d->len =strlen (entry->procname); +if (d->len >NAME_MAX ) { + ret = -ENAMETOOLONG ; + goto out; } - *phead = head; - *pentry = entry; -return 0 ; +memcpy (d->name, entry->procname, d->len); + d->mode = entry->mode; + d->skip = false; + d->node = ctl_node; +if (unlikely (S_ISLNK (entry->mode))) { +/* the entry it stands for in the caller's set, or nothing */ +struct ctl_table_root *root = entry->data; +struct ctl_table_set *set =lookup_header_set (root); +struct ctl_dir *xdir =xlate_dir (set, head->parent); +struct ctl_table_header *xhead =NULL ; +const struct ctl_table *xentry =NULL ; + +if (!IS_ERR (xdir)) + xentry =find_entry (&xhead, xdir, d->name, d->len); +if (!xentry || xhead->unregistering) { + d->skip = true; + }else { + head = xhead; + entry = xentry; + } + d->node =NULL ; + } +sysctl_pin (head); + d->head = head; + d->table = entry; + ret =1 ; +out: +spin_unlock (&sysctl_lock); +return ret; +} + +static void sysctl_dir_done (struct sysctl_dirent *d) +{ +if (!d->head) +return ; +spin_lock (&sysctl_lock); +sysctl_unpin (d->head); +spin_unlock (&sysctl_lock); + d->head =NULL ; }/*
@@ -765,29 +819,29 @@ static void sysctl_cursor_set(struct file *file, const char *name, int len,memcpy (cursor->name, name, len); } -static bool proc_sys_fill_cache (struct file *file, -struct dir_context *ctx, -struct ctl_table_header *head, -const struct ctl_table *table) +/* +* The inode number and type of the entry @d holds, instantiating its dentry +* if there is none. Needs the header pinned, proc_sys_make_inode() checks +* for itself that the table is still registered. +*/ +static int proc_sys_fill_dirent (struct file *file,struct sysctl_dirent *d) {struct dentry *child, *dir = file->f_path.dentry;struct inode *inode;struct qstr qname; -ino_t ino =0 ; -unsigned type =DT_UNKNOWN ; - qname.name = table->procname; - qname.len =strlen (table->procname); + qname.name = d->name; + qname.len = d->len; qname.hash =full_name_hash (dir, qname.name, qname.len); child =d_lookup (dir, &qname);if (!child) { child =d_alloc_parallel (dir, &qname);if (IS_ERR (child)) -return false; +return PTR_ERR (child);if (d_in_lookup (child)) {struct dentry *res; - inode =proc_sys_make_inode (dir->d_sb, head, table); + inode =proc_sys_make_inode (dir->d_sb, d->head, d->table); res =d_splice_alias_ops (inode, child, &proc_sys_dentry_operations);d_lookup_done (child);
@@ -795,79 +849,41 @@ static bool proc_sys_fill_cache(struct file *file,dput (child);if (IS_ERR (res)) -return false; +return PTR_ERR (res); child = res; } } } inode =d_inode (child); - ino = inode->i_ino; - type = inode->i_mode >>12 ; + d->ino = inode->i_ino; + d->type = inode->i_mode >>12 ;dput (child); -return dir_emit (ctx, qname.name, qname.len, ino, type); -} - -static bool proc_sys_link_fill_cache (struct file *file, -struct dir_context *ctx, -struct ctl_table_header *head, -const struct ctl_table *table) -{ -bool ret = true; - - head =sysctl_head_grab (head); -if (IS_ERR (head)) -return false; - -/* It is not an error if we can not follow the link ignore it */ -if (sysctl_follow_link (&head, &table)) - goto out; - - ret =proc_sys_fill_cache (file, ctx, head, table); -out: -sysctl_head_finish (head); -return ret; -} - -static int scan (struct ctl_table_header *head,const struct ctl_table *table, -unsigned long *pos,struct file *file, -struct dir_context *ctx) -{ -bool res; - -if ((*pos)++ < ctx->pos) -return true; - -if (unlikely (S_ISLNK (table->mode))) - res =proc_sys_link_fill_cache (file, ctx, head, table); -else - res =proc_sys_fill_cache (file, ctx, head, table); - -if (res) { - ctx->pos = *pos; -sysctl_cursor_set (file, table->procname,strlen (table->procname), - ctx->pos); - } - -return res; +return 0 ; } +/* +* Unregistering a table waits until nobody uses it anymore and a copy to +* userspace can take as long as userspace wants. So a listing doesn't take +* any table into use. It pins the memory of the entry it is at, finds the +* next one under sysctl_lock and emits with nothing held, like +* kernfs_fop_readdir() and proc_readdir_de() do. Between calls a listing +* resumes after the name of the last entry it emitted. +*/ static int proc_sys_readdir (struct file *file,struct dir_context *ctx) { -struct ctl_table_header *head =grab_header (file_inode (file)); +struct ctl_table_header *head =PROC_I (file_inode (file))->sysctl;struct sysctl_cursor *cursor = file->private_data; -struct ctl_table_header *h =NULL ; -const struct ctl_table *entry; +struct sysctl_dirent d = {};struct ctl_dir *ctl_dir; -unsigned long pos; - -if (IS_ERR (head)) -return PTR_ERR (head); +int err; +if (!head) + head = &sysctl_table_root.default_set.dir.header; ctl_dir = container_of(head, struct ctl_dir, header);if (!dir_emit_dots (file, ctx)) - goto out; +return 0 ;/* * Resume after the last entry the previous call emitted. Only a seek
@@ -875,21 +891,30 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)* seeking to an arbitrary position always did. */ if (cursor && cursor->pos == ctx->pos) { -entry_after (ctl_dir, cursor->name, cursor->len, &h, &entry); - pos = ctx->pos; + err =sysctl_dir_next (ctl_dir, &d, cursor->name, cursor->len); }else { -first_entry (ctl_dir, &h, &entry); - pos =2 ; +loff_t pos =2 ; + + err =sysctl_dir_next (ctl_dir, &d,NULL ,0 ); +while (err >0 && pos++ < ctx->pos) + err =sysctl_dir_next (ctl_dir, &d, d.name, d.len); } +if (err <0 ) +return err; -for (; h;next_entry (&h, &entry)) { -if (!scan (h, entry, &pos, file, ctx)) { -sysctl_head_finish (h); -break ; +while (err >0 ) { +if (!d.skip) { + err =proc_sys_fill_dirent (file, &d); +if (err) +break ; +if (!dir_emit (ctx, d.name, d.len, d.ino, d.type)) +break ; } + ctx->pos++; +sysctl_cursor_set (file, d.name, d.len, ctx->pos); + err =sysctl_dir_next (ctl_dir, &d, d.name, d.len); } -out: -sysctl_head_finish (head); +sysctl_dir_done (&d);return 0 ; }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] proc: sysctl: premature EOF and lost errors in proc_sys_readdir() - [Low] proc: sysctl: unused…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] proc: sysctl: premature EOF and lost errors in proc_sys_readdir()
- [Low] proc: sysctl: unused mode field in struct sysctl_dirent
--
commit 35a95ac245f1b380541472c3423535528d1bbf7d
Author: Christian Brauner <[email protected]>
proc: sysctl: don't hold the tables while readdir copies to userspace
This commit changes proc_sys_readdir() to avoid taking sysctl tables into use during directory iteration, pinning only the header memory of the current entry while dropping sysctl_lock before instantiating dentries and copying entries to userspace.
Quoted 4 lines
fs/proc/proc_sysctl.c +0
-0
[ ... ]
Quoted 24 lines
fs/proc/proc_sysctl.c +17
-3@@ -367,86 +369,97 @@ static const struct ctl_table *lookup_entry(struct ctl_table_header **phead,return entry; } -static struct ctl_node *first_usable_entry (struct rb_node *node) -{ -struct ctl_node *ctl_node; +/* +* What readdir takes along for an entry between the trips through +* sysctl_lock: the header pinned, which keeps its memory and nothing else, +* the entry, its name and mode. For a link the header and entry are the +* ones it stands for in the caller's set. +*/ +struct sysctl_dirent { +struct ctl_table_header *head; +const struct ctl_table *table; +struct ctl_node *node;/* the entry's node in the directory, NULL for a link */ +ino_t ino; +unsigned int type; +umode_t mode; +bool skip; +int len; +char name[NAME_MAX +1 ]; +};
[ ... ]
Quoted 15 lines
fs/proc/proc_sysctl.c +6
-5@@ -454,15 +467,56 @@ static int entry_after(struct ctl_dir *dir, const char *name, int namelen, node = node->rb_right; } } - ctl_node =first_usable_entry (after); -spin_unlock (&sysctl_lock); -if (ctl_node) { - head = ctl_node->header; - entry = &head->ctl_table[ctl_node - head->node]; + ctl_node =first_live_entry (after); +if (!ctl_node) + goto out; + head = ctl_node->header; + entry = &head->ctl_table[ctl_node - head->node]; +
fs/proc/proc_sysctl.c static int entry_after(struct ctl_dir *dir, const char *name, int namelen, · line 482
Quoted 10 lines
fs/proc/proc_sysctl.c +6
-3+ d->len =strlen (entry->procname); +if (d->len >NAME_MAX ) { + ret = -ENAMETOOLONG ; + goto out; } - *phead = head; - *pentry = entry; -return 0 ; +memcpy (d->name, entry->procname, d->len); + d->mode = entry->mode;
[Severity: Low]
This isn't a bug, but is d->mode in struct sysctl_dirent needed?
sysctl_dir_next() stores entry->mode into d->mode here, but neither proc_sys_fill_dirent() nor proc_sys_readdir() ever reads d->mode, as proc_sys_make_inode() reads table->mode under sysctl_lock and proc_sys_fill_dirent() derives d->type from inode->i_mode.
[ ... ]
Quoted 31 lines
fs/proc/proc_sysctl.c +16
-9@@ -875,21 +891,30 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)* seeking to an arbitrary position always did. */ if (cursor && cursor->pos == ctx->pos) { -entry_after (ctl_dir, cursor->name, cursor->len, &h, &entry); - pos = ctx->pos; + err =sysctl_dir_next (ctl_dir, &d, cursor->name, cursor->len); }else { -first_entry (ctl_dir, &h, &entry); - pos =2 ; - } +loff_t pos =2 ; -for (; h;next_entry (&h, &entry)) { -if (!scan (h, entry, &pos, file, ctx)) { -sysctl_head_finish (h); -break ; + err =sysctl_dir_next (ctl_dir, &d,NULL ,0 ); +while (err >0 && pos++ < ctx->pos) + err =sysctl_dir_next (ctl_dir, &d, d.name, d.len); + } +if (err <0 ) +return err; + +while (err >0 ) { +if (!d.skip) { + err =proc_sys_fill_dirent (file, &d); +if (err) +break ; +if (!dir_emit (ctx, d.name, d.len, d.ino, d.type)) +break ;
fs/proc/proc_sysctl.c static int proc_sys_readdir(struct file *file, struct dir_context *ctx) · line 919
Quoted 10 lines
fs/proc/proc_sysctl.c +4
-2} + ctx->pos++; +sysctl_cursor_set (file, d.name, d.len, ctx->pos); + err =sysctl_dir_next (ctl_dir, &d, d.name, d.len); } -out: -sysctl_head_finish (head); +sysctl_dir_done (&d);return 0 ; }
[Severity: Medium]
Can a concurrent unregister_sysctl_table() or error during the loop in proc_sys_readdir() cause premature EOF or drop error codes?
Because sysctl_dir_next() only pins d->head->count via sysctl_pin() and does not increment d->head->used, a concurrent unregister_sysctl_table() does not wait in start_unregistering() and can set d->head->unregistering as soon as sysctl_dir_next() releases sysctl_lock.
When proc_sys_readdir() then calls proc_sys_fill_dirent() -> proc_sys_make_inode(), proc_sys_make_inode() checks head->unregistering under sysctl_lock:
spin_lock(&sysctl_lock);
if (unlikely(head->unregistering)) {
spin_unlock(&sysctl_lock);
iput(inode);
return ERR_PTR(-ENOENT);
}proc_sys_fill_dirent() propagates this -ENOENT back to proc_sys_readdir(), which breaks out of the while (err > 0) loop and returns 0. If no entries have been emitted yet in the current getdents64() call (for example on a subsequent getdents64() call where ctx->pos >= 2), returning 0 causes getdents64() to report EOF to userspace and omit all remaining live entries in the directory.
Similarly, if proc_sys_fill_dirent() fails with -ENOMEM, or if the in-loop sysctl_dir_next() call returns a negative error such as -ENOENT (when ctl_dir itself is unregistered after a skipped link) or -ENAMETOOLONG, proc_sys_readdir() still returns 0 instead of err.
Should proc_sys_readdir() skip entries when proc_sys_fill_dirent() returns -ENOENT and continue to the next entry, and return a negative err at the end if failed before emitting?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=35
Christian Brauner
maintainerRe: [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace
Quoted 1 line · On Tue, Nov 03, 2026 at 02:48:38PM +0000, [email protected] wrote:
- [Medium] proc: sysctl: premature EOF and lost errors in
proc_sys_readdir()
This is pre-existing. proc_sys_make_inode() refuses a table that is being unregistered since ace0c791e6c3 ("proc/sysctl: Don't grab i_lock under sysctl_lock."). The old proc_sys_readdir() stops at an entry the same way.
We can fix this by skpping the entry.
Quoted 1 line
- [Low] proc: sysctl: unused mode field in struct
sysctl_dirent
Dropped.
Jan Kara
reviewerRe: [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace
In reply to Christian BraunerQuoted 35 lines · Christian Brauner
proc_sys_readdir()holds the directory's table for the whole readdir and the table of each entry whiledir_emit()copies its name to userspace. Unregistering a table waits until nobody uses it anymore. So agetdents64()whose buffer faults for as long as it wants keepsunregister_sysctl_table()waiting, just like a stalledread()does. Listing /proc/sys/net/ipv6/conf/<dev>/ that way holds up the removal of the device under RTNL.So don't take any table into use for a listing. A header counts what refers to its memory, the inodes made for it, and unregistering doesn't wait for those. readdir takes one of those references on the entry it is at instead, which keeps the header around and nothing else. Under
sysctl_lock, where unregistering erases an entry, it finds the entry, copies its name and mode, follows a link to the entry it stands for and pins that. Then it looks the dentry up, creates the inode if there is none, which checks for itself undersysctl_lockthat the table is still registered, and emits the entry with nothing held at all. The next entry is the first live one after that name, which is where a listing resumes between calls since "proc: sysctl: resume readdir after the last name it returned" anyway.kernfs_fop_readdir()andproc_readdir_de()work the same way, a reference on the entry and the lock only to find the next one.While the entry is still registered its node is still in the tree and the next one is right behind it, like
kernfs_dir_next_pos()continues from the node it holds. Only once it went away, and to resume, is the next entry found by name. That's one trip throughsysctl_lockper entry like before, and the link entries of/proc/sys/netno longer take two more. The directory isn't
taken into use either, an entry keeps its directory registered while it is used and readdir looks at the directory under the lock. So the dots don't wait for anything, a directory that went away lists them and then fails with-ENOENT. A seek to an arbitrary position counts the entries from the start like before, one trip per entry.
Quoted 3 lines
Fixes 77b14db502cb ("[PATCH] sysctl: reimplement the sysctl proc support")Signed-off-by Christian Brauner (Amutable) <[email protected]>
So I didn't really carefully check all the details but overall this looks sane to me. Just one thing that was confusing me is: Why is sysctl_dirent separate from sysctl_cursor? Wouldn't it be more logical for sysctl_cursor itself to start what it needs to pin the entry it wants to restart from?
Honza
Quoted 420 lines
--- fs/proc/proc_sysctl.c | 307 +++++++++++++++++++++++++++----------------------- 1 file changed, 166 insertions(+), 141 deletions(-)fs/proc/proc_sysctl.c +166
-141@@ -91,6 +91,8 @@ static void drop_sysctl_table(struct ctl_table_header *header);static int sysctl_follow_link (struct ctl_table_header **phead,const struct ctl_table **pentry);static int insert_links (struct ctl_table_header *head); +static struct ctl_table_set *lookup_header_set (struct ctl_table_root *root); +static struct ctl_dir *xlate_dir (struct ctl_table_set *set,struct ctl_dir *dir);static void put_links (struct ctl_table_header *header);static void sysctl_print_dir (struct ctl_dir *dir)@@ -367,86 +369,97 @@ static const struct ctl_table *lookup_entry(struct ctl_table_header **phead,return entry; } -static struct ctl_node *first_usable_entry (struct rb_node *node) -{ -struct ctl_node *ctl_node; +/* +* What readdir takes along for an entry between the trips through +* sysctl_lock: the header pinned, which keeps its memory and nothing else, +* the entry, its name and mode. For a link the header and entry are the +* ones it stands for in the caller's set. +*/ +struct sysctl_dirent { +struct ctl_table_header *head; +const struct ctl_table *table; +struct ctl_node *node;/* the entry's node in the directory, NULL for a link */ +ino_t ino; +unsigned int type; +umode_t mode; +bool skip; +int len; +char name[NAME_MAX +1 ]; +}; -for (;node; node =rb_next (node)) { - ctl_node = rb_entry(node, struct ctl_node, node); -if (use_table (ctl_node->header)) -return ctl_node; - } -return NULL ; +/* A reference on the memory of @head, like an inode made for it holds. */ +static void sysctl_pin (struct ctl_table_header *head) +{ +lockdep_assert_held (&sysctl_lock); + head->count++; } -static void first_entry (struct ctl_dir *dir, -struct ctl_table_header **phead,const struct ctl_table **pentry) +static void sysctl_unpin (struct ctl_table_header *head) { -struct ctl_table_header *head =NULL ; -const struct ctl_table *entry =NULL ; -struct ctl_node *ctl_node; - -spin_lock (&sysctl_lock); - ctl_node =first_usable_entry (rb_first (&dir->root)); -spin_unlock (&sysctl_lock); -if (ctl_node) { - head = ctl_node->header; - entry = &head->ctl_table[ctl_node - head->node]; - } - *phead = head; - *pentry = entry; +lockdep_assert_held (&sysctl_lock); +if (!--head->count) +kfree_rcu (head, rcu); } -static void next_entry (struct ctl_table_header **phead,const struct ctl_table **pentry) +/* the first entry from @node on whose table isn't being unregistered */ +static struct ctl_node *first_live_entry (struct rb_node *node) { -struct ctl_table_header *head = *phead; -const struct ctl_table *entry = *pentry; -struct ctl_node *ctl_node = &head->node[entry - head->ctl_table]; - -spin_lock (&sysctl_lock); -unuse_table (head); +struct ctl_node *ctl_node; - ctl_node =first_usable_entry (rb_next (&ctl_node->node)); -spin_unlock (&sysctl_lock); - head =NULL ; -if (ctl_node) { - head = ctl_node->header; - entry = &head->ctl_table[ctl_node - head->node]; +lockdep_assert_held (&sysctl_lock); +for (; node; node =rb_next (node)) { + ctl_node = rb_entry(node, struct ctl_node, node); +if (!ctl_node->header->unregistering) +return ctl_node; } - *phead = head; - *pentry = entry; +return NULL ; }/* -* The first usable entry of @dir after @name, or the first one at all for a -* NULL @name. The entries are sorted by name, so that is where a listing -* resumes when entries in front of it came or went in between. -ENOENT if -* the directory is being unregistered. +* Fill @d with the entry of @dir behind the one it holds, letting go of that +* one, or with the first live entry after @name, or the first one at all for +* a NULL @name. While the entry @d holds is still registered its node is +* still in the tree and the next one is right behind it. Once it is gone the +* entries are found by name, they are sorted by it, so that is also where a +* listing resumes when entries in front of it came or went in between. +* 1 with the next entry pinned in @d, 0 at the end of the directory and +* -ENOENT once @dir itself is being unregistered. +* +* Everything that reads the tables happens under sysctl_lock, where an entry +* is erased when it is unregistered, since nothing holds the tables in use. */ -static int entry_after (struct ctl_dir *dir,const char *name,int namelen, -struct ctl_table_header **phead, -const struct ctl_table **pentry) +static int sysctl_dir_next (struct ctl_dir *dir,struct sysctl_dirent *d, +const char *name,int namelen) { -struct ctl_table_header *head =NULL ; -const struct ctl_table *entry =NULL ;struct rb_node *node, *after =NULL ; +struct ctl_table_header *head; +const struct ctl_table *entry;struct ctl_node *ctl_node; +bool by_name = true; +int ret =0 ;spin_lock (&sysctl_lock); +if (d->head) { +if (d->node && !d->head->unregistering) { + after =rb_next (&d->node->node); + by_name = false; + } +sysctl_unpin (d->head); + d->head =NULL ; + }if (unlikely (dir->header.unregistering)) { -spin_unlock (&sysctl_lock); -return -ENOENT ; + ret = -ENOENT ; + goto out; } -if (!name) { +if (!by_name) { +/* behind the entry @d held */ + }else if (!name) { after =rb_first (&dir->root); }else for (node = dir->root.rb_node; node;) { -const struct ctl_table *table; -struct ctl_table_header *h; - ctl_node = rb_entry(node, struct ctl_node, node); - h = ctl_node->header; - table = &h->ctl_table[ctl_node - h->node]; -if (namecmp (table->procname,strlen (table->procname), + head = ctl_node->header; + entry = &head->ctl_table[ctl_node - head->node]; +if (namecmp (entry->procname,strlen (entry->procname), name, namelen) >0 ) { after = node; node = node->rb_left;@@ -454,15 +467,56 @@ static int entry_after(struct ctl_dir *dir, const char *name, int namelen, node = node->rb_right; } } - ctl_node =first_usable_entry (after); -spin_unlock (&sysctl_lock); -if (ctl_node) { - head = ctl_node->header; - entry = &head->ctl_table[ctl_node - head->node]; + ctl_node =first_live_entry (after); +if (!ctl_node) + goto out; + head = ctl_node->header; + entry = &head->ctl_table[ctl_node - head->node]; + + d->len =strlen (entry->procname); +if (d->len >NAME_MAX ) { + ret = -ENAMETOOLONG ; + goto out; } - *phead = head; - *pentry = entry; -return 0 ; +memcpy (d->name, entry->procname, d->len); + d->mode = entry->mode; + d->skip = false; + d->node = ctl_node; +if (unlikely (S_ISLNK (entry->mode))) { +/* the entry it stands for in the caller's set, or nothing */ +struct ctl_table_root *root = entry->data; +struct ctl_table_set *set =lookup_header_set (root); +struct ctl_dir *xdir =xlate_dir (set, head->parent); +struct ctl_table_header *xhead =NULL ; +const struct ctl_table *xentry =NULL ; + +if (!IS_ERR (xdir)) + xentry =find_entry (&xhead, xdir, d->name, d->len); +if (!xentry || xhead->unregistering) { + d->skip = true; + }else { + head = xhead; + entry = xentry; + } + d->node =NULL ; + } +sysctl_pin (head); + d->head = head; + d->table = entry; + ret =1 ; +out: +spin_unlock (&sysctl_lock); +return ret; +} + +static void sysctl_dir_done (struct sysctl_dirent *d) +{ +if (!d->head) +return ; +spin_lock (&sysctl_lock); +sysctl_unpin (d->head); +spin_unlock (&sysctl_lock); + d->head =NULL ; }/* @@ -765,29 +819,29 @@ static void sysctl_cursor_set(struct file *file, const char *name, int len,memcpy (cursor->name, name, len); } -static bool proc_sys_fill_cache (struct file *file, -struct dir_context *ctx, -struct ctl_table_header *head, -const struct ctl_table *table) +/* +* The inode number and type of the entry @d holds, instantiating its dentry +* if there is none. Needs the header pinned, proc_sys_make_inode() checks +* for itself that the table is still registered. +*/ +static int proc_sys_fill_dirent (struct file *file,struct sysctl_dirent *d) {struct dentry *child, *dir = file->f_path.dentry;struct inode *inode;struct qstr qname; -ino_t ino =0 ; -unsigned type =DT_UNKNOWN ; - qname.name = table->procname; - qname.len =strlen (table->procname); + qname.name = d->name; + qname.len = d->len; qname.hash =full_name_hash (dir, qname.name, qname.len); child =d_lookup (dir, &qname);if (!child) { child =d_alloc_parallel (dir, &qname);if (IS_ERR (child)) -return false; +return PTR_ERR (child);if (d_in_lookup (child)) {struct dentry *res; - inode =proc_sys_make_inode (dir->d_sb, head, table); + inode =proc_sys_make_inode (dir->d_sb, d->head, d->table); res =d_splice_alias_ops (inode, child, &proc_sys_dentry_operations);d_lookup_done (child);@@ -795,79 +849,41 @@ static bool proc_sys_fill_cache(struct file *file,dput (child);if (IS_ERR (res)) -return false; +return PTR_ERR (res); child = res; } } } inode =d_inode (child); - ino = inode->i_ino; - type = inode->i_mode >>12 ; + d->ino = inode->i_ino; + d->type = inode->i_mode >>12 ;dput (child); -return dir_emit (ctx, qname.name, qname.len, ino, type); -} - -static bool proc_sys_link_fill_cache (struct file *file, -struct dir_context *ctx, -struct ctl_table_header *head, -const struct ctl_table *table) -{ -bool ret = true; - - head =sysctl_head_grab (head); -if (IS_ERR (head)) -return false; - -/* It is not an error if we can not follow the link ignore it */ -if (sysctl_follow_link (&head, &table)) - goto out; - - ret =proc_sys_fill_cache (file, ctx, head, table); -out: -sysctl_head_finish (head); -return ret; -} - -static int scan (struct ctl_table_header *head,const struct ctl_table *table, -unsigned long *pos,struct file *file, -struct dir_context *ctx) -{ -bool res; - -if ((*pos)++ < ctx->pos) -return true; - -if (unlikely (S_ISLNK (table->mode))) - res =proc_sys_link_fill_cache (file, ctx, head, table); -else - res =proc_sys_fill_cache (file, ctx, head, table); - -if (res) { - ctx->pos = *pos; -sysctl_cursor_set (file, table->procname,strlen (table->procname), - ctx->pos); - } - -return res; +return 0 ; } +/* +* Unregistering a table waits until nobody uses it anymore and a copy to +* userspace can take as long as userspace wants. So a listing doesn't take +* any table into use. It pins the memory of the entry it is at, finds the +* next one under sysctl_lock and emits with nothing held, like +* kernfs_fop_readdir() and proc_readdir_de() do. Between calls a listing +* resumes after the name of the last entry it emitted. +*/ static int proc_sys_readdir (struct file *file,struct dir_context *ctx) { -struct ctl_table_header *head =grab_header (file_inode (file)); +struct ctl_table_header *head =PROC_I (file_inode (file))->sysctl;struct sysctl_cursor *cursor = file->private_data; -struct ctl_table_header *h =NULL ; -const struct ctl_table *entry; +struct sysctl_dirent d = {};struct ctl_dir *ctl_dir; -unsigned long pos; - -if (IS_ERR (head)) -return PTR_ERR (head); +int err; +if (!head) + head = &sysctl_table_root.default_set.dir.header; ctl_dir = container_of(head, struct ctl_dir, header);if (!dir_emit_dots (file, ctx)) - goto out; +return 0 ;/* * Resume after the last entry the previous call emitted. Only a seek @@ -875,21 +891,30 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)* seeking to an arbitrary position always did. */ if (cursor && cursor->pos == ctx->pos) { -entry_after (ctl_dir, cursor->name, cursor->len, &h, &entry); - pos = ctx->pos; + err =sysctl_dir_next (ctl_dir, &d, cursor->name, cursor->len); }else { -first_entry (ctl_dir, &h, &entry); - pos =2 ; +loff_t pos =2 ; + + err =sysctl_dir_next (ctl_dir, &d,NULL ,0 ); +while (err >0 && pos++ < ctx->pos) + err =sysctl_dir_next (ctl_dir, &d, d.name, d.len); } +if (err <0 ) +return err; -for (; h;next_entry (&h, &entry)) { -if (!scan (h, entry, &pos, file, ctx)) { -sysctl_head_finish (h); -break ; +while (err >0 ) { +if (!d.skip) { + err =proc_sys_fill_dirent (file, &d); +if (err) +break ; +if (!dir_emit (ctx, d.name, d.len, d.ino, d.type)) +break ; } + ctx->pos++; +sysctl_cursor_set (file, d.name, d.len, ctx->pos); + err =sysctl_dir_next (ctl_dir, &d, d.name, d.len); } -out: -sysctl_head_finish (head); +sysctl_dir_done (&d);return 0 ; }Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRe: [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace
Quoted 4 lines · Jan Kara
So I didn't really carefully check all the details but overall this looks sane to me. Just one thing that was confusing me is: Why is
sysctl_direntseparate fromsysctl_cursor? Wouldn't it be more logical forsysctl_cursoritself to start what it needs to pin the entry it wants to restart from?
Because they have different lifetimes. The cursor lives in the file between two getdents() calls. So nothing in the cursor may be something that unregistering the sysctl table waits for. So it only remembers the name of the last entry and the position behind it.
sysctl_dirent is the state of one call. It does hold a grabbed header which we drop before the call returns.
If the cursor were to hold the header too then every open directory would keep the table of its last entry pinned until the next getdents() or close() which is the indefinite delay this patch kills.
Jan Kara
reviewerRe: [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace
Quoted 11 lines · Christian Brauner
Quoted 4 lines · Jan Kara
So I didn't really carefully check all the details but overall this looks sane to me. Just one thing that was confusing me is: Why is
sysctl_direntseparate fromsysctl_cursor? Wouldn't it be more logical forsysctl_cursoritself to start what it needs to pin the entry it wants to restart from?Because they have different lifetimes. The cursor lives in the file between two
getdents()calls. So nothing in the cursor may be something that unregistering the sysctl table waits for. So it only remembers the name of the last entry and the position behind it.
Quoted 6 lines
sysctl_direntis the state of one call. It does hold a grabbed header which we drop before the call returns.If the cursor were to hold the header too then every open directory would keep the table of its last entry pinned until the next
getdents()orclose()which is the indefinite delay this patch kills.
Right. Thanks for explanation!
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 36/38 selftests/proc: check that faulting sysctl accesses don't hold up removal
In reply to Christian BraunerRead, write and list /proc/sys/net/ipv6/conf/<dev>/ of a tun device with a buffer whose first page is backed by userfaultfd and remove the device while the task sleeps in its copy from or to userspace:
- unregistering the table doesn't wait for a faulting read(), write() or getdents64()
- the read still gets the value that was produced before the removal and the next read fails with -ENOENT
- the write is copied in after the removal and fails with -ENOENT
- getdents64() still lists what it looked up before the removal and fails with -ENOENT afterwards
The tables are unregistered under RTNL, so a regression stalls each test for five seconds before it fails.
--- tools/testing/selftests/proc/.gitignore | 1 + tools/testing/selftests/proc/Makefile | 1 + tools/testing/selftests/proc/config | 3 + .../selftests/proc/proc-remove-slow-sysctl.c | 213 +++++++++++++++++++++ 4 files changed, 218 insertions(+)
tools/testing/selftests/proc/.gitignore +1 -0
@@ -21,6 +21,7 @@ /proc-uptime-001 /proc-uptime-002 /proc-pidns +/proc-remove-slow-sysctl /read /self /setns-dcache
tools/testing/selftests/proc/Makefile +1 -0
@@ -32,5 +32,6 @@ TEST_GEN_PROGS += proc-multiple-procfs TEST_GEN_PROGS += proc-fsconfig-hidepid TEST_GEN_PROGS += proc-sysctl-readdir-churn TEST_GEN_PROGS += proc-pidns +TEST_GEN_PROGS += proc-remove-slow-sysctl include ../lib.mk
tools/testing/selftests/proc/config +3 -0
@@ -1 +1,4 @@ CONFIG_PROC_FS=y +CONFIG_IPV6=y +CONFIG_TUN=y +CONFIG_USERFAULTFD=y
tools/testing/selftests/proc/proc-remove-slow-sysctl.c new file +213 -0
213 lines not on this page · raw message
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 36/38] selftests/proc: check that faulting sysctl accesses don't hold up removal
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] selftests/proc: getdents test faults in dir_emit_dots() before entry lookup --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] selftests/proc: getdents test faults in dir_emit_dots() before entry lookup --
commit fc0ab3f791b6ab04b43d13e015f97882092d54fc
Author: Christian Brauner <[email protected]>
selftests/proc: check that faulting sysctl accesses don't hold up removal
This commit adds a proc selftest using userfaultfd and a tun device in a new network namespace to verify that page faults during read(), write(), and getdents64() on /proc/sys do not block sysctl table unregistration.
Quoted 6 lines
tools/testing/selftests/proc/proc-remove-slow-sysctl.c new file +0
-0@@ -0,0 +1,213 @@
[ ... ]
tools/testing/selftests/proc/proc-remove-slow-sysctl.c line 208
Quoted 13 lines
tools/testing/selftests/proc/proc-remove-slow-sysctl.c new file +13
-0+TEST_F(proc_sys_slow, getdents) +{ +struct worker w = { + .op =SYSCTL_GETDENTS , + .len =BUF_PAGES * self->psz, + }; + + w.fd =open (CONF_PATH ,O_RDONLY |O_DIRECTORY |O_CLOEXEC ); +ASSERT_GE (w.fd,0 ); +stall_and_remove (_metadata, self, &w); + +/* What was looked up before the removal still arrives, then nothing. */ +EXPECT_GT (w.ret,0 );
[Severity: Medium]
Does TEST_F(proc_sys_slow, getdents) in tools/testing/selftests/proc/proc-remove-slow-sysctl.c actually fault on a looked-up sysctl entry, or does it fault inside dir_emit_dots() before any entry is looked up?
In FIXTURE_SETUP(proc_sys_slow), only the first page of self->buf is registered with userfaultfd. When TEST_F(proc_sys_slow, getdents) opens CONF_PATH, w.fd starts at file position 0 and is passed directly to stall_and_remove() -> work_thread() -> getdents64().
In fs/proc/proc_sysctl.c:proc_sys_readdir(), dir_emit_dots() runs before sysctl_dir_next():
fs/proc/proc_sysctl.c:proc_sys_readdir() {
...
if (!dir_emit_dots(file, ctx))
return 0;
if (cursor && cursor->pos == ctx->pos) {
err = sysctl_dir_next(ctl_dir, &d, cursor->name, cursor->len);
} else {
loff_t pos = 2;
err = sysctl_dir_next(ctl_dir, &d, NULL, 0);
...
}Because ctx->pos is 0, dir_emit_dots() writes "." at the start of self->buf and triggers the single userfaultfd page fault before sysctl_dir_next() is called and while d.head is still NULL.
While work_thread() is sleeping in dir_emit_dots(), stall_and_remove() closes self->tun (which unregisters CONF_PATH and marks ctl_dir->header as unregistering) and resolves the fault on the first page with UFFDIO_ZEROPAGE. When work_thread() resumes, dir_emit_dots() finishes emitting "." and "..", sysctl_dir_next() immediately returns -ENOENT, and getdents64() returns the 48 bytes from the dot entries without ever entering the entry-emission loop.
Should TEST_F(proc_sys_slow, getdents) advance w.fd past "." and ".." first (for example with lseek(w.fd, 2, SEEK_SET)) before calling stall_and_remove() so the fault occurs in dir_emit() after sysctl_dir_next() and proc_sys_fill_dirent() have looked up an entry?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=36
Christian Brauner
maintainerRe: [PATCH RFC 36/38] selftests/proc: check that faulting sysctl accesses don't hold up removal
Quoted 1 line · On Tue, Nov 03, 2026 at 02:48:40PM +0000, [email protected] wrote:
- [Medium] selftests/proc: getdents test faults in
dir_emit_dots()before entry lookup
Fixed.
Christian Brauner
maintainerRFC PATCH 37/38 proc: don't pin a symlink's entry while readlink() copies it out
In reply to Christian Braunerproc_get_link() pins the entry with use_pde() and the delayed call unpins it. vfs_readlink() copies the link to userspace in between. So a page fault in that copy holds up remove_proc_entry() just like the copies of reads and writes do, and the callers hold locks while they wait. /proc/asound/<id> is removed with snd_card_mutex and info_mutex held when a card changes its id.
The pin doesn't keep anything alive. proc_symlink() copies the target and pde_free() frees it with the entry, and the inode holds a reference to the entry. What the pin does is keep a removed symlink from being followed. So check ->in_use instead and drop the delayed call. That also takes an atomic pair off every traversal of /proc/net and /proc/mounts.
--- fs/proc/inode.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
fs/proc/inode.c +7 -7
@@ -629,19 +629,19 @@ static const struct file_operations proc_iter_file_ops_compat = { };#endif -static void proc_put_link (void *p) -{ -unuse_pde (p); -} -static const char *proc_get_link (struct dentry *dentry,struct inode *inode,struct delayed_call *done) {struct proc_dir_entry *pde =PDE (inode); -if (!use_pde (pde)) + +/* +* No pin. The link lives with the entry, which the inode holds, and a +* reader could fault while vfs_readlink() copies it out. Just don't +* follow a removed one. +*/ +if (unlikely (atomic_read (&pde->in_use) <0 ))return ERR_PTR (-EINVAL ); -set_delayed_call (done, proc_put_link, pde);return pde->data; }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out
Sashiko has reviewed this patch and found no issues.
Quoted 14 lines
proc_get_link()pins the entry withuse_pde()and the delayed call unpins it.vfs_readlink()copies the link to userspace in between. So a page fault in that copy holds upremove_proc_entry()just like the copies of reads and writes do, and the callers hold locks while they wait. /proc/asound/<id> is removed withsnd_card_mutexandinfo_mutexheld when a card changes its id.The pin doesn't keep anything alive.
proc_symlink()copies the target andpde_free()frees it with the entry, and the inode holds a reference to the entry. What the pin does is keep a removed symlink from being followed. So check ->in_use instead and drop the delayed call. That also takes an atomic pair off every traversal of/proc/netand/proc/mounts.
Quoted 3 lines
Fixes 7e0e953bb0cf ("procfs: fix race between symlink removals and traversals")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=37
Re: [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out
In reply to Christian BraunerQuoted 14 lines · Christian Brauner
proc_get_link()pins the entry withuse_pde()and the delayed call unpins it.vfs_readlink()copies the link to userspace in between. So a page fault in that copy holds upremove_proc_entry()just like the copies of reads and writes do, and the callers hold locks while they wait. /proc/asound/<id> is removed withsnd_card_mutexandinfo_mutexheld when a card changes its id.The pin doesn't keep anything alive.
proc_symlink()copies the target andpde_free()frees it with the entry, and the inode holds a reference to the entry. What the pin does is keep a removed symlink from being followed. So check ->in_use instead and drop the delayed call. That also takes an atomic pair off every traversal of/proc/netand/proc/mounts.
Quoted 3 lines
Fixes 7e0e953bb0cf ("procfs: fix race between symlink removals and traversals")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Looks good. Feel free to add:
Honza
Quoted 39 lines
--- fs/proc/inode.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)fs/proc/inode.c +7
-7@@ -629,19 +629,19 @@ static const struct file_operations proc_iter_file_ops_compat = { };#endif -static void proc_put_link (void *p) -{ -unuse_pde (p); -} -static const char *proc_get_link (struct dentry *dentry,struct inode *inode,struct delayed_call *done) {struct proc_dir_entry *pde =PDE (inode); -if (!use_pde (pde)) + +/* +* No pin. The link lives with the entry, which the inode holds, and a +* reader could fault while vfs_readlink() copies it out. Just don't +* follow a removed one. +*/ +if (unlikely (atomic_read (&pde->in_use) <0 ))return ERR_PTR (-EINVAL ); -set_delayed_call (done, proc_put_link, pde);return pde->data; }Signature · 2 lines
-- 2.53.0
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Christian Brauner
maintainerRFC PATCH 38/38 selftests/proc: check that a faulting readlink() doesn't hold up removal
In reply to Christian BraunerAdd the symlink case:
- readlink() /proc/asound/<id> of a dummy card into a buffer backed by userfaultfd
- change the card's id through sysfs while the reader faults, which removes the symlink with snd_card_mutex and info_mutex held
- expect that to finish without waiting for the reader
Skipped without snd-dummy.
--- tools/testing/selftests/proc/.gitignore | 2 + tools/testing/selftests/proc/Makefile | 2 + tools/testing/selftests/proc/config | 3 + .../selftests/proc/proc-remove-slow-readlink.c | 191 +++++++++++++++++++++ 4 files changed, 198 insertions(+)
tools/testing/selftests/proc/.gitignore +2 -0
@@ -22,6 +22,8 @@ /proc-uptime-002 /proc-pidns /proc-remove-slow-sysctl +/proc-remove-slow-reader +/proc-remove-slow-readlink /read /self /setns-dcache
tools/testing/selftests/proc/Makefile +2 -0
@@ -33,5 +33,7 @@ TEST_GEN_PROGS += proc-fsconfig-hidepid TEST_GEN_PROGS += proc-sysctl-readdir-churn TEST_GEN_PROGS += proc-pidns TEST_GEN_PROGS += proc-remove-slow-sysctl +TEST_GEN_PROGS += proc-remove-slow-reader +TEST_GEN_PROGS += proc-remove-slow-readlink include ../lib.mk
tools/testing/selftests/proc/config +3 -0
@@ -2,3 +2,6 @@ CONFIG_PROC_FS=y CONFIG_IPV6=y CONFIG_TUN=y CONFIG_USERFAULTFD=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_DUMMY=m
tools/testing/selftests/proc/proc-remove-slow-readlink.c new file +191 -0
191 lines not on this page · raw message
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 38/38] selftests/proc: check that a faulting readlink() doesn't hold up removal
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] selftests/proc: Makefile adds nonexistent proc-remove-slow-reader target --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] selftests/proc: Makefile adds nonexistent proc-remove-slow-reader target --
commit ac0073c08526e8690efa58576485d79bbd906522
Author: Christian Brauner <[email protected]>
selftests/proc: check that a faulting readlink() doesn't hold up removal
This commit adds a selftest verifying that a readlink() call faulting via userfaultfd on /proc/asound/<id> does not block removal of the /proc symlink when renaming a dummy sound card.
Quoted 18 lines
tools/testing/selftests/proc/.gitignore +2
-0@@ -22,6 +22,8 @@ /proc-uptime-002 /proc-pidns /proc-remove-slow-sysctl +/proc-remove-slow-reader +/proc-remove-slow-readlink /read /self /setns-dcachetools/testing/selftests/proc/Makefile +0
-0
tools/testing/selftests/proc/Makefile TEST_GEN_PROGS += proc-fsconfig-hidepid · line 36
Quoted 4 lines
tools/testing/selftests/proc/Makefile +1
-0@@ -33,5 +33,7 @@ TEST_GEN_PROGS += proc-fsconfig-hidepid TEST_GEN_PROGS += proc-sysctl-readdir-churn TEST_GEN_PROGS += proc-pidns TEST_GEN_PROGS += proc-remove-slow-sysctl +TEST_GEN_PROGS += proc-remove-slow-reader
[Severity: Medium]
Was proc-remove-slow-reader added to TEST_GEN_PROGS in tools/testing/selftests/proc/Makefile (and to tools/testing/selftests/proc/.gitignore) by mistake?
This patch adds tools/testing/selftests/proc/proc-remove-slow-readlink.c, but tools/testing/selftests/proc/proc-remove-slow-reader.c does not exist in the tree. Because the default all target in tools/testing/selftests/lib.mk depends on $(TEST_GEN_PROGS), building the proc selftests fails with no rule to make target proc-remove-slow-reader.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=38
Christian Brauner
maintainerRe: [PATCH RFC 38/38] selftests/proc: check that a faulting readlink() doesn't hold up removal
Quoted 1 line · On Tue, Nov 03, 2026 at 02:48:45PM +0000, [email protected] wrote:
- [Medium] selftests/proc:
Makefileadds nonexistent proc-remove-slow-reader target
Fixed.
Christian Brauner
maintainercover proc: let procfs own the data path
In reply to Christian Braunerremove_proc_entry() waits until every call into the entry has returned. For a seq_file entry that's all of seq_read_iter() including the copy to userspace. For everything else it's ->proc_read(), ->proc_write() and ->proc_ioctl(), which copy to and from userspace themselves. A reader or writer can make a page fault in that copy take as long as it wants, e.g. with userfaultfd or by punching holes into a memfd over and over. And callers of remove_proc_entry() like to hold locks while they wait. addrconf holds RTNL when it removes /proc/net/dev_snmp6/<dev> and xt_hashlimit holds hashlimit_mutex which is shared by all network namespaces.
An earlier AI generated version bounced the text through a second buffer in seq_file so the copy to userspace could happen with the entry unpinned. That second buffering is really nasty and complicates everything unnecessarily. So after chatting with Jann a bit I decided to do what kernfs does instead: the entry's code only ever runs with the entry pinned and never touches userspace memory. This series is the procfs side of that. The entries are converted in four series on top of it, one per area of the tree, which don't depend on each other, and a last one drops the __user methods.
seq_file reads: procfs owns the seq_file and only calls into the entry with it pinned. An entry declares its seq_operations, or just a show function, and the size of its private state in struct proc_ops. procfs opens and frees the seq_file and reads and seeks through seq_file directly. ->proc_open() and ->proc_release() only deal with whatever the entry needs on top of that. proc_create_seq() and friends already keep the seq_operations in the entry so they're switched over in one go here. That covers most of /proc/net. The open coded single_open() and seq_open() users that don't do anything else are converted with a coccinelle script, also here. A nice side effect is that opening one of these entries no longer allocates a pde_opener, they have no ->proc_release() of their own anymore.
Writes: procfs copies in what is written before it pins the entry and hands the entry a NUL-terminated kernel copy through ->proc_write_buf(). The copy is capped at PAGE_SIZE - 1 bytes unless the entry asks for more in ->proc_write_size, which is what proc_simple_write() always did. Entries that take binary data in pieces, the PCI config space, ALSA's data entries, cio_ignore and the rtas flash files, ask for PROC_ENTRY_SHORT_WRITES and their writes come up short. Since procfs copies in the whole write first, a write that is too large or whose buffer can't be read fails before the entry's own checks now, permission checks included.
Reads that don't go through seq_file: an entry fills a kernel buffer in ->proc_read_buf(), procfs copies that to userspace after unpinning the entry and only moves the position if that copy worked, like kernfs_file_read_iter(). The buffer is a page unless the entry asks for more in ->proc_read_size, and like with kernfs a larger read comes up short. Entries whose readers want all of a large file in one read, /proc/vmcore, /proc/config.gz and ALSA's data entries, ask for PROC_ENTRY_FULL_READS and procfs fills their reads in pieces. These entries are read through ->read_iter() now, so sendfile() and splice() work for them.
ioctls: the command number doesn't reliably say what an ioctl copies. The PCI ioctls aren't encoded at all, i8k's encode the wrong sizes and esas2r uses a range of plain numbers. So an entry lists its commands in ->proc_ioctl_args with what goes in and what comes back out, like DRM's ioctl tables. procfs copies the argument in before it pins the entry, calls ->proc_ioctl_buf() with that copy and copies it back out after unpinning the entry. A command that isn't in the table fails with -ENOTTY. 32-bit tasks go through the same table, so the compat hooks go away.
The limits of reads and writes are remembered in the entry when it is registered, the proc_ops go away with the entry and procfs shouldn't have to pin it just to look up a number. An entry that declares a seq_file and a read or seek method of its own, or one of the new methods next to the old one it replaces, fails to register.
Entries that are never removed, kcore, kmsg, the kpage files and /proc/profile, keep reading straight into userspace. procfs never pins those, so nothing can wait for them. The last series enforces that.
What this doesn't cover is mmap(). procfs pins the entry around ->proc_mmap() only and a page fault into the mapping runs the entry's vm_ops unpinned. kernfs wraps those. That's for another day.
On a KASAN and lockdep kernel the reader selftest fails without the series and passes with it. The other proc selftests behave the same with and without the series and so do the entries converted here that I could get at in a VM.
This is on top of "proc: fix a few old bugs in /proc handlers".
---
Christian Brauner (16):
proc: drop the compat file operations
proc: add pde_pin() and pde_unpin()
proc: make room for more entry flags
proc: let procfs own the seq_file of seq_file entries
proc: let procfs own the seq_file of proc_create_seq() and friends
proc: let procfs own the seq_file of DEFINE_PROC_SHOW_ATTRIBUTE() files
treewide: let procfs own the seq_file of simple proc entries selftests/proc: check that a faulting reader doesn't hold up removal
seq_file: add seq_preallocate()
proc: let procfs own the seq_file of /proc/stat
proc: let procfs copy in writes before pinning the entry
proc: copy in writes to the network namespace entries before pinning
proc: let procfs copy out reads that don't go through seq_file fs/proc/vmcore: let procfs copy out reads
proc: let procfs copy ioctl arguments in and out
Documentation: seq_file: describe how a /proc file uses seq_file
Documentation/filesystems/seq_file.rst | 42 +- arch/alpha/kernel/srm_env.c | 10 +- arch/arm/mm/alignment.c | 10 +- arch/powerpc/kernel/rtas-proc.c | 50 +-- arch/powerpc/platforms/pseries/lpar.c | 20 +- arch/powerpc/platforms/pseries/lparcfg.c | 10 +- arch/sh/mm/alignment.c | 10 +- arch/sparc/kernel/led.c | 10 +- arch/um/kernel/exitcode.c | 10 +- drivers/acpi/proc.c | 12 +- drivers/hwmon/dell-smm-hwmon.c | 10 +- drivers/input/input.c | 24 +- drivers/macintosh/via-pmu.c | 10 +- drivers/net/wireless/intel/ipw2x00/libipw_module.c | 10 +- drivers/platform/x86/lenovo/thinkpad_acpi.c | 10 +- drivers/platform/x86/toshiba_acpi.c | 40 +- drivers/pnp/pnpbios/proc.c | 10 +- drivers/s390/block/dasd_proc.c | 10 +- drivers/s390/cio/blacklist.c | 13 +- drivers/scsi/scsi_devinfo.c | 10 +- drivers/scsi/scsi_proc.c | 21 +- drivers/usb/gadget/function/rndis.c | 10 +- drivers/video/fbdev/via/viafbdev.c | 70 +--- fs/jfs/jfs_debug.c | 10 +- fs/ntfs3/super.c | 20 +- fs/proc/cpuinfo.c | 10 +- fs/proc/generic.c | 126 +++--- fs/proc/inode.c | 457 ++++++++++++++++----- fs/proc/internal.h | 54 ++- fs/proc/proc_net.c | 44 +- fs/proc/stat.c | 6 +- fs/proc/vmcore.c | 14 +- fs/seq_file.c | 36 +- fs/smb/client/cifs_debug.c | 79 +--- fs/smb/client/dfs_cache.c | 10 +- include/linux/proc_fs.h | 91 +++- include/linux/seq_file.h | 11 +- kernel/irq/proc.c | 30 +- kernel/latencytop.c | 10 +- lib/dynamic_debug.c | 6 +- mm/slab_common.c | 10 +- net/core/pktgen.c | 30 +- net/sunrpc/stats.c | 10 +- .../selftests/proc/proc-remove-slow-reader.c | 133 ++++++ 44 files changed, 837 insertions(+), 792 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261101-work-procfs-core-8fa4a4f412cf
prerequisite-change-id: 20261101-work-procfs-bugfixes-054d00028491:v1
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: 540aa9ea28adcbc54d8264f66314e29e3b8c9d49
prerequisite-patch-id: 2f7139d1c972c4439dccca610caa5f1288987a3d
prerequisite-patch-id: b0031a7c4855b7ec38259c59f30f65101a8fc4b5
prerequisite-patch-id: 0b7f226abf2b9ff41873a1aa55cf97747647d56b
prerequisite-patch-id: f63e345f897619af54813958046617c655fb7daf
prerequisite-patch-id: c0e21e762a2960e3324c5ab04634a7035e515fa6
prerequisite-patch-id: fd59c71eadd8f39c58b3baea93567bc093bc9b0e
prerequisite-patch-id: 5d52cc8a5c73e4c5ba16b8e24f51408c17583bf9
prerequisite-patch-id: 1f9179284e2f3314d4b46ac5e989089a21c7576c
prerequisite-patch-id: 10397bbf696bee16dc71eb79fff6c5e73842bcbc
prerequisite-patch-id: 9b44279dcf08912238d2f6438a3328a778a79a0c
prerequisite-patch-id: 16854b621a3a71dfc365aaf7abaa7f65adfe9f7e
prerequisite-patch-id: ca42eea21fae1fd1cd3518ab057c74b61ffd68fe
prerequisite-patch-id: 23255dc2be76b0479c083dbd4c897bfbb533aed5
prerequisite-patch-id: 82129c687ebf0a41b369893b92f2c038420ab801
prerequisite-patch-id: 56ad57a99a5bda075a9f4e844368974296744934
prerequisite-patch-id: 288b3cbaa11e01a778cc3d7beb6c0549aacf59f2
prerequisite-patch-id: a163b1549608652bfa5bd9ff93efa55b541010c4
prerequisite-patch-id: 78f067c343177e52d49ddfe9ee2992e602549869
prerequisite-patch-id: 19c12bc6e3fbed35acc1f657196b1a997f48cdfe
prerequisite-patch-id: 5800c3076694bad719954b2ad4d7448c6e013bfd
prerequisite-patch-id: 2a2f63d8843b159594b9eee6ddea028ce76a6270
prerequisite-patch-id: 40a1951d57906c4a1b98a4bd96a814b80339b9c5
prerequisite-patch-id: e2141077e69f20a2b27fab69c8d61d0686682f53
prerequisite-patch-id: c4d6018c26c40dc549d88330d23ce48f872c2b7f
prerequisite-patch-id: 6de8ec17746031bc6d1d9ac262c5be0f905fcd79
prerequisite-patch-id: b87bcb167884605bff1fd0f2ed8a88942d07efd5
prerequisite-patch-id: cfa9a8b4c67e20cfd22d77035bfd21ee96ab3298
prerequisite-patch-id: abf3482ed2399b7b2e1ee72e937af05bb9200667
prerequisite-patch-id: 02c175b2dee565a85b3308ecf2ec8e3c9a029e0f
prerequisite-patch-id: 4ed6b3d3d45de49d4967e88ba4e7f03372775f55
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: cf9ce0099005fd3bc31a1e15131d48e315ac3bee
prerequisite-patch-id: 16a657a5d0046068dbace29e7ea046ac1441f46f
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: e6ffe2a5c3ee5645f56c8366b568fbc9f140848c
Christian Brauner
maintainerRFC PATCH 1/16 proc: drop the compat file operations
procfs has both sets of file operations twice, with and without ->compat_ioctl(), and picks the one with it for entries that have ->proc_compat_ioctl(). That's from commit 778f3dd5a13c ("Fix procfs compat_ioctl regression") when a file without ->compat_ioctl() went through the translation tables in fs/compat_ioctl.c and one with it didn't. Those tables are gone since commit 77b9040195de ("compat_ioctl:
simplify the implementation"). compat_sys_ioctl() answers -ENOTTY for a file without ->compat_ioctl() now, which is what proc_reg_compat_ioctl() answers for an entry without ->proc_compat_ioctl().
So always set ->compat_ioctl() and drop the second set of file operations and the flag that picked it.
No functional changes.
--- fs/proc/generic.c | 4 ---- fs/proc/inode.c | 45 +++------------------------------------------ fs/proc/internal.h | 9 --------- include/linux/proc_fs.h | 1 - 4 files changed, 3 insertions(+), 56 deletions(-)
fs/proc/generic.c +0 -4
@@ -445,10 +445,6 @@ static void pde_set_flags(struct proc_dir_entry *pde) pde->flags |=PROC_ENTRY_PERMANENT ;if (proc_ops->proc_read_iter) pde->flags |= PROC_ENTRY_proc_read_iter; -#ifdef CONFIG_COMPAT -if (proc_ops->proc_compat_ioctl) - pde->flags |= PROC_ENTRY_proc_compat_ioctl; -#endif if (proc_ops->proc_lseek) pde->flags |= PROC_ENTRY_proc_lseek; }
fs/proc/inode.c +3 -42
75 lines not on this page · raw message
fs/proc/internal.h +0 -9
@@ -92,15 +92,6 @@ static inline bool pde_has_proc_read_iter(const struct proc_dir_entry *pde)return pde->flags & PROC_ENTRY_proc_read_iter; } -static inline bool pde_has_proc_compat_ioctl (const struct proc_dir_entry *pde) -{ -#ifdef CONFIG_COMPAT -return pde->flags & PROC_ENTRY_proc_compat_ioctl; -#else -return false; -#endif -} -static inline bool pde_has_proc_lseek (const struct proc_dir_entry *pde) {return pde->flags & PROC_ENTRY_proc_lseek;
include/linux/proc_fs.h +0 -1
@@ -26,7 +26,6 @@ enum {#endif PROC_ENTRY_proc_read_iter =1U <<1 , - PROC_ENTRY_proc_compat_ioctl =1U <<2 , PROC_ENTRY_proc_lseek =1U <<3 , PROC_ENTRY_FORCE_LOOKUP = 1U << 7,
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 01/16] proc: drop the compat file operations
Sashiko has reviewed this patch and found no issues.
Quoted 11 lines
procfs has both sets of file operations twice, with and without ->compat_ioctl(), and picks the one with it for entries that have ->proc_compat_ioctl(). That's from commit
778f3dd5a13c("Fix procfscompat_ioctlregression") when a file without ->compat_ioctl() went through the translation tables infs/compat_ioctl.cand one with it didn't. Those tables are gone since commit77b9040195de("compat_ioctl:
simplify the implementation").compat_sys_ioctl()answers-ENOTTYfor a file without ->compat_ioctl() now, which is whatproc_reg_compat_ioctl()answers for an entry without ->proc_compat_ioctl().
Quoted 6 lines
So always set ->compat_ioctl() and drop the second set of file operations and the flag that picked it.
No functional changes.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Christian Brauner
maintainerRFC PATCH 2/16 proc: add pde_pin() and pde_unpin()
In reply to Christian BraunerEvery call into an entry that can go away is wrapped in use_pde() and unuse_pde(), and a permanent entry is called straight away. The read, write, poll, ioctl, mmap and lseek methods all spell that out as the same if-else. The next patches add a few more callers. So put it into pde_pin() and pde_unpin() and use them everywhere.
No functional changes.
--- fs/proc/inode.c | 116 +++++++++++++++++++++++++++----------------------------- 1 file changed, 55 insertions(+), 61 deletions(-)
fs/proc/inode.c +55 -61
@@ -206,6 +206,18 @@ static void unuse_pde(struct proc_dir_entry *pde)complete (pde->pde_unload_completion); } +/* Pin an entry that can go away around a call into it, see use_pde(). */ +static bool pde_pin (struct proc_dir_entry *pde) +{ +return pde_is_permanent (pde) ||use_pde (pde); +} + +static void pde_unpin (struct proc_dir_entry *pde) +{ +if (!pde_is_permanent (pde)) +unuse_pde (pde); +} +/* * At most 2 contexts can enter this function: the one doing the last * close on the descriptor and whoever is deleting PDE itself.
@@ -275,14 +287,12 @@ void proc_entry_rundown(struct proc_dir_entry *de)static loff_t proc_reg_llseek (struct file *file,loff_t offset,int whence) {struct proc_dir_entry *pde =PDE (file_inode (file)); -loff_t rv = -EINVAL ; +loff_t rv; -if (pde_is_permanent (pde)) { -return pde->proc_ops->proc_lseek (file, offset, whence); - }else if (use_pde (pde)) { - rv = pde->proc_ops->proc_lseek (file, offset, whence); -unuse_pde (pde); - } +if (!pde_pin (pde)) +return -EINVAL ; + rv = pde->proc_ops->proc_lseek (file, offset, whence); +pde_unpin (pde);return rv; }
@@ -291,13 +301,10 @@ static ssize_t proc_reg_read_iter(struct kiocb *iocb, struct iov_iter *iter)struct proc_dir_entry *pde =PDE (file_inode (iocb->ki_filp));ssize_t ret; -if (pde_is_permanent (pde)) -return pde->proc_ops->proc_read_iter (iocb, iter); - -if (!use_pde (pde)) +if (!pde_pin (pde))return -EIO ; ret = pde->proc_ops->proc_read_iter (iocb, iter); -unuse_pde (pde); +pde_unpin (pde);return ret; }
@@ -312,14 +319,12 @@ static ssize_t pde_read(struct proc_dir_entry *pde, struct file *file, char __usstatic ssize_t proc_reg_read (struct file *file,char __user *buf,size_t count,loff_t *ppos) {struct proc_dir_entry *pde =PDE (file_inode (file)); -ssize_t rv = -EIO ; +ssize_t rv; -if (pde_is_permanent (pde)) { -return pde_read (pde, file, buf, count, ppos); - }else if (use_pde (pde)) { - rv =pde_read (pde, file, buf, count, ppos); -unuse_pde (pde); - } +if (!pde_pin (pde)) +return -EIO ; + rv =pde_read (pde, file, buf, count, ppos); +pde_unpin (pde);return rv; }
@@ -334,14 +339,12 @@ static ssize_t pde_write(struct proc_dir_entry *pde, struct file *file, const chstatic ssize_t proc_reg_write (struct file *file,const char __user *buf,size_t count,loff_t *ppos) {struct proc_dir_entry *pde =PDE (file_inode (file)); -ssize_t rv = -EIO ; +ssize_t rv; -if (pde_is_permanent (pde)) { -return pde_write (pde, file, buf, count, ppos); - }else if (use_pde (pde)) { - rv =pde_write (pde, file, buf, count, ppos); -unuse_pde (pde); - } +if (!pde_pin (pde)) +return -EIO ; + rv =pde_write (pde, file, buf, count, ppos); +pde_unpin (pde);return rv; }
@@ -356,14 +359,12 @@ static __poll_t pde_poll(struct proc_dir_entry *pde, struct file *file, struct pstatic __poll_t proc_reg_poll (struct file *file,struct poll_table_struct *pts) {struct proc_dir_entry *pde =PDE (file_inode (file)); -__poll_t rv =DEFAULT_POLLMASK ; +__poll_t rv; -if (pde_is_permanent (pde)) { -return pde_poll (pde, file, pts); - }else if (use_pde (pde)) { - rv =pde_poll (pde, file, pts); -unuse_pde (pde); - } +if (!pde_pin (pde)) +return DEFAULT_POLLMASK ; + rv =pde_poll (pde, file, pts); +pde_unpin (pde);return rv; }
@@ -378,14 +379,12 @@ static long pde_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned instatic long proc_reg_unlocked_ioctl (struct file *file,unsigned int cmd,unsigned long arg) {struct proc_dir_entry *pde =PDE (file_inode (file)); -long rv = -ENOTTY ; +long rv; -if (pde_is_permanent (pde)) { -return pde_ioctl (pde, file, cmd, arg); - }else if (use_pde (pde)) { - rv =pde_ioctl (pde, file, cmd, arg); -unuse_pde (pde); - } +if (!pde_pin (pde)) +return -ENOTTY ; + rv =pde_ioctl (pde, file, cmd, arg); +pde_unpin (pde);return rv; }
@@ -401,13 +400,12 @@ static long pde_compat_ioctl(struct proc_dir_entry *pde, struct file *file, unsistatic long proc_reg_compat_ioctl (struct file *file,unsigned int cmd,unsigned long arg) {struct proc_dir_entry *pde =PDE (file_inode (file)); -long rv = -ENOTTY ; -if (pde_is_permanent (pde)) { -return pde_compat_ioctl (pde, file, cmd, arg); - }else if (use_pde (pde)) { - rv =pde_compat_ioctl (pde, file, cmd, arg); -unuse_pde (pde); - } +long rv; + +if (!pde_pin (pde)) +return -ENOTTY ; + rv =pde_compat_ioctl (pde, file, cmd, arg); +pde_unpin (pde);return rv; }#else
@@ -425,14 +423,12 @@ static int pde_mmap(struct proc_dir_entry *pde, struct file *file, struct vm_arestatic int proc_reg_mmap (struct file *file,struct vm_area_struct *vma) {struct proc_dir_entry *pde =PDE (file_inode (file)); -int rv = -EIO ; +int rv; -if (pde_is_permanent (pde)) { -return pde_mmap (pde, file, vma); - }else if (use_pde (pde)) { - rv =pde_mmap (pde, file, vma); -unuse_pde (pde); - } +if (!pde_pin (pde)) +return -EIO ; + rv =pde_mmap (pde, file, vma); +pde_unpin (pde);return rv; }
@@ -457,14 +453,12 @@ proc_reg_get_unmapped_area(struct file *file, unsigned long orig_addr, unsigned long flags) {struct proc_dir_entry *pde =PDE (file_inode (file)); -unsigned long rv = -EIO ; +unsigned long rv; -if (pde_is_permanent (pde)) { -return pde_get_unmapped_area (pde, file, orig_addr, len, pgoff, flags); - }else if (use_pde (pde)) { - rv =pde_get_unmapped_area (pde, file, orig_addr, len, pgoff, flags); -unuse_pde (pde); - } +if (!pde_pin (pde)) +return -EIO ; + rv =pde_get_unmapped_area (pde, file, orig_addr, len, pgoff, flags); +pde_unpin (pde);return rv; }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 02/16] proc: add pde_pin() and pde_unpin()
Sashiko has reviewed this patch and found no issues.
Quoted 9 lines
Every call into an entry that can go away is wrapped in
use_pde()andunuse_pde(), and a permanent entry is called straight away. The read, write, poll, ioctl, mmap and lseek methods all spell that out as the same if-else. The next patches add a few more callers. So put it intopde_pin()andpde_unpin()and use them everywhere.No functional changes.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Christian Brauner
maintainerRFC PATCH 3/16 proc: make room for more entry flags
In reply to Christian Brauner->flags of struct proc_dir_entry is a u8 and the next patches need more flags than it has room for, so make it a u16. Bit 6 stays free, vfs-7.5.mount takes it for PROC_ENTRY_CANT_MOUNT.
No functional changes.
--- fs/proc/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/proc/internal.h +1 -1
@@ -61,7 +61,7 @@ struct proc_dir_entry {struct rb_node subdir_node;char *name;umode_t mode; -u8 flags; +u16 flags;u8 namelen;char inline_name[]; } __randomize_layout;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 03/16] proc: make room for more entry flags
Sashiko has reviewed this patch and found no issues.
Quoted 7 lines
->flags of struct
proc_dir_entryis a u8 and the next patches need more flags than it has room for, so make it a u16. Bit 6 stays free, vfs-7.5.mount takes it forPROC_ENTRY_CANT_MOUNT.No functional changes.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Christian Brauner
maintainerRFC PATCH 4/16 proc: let procfs own the seq_file of seq_file entries
In reply to Christian Braunerremove_proc_entry() waits until every call into the entry has returned. For a seq_file entry that's all of seq_read_iter(), including the copy to userspace. A page fault in that copy can be made to take as long as the reader wants, e.g. with userfaultfd or by punching holes into a memfd over and over. And callers of remove_proc_entry() like to hold locks while they wait. xt_hashlimit holds hashlimit_mutex, which all network namespaces share, and addrconf holds RTNL.
But the entry's code only runs between ->start() and ->stop() and the copies to userspace happen outside of that. So let procfs own the seq_file the way kernfs does. An entry declares its seq_operations, or just a show function, and the size of its private state in struct proc_ops. procfs then opens the seq_file with seq_operations of its own that pin the entry around every call into the entry, and reads and seeks through seq_file directly. The copies to userspace happen with the entry unpinned.
That changes who owns the seq_file. remove_proc_entry() can now finish while a reader is still copying out of the buffer, so the seq_file can't go away with the entry. procfs frees it once the file is released. ->proc_open() finds the seq_file in ->private_data and sets up whatever the entry needs and ->proc_release() releases just that. It still runs when the entry is removed, or when the file is closed before that.
Since procfs reads these entries through seq_read_iter() itself they all have ->splice_read() now, so sendfile(), splice(), aio, the RWF flags and kernel_read() work on them, and readv() is one read instead of one per segment. Note that a removed entry still hands out what it had already formatted and then answers -EIO to reads, and to seeks that have to walk the records. Seeking it failed with -EINVAL right away before.
The seq_operations of procfs serve both kinds of entries. For an entry that only has a show function they do what single_open() does, like kernfs_seq_start(). seq_file's complaint about a ->next() that doesn't move the position would name procfs' wrapper, so the wrapper checks that itself and names the entry's. An entry that declares a seq_file and also a read or seek method of its own, or one that releases the seq_file with seq_release() itself, doesn't make sense, procfs reads, seeks and releases the seq_file itself, so registering it fails.
No users yet.
--- fs/proc/generic.c | 36 +++++++++-- fs/proc/inode.c | 167 ++++++++++++++++++++++++++++++++++++++++++++---- fs/proc/internal.h | 16 +++++ include/linux/proc_fs.h | 22 ++++++- 4 files changed, 219 insertions(+), 22 deletions(-)
fs/proc/generic.c +29 -7
@@ -434,19 +434,41 @@ static const struct inode_operations proc_dir_inode_operations = { .setattr = proc_setattr, }; -static void pde_set_flags (struct proc_dir_entry *pde) +static int pde_set_flags (struct proc_dir_entry *pde) {const struct proc_ops *proc_ops = pde->proc_ops;if (!proc_ops) -return ; +return 0 ;if (proc_ops->proc_flags &PROC_ENTRY_PERMANENT ) pde->flags |=PROC_ENTRY_PERMANENT ; -if (proc_ops->proc_read_iter) - pde->flags |= PROC_ENTRY_proc_read_iter; -if (proc_ops->proc_lseek) +if (proc_ops->proc_seq_ops) { + pde->seq_ops = proc_ops->proc_seq_ops; + pde->state_size = proc_ops->proc_state_size; + pde->flags |= PROC_ENTRY_proc_seq_ops; + }else if (proc_ops->proc_show) { + pde->single_show = proc_ops->proc_show; + pde->flags |= PROC_ENTRY_proc_show; + } +if (pde_is_seq_file (pde)) { +/* procfs reads and seeks through the seq_file itself */ +if (WARN_ON_ONCE (proc_ops->proc_read || proc_ops->proc_read_iter || + proc_ops->proc_lseek)) +return -EINVAL ; +/* and frees it, see pde_seq_release() */ +if (WARN_ON_ONCE (proc_ops->proc_release == seq_release || + proc_ops->proc_release == seq_release_private || + proc_ops->proc_release == single_release)) +return -EINVAL ; pde->flags |= PROC_ENTRY_proc_lseek; + }else { +if (proc_ops->proc_read_iter) + pde->flags |= PROC_ENTRY_proc_read_iter; +if (proc_ops->proc_lseek) + pde->flags |= PROC_ENTRY_proc_lseek; + } +return 0 ; }/* returns the registered entry, or frees dp and returns NULL on failure */
@@ -456,8 +478,8 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,if (proc_alloc_inum (&dp->low_ino)) goto out_free_entry; -if (!S_ISDIR (dp->mode)) -pde_set_flags (dp); +if (!S_ISDIR (dp->mode) &&pde_set_flags (dp)) + goto out_free_inum;write_lock (&proc_subdir_lock); dp->parent = dir;
fs/proc/inode.c +153 -14
218 lines not on this page · raw message
fs/proc/internal.h +16 -0
@@ -97,6 +97,22 @@ static inline bool pde_has_proc_lseek(const struct proc_dir_entry *pde)return pde->flags & PROC_ENTRY_proc_lseek; } +static inline bool pde_has_proc_seq_ops (const struct proc_dir_entry *pde) +{ +return pde->flags & PROC_ENTRY_proc_seq_ops; +} + +static inline bool pde_has_proc_show (const struct proc_dir_entry *pde) +{ +return pde->flags & PROC_ENTRY_proc_show; +} + +/* procfs owns the seq_file, see pde_seq_ops */ +static inline bool pde_is_seq_file (const struct proc_dir_entry *pde) +{ +return pde_has_proc_seq_ops (pde) ||pde_has_proc_show (pde); +} +extern struct kmem_cache *proc_dir_entry_cache;void pde_free (struct proc_dir_entry *pde);
include/linux/proc_fs.h +21 -1
@@ -27,6 +27,8 @@ enum { PROC_ENTRY_proc_read_iter =1U <<1 , PROC_ENTRY_proc_lseek =1U <<3 , + PROC_ENTRY_proc_seq_ops =1U <<4 , + PROC_ENTRY_proc_show =1U <<5 , PROC_ENTRY_FORCE_LOOKUP = 1U << 7, };
@@ -37,7 +39,10 @@ struct proc_ops {ssize_t (*proc_read)(struct file *,char __user *,size_t ,loff_t *);ssize_t (*proc_read_iter)(struct kiocb *,struct iov_iter *);ssize_t (*proc_write)(struct file *,const char __user *,size_t ,loff_t *); -/* mandatory unless nonseekable_open() or equivalent is used */ +/* +* mandatory unless nonseekable_open() or equivalent is used, procfs +* seeks seq_file entries itself +*/ loff_t (*proc_lseek)(struct file *,loff_t ,int );int (*proc_release)(struct inode *,struct file *);__poll_t (*proc_poll)(struct file *,struct poll_table_struct *);
@@ -47,6 +52,21 @@ struct proc_ops {#endif int (*proc_mmap)(struct file *,struct vm_area_struct *);unsigned long (*proc_get_unmapped_area)(struct file *,unsigned long ,unsigned long ,unsigned long ,unsigned long ); +/* +* For seq_file entries procfs opens the seq_file, reads and seeks +* through it and frees it once the file is released. ->proc_open() +* finds the seq_file in ->private_data and ->proc_release() only +* releases what ->proc_open() set up. It can run while a reader still +* copies out of the seq_file, so it must leave that alone. procfs +* allocates ->proc_state_size bytes of ->private state for +* ->proc_seq_ops and points ->private at the entry's data for +* ->proc_show(). Nothing that runs with the entry pinned may wait for +* something a copy to or from userspace can hold, like the seq_file's +* lock. +*/ +const struct seq_operations *proc_seq_ops; +int (*proc_show)(struct seq_file *,void *); +unsigned int proc_state_size; } __randomize_layout;/* definitions for hide_pid field */
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 04/16] proc: let procfs own the seq_file of seq_file entries
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] proc: unpinned dereference of pde->seq_ops->next in pde_seq_next() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] proc: unpinned dereference of pde->seq_ops->next in pde_seq_next() --
commit ae41131fe28a1e7fcf9c3b743e2a30697e315fe9
Author: Christian Brauner <[email protected]>
proc: let procfs own the seq_file of seq_file entries
This commit allows procfs to own the seq_file for seq_file and single-show entries by wrapping their seq_operations and pinning the proc_dir_entry only around start, next, show, and stop callbacks rather than across userspace copies in seq_read_iter().
Quoted 8 lines
fs/proc/inode.c +0
-0@@ -218,6 +218,110 @@ static void pde_unpin(struct proc_dir_entry *pde)unuse_pde (pde); }
[ ... ]
Quoted 34 lines
fs/proc/inode.c +34
-0+static void pde_seq_stop_pinned (struct seq_file *m,void *v) +{ +struct proc_dir_entry *pde =seq_pde (m); + +if (pde_has_proc_seq_ops (pde)) + pde->seq_ops->stop (m, v); +pde_unpin (pde); +} + +static void *pde_seq_start (struct seq_file *m,loff_t *pos) +{ +struct proc_dir_entry *pde =seq_pde (m); +void *v; + +if (!pde_pin (pde)) +return ERR_PTR (-EIO ); +if (!pde_has_proc_seq_ops (pde)) +return single_start (m, pos); + v = pde->seq_ops->start (m, pos); +if (v ==ERR_PTR (-EIO )) +pde_seq_stop_pinned (m, v); +return v; +} + +static void *pde_seq_next (struct seq_file *m,void *v,loff_t *pos) +{ +struct proc_dir_entry *pde =seq_pde (m); +loff_t old = *pos; + +if (!pde_has_proc_seq_ops (pde)) { + ++*pos; +return NULL ; + } + v = pde->seq_ops->next (m, v, pos);
fs/proc/inode.c static void pde_unpin(struct proc_dir_entry *pde) · line 280
Quoted 10 lines
fs/proc/inode.c +10
-0+if (v ==ERR_PTR (-EIO )) +pde_seq_stop_pinned (m, v); +/* seq_file would blame these seq_operations, blame the entry's */ +if (*pos == old) { +pr_info_ratelimited ("buggy .next function %ps did not update position index\n" , + pde->seq_ops->next); + ++*pos; + } +return v; +}
[Severity: High]
In pde_seq_next(), if pde->seq_ops->next(m, v, pos) returns ERR_PTR(-EIO) without advancing *pos, pde_seq_stop_pinned(m, v) is called before the if (*pos == old) check.
Since pde_seq_stop_pinned() calls pde_unpin(pde), which decrements pde->in_use and can complete pde->pde_unload_completion, a concurrent remove_proc_entry() waiting in proc_entry_rundown() can finish and allow a module to unload or free the memory backing pde->seq_ops before pr_info_ratelimited() dereferences pde->seq_ops->next and formats it with %ps. In addition, pde->seq_ops->stop(m, v) in pde_seq_stop_pinned() will observe the unincremented *pos value.
Could the if (*pos == old) check be moved ahead of the
if (v == ERR_PTR(-EIO)) check so that pde->seq_ops->next is only accessed while the entry is still pinned and *pos is updated before ->stop() is called?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Christian Brauner
maintainerRe: [PATCH RFC 04/16] proc: let procfs own the seq_file of seq_file entries
Quoted 1 line · On Tue, Nov 03, 2026 at 03:01:06PM +0000, [email protected] wrote:
- [High] proc: unpinned dereference of
pde->seq_ops->nextinpde_seq_next()
Nothing in the tree returns ERR_PTR(-EIO) from ->next but moving the check up would remove that possibility so yes.
Christian Brauner
maintainerRFC PATCH 5/16 proc: let procfs own the seq_file of proc_create_seq() and friends
In reply to Christian Braunerproc_create_seq_private(), proc_create_single_data() and their network namespace variants keep the seq_operations or the show function in the entry already. Let procfs own the seq_file for all of them. For the network namespace variants ->proc_open() and ->proc_release() keep taking and dropping the reference to the network namespace.
That's most of /proc/net, among them the files of xt_hashlimit and /proc/net/dev_snmp6/<dev>. A reader that faults on its buffer no longer holds up their removal.
--- fs/proc/generic.c | 42 ++++-------------------------------------- fs/proc/proc_net.c | 40 +++++++++++++--------------------------- 2 files changed, 17 insertions(+), 65 deletions(-)
fs/proc/generic.c +4 -38
@@ -694,30 +694,9 @@ struct proc_dir_entry *proc_create(const char *name, umode_t mode, }EXPORT_SYMBOL (proc_create); -static int proc_seq_open (struct inode *inode,struct file *file) -{ -struct proc_dir_entry *de =PDE (inode); - -if (de->state_size) -return seq_open_private (file, de->seq_ops, de->state_size); -return seq_open (file, de->seq_ops); -} - -static int proc_seq_release (struct inode *inode,struct file *file) -{ -struct proc_dir_entry *de =PDE (inode); - -if (de->state_size) -return seq_release_private (inode, file); -return seq_release (inode, file); -} - +/* procfs owns the seq_file of these, see pde_seq_ops */ static const struct proc_ops proc_seq_ops = {/* not permanent -- can call into arbitrary seq_operations */ - .proc_open = proc_seq_open, - .proc_read_iter = seq_read_iter, - .proc_lseek = seq_lseek, - .proc_release = proc_seq_release, }; struct proc_dir_entry *proc_create_seq_private(const char *name, umode_t mode,
@@ -732,25 +711,11 @@ struct proc_dir_entry *proc_create_seq_private(const char *name, umode_t mode, p->proc_ops = &proc_seq_ops; p->seq_ops = ops; p->state_size = state_size; + p->flags |= PROC_ENTRY_proc_seq_ops;return proc_register (parent, p); }EXPORT_SYMBOL (proc_create_seq_private); -static int proc_single_open (struct inode *inode,struct file *file) -{ -struct proc_dir_entry *de =PDE (inode); - -return single_open (file, de->single_show, de->data); -} - -static const struct proc_ops proc_single_ops = { -/* not permanent -- can call into arbitrary ->single_show */ - .proc_open = proc_single_open, - .proc_read_iter = seq_read_iter, - .proc_lseek = seq_lseek, - .proc_release = single_release, -}; -struct proc_dir_entry *proc_create_single_data (const char *name,umode_t mode,struct proc_dir_entry *parent,int (*show)(struct seq_file *,void *),void *data)
@@ -760,8 +725,9 @@ struct proc_dir_entry *proc_create_single_data(const char *name, umode_t mode, p =proc_create_reg (name, mode, &parent, data);if (!p)return NULL ; - p->proc_ops = &proc_single_ops; + p->proc_ops = &proc_seq_ops; p->single_show = show; + p->flags |= PROC_ENTRY_proc_show;return proc_register (parent, p); }EXPORT_SYMBOL (proc_create_single_data);
fs/proc/proc_net.c +13 -27
@@ -37,13 +37,13 @@ static struct net *get_proc_net(const struct inode *inode)return maybe_get_net (PDE_NET (PDE (inode))); } +/* procfs opened the seq_file and allocated its state, see pde_seq_ops */ static int seq_open_net (struct inode *inode,struct file *file) { -unsigned int state_size =PDE (inode)->state_size; -struct seq_net_private *p; +struct seq_net_private *p = ((struct seq_file *)file->private_data)->private;struct net *net; -WARN_ON_ONCE (state_size <sizeof (*p)); +WARN_ON_ONCE (PDE (inode)->state_size <sizeof (*p));if (file->f_mode &FMODE_WRITE && !PDE (inode)->write)return -EACCES ;
@@ -51,12 +51,6 @@ static int seq_open_net(struct inode *inode, struct file *file) net =get_proc_net (inode);if (!net)return -ENXIO ; - - p =__seq_open_private (file,PDE (inode)->seq_ops, state_size); -if (!p) { -put_net (net); -return -ENOMEM ; - }#ifdef CONFIG_NET_NS p->net = net;netns_tracker_alloc (net, &p->ns_tracker,GFP_KERNEL );
@@ -77,18 +71,13 @@ static void seq_file_net_put_net(struct seq_file *seq)static int seq_release_net (struct inode *ino,struct file *f) { -struct seq_file *seq = f->private_data; - -seq_file_net_put_net (seq); -seq_release_private (ino, f); +seq_file_net_put_net (f->private_data);return 0 ; }static const struct proc_ops proc_net_seq_ops = { .proc_open = seq_open_net, - .proc_read = seq_read, .proc_write = proc_simple_write, - .proc_lseek = seq_lseek, .proc_release = seq_release_net, };
@@ -125,6 +114,7 @@ struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode, p->proc_ops = &proc_net_seq_ops; p->seq_ops = ops; p->state_size = state_size; + p->flags |= PROC_ENTRY_proc_seq_ops;return proc_register (parent, p); }EXPORT_SYMBOL_GPL (proc_create_net_data);
@@ -170,6 +160,7 @@ struct proc_dir_entry *proc_create_net_data_write(const char *name, umode_t mode p->proc_ops = &proc_net_seq_ops; p->seq_ops = ops; p->state_size = state_size; + p->flags |= PROC_ENTRY_proc_seq_ops; p->write = write;return proc_register (parent, p); }
@@ -177,32 +168,25 @@ EXPORT_SYMBOL_GPL(proc_create_net_data_write);static int single_open_net (struct inode *inode,struct file *file) { -struct proc_dir_entry *de =PDE (inode); -struct net *net; -int err; +struct net *net =get_proc_net (inode); - net =get_proc_net (inode);if (!net)return -ENXIO ; - - err =single_open (file, de->single_show, net); -if (err) -put_net (net); -return err; + ((struct seq_file *)file->private_data)->private = net; +return 0 ; }static int single_release_net (struct inode *ino,struct file *f) {struct seq_file *seq = f->private_data; +put_net (seq->private); -return single_release (ino, f); +return 0 ; }static const struct proc_ops proc_net_single_ops = { .proc_open = single_open_net, - .proc_read = seq_read, .proc_write = proc_simple_write, - .proc_lseek = seq_lseek, .proc_release = single_release_net, };
@@ -218,6 +202,7 @@ struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode,pde_force_lookup (p); p->proc_ops = &proc_net_single_ops; p->single_show = show; + p->flags |= PROC_ENTRY_proc_show;return proc_register (parent, p); }EXPORT_SYMBOL_GPL (proc_create_net_single);
@@ -261,6 +246,7 @@ struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mopde_force_lookup (p); p->proc_ops = &proc_net_single_ops; p->single_show = show; + p->flags |= PROC_ENTRY_proc_show; p->write = write;return proc_register (parent, p); }
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 05/16] proc: let procfs own the seq_file of proc_create_seq() and friends
Sashiko has reviewed this patch and found no issues.
Quoted 6 lines
proc_create_seq_private(),proc_create_single_data()and their network namespace variants keep theseq_operationsor the show function in the entry already. Let procfs own theseq_filefor all of them. For the network namespace variants ->proc_open() and ->proc_release() keep taking and dropping the reference to the network namespace.
Quoted 7 lines
That's most of
/proc/net, among them the files ofxt_hashlimitand /proc/net/dev_snmp6/<dev>. A reader that faults on its buffer no longer holds up their removal.Reported-by Chris Mason <[email protected]>Fixes 786d7e1612f0 ("Fix rmmod/read/write races in /proc entries")Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Christian Brauner
maintainerRFC PATCH 6/16 proc: let procfs own the seq_file of DEFINE_PROC_SHOW_ATTRIBUTE() files
In reply to Christian BraunerDeclare the show function and let procfs open the seq_file.
--- include/linux/seq_file.h | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-)
include/linux/seq_file.h +1 -9
@@ -233,16 +233,8 @@ static const struct file_operations __name ## _fops = { \ }#define DEFINE_PROC_SHOW_ATTRIBUTE (__name) \ -static int __name ## _open(struct inode *inode, struct file *file) \ -{ \ - return single_open(file, __name ## _show, pde_data(inode)); \ -} \ - \ static const struct proc_ops __name ## _proc_ops = { \ - .proc_open = __name ## _open, \ - .proc_read = seq_read, \ - .proc_lseek = seq_lseek, \ - .proc_release = single_release, \ + .proc_show = __name ## _show, \ }static inline struct user_namespace *seq_user_ns (struct seq_file *seq)
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 06/16] proc: let procfs own the seq_file of DEFINE_PROC_SHOW_ATTRIBUTE() files
Sashiko has reviewed this patch and found no issues.
Quoted 3 lines
Declare the show function and let procfs open the
seq_file.Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
Christian Brauner
maintainerRFC PATCH 7/16 treewide: let procfs own the seq_file of simple proc entries
In reply to Christian BraunerMost proc entries that bring their own struct proc_ops only call single_open(), seq_open() or seq_open_private() in ->proc_open() and free the seq_file with the matching helper. Declare the show function or the seq_operations instead and let procfs open and free the seq_file. Reading these files then no longer holds up their removal.
Done with the following semantic patch. The new initializers were aligned like their neighbours afterwards:
// SPDX-License-Identifier: GPL-2.0-only
/// Let procfs open the seq_file of proc entries whose ->proc_open() only /// calls single_open(), seq_open() or seq_open_private().
//
// Confidence: High
// Options: --no-includes --include-headers
virtual patch
@single depends on patch@
identifier open_fn, show_fn, i, f;
@@
static int open_fn(struct inode *i, struct file *f)
{
return single_open(f, show_fn, \(NULL\|pde_data(i)\));
} @single_ops depends on single@
identifier single.open_fn, single.show_fn, ops;
symbol single_release;
@@
const struct proc_ops ops = {
- .proc_open = open_fn,
+ .proc_show = show_fn,
...,
- .proc_release = single_release,
...
};
@seq depends on patch@
identifier open_fn, sops, i, f;
@@
static int open_fn(struct inode *i, struct file *f)
{
return seq_open(f, &sops);
} @seq_ops depends on seq@
identifier seq.open_fn, seq.sops, ops;
symbol seq_release;
@@
const struct proc_ops ops = {
- .proc_open = open_fn,
+ .proc_seq_ops = &sops,
...,
- .proc_release = seq_release,
...
};
@priv depends on patch@
identifier open_fn, sops, i, f;
expression size;
@@
static int open_fn(struct inode *i, struct file *f)
{
return seq_open_private(f, &sops, size);
} @priv_ops depends on priv@
identifier priv.open_fn, priv.sops, ops;
expression priv.size;
symbol seq_release_private;
@@
const struct proc_ops ops = {
- .proc_open = open_fn,
+ .proc_seq_ops = &sops,
+ .proc_state_size = size,
...,
- .proc_release = seq_release_private,
...
};
// procfs reads and seeks through the seq_file itself
@depends on single_ops || seq_ops || priv_ops@
identifier ops = {single_ops.ops, seq_ops.ops, priv_ops.ops};
symbol seq_read_iter;
@@
const struct proc_ops ops = {
...,
- .proc_read_iter = seq_read_iter,
...
};
@depends on single_ops || seq_ops || priv_ops@
identifier ops = {single_ops.ops, seq_ops.ops, priv_ops.ops};
symbol seq_read;
@@
const struct proc_ops ops = {
...,
- .proc_read = seq_read,
...
};
@depends on single_ops || seq_ops || priv_ops@
identifier ops = {single_ops.ops, seq_ops.ops, priv_ops.ops};
symbol seq_lseek;
@@
const struct proc_ops ops = {
...,
- .proc_lseek = seq_lseek,
...
}; // drop the open function unless something else still uses it @open_used@
identifier open_fn = {single.open_fn, seq.open_fn, priv.open_fn}; @@
open_fn
@depends on !open_used && (single_ops || seq_ops || priv_ops)@
identifier open_fn = {single.open_fn, seq.open_fn, priv.open_fn};
@@
- static int open_fn(...) { ... }--- arch/alpha/kernel/srm_env.c | 10 +-- arch/arm/mm/alignment.c | 10 +-- arch/powerpc/kernel/rtas-proc.c | 50 ++------------ arch/powerpc/platforms/pseries/lpar.c | 20 +----- arch/powerpc/platforms/pseries/lparcfg.c | 10 +-- arch/sh/mm/alignment.c | 10 +-- arch/sparc/kernel/led.c | 10 +-- arch/um/kernel/exitcode.c | 10 +-- drivers/acpi/proc.c | 12 +--- drivers/hwmon/dell-smm-hwmon.c | 10 +-- drivers/input/input.c | 24 ++----- drivers/macintosh/via-pmu.c | 10 +-- drivers/net/wireless/intel/ipw2x00/libipw_module.c | 10 +-- drivers/platform/x86/lenovo/thinkpad_acpi.c | 10 +-- drivers/platform/x86/toshiba_acpi.c | 40 ++--------- drivers/pnp/pnpbios/proc.c | 10 +-- drivers/s390/block/dasd_proc.c | 10 +-- drivers/s390/cio/blacklist.c | 13 +--- drivers/scsi/scsi_devinfo.c | 10 +-- drivers/scsi/scsi_proc.c | 21 +----- drivers/usb/gadget/function/rndis.c | 10 +-- drivers/video/fbdev/via/viafbdev.c | 70 ++----------------- fs/jfs/jfs_debug.c | 10 +-- fs/ntfs3/super.c | 20 +----- fs/proc/cpuinfo.c | 10 +-- fs/smb/client/cifs_debug.c | 79 +++------------------- fs/smb/client/dfs_cache.c | 10 +-- kernel/irq/proc.c | 30 +------- kernel/latencytop.c | 10 +-- lib/dynamic_debug.c | 6 +- mm/slab_common.c | 10 +-- net/core/pktgen.c | 30 +------- net/sunrpc/stats.c | 10 +-- 33 files changed, 64 insertions(+), 551 deletions(-)
arch/alpha/kernel/srm_env.c +1 -9
@@ -81,11 +81,6 @@ static int srm_env_proc_show(struct seq_file *m, void *v)return ret; } -static int srm_env_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, srm_env_proc_show,pde_data (inode)); -} -static ssize_t srm_env_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *pos) {
@@ -120,10 +115,7 @@ static ssize_t srm_env_proc_write(struct file *file, const char __user *buffer, }static const struct proc_ops srm_env_proc_ops = { - .proc_open = srm_env_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = srm_env_proc_show, .proc_write = srm_env_proc_write, };
arch/arm/mm/alignment.c +1 -9
@@ -144,11 +144,6 @@ static int alignment_proc_show(struct seq_file *m, void *v)return 0 ; } -static int alignment_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, alignment_proc_show,NULL ); -} -static ssize_t alignment_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *pos) {
@@ -164,10 +159,7 @@ static ssize_t alignment_proc_write(struct file *file, const char __user *buffer }static const struct proc_ops alignment_proc_ops = { - .proc_open = alignment_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = alignment_proc_show, .proc_write = alignment_proc_write, };#endif /* CONFIG_PROC_FS */
arch/powerpc/kernel/rtas-proc.c +5 -45
74 lines not on this page · raw message
arch/powerpc/platforms/pseries/lpar.c +2 -18
@@ -584,17 +584,9 @@ static int vcpudispatch_stats_display(struct seq_file *p, void *v)return 0 ; } -static int vcpudispatch_stats_open (struct inode *inode,struct file *file) -{ -return single_open (file, vcpudispatch_stats_display,NULL ); -} -static const struct proc_ops vcpudispatch_stats_proc_ops = { - .proc_open = vcpudispatch_stats_open, - .proc_read = seq_read, + .proc_show = vcpudispatch_stats_display, .proc_write = vcpudispatch_stats_write, - .proc_lseek = seq_lseek, - .proc_release = single_release, }; static ssize_t vcpudispatch_stats_freq_write(struct file *file,
@@ -628,17 +620,9 @@ static int vcpudispatch_stats_freq_display(struct seq_file *p, void *v)return 0 ; } -static int vcpudispatch_stats_freq_open (struct inode *inode,struct file *file) -{ -return single_open (file, vcpudispatch_stats_freq_display,NULL ); -} -static const struct proc_ops vcpudispatch_stats_freq_proc_ops = { - .proc_open = vcpudispatch_stats_freq_open, - .proc_read = seq_read, + .proc_show = vcpudispatch_stats_freq_display, .proc_write = vcpudispatch_stats_freq_write, - .proc_lseek = seq_lseek, - .proc_release = single_release, };static int __init vcpudispatch_stats_procfs_init (void )
arch/powerpc/platforms/pseries/lparcfg.c +1 -9
@@ -798,17 +798,9 @@ static int lparcfg_data(struct seq_file *m, void *v)return pseries_lparcfg_data (m, v); } -static int lparcfg_open (struct inode *inode,struct file *file) -{ -return single_open (file, lparcfg_data,NULL ); -} -static const struct proc_ops lparcfg_proc_ops = { - .proc_read = seq_read, .proc_write = lparcfg_write, - .proc_open = lparcfg_open, - .proc_release = single_release, - .proc_lseek = seq_lseek, + .proc_show = lparcfg_data, };static int __init lparcfg_init (void )
arch/sh/mm/alignment.c +1 -9
@@ -132,11 +132,6 @@ static int alignment_proc_show(struct seq_file *m, void *v)return 0 ; } -static int alignment_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, alignment_proc_show,NULL ); -} -static ssize_t alignment_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *pos) {
@@ -153,10 +148,7 @@ static ssize_t alignment_proc_write(struct file *file, }static const struct proc_ops alignment_proc_ops = { - .proc_open = alignment_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = alignment_proc_show, .proc_write = alignment_proc_write, };
arch/sparc/kernel/led.c +1 -9
@@ -60,11 +60,6 @@ static int led_proc_show(struct seq_file *m, void *v)return 0 ; } -static int led_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, led_proc_show,NULL ); -} -static ssize_t led_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *ppos) {
@@ -106,10 +101,7 @@ static ssize_t led_proc_write(struct file *file, const char __user *buffer, }static const struct proc_ops led_proc_ops = { - .proc_open = led_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = led_proc_show, .proc_write = led_proc_write, };#endif
arch/um/kernel/exitcode.c +1 -9
@@ -31,11 +31,6 @@ static int exitcode_proc_show(struct seq_file *m, void *v)return 0 ; } -static int exitcode_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, exitcode_proc_show,NULL ); -} -static ssize_t exitcode_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *pos) {
@@ -57,10 +52,7 @@ static ssize_t exitcode_proc_write(struct file *file, }static const struct proc_ops exitcode_proc_ops = { - .proc_open = exitcode_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = exitcode_proc_show, .proc_write = exitcode_proc_write, };
drivers/acpi/proc.c +1 -11
@@ -121,19 +121,9 @@ acpi_system_write_wakeup_device(struct file *file,return count; } -static int -acpi_system_wakeup_device_open_fs (struct inode *inode,struct file *file) -{ -return single_open (file, acpi_system_wakeup_device_seq_show, -pde_data (inode)); -} -static const struct proc_ops acpi_system_wakeup_device_proc_ops = { - .proc_open = acpi_system_wakeup_device_open_fs, - .proc_read = seq_read, + .proc_show = acpi_system_wakeup_device_seq_show, .proc_write = acpi_system_write_wakeup_device, - .proc_lseek = seq_lseek, - .proc_release = single_release, };void __init acpi_sleep_proc_init (void )
drivers/hwmon/dell-smm-hwmon.c +1 -9
@@ -694,16 +694,8 @@ static int i8k_proc_show(struct seq_file *seq, void *offset)return 0 ; } -static int i8k_open_fs (struct inode *inode,struct file *file) -{ -return single_open (file, i8k_proc_show,pde_data (inode)); -} -static const struct proc_ops i8k_proc_ops = { - .proc_open = i8k_open_fs, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = i8k_proc_show, .proc_ioctl = i8k_ioctl, };
drivers/input/input.c +4 -20
@@ -1218,18 +1218,10 @@ static const struct seq_operations input_devices_seq_ops = { .show = input_devices_seq_show, }; -static int input_proc_devices_open (struct inode *inode,struct file *file) -{ -return seq_open_private (file, &input_devices_seq_ops, -sizeof (struct input_seq_state )); -} -static const struct proc_ops input_devices_proc_ops = { - .proc_open = input_proc_devices_open, + .proc_seq_ops = &input_devices_seq_ops, + .proc_state_size =sizeof (struct input_seq_state ), .proc_poll = input_proc_devices_poll, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = seq_release_private, };static void *input_handlers_seq_start (struct seq_file *seq,loff_t *pos)
@@ -1279,17 +1271,9 @@ static const struct seq_operations input_handlers_seq_ops = { .show = input_handlers_seq_show, }; -static int input_proc_handlers_open (struct inode *inode,struct file *file) -{ -return seq_open_private (file, &input_handlers_seq_ops, -sizeof (struct input_seq_state )); -} -static const struct proc_ops input_handlers_proc_ops = { - .proc_open = input_proc_handlers_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = seq_release_private, + .proc_seq_ops = &input_handlers_seq_ops, + .proc_state_size =sizeof (struct input_seq_state ), };static int __init input_proc_init (void )
drivers/macintosh/via-pmu.c +1 -9
@@ -912,11 +912,6 @@ static int pmu_options_proc_show(struct seq_file *m, void *v)return 0 ; } -static int pmu_options_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, pmu_options_proc_show,NULL ); -} -static ssize_t pmu_options_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *pos) {
@@ -962,10 +957,7 @@ static ssize_t pmu_options_proc_write(struct file *file, }static const struct proc_ops pmu_options_proc_ops = { - .proc_open = pmu_options_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = pmu_options_proc_show, .proc_write = pmu_options_proc_write, };#endif
drivers/net/wireless/intel/ipw2x00/libipw_module.c +1 -9
@@ -215,11 +215,6 @@ static int debug_level_proc_show(struct seq_file *m, void *v)return 0 ; } -static int debug_level_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, debug_level_proc_show,NULL ); -} -static ssize_t debug_level_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *pos) {
@@ -240,10 +235,7 @@ static ssize_t debug_level_proc_write(struct file *file, }static const struct proc_ops debug_level_proc_ops = { - .proc_open = debug_level_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = debug_level_proc_show, .proc_write = debug_level_proc_write, };#endif /* CONFIG_LIBIPW_DEBUG */
drivers/platform/x86/lenovo/thinkpad_acpi.c +1 -9
@@ -880,11 +880,6 @@ static int dispatch_proc_show(struct seq_file *m, void *v)return ibm->read (m); } -static int dispatch_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, dispatch_proc_show,pde_data (inode)); -} -static ssize_t dispatch_proc_write (struct file *file,const char __user *userbuf,size_t count,loff_t *pos)
@@ -911,10 +906,7 @@ static ssize_t dispatch_proc_write(struct file *file, }static const struct proc_ops dispatch_proc_ops = { - .proc_open = dispatch_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = dispatch_proc_show, .proc_write = dispatch_proc_write, };
drivers/platform/x86/toshiba_acpi.c +4 -36
@@ -1448,11 +1448,6 @@ static int lcd_proc_show(struct seq_file *m, void *v)return 0 ; } -static int lcd_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, lcd_proc_show,pde_data (inode)); -} -static int set_lcd_brightness (struct toshiba_acpi_dev *dev,int value) {u32 result;
@@ -1509,10 +1504,7 @@ static ssize_t lcd_proc_write(struct file *file, const char __user *buf, }static const struct proc_ops lcd_proc_ops = { - .proc_open = lcd_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = lcd_proc_show, .proc_write = lcd_proc_write, };
@@ -1549,11 +1541,6 @@ static int video_proc_show(struct seq_file *m, void *v)return 0 ; } -static int video_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, video_proc_show,pde_data (inode)); -} -static ssize_t video_proc_write (struct file *file,const char __user *buf,size_t count,loff_t *pos) {
@@ -1614,10 +1601,7 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, }static const struct proc_ops video_proc_ops = { - .proc_open = video_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = video_proc_show, .proc_write = video_proc_write, };
@@ -1660,11 +1644,6 @@ static int fan_proc_show(struct seq_file *m, void *v)return 0 ; } -static int fan_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, fan_proc_show,pde_data (inode)); -} -static ssize_t fan_proc_write (struct file *file,const char __user *buf,size_t count,loff_t *pos) {
@@ -1691,10 +1670,7 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf, }static const struct proc_ops fan_proc_ops = { - .proc_open = fan_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = fan_proc_show, .proc_write = fan_proc_write, };
@@ -1731,11 +1707,6 @@ static int keys_proc_show(struct seq_file *m, void *v)return 0 ; } -static int keys_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, keys_proc_show,pde_data (inode)); -} -static ssize_t keys_proc_write (struct file *file,const char __user *buf,size_t count,loff_t *pos) {
@@ -1758,10 +1729,7 @@ static ssize_t keys_proc_write(struct file *file, const char __user *buf, }static const struct proc_ops keys_proc_ops = { - .proc_open = keys_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = keys_proc_show, .proc_write = keys_proc_write, };
drivers/pnp/pnpbios/proc.c +1 -9
@@ -171,11 +171,6 @@ static int pnpbios_proc_show(struct seq_file *m, void *v)return 0 ; } -static int pnpbios_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, pnpbios_proc_show,pde_data (inode)); -} -static ssize_t pnpbios_proc_write (struct file *file,const char __user *buf,size_t count,loff_t *pos) {
@@ -211,10 +206,7 @@ out: }static const struct proc_ops pnpbios_proc_ops = { - .proc_open = pnpbios_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = pnpbios_proc_show, .proc_write = pnpbios_proc_write, };
drivers/s390/block/dasd_proc.c +1 -9
@@ -246,11 +246,6 @@ static int dasd_stats_proc_show(struct seq_file *m, void *v)return 0 ; } -static int dasd_stats_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, dasd_stats_proc_show,NULL ); -} -static ssize_t dasd_stats_proc_write (struct file *file,const char __user *user_buf,size_t user_len,loff_t *pos) {
@@ -316,10 +311,7 @@ out_error: }static const struct proc_ops dasd_stats_proc_ops = { - .proc_open = dasd_stats_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = dasd_stats_proc_show, .proc_write = dasd_stats_proc_write, };
drivers/s390/cio/blacklist.c +2 -11
@@ -397,18 +397,9 @@ static const struct seq_operations cio_ignore_proc_seq_ops = { .show = cio_ignore_proc_seq_show, }; -static int -cio_ignore_proc_open (struct inode *inode,struct file *file) -{ -return seq_open_private (file, &cio_ignore_proc_seq_ops, -sizeof (struct ccwdev_iter )); -} -static const struct proc_ops cio_ignore_proc_ops = { - .proc_open = cio_ignore_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = seq_release_private, + .proc_seq_ops = &cio_ignore_proc_seq_ops, + .proc_state_size =sizeof (struct ccwdev_iter ), .proc_write = cio_ignore_write, };
drivers/scsi/scsi_devinfo.c +1 -9
@@ -670,11 +670,6 @@ static const struct seq_operations scsi_devinfo_seq_ops = { .show = devinfo_seq_show, }; -static int proc_scsi_devinfo_open (struct inode *inode,struct file *file) -{ -return seq_open (file, &scsi_devinfo_seq_ops); -} -/* * proc_scsi_dev_info_write - allow additions to scsi_dev_info_list via /proc. *
@@ -714,11 +709,8 @@ out: }static const struct proc_ops scsi_devinfo_proc_ops = { - .proc_open = proc_scsi_devinfo_open, - .proc_read = seq_read, + .proc_seq_ops = &scsi_devinfo_seq_ops, .proc_write = proc_scsi_devinfo_write, - .proc_lseek = seq_lseek, - .proc_release = seq_release, };#endif /* CONFIG_SCSI_PROC_FS */
drivers/scsi/scsi_proc.c +1 -20
@@ -521,28 +521,9 @@ static const struct seq_operations scsi_seq_ops = { .show = scsi_seq_show }; -/** -* proc_scsi_open - glue function -* @inode: not used -* @file: passed to single_open() -* -* Associates proc_scsi_show with this file -*/ -static int proc_scsi_open (struct inode *inode,struct file *file) -{ -/* -* We don't really need this for the write case but it doesn't -* harm either. -*/ -return seq_open (file, &scsi_seq_ops); -} -static const struct proc_ops scsi_scsi_proc_ops = { - .proc_open = proc_scsi_open, - .proc_read = seq_read, + .proc_seq_ops = &scsi_seq_ops, .proc_write = proc_scsi_write, - .proc_lseek = seq_lseek, - .proc_release = seq_release, };/**
drivers/usb/gadget/function/rndis.c +1 -9
@@ -1183,16 +1183,8 @@ static ssize_t rndis_proc_write(struct file *file, const char __user *buffer,return count; } -static int rndis_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, rndis_proc_show,pde_data (inode)); -} -static const struct proc_ops rndis_proc_ops = { - .proc_open = rndis_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = rndis_proc_show, .proc_write = rndis_proc_write, };
drivers/video/fbdev/via/viafbdev.c +7 -63
154 lines not on this page · raw message
fs/jfs/jfs_debug.c +1 -9
@@ -23,11 +23,6 @@ static int jfs_loglevel_proc_show(struct seq_file *m, void *v)return 0 ; } -static int jfs_loglevel_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, jfs_loglevel_proc_show,NULL ); -} -static ssize_t jfs_loglevel_proc_write (struct file *file,const char __user *buffer,size_t count,loff_t *ppos) {
@@ -44,10 +39,7 @@ static ssize_t jfs_loglevel_proc_write(struct file *file, }static const struct proc_ops jfs_loglevel_proc_ops = { - .proc_open = jfs_loglevel_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = jfs_loglevel_proc_show, .proc_write = jfs_loglevel_proc_write, };#endif
fs/ntfs3/super.c +2 -18
@@ -524,11 +524,6 @@ static int ntfs3_volinfo(struct seq_file *m, void *o)return 0 ; } -static int ntfs3_volinfo_open (struct inode *inode,struct file *file) -{ -return single_open (file, ntfs3_volinfo,pde_data (inode)); -} -/* read /proc/fs/ntfs3/<dev>/label */ static int ntfs3_label_show (struct seq_file *m,void *o) {
@@ -574,23 +569,12 @@ out:return ret; } -static int ntfs3_label_open (struct inode *inode,struct file *file) -{ -return single_open (file, ntfs3_label_show,pde_data (inode)); -} -static const struct proc_ops ntfs3_volinfo_fops = { - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, - .proc_open = ntfs3_volinfo_open, + .proc_show = ntfs3_volinfo, };static const struct proc_ops ntfs3_label_fops = { - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, - .proc_open = ntfs3_label_open, + .proc_show = ntfs3_label_show, .proc_write = ntfs3_label_write, };
fs/proc/cpuinfo.c +1 -9
@@ -7,17 +7,9 @@extern const struct seq_operations cpuinfo_op; -static int cpuinfo_open (struct inode *inode,struct file *file) -{ -return seq_open (file, &cpuinfo_op); -} -static const struct proc_ops cpuinfo_proc_ops = { .proc_flags =PROC_ENTRY_PERMANENT , - .proc_open = cpuinfo_open, - .proc_read_iter = seq_read_iter, - .proc_lseek = seq_lseek, - .proc_release = seq_release, + .proc_seq_ops = &cpuinfo_op, };static int __init proc_cpuinfo_init (void )
fs/smb/client/cifs_debug.c +8 -71
165 lines not on this page · raw message
fs/smb/client/dfs_cache.c +1 -9
@@ -213,16 +213,8 @@ static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer,return count; } -static int dfscache_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, dfscache_proc_show,NULL ); -} -const struct proc_ops dfscache_proc_ops = { - .proc_open = dfscache_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = dfscache_proc_show, .proc_write = dfscache_proc_write, };
kernel/irq/proc.c +3 -27
@@ -188,29 +188,13 @@ static ssize_t irq_affinity_list_proc_write(struct file *file,return write_irq_affinity (1 , file, buffer, count, pos); } -static int irq_affinity_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, irq_affinity_proc_show,pde_data (inode)); -} - -static int irq_affinity_list_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, irq_affinity_list_proc_show,pde_data (inode)); -} -static const struct proc_ops irq_affinity_proc_ops = { - .proc_open = irq_affinity_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = irq_affinity_proc_show, .proc_write = irq_affinity_proc_write, };static const struct proc_ops irq_affinity_list_proc_ops = { - .proc_open = irq_affinity_list_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = irq_affinity_list_proc_show, .proc_write = irq_affinity_list_proc_write, };
@@ -263,16 +247,8 @@ out:return err; } -static int default_affinity_open (struct inode *inode,struct file *file) -{ -return single_open (file, default_affinity_show,pde_data (inode)); -} -static const struct proc_ops default_affinity_proc_ops = { - .proc_open = default_affinity_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = default_affinity_show, .proc_write = default_affinity_write, };
kernel/latencytop.c +1 -9
@@ -275,17 +275,9 @@ lstats_write(struct file *file, const char __user *buf, size_t count,return count; } -static int lstats_open (struct inode *inode,struct file *filp) -{ -return single_open (filp, lstats_show,NULL ); -} -static const struct proc_ops lstats_proc_ops = { - .proc_open = lstats_open, - .proc_read = seq_read, + .proc_show = lstats_show, .proc_write = lstats_write, - .proc_lseek = seq_lseek, - .proc_release = single_release, };static int __init init_lstats_procfs (void )
lib/dynamic_debug.c +2 -4
@@ -1194,10 +1194,8 @@ static const struct file_operations ddebug_proc_fops = { };static const struct proc_ops proc_fops = { - .proc_open = ddebug_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = seq_release_private, + .proc_seq_ops = &ddebug_proc_seqops, + .proc_state_size =sizeof (struct ddebug_iter ), .proc_write = ddebug_proc_write };
mm/slab_common.c +1 -9
@@ -1233,17 +1233,9 @@ static const struct seq_operations slabinfo_op = { .show = slab_show, }; -static int slabinfo_open (struct inode *inode,struct file *file) -{ -return seq_open (file, &slabinfo_op); -} -static const struct proc_ops slabinfo_proc_ops = { .proc_flags =PROC_ENTRY_PERMANENT , - .proc_open = slabinfo_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = seq_release, + .proc_seq_ops = &slabinfo_op, };static int __init slab_proc_init (void )
net/core/pktgen.c +3 -27
@@ -550,17 +550,9 @@ static ssize_t pgctrl_write(struct file *file, const char __user *buf,return count; } -static int pgctrl_open (struct inode *inode,struct file *file) -{ -return single_open (file, pgctrl_show,pde_data (inode)); -} -static const struct proc_ops pktgen_proc_ops = { - .proc_open = pgctrl_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, + .proc_show = pgctrl_show, .proc_write = pgctrl_write, - .proc_release = single_release, };static int pktgen_if_show (struct seq_file *seq,void *v)
@@ -1870,17 +1862,9 @@ static ssize_t pktgen_if_write(struct file *file,return -EINVAL ; } -static int pktgen_if_open (struct inode *inode,struct file *file) -{ -return single_open (file, pktgen_if_show,pde_data (inode)); -} -static const struct proc_ops pktgen_if_proc_ops = { - .proc_open = pktgen_if_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, + .proc_show = pktgen_if_show, .proc_write = pktgen_if_write, - .proc_release = single_release, };static int pktgen_thread_show (struct seq_file *seq,void *v)
@@ -2010,17 +1994,9 @@ out:return ret; } -static int pktgen_thread_open (struct inode *inode,struct file *file) -{ -return single_open (file, pktgen_thread_show,pde_data (inode)); -} -static const struct proc_ops pktgen_thread_proc_ops = { - .proc_open = pktgen_thread_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, + .proc_show = pktgen_thread_show, .proc_write = pktgen_thread_write, - .proc_release = single_release, };/* Think find or remove for NN */
net/sunrpc/stats.c +1 -9
@@ -64,16 +64,8 @@ static int rpc_proc_show(struct seq_file *seq, void *v) {return 0 ; } -static int rpc_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, rpc_proc_show,pde_data (inode)); -} -static const struct proc_ops rpc_proc_ops = { - .proc_open = rpc_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = rpc_proc_show, };/*
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 07/16] treewide: let procfs own the seq_file of simple proc entries
Sashiko has reviewed this patch and found no issues.
Quoted 20 lines
Most proc entries that bring their own struct
proc_opsonly callsingle_open(),seq_open()orseq_open_private()in ->proc_open() and free theseq_filewith the matching helper. Declare the show function or theseq_operationsinstead and let procfs open and free theseq_file. Reading these files then no longer holds up their removal.Done with the following semantic patch. The new initializers were aligned like their neighbours afterwards:
// SPDX-License-Identifier: GPL-2.0-only
/// Let procfs open theseq_fileof proc entries whose ->proc_open() only /// callssingle_open(),seq_open()orseq_open_private().
//
// Confidence: High
// Options: --no-includes --include-headers
[ ... ]
identifieropen_fn= {single.open_fn, seq.open_fn, priv.open_fn}; @@
- static intopen_fn(...){ ... }
Quoted 1 line
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
Christian Brauner
maintainerRFC PATCH 8/16 selftests/proc: check that a faulting reader doesn't hold up removal
In reply to Christian BraunerRead /proc/net/dev_snmp6/<dev> of a tun device into a buffer whose first page is backed by userfaultfd and remove the device while the reader sleeps in its copy to userspace:
- removing the entry doesn't wait for the reader
- the reader still gets what was printed before the removal
- and the next read fails with -EIO
The entry is removed under RTNL, so a regression stalls the test for five seconds before it fails.
--- .../selftests/proc/proc-remove-slow-reader.c | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+)
tools/testing/selftests/proc/proc-remove-slow-reader.c new file +133 -0
@@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* A read that faults while copying to userspace must not hold up removal of +* the /proc entry. /proc/net/dev_snmp6/<dev> of a tun device is removed under +* RTNL when the device's file is closed. +*/ +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <linux/if.h> +#include <linux/if_tun.h> +#include <linux/userfaultfd.h> +#include <poll.h> +#include <pthread.h> +#include <sched.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "../kselftest_harness.h" + +#define TUN_NAME "procslow0" +#define SNMP6_PATH "/proc/net/dev_snmp6/" TUN_NAME +#define READ_PAGES 16 +#define TIMEOUT_MS 5000 + +struct reader { +int fd; +char *buf; +size_t len; +ssize_t ret; +}; + +struct closer { +int tun; +int done; +}; + +static void *read_thread (void *arg) +{ +struct reader *r = arg; + + r->ret =read (r->fd, r->buf, r->len); +return NULL ; +} + +static void *close_thread (void *arg) +{ +struct closer *c = arg; +ssize_t ret; + +close (c->tun); + ret =write (c->done,"x" ,1 ); +return (void *)ret; +} + +TEST (remove_while_reader_faults) +{ +struct uffdio_api api = { .api =UFFD_API }; +struct uffdio_register reg = {}; +struct uffdio_zeropage zero = {}; +long psz =sysconf (_SC_PAGESIZE); +struct ifreq ifr = {}; +struct reader r = {}; +struct closer c = {}; +struct uffd_msg msg; +struct pollfd pfd; +pthread_t rt, ct; +int uffd, done[2 ]; +char *buf; + +if (unshare (CLONE_NEWNET )) +SKIP (return,"unshare(CLONE_NEWNET): %s" ,strerror (errno)); + + c.tun =open ("/dev/net/tun" ,O_RDWR |O_CLOEXEC ); +if (c.tun <0 ) +SKIP (return,"/dev/net/tun: %s" ,strerror (errno)); +strncpy (ifr.ifr_name,TUN_NAME ,IFNAMSIZ -1 ); + ifr.ifr_flags =IFF_TUN |IFF_NO_PI ; +ASSERT_EQ (ioctl (c.tun,TUNSETIFF , &ifr),0 ); + + r.fd =open (SNMP6_PATH ,O_RDONLY |O_CLOEXEC ); +if (r.fd <0 ) +SKIP (return,SNMP6_PATH ": %s" ,strerror (errno)); + + uffd =syscall (__NR_userfaultfd,O_CLOEXEC |O_NONBLOCK ); +if (uffd <0 ) +SKIP (return,"userfaultfd: %s" ,strerror (errno)); +ASSERT_EQ (ioctl (uffd,UFFDIO_API , &api),0 ); + +/* Only the first page of the buffer goes through userfaultfd. */ + buf =mmap (NULL ,READ_PAGES * psz,PROT_READ |PROT_WRITE , +MAP_PRIVATE |MAP_ANONYMOUS ,-1 ,0 ); +ASSERT_NE (buf,MAP_FAILED ); + reg.range.start = (unsigned long )buf; + reg.range.len = psz; + reg.mode =UFFDIO_REGISTER_MODE_MISSING ; +ASSERT_EQ (ioctl (uffd,UFFDIO_REGISTER , ®),0 ); + + r.buf = buf; + r.len =READ_PAGES * psz; +ASSERT_EQ (pthread_create (&rt,NULL , read_thread, &r),0 ); + +/* The reader now sleeps in its copy to userspace. */ + pfd = (struct pollfd ){ .fd = uffd, .events =POLLIN }; +ASSERT_EQ (poll (&pfd,1 ,TIMEOUT_MS ),1 ); +ASSERT_EQ (read (uffd, &msg,sizeof (msg)),sizeof (msg)); +ASSERT_EQ (msg.event,UFFD_EVENT_PAGEFAULT ); + +/* Closing the device removes the file that is being read. */ +ASSERT_EQ (pipe2 (done,O_CLOEXEC ),0 ); + c.done = done[1 ]; +ASSERT_EQ (pthread_create (&ct,NULL , close_thread, &c),0 ); + pfd = (struct pollfd ){ .fd = done[0 ], .events =POLLIN }; + EXPECT_EQ(poll(&pfd, 1, TIMEOUT_MS), 1) { +TH_LOG ("removing " SNMP6_PATH " waited for the reader" ); + } + + zero.range.start = (unsigned long )buf; + zero.range.len = psz; +ASSERT_EQ (ioctl (uffd,UFFDIO_ZEROPAGE , &zero),0 ); +ASSERT_EQ (pthread_join (rt,NULL ),0 ); +ASSERT_EQ (pthread_join (ct,NULL ),0 ); + +/* What was printed before the removal still arrives, then nothing. */ +EXPECT_GT (r.ret,0 ); +EXPECT_EQ (read (r.fd, buf, psz),-1 ); +EXPECT_EQ (errno,EIO ); +} + +TEST_HARNESS_MAIN
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 08/16] selftests/proc: check that a faulting reader doesn't hold up removal
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] selftests/proc: Makefile and .gitignore entries in wrong commit --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] selftests/proc: Makefile and .gitignore entries in wrong commit
--
commit 9de4ed28fcd6656e6c6c2bb55f1514d9aa6600f6
Author: Christian Brauner <[email protected]>
selftests/proc: check that a faulting reader doesn't hold up removal
This commit adds a selftest in tools/testing/selftests/proc/proc-remove-slow-reader.c that verifies removing a /proc/net/dev_snmp6/<dev> entry does not block when a concurrent reader faults while copying to userspace via userfaultfd.
Quoted 6 lines
tools/testing/selftests/proc/proc-remove-slow-reader.c new file +0
-0@@ -0,0 +1,133 @@
[ ... ]
tools/testing/selftests/proc/proc-remove-slow-reader.c line 60
Quoted 2 lines
tools/testing/selftests/proc/proc-remove-slow-reader.c new file +2
-0+TEST (remove_while_reader_faults) +{
[Severity: Low]
Should this commit also include the corresponding tools/testing/selftests/proc/Makefile and tools/testing/selftests/proc/.gitignore entries for proc-remove-slow-reader?
In the current series, those entries:
tools/testing/selftests/proc/Makefile:
TEST_GEN_PROGS += proc-remove-slow-reader
tools/testing/selftests/proc/.gitignore:
/proc-remove-slow-reader
were placed in the earlier commit ("selftests/proc: check that a faulting readlink() doesn't hold up removal") before proc-remove-slow-reader.c exists. Building tools/testing/selftests/proc at that earlier commit or any intermediate commit before this one fails due to the missing source file, breaking bisection, and cherry-picking this commit on its own leaves the new test unbuilt.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
Christian Brauner
maintainerRe: [PATCH RFC 08/16] selftests/proc: check that a faulting reader doesn't hold up removal
Quoted 1 line · On Tue, Nov 03, 2026 at 03:01:17PM +0000, [email protected] wrote:
- [Low] selftests/proc:
Makefileand .gitignore entries in wrong commit
Fixed.
Christian Brauner
maintainerRFC PATCH 9/16 seq_file: add seq_preallocate()
In reply to Christian Braunersingle_open_size() allocates the buffer itself because it does the single_open() too. A file whose seq_file is opened by somebody else, like procfs does now, needs to do that after the fact. Add seq_preallocate() for that and use it in single_open_size().
No functional changes.
--- fs/seq_file.c | 36 ++++++++++++++++++++++++++++-------- include/linux/seq_file.h | 1 + 2 files changed, 29 insertions(+), 8 deletions(-)
fs/seq_file.c +28 -8
@@ -590,21 +590,41 @@ int single_open(struct file *file, int (*show)(struct seq_file *, void *), }EXPORT_SYMBOL (single_open); -int single_open_size (struct file *file,int (*show)(struct seq_file *,void *), -void *data,size_t size) +/** +* seq_preallocate - start out with a buffer of @size +* @m: the seq_file, before it is first read +* @size: the size of the buffer +* +* For files whose records don't fit into a page, so that the first read +* doesn't have to work its way up to the right buffer size. +*/ +int seq_preallocate (struct seq_file *m,size_t size) {char *buf =seq_buf_alloc (size); -int ret; +if (!buf)return -ENOMEM ; +kvfree (m->buf); + m->buf = buf; + m->size = size; +return 0 ; +} +EXPORT_SYMBOL (seq_preallocate); + +int single_open_size (struct file *file,int (*show)(struct seq_file *,void *), +void *data,size_t size) +{ +int ret; + ret =single_open (file, show, data); -if (ret) { -kvfree (buf); +if (ret)return ret; + ret =seq_preallocate (file->private_data, size); +if (ret) { +single_release (file_inode (file), file); + file->private_data =NULL ; } - ((struct seq_file *)file->private_data)->buf = buf; - ((struct seq_file *)file->private_data)->size = size; -return 0 ; +return ret; }EXPORT_SYMBOL (single_open_size);
include/linux/seq_file.h +1 -0
@@ -175,6 +175,7 @@ int seq_path_root(struct seq_file *m, const struct path *path,void *single_start (struct seq_file *,loff_t *);int single_open (struct file *,int (*)(struct seq_file *,void *),void *);int single_open_size (struct file *,int (*)(struct seq_file *,void *),void *,size_t ); +int seq_preallocate (struct seq_file *m,size_t size);int single_release (struct inode *,struct file *);void *__seq_open_private (struct file *,const struct seq_operations *,int );int seq_open_private (struct file *,const struct seq_operations *,int );
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 09/16] seq_file: add seq_preallocate()
Sashiko has reviewed this patch and found no issues.
Quoted 8 lines
single_open_size()allocates the buffer itself because it does thesingle_open()too. A file whoseseq_fileis opened by somebody else, like procfs does now, needs to do that after the fact. Addseq_preallocate()for that and use it insingle_open_size().No functional changes.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Christian Brauner
maintainerRFC PATCH 10/16 proc: let procfs own the seq_file of /proc/stat
In reply to Christian BraunerDeclare the show function and let procfs open and free the seq_file. ->proc_open() only preallocates the buffer now.
--- fs/proc/stat.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
fs/proc/stat.c +2 -4
@@ -161,15 +161,13 @@ static int stat_open(struct inode *inode, struct file *file)/* minimum size to display an interrupt count : 2 bytes */ size +=2 *irq_get_nr_irqs (); -return single_open_size (file, show_stat,NULL , size); +return seq_preallocate (file->private_data, size); }static const struct proc_ops stat_proc_ops = { .proc_flags =PROC_ENTRY_PERMANENT , + .proc_show = show_stat, .proc_open = stat_open, - .proc_read_iter = seq_read_iter, - .proc_lseek = seq_lseek, - .proc_release = single_release, };static int __init proc_stat_init (void )
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 10/16] proc: let procfs own the seq_file of /proc/stat
Sashiko has reviewed this patch and found no issues.
Quoted 4 lines
Declare the show function and let procfs open and free the
seq_file. ->proc_open() only preallocates the buffer now.Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Christian Brauner
maintainerRFC PATCH 11/16 proc: let procfs copy in writes before pinning the entry
In reply to Christian Braunerproc_reg_write() pins the entry and calls ->proc_write() which copies from userspace itself. A page fault in that copy can take as long as the writer wants, just like the copy to userspace of a read, and remove_proc_entry() waits for it.
So do what kernfs does for writes as well. An entry that implements ->proc_write_buf() gets a NUL-terminated kernel copy of what was written. procfs makes that copy before it pins the entry. The copy is capped at ->proc_write_size bytes, or at PAGE_SIZE - 1 like proc_simple_write() if the entry doesn't care. Larger writes fail with -EINVAL. The proc_ops go away with the entry, so procfs remembers the limit in the entry when it is registered, like the size of the seq_file state, instead of pinning the entry just to look up a number.
Entries that take binary data in pieces, like the PCI config space, ALSA's data entries or a firmware image, can ask for PROC_ENTRY_SHORT_WRITES instead. procfs then copies in the first ->proc_write_size bytes and the write comes up short.
->proc_write_buf() gets the position like ->proc_write() so entries that write at an offset keep working.
Note that procfs copies in the whole write before the entry looks at any of it. So a write that is too large or whose buffer can't be read fails before the entry's own checks run, permission checks included, even where the entry only ever looked at the first few bytes.
An entry that implements ->proc_write_buf() and still has a ->proc_write() fails to register.
No users yet.
--- fs/proc/generic.c | 8 ++++++++ fs/proc/inode.c | 33 +++++++++++++++++++++++++++++++++ fs/proc/internal.h | 11 +++++++++++ include/linux/proc_fs.h | 17 +++++++++++++++++ 4 files changed, 69 insertions(+)
fs/proc/generic.c +8 -0
@@ -468,6 +468,14 @@ static int pde_set_flags(struct proc_dir_entry *pde)if (proc_ops->proc_lseek) pde->flags |= PROC_ENTRY_proc_lseek; } +if (proc_ops->proc_write_buf) { +if (WARN_ON_ONCE (proc_ops->proc_write)) +return -EINVAL ; + pde->flags |= PROC_ENTRY_proc_write_buf; + pde->write_size = proc_ops->proc_write_size ?:PAGE_SIZE -1 ; +if (proc_ops->proc_flags &PROC_ENTRY_SHORT_WRITES ) + pde->flags |=PROC_ENTRY_SHORT_WRITES ; + }return 0 ; }
fs/proc/inode.c +33 -0
@@ -440,11 +440,44 @@ static ssize_t pde_write(struct proc_dir_entry *pde, struct file *file, const chreturn -EIO ; } +/* +* procfs copies in what is written before it pins the entry. A writer that +* faults on its buffer then doesn't hold up remove_proc_entry(). +*/ +static ssize_t pde_write_buf (struct proc_dir_entry *pde,struct file *file, +const char __user *ubuf,size_t count,loff_t *ppos) +{ + char *buf __free(kvfree) = NULL; +size_t size; +ssize_t rv; + + size = pde->write_size; +if (count > size) { +if (!pde_has_short_writes (pde)) +return -EINVAL ; + count = size; + } + buf =kvmalloc (count +1 ,GFP_KERNEL ); +if (!buf) +return -ENOMEM ; +if (copy_from_user (buf, ubuf, count)) +return -EFAULT ; + buf[count] ='\0' ; + +if (!pde_pin (pde)) +return -EIO ; + rv = pde->proc_ops->proc_write_buf (file, buf, count, ppos); +pde_unpin (pde); +return rv; +} +static ssize_t proc_reg_write (struct file *file,const char __user *buf,size_t count,loff_t *ppos) {struct proc_dir_entry *pde =PDE (file_inode (file));ssize_t rv; +if (pde_has_proc_write_buf (pde)) +return pde_write_buf (pde, file, buf, count, ppos);if (!pde_pin (pde))return -EIO ; rv =pde_write (pde, file, buf, count, ppos);
fs/proc/internal.h +11 -0
@@ -51,6 +51,7 @@ struct proc_dir_entry {proc_write_t write;void *data;unsigned int state_size; +unsigned int write_size;unsigned int low_ino;nlink_t nlink;kuid_t uid;
@@ -107,6 +108,16 @@ static inline bool pde_has_proc_show(const struct proc_dir_entry *pde)return pde->flags & PROC_ENTRY_proc_show; } +static inline bool pde_has_proc_write_buf (const struct proc_dir_entry *pde) +{ +return pde->flags & PROC_ENTRY_proc_write_buf; +} + +static inline bool pde_has_short_writes (const struct proc_dir_entry *pde) +{ +return pde->flags &PROC_ENTRY_SHORT_WRITES ; +} +/* procfs owns the seq_file, see pde_seq_ops */ static inline bool pde_is_seq_file (const struct proc_dir_entry *pde) {
include/linux/proc_fs.h +17 -0
@@ -31,6 +31,13 @@ enum { PROC_ENTRY_proc_show =1U <<5 ,PROC_ENTRY_FORCE_LOOKUP =1U <<7 , + PROC_ENTRY_proc_write_buf =1U <<8 , + +/* +* Writes larger than ->proc_write_size come up short instead of +* failing with -EINVAL. +*/ + PROC_ENTRY_SHORT_WRITES = 1U << 9, }; struct proc_ops {
@@ -67,6 +74,16 @@ struct proc_ops {const struct seq_operations *proc_seq_ops;int (*proc_show)(struct seq_file *,void *);unsigned int proc_state_size; +/* +* procfs copies what is written into a kernel buffer and calls +* ->proc_write_buf() with it. The buffer is NUL-terminated and holds +* at most ->proc_write_size bytes, PAGE_SIZE - 1 if that's 0. Larger +* writes fail with -EINVAL unless the entry asks for +* PROC_ENTRY_SHORT_WRITES. The position is NULL for a stream, like +* for ->write(). +*/ +ssize_t (*proc_write_buf)(struct file *,char *,size_t ,loff_t *); +unsigned int proc_write_size; } __randomize_layout;/* definitions for hide_pid field */
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 11/16] proc: let procfs copy in writes before pinning the entry
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] proc: split __free(kvfree) declaration and initialization in pde_write_buf() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] proc: split __free(kvfree) declaration and initialization in pde_write_buf() --
commit d672761b59bb5333b9344e6a78cc97135a5b70b1
Author: Christian Brauner <[email protected]>
proc: let procfs copy in writes before pinning the entry
This commit introduces ->proc_write_buf() in struct proc_ops so procfs can copy user write buffers into a NUL-terminated kernel buffer before pinning the proc_dir_entry, preventing page faults during user copies from blocking remove_proc_entry().
Quoted 16 lines
fs/proc/inode.c +8
-0@@ -440,11 +440,44 @@ static ssize_t pde_write(struct proc_dir_entry *pde, struct file *file, const chreturn -EIO ; } +/* +* procfs copies in what is written before it pins the entry. A writer that +* faults on its buffer then doesn't hold up remove_proc_entry(). +*/ +static ssize_t pde_write_buf (struct proc_dir_entry *pde,struct file *file, +const char __user *ubuf,size_t count,loff_t *ppos) +{ + char *buf __free(kvfree) = NULL;
fs/proc/inode.c static ssize_t pde_write(struct proc_dir_entry *pde, struct file *file, const ch · line 460
Quoted 10 lines
fs/proc/inode.c +10
-0+size_t size; +ssize_t rv; + + size = pde->write_size; +if (count > size) { +if (!pde_has_short_writes (pde)) +return -EINVAL ; + count = size; + } + buf =kvmalloc (count +1 ,GFP_KERNEL );
[Severity: Low]
This isn't a bug, but in pde_write_buf() (called via proc_reg_write()), buf is declared with __free(kvfree) = NULL at the top of the function and assigned later after the count > size check.
Per the Cleanup API conventions, could buf be defined and initialized in a single statement at the point of allocation:
char *buf __free(kvfree) = kvmalloc(count + 1, GFP_KERNEL);
after the size check?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
Christian Brauner
maintainerRe: [PATCH RFC 11/16] proc: let procfs copy in writes before pinning the entry
Quoted 1 line · On Tue, Nov 03, 2026 at 03:01:26PM +0000, [email protected] wrote:
- [Low] proc: split
__free(kvfree)declaration and initialization inpde_write_buf()
Intentional.
Christian Brauner
maintainerRFC PATCH 12/16 proc: copy in writes to the network namespace entries before pinning
In reply to Christian Braunerproc_simple_write() already hands ->write() a kernel copy of what was written but makes that copy with the entry pinned. Let procfs make it before it pins the entry instead. That covers everything created with proc_create_net_data_write() and proc_create_net_single_write(), the afs files and the rxrpc stats.
So a write to a proc_create_net_single() file without ->write() can fail with -EINVAL or -EFAULT now before proc_simple_write() gets to refuse it with -EACCES.
--- fs/proc/generic.c | 15 ++++----------- fs/proc/internal.h | 2 +- fs/proc/proc_net.c | 4 ++-- 3 files changed, 7 insertions(+), 14 deletions(-)
fs/proc/generic.c +4 -11
@@ -904,26 +904,19 @@ void proc_remove(struct proc_dir_entry *de)EXPORT_SYMBOL (proc_remove);/* -* Pull a user buffer into memory and pass it to the file's write handler if -* one is supplied. The ->write() method is permitted to modify the -* kernel-side buffer. +* Pass what was written to the file's write handler if one is supplied. The +* ->write() method is permitted to modify the buffer. */ -ssize_t proc_simple_write (struct file *f,const char __user *ubuf,size_t size, -loff_t *_pos) +ssize_t proc_simple_write (struct file *f,char *buf,size_t size,loff_t *_pos) {struct proc_dir_entry *pde =PDE (file_inode (f)); -char *buf;int ret;if (!pde->write)return -EACCES ; -if (size ==0 || size >PAGE_SIZE -1 ) +if (size ==0 )return -EINVAL ; - buf =memdup_user_nul (ubuf, size); -if (IS_ERR (buf)) -return PTR_ERR (buf); ret = pde->write (f, buf, size); -kfree (buf);return ret ==0 ? size : ret; }
fs/proc/internal.h +1 -1
@@ -317,7 +317,7 @@ static inline bool is_empty_pde(const struct proc_dir_entry *pde) {return S_ISDIR (pde->mode) && !pde->proc_iops; } -extern ssize_t proc_simple_write (struct file *,const char __user *,size_t ,loff_t *); +extern ssize_t proc_simple_write (struct file *,char *,size_t ,loff_t *);/* * inode.c
fs/proc/proc_net.c +2 -2
@@ -77,7 +77,7 @@ static int seq_release_net(struct inode *ino, struct file *f)static const struct proc_ops proc_net_seq_ops = { .proc_open = seq_open_net, - .proc_write = proc_simple_write, + .proc_write_buf = proc_simple_write, .proc_release = seq_release_net, };
@@ -186,7 +186,7 @@ static int single_release_net(struct inode *ino, struct file *f)static const struct proc_ops proc_net_single_ops = { .proc_open = single_open_net, - .proc_write = proc_simple_write, + .proc_write_buf = proc_simple_write, .proc_release = single_release_net, };
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 12/16] proc: copy in writes to the network namespace entries before pinning
Sashiko has reviewed this patch and found no issues.
Quoted 11 lines
proc_simple_write()already hands ->write() a kernel copy of what was written but makes that copy with the entry pinned. Let procfs make it before it pins the entry instead. That covers everything created withproc_create_net_data_write()andproc_create_net_single_write(), the afs files and the rxrpc stats.So a write to a
proc_create_net_single()file without ->write() can fail with-EINVALor-EFAULTnow beforeproc_simple_write()gets to refuse it with-EACCES.Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
Christian Brauner
maintainerRFC PATCH 13/16 proc: let procfs copy out reads that don't go through seq_file
In reply to Christian Braunerproc_reg_read() and proc_reg_read_iter() pin the entry and call ->proc_read() or ->proc_read_iter() which copy to userspace themselves. Like for seq_file reads and for writes, a reader can make a page fault in that copy take as long as it wants and remove_proc_entry() waits for it.
So do what kernfs does for these reads as well. An entry that implements ->proc_read_buf() fills a kernel buffer of at most ->proc_read_size bytes, a page if the entry doesn't care, and procfs copies it to userspace after unpinning the entry. The entry moves the position like it did for ->proc_read() but that only sticks if the copy to userspace worked, like in kernfs_file_read_iter(). If the copy fails halfway the read fails with -EFAULT and the position stays, where simple_read_from_buffer() returned what it had copied until then. procfs remembers ->proc_read_size in the entry like ->proc_write_size.
Larger reads come up short, like with kernfs. An entry whose readers want all of a large file in one read asks for PROC_ENTRY_FULL_READS and procfs fills the read in pieces of ->proc_read_size, unpinning the entry in between. A piece whose copy fails ends the read with what the pieces before it returned. kexec-tools treats any short read of /proc/vmcore as fatal for example (cf. [1]).
These entries are read through ->read_iter() so readv(), splice(), aio, the RWF flags, kernel_read() and io_uring work for them like for the ->proc_read_iter() ones. io_uring's nonblocking attempt gets -EAGAIN though, like loop_rw_iter() gave it for ->proc_read() entries. Neither the allocation nor the entry can honor IOCB_NOWAIT, and io_uring would take a short read of a record file for an unfinished one and glue the next record to it. So it retries from a worker like before.
An entry that implements ->proc_read_buf() and also declares a seq_file or one of the other read methods fails to register.
No users yet.
[1]: https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git/tree/util_lib/elf_info.c
--- fs/proc/generic.c | 9 ++++++++ fs/proc/inode.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++- fs/proc/internal.h | 11 +++++++++ include/linux/proc_fs.h | 16 +++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-)
fs/proc/generic.c +9 -0
@@ -476,6 +476,15 @@ static int pde_set_flags(struct proc_dir_entry *pde)if (proc_ops->proc_flags &PROC_ENTRY_SHORT_WRITES ) pde->flags |=PROC_ENTRY_SHORT_WRITES ; } +if (proc_ops->proc_read_buf) { +if (WARN_ON_ONCE (pde_is_seq_file (pde) || proc_ops->proc_read || + proc_ops->proc_read_iter)) +return -EINVAL ; + pde->flags |= PROC_ENTRY_proc_read_buf; + pde->read_size = proc_ops->proc_read_size ?:PAGE_SIZE ; +if (proc_ops->proc_flags &PROC_ENTRY_FULL_READS ) + pde->flags |=PROC_ENTRY_FULL_READS ; + }return 0 ; }
fs/proc/inode.c +59 -1
83 lines not on this page · raw message
fs/proc/internal.h +11 -0
@@ -52,6 +52,7 @@ struct proc_dir_entry {void *data;unsigned int state_size;unsigned int write_size; +unsigned int read_size;unsigned int low_ino;nlink_t nlink;kuid_t uid;
@@ -118,6 +119,16 @@ static inline bool pde_has_short_writes(const struct proc_dir_entry *pde)return pde->flags &PROC_ENTRY_SHORT_WRITES ; } +static inline bool pde_has_proc_read_buf (const struct proc_dir_entry *pde) +{ +return pde->flags & PROC_ENTRY_proc_read_buf; +} + +static inline bool pde_has_full_reads (const struct proc_dir_entry *pde) +{ +return pde->flags &PROC_ENTRY_FULL_READS ; +} +/* procfs owns the seq_file, see pde_seq_ops */ static inline bool pde_is_seq_file (const struct proc_dir_entry *pde) {
include/linux/proc_fs.h +16 -0
@@ -38,6 +38,13 @@ enum {* failing with -EINVAL. */ PROC_ENTRY_SHORT_WRITES =1U <<9 , + PROC_ENTRY_proc_read_buf =1U <<10 , + +/* +* Reads larger than ->proc_read_size are filled in pieces instead of +* coming up short. +*/ + PROC_ENTRY_FULL_READS = 1U << 11, }; struct proc_ops {
@@ -84,6 +91,15 @@ struct proc_ops {*/ ssize_t (*proc_write_buf)(struct file *,char *,size_t ,loff_t *);unsigned int proc_write_size; +/* +* procfs calls ->proc_read_buf() to fill a kernel buffer of at most +* ->proc_read_size bytes, PAGE_SIZE if that's 0, and copies it to +* userspace afterwards. Larger reads come up short unless the entry +* asks for PROC_ENTRY_FULL_READS. The position the entry moves to only +* sticks if the copy works. +*/ +ssize_t (*proc_read_buf)(struct file *,char *,size_t ,loff_t *); +unsigned int proc_read_size; } __randomize_layout;/* definitions for hide_pid field */
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 13/16] proc: let procfs copy out reads that don't go through seq_file
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] proc: define and initialize __free(kvfree) variable buf in one statement in pde_read_buf() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] proc: define and initialize __free(kvfree) variable buf in one statement in pde_read_buf() --
commit f2c1eb05c1e48ef170c932c3f77db0e09db3d058
Author: Christian Brauner <[email protected]>
proc: let procfs copy out reads that don't go through seq_file
This commit adds a proc_read_buf callback to struct proc_ops so procfs entries can fill a kernel buffer while pinned and let procfs copy the buffer to userspace after unpinning the entry.
Quoted 4 lines
fs/proc/inode.c +0
-0
[ ... ]
Quoted 15 lines
fs/proc/inode.c +11
-0@@ -400,11 +402,67 @@ static loff_t proc_reg_llseek(struct file *file, loff_t offset, int whence)return rv; } +/* +* The entry fills a kernel buffer and procfs copies that to userspace after +* unpinning the entry. A reader that faults on its buffer then doesn't hold +* up remove_proc_entry(). +*/ +static ssize_t pde_read_buf (struct proc_dir_entry *pde,struct kiocb *iocb, +struct iov_iter *iter) +{ +struct file *file = iocb->ki_filp; + char *buf __free(kvfree) = NULL; +ssize_t rv, done =0 ;
fs/proc/inode.c static loff_t proc_reg_llseek(struct file *file, loff_t offset, int whence) · line 425
Quoted 10 lines
fs/proc/inode.c +10
-0+size_t len, count; +loff_t pos; + +if (!iov_iter_count (iter)) +return 0 ; +/* neither the allocation nor the entry can honor IOCB_NOWAIT */ +if ((iocb->ki_flags &IOCB_NOWAIT ) && !(file->f_flags &O_NONBLOCK )) +return -EAGAIN ; + len =min_t (size_t,iov_iter_count (iter), pde->read_size); + buf =kvmalloc (len,GFP_KERNEL );
[Severity: Low]
This isn't a bug, but in pde_read_buf(), buf is declared with __free(kvfree) = NULL at the top of the function before the early return checks for !iov_iter_count(iter) and IOCB_NOWAIT, and then assigned separately via kvmalloc().
Could buf be defined and initialized in a single statement at the allocation site (char *buf __free(kvfree) = kvmalloc(len, GFP_KERNEL);) to follow the cleanup.h conventions?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
Christian Brauner
maintainerRe: [PATCH RFC 13/16] proc: let procfs copy out reads that don't go through seq_file
Quoted 1 line · On Tue, Nov 03, 2026 at 03:01:30PM +0000, [email protected] wrote:
- [Low] proc: define and initialize
__free(kvfree)variable buf in one statement inpde_read_buf()
Intentional.
Christian Brauner
maintainerRFC PATCH 14/16 fs/proc/vmcore: let procfs copy out reads
In reply to Christian BraunerImplement ->proc_read_buf() so procfs copies what is read to userspace after it unpinned the entry. __read_vmcore() fills procfs' buffer through a kvec iov_iter. So a read goes through that buffer now instead of straight from the old memory into the reader's, which is what splice() did already. mmap() isn't affected. On ppc64 a machine check while reading the old memory used to be recovered through the exception table of copy_to_user(), by accident. copy_oldmem_page() copies with _copy_mc_to_iter() now, so a read through procfs' buffer recovers from it as well.
The buffer is 128k, which keeps the entry pinned for one such piece at a time, and /proc/vmcore asks for PROC_ENTRY_FULL_READS so that a larger read is filled in pieces. kexec-tools treats a short read of it as fatal. A read that faults on its buffer in a later piece returns the pieces before it now instead of failing with -EFAULT. fadump removes /proc/vmcore when its memory is released and that no longer waits for a reader that faults on its buffer.
--- fs/proc/vmcore.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
fs/proc/vmcore.c +11 -3
@@ -27,6 +27,7 @@#include <linux/mutex.h> #include <linux/vmalloc.h> #include <linux/pagemap.h> +#include <linux/sizes.h> #include <linux/uio.h> #include <linux/cc_platform.h> #include <asm/io.h>
@@ -427,9 +428,14 @@ static ssize_t __read_vmcore(struct iov_iter *iter, loff_t *fpos)return acc; } -static ssize_t read_vmcore (struct kiocb *iocb,struct iov_iter *iter) +static ssize_t read_vmcore (struct file *file,char *buf,size_t count, +loff_t *ppos) { -return __read_vmcore (iter, &iocb->ki_pos); +struct kvec kvec = { .iov_base = buf, .iov_len = count }; +struct iov_iter iter; + +iov_iter_kvec (&iter,ITER_DEST , &kvec,1 , count); +return __read_vmcore (&iter, ppos); }/**
@@ -709,9 +715,11 @@ static int mmap_vmcore(struct file *file, struct vm_area_struct *vma)#endif static const struct proc_ops vmcore_proc_ops = { + .proc_flags =PROC_ENTRY_FULL_READS , .proc_open = open_vmcore, .proc_release = release_vmcore, - .proc_read_iter = read_vmcore, + .proc_read_buf = read_vmcore, + .proc_read_size =SZ_128K , .proc_lseek = default_llseek, .proc_mmap = mmap_vmcore, };
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 14/16] fs/proc/vmcore: let procfs copy out reads
Sashiko has reviewed this patch and found no issues.
Quoted 18 lines
Implement ->proc_read_buf() so procfs copies what is read to userspace after it unpinned the entry.
__read_vmcore()fills procfs' buffer through a kveciov_iter. So a read goes through that buffer now instead of straight from the old memory into the reader's, which is whatsplice()did already.mmap()isn't affected. On ppc64 a machine check while reading the old memory used to be recovered through the exception table ofcopy_to_user(), by accident.copy_oldmem_page()copies with_copy_mc_to_iter()now, so a read through procfs' buffer recovers from it as well.The buffer is 128k, which keeps the entry pinned for one such piece at a time, and
/proc/vmcoreasks forPROC_ENTRY_FULL_READSso that a larger read is filled in pieces. kexec-tools treats a short read of it as fatal. A read that faults on its buffer in a later piece returns the pieces before it now instead of failing with-EFAULT. fadump removes/proc/vmcorewhen its memory is released and that no longer waits for a reader that faults on its buffer.
Quoted 1 line
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
Christian Brauner
maintainerRFC PATCH 15/16 proc: let procfs copy ioctl arguments in and out
In reply to Christian Braunerproc_reg_unlocked_ioctl() pins the entry and calls ->proc_ioctl() which copies its argument from and to userspace itself. Like for reads and writes, an ioctl can make a page fault in those copies take as long as it wants and remove_proc_entry() waits for it.
The command number doesn't reliably say what an ioctl copies. Some of the procfs ones aren't encoded at all, like the PCI ones or esas2r's, and i8k's encode the wrong sizes. So do what DRM does instead and list what each command copies. An entry that implements ->proc_ioctl_buf() lists its commands in ->proc_ioctl_args with the number of bytes that go in and that come back out. procfs copies the argument in before it pins the entry, calls ->proc_ioctl_buf() with that kernel copy and copies it back out after unpinning the entry if the ioctl succeeded. A command that copies neither gets its argument as is, a command that isn't in the table fails with -ENOTTY. PROC_IOCTL_IOC() fills in an entry from a command that encodes its argument.
Note that, like for writes, procfs copies the argument in before the entry's own checks run, permission checks included. So a bad pointer fails with -EFAULT where the entry used to say -EPERM first.
The table lives with the proc_ops, which go away with the entry, so procfs pins the entry to look the command up and lets go of it again before it copies anything. 32-bit tasks go through the same table with their pointer converted by compat_ptr(). An entry that implements ->proc_ioctl_buf() and still has a ->proc_ioctl() fails to register, and so does one that lists commands without implementing it or implements it without listing any.
No users yet.
--- fs/proc/generic.c | 12 ++++++++++ fs/proc/inode.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ fs/proc/internal.h | 5 +++++ include/linux/proc_fs.h | 35 +++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+)
fs/proc/generic.c +12 -0
@@ -485,6 +485,18 @@ static int pde_set_flags(struct proc_dir_entry *pde)if (proc_ops->proc_flags &PROC_ENTRY_FULL_READS ) pde->flags |=PROC_ENTRY_FULL_READS ; } +if (proc_ops->proc_ioctl_buf) { +if (WARN_ON_ONCE (proc_ops->proc_ioctl)) +return -EINVAL ; +/* the table says what procfs copies, see pde_ioctl_arg() */ +if (WARN_ON_ONCE (!proc_ops->proc_ioctl_args || + !proc_ops->proc_nr_ioctl_args)) +return -EINVAL ; + pde->flags |= PROC_ENTRY_proc_ioctl_buf; + }else if (WARN_ON_ONCE (proc_ops->proc_ioctl_args || + proc_ops->proc_nr_ioctl_args)) { +return -EINVAL ; + }return 0 ; }
fs/proc/inode.c +58 -0
81 lines not on this page · raw message
fs/proc/internal.h +5 -0
@@ -129,6 +129,11 @@ static inline bool pde_has_full_reads(const struct proc_dir_entry *pde)return pde->flags &PROC_ENTRY_FULL_READS ; } +static inline bool pde_has_proc_ioctl_buf (const struct proc_dir_entry *pde) +{ +return pde->flags & PROC_ENTRY_proc_ioctl_buf; +} +/* procfs owns the seq_file, see pde_seq_ops */ static inline bool pde_is_seq_file (const struct proc_dir_entry *pde) {
include/linux/proc_fs.h +35 -0
@@ -8,6 +8,7 @@#include <linux/compiler.h> #include <linux/types.h> #include <linux/fs.h> +#include <linux/ioctl.h> struct proc_dir_entry ;struct seq_file ;
@@ -45,8 +46,29 @@ enum {* coming up short. */ PROC_ENTRY_FULL_READS =1U <<11 , + PROC_ENTRY_proc_ioctl_buf = 1U << 12, }; +/* +* How many bytes of the argument of @cmd procfs copies in before calling +* ->proc_ioctl_buf() and copies back out after it. A command that copies +* neither gets its argument as is. +*/ +struct proc_ioctl_arg { +unsigned int cmd; +unsigned int in; +unsigned int out; +}; + +#define PROC_IOCTL_ARG (_cmd, _in, _out) \ + { .cmd = (_cmd), .in = (_in), .out = (_out) } + +/* A command that encodes the size and direction of its argument. */ +#define PROC_IOCTL_IOC (_cmd) \ + PROC_IOCTL_ARG(_cmd, \ + (_IOC_DIR(_cmd) & _IOC_WRITE) ? _IOC_SIZE(_cmd) : 0, \ + (_IOC_DIR(_cmd) & _IOC_READ) ? _IOC_SIZE(_cmd) : 0) +struct proc_ops {unsigned int proc_flags;int (*proc_open)(struct inode *,struct file *);
@@ -100,6 +122,19 @@ struct proc_ops {*/ ssize_t (*proc_read_buf)(struct file *,char *,size_t ,loff_t *);unsigned int proc_read_size; +/* +* procfs looks the command up in ->proc_ioctl_args, copies in what it +* passes before pinning the entry, calls ->proc_ioctl_buf() with that +* copy and copies out what it returns after unpinning the entry. +* Commands that aren't in the table fail with -ENOTTY. 32-bit tasks +* go through the same table. The copy is zeroed and as large as the +* larger of the two sizes, it only goes back out if the ioctl +* succeeded, and a command that copies neither gets its argument as +* is. +*/ +long (*proc_ioctl_buf)(struct file *,unsigned int ,void *); +const struct proc_ioctl_arg *proc_ioctl_args; +unsigned int proc_nr_ioctl_args; } __randomize_layout;/* definitions for hide_pid field */
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 15/16] proc: let procfs copy ioctl arguments in and out
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] proc: unguarded zero-length user copies in pde_ioctl_buf() - [Low] proc: split declaration and…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] proc: unguarded zero-length user copies in pde_ioctl_buf()
- [Low] proc: split declaration and initialization of __free() variable in pde_ioctl_buf() --
commit 468d1faef733a0559be5a05e125e1ce20ec69e4e
Author: Christian Brauner <[email protected]>
proc: let procfs copy ioctl arguments in and out
This commit introduces ->proc_ioctl_buf() and ->proc_ioctl_args to allow procfs to copy ioctl arguments from and to userspace outside of pinning the proc_dir_entry, preventing page faults during user copies from blocking remove_proc_entry().
Quoted 4 lines
fs/proc/inode.c +0
-0
[ ... ]
Quoted 1 line
fs/proc/inode.c +0
-0@@ -571,11 +572,66 @@ static long pde_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned in
[ ... ]
fs/proc/inode.c static long pde_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned in · line 612
Quoted 13 lines
fs/proc/inode.c +13
-0+static long pde_ioctl_buf (struct proc_dir_entry *pde,struct file *file, +unsigned int cmd,void __user *uarg,unsigned long arg) +{ + void *buf __free(kvfree) = NULL; +struct proc_ioctl_arg a; +size_t size; +long rv; + +if (!pde_ioctl_arg (pde, cmd, &a)) +return -ENOTTY ; + size =max (a.in, a.out); +if (size) { + buf =kvzalloc (size,GFP_KERNEL );
[Severity: Low]
This isn't a bug, but in pde_ioctl_buf() in fs/proc/inode.c, buf is declared with __free(kvfree) = NULL at the top of the function and assigned later when size is non-zero. Per the cleanup.h guidelines, could buf be defined and initialized in a single statement once size is known (for example, void *buf __free(kvfree) = size ? kvzalloc(size, GFP_KERNEL) : NULL;)?
fs/proc/inode.c static long pde_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned in · line 624
Quoted 12 lines
fs/proc/inode.c +12
-0+if (!buf) +return -ENOMEM ; +if (copy_from_user (buf, uarg, a.in)) +return -EFAULT ; + } + +if (!pde_pin (pde)) +return -ENOTTY ; + rv = pde->proc_ops->proc_ioctl_buf (file, cmd, buf ?: (void *)arg); +pde_unpin (pde); +if (rv >=0 &©_to_user (uarg, buf, a.out)) +return -EFAULT ;
[Severity: Medium]
Should copy_from_user() and copy_to_user() in pde_ioctl_buf() in fs/proc/inode.c be guarded by checking a.in and a.out respectively?
Because a.in and a.out are runtime values loaded from proc_ops->proc_ioctl_args, check_copy_size() and _inline_copy_from_user() / _inline_copy_to_user() do not short-circuit when the length is 0 and still invoke raw_copy_from_user() / raw_copy_to_user().
When a command has a.out == 0 (such as an _IOW or _IO command) and userspace passes arg == 0, calling copy_to_user(NULL, buf, 0) fails with -EFAULT on C-SKY after ->proc_ioctl_buf() has already succeeded, because C-SKY checks the destination pointer for NULL before checking whether the length is 0:
arch/csky/lib/usercopy.c:raw_copy_to_user() {
...
"0: cmpnei %1, 0 \n"
" bf 8f \n"
...
}On UML, calling copy_from_user() with a.in == 0 or copy_to_user() with a.out == 0 still enters buffer_op() -> do_op_one_page() -> maybe_map() -> handle_page_fault() on uarg before checking if remain == 0:
arch/um/kernel/skas/uaccess.c:buffer_op() {
...
size = min(PAGE_ALIGN(addr) - addr, (unsigned long) len);
remain = len;
n = do_op_one_page(addr, size, is_write, op, arg);
if (n != 0)
return (n < 0 ? remain : 0);
...
}Additionally, when both a.in == 0 and a.out == 0, size is 0 and buf remains NULL, so copy_to_user(uarg, NULL, 0) passes a NULL source pointer into raw_copy_to_user() (and memcpy() on CONFIG_UACCESS_MEMCPY or UML builds).
Would it make sense to check if (a.in && copy_from_user(buf, uarg, a.in)) and if (rv >= 0 && a.out && copy_to_user(uarg, buf, a.out))?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
Christian Brauner
maintainerRe: [PATCH RFC 15/16] proc: let procfs copy ioctl arguments in and out
Quoted 1 line · On Tue, Nov 03, 2026 at 03:01:36PM +0000, [email protected] wrote:
- [Medium] proc: unguarded zero-length user copies in
pde_ioctl_buf()
Hm, a zero-length copy isn't a nop on every arch. Adventurous. It's skipped now.
Quoted 1 line
- [Low] proc: split declaration and initialization of
__free()variable inpde_ioctl_buf()
Intentional.
Christian Brauner
maintainerRFC PATCH 16/16 Documentation: seq_file: describe how a /proc file uses seq_file
In reply to Christian BraunerThe example in seq_file.rst hands proc_create() file operations built from an open method that calls seq_open() and from seq_read(), seq_lseek() and seq_release(). procfs doesn't work that way anymore. It opens and releases the seq_file itself when the entry declares its seq_operations or its show function in struct proc_ops. Say so, and point the extra-simple version at ->proc_show() for /proc files.
--- Documentation/filesystems/seq_file.rst | 42 ++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-)
Documentation/filesystems/seq_file.rst +33 -9
@@ -68,7 +68,7 @@ kernel 3.10. Current versions require the following update:: - entry = create_proc_entry("sequence", 0, NULL); - if (entry) - entry->proc_fops = &ct_file_ops; - + entry = proc_create("sequence", 0, NULL, &ct_file_ops); + + entry = proc_create("sequence", 0, NULL, &ct_proc_ops); The iterator interface ======================
@@ -277,9 +277,10 @@ seq_file system, but we have not yet turned them into a file that a user can see. Creating a file within the kernel requires, of course, the creation of a set of file_operations which implement the operations on that file. The seq_file interface provides a set of canned operations which do -most of the work. The virtual file author still must implement the open() -method, however, to hook everything up. The open function is often a single -line, as in the example module:: +most of the work. A file in /proc is special, procfs hooks the seq_file up +itself, see below. Everywhere else the virtual file author still must +implement the open() method to do that. The open function is often a +single line:: static int ct_open(struct inode *inode, struct file *file) {
@@ -343,19 +344,38 @@ file_operations structure will look like:: There is also a seq_release_private() which passes the contents of the seq_file private field to kfree() before releasing the structure. -The final step is the creation of the /proc file itself. In the example -code, that is done in the initialization code in the usual way:: +The final step is the creation of the file itself. A file in /proc is +special here: procfs opens and releases the seq_file itself, so there is +no open method and no file_operations for it. The entry declares its +seq_operations in struct proc_ops instead, and the size of a private +block in proc_state_size if it wants procfs to allocate one like +seq_open_private() would:: + + static const struct proc_ops ct_proc_ops = { + .proc_seq_ops = &ct_seq_ops, + }; static int ct_init(void) { - struct proc_dir_entry *entry; - - proc_create("sequence", 0, NULL, &ct_file_ops); + proc_create("sequence", 0, NULL, &ct_proc_ops); return 0; } module_init(ct_init); +A ->proc_open() method of such an entry finds the seq_file in +file->private_data and may set up more state; ->proc_release() releases +only that. It runs when the entry is removed, which can be while a reader +is still copying out of the seq_file, so it must leave the seq_file +alone. procfs only calls into the entry between start() and stop() with +the entry pinned, so nothing that runs there may wait for something a +reader can hold while it copies to userspace, like the seq_file's lock. +start() and next() may return ERR_PTR(-EIO) to say that the entry is +gone; procfs unpins it right away then. An entry that wants to start out +with a larger buffer, like single_open_size() gives, calls +seq_preallocate() from ->proc_open(). The file_operations above are what +a file elsewhere, in debugfs for example, uses. + And that is pretty much it.
@@ -394,3 +414,7 @@ value given to single_open() can be found in the private field of the seq_file structure. When using single_open(), the programmer should use single_release() instead of seq_release() in the file_operations structure to avoid a memory leak. + +A file in /proc puts the show() function into the proc_show field of its +struct proc_ops instead and procfs does the rest, with the entry's data in +the private field of the seq_file structure.
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 16/16] Documentation: seq_file: describe how a /proc file uses seq_file
Sashiko has reviewed this patch and found no issues.
Quoted 8 lines
The example in
seq_file.rsthandsproc_create()file operations built from an open method that callsseq_open()and fromseq_read(),seq_lseek()andseq_release(). procfs doesn't work that way anymore. It opens and releases theseq_fileitself when the entry declares itsseq_operationsor its show function in structproc_ops. Say so, and point the extra-simple version at ->proc_show() for /proc files.Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
Christian Brauner
maintainercover proc: convert the arch and platform entries
In reply to Christian BraunerThis converts the proc entries under arch/ and the ones of ACPI, the platform drivers, via-pmu and PNP to what "proc: let procfs own the data path" set up: procfs owns the seq_file, copies in writes, copies out reads and copies ioctl arguments in and out, so the entries' code only ever runs with the entry pinned and never touches userspace memory.
One patch per subsystem and method, in the order seq_file, write, read, ioctl. Most of them just stop copying in or out themselves, or switch from kstrto*_from_user() to kstrto*() and from simple_read_from_buffer() to memory_read_from_buffer(). Where a write that is larger than anything the entry parses now fails with -EINVAL from procfs I've called that out in the commit. The rtas flash files take their image in 4k pieces and keep doing that with PROC_ENTRY_SHORT_WRITES, the rtas error log is a stream and hands out a record before procfs copies it out, so a reader that passes a bad buffer loses it. /proc/mtrr's ioctls are table driven now, the native and the 32-bit commands share one table.
Build tested per commit on x86_64 and with W=1 on the touched objects on alpha, arm, m68k, powerpc, sh, sparc, um, x86 and xtensa. The entries I could get at in an x86 VM behave the same with and without the series.
This is on top of "proc: let procfs own the data path". The other three conversion series don't depend on it and it doesn't depend on them.
---
Christian Brauner (22):
alpha: srm_env: let procfs copy in writes
ARM: alignment: let procfs copy in writes
ARM: atags: let procfs copy out reads of /proc/atags m68k/bootinfo: let procfs copy out reads of /proc/bootinfo powerpc/rtas: let procfs copy in writes
powerpc/rtas: let procfs copy out reads
powerpc/pseries: let procfs copy in writes powerpc/procfs: let procfs copy out reads of /proc/powerpc/systemcfg
sh: alignment: let procfs copy in writes
sparc: led: let procfs copy in writes
um: let procfs copy in writes
x86/mtrr: let procfs own the seq_file of /proc/mtrr x86/mtrr: let procfs copy in writes to /proc/mtrr x86/mtrr: let procfs copy the ioctl arguments of /proc/mtrr
xtensa: simdisk: let procfs copy in writes
xtensa: simdisk: let procfs copy out reads
ACPI: proc: let procfs copy in writes to /proc/acpi/wakeup macintosh/via-pmu: let procfs copy in writes to the options file platform/x86: thinkpad_acpi: let procfs copy in writes platform/x86: toshiba_acpi: let procfs copy in writes
PNP: pnpbios: let procfs copy in writes
PNP: isapnp: let procfs copy out reads
arch/alpha/kernel/srm_env.c | 12 +--- arch/arm/kernel/atags_proc.c | 6 +- arch/arm/mm/alignment.c | 7 +-- arch/m68k/kernel/bootinfo_proc.c | 6 +- arch/powerpc/kernel/proc_powerpc.c | 6 +- arch/powerpc/kernel/rtas-proc.c | 40 ++++++------ arch/powerpc/kernel/rtas_flash.c | 62 +++++++++---------- arch/powerpc/kernel/rtasd.c | 19 ++---- arch/powerpc/platforms/pseries/lpar.c | 14 ++--- arch/powerpc/platforms/pseries/lparcfg.c | 7 +-- arch/powerpc/platforms/pseries/reconfig.c | 22 +++---- arch/sh/mm/alignment.c | 7 +-- arch/sparc/kernel/led.c | 13 +--- arch/um/drivers/mconsole_kern.c | 11 +--- arch/um/kernel/exitcode.c | 14 ++--- arch/x86/kernel/cpu/mtrr/if.c | 95 ++++++++++++++++------------- arch/xtensa/platforms/iss/simdisk.c | 32 ++++------ drivers/acpi/proc.c | 7 +-- drivers/macintosh/via-pmu.c | 7 +-- drivers/platform/x86/lenovo/thinkpad_acpi.c | 14 +---- drivers/platform/x86/toshiba_acpi.c | 36 ++++------- drivers/pnp/isapnp/proc.c | 20 +++--- drivers/pnp/pnpbios/proc.c | 9 +-- 23 files changed, 193 insertions(+), 273 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261101-work-procfs-arch-8d4ade48be31
prerequisite-change-id: 20261101-work-procfs-core-8fa4a4f412cf:v1
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: 540aa9ea28adcbc54d8264f66314e29e3b8c9d49
prerequisite-patch-id: 2f7139d1c972c4439dccca610caa5f1288987a3d
prerequisite-patch-id: b0031a7c4855b7ec38259c59f30f65101a8fc4b5
prerequisite-patch-id: 0b7f226abf2b9ff41873a1aa55cf97747647d56b
prerequisite-patch-id: f63e345f897619af54813958046617c655fb7daf
prerequisite-patch-id: c0e21e762a2960e3324c5ab04634a7035e515fa6
prerequisite-patch-id: fd59c71eadd8f39c58b3baea93567bc093bc9b0e
prerequisite-patch-id: 5d52cc8a5c73e4c5ba16b8e24f51408c17583bf9
prerequisite-patch-id: 1f9179284e2f3314d4b46ac5e989089a21c7576c
prerequisite-patch-id: 10397bbf696bee16dc71eb79fff6c5e73842bcbc
prerequisite-patch-id: 9b44279dcf08912238d2f6438a3328a778a79a0c
prerequisite-patch-id: 16854b621a3a71dfc365aaf7abaa7f65adfe9f7e
prerequisite-patch-id: ca42eea21fae1fd1cd3518ab057c74b61ffd68fe
prerequisite-patch-id: 23255dc2be76b0479c083dbd4c897bfbb533aed5
prerequisite-patch-id: 82129c687ebf0a41b369893b92f2c038420ab801
prerequisite-patch-id: 56ad57a99a5bda075a9f4e844368974296744934
prerequisite-patch-id: 288b3cbaa11e01a778cc3d7beb6c0549aacf59f2
prerequisite-patch-id: a163b1549608652bfa5bd9ff93efa55b541010c4
prerequisite-patch-id: 78f067c343177e52d49ddfe9ee2992e602549869
prerequisite-patch-id: 19c12bc6e3fbed35acc1f657196b1a997f48cdfe
prerequisite-patch-id: 5800c3076694bad719954b2ad4d7448c6e013bfd
prerequisite-patch-id: 2a2f63d8843b159594b9eee6ddea028ce76a6270
prerequisite-patch-id: 40a1951d57906c4a1b98a4bd96a814b80339b9c5
prerequisite-patch-id: e2141077e69f20a2b27fab69c8d61d0686682f53
prerequisite-patch-id: c4d6018c26c40dc549d88330d23ce48f872c2b7f
prerequisite-patch-id: 6de8ec17746031bc6d1d9ac262c5be0f905fcd79
prerequisite-patch-id: b87bcb167884605bff1fd0f2ed8a88942d07efd5
prerequisite-patch-id: cfa9a8b4c67e20cfd22d77035bfd21ee96ab3298
prerequisite-patch-id: abf3482ed2399b7b2e1ee72e937af05bb9200667
prerequisite-patch-id: 02c175b2dee565a85b3308ecf2ec8e3c9a029e0f
prerequisite-patch-id: 4ed6b3d3d45de49d4967e88ba4e7f03372775f55
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: cf9ce0099005fd3bc31a1e15131d48e315ac3bee
prerequisite-patch-id: 16a657a5d0046068dbace29e7ea046ac1441f46f
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: e6ffe2a5c3ee5645f56c8366b568fbc9f140848c
prerequisite-patch-id: 117db27dcdef433de7c6be5e95973cac1e373927
prerequisite-patch-id: a0844aa26ffb65ccb362b88ef6c2f4758e15edbf
prerequisite-patch-id: 143fb91bddf33068b6ee11e205818c4c2815e23d
prerequisite-patch-id: 6490e4da1cb2f543683bd164bc9a5a1c943057e7
prerequisite-patch-id: cdefca57d3c552fb6f6b928699edd85f33be25fd
prerequisite-patch-id: 2f0fdb970ddd760d85ff5d2caad0b96348270182
prerequisite-patch-id: f2aa468ff5cbb1cabab3f70e1b8eeee0286cfb4a
prerequisite-patch-id: 037030d95eeb7c49b1e43f638ec954f2cebbcfb1
prerequisite-patch-id: 487141c49e2548310bfb6c2a0964a78f08b285a9
prerequisite-patch-id: c3d3008819fa5629a18142ec9ba6d7024ade7fdb
prerequisite-patch-id: ba15e7c86b60ccc2f0f7ee4c9bc99073068757bc
prerequisite-patch-id: 9423f410b99853693db06c29f12f1c08278b050d
prerequisite-patch-id: 4e7966978e4b11c4d843831f312cdcd5b848f00c
prerequisite-patch-id: 3d24be1897a624d519ef144801f76d152c7528a9
prerequisite-patch-id: 3b4d9fe7b26f6b7b9b3f7725ea333df357107c44
prerequisite-patch-id: 7d9dd345428baeb88c862ef902612f7160dc1d8c
Christian Brauner
maintainerRFC PATCH 1/22 alpha: srm_env: let procfs copy in writes
Implement ->proc_write_buf() so procfs copies in what is written to /proc/srm_environment/* before it pins the entry, and hand that copy to the console callback through the page it used before.
--- arch/alpha/kernel/srm_env.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-)
arch/alpha/kernel/srm_env.c +3 -9
@@ -81,7 +81,7 @@ static int srm_env_proc_show(struct seq_file *m, void *v)return ret; } -static ssize_t srm_env_proc_write (struct file *file,const char __user *buffer, +static ssize_t srm_env_proc_write (struct file *file,char *buffer,size_t count,loff_t *pos) {int res;
@@ -92,13 +92,8 @@ static ssize_t srm_env_proc_write(struct file *file, const char __user *buffer,if (!buf)return -ENOMEM ; - res = -EINVAL ; -if (count >=PAGE_SIZE ) - goto out; - res = -EFAULT ; -if (copy_from_user (buf, buffer, count)) - goto out; +memcpy (buf, buffer, count); buf[count] ='\0' ; ret1 =callback_setenv (id, buf, count);
@@ -109,14 +104,13 @@ static ssize_t srm_env_proc_write(struct file *file, const char __user *buffer, res = (int ) ret1; } - out:free_page ((unsigned long )buf);return res; }static const struct proc_ops srm_env_proc_ops = { .proc_show = srm_env_proc_show, - .proc_write = srm_env_proc_write, + .proc_write_buf = srm_env_proc_write, }; static int __init
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 2/22 ARM: alignment: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written to /proc/cpu/alignment before it pins the entry, and take the mode from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
--- arch/arm/mm/alignment.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
arch/arm/mm/alignment.c +3 -4
@@ -144,14 +144,13 @@ static int alignment_proc_show(struct seq_file *m, void *v)return 0 ; } -static ssize_t alignment_proc_write (struct file *file,const char __user *buffer, +static ssize_t alignment_proc_write (struct file *file,char *buffer,size_t count,loff_t *pos) {char mode;if (count >0 ) { -if (get_user (mode, buffer)) -return -EFAULT ; + mode = buffer[0 ];if (mode >='0' && mode <='5' ) ai_usermode =safe_usermode (mode -'0' , true); }
@@ -160,7 +159,7 @@ static ssize_t alignment_proc_write(struct file *file, const char __user *bufferstatic const struct proc_ops alignment_proc_ops = { .proc_show = alignment_proc_show, - .proc_write = alignment_proc_write, + .proc_write_buf = alignment_proc_write, };#endif /* CONFIG_PROC_FS */
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 3/22 ARM: atags: let procfs copy out reads of /proc/atags
In reply to Christian BraunerImplement ->proc_read_buf() so procfs copies what is read to userspace after it unpinned the entry.
--- arch/arm/kernel/atags_proc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
arch/arm/kernel/atags_proc.c +3 -3
@@ -10,15 +10,15 @@ struct buffer { char data[] __counted_by(size); }; -static ssize_t atags_read (struct file *file,char __user *buf, +static ssize_t atags_read (struct file *file,char *buf,size_t count,loff_t *ppos) {struct buffer *b =pde_data (file_inode (file)); -return simple_read_from_buffer (buf, count, ppos, b->data, b->size); +return memory_read_from_buffer (buf, count, ppos, b->data, b->size); }static const struct proc_ops atags_proc_ops = { - .proc_read = atags_read, + .proc_read_buf = atags_read, .proc_lseek = default_llseek, };
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 4/22 m68k/bootinfo: let procfs copy out reads of /proc/bootinfo
In reply to Christian BraunerImplement ->proc_read_buf() so procfs copies what is read to userspace after it unpinned the entry.
--- arch/m68k/kernel/bootinfo_proc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
arch/m68k/kernel/bootinfo_proc.c +3 -3
@@ -19,15 +19,15 @@ static char bootinfo_tmp[1536] __initdata;static void *bootinfo_copy;static size_t bootinfo_size; -static ssize_t bootinfo_read (struct file *file,char __user *buf, +static ssize_t bootinfo_read (struct file *file,char *buf,size_t count,loff_t *ppos) { -return simple_read_from_buffer (buf, count, ppos, bootinfo_copy, +return memory_read_from_buffer (buf, count, ppos, bootinfo_copy, bootinfo_size); }static const struct proc_ops bootinfo_proc_ops = { - .proc_read = bootinfo_read, + .proc_read_buf = bootinfo_read, .proc_lseek = default_llseek, };
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 5/22 powerpc/rtas: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() for the files in /proc/powerpc/rtas and /proc/ppc64/rtas so procfs copies in what is written before it pins the entry, and parse what was written from that copy.
firmware_flash, firmware_update and validate_flash take an image in 4k pieces and relied on writes coming up short. They keep doing that with PROC_ENTRY_SHORT_WRITES. Writes to the other files that are larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
validate_flash only keeps the first 4k of the image and used to swallow whatever was written beyond that in one go. It takes at most 4k per write now, like for the part it keeps, so a writer has to loop there like it has to for the image itself.
--- arch/powerpc/kernel/rtas-proc.c | 40 ++++++++++++++++++---------------------- arch/powerpc/kernel/rtas_flash.c | 38 +++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 43 deletions(-)
arch/powerpc/kernel/rtas-proc.c +18 -22
@@ -138,45 +138,45 @@ static unsigned long rtas_tone_volume = 0;static int ppc_rtas_sensors_show (struct seq_file *m,void *v);static int ppc_rtas_clock_show (struct seq_file *m,void *v);static ssize_t ppc_rtas_clock_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos); +char *buf,size_t count,loff_t *ppos);static int ppc_rtas_progress_show (struct seq_file *m,void *v);static ssize_t ppc_rtas_progress_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos); +char *buf,size_t count,loff_t *ppos);static int ppc_rtas_poweron_show (struct seq_file *m,void *v);static ssize_t ppc_rtas_poweron_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos); +char *buf,size_t count,loff_t *ppos);static ssize_t ppc_rtas_tone_freq_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos); +char *buf,size_t count,loff_t *ppos);static int ppc_rtas_tone_freq_show (struct seq_file *m,void *v);static ssize_t ppc_rtas_tone_volume_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos); +char *buf,size_t count,loff_t *ppos);static int ppc_rtas_tone_volume_show (struct seq_file *m,void *v);static int ppc_rtas_rmo_buf_show (struct seq_file *m,void *v);static const struct proc_ops ppc_rtas_poweron_proc_ops = { .proc_show = ppc_rtas_poweron_show, - .proc_write = ppc_rtas_poweron_write, + .proc_write_buf = ppc_rtas_poweron_write, };static const struct proc_ops ppc_rtas_progress_proc_ops = { .proc_show = ppc_rtas_progress_show, - .proc_write = ppc_rtas_progress_write, + .proc_write_buf = ppc_rtas_progress_write, };static const struct proc_ops ppc_rtas_clock_proc_ops = { .proc_show = ppc_rtas_clock_show, - .proc_write = ppc_rtas_clock_write, + .proc_write_buf = ppc_rtas_clock_write, };static const struct proc_ops ppc_rtas_tone_freq_proc_ops = { .proc_show = ppc_rtas_tone_freq_show, - .proc_write = ppc_rtas_tone_freq_write, + .proc_write_buf = ppc_rtas_tone_freq_write, };static const struct proc_ops ppc_rtas_tone_volume_proc_ops = { .proc_show = ppc_rtas_tone_volume_show, - .proc_write = ppc_rtas_tone_volume_write, + .proc_write_buf = ppc_rtas_tone_volume_write, };static int ppc_rtas_find_all_sensors (void );
@@ -216,16 +216,14 @@ static int __init proc_rtas_init(void)__initcall (proc_rtas_init); -static int parse_number (const char __user *p,size_t count,u64 *val) +static int parse_number (const char *p,size_t count,u64 *val) {char buf[40 ];if (count >39 )return -EINVAL ; -if (copy_from_user (buf, p, count)) -return -EFAULT ; - +memcpy (buf, p, count); buf[count] =0 ;return kstrtoull (buf,10 , val);
@@ -235,7 +233,7 @@ static int parse_number(const char __user *p, size_t count, u64 *val)/* POWER-ON-TIME */ /* ****************************************************************** */ static ssize_t ppc_rtas_poweron_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos) +char *buf,size_t count,loff_t *ppos) {struct rtc_time tm;time64_t nowtime;
@@ -269,15 +267,13 @@ static int ppc_rtas_poweron_show(struct seq_file *m, void *v)/* PROGRESS */ /* ****************************************************************** */ static ssize_t ppc_rtas_progress_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos) +char *buf,size_t count,loff_t *ppos) {unsigned long hex;if (count >=MAX_LINELENGTH ) count =MAX_LINELENGTH -1 ; -if (copy_from_user (progress_led, buf, count)) {/* save the string */ -return -EFAULT ; - } +memcpy (progress_led, buf, count);/* save the string */ progress_led[count] =0 ;/* Lets see if the user passed hexdigits */
@@ -301,7 +297,7 @@ static int ppc_rtas_progress_show(struct seq_file *m, void *v)/* CLOCK */ /* ****************************************************************** */ static ssize_t ppc_rtas_clock_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos) +char *buf,size_t count,loff_t *ppos) {struct rtc_time tm;time64_t nowtime;
@@ -660,7 +656,7 @@ static void get_location_code(struct seq_file *m, struct individual_sensor *s,/* INDICATORS - Tone Frequency */ /* ****************************************************************** */ static ssize_t ppc_rtas_tone_freq_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos) +char *buf,size_t count,loff_t *ppos) {u64 freq;int error =parse_number (buf, count, &freq);
@@ -685,7 +681,7 @@ static int ppc_rtas_tone_freq_show(struct seq_file *m, void *v)/* INDICATORS - Tone Volume */ /* ****************************************************************** */ static ssize_t ppc_rtas_tone_volume_write (struct file *file, -const char __user *buf,size_t count,loff_t *ppos) +char *buf,size_t count,loff_t *ppos) {u64 volume;int error =parse_number (buf, count, &volume);
arch/powerpc/kernel/rtas_flash.c +17 -21
@@ -307,7 +307,7 @@ static ssize_t rtas_flash_read_num(struct file *file, char __user *buf,* count is. If the system is low on memory it will be just as well * that we fail.... */ -static ssize_t rtas_flash_write (struct file *file,const char __user *buffer, +static ssize_t rtas_flash_write (struct file *file,char *buffer,size_t count,loff_t *off) {struct rtas_update_flash_t *const uf = &rtas_update_flash_data;
@@ -343,16 +343,11 @@ static ssize_t rtas_flash_write(struct file *file, const char __user *buffer, next_free =0 ; } -if (count >RTAS_BLK_SIZE ) - count =RTAS_BLK_SIZE ; p =kmem_cache_zalloc (flash_block_cache,GFP_KERNEL );if (!p)return -ENOMEM ; -if (copy_from_user (p, buffer, count)) { -kmem_cache_free (flash_block_cache, p); -return -EFAULT ; - } +memcpy (p, buffer, count); fl->blocks[next_free].data = p; fl->blocks[next_free].length = count; fl->num_blocks++;
@@ -390,7 +385,7 @@ static ssize_t manage_flash_read(struct file *file, char __user *buf,return simple_read_from_buffer (buf, count, ppos, msg, msglen); } -static ssize_t manage_flash_write (struct file *file,const char __user *buf, +static ssize_t manage_flash_write (struct file *file,char *buf,size_t count,loff_t *off) {struct rtas_manage_flash_t *const args_buf = &rtas_manage_flash_data;
@@ -401,9 +396,8 @@ static ssize_t manage_flash_write(struct file *file, const char __user *buf,if ((args_buf->status ==MANAGE_AUTH ) || (count ==0 ))return count; - -if (get_user (cmd, buf)) -return -EFAULT ; + + cmd = buf[0 ];if (cmd =='0' ) op =RTAS_REJECT_TMP_IMG ;
@@ -471,7 +465,7 @@ static ssize_t validate_flash_read(struct file *file, char __user *buf,return simple_read_from_buffer (buf, count, ppos, msg, msglen); } -static ssize_t validate_flash_write (struct file *file,const char __user *buf, +static ssize_t validate_flash_write (struct file *file,char *buf,size_t count,loff_t *off) { struct rtas_validate_flash_t *const args_buf =
@@ -494,11 +488,7 @@ static ssize_t validate_flash_write(struct file *file, const char __user *buf, args_buf->status =VALIDATE_INCOMPLETE ; } -if (!access_ok (buf, count)) -return -EFAULT ; - -if (copy_from_user (args_buf->buf + *off, buf, count)) -return -EFAULT ; +memcpy (args_buf->buf + *off, buf, count); *off += count;return count;
@@ -632,8 +622,10 @@ static const struct rtas_flash_file rtas_flash_files[] = { .filename = "powerpc/rtas/" FIRMWARE_FLASH_NAME, .handle = RTAS_FN_IBM_UPDATE_FLASH_64_AND_REBOOT, .status = &rtas_update_flash_data.status, + .ops.proc_flags = PROC_ENTRY_SHORT_WRITES, .ops.proc_read = rtas_flash_read_msg, - .ops.proc_write = rtas_flash_write, + .ops.proc_write_buf = rtas_flash_write, + .ops.proc_write_size = RTAS_BLK_SIZE, .ops.proc_release = rtas_flash_release, .ops.proc_lseek = default_llseek, },
@@ -641,8 +633,10 @@ static const struct rtas_flash_file rtas_flash_files[] = { .filename = "powerpc/rtas/" FIRMWARE_UPDATE_NAME, .handle = RTAS_FN_IBM_UPDATE_FLASH_64_AND_REBOOT, .status = &rtas_update_flash_data.status, + .ops.proc_flags = PROC_ENTRY_SHORT_WRITES, .ops.proc_read = rtas_flash_read_num, - .ops.proc_write = rtas_flash_write, + .ops.proc_write_buf = rtas_flash_write, + .ops.proc_write_size = RTAS_BLK_SIZE, .ops.proc_release = rtas_flash_release, .ops.proc_lseek = default_llseek, },
@@ -650,8 +644,10 @@ static const struct rtas_flash_file rtas_flash_files[] = { .filename = "powerpc/rtas/" VALIDATE_FLASH_NAME, .handle = RTAS_FN_IBM_VALIDATE_FLASH_IMAGE, .status = &rtas_validate_flash_data.status, + .ops.proc_flags = PROC_ENTRY_SHORT_WRITES, .ops.proc_read = validate_flash_read, - .ops.proc_write = validate_flash_write, + .ops.proc_write_buf = validate_flash_write, + .ops.proc_write_size = VALIDATE_BUF_SIZE, .ops.proc_release = validate_flash_release, .ops.proc_lseek = default_llseek, },
@@ -660,7 +656,7 @@ static const struct rtas_flash_file rtas_flash_files[] = { .handle = RTAS_FN_IBM_MANAGE_FLASH_IMAGE, .status = &rtas_manage_flash_data.status, .ops.proc_read = manage_flash_read, - .ops.proc_write = manage_flash_write, + .ops.proc_write_buf = manage_flash_write, .ops.proc_lseek = default_llseek, } };
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 6/22 powerpc/rtas: let procfs copy out reads
In reply to Christian BraunerImplement ->proc_read_buf() for the error log and the flash files so procfs copies what is read to userspace after it unpinned the entry.
The error log hands out a record the way it did before, the record is consumed before it reaches userspace. A reader that passes a NULL buffer now loses a record like one that passed a bad buffer always did, instead of getting -EINVAL. The files can be spliced now, like every entry that is read through ->read_iter(). Note that a sendfile() from the error log into a target that fails or takes less than was read drops the records in between, splice_direct_to_actor() rewinds any seekable file like that and the error log doesn't hand a record out twice.
--- arch/powerpc/kernel/rtas_flash.c | 24 ++++++++++++------------ arch/powerpc/kernel/rtasd.c | 19 +++++-------------- 2 files changed, 17 insertions(+), 26 deletions(-)
arch/powerpc/kernel/rtas_flash.c +12 -12
@@ -269,7 +269,7 @@ static size_t get_flash_status_msg(int status, char *buf) }/* Reading the proc file will show status (not the firmware contents) */ -static ssize_t rtas_flash_read_msg (struct file *file,char __user *buf, +static ssize_t rtas_flash_read_msg (struct file *file,char *buf,size_t count,loff_t *ppos) {struct rtas_update_flash_t *const uf = &rtas_update_flash_data;
@@ -283,10 +283,10 @@ static ssize_t rtas_flash_read_msg(struct file *file, char __user *buf,/* Read as text message */ len =get_flash_status_msg (status, msg); -return simple_read_from_buffer (buf, count, ppos, msg, len); +return memory_read_from_buffer (buf, count, ppos, msg, len); } -static ssize_t rtas_flash_read_num (struct file *file,char __user *buf, +static ssize_t rtas_flash_read_num (struct file *file,char *buf,size_t count,loff_t *ppos) {struct rtas_update_flash_t *const uf = &rtas_update_flash_data;
@@ -299,7 +299,7 @@ static ssize_t rtas_flash_read_num(struct file *file, char __user *buf,/* Read as number */ sprintf (msg,"%d\n" , status); -return simple_read_from_buffer (buf, count, ppos, msg,strlen (msg)); +return memory_read_from_buffer (buf, count, ppos, msg,strlen (msg)); }/* We could be much more efficient here. But to keep this function
@@ -370,7 +370,7 @@ static void manage_flash(struct rtas_manage_flash_t *args_buf, unsigned int op) args_buf->status = rc; } -static ssize_t manage_flash_read (struct file *file,char __user *buf, +static ssize_t manage_flash_read (struct file *file,char *buf,size_t count,loff_t *ppos) {struct rtas_manage_flash_t *const args_buf = &rtas_manage_flash_data;
@@ -382,7 +382,7 @@ static ssize_t manage_flash_read(struct file *file, char __user *buf,mutex_unlock (&rtas_manage_flash_mutex); msglen =sprintf (msg,"%d\n" , status); -return simple_read_from_buffer (buf, count, ppos, msg, msglen); +return memory_read_from_buffer (buf, count, ppos, msg, msglen); } static ssize_t manage_flash_write(struct file *file, char *buf,
@@ -450,7 +450,7 @@ static int get_validate_flash_msg(struct rtas_validate_flash_t *args_buf,return n; } -static ssize_t validate_flash_read (struct file *file,char __user *buf, +static ssize_t validate_flash_read (struct file *file,char *buf,size_t count,loff_t *ppos) { struct rtas_validate_flash_t *const args_buf =
@@ -462,7 +462,7 @@ static ssize_t validate_flash_read(struct file *file, char __user *buf, msglen =get_validate_flash_msg (args_buf, msg,VALIDATE_MSG_LEN );mutex_unlock (&rtas_validate_flash_mutex); -return simple_read_from_buffer (buf, count, ppos, msg, msglen); +return memory_read_from_buffer (buf, count, ppos, msg, msglen); } static ssize_t validate_flash_write(struct file *file, char *buf,
@@ -623,7 +623,7 @@ static const struct rtas_flash_file rtas_flash_files[] = { .handle = RTAS_FN_IBM_UPDATE_FLASH_64_AND_REBOOT, .status = &rtas_update_flash_data.status, .ops.proc_flags = PROC_ENTRY_SHORT_WRITES, - .ops.proc_read = rtas_flash_read_msg, + .ops.proc_read_buf = rtas_flash_read_msg, .ops.proc_write_buf = rtas_flash_write, .ops.proc_write_size = RTAS_BLK_SIZE, .ops.proc_release = rtas_flash_release,
@@ -634,7 +634,7 @@ static const struct rtas_flash_file rtas_flash_files[] = { .handle = RTAS_FN_IBM_UPDATE_FLASH_64_AND_REBOOT, .status = &rtas_update_flash_data.status, .ops.proc_flags = PROC_ENTRY_SHORT_WRITES, - .ops.proc_read = rtas_flash_read_num, + .ops.proc_read_buf = rtas_flash_read_num, .ops.proc_write_buf = rtas_flash_write, .ops.proc_write_size = RTAS_BLK_SIZE, .ops.proc_release = rtas_flash_release,
@@ -645,7 +645,7 @@ static const struct rtas_flash_file rtas_flash_files[] = { .handle = RTAS_FN_IBM_VALIDATE_FLASH_IMAGE, .status = &rtas_validate_flash_data.status, .ops.proc_flags = PROC_ENTRY_SHORT_WRITES, - .ops.proc_read = validate_flash_read, + .ops.proc_read_buf = validate_flash_read, .ops.proc_write_buf = validate_flash_write, .ops.proc_write_size = VALIDATE_BUF_SIZE, .ops.proc_release = validate_flash_release,
@@ -655,7 +655,7 @@ static const struct rtas_flash_file rtas_flash_files[] = { .filename = "powerpc/rtas/" MANAGE_FLASH_NAME, .handle = RTAS_FN_IBM_MANAGE_FLASH_IMAGE, .status = &rtas_manage_flash_data.status, - .ops.proc_read = manage_flash_read, + .ops.proc_read_buf = manage_flash_read, .ops.proc_write_buf = manage_flash_write, .ops.proc_lseek = default_llseek, }
arch/powerpc/kernel/rtasd.c +5 -14
@@ -291,26 +291,18 @@ static int rtas_log_release(struct inode * inode, struct file * file)* know that we can safely clear the events in NVRAM. * Next we'll sit and wait for something else to log. */ -static ssize_t rtas_log_read (struct file * file,char __user * buf, +static ssize_t rtas_log_read (struct file * file,char * buf,size_t count,loff_t *ppos) {int error; -char *tmp;unsigned long s;unsigned long offset; -if (!buf || count < rtas_error_log_buffer_max) +if (count < rtas_error_log_buffer_max)return -EINVAL ; count = rtas_error_log_buffer_max; -if (!access_ok (buf, count)) -return -EFAULT ; - - tmp =kmalloc (count,GFP_KERNEL ); -if (!tmp) -return -ENOMEM ; -spin_lock_irqsave (&rtasd_log_lock, s);/* if it's 0, then we know we got the last one (the one in NVRAM) */
@@ -338,15 +330,14 @@ static ssize_t rtas_log_read(struct file * file, char __user * buf, } offset = rtas_error_log_buffer_max * (rtas_log_start &LOG_NUMBER_MASK ); -memcpy (tmp, &rtas_log_buf[offset], count); +memcpy (buf, &rtas_log_buf[offset], count); rtas_log_start +=1 ; rtas_log_size -=1 ;spin_unlock_irqrestore (&rtasd_log_lock, s); - error =copy_to_user (buf, tmp, count) ? -EFAULT : count; + error = count; out: -kfree (tmp);return error; }
@@ -359,7 +350,7 @@ static __poll_t rtas_log_poll(struct file *file, poll_table * wait) }static const struct proc_ops rtas_log_proc_ops = { - .proc_read = rtas_log_read, + .proc_read_buf = rtas_log_read, .proc_poll = rtas_log_poll, .proc_open = rtas_log_open, .proc_release = rtas_log_release,
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 7/22 powerpc/pseries: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() for lparcfg, ofdt and the vcpudispatch files so procfs copies in what is written before it pins the entry, and parse what was written from that copy. ofdt takes as much as memdup_user_nul() could copy before since device tree updates can be large.
--- arch/powerpc/platforms/pseries/lpar.c | 14 ++++++-------- arch/powerpc/platforms/pseries/lparcfg.c | 7 +++---- arch/powerpc/platforms/pseries/reconfig.c | 22 ++++++++-------------- 3 files changed, 17 insertions(+), 26 deletions(-)
arch/powerpc/platforms/pseries/lpar.c +6 -8
@@ -498,7 +498,7 @@ static void dtl_worker_disable(unsigned long *time_limit)up_write (&dtl_access_lock); } -static ssize_t vcpudispatch_stats_write (struct file *file,const char __user *p, +static ssize_t vcpudispatch_stats_write (struct file *file,char *p,size_t count,loff_t *ppos) {unsigned long time_limit = jiffies +HZ ;
@@ -509,8 +509,7 @@ static ssize_t vcpudispatch_stats_write(struct file *file, const char __user *p,if (count >15 )return -EINVAL ; -if (copy_from_user (buf, p, count)) -return -EFAULT ; +memcpy (buf, p, count); buf[count] =0 ; rc =kstrtoint (buf,0 , &cmd);
@@ -586,11 +585,11 @@ static int vcpudispatch_stats_display(struct seq_file *p, void *v)static const struct proc_ops vcpudispatch_stats_proc_ops = { .proc_show = vcpudispatch_stats_display, - .proc_write = vcpudispatch_stats_write, + .proc_write_buf = vcpudispatch_stats_write, };static ssize_t vcpudispatch_stats_freq_write (struct file *file, -const char __user *p,size_t count,loff_t *ppos) +char *p,size_t count,loff_t *ppos) {int rc, freq;char buf[16 ];
@@ -598,8 +597,7 @@ static ssize_t vcpudispatch_stats_freq_write(struct file *file,if (count >15 )return -EINVAL ; -if (copy_from_user (buf, p, count)) -return -EFAULT ; +memcpy (buf, p, count); buf[count] =0 ; rc =kstrtoint (buf,0 , &freq);
@@ -622,7 +620,7 @@ static int vcpudispatch_stats_freq_display(struct seq_file *p, void *v)static const struct proc_ops vcpudispatch_stats_freq_proc_ops = { .proc_show = vcpudispatch_stats_freq_display, - .proc_write = vcpudispatch_stats_freq_write, + .proc_write_buf = vcpudispatch_stats_freq_write, };static int __init vcpudispatch_stats_procfs_init (void )
arch/powerpc/platforms/pseries/lparcfg.c +3 -4
@@ -687,7 +687,7 @@ static ssize_t update_mpp(u64 *entitlement, u8 *weight)* This function should be invoked only on systems with * FW_FEATURE_SPLPAR. */ -static ssize_t lparcfg_write (struct file *file,const char __user * buf, +static ssize_t lparcfg_write (struct file *file,char *buf,size_t count,loff_t * off) {char kbuf[64 ];
@@ -702,8 +702,7 @@ static ssize_t lparcfg_write(struct file *file, const char __user * buf,if (count ==0 || count >sizeof (kbuf))return -EINVAL ; -if (copy_from_user (kbuf, buf, count)) -return -EFAULT ; +memcpy (kbuf, buf, count); kbuf[count -1 ] ='\0' ; tmp =strchr (kbuf,'=' );
@@ -799,7 +798,7 @@ static int lparcfg_data(struct seq_file *m, void *v) }static const struct proc_ops lparcfg_proc_ops = { - .proc_write = lparcfg_write, + .proc_write_buf = lparcfg_write, .proc_show = lparcfg_data, };
arch/powerpc/platforms/pseries/reconfig.c +8 -14
@@ -349,7 +349,7 @@ static int do_update_property(char *buf, size_t bufsize)* ofdt_write - perform operations on the Open Firmware device tree * * @file: not used -* @buf: command and arguments +* @kbuf: command and arguments * @count: size of the command buffer * @off: not used *
@@ -357,26 +357,19 @@ static int do_update_property(char *buf, size_t bufsize)* whole nodes along with their properties. Operations on individual * properties are not implemented (yet). */ -static ssize_t ofdt_write (struct file *file,const char __user *buf,size_t count, +static ssize_t ofdt_write (struct file *file,char *kbuf,size_t count,loff_t *off) {int rv; -char *kbuf;char *tmp; rv =security_locked_down (LOCKDOWN_DEVICE_TREE );if (rv)return rv; - kbuf =memdup_user_nul (buf, count); -if (IS_ERR (kbuf)) -return PTR_ERR (kbuf); - tmp =strchr (kbuf,' ' ); -if (!tmp) { - rv = -EINVAL ; - goto out; - } +if (!tmp) +return -EINVAL ; *tmp ='\0' ; tmp++;
@@ -392,13 +385,14 @@ static ssize_t ofdt_write(struct file *file, const char __user *buf, size_t coun rv =do_update_property (tmp, count - (tmp - kbuf)); else rv = -EINVAL ; -out: -kfree (kbuf); +return rv ? rv : count; }static const struct proc_ops ofdt_proc_ops = { - .proc_write = ofdt_write, + .proc_write_buf = ofdt_write, +/* what memdup_user_nul() could copy in */ + .proc_write_size =KMALLOC_MAX_SIZE -1 , .proc_lseek = noop_llseek, };
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 8/22 powerpc/procfs: let procfs copy out reads of /proc/powerpc/systemcfg
In reply to Christian BraunerImplement ->proc_read_buf() so procfs copies what is read to userspace after it unpinned the entry.
--- arch/powerpc/kernel/proc_powerpc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
arch/powerpc/kernel/proc_powerpc.c +3 -3
@@ -24,10 +24,10 @@ static loff_t page_map_seek(struct file *file, loff_t off, int whence)return fixed_size_llseek (file, off, whence,PAGE_SIZE ); } -static ssize_t page_map_read (struct file *file,char __user *buf,size_t nbytes, +static ssize_t page_map_read (struct file *file,char *buf,size_t nbytes,loff_t *ppos) { -return simple_read_from_buffer (buf, nbytes, ppos, +return memory_read_from_buffer (buf, nbytes, ppos,pde_data (file_inode (file)),PAGE_SIZE ); }
@@ -43,7 +43,7 @@ static int page_map_mmap( struct file *file, struct vm_area_struct *vma )static const struct proc_ops page_map_proc_ops = { .proc_lseek = page_map_seek, - .proc_read = page_map_read, + .proc_read_buf = page_map_read, .proc_mmap = page_map_mmap, };
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 9/22 sh: alignment: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written to the alignment files before it pins the entry, and take the mode from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
--- arch/sh/mm/alignment.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
arch/sh/mm/alignment.c +3 -4
@@ -133,14 +133,13 @@ static int alignment_proc_show(struct seq_file *m, void *v) }static ssize_t alignment_proc_write (struct file *file, -const char __user *buffer,size_t count,loff_t *pos) +char *buffer,size_t count,loff_t *pos) {int *data =pde_data (file_inode (file));char mode;if (count >0 ) { -if (get_user (mode, buffer)) -return -EFAULT ; + mode = buffer[0 ];if (mode >='0' && mode <='5' ) *data = mode -'0' ; }
@@ -149,7 +148,7 @@ static ssize_t alignment_proc_write(struct file *file,static const struct proc_ops alignment_proc_ops = { .proc_show = alignment_proc_show, - .proc_write = alignment_proc_write, + .proc_write_buf = alignment_proc_write, };/*
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 10/22 sparc: led: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written to /proc/led before it pins the entry, and parse the command from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
--- arch/sparc/kernel/led.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-)
arch/sparc/kernel/led.c +3 -10
@@ -60,17 +60,12 @@ static int led_proc_show(struct seq_file *m, void *v)return 0 ; } -static ssize_t led_proc_write (struct file *file,const char __user *buffer, +static ssize_t led_proc_write (struct file *file,char *buf,size_t count,loff_t *ppos) { -char *buf =NULL ; -if (count >LED_MAX_LENGTH ) count =LED_MAX_LENGTH ; - - buf =memdup_user_nul (buffer, count); -if (IS_ERR (buf)) -return PTR_ERR (buf); + buf[count] ='\0' ;/* work around \n when echo'ing into proc */ if (count >0 && buf[count -1 ] =='\n' )
@@ -95,14 +90,12 @@ static ssize_t led_proc_write(struct file *file, const char __user *buffer, auxio_set_led(AUXIO_LED_OFF); } -kfree (buf); -return count; }static const struct proc_ops led_proc_ops = { .proc_show = led_proc_show, - .proc_write = led_proc_write, + .proc_write_buf = led_proc_write, };#endif
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 11/22 um: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() for the mconsole and exitcode files so procfs copies in what is written before it pins the entry. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
exitcode parses procfs' copy as is, so it no longer stops at the first six bytes of what is written.
--- arch/um/drivers/mconsole_kern.c | 11 ++--------- arch/um/kernel/exitcode.c | 14 ++++---------- 2 files changed, 6 insertions(+), 19 deletions(-)
arch/um/drivers/mconsole_kern.c +2 -9
@@ -769,21 +769,14 @@ static int __init mconsole_init(void)__initcall (mconsole_init);static ssize_t mconsole_proc_write (struct file *file, -const char __user *buffer,size_t count,loff_t *pos) +char *buf,size_t count,loff_t *pos) { -char *buf; - - buf =memdup_user_nul (buffer, count); -if (IS_ERR (buf)) -return PTR_ERR (buf); -mconsole_notify (notify_socket,MCONSOLE_USER_NOTIFY , buf, count); -kfree (buf);return count; }static const struct proc_ops mconsole_proc_ops = { - .proc_write = mconsole_proc_write, + .proc_write_buf = mconsole_proc_write, .proc_lseek = noop_llseek, };
arch/um/kernel/exitcode.c +4 -10
@@ -32,18 +32,12 @@ static int exitcode_proc_show(struct seq_file *m, void *v) }static ssize_t exitcode_proc_write (struct file *file, -const char __user *buffer,size_t count,loff_t *pos) +char *buffer,size_t count,loff_t *pos) { -char *end, buf[sizeof ("nnnnn\0" )]; -size_t size; +char *end;int tmp; - size =min (count,sizeof (buf) -1 ); -if (copy_from_user (buf, buffer, size)) -return -EFAULT ; - buf[size] ='\0' ; - - tmp =simple_strtol (buf, &end,0 ); + tmp =simple_strtol (buffer, &end,0 );if ((*end !='\0' ) && !isspace (*end))return -EINVAL ;
@@ -53,7 +47,7 @@ static ssize_t exitcode_proc_write(struct file *file,static const struct proc_ops exitcode_proc_ops = { .proc_show = exitcode_proc_show, - .proc_write = exitcode_proc_write, + .proc_write_buf = exitcode_proc_write, };static int make_proc_exitcode (void )
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 12/22 x86/mtrr: let procfs own the seq_file of /proc/mtrr
In reply to Christian BraunerDeclare the show function and let procfs open and free the seq_file. ->proc_open() only does the checks now and ->proc_release() only drops the MTRRs the file added.
--- arch/x86/kernel/cpu/mtrr/if.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
arch/x86/kernel/cpu/mtrr/if.c +3 -4
@@ -349,7 +349,7 @@ static int mtrr_close(struct inode *ino, struct file *file)kfree (fcount);FILE_FCOUNT (file) =NULL ; } -return single_release (ino, file); +return 0 ; }static int mtrr_seq_show (struct seq_file *seq,void *offset)
@@ -391,13 +391,12 @@ static int mtrr_open(struct inode *inode, struct file *file)return -ENXIO ;if (!capable (CAP_SYS_ADMIN ))return -EPERM ; -return single_open (file, mtrr_seq_show,NULL ); +return 0 ; }static const struct proc_ops mtrr_proc_ops = { + .proc_show = mtrr_seq_show, .proc_open = mtrr_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, .proc_write = mtrr_write, .proc_ioctl = mtrr_ioctl, #ifdef CONFIG_COMPAT
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 13/22 x86/mtrr: let procfs copy in writes to /proc/mtrr
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written before it pins the entry, and parse the command from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
--- arch/x86/kernel/cpu/mtrr/if.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-)
arch/x86/kernel/cpu/mtrr/if.c +4 -5
@@ -91,7 +91,7 @@ mtrr_file_del(unsigned long base, unsigned long size,* "base=%Lx size=%Lx type=%s" or "disable=%d" */ static ssize_t -mtrr_write (struct file *file,const char __user *buf,size_t len,loff_t * ppos) +mtrr_write (struct file *file,char *buf,size_t len,loff_t * ppos) {int i, err;unsigned long reg;
@@ -103,9 +103,8 @@ mtrr_write(struct file *file, const char __user *buf, size_t len, loff_t * ppos)memset (line,0 ,LINE_SIZE ); len =min_t (size_t, len,LINE_SIZE -1 ); - length =strncpy_from_user (line, buf, len); -if (length <0 ) -return length; + length =strnlen (buf, len); +memcpy (line, buf, length); ptr = line + length -1 ;if (length && *ptr =='\n' )
@@ -397,7 +396,7 @@ static int mtrr_open(struct inode *inode, struct file *file)static const struct proc_ops mtrr_proc_ops = { .proc_show = mtrr_seq_show, .proc_open = mtrr_open, - .proc_write = mtrr_write, + .proc_write_buf = mtrr_write, .proc_ioctl = mtrr_ioctl, #ifdef CONFIG_COMPAT .proc_compat_ioctl = mtrr_ioctl,
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 14/22 x86/mtrr: let procfs copy the ioctl arguments of /proc/mtrr
In reply to Christian BraunerImplement ->proc_ioctl_buf() so procfs copies the arguments in before it pins the entry and back out after unpinning it. The native and the 32-bit commands encode their arguments and share one table. mtrr_ioctl() reads and fills procfs' copy, also in the 32-bit layouts.
--- arch/x86/kernel/cpu/mtrr/if.c | 79 +++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 33 deletions(-)
arch/x86/kernel/cpu/mtrr/if.c +46 -33
@@ -149,7 +149,7 @@ mtrr_write(struct file *file, char *buf, size_t len, loff_t * ppos) }static long -mtrr_ioctl (struct file *file,unsigned int cmd,unsigned long __arg) +mtrr_ioctl (struct file *file,unsigned int cmd,void *arg) {int err =0 ;mtrr_type type;
@@ -157,7 +157,6 @@ mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)unsigned long size;struct mtrr_sentry sentry;struct mtrr_gentry gentry; -void __user *arg = (void __user *) __arg;memset (&gentry,0 ,sizeof (gentry));
@@ -170,13 +169,11 @@ mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)case MTRRIOC_SET_PAGE_ENTRY :case MTRRIOC_DEL_PAGE_ENTRY :case MTRRIOC_KILL_PAGE_ENTRY : -if (copy_from_user (&sentry, arg,sizeof (sentry))) -return -EFAULT ; +memcpy (&sentry, arg,sizeof (sentry));break ;case MTRRIOC_GET_ENTRY :case MTRRIOC_GET_PAGE_ENTRY : -if (copy_from_user (&gentry, arg,sizeof (gentry))) -return -EFAULT ; +memcpy (&gentry, arg,sizeof (gentry));break ;#ifdef CONFIG_COMPAT case MTRRIOC32_ADD_ENTRY :
@@ -187,27 +184,21 @@ mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)case MTRRIOC32_SET_PAGE_ENTRY :case MTRRIOC32_DEL_PAGE_ENTRY :case MTRRIOC32_KILL_PAGE_ENTRY : { -struct mtrr_sentry32 __user *s32; +struct mtrr_sentry32 *s32 = arg; - s32 = (struct mtrr_sentry32 __user *)__arg; - err =get_user (sentry.base, &s32->base); - err |=get_user (sentry.size, &s32->size); - err |=get_user (sentry.type, &s32->type); -if (err) -return err; + sentry.base = s32->base; + sentry.size = s32->size; + sentry.type = s32->type;break ; }case MTRRIOC32_GET_ENTRY :case MTRRIOC32_GET_PAGE_ENTRY : { -struct mtrr_gentry32 __user *g32; +struct mtrr_gentry32 *g32 = arg; - g32 = (struct mtrr_gentry32 __user *)__arg; - err =get_user (gentry.regnum, &g32->regnum); - err |=get_user (gentry.base, &g32->base); - err |=get_user (gentry.size, &g32->size); - err |=get_user (gentry.type, &g32->type); -if (err) -return err; + gentry.regnum = g32->regnum; + gentry.base = g32->base; + gentry.size = g32->size; + gentry.type = g32->type;break ; }#endif
@@ -312,19 +303,17 @@ mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)switch (cmd) {case MTRRIOC_GET_ENTRY :case MTRRIOC_GET_PAGE_ENTRY : -if (copy_to_user (arg, &gentry,sizeof (gentry))) - err = -EFAULT ; +memcpy (arg, &gentry,sizeof (gentry));break ;#ifdef CONFIG_COMPAT case MTRRIOC32_GET_ENTRY :case MTRRIOC32_GET_PAGE_ENTRY : { -struct mtrr_gentry32 __user *g32; +struct mtrr_gentry32 *g32 = arg; - g32 = (struct mtrr_gentry32 __user *)__arg; - err =put_user (gentry.base, &g32->base); - err |=put_user (gentry.size, &g32->size); - err |=put_user (gentry.regnum, &g32->regnum); - err |=put_user (gentry.type, &g32->type); + g32->base = gentry.base; + g32->size = gentry.size; + g32->regnum = gentry.regnum; + g32->type = gentry.type;break ; }#endif
@@ -393,14 +382,38 @@ static int mtrr_open(struct inode *inode, struct file *file)return 0 ; } +static const struct proc_ioctl_arg mtrr_ioctl_args[] = { +PROC_IOCTL_IOC (MTRRIOC_ADD_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_SET_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_DEL_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_GET_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_KILL_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_ADD_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_SET_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_DEL_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_GET_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC_KILL_PAGE_ENTRY ), +#ifdef CONFIG_COMPAT +PROC_IOCTL_IOC (MTRRIOC32_ADD_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_SET_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_DEL_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_GET_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_KILL_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_ADD_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_SET_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_DEL_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_GET_PAGE_ENTRY ), +PROC_IOCTL_IOC (MTRRIOC32_KILL_PAGE_ENTRY ), +#endif +}; +static const struct proc_ops mtrr_proc_ops = { .proc_show = mtrr_seq_show, .proc_open = mtrr_open, .proc_write_buf = mtrr_write, - .proc_ioctl = mtrr_ioctl, -#ifdef CONFIG_COMPAT - .proc_compat_ioctl = mtrr_ioctl, -#endif + .proc_ioctl_buf = mtrr_ioctl, + .proc_ioctl_args = mtrr_ioctl_args, + .proc_nr_ioctl_args =ARRAY_SIZE (mtrr_ioctl_args), .proc_release = mtrr_close, };
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 15/22 xtensa: simdisk: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written to the simdisk files before it pins the entry, and take the file name from that copy. The files took up to PAGE_SIZE bytes and keep doing that through ->proc_write_size.
--- arch/xtensa/platforms/iss/simdisk.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-)
arch/xtensa/platforms/iss/simdisk.c +10 -16
@@ -241,7 +241,7 @@ static ssize_t proc_read_simdisk(struct file *file, char __user *buf,ssize_t len;char *temp; -/* a write sets at most a page, see proc_write_simdisk() */ +/* a write sets at most a page, see simdisk_proc_ops */ temp =kmalloc (PAGE_SIZE +2 ,GFP_KERNEL );if (!temp)return -ENOMEM ;
@@ -255,40 +255,34 @@ static ssize_t proc_read_simdisk(struct file *file, char __user *buf,return len; } -static ssize_t proc_write_simdisk (struct file *file,const char __user *buf, +static ssize_t proc_write_simdisk (struct file *file,char *buf,size_t count,loff_t *ppos) { -char *tmp;struct simdisk *dev =pde_data (file_inode (file));int err; -if (count ==0 || count >PAGE_SIZE ) +if (count ==0 )return -EINVAL ; - tmp =memdup_user_nul (buf, count); -if (IS_ERR (tmp)) -return PTR_ERR (tmp); - err =simdisk_detach (dev);if (err !=0 ) - goto out_free; +return err; -if (count >0 && tmp[count -1 ] =='\n' ) - tmp[count -1 ] =0 ; +if (count >0 && buf[count -1 ] =='\n' ) + buf[count -1 ] =0 ; -if (tmp[0 ]) - err =simdisk_attach (dev, tmp); +if (buf[0 ]) + err =simdisk_attach (dev, buf);if (err ==0 ) err = count; -out_free: -kfree (tmp);return err; }static const struct proc_ops simdisk_proc_ops = { .proc_read = proc_read_simdisk, - .proc_write = proc_write_simdisk, + .proc_write_buf = proc_write_simdisk, + .proc_write_size =PAGE_SIZE , .proc_lseek = default_llseek, };
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 16/22 xtensa: simdisk: let procfs copy out reads
In reply to Christian BraunerImplement ->proc_read_buf() so procfs copies what is read to userspace after it unpinned the entry.
--- arch/xtensa/platforms/iss/simdisk.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
arch/xtensa/platforms/iss/simdisk.c +3 -3
@@ -234,7 +234,7 @@ static int simdisk_detach(struct simdisk *dev)return err; } -static ssize_t proc_read_simdisk (struct file *file,char __user *buf, +static ssize_t proc_read_simdisk (struct file *file,char *buf,size_t size,loff_t *ppos) {struct simdisk *dev =pde_data (file_inode (file));
@@ -250,7 +250,7 @@ static ssize_t proc_read_simdisk(struct file *file, char __user *buf, len =scnprintf (temp,PAGE_SIZE +2 ,"%s\n" , dev->filename ?:"" );spin_unlock (&dev->lock); - len =simple_read_from_buffer (buf, size, ppos, temp, len); + len =memory_read_from_buffer (buf, size, ppos, temp, len);kfree (temp);return len; }
@@ -280,7 +280,7 @@ static ssize_t proc_write_simdisk(struct file *file, char *buf, }static const struct proc_ops simdisk_proc_ops = { - .proc_read = proc_read_simdisk, + .proc_read_buf = proc_read_simdisk, .proc_write_buf = proc_write_simdisk, .proc_write_size =PAGE_SIZE , .proc_lseek = default_llseek,
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 17/22 ACPI: proc: let procfs copy in writes to /proc/acpi/wakeup
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written before it pins the entry, and take the device name from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
--- drivers/acpi/proc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
drivers/acpi/proc.c +3 -4
@@ -86,7 +86,7 @@ static void physical_device_enable_wakeup(struct acpi_device *adev)static ssize_t acpi_system_write_wakeup_device (struct file *file, -const char __user * buffer, +char *buffer,size_t count,loff_t * ppos) {struct acpi_device *dev, *tmp;
@@ -96,8 +96,7 @@ acpi_system_write_wakeup_device(struct file *file,if (count >4 ) count =4 ; -if (copy_from_user (strbuf, buffer, count)) -return -EFAULT ; +memcpy (strbuf, buffer, count); strbuf[count] ='\0' ;sscanf (strbuf,"%s" , str);
@@ -123,7 +122,7 @@ acpi_system_write_wakeup_device(struct file *file,static const struct proc_ops acpi_system_wakeup_device_proc_ops = { .proc_show = acpi_system_wakeup_device_seq_show, - .proc_write = acpi_system_write_wakeup_device, + .proc_write_buf = acpi_system_write_wakeup_device, };void __init acpi_sleep_proc_init (void )
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 18/22 macintosh/via-pmu: let procfs copy in writes to the options file
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written before it pins the entry, and parse the option from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.
--- drivers/macintosh/via-pmu.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
drivers/macintosh/via-pmu.c +3 -4
@@ -913,7 +913,7 @@ static int pmu_options_proc_show(struct seq_file *m, void *v) }static ssize_t pmu_options_proc_write (struct file *file, -const char __user *buffer,size_t count,loff_t *pos) +char *buffer,size_t count,loff_t *pos) {char tmp[33 ];char *label, *val;
@@ -923,8 +923,7 @@ static ssize_t pmu_options_proc_write(struct file *file,return -EINVAL ;if (count >32 ) count =32 ; -if (copy_from_user (tmp, buffer, count)) -return -EFAULT ; +memcpy (tmp, buffer, count); tmp[count] =0 ; label = tmp;
@@ -958,7 +957,7 @@ static ssize_t pmu_options_proc_write(struct file *file,static const struct proc_ops pmu_options_proc_ops = { .proc_show = pmu_options_proc_show, - .proc_write = pmu_options_proc_write, + .proc_write_buf = pmu_options_proc_write, };#endif
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 19/22 platform/x86: thinkpad_acpi: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written to /proc/acpi/ibm/* before it pins the entry, and hand that copy to ->write(). procfs already rejects writes larger than PAGE_SIZE - 1 bytes.
--- drivers/platform/x86/lenovo/thinkpad_acpi.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-)
drivers/platform/x86/lenovo/thinkpad_acpi.c +3 -11
@@ -881,33 +881,25 @@ static int dispatch_proc_show(struct seq_file *m, void *v) }static ssize_t dispatch_proc_write (struct file *file, -const char __user *userbuf, +char *buf,size_t count,loff_t *pos) {struct ibm_struct *ibm =pde_data (file_inode (file)); -char *kernbuf;int ret;if (!ibm || !ibm->write)return -EINVAL ; -if (count >PAGE_SIZE -1 ) -return -EINVAL ; - kernbuf =memdup_user_nul (userbuf, count); -if (IS_ERR (kernbuf)) -return PTR_ERR (kernbuf); - ret = ibm->write (kernbuf); + ret = ibm->write (buf);if (ret ==0 ) ret = count; -kfree (kernbuf); -return ret; }static const struct proc_ops dispatch_proc_ops = { .proc_show = dispatch_proc_show, - .proc_write = dispatch_proc_write, + .proc_write_buf = dispatch_proc_write, };/****************************************************************************
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 20/22 platform/x86: toshiba_acpi: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() for the files in /proc/acpi/toshiba so procfs copies in what is written before it pins the entry, and parse what was written from that copy. Commands longer than PAGE_SIZE - 1 bytes are rejected now.
--- drivers/platform/x86/toshiba_acpi.c | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-)
drivers/platform/x86/toshiba_acpi.c +12 -24
@@ -1478,7 +1478,7 @@ static int set_lcd_status(struct backlight_device *bd)return set_lcd_brightness (dev, bd->props.brightness); } -static ssize_t lcd_proc_write (struct file *file,const char __user *buf, +static ssize_t lcd_proc_write (struct file *file,char *buf,size_t count,loff_t *pos) {struct toshiba_acpi_dev *dev =pde_data (file_inode (file));
@@ -1488,8 +1488,7 @@ static ssize_t lcd_proc_write(struct file *file, const char __user *buf,int value; len =min (count,sizeof (cmd) -1 ); -if (copy_from_user (cmd, buf, len)) -return -EFAULT ; +memcpy (cmd, buf, len); cmd[len] ='\0' ; levels = dev->backlight_dev->props.max_brightness +1 ;
@@ -1505,7 +1504,7 @@ static ssize_t lcd_proc_write(struct file *file, const char __user *buf,static const struct proc_ops lcd_proc_ops = { .proc_show = lcd_proc_show, - .proc_write = lcd_proc_write, + .proc_write_buf = lcd_proc_write, };/* Video-Out */
@@ -1541,24 +1540,17 @@ static int video_proc_show(struct seq_file *m, void *v)return 0 ; } -static ssize_t video_proc_write (struct file *file,const char __user *buf, +static ssize_t video_proc_write (struct file *file,char *buf,size_t count,loff_t *pos) {struct toshiba_acpi_dev *dev =pde_data (file_inode (file)); -char *buffer; -char *cmd; +char *buffer = buf;int lcd_out =-1 , crt_out =-1 , tv_out =-1 ;int remain = count;int value;int ret;u32 video_out; - cmd =memdup_user_nul (buf, count); -if (IS_ERR (cmd)) -return PTR_ERR (cmd); - - buffer = cmd; -/* * Scan expression. Multiple expressions may be delimited with ; * NOTE: To keep scanning simple, invalid fields are ignored.
@@ -1577,8 +1569,6 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf, }while (remain && *(buffer -1 ) !=';' ); } -kfree (cmd); - ret =get_video_status (dev, &video_out);if (!ret) {unsigned int new_video_out = video_out;
@@ -1602,7 +1592,7 @@ static ssize_t video_proc_write(struct file *file, const char __user *buf,static const struct proc_ops video_proc_ops = { .proc_show = video_proc_show, - .proc_write = video_proc_write, + .proc_write_buf = video_proc_write, };/* Fan status */
@@ -1644,7 +1634,7 @@ static int fan_proc_show(struct seq_file *m, void *v)return 0 ; } -static ssize_t fan_proc_write (struct file *file,const char __user *buf, +static ssize_t fan_proc_write (struct file *file,char *buf,size_t count,loff_t *pos) {struct toshiba_acpi_dev *dev =pde_data (file_inode (file));
@@ -1653,8 +1643,7 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf,int value; len =min (count,sizeof (cmd) -1 ); -if (copy_from_user (cmd, buf, len)) -return -EFAULT ; +memcpy (cmd, buf, len); cmd[len] ='\0' ; if (sscanf(cmd, " force_on : %i", &value) != 1 ||
@@ -1671,7 +1660,7 @@ static ssize_t fan_proc_write(struct file *file, const char __user *buf,static const struct proc_ops fan_proc_ops = { .proc_show = fan_proc_show, - .proc_write = fan_proc_write, + .proc_write_buf = fan_proc_write, };/* Fan RPM */
@@ -1707,7 +1696,7 @@ static int keys_proc_show(struct seq_file *m, void *v)return 0 ; } -static ssize_t keys_proc_write (struct file *file,const char __user *buf, +static ssize_t keys_proc_write (struct file *file,char *buf,size_t count,loff_t *pos) {struct toshiba_acpi_dev *dev =pde_data (file_inode (file));
@@ -1716,8 +1705,7 @@ static ssize_t keys_proc_write(struct file *file, const char __user *buf,int value; len =min (count,sizeof (cmd) -1 ); -if (copy_from_user (cmd, buf, len)) -return -EFAULT ; +memcpy (cmd, buf, len); cmd[len] ='\0' ;if (sscanf (cmd," hotkey_ready : %i" , &value) ==1 && value ==0 )
@@ -1730,7 +1718,7 @@ static ssize_t keys_proc_write(struct file *file, const char __user *buf,static const struct proc_ops keys_proc_ops = { .proc_show = keys_proc_show, - .proc_write = keys_proc_write, + .proc_write_buf = keys_proc_write, };static int __maybe_unused version_proc_show (struct seq_file *m,void *v)
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 21/22 PNP: pnpbios: let procfs copy in writes
In reply to Christian BraunerImplement ->proc_write_buf() so procfs copies in what is written to /proc/bus/pnp/<node> before it pins the entry, and take the node data from that copy. A node with more than PAGE_SIZE - 1 bytes of data can't be written anymore. I don't think there are any.
--- drivers/pnp/pnpbios/proc.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-)
drivers/pnp/pnpbios/proc.c +3 -6
@@ -171,7 +171,7 @@ static int pnpbios_proc_show(struct seq_file *m, void *v)return 0 ; } -static ssize_t pnpbios_proc_write (struct file *file,const char __user *buf, +static ssize_t pnpbios_proc_write (struct file *file,char *buf,size_t count,loff_t *pos) {void *data =pde_data (file_inode (file));
@@ -191,10 +191,7 @@ static ssize_t pnpbios_proc_write(struct file *file, const char __user *buf, ret = -EINVAL ; goto out; } -if (copy_from_user (node->data, buf, count)) { - ret = -EFAULT ; - goto out; - } +memcpy (node->data, buf, count);if (pnp_bios_set_dev_node (node->handle, boot, node) !=0 ) { ret = -EINVAL ; goto out;
@@ -207,7 +204,7 @@ out:static const struct proc_ops pnpbios_proc_ops = { .proc_show = pnpbios_proc_show, - .proc_write = pnpbios_proc_write, + .proc_write_buf = pnpbios_proc_write, };int pnpbios_interface_attach_device (struct pnp_bios_node *node)
Signature · 1 line
2.53.0
Christian Brauner
maintainerRFC PATCH 22/22 PNP: isapnp: let procfs copy out reads
In reply to Christian BraunerImplement ->proc_read_buf() so procfs copies what is read from /proc/bus/isapnp/<card>/<dev> to userspace after it unpinned the entry.
--- drivers/pnp/isapnp/proc.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-)
drivers/pnp/isapnp/proc.c +7 -13
@@ -19,37 +19,31 @@ static loff_t isapnp_proc_bus_lseek(struct file *file, loff_t off, int whence)return fixed_size_llseek (file, off, whence,256 ); } -static ssize_t isapnp_proc_bus_read (struct file *file,char __user * buf, +static ssize_t isapnp_proc_bus_read (struct file *file,char *buf,size_t nbytes,loff_t * ppos) {struct pnp_dev *dev =pde_data (file_inode (file));loff_t pos = *ppos; -int cnt, size =256 ; +int size =256 ; +size_t cnt;if (pos >= size)return 0 ;if (nbytes > size - pos) nbytes = size - pos; - cnt = nbytes; - -if (!access_ok (buf, cnt)) -return -EINVAL ;isapnp_cfg_begin (dev->card->number, dev->number); -for (; pos <256 && cnt >0 ; pos++, buf++, cnt--) { -unsigned char val; - val =isapnp_read_byte (pos); -put_user (val, buf); - } +for (cnt =0 ; cnt < nbytes; cnt++) + buf[cnt] =isapnp_read_byte (pos + cnt);isapnp_cfg_end (); - *ppos = pos; + *ppos = pos + nbytes;return nbytes; }static const struct proc_ops isapnp_proc_bus_proc_ops = { .proc_lseek = isapnp_proc_bus_lseek, - .proc_read = isapnp_proc_bus_read, + .proc_read_buf = isapnp_proc_bus_read, };static int isapnp_proc_attach_device (struct pnp_dev *dev)
Signature · 1 line
2.53.0
Christian Brauner
maintainercover proc: convert the driver entries
In reply to Christian BraunerThis converts the proc entries of nubus, zorro, PCI, md, dasd and cio, scsi, dell-smm, viafb, rndis, ipw2x00, sysrq and ALSA to what "proc:
let procfs own the data path" set up: procfs owns the seq_file, copies in writes, copies out reads and copies ioctl arguments in and out, so the entries' code only ever runs with the entry pinned and never touches userspace memory.
One patch per subsystem and method, in the order seq_file, write, read, ioctl. The PCI config space and ALSA's data entries take binary data at positions and keep doing that: PROC_ENTRY_SHORT_WRITES for writes in pieces, and ALSA's data entries ask for PROC_ENTRY_FULL_READS so that reading all of one at once keeps working. They hand procfs' buffer to the drivers, so struct snd_info_entry_ops changes along with them. The PCI ioctls only pass values and the i8k ones encode the wrong sizes, both list what is really copied. ALSA's ioctl hook that no driver implements goes away. Where a write that is larger than anything the entry parses now fails with -EINVAL from procfs I've called that out in the commit.
The selftest writes to the file of a scsi_debug host from a buffer backed by userfaultfd and removes the host while procfs copies in. On a KASAN and lockdep kernel it fails without the series and passes with it. Build tested per commit on x86_64 and with W=1 on the touched objects on their own architectures. The entries I could get at in a VM behave the same with and without the series.
This is on top of "proc: let procfs own the data path". The other three conversion series don't depend on it and it doesn't depend on them.
---
Christian Brauner (23):
nubus: let procfs own the seq_file of the resource files
zorro: let procfs copy out reads
PCI/proc: let procfs copy in config space writes
PCI/proc: let procfs copy out config space reads
PCI/proc: let procfs handle the ioctls
md: let procfs own the seq_file of /proc/mdstat
s390/dasd: let procfs copy in writes to /proc/dasd/statistics s390/cio: let procfs copy in writes
scsi: let procfs own the seq_file of the host files
scsi: core: let procfs copy in writes
selftests/proc: check that a faulting writer doesn't hold up removal
scsi: sg: let procfs own the seq_file of allow_dio and def_reserved_size
scsi: sg: let procfs copy in writes
scsi: esas2r: let procfs copy the ioctl argument of the ATTONode file
hwmon: (dell-smm) let procfs copy the i8k ioctl arguments
fbdev: viafb: let procfs copy in writes
usb: gadget: function: rndis: let procfs copy in writes
wifi: ipw2x00: let procfs copy in writes to the debug level
tty: sysrq: let procfs copy in writes to /proc/sysrq-trigger
ALSA: info: let procfs own the seq_file of text entries
ALSA: info: let procfs copy in writes
ALSA: info: let procfs copy out reads of data entries
ALSA: info: drop the ioctl hook of data entries
.../sound/kernel-api/writing-an-alsa-driver.rst | 10 +- drivers/hwmon/dell-smm-hwmon.c | 48 ++--- drivers/md/md.c | 15 +- drivers/net/wireless/intel/ipw2x00/libipw_module.c | 7 +- drivers/nubus/proc.c | 12 +- drivers/pci/proc.c | 69 ++++--- drivers/s390/block/dasd_proc.c | 17 +- drivers/s390/cio/blacklist.c | 33 +--- drivers/s390/cio/css.c | 4 +- drivers/scsi/esas2r/esas2r.h | 2 + drivers/scsi/esas2r/esas2r_ioctl.c | 58 +++--- drivers/scsi/esas2r/esas2r_main.c | 44 ++++- drivers/scsi/scsi_devinfo.c | 29 +-- drivers/scsi/scsi_proc.c | 55 ++---- drivers/scsi/sg.c | 44 ++--- drivers/tty/sysrq.c | 9 +- drivers/usb/gadget/function/rndis.c | 9 +- drivers/video/fbdev/via/viafbdev.c | 46 +++-- drivers/zorro/proc.c | 7 +- include/sound/gus.h | 2 +- include/sound/info.h | 6 +- sound/core/info.c | 57 +++--- sound/drivers/opl4/opl4_proc.c | 23 +-- sound/isa/gus/gus_dram.c | 7 +- sound/isa/gus/gus_mem_proc.c | 2 +- sound/pci/cs4281.c | 10 +- sound/pci/cs46xx/cs46xx_lib.c | 5 +- sound/pci/emu10k1/emuproc.c | 8 +- sound/pci/mixart/mixart.c | 10 +- sound/usb/mixer_scarlett2.c | 9 +- tools/testing/selftests/proc/.gitignore | 1 + tools/testing/selftests/proc/Makefile | 1 + tools/testing/selftests/proc/config | 3 + .../selftests/proc/proc-remove-slow-writer.c | 203 +++++++++++++++++++++ 34 files changed, 480 insertions(+), 385 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261101-work-procfs-drivers-58cd83649658
prerequisite-change-id: 20261101-work-procfs-core-8fa4a4f412cf:v1
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: 540aa9ea28adcbc54d8264f66314e29e3b8c9d49
prerequisite-patch-id: 2f7139d1c972c4439dccca610caa5f1288987a3d
prerequisite-patch-id: b0031a7c4855b7ec38259c59f30f65101a8fc4b5
prerequisite-patch-id: 0b7f226abf2b9ff41873a1aa55cf97747647d56b
prerequisite-patch-id: f63e345f897619af54813958046617c655fb7daf
prerequisite-patch-id: c0e21e762a2960e3324c5ab04634a7035e515fa6
prerequisite-patch-id: fd59c71eadd8f39c58b3baea93567bc093bc9b0e
prerequisite-patch-id: 5d52cc8a5c73e4c5ba16b8e24f51408c17583bf9
prerequisite-patch-id: 1f9179284e2f3314d4b46ac5e989089a21c7576c
prerequisite-patch-id: 10397bbf696bee16dc71eb79fff6c5e73842bcbc
prerequisite-patch-id: 9b44279dcf08912238d2f6438a3328a778a79a0c
prerequisite-patch-id: 16854b621a3a71dfc365aaf7abaa7f65adfe9f7e
prerequisite-patch-id: ca42eea21fae1fd1cd3518ab057c74b61ffd68fe
prerequisite-patch-id: 23255dc2be76b0479c083dbd4c897bfbb533aed5
prerequisite-patch-id: 82129c687ebf0a41b369893b92f2c038420ab801
prerequisite-patch-id: 56ad57a99a5bda075a9f4e844368974296744934
prerequisite-patch-id: 288b3cbaa11e01a778cc3d7beb6c0549aacf59f2
prerequisite-patch-id: a163b1549608652bfa5bd9ff93efa55b541010c4
prerequisite-patch-id: 78f067c343177e52d49ddfe9ee2992e602549869
prerequisite-patch-id: 19c12bc6e3fbed35acc1f657196b1a997f48cdfe
prerequisite-patch-id: 5800c3076694bad719954b2ad4d7448c6e013bfd
prerequisite-patch-id: 2a2f63d8843b159594b9eee6ddea028ce76a6270
prerequisite-patch-id: 40a1951d57906c4a1b98a4bd96a814b80339b9c5
prerequisite-patch-id: e2141077e69f20a2b27fab69c8d61d0686682f53
prerequisite-patch-id: c4d6018c26c40dc549d88330d23ce48f872c2b7f
prerequisite-patch-id: 6de8ec17746031bc6d1d9ac262c5be0f905fcd79
prerequisite-patch-id: b87bcb167884605bff1fd0f2ed8a88942d07efd5
prerequisite-patch-id: cfa9a8b4c67e20cfd22d77035bfd21ee96ab3298
prerequisite-patch-id: abf3482ed2399b7b2e1ee72e937af05bb9200667
prerequisite-patch-id: 02c175b2dee565a85b3308ecf2ec8e3c9a029e0f
prerequisite-patch-id: 4ed6b3d3d45de49d4967e88ba4e7f03372775f55
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: cf9ce0099005fd3bc31a1e15131d48e315ac3bee
prerequisite-patch-id: 16a657a5d0046068dbace29e7ea046ac1441f46f
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: e6ffe2a5c3ee5645f56c8366b568fbc9f140848c
prerequisite-patch-id: 117db27dcdef433de7c6be5e95973cac1e373927
prerequisite-patch-id: a0844aa26ffb65ccb362b88ef6c2f4758e15edbf
prerequisite-patch-id: 143fb91bddf33068b6ee11e205818c4c2815e23d
prerequisite-patch-id: 6490e4da1cb2f543683bd164bc9a5a1c943057e7
prerequisite-patch-id: cdefca57d3c552fb6f6b928699edd85f33be25fd
prerequisite-patch-id: 2f0fdb970ddd760d85ff5d2caad0b96348270182
prerequisite-patch-id: f2aa468ff5cbb1cabab3f70e1b8eeee0286cfb4a
prerequisite-patch-id: 037030d95eeb7c49b1e43f638ec954f2cebbcfb1
prerequisite-patch-id: 487141c49e2548310bfb6c2a0964a78f08b285a9
prerequisite-patch-id: c3d3008819fa5629a18142ec9ba6d7024ade7fdb
prerequisite-patch-id: ba15e7c86b60ccc2f0f7ee4c9bc99073068757bc
prerequisite-patch-id: 9423f410b99853693db06c29f12f1c08278b050d
prerequisite-patch-id: 4e7966978e4b11c4d843831f312cdcd5b848f00c
prerequisite-patch-id: 3d24be1897a624d519ef144801f76d152c7528a9
prerequisite-patch-id: 3b4d9fe7b26f6b7b9b3f7725ea333df357107c44
prerequisite-patch-id: 7d9dd345428baeb88c862ef902612f7160dc1d8c
Christian Brauner
maintainerRFC PATCH 1/23 nubus: let procfs own the seq_file of the resource files
Get the inode from the file instead of ->private, declare the show function and let procfs open and free the seq_file.
--- drivers/nubus/proc.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-)
drivers/nubus/proc.c +2 -10
@@ -107,7 +107,7 @@ nubus_proc_alloc_pde_data(unsigned char *ptr, unsigned int size)static int nubus_proc_rsrc_show (struct seq_file *m,void *v) { -struct inode *inode = m->private; +struct inode *inode =file_inode (m->file);struct nubus_proc_pde_data *pded; pded =pde_data (inode);
@@ -138,16 +138,8 @@ static int nubus_proc_rsrc_show(struct seq_file *m, void *v)return 0 ; } -static int nubus_rsrc_proc_open (struct inode *inode,struct file *file) -{ -return single_open (file, nubus_proc_rsrc_show, inode); -} -static const struct proc_ops nubus_rsrc_proc_ops = { - .proc_open = nubus_rsrc_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, + .proc_show = nubus_proc_rsrc_show, }; void nubus_proc_add_rsrc_mem(struct proc_dir_entry *procdir,
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 01/23] nubus: let procfs own the seq_file of the resource files
Sashiko has reviewed this patch and found no issues.
Quoted 4 lines
Get the inode from the file instead of ->private, declare the show function and let procfs open and free the
seq_file.Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Christian Brauner
maintainerRFC PATCH 2/23 zorro: let procfs copy out reads
In reply to Christian BraunerImplement ->proc_read_buf() so procfs copies what is read from /proc/bus/zorro/<slot> to userspace after it unpinned the entry.
--- drivers/zorro/proc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
drivers/zorro/proc.c +3 -4
@@ -28,7 +28,7 @@ proc_bus_zorro_lseek(struct file *file, loff_t off, int whence) }static ssize_t -proc_bus_zorro_read (struct file *file,char __user *buf,size_t nbytes,loff_t *ppos) +proc_bus_zorro_read (struct file *file,char *buf,size_t nbytes,loff_t *ppos) {struct zorro_dev *z =pde_data (file_inode (file));struct ConfigDev cd;
@@ -49,8 +49,7 @@ proc_bus_zorro_read(struct file *file, char __user *buf, size_t nbytes, loff_t * cd.cd_BoardAddr =cpu_to_be32 (zorro_resource_start (z)); cd.cd_BoardSize =cpu_to_be32 (zorro_resource_len (z)); -if (copy_to_user (buf, (void *)&cd + pos, nbytes)) -return -EFAULT ; +memcpy (buf, (void *)&cd + pos, nbytes); *ppos += nbytes;return nbytes;
@@ -58,7 +57,7 @@ proc_bus_zorro_read(struct file *file, char __user *buf, size_t nbytes, loff_t *static const struct proc_ops bus_zorro_proc_ops = { .proc_lseek = proc_bus_zorro_lseek, - .proc_read = proc_bus_zorro_read, + .proc_read_buf = proc_bus_zorro_read, };static void *zorro_seq_start (struct seq_file *m,loff_t *pos)
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH RFC 02/23] zorro: let procfs copy out reads
Sashiko has reviewed this patch and found no issues.
Quoted 4 lines
Implement ->proc_read_buf() so procfs copies what is read from /proc/bus/zorro/<slot> to userspace after it unpinned the entry.
Signed-off-by Christian Brauner (Amutable) <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2