Netdev List
 help / color / mirror / Atom feed
* [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does
@ 2026-11-04 19:04 Christian Brauner
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                   ` (5 more replies)
  0 siblings, 6 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:04 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Chuck Lever, Takashi Iwai, linux-input,
	linux-raid, linux-ext4, linux-modules, linux-debuggers

This series aims to get rid of copying to and from userspace while pinning
procfs entries allowing userspace to delay removal as long as it wants.

In essence, it aligns procfs with what kernfs is already doing. That
gets rid of a big attack surface and a whole bug class in one go. The
bounce buffer will come with some costs but luckily some codepaths (like
readdir) have been so criminally slow and buggy that massaging them ends
up speeding things up or at least leaving them on par with mainline even
with a bounce buffer in the mix.

There was a hackish and very narrow AI solution to a small part of this
bug class which was sent off-list. In essence, it used a second
buffering mechanism on top of seqfiles which was really nasty. It also
didn't address any of the non-seqfile users.

If we want to get rid of the this whole bug class we need a bigger
hammer. That means reworking how procfs handles data. So I tried to work
through all of that. The series is now in a decent shape (I'm not saying
"glorious" as there's so so many warts. If they were ironed out it could
be a lot nicer but that's for another day.).

I understand it's big but I think it is nicer to be able to look at the
end result since we have some transitory states that are gone at the end
of the series.

I have polished this a bit more and have sorted out a few more bugs and
tried to clean up some codepaths given the current constraints. Again, 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, remove_proc_entry() waits until every call into the procfs entry has
returned. Most of those calls copy to or from userspace with the entry
pinned.

A reader or writer can induce a page fault in that copy take as long as
it wants. For example, with userfaultfd or by punching holes into a
memfd over and over. Plus, the callers of remove_proc_entry() like to
hold locks while they wait. For example, addrconf holds RTNL when it
removes /proc/net/dev_snmp6/<dev> and xt_hashlimit holds a mutex that
all network namespaces share.

So 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.

If the caller lacks privileges for userfaultfd 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 of course, 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. For example, 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.

And btw, 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.

This series 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:

Part 1/7: proc: fix a few old bugs in /proc handlers (51 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.

Part 2/7: proc: let procfs own the data path (23 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. The open files procfs has
    to know and the wait queue a pollable entry is polled on live in an
    open node that only those entries get, the entry is pinned around
    the vm_operations of its mappings, and lockdep checks the pin.
    proc_create_seq() and friends and the plain seq_file users are
    switched over here too.

Part 3/7: proc: convert the arch and platform entries (23 patches)
Part 4/7: proc: convert the driver entries (26 patches)
Part 5/7: proc: convert the fs, kernel and mm entries (30 patches)
Part 6/7: proc: convert the net entries and the kfifo samples (13 patches)

Part 7/7: proc: drop the __user methods (6 patches)

    Drops the old methods. kcore, kmsg and the kpage files, which are
    never removed and keep reading straight into userspace, get
    file_operations of their own, so nothing in struct proc_ops takes a
    __user pointer anymore, and the new methods get the names kernfs
    uses, ->proc_read(), ->proc_write() and ->proc_ioctl(). The last
    patch takes the poll_table away from ->proc_poll(). This needs all
    four conversion series.

Parts 1 and 2 are the base. Parts 3 to 6 go on top of part 2 in any
order and don't depend on each other. Part 7 needs all four.

The series should all indicate cleanly what dependencies they have so b4
should be able to pull it for review.

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/000000000000f24f4705b6988f3e@google.com
[2]: https://lore.kernel.org/r/20231109212251.213873-7-johannes@sipsolutions.net

---
Link to v1: https://patch.msgid.link/20261102-defizit-jobaussicht-kurios-7016c6e67442@brauner

-- 
Christian Brauner <brauner@kernel.org>

^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7)
  2026-11-04 19:04 [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does Christian Brauner
@ 2026-11-04 19:05 ` Christian Brauner
  2026-11-04 19:06   ` [PATCH RESEND v2 01/51] alloc_tag: don't remove /proc/allocinfo under the codetag module lock Christian Brauner
                     ` (50 more replies)
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                   ` (4 subsequent siblings)
  5 siblings, 51 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:05 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable,
	Rafael J. Wysocki (Intel)

Part 1/7 of "proc: move procfs to what kernfs does", the base of the
other six parts:

  https://patch.msgid.link/20261104-work-procfs-v2-0-b45cd5789ac5@brauner

Moving procfs to what kernfs does touches just about every
->proc_read(), ->proc_write() and ->proc_ioctl() in the tree. Reviewing
that turned up bugs that have nothing to do with the conversion and are
a lot older. They go first so they can go to stable on their own. Four
selftests come with them.

In procfs itself:

- readdir resumed a listing at a counted position, so an entry that came
  or went in between repeated or skipped one, and it ended a listing
  early when the entry it was emitting went away. It resumes after the
  last name now, like kernfs. /proc/sys had the same bug.
- /proc/sys readdir ended a listing early at an entry that was being
  unregistered, so getdents64() reported EOF with entries left.
- /proc/sys copied to and from userspace with the table held, so a read,
  write or getdents64() that faults for as long as userspace wants kept
  unregister_sysctl_table() waiting, which addrconf calls under RTNL.
- proc_sys_make_inode() read the table after it had dropped sysctl_lock.
- a symlink's entry stayed pinned while readlink() copied it out, so a
  faulting readlink() held up remove_proc_entry(). ALSA removes its card
  links with snd_card_mutex held.
- name resolution walked through regular files, and pktgen lets
  userspace pick names, so an unprivileged user could hang an entry
  below another file and get it freed. pktgen refuses a '/' now.
- proc_register() WARNed on a duplicate name, which pktgen's names can
  provoke.
- proc_remove() removed by name, so removing an entry that was already
  gone could take out a different one.
- the parent of entries a dying network namespace left in /proc/net was
  freed silently. procfs warns now.
- on NOMMU every /proc file could be mapped privately, which reads it
  with nommu_region_sem held, and /proc/maps takes that lock. Any user
  could hang the machine.
- sunrpc queued the pollers of its channel files on a wait queue in the
  per-namespace cache_detail, which is freed with the namespace. Back to
  one static queue here, the conversion makes procfs own the queues.
- vmcore_cleanup() could run twice at once.

In the handlers:

- PCI/proc and isapnp truncated the position to an int, so a pwrite()
  or pread() at a huge offset went negative and accessed config space
  far outside the file.
- dasd looked in front of its buffer on an empty write.
- toshiba_acpi's brightness and fan write checks could never fail.
- simdisk read the file name without the lock that protects it, so a
  reader could copy out memory a concurrent detach had freed.
- rtas_flash read past its validate message, and rtasd handed out what
  vmalloc() left in its log buffer.
- ALSA's info layer let a write past the end of a data entry through
  unclamped.
- BusLogic, ips and libipw returned 0 from writes, so stdio writers
  retry forever.
- the scsi host files and um's /proc/exitcode parsed writes without
  NUL-terminating them.
- ntfs3 tripped the page allocator's warning on a huge label write.
- esas2r took the lengths of its HBA, SMP and firmware ioctls from
  userspace unchecked, through a file everyone can open.
- powerpc's copy_oldmem_page() didn't check ioremap_cache() and only
  recovered from a machine check for read(), the pseries ofdt parser
  read past its input, and lparcfg reported a failed hcall as a huge
  write.
- alloc_tag removed /proc/allocinfo with the codetag module lock held
  when a module's tags couldn't be reserved, and a reader of the file
  takes that lock with the entry pinned, so the module load and the
  reader waited for each other for good.
- esas2r copied the kernel addresses of the adapter, its pci_dev and its
  Scsi_Host out through the 0444 ATTONode.
- cs46xx stored the chip in the private data of its region files and
  read it back as the region.
- isapnp went on talking to whatever device the card had selected, and
  unlocked a mutex it never took, when isapnp_cfg_begin() refused a
  logical device number.
- simdisk freed the gendisk before it removed the entry, so a write in
  between set the capacity of a freed disk.
- the pseries ofdt commands leaked a node reference per property
  command, and lockd handed out the NUL behind "Y\n".
- lparcfg and psi overwrote the last byte of a write to terminate it,
  so a write without a trailing newline lost a character.
- the sparc LED and rtas progress files consumed only the start of a
  write, so a looping writer sent the rest as a second command.
- profiling reported success when /proc/profile couldn't be created,
  and zorro sized its files like a zorro_dev instead of the ConfigDev
  they serve.

The xt_recent deadlock v1 fixed here goes in through netfilter instead.

The procfs, sysctl, readlink, pktgen, sunrpc, PCI and duplicate name
bugs reproduce on a KASAN and lockdep kernel in a VM and are gone with
the series. The rest is build tested.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- thirteen more fixes that the review of v1 turned up: alloc_tag,
  esas2r, cs46xx, isapnp, simdisk, pseries ofdt, lockd, lparcfg, psi,
  the sparc LED, rtas progress, profiling and zorro
- the NOMMU, crash_dump and lparcfg patches say more about the bug
- new patch: sysctl readdir ended a listing early when an entry was
  being unregistered, pre-existing
- the xt_recent patch is dropped, Cen Zhang's fix for the same lock is
  on its way through netfilter
- the readdir churn selftests skip instead of failing without
  /dev/net/tun, the sysctl removal selftest seeks past the dots first so
  its fault lands in an entry, the selftests' Makefile, .gitignore and
  config lines sit in the series that adds each test
- struct sysctl_dirent loses its unused mode
- Reviewed-by and Acked-by from Rafael, Takashi and Chuck
- rebased onto v7.4-rc1
- Link to v1: https://patch.msgid.link/20261102-work-procfs-bugfixes-v1-0-59422ed3e465@kernel.org

---
Christian Brauner (51):
      alloc_tag: don't remove /proc/allocinfo under the codetag module lock
      scsi: esas2r: don't copy kernel pointers out in EXPRESS_IOCTL_GET_MOD_INFO
      ALSA: cs46xx: hand the region to the region proc files
      PNP: isapnp: don't go on when isapnp_cfg_begin() fails
      xtensa: simdisk: remove the proc entry before the disk
      powerpc/pseries: put the node that the ofdt property commands look up
      lockd: don't hand out the terminator of nlm_end_grace
      powerpc/pseries/lparcfg: don't drop the last byte of a write
      profiling: report it when /proc/profile can't be created
      zorro: size the proc files like what they serve
      sparc: led: consume the whole write
      powerpc/rtas: consume the whole write to /proc/powerpc/rtas/progress
      sched/psi: don't drop the last byte of a trigger write
      proc: sysctl: don't stop readdir at an entry that is being unregistered
      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()
      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-proc.c                    |   3 +-
 arch/powerpc/kernel/rtas_flash.c                   |   2 +-
 arch/powerpc/kernel/rtasd.c                        |   2 +-
 arch/powerpc/platforms/pseries/lparcfg.c           |   7 +-
 arch/powerpc/platforms/pseries/reconfig.c          |  24 +-
 arch/sparc/kernel/led.c                            |   3 +-
 arch/um/kernel/exitcode.c                          |   3 +-
 arch/xtensa/platforms/iss/simdisk.c                |  28 +-
 drivers/net/wireless/intel/ipw2x00/libipw_module.c |   2 +-
 drivers/pci/proc.c                                 |  15 +-
 drivers/platform/x86/toshiba_acpi.c                |   8 +-
 drivers/pnp/isapnp/core.c                          |  16 +-
 drivers/pnp/isapnp/proc.c                          |  11 +-
 drivers/s390/block/dasd.c                          |   2 +-
 drivers/scsi/BusLogic.c                            |   2 +-
 drivers/scsi/esas2r/esas2r.h                       |   2 +-
 drivers/scsi/esas2r/esas2r_ioctl.c                 |  48 ++-
 drivers/scsi/esas2r/esas2r_main.c                  |   3 +-
 drivers/scsi/ips.c                                 |   2 +-
 drivers/scsi/scsi_proc.c                           |   1 +
 drivers/zorro/proc.c                               |   2 +-
 fs/lockd/procfs.c                                  |   2 +-
 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                              | 388 ++++++++++++++-------
 fs/proc/root.c                                     |   1 +
 fs/proc/vmcore.c                                   |   5 +
 include/linux/sunrpc/cache.h                       |   1 -
 kernel/profile.c                                   |   8 +-
 kernel/sched/psi.c                                 |   4 +-
 mm/alloc_tag.c                                     |  27 +-
 net/core/pktgen.c                                  |   4 +
 net/sunrpc/cache.c                                 |  12 +-
 sound/core/info.c                                  |   2 +
 sound/isa/es18xx.c                                 |   6 +-
 sound/pci/cs46xx/cs46xx_lib.c                      |   2 +-
 tools/testing/selftests/proc/.gitignore            |   4 +
 tools/testing/selftests/proc/Makefile              |   4 +
 tools/testing/selftests/proc/config                |   9 +
 tools/testing/selftests/proc/proc-readdir-churn.c  | 240 +++++++++++++
 .../selftests/proc/proc-remove-slow-readlink.c     | 191 ++++++++++
 .../selftests/proc/proc-remove-slow-sysctl.c       | 215 ++++++++++++
 .../selftests/proc/proc-sysctl-readdir-churn.c     | 152 ++++++++
 47 files changed, 1420 insertions(+), 280 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261101-work-procfs-bugfixes-054d00028491


^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 01/51] alloc_tag: don't remove /proc/allocinfo under the codetag module lock
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
@ 2026-11-04 19:06   ` Christian Brauner
  2026-11-04 19:16   ` [PATCH RESEND v2 02/51] scsi: esas2r: don't copy kernel pointers out in EXPRESS_IOCTL_GET_MOD_INFO Christian Brauner
                     ` (49 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:06 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

codetag_alloc_module_section() takes the codetag type's mod_lock for
writing and calls reserve_module_tags() with it held. When that can't
reserve the tags, because the section's alignment doesn't fit, because
there are too many tags for the page flag bits or because the memory
for them can't be allocated, it shuts memory allocation profiling down
and removes /proc/allocinfo, still with mod_lock held.

remove_proc_entry() waits until every reader of the entry has let go of
it, and a reader of /proc/allocinfo takes mod_lock for reading in
allocinfo_start() while it holds the entry. So a module load that hits
one of those paths while /proc/allocinfo is being read waits for the
reader and the reader waits for the module load, for good.

Let a work item remove the entry in that case. The profiling is shut
down right away like before, the file just goes a moment later.

Fixes: 4835f747d3ed ("alloc_tag: support for page allocation tag compression")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 mm/alloc_tag.c | 27 ++++++++++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index 82e2c3448dcf..7d7ad6704953 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -15,6 +15,7 @@
 #include <linux/seq_file.h>
 #include <linux/string_choices.h>
 #include <linux/vmalloc.h>
+#include <linux/workqueue.h>
 #include <linux/kmemleak.h>
 #include <uapi/linux/alloc_tag.h>
 
@@ -604,6 +605,26 @@ static void shutdown_mem_profiling(bool remove_file)
 	mem_profiling_support = false;
 }
 
+static void remove_allocinfo_file(struct work_struct *work)
+{
+	remove_proc_entry(ALLOCINFO_FILE_NAME, NULL);
+}
+static DECLARE_WORK(remove_allocinfo_work, remove_allocinfo_file);
+
+/*
+ * A module's tags are reserved with the codetag module lock held for
+ * writing, and a reader of /proc/allocinfo takes that lock with the entry
+ * pinned. Removing the entry there would wait for that reader for good, so
+ * let a work item do it.
+ */
+static void shutdown_mem_profiling_async(void)
+{
+	if (!mem_profiling_support)
+		return;
+	shutdown_mem_profiling(false);
+	schedule_work(&remove_allocinfo_work);
+}
+
 void __init alloc_tag_sec_init(void)
 {
 	struct alloc_tag *last_codetag;
@@ -855,7 +876,7 @@ static void *reserve_module_tags(struct module *mod, unsigned long size,
 		align = 1;
 
 	if (!ensure_alignment(align, &prepend)) {
-		shutdown_mem_profiling(true);
+		shutdown_mem_profiling_async();
 		pr_err("%s: alignment %lu is incompatible with allocation tag indexing. Memory allocation profiling is disabled!\n",
 			mod->name, align);
 		return ERR_PTR(-EINVAL);
@@ -905,14 +926,14 @@ unlock:
 
 		module_tags.size = offset + size;
 		if (mem_alloc_profiling_enabled() && !tags_addressable()) {
-			shutdown_mem_profiling(true);
+			shutdown_mem_profiling_async();
 			pr_warn("With module %s there are too many tags to fit in %d page flag bits. Memory allocation profiling is disabled!\n",
 				mod->name, NR_UNUSED_PAGEFLAG_BITS);
 		}
 
 		grow_res = vm_module_tags_populate();
 		if (grow_res) {
-			shutdown_mem_profiling(true);
+			shutdown_mem_profiling_async();
 			pr_err("Failed to allocate memory for allocation tags in the module %s. Memory allocation profiling is disabled!\n",
 			       mod->name);
 			return ERR_PTR(grow_res);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 02/51] scsi: esas2r: don't copy kernel pointers out in EXPRESS_IOCTL_GET_MOD_INFO
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
  2026-11-04 19:06   ` [PATCH RESEND v2 01/51] alloc_tag: don't remove /proc/allocinfo under the codetag module lock Christian Brauner
@ 2026-11-04 19:16   ` Christian Brauner
  2026-11-04 19:16   ` [PATCH RESEND v2 03/51] ALSA: cs46xx: hand the region to the region proc files Christian Brauner
                     ` (48 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:16 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

EXPRESS_IOCTL_GET_MOD_INFO fills in the addresses of the adapter, its
pci_dev and its Scsi_Host and copies the whole struct back to userspace.
Nothing in userspace can do anything with those addresses, and the
ioctl is reachable through /proc/scsi/esas2r/ATTONode, which is created
with mode 0444. Only pass the host number.

Fixes: 26780d9e12ed ("[SCSI] esas2r: ATTO Technology ExpressSAS 6G SAS/SATA RAID Adapter Driver")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/scsi/esas2r/esas2r_ioctl.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/scsi/esas2r/esas2r_ioctl.c b/drivers/scsi/esas2r/esas2r_ioctl.c
index 3f7c1d131ec3..c93d9265e100 100644
--- a/drivers/scsi/esas2r/esas2r_ioctl.c
+++ b/drivers/scsi/esas2r/esas2r_ioctl.c
@@ -1468,9 +1468,10 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg)
 
 	case EXPRESS_IOCTL_GET_MOD_INFO:
 
-		ioctl->data.modinfo.adapter = a;
-		ioctl->data.modinfo.pci_dev = a->pcid;
-		ioctl->data.modinfo.scsi_host = a->host;
+		/* these are kernel pointers, userspace has no use for them */
+		ioctl->data.modinfo.adapter = NULL;
+		ioctl->data.modinfo.pci_dev = NULL;
+		ioctl->data.modinfo.scsi_host = NULL;
 		ioctl->data.modinfo.host_no = a->host->host_no;
 
 		break;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 03/51] ALSA: cs46xx: hand the region to the region proc files
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
  2026-11-04 19:06   ` [PATCH RESEND v2 01/51] alloc_tag: don't remove /proc/allocinfo under the codetag module lock Christian Brauner
  2026-11-04 19:16   ` [PATCH RESEND v2 02/51] scsi: esas2r: don't copy kernel pointers out in EXPRESS_IOCTL_GET_MOD_INFO Christian Brauner
@ 2026-11-04 19:16   ` Christian Brauner
  2026-11-04 19:16   ` [PATCH RESEND v2 04/51] PNP: isapnp: don't go on when isapnp_cfg_begin() fails Christian Brauner
                     ` (47 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:16 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

snd_cs46xx_proc_init() stores the chip in the private data of the five
region entries, but snd_cs46xx_io_read() reads that back as the region,
so a read of /proc/asound/cardN/CS46xx_BA0 and friends copies from
whatever sits at the region's offset in struct snd_cs46xx instead of
the mapped registers. Store the region.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 sound/pci/cs46xx/cs46xx_lib.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c
index 797a5f09cade..efc7df444cb5 100644
--- a/sound/pci/cs46xx/cs46xx_lib.c
+++ b/sound/pci/cs46xx/cs46xx_lib.c
@@ -2764,7 +2764,7 @@ static int snd_cs46xx_proc_init(struct snd_card *card, struct snd_cs46xx *chip)
 		struct snd_cs46xx_region *region = &chip->region.idx[idx];
 		if (! snd_card_proc_new(card, region->name, &entry)) {
 			entry->content = SNDRV_INFO_CONTENT_DATA;
-			entry->private_data = chip;
+			entry->private_data = region;
 			entry->c.ops = &snd_cs46xx_proc_io_ops;
 			entry->size = region->size;
 			entry->mode = S_IFREG | 0400;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 04/51] PNP: isapnp: don't go on when isapnp_cfg_begin() fails
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (2 preceding siblings ...)
  2026-11-04 19:16   ` [PATCH RESEND v2 03/51] ALSA: cs46xx: hand the region to the region proc files Christian Brauner
@ 2026-11-04 19:16   ` Christian Brauner
  2026-11-04 19:17   ` [PATCH RESEND v2 05/51] xtensa: simdisk: remove the proc entry before the disk Christian Brauner
                     ` (46 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:16 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

isapnp_cfg_begin() refuses a logical device number above 10 and a bad
card number with -EINVAL before it takes isapnp_cfg_mutex. The callers
that read and write the device's configuration, the proc file included,
don't look at that: they go on talking to whatever device the card
currently has selected and then unlock a mutex they never took in
isapnp_cfg_end(). isapnp_create_device() hands out logical device
numbers without a bound, so a card with twelve or more logical devices
gets there. Return the error instead.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pnp/isapnp/core.c | 16 ++++++++++++----
 drivers/pnp/isapnp/proc.c |  5 ++++-
 sound/isa/es18xx.c        |  6 +++++-
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/drivers/pnp/isapnp/core.c b/drivers/pnp/isapnp/core.c
index 219f96f2aaaf..3591a2791c1a 100644
--- a/drivers/pnp/isapnp/core.c
+++ b/drivers/pnp/isapnp/core.c
@@ -853,7 +853,9 @@ static int isapnp_get_resources(struct pnp_dev *dev)
 
 	pnp_dbg(&dev->dev, "get resources\n");
 	pnp_init_resources(dev);
-	isapnp_cfg_begin(dev->card->number, dev->number);
+	ret = isapnp_cfg_begin(dev->card->number, dev->number);
+	if (ret)
+		return ret;
 	dev->active = isapnp_read_byte(ISAPNP_CFG_ACTIVATE);
 	if (!dev->active)
 		goto __end;
@@ -887,10 +889,12 @@ __end:
 static int isapnp_set_resources(struct pnp_dev *dev)
 {
 	struct resource *res;
-	int tmp;
+	int tmp, ret;
 
 	pnp_dbg(&dev->dev, "set resources\n");
-	isapnp_cfg_begin(dev->card->number, dev->number);
+	ret = isapnp_cfg_begin(dev->card->number, dev->number);
+	if (ret)
+		return ret;
 	dev->active = 1;
 	for (tmp = 0; tmp < ISAPNP_MAX_PORT; tmp++) {
 		res = pnp_get_resource(dev, IORESOURCE_IO, tmp);
@@ -936,9 +940,13 @@ static int isapnp_set_resources(struct pnp_dev *dev)
 
 static int isapnp_disable_resources(struct pnp_dev *dev)
 {
+	int ret;
+
 	if (!dev->active)
 		return -EINVAL;
-	isapnp_cfg_begin(dev->card->number, dev->number);
+	ret = isapnp_cfg_begin(dev->card->number, dev->number);
+	if (ret)
+		return ret;
 	isapnp_deactivate(dev->number);
 	dev->active = 0;
 	isapnp_cfg_end();
diff --git a/drivers/pnp/isapnp/proc.c b/drivers/pnp/isapnp/proc.c
index c1f6e6bd0eb2..b3abab24050b 100644
--- a/drivers/pnp/isapnp/proc.c
+++ b/drivers/pnp/isapnp/proc.c
@@ -25,6 +25,7 @@ static ssize_t isapnp_proc_bus_read(struct file *file, char __user * buf,
 	struct pnp_dev *dev = pde_data(file_inode(file));
 	int pos = *ppos;
 	int cnt, size = 256;
+	int ret;
 
 	if (pos >= size)
 		return 0;
@@ -37,7 +38,9 @@ static ssize_t isapnp_proc_bus_read(struct file *file, char __user * buf,
 	if (!access_ok(buf, cnt))
 		return -EINVAL;
 
-	isapnp_cfg_begin(dev->card->number, dev->number);
+	ret = isapnp_cfg_begin(dev->card->number, dev->number);
+	if (ret)
+		return ret;
 	for (; pos < 256 && cnt > 0; pos++, buf++, cnt--) {
 		unsigned char val;
 		val = isapnp_read_byte(pos);
diff --git a/sound/isa/es18xx.c b/sound/isa/es18xx.c
index 3cadbf7cf238..de1eda5a0656 100644
--- a/sound/isa/es18xx.c
+++ b/sound/isa/es18xx.c
@@ -1950,7 +1950,11 @@ static int snd_audiodrive_pnp_init_main(int dev, struct pnp_dev *pdev)
 	/* ok. hack using Vendor-Defined Card-Level registers */
 	/* skip csn and logdev initialization - already done in isapnp_configure */
 	if (pnp_device_is_isapnp(pdev)) {
-		isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev));
+		int ret = isapnp_cfg_begin(isapnp_card_number(pdev),
+					   isapnp_csn_number(pdev));
+
+		if (ret)
+			return ret;
 		isapnp_write_byte(0x27, pnp_irq(pdev, 0));	/* Hardware Volume IRQ Number */
 		if (mpu_port[dev] != SNDRV_AUTO_PORT)
 			isapnp_write_byte(0x28, pnp_irq(pdev, 0)); /* MPU-401 IRQ Number */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 05/51] xtensa: simdisk: remove the proc entry before the disk
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (3 preceding siblings ...)
  2026-11-04 19:16   ` [PATCH RESEND v2 04/51] PNP: isapnp: don't go on when isapnp_cfg_begin() fails Christian Brauner
@ 2026-11-04 19:17   ` Christian Brauner
  2026-11-04 19:17   ` [PATCH RESEND v2 06/51] powerpc/pseries: put the node that the ofdt property commands look up Christian Brauner
                     ` (45 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:17 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

simdisk_teardown() frees the gendisk and only then removes
/proc/simdisk/<n>. A write to that entry in between attaches the file
that was written to the disk, and simdisk_attach() sets the capacity of
the gendisk that is gone. Remove the entry first, remove_proc_entry()
waits for a writer that is in the middle of that.

Fixes: b6c7e873daf7 ("xtensa: ISS: add host file-based simulated disk")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/xtensa/platforms/iss/simdisk.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/xtensa/platforms/iss/simdisk.c b/arch/xtensa/platforms/iss/simdisk.c
index a2ddb49c269d..e51edb8cf872 100644
--- a/arch/xtensa/platforms/iss/simdisk.c
+++ b/arch/xtensa/platforms/iss/simdisk.c
@@ -379,12 +379,13 @@ static void simdisk_teardown(struct simdisk *dev, int which,
 {
 	char tmp[2] = { '0' + which, 0 };
 
+	/* a write to the entry attaches a file to the disk, so it goes first */
+	remove_proc_entry(tmp, procdir);
 	simdisk_detach(dev);
 	if (dev->gd) {
 		del_gendisk(dev->gd);
 		put_disk(dev->gd);
 	}
-	remove_proc_entry(tmp, procdir);
 }
 
 static void __exit simdisk_exit(void)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 06/51] powerpc/pseries: put the node that the ofdt property commands look up
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (4 preceding siblings ...)
  2026-11-04 19:17   ` [PATCH RESEND v2 05/51] xtensa: simdisk: remove the proc entry before the disk Christian Brauner
@ 2026-11-04 19:17   ` Christian Brauner
  2026-11-04 19:17   ` [PATCH RESEND v2 07/51] lockd: don't hand out the terminator of nlm_end_grace Christian Brauner
                     ` (44 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:17 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

parse_node() looks the node up with of_find_node_by_phandle(), which
returns it with a reference, and do_add_property(),
do_remove_property() and do_update_property() never put that
reference. Every property command written to /proc/powerpc/ofdt leaks
one. Put it when the command is done.

Fixes: 610d91511f99 ("[PATCH] powerpc: Add support for changing properties from userspace")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/platforms/pseries/reconfig.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/powerpc/platforms/pseries/reconfig.c b/arch/powerpc/platforms/pseries/reconfig.c
index 18e3f1a036e3..5a0c9c0b6253 100644
--- a/arch/powerpc/platforms/pseries/reconfig.c
+++ b/arch/powerpc/platforms/pseries/reconfig.c
@@ -271,8 +271,8 @@ static char *parse_node(char *buf, size_t bufsize, struct device_node **npp)
 
 static int do_add_property(char *buf, size_t bufsize)
 {
+	struct device_node *np __free(device_node) = NULL;
 	struct property *prop = NULL;
-	struct device_node *np;
 	unsigned char *value;
 	char *name, *end;
 	int length;
@@ -296,7 +296,7 @@ static int do_add_property(char *buf, size_t bufsize)
 
 static int do_remove_property(char *buf, size_t bufsize)
 {
-	struct device_node *np;
+	struct device_node *np __free(device_node) = NULL;
 	char *tmp;
 	buf = parse_node(buf, bufsize, &np);
 
@@ -315,7 +315,7 @@ static int do_remove_property(char *buf, size_t bufsize)
 
 static int do_update_property(char *buf, size_t bufsize)
 {
-	struct device_node *np;
+	struct device_node *np __free(device_node) = NULL;
 	unsigned char *value;
 	char *name, *end, *next_prop;
 	int length;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 07/51] lockd: don't hand out the terminator of nlm_end_grace
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (5 preceding siblings ...)
  2026-11-04 19:17   ` [PATCH RESEND v2 06/51] powerpc/pseries: put the node that the ofdt property commands look up Christian Brauner
@ 2026-11-04 19:17   ` Christian Brauner
  2026-11-04 19:17   ` [PATCH RESEND v2 08/51] powerpc/pseries/lparcfg: don't drop the last byte of a write Christian Brauner
                     ` (43 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:17 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

nlm_end_grace_read() answers with "Y\n" or "N\n" from a three byte
buffer and hands out all three bytes, the terminating NUL included.
Leave the NUL out.

Fixes: d68e3c4aa416 ("lockd: add a /proc/fs/lockd/nlm_end_grace file")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/lockd/procfs.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/lockd/procfs.c b/fs/lockd/procfs.c
index a01f08c8c2f3..74f7ab77535a 100644
--- a/fs/lockd/procfs.c
+++ b/fs/lockd/procfs.c
@@ -57,7 +57,7 @@ nlm_end_grace_read(struct file *file, char __user *buf, size_t size,
 	resp[1] = '\n';
 	resp[2] = '\0';
 
-	return simple_read_from_buffer(buf, size, pos, resp, sizeof(resp));
+	return simple_read_from_buffer(buf, size, pos, resp, sizeof(resp) - 1);
 }
 
 static const struct proc_ops lockd_end_grace_proc_ops = {

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 08/51] powerpc/pseries/lparcfg: don't drop the last byte of a write
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (6 preceding siblings ...)
  2026-11-04 19:17   ` [PATCH RESEND v2 07/51] lockd: don't hand out the terminator of nlm_end_grace Christian Brauner
@ 2026-11-04 19:17   ` Christian Brauner
  2026-11-04 19:18   ` [PATCH RESEND v2 09/51] profiling: report it when /proc/profile can't be created Christian Brauner
                     ` (42 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:17 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

lparcfg_write() terminates its copy of the write by overwriting the
last byte, so a write without a trailing newline loses its last
character: "partition_entitled_capacity=5" is parsed as an empty value
and fails with -EINVAL. Terminate behind the copy instead, and refuse a
write that fills the whole buffer, which can't be terminated.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/platforms/pseries/lparcfg.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/arch/powerpc/platforms/pseries/lparcfg.c b/arch/powerpc/platforms/pseries/lparcfg.c
index 3280b7968cab..9882f98c0efb 100644
--- a/arch/powerpc/platforms/pseries/lparcfg.c
+++ b/arch/powerpc/platforms/pseries/lparcfg.c
@@ -699,13 +699,12 @@ static ssize_t lparcfg_write(struct file *file, const char __user * buf,
 	if (!firmware_has_feature(FW_FEATURE_SPLPAR))
 		return -EINVAL;
 
-	if (count == 0 || count > sizeof(kbuf))
+	if (count == 0 || count >= sizeof(kbuf))
 		return -EINVAL;
 
 	if (copy_from_user(kbuf, buf, count))
 		return -EFAULT;
-
-	kbuf[count - 1] = '\0';
+	kbuf[count] = '\0';
 	tmp = strchr(kbuf, '=');
 	if (!tmp)
 		return -EINVAL;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 09/51] profiling: report it when /proc/profile can't be created
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (7 preceding siblings ...)
  2026-11-04 19:17   ` [PATCH RESEND v2 08/51] powerpc/pseries/lparcfg: don't drop the last byte of a write Christian Brauner
@ 2026-11-04 19:18   ` Christian Brauner
  2026-11-04 19:18   ` [PATCH RESEND v2 10/51] zorro: size the proc files like what they serve Christian Brauner
                     ` (41 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:18 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

create_proc_profile() returns 0 whether proc_create() worked or not, so
a write to /sys/kernel/profiling that turned profiling on but didn't
get the file reports success and there is no file to read the profile
from. Return -ENOMEM like the other failures of that write do.

Fixes: 22b8ce94708f ("profiling: dynamically enable readprofile at runtime")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/profile.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/kernel/profile.c b/kernel/profile.c
index e7defa90a34c..820d5ec909d4 100644
--- a/kernel/profile.c
+++ b/kernel/profile.c
@@ -216,15 +216,15 @@ static const struct proc_ops profile_proc_ops = {
 int __ref create_proc_profile(void)
 {
 	struct proc_dir_entry *entry;
-	int err = 0;
 
 	if (!prof_on)
 		return 0;
 	entry = proc_create("profile", S_IWUSR | S_IRUGO,
 			    NULL, &profile_proc_ops);
-	if (entry)
-		proc_set_size(entry, (1 + prof_len) * sizeof(atomic_t));
-	return err;
+	if (!entry)
+		return -ENOMEM;
+	proc_set_size(entry, (1 + prof_len) * sizeof(atomic_t));
+	return 0;
 }
 subsys_initcall(create_proc_profile);
 #endif /* CONFIG_PROC_FS */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 10/51] zorro: size the proc files like what they serve
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (8 preceding siblings ...)
  2026-11-04 19:18   ` [PATCH RESEND v2 09/51] profiling: report it when /proc/profile can't be created Christian Brauner
@ 2026-11-04 19:18   ` Christian Brauner
  2026-11-04 19:18   ` [PATCH RESEND v2 11/51] sparc: led: consume the whole write Christian Brauner
                     ` (40 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:18 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

The files under /proc/bus/zorro hand out a struct ConfigDev and seek
within that, but their size says sizeof(struct zorro_dev). Say
sizeof(struct ConfigDev).

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/zorro/proc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/zorro/proc.c b/drivers/zorro/proc.c
index f916bf60b312..4c6f8f1c4d71 100644
--- a/drivers/zorro/proc.c
+++ b/drivers/zorro/proc.c
@@ -108,7 +108,7 @@ static int __init zorro_proc_attach_device(unsigned int slot)
 				 &zorro_autocon[slot]);
 	if (!entry)
 		return -ENOMEM;
-	proc_set_size(entry, sizeof(struct zorro_dev));
+	proc_set_size(entry, sizeof(struct ConfigDev));
 	return 0;
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 11/51] sparc: led: consume the whole write
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (9 preceding siblings ...)
  2026-11-04 19:18   ` [PATCH RESEND v2 10/51] zorro: size the proc files like what they serve Christian Brauner
@ 2026-11-04 19:18   ` Christian Brauner
  2026-11-04 19:18   ` [PATCH RESEND v2 12/51] powerpc/rtas: consume the whole write to /proc/powerpc/rtas/progress Christian Brauner
                     ` (39 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:18 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

led_proc_write() looks at the first eight bytes of a write and returns
that many, so a writer that loops until everything is written, stdio
does, sends the rest as a second command. That one is unknown and
switches the LED off. Return the whole count, the rest is ignored.

Fixes: ee1858d3122d ("[SPARC]: Add sun4m LED driver.")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/sparc/kernel/led.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/sparc/kernel/led.c b/arch/sparc/kernel/led.c
index 9b53ac1fe533..a1478de9fe5d 100644
--- a/arch/sparc/kernel/led.c
+++ b/arch/sparc/kernel/led.c
@@ -68,6 +68,7 @@ static int led_proc_open(struct inode *inode, struct file *file)
 static ssize_t led_proc_write(struct file *file, const char __user *buffer,
 			      size_t count, loff_t *ppos)
 {
+	size_t len = count;
 	char *buf = NULL;
 
 	if (count > LED_MAX_LENGTH)
@@ -102,7 +103,7 @@ static ssize_t led_proc_write(struct file *file, const char __user *buffer,
 
 	kfree(buf);
 
-	return count;
+	return len;
 }
 
 static const struct proc_ops led_proc_ops = {

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 12/51] powerpc/rtas: consume the whole write to /proc/powerpc/rtas/progress
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (10 preceding siblings ...)
  2026-11-04 19:18   ` [PATCH RESEND v2 11/51] sparc: led: consume the whole write Christian Brauner
@ 2026-11-04 19:18   ` Christian Brauner
  2026-11-04 19:19   ` [PATCH RESEND v2 13/51] sched/psi: don't drop the last byte of a trigger write Christian Brauner
                     ` (38 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:18 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

ppc_rtas_progress_write() shows the first 255 bytes of a write and
returns that many, so a writer that loops until everything is written
shows the rest as a second message. Return the whole count, the rest is
ignored.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/rtas-proc.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/powerpc/kernel/rtas-proc.c b/arch/powerpc/kernel/rtas-proc.c
index f38df72e64b8..89b165986851 100644
--- a/arch/powerpc/kernel/rtas-proc.c
+++ b/arch/powerpc/kernel/rtas-proc.c
@@ -311,6 +311,7 @@ static int ppc_rtas_poweron_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)
 {
+	size_t len = count;
 	unsigned long hex;
 
 	if (count >= MAX_LINELENGTH)
@@ -324,7 +325,7 @@ static ssize_t ppc_rtas_progress_write(struct file *file,
 	hex = simple_strtoul(progress_led, NULL, 10);
 
 	rtas_progress ((char *)progress_led, hex);
-	return count;
+	return len;
 
 	/* clear the line */
 	/* rtas_progress("                   ", 0xffff);*/

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 13/51] sched/psi: don't drop the last byte of a trigger write
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (11 preceding siblings ...)
  2026-11-04 19:18   ` [PATCH RESEND v2 12/51] powerpc/rtas: consume the whole write to /proc/powerpc/rtas/progress Christian Brauner
@ 2026-11-04 19:19   ` Christian Brauner
  2026-11-04 19:19   ` [PATCH RESEND v2 14/51] proc: sysctl: don't stop readdir at an entry that is being unregistered Christian Brauner
                     ` (37 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:19 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

psi_write() copies at most 32 bytes of the write into its buffer and
terminates the buffer by overwriting its last byte. For a write shorter
than 32 bytes that is the last byte the writer sent. A trigger written
without a trailing newline loses its last digit then, "some 150000
1000000" turns into "some 150000 100000", and a shorter one like
"some 150 10000" fails with -EINVAL because the window doesn't fit
anymore. Only writes that end in a newline parse as intended.

Copy at most 31 bytes and terminate behind them.

Fixes: 0e94682b73bf ("psi: introduce psi monitor")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/sched/psi.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c
index 7c5381423334..8b59635e95d4 100644
--- a/kernel/sched/psi.c
+++ b/kernel/sched/psi.c
@@ -1566,11 +1566,11 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
 	if (!nbytes)
 		return -EINVAL;
 
-	buf_size = min(nbytes, sizeof(buf));
+	buf_size = min(nbytes, sizeof(buf) - 1);
 	if (copy_from_user(buf, user_buf, buf_size))
 		return -EFAULT;
 
-	buf[buf_size - 1] = '\0';
+	buf[buf_size] = '\0';
 
 	seq = file->private_data;
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 14/51] proc: sysctl: don't stop readdir at an entry that is being unregistered
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (12 preceding siblings ...)
  2026-11-04 19:19   ` [PATCH RESEND v2 13/51] sched/psi: don't drop the last byte of a trigger write Christian Brauner
@ 2026-11-04 19:19   ` Christian Brauner
  2026-11-04 19:19   ` [PATCH RESEND v2 15/51] PCI/proc: don't truncate the position of config space accesses Christian Brauner
                     ` (36 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:19 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

proc_sys_fill_cache() creates the inode of an entry that has no dentry
yet. Since ace0c791e6c3 ("proc/sysctl: Don't grab i_lock under
sysctl_lock.") proc_sys_make_inode() refuses to do that once the table
is being unregistered and fails with -ENOENT. readdir takes every table
it lists into use and unregister_sysctl_table() marks the table as
being unregistered before it waits for readdir to let go of it. So an
entry that is unregistered right then makes proc_sys_fill_cache() fail,
proc_sys_readdir() stops there and returns 0. If that getdents64() call
hadn't emitted anything before that entry userspace sees the end of the
directory and never gets the entries behind it. A listing of
/proc/sys/net/ipv6/conf can end early that way while a device goes
away.

So skip an entry whose table is being unregistered, like readdir
already skips a link it can't follow. Lookup treats such an entry as
gone too since ea5751ccd665 ("proc/sysctl: don't return ENOMEM on
lookup when a table is unregistering").

Fixes: ace0c791e6c3 ("proc/sysctl: Don't grab i_lock under sysctl_lock.")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/proc_sysctl.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 6c0859c66563..b4a309d49698 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -704,6 +704,9 @@ static bool proc_sys_fill_cache(struct file *file,
 			if (unlikely(res)) {
 				dput(child);
 
+				/* skip an entry whose table is being unregistered */
+				if (res == ERR_PTR(-ENOENT))
+					return true;
 				if (IS_ERR(res))
 					return false;
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 15/51] PCI/proc: don't truncate the position of config space accesses
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (13 preceding siblings ...)
  2026-11-04 19:19   ` [PATCH RESEND v2 14/51] proc: sysctl: don't stop readdir at an entry that is being unregistered Christian Brauner
@ 2026-11-04 19:19   ` Christian Brauner
  2026-11-04 19:19   ` [PATCH RESEND v2 16/51] PNP: isapnp: don't read past the end of the buffer on a large pread() Christian Brauner
                     ` (35 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:19 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

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.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pci/proc.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c
index 3d7a9ed5fc22..e0dc70123f82 100644
--- a/drivers/pci/proc.c
+++ b/drivers/pci/proc.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 16/51] PNP: isapnp: don't read past the end of the buffer on a large pread()
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (14 preceding siblings ...)
  2026-11-04 19:19   ` [PATCH RESEND v2 15/51] PCI/proc: don't truncate the position of config space accesses Christian Brauner
@ 2026-11-04 19:19   ` Christian Brauner
  2026-11-04 19:20   ` [PATCH RESEND v2 17/51] s390/dasd: don't look in front of the buffer on an empty write Christian Brauner
                     ` (34 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:19 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever,
	Rafael J. Wysocki (Intel), stable

isapnp_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.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reviewed-by: Rafael J. Wysocki (Intel) <rafael@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pnp/isapnp/proc.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/pnp/isapnp/proc.c b/drivers/pnp/isapnp/proc.c
index b3abab24050b..1829c20d5446 100644
--- a/drivers/pnp/isapnp/proc.c
+++ b/drivers/pnp/isapnp/proc.c
@@ -23,15 +23,13 @@ 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;
 	int ret;
 
 	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


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 17/51] s390/dasd: don't look in front of the buffer on an empty write
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (15 preceding siblings ...)
  2026-11-04 19:19   ` [PATCH RESEND v2 16/51] PNP: isapnp: don't read past the end of the buffer on a large pread() Christian Brauner
@ 2026-11-04 19:20   ` Christian Brauner
  2026-11-04 19:20   ` [PATCH RESEND v2 18/51] platform/x86: toshiba_acpi: reject malformed brightness and fan writes Christian Brauner
                     ` (33 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:20 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

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")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/s390/block/dasd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/s390/block/dasd.c b/drivers/s390/block/dasd.c
index 2ffe404b890f..e97891e58957 100644
--- a/drivers/s390/block/dasd.c
+++ b/drivers/s390/block/dasd.c
@@ -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;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 18/51] platform/x86: toshiba_acpi: reject malformed brightness and fan writes
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (16 preceding siblings ...)
  2026-11-04 19:20   ` [PATCH RESEND v2 17/51] s390/dasd: don't look in front of the buffer on an empty write Christian Brauner
@ 2026-11-04 19:20   ` Christian Brauner
  2026-11-04 19:20   ` [PATCH RESEND v2 19/51] xtensa: simdisk: don't read the file name without the lock Christian Brauner
                     ` (32 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:20 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

When 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.

Fixes: e1a949c1b988 ("toshiba_acpi: Refactor *{get, set} functions return value")
Fixes: 3e07e5baa2af ("toshiba_acpi: Add set_fan_status function")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/platform/x86/toshiba_acpi.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index a0b8060836d0..9eb8f1e5aeda 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -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))

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 19/51] xtensa: simdisk: don't read the file name without the lock
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (17 preceding siblings ...)
  2026-11-04 19:20   ` [PATCH RESEND v2 18/51] platform/x86: toshiba_acpi: reject malformed brightness and fan writes Christian Brauner
@ 2026-11-04 19:20   ` Christian Brauner
  2026-11-04 19:20   ` [PATCH RESEND v2 20/51] powerpc/rtas_flash: don't read past the validate message Christian Brauner
                     ` (31 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:20 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

proc_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.

Fixes: b6c7e873daf7 ("xtensa: ISS: add host file-based simulated disk")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/xtensa/platforms/iss/simdisk.c | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/arch/xtensa/platforms/iss/simdisk.c b/arch/xtensa/platforms/iss/simdisk.c
index e51edb8cf872..1248d960e5d3 100644
--- a/arch/xtensa/platforms/iss/simdisk.c
+++ b/arch/xtensa/platforms/iss/simdisk.c
@@ -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,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 20/51] powerpc/rtas_flash: don't read past the validate message
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (18 preceding siblings ...)
  2026-11-04 19:20   ` [PATCH RESEND v2 19/51] xtensa: simdisk: don't read the file name without the lock Christian Brauner
@ 2026-11-04 19:20   ` Christian Brauner
  2026-11-04 19:21   ` [PATCH RESEND v2 21/51] powerpc/rtasd: zero the error log buffer Christian Brauner
                     ` (30 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:20 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

get_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().

Fixes: a94a14720eaf ("powerpc/rtas_flash: Fix validate_flash buffer overflow issue")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/rtas_flash.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/powerpc/kernel/rtas_flash.c b/arch/powerpc/kernel/rtas_flash.c
index 722dbfb6fbf8..f1cd105f7426 100644
--- a/arch/powerpc/kernel/rtas_flash.c
+++ b/arch/powerpc/kernel/rtas_flash.c
@@ -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);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 21/51] powerpc/rtasd: zero the error log buffer
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (19 preceding siblings ...)
  2026-11-04 19:20   ` [PATCH RESEND v2 20/51] powerpc/rtas_flash: don't read past the validate message Christian Brauner
@ 2026-11-04 19:21   ` Christian Brauner
  2026-11-04 19:21   ` [PATCH RESEND v2 22/51] ALSA: info: don't write past the end of a data entry Christian Brauner
                     ` (29 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:21 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

rtas_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().

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/rtasd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/powerpc/kernel/rtasd.c b/arch/powerpc/kernel/rtasd.c
index fd40864bdb70..dd0df5e164a3 100644
--- a/arch/powerpc/kernel/rtasd.c
+++ b/arch/powerpc/kernel/rtasd.c
@@ -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");

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 22/51] ALSA: info: don't write past the end of a data entry
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (20 preceding siblings ...)
  2026-11-04 19:21   ` [PATCH RESEND v2 21/51] powerpc/rtasd: zero the error log buffer Christian Brauner
@ 2026-11-04 19:21   ` Christian Brauner
  2026-11-04 19:21   ` [PATCH RESEND v2 23/51] scsi: BusLogic: don't return 0 from writes to the proc file Christian Brauner
                     ` (28 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:21 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

snd_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.

Fixes: d97e1b78239c ("ALSA: info - Check file position validity in common layer")
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 sound/core/info.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/sound/core/info.c b/sound/core/info.c
index 023b4736aa6d..f608d335b6c9 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -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);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 23/51] scsi: BusLogic: don't return 0 from writes to the proc file
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (21 preceding siblings ...)
  2026-11-04 19:21   ` [PATCH RESEND v2 22/51] ALSA: info: don't write past the end of a data entry Christian Brauner
@ 2026-11-04 19:21   ` Christian Brauner
  2026-11-04 19:21   ` [PATCH RESEND v2 24/51] scsi: ips: " Christian Brauner
                     ` (27 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:21 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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) <brauner@kernel.org>
---
 drivers/scsi/BusLogic.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/scsi/BusLogic.c b/drivers/scsi/BusLogic.c
index 27b446c0c5d0..7ad814f549ae 100644
--- a/drivers/scsi/BusLogic.c
+++ b/drivers/scsi/BusLogic.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 24/51] scsi: ips: don't return 0 from writes to the proc file
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (22 preceding siblings ...)
  2026-11-04 19:21   ` [PATCH RESEND v2 23/51] scsi: BusLogic: don't return 0 from writes to the proc file Christian Brauner
@ 2026-11-04 19:21   ` Christian Brauner
  2026-11-04 19:22   ` [PATCH RESEND v2 25/51] wifi: libipw: don't return 0 from writes to debug_level Christian Brauner
                     ` (26 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:21 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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) <brauner@kernel.org>
---
 drivers/scsi/ips.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/scsi/ips.c b/drivers/scsi/ips.c
index c7d6c5aae340..85c4fb164743 100644
--- a/drivers/scsi/ips.c
+++ b/drivers/scsi/ips.c
@@ -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

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 25/51] wifi: libipw: don't return 0 from writes to debug_level
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (23 preceding siblings ...)
  2026-11-04 19:21   ` [PATCH RESEND v2 24/51] scsi: ips: " Christian Brauner
@ 2026-11-04 19:22   ` Christian Brauner
  2026-11-04 19:22   ` [PATCH RESEND v2 26/51] scsi: core: NUL-terminate what is written to the host proc files Christian Brauner
                     ` (25 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:22 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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) <brauner@kernel.org>
---
 drivers/net/wireless/intel/ipw2x00/libipw_module.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_module.c b/drivers/net/wireless/intel/ipw2x00/libipw_module.c
index 22358b2c2ecd..552c8ff3928b 100644
--- a/drivers/net/wireless/intel/ipw2x00/libipw_module.c
+++ b/drivers/net/wireless/intel/ipw2x00/libipw_module.c
@@ -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 = {

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 26/51] scsi: core: NUL-terminate what is written to the host proc files
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (24 preceding siblings ...)
  2026-11-04 19:22   ` [PATCH RESEND v2 25/51] wifi: libipw: don't return 0 from writes to debug_level Christian Brauner
@ 2026-11-04 19:22   ` Christian Brauner
  2026-11-04 19:22   ` [PATCH RESEND v2 27/51] um: NUL-terminate what is written to /proc/exitcode Christian Brauner
                     ` (24 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:22 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

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 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.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/scsi/scsi_proc.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/scsi/scsi_proc.c b/drivers/scsi/scsi_proc.c
index fdc355d783da..dbd03360b5a4 100644
--- a/drivers/scsi/scsi_proc.c
+++ b/drivers/scsi/scsi_proc.c
@@ -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:

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 27/51] um: NUL-terminate what is written to /proc/exitcode
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (25 preceding siblings ...)
  2026-11-04 19:22   ` [PATCH RESEND v2 26/51] scsi: core: NUL-terminate what is written to the host proc files Christian Brauner
@ 2026-11-04 19:22   ` Christian Brauner
  2026-11-04 19:22   ` [PATCH RESEND v2 28/51] ntfs3: don't warn when a huge label is written Christian Brauner
                     ` (23 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:22 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

exitcode_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.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/um/kernel/exitcode.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/um/kernel/exitcode.c b/arch/um/kernel/exitcode.c
index 43edc2aa57e4..0230354f4812 100644
--- a/arch/um/kernel/exitcode.c
+++ b/arch/um/kernel/exitcode.c
@@ -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))

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 28/51] ntfs3: don't warn when a huge label is written
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (26 preceding siblings ...)
  2026-11-04 19:22   ` [PATCH RESEND v2 27/51] um: NUL-terminate what is written to /proc/exitcode Christian Brauner
@ 2026-11-04 19:22   ` Christian Brauner
  2026-11-04 19:23   ` [PATCH RESEND v2 29/51] proc: don't walk through a non-directory when resolving a name Christian Brauner
                     ` (22 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:22 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

ntfs3_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.

Fixes: 7832e123490a ("fs/ntfs3: Add support /proc/fs/ntfs3/<dev>/volinfo and /proc/fs/ntfs3/<dev>/label")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/ntfs3/super.c | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index 29aabdc8a801..37e27b73b7a5 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -576,15 +576,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;
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 29/51] proc: don't walk through a non-directory when resolving a name
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (27 preceding siblings ...)
  2026-11-04 19:22   ` [PATCH RESEND v2 28/51] ntfs3: don't warn when a huge label is written Christian Brauner
@ 2026-11-04 19:23   ` Christian Brauner
  2026-11-04 19:23   ` [PATCH RESEND v2 30/51] net: pktgen: reject device names with a '/' Christian Brauner
                     ` (21 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:23 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

__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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 4c3fcaf68ef4..6ac9c4c13897 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -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;
 		}

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 30/51] net: pktgen: reject device names with a '/'
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (28 preceding siblings ...)
  2026-11-04 19:23   ` [PATCH RESEND v2 29/51] proc: don't walk through a non-directory when resolving a name Christian Brauner
@ 2026-11-04 19:23   ` Christian Brauner
  2026-11-04 19:23   ` [PATCH RESEND v2 31/51] proc: don't WARN about a name that is already registered Christian Brauner
                     ` (20 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:23 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

"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 '/'.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: e6fce5b916cd ("pktgen: multiqueue etc.")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/core/pktgen.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index d63eeadddfcc..02af70d360bc 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -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)) {

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 31/51] proc: don't WARN about a name that is already registered
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (29 preceding siblings ...)
  2026-11-04 19:23   ` [PATCH RESEND v2 30/51] net: pktgen: reject device names with a '/' Christian Brauner
@ 2026-11-04 19:23   ` Christian Brauner
  2026-11-04 19:23   ` [PATCH RESEND v2 32/51] proc: resume readdir after the last name it returned Christian Brauner
                     ` (19 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:23 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

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_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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 6c2f91e077f1 ("proc: use WARN() rather than printk+backtrace")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 6ac9c4c13897..9e59796349e1 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -402,9 +402,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);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 32/51] proc: resume readdir after the last name it returned
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (30 preceding siblings ...)
  2026-11-04 19:23   ` [PATCH RESEND v2 31/51] proc: don't WARN about a name that is already registered Christian Brauner
@ 2026-11-04 19:23   ` Christian Brauner
  2026-11-04 19:24   ` [PATCH RESEND v2 33/51] selftests/proc: check a /proc listing while entries come and go Christian Brauner
                     ` (18 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:23 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

proc_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.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: 895b4c0c79b0 ("fs/proc: fix uaf in proc_readdir_de()")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 9e59796349e1..588999058e66 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -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)
 {
@@ -278,6 +300,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
@@ -290,23 +346,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 {
@@ -319,8 +378,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);
@@ -348,6 +412,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,
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index df873df9afc5..04f7ce2b24ed 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -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)
 {
diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
index b1f5eafb069a..dbcf87a257e1 100644
--- a/fs/proc/proc_net.c
+++ b/fs/proc/proc_net.c
@@ -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)
diff --git a/fs/proc/root.c b/fs/proc/root.c
index 7fbbe92bf73a..dfee1caa26f7 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -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,
 };
 
 /*

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 33/51] selftests/proc: check a /proc listing while entries come and go
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (31 preceding siblings ...)
  2026-11-04 19:23   ` [PATCH RESEND v2 32/51] proc: resume readdir after the last name it returned Christian Brauner
@ 2026-11-04 19:24   ` Christian Brauner
  2026-11-04 19:24   ` [PATCH RESEND v2 34/51] proc: let proc_remove() remove the entry it was given Christian Brauner
                     ` (17 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:24 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

List /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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore           |   1 +
 tools/testing/selftests/proc/Makefile             |   1 +
 tools/testing/selftests/proc/config               |   4 +
 tools/testing/selftests/proc/proc-readdir-churn.c | 240 ++++++++++++++++++++++
 4 files changed, 246 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 9c9735570abf..8520c7878ea2 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -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
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index a7de2bb6d8be..e0c0d313f4d0 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -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
diff --git a/tools/testing/selftests/proc/config b/tools/testing/selftests/proc/config
index 68fbd2b35884..052e365af5a9 100644
--- a/tools/testing/selftests/proc/config
+++ b/tools/testing/selftests/proc/config
@@ -1 +1,5 @@
 CONFIG_PROC_FS=y
+CONFIG_NET_NS=y
+CONFIG_IPV6=y
+CONFIG_TUN=y
+CONFIG_USERFAULTFD=y
diff --git a/tools/testing/selftests/proc/proc-readdir-churn.c b/tools/testing/selftests/proc/proc-readdir-churn.c
new file mode 100644
index 000000000000..fd9b504b60f5
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-readdir-churn.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A listing of a /proc directory that takes more than one getdents64()
+ * must not show an entry twice or skip one when an entry is added or
+ * removed in between, and a listing must go on after an entry that is
+ * removed while it is being emitted. /proc/net/dev_snmp6/<dev> of a tun
+ * device comes and goes with the device.
+ */
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.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 SNMP6_DIR	"/proc/net/dev_snmp6"
+#define TUN_NAME	"a0"		/* sorts before "lo" */
+#define TUN_PATH	SNMP6_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))
+			c++;
+	return c;
+}
+
+/* removes the device once getdents64() faults on its entry, then resolves */
+struct stall {
+	int uffd;
+	int tun;
+	unsigned long addr;
+	unsigned long len;
+	int removed;
+};
+
+static void *stall_resolve(void *arg)
+{
+	struct stall *s = arg;
+	struct pollfd pfd = { .fd = s->uffd, .events = POLLIN };
+	struct uffdio_zeropage zero = {
+		.range = { .start = s->addr, .len = s->len },
+	};
+	struct uffd_msg msg;
+
+	if (poll(&pfd, 1, 10000) != 1)
+		return NULL;
+	if (read(s->uffd, &msg, sizeof(msg)) != sizeof(msg) ||
+	    msg.event != UFFD_EVENT_PAGEFAULT)
+		return NULL;
+	close(s->tun);
+	s->removed = access(TUN_PATH, F_OK) == -1;
+	ioctl(s->uffd, UFFDIO_ZEROPAGE, &zero);
+	return NULL;
+}
+
+FIXTURE(proc_readdir) {
+	int dir;
+	int n;
+	char names[NAMES][NAME_MAX + 1];
+	char buf[65536];
+};
+
+FIXTURE_SETUP(proc_readdir)
+{
+	int tun;
+
+	if (unshare(CLONE_NEWNET))
+		SKIP(return, "unshare(CLONE_NEWNET): %s", strerror(errno));
+	tun = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
+	if (tun < 0)
+		SKIP(return, "/dev/net/tun: %s", strerror(errno));
+	close(tun);
+	self->dir = open(SNMP6_DIR, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	if (self->dir < 0)
+		SKIP(return, SNMP6_DIR ": %s", strerror(errno));
+	self->n = 0;
+}
+
+FIXTURE_TEARDOWN(proc_readdir)
+{
+	close(self->dir);
+}
+
+/* a device added in front of the position doesn't repeat an entry */
+TEST_F(proc_readdir, add_in_front)
+{
+	int tun;
+
+	/* ".", ".." and "lo" */
+	ASSERT_EQ(list(self->dir, self->buf, SMALL_BUF, self->names, &self->n), 3);
+	ASSERT_EQ(count(self->names, self->n, "lo"), 1);
+
+	tun = tun_open(TUN_NAME);
+	ASSERT_GE(tun, 0);
+	ASSERT_EQ(access(TUN_PATH, F_OK), 0);
+
+	while (list(self->dir, self->buf, sizeof(self->buf), self->names,
+		    &self->n) > 0)
+		;
+	EXPECT_EQ(count(self->names, self->n, "lo"), 1);
+	close(tun);
+}
+
+/* a device removed in front of the position doesn't skip an entry */
+TEST_F(proc_readdir, remove_in_front)
+{
+	int tun;
+
+	tun = tun_open(TUN_NAME);
+	ASSERT_GE(tun, 0);
+	ASSERT_EQ(access(TUN_PATH, F_OK), 0);
+
+	/* ".", ".." and the device, "lo" doesn't fit */
+	ASSERT_EQ(list(self->dir, self->buf, SMALL_BUF, self->names, &self->n), 3);
+	ASSERT_EQ(count(self->names, self->n, TUN_NAME), 1);
+
+	/* closing the device unregisters it and removes its entry */
+	close(tun);
+	ASSERT_EQ(access(TUN_PATH, F_OK), -1);
+
+	while (list(self->dir, self->buf, sizeof(self->buf), self->names,
+		    &self->n) > 0)
+		;
+	EXPECT_EQ(count(self->names, self->n, "lo"), 1);
+}
+
+/* the entry being emitted is removed meanwhile, the listing goes on */
+TEST_F(proc_readdir, remove_current)
+{
+	struct uffdio_api api = { .api = UFFD_API };
+	struct uffdio_register reg = {};
+	long page = sysconf(_SC_PAGESIZE);
+	struct stall s = {};
+	pthread_t thread;
+	char *map;
+	long ret, off;
+
+	s.uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+	if (s.uffd < 0)
+		SKIP(return, "userfaultfd: %s", strerror(errno));
+	ASSERT_EQ(ioctl(s.uffd, UFFDIO_API, &api), 0);
+
+	/* the first page is there, the second one faults */
+	map = mmap(NULL, 2 * page, PROT_READ | PROT_WRITE,
+		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(map, MAP_FAILED);
+	map[0] = 0;
+	s.addr = (unsigned long)map + page;
+	s.len = page;
+	reg.range.start = s.addr;
+	reg.range.len = s.len;
+	reg.mode = UFFDIO_REGISTER_MODE_MISSING;
+	ASSERT_EQ(ioctl(s.uffd, UFFDIO_REGISTER, &reg), 0);
+
+	s.tun = tun_open(TUN_NAME);
+	ASSERT_GE(s.tun, 0);
+	ASSERT_EQ(access(TUN_PATH, F_OK), 0);
+	ASSERT_EQ(pthread_create(&thread, NULL, stall_resolve, &s), 0);
+
+	/*
+	 * ".." ends right before the second page, so writing the record of
+	 * the device stalls until it has been removed.
+	 */
+	ret = syscall(__NR_getdents64, self->dir, map + page - 48, 48 + page);
+	ASSERT_EQ(pthread_join(thread, NULL), 0);
+	ASSERT_TRUE(s.removed);
+	ASSERT_GT(ret, 0);
+	for (off = 0; off < ret;) {
+		struct dirent64 *d = (void *)(map + page - 48 + off);
+
+		if (self->n < NAMES)
+			strcpy(self->names[self->n++], d->d_name);
+		off += d->d_reclen;
+	}
+	ASSERT_EQ(count(self->names, self->n, TUN_NAME), 1);
+
+	while (list(self->dir, self->buf, sizeof(self->buf), self->names,
+		    &self->n) > 0)
+		;
+	EXPECT_EQ(count(self->names, self->n, "lo"), 1);
+	munmap(map, 2 * page);
+	close(s.uffd);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 34/51] proc: let proc_remove() remove the entry it was given
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (32 preceding siblings ...)
  2026-11-04 19:24   ` [PATCH RESEND v2 33/51] selftests/proc: check a /proc listing while entries come and go Christian Brauner
@ 2026-11-04 19:24   ` Christian Brauner
  2026-11-04 19:24   ` [PATCH RESEND v2 35/51] proc: warn about entries left in /proc/net of a dying namespace Christian Brauner
                     ` (16 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:24 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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->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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: a8ca16ea7b0a ("proc: Supply a function to remove a proc entry by PDE")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c | 78 ++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 51 insertions(+), 27 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 588999058e66..9a0a2fcac7c7 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -818,33 +818,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) {
@@ -872,6 +853,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)
@@ -883,8 +892,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);
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 35/51] proc: warn about entries left in /proc/net of a dying namespace
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (33 preceding siblings ...)
  2026-11-04 19:24   ` [PATCH RESEND v2 34/51] proc: let proc_remove() remove the entry it was given Christian Brauner
@ 2026-11-04 19:24   ` Christian Brauner
  2026-11-04 19:24   ` [PATCH RESEND v2 36/51] proc: don't let NOMMU map procfs files Christian Brauner
                     ` (15 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:24 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: e9720acd728a ("[NET]: Make /proc/net a symlink on /proc/self/net (v3)")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/proc_net.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
index dbcf87a257e1..df5484813d5d 100644
--- a/fs/proc/proc_net.c
+++ b/fs/proc/proc_net.c
@@ -410,7 +410,13 @@ out:
 
 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);
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 36/51] proc: don't let NOMMU map procfs files
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (34 preceding siblings ...)
  2026-11-04 19:24   ` [PATCH RESEND v2 35/51] proc: warn about entries left in /proc/net of a dying namespace Christian Brauner
@ 2026-11-04 19:24   ` Christian Brauner
  2026-11-04 19:25   ` [PATCH RESEND v2 37/51] proc/vmcore: don't let vmcore_cleanup() run twice at once Christian Brauner
                     ` (14 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:24 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

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 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. That covers every entry and not just /proc/maps, procfs can't
know which entry's read takes nommu_region_sem. 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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 786d7e1612f0 ("Fix rmmod/read/write races in /proc entries")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/inode.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index b7634f975d98..9933f03ab9fb 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -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,
 };

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 37/51] proc/vmcore: don't let vmcore_cleanup() run twice at once
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (35 preceding siblings ...)
  2026-11-04 19:24   ` [PATCH RESEND v2 36/51] proc: don't let NOMMU map procfs files Christian Brauner
@ 2026-11-04 19:25   ` Christian Brauner
  2026-11-04 19:25   ` [PATCH RESEND v2 38/51] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() Christian Brauner
                     ` (13 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:25 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

vmcore_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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: b500afff11f6 ("fadump: Invalidate registration and release reserved memory for general use.")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/vmcore.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/fs/proc/vmcore.c b/fs/proc/vmcore.c
index 1fe70c4ecc54..ccb795b85509 100644
--- a/fs/proc/vmcore.c
+++ b/fs/proc/vmcore.c
@@ -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;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 38/51] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page()
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (36 preceding siblings ...)
  2026-11-04 19:25   ` [PATCH RESEND v2 37/51] proc/vmcore: don't let vmcore_cleanup() run twice at once Christian Brauner
@ 2026-11-04 19:25   ` Christian Brauner
  2026-11-04 19:25   ` [PATCH RESEND v2 39/51] powerpc/crash_dump: recover from a machine check " Christian Brauner
                     ` (12 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:25 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

copy_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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 54c32021eb6f ("[PATCH] powerpc: Add arch-dependent copy_oldmem_page")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/crash_dump.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/powerpc/kernel/crash_dump.c b/arch/powerpc/kernel/crash_dump.c
index 103b6605dd68..1543917bd3e1 100644
--- a/arch/powerpc/kernel/crash_dump.c
+++ b/arch/powerpc/kernel/crash_dump.c
@@ -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);
 	}

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 39/51] powerpc/crash_dump: recover from a machine check in copy_oldmem_page()
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (37 preceding siblings ...)
  2026-11-04 19:25   ` [PATCH RESEND v2 38/51] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() Christian Brauner
@ 2026-11-04 19:25   ` Christian Brauner
  2026-11-04 19:25   ` [PATCH RESEND v2 40/51] sunrpc: make queue_wait a global wait queue again Christian Brauner
                     ` (11 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:25 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

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 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, and those have been memcpy() for as long as
copy_oldmem_page() has had its kernel buffer branch, since commit
54c32021eb6f ("[PATCH] powerpc: Add arch-dependent copy_oldmem_page").

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().

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 54c32021eb6f ("[PATCH] powerpc: Add arch-dependent copy_oldmem_page")
Fixes: 4a22fd20379c ("vmcore: convert __read_vmcore to use an iov_iter")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/crash_dump.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/powerpc/kernel/crash_dump.c b/arch/powerpc/kernel/crash_dump.c
index 1543917bd3e1..ca7e019ae3e9 100644
--- a/arch/powerpc/kernel/crash_dump.c
+++ b/arch/powerpc/kernel/crash_dump.c
@@ -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);
 	}
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 40/51] sunrpc: make queue_wait a global wait queue again
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (38 preceding siblings ...)
  2026-11-04 19:25   ` [PATCH RESEND v2 39/51] powerpc/crash_dump: recover from a machine check " Christian Brauner
@ 2026-11-04 19:25   ` Christian Brauner
  2026-11-04 19:26   ` [PATCH RESEND v2 41/51] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls Christian Brauner
                     ` (10 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:25 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

Since 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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 552d0e17ea04 ("sunrpc: convert queue_wait from global to per-cache-detail waitqueue")
Acked-by: Chuck Lever <cel@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 include/linux/sunrpc/cache.h |  1 -
 net/sunrpc/cache.c           | 12 +++++++++---
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h
index 2735c332ddb7..bf4fc543e1e5 100644
--- a/include/linux/sunrpc/cache.h
+++ b/include/linux/sunrpc/cache.h
@@ -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 */
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 1bc04109d213..1ceaea7e01b9 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -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 @@ out:
 	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);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 41/51] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (39 preceding siblings ...)
  2026-11-04 19:25   ` [PATCH RESEND v2 40/51] sunrpc: make queue_wait a global wait queue again Christian Brauner
@ 2026-11-04 19:26   ` Christian Brauner
  2026-11-04 19:26   ` [PATCH RESEND v2 42/51] powerpc/pseries: bound the lengths in the ofdt property parser Christian Brauner
                     ` (9 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:26 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

esas2r_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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 26780d9e12ed ("[SCSI] esas2r: ATTO Technology ExpressSAS 6G SAS/SATA RAID Adapter Driver")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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(-)

diff --git a/drivers/scsi/esas2r/esas2r.h b/drivers/scsi/esas2r/esas2r.h
index a763edd05fe4..10101f50b282 100644
--- a/drivers/scsi/esas2r/esas2r.h
+++ b/drivers/scsi/esas2r/esas2r.h
@@ -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);
diff --git a/drivers/scsi/esas2r/esas2r_ioctl.c b/drivers/scsi/esas2r/esas2r_ioctl.c
index c93d9265e100..80ae5b9d45db 100644
--- a/drivers/scsi/esas2r/esas2r_ioctl.c
+++ b/drivers/scsi/esas2r/esas2r_ioctl.c
@@ -311,17 +311,27 @@ static int smp_ioctl_callback(struct esas2r_adapter *a,
 	return true;
 }
 
+/* The room from @member to the end of a struct atto_express_ioctl. */
+#define EXPRESS_IOCTL_ROOM(member)				\
+	(sizeof(struct atto_express_ioctl) -			\
+	 offsetof(struct atto_express_ioctl, member))
+
 static u8 handle_smp_ioctl(struct esas2r_adapter *a, struct atto_ioctl_smp *si)
 {
 	struct esas2r_buffered_ioctl bi;
+	u64 length = (u64)sizeof(struct atto_ioctl_smp) +
+		     le32_to_cpu(si->req_length) +
+		     le32_to_cpu(si->rsp_length);
+
+	/* The request and the response follow @si in the same buffer. */
+	if (length > EXPRESS_IOCTL_ROOM(data.ioctl_smp))
+		return IOCTL_INVALID_PARAM;
 
 	memset(&bi, 0, sizeof(bi));
 
 	bi.a = a;
 	bi.ioctl = si;
-	bi.length = sizeof(struct atto_ioctl_smp)
-		    + le32_to_cpu(si->req_length)
-		    + le32_to_cpu(si->rsp_length);
+	bi.length = length;
 	bi.offset = 0;
 	bi.callback = smp_ioctl_callback;
 	return handle_buffered_ioctl(&bi);
@@ -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));
 
 	bi.a = a;
@@ -1349,31 +1364,27 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg)
 			err = esas2r_write_fw(a,
 					      (char *)ioctl->data.fwrw.image,
 					      0,
-					      sizeof(struct
-						     atto_express_ioctl));
+					      EXPRESS_IOCTL_ROOM(data.fwrw.image));
 
 			if (err >= 0) {
 				err = esas2r_read_fw(a,
 						     (char *)ioctl->data.fwrw.
 						     image,
 						     0,
-						     sizeof(struct
-							    atto_express_ioctl));
+						     EXPRESS_IOCTL_ROOM(data.fwrw.image));
 			}
 		} else if (ioctl->data.fwrw.img_type == FW_IMG_FS_API) {
 			err = esas2r_write_fs(a,
 					      (char *)ioctl->data.fwrw.image,
 					      0,
-					      sizeof(struct
-						     atto_express_ioctl));
+					      EXPRESS_IOCTL_ROOM(data.fwrw.image));
 
 			if (err >= 0) {
 				err = esas2r_read_fs(a,
 						     (char *)ioctl->data.fwrw.
 						     image,
 						     0,
-						     sizeof(struct
-							    atto_express_ioctl));
+						     EXPRESS_IOCTL_ROOM(data.fwrw.image));
 			}
 		} else {
 			ioctl->header.return_code = IOCTL_BAD_FLASH_IMGTYPE;
@@ -1441,9 +1452,9 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg)
 		break;
 
 	case EXPRESS_IOCTL_HBA:
-		ioctl->header.return_code = handle_hba_ioctl(a,
-							     &ioctl->data.
-							     ioctl_hba);
+		ioctl->header.return_code =
+			handle_hba_ioctl(a, &ioctl->data.ioctl_hba,
+					 EXPRESS_IOCTL_ROOM(data.ioctl_hba));
 		break;
 
 	case EXPRESS_IOCTL_VDA:
diff --git a/drivers/scsi/esas2r/esas2r_main.c b/drivers/scsi/esas2r/esas2r_main.c
index ada278c24c51..75a70ae9441b 100644
--- a/drivers/scsi/esas2r/esas2r_main.c
+++ b/drivers/scsi/esas2r/esas2r_main.c
@@ -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);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 42/51] powerpc/pseries: bound the lengths in the ofdt property parser
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (40 preceding siblings ...)
  2026-11-04 19:26   ` [PATCH RESEND v2 41/51] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls Christian Brauner
@ 2026-11-04 19:26   ` Christian Brauner
  2026-11-04 19:26   ` [PATCH RESEND v2 43/51] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long Christian Brauner
                     ` (8 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:26 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

The 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.

Reported-by: Chris Mason <mason@kernel.org>
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")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/platforms/pseries/reconfig.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/arch/powerpc/platforms/pseries/reconfig.c b/arch/powerpc/platforms/pseries/reconfig.c
index 5a0c9c0b6253..b543afbc1efb 100644
--- a/arch/powerpc/platforms/pseries/reconfig.c
+++ b/arch/powerpc/platforms/pseries/reconfig.c
@@ -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);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 43/51] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (41 preceding siblings ...)
  2026-11-04 19:26   ` [PATCH RESEND v2 42/51] powerpc/pseries: bound the lengths in the ofdt property parser Christian Brauner
@ 2026-11-04 19:26   ` Christian Brauner
  2026-11-04 19:26   ` [PATCH RESEND v2 44/51] proc: sysctl: resume readdir after the last name it returned Christian Brauner
                     ` (7 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:26 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

h_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: an unknown error status is at least
negative, and one of the positive long-busy statuses comes back as a
byte count, as it does for H_SET_PPP already. Before commit
11529396ea31 ("powerpc/pseries: Split processor entitlement retrieval
and gathering to helper routines") a failed H_GET_PPP turned into -EIO.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 11529396ea31 ("powerpc/pseries: Split processor entitlement retrieval and gathering to helper routines")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/platforms/pseries/lparcfg.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/powerpc/platforms/pseries/lparcfg.c b/arch/powerpc/platforms/pseries/lparcfg.c
index 9882f98c0efb..4f6b2975c6e9 100644
--- a/arch/powerpc/platforms/pseries/lparcfg.c
+++ b/arch/powerpc/platforms/pseries/lparcfg.c
@@ -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;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 44/51] proc: sysctl: resume readdir after the last name it returned
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (42 preceding siblings ...)
  2026-11-04 19:26   ` [PATCH RESEND v2 43/51] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long Christian Brauner
@ 2026-11-04 19:26   ` Christian Brauner
  2026-11-04 19:27   ` [PATCH RESEND v2 45/51] selftests/proc: check a sysctl listing while entries come and go Christian Brauner
                     ` (6 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:26 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

proc_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.

Fixes: 77b14db502cb ("[PATCH] sysctl: reimplement the sysctl proc support")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/proc_sysctl.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 103 insertions(+), 3 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index b4a309d49698..ba52f13eb8f3 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -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 @@ out:
 	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,
@@ -756,8 +834,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;
 }
@@ -765,6 +846,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;
@@ -778,9 +860,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;
@@ -791,6 +884,12 @@ out:
 	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)
 {
@@ -870,6 +969,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 = {

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 45/51] selftests/proc: check a sysctl listing while entries come and go
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (43 preceding siblings ...)
  2026-11-04 19:26   ` [PATCH RESEND v2 44/51] proc: sysctl: resume readdir after the last name it returned Christian Brauner
@ 2026-11-04 19:27   ` Christian Brauner
  2026-11-04 19:27   ` [PATCH RESEND v2 46/51] proc: sysctl: copy to and from userspace without holding the table Christian Brauner
                     ` (5 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:27 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

List /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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore            |   1 +
 tools/testing/selftests/proc/Makefile              |   1 +
 .../selftests/proc/proc-sysctl-readdir-churn.c     | 152 +++++++++++++++++++++
 3 files changed, 154 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 8520c7878ea2..5b5e24e39cbd 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -25,4 +25,5 @@
 /self
 /setns-dcache
 /setns-sysvipc
+/proc-sysctl-readdir-churn
 /thread-self
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index e0c0d313f4d0..dae09f333455 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -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
diff --git a/tools/testing/selftests/proc/proc-sysctl-readdir-churn.c b/tools/testing/selftests/proc/proc-sysctl-readdir-churn.c
new file mode 100644
index 000000000000..b2ad097b464a
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-sysctl-readdir-churn.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A listing of a /proc/sys directory that takes more than one getdents64()
+ * must not show an entry twice or skip one when an entry is registered or
+ * unregistered in between. /proc/sys/net/ipv4/conf/<dev> of a tun device
+ * comes and goes with the device.
+ */
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <sched.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#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))
+			c++;
+	return c;
+}
+
+FIXTURE(sysctl_readdir) {
+	int dir;
+	int n;
+	char names[NAMES][NAME_MAX + 1];
+	char buf[65536];
+};
+
+FIXTURE_SETUP(sysctl_readdir)
+{
+	int tun;
+
+	if (unshare(CLONE_NEWNET))
+		SKIP(return, "unshare(CLONE_NEWNET): %s", strerror(errno));
+	tun = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
+	if (tun < 0)
+		SKIP(return, "/dev/net/tun: %s", strerror(errno));
+	close(tun);
+	self->dir = open(CONF_DIR, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	if (self->dir < 0)
+		SKIP(return, CONF_DIR ": %s", strerror(errno));
+	self->n = 0;
+}
+
+FIXTURE_TEARDOWN(sysctl_readdir)
+{
+	close(self->dir);
+}
+
+/* a device registered in front of the position doesn't repeat an entry */
+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);
+	ASSERT_EQ(access(TUN_PATH, F_OK), 0);
+
+	while (list(self->dir, self->buf, sizeof(self->buf), self->names,
+		    &self->n) > 0)
+		;
+	EXPECT_EQ(count(self->names, self->n, "all"), 1);
+	EXPECT_EQ(count(self->names, self->n, "default"), 1);
+	EXPECT_EQ(count(self->names, self->n, "lo"), 1);
+	close(tun);
+}
+
+/* a device unregistered in front of the position doesn't skip an entry */
+TEST_F(sysctl_readdir, unregister_in_front)
+{
+	int tun;
+
+	tun = tun_open(TUN_NAME);
+	ASSERT_GE(tun, 0);
+	ASSERT_EQ(access(TUN_PATH, F_OK), 0);
+
+	/* ".", ".." and the device, "all" doesn't fit */
+	ASSERT_EQ(list(self->dir, self->buf, SMALL_BUF, self->names, &self->n), 3);
+	ASSERT_EQ(count(self->names, self->n, TUN_NAME), 1);
+
+	/* closing the device unregisters its sysctls under RTNL */
+	close(tun);
+	ASSERT_EQ(access(TUN_PATH, F_OK), -1);
+
+	while (list(self->dir, self->buf, sizeof(self->buf), self->names,
+		    &self->n) > 0)
+		;
+	EXPECT_EQ(count(self->names, self->n, "all"), 1);
+	EXPECT_EQ(count(self->names, self->n, "default"), 1);
+	EXPECT_EQ(count(self->names, self->n, "lo"), 1);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 46/51] proc: sysctl: copy to and from userspace without holding the table
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (44 preceding siblings ...)
  2026-11-04 19:27   ` [PATCH RESEND v2 45/51] selftests/proc: check a sysctl listing while entries come and go Christian Brauner
@ 2026-11-04 19:27   ` Christian Brauner
  2026-11-04 19:27   ` [PATCH RESEND v2 47/51] proc: sysctl: look at the table under sysctl_lock when creating an inode Christian Brauner
                     ` (4 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:27 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

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, 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.

Fixes: 330d57fb98a9 ("[PATCH] Fix sysctl unregistration oops (CVE-2005-2709)")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/proc_sysctl.c | 50 ++++++++++++++++++++++++++++----------------------
 1 file changed, 28 insertions(+), 22 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index ba52f13eb8f3..f5c9f04e8509 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -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;
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 47/51] proc: sysctl: look at the table under sysctl_lock when creating an inode
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (45 preceding siblings ...)
  2026-11-04 19:27   ` [PATCH RESEND v2 46/51] proc: sysctl: copy to and from userspace without holding the table Christian Brauner
@ 2026-11-04 19:27   ` Christian Brauner
  2026-11-04 19:27   ` [PATCH RESEND v2 48/51] proc: sysctl: don't hold the tables while readdir copies to userspace Christian Brauner
                     ` (3 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:27 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/proc_sysctl.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index f5c9f04e8509..82024f1ea756 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -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;
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 48/51] proc: sysctl: don't hold the tables while readdir copies to userspace
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (46 preceding siblings ...)
  2026-11-04 19:27   ` [PATCH RESEND v2 47/51] proc: sysctl: look at the table under sysctl_lock when creating an inode Christian Brauner
@ 2026-11-04 19:27   ` Christian Brauner
  2026-11-04 19:27   ` [PATCH RESEND v2 49/51] selftests/proc: check that faulting sysctl accesses don't hold up removal Christian Brauner
                     ` (2 subsequent siblings)
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:27 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

proc_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, 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. An entry whose
table is being unregistered by then is still skipped. 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.

While at it, return errors instead of ending the listing as if the
directory was done.

Fixes: 77b14db502cb ("[PATCH] sysctl: reimplement the sysctl proc support")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/proc_sysctl.c | 311 +++++++++++++++++++++++++++-----------------------
 1 file changed, 166 insertions(+), 145 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 82024f1ea756..6a92c33db9b5 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -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,96 @@ 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 and its name. 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;
+	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 +466,55 @@ 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->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,112 +817,71 @@ 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);
 			if (unlikely(res)) {
 				dput(child);
 
-				/* skip an entry whose table is being unregistered */
-				if (res == ERR_PTR(-ENOENT))
-					return true;
 				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
@@ -878,22 +889,32 @@ 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);
+			/* skip an entry whose table is being unregistered */
+			if (err && err != -ENOENT)
+				break;
+			if (!err && !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);
-	return 0;
+	sysctl_dir_done(&d);
+	return err;
 }
 
 static int proc_sys_dir_release(struct inode *inode, struct file *file)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 49/51] selftests/proc: check that faulting sysctl accesses don't hold up removal
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (47 preceding siblings ...)
  2026-11-04 19:27   ` [PATCH RESEND v2 48/51] proc: sysctl: don't hold the tables while readdir copies to userspace Christian Brauner
@ 2026-11-04 19:27   ` Christian Brauner
  2026-11-04 19:28   ` [PATCH RESEND v2 50/51] proc: don't pin a symlink's entry while readlink() copies it out Christian Brauner
  2026-11-04 19:28   ` [PATCH RESEND v2 51/51] selftests/proc: check that a faulting readlink() doesn't hold up removal Christian Brauner
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:27 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Read, 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore            |   1 +
 tools/testing/selftests/proc/Makefile              |   1 +
 .../selftests/proc/proc-remove-slow-sysctl.c       | 215 +++++++++++++++++++++
 3 files changed, 217 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 5b5e24e39cbd..7b8133492782 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -21,6 +21,7 @@
 /proc-uptime-001
 /proc-uptime-002
 /proc-pidns
+/proc-remove-slow-sysctl
 /read
 /self
 /setns-dcache
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index dae09f333455..bce6beca7274 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -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
diff --git a/tools/testing/selftests/proc/proc-remove-slow-sysctl.c b/tools/testing/selftests/proc/proc-remove-slow-sysctl.c
new file mode 100644
index 000000000000..e2992a26ed69
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-remove-slow-sysctl.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A read(), write() or getdents64() of /proc/sys that faults while copying
+ * from or to userspace must not hold up unregistering the sysctl table.
+ * /proc/sys/net/ipv6/conf/<dev>/ of a tun device is unregistered 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 CONF_PATH	"/proc/sys/net/ipv6/conf/" TUN_NAME
+#define MTU_PATH	CONF_PATH "/mtu"
+#define BUF_PAGES	16
+#define TIMEOUT_MS	5000
+
+enum sysctl_op {
+	SYSCTL_READ,
+	SYSCTL_WRITE,
+	SYSCTL_GETDENTS,
+};
+
+struct worker {
+	enum sysctl_op op;
+	int fd;
+	char *buf;
+	size_t len;
+	ssize_t ret;
+	int err;
+};
+
+struct closer {
+	int tun;
+	int done;
+};
+
+static void *work_thread(void *arg)
+{
+	struct worker *w = arg;
+
+	switch (w->op) {
+	case SYSCTL_READ:
+		w->ret = read(w->fd, w->buf, w->len);
+		break;
+	case SYSCTL_WRITE:
+		w->ret = write(w->fd, w->buf, w->len);
+		break;
+	case SYSCTL_GETDENTS:
+		w->ret = syscall(__NR_getdents64, w->fd, w->buf, w->len);
+		break;
+	}
+	w->err = errno;
+	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;
+}
+
+FIXTURE(proc_sys_slow) {
+	int tun;
+	int uffd;
+	char *buf;
+	long psz;
+};
+
+FIXTURE_SETUP(proc_sys_slow)
+{
+	struct uffdio_api api = { .api = UFFD_API };
+	struct uffdio_register reg = {};
+	struct ifreq ifr = {};
+
+	if (unshare(CLONE_NEWNET))
+		SKIP(return, "unshare(CLONE_NEWNET): %s", strerror(errno));
+
+	self->tun = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
+	if (self->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(self->tun, TUNSETIFF, &ifr), 0);
+	if (access(CONF_PATH, F_OK))
+		SKIP(return, CONF_PATH ": %s", strerror(errno));
+
+	self->uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+	if (self->uffd < 0)
+		SKIP(return, "userfaultfd: %s", strerror(errno));
+	ASSERT_EQ(ioctl(self->uffd, UFFDIO_API, &api), 0);
+
+	/* Only the first page of the buffer goes through userfaultfd. */
+	self->psz = sysconf(_SC_PAGESIZE);
+	self->buf = mmap(NULL, BUF_PAGES * self->psz, PROT_READ | PROT_WRITE,
+			 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(self->buf, MAP_FAILED);
+	reg.range.start = (unsigned long)self->buf;
+	reg.range.len = self->psz;
+	reg.mode = UFFDIO_REGISTER_MODE_MISSING;
+	ASSERT_EQ(ioctl(self->uffd, UFFDIO_REGISTER, &reg), 0);
+}
+
+FIXTURE_TEARDOWN(proc_sys_slow)
+{
+}
+
+/*
+ * Let @w sleep in its copy from or to the first page of the buffer, then
+ * close the device. Unregistering the table must not wait for @w.
+ */
+static void stall_and_remove(struct __test_metadata *_metadata,
+			     FIXTURE_DATA(proc_sys_slow) *self,
+			     struct worker *w)
+{
+	struct closer c = { .tun = self->tun };
+	struct uffdio_zeropage zero = {};
+	struct uffd_msg msg;
+	struct pollfd pfd;
+	pthread_t wt, ct;
+	int done[2];
+
+	w->buf = self->buf;
+	ASSERT_EQ(pthread_create(&wt, NULL, work_thread, w), 0);
+
+	pfd = (struct pollfd){ .fd = self->uffd, .events = POLLIN };
+	ASSERT_EQ(poll(&pfd, 1, TIMEOUT_MS), 1);
+	ASSERT_EQ(read(self->uffd, &msg, sizeof(msg)), sizeof(msg));
+	ASSERT_EQ(msg.event, UFFD_EVENT_PAGEFAULT);
+
+	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("unregistering " CONF_PATH " waited for the faulting task");
+	}
+
+	zero.range.start = (unsigned long)self->buf;
+	zero.range.len = self->psz;
+	ASSERT_EQ(ioctl(self->uffd, UFFDIO_ZEROPAGE, &zero), 0);
+	ASSERT_EQ(pthread_join(wt, NULL), 0);
+	ASSERT_EQ(pthread_join(ct, NULL), 0);
+}
+
+TEST_F(proc_sys_slow, read)
+{
+	struct worker w = {
+		.op	= SYSCTL_READ,
+		.len	= BUF_PAGES * self->psz,
+	};
+
+	w.fd = open(MTU_PATH, O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(w.fd, 0);
+	stall_and_remove(_metadata, self, &w);
+
+	/* The value was produced before the table went away, then nothing. */
+	EXPECT_GT(w.ret, 0);
+	EXPECT_EQ(read(w.fd, self->buf, self->psz), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_F(proc_sys_slow, write)
+{
+	struct worker w = {
+		.op	= SYSCTL_WRITE,
+		.len	= 5,
+	};
+
+	w.fd = open(MTU_PATH, O_WRONLY | O_CLOEXEC);
+	ASSERT_GE(w.fd, 0);
+	stall_and_remove(_metadata, self, &w);
+
+	/* The table went away while the value was copied in. */
+	EXPECT_EQ(w.ret, -1);
+	EXPECT_EQ(w.err, ENOENT);
+}
+
+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);
+	/* start behind the dots so that it faults on an entry it looked up */
+	ASSERT_EQ(lseek(w.fd, 2, SEEK_SET), 2);
+	stall_and_remove(_metadata, self, &w);
+
+	/* What was looked up before the removal still arrives, then nothing. */
+	EXPECT_GT(w.ret, 0);
+	EXPECT_EQ(syscall(__NR_getdents64, w.fd, self->buf, self->psz), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 50/51] proc: don't pin a symlink's entry while readlink() copies it out
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (48 preceding siblings ...)
  2026-11-04 19:27   ` [PATCH RESEND v2 49/51] selftests/proc: check that faulting sysctl accesses don't hold up removal Christian Brauner
@ 2026-11-04 19:28   ` Christian Brauner
  2026-11-04 19:28   ` [PATCH RESEND v2 51/51] selftests/proc: check that a faulting readlink() doesn't hold up removal Christian Brauner
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:28 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, stable

proc_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.

Fixes: 7e0e953bb0cf ("procfs: fix race between symlink removals and traversals")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/inode.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 9933f03ab9fb..2d65f3c8ec03 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -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;
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 51/51] selftests/proc: check that a faulting readlink() doesn't hold up removal
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
                     ` (49 preceding siblings ...)
  2026-11-04 19:28   ` [PATCH RESEND v2 50/51] proc: don't pin a symlink's entry while readlink() copies it out Christian Brauner
@ 2026-11-04 19:28   ` Christian Brauner
  50 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:28 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Add 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore            |   1 +
 tools/testing/selftests/proc/Makefile              |   1 +
 tools/testing/selftests/proc/config                |   5 +
 .../selftests/proc/proc-remove-slow-readlink.c     | 191 +++++++++++++++++++++
 4 files changed, 198 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 7b8133492782..3fa89f46b9c7 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -22,6 +22,7 @@
 /proc-uptime-002
 /proc-pidns
 /proc-remove-slow-sysctl
+/proc-remove-slow-readlink
 /read
 /self
 /setns-dcache
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index bce6beca7274..df02699a3587 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -33,5 +33,6 @@ 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-readlink
 
 include ../lib.mk
diff --git a/tools/testing/selftests/proc/config b/tools/testing/selftests/proc/config
index 052e365af5a9..1504a761b39b 100644
--- a/tools/testing/selftests/proc/config
+++ b/tools/testing/selftests/proc/config
@@ -3,3 +3,8 @@ CONFIG_NET_NS=y
 CONFIG_IPV6=y
 CONFIG_TUN=y
 CONFIG_USERFAULTFD=y
+CONFIG_SOUND=y
+CONFIG_SND=y
+CONFIG_SND_DUMMY=m
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
diff --git a/tools/testing/selftests/proc/proc-remove-slow-readlink.c b/tools/testing/selftests/proc/proc-remove-slow-readlink.c
new file mode 100644
index 000000000000..3290225daecd
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-remove-slow-readlink.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A readlink() that faults while copying out the link must not hold up
+ * removal of the /proc symlink. Changing a sound card's id removes
+ * /proc/asound/<id> with snd_card_mutex and info_mutex held.
+ */
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/userfaultfd.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define CARD_ID		"Dummy"
+#define NEW_ID		"procslow"
+#define LINK_PATH	"/proc/asound/" CARD_ID
+#define TIMEOUT_MS	5000
+
+struct reader {
+	char *buf;
+	size_t len;
+	ssize_t ret;
+};
+
+struct renamer {
+	const char *id_path;
+	const char *id;
+	int done;
+	int ret;
+};
+
+static void *readlink_thread(void *arg)
+{
+	struct reader *r = arg;
+
+	r->ret = readlink(LINK_PATH, r->buf, r->len);
+	return NULL;
+}
+
+/* a new id removes the card's old /proc/asound/<id> link */
+static int set_id(const char *id_path, const char *id)
+{
+	int fd, ret;
+
+	fd = open(id_path, O_WRONLY | O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+	ret = write(fd, id, strlen(id)) < 0 ? -errno : 0;
+	close(fd);
+	return ret;
+}
+
+static void *rename_thread(void *arg)
+{
+	struct renamer *r = arg;
+	ssize_t ret;
+
+	r->ret = set_id(r->id_path, r->id);
+	ret = write(r->done, "x", 1);
+	return (void *)ret;
+}
+
+/* the sysfs id file of the card with @id */
+static int find_card(const char *id, char *id_path, size_t size)
+{
+	struct dirent *d;
+	int ret = -1;
+	DIR *dir;
+
+	dir = opendir("/sys/class/sound");
+	if (!dir)
+		return -1;
+	while (ret && (d = readdir(dir))) {
+		char cur[32];
+		FILE *f;
+
+		if (strncmp(d->d_name, "card", 4))
+			continue;
+		snprintf(id_path, size, "/sys/class/sound/%s/id", d->d_name);
+		f = fopen(id_path, "r");
+		if (!f)
+			continue;
+		if (fgets(cur, sizeof(cur), f)) {
+			cur[strcspn(cur, "\n")] = '\0';
+			if (!strcmp(cur, id))
+				ret = 0;
+		}
+		fclose(f);
+	}
+	closedir(dir);
+	return ret;
+}
+
+FIXTURE(dummy) {
+	char id_path[PATH_MAX];
+	bool renamed;
+};
+
+FIXTURE_SETUP(dummy)
+{
+	self->renamed = false;
+	if (access(LINK_PATH, F_OK) && system("modprobe -q snd-dummy"))
+		SKIP(return, "no snd-dummy");
+	if (access(LINK_PATH, F_OK))
+		SKIP(return, LINK_PATH ": %s", strerror(errno));
+	if (find_card(CARD_ID, self->id_path, sizeof(self->id_path)))
+		SKIP(return, "no sysfs id file for " CARD_ID);
+	if (access(self->id_path, W_OK))
+		SKIP(return, "%s: %s", self->id_path, strerror(errno));
+}
+
+FIXTURE_TEARDOWN(dummy)
+{
+	if (self->renamed && set_id(self->id_path, CARD_ID))
+		TH_LOG("couldn't name the card " CARD_ID " again");
+}
+
+TEST_F(dummy, remove_while_readlink_faults)
+{
+	struct uffdio_api api = { .api = UFFD_API };
+	struct uffdio_register reg = {};
+	struct uffdio_zeropage zero = {};
+	long psz = sysconf(_SC_PAGESIZE);
+	struct renamer rn = {};
+	struct reader r = {};
+	struct uffd_msg msg;
+	struct pollfd pfd;
+	pthread_t rt, nt;
+	int uffd, done[2];
+	char *buf;
+
+	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);
+
+	buf = mmap(NULL, 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, &reg), 0);
+
+	r.buf = buf;
+	r.len = psz;
+	ASSERT_EQ(pthread_create(&rt, NULL, readlink_thread, &r), 0);
+
+	/* The reader now sleeps in the copy of the link 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);
+
+	/* Renaming the card removes the link that is being read. */
+	ASSERT_EQ(pipe2(done, O_CLOEXEC), 0);
+	rn.id_path = self->id_path;
+	rn.id = NEW_ID;
+	rn.done = done[1];
+	self->renamed = true;
+	ASSERT_EQ(pthread_create(&nt, NULL, rename_thread, &rn), 0);
+	pfd = (struct pollfd){ .fd = done[0], .events = POLLIN };
+	EXPECT_EQ(poll(&pfd, 1, TIMEOUT_MS), 1) {
+		TH_LOG("removing " LINK_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(nt, NULL), 0);
+	EXPECT_EQ(rn.ret, 0);
+
+	/* The link was read before it went away. */
+	EXPECT_GT(r.ret, 0);
+	EXPECT_EQ(readlink(LINK_PATH, buf, psz), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7)
  2026-11-04 19:04 [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does Christian Brauner
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
@ 2026-11-04 19:29 ` Christian Brauner
  2026-11-04 19:29   ` [PATCH RESEND v2 01/23] proc: drop the compat file operations Christian Brauner
                     ` (22 more replies)
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                   ` (3 subsequent siblings)
  5 siblings, 23 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:29 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Part 2/7 of "proc: move procfs to what kernfs does", on top of part 1:

  https://patch.msgid.link/20261104-work-procfs-v2-0-b45cd5789ac5@brauner

remove_proc_entry() waits until every call into the entry has returned.
A seq_file entry wait across seq_read_iter() including the copy to
userspace. Non-seq_file entries wait across ->proc_read(),
->proc_write() and ->proc_ioctl(), which copy to and from userspace
as well.

A reader or writer can abuse a page fault to make copy take as long as
it wants. For example, by using userfaultfd or by punching holes into a
memfd over and over. And callers of remove_proc_entry() like to hold
locks while they wait. For instance, addrconf holds RTNL when it removes
/proc/net/dev_snmp6/<dev> and xt_hashlimit holds hashlimit_mutex which
is shared by all network namespaces.

There was a hackish and very narrow AI solution to a small part of this
bug class which was sent off-list. In essence, it used a second
buffering mechanism on top of seqfiles which was really nasty. It also
didn't address any of the non-seqfile users.

If we want to get rid of the this whole bug class we need a bigger
hammer. That means reworking how procfs handles data. So I tried to work
through all of that.

This changes procfs so the entry's code only ever runs with the entry
pinned and never touches userspace memory. This is the core procfs
change required for this. The entries themselves are converted in four
series on top of it. The are independent of each other. The last series
drops all __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. A nice side
  effect is that opening one of these entries no longer allocates a
  pde_opener as 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.

  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.

* 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. That works 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.

* permanent entries

  Entries that are never removed, kcore, kmsg and the kpage files, keep
  reading straight into userspace. The last series gives them
  file_operations of their own and takes the __user methods out of
  struct proc_ops altogether.

* mmap()

  procfs pins the entry around ->proc_mmap() and then wraps the
  vm_operations of the mapping the way kernfs does. So the entry's
  ->fault(), ->page_mkwrite(), ->access() and ->open() run pinned as
  well.

  Removing the entry unmaps the mappings in its own address space, like
  kernfs does, and a fault into one of them is SIGBUS from then on.

  A file that maps through another address space, /proc/bus/pci through
  the one of /dev/mem, keeps its mappings like a sysfs resource file
  does. The entry's vm_operations live in its open node. vmcore's
  mappings of the old memory no longer outlive fadump releasing it.

* Poll

  An entry with a ->proc_poll() used to sleep its pollers on a wait
  queue of its own and procfs only pinned the entry around the call.

  The poller stays on that queue after the call, so after
   remove_proc_entry() ran ->proc_release() on the still open file and
   the owner freed the queue with the rest.

   sunrpc kept the queue of its channel files in the per-namespace
   cache_detail, so a poll() across the end of the namespace was a
   use-after-free. md works around the same thing at unload by waking
   its pollers in a loop until nobody is left.

   Make procfs own the wait queue. It lives in the open node,
   proc_reg_poll() does the poll_wait() on it before it calls the
   method. The owner wakes the pollers with proc_poll_notify(), and a
   poll of a removed entry reports EPOLLERR | EPOLLPRI.

   The seven pollable entries in the tree move over in the conversion
   series.

   The last series takes the poll_table away from ->proc_poll() once
   nothing needs it anymore.

* Lockdep

  The pin is a read lock to lockdep and a removal takes the write side
  around waiting for the last user, the way kernfs annotates its active
  reference.

  A method that takes a lock while pinned and a removal under that lock
  is the deadlock xt_recent had, and lockdep reports it now instead of a
  reviewer having to find it.

  The class is per kind of entry, keyed on the seq_operations or
  proc_ops the entries of a kind share, and per registration site for
  the entries that only share a show function, like sysfs keys per
  attribute.

On a KASAN and lockdep kernel the reader selftest fails without the
series and passes with it, and the mapped-entry selftest, run against
a virtio-rng device in a VM, fails without the mmap patch 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- the open files of an entry live in an open node that only entries with
  a release or mmap hook get, a removal waits with wait_var_event(), the
  readdir cursor is shared with sysctl
- procfs pins the entry around the vm_operations of its mappings and
  unmaps a removed entry's own address space like kernfs does, with a
  selftest; a file that maps through another address space,
  /proc/bus/pci through the one of /dev/mem, keeps its mappings like a
  sysfs resource file; vmcore's mappings of the old memory no longer
  outlive fadump releasing it
- lockdep checks the pin
- procfs owns the wait queue of an entry with a ->proc_poll(), the
  conversions are in the area series
- the seq_file runs the entry's own ->next() and ->show() with procfs's
  ->start() and ->stop() around them instead of a wrapper per record,
  which cost up to 24% on /proc/vmstat, and the ERR_PTR(-EIO) sentinel
  is gone
- ioctls that only pass a value no longer copy to a NULL pointer
  (-EFAULT on csky), and registration also refuses a
  ->proc_compat_ioctl() next to ->proc_ioctl_buf()
- rebased onto v7.4-rc1
- Link to v1: https://patch.msgid.link/20261102-work-procfs-core-v1-0-75df35e4f8b8@kernel.org

---
Christian Brauner (23):
      proc: drop the compat file operations
      proc: add pde_pin() and pde_unpin()
      proc: share the readdir cursor with sysctl
      proc: wait for the last user of an entry with wait_var_event()
      proc: keep the open files of an entry in an open node
      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
      proc: pin the entry around the vm_operations of its mappings
      selftests/proc: check that a mapped entry can be removed
      proc: let lockdep check the pin
      proc: own the wait queue of an entry with a ->proc_poll()
      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                                  | 261 ++++++---
 fs/proc/inode.c                                    | 652 ++++++++++++++++-----
 fs/proc/internal.h                                 | 146 ++++-
 fs/proc/proc_net.c                                 |  72 +--
 fs/proc/proc_sysctl.c                              |  45 +-
 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                            | 159 ++++-
 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 +-
 tools/testing/selftests/proc/.gitignore            |   2 +
 tools/testing/selftests/proc/Makefile              |   2 +
 tools/testing/selftests/proc/proc-remove-mapped.c  | 160 +++++
 .../selftests/proc/proc-remove-slow-reader.c       | 133 +++++
 48 files changed, 1440 insertions(+), 916 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261101-work-procfs-core-8fa4a4f412cf
prerequisite-change-id: 20261101-work-procfs-bugfixes-054d00028491:v2
prerequisite-patch-id: a81e8b894107e48d607f8257d2c9f56e0dd6f400
prerequisite-patch-id: 7edf7ca7ecd32c9e09085e4b65922af10097beaa
prerequisite-patch-id: 81f6893f79b4236637675553298a6f2892b603f3
prerequisite-patch-id: 598c2b1a389f47adcac88ebcf1c9f74e2511fbab
prerequisite-patch-id: 6f5a2172dacc5ff2491fa9832c48ccd0e1ce1eda
prerequisite-patch-id: 8947af6490288e4e2a3b2bf8844a974bedb1d975
prerequisite-patch-id: 8cbe756286fd95967d31b0c46cacfeb05f0a2c6b
prerequisite-patch-id: 3fa159f2b6ccd0ac51a2662e4eec5587ec18de89
prerequisite-patch-id: bfb4e4ffe9631cb58015fefe6bd3698cd53e2244
prerequisite-patch-id: 56ff6667a57a962264c07c0227c61aa2f1f4c22c
prerequisite-patch-id: bc506323e29944ec0c29eae84950c706a8592042
prerequisite-patch-id: 326c0ebb5accf0f40cbee1323fb4c3378f5be154
prerequisite-patch-id: d6eaf7690a54a0dfda7500936a048b769fa265dd
prerequisite-patch-id: 7c7b2535642b4d388e13eab518cbe0f9550eb1e6
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: f6d4191e23c22e2d2cc90cc5d04781f913e98a8e
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: 43b92dfc1a98fbf0d14597a3e3ae99972844773b
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: d58c239fba616b366a725de6acba3f88734156d0
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: 5348d8957eb7eae7430ca8b101aa84ce77d3aa4f
prerequisite-patch-id: 36c96d781e2dd80b20932667bbe6e7fd14976069
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: 17f95c7fdf0bdc7c8aaaf7883bff1dcc19a0f529


^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 01/23] proc: drop the compat file operations
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
@ 2026-11-04 19:29   ` Christian Brauner
  2026-11-04 19:29   ` [PATCH RESEND v2 02/23] proc: add pde_pin() and pde_unpin() Christian Brauner
                     ` (21 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:29 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 9a0a2fcac7c7..e7264f0cf5ab 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -446,10 +446,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;
 }
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 2d65f3c8ec03..18f6490bfe77 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -410,6 +410,8 @@ static long proc_reg_compat_ioctl(struct file *file, unsigned int cmd, unsigned
 	}
 	return rv;
 }
+#else
+#define proc_reg_compat_ioctl NULL
 #endif
 
 static int pde_mmap(struct proc_dir_entry *pde, struct file *file, struct vm_area_struct *vma)
@@ -569,6 +571,7 @@ static const struct file_operations proc_reg_file_ops = {
 	.write		= proc_reg_write,
 	.poll		= proc_reg_poll,
 	.unlocked_ioctl	= proc_reg_unlocked_ioctl,
+	.compat_ioctl	= proc_reg_compat_ioctl,
 	.mmap		= proc_reg_mmap,
 	.get_unmapped_area = proc_reg_get_unmapped_area,
 #ifndef CONFIG_MMU
@@ -585,39 +588,6 @@ static const struct file_operations proc_iter_file_ops = {
 	.splice_read	= copy_splice_read,
 	.poll		= proc_reg_poll,
 	.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,
-};
-
-#ifdef CONFIG_COMPAT
-static const struct file_operations proc_reg_file_ops_compat = {
-	.llseek		= proc_reg_llseek,
-	.read		= proc_reg_read,
-	.write		= proc_reg_write,
-	.poll		= proc_reg_poll,
-	.unlocked_ioctl	= proc_reg_unlocked_ioctl,
-	.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,
-};
-
-static const struct file_operations proc_iter_file_ops_compat = {
-	.llseek		= proc_reg_llseek,
-	.read_iter	= proc_reg_read_iter,
-	.splice_read	= copy_splice_read,
-	.write		= proc_reg_write,
-	.poll		= proc_reg_poll,
-	.unlocked_ioctl	= proc_reg_unlocked_ioctl,
 	.compat_ioctl	= proc_reg_compat_ioctl,
 	.mmap		= proc_reg_mmap,
 	.get_unmapped_area = proc_reg_get_unmapped_area,
@@ -627,7 +597,6 @@ static const struct file_operations proc_iter_file_ops_compat = {
 	.open		= proc_reg_open,
 	.release	= proc_reg_release,
 };
-#endif
 
 static const char *proc_get_link(struct dentry *dentry,
 				 struct inode *inode,
@@ -683,14 +652,6 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
 			inode->i_fop = &proc_iter_file_ops;
 		else
 			inode->i_fop = &proc_reg_file_ops;
-#ifdef CONFIG_COMPAT
-		if (pde_has_proc_compat_ioctl(de)) {
-			if (pde_has_proc_read_iter(de))
-				inode->i_fop = &proc_iter_file_ops_compat;
-			else
-				inode->i_fop = &proc_reg_file_ops_compat;
-		}
-#endif
 	} else if (S_ISDIR(inode->i_mode)) {
 		inode->i_op = de->proc_iops;
 		inode->i_fop = de->proc_dir_ops;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 04f7ce2b24ed..268a0523be12 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -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;
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 47d7deaeed8f..6ad5150020bf 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -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,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 02/23] proc: add pde_pin() and pde_unpin()
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
  2026-11-04 19:29   ` [PATCH RESEND v2 01/23] proc: drop the compat file operations Christian Brauner
@ 2026-11-04 19:29   ` Christian Brauner
  2026-11-04 19:30   ` [PATCH RESEND v2 03/23] proc: share the readdir cursor with sysctl Christian Brauner
                     ` (20 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:29 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Every 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/inode.c | 116 +++++++++++++++++++++++++++-----------------------------
 1 file changed, 55 insertions(+), 61 deletions(-)

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 18f6490bfe77..66a3688757dd 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -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 __us
 static 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 ch
 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 = -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 p
 static __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 in
 static 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, unsi
 static 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_are
 static 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;
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 03/23] proc: share the readdir cursor with sysctl
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
  2026-11-04 19:29   ` [PATCH RESEND v2 01/23] proc: drop the compat file operations Christian Brauner
  2026-11-04 19:29   ` [PATCH RESEND v2 02/23] proc: add pde_pin() and pde_unpin() Christian Brauner
@ 2026-11-04 19:30   ` Christian Brauner
  2026-11-04 19:30   ` [PATCH RESEND v2 04/23] proc: wait for the last user of an entry with wait_var_event() Christian Brauner
                     ` (19 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:30 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_readdir_de() and proc_sys_readdir() both resume a listing after
the last entry it emitted and each remembers that entry in the open
file with a cursor of its own: the same struct, the same way of
setting it, the same release. Keep one in generic.c and let sysctl
use it.

No functional changes.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c     | 29 ++++++++++++-----------------
 fs/proc/internal.h    | 25 +++++++++++++++++++++++++
 fs/proc/proc_sysctl.c | 45 +++++----------------------------------------
 3 files changed, 42 insertions(+), 57 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index e7264f0cf5ab..f6d620a5c3d2 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -301,22 +301,16 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
 }
 
 /*
- * 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.
+ * Remember @name as the entry the listing is behind at @pos. If this can't
+ * the next call counts from the start.
  */
-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)
+void proc_dir_cursor_set(struct file *file, const char *name, unsigned int len,
+			 loff_t pos)
 {
 	struct proc_dir_cursor *cursor = file->private_data;
 
+	if (len > NAME_MAX)
+		return;
 	if (!cursor) {
 		cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
 		if (!cursor)
@@ -324,8 +318,8 @@ static void proc_dir_cursor_set(struct file *file, struct proc_dir_entry *de,
 		file->private_data = cursor;
 	}
 	cursor->pos = pos;
-	cursor->len = de->namelen;
-	memcpy(cursor->name, de->name, de->namelen);
+	cursor->len = len;
+	memcpy(cursor->name, name, len);
 }
 
 int proc_dir_release(struct inode *inode, struct file *file)
@@ -346,7 +340,7 @@ int proc_dir_release(struct inode *inode, struct file *file)
 int proc_readdir_de(struct file *file, struct dir_context *ctx,
 		    struct proc_dir_entry *de)
 {
-	struct proc_dir_cursor *cursor = file->private_data;
+	const struct proc_dir_cursor *cursor;
 	struct proc_dir_entry *dir = de;
 	int i;
 
@@ -354,7 +348,8 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
 		return 0;
 
 	read_lock(&proc_subdir_lock);
-	if (cursor && cursor->pos == ctx->pos) {
+	cursor = proc_dir_cursor_at(file, ctx->pos);
+	if (cursor) {
 		/* Resume after the last entry emitted, wherever it is now. */
 		de = pde_subdir_after(dir, cursor->name, cursor->len);
 	} else {
@@ -378,7 +373,7 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
 			return 0;
 		}
 		ctx->pos++;
-		proc_dir_cursor_set(file, de, ctx->pos);
+		proc_dir_cursor_set(file, de->name, de->namelen, ctx->pos);
 		read_lock(&proc_subdir_lock);
 		/* Removed in the meantime, go on after its name. */
 		if (RB_EMPTY_NODE(&de->subdir_node))
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 268a0523be12..6b61d09ac5e5 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -278,6 +278,31 @@ 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 *);
+
+/*
+ * 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. The directory's
+ * file keeps it, see proc_dir_release().
+ */
+struct proc_dir_cursor {
+	loff_t pos;			/* the position behind the entry */
+	unsigned int len;
+	char name[NAME_MAX + 1];
+};
+
+/* The entry to resume after, if the position is still the one behind it. */
+static inline const struct proc_dir_cursor *
+proc_dir_cursor_at(struct file *file, loff_t pos)
+{
+	const struct proc_dir_cursor *cursor = file->private_data;
+
+	if (cursor && cursor->pos == pos)
+		return cursor;
+	return NULL;
+}
+void proc_dir_cursor_set(struct file *file, const char *name, unsigned int len,
+			 loff_t pos);
 int proc_dir_release(struct inode *, struct file *);
 
 static inline void pde_get(struct proc_dir_entry *pde)
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 6a92c33db9b5..c1e41a3f8f6b 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -787,36 +787,6 @@ out:
 	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);
-}
-
 /*
  * 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
@@ -871,7 +841,7 @@ static int proc_sys_fill_dirent(struct file *file, struct sysctl_dirent *d)
 static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 {
 	struct ctl_table_header *head = PROC_I(file_inode(file))->sysctl;
-	struct sysctl_cursor *cursor = file->private_data;
+	const struct proc_dir_cursor *cursor;
 	struct sysctl_dirent d = {};
 	struct ctl_dir *ctl_dir;
 	int err;
@@ -888,7 +858,8 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 	 * 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) {
+	cursor = proc_dir_cursor_at(file, ctx->pos);
+	if (cursor) {
 		err = sysctl_dir_next(ctl_dir, &d, cursor->name, cursor->len);
 	} else {
 		loff_t pos = 2;
@@ -910,19 +881,13 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 				break;
 		}
 		ctx->pos++;
-		sysctl_cursor_set(file, d.name, d.len, ctx->pos);
+		proc_dir_cursor_set(file, d.name, d.len, ctx->pos);
 		err = sysctl_dir_next(ctl_dir, &d, d.name, d.len);
 	}
 	sysctl_dir_done(&d);
 	return err;
 }
 
-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)
 {
@@ -1002,7 +967,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,
+	.release	= proc_dir_release,
 };
 
 static const struct inode_operations proc_sys_inode_operations = {

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 04/23] proc: wait for the last user of an entry with wait_var_event()
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (2 preceding siblings ...)
  2026-11-04 19:30   ` [PATCH RESEND v2 03/23] proc: share the readdir cursor with sysctl Christian Brauner
@ 2026-11-04 19:30   ` Christian Brauner
  2026-11-04 19:30   ` [PATCH RESEND v2 05/23] proc: keep the open files of an entry in an open node Christian Brauner
                     ` (18 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:30 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_entry_rundown() waits for the last user of an entry with a
completion on its stack that it points the entry at while it waits.
That's a pointer in every struct proc_dir_entry for something that
happens once in the life of an entry, if at all.

Wait on the count itself with wait_var_event() instead and let
unuse_pde() wake the waiter with wake_up_var() when the count hits the
bias. The waiters sleep on the hashed wait queues behind that, so
procfs doesn't need a wait queue of its own for this, neither in the
entry nor a global one. kernfs keeps one per root for the same thing,
deactivate_waitq. Note that atomic_dec_return() is fully ordered, so
the count is visible before wake_up_var() looks for waiters.

No functional changes.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/inode.c    | 7 +++----
 fs/proc/internal.h | 1 -
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 66a3688757dd..8795add1898d 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -25,6 +25,7 @@
 #include <linux/slab.h>
 #include <linux/mount.h>
 #include <linux/bug.h>
+#include <linux/wait_bit.h>
 
 #include "internal.h"
 
@@ -203,7 +204,7 @@ static inline int use_pde(struct proc_dir_entry *pde)
 static void unuse_pde(struct proc_dir_entry *pde)
 {
 	if (unlikely(atomic_dec_return(&pde->in_use) == BIAS))
-		complete(pde->pde_unload_completion);
+		wake_up_var(&pde->in_use);
 }
 
 /* Pin an entry that can go away around a call into it, see use_pde(). */
@@ -266,11 +267,9 @@ static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo)
 
 void proc_entry_rundown(struct proc_dir_entry *de)
 {
-	DECLARE_COMPLETION_ONSTACK(c);
 	/* Wait until all existing callers into module are done. */
-	de->pde_unload_completion = &c;
 	if (atomic_add_return(BIAS, &de->in_use) != BIAS)
-		wait_for_completion(&c);
+		wait_var_event(&de->in_use, atomic_read(&de->in_use) == BIAS);
 
 	/* ->pde_openers list can't grow from now on. */
 
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 6b61d09ac5e5..ab4d7bd1fb75 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -38,7 +38,6 @@ struct proc_dir_entry {
 	struct list_head pde_openers;	/* who did ->open, but not ->release */
 	/* protects ->pde_openers and all struct pde_opener instances */
 	spinlock_t pde_unload_lock;
-	struct completion *pde_unload_completion;
 	const struct inode_operations *proc_iops;
 	union {
 		const struct proc_ops *proc_ops;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 05/23] proc: keep the open files of an entry in an open node
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (3 preceding siblings ...)
  2026-11-04 19:30   ` [PATCH RESEND v2 04/23] proc: wait for the last user of an entry with wait_var_event() Christian Brauner
@ 2026-11-04 19:30   ` Christian Brauner
  2026-11-04 19:30   ` [PATCH RESEND v2 06/23] proc: make room for more entry flags Christian Brauner
                     ` (17 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:30 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

procfs remembers the open files of an entry that has a ->proc_release(),
so that it can call the release hook when the entry is removed while
files are still open. The list head for that and the lock that protects
it sit in every struct proc_dir_entry, for the few entries that need
them, and the next patches want to keep more there, the vm_operations
of an entry's mappings for a start.

Move the list into a struct pde_open_node that only those entries get.
It is allocated when the entry is registered, whether an entry needs
one follows from its proc_ops, and freed with the entry. So ->open is
constant for the life of an entry, nothing has to look it up or pin it,
and the removal path stays what it is. kernfs allocates its
kernfs_open_node on the first open instead, but there every attribute
can be polled and so every one of them would need one. In procfs the
entries that need the node are the exception. Permanent entries don't
get one, they are never removed, so their release hook only ever runs
when a file is closed.

The spinlock stays in the entry and now protects what hangs off ->open.
Moving it behind the gid fills a hole, so struct proc_dir_entry shrinks
from 160 to 144 bytes on x86_64 here, and it ends up at 160 bytes at
the end of this series instead of the 184 it would have grown to with
the read and write limits and the vm_operations of its mappings in it.
That keeps 32 bytes of inline name instead of 8. 34 of the 68 /proc/net
names a network namespace gets are longer than 7 characters.

No functional changes.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c  | 20 +++++++++++++++++++-
 fs/proc/inode.c    | 23 +++++++++++++----------
 fs/proc/internal.h | 16 +++++++++++++---
 3 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index f6d620a5c3d2..84f064a651e4 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -40,6 +40,7 @@ void pde_free(struct proc_dir_entry *pde)
 		kfree(pde->data);
 	if (pde->name != pde->inline_name)
 		kfree(pde->name);
+	kfree(pde->open);
 	kmem_cache_free(proc_dir_entry_cache, pde);
 }
 
@@ -445,6 +446,18 @@ static void pde_set_flags(struct proc_dir_entry *pde)
 		pde->flags |= PROC_ENTRY_proc_lseek;
 }
 
+/*
+ * procfs has to know the open files of an entry whose ->proc_release() it
+ * calls at removal. A permanent entry is never removed and skips all that,
+ * see proc_reg_open().
+ */
+static bool pde_needs_open_node(const struct proc_dir_entry *pde)
+{
+	if (!S_ISREG(pde->mode) || pde_is_permanent(pde))
+		return false;
+	return pde->proc_ops->proc_release;
+}
+
 /* returns the registered entry, or frees dp and returns NULL on failure */
 struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
 		struct proc_dir_entry *dp)
@@ -454,6 +467,12 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
 
 	if (!S_ISDIR(dp->mode))
 		pde_set_flags(dp);
+	if (pde_needs_open_node(dp)) {
+		dp->open = kzalloc(sizeof(*dp->open), GFP_KERNEL);
+		if (!dp->open)
+			goto out_free_inum;
+		INIT_LIST_HEAD(&dp->open->files);
+	}
 
 	write_lock(&proc_subdir_lock);
 	dp->parent = dir;
@@ -533,7 +552,6 @@ static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent,
 	ent->subdir = RB_ROOT;
 	refcount_set(&ent->refcnt, 1);
 	spin_lock_init(&ent->pde_unload_lock);
-	INIT_LIST_HEAD(&ent->pde_openers);
 	proc_set_user(ent, (*parent)->uid, (*parent)->gid);
 
 	/* Revalidate everything under /proc/${pid}/net */
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 8795add1898d..56eab69e1dcc 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -271,12 +271,13 @@ void proc_entry_rundown(struct proc_dir_entry *de)
 	if (atomic_add_return(BIAS, &de->in_use) != BIAS)
 		wait_var_event(&de->in_use, atomic_read(&de->in_use) == BIAS);
 
-	/* ->pde_openers list can't grow from now on. */
-
+	/* ->open->files can't grow from now on. */
+	if (!de->open)
+		return;
 	spin_lock(&de->pde_unload_lock);
-	while (!list_empty(&de->pde_openers)) {
+	while (!list_empty(&de->open->files)) {
 		struct pde_opener *pdeo;
-		pdeo = list_first_entry(&de->pde_openers, struct pde_opener, lh);
+		pdeo = list_first_entry(&de->open->files, struct pde_opener, lh);
 		close_pdeo(de, pdeo);
 		spin_lock(&de->pde_unload_lock);
 	}
@@ -499,13 +500,13 @@ static int proc_reg_open(struct inode *inode, struct file *file)
 	 * 2) rmmod isn't blocked by opening file in /proc and sitting on
 	 *    the descriptor (including "rmmod foo </proc/foo" scenario).
 	 *
-	 * Save every "struct file" with custom ->release hook.
+	 * Save every "struct file" with custom ->release hook. Those entries
+	 * have an open node, see proc_register().
 	 */
 	if (!use_pde(pde))
 		return -ENOENT;
 
-	const auto release = pde->proc_ops->proc_release;
-	if (release) {
+	if (pde->open) {
 		pdeo = kmem_cache_alloc(pde_opener_cache, GFP_KERNEL);
 		if (!pdeo) {
 			rv = -ENOMEM;
@@ -517,14 +518,14 @@ static int proc_reg_open(struct inode *inode, struct file *file)
 	if (open)
 		rv = open(inode, file);
 
-	if (release) {
+	if (pde->open) {
 		if (rv == 0) {
 			/* To know what to release. */
 			pdeo->file = file;
 			pdeo->closing = false;
 			pdeo->c = NULL;
 			spin_lock(&pde->pde_unload_lock);
-			list_add(&pdeo->lh, &pde->pde_openers);
+			list_add(&pdeo->lh, &pde->open->files);
 			spin_unlock(&pde->pde_unload_lock);
 		} else
 			kmem_cache_free(pde_opener_cache, pdeo);
@@ -547,8 +548,10 @@ static int proc_reg_release(struct inode *inode, struct file *file)
 		return 0;
 	}
 
+	if (!pde->open)
+		return 0;
 	spin_lock(&pde->pde_unload_lock);
-	list_for_each_entry(pdeo, &pde->pde_openers, lh) {
+	list_for_each_entry(pdeo, &pde->open->files, lh) {
 		if (pdeo->file == file) {
 			close_pdeo(pde, pdeo);
 			return 0;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index ab4d7bd1fb75..33776bc00ff0 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -35,9 +35,6 @@ struct proc_dir_entry {
 	 */
 	atomic_t in_use;
 	refcount_t refcnt;
-	struct list_head pde_openers;	/* who did ->open, but not ->release */
-	/* protects ->pde_openers and all struct pde_opener instances */
-	spinlock_t pde_unload_lock;
 	const struct inode_operations *proc_iops;
 	union {
 		const struct proc_ops *proc_ops;
@@ -47,6 +44,8 @@ struct proc_dir_entry {
 		const struct seq_operations *seq_ops;
 		int (*single_show)(struct seq_file *, void *);
 	};
+	/* the open files procfs has to know, see struct pde_open_node */
+	struct pde_open_node *open;
 	proc_write_t write;
 	void *data;
 	unsigned int state_size;
@@ -54,6 +53,8 @@ struct proc_dir_entry {
 	nlink_t nlink;
 	kuid_t uid;
 	kgid_t gid;
+	/* protects ->open->files and all struct pde_opener instances */
+	spinlock_t pde_unload_lock;
 	loff_t size;
 	struct proc_dir_entry *parent;
 	struct rb_root subdir;
@@ -325,6 +326,15 @@ struct pde_opener {
 	bool closing;
 	struct completion *c;
 } __randomize_layout;
+
+/*
+ * The open files of an entry whose ->proc_release() procfs has to call when
+ * the entry is removed while they are still open. Allocated with the entry,
+ * see proc_register().
+ */
+struct pde_open_node {
+	struct list_head files;		/* who did ->open, but not ->release */
+};
 extern const struct inode_operations proc_link_inode_operations;
 extern const struct inode_operations proc_pid_link_inode_operations;
 extern const struct super_operations proc_sops;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 06/23] proc: make room for more entry flags
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (4 preceding siblings ...)
  2026-11-04 19:30   ` [PATCH RESEND v2 05/23] proc: keep the open files of an entry in an open node Christian Brauner
@ 2026-11-04 19:30   ` Christian Brauner
  2026-11-04 19:30   ` [PATCH RESEND v2 07/23] proc: let procfs own the seq_file of seq_file entries Christian Brauner
                     ` (16 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:30 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/internal.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 33776bc00ff0..7dea0237f08b 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -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;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 07/23] proc: let procfs own the seq_file of seq_file entries
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (5 preceding siblings ...)
  2026-11-04 19:30   ` [PATCH RESEND v2 06/23] proc: make room for more entry flags Christian Brauner
@ 2026-11-04 19:30   ` Christian Brauner
  2026-11-04 19:31   ` [PATCH RESEND v2 08/23] proc: let procfs own the seq_file of proc_create_seq() and friends Christian Brauner
                     ` (15 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:30 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

remove_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 it builds
for the entry when it is registered: the entry's own ->next() and
->show() with a ->start() and ->stop() of procfs around them that pin
the entry and unpin it again. Nothing of procfs runs between the
records that way. procfs 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.

For an entry that only has a show function the seq_operations do what
single_open() does, like kernfs_seq_start(). If the entry can't be
pinned ->start() fails with -EIO and switches the seq_file to
seq_operations whose ->stop() doesn't unpin, so the entry's own
->start() and ->next() can return any error they like. 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.

Suggested-by: Jann Horn <jannh@google.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c       |  65 ++++++++++++++++++---
 fs/proc/inode.c         | 146 +++++++++++++++++++++++++++++++++++++++++++-----
 fs/proc/internal.h      |  21 +++++++
 include/linux/proc_fs.h |  22 +++++++-
 4 files changed, 231 insertions(+), 23 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 84f064a651e4..59b89e251ba3 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -41,6 +41,7 @@ void pde_free(struct proc_dir_entry *pde)
 	if (pde->name != pde->inline_name)
 		kfree(pde->name);
 	kfree(pde->open);
+	kfree(pde->seq_direct);
 	kmem_cache_free(proc_dir_entry_cache, pde);
 }
 
@@ -431,19 +432,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;
 }
 
 /*
@@ -458,6 +481,32 @@ static bool pde_needs_open_node(const struct proc_dir_entry *pde)
 	return pde->proc_ops->proc_release;
 }
 
+/*
+ * The seq_operations of a seq_file entry: procfs's start and stop around the
+ * entry's own next and show, see pde_seq_start().
+ */
+static int pde_init_seq_direct(struct proc_dir_entry *pde)
+{
+	struct seq_operations *ops;
+
+	if (!pde_is_seq_file(pde))
+		return 0;
+	ops = kmalloc(sizeof(*ops), GFP_KERNEL);
+	if (!ops)
+		return -ENOMEM;
+	ops->start = pde_seq_start;
+	ops->stop = pde_seq_stop;
+	if (pde_has_proc_seq_ops(pde)) {
+		ops->next = pde->seq_ops->next;
+		ops->show = pde->seq_ops->show;
+	} else {
+		ops->next = pde_single_next;
+		ops->show = pde->single_show;
+	}
+	pde->seq_direct = ops;
+	return 0;
+}
+
 /* returns the registered entry, or frees dp and returns NULL on failure */
 struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
 		struct proc_dir_entry *dp)
@@ -465,14 +514,16 @@ 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;
 	if (pde_needs_open_node(dp)) {
 		dp->open = kzalloc(sizeof(*dp->open), GFP_KERNEL);
 		if (!dp->open)
 			goto out_free_inum;
 		INIT_LIST_HEAD(&dp->open->files);
 	}
+	if (pde_init_seq_direct(dp))
+		goto out_free_inum;
 
 	write_lock(&proc_subdir_lock);
 	dp->parent = dir;
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 56eab69e1dcc..d51ab7548b11 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -219,6 +219,87 @@ static void pde_unpin(struct proc_dir_entry *pde)
 		unuse_pde(pde);
 }
 
+/*
+ * procfs owns the seq_file of a seq_file entry and only calls into the entry
+ * with it pinned. A read copies to userspace without the pin, so a reader
+ * that faults on its buffer doesn't hold up remove_proc_entry().
+ *
+ * The seq_operations the seq_file runs are the entry's own next and show
+ * with procfs's start and stop around them, see pde_init_seq_direct(): the
+ * pin is taken in start and dropped in stop, and nothing sits between the
+ * seq_file and the entry's per record code. An entry that only has a show
+ * function is iterated like single_open() does it.
+ *
+ * If the pin fails start switches the seq_file to seq_operations whose stop
+ * doesn't unpin, so that the entry's own start or next can return any error
+ * they like, ERR_PTR(-EIO) included.
+ */
+static struct proc_dir_entry *seq_pde(struct seq_file *m)
+{
+	return PDE(file_inode(m->file));
+}
+
+static void pde_seq_stop_unpinned(struct seq_file *m, void *v)
+{
+	m->op = seq_pde(m)->seq_direct;
+}
+
+static const struct seq_operations pde_seq_ops_unpinned = {
+	.start	= pde_seq_start,
+	.stop	= pde_seq_stop_unpinned,
+};
+
+void *pde_seq_start(struct seq_file *m, loff_t *pos)
+{
+	struct proc_dir_entry *pde = seq_pde(m);
+
+	if (!pde_pin(pde)) {
+		m->op = &pde_seq_ops_unpinned;
+		return ERR_PTR(-EIO);
+	}
+	m->op = pde->seq_direct;
+	if (!pde_has_proc_seq_ops(pde))
+		return single_start(m, pos);
+	return pde->seq_ops->start(m, pos);
+}
+
+void pde_seq_stop(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);
+}
+
+/* what single_open() iterates with, seq_file keeps its own private */
+void *pde_single_next(struct seq_file *m, void *v, loff_t *pos)
+{
+	++*pos;
+	return NULL;
+}
+
+static int pde_seq_open(struct proc_dir_entry *pde, struct file *file)
+{
+	int err;
+
+	if (pde->state_size)
+		return seq_open_private(file, pde->seq_direct, pde->state_size);
+	err = seq_open(file, pde->seq_direct);
+	if (!err && pde_has_proc_show(pde))
+		((struct seq_file *)file->private_data)->private = pde->data;
+	return err;
+}
+
+static void pde_seq_release(struct proc_dir_entry *pde, struct inode *inode,
+			    struct file *file)
+{
+	if (pde->state_size)
+		seq_release_private(inode, file);
+	else
+		seq_release(inode, file);
+}
+
 /*
  * At most 2 contexts can enter this function: the one doing the last
  * close on the descriptor and whoever is deleting PDE itself.
@@ -474,22 +555,35 @@ static unsigned int proc_reg_mmap_capabilities(struct file *file)
 }
 #endif
 
+static int pde_open(struct proc_dir_entry *pde, struct inode *inode,
+		    struct file *file)
+{
+	const auto open = pde->proc_ops->proc_open;
+	int rv;
+
+	if (!pde_is_seq_file(pde))
+		return open ? open(inode, file) : 0;
+
+	rv = pde_seq_open(pde, file);
+	if (rv || !open)
+		return rv;
+	rv = open(inode, file);
+	if (rv)
+		pde_seq_release(pde, inode, file);
+	return rv;
+}
+
 static int proc_reg_open(struct inode *inode, struct file *file)
 {
 	struct proc_dir_entry *pde = PDE(inode);
 	int rv = 0;
-	typeof_member(struct proc_ops, proc_open) open;
 	struct pde_opener *pdeo;
 
 	if (!pde_has_proc_lseek(pde))
 		file->f_mode &= ~FMODE_LSEEK;
 
-	if (pde_is_permanent(pde)) {
-		open = pde->proc_ops->proc_open;
-		if (open)
-			rv = open(inode, file);
-		return rv;
-	}
+	if (pde_is_permanent(pde))
+		return pde_open(pde, inode, file);
 
 	/*
 	 * Ensure that
@@ -514,9 +608,7 @@ static int proc_reg_open(struct inode *inode, struct file *file)
 		}
 	}
 
-	open = pde->proc_ops->proc_open;
-	if (open)
-		rv = open(inode, file);
+	rv = pde_open(pde, inode, file);
 
 	if (pde->open) {
 		if (rv == 0) {
@@ -544,23 +636,45 @@ static int proc_reg_release(struct inode *inode, struct file *file)
 	if (pde_is_permanent(pde)) {
 		const auto release = pde->proc_ops->proc_release;
 		if (release)
-			return release(inode, file);
-		return 0;
+			release(inode, file);
+		goto out;
 	}
 
 	if (!pde->open)
-		return 0;
+		goto out;
 	spin_lock(&pde->pde_unload_lock);
 	list_for_each_entry(pdeo, &pde->open->files, lh) {
 		if (pdeo->file == file) {
 			close_pdeo(pde, pdeo);
-			return 0;
+			goto out;
 		}
 	}
 	spin_unlock(&pde->pde_unload_lock);
+out:
+	/* ->proc_release() has run, here or in remove_proc_entry() */
+	if (pde_is_seq_file(pde))
+		pde_seq_release(pde, inode, file);
 	return 0;
 }
 
+/* For seq_file entries, see pde_seq_ops. */
+static const struct file_operations proc_seq_file_ops = {
+	.llseek		= seq_lseek,
+	.read_iter	= seq_read_iter,
+	.splice_read	= copy_splice_read,
+	.write		= proc_reg_write,
+	.poll		= proc_reg_poll,
+	.unlocked_ioctl	= proc_reg_unlocked_ioctl,
+	.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,
+};
+
 static const struct file_operations proc_reg_file_ops = {
 	.llseek		= proc_reg_llseek,
 	.read		= proc_reg_read,
@@ -644,7 +758,9 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
 
 	if (S_ISREG(inode->i_mode)) {
 		inode->i_op = de->proc_iops;
-		if (pde_has_proc_read_iter(de))
+		if (pde_is_seq_file(de))
+			inode->i_fop = &proc_seq_file_ops;
+		else if (pde_has_proc_read_iter(de))
 			inode->i_fop = &proc_iter_file_ops;
 		else
 			inode->i_fop = &proc_reg_file_ops;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 7dea0237f08b..674e6b4ed21e 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -46,6 +46,8 @@ struct proc_dir_entry {
 	};
 	/* the open files procfs has to know, see struct pde_open_node */
 	struct pde_open_node *open;
+	/* the seq_operations of the seq_file, see pde_init_seq_direct() */
+	struct seq_operations *seq_direct;
 	proc_write_t write;
 	void *data;
 	unsigned int state_size;
@@ -97,6 +99,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_start() */
+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);
 
@@ -343,6 +361,9 @@ void proc_init_kmemcache(void);
 void proc_invalidate_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock);
 void set_proc_pid_nlink(void);
 extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *);
+void *pde_seq_start(struct seq_file *m, loff_t *pos);
+void pde_seq_stop(struct seq_file *m, void *v);
+void *pde_single_next(struct seq_file *m, void *v, loff_t *pos);
 extern void proc_entry_rundown(struct proc_dir_entry *);
 
 /*
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 6ad5150020bf..ea55ed85a2d6 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -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 */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 08/23] proc: let procfs own the seq_file of proc_create_seq() and friends
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (6 preceding siblings ...)
  2026-11-04 19:30   ` [PATCH RESEND v2 07/23] proc: let procfs own the seq_file of seq_file entries Christian Brauner
@ 2026-11-04 19:31   ` Christian Brauner
  2026-11-04 19:31   ` [PATCH RESEND v2 09/23] proc: let procfs own the seq_file of DEFINE_PROC_SHOW_ATTRIBUTE() files Christian Brauner
                     ` (14 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:31 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_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.

Reported-by: Chris Mason <mason@kernel.org>
Fixes: 786d7e1612f0 ("Fix rmmod/read/write races in /proc entries")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c  | 42 ++++--------------------------------------
 fs/proc/proc_net.c | 40 +++++++++++++---------------------------
 2 files changed, 17 insertions(+), 65 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 59b89e251ba3..959bf9e33245 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -737,30 +737,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_start() */
 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,
@@ -775,25 +754,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)
@@ -803,8 +768,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);
diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
index df5484813d5d..9973f846c467 100644
--- a/fs/proc/proc_net.c
+++ b/fs/proc/proc_net.c
@@ -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_start() */
 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 mo
 	pde_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);
 }

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 09/23] proc: let procfs own the seq_file of DEFINE_PROC_SHOW_ATTRIBUTE() files
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (7 preceding siblings ...)
  2026-11-04 19:31   ` [PATCH RESEND v2 08/23] proc: let procfs own the seq_file of proc_create_seq() and friends Christian Brauner
@ 2026-11-04 19:31   ` Christian Brauner
  2026-11-04 19:31   ` [PATCH RESEND v2 10/23] treewide: let procfs own the seq_file of simple proc entries Christian Brauner
                     ` (13 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:31 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the show function and let procfs open the seq_file.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 include/linux/seq_file.h | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/include/linux/seq_file.h b/include/linux/seq_file.h
index dc0e8c62d9e0..b813b7e63d89 100644
--- a/include/linux/seq_file.h
+++ b/include/linux/seq_file.h
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 10/23] treewide: let procfs own the seq_file of simple proc entries
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (8 preceding siblings ...)
  2026-11-04 19:31   ` [PATCH RESEND v2 09/23] proc: let procfs own the seq_file of DEFINE_PROC_SHOW_ATTRIBUTE() files Christian Brauner
@ 2026-11-04 19:31   ` Christian Brauner
  2026-11-04 19:31   ` [PATCH RESEND v2 11/23] selftests/proc: check that a faulting reader doesn't hold up removal Christian Brauner
                     ` (12 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:31 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Most 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(...) { ... }

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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(-)

diff --git a/arch/alpha/kernel/srm_env.c b/arch/alpha/kernel/srm_env.c
index 217b4dca51dd..faafc2df7886 100644
--- a/arch/alpha/kernel/srm_env.c
+++ b/arch/alpha/kernel/srm_env.c
@@ -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,
 };
 
diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c
index f6c1e6b61179..3e5e30968634 100644
--- a/arch/arm/mm/alignment.c
+++ b/arch/arm/mm/alignment.c
@@ -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 */
diff --git a/arch/powerpc/kernel/rtas-proc.c b/arch/powerpc/kernel/rtas-proc.c
index 89b165986851..808023f9d1f6 100644
--- a/arch/powerpc/kernel/rtas-proc.c
+++ b/arch/powerpc/kernel/rtas-proc.c
@@ -154,69 +154,29 @@ static ssize_t ppc_rtas_tone_volume_write(struct file *file,
 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 int poweron_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, ppc_rtas_poweron_show, NULL);
-}
-
 static const struct proc_ops ppc_rtas_poweron_proc_ops = {
-	.proc_open	= poweron_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= ppc_rtas_poweron_show,
 	.proc_write	= ppc_rtas_poweron_write,
-	.proc_release	= single_release,
 };
 
-static int progress_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, ppc_rtas_progress_show, NULL);
-}
-
 static const struct proc_ops ppc_rtas_progress_proc_ops = {
-	.proc_open	= progress_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= ppc_rtas_progress_show,
 	.proc_write	= ppc_rtas_progress_write,
-	.proc_release	= single_release,
 };
 
-static int clock_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, ppc_rtas_clock_show, NULL);
-}
-
 static const struct proc_ops ppc_rtas_clock_proc_ops = {
-	.proc_open	= clock_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= ppc_rtas_clock_show,
 	.proc_write	= ppc_rtas_clock_write,
-	.proc_release	= single_release,
 };
 
-static int tone_freq_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, ppc_rtas_tone_freq_show, NULL);
-}
-
 static const struct proc_ops ppc_rtas_tone_freq_proc_ops = {
-	.proc_open	= tone_freq_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= ppc_rtas_tone_freq_show,
 	.proc_write	= ppc_rtas_tone_freq_write,
-	.proc_release	= single_release,
 };
 
-static int tone_volume_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, ppc_rtas_tone_volume_show, NULL);
-}
-
 static const struct proc_ops ppc_rtas_tone_volume_proc_ops = {
-	.proc_open	= tone_volume_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= ppc_rtas_tone_volume_show,
 	.proc_write	= ppc_rtas_tone_volume_write,
-	.proc_release	= single_release,
 };
 
 static int ppc_rtas_find_all_sensors(void);
diff --git a/arch/powerpc/platforms/pseries/lpar.c b/arch/powerpc/platforms/pseries/lpar.c
index 6a415febc53b..48fe0be71a89 100644
--- a/arch/powerpc/platforms/pseries/lpar.c
+++ b/arch/powerpc/platforms/pseries/lpar.c
@@ -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)
diff --git a/arch/powerpc/platforms/pseries/lparcfg.c b/arch/powerpc/platforms/pseries/lparcfg.c
index 4f6b2975c6e9..cb3d96efb225 100644
--- a/arch/powerpc/platforms/pseries/lparcfg.c
+++ b/arch/powerpc/platforms/pseries/lparcfg.c
@@ -797,17 +797,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)
diff --git a/arch/sh/mm/alignment.c b/arch/sh/mm/alignment.c
index 3a76a766f423..2198c6f2ea63 100644
--- a/arch/sh/mm/alignment.c
+++ b/arch/sh/mm/alignment.c
@@ -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,
 };
 
diff --git a/arch/sparc/kernel/led.c b/arch/sparc/kernel/led.c
index a1478de9fe5d..0f7376b51a04 100644
--- a/arch/sparc/kernel/led.c
+++ b/arch/sparc/kernel/led.c
@@ -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)
 {
@@ -107,10 +102,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
diff --git a/arch/um/kernel/exitcode.c b/arch/um/kernel/exitcode.c
index 0230354f4812..c71657df38f5 100644
--- a/arch/um/kernel/exitcode.c
+++ b/arch/um/kernel/exitcode.c
@@ -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,
 };
 
diff --git a/drivers/acpi/proc.c b/drivers/acpi/proc.c
index c08ead07252b..b8a0bd4580e0 100644
--- a/drivers/acpi/proc.c
+++ b/drivers/acpi/proc.c
@@ -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)
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 88837f028405..492b2b6c6d7e 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -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,
 };
 
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 0859b60ca924..d27eecbf4a03 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -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)
diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c
index 8c652c15018c..49f929ae10e4 100644
--- a/drivers/macintosh/via-pmu.c
+++ b/drivers/macintosh/via-pmu.c
@@ -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
diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_module.c b/drivers/net/wireless/intel/ipw2x00/libipw_module.c
index 552c8ff3928b..15d9a5efb581 100644
--- a/drivers/net/wireless/intel/ipw2x00/libipw_module.c
+++ b/drivers/net/wireless/intel/ipw2x00/libipw_module.c
@@ -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 */
diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index 4f81855ad721..2ee208c82801 100644
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -881,11 +881,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)
@@ -910,10 +905,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,
 };
 
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index 9eb8f1e5aeda..560e62da05a9 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -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,
 };
 
diff --git a/drivers/pnp/pnpbios/proc.c b/drivers/pnp/pnpbios/proc.c
index 0f0d819b157f..14632a8d0da0 100644
--- a/drivers/pnp/pnpbios/proc.c
+++ b/drivers/pnp/pnpbios/proc.c
@@ -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,
 };
 
diff --git a/drivers/s390/block/dasd_proc.c b/drivers/s390/block/dasd_proc.c
index 48e12e81df00..86399427f01d 100644
--- a/drivers/s390/block/dasd_proc.c
+++ b/drivers/s390/block/dasd_proc.c
@@ -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,
 };
 
diff --git a/drivers/s390/cio/blacklist.c b/drivers/s390/cio/blacklist.c
index 020d210bde9f..4e02f78f6102 100644
--- a/drivers/s390/cio/blacklist.c
+++ b/drivers/s390/cio/blacklist.c
@@ -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,
 };
 
diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c
index cfc259fd0141..0b8f5ff40543 100644
--- a/drivers/scsi/scsi_devinfo.c
+++ b/drivers/scsi/scsi_devinfo.c
@@ -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 */
 
diff --git a/drivers/scsi/scsi_proc.c b/drivers/scsi/scsi_proc.c
index dbd03360b5a4..3e49c4c8704f 100644
--- a/drivers/scsi/scsi_proc.c
+++ b/drivers/scsi/scsi_proc.c
@@ -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,
 };
 
 /**
diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c
index a2fd239b7ad3..3c7bd30b8a3a 100644
--- a/drivers/usb/gadget/function/rndis.c
+++ b/drivers/usb/gadget/function/rndis.c
@@ -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,
 };
 
diff --git a/drivers/video/fbdev/via/viafbdev.c b/drivers/video/fbdev/via/viafbdev.c
index 2c67a18d16ad..4b617e80a77c 100644
--- a/drivers/video/fbdev/via/viafbdev.c
+++ b/drivers/video/fbdev/via/viafbdev.c
@@ -1120,11 +1120,6 @@ static int viafb_dvp0_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int viafb_dvp0_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, viafb_dvp0_proc_show, NULL);
-}
-
 static ssize_t viafb_dvp0_proc_write(struct file *file,
 	const char __user *buffer, size_t count, loff_t *pos)
 {
@@ -1173,10 +1168,7 @@ static ssize_t viafb_dvp0_proc_write(struct file *file,
 }
 
 static const struct proc_ops viafb_dvp0_proc_ops = {
-	.proc_open	= viafb_dvp0_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= viafb_dvp0_proc_show,
 	.proc_write	= viafb_dvp0_proc_write,
 };
 
@@ -1190,11 +1182,6 @@ static int viafb_dvp1_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int viafb_dvp1_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, viafb_dvp1_proc_show, NULL);
-}
-
 static ssize_t viafb_dvp1_proc_write(struct file *file,
 	const char __user *buffer, size_t count, loff_t *pos)
 {
@@ -1237,10 +1224,7 @@ static ssize_t viafb_dvp1_proc_write(struct file *file,
 }
 
 static const struct proc_ops viafb_dvp1_proc_ops = {
-	.proc_open	= viafb_dvp1_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= viafb_dvp1_proc_show,
 	.proc_write	= viafb_dvp1_proc_write,
 };
 
@@ -1252,11 +1236,6 @@ static int viafb_dfph_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int viafb_dfph_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, viafb_dfph_proc_show, NULL);
-}
-
 static ssize_t viafb_dfph_proc_write(struct file *file,
 	const char __user *buffer, size_t count, loff_t *pos)
 {
@@ -1271,10 +1250,7 @@ static ssize_t viafb_dfph_proc_write(struct file *file,
 }
 
 static const struct proc_ops viafb_dfph_proc_ops = {
-	.proc_open	= viafb_dfph_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= viafb_dfph_proc_show,
 	.proc_write	= viafb_dfph_proc_write,
 };
 
@@ -1286,11 +1262,6 @@ static int viafb_dfpl_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int viafb_dfpl_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, viafb_dfpl_proc_show, NULL);
-}
-
 static ssize_t viafb_dfpl_proc_write(struct file *file,
 	const char __user *buffer, size_t count, loff_t *pos)
 {
@@ -1305,10 +1276,7 @@ static ssize_t viafb_dfpl_proc_write(struct file *file,
 }
 
 static const struct proc_ops viafb_dfpl_proc_ops = {
-	.proc_open	= viafb_dfpl_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= viafb_dfpl_proc_show,
 	.proc_write	= viafb_dfpl_proc_write,
 };
 
@@ -1344,11 +1312,6 @@ static int viafb_vt1636_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int viafb_vt1636_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, viafb_vt1636_proc_show, NULL);
-}
-
 static ssize_t viafb_vt1636_proc_write(struct file *file,
 	const char __user *buffer, size_t count, loff_t *pos)
 {
@@ -1440,10 +1403,7 @@ static ssize_t viafb_vt1636_proc_write(struct file *file,
 }
 
 static const struct proc_ops viafb_vt1636_proc_ops = {
-	.proc_open	= viafb_vt1636_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= viafb_vt1636_proc_show,
 	.proc_write	= viafb_vt1636_proc_write,
 };
 
@@ -1491,11 +1451,6 @@ static int viafb_iga1_odev_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int viafb_iga1_odev_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, viafb_iga1_odev_proc_show, NULL);
-}
-
 static ssize_t viafb_iga1_odev_proc_write(struct file *file,
 	const char __user *buffer, size_t count, loff_t *pos)
 {
@@ -1517,10 +1472,7 @@ static ssize_t viafb_iga1_odev_proc_write(struct file *file,
 }
 
 static const struct proc_ops viafb_iga1_odev_proc_ops = {
-	.proc_open	= viafb_iga1_odev_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= viafb_iga1_odev_proc_show,
 	.proc_write	= viafb_iga1_odev_proc_write,
 };
 
@@ -1530,11 +1482,6 @@ static int viafb_iga2_odev_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int viafb_iga2_odev_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, viafb_iga2_odev_proc_show, NULL);
-}
-
 static ssize_t viafb_iga2_odev_proc_write(struct file *file,
 	const char __user *buffer, size_t count, loff_t *pos)
 {
@@ -1556,10 +1503,7 @@ static ssize_t viafb_iga2_odev_proc_write(struct file *file,
 }
 
 static const struct proc_ops viafb_iga2_odev_proc_ops = {
-	.proc_open	= viafb_iga2_odev_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= viafb_iga2_odev_proc_show,
 	.proc_write	= viafb_iga2_odev_proc_write,
 };
 
diff --git a/fs/jfs/jfs_debug.c b/fs/jfs/jfs_debug.c
index 44b62b3c322e..5eb35b0d420f 100644
--- a/fs/jfs/jfs_debug.c
+++ b/fs/jfs/jfs_debug.c
@@ -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
diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index 37e27b73b7a5..cd47839872b0 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -548,11 +548,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)
 {
@@ -598,23 +593,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,
 };
 
diff --git a/fs/proc/cpuinfo.c b/fs/proc/cpuinfo.c
index f38bda5b83ec..d8cd718c6030 100644
--- a/fs/proc/cpuinfo.c
+++ b/fs/proc/cpuinfo.c
@@ -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)
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 577f0ddede90..dfcd2cdbb2a8 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -357,10 +357,6 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v)
 }
 
 #ifdef CONFIG_CIFS_DEBUG
-static int cifs_debug_dirs_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, cifs_debug_dirs_proc_show, NULL);
-}
 
 /* Drop all cached directory entries across all CIFS mounts. */
 static ssize_t cifs_debug_dirs_proc_write(struct file *file, const char __user *buffer,
@@ -393,10 +389,7 @@ static ssize_t cifs_debug_dirs_proc_write(struct file *file, const char __user *
 }
 
 static const struct proc_ops cifs_debug_dirs_proc_ops = {
-	.proc_open	= cifs_debug_dirs_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= cifs_debug_dirs_proc_show,
 	.proc_write	= cifs_debug_dirs_proc_write,
 };
 #endif /* CONFIG_CIFS_DEBUG */
@@ -859,16 +852,8 @@ static int cifs_stats_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int cifs_stats_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, cifs_stats_proc_show, NULL);
-}
-
 static const struct proc_ops cifs_stats_proc_ops = {
-	.proc_open	= cifs_stats_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= cifs_stats_proc_show,
 	.proc_write	= cifs_stats_proc_write,
 };
 
@@ -1012,11 +997,6 @@ static int cifsFYI_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int cifsFYI_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, cifsFYI_proc_show, NULL);
-}
-
 static ssize_t cifsFYI_proc_write(struct file *file, const char __user *buffer,
 		size_t count, loff_t *ppos)
 {
@@ -1038,10 +1018,7 @@ static ssize_t cifsFYI_proc_write(struct file *file, const char __user *buffer,
 }
 
 static const struct proc_ops cifsFYI_proc_ops = {
-	.proc_open	= cifsFYI_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= cifsFYI_proc_show,
 	.proc_write	= cifsFYI_proc_write,
 };
 
@@ -1051,11 +1028,6 @@ static int cifs_linux_ext_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int cifs_linux_ext_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, cifs_linux_ext_proc_show, NULL);
-}
-
 static ssize_t cifs_linux_ext_proc_write(struct file *file,
 		const char __user *buffer, size_t count, loff_t *ppos)
 {
@@ -1069,10 +1041,7 @@ static ssize_t cifs_linux_ext_proc_write(struct file *file,
 }
 
 static const struct proc_ops cifs_linux_ext_proc_ops = {
-	.proc_open	= cifs_linux_ext_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= cifs_linux_ext_proc_show,
 	.proc_write	= cifs_linux_ext_proc_write,
 };
 
@@ -1082,11 +1051,6 @@ static int cifs_lookup_cache_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int cifs_lookup_cache_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, cifs_lookup_cache_proc_show, NULL);
-}
-
 static ssize_t cifs_lookup_cache_proc_write(struct file *file,
 		const char __user *buffer, size_t count, loff_t *ppos)
 {
@@ -1100,10 +1064,7 @@ static ssize_t cifs_lookup_cache_proc_write(struct file *file,
 }
 
 static const struct proc_ops cifs_lookup_cache_proc_ops = {
-	.proc_open	= cifs_lookup_cache_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= cifs_lookup_cache_proc_show,
 	.proc_write	= cifs_lookup_cache_proc_write,
 };
 
@@ -1113,11 +1074,6 @@ static int traceSMB_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int traceSMB_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, traceSMB_proc_show, NULL);
-}
-
 static ssize_t traceSMB_proc_write(struct file *file, const char __user *buffer,
 		size_t count, loff_t *ppos)
 {
@@ -1131,10 +1087,7 @@ static ssize_t traceSMB_proc_write(struct file *file, const char __user *buffer,
 }
 
 static const struct proc_ops traceSMB_proc_ops = {
-	.proc_open	= traceSMB_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= traceSMB_proc_show,
 	.proc_write	= traceSMB_proc_write,
 };
 
@@ -1144,11 +1097,6 @@ static int cifs_security_flags_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int cifs_security_flags_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, cifs_security_flags_proc_show, NULL);
-}
-
 /*
  * Ensure that if someone sets a MUST flag, that we disable all other MAY
  * flags except for the ones corresponding to the given MUST flag. If there are
@@ -1234,10 +1182,7 @@ static ssize_t cifs_security_flags_proc_write(struct file *file,
 }
 
 static const struct proc_ops cifs_security_flags_proc_ops = {
-	.proc_open	= cifs_security_flags_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show	= cifs_security_flags_proc_show,
 	.proc_write	= cifs_security_flags_proc_write,
 };
 
@@ -1271,16 +1216,8 @@ static int cifs_mount_params_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static int cifs_mount_params_proc_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, cifs_mount_params_proc_show, NULL);
-}
-
 static const struct proc_ops cifs_mount_params_proc_ops = {
-	.proc_open	= cifs_mount_params_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= single_release,
+	.proc_show = cifs_mount_params_proc_show,
 	/* No need for write for now */
 	/* .proc_write	= cifs_mount_params_proc_write, */
 };
diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c
index 26956e33037e..58f16cf83d36 100644
--- a/fs/smb/client/dfs_cache.c
+++ b/fs/smb/client/dfs_cache.c
@@ -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,
 };
 
diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
index 0c0df16de581..aa3d5544cfc1 100644
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -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,
 };
 
diff --git a/kernel/latencytop.c b/kernel/latencytop.c
index d4281d1e13a6..b764f47606d3 100644
--- a/kernel/latencytop.c
+++ b/kernel/latencytop.c
@@ -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)
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index 18a71a9108d3..64a211290872 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -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
 };
 
diff --git a/mm/slab_common.c b/mm/slab_common.c
index 01c64ef661e7..fbf303b45858 100644
--- a/mm/slab_common.c
+++ b/mm/slab_common.c
@@ -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)
diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index 02af70d360bc..12363a373fb8 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -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 */
diff --git a/net/sunrpc/stats.c b/net/sunrpc/stats.c
index d08711bee18e..f5ee18a0eb37 100644
--- a/net/sunrpc/stats.c
+++ b/net/sunrpc/stats.c
@@ -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,
 };
 
 /*

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 11/23] selftests/proc: check that a faulting reader doesn't hold up removal
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (9 preceding siblings ...)
  2026-11-04 19:31   ` [PATCH RESEND v2 10/23] treewide: let procfs own the seq_file of simple proc entries Christian Brauner
@ 2026-11-04 19:31   ` Christian Brauner
  2026-11-04 19:32   ` [PATCH RESEND v2 12/23] seq_file: add seq_preallocate() Christian Brauner
                     ` (11 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:31 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Read /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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore            |   1 +
 tools/testing/selftests/proc/Makefile              |   1 +
 .../selftests/proc/proc-remove-slow-reader.c       | 133 +++++++++++++++++++++
 3 files changed, 135 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 3fa89f46b9c7..e2566cc0ae07 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -22,6 +22,7 @@
 /proc-uptime-002
 /proc-pidns
 /proc-remove-slow-sysctl
+/proc-remove-slow-reader
 /proc-remove-slow-readlink
 /read
 /self
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index df02699a3587..6a9bdc999f54 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -33,6 +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
diff --git a/tools/testing/selftests/proc/proc-remove-slow-reader.c b/tools/testing/selftests/proc/proc-remove-slow-reader.c
new file mode 100644
index 000000000000..a201645beca9
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-remove-slow-reader.c
@@ -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, &reg), 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

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 12/23] seq_file: add seq_preallocate()
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (10 preceding siblings ...)
  2026-11-04 19:31   ` [PATCH RESEND v2 11/23] selftests/proc: check that a faulting reader doesn't hold up removal Christian Brauner
@ 2026-11-04 19:32   ` Christian Brauner
  2026-11-04 19:32   ` [PATCH RESEND v2 13/23] proc: let procfs own the seq_file of /proc/stat Christian Brauner
                     ` (10 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:32 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

single_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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/seq_file.c            | 36 ++++++++++++++++++++++++++++--------
 include/linux/seq_file.h |  1 +
 2 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/fs/seq_file.c b/fs/seq_file.c
index 456c78719fd0..a4644cdda7e6 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -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);
 
diff --git a/include/linux/seq_file.h b/include/linux/seq_file.h
index b813b7e63d89..834daff48b7d 100644
--- a/include/linux/seq_file.h
+++ b/include/linux/seq_file.h
@@ -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);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 13/23] proc: let procfs own the seq_file of /proc/stat
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (11 preceding siblings ...)
  2026-11-04 19:32   ` [PATCH RESEND v2 12/23] seq_file: add seq_preallocate() Christian Brauner
@ 2026-11-04 19:32   ` Christian Brauner
  2026-11-04 19:32   ` [PATCH RESEND v2 14/23] proc: let procfs copy in writes before pinning the entry Christian Brauner
                     ` (9 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:32 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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) <brauner@kernel.org>
---
 fs/proc/stat.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/fs/proc/stat.c b/fs/proc/stat.c
index 79b42f2bfd00..3bfb79edc0f6 100644
--- a/fs/proc/stat.c
+++ b/fs/proc/stat.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 14/23] proc: let procfs copy in writes before pinning the entry
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (12 preceding siblings ...)
  2026-11-04 19:32   ` [PATCH RESEND v2 13/23] proc: let procfs own the seq_file of /proc/stat Christian Brauner
@ 2026-11-04 19:32   ` Christian Brauner
  2026-11-04 19:32   ` [PATCH RESEND v2 15/23] proc: copy in writes to the network namespace entries before pinning Christian Brauner
                     ` (8 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:32 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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(+)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 959bf9e33245..4f1f83e9f288 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -466,6 +466,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;
 }
 
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index d51ab7548b11..df1caa0f238d 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -417,11 +417,44 @@ static ssize_t pde_write(struct proc_dir_entry *pde, struct file *file, const ch
 	return -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);
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 674e6b4ed21e..da246c5ed4bf 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -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;
@@ -109,6 +110,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_start() */
 static inline bool pde_is_seq_file(const struct proc_dir_entry *pde)
 {
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index ea55ed85a2d6..c81c93325044 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -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 */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 15/23] proc: copy in writes to the network namespace entries before pinning
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (13 preceding siblings ...)
  2026-11-04 19:32   ` [PATCH RESEND v2 14/23] proc: let procfs copy in writes before pinning the entry Christian Brauner
@ 2026-11-04 19:32   ` Christian Brauner
  2026-11-04 19:33   ` [PATCH RESEND v2 16/23] proc: let procfs copy out reads that don't go through seq_file Christian Brauner
                     ` (7 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:32 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c  | 15 ++++-----------
 fs/proc/internal.h |  2 +-
 fs/proc/proc_net.c |  4 ++--
 3 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 4f1f83e9f288..5e2ed5a04ec9 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -947,26 +947,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;
 }
 
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index da246c5ed4bf..1d36fe4b1af5 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -344,7 +344,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
diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
index 9973f846c467..b78d627b024f 100644
--- a/fs/proc/proc_net.c
+++ b/fs/proc/proc_net.c
@@ -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,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 16/23] proc: let procfs copy out reads that don't go through seq_file
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (14 preceding siblings ...)
  2026-11-04 19:32   ` [PATCH RESEND v2 15/23] proc: copy in writes to the network namespace entries before pinning Christian Brauner
@ 2026-11-04 19:33   ` Christian Brauner
  2026-11-04 19:33   ` [PATCH RESEND v2 17/23] fs/proc/vmcore: let procfs copy out reads Christian Brauner
                     ` (6 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:33 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_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

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 5e2ed5a04ec9..2d503250de75 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -474,6 +474,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;
 }
 
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index df1caa0f238d..caa47933c233 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -23,6 +23,8 @@
 #include <linux/sysctl.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
+#include <linux/sched/signal.h>
+#include <linux/uio.h>
 #include <linux/mount.h>
 #include <linux/bug.h>
 #include <linux/wait_bit.h>
@@ -377,11 +379,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;
+	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);
+	if (!buf)
+		return -ENOMEM;
+
+	/* PROC_ENTRY_FULL_READS fills the read in pieces */
+	do {
+		count = min_t(size_t, iov_iter_count(iter), len);
+		pos = iocb->ki_pos;
+		if (!pde_pin(pde)) {
+			rv = -EIO;
+			break;
+		}
+		rv = pde->proc_ops->proc_read_buf(file, buf, count, &pos);
+		pde_unpin(pde);
+		if (rv <= 0)
+			break;
+		if (WARN_ON_ONCE(rv > count)) {
+			rv = -EIO;
+			break;
+		}
+		if (copy_to_iter(buf, rv, iter) != rv) {
+			rv = -EFAULT;
+			break;
+		}
+		iocb->ki_pos = pos;
+		done += rv;
+		if (!pde_has_full_reads(pde) || rv < count)
+			break;
+		cond_resched();
+	} while (iov_iter_count(iter) && !fatal_signal_pending(current));
+
+	return done ?: rv;
+}
+
 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_has_proc_read_buf(pde))
+		return pde_read_buf(pde, iocb, iter);
 	if (!pde_pin(pde))
 		return -EIO;
 	ret = pde->proc_ops->proc_read_iter(iocb, iter);
@@ -793,7 +851,7 @@ struct inode *proc_get_inode(struct super_block *sb, struct proc_dir_entry *de)
 		inode->i_op = de->proc_iops;
 		if (pde_is_seq_file(de))
 			inode->i_fop = &proc_seq_file_ops;
-		else if (pde_has_proc_read_iter(de))
+		else if (pde_has_proc_read_iter(de) || pde_has_proc_read_buf(de))
 			inode->i_fop = &proc_iter_file_ops;
 		else
 			inode->i_fop = &proc_reg_file_ops;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 1d36fe4b1af5..b5c78f4f392c 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -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;
@@ -120,6 +121,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_start() */
 static inline bool pde_is_seq_file(const struct proc_dir_entry *pde)
 {
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index c81c93325044..0f0997898ba3 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -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 */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 17/23] fs/proc/vmcore: let procfs copy out reads
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (15 preceding siblings ...)
  2026-11-04 19:33   ` [PATCH RESEND v2 16/23] proc: let procfs copy out reads that don't go through seq_file Christian Brauner
@ 2026-11-04 19:33   ` Christian Brauner
  2026-11-04 19:33   ` [PATCH RESEND v2 18/23] proc: let procfs copy ioctl arguments in and out Christian Brauner
                     ` (5 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:33 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/vmcore.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/fs/proc/vmcore.c b/fs/proc/vmcore.c
index ccb795b85509..1c1e71231f4e 100644
--- a/fs/proc/vmcore.c
+++ b/fs/proc/vmcore.c
@@ -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,
 };

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 18/23] proc: let procfs copy ioctl arguments in and out
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (16 preceding siblings ...)
  2026-11-04 19:33   ` [PATCH RESEND v2 17/23] fs/proc/vmcore: let procfs copy out reads Christian Brauner
@ 2026-11-04 19:33   ` Christian Brauner
  2026-11-04 19:33   ` [PATCH RESEND v2 19/23] proc: pin the entry around the vm_operations of its mappings Christian Brauner
                     ` (4 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:33 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_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 keep a table of the commands, like
DRM keeps one of its ioctls, and let it say 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() or a
->proc_compat_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       | 17 +++++++++++++++
 fs/proc/inode.c         | 58 +++++++++++++++++++++++++++++++++++++++++++++++++
 fs/proc/internal.h      |  5 +++++
 include/linux/proc_fs.h | 35 +++++++++++++++++++++++++++++
 4 files changed, 115 insertions(+)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 2d503250de75..1bad3b068d53 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -483,6 +483,23 @@ 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;
+#ifdef CONFIG_COMPAT
+		/* 32-bit tasks go through the table as well */
+		if (WARN_ON_ONCE(proc_ops->proc_compat_ioctl))
+			return -EINVAL;
+#endif
+		/* 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;
 }
 
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index caa47933c233..717cbaad65df 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/cache.h>
+#include <linux/compat.h>
 #include <linux/time.h>
 #include <linux/proc_fs.h>
 #include <linux/kernel.h>
@@ -548,11 +549,66 @@ static long pde_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned in
 	return -ENOTTY;
 }
 
+/* the table lives in proc_ops, which goes away with the entry */
+static bool pde_ioctl_arg(struct proc_dir_entry *pde, unsigned int cmd,
+			  struct proc_ioctl_arg *arg)
+{
+	const struct proc_ops *proc_ops = pde->proc_ops;
+	bool found = false;
+
+	if (!pde_pin(pde))
+		return false;
+	for (unsigned int i = 0; i < proc_ops->proc_nr_ioctl_args; i++) {
+		if (proc_ops->proc_ioctl_args[i].cmd == cmd) {
+			*arg = proc_ops->proc_ioctl_args[i];
+			found = true;
+			break;
+		}
+	}
+	pde_unpin(pde);
+	return found;
+}
+
+/*
+ * procfs copies the argument in before it pins the entry and copies it back
+ * out after unpinning it. An ioctl that faults on its argument then doesn't
+ * hold up remove_proc_entry().
+ */
+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);
+		if (!buf)
+			return -ENOMEM;
+		if (a.in && 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 && a.out && copy_to_user(uarg, buf, a.out))
+		return -EFAULT;
+	return rv;
+}
+
 static 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;
 
+	if (pde_has_proc_ioctl_buf(pde))
+		return pde_ioctl_buf(pde, file, cmd, (void __user *)arg, arg);
 	if (!pde_pin(pde))
 		return -ENOTTY;
 	rv = pde_ioctl(pde, file, cmd, arg);
@@ -574,6 +630,8 @@ static long proc_reg_compat_ioctl(struct file *file, unsigned int cmd, unsigned
 	struct proc_dir_entry *pde = PDE(file_inode(file));
 	long rv;
 
+	if (pde_has_proc_ioctl_buf(pde))
+		return pde_ioctl_buf(pde, file, cmd, compat_ptr(arg), arg);
 	if (!pde_pin(pde))
 		return -ENOTTY;
 	rv = pde_compat_ioctl(pde, file, cmd, arg);
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index b5c78f4f392c..ae0f9779cf1b 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -131,6 +131,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_start() */
 static inline bool pde_is_seq_file(const struct proc_dir_entry *pde)
 {
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 0f0997898ba3..2ba4e4a39e13 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -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 */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 19/23] proc: pin the entry around the vm_operations of its mappings
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (17 preceding siblings ...)
  2026-11-04 19:33   ` [PATCH RESEND v2 18/23] proc: let procfs copy ioctl arguments in and out Christian Brauner
@ 2026-11-04 19:33   ` Christian Brauner
  2026-11-04 19:34   ` [PATCH RESEND v2 20/23] selftests/proc: check that a mapped entry can be removed Christian Brauner
                     ` (3 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:33 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

proc_reg_mmap() pins the entry around ->proc_mmap() and that's it. What
the entry hands out lives on without the pin: the vm_operations it put
on the mapping run on every fault, on every write to a read-only page
and on every ptrace access with the entry unpinned, and the mapping
itself survives remove_proc_entry(). For /proc/vmcore that means the
fault handler runs after vmcore_cleanup() freed what it looks at, and a
mapping of the old memory keeps pointing at it after fadump gave that
memory back to the page allocator. /proc/bus/pci has the same problem
when a device goes away.

So do what kernfs does for the mappings of sysfs files. procfs puts
vm_operations of its own on the mapping. They pin the entry around its
->open(), ->fault(), ->page_mkwrite() and ->access() and fail with
SIGBUS, or do nothing, once the entry is gone. The entry's
vm_operations are kept in its open node, so every mapping of an entry
has to use the same ones, like kernfs asks that of every mapping of an
open file. ->close() can't be wrapped, it runs when the mapping goes
away and that can be after the entry is gone, so a mapping that has
one is refused.

When the entry is removed procfs unmaps the mappings of every open file
of an entry that has been mapped before it calls ->proc_release(), so a
fault into one of them finds the entry gone. That unmaps the entry's
own address space, the way kernfs_drain_open_files() does it.
/proc/bus/pci maps through the iomem address space that /dev/mem and
the sysfs resource files share, see iomem_get_mapping(), and
unmapping that would take every /dev/mem and BAR mapping on the system
with it. So those mappings are left alone, like the sysfs resource
files leave theirs, and keep pointing at the BAR of a device that is
gone. To know the files an entry that can be mapped gets an open node
like an entry with a ->proc_release() has one. The mapping is linked
into the file's address space only after ->mmap() returned, so a
removal that runs in between doesn't find it. kernfs has the same
window.

Entries that are never removed aren't wrapped, procfs never pins them.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c       |   6 +--
 fs/proc/inode.c         | 126 ++++++++++++++++++++++++++++++++++++++++++++----
 fs/proc/internal.h      |   9 ++--
 include/linux/proc_fs.h |  15 ++++++
 4 files changed, 140 insertions(+), 16 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 1bad3b068d53..81a6cb560c75 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -505,14 +505,14 @@ static int pde_set_flags(struct proc_dir_entry *pde)
 
 /*
  * procfs has to know the open files of an entry whose ->proc_release() it
- * calls at removal. A permanent entry is never removed and skips all that,
- * see proc_reg_open().
+ * calls at removal and whose mappings it undoes then. A permanent entry is
+ * never removed and skips all that, see proc_reg_open().
  */
 static bool pde_needs_open_node(const struct proc_dir_entry *pde)
 {
 	if (!S_ISREG(pde->mode) || pde_is_permanent(pde))
 		return false;
-	return pde->proc_ops->proc_release;
+	return pde->proc_ops->proc_release || pde->proc_ops->proc_mmap;
 }
 
 /*
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 717cbaad65df..5e423bcad278 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -312,7 +312,8 @@ static void pde_seq_release(struct proc_dir_entry *pde, struct inode *inode,
  *
  * PDE is locked on entry, unlocked on exit.
  */
-static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo)
+static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo,
+		       bool rundown)
 	__releases(&pde->pde_unload_lock)
 {
 	/*
@@ -336,7 +337,17 @@ static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo)
 		spin_unlock(&pde->pde_unload_lock);
 
 		file = pdeo->file;
-		pde->proc_ops->proc_release(file_inode(file), file);
+		/*
+		 * A mapping of a removed entry faults, see proc_vm_ops. Like
+		 * kernfs this unmaps the entry's own address space. A file
+		 * that maps through another one, /proc/bus/pci through the
+		 * iomem address space of /dev/mem, keeps its mappings like a
+		 * sysfs resource file does.
+		 */
+		if (rundown && pde->open->vm_ops)
+			unmap_mapping_range(file_inode(file)->i_mapping, 0, 0, 1);
+		if (pde->proc_ops->proc_release)
+			pde->proc_ops->proc_release(file_inode(file), file);
 
 		spin_lock(&pde->pde_unload_lock);
 		/* Strictly after ->proc_release, see above. */
@@ -362,7 +373,7 @@ void proc_entry_rundown(struct proc_dir_entry *de)
 	while (!list_empty(&de->open->files)) {
 		struct pde_opener *pdeo;
 		pdeo = list_first_entry(&de->open->files, struct pde_opener, lh);
-		close_pdeo(de, pdeo);
+		close_pdeo(de, pdeo, true);
 		spin_lock(&de->pde_unload_lock);
 	}
 	spin_unlock(&de->pde_unload_lock);
@@ -642,12 +653,106 @@ static long proc_reg_compat_ioctl(struct file *file, unsigned int cmd, unsigned
 #define proc_reg_compat_ioctl NULL
 #endif
 
-static int pde_mmap(struct proc_dir_entry *pde, struct file *file, struct vm_area_struct *vma)
+/*
+ * The vm_operations of an entry's mappings run with the entry pinned, like
+ * the entry's file methods do, and a mapping of a removed entry faults.
+ * proc_entry_rundown() unmaps the mappings of every open file and a fault
+ * after that finds the entry gone. Like kernfs_vm_ops, and like there only
+ * these four methods of the entry's vm_operations are called.
+ */
+static void proc_vma_open(struct vm_area_struct *vma)
+{
+	struct proc_dir_entry *pde = PDE(file_inode(vma->vm_file));
+	const struct vm_operations_struct *vm_ops = pde->open->vm_ops;
+
+	if (!pde_pin(pde))
+		return;
+	if (vm_ops->open)
+		vm_ops->open(vma);
+	pde_unpin(pde);
+}
+
+static vm_fault_t proc_vma_fault(struct vm_fault *vmf)
+{
+	struct proc_dir_entry *pde = PDE(file_inode(vmf->vma->vm_file));
+	const struct vm_operations_struct *vm_ops = pde->open->vm_ops;
+	vm_fault_t ret = VM_FAULT_SIGBUS;
+
+	if (!pde_pin(pde))
+		return VM_FAULT_SIGBUS;
+	if (vm_ops->fault)
+		ret = vm_ops->fault(vmf);
+	pde_unpin(pde);
+	return ret;
+}
+
+static vm_fault_t proc_vma_page_mkwrite(struct vm_fault *vmf)
+{
+	struct proc_dir_entry *pde = PDE(file_inode(vmf->vma->vm_file));
+	const struct vm_operations_struct *vm_ops = pde->open->vm_ops;
+	vm_fault_t ret = 0;
+
+	if (!pde_pin(pde))
+		return VM_FAULT_SIGBUS;
+	if (vm_ops->page_mkwrite)
+		ret = vm_ops->page_mkwrite(vmf);
+	else
+		file_update_time(vmf->vma->vm_file);
+	pde_unpin(pde);
+	return ret;
+}
+
+static int proc_vma_access(struct vm_area_struct *vma, unsigned long addr,
+			   void *buf, int len, int write)
+{
+	struct proc_dir_entry *pde = PDE(file_inode(vma->vm_file));
+	const struct vm_operations_struct *vm_ops = pde->open->vm_ops;
+	int ret = -EINVAL;
+
+	if (!pde_pin(pde))
+		return -EINVAL;
+	if (vm_ops->access)
+		ret = vm_ops->access(vma, addr, buf, len, write);
+	pde_unpin(pde);
+	return ret;
+}
+
+static const struct vm_operations_struct proc_vm_ops = {
+	.open		= proc_vma_open,
+	.fault		= proc_vma_fault,
+	.page_mkwrite	= proc_vma_page_mkwrite,
+	.access		= proc_vma_access,
+};
+
+static int pde_mmap(struct proc_dir_entry *pde, struct file *file,
+		    struct vm_area_struct *vma)
 {
 	const auto mmap = pde->proc_ops->proc_mmap;
-	if (mmap)
-		return mmap(file, vma);
-	return -EIO;
+	struct pde_open_node *on = pde->open;
+	int rv;
+
+	if (!mmap)
+		return -EIO;
+	rv = mmap(file, vma);
+	if (rv || pde_is_permanent(pde))
+		return rv;
+	/* the entry mapped something else, that isn't ours to wrap */
+	if (vma->vm_file != file)
+		return 0;
+	/*
+	 * ->close() can't be wrapped, it runs when the mapping goes away and
+	 * that can be after the entry is gone, see close_pdeo().
+	 */
+	if (!vma->vm_ops || vma->vm_ops->close)
+		return -EINVAL;
+
+	/* every mapping of an entry uses the same vm_operations, see above */
+	guard(spinlock)(&pde->pde_unload_lock);
+	if (on->vm_ops && on->vm_ops != vma->vm_ops)
+		return -EINVAL;
+	on->vm_ops = vma->vm_ops;
+	vma->vm_ops = &proc_vm_ops;
+	return 0;
 }
 
 static int proc_reg_mmap(struct file *file, struct vm_area_struct *vma)
@@ -743,8 +848,9 @@ static int proc_reg_open(struct inode *inode, struct file *file)
 	 * 2) rmmod isn't blocked by opening file in /proc and sitting on
 	 *    the descriptor (including "rmmod foo </proc/foo" scenario).
 	 *
-	 * Save every "struct file" with custom ->release hook. Those entries
-	 * have an open node, see proc_register().
+	 * Save every "struct file" with custom ->release hook, and every
+	 * one that can be mapped, proc_entry_rundown() unmaps those. Those
+	 * entries have an open node, see proc_register().
 	 */
 	if (!use_pde(pde))
 		return -ENOENT;
@@ -794,7 +900,7 @@ static int proc_reg_release(struct inode *inode, struct file *file)
 	spin_lock(&pde->pde_unload_lock);
 	list_for_each_entry(pdeo, &pde->open->files, lh) {
 		if (pdeo->file == file) {
-			close_pdeo(pde, pdeo);
+			close_pdeo(pde, pdeo, false);
 			goto out;
 		}
 	}
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index ae0f9779cf1b..0b1c0585d6a6 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -373,12 +373,15 @@ struct pde_opener {
 } __randomize_layout;
 
 /*
- * The open files of an entry whose ->proc_release() procfs has to call when
- * the entry is removed while they are still open. Allocated with the entry,
- * see proc_register().
+ * The open files of an entry whose ->proc_release() procfs has to call and
+ * whose mappings it has to undo when the entry is removed while they are
+ * still open, that is of an entry with a ->proc_release() or a
+ * ->proc_mmap(). Allocated with the entry, see proc_register().
  */
 struct pde_open_node {
 	struct list_head files;		/* who did ->open, but not ->release */
+	/* of its mappings, the same for all of them, see pde_mmap() */
+	const struct vm_operations_struct *vm_ops;
 };
 extern const struct inode_operations proc_link_inode_operations;
 extern const struct inode_operations proc_pid_link_inode_operations;
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 2ba4e4a39e13..5bf039d20990 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -86,6 +86,21 @@ struct proc_ops {
 #ifdef CONFIG_COMPAT
 	long	(*proc_compat_ioctl)(struct file *, unsigned int, unsigned long);
 #endif
+	/*
+	 * procfs pins the entry around ->proc_mmap() and wraps the
+	 * vm_operations of the mapping so that ->open(), ->fault(),
+	 * ->page_mkwrite() and ->access() run with the entry pinned as well.
+	 * Nothing else of them is called, like for a kernfs file. Every
+	 * mapping of an entry has to use the same vm_operations. A ->close()
+	 * can't be wrapped, it runs when the mapping goes away and that can
+	 * be after the entry is gone, so a mapping that has one is refused.
+	 * Removing the entry unmaps the mappings in its own address space,
+	 * like kernfs does, and a fault into one of them is SIGBUS from then
+	 * on. A file that maps through another address space, /proc/bus/pci
+	 * through the one of /dev/mem, keeps its mappings like a sysfs
+	 * resource file does. The mappings of a permanent entry are left
+	 * alone.
+	 */
 	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);
 	/*

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 20/23] selftests/proc: check that a mapped entry can be removed
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (18 preceding siblings ...)
  2026-11-04 19:33   ` [PATCH RESEND v2 19/23] proc: pin the entry around the vm_operations of its mappings Christian Brauner
@ 2026-11-04 19:34   ` Christian Brauner
  2026-11-04 19:34   ` [PATCH RESEND v2 21/23] proc: let lockdep check the pin Christian Brauner
                     ` (2 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Map a memory BAR of a PCI device through /proc/bus/pci and remove the
device through sysfs:

- the removal doesn't wait for the mapped file

- the entry is gone

- and reading the file fails with -EIO

The mapping itself survives. /proc/bus/pci maps through the iomem
address space of /dev/mem and procfs leaves that alone, like the sysfs
resource files leave theirs, so the test doesn't touch the mapping
after the removal. What the BAR of a removed device reads like is
between the reader and the hardware.

Nothing in the test can know which device is safe to remove, so it
takes the device from PROC_PCI_DEV, skips without it and rescans the bus
afterwards. It also skips where PCI files can't be mapped or that isn't
allowed.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore           |   1 +
 tools/testing/selftests/proc/Makefile             |   1 +
 tools/testing/selftests/proc/proc-remove-mapped.c | 160 ++++++++++++++++++++++
 3 files changed, 162 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index e2566cc0ae07..cafd8c263c9a 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -23,6 +23,7 @@
 /proc-pidns
 /proc-remove-slow-sysctl
 /proc-remove-slow-reader
+/proc-remove-mapped
 /proc-remove-slow-readlink
 /read
 /self
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index 6a9bdc999f54..c38297afc244 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -34,6 +34,7 @@ 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-mapped
 TEST_GEN_PROGS += proc-remove-slow-readlink
 
 include ../lib.mk
diff --git a/tools/testing/selftests/proc/proc-remove-mapped.c b/tools/testing/selftests/proc/proc-remove-mapped.c
new file mode 100644
index 000000000000..92860fb2a8f4
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-remove-mapped.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Removing a /proc entry with a mapped file open must not wait for that file
+ * and must leave the file dead. Map a memory BAR of a PCI device through
+ * /proc/bus/pci and remove the device, which removes its entry. Nothing here
+ * can know which device is safe to remove and rescan, so it is named in
+ * PROC_PCI_DEV, e.g. PROC_PCI_DEV=0000:00:03.0.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/pci.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define DEV_ENV		"PROC_PCI_DEV"
+#define SYSFS_DEVICES	"/sys/bus/pci/devices/"
+#define SYSFS_RESCAN	"/sys/bus/pci/rescan"
+#define PROC_PCI	"/proc/bus/pci/"
+#define IORESOURCE_MEM	0x00000200
+
+static sigjmp_buf fault;
+
+static void on_sigbus(int sig)
+{
+	siglongjmp(fault, 1);
+}
+
+/* the first memory BAR of @dev */
+static int mem_bar(const char *dev, unsigned long long *start,
+		   unsigned long long *len)
+{
+	unsigned long long s, e, flags;
+	char path[PATH_MAX];
+	int ret = -ENODEV;
+	FILE *f;
+
+	snprintf(path, sizeof(path), SYSFS_DEVICES "%s/resource", dev);
+	f = fopen(path, "r");
+	if (!f)
+		return -errno;
+	while (fscanf(f, "%llx %llx %llx", &s, &e, &flags) == 3) {
+		if ((flags & IORESOURCE_MEM) && e >= s) {
+			*start = s;
+			*len = e - s + 1;
+			ret = 0;
+			break;
+		}
+	}
+	fclose(f);
+	return ret;
+}
+
+static int write_one(const char *path)
+{
+	int fd, ret;
+
+	fd = open(path, O_WRONLY | O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+	ret = write(fd, "1", 1) < 0 ? -errno : 0;
+	close(fd);
+	return ret;
+}
+
+FIXTURE(pci) {
+	char dev[16];
+	char path[64];
+	bool removed;
+};
+
+FIXTURE_SETUP(pci)
+{
+	const char *dev = getenv(DEV_ENV);
+
+	self->removed = false;
+	if (!dev)
+		SKIP(return, DEV_ENV " isn't set");
+	/* 0000:BB:DD.F is /proc/bus/pci/BB/DD.F */
+	if (strlen(dev) != 12 || strncmp(dev, "0000:", 5))
+		SKIP(return, "%s: not a device in PCI domain 0", dev);
+	strcpy(self->dev, dev);
+	snprintf(self->path, sizeof(self->path), PROC_PCI "%.2s/%s",
+		 dev + 5, dev + 8);
+	if (access(self->path, R_OK))
+		SKIP(return, "%s: %s", self->path, strerror(errno));
+}
+
+FIXTURE_TEARDOWN(pci)
+{
+	if (self->removed && write_one(SYSFS_RESCAN))
+		TH_LOG("couldn't rescan the PCI bus for %s", self->dev);
+}
+
+TEST_F(pci, remove_while_mapped)
+{
+	long psz = sysconf(_SC_PAGESIZE);
+	unsigned long long start = 0, len = 0;
+	char path[PATH_MAX];
+	volatile unsigned int *p;
+	unsigned int v;
+	int fd;
+
+	if (mem_bar(self->dev, &start, &len))
+		SKIP(return, "%s has no memory BAR", self->dev);
+
+	fd = open(self->path, O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+	if (ioctl(fd, PCIIOC_MMAP_IS_MEM)) {
+		/* no PCI mmap on this architecture, or locked down */
+		if (errno == EINVAL || errno == EPERM) {
+			close(fd);
+			SKIP(return, "PCIIOC_MMAP_IS_MEM: %s", strerror(errno));
+		}
+		ASSERT_TRUE(false) {
+			TH_LOG("PCIIOC_MMAP_IS_MEM: %s", strerror(errno));
+		}
+	}
+	p = mmap(NULL, psz, PROT_READ, MAP_SHARED, fd, start);
+	if (p == MAP_FAILED)
+		SKIP(return, "mmap: %s", strerror(errno));
+
+	/* The BAR can be read while the device is there. */
+	ASSERT_NE(signal(SIGBUS, on_sigbus), SIG_ERR);
+	if (sigsetjmp(fault, 1) == 0) {
+		v = *p;
+	} else {
+		ASSERT_TRUE(false) {
+			TH_LOG("reading the mapping faulted before the removal");
+		}
+	}
+
+	/* Removing the device removes its entry. */
+	snprintf(path, sizeof(path), SYSFS_DEVICES "%s/remove", self->dev);
+	ASSERT_EQ(write_one(path), 0);
+	self->removed = true;
+	EXPECT_EQ(access(self->path, F_OK), -1);
+
+	/*
+	 * The file is dead. The mapping isn't procfs's to unmap, it lives in
+	 * the iomem address space of /dev/mem, see proc_bus_pci_open(), and
+	 * is left alone like a sysfs resource file's.
+	 */
+	EXPECT_EQ(pread(fd, &v, sizeof(v), 0), -1);
+	EXPECT_EQ(errno, EIO);
+
+	munmap((void *)p, psz);
+	close(fd);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 21/23] proc: let lockdep check the pin
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (19 preceding siblings ...)
  2026-11-04 19:34   ` [PATCH RESEND v2 20/23] selftests/proc: check that a mapped entry can be removed Christian Brauner
@ 2026-11-04 19:34   ` Christian Brauner
  2026-11-04 19:34   ` [PATCH RESEND v2 22/23] proc: own the wait queue of an entry with a ->proc_poll() Christian Brauner
  2026-11-04 19:34   ` [PATCH RESEND v2 23/23] Documentation: seq_file: describe how a /proc file uses seq_file Christian Brauner
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

remove_proc_entry() waits until every call into the entry has returned,
and its callers like to hold locks while they wait. A method of the
entry that takes one of those locks deadlocks with them. xt_recent
removed its entries with a spinlock held that its seq_file took, and
ALSA has to remove an entry before it takes info_mutex because its open
takes it. Both of those were found by reading the code.

kernfs lets lockdep find them. kernfs_get_active() is a read lock to
lockdep and kernfs_drain() takes the write side around its wait. So a
method that takes a lock L while active records class -> L, a removal
under L records L -> class, and lockdep reports the cycle.

Do the same for the pin. use_pde() records a read lock on the entry's
lockdep map, unuse_pde() releases it and proc_entry_rundown() takes the
write side around waiting for the last user. The key of the class is
what the entries of a kind already share, their seq_operations or
their proc_ops, static objects that lockdep takes as a key. The entries
that only share a show function, those of proc_create_single() and
friends, get a key per registration site instead, like every sysfs
attribute has one: a show function lives in text and lockdep doesn't
take that as a key. So all /proc/net/dev entries are one class, which
is right. The same code runs pinned in every network namespace and a
removal under a lock that its reader takes is a deadlock in any of
them.

Nothing in the tree removes an entry of its own kind from one of its
methods, so there's no need for an escape hatch like
kernfs_break_active_protection() yet.

The map only exists on lockdep kernels.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c       | 46 ++++++++++++++++++++++++++++++++++++----------
 fs/proc/inode.c         | 29 ++++++++++++++++++++++++++---
 fs/proc/internal.h      | 17 ++++++++++++++++-
 fs/proc/proc_net.c      | 28 +++++++++++++++-------------
 include/linux/proc_fs.h | 42 +++++++++++++++++++++++++++++++++---------
 5 files changed, 126 insertions(+), 36 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 81a6cb560c75..ca4025ee7aa8 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -541,9 +541,33 @@ static int pde_init_seq_direct(struct proc_dir_entry *pde)
 	return 0;
 }
 
+static void pde_lockdep_init(struct proc_dir_entry *pde,
+			     struct lock_class_key *key)
+{
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+	const void *k = key;
+
+	if (!pde_lockdep(pde))
+		return;
+	/*
+	 * To lockdep the pin is a read lock that proc_entry_rundown() takes
+	 * the write side of, see use_pde(). The entries that share
+	 * seq_operations or proc_ops run the same code pinned, so they share
+	 * a class, and those are static objects that lockdep takes as its
+	 * key. The entries that only share a show function get a key per
+	 * registration site instead, see proc_create_single_data().
+	 */
+	if (!k)
+		k = pde_has_proc_seq_ops(pde) ? (const void *)pde->seq_ops :
+						pde->proc_ops;
+	lockdep_init_map(&pde->dep_map, "pde->in_use",
+			 (struct lock_class_key *)k, 0);
+#endif
+}
+
 /* returns the registered entry, or frees dp and returns NULL on failure */
 struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
-		struct proc_dir_entry *dp)
+		struct proc_dir_entry *dp, struct lock_class_key *key)
 {
 	if (proc_alloc_inum(&dp->low_ino))
 		goto out_free_entry;
@@ -558,6 +582,7 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
 	}
 	if (pde_init_seq_direct(dp))
 		goto out_free_inum;
+	pde_lockdep_init(dp, key);
 
 	write_lock(&proc_subdir_lock);
 	dp->parent = dir;
@@ -660,7 +685,7 @@ struct proc_dir_entry *proc_symlink(const char *name,
 		ent->data = kmemdup(dest, ent->size + 1, GFP_KERNEL);
 		if (ent->data) {
 			ent->proc_iops = &proc_link_inode_operations;
-			ent = proc_register(parent, ent);
+			ent = proc_register(parent, ent, NULL);
 		} else {
 			pde_free(ent);
 			ent = NULL;
@@ -686,7 +711,7 @@ struct proc_dir_entry *_proc_mkdir(const char *name, umode_t mode,
 		if (force_lookup) {
 			pde_force_lookup(ent);
 		}
-		ent = proc_register(parent, ent);
+		ent = proc_register(parent, ent, NULL);
 	}
 	return ent;
 }
@@ -723,7 +748,7 @@ struct proc_dir_entry *proc_create_mount_point(const char *name)
 		ent->data = NULL;
 		ent->proc_dir_ops = NULL;
 		ent->proc_iops = NULL;
-		ent = proc_register(parent, ent);
+		ent = proc_register(parent, ent, NULL);
 	}
 	return ent;
 }
@@ -759,7 +784,7 @@ struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
 	if (!p)
 		return NULL;
 	p->proc_ops = proc_ops;
-	return proc_register(parent, p);
+	return proc_register(parent, p, NULL);
 }
 EXPORT_SYMBOL(proc_create_data);
  
@@ -789,13 +814,14 @@ struct proc_dir_entry *proc_create_seq_private(const char *name, umode_t mode,
 	p->seq_ops = ops;
 	p->state_size = state_size;
 	p->flags |= PROC_ENTRY_proc_seq_ops;
-	return proc_register(parent, p);
+	return proc_register(parent, p, NULL);
 }
 EXPORT_SYMBOL(proc_create_seq_private);
 
-struct proc_dir_entry *proc_create_single_data(const char *name, umode_t mode,
+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)
+		int (*show)(struct seq_file *, void *), void *data,
+		struct lock_class_key *key)
 {
 	struct proc_dir_entry *p;
 
@@ -805,9 +831,9 @@ struct proc_dir_entry *proc_create_single_data(const char *name, umode_t mode,
 	p->proc_ops = &proc_seq_ops;
 	p->single_show = show;
 	p->flags |= PROC_ENTRY_proc_show;
-	return proc_register(parent, p);
+	return proc_register(parent, p, key);
 }
-EXPORT_SYMBOL(proc_create_single_data);
+EXPORT_SYMBOL(__proc_create_single_data);
 
 void proc_set_size(struct proc_dir_entry *de, loff_t size)
 {
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 5e423bcad278..59de4681d845 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -199,13 +199,24 @@ const struct super_operations proc_sops = {
 
 enum {BIAS = -1U<<31};
 
+/*
+ * To lockdep the pin is a read lock and proc_entry_rundown() takes the write
+ * side, see pde_lockdep_init(). So a method that takes a lock while pinned
+ * and a removal under that lock are the deadlock lockdep knows how to report.
+ */
 static inline int use_pde(struct proc_dir_entry *pde)
 {
-	return likely(atomic_inc_unless_negative(&pde->in_use));
+	if (unlikely(!atomic_inc_unless_negative(&pde->in_use)))
+		return 0;
+	if (pde_lockdep(pde))
+		rwsem_acquire_read(&pde->dep_map, 0, 1, _RET_IP_);
+	return 1;
 }
 
 static void unuse_pde(struct proc_dir_entry *pde)
 {
+	if (pde_lockdep(pde))
+		rwsem_release(&pde->dep_map, _RET_IP_);
 	if (unlikely(atomic_dec_return(&pde->in_use) == BIAS))
 		wake_up_var(&pde->in_use);
 }
@@ -362,9 +373,21 @@ static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo,
 
 void proc_entry_rundown(struct proc_dir_entry *de)
 {
-	/* Wait until all existing callers into module are done. */
-	if (atomic_add_return(BIAS, &de->in_use) != BIAS)
+	/*
+	 * Wait until all existing callers into module are done. That's the
+	 * write side of the pin to lockdep, see use_pde().
+	 */
+	if (pde_lockdep(de))
+		rwsem_acquire(&de->dep_map, 0, 0, _RET_IP_);
+	if (atomic_add_return(BIAS, &de->in_use) != BIAS) {
+		if (pde_lockdep(de))
+			lock_contended(&de->dep_map, _RET_IP_);
 		wait_var_event(&de->in_use, atomic_read(&de->in_use) == BIAS);
+	}
+	if (pde_lockdep(de)) {
+		lock_acquired(&de->dep_map, _RET_IP_);
+		rwsem_release(&de->dep_map, _RET_IP_);
+	}
 
 	/* ->open->files can't grow from now on. */
 	if (!de->open)
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 0b1c0585d6a6..7a197e2920f8 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -9,6 +9,7 @@
 #include <linux/proc_ns.h>
 #include <linux/refcount.h>
 #include <linux/spinlock.h>
+#include <linux/lockdep.h>
 #include <linux/atomic.h>
 #include <linux/binfmts.h>
 #include <linux/sched/coredump.h>
@@ -67,6 +68,9 @@ struct proc_dir_entry {
 	umode_t mode;
 	u16 flags;
 	u8 namelen;
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+	struct lockdep_map dep_map;	/* the pin as a lock, see use_pde() */
+#endif
 	char inline_name[];
 } __randomize_layout;
 
@@ -142,6 +146,16 @@ 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);
 }
 
+/* Only regular files are pinned, and only their map is initialized. */
+static inline bool pde_lockdep(const struct proc_dir_entry *pde)
+{
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
+	return S_ISREG(pde->mode);
+#else
+	return false;
+#endif
+}
+
 extern struct kmem_cache *proc_dir_entry_cache;
 void pde_free(struct proc_dir_entry *pde);
 
@@ -318,7 +332,8 @@ bool proc_fill_cache(struct file *, struct dir_context *, const char *, unsigned
 struct proc_dir_entry *proc_create_reg(const char *name, umode_t mode,
 		struct proc_dir_entry **parent, void *data);
 struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
-		struct proc_dir_entry *dp);
+		struct proc_dir_entry *dp,
+		struct lock_class_key *key);
 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 *);
diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
index b78d627b024f..9ddd7c323dfc 100644
--- a/fs/proc/proc_net.c
+++ b/fs/proc/proc_net.c
@@ -115,7 +115,7 @@ struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode,
 	p->seq_ops = ops;
 	p->state_size = state_size;
 	p->flags |= PROC_ENTRY_proc_seq_ops;
-	return proc_register(parent, p);
+	return proc_register(parent, p, NULL);
 }
 EXPORT_SYMBOL_GPL(proc_create_net_data);
 
@@ -162,7 +162,7 @@ struct proc_dir_entry *proc_create_net_data_write(const char *name, umode_t mode
 	p->state_size = state_size;
 	p->flags |= PROC_ENTRY_proc_seq_ops;
 	p->write = write;
-	return proc_register(parent, p);
+	return proc_register(parent, p, NULL);
 }
 EXPORT_SYMBOL_GPL(proc_create_net_data_write);
 
@@ -190,9 +190,10 @@ static const struct proc_ops proc_net_single_ops = {
 	.proc_release	= single_release_net,
 };
 
-struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode,
+struct proc_dir_entry *__proc_create_net_single(const char *name, umode_t mode,
 		struct proc_dir_entry *parent,
-		int (*show)(struct seq_file *, void *), void *data)
+		int (*show)(struct seq_file *, void *), void *data,
+		struct lock_class_key *key)
 {
 	struct proc_dir_entry *p;
 
@@ -203,9 +204,9 @@ struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode,
 	p->proc_ops = &proc_net_single_ops;
 	p->single_show = show;
 	p->flags |= PROC_ENTRY_proc_show;
-	return proc_register(parent, p);
+	return proc_register(parent, p, key);
 }
-EXPORT_SYMBOL_GPL(proc_create_net_single);
+EXPORT_SYMBOL_GPL(__proc_create_net_single);
 
 /**
  * proc_create_net_single_write - Create a writable net_ns-specific proc file
@@ -232,11 +233,12 @@ EXPORT_SYMBOL_GPL(proc_create_net_single);
  * pde_data() on the file inode.  The network namespace must be accessed by
  * calling seq_file_single_net() on the seq_file struct.
  */
-struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mode,
-						    struct proc_dir_entry *parent,
-						    int (*show)(struct seq_file *, void *),
-						    proc_write_t write,
-						    void *data)
+struct proc_dir_entry *__proc_create_net_single_write(const char *name, umode_t mode,
+						      struct proc_dir_entry *parent,
+						      int (*show)(struct seq_file *, void *),
+						      proc_write_t write,
+						      void *data,
+						      struct lock_class_key *key)
 {
 	struct proc_dir_entry *p;
 
@@ -248,9 +250,9 @@ struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mo
 	p->single_show = show;
 	p->flags |= PROC_ENTRY_proc_show;
 	p->write = write;
-	return proc_register(parent, p);
+	return proc_register(parent, p, key);
 }
-EXPORT_SYMBOL_GPL(proc_create_net_single_write);
+EXPORT_SYMBOL_GPL(__proc_create_net_single_write);
 
 static struct net *get_proc_task_net(struct inode *dir)
 {
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 5bf039d20990..feb3228cb5af 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -204,9 +204,20 @@ struct proc_dir_entry *proc_create_seq_private(const char *name, umode_t mode,
 	proc_create_seq_private(name, mode, parent, ops, 0, data)
 #define proc_create_seq(name, mode, parent, ops) \
 	proc_create_seq_private(name, mode, parent, ops, 0, NULL)
-struct proc_dir_entry *proc_create_single_data(const char *name, umode_t mode,
+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);
+		int (*show)(struct seq_file *, void *), void *data,
+		struct lock_class_key *key);
+/*
+ * The entries a registration site creates share nothing that lockdep takes as
+ * the key of their class but the show function, which isn't a static object,
+ * so each site gets a key of its own, like every sysfs attribute has one.
+ */
+#define proc_create_single_data(name, mode, parent, show, data)		\
+({									\
+	static struct lock_class_key __key;				\
+	__proc_create_single_data(name, mode, parent, show, data, &__key); \
+})
 #define proc_create_single(name, mode, parent, show) \
 	proc_create_single_data(name, mode, parent, show, NULL)
  
@@ -238,19 +249,32 @@ struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode,
 		unsigned int state_size, void *data);
 #define proc_create_net(name, mode, parent, ops, state_size) \
 	proc_create_net_data(name, mode, parent, ops, state_size, NULL)
-struct proc_dir_entry *proc_create_net_single(const char *name, umode_t mode,
+struct proc_dir_entry *__proc_create_net_single(const char *name, umode_t mode,
 		struct proc_dir_entry *parent,
-		int (*show)(struct seq_file *, void *), void *data);
+		int (*show)(struct seq_file *, void *), void *data,
+		struct lock_class_key *key);
+#define proc_create_net_single(name, mode, parent, show, data)		\
+({									\
+	static struct lock_class_key __key;				\
+	__proc_create_net_single(name, mode, parent, show, data, &__key); \
+})
 struct proc_dir_entry *proc_create_net_data_write(const char *name, umode_t mode,
 						  struct proc_dir_entry *parent,
 						  const struct seq_operations *ops,
 						  proc_write_t write,
 						  unsigned int state_size, void *data);
-struct proc_dir_entry *proc_create_net_single_write(const char *name, umode_t mode,
-						    struct proc_dir_entry *parent,
-						    int (*show)(struct seq_file *, void *),
-						    proc_write_t write,
-						    void *data);
+struct proc_dir_entry *__proc_create_net_single_write(const char *name, umode_t mode,
+						      struct proc_dir_entry *parent,
+						      int (*show)(struct seq_file *, void *),
+						      proc_write_t write,
+						      void *data,
+						      struct lock_class_key *key);
+#define proc_create_net_single_write(name, mode, parent, show, write, data)	\
+({									\
+	static struct lock_class_key __key;				\
+	__proc_create_net_single_write(name, mode, parent, show, write, data, \
+				       &__key);				\
+})
 extern struct pid *tgid_pidfd_to_pid(const struct file *file);
 
 struct bpf_iter_aux_info;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 22/23] proc: own the wait queue of an entry with a ->proc_poll()
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (20 preceding siblings ...)
  2026-11-04 19:34   ` [PATCH RESEND v2 21/23] proc: let lockdep check the pin Christian Brauner
@ 2026-11-04 19:34   ` Christian Brauner
  2026-11-04 19:34   ` [PATCH RESEND v2 23/23] Documentation: seq_file: describe how a /proc file uses seq_file Christian Brauner
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

An entry with a ->proc_poll() calls poll_wait() on a wait queue of its
own and procfs pins the entry around that. The pin covers the call, not
what it leaves behind: the poller stays queued on the entry's queue
after the call, and so after remove_proc_entry() ran ->proc_release()
on the still open file and the entry's owner freed the queue with the
rest. sunrpc had the queue of its channel files in the per-namespace
cache_detail, which is freed when the namespace goes, so a poll() or
epoll on such a file across the end of the namespace was a
use-after-free, fixed for stable by going back to one static queue. md
works around the same problem at module unload by waking its pollers
in a loop until none is left.

So do what kernfs does with kernfs_open_node: procfs owns the wait
queue. Every entry with a ->proc_poll() gets an open node, which lives
as long as the entry does, and the entry's inode holds the entry, so
the queue outlives every open file and every poll or epoll entry on it.
proc_reg_poll() does the poll_wait() on it before it calls the method,
the owner wakes the pollers with proc_poll_notify(), and procfs wakes
them when the entry is removed, after which a poll of it reports
EPOLLERR | EPOLLPRI, like kernfs_fop_poll() does. The open node tracks
the open files only for the entries whose release hook procfs has to
call at removal, as before. Which methods an entry has procfs keeps in
the entry's flags, and whether its files are tracked comes from there
too: the last close of a file can come after the entry was removed and
its proc_ops freed with their module.

The method still gets the poll_table for now and the entries keep
sleeping on their own queues until they are converted. The last series
takes the poll_table away, so that an entry can't hand procfs a queue
that dies before its files do.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/proc/generic.c       | 38 ++++++++++++++++++++--------------
 fs/proc/inode.c         | 55 ++++++++++++++++++++++++++++++++++++-------------
 fs/proc/internal.h      | 35 ++++++++++++++++++++++++++-----
 include/linux/proc_fs.h | 11 ++++++++++
 4 files changed, 105 insertions(+), 34 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index ca4025ee7aa8..9c073cb867e5 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -466,6 +466,12 @@ 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_poll)
+		pde->flags |= PROC_ENTRY_proc_poll;
+	if (proc_ops->proc_release)
+		pde->flags |= PROC_ENTRY_proc_release;
+	if (proc_ops->proc_mmap)
+		pde->flags |= PROC_ENTRY_proc_mmap;
 	if (proc_ops->proc_write_buf) {
 		if (WARN_ON_ONCE(proc_ops->proc_write))
 			return -EINVAL;
@@ -503,16 +509,22 @@ static int pde_set_flags(struct proc_dir_entry *pde)
 	return 0;
 }
 
-/*
- * procfs has to know the open files of an entry whose ->proc_release() it
- * calls at removal and whose mappings it undoes then. A permanent entry is
- * never removed and skips all that, see proc_reg_open().
- */
-static bool pde_needs_open_node(const struct proc_dir_entry *pde)
+/* The open node, see struct pde_open_node. */
+static int pde_init_open_node(struct proc_dir_entry *pde)
 {
-	if (!S_ISREG(pde->mode) || pde_is_permanent(pde))
-		return false;
-	return pde->proc_ops->proc_release || pde->proc_ops->proc_mmap;
+	struct pde_open_node *on;
+
+	if (!S_ISREG(pde->mode))
+		return 0;
+	if (!pde_has_proc_poll(pde) && !pde_tracks_files(pde))
+		return 0;
+	on = kzalloc(sizeof(*on), GFP_KERNEL);
+	if (!on)
+		return -ENOMEM;
+	INIT_LIST_HEAD(&on->files);
+	init_waitqueue_head(&on->poll);
+	pde->open = on;
+	return 0;
 }
 
 /*
@@ -574,12 +586,8 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
 
 	if (!S_ISDIR(dp->mode) && pde_set_flags(dp))
 		goto out_free_inum;
-	if (pde_needs_open_node(dp)) {
-		dp->open = kzalloc(sizeof(*dp->open), GFP_KERNEL);
-		if (!dp->open)
-			goto out_free_inum;
-		INIT_LIST_HEAD(&dp->open->files);
-	}
+	if (pde_init_open_node(dp))
+		goto out_free_inum;
 	if (pde_init_seq_direct(dp))
 		goto out_free_inum;
 	pde_lockdep_init(dp, key);
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 59de4681d845..ab30a58879d5 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -389,9 +389,15 @@ void proc_entry_rundown(struct proc_dir_entry *de)
 		rwsem_release(&de->dep_map, _RET_IP_);
 	}
 
-	/* ->open->files can't grow from now on. */
 	if (!de->open)
 		return;
+	/* a poll of a removed entry reports EPOLLERR, see proc_reg_poll() */
+	if (pde_has_proc_poll(de))
+		wake_up_interruptible_all(&de->open->poll);
+
+	/* ->open->files can't grow from now on. */
+	if (!pde_tracks_files(de))
+		return;
 	spin_lock(&de->pde_unload_lock);
 	while (!list_empty(&de->open->files)) {
 		struct pde_opener *pdeo;
@@ -555,26 +561,46 @@ static ssize_t proc_reg_write(struct file *file, const char __user *buf, size_t
 	return rv;
 }
 
-static __poll_t pde_poll(struct proc_dir_entry *pde, struct file *file, struct poll_table_struct *pts)
-{
-	const auto poll = pde->proc_ops->proc_poll;
-	if (poll)
-		return poll(file, pts);
-	return DEFAULT_POLLMASK;
-}
-
+/*
+ * The pollers of an entry sleep on a wait queue procfs owns, which is there
+ * for as long as the entry's inode is, however the entry's owner goes away.
+ * The owner wakes them through proc_poll_notify(), and procfs does when the
+ * entry is removed: a poll of a removed entry reports EPOLLERR | EPOLLPRI,
+ * like kernfs_fop_poll().
+ */
 static __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;
 
-	if (!pde_pin(pde))
+	if (!pde_has_proc_poll(pde))
 		return DEFAULT_POLLMASK;
-	rv = pde_poll(pde, file, pts);
+	poll_wait(file, &pde->open->poll, pts);
+	if (!pde_pin(pde))
+		return DEFAULT_POLLMASK | EPOLLERR | EPOLLPRI;
+	rv = pde->proc_ops->proc_poll(file, pts);
 	pde_unpin(pde);
 	return rv;
 }
 
+/**
+ * proc_poll_notify - wake up the pollers of an entry
+ * @pde: an entry with a ->proc_poll(), may be NULL
+ *
+ * May be called from any context, as long as the entry is still registered.
+ * procfs doesn't pin it here, the caller keeps it alive like it does for
+ * every other use of the pointer proc_create() returned.
+ */
+void proc_poll_notify(struct proc_dir_entry *pde)
+{
+	if (!pde)
+		return;
+	if (WARN_ON_ONCE(!pde_has_proc_poll(pde)))
+		return;
+	wake_up_interruptible_all(&pde->open->poll);
+}
+EXPORT_SYMBOL_GPL(proc_poll_notify);
+
 static long pde_ioctl(struct proc_dir_entry *pde, struct file *file, unsigned int cmd, unsigned long arg)
 {
 	const auto ioctl = pde->proc_ops->proc_ioctl;
@@ -878,7 +904,8 @@ static int proc_reg_open(struct inode *inode, struct file *file)
 	if (!use_pde(pde))
 		return -ENOENT;
 
-	if (pde->open) {
+	const bool track = pde_tracks_files(pde);
+	if (track) {
 		pdeo = kmem_cache_alloc(pde_opener_cache, GFP_KERNEL);
 		if (!pdeo) {
 			rv = -ENOMEM;
@@ -888,7 +915,7 @@ static int proc_reg_open(struct inode *inode, struct file *file)
 
 	rv = pde_open(pde, inode, file);
 
-	if (pde->open) {
+	if (track) {
 		if (rv == 0) {
 			/* To know what to release. */
 			pdeo->file = file;
@@ -918,7 +945,7 @@ static int proc_reg_release(struct inode *inode, struct file *file)
 		goto out;
 	}
 
-	if (!pde->open)
+	if (!pde_tracks_files(pde))
 		goto out;
 	spin_lock(&pde->pde_unload_lock);
 	list_for_each_entry(pdeo, &pde->open->files, lh) {
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 7a197e2920f8..d2d731706788 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -15,6 +15,7 @@
 #include <linux/sched/coredump.h>
 #include <linux/sched/task.h>
 #include <linux/mm.h>
+#include <linux/wait.h>
 
 struct ctl_table_header;
 struct mempolicy;
@@ -140,6 +141,26 @@ static inline bool pde_has_proc_ioctl_buf(const struct proc_dir_entry *pde)
 	return pde->flags & PROC_ENTRY_proc_ioctl_buf;
 }
 
+static inline bool pde_has_proc_poll(const struct proc_dir_entry *pde)
+{
+	return pde->flags & PROC_ENTRY_proc_poll;
+}
+
+/*
+ * procfs has to know the open files of an entry whose ->proc_release() it
+ * calls at removal and whose mappings it undoes then. A permanent entry is
+ * never removed and skips all that, see proc_reg_open(). This looks at the
+ * flags and not at the proc_ops: the last close of a file can come after
+ * the entry was removed and the proc_ops freed with their module, see
+ * proc_reg_release().
+ */
+static inline bool pde_tracks_files(const struct proc_dir_entry *pde)
+{
+	if (pde_is_permanent(pde))
+		return false;
+	return pde->flags & (PROC_ENTRY_proc_release | PROC_ENTRY_proc_mmap);
+}
+
 /* procfs owns the seq_file, see pde_seq_start() */
 static inline bool pde_is_seq_file(const struct proc_dir_entry *pde)
 {
@@ -388,15 +409,19 @@ struct pde_opener {
 } __randomize_layout;
 
 /*
- * The open files of an entry whose ->proc_release() procfs has to call and
- * whose mappings it has to undo when the entry is removed while they are
- * still open, that is of an entry with a ->proc_release() or a
- * ->proc_mmap(). Allocated with the entry, see proc_register().
+ * What procfs keeps for the open files of an entry: the files themselves if
+ * it has to call their ->proc_release() and undo their mappings when the
+ * entry is removed, that is for an entry with a ->proc_release() or a
+ * ->proc_mmap(), and the wait queue their pollers sleep on for an entry with
+ * a ->proc_poll(). Allocated with the entry, see proc_register().
  */
 struct pde_open_node {
-	struct list_head files;		/* who did ->open, but not ->release */
+	/* who did ->open, but not ->release, see pde_tracks_files() */
+	struct list_head files;
 	/* of its mappings, the same for all of them, see pde_mmap() */
 	const struct vm_operations_struct *vm_ops;
+	/* what poll() and epoll wait on, see proc_poll_notify() */
+	wait_queue_head_t poll;
 };
 extern const struct inode_operations proc_link_inode_operations;
 extern const struct inode_operations proc_pid_link_inode_operations;
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index feb3228cb5af..8520b18ce1f7 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -47,6 +47,9 @@ enum {
 	 */
 	PROC_ENTRY_FULL_READS		= 1U << 11,
 	PROC_ENTRY_proc_ioctl_buf	= 1U << 12,
+	PROC_ENTRY_proc_poll		= 1U << 13,
+	PROC_ENTRY_proc_release		= 1U << 14,
+	PROC_ENTRY_proc_mmap		= 1U << 15,
 };
 
 /*
@@ -81,6 +84,12 @@ struct proc_ops {
 	 */
 	loff_t	(*proc_lseek)(struct file *, loff_t, int);
 	int	(*proc_release)(struct inode *, struct file *);
+	/*
+	 * procfs owns a wait queue for an entry with a ->proc_poll() and
+	 * does the poll_wait() on it before it calls the method. The owner
+	 * wakes the pollers with proc_poll_notify(). A poll on a removed
+	 * entry reports EPOLLERR | EPOLLPRI.
+	 */
 	__poll_t (*proc_poll)(struct file *, struct poll_table_struct *);
 	long	(*proc_ioctl)(struct file *, unsigned int, unsigned long);
 #ifdef CONFIG_COMPAT
@@ -243,6 +252,7 @@ extern void *proc_get_parent_data(const struct inode *);
 extern void proc_remove(struct proc_dir_entry *);
 extern void remove_proc_entry(const char *, struct proc_dir_entry *);
 extern int remove_proc_subtree(const char *, struct proc_dir_entry *);
+void proc_poll_notify(struct proc_dir_entry *pde);
 
 struct proc_dir_entry *proc_create_net_data(const char *name, umode_t mode,
 		struct proc_dir_entry *parent, const struct seq_operations *ops,
@@ -341,6 +351,7 @@ static inline void *proc_get_parent_data(const struct inode *inode) { BUG(); ret
 static inline void proc_remove(struct proc_dir_entry *de) {}
 #define remove_proc_entry(name, parent) do {} while (0)
 static inline int remove_proc_subtree(const char *name, struct proc_dir_entry *parent) { return 0; }
+static inline void proc_poll_notify(struct proc_dir_entry *pde) {}
 
 #define proc_create_net_data(name, mode, parent, ops, state_size, data) ({NULL;})
 #define proc_create_net_data_write(name, mode, parent, ops, write, state_size, data) ({NULL;})

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 23/23] Documentation: seq_file: describe how a /proc file uses seq_file
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
                     ` (21 preceding siblings ...)
  2026-11-04 19:34   ` [PATCH RESEND v2 22/23] proc: own the wait queue of an entry with a ->proc_poll() Christian Brauner
@ 2026-11-04 19:34   ` Christian Brauner
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:34 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

The 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 Documentation/filesystems/seq_file.rst | 42 ++++++++++++++++++++++++++--------
 1 file changed, 33 insertions(+), 9 deletions(-)

diff --git a/Documentation/filesystems/seq_file.rst b/Documentation/filesystems/seq_file.rst
index d753d8177bcb..3e6ff1c1226d 100644
--- a/Documentation/filesystems/seq_file.rst
+++ b/Documentation/filesystems/seq_file.rst
@@ -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 with the entry pinned, between
+start() and stop() like in its other methods, so nothing that runs there
+may wait for something a reader can hold while it copies to userspace,
+like the seq_file's lock.
+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.

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7)
  2026-11-04 19:04 [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does Christian Brauner
  2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
  2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
@ 2026-11-04 19:35 ` Christian Brauner
  2026-11-04 19:35   ` [PATCH RESEND v2 01/23] alpha: srm_env: let procfs copy in writes Christian Brauner
                     ` (22 more replies)
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                   ` (2 subsequent siblings)
  5 siblings, 23 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:35 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Part 3/7 of "proc: move procfs to what kernfs does", on top of part 2
and independent of parts 4 to 6:

  https://patch.msgid.link/20261104-work-procfs-v2-0-b45cd5789ac5@brauner

This 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. Afterwards the entries' code only runs with the entry
pinned and never touches userspace memory. One patch per subsystem and
method.

- procfs copies in the writes of alpha's srm_env, the ARM and sh
  alignment files, the rtas, pseries, mconsole and exitcode files,
  sparc's led, /proc/mtrr, simdisk, /proc/acpi/wakeup, via-pmu's
  options, thinkpad_acpi, toshiba_acpi and pnpbios. Most of them just
  stop copying in themselves or switch from kstrto*_from_user() to
  kstrto*(). Where a write larger than anything the entry parses now
  fails with -EINVAL from procfs the commit says so.
- procfs copies out the reads of /proc/atags, /proc/bootinfo, the rtas
  error log and flash files, /proc/powerpc/systemcfg, simdisk and
  /proc/bus/isapnp, mostly simple_read_from_buffer() turned into
  memory_read_from_buffer().
- procfs owns the seq_file of /proc/mtrr and copies its ioctl arguments
  in and out. The ioctls are table driven now, the native and the 32-bit
  commands share one table.
- 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, it hands out a record before procfs
  copies it out, so a reader that passes a bad buffer loses it. Its
  pollers sleep on the wait queue procfs owns for the entry now and
  rtasd wakes them with proc_poll_notify().

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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- rtasd lets procfs own the wait queue of the rtas error log
- rebased onto v7.4-rc1, the thinkpad_acpi write merged with its
  cleanup.h restyle upstream
- Link to v1: https://patch.msgid.link/20261102-work-procfs-arch-v1-0-c9492ab08480@kernel.org

---
Christian Brauner (23):
      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/rtasd: let procfs own the wait queue of the rtas error log
      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                 | 30 +++------
 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                     | 12 +---
 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 | 12 +---
 drivers/platform/x86/toshiba_acpi.c         | 36 ++++-------
 drivers/pnp/isapnp/proc.c                   | 20 +++---
 drivers/pnp/pnpbios/proc.c                  |  9 +--
 23 files changed, 198 insertions(+), 276 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261101-work-procfs-arch-8d4ade48be31
prerequisite-change-id: 20261101-work-procfs-core-8fa4a4f412cf:v2
prerequisite-patch-id: a81e8b894107e48d607f8257d2c9f56e0dd6f400
prerequisite-patch-id: 7edf7ca7ecd32c9e09085e4b65922af10097beaa
prerequisite-patch-id: 81f6893f79b4236637675553298a6f2892b603f3
prerequisite-patch-id: 598c2b1a389f47adcac88ebcf1c9f74e2511fbab
prerequisite-patch-id: 6f5a2172dacc5ff2491fa9832c48ccd0e1ce1eda
prerequisite-patch-id: 8947af6490288e4e2a3b2bf8844a974bedb1d975
prerequisite-patch-id: 8cbe756286fd95967d31b0c46cacfeb05f0a2c6b
prerequisite-patch-id: 3fa159f2b6ccd0ac51a2662e4eec5587ec18de89
prerequisite-patch-id: bfb4e4ffe9631cb58015fefe6bd3698cd53e2244
prerequisite-patch-id: 56ff6667a57a962264c07c0227c61aa2f1f4c22c
prerequisite-patch-id: bc506323e29944ec0c29eae84950c706a8592042
prerequisite-patch-id: 326c0ebb5accf0f40cbee1323fb4c3378f5be154
prerequisite-patch-id: d6eaf7690a54a0dfda7500936a048b769fa265dd
prerequisite-patch-id: 7c7b2535642b4d388e13eab518cbe0f9550eb1e6
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: f6d4191e23c22e2d2cc90cc5d04781f913e98a8e
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: 43b92dfc1a98fbf0d14597a3e3ae99972844773b
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: d58c239fba616b366a725de6acba3f88734156d0
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: 5348d8957eb7eae7430ca8b101aa84ce77d3aa4f
prerequisite-patch-id: 36c96d781e2dd80b20932667bbe6e7fd14976069
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: 17f95c7fdf0bdc7c8aaaf7883bff1dcc19a0f529
prerequisite-patch-id: 117db27dcdef433de7c6be5e95973cac1e373927
prerequisite-patch-id: a0844aa26ffb65ccb362b88ef6c2f4758e15edbf
prerequisite-patch-id: 9c64355dfde62e399f2717d9208c4607834d4650
prerequisite-patch-id: 9644739854ddfe2876f17fda8727fd5119255957
prerequisite-patch-id: c61bba24c035f79a2a32a80ace0924ab58e97f46
prerequisite-patch-id: 143fb91bddf33068b6ee11e205818c4c2815e23d
prerequisite-patch-id: 9ce9be2ff05250f92d945c0084f4bb31d3d0b27d
prerequisite-patch-id: aee0961341523547095a43ae298cb8b551302f10
prerequisite-patch-id: 2f0fdb970ddd760d85ff5d2caad0b96348270182
prerequisite-patch-id: f2aa468ff5cbb1cabab3f70e1b8eeee0286cfb4a
prerequisite-patch-id: 5555895bcbca4d7c9888ecc9e82c0e4d76da8f40
prerequisite-patch-id: 487141c49e2548310bfb6c2a0964a78f08b285a9
prerequisite-patch-id: c3d3008819fa5629a18142ec9ba6d7024ade7fdb
prerequisite-patch-id: ca587c976690ac16e3d759fccab2116fef0508e8
prerequisite-patch-id: 9423f410b99853693db06c29f12f1c08278b050d
prerequisite-patch-id: e269815617a6324615a38916b8886f99d98c641e
prerequisite-patch-id: 3d24be1897a624d519ef144801f76d152c7528a9
prerequisite-patch-id: 1ea3fc8319d9012faaf3a182ac61d5da89aafdb7
prerequisite-patch-id: 7547b4e2c1b3588acf6caf90cc01fe7884fdb6bd
prerequisite-patch-id: 695d325349ebeb89bf0ea24bcf0c4f1c4edacab5
prerequisite-patch-id: e33502a202979aba0d6fdaf196e8471d906992a1
prerequisite-patch-id: 47bfe4f07677ad75fe8d9accc8923a1cbdf3afa6
prerequisite-patch-id: 9fc0739226572203fcb36f2c271be7b27f405ef2


^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 01/23] alpha: srm_env: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
@ 2026-11-04 19:35   ` Christian Brauner
  2026-11-04 19:36   ` [PATCH RESEND v2 02/23] ARM: alignment: " Christian Brauner
                     ` (21 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:35 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/alpha/kernel/srm_env.c | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/arch/alpha/kernel/srm_env.c b/arch/alpha/kernel/srm_env.c
index faafc2df7886..6a1742b09a82 100644
--- a/arch/alpha/kernel/srm_env.c
+++ b/arch/alpha/kernel/srm_env.c
@@ -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

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 02/23] ARM: alignment: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
  2026-11-04 19:35   ` [PATCH RESEND v2 01/23] alpha: srm_env: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:36   ` Christian Brauner
  2026-11-04 19:36   ` [PATCH RESEND v2 03/23] ARM: atags: let procfs copy out reads of /proc/atags Christian Brauner
                     ` (20 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:36 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/arm/mm/alignment.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c
index 3e5e30968634..11ceefdfa388 100644
--- a/arch/arm/mm/alignment.c
+++ b/arch/arm/mm/alignment.c
@@ -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 *buffer
 
 static 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 */
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 03/23] ARM: atags: let procfs copy out reads of /proc/atags
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
  2026-11-04 19:35   ` [PATCH RESEND v2 01/23] alpha: srm_env: let procfs copy in writes Christian Brauner
  2026-11-04 19:36   ` [PATCH RESEND v2 02/23] ARM: alignment: " Christian Brauner
@ 2026-11-04 19:36   ` Christian Brauner
  2026-11-04 19:36   ` [PATCH RESEND v2 04/23] m68k/bootinfo: let procfs copy out reads of /proc/bootinfo Christian Brauner
                     ` (19 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:36 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read to userspace
after it unpinned the entry.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/arm/kernel/atags_proc.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm/kernel/atags_proc.c b/arch/arm/kernel/atags_proc.c
index 8411fbfbcb8b..ff28bf24dd30 100644
--- a/arch/arm/kernel/atags_proc.c
+++ b/arch/arm/kernel/atags_proc.c
@@ -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,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 04/23] m68k/bootinfo: let procfs copy out reads of /proc/bootinfo
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (2 preceding siblings ...)
  2026-11-04 19:36   ` [PATCH RESEND v2 03/23] ARM: atags: let procfs copy out reads of /proc/atags Christian Brauner
@ 2026-11-04 19:36   ` Christian Brauner
  2026-11-04 19:36   ` [PATCH RESEND v2 05/23] powerpc/rtas: let procfs copy in writes Christian Brauner
                     ` (18 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:36 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read to userspace
after it unpinned the entry.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/m68k/kernel/bootinfo_proc.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/m68k/kernel/bootinfo_proc.c b/arch/m68k/kernel/bootinfo_proc.c
index 857fa2aaa748..4936cf44e86a 100644
--- a/arch/m68k/kernel/bootinfo_proc.c
+++ b/arch/m68k/kernel/bootinfo_proc.c
@@ -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,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 05/23] powerpc/rtas: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (3 preceding siblings ...)
  2026-11-04 19:36   ` [PATCH RESEND v2 04/23] m68k/bootinfo: let procfs copy out reads of /proc/bootinfo Christian Brauner
@ 2026-11-04 19:36   ` Christian Brauner
  2026-11-04 19:37   ` [PATCH RESEND v2 06/23] powerpc/rtas: let procfs copy out reads Christian Brauner
                     ` (17 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:36 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/rtas-proc.c  | 40 ++++++++++++++++++----------------------
 arch/powerpc/kernel/rtas_flash.c | 38 +++++++++++++++++---------------------
 2 files changed, 35 insertions(+), 43 deletions(-)

diff --git a/arch/powerpc/kernel/rtas-proc.c b/arch/powerpc/kernel/rtas-proc.c
index 808023f9d1f6..75d64a410209 100644
--- a/arch/powerpc/kernel/rtas-proc.c
+++ b/arch/powerpc/kernel/rtas-proc.c
@@ -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,16 +267,14 @@ 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)
 {
 	size_t len = count;
 	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 */
@@ -302,7 +298,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;
@@ -661,7 +657,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);
@@ -686,7 +682,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);
diff --git a/arch/powerpc/kernel/rtas_flash.c b/arch/powerpc/kernel/rtas_flash.c
index f1cd105f7426..77a4627f2507 100644
--- a/arch/powerpc/kernel/rtas_flash.c
+++ b/arch/powerpc/kernel/rtas_flash.c
@@ -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,
 	}
 };

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 06/23] powerpc/rtas: let procfs copy out reads
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (4 preceding siblings ...)
  2026-11-04 19:36   ` [PATCH RESEND v2 05/23] powerpc/rtas: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:37   ` Christian Brauner
  2026-11-04 19:37   ` [PATCH RESEND v2 07/23] powerpc/rtasd: let procfs own the wait queue of the rtas error log Christian Brauner
                     ` (16 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:37 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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. A blocking read still waits for a record with the
entry pinned, as it did under use_pde() before; the entry is never
removed, so nothing waits for it. 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/rtas_flash.c | 24 ++++++++++++------------
 arch/powerpc/kernel/rtasd.c      | 19 +++++--------------
 2 files changed, 17 insertions(+), 26 deletions(-)

diff --git a/arch/powerpc/kernel/rtas_flash.c b/arch/powerpc/kernel/rtas_flash.c
index 77a4627f2507..5ebaee7683ef 100644
--- a/arch/powerpc/kernel/rtas_flash.c
+++ b/arch/powerpc/kernel/rtas_flash.c
@@ -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,
 	}
diff --git a/arch/powerpc/kernel/rtasd.c b/arch/powerpc/kernel/rtasd.c
index dd0df5e164a3..360ed2e30fd5 100644
--- a/arch/powerpc/kernel/rtasd.c
+++ b/arch/powerpc/kernel/rtasd.c
@@ -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,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 07/23] powerpc/rtasd: let procfs own the wait queue of the rtas error log
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (5 preceding siblings ...)
  2026-11-04 19:37   ` [PATCH RESEND v2 06/23] powerpc/rtas: let procfs copy out reads Christian Brauner
@ 2026-11-04 19:37   ` Christian Brauner
  2026-11-04 19:37   ` [PATCH RESEND v2 08/23] powerpc/pseries: let procfs copy in writes Christian Brauner
                     ` (15 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:37 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

rtas_log_poll() sleeps the poller on rtas_log_wait, which the blocking
read sleeps on as well. procfs owns the queue poll() uses now, so leave
the poll_wait() to it and wake its pollers along with the readers.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/rtasd.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/arch/powerpc/kernel/rtasd.c b/arch/powerpc/kernel/rtasd.c
index 360ed2e30fd5..fa9574131f40 100644
--- a/arch/powerpc/kernel/rtasd.c
+++ b/arch/powerpc/kernel/rtasd.c
@@ -33,6 +33,7 @@
 static DEFINE_SPINLOCK(rtasd_log_lock);
 
 static DECLARE_WAIT_QUEUE_HEAD(rtas_log_wait);
+static struct proc_dir_entry *rtas_log_entry;
 
 static char *rtas_log_buf;
 static unsigned long rtas_log_start;
@@ -259,6 +260,7 @@ void pSeries_log_error(char *buf, unsigned int err_type, int fatal)
 		WARN_ON_ONCE(!irqs_disabled()); /* @@@ DEBUG @@@ */
 		spin_unlock_irqrestore(&rtasd_log_lock, s);
 		wake_up_interruptible(&rtas_log_wait);
+		proc_poll_notify(rtas_log_entry);
 		break;
 	case ERR_TYPE_KERNEL_PANIC:
 	default:
@@ -343,7 +345,6 @@ out:
 
 static __poll_t rtas_log_poll(struct file *file, poll_table * wait)
 {
-	poll_wait(file, &rtas_log_wait, wait);
 	if (rtas_log_size)
 		return EPOLLIN | EPOLLRDNORM;
 	return 0;
@@ -529,17 +530,15 @@ arch_initcall(rtas_event_scan_init);
 
 static int __init rtas_init(void)
 {
-	struct proc_dir_entry *entry;
-
 	if (!machine_is(pseries) && !machine_is(chrp))
 		return 0;
 
 	if (!rtas_log_buf)
 		return -ENODEV;
 
-	entry = proc_create("powerpc/rtas/error_log", 0400, NULL,
-			    &rtas_log_proc_ops);
-	if (!entry)
+	rtas_log_entry = proc_create("powerpc/rtas/error_log", 0400, NULL,
+				     &rtas_log_proc_ops);
+	if (!rtas_log_entry)
 		printk(KERN_ERR "Failed to create error_log proc entry\n");
 
 	return 0;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 08/23] powerpc/pseries: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (6 preceding siblings ...)
  2026-11-04 19:37   ` [PATCH RESEND v2 07/23] powerpc/rtasd: let procfs own the wait queue of the rtas error log Christian Brauner
@ 2026-11-04 19:37   ` Christian Brauner
  2026-11-04 19:37   ` [PATCH RESEND v2 09/23] powerpc/procfs: let procfs copy out reads of /proc/powerpc/systemcfg Christian Brauner
                     ` (14 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:37 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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(-)

diff --git a/arch/powerpc/platforms/pseries/lpar.c b/arch/powerpc/platforms/pseries/lpar.c
index 48fe0be71a89..dfd027cc9047 100644
--- a/arch/powerpc/platforms/pseries/lpar.c
+++ b/arch/powerpc/platforms/pseries/lpar.c
@@ -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)
diff --git a/arch/powerpc/platforms/pseries/lparcfg.c b/arch/powerpc/platforms/pseries/lparcfg.c
index cb3d96efb225..bda3cac1552b 100644
--- a/arch/powerpc/platforms/pseries/lparcfg.c
+++ b/arch/powerpc/platforms/pseries/lparcfg.c
@@ -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] = '\0';
 	tmp = strchr(kbuf, '=');
 	if (!tmp)
@@ -798,7 +797,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,
 };
 
diff --git a/arch/powerpc/platforms/pseries/reconfig.c b/arch/powerpc/platforms/pseries/reconfig.c
index b543afbc1efb..d74a0710ec74 100644
--- a/arch/powerpc/platforms/pseries/reconfig.c
+++ b/arch/powerpc/platforms/pseries/reconfig.c
@@ -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,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 09/23] powerpc/procfs: let procfs copy out reads of /proc/powerpc/systemcfg
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (7 preceding siblings ...)
  2026-11-04 19:37   ` [PATCH RESEND v2 08/23] powerpc/pseries: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:37   ` Christian Brauner
  2026-11-04 19:38   ` [PATCH RESEND v2 10/23] sh: alignment: let procfs copy in writes Christian Brauner
                     ` (13 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:37 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read to userspace
after it unpinned the entry.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/powerpc/kernel/proc_powerpc.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/powerpc/kernel/proc_powerpc.c b/arch/powerpc/kernel/proc_powerpc.c
index d083b4517065..46bc914d74b9 100644
--- a/arch/powerpc/kernel/proc_powerpc.c
+++ b/arch/powerpc/kernel/proc_powerpc.c
@@ -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,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 10/23] sh: alignment: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (8 preceding siblings ...)
  2026-11-04 19:37   ` [PATCH RESEND v2 09/23] powerpc/procfs: let procfs copy out reads of /proc/powerpc/systemcfg Christian Brauner
@ 2026-11-04 19:38   ` Christian Brauner
  2026-11-04 19:38   ` [PATCH RESEND v2 11/23] sparc: led: " Christian Brauner
                     ` (12 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:38 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/sh/mm/alignment.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/arch/sh/mm/alignment.c b/arch/sh/mm/alignment.c
index 2198c6f2ea63..d832249a8e18 100644
--- a/arch/sh/mm/alignment.c
+++ b/arch/sh/mm/alignment.c
@@ -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,
 };
 
 /*

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 11/23] sparc: led: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (9 preceding siblings ...)
  2026-11-04 19:38   ` [PATCH RESEND v2 10/23] sh: alignment: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:38   ` Christian Brauner
  2026-11-04 19:38   ` [PATCH RESEND v2 12/23] um: " Christian Brauner
                     ` (11 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:38 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/sparc/kernel/led.c | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/arch/sparc/kernel/led.c b/arch/sparc/kernel/led.c
index 0f7376b51a04..1f244e394207 100644
--- a/arch/sparc/kernel/led.c
+++ b/arch/sparc/kernel/led.c
@@ -60,18 +60,14 @@ 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)
 {
 	size_t len = count;
-	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')
@@ -96,14 +92,12 @@ static ssize_t led_proc_write(struct file *file, const char __user *buffer,
 		auxio_set_led(AUXIO_LED_OFF);
 	}
 
-	kfree(buf);
-
 	return len;
 }
 
 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
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 12/23] um: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (10 preceding siblings ...)
  2026-11-04 19:38   ` [PATCH RESEND v2 11/23] sparc: led: " Christian Brauner
@ 2026-11-04 19:38   ` Christian Brauner
  2026-11-04 19:38   ` [PATCH RESEND v2 13/23] x86/mtrr: let procfs own the seq_file of /proc/mtrr Christian Brauner
                     ` (10 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:38 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/um/drivers/mconsole_kern.c | 11 ++---------
 arch/um/kernel/exitcode.c       | 14 ++++----------
 2 files changed, 6 insertions(+), 19 deletions(-)

diff --git a/arch/um/drivers/mconsole_kern.c b/arch/um/drivers/mconsole_kern.c
index f01b7d8a07bc..a4370470286f 100644
--- a/arch/um/drivers/mconsole_kern.c
+++ b/arch/um/drivers/mconsole_kern.c
@@ -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,
 };
 
diff --git a/arch/um/kernel/exitcode.c b/arch/um/kernel/exitcode.c
index c71657df38f5..8f76e3b20c18 100644
--- a/arch/um/kernel/exitcode.c
+++ b/arch/um/kernel/exitcode.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 13/23] x86/mtrr: let procfs own the seq_file of /proc/mtrr
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (11 preceding siblings ...)
  2026-11-04 19:38   ` [PATCH RESEND v2 12/23] um: " Christian Brauner
@ 2026-11-04 19:38   ` Christian Brauner
  2026-11-04 19:39   ` [PATCH RESEND v2 14/23] x86/mtrr: let procfs copy in writes to /proc/mtrr Christian Brauner
                     ` (9 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:38 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare 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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/x86/kernel/cpu/mtrr/if.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kernel/cpu/mtrr/if.c b/arch/x86/kernel/cpu/mtrr/if.c
index 4049235b1bfe..9510e01d5103 100644
--- a/arch/x86/kernel/cpu/mtrr/if.c
+++ b/arch/x86/kernel/cpu/mtrr/if.c
@@ -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

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 14/23] x86/mtrr: let procfs copy in writes to /proc/mtrr
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (12 preceding siblings ...)
  2026-11-04 19:38   ` [PATCH RESEND v2 13/23] x86/mtrr: let procfs own the seq_file of /proc/mtrr Christian Brauner
@ 2026-11-04 19:39   ` Christian Brauner
  2026-11-04 19:39   ` [PATCH RESEND v2 15/23] x86/mtrr: let procfs copy the ioctl arguments of /proc/mtrr Christian Brauner
                     ` (8 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:39 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/x86/kernel/cpu/mtrr/if.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/arch/x86/kernel/cpu/mtrr/if.c b/arch/x86/kernel/cpu/mtrr/if.c
index 9510e01d5103..038e2dc38797 100644
--- a/arch/x86/kernel/cpu/mtrr/if.c
+++ b/arch/x86/kernel/cpu/mtrr/if.c
@@ -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,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 15/23] x86/mtrr: let procfs copy the ioctl arguments of /proc/mtrr
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (13 preceding siblings ...)
  2026-11-04 19:39   ` [PATCH RESEND v2 14/23] x86/mtrr: let procfs copy in writes to /proc/mtrr Christian Brauner
@ 2026-11-04 19:39   ` Christian Brauner
  2026-11-04 19:39   ` [PATCH RESEND v2 16/23] xtensa: simdisk: let procfs copy in writes Christian Brauner
                     ` (7 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:39 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/x86/kernel/cpu/mtrr/if.c | 79 +++++++++++++++++++++++++------------------
 1 file changed, 46 insertions(+), 33 deletions(-)

diff --git a/arch/x86/kernel/cpu/mtrr/if.c b/arch/x86/kernel/cpu/mtrr/if.c
index 038e2dc38797..9c33c0428dfe 100644
--- a/arch/x86/kernel/cpu/mtrr/if.c
+++ b/arch/x86/kernel/cpu/mtrr/if.c
@@ -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,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 16/23] xtensa: simdisk: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (14 preceding siblings ...)
  2026-11-04 19:39   ` [PATCH RESEND v2 15/23] x86/mtrr: let procfs copy the ioctl arguments of /proc/mtrr Christian Brauner
@ 2026-11-04 19:39   ` Christian Brauner
  2026-11-04 19:39   ` [PATCH RESEND v2 17/23] xtensa: simdisk: let procfs copy out reads Christian Brauner
                     ` (6 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:39 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/xtensa/platforms/iss/simdisk.c | 26 ++++++++++----------------
 1 file changed, 10 insertions(+), 16 deletions(-)

diff --git a/arch/xtensa/platforms/iss/simdisk.c b/arch/xtensa/platforms/iss/simdisk.c
index 1248d960e5d3..26c2fa39a66e 100644
--- a/arch/xtensa/platforms/iss/simdisk.c
+++ b/arch/xtensa/platforms/iss/simdisk.c
@@ -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,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 17/23] xtensa: simdisk: let procfs copy out reads
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (15 preceding siblings ...)
  2026-11-04 19:39   ` [PATCH RESEND v2 16/23] xtensa: simdisk: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:39   ` Christian Brauner
  2026-11-04 19:40   ` [PATCH RESEND v2 18/23] ACPI: proc: let procfs copy in writes to /proc/acpi/wakeup Christian Brauner
                     ` (5 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:39 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read to userspace
after it unpinned the entry.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 arch/xtensa/platforms/iss/simdisk.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/xtensa/platforms/iss/simdisk.c b/arch/xtensa/platforms/iss/simdisk.c
index 26c2fa39a66e..cfddfcd06be7 100644
--- a/arch/xtensa/platforms/iss/simdisk.c
+++ b/arch/xtensa/platforms/iss/simdisk.c
@@ -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,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 18/23] ACPI: proc: let procfs copy in writes to /proc/acpi/wakeup
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (16 preceding siblings ...)
  2026-11-04 19:39   ` [PATCH RESEND v2 17/23] xtensa: simdisk: let procfs copy out reads Christian Brauner
@ 2026-11-04 19:40   ` Christian Brauner
  2026-11-04 19:40   ` [PATCH RESEND v2 19/23] macintosh/via-pmu: let procfs copy in writes to the options file Christian Brauner
                     ` (4 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:40 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/acpi/proc.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/acpi/proc.c b/drivers/acpi/proc.c
index b8a0bd4580e0..feb8283fd855 100644
--- a/drivers/acpi/proc.c
+++ b/drivers/acpi/proc.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 19/23] macintosh/via-pmu: let procfs copy in writes to the options file
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (17 preceding siblings ...)
  2026-11-04 19:40   ` [PATCH RESEND v2 18/23] ACPI: proc: let procfs copy in writes to /proc/acpi/wakeup Christian Brauner
@ 2026-11-04 19:40   ` Christian Brauner
  2026-11-04 19:40   ` [PATCH RESEND v2 20/23] platform/x86: thinkpad_acpi: let procfs copy in writes Christian Brauner
                     ` (3 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:40 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/macintosh/via-pmu.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c
index 49f929ae10e4..079c085be5d2 100644
--- a/drivers/macintosh/via-pmu.c
+++ b/drivers/macintosh/via-pmu.c
@@ -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
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 20/23] platform/x86: thinkpad_acpi: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (18 preceding siblings ...)
  2026-11-04 19:40   ` [PATCH RESEND v2 19/23] macintosh/via-pmu: let procfs copy in writes to the options file Christian Brauner
@ 2026-11-04 19:40   ` Christian Brauner
  2026-11-04 19:40   ` [PATCH RESEND v2 21/23] platform/x86: toshiba_acpi: " Christian Brauner
                     ` (2 subsequent siblings)
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:40 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/platform/x86/lenovo/thinkpad_acpi.c | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
index 2ee208c82801..8287b36bd90d 100644
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -882,7 +882,7 @@ 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));
@@ -890,14 +890,8 @@ static ssize_t dispatch_proc_write(struct file *file,
 
 	if (!ibm || !ibm->write)
 		return -EINVAL;
-	if (count > PAGE_SIZE - 1)
-		return -EINVAL;
-
-	char *kernbuf __free(kfree) = memdup_user_nul(userbuf, count);
-	if (IS_ERR(kernbuf))
-		return PTR_ERR(kernbuf);
 
-	ret = ibm->write(kernbuf);
+	ret = ibm->write(buf);
 	if (ret)
 		return ret;
 
@@ -906,7 +900,7 @@ static ssize_t dispatch_proc_write(struct file *file,
 
 static const struct proc_ops dispatch_proc_ops = {
 	.proc_show	= dispatch_proc_show,
-	.proc_write	= dispatch_proc_write,
+	.proc_write_buf	= dispatch_proc_write,
 };
 
 /****************************************************************************

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 21/23] platform/x86: toshiba_acpi: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (19 preceding siblings ...)
  2026-11-04 19:40   ` [PATCH RESEND v2 20/23] platform/x86: thinkpad_acpi: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:40   ` Christian Brauner
  2026-11-04 19:41   ` [PATCH RESEND v2 22/23] PNP: pnpbios: " Christian Brauner
  2026-11-04 19:41   ` [PATCH RESEND v2 23/23] PNP: isapnp: let procfs copy out reads Christian Brauner
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:40 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/platform/x86/toshiba_acpi.c | 36 ++++++++++++------------------------
 1 file changed, 12 insertions(+), 24 deletions(-)

diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
index 560e62da05a9..895e321ada03 100644
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 22/23] PNP: pnpbios: let procfs copy in writes
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (20 preceding siblings ...)
  2026-11-04 19:40   ` [PATCH RESEND v2 21/23] platform/x86: toshiba_acpi: " Christian Brauner
@ 2026-11-04 19:41   ` Christian Brauner
  2026-11-04 19:41   ` [PATCH RESEND v2 23/23] PNP: isapnp: let procfs copy out reads Christian Brauner
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:41 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pnp/pnpbios/proc.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/drivers/pnp/pnpbios/proc.c b/drivers/pnp/pnpbios/proc.c
index 14632a8d0da0..00163aedd738 100644
--- a/drivers/pnp/pnpbios/proc.c
+++ b/drivers/pnp/pnpbios/proc.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 23/23] PNP: isapnp: let procfs copy out reads
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
                     ` (21 preceding siblings ...)
  2026-11-04 19:41   ` [PATCH RESEND v2 22/23] PNP: pnpbios: " Christian Brauner
@ 2026-11-04 19:41   ` Christian Brauner
  22 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:41 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read from
/proc/bus/isapnp/<card>/<dev> to userspace after it unpinned the entry.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pnp/isapnp/proc.c | 20 +++++++-------------
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/drivers/pnp/isapnp/proc.c b/drivers/pnp/isapnp/proc.c
index 1829c20d5446..2634db5f41e2 100644
--- a/drivers/pnp/isapnp/proc.c
+++ b/drivers/pnp/isapnp/proc.c
@@ -19,40 +19,34 @@ 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;
 	int ret;
 
 	if (pos >= size)
 		return 0;
 	if (nbytes > size - pos)
 		nbytes = size - pos;
-	cnt = nbytes;
-
-	if (!access_ok(buf, cnt))
-		return -EINVAL;
 
 	ret = isapnp_cfg_begin(dev->card->number, dev->number);
 	if (ret)
 		return ret;
-	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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7)
  2026-11-04 19:04 [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does Christian Brauner
                   ` (2 preceding siblings ...)
  2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
@ 2026-11-04 19:42 ` Christian Brauner
  2026-11-04 19:42   ` [PATCH RESEND v2 01/26] nubus: let procfs own the seq_file of the resource files Christian Brauner
                     ` (25 more replies)
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
  5 siblings, 26 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:42 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, Jiri Slaby

Part 4/7 of "proc: move procfs to what kernfs does", on top of part 2
and independent of parts 3, 5 and 6:

  https://patch.msgid.link/20261104-work-procfs-v2-0-b45cd5789ac5@brauner

This converts the proc entries of nubus, zorro, PCI, md, dasd and cio,
scsi, dell-smm, viafb, rndis, ipw2x00, sysrq, the input devices file and
ALSA to what "proc: let procfs own the data path" set up. Afterwards the
entries' code only runs with the entry pinned and never touches
userspace memory. One patch per subsystem and method.

- procfs owns the seq_file of the nubus resource files, /proc/mdstat,
  the scsi host files, sg's allow_dio and def_reserved_size and ALSA's
  text entries. Their open and release hooks only do what is left,
  private data or the event count, and reading a scsi host file or an
  ALSA entry no longer holds up the removal of the host or the card.
- procfs copies in the writes of the PCI config space,
  /proc/dasd/statistics, cio_ignore and cio_settle, /proc/scsi/scsi,
  device_info and the host files, sg, viafb, rndis, ipw2x00's debug
  level, /proc/sysrq-trigger and ALSA's entries. Most of them just parse
  procfs' copy. Where a write larger than anything the entry parses now
  fails with -EINVAL from procfs the commit says so.
- procfs copies out the reads of /proc/bus/zorro, the PCI config space
  and ALSA's data entries.
- 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.
- procfs copies the ioctl arguments of PCI, esas2r's ATTONode file and
  dell-smm's i8k in and out. The PCI commands only pass values and the
  i8k ones encode the wrong sizes, both tables list what is really
  copied. ALSA's ioctl hook that no driver implements goes away.
- /proc/mdstat and /proc/bus/input/devices move their pollers to the
  wait queue procfs owns for the entry, which ends md's wakeup loop at
  unload, and the poll op of an ALSA info entry, which nothing
  implements, loses its poll_table.

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.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- md and Input let procfs own the wait queues of /proc/mdstat and
  /proc/bus/input/devices, md's wakeup loop at unload is gone and
  /proc/mdstat is removed last, Input forgets the entry when it is
  removed, and the poll op of an ALSA info entry loses its poll_table
- the scsi_debug selftest skips without /proc/scsi and removes the host
  it added when its file can't be found
- Reviewed-by from Jiri on sysrq
- rebased onto v7.4-rc1
- Link to v1: https://patch.msgid.link/20261102-work-procfs-drivers-v1-0-a994bd4d3b1a@kernel.org

---
Christian Brauner (26):
      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
      md: let procfs own the wait queue 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
      Input: let procfs own the wait queue of /proc/bus/input/devices
      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
      ALSA: info: drop the poll_table from the info entry poll op

 .../sound/kernel-api/writing-an-alsa-driver.rst    |  10 +-
 drivers/hwmon/dell-smm-hwmon.c                     |  48 ++---
 drivers/input/input.c                              |  13 +-
 drivers/md/md.c                                    |  44 ++---
 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                               |   9 +-
 sound/core/info.c                                  |  59 +++---
 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       | 209 +++++++++++++++++++++
 35 files changed, 503 insertions(+), 415 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261101-work-procfs-drivers-58cd83649658
prerequisite-change-id: 20261101-work-procfs-core-8fa4a4f412cf:v2
prerequisite-patch-id: a81e8b894107e48d607f8257d2c9f56e0dd6f400
prerequisite-patch-id: 7edf7ca7ecd32c9e09085e4b65922af10097beaa
prerequisite-patch-id: 81f6893f79b4236637675553298a6f2892b603f3
prerequisite-patch-id: 598c2b1a389f47adcac88ebcf1c9f74e2511fbab
prerequisite-patch-id: 6f5a2172dacc5ff2491fa9832c48ccd0e1ce1eda
prerequisite-patch-id: 8947af6490288e4e2a3b2bf8844a974bedb1d975
prerequisite-patch-id: 8cbe756286fd95967d31b0c46cacfeb05f0a2c6b
prerequisite-patch-id: 3fa159f2b6ccd0ac51a2662e4eec5587ec18de89
prerequisite-patch-id: bfb4e4ffe9631cb58015fefe6bd3698cd53e2244
prerequisite-patch-id: 56ff6667a57a962264c07c0227c61aa2f1f4c22c
prerequisite-patch-id: bc506323e29944ec0c29eae84950c706a8592042
prerequisite-patch-id: 326c0ebb5accf0f40cbee1323fb4c3378f5be154
prerequisite-patch-id: d6eaf7690a54a0dfda7500936a048b769fa265dd
prerequisite-patch-id: 7c7b2535642b4d388e13eab518cbe0f9550eb1e6
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: f6d4191e23c22e2d2cc90cc5d04781f913e98a8e
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: 43b92dfc1a98fbf0d14597a3e3ae99972844773b
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: d58c239fba616b366a725de6acba3f88734156d0
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: 5348d8957eb7eae7430ca8b101aa84ce77d3aa4f
prerequisite-patch-id: 36c96d781e2dd80b20932667bbe6e7fd14976069
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: 17f95c7fdf0bdc7c8aaaf7883bff1dcc19a0f529
prerequisite-patch-id: 117db27dcdef433de7c6be5e95973cac1e373927
prerequisite-patch-id: a0844aa26ffb65ccb362b88ef6c2f4758e15edbf
prerequisite-patch-id: 9c64355dfde62e399f2717d9208c4607834d4650
prerequisite-patch-id: 9644739854ddfe2876f17fda8727fd5119255957
prerequisite-patch-id: c61bba24c035f79a2a32a80ace0924ab58e97f46
prerequisite-patch-id: 143fb91bddf33068b6ee11e205818c4c2815e23d
prerequisite-patch-id: 9ce9be2ff05250f92d945c0084f4bb31d3d0b27d
prerequisite-patch-id: aee0961341523547095a43ae298cb8b551302f10
prerequisite-patch-id: 2f0fdb970ddd760d85ff5d2caad0b96348270182
prerequisite-patch-id: f2aa468ff5cbb1cabab3f70e1b8eeee0286cfb4a
prerequisite-patch-id: 5555895bcbca4d7c9888ecc9e82c0e4d76da8f40
prerequisite-patch-id: 487141c49e2548310bfb6c2a0964a78f08b285a9
prerequisite-patch-id: c3d3008819fa5629a18142ec9ba6d7024ade7fdb
prerequisite-patch-id: ca587c976690ac16e3d759fccab2116fef0508e8
prerequisite-patch-id: 9423f410b99853693db06c29f12f1c08278b050d
prerequisite-patch-id: e269815617a6324615a38916b8886f99d98c641e
prerequisite-patch-id: 3d24be1897a624d519ef144801f76d152c7528a9
prerequisite-patch-id: 1ea3fc8319d9012faaf3a182ac61d5da89aafdb7
prerequisite-patch-id: 7547b4e2c1b3588acf6caf90cc01fe7884fdb6bd
prerequisite-patch-id: 695d325349ebeb89bf0ea24bcf0c4f1c4edacab5
prerequisite-patch-id: e33502a202979aba0d6fdaf196e8471d906992a1
prerequisite-patch-id: 47bfe4f07677ad75fe8d9accc8923a1cbdf3afa6
prerequisite-patch-id: 9fc0739226572203fcb36f2c271be7b27f405ef2


^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 01/26] nubus: let procfs own the seq_file of the resource files
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
@ 2026-11-04 19:42   ` Christian Brauner
  2026-11-04 19:42   ` [PATCH RESEND v2 02/26] zorro: let procfs copy out reads Christian Brauner
                     ` (24 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:42 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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) <brauner@kernel.org>
---
 drivers/nubus/proc.c | 12 ++----------
 1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/drivers/nubus/proc.c b/drivers/nubus/proc.c
index 3ea3d1738eae..cb21ad1d9964 100644
--- a/drivers/nubus/proc.c
+++ b/drivers/nubus/proc.c
@@ -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,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 02/26] zorro: let procfs copy out reads
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
  2026-11-04 19:42   ` [PATCH RESEND v2 01/26] nubus: let procfs own the seq_file of the resource files Christian Brauner
@ 2026-11-04 19:42   ` Christian Brauner
  2026-11-04 19:42   ` [PATCH RESEND v2 03/26] PCI/proc: let procfs copy in config space writes Christian Brauner
                     ` (23 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:42 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

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) <brauner@kernel.org>
---
 drivers/zorro/proc.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/zorro/proc.c b/drivers/zorro/proc.c
index 4c6f8f1c4d71..99a8b478192d 100644
--- a/drivers/zorro/proc.c
+++ b/drivers/zorro/proc.c
@@ -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)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 03/26] PCI/proc: let procfs copy in config space writes
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
  2026-11-04 19:42   ` [PATCH RESEND v2 01/26] nubus: let procfs own the seq_file of the resource files Christian Brauner
  2026-11-04 19:42   ` [PATCH RESEND v2 02/26] zorro: let procfs copy out reads Christian Brauner
@ 2026-11-04 19:42   ` Christian Brauner
  2026-11-04 19:43   ` [PATCH RESEND v2 04/26] PCI/proc: let procfs copy out config space reads Christian Brauner
                     ` (22 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:42 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and write the config space from that copy with
get_unaligned_le16() and get_unaligned_le32() instead of get_user().

The copy holds at most PCI_CFG_SPACE_EXP_SIZE bytes. Larger writes come
up short with PROC_ENTRY_SHORT_WRITES, like they did before because
they were cut at the end of the config space.

A buffer that can't be read used to write zeroes to the config space and
report success. It fails with -EFAULT now and nothing is written.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pci/proc.c | 32 +++++++++++---------------------
 1 file changed, 11 insertions(+), 21 deletions(-)

diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c
index e0dc70123f82..d420353ee2b3 100644
--- a/drivers/pci/proc.c
+++ b/drivers/pci/proc.c
@@ -16,6 +16,7 @@
 #include <linux/security.h>
 #include <linux/panic.h>
 #include <linux/sched.h>
+#include <linux/unaligned.h>
 #include <asm/byteorder.h>
 #include "pci.h"
 
@@ -114,8 +115,8 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,
 	return nbytes;
 }
 
-static ssize_t proc_bus_pci_write(struct file *file, const char __user *buf,
-				  size_t nbytes, loff_t *ppos)
+static ssize_t proc_bus_pci_write(struct file *file, char *buf, size_t nbytes,
+				  loff_t *ppos)
 {
 	struct inode *ino = file_inode(file);
 	struct pci_dev *dev = pde_data(ino);
@@ -145,51 +146,38 @@ static ssize_t proc_bus_pci_write(struct file *file, const char __user *buf,
 		nbytes = size - pos;
 	cnt = nbytes;
 
-	if (!access_ok(buf, cnt))
-		return -EINVAL;
-
 	pci_config_pm_runtime_get(dev);
 
 	if ((pos & 1) && cnt) {
-		unsigned char val;
-		get_user(val, buf);
-		pci_user_write_config_byte(dev, pos, val);
+		pci_user_write_config_byte(dev, pos, *buf);
 		buf++;
 		pos++;
 		cnt--;
 	}
 
 	if ((pos & 3) && cnt > 2) {
-		__le16 val;
-		get_user(val, (__le16 __user *) buf);
-		pci_user_write_config_word(dev, pos, le16_to_cpu(val));
+		pci_user_write_config_word(dev, pos, get_unaligned_le16(buf));
 		buf += 2;
 		pos += 2;
 		cnt -= 2;
 	}
 
 	while (cnt >= 4) {
-		__le32 val;
-		get_user(val, (__le32 __user *) buf);
-		pci_user_write_config_dword(dev, pos, le32_to_cpu(val));
+		pci_user_write_config_dword(dev, pos, get_unaligned_le32(buf));
 		buf += 4;
 		pos += 4;
 		cnt -= 4;
 	}
 
 	if (cnt >= 2) {
-		__le16 val;
-		get_user(val, (__le16 __user *) buf);
-		pci_user_write_config_word(dev, pos, le16_to_cpu(val));
+		pci_user_write_config_word(dev, pos, get_unaligned_le16(buf));
 		buf += 2;
 		pos += 2;
 		cnt -= 2;
 	}
 
 	if (cnt) {
-		unsigned char val;
-		get_user(val, buf);
-		pci_user_write_config_byte(dev, pos, val);
+		pci_user_write_config_byte(dev, pos, *buf);
 		pos++;
 	}
 
@@ -337,9 +325,11 @@ static int proc_bus_pci_release(struct inode *inode, struct file *file)
 #endif /* HAVE_PCI_MMAP */
 
 static const struct proc_ops proc_bus_pci_ops = {
+	.proc_flags	= PROC_ENTRY_SHORT_WRITES,
 	.proc_lseek	= proc_bus_pci_lseek,
 	.proc_read	= proc_bus_pci_read,
-	.proc_write	= proc_bus_pci_write,
+	.proc_write_buf	= proc_bus_pci_write,
+	.proc_write_size = PCI_CFG_SPACE_EXP_SIZE,
 	.proc_ioctl	= proc_bus_pci_ioctl,
 #ifdef CONFIG_COMPAT
 	.proc_compat_ioctl = proc_bus_pci_ioctl,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 04/26] PCI/proc: let procfs copy out config space reads
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (2 preceding siblings ...)
  2026-11-04 19:42   ` [PATCH RESEND v2 03/26] PCI/proc: let procfs copy in config space writes Christian Brauner
@ 2026-11-04 19:43   ` Christian Brauner
  2026-11-04 19:43   ` [PATCH RESEND v2 05/26] PCI/proc: let procfs handle the ioctls Christian Brauner
                     ` (21 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:43 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read to userspace
after it unpinned the entry, and read the config space into procfs'
buffer with put_unaligned_le16() and put_unaligned_le32() instead of
put_user(). A read into a buffer that can't be written used to report
success. It fails with -EFAULT now. A page holds all of the config
space, so a single read of it still works, libpci treats a short read as
an error.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pci/proc.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c
index d420353ee2b3..a8d7469f222b 100644
--- a/drivers/pci/proc.c
+++ b/drivers/pci/proc.c
@@ -29,8 +29,8 @@ static loff_t proc_bus_pci_lseek(struct file *file, loff_t off, int whence)
 	return fixed_size_llseek(file, off, whence, dev->cfg_size);
 }
 
-static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,
-				 size_t nbytes, loff_t *ppos)
+static ssize_t proc_bus_pci_read(struct file *file, char *buf, size_t nbytes,
+				 loff_t *ppos)
 {
 	struct pci_dev *dev = pde_data(file_inode(file));
 	unsigned int pos, cnt, size;
@@ -60,15 +60,12 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,
 		nbytes = size - pos;
 	cnt = nbytes;
 
-	if (!access_ok(buf, cnt))
-		return -EINVAL;
-
 	pci_config_pm_runtime_get(dev);
 
 	if ((pos & 1) && cnt) {
 		unsigned char val;
 		pci_user_read_config_byte(dev, pos, &val);
-		put_user(val, buf);
+		*buf = val;
 		buf++;
 		pos++;
 		cnt--;
@@ -77,7 +74,7 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,
 	if ((pos & 3) && cnt > 2) {
 		unsigned short val;
 		pci_user_read_config_word(dev, pos, &val);
-		put_user(cpu_to_le16(val), (__le16 __user *) buf);
+		put_unaligned_le16(val, buf);
 		buf += 2;
 		pos += 2;
 		cnt -= 2;
@@ -86,7 +83,7 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,
 	while (cnt >= 4) {
 		unsigned int val;
 		pci_user_read_config_dword(dev, pos, &val);
-		put_user(cpu_to_le32(val), (__le32 __user *) buf);
+		put_unaligned_le32(val, buf);
 		buf += 4;
 		pos += 4;
 		cnt -= 4;
@@ -96,7 +93,7 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,
 	if (cnt >= 2) {
 		unsigned short val;
 		pci_user_read_config_word(dev, pos, &val);
-		put_user(cpu_to_le16(val), (__le16 __user *) buf);
+		put_unaligned_le16(val, buf);
 		buf += 2;
 		pos += 2;
 		cnt -= 2;
@@ -105,7 +102,7 @@ static ssize_t proc_bus_pci_read(struct file *file, char __user *buf,
 	if (cnt) {
 		unsigned char val;
 		pci_user_read_config_byte(dev, pos, &val);
-		put_user(val, buf);
+		*buf = val;
 		pos++;
 	}
 
@@ -327,7 +324,7 @@ static int proc_bus_pci_release(struct inode *inode, struct file *file)
 static const struct proc_ops proc_bus_pci_ops = {
 	.proc_flags	= PROC_ENTRY_SHORT_WRITES,
 	.proc_lseek	= proc_bus_pci_lseek,
-	.proc_read	= proc_bus_pci_read,
+	.proc_read_buf	= proc_bus_pci_read,
 	.proc_write_buf	= proc_bus_pci_write,
 	.proc_write_size = PCI_CFG_SPACE_EXP_SIZE,
 	.proc_ioctl	= proc_bus_pci_ioctl,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 05/26] PCI/proc: let procfs handle the ioctls
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (3 preceding siblings ...)
  2026-11-04 19:43   ` [PATCH RESEND v2 04/26] PCI/proc: let procfs copy out config space reads Christian Brauner
@ 2026-11-04 19:43   ` Christian Brauner
  2026-11-04 19:43   ` [PATCH RESEND v2 06/26] md: let procfs own the seq_file of /proc/mdstat Christian Brauner
                     ` (20 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:43 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

The PCIIOC_* commands take and return plain values and don't touch
userspace memory. Implement ->proc_ioctl_buf() and list them with
nothing to copy. 32-bit tasks go through the same table, so the compat
hook goes away. Unknown commands fail with -ENOTTY from procfs now
instead of -EINVAL, and instead of -EPERM under lockdown, since they
never reach the handler.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/pci/proc.c | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c
index a8d7469f222b..6b0cb5bcbea3 100644
--- a/drivers/pci/proc.c
+++ b/drivers/pci/proc.c
@@ -192,8 +192,7 @@ struct pci_filp_private {
 };
 #endif /* HAVE_PCI_MMAP */
 
-static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd,
-			       unsigned long arg)
+static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd, void *arg)
 {
 	struct pci_dev *dev = pde_data(file_inode(file));
 #ifdef HAVE_PCI_MMAP
@@ -321,16 +320,23 @@ static int proc_bus_pci_release(struct inode *inode, struct file *file)
 }
 #endif /* HAVE_PCI_MMAP */
 
+/* The commands take and return plain values. */
+static const struct proc_ioctl_arg proc_bus_pci_ioctl_args[] = {
+	PROC_IOCTL_ARG(PCIIOC_CONTROLLER, 0, 0),
+	PROC_IOCTL_ARG(PCIIOC_MMAP_IS_IO, 0, 0),
+	PROC_IOCTL_ARG(PCIIOC_MMAP_IS_MEM, 0, 0),
+	PROC_IOCTL_ARG(PCIIOC_WRITE_COMBINE, 0, 0),
+};
+
 static const struct proc_ops proc_bus_pci_ops = {
 	.proc_flags	= PROC_ENTRY_SHORT_WRITES,
 	.proc_lseek	= proc_bus_pci_lseek,
 	.proc_read_buf	= proc_bus_pci_read,
 	.proc_write_buf	= proc_bus_pci_write,
 	.proc_write_size = PCI_CFG_SPACE_EXP_SIZE,
-	.proc_ioctl	= proc_bus_pci_ioctl,
-#ifdef CONFIG_COMPAT
-	.proc_compat_ioctl = proc_bus_pci_ioctl,
-#endif
+	.proc_ioctl_buf	= proc_bus_pci_ioctl,
+	.proc_ioctl_args = proc_bus_pci_ioctl_args,
+	.proc_nr_ioctl_args = ARRAY_SIZE(proc_bus_pci_ioctl_args),
 #ifdef HAVE_PCI_MMAP
 	.proc_open	= proc_bus_pci_open,
 	.proc_release	= proc_bus_pci_release,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 06/26] md: let procfs own the seq_file of /proc/mdstat
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (4 preceding siblings ...)
  2026-11-04 19:43   ` [PATCH RESEND v2 05/26] PCI/proc: let procfs handle the ioctls Christian Brauner
@ 2026-11-04 19:43   ` Christian Brauner
  2026-11-04 19:43   ` [PATCH RESEND v2 07/26] md: let procfs own the wait queue " Christian Brauner
                     ` (19 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:43 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and let procfs open and free the seq_file.
->proc_open() only records the event count for ->proc_poll() now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/md/md.c | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/drivers/md/md.c b/drivers/md/md.c
index 03e35386a802..64abf1743e41 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -9167,18 +9167,13 @@ static const struct seq_operations md_seq_ops = {
 	.show   = md_seq_show,
 };
 
+/* procfs opens the seq_file */
 static int md_seq_open(struct inode *inode, struct file *file)
 {
-	struct seq_file *seq;
-	int error;
-
-	error = seq_open(file, &md_seq_ops);
-	if (error)
-		return error;
+	struct seq_file *seq = file->private_data;
 
-	seq = file->private_data;
 	seq->poll_event = atomic_read(&md_event_count);
-	return error;
+	return 0;
 }
 
 static int md_unloading;
@@ -9200,10 +9195,8 @@ static __poll_t mdstat_poll(struct file *filp, poll_table *wait)
 }
 
 static const struct proc_ops mdstat_proc_ops = {
+	.proc_seq_ops	= &md_seq_ops,
 	.proc_open	= md_seq_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= seq_release,
 	.proc_poll	= mdstat_poll,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 07/26] md: let procfs own the wait queue of /proc/mdstat
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (5 preceding siblings ...)
  2026-11-04 19:43   ` [PATCH RESEND v2 06/26] md: let procfs own the seq_file of /proc/mdstat Christian Brauner
@ 2026-11-04 19:43   ` Christian Brauner
  2026-11-04 19:44   ` [PATCH RESEND v2 08/26] s390/dasd: let procfs copy in writes to /proc/dasd/statistics Christian Brauner
                     ` (18 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:43 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

mdstat_poll() sleeps the poller on md_event_waiters and md_exit() has
to wake it in a loop until nobody is left on the queue, because the
queue goes away with the module. procfs owns the queue poll() uses
now, it outlives the module, and a poll of the removed entry reports
EPOLLERR | EPOLLPRI, so md_new_event() wakes the pollers through
proc_poll_notify() and the loop and md_unloading go.

md_exit() removes /proc/mdstat last now. A sysfs store on an inactive
array doesn't hold the module, so it can still reach md_new_event()
while md_exit() runs, until destroy_workqueue() has waited for the last
array to go, and a wake-up through a removed entry is a use after free.
The static queue didn't mind that, the entry does.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/md/md.c | 29 ++++++++---------------------
 1 file changed, 8 insertions(+), 21 deletions(-)

diff --git a/drivers/md/md.c b/drivers/md/md.c
index 64abf1743e41..7560dcf0f5c8 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -351,12 +351,12 @@ static bool check_new_feature = true;
  *  start array, stop array, error, add device, remove device,
  *  start build, activate spare
  */
-static DECLARE_WAIT_QUEUE_HEAD(md_event_waiters);
+static struct proc_dir_entry *mdstat_entry;
 static atomic_t md_event_count;
 void md_new_event(void)
 {
 	atomic_inc(&md_event_count);
-	wake_up(&md_event_waiters);
+	proc_poll_notify(mdstat_entry);
 }
 EXPORT_SYMBOL_GPL(md_new_event);
 
@@ -9176,16 +9176,11 @@ static int md_seq_open(struct inode *inode, struct file *file)
 	return 0;
 }
 
-static int md_unloading;
 static __poll_t mdstat_poll(struct file *filp, poll_table *wait)
 {
 	struct seq_file *seq = filp->private_data;
 	__poll_t mask;
 
-	if (md_unloading)
-		return EPOLLIN|EPOLLRDNORM|EPOLLERR|EPOLLPRI;
-	poll_wait(filp, &md_event_waiters, wait);
-
 	/* always allow read */
 	mask = EPOLLIN | EPOLLRDNORM;
 
@@ -10765,7 +10760,7 @@ static void md_geninit(void)
 {
 	pr_debug("md: sizeof(mdp_super_t) = %d\n", (int)sizeof(mdp_super_t));
 
-	proc_create("mdstat", S_IRUGO, NULL, &mdstat_proc_ops);
+	mdstat_entry = proc_create("mdstat", S_IRUGO, NULL, &mdstat_proc_ops);
 }
 
 static int __init md_init(void)
@@ -11074,25 +11069,12 @@ void md_autostart_arrays(int part)
 static __exit void md_exit(void)
 {
 	struct mddev *mddev;
-	int delay = 1;
 
 	unregister_blkdev(MD_MAJOR,"md");
 	unregister_blkdev(mdp_major, "mdp");
 	unregister_reboot_notifier(&md_notifier);
 	unregister_sysctl_table(raid_table_header);
 
-	/* We cannot unload the modules while some process is
-	 * waiting for us in select() or poll() - wake them up
-	 */
-	md_unloading = 1;
-	while (waitqueue_active(&md_event_waiters)) {
-		/* not safe to leave yet */
-		wake_up(&md_event_waiters);
-		msleep(delay);
-		delay += delay;
-	}
-	remove_proc_entry("mdstat", NULL);
-
 	spin_lock(&all_mddevs_lock);
 	list_for_each_entry(mddev, &all_mddevs, all_mddevs) {
 		if (!mddev_get(mddev))
@@ -11112,6 +11094,11 @@ static __exit void md_exit(void)
 	spin_unlock(&all_mddevs_lock);
 
 	destroy_workqueue(md_misc_wq);
+	/*
+	 * Only now can nothing reach md_new_event() anymore, which wakes the
+	 * pollers of /proc/mdstat through its entry.
+	 */
+	remove_proc_entry("mdstat", NULL);
 	md_bitmap_exit();
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 08/26] s390/dasd: let procfs copy in writes to /proc/dasd/statistics
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (6 preceding siblings ...)
  2026-11-04 19:43   ` [PATCH RESEND v2 07/26] md: let procfs own the wait queue " Christian Brauner
@ 2026-11-04 19:44   ` Christian Brauner
  2026-11-04 19:44   ` [PATCH RESEND v2 09/26] s390/cio: let procfs copy in writes Christian Brauner
                     ` (17 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:44 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and parse the command from that copy. Commands longer
than PAGE_SIZE - 1 bytes are rejected now instead of cut at 64k.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/s390/block/dasd_proc.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/drivers/s390/block/dasd_proc.c b/drivers/s390/block/dasd_proc.c
index 86399427f01d..6c3bbb7dfe85 100644
--- a/drivers/s390/block/dasd_proc.c
+++ b/drivers/s390/block/dasd_proc.c
@@ -15,7 +15,6 @@
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/seq_file.h>
-#include <linux/vmalloc.h>
 #include <linux/proc_fs.h>
 
 #include <asm/debug.h>
@@ -247,17 +246,15 @@ static int dasd_stats_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t dasd_stats_proc_write(struct file *file,
-		const char __user *user_buf, size_t user_len, loff_t *pos)
+		char *buffer, size_t user_len, loff_t *pos)
 {
 #ifdef CONFIG_DASD_PROFILE
-	char *buffer, *str;
+	char *str;
 	int rc;
 
-	if (user_len > 65536)
-		user_len = 65536;
-	buffer = dasd_get_user_string(user_buf, user_len);
-	if (IS_ERR(buffer))
-		return PTR_ERR(buffer);
+	/* strip linefeed */
+	if (user_len && buffer[user_len - 1] == '\n')
+		buffer[user_len - 1] = 0;
 
 	/* check for valid verbs */
 	str = skip_spaces(buffer);
@@ -296,13 +293,11 @@ static ssize_t dasd_stats_proc_write(struct file *file,
 		pr_info("The statistics have been reset\n");
 	} else
 		goto out_parse_error;
-	vfree(buffer);
 	return user_len;
 out_parse_error:
 	rc = -EINVAL;
 	pr_warn("%s is not a supported value for /proc/dasd/statistics\n", str);
 out_error:
-	vfree(buffer);
 	return rc;
 #else
 	pr_warn("/proc/dasd/statistics: is not activated in this kernel\n");
@@ -312,7 +307,7 @@ out_error:
 
 static const struct proc_ops dasd_stats_proc_ops = {
 	.proc_show	= dasd_stats_proc_show,
-	.proc_write	= dasd_stats_proc_write,
+	.proc_write_buf	= dasd_stats_proc_write,
 };
 
 /*

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 09/26] s390/cio: let procfs copy in writes
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (7 preceding siblings ...)
  2026-11-04 19:44   ` [PATCH RESEND v2 08/26] s390/dasd: let procfs copy in writes to /proc/dasd/statistics Christian Brauner
@ 2026-11-04 19:44   ` Christian Brauner
  2026-11-04 19:44   ` [PATCH RESEND v2 10/26] scsi: let procfs own the seq_file of the host files Christian Brauner
                     ` (16 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:44 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for cio_ignore and cio_settle so procfs
copies in what is written before it pins the entry. cio_ignore parses
the list from that copy, up to the first NUL like strncpy_from_user()
handed it to it before. Writes larger than 64k still come up short, now
through PROC_ENTRY_SHORT_WRITES. Writes to cio_settle larger than
PAGE_SIZE - 1 bytes fail with -EINVAL now, it never looked at what was
written.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/s390/cio/blacklist.c | 33 ++++++++++-----------------------
 drivers/s390/cio/css.c       |  4 ++--
 2 files changed, 12 insertions(+), 25 deletions(-)

diff --git a/drivers/s390/cio/blacklist.c b/drivers/s390/cio/blacklist.c
index 4e02f78f6102..1ec7915be6b3 100644
--- a/drivers/s390/cio/blacklist.c
+++ b/drivers/s390/cio/blacklist.c
@@ -12,7 +12,7 @@
 
 #include <linux/hex.h>
 #include <linux/init.h>
-#include <linux/vmalloc.h>
+#include <linux/sizes.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
 #include <linux/ctype.h>
@@ -355,39 +355,24 @@ cio_ignore_proc_seq_show(struct seq_file *s, void *it)
 }
 
 static ssize_t
-cio_ignore_write(struct file *file, const char __user *user_buf,
+cio_ignore_write(struct file *file, char *buf,
 		 size_t user_len, loff_t *offset)
 {
-	char *buf;
-	ssize_t rc, ret, i;
+	ssize_t ret, i;
 
 	if (*offset)
 		return -EINVAL;
-	if (user_len > 65536)
-		user_len = 65536;
-	buf = vzalloc(user_len + 1); /* maybe better use the stack? */
-	if (buf == NULL)
-		return -ENOMEM;
 
-	if (strncpy_from_user (buf, user_buf, user_len) < 0) {
-		rc = -EFAULT;
-		goto out_free;
-	}
-
-	i = user_len - 1;
+	/* the parser never saw what follows a NUL */
+	i = strnlen(buf, user_len) - 1;
 	while ((i >= 0) && (isspace(buf[i]) || (buf[i] == 0))) {
 		buf[i] = '\0';
 		i--;
 	}
 	ret = blacklist_parse_proc_parameters(buf);
 	if (ret)
-		rc = ret;
-	else
-		rc = user_len;
-
-out_free:
-	vfree (buf);
-	return rc;
+		return ret;
+	return user_len;
 }
 
 static const struct seq_operations cio_ignore_proc_seq_ops = {
@@ -398,9 +383,11 @@ static const struct seq_operations cio_ignore_proc_seq_ops = {
 };
 
 static const struct proc_ops cio_ignore_proc_ops = {
+	.proc_flags	= PROC_ENTRY_SHORT_WRITES,
 	.proc_seq_ops	= &cio_ignore_proc_seq_ops,
 	.proc_state_size = sizeof(struct ccwdev_iter),
-	.proc_write	= cio_ignore_write,
+	.proc_write_buf	= cio_ignore_write,
+	.proc_write_size = SZ_64K,
 };
 
 static int
diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c
index 54b8b8639694..efd3df3e90ba 100644
--- a/drivers/s390/cio/css.c
+++ b/drivers/s390/cio/css.c
@@ -1278,7 +1278,7 @@ static int __init channel_subsystem_init_sync(void)
 subsys_initcall_sync(channel_subsystem_init_sync);
 
 #ifdef CONFIG_PROC_FS
-static ssize_t cio_settle_write(struct file *file, const char __user *buf,
+static ssize_t cio_settle_write(struct file *file, char *buf,
 				size_t count, loff_t *ppos)
 {
 	int ret;
@@ -1292,7 +1292,7 @@ static ssize_t cio_settle_write(struct file *file, const char __user *buf,
 
 static const struct proc_ops cio_settle_proc_ops = {
 	.proc_open	= nonseekable_open,
-	.proc_write	= cio_settle_write,
+	.proc_write_buf	= cio_settle_write,
 };
 
 static int __init cio_settle_init(void)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 10/26] scsi: let procfs own the seq_file of the host files
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (8 preceding siblings ...)
  2026-11-04 19:44   ` [PATCH RESEND v2 09/26] s390/cio: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:44   ` Christian Brauner
  2026-11-04 19:44   ` [PATCH RESEND v2 11/26] scsi: core: let procfs copy in writes Christian Brauner
                     ` (15 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:44 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the show function and let procfs open and free the seq_file of
/proc/scsi/<driver>/<host>. ->proc_open() only preallocates the buffer
now. Reading the file then no longer holds up its removal when the host
goes away.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/scsi/scsi_proc.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/drivers/scsi/scsi_proc.c b/drivers/scsi/scsi_proc.c
index 3e49c4c8704f..59229bf9938e 100644
--- a/drivers/scsi/scsi_proc.c
+++ b/drivers/scsi/scsi_proc.c
@@ -94,10 +94,10 @@ static int proc_scsi_show(struct seq_file *m, void *v)
 	return shost->hostt->show_info(m, shost);
 }
 
+/* procfs opens the seq_file */
 static int proc_scsi_host_open(struct inode *inode, struct file *file)
 {
-	return single_open_size(file, proc_scsi_show, pde_data(inode),
-				4 * PAGE_SIZE);
+	return seq_preallocate(file->private_data, 4 * PAGE_SIZE);
 }
 
 static struct scsi_proc_entry *
@@ -140,10 +140,8 @@ scsi_template_proc_dir(const struct scsi_host_template *sht)
 EXPORT_SYMBOL_GPL(scsi_template_proc_dir);
 
 static const struct proc_ops proc_scsi_ops = {
+	.proc_show	= proc_scsi_show,
 	.proc_open	= proc_scsi_host_open,
-	.proc_release	= single_release,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
 	.proc_write	= proc_scsi_host_write
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 11/26] scsi: core: let procfs copy in writes
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (9 preceding siblings ...)
  2026-11-04 19:44   ` [PATCH RESEND v2 10/26] scsi: let procfs own the seq_file of the host files Christian Brauner
@ 2026-11-04 19:44   ` Christian Brauner
  2026-11-04 19:45   ` [PATCH RESEND v2 12/26] selftests/proc: check that a faulting writer doesn't hold up removal Christian Brauner
                     ` (14 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:44 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for /proc/scsi/scsi, /proc/scsi/device_info
and the host files so procfs copies in what is written before it pins
the entry, and parse what was written from that copy. /proc/scsi/scsi
and device_info take up to PAGE_SIZE bytes like before. The host files
pass the copy to ->write_info(), writes to them that are larger than
PAGE_SIZE - 1 bytes fail with -EINVAL from procfs instead of -EOVERFLOW.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/scsi/scsi_devinfo.c | 29 +++++-----------------------
 drivers/scsi/scsi_proc.c    | 47 +++++++++++----------------------------------
 2 files changed, 16 insertions(+), 60 deletions(-)

diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c
index 0b8f5ff40543..d9093851b23e 100644
--- a/drivers/scsi/scsi_devinfo.c
+++ b/drivers/scsi/scsi_devinfo.c
@@ -678,39 +678,20 @@ static const struct seq_operations scsi_devinfo_seq_ops = {
  * To use, echo "vendor:model:flag" > /proc/scsi/device_info
  */
 static ssize_t proc_scsi_devinfo_write(struct file *file,
-				       const char __user *buf,
+				       char *buffer,
 				       size_t length, loff_t *ppos)
 {
-	char *buffer;
-	ssize_t err = length;
-
-	if (!buf || length>PAGE_SIZE)
+	if (length == PAGE_SIZE && buffer[PAGE_SIZE-1])
 		return -EINVAL;
-	buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!buffer)
-		return -ENOMEM;
-	if (copy_from_user(buffer, buf, length)) {
-		err =-EFAULT;
-		goto out;
-	}
-
-	if (length < PAGE_SIZE)
-		buffer[length] = '\0';
-	else if (buffer[PAGE_SIZE-1]) {
-		err = -EINVAL;
-		goto out;
-	}
 
 	scsi_dev_info_list_add_str(buffer);
-
-out:
-	kfree(buffer);
-	return err;
+	return length;
 }
 
 static const struct proc_ops scsi_devinfo_proc_ops = {
 	.proc_seq_ops	= &scsi_devinfo_seq_ops,
-	.proc_write	= proc_scsi_devinfo_write,
+	.proc_write_buf	= proc_scsi_devinfo_write,
+	.proc_write_size = PAGE_SIZE,
 };
 #endif /* CONFIG_SCSI_PROC_FS */
 
diff --git a/drivers/scsi/scsi_proc.c b/drivers/scsi/scsi_proc.c
index 59229bf9938e..5962e671cc83 100644
--- a/drivers/scsi/scsi_proc.c
+++ b/drivers/scsi/scsi_proc.c
@@ -62,30 +62,18 @@ struct scsi_proc_entry {
 	unsigned int		present;
 };
 
-static ssize_t proc_scsi_host_write(struct file *file, const char __user *buf,
+static ssize_t proc_scsi_host_write(struct file *file, char *buf,
                            size_t count, loff_t *ppos)
 {
 	struct Scsi_Host *shost = pde_data(file_inode(file));
-	ssize_t ret = -ENOMEM;
-	char *page;
-    
+
 	if (count > PROC_BLOCK_SIZE)
 		return -EOVERFLOW;
 
 	if (!shost->hostt->write_info)
 		return -EINVAL;
 
-	page = kmalloc(PAGE_SIZE, GFP_KERNEL);
-	if (page) {
-		ret = -EFAULT;
-		if (copy_from_user(page, buf, count))
-			goto out;
-		page[count] = '\0';
-		ret = shost->hostt->write_info(shost, page, count);
-	}
-out:
-	kfree(page);
-	return ret;
+	return shost->hostt->write_info(shost, buf, count);
 }
 
 static int proc_scsi_show(struct seq_file *m, void *v)
@@ -142,7 +130,7 @@ EXPORT_SYMBOL_GPL(scsi_template_proc_dir);
 static const struct proc_ops proc_scsi_ops = {
 	.proc_show	= proc_scsi_show,
 	.proc_open	= proc_scsi_host_open,
-	.proc_write	= proc_scsi_host_write
+	.proc_write_buf	= proc_scsi_host_write
 };
 
 /**
@@ -386,8 +374,8 @@ static int scsi_remove_single_device(uint host, uint channel, uint id, uint lun)
 /**
  * proc_scsi_write - handle writes to /proc/scsi/scsi
  * @file: not used
- * @buf: buffer to write
- * @length: length of buf, at most PAGE_SIZE
+ * @buffer: buffer to write
+ * @length: length of buffer, at most PAGE_SIZE
  * @ppos: not used
  *
  * Description: this provides a legacy mechanism to add or remove devices by
@@ -402,32 +390,20 @@ static int scsi_remove_single_device(uint host, uint channel, uint id, uint lun)
  */
 
 
-static ssize_t proc_scsi_write(struct file *file, const char __user *buf,
+static ssize_t proc_scsi_write(struct file *file, char *buffer,
 			       size_t length, loff_t *ppos)
 {
 	int host, channel, id, lun;
-	char *buffer, *end, *p;
+	char *end, *p;
 	int err;
 
-	if (!buf || length > PAGE_SIZE)
-		return -EINVAL;
-
-	buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!buffer)
-		return -ENOMEM;
-
-	err = -EFAULT;
-	if (copy_from_user(buffer, buf, length))
-		goto out;
-
 	err = -EINVAL;
 	if (length < PAGE_SIZE) {
 		end = buffer + length;
-		*end = '\0';
 	} else {
 		end = buffer + PAGE_SIZE - 1;
 		if (*end)
-			goto out;
+			return err;
 	}
 
 	/*
@@ -466,8 +442,6 @@ static ssize_t proc_scsi_write(struct file *file, const char __user *buf,
 	if (!err)
 		err = length;
 
- out:
-	kfree(buffer);
 	return err;
 }
 
@@ -521,7 +495,8 @@ static const struct seq_operations scsi_seq_ops = {
 
 static const struct proc_ops scsi_scsi_proc_ops = {
 	.proc_seq_ops	= &scsi_seq_ops,
-	.proc_write	= proc_scsi_write,
+	.proc_write_buf	= proc_scsi_write,
+	.proc_write_size = PAGE_SIZE,
 };
 
 /**

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 12/26] selftests/proc: check that a faulting writer doesn't hold up removal
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (10 preceding siblings ...)
  2026-11-04 19:44   ` [PATCH RESEND v2 11/26] scsi: core: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:45   ` Christian Brauner
  2026-11-04 19:45   ` [PATCH RESEND v2 13/26] scsi: sg: let procfs own the seq_file of allow_dio and def_reserved_size Christian Brauner
                     ` (13 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:45 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Write to /proc/scsi/scsi_debug/<host> from a buffer whose page is backed
by userfaultfd and remove the host while procfs copies in what is
written:

- removing the entry doesn't wait for the writer

- the write fails with -EIO once the fault is resolved

The test loads scsi_debug if it isn't there and skips without it or
without /proc/scsi.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 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       | 209 +++++++++++++++++++++
 4 files changed, 214 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index cafd8c263c9a..a1994bda676c 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -25,6 +25,7 @@
 /proc-remove-slow-reader
 /proc-remove-mapped
 /proc-remove-slow-readlink
+/proc-remove-slow-writer
 /read
 /self
 /setns-dcache
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index c38297afc244..8fcab5af502b 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -36,5 +36,6 @@ TEST_GEN_PROGS += proc-remove-slow-sysctl
 TEST_GEN_PROGS += proc-remove-slow-reader
 TEST_GEN_PROGS += proc-remove-mapped
 TEST_GEN_PROGS += proc-remove-slow-readlink
+TEST_GEN_PROGS += proc-remove-slow-writer
 
 include ../lib.mk
diff --git a/tools/testing/selftests/proc/config b/tools/testing/selftests/proc/config
index 1504a761b39b..992577d15295 100644
--- a/tools/testing/selftests/proc/config
+++ b/tools/testing/selftests/proc/config
@@ -1,4 +1,7 @@
 CONFIG_PROC_FS=y
+CONFIG_SCSI=y
+CONFIG_SCSI_PROC_FS=y
+CONFIG_SCSI_DEBUG=m
 CONFIG_NET_NS=y
 CONFIG_IPV6=y
 CONFIG_TUN=y
diff --git a/tools/testing/selftests/proc/proc-remove-slow-writer.c b/tools/testing/selftests/proc/proc-remove-slow-writer.c
new file mode 100644
index 000000000000..3539293d353f
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-remove-slow-writer.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A write that faults while procfs copies it in must not hold up removal of
+ * the /proc entry. Removing a scsi_debug host removes its
+ * /proc/scsi/scsi_debug/<host> file.
+ */
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/userfaultfd.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define ADD_HOST	"/sys/bus/pseudo/drivers/scsi_debug/add_host"
+#define HOST_DIR	"/proc/scsi/scsi_debug"
+#define TIMEOUT_MS	5000
+#define MAX_HOSTS	4096
+#define HOST_BITS	(8 * sizeof(unsigned long))
+
+struct writer {
+	int fd;
+	char *buf;
+	ssize_t ret;
+	int err;
+};
+
+struct remover {
+	int done;
+	int ret;
+};
+
+/* a positive @nr adds hosts, a negative one removes the last ones added */
+static int add_host(int nr)
+{
+	char s[16];
+	int fd, len, ret;
+
+	fd = open(ADD_HOST, O_WRONLY | O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+	len = snprintf(s, sizeof(s), "%d", nr);
+	ret = write(fd, s, len) == len ? 0 : -errno;
+	close(fd);
+	return ret;
+}
+
+/* the numbers of the hosts in /proc/scsi/scsi_debug as a bitmap */
+static int list_hosts(unsigned long *hosts)
+{
+	struct dirent *d;
+	DIR *dir;
+
+	memset(hosts, 0, MAX_HOSTS / 8);
+	dir = opendir(HOST_DIR);
+	if (!dir)
+		/* the directory comes with the first host and goes with the last */
+		return errno == ENOENT ? 0 : -errno;
+	while ((d = readdir(dir))) {
+		char *end;
+		long nr = strtol(d->d_name, &end, 10);
+
+		if (end != d->d_name && !*end && nr >= 0 && nr < MAX_HOSTS)
+			hosts[nr / HOST_BITS] |= 1UL << (nr % HOST_BITS);
+	}
+	closedir(dir);
+	return 0;
+}
+
+/* the host that is in @after but wasn't in @before */
+static int new_host(const unsigned long *before, const unsigned long *after)
+{
+	for (int i = 0; i < MAX_HOSTS / HOST_BITS; i++) {
+		unsigned long new = after[i] & ~before[i];
+
+		if (new)
+			return i * HOST_BITS + __builtin_ctzl(new);
+	}
+	return -1;
+}
+
+static void *write_thread(void *arg)
+{
+	struct writer *w = arg;
+
+	w->ret = write(w->fd, w->buf, 2);
+	w->err = errno;
+	return NULL;
+}
+
+static void *remove_thread(void *arg)
+{
+	struct remover *r = arg;
+	ssize_t ret;
+
+	r->ret = add_host(-1);
+	ret = write(r->done, "x", 1);
+	return (void *)ret;
+}
+
+FIXTURE(host) {
+	char path[PATH_MAX];
+	int host;
+};
+
+FIXTURE_SETUP(host)
+{
+	unsigned long before[MAX_HOSTS / HOST_BITS];
+	unsigned long after[MAX_HOSTS / HOST_BITS];
+
+	self->host = -1;
+	if (access(ADD_HOST, W_OK) && system("modprobe -q scsi_debug add_host=0"))
+		SKIP(return, "no scsi_debug");
+	if (access(ADD_HOST, W_OK))
+		SKIP(return, ADD_HOST ": %s", strerror(errno));
+	/* the host files need CONFIG_SCSI_PROC_FS */
+	if (access("/proc/scsi", F_OK))
+		SKIP(return, "/proc/scsi: %s", strerror(errno));
+
+	/* host numbers are reused, so look for the one that's new */
+	ASSERT_EQ(list_hosts(before), 0);
+	ASSERT_EQ(add_host(1), 0);
+	if (!list_hosts(after))
+		self->host = new_host(before, after);
+	/* the teardown doesn't run when the setup fails */
+	if (self->host < 0)
+		add_host(-1);
+	ASSERT_GE(self->host, 0);
+	snprintf(self->path, sizeof(self->path), HOST_DIR "/%d", self->host);
+}
+
+FIXTURE_TEARDOWN(host)
+{
+	if (self->host >= 0 && !access(self->path, F_OK))
+		add_host(-1);
+}
+
+TEST_F(host, remove_while_writer_faults)
+{
+	struct uffdio_api api = { .api = UFFD_API };
+	struct uffdio_register reg = {};
+	struct uffdio_zeropage zero = {};
+	long psz = sysconf(_SC_PAGESIZE);
+	struct writer w = {};
+	struct remover r = {};
+	struct uffd_msg msg;
+	struct pollfd pfd;
+	pthread_t wt, rt;
+	int uffd, done[2];
+	char *buf;
+
+	w.fd = open(self->path, O_WRONLY | O_CLOEXEC);
+	if (w.fd < 0)
+		SKIP(return, "%s: %s", self->path, 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);
+
+	buf = mmap(NULL, 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, &reg), 0);
+
+	w.buf = buf;
+	ASSERT_EQ(pthread_create(&wt, NULL, write_thread, &w), 0);
+
+	/* The writer now sleeps while what it writes is copied in. */
+	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);
+
+	/* Removing the host removes the file that is being written. */
+	ASSERT_EQ(pipe2(done, O_CLOEXEC), 0);
+	r.done = done[1];
+	ASSERT_EQ(pthread_create(&rt, NULL, remove_thread, &r), 0);
+	pfd = (struct pollfd){ .fd = done[0], .events = POLLIN };
+	EXPECT_EQ(poll(&pfd, 1, TIMEOUT_MS), 1) {
+		TH_LOG("removing %s waited for the writer", self->path);
+	}
+
+	zero.range.start = (unsigned long)buf;
+	zero.range.len = psz;
+	ASSERT_EQ(ioctl(uffd, UFFDIO_ZEROPAGE, &zero), 0);
+	ASSERT_EQ(pthread_join(wt, NULL), 0);
+	ASSERT_EQ(pthread_join(rt, NULL), 0);
+	EXPECT_EQ(r.ret, 0);
+
+	/* The entry was gone by the time the write got to it. */
+	EXPECT_EQ(w.ret, -1);
+	EXPECT_EQ(w.err, EIO);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 13/26] scsi: sg: let procfs own the seq_file of allow_dio and def_reserved_size
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (11 preceding siblings ...)
  2026-11-04 19:45   ` [PATCH RESEND v2 12/26] selftests/proc: check that a faulting writer doesn't hold up removal Christian Brauner
@ 2026-11-04 19:45   ` Christian Brauner
  2026-11-04 19:45   ` [PATCH RESEND v2 14/26] scsi: sg: let procfs copy in writes Christian Brauner
                     ` (12 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:45 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Pass the integer they show as the data of the entry and declare the
show function, procfs then opens and frees the seq_file. That makes the
open functions unnecessary.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/scsi/sg.c | 28 ++++++----------------------
 1 file changed, 6 insertions(+), 22 deletions(-)

diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
index 6551f1284d44..fb3ab74e8727 100644
--- a/drivers/scsi/sg.c
+++ b/drivers/scsi/sg.c
@@ -2288,26 +2288,18 @@ sg_get_dev(int dev)
 #ifdef CONFIG_SCSI_PROC_FS
 static int sg_proc_seq_show_int(struct seq_file *s, void *v);
 
-static int sg_proc_single_open_adio(struct inode *inode, struct file *file);
 static ssize_t sg_proc_write_adio(struct file *filp, const char __user *buffer,
 			          size_t count, loff_t *off);
 static const struct proc_ops adio_proc_ops = {
-	.proc_open	= sg_proc_single_open_adio,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= sg_proc_seq_show_int,
 	.proc_write	= sg_proc_write_adio,
-	.proc_release	= single_release,
 };
 
-static int sg_proc_single_open_dressz(struct inode *inode, struct file *file);
 static ssize_t sg_proc_write_dressz(struct file *filp, 
 		const char __user *buffer, size_t count, loff_t *off);
 static const struct proc_ops dressz_proc_ops = {
-	.proc_open	= sg_proc_single_open_dressz,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= sg_proc_seq_show_int,
 	.proc_write	= sg_proc_write_dressz,
-	.proc_release	= single_release,
 };
 
 static int sg_proc_seq_show_version(struct seq_file *s, void *v);
@@ -2348,9 +2340,11 @@ sg_proc_init(void)
 	if (!p)
 		return 1;
 
-	proc_create("allow_dio", S_IRUGO | S_IWUSR, p, &adio_proc_ops);
+	proc_create_data("allow_dio", S_IRUGO | S_IWUSR, p, &adio_proc_ops,
+			 &sg_allow_dio);
 	proc_create_seq("debug", S_IRUGO, p, &debug_seq_ops);
-	proc_create("def_reserved_size", S_IRUGO | S_IWUSR, p, &dressz_proc_ops);
+	proc_create_data("def_reserved_size", S_IRUGO | S_IWUSR, p,
+			 &dressz_proc_ops, &def_reserved_size);
 	proc_create_single("device_hdr", S_IRUGO, p, sg_proc_seq_show_devhdr);
 	proc_create_seq("devices", S_IRUGO, p, &dev_seq_ops);
 	proc_create_seq("device_strs", S_IRUGO, p, &devstrs_seq_ops);
@@ -2365,11 +2359,6 @@ static int sg_proc_seq_show_int(struct seq_file *s, void *v)
 	return 0;
 }
 
-static int sg_proc_single_open_adio(struct inode *inode, struct file *file)
-{
-	return single_open(file, sg_proc_seq_show_int, &sg_allow_dio);
-}
-
 static ssize_t 
 sg_proc_write_adio(struct file *filp, const char __user *buffer,
 		   size_t count, loff_t *off)
@@ -2386,11 +2375,6 @@ sg_proc_write_adio(struct file *filp, const char __user *buffer,
 	return count;
 }
 
-static int sg_proc_single_open_dressz(struct inode *inode, struct file *file)
-{
-	return single_open(file, sg_proc_seq_show_int, &def_reserved_size);
-}
-
 static ssize_t 
 sg_proc_write_dressz(struct file *filp, const char __user *buffer,
 		     size_t count, loff_t *off)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 14/26] scsi: sg: let procfs copy in writes
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (12 preceding siblings ...)
  2026-11-04 19:45   ` [PATCH RESEND v2 13/26] scsi: sg: let procfs own the seq_file of allow_dio and def_reserved_size Christian Brauner
@ 2026-11-04 19:45   ` Christian Brauner
  2026-11-04 19:45   ` [PATCH RESEND v2 15/26] scsi: esas2r: let procfs copy the ioctl argument of the ATTONode file Christian Brauner
                     ` (11 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:45 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for allow_dio and def_reserved_size so
procfs copies in what is written before it pins the entry, and parse
the number from that copy.

kstrtoul_from_user() looked at the first 66 bytes of whatever was
written. Now the whole write is parsed, and anything larger than
PAGE_SIZE - 1 bytes fails with -EINVAL from procfs before the
capability check.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/scsi/sg.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
index fb3ab74e8727..df955896e65f 100644
--- a/drivers/scsi/sg.c
+++ b/drivers/scsi/sg.c
@@ -2288,18 +2288,18 @@ sg_get_dev(int dev)
 #ifdef CONFIG_SCSI_PROC_FS
 static int sg_proc_seq_show_int(struct seq_file *s, void *v);
 
-static ssize_t sg_proc_write_adio(struct file *filp, const char __user *buffer,
+static ssize_t sg_proc_write_adio(struct file *filp, char *buffer,
 			          size_t count, loff_t *off);
 static const struct proc_ops adio_proc_ops = {
 	.proc_show	= sg_proc_seq_show_int,
-	.proc_write	= sg_proc_write_adio,
+	.proc_write_buf	= sg_proc_write_adio,
 };
 
 static ssize_t sg_proc_write_dressz(struct file *filp, 
-		const char __user *buffer, size_t count, loff_t *off);
+		char *buffer, size_t count, loff_t *off);
 static const struct proc_ops dressz_proc_ops = {
 	.proc_show	= sg_proc_seq_show_int,
-	.proc_write	= sg_proc_write_dressz,
+	.proc_write_buf	= sg_proc_write_dressz,
 };
 
 static int sg_proc_seq_show_version(struct seq_file *s, void *v);
@@ -2360,7 +2360,7 @@ static int sg_proc_seq_show_int(struct seq_file *s, void *v)
 }
 
 static ssize_t 
-sg_proc_write_adio(struct file *filp, const char __user *buffer,
+sg_proc_write_adio(struct file *filp, char *buffer,
 		   size_t count, loff_t *off)
 {
 	int err;
@@ -2368,7 +2368,7 @@ sg_proc_write_adio(struct file *filp, const char __user *buffer,
 
 	if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO))
 		return -EACCES;
-	err = kstrtoul_from_user(buffer, count, 0, &num);
+	err = kstrtoul(buffer, 0, &num);
 	if (err)
 		return err;
 	sg_allow_dio = num ? 1 : 0;
@@ -2376,7 +2376,7 @@ sg_proc_write_adio(struct file *filp, const char __user *buffer,
 }
 
 static ssize_t 
-sg_proc_write_dressz(struct file *filp, const char __user *buffer,
+sg_proc_write_dressz(struct file *filp, char *buffer,
 		     size_t count, loff_t *off)
 {
 	int err;
@@ -2385,7 +2385,7 @@ sg_proc_write_dressz(struct file *filp, const char __user *buffer,
 	if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO))
 		return -EACCES;
 
-	err = kstrtoul_from_user(buffer, count, 0, &k);
+	err = kstrtoul(buffer, 0, &k);
 	if (err)
 		return err;
 	if (k <= 1048576) {	/* limit "big buff" to 1 MB */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 15/26] scsi: esas2r: let procfs copy the ioctl argument of the ATTONode file
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (13 preceding siblings ...)
  2026-11-04 19:45   ` [PATCH RESEND v2 14/26] scsi: sg: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:45   ` Christian Brauner
  2026-11-04 19:46   ` [PATCH RESEND v2 16/26] hwmon: (dell-smm) let procfs copy the i8k ioctl arguments Christian Brauner
                     ` (10 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:45 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Move the work of esas2r_ioctl_handler() into esas2r_ioctl_buf() which
works on a kernel copy of struct atto_express_ioctl. The SCSI ioctl and
the character device keep copying in and out in
esas2r_ioctl_handler().

The procfs file implements ->proc_ioctl_buf() with esas2r_ioctl_buf()
and lists every command from EXPRESS_IOCTL_MIN to EXPRESS_IOCTL_MAX, so
procfs copies the struct in before it pins the entry and back out after
unpinning it. Commands outside of that range fail with -ENOTTY instead
of -ENOTSUPP now, a NULL argument with -EFAULT.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/scsi/esas2r/esas2r.h       |  2 ++
 drivers/scsi/esas2r/esas2r_ioctl.c | 58 ++++++++++++++++++++++----------------
 drivers/scsi/esas2r/esas2r_main.c  | 44 +++++++++++++++++++++++------
 3 files changed, 72 insertions(+), 32 deletions(-)

diff --git a/drivers/scsi/esas2r/esas2r.h b/drivers/scsi/esas2r/esas2r.h
index 10101f50b282..4d75b8adbb3b 100644
--- a/drivers/scsi/esas2r/esas2r.h
+++ b/drivers/scsi/esas2r/esas2r.h
@@ -964,6 +964,8 @@ struct esas2r_adapter {
 const char *esas2r_info(struct Scsi_Host *);
 int esas2r_write_params(struct esas2r_adapter *a, struct esas2r_request *rq,
 			struct esas2r_sas_nvram *data);
+int esas2r_ioctl_buf(void *hostdata, unsigned int cmd,
+		     struct atto_express_ioctl *ioctl);
 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,
diff --git a/drivers/scsi/esas2r/esas2r_ioctl.c b/drivers/scsi/esas2r/esas2r_ioctl.c
index 80ae5b9d45db..901c5d40cda8 100644
--- a/drivers/scsi/esas2r/esas2r_ioctl.c
+++ b/drivers/scsi/esas2r/esas2r_ioctl.c
@@ -1279,37 +1279,25 @@ int esas2r_write_params(struct esas2r_adapter *a, struct esas2r_request *rq,
 }
 
 
-/* This function only cares about ATTO-specific ioctls (atto_express_ioctl) */
-int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg)
+/*
+ * This function only cares about ATTO-specific ioctls (atto_express_ioctl).
+ * @ioctl is a kernel copy of what the caller passed. It goes back to the
+ * caller unless this fails.
+ */
+int esas2r_ioctl_buf(void *hostdata, unsigned int cmd,
+		     struct atto_express_ioctl *ioctl)
 {
-	struct atto_express_ioctl *ioctl = NULL;
 	struct esas2r_adapter *a;
 	struct esas2r_request *rq;
 	u16 code;
 	int err;
 
-	esas2r_log(ESAS2R_LOG_DEBG, "ioctl (%p, %x, %p)", hostdata, cmd, arg);
-
-	if ((arg == NULL)
-	    || (cmd < EXPRESS_IOCTL_MIN)
-	    || (cmd > EXPRESS_IOCTL_MAX))
-		return -ENOTSUPP;
-
-	ioctl = memdup_user(arg, sizeof(struct atto_express_ioctl));
-	if (IS_ERR(ioctl)) {
-		esas2r_log(ESAS2R_LOG_WARN,
-			   "ioctl_handler access_ok failed for cmd %u, address %p",
-			   cmd, arg);
-		return PTR_ERR(ioctl);
-	}
-
 	/* verify the signature */
 
 	if (memcmp(ioctl->header.signature,
 		   EXPRESS_IOCTL_SIGNATURE,
 		   EXPRESS_IOCTL_SIGNATURE_SIZE) != 0) {
 		esas2r_log(ESAS2R_LOG_WARN, "invalid signature");
-		kfree(ioctl);
 
 		return -ENOTSUPP;
 	}
@@ -1350,7 +1338,6 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg)
 			esas2r_adapters[ioctl->header.channel] == NULL) {
 			ioctl->header.return_code = IOCTL_BAD_CHANNEL;
 			esas2r_log(ESAS2R_LOG_WARN, "bad channel value");
-			kfree(ioctl);
 
 			return -ENOTSUPP;
 		}
@@ -1403,7 +1390,6 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg)
 
 		rq = esas2r_alloc_request(a);
 		if (rq == NULL) {
-			kfree(ioctl);
 			esas2r_log(ESAS2R_LOG_WARN,
 			   "could not allocate an internal request");
 			return -ENOMEM;
@@ -1516,19 +1502,43 @@ ioctl_done:
 
 	}
 
+	return 0;
+}
+
+int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg)
+{
+	struct atto_express_ioctl *ioctl __free(kfree) = NULL;
+	int err;
+
+	esas2r_log(ESAS2R_LOG_DEBG, "ioctl (%p, %x, %p)", hostdata, cmd, arg);
+
+	if ((arg == NULL)
+	    || (cmd < EXPRESS_IOCTL_MIN)
+	    || (cmd > EXPRESS_IOCTL_MAX))
+		return -ENOTSUPP;
+
+	ioctl = memdup_user(arg, sizeof(struct atto_express_ioctl));
+	if (IS_ERR(ioctl)) {
+		esas2r_log(ESAS2R_LOG_WARN,
+			   "ioctl_handler access_ok failed for cmd %u, address %p",
+			   cmd, arg);
+		return PTR_ERR(ioctl);
+	}
+
+	err = esas2r_ioctl_buf(hostdata, cmd, ioctl);
+	if (err)
+		return err;
+
 	/* Always copy the buffer back, if only to pick up the status */
 	err = copy_to_user(arg, ioctl, sizeof(struct atto_express_ioctl));
 	if (err != 0) {
 		esas2r_log(ESAS2R_LOG_WARN,
 			   "ioctl_handler copy_to_user didn't copy everything (err %d, cmd %u)",
 			   err, cmd);
-		kfree(ioctl);
 
 		return -EFAULT;
 	}
 
-	kfree(ioctl);
-
 	return 0;
 }
 
diff --git a/drivers/scsi/esas2r/esas2r_main.c b/drivers/scsi/esas2r/esas2r_main.c
index 75a70ae9441b..720be2a0907d 100644
--- a/drivers/scsi/esas2r/esas2r_main.c
+++ b/drivers/scsi/esas2r/esas2r_main.c
@@ -613,14 +613,6 @@ static const struct file_operations esas2r_proc_fops = {
 	.unlocked_ioctl = esas2r_proc_ioctl,
 };
 
-static const struct proc_ops esas2r_proc_ops = {
-	.proc_lseek		= default_llseek,
-	.proc_ioctl		= esas2r_proc_ioctl,
-#ifdef CONFIG_COMPAT
-	.proc_compat_ioctl	= compat_ptr_ioctl,
-#endif
-};
-
 static struct Scsi_Host *esas2r_proc_host;
 static int esas2r_proc_major;
 
@@ -630,6 +622,42 @@ long esas2r_proc_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
 				    cmd, (void __user *)arg);
 }
 
+static long esas2r_proc_ioctl_buf(struct file *fp, unsigned int cmd, void *arg)
+{
+	return esas2r_ioctl_buf(esas2r_proc_host->hostdata, cmd, arg);
+}
+
+#define ESAS2R_PROC_IOCTL(_cmd)						\
+	PROC_IOCTL_ARG(_cmd, sizeof(struct atto_express_ioctl),		\
+		       sizeof(struct atto_express_ioctl))
+
+/* esas2r_ioctl_buf() takes every command from EXPRESS_IOCTL_MIN to _MAX. */
+static const struct proc_ioctl_arg esas2r_proc_ioctl_args[] = {
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_RW_FIRMWARE),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_READ_PARAMS),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_WRITE_PARAMS),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_FC_API),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_GET_CHANNELS),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_CHAN_INFO),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_DEFAULT_PARAMS),
+	ESAS2R_PROC_IOCTL(EXPRESS_ADDR_MEMORY),
+	ESAS2R_PROC_IOCTL(EXPRESS_RW_MEMORY),
+	ESAS2R_PROC_IOCTL(EXPRESS_TSDK_DUMP),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_SMP),
+	ESAS2R_PROC_IOCTL(EXPRESS_CSMI),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_HBA),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_VDA),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_GET_ID),
+	ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_GET_MOD_INFO),
+};
+
+static const struct proc_ops esas2r_proc_ops = {
+	.proc_lseek		= default_llseek,
+	.proc_ioctl_buf		= esas2r_proc_ioctl_buf,
+	.proc_ioctl_args	= esas2r_proc_ioctl_args,
+	.proc_nr_ioctl_args	= ARRAY_SIZE(esas2r_proc_ioctl_args),
+};
+
 static void __exit esas2r_exit(void)
 {
 	esas2r_log(ESAS2R_LOG_INFO, "%s called", __func__);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 16/26] hwmon: (dell-smm) let procfs copy the i8k ioctl arguments
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (14 preceding siblings ...)
  2026-11-04 19:45   ` [PATCH RESEND v2 15/26] scsi: esas2r: let procfs copy the ioctl argument of the ATTONode file Christian Brauner
@ 2026-11-04 19:46   ` Christian Brauner
  2026-11-04 19:46   ` [PATCH RESEND v2 17/26] fbdev: viafb: let procfs copy in writes Christian Brauner
                     ` (9 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:46 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_ioctl_buf() so procfs copies the arguments in before
it pins the entry and back out after unpinning it. The command numbers
in <uapi/linux/i8k.h> encode the wrong sizes, so the table lists what
is really copied: an int or the 16 bytes of the machine id out, and one
or two ints in.

A NULL argument fails with -EFAULT now instead of -EINVAL, and so does
a bad pointer to SET_FAN from an unprivileged task, which got -EPERM
before, since procfs copies the argument in before the entry's own
checks run. 32-bit tasks can use the commands whose numbers don't
depend on the size of size_t.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/hwmon/dell-smm-hwmon.c | 48 +++++++++++++++++++++---------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 492b2b6c6d7e..67b8c6e5752f 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -549,16 +549,13 @@ static int i8k_get_power_status(const struct dell_smm_data *data)
  * Procfs interface
  */
 
-static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+static long i8k_ioctl(struct file *fp, unsigned int cmd, void *arg)
 {
 	struct dell_smm_data *data = pde_data(file_inode(fp));
-	int __user *argp = (int __user *)arg;
+	int *argp = arg;
 	int speed, err;
 	int val = 0;
 
-	if (!argp)
-		return -EINVAL;
-
 	switch (cmd) {
 	case I8K_BIOS_VERSION:
 		if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) ||
@@ -568,17 +565,13 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
 		val = (data->bios_version[0] << 16) |
 				(data->bios_version[1] << 8) | data->bios_version[2];
 
-		if (copy_to_user(argp, &val, sizeof(val)))
-			return -EFAULT;
-
+		*argp = val;
 		return 0;
 	case I8K_MACHINE_ID:
 		if (restricted && !capable(CAP_SYS_ADMIN))
 			return -EPERM;
 
-		if (copy_to_user(argp, data->bios_machineid, sizeof(data->bios_machineid)))
-			return -EFAULT;
-
+		memcpy(argp, data->bios_machineid, sizeof(data->bios_machineid));
 		return 0;
 	case I8K_FN_STATUS:
 		val = i8k_get_fn_status(data);
@@ -593,8 +586,7 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
 		break;
 
 	case I8K_GET_SPEED:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
+		val = *argp;
 
 		if (val > U8_MAX || val < 0)
 			return -EINVAL;
@@ -603,8 +595,7 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
 		break;
 
 	case I8K_GET_FAN:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
+		val = *argp;
 
 		if (val > U8_MAX || val < 0)
 			return -EINVAL;
@@ -616,14 +607,11 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
 		if (restricted && !capable(CAP_SYS_ADMIN))
 			return -EPERM;
 
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
+		val = argp[0];
 		if (val > U8_MAX || val < 0)
 			return -EINVAL;
 
-		if (copy_from_user(&speed, argp + 1, sizeof(int)))
-			return -EFAULT;
+		speed = argp[1];
 
 		speed = clamp_val(speed, 0, data->i8k_fan_max);
 
@@ -643,12 +631,22 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
 	if (val < 0)
 		return val;
 
-	if (copy_to_user(argp, &val, sizeof(int)))
-		return -EFAULT;
-
+	*argp = val;
 	return 0;
 }
 
+/* The command numbers don't encode what is copied, see <uapi/linux/i8k.h>. */
+static const struct proc_ioctl_arg i8k_ioctl_args[] = {
+	PROC_IOCTL_ARG(I8K_BIOS_VERSION, 0, sizeof(int)),
+	PROC_IOCTL_ARG(I8K_MACHINE_ID, 0, sizeof_field(struct dell_smm_data, bios_machineid)),
+	PROC_IOCTL_ARG(I8K_FN_STATUS, 0, sizeof(int)),
+	PROC_IOCTL_ARG(I8K_POWER_STATUS, 0, sizeof(int)),
+	PROC_IOCTL_ARG(I8K_GET_TEMP, 0, sizeof(int)),
+	PROC_IOCTL_ARG(I8K_GET_SPEED, sizeof(int), sizeof(int)),
+	PROC_IOCTL_ARG(I8K_GET_FAN, sizeof(int), sizeof(int)),
+	PROC_IOCTL_ARG(I8K_SET_FAN, 2 * sizeof(int), sizeof(int)),
+};
+
 /*
  * Print the information for /proc/i8k.
  */
@@ -696,7 +694,9 @@ static int i8k_proc_show(struct seq_file *seq, void *offset)
 
 static const struct proc_ops i8k_proc_ops = {
 	.proc_show	= i8k_proc_show,
-	.proc_ioctl	= i8k_ioctl,
+	.proc_ioctl_buf	= i8k_ioctl,
+	.proc_ioctl_args = i8k_ioctl_args,
+	.proc_nr_ioctl_args = ARRAY_SIZE(i8k_ioctl_args),
 };
 
 static void i8k_exit_procfs(void *param)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 17/26] fbdev: viafb: let procfs copy in writes
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (15 preceding siblings ...)
  2026-11-04 19:46   ` [PATCH RESEND v2 16/26] hwmon: (dell-smm) let procfs copy the i8k ioctl arguments Christian Brauner
@ 2026-11-04 19:46   ` Christian Brauner
  2026-11-04 19:46   ` [PATCH RESEND v2 18/26] usb: gadget: function: rndis: " Christian Brauner
                     ` (8 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:46 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for the files in /proc/viafb so procfs
copies in what is written before it pins the entry, and parse what was
written from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with
-EINVAL now, the output device files refused anything over 63 bytes
already. kstrtou8_from_user() looked at the first 10 bytes of what was
written to dfph and dfpl, now the whole write is parsed.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/video/fbdev/via/viafbdev.c | 46 +++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 25 deletions(-)

diff --git a/drivers/video/fbdev/via/viafbdev.c b/drivers/video/fbdev/via/viafbdev.c
index 4b617e80a77c..94b303006915 100644
--- a/drivers/video/fbdev/via/viafbdev.c
+++ b/drivers/video/fbdev/via/viafbdev.c
@@ -1121,7 +1121,7 @@ static int viafb_dvp0_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t viafb_dvp0_proc_write(struct file *file,
-	const char __user *buffer, size_t count, loff_t *pos)
+	char *buffer, size_t count, loff_t *pos)
 {
 	char buf[20], *value, *pbuf;
 	u8 reg_val = 0;
@@ -1129,8 +1129,7 @@ static ssize_t viafb_dvp0_proc_write(struct file *file,
 	if (count < 1)
 		return -EINVAL;
 	length = count > 20 ? 20 : count;
-	if (copy_from_user(&buf[0], buffer, length))
-		return -EFAULT;
+	memcpy(&buf[0], buffer, length);
 	buf[length - 1] = '\0';	/*Ensure end string */
 	pbuf = &buf[0];
 	for (i = 0; i < 3; i++) {
@@ -1169,7 +1168,7 @@ static ssize_t viafb_dvp0_proc_write(struct file *file,
 
 static const struct proc_ops viafb_dvp0_proc_ops = {
 	.proc_show	= viafb_dvp0_proc_show,
-	.proc_write	= viafb_dvp0_proc_write,
+	.proc_write_buf	= viafb_dvp0_proc_write,
 };
 
 static int viafb_dvp1_proc_show(struct seq_file *m, void *v)
@@ -1183,7 +1182,7 @@ static int viafb_dvp1_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t viafb_dvp1_proc_write(struct file *file,
-	const char __user *buffer, size_t count, loff_t *pos)
+	char *buffer, size_t count, loff_t *pos)
 {
 	char buf[20], *value, *pbuf;
 	u8 reg_val = 0;
@@ -1191,8 +1190,7 @@ static ssize_t viafb_dvp1_proc_write(struct file *file,
 	if (count < 1)
 		return -EINVAL;
 	length = count > 20 ? 20 : count;
-	if (copy_from_user(&buf[0], buffer, length))
-		return -EFAULT;
+	memcpy(&buf[0], buffer, length);
 	buf[length - 1] = '\0';	/*Ensure end string */
 	pbuf = &buf[0];
 	for (i = 0; i < 3; i++) {
@@ -1225,7 +1223,7 @@ static ssize_t viafb_dvp1_proc_write(struct file *file,
 
 static const struct proc_ops viafb_dvp1_proc_ops = {
 	.proc_show	= viafb_dvp1_proc_show,
-	.proc_write	= viafb_dvp1_proc_write,
+	.proc_write_buf	= viafb_dvp1_proc_write,
 };
 
 static int viafb_dfph_proc_show(struct seq_file *m, void *v)
@@ -1237,11 +1235,11 @@ static int viafb_dfph_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t viafb_dfph_proc_write(struct file *file,
-	const char __user *buffer, size_t count, loff_t *pos)
+	char *buffer, size_t count, loff_t *pos)
 {
 	int err;
 	u8 reg_val;
-	err = kstrtou8_from_user(buffer, count, 0, &reg_val);
+	err = kstrtou8(buffer, 0, &reg_val);
 	if (err)
 		return err;
 
@@ -1251,7 +1249,7 @@ static ssize_t viafb_dfph_proc_write(struct file *file,
 
 static const struct proc_ops viafb_dfph_proc_ops = {
 	.proc_show	= viafb_dfph_proc_show,
-	.proc_write	= viafb_dfph_proc_write,
+	.proc_write_buf	= viafb_dfph_proc_write,
 };
 
 static int viafb_dfpl_proc_show(struct seq_file *m, void *v)
@@ -1263,11 +1261,11 @@ static int viafb_dfpl_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t viafb_dfpl_proc_write(struct file *file,
-	const char __user *buffer, size_t count, loff_t *pos)
+	char *buffer, size_t count, loff_t *pos)
 {
 	int err;
 	u8 reg_val;
-	err = kstrtou8_from_user(buffer, count, 0, &reg_val);
+	err = kstrtou8(buffer, 0, &reg_val);
 	if (err)
 		return err;
 
@@ -1277,7 +1275,7 @@ static ssize_t viafb_dfpl_proc_write(struct file *file,
 
 static const struct proc_ops viafb_dfpl_proc_ops = {
 	.proc_show	= viafb_dfpl_proc_show,
-	.proc_write	= viafb_dfpl_proc_write,
+	.proc_write_buf	= viafb_dfpl_proc_write,
 };
 
 static int viafb_vt1636_proc_show(struct seq_file *m, void *v)
@@ -1313,7 +1311,7 @@ static int viafb_vt1636_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t viafb_vt1636_proc_write(struct file *file,
-	const char __user *buffer, size_t count, loff_t *pos)
+	char *buffer, size_t count, loff_t *pos)
 {
 	char buf[30], *value, *pbuf;
 	struct IODATA reg_val;
@@ -1321,8 +1319,7 @@ static ssize_t viafb_vt1636_proc_write(struct file *file,
 	if (count < 1)
 		return -EINVAL;
 	length = count > 30 ? 30 : count;
-	if (copy_from_user(&buf[0], buffer, length))
-		return -EFAULT;
+	memcpy(&buf[0], buffer, length);
 	buf[length - 1] = '\0';	/*Ensure end string */
 	pbuf = &buf[0];
 	switch (viaparinfo->chip_info->lvds_chip_info.lvds_chip_name) {
@@ -1404,7 +1401,7 @@ static ssize_t viafb_vt1636_proc_write(struct file *file,
 
 static const struct proc_ops viafb_vt1636_proc_ops = {
 	.proc_show	= viafb_vt1636_proc_show,
-	.proc_write	= viafb_vt1636_proc_write,
+	.proc_write_buf	= viafb_vt1636_proc_write,
 };
 
 #endif /* CONFIG_FB_VIA_DIRECT_PROCFS */
@@ -1416,7 +1413,7 @@ static int __maybe_unused viafb_sup_odev_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t odev_update(const char __user *buffer, size_t count, u32 *odev)
+static ssize_t odev_update(const char *buffer, size_t count, u32 *odev)
 {
 	char buf[64], *ptr = buf;
 	u32 devices;
@@ -1424,8 +1421,7 @@ static ssize_t odev_update(const char __user *buffer, size_t count, u32 *odev)
 
 	if (count < 1 || count > 63)
 		return -EINVAL;
-	if (copy_from_user(&buf[0], buffer, count))
-		return -EFAULT;
+	memcpy(&buf[0], buffer, count);
 	buf[count] = '\0';
 	add = buf[0] == '+';
 	sub = buf[0] == '-';
@@ -1452,7 +1448,7 @@ static int viafb_iga1_odev_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t viafb_iga1_odev_proc_write(struct file *file,
-	const char __user *buffer, size_t count, loff_t *pos)
+	char *buffer, size_t count, loff_t *pos)
 {
 	u32 dev_on, dev_off, dev_old, dev_new;
 	ssize_t res;
@@ -1473,7 +1469,7 @@ static ssize_t viafb_iga1_odev_proc_write(struct file *file,
 
 static const struct proc_ops viafb_iga1_odev_proc_ops = {
 	.proc_show	= viafb_iga1_odev_proc_show,
-	.proc_write	= viafb_iga1_odev_proc_write,
+	.proc_write_buf	= viafb_iga1_odev_proc_write,
 };
 
 static int viafb_iga2_odev_proc_show(struct seq_file *m, void *v)
@@ -1483,7 +1479,7 @@ static int viafb_iga2_odev_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t viafb_iga2_odev_proc_write(struct file *file,
-	const char __user *buffer, size_t count, loff_t *pos)
+	char *buffer, size_t count, loff_t *pos)
 {
 	u32 dev_on, dev_off, dev_old, dev_new;
 	ssize_t res;
@@ -1504,7 +1500,7 @@ static ssize_t viafb_iga2_odev_proc_write(struct file *file,
 
 static const struct proc_ops viafb_iga2_odev_proc_ops = {
 	.proc_show	= viafb_iga2_odev_proc_show,
-	.proc_write	= viafb_iga2_odev_proc_write,
+	.proc_write_buf	= viafb_iga2_odev_proc_write,
 };
 
 #define IS_VT1636(lvds_chip)	((lvds_chip).lvds_chip_name == VT1636_LVDS)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 18/26] usb: gadget: function: rndis: let procfs copy in writes
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (16 preceding siblings ...)
  2026-11-04 19:46   ` [PATCH RESEND v2 17/26] fbdev: viafb: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:46   ` Christian Brauner
  2026-11-04 19:46   ` [PATCH RESEND v2 19/26] wifi: ipw2x00: let procfs copy in writes to the debug level Christian Brauner
                     ` (7 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:46 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written to
/proc/driver/rndis-<n> before it pins the entry, and handle the
commands from that copy. Writes larger than PAGE_SIZE - 1 bytes fail
with -EINVAL now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/usb/gadget/function/rndis.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c
index 3c7bd30b8a3a..e05e0c6ae8fb 100644
--- a/drivers/usb/gadget/function/rndis.c
+++ b/drivers/usb/gadget/function/rndis.c
@@ -1138,7 +1138,7 @@ static int rndis_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t rndis_proc_write(struct file *file, const char __user *buffer,
+static ssize_t rndis_proc_write(struct file *file, char *buffer,
 				size_t count, loff_t *ppos)
 {
 	rndis_params *p = pde_data(file_inode(file));
@@ -1146,9 +1146,8 @@ static ssize_t rndis_proc_write(struct file *file, const char __user *buffer,
 	int i, fl_speed = 0;
 
 	for (i = 0; i < count; i++) {
-		char c;
-		if (get_user(c, buffer))
-			return -EFAULT;
+		char c = *buffer;
+
 		switch (c) {
 		case '0':
 		case '1':
@@ -1185,7 +1184,7 @@ static ssize_t rndis_proc_write(struct file *file, const char __user *buffer,
 
 static const struct proc_ops rndis_proc_ops = {
 	.proc_show	= rndis_proc_show,
-	.proc_write	= rndis_proc_write,
+	.proc_write_buf	= rndis_proc_write,
 };
 
 #define	NAME_TEMPLATE "driver/rndis-%03d"

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 19/26] wifi: ipw2x00: let procfs copy in writes to the debug level
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (17 preceding siblings ...)
  2026-11-04 19:46   ` [PATCH RESEND v2 18/26] usb: gadget: function: rndis: " Christian Brauner
@ 2026-11-04 19:46   ` Christian Brauner
  2026-11-04 19:47   ` [PATCH RESEND v2 20/26] tty: sysrq: let procfs copy in writes to /proc/sysrq-trigger Christian Brauner
                     ` (6 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:46 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and parse the level from that copy. Writes larger
than PAGE_SIZE - 1 bytes fail with -EINVAL now, and a buffer that can't
be read fails with -EFAULT instead of looking like it was written.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/net/wireless/intel/ipw2x00/libipw_module.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/intel/ipw2x00/libipw_module.c b/drivers/net/wireless/intel/ipw2x00/libipw_module.c
index 15d9a5efb581..08cc0d7f57c1 100644
--- a/drivers/net/wireless/intel/ipw2x00/libipw_module.c
+++ b/drivers/net/wireless/intel/ipw2x00/libipw_module.c
@@ -216,14 +216,13 @@ static int debug_level_proc_show(struct seq_file *m, void *v)
 }
 
 static ssize_t debug_level_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *pos)
+		char *buffer, size_t count, loff_t *pos)
 {
 	char buf[] = "0x00000000\n";
 	size_t len = min(sizeof(buf) - 1, count);
 	unsigned long val;
 
-	if (copy_from_user(buf, buffer, len))
-		return count;
+	memcpy(buf, buffer, len);
 	buf[len] = 0;
 	if (sscanf(buf, "%li", &val) != 1)
 		printk(KERN_INFO DRV_NAME
@@ -236,7 +235,7 @@ static ssize_t debug_level_proc_write(struct file *file,
 
 static const struct proc_ops debug_level_proc_ops = {
 	.proc_show	= debug_level_proc_show,
-	.proc_write	= debug_level_proc_write,
+	.proc_write_buf	= debug_level_proc_write,
 };
 #endif				/* CONFIG_LIBIPW_DEBUG */
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 20/26] tty: sysrq: let procfs copy in writes to /proc/sysrq-trigger
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (18 preceding siblings ...)
  2026-11-04 19:46   ` [PATCH RESEND v2 19/26] wifi: ipw2x00: let procfs copy in writes to the debug level Christian Brauner
@ 2026-11-04 19:47   ` Christian Brauner
  2026-11-04 19:47   ` [PATCH RESEND v2 21/26] Input: let procfs own the wait queue of /proc/bus/input/devices Christian Brauner
                     ` (5 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:47 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever, Jiri Slaby

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and handle the keys from that copy. Writes larger
than PAGE_SIZE - 1 bytes fail with -EINVAL now. They used to trigger the
first key, or all of them in bulk mode. procfs allocates the copy, which
is as small as the write, opening the file allocated as much already.

Reviewed-by: Jiri Slaby <jirislaby@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/tty/sysrq.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index f4571d05b496..99363e1b59ed 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -1200,17 +1200,14 @@ EXPORT_SYMBOL(unregister_sysrq_key);
  * However, if the first character is an underscore,
  * all characters are processed.
  */
-static ssize_t write_sysrq_trigger(struct file *file, const char __user *buf,
+static ssize_t write_sysrq_trigger(struct file *file, char *buf,
 				   size_t count, loff_t *ppos)
 {
 	bool bulk = false;
 	size_t i;
 
 	for (i = 0; i < count; i++) {
-		char c;
-
-		if (get_user(c, buf + i))
-			return -EFAULT;
+		char c = buf[i];
 
 		if (c == '_')
 			bulk = true;
@@ -1225,7 +1222,7 @@ static ssize_t write_sysrq_trigger(struct file *file, const char __user *buf,
 }
 
 static const struct proc_ops sysrq_trigger_proc_ops = {
-	.proc_write	= write_sysrq_trigger,
+	.proc_write_buf	= write_sysrq_trigger,
 	.proc_lseek	= noop_llseek,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 21/26] Input: let procfs own the wait queue of /proc/bus/input/devices
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (19 preceding siblings ...)
  2026-11-04 19:47   ` [PATCH RESEND v2 20/26] tty: sysrq: let procfs copy in writes to /proc/sysrq-trigger Christian Brauner
@ 2026-11-04 19:47   ` Christian Brauner
  2026-11-04 19:47   ` [PATCH RESEND v2 22/26] ALSA: info: let procfs own the seq_file of text entries Christian Brauner
                     ` (4 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:47 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

input_proc_devices_poll() sleeps the poller on a queue of its own.
procfs owns the queue poll() uses now, so leave the poll_wait() to it
and wake its pollers instead.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 drivers/input/input.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/drivers/input/input.c b/drivers/input/input.c
index d27eecbf4a03..a984810eb80d 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -1082,13 +1082,13 @@ static int input_attach_handler(struct input_dev *dev, struct input_handler *han
 #ifdef CONFIG_PROC_FS
 
 static struct proc_dir_entry *proc_bus_input_dir;
-static DECLARE_WAIT_QUEUE_HEAD(input_devices_poll_wait);
+static struct proc_dir_entry *proc_bus_input_devices;
 static int input_devices_state;
 
 static inline void input_wakeup_procfs_readers(void)
 {
 	input_devices_state++;
-	wake_up(&input_devices_poll_wait);
+	proc_poll_notify(proc_bus_input_devices);
 }
 
 struct input_seq_state {
@@ -1102,7 +1102,6 @@ static __poll_t input_proc_devices_poll(struct file *file, poll_table *wait)
 	struct seq_file *seq = file->private_data;
 	struct input_seq_state *state = seq->private;
 
-	poll_wait(file, &input_devices_poll_wait, wait);
 	if (state->input_devices_state != input_devices_state) {
 		state->input_devices_state = input_devices_state;
 		return EPOLLIN | EPOLLRDNORM;
@@ -1284,9 +1283,9 @@ static int __init input_proc_init(void)
 	if (!proc_bus_input_dir)
 		return -ENOMEM;
 
-	entry = proc_create("devices", 0, proc_bus_input_dir,
-			    &input_devices_proc_ops);
-	if (!entry)
+	proc_bus_input_devices = proc_create("devices", 0, proc_bus_input_dir,
+					     &input_devices_proc_ops);
+	if (!proc_bus_input_devices)
 		goto fail1;
 
 	entry = proc_create("handlers", 0, proc_bus_input_dir,
@@ -1297,6 +1296,7 @@ static int __init input_proc_init(void)
 	return 0;
 
  fail2:	remove_proc_entry("devices", proc_bus_input_dir);
+	proc_bus_input_devices = NULL;
  fail1: remove_proc_entry("bus/input", NULL);
 	return -ENOMEM;
 }
@@ -1304,6 +1304,7 @@ static int __init input_proc_init(void)
 static void input_proc_exit(void)
 {
 	remove_proc_entry("devices", proc_bus_input_dir);
+	proc_bus_input_devices = NULL;
 	remove_proc_entry("handlers", proc_bus_input_dir);
 	remove_proc_entry("bus/input", NULL);
 }

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 22/26] ALSA: info: let procfs own the seq_file of text entries
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (20 preceding siblings ...)
  2026-11-04 19:47   ` [PATCH RESEND v2 21/26] Input: let procfs own the wait queue of /proc/bus/input/devices Christian Brauner
@ 2026-11-04 19:47   ` Christian Brauner
  2026-11-04 19:47   ` [PATCH RESEND v2 23/26] ALSA: info: let procfs copy in writes Christian Brauner
                     ` (3 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:47 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the show function and let procfs open and free the seq_file.
->proc_open() only sets up the private data and preallocates the buffer
now and ->proc_release() no longer releases the seq_file. Reading an
entry then no longer holds up its removal when the card goes away.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 sound/core/info.c | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/sound/core/info.c b/sound/core/info.c
index f608d335b6c9..42a4f8fb3198 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -346,9 +346,11 @@ static int snd_info_seq_show(struct seq_file *seq, void *p)
 	return 0;
 }
 
+/* procfs opens the seq_file */
 static int snd_info_text_entry_open(struct inode *inode, struct file *file)
 {
 	struct snd_info_entry *entry = pde_data(inode);
+	struct seq_file *m = file->private_data;
 	struct snd_info_private_data *data;
 	int err;
 
@@ -362,13 +364,12 @@ static int snd_info_text_entry_open(struct inode *inode, struct file *file)
 		err = -ENOMEM;
 		goto error;
 	}
-	if (entry->size)
-		err = single_open_size(file, snd_info_seq_show, data,
-				       entry->size);
-	else
-		err = single_open(file, snd_info_seq_show, data);
-	if (err < 0)
-		goto error;
+	if (entry->size) {
+		err = seq_preallocate(m, entry->size);
+		if (err < 0)
+			goto error;
+	}
+	m->private = data;
 	return 0;
 
  error:
@@ -387,7 +388,6 @@ static int snd_info_text_entry_release(struct inode *inode, struct file *file)
 	if (data->wbuffer && entry->c.text.write)
 		entry->c.text.write(entry, data->wbuffer);
 
-	single_release(inode, file);
 	kfree(data->rbuffer);
 	if (data->wbuffer) {
 		kvfree(data->wbuffer->buffer);
@@ -401,11 +401,10 @@ static int snd_info_text_entry_release(struct inode *inode, struct file *file)
 
 static const struct proc_ops snd_info_text_entry_ops =
 {
+	.proc_show	= snd_info_seq_show,
 	.proc_open	= snd_info_text_entry_open,
 	.proc_release	= snd_info_text_entry_release,
 	.proc_write	= snd_info_text_entry_write,
-	.proc_lseek	= seq_lseek,
-	.proc_read	= seq_read,
 };
 
 static struct snd_info_entry *create_subdir(struct module *mod,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 23/26] ALSA: info: let procfs copy in writes
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (21 preceding siblings ...)
  2026-11-04 19:47   ` [PATCH RESEND v2 22/26] ALSA: info: let procfs own the seq_file of text entries Christian Brauner
@ 2026-11-04 19:47   ` Christian Brauner
  2026-11-04 19:48   ` [PATCH RESEND v2 24/26] ALSA: info: let procfs copy out reads of data entries Christian Brauner
                     ` (2 subsequent siblings)
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:47 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for data and text entries so procfs copies
in what is written before it pins the entry.

Data entries take writes a page at a time with PROC_ENTRY_SHORT_WRITES.
They used to get the whole write at once. ->write() of struct
snd_info_entry_ops gets the kernel copy and opl4, its only user, writes
it to the sample memory without bouncing it through a vmalloc() buffer
of its own.

Text entries take up to SND_INFO_TEXT_WRITE_MAX bytes at once like
before. A single write that is larger fails with -EINVAL from procfs
instead of -EIO now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 include/sound/info.h           |  2 +-
 sound/core/info.c              | 21 ++++++++++++---------
 sound/drivers/opl4/opl4_proc.c | 11 +----------
 3 files changed, 14 insertions(+), 20 deletions(-)

diff --git a/include/sound/info.h b/include/sound/info.h
index f216291e5c41..a9344c44e352 100644
--- a/include/sound/info.h
+++ b/include/sound/info.h
@@ -42,7 +42,7 @@ struct snd_info_entry_ops {
 			struct file *file, char __user *buf,
 			size_t count, loff_t pos);
 	ssize_t (*write)(struct snd_info_entry *entry, void *file_private_data,
-			 struct file *file, const char __user *buf,
+			 struct file *file, const char *buf,
 			 size_t count, loff_t pos);
 	loff_t (*llseek)(struct snd_info_entry *entry,
 			 void *file_private_data, struct file *file,
diff --git a/sound/core/info.c b/sound/core/info.c
index 42a4f8fb3198..3bfeab1a5a1f 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -160,7 +160,7 @@ static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
 	return size;
 }
 
-static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
+static ssize_t snd_info_entry_write(struct file *file, char *buffer,
 				    size_t count, loff_t * offset)
 {
 	struct snd_info_private_data *data = file->private_data;
@@ -277,9 +277,11 @@ static int snd_info_entry_release(struct inode *inode, struct file *file)
 
 static const struct proc_ops snd_info_entry_operations =
 {
+	.proc_flags	= PROC_ENTRY_SHORT_WRITES,
 	.proc_lseek	= snd_info_entry_llseek,
 	.proc_read	= snd_info_entry_read,
-	.proc_write	= snd_info_entry_write,
+	.proc_write_buf	= snd_info_entry_write,
+	.proc_write_size = PAGE_SIZE,
 	.proc_poll	= snd_info_entry_poll,
 	.proc_ioctl	= snd_info_entry_ioctl,
 	.proc_mmap	= snd_info_entry_mmap,
@@ -287,11 +289,13 @@ static const struct proc_ops snd_info_entry_operations =
 	.proc_release	= snd_info_entry_release,
 };
 
+/* don't handle too large text inputs */
+#define SND_INFO_TEXT_WRITE_MAX	(16 * 1024)
+
 /*
  * file ops for text proc files
  */
-static ssize_t snd_info_text_entry_write(struct file *file,
-					 const char __user *buffer,
+static ssize_t snd_info_text_entry_write(struct file *file, char *buffer,
 					 size_t count, loff_t *offset)
 {
 	struct seq_file *m = file->private_data;
@@ -307,8 +311,7 @@ static ssize_t snd_info_text_entry_write(struct file *file,
 	if (!valid_pos(pos, count))
 		return -EIO;
 	next = pos + count;
-	/* don't handle too large text inputs */
-	if (next > 16 * 1024)
+	if (next > SND_INFO_TEXT_WRITE_MAX)
 		return -EIO;
 	guard(mutex)(&entry->access);
 	buf = data->wbuffer;
@@ -325,8 +328,7 @@ static ssize_t snd_info_text_entry_write(struct file *file,
 		buf->buffer = nbuf;
 		buf->len = PAGE_ALIGN(next);
 	}
-	if (copy_from_user(buf->buffer + pos, buffer, count))
-		return -EFAULT;
+	memcpy(buf->buffer + pos, buffer, count);
 	buf->size = next;
 	*offset = next;
 	return count;
@@ -404,7 +406,8 @@ static const struct proc_ops snd_info_text_entry_ops =
 	.proc_show	= snd_info_seq_show,
 	.proc_open	= snd_info_text_entry_open,
 	.proc_release	= snd_info_text_entry_release,
-	.proc_write	= snd_info_text_entry_write,
+	.proc_write_buf	= snd_info_text_entry_write,
+	.proc_write_size = SND_INFO_TEXT_WRITE_MAX,
 };
 
 static struct snd_info_entry *create_subdir(struct module *mod,
diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c
index fd0ba4704d9f..bdcae02a4dc2 100644
--- a/sound/drivers/opl4/opl4_proc.c
+++ b/sound/drivers/opl4/opl4_proc.c
@@ -54,21 +54,12 @@ static ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry,
 static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry,
 				       void *file_private_data,
 				       struct file *file,
-				       const char __user *_buf,
+				       const char *buf,
 				       size_t count, loff_t pos)
 {
 	struct snd_opl4 *opl4 = entry->private_data;
-	char *buf;
 
-	buf = vmalloc(count);
-	if (!buf)
-		return -ENOMEM;
-	if (copy_from_user(buf, _buf, count)) {
-		vfree(buf);
-		return -EFAULT;
-	}
 	snd_opl4_write_memory(opl4, buf, pos, count);
-	vfree(buf);
 	return count;
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 24/26] ALSA: info: let procfs copy out reads of data entries
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (22 preceding siblings ...)
  2026-11-04 19:47   ` [PATCH RESEND v2 23/26] ALSA: info: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:48   ` Christian Brauner
  2026-11-04 19:48   ` [PATCH RESEND v2 25/26] ALSA: info: drop the ioctl hook " Christian Brauner
  2026-11-04 19:48   ` [PATCH RESEND v2 26/26] ALSA: info: drop the poll_table from the info entry poll op Christian Brauner
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() for data entries so procfs copies what is
read to userspace after it unpinned the entry. ->read() of struct
snd_info_entry_ops fills procfs' buffer now. opl4 reads the sample
memory into it without a vmalloc() buffer of its own, gus, cs46xx,
emu10k1, scarlett2, cs4281 and mixart copy into it instead of to
userspace. The driver guide still told raw callbacks to copy to and from
userspace, so update its example as well. Data entries ask for
PROC_ENTRY_FULL_READS so that reading all of an entry at once keeps
working. A read that faults on its buffer in a later piece returns the
pieces before it now instead of failing with -EFAULT.

Unbinding a card then no longer waits for a reader that faults on its
buffer.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 Documentation/sound/kernel-api/writing-an-alsa-driver.rst | 10 +++++-----
 include/sound/gus.h                                       |  2 +-
 include/sound/info.h                                      |  2 +-
 sound/core/info.c                                         |  6 +++---
 sound/drivers/opl4/opl4_proc.c                            | 12 +-----------
 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 ++++-----
 12 files changed, 32 insertions(+), 51 deletions(-)

diff --git a/Documentation/sound/kernel-api/writing-an-alsa-driver.rst b/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
index 86b64de03140..d4b4753879b6 100644
--- a/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
+++ b/Documentation/sound/kernel-api/writing-an-alsa-driver.rst
@@ -3679,9 +3679,10 @@ For raw data, ``size`` field must be set properly. This specifies
 the maximum size of the proc file access.
 
 The read/write callbacks of raw mode are more direct than the text mode.
-You need to use a low-level I/O functions such as
-:c:func:`copy_from_user()` and :c:func:`copy_to_user()` to transfer the
-data::
+They get a kernel buffer that procfs copies to or from userspace, so they
+just copy the data. The buffer holds at most a page and isn't zeroed, so
+a read callback must return exactly what it copied into it; a larger
+read comes back for the rest::
 
   static ssize_t my_file_io_read(struct snd_info_entry *entry,
                               void *file_private_data,
@@ -3690,8 +3691,7 @@ data::
                               size_t count,
                               loff_t pos)
   {
-          if (copy_to_user(buf, local_data + pos, count))
-                  return -EFAULT;
+          memcpy(buf, local_data + pos, count);
           return count;
   }
 
diff --git a/include/sound/gus.h b/include/sound/gus.h
index 3feb42627de1..1b504f516ebf 100644
--- a/include/sound/gus.h
+++ b/include/sound/gus.h
@@ -594,7 +594,7 @@ void snd_gf1_uart_resume(struct snd_gus_card *gus);
 /* gus_dram.c */
 int snd_gus_dram_write(struct snd_gus_card *gus, char __user *ptr,
 		       unsigned int addr, unsigned int size);
-int snd_gus_dram_read(struct snd_gus_card *gus, char __user *ptr,
+int snd_gus_dram_read(struct snd_gus_card *gus, char *ptr,
 		      unsigned int addr, unsigned int size, int rom);
 
 /* gus_timer.c */
diff --git a/include/sound/info.h b/include/sound/info.h
index a9344c44e352..67378da4d36c 100644
--- a/include/sound/info.h
+++ b/include/sound/info.h
@@ -39,7 +39,7 @@ struct snd_info_entry_ops {
 	int (*release)(struct snd_info_entry *entry,
 		       unsigned short mode, void *file_private_data);
 	ssize_t (*read)(struct snd_info_entry *entry, void *file_private_data,
-			struct file *file, char __user *buf,
+			struct file *file, char *buf,
 			size_t count, loff_t pos);
 	ssize_t (*write)(struct snd_info_entry *entry, void *file_private_data,
 			 struct file *file, const char *buf,
diff --git a/sound/core/info.c b/sound/core/info.c
index 3bfeab1a5a1f..663ecb96f828 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -138,7 +138,7 @@ static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
 	return offset;
 }
 
-static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
+static ssize_t snd_info_entry_read(struct file *file, char *buffer,
 				   size_t count, loff_t * offset)
 {
 	struct snd_info_private_data *data = file->private_data;
@@ -277,9 +277,9 @@ static int snd_info_entry_release(struct inode *inode, struct file *file)
 
 static const struct proc_ops snd_info_entry_operations =
 {
-	.proc_flags	= PROC_ENTRY_SHORT_WRITES,
+	.proc_flags	= PROC_ENTRY_SHORT_WRITES | PROC_ENTRY_FULL_READS,
 	.proc_lseek	= snd_info_entry_llseek,
-	.proc_read	= snd_info_entry_read,
+	.proc_read_buf	= snd_info_entry_read,
 	.proc_write_buf	= snd_info_entry_write,
 	.proc_write_size = PAGE_SIZE,
 	.proc_poll	= snd_info_entry_poll,
diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c
index bdcae02a4dc2..b8b282cb0a02 100644
--- a/sound/drivers/opl4/opl4_proc.c
+++ b/sound/drivers/opl4/opl4_proc.c
@@ -5,7 +5,6 @@
  */
 
 #include "opl4_local.h"
-#include <linux/vmalloc.h>
 #include <linux/export.h>
 #include <sound/info.h>
 
@@ -33,21 +32,12 @@ static int snd_opl4_mem_proc_release(struct snd_info_entry *entry,
 
 static ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry,
 				      void *file_private_data,
-				      struct file *file, char __user *_buf,
+				      struct file *file, char *buf,
 				      size_t count, loff_t pos)
 {
 	struct snd_opl4 *opl4 = entry->private_data;
-	char* buf;
 
-	buf = vmalloc(count);
-	if (!buf)
-		return -ENOMEM;
 	snd_opl4_read_memory(opl4, buf, pos, count);
-	if (copy_to_user(_buf, buf, count)) {
-		vfree(buf);
-		return -EFAULT;
-	}
-	vfree(buf);
 	return count;
 }
 
diff --git a/sound/isa/gus/gus_dram.c b/sound/isa/gus/gus_dram.c
index 50fe738ee3ea..98b94db8e819 100644
--- a/sound/isa/gus/gus_dram.c
+++ b/sound/isa/gus/gus_dram.c
@@ -45,7 +45,7 @@ int snd_gus_dram_write(struct snd_gus_card *gus, char __user *buffer,
 	return snd_gus_dram_poke(gus, buffer, address, size);
 }
 
-static int snd_gus_dram_peek(struct snd_gus_card *gus, char __user *_buffer,
+static int snd_gus_dram_peek(struct snd_gus_card *gus, char *_buffer,
 			     unsigned int address, unsigned int size,
 			     int rom)
 {
@@ -67,15 +67,14 @@ static int snd_gus_dram_peek(struct snd_gus_card *gus, char __user *_buffer,
 			while (size2--)
 				*pbuffer++ = snd_gf1_peek(gus, address++);
 		}
-		if (copy_to_user(_buffer, buffer, size1))
-			return -EFAULT;
+		memcpy(_buffer, buffer, size1);
 		size -= size1;
 		_buffer += size1;
 	}
 	return 0;
 }
 
-int snd_gus_dram_read(struct snd_gus_card *gus, char __user *buffer,
+int snd_gus_dram_read(struct snd_gus_card *gus, char *buffer,
 		      unsigned int address, unsigned int size,
 		      int rom)
 {
diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c
index 04c212882718..c939bfd5a966 100644
--- a/sound/isa/gus/gus_mem_proc.c
+++ b/sound/isa/gus/gus_mem_proc.c
@@ -18,7 +18,7 @@ struct gus_proc_private {
 
 static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry,
 				     void *file_private_data,
-				     struct file *file, char __user *buf,
+				     struct file *file, char *buf,
 				     size_t count, loff_t pos)
 {
 	struct gus_proc_private *priv = entry->private_data;
diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c
index b831f1d50750..1444e39fdbc1 100644
--- a/sound/pci/cs4281.c
+++ b/sound/pci/cs4281.c
@@ -1104,25 +1104,23 @@ static void snd_cs4281_proc_read(struct snd_info_entry *entry,
 
 static ssize_t snd_cs4281_BA0_read(struct snd_info_entry *entry,
 				   void *file_private_data,
-				   struct file *file, char __user *buf,
+				   struct file *file, char *buf,
 				   size_t count, loff_t pos)
 {
 	struct cs4281 *chip = entry->private_data;
 	
-	if (copy_to_user_fromio(buf, chip->ba0 + pos, count))
-		return -EFAULT;
+	memcpy_fromio(buf, chip->ba0 + pos, count);
 	return count;
 }
 
 static ssize_t snd_cs4281_BA1_read(struct snd_info_entry *entry,
 				   void *file_private_data,
-				   struct file *file, char __user *buf,
+				   struct file *file, char *buf,
 				   size_t count, loff_t pos)
 {
 	struct cs4281 *chip = entry->private_data;
 	
-	if (copy_to_user_fromio(buf, chip->ba1 + pos, count))
-		return -EFAULT;
+	memcpy_fromio(buf, chip->ba1 + pos, count);
 	return count;
 }
 
diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c
index efc7df444cb5..14e148e670ac 100644
--- a/sound/pci/cs46xx/cs46xx_lib.c
+++ b/sound/pci/cs46xx/cs46xx_lib.c
@@ -2741,13 +2741,12 @@ static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) { }
 
 static ssize_t snd_cs46xx_io_read(struct snd_info_entry *entry,
 				  void *file_private_data,
-				  struct file *file, char __user *buf,
+				  struct file *file, char *buf,
 				  size_t count, loff_t pos)
 {
 	struct snd_cs46xx_region *region = entry->private_data;
 	
-	if (copy_to_user_fromio(buf, region->remap_addr + pos, count))
-		return -EFAULT;
+	memcpy_fromio(buf, region->remap_addr + pos, count);
 	return count;
 }
 
diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c
index f6186b5be049..acacce976415 100644
--- a/sound/pci/emu10k1/emuproc.c
+++ b/sound/pci/emu10k1/emuproc.c
@@ -382,7 +382,7 @@ static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry,
 
 static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry,
 				       void *file_private_data,
-				       struct file *file, char __user *buf,
+				       struct file *file, char *buf,
 				       size_t count, loff_t pos)
 {
 	struct snd_emu10k1 *emu = entry->private_data;
@@ -415,10 +415,8 @@ static ssize_t snd_emu10k1_fx8010_read(struct snd_info_entry *entry,
 		}
 		tmp[idx] = val;
 	}
-	if (copy_to_user(buf, ((char *)tmp) + (pos & 3), count))
-		res = -EFAULT;
-	else
-		res = count;
+	memcpy(buf, ((char *)tmp) + (pos & 3), count);
+	res = count;
 	kfree(tmp);
 	return res;
 }
diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c
index f451554cff5f..c44001f0d320 100644
--- a/sound/pci/mixart/mixart.c
+++ b/sound/pci/mixart/mixart.c
@@ -1110,14 +1110,13 @@ static int snd_mixart_free(struct mixart_mgr *mgr)
  */
 static ssize_t snd_mixart_BA0_read(struct snd_info_entry *entry,
 				   void *file_private_data,
-				   struct file *file, char __user *buf,
+				   struct file *file, char *buf,
 				   size_t count, loff_t pos)
 {
 	struct mixart_mgr *mgr = entry->private_data;
 
 	count = count & ~3; /* make sure the read size is a multiple of 4 bytes */
-	if (copy_to_user_fromio(buf, MIXART_MEM(mgr, pos), count))
-		return -EFAULT;
+	memcpy_fromio(buf, MIXART_MEM(mgr, pos), count);
 	return count;
 }
 
@@ -1126,14 +1125,13 @@ static ssize_t snd_mixart_BA0_read(struct snd_info_entry *entry,
  */
 static ssize_t snd_mixart_BA1_read(struct snd_info_entry *entry,
 				   void *file_private_data,
-				   struct file *file, char __user *buf,
+				   struct file *file, char *buf,
 				   size_t count, loff_t pos)
 {
 	struct mixart_mgr *mgr = entry->private_data;
 
 	count = count & ~3; /* make sure the read size is a multiple of 4 bytes */
-	if (copy_to_user_fromio(buf, MIXART_REG(mgr, pos), count))
-		return -EFAULT;
+	memcpy_fromio(buf, MIXART_REG(mgr, pos), count);
 	return count;
 }
 
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c
index ed5fe746d438..48557235c389 100644
--- a/sound/usb/mixer_scarlett2.c
+++ b/sound/usb/mixer_scarlett2.c
@@ -9763,7 +9763,7 @@ static ssize_t scarlett2_devmap_read(
 	struct snd_info_entry *entry,
 	void                  *file_private_data,
 	struct file           *file,
-	char __user           *buf,
+	char                  *buf,
 	size_t                 count,
 	loff_t                 pos)
 {
@@ -9785,7 +9785,7 @@ static ssize_t scarlett2_devmap_read(
 	while (count > 0) {
 		/* SCARLETT2_USB_GET_DEVMAP reads only on block boundaries,
 		 * so we need to read a whole block and copy the requested
-		 * chunk to userspace.
+		 * chunk out of it.
 		 */
 
 		__le32 req;
@@ -9800,7 +9800,7 @@ static ssize_t scarlett2_devmap_read(
 					 block_size,
 					 entry->size - block_start);
 
-		/* size of the chunk to copy to userspace */
+		/* size of the chunk to copy out of the block */
 		size_t copy_size = min_t(size_t, count, read_size - offset);
 
 		/* request the block */
@@ -9810,8 +9810,7 @@ static ssize_t scarlett2_devmap_read(
 		if (err < 0)
 			return copied ? copied : err;
 
-		if (copy_to_user(buf, resp_buf + offset, copy_size))
-			return -EFAULT;
+		memcpy(buf, resp_buf + offset, copy_size);
 
 		buf += copy_size;
 		pos += copy_size;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 25/26] ALSA: info: drop the ioctl hook of data entries
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (23 preceding siblings ...)
  2026-11-04 19:48   ` [PATCH RESEND v2 24/26] ALSA: info: let procfs copy out reads of data entries Christian Brauner
@ 2026-11-04 19:48   ` Christian Brauner
  2026-11-04 19:48   ` [PATCH RESEND v2 26/26] ALSA: info: drop the poll_table from the info entry poll op Christian Brauner
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

No driver implements ->ioctl() of struct snd_info_entry_ops.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 include/sound/info.h |  2 --
 sound/core/info.c    | 13 -------------
 2 files changed, 15 deletions(-)

diff --git a/include/sound/info.h b/include/sound/info.h
index 67378da4d36c..d6cb0346f77d 100644
--- a/include/sound/info.h
+++ b/include/sound/info.h
@@ -50,8 +50,6 @@ struct snd_info_entry_ops {
 	__poll_t (*poll)(struct snd_info_entry *entry,
 			     void *file_private_data, struct file *file,
 			     poll_table *wait);
-	int (*ioctl)(struct snd_info_entry *entry, void *file_private_data,
-		     struct file *file, unsigned int cmd, unsigned long arg);
 	int (*mmap)(struct snd_info_entry *entry, void *file_private_data,
 		    struct inode *inode, struct file *file,
 		    struct vm_area_struct *vma);
diff --git a/sound/core/info.c b/sound/core/info.c
index 663ecb96f828..f837cfbe7313 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -201,18 +201,6 @@ static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait)
 	return mask;
 }
 
-static long snd_info_entry_ioctl(struct file *file, unsigned int cmd,
-				unsigned long arg)
-{
-	struct snd_info_private_data *data = file->private_data;
-	struct snd_info_entry *entry = data->entry;
-
-	if (!entry->c.ops->ioctl)
-		return -ENOTTY;
-	return entry->c.ops->ioctl(entry, data->file_private_data,
-				   file, cmd, arg);
-}
-
 static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	struct inode *inode = file_inode(file);
@@ -283,7 +271,6 @@ static const struct proc_ops snd_info_entry_operations =
 	.proc_write_buf	= snd_info_entry_write,
 	.proc_write_size = PAGE_SIZE,
 	.proc_poll	= snd_info_entry_poll,
-	.proc_ioctl	= snd_info_entry_ioctl,
 	.proc_mmap	= snd_info_entry_mmap,
 	.proc_open	= snd_info_entry_open,
 	.proc_release	= snd_info_entry_release,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 26/26] ALSA: info: drop the poll_table from the info entry poll op
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
                     ` (24 preceding siblings ...)
  2026-11-04 19:48   ` [PATCH RESEND v2 25/26] ALSA: info: drop the ioctl hook " Christian Brauner
@ 2026-11-04 19:48   ` Christian Brauner
  25 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:48 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

procfs owns the wait queue a /proc/asound file is polled on and wakes
the pollers through proc_poll_notify(), so the poll op of an info entry
has nothing to do with the poll_table anymore and only reports the
mask. Nothing in the tree implements the op.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 include/sound/info.h | 3 +--
 sound/core/info.c    | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/include/sound/info.h b/include/sound/info.h
index d6cb0346f77d..327d92059f67 100644
--- a/include/sound/info.h
+++ b/include/sound/info.h
@@ -48,8 +48,7 @@ struct snd_info_entry_ops {
 			 void *file_private_data, struct file *file,
 			 loff_t offset, int orig);
 	__poll_t (*poll)(struct snd_info_entry *entry,
-			     void *file_private_data, struct file *file,
-			     poll_table *wait);
+			     void *file_private_data, struct file *file);
 	int (*mmap)(struct snd_info_entry *entry, void *file_private_data,
 		    struct inode *inode, struct file *file,
 		    struct vm_area_struct *vma);
diff --git a/sound/core/info.c b/sound/core/info.c
index f837cfbe7313..c8735a6ec789 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -193,7 +193,7 @@ static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait)
 	if (entry->c.ops->poll)
 		return entry->c.ops->poll(entry,
 					  data->file_private_data,
-					  file, wait);
+					  file);
 	if (entry->c.ops->read)
 		mask |= EPOLLIN | EPOLLRDNORM;
 	if (entry->c.ops->write)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7)
  2026-11-04 19:04 [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does Christian Brauner
                   ` (3 preceding siblings ...)
  2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
@ 2026-11-04 19:49 ` Christian Brauner
  2026-11-04 19:49   ` [PATCH RESEND v2 01/30] jbd2: let procfs own the seq_file of the info file Christian Brauner
                     ` (30 more replies)
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
  5 siblings, 31 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:49 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Part 5/7 of "proc: move procfs to what kernfs does", on top of part 2
and independent of parts 3, 4 and 6:

  https://patch.msgid.link/20261104-work-procfs-v2-0-b45cd5789ac5@brauner

This converts the proc entries of jbd2, jfs, lockd, nfsd, ntfs3 and
cifs, the irq affinity files, latencytop, lockdep, /proc/profile,
/proc/pressure, kallsyms, /proc/modules, dynamic_debug, sysvipc,
allocinfo, /proc/swaps and /proc/config.gz to what "proc: let procfs own
the data path" set up. Afterwards the entries' code only runs with the
entry pinned and never touches userspace memory. One patch per subsystem
and method.

- procfs owns the seq_file of jbd2's info file, /proc/fs/nfs/exports,
  the smbdirect files, /proc/lock_stat, the pressure files,
  /proc/kallsyms, /proc/modules, the sysvipc files, /proc/allocinfo and
  /proc/swaps. Their open and release hooks only do what is left, a
  snapshot, an iterator or a reference, and reading one of them no
  longer holds up its removal.
- procfs copies in the writes of jfs's loglevel, nlm_end_grace, ntfs3's
  label, the cifs files, the irq affinity masks, /proc/latency_stats,
  /proc/lock_stat, /proc/profile, the pressure triggers and
  dynamic_debug's control. Most of them just parse procfs' copy, the
  affinity masks and dynamic_debug ask for a larger write size. Where a
  write larger than anything the entry parses now fails with -EINVAL
  from procfs the commit says so.
- procfs copies out the reads of nlm_end_grace, /proc/profile and
  /proc/config.gz. readprofile reads all of /proc/profile in one go and
  so does whoever reads /proc/config.gz, both ask for
  PROC_ENTRY_FULL_READS so that reading all of them at once keeps
  working.
- procfs copies the ioctl arguments of /proc/allocinfo in and out and
  converts the pointers of 32-bit tasks, so the compat ioctl goes away.
- /proc/profile and /proc/pressure/* are never removed and are marked
  permanent, procfs calls into them without pinning.
- /proc/swaps and the pressure files move their pollers to the wait
  queue procfs owns for the entry. A pressure file has one queue for all
  its triggers then, like the cgroup pressure files have in kernfs, so
  every poller of the file is woken when any trigger on it fires and
  each reports the state of its own trigger. That's a few extra wakeups
  for the daemons that poll these files, I don't think it matters.

The selftest unloads configs.ko while a reader of /proc/config.gz faults
in procfs' copy to userspace. 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. The proc and allocinfo selftests
behave the same with and without the series and so do the entries I
could get at in a VM.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- psi and swapfile let procfs own the wait queues of the pressure files
  and /proc/swaps, a pressure file has one queue for all its triggers
- /proc/profile reads go through procfs now like everything else, with
  PROC_ENTRY_FULL_READS; v1 left it reading straight into userspace
- Acked-by from Chuck on lockd and nfsd
- the selftest commit applies on the core series alone
- rebased onto v7.4-rc1
- Link to v1: https://patch.msgid.link/20261102-work-procfs-fs-v1-0-d09a996317b3@kernel.org

---
Christian Brauner (30):
      jbd2: let procfs own the seq_file of the info file
      jfs: let procfs copy in writes to the loglevel file
      lockd: let procfs copy in writes to nlm_end_grace
      lockd: let procfs copy out reads of nlm_end_grace
      nfsd: factor out setting up the exports seq_file
      nfsd: let procfs own the seq_file of /proc/fs/nfs/exports
      ntfs3: let procfs copy in writes to the label file
      smb: client: let procfs own the seq_file of the smbdirect files
      smb: client: let procfs copy in writes
      genirq/proc: let procfs copy in the affinity masks
      latencytop: let procfs copy in writes to /proc/latency_stats
      locking/lockdep: let procfs own the seq_file of /proc/lock_stat
      locking/lockdep: let procfs copy in writes to /proc/lock_stat
      profiling: let procfs copy in writes to /proc/profile
      profiling: let procfs copy out reads of /proc/profile
      profiling: mark /proc/profile permanent
      sched/psi: let procfs own the seq_file of the pressure files
      sched/psi: let procfs copy in trigger writes
      sched/psi: mark the pressure files permanent
      sched/psi: let procfs own the wait queue of the pressure files
      kallsyms: let procfs own the seq_file of /proc/kallsyms
      module: procfs: let procfs own the seq_file of /proc/modules
      dyndbg: let procfs copy in writes to /proc/dynamic_debug/control
      ipc: let procfs own the seq_file of the sysvipc files
      alloc_tag: let procfs own the seq_file of /proc/allocinfo
      alloc_tag: let procfs copy the ioctl arguments of /proc/allocinfo
      mm/swapfile: let procfs own the seq_file of /proc/swaps
      mm/swapfile: let procfs own the wait queue of /proc/swaps
      kernel/configs: let procfs copy out reads of /proc/config.gz
      selftests/proc: check that unloading configs doesn't wait for a reader

 fs/jbd2/journal.c                                  |  33 ++---
 fs/jfs/jfs_debug.c                                 |  11 +-
 fs/lockd/procfs.c                                  |  19 +--
 fs/nfsd/nfsctl.c                                   |  41 ++++--
 fs/ntfs3/super.c                                   |  27 ++--
 fs/smb/client/cifs_debug.c                         |  71 ++++------
 fs/smb/client/dfs_cache.c                          |  13 +-
 include/linux/psi.h                                |   3 +-
 include/linux/psi_types.h                          |   8 +-
 ipc/util.c                                         |  14 +-
 kernel/cgroup/cgroup.c                             |   2 +-
 kernel/configs.c                                   |   7 +-
 kernel/irq/proc.c                                  |  27 ++--
 kernel/kallsyms.c                                  |  12 +-
 kernel/latencytop.c                                |   5 +-
 kernel/locking/lockdep_proc.c                      |  60 ++++----
 kernel/module/procfs.c                             |  19 +--
 kernel/profile.c                                   |  22 ++-
 kernel/sched/psi.c                                 | 112 ++++++---------
 lib/dynamic_debug.c                                |  34 +++--
 mm/alloc_tag.c                                     |  75 ++++------
 mm/swapfile.c                                      |  23 +--
 tools/testing/selftests/proc/.gitignore            |   1 +
 tools/testing/selftests/proc/Makefile              |   1 +
 tools/testing/selftests/proc/config                |   2 +
 .../proc/proc-remove-slow-config-reader.c          | 157 +++++++++++++++++++++
 26 files changed, 430 insertions(+), 369 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261101-work-procfs-fs-41f9fdc489cb
prerequisite-change-id: 20261101-work-procfs-core-8fa4a4f412cf:v2
prerequisite-patch-id: a81e8b894107e48d607f8257d2c9f56e0dd6f400
prerequisite-patch-id: 7edf7ca7ecd32c9e09085e4b65922af10097beaa
prerequisite-patch-id: 81f6893f79b4236637675553298a6f2892b603f3
prerequisite-patch-id: 598c2b1a389f47adcac88ebcf1c9f74e2511fbab
prerequisite-patch-id: 6f5a2172dacc5ff2491fa9832c48ccd0e1ce1eda
prerequisite-patch-id: 8947af6490288e4e2a3b2bf8844a974bedb1d975
prerequisite-patch-id: 8cbe756286fd95967d31b0c46cacfeb05f0a2c6b
prerequisite-patch-id: 3fa159f2b6ccd0ac51a2662e4eec5587ec18de89
prerequisite-patch-id: bfb4e4ffe9631cb58015fefe6bd3698cd53e2244
prerequisite-patch-id: 56ff6667a57a962264c07c0227c61aa2f1f4c22c
prerequisite-patch-id: bc506323e29944ec0c29eae84950c706a8592042
prerequisite-patch-id: 326c0ebb5accf0f40cbee1323fb4c3378f5be154
prerequisite-patch-id: d6eaf7690a54a0dfda7500936a048b769fa265dd
prerequisite-patch-id: 7c7b2535642b4d388e13eab518cbe0f9550eb1e6
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: f6d4191e23c22e2d2cc90cc5d04781f913e98a8e
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: 43b92dfc1a98fbf0d14597a3e3ae99972844773b
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: d58c239fba616b366a725de6acba3f88734156d0
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: 5348d8957eb7eae7430ca8b101aa84ce77d3aa4f
prerequisite-patch-id: 36c96d781e2dd80b20932667bbe6e7fd14976069
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: 17f95c7fdf0bdc7c8aaaf7883bff1dcc19a0f529
prerequisite-patch-id: 117db27dcdef433de7c6be5e95973cac1e373927
prerequisite-patch-id: a0844aa26ffb65ccb362b88ef6c2f4758e15edbf
prerequisite-patch-id: 9c64355dfde62e399f2717d9208c4607834d4650
prerequisite-patch-id: 9644739854ddfe2876f17fda8727fd5119255957
prerequisite-patch-id: c61bba24c035f79a2a32a80ace0924ab58e97f46
prerequisite-patch-id: 143fb91bddf33068b6ee11e205818c4c2815e23d
prerequisite-patch-id: 9ce9be2ff05250f92d945c0084f4bb31d3d0b27d
prerequisite-patch-id: aee0961341523547095a43ae298cb8b551302f10
prerequisite-patch-id: 2f0fdb970ddd760d85ff5d2caad0b96348270182
prerequisite-patch-id: f2aa468ff5cbb1cabab3f70e1b8eeee0286cfb4a
prerequisite-patch-id: 5555895bcbca4d7c9888ecc9e82c0e4d76da8f40
prerequisite-patch-id: 487141c49e2548310bfb6c2a0964a78f08b285a9
prerequisite-patch-id: c3d3008819fa5629a18142ec9ba6d7024ade7fdb
prerequisite-patch-id: ca587c976690ac16e3d759fccab2116fef0508e8
prerequisite-patch-id: 9423f410b99853693db06c29f12f1c08278b050d
prerequisite-patch-id: e269815617a6324615a38916b8886f99d98c641e
prerequisite-patch-id: 3d24be1897a624d519ef144801f76d152c7528a9
prerequisite-patch-id: 1ea3fc8319d9012faaf3a182ac61d5da89aafdb7
prerequisite-patch-id: 7547b4e2c1b3588acf6caf90cc01fe7884fdb6bd
prerequisite-patch-id: 695d325349ebeb89bf0ea24bcf0c4f1c4edacab5
prerequisite-patch-id: e33502a202979aba0d6fdaf196e8471d906992a1
prerequisite-patch-id: 47bfe4f07677ad75fe8d9accc8923a1cbdf3afa6
prerequisite-patch-id: 9fc0739226572203fcb36f2c271be7b27f405ef2


^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 01/30] jbd2: let procfs own the seq_file of the info file
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
@ 2026-11-04 19:49   ` Christian Brauner
  2026-11-04 19:50   ` [PATCH RESEND v2 02/30] jfs: let procfs copy in writes to the loglevel file Christian Brauner
                     ` (29 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:49 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and the size of the session state and let
procfs open and free the seq_file of /proc/fs/jbd2/<dev>/info.
->proc_open() only takes the snapshot of the statistics now and
->proc_release() frees it. Reading the file then no longer holds up its
removal when the journal goes away.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/jbd2/journal.c | 33 +++++++++------------------------
 1 file changed, 9 insertions(+), 24 deletions(-)

diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c
index 3a4f3f613ead..1e4f4e95a00e 100644
--- a/fs/jbd2/journal.c
+++ b/fs/jbd2/journal.c
@@ -1173,51 +1173,36 @@ static const struct seq_operations jbd2_seq_info_ops = {
 	.show   = jbd2_seq_info_show,
 };
 
+/* procfs opened the seq_file and allocated @s */
 static int jbd2_seq_info_open(struct inode *inode, struct file *file)
 {
 	journal_t *journal = pde_data(inode);
-	struct jbd2_stats_proc_session *s;
-	int rc, size;
+	struct jbd2_stats_proc_session *s = ((struct seq_file *)file->private_data)->private;
+	int size = sizeof(struct transaction_stats_s);
 
-	s = kmalloc_obj(*s);
-	if (s == NULL)
-		return -ENOMEM;
-	size = sizeof(struct transaction_stats_s);
 	s->stats = kmalloc(size, GFP_KERNEL);
-	if (s->stats == NULL) {
-		kfree(s);
+	if (s->stats == NULL)
 		return -ENOMEM;
-	}
 	spin_lock(&journal->j_history_lock);
 	memcpy(s->stats, &journal->j_stats, size);
 	s->journal = journal;
 	spin_unlock(&journal->j_history_lock);
-
-	rc = seq_open(file, &jbd2_seq_info_ops);
-	if (rc == 0) {
-		struct seq_file *m = file->private_data;
-		m->private = s;
-	} else {
-		kfree(s->stats);
-		kfree(s);
-	}
-	return rc;
-
+	return 0;
 }
 
 static int jbd2_seq_info_release(struct inode *inode, struct file *file)
 {
 	struct seq_file *seq = file->private_data;
 	struct jbd2_stats_proc_session *s = seq->private;
+
 	kfree(s->stats);
-	kfree(s);
-	return seq_release(inode, file);
+	return 0;
 }
 
 static const struct proc_ops jbd2_info_proc_ops = {
+	.proc_seq_ops	= &jbd2_seq_info_ops,
+	.proc_state_size = sizeof(struct jbd2_stats_proc_session),
 	.proc_open	= jbd2_seq_info_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
 	.proc_release	= jbd2_seq_info_release,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 02/30] jfs: let procfs copy in writes to the loglevel file
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
  2026-11-04 19:49   ` [PATCH RESEND v2 01/30] jbd2: let procfs own the seq_file of the info file Christian Brauner
@ 2026-11-04 19:50   ` Christian Brauner
  2026-11-04 19:50   ` [PATCH RESEND v2 03/30] lockd: let procfs copy in writes to nlm_end_grace Christian Brauner
                     ` (28 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:50 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and take the level from that copy. Writes larger than
PAGE_SIZE - 1 bytes fail with -EINVAL now and so do empty writes, which
looked at a byte that wasn't written before.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/jfs/jfs_debug.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/fs/jfs/jfs_debug.c b/fs/jfs/jfs_debug.c
index 5eb35b0d420f..e1c0211a6a32 100644
--- a/fs/jfs/jfs_debug.c
+++ b/fs/jfs/jfs_debug.c
@@ -23,13 +23,10 @@ static int jfs_loglevel_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t jfs_loglevel_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *ppos)
+static ssize_t jfs_loglevel_proc_write(struct file *file, char *buffer,
+		size_t count, loff_t *ppos)
 {
-	char c;
-
-	if (get_user(c, buffer))
-		return -EFAULT;
+	char c = buffer[0];
 
 	/* yes, I know this is an ASCIIism.  --hch */
 	if (c < '0' || c > '9')
@@ -40,7 +37,7 @@ static ssize_t jfs_loglevel_proc_write(struct file *file,
 
 static const struct proc_ops jfs_loglevel_proc_ops = {
 	.proc_show	= jfs_loglevel_proc_show,
-	.proc_write	= jfs_loglevel_proc_write,
+	.proc_write_buf	= jfs_loglevel_proc_write,
 };
 #endif
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 03/30] lockd: let procfs copy in writes to nlm_end_grace
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
  2026-11-04 19:49   ` [PATCH RESEND v2 01/30] jbd2: let procfs own the seq_file of the info file Christian Brauner
  2026-11-04 19:50   ` [PATCH RESEND v2 02/30] jfs: let procfs copy in writes to the loglevel file Christian Brauner
@ 2026-11-04 19:50   ` Christian Brauner
  2026-11-04 19:50   ` [PATCH RESEND v2 04/30] lockd: let procfs copy out reads of nlm_end_grace Christian Brauner
                     ` (27 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:50 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry. simple_transaction_get() was only used to get a copy
of what was written, so look at procfs' copy instead and drop the
transaction. A second write to the same file doesn't fail with -EBUSY
anymore. Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL from
procfs instead of -EFBIG from simple_transaction_get(), which refused a
few bytes less.

Acked-by: Chuck Lever <cel@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/lockd/procfs.c | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/fs/lockd/procfs.c b/fs/lockd/procfs.c
index 74f7ab77535a..852cca8c3588 100644
--- a/fs/lockd/procfs.c
+++ b/fs/lockd/procfs.c
@@ -18,21 +18,15 @@
  * We only allow strings that start with 'Y', 'y', or '1'.
  */
 static ssize_t
-nlm_end_grace_write(struct file *file, const char __user *buf, size_t size,
-		    loff_t *pos)
+nlm_end_grace_write(struct file *file, char *buf, size_t size, loff_t *pos)
 {
-	char *data;
 	struct lockd_net *ln = net_generic(current->nsproxy->net_ns,
 					   lockd_net_id);
 
 	if (size < 1)
 		return -EINVAL;
 
-	data = simple_transaction_get(file, buf, size);
-	if (IS_ERR(data))
-		return PTR_ERR(data);
-
-	switch(data[0]) {
+	switch(buf[0]) {
 	case 'Y':
 	case 'y':
 	case '1':
@@ -61,10 +55,9 @@ nlm_end_grace_read(struct file *file, char __user *buf, size_t size,
 }
 
 static const struct proc_ops lockd_end_grace_proc_ops = {
-	.proc_write	= nlm_end_grace_write,
+	.proc_write_buf	= nlm_end_grace_write,
 	.proc_read	= nlm_end_grace_read,
 	.proc_lseek	= default_llseek,
-	.proc_release	= simple_transaction_release,
 };
 
 int __init

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 04/30] lockd: let procfs copy out reads of nlm_end_grace
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (2 preceding siblings ...)
  2026-11-04 19:50   ` [PATCH RESEND v2 03/30] lockd: let procfs copy in writes to nlm_end_grace Christian Brauner
@ 2026-11-04 19:50   ` Christian Brauner
  2026-11-04 19:50   ` [PATCH RESEND v2 05/30] nfsd: factor out setting up the exports seq_file Christian Brauner
                     ` (26 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:50 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read to userspace
after it unpinned the entry.

Acked-by: Chuck Lever <cel@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/lockd/procfs.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/fs/lockd/procfs.c b/fs/lockd/procfs.c
index 852cca8c3588..64195c564cd3 100644
--- a/fs/lockd/procfs.c
+++ b/fs/lockd/procfs.c
@@ -40,7 +40,7 @@ nlm_end_grace_write(struct file *file, char *buf, size_t size, loff_t *pos)
 }
 
 static ssize_t
-nlm_end_grace_read(struct file *file, char __user *buf, size_t size,
+nlm_end_grace_read(struct file *file, char *buf, size_t size,
 		   loff_t *pos)
 {
 	struct lockd_net *ln = net_generic(current->nsproxy->net_ns,
@@ -51,12 +51,12 @@ nlm_end_grace_read(struct file *file, char __user *buf, size_t size,
 	resp[1] = '\n';
 	resp[2] = '\0';
 
-	return simple_read_from_buffer(buf, size, pos, resp, sizeof(resp) - 1);
+	return memory_read_from_buffer(buf, size, pos, resp, sizeof(resp) - 1);
 }
 
 static const struct proc_ops lockd_end_grace_proc_ops = {
 	.proc_write_buf	= nlm_end_grace_write,
-	.proc_read	= nlm_end_grace_read,
+	.proc_read_buf	= nlm_end_grace_read,
 	.proc_lseek	= default_llseek,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 05/30] nfsd: factor out setting up the exports seq_file
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (3 preceding siblings ...)
  2026-11-04 19:50   ` [PATCH RESEND v2 04/30] lockd: let procfs copy out reads of nlm_end_grace Christian Brauner
@ 2026-11-04 19:50   ` Christian Brauner
  2026-11-04 19:51   ` [PATCH RESEND v2 06/30] nfsd: let procfs own the seq_file of /proc/fs/nfs/exports Christian Brauner
                     ` (25 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:50 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Move setting up ->private and taking the reference to the network
namespace into exports_net_init() and dropping it into
exports_net_put(). The procfs file needs them without the seq_open()
and seq_release() around them.

No functional changes.

Acked-by: Chuck Lever <cel@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/nfsd/nfsctl.c | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index f32311f2d7cf..f8ce3c0fb077 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -143,28 +143,36 @@ static const struct file_operations transaction_ops = {
 	.llseek		= default_llseek,
 };
 
+static void exports_net_init(struct net *net, struct seq_file *seq)
+{
+	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+
+	seq->private = nn->svc_export_cache;
+	get_net(net);
+}
+
+static void exports_net_put(struct seq_file *seq)
+{
+	struct cache_detail *cd = seq->private;
+
+	put_net(cd->net);
+}
+
 static int exports_net_open(struct net *net, struct file *file)
 {
 	int err;
-	struct seq_file *seq;
-	struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
 	err = seq_open(file, &nfs_exports_op);
 	if (err)
 		return err;
 
-	seq = file->private_data;
-	seq->private = nn->svc_export_cache;
-	get_net(net);
+	exports_net_init(net, file->private_data);
 	return 0;
 }
 
 static int exports_release(struct inode *inode, struct file *file)
 {
-	struct seq_file *seq = file->private_data;
-	struct cache_detail *cd = seq->private;
-
-	put_net(cd->net);
+	exports_net_put(file->private_data);
 	return seq_release(inode, file);
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 06/30] nfsd: let procfs own the seq_file of /proc/fs/nfs/exports
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (4 preceding siblings ...)
  2026-11-04 19:50   ` [PATCH RESEND v2 05/30] nfsd: factor out setting up the exports seq_file Christian Brauner
@ 2026-11-04 19:51   ` Christian Brauner
  2026-11-04 19:51   ` [PATCH RESEND v2 07/30] ntfs3: let procfs copy in writes to the label file Christian Brauner
                     ` (24 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:51 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and let procfs open and free the seq_file.
->proc_open() and ->proc_release() only set up ->private and take and
drop the reference to the network namespace now. Reading the file then
no longer holds up its removal.

Acked-by: Chuck Lever <cel@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/nfsd/nfsctl.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index f8ce3c0fb077..5b2782f9dbbd 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -1407,16 +1407,23 @@ MODULE_ALIAS_FS("nfsd");
 
 #ifdef CONFIG_PROC_FS
 
+/* procfs opens the seq_file */
 static int exports_proc_open(struct inode *inode, struct file *file)
 {
-	return exports_net_open(current->nsproxy->net_ns, file);
+	exports_net_init(current->nsproxy->net_ns, file->private_data);
+	return 0;
+}
+
+static int exports_proc_release(struct inode *inode, struct file *file)
+{
+	exports_net_put(file->private_data);
+	return 0;
 }
 
 static const struct proc_ops exports_proc_ops = {
+	.proc_seq_ops	= &nfs_exports_op,
 	.proc_open	= exports_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= exports_release,
+	.proc_release	= exports_proc_release,
 };
 
 static int create_proc_exports_entry(void)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 07/30] ntfs3: let procfs copy in writes to the label file
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (5 preceding siblings ...)
  2026-11-04 19:51   ` [PATCH RESEND v2 06/30] nfsd: let procfs own the seq_file of /proc/fs/nfs/exports Christian Brauner
@ 2026-11-04 19:51   ` Christian Brauner
  2026-11-04 19:51   ` [PATCH RESEND v2 08/30] smb: client: let procfs own the seq_file of the smbdirect files Christian Brauner
                     ` (23 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:51 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and set the label from that copy. Writes larger than
PAGE_SIZE - 1 bytes fail with -EINVAL from procfs now. A write that
large only ever worked when most of it was trailing newlines or came
after a NUL, ntfs_set_label() refused a label that long with
-ENAMETOOLONG or -EFBIG.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/ntfs3/super.c | 27 +++++++++------------------
 1 file changed, 9 insertions(+), 18 deletions(-)

diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index cd47839872b0..cdeccbe506e8 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -560,37 +560,28 @@ static int ntfs3_label_show(struct seq_file *m, void *o)
 }
 
 /* write /proc/fs/ntfs3/<dev>/label */
-static ssize_t ntfs3_label_write(struct file *file, const char __user *buffer,
-				 size_t count, loff_t *ppos)
+static ssize_t ntfs3_label_write(struct file *file, char *buf, size_t count,
+				 loff_t *ppos)
 {
 	int err;
 	struct super_block *sb = pde_data(file_inode(file));
-	ssize_t ret = count;
-	u8 *label;
+	size_t len = count;
 
 	if (sb_rdonly(sb))
 		return -EROFS;
 
-	label = memdup_user(buffer, count);
-	if (IS_ERR(label))
-		return PTR_ERR(label);
+	while (len > 0 && buf[len - 1] == '\n')
+		len -= 1;
 
-	while (ret > 0 && label[ret - 1] == '\n')
-		ret -= 1;
-
-	err = ntfs_set_label(sb->s_fs_info, label, ret);
+	err = ntfs_set_label(sb->s_fs_info, (u8 *)buf, len);
 
 	if (err < 0) {
 		ntfs_err(sb, "failed (%d) to write label", err);
-		ret = err;
-		goto out;
+		return err;
 	}
 
 	*ppos += count;
-	ret = count;
-out:
-	kfree(label);
-	return ret;
+	return count;
 }
 
 static const struct proc_ops ntfs3_volinfo_fops = {
@@ -599,7 +590,7 @@ static const struct proc_ops ntfs3_volinfo_fops = {
 
 static const struct proc_ops ntfs3_label_fops = {
 	.proc_show = ntfs3_label_show,
-	.proc_write = ntfs3_label_write,
+	.proc_write_buf = ntfs3_label_write,
 };
 
 static void ntfs_create_procdir(struct super_block *sb)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 08/30] smb: client: let procfs own the seq_file of the smbdirect files
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (6 preceding siblings ...)
  2026-11-04 19:51   ` [PATCH RESEND v2 07/30] ntfs3: let procfs copy in writes to the label file Christian Brauner
@ 2026-11-04 19:51   ` Christian Brauner
  2026-11-04 19:51   ` [PATCH RESEND v2 09/30] smb: client: let procfs copy in writes Christian Brauner
                     ` (22 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:51 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the show function in PROC_FILE_DEFINE() and let procfs open and
free the seq_file. That makes the open functions unnecessary.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/smb/client/cifs_debug.c | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index dfcd2cdbb2a8..664072ab3342 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -873,16 +873,9 @@ static int name##_proc_show(struct seq_file *m, void *v) \
 	seq_printf(m, "%d\n", name); \
 	return 0; \
 } \
-static int name##_open(struct inode *inode, struct file *file) \
-{ \
-	return single_open(file, name##_proc_show, NULL); \
-} \
 \
 static const struct proc_ops cifs_##name##_proc_fops = { \
-	.proc_open	= name##_open, \
-	.proc_read	= seq_read, \
-	.proc_lseek	= seq_lseek, \
-	.proc_release	= single_release, \
+	.proc_show	= name##_proc_show, \
 	.proc_write	= name##_write, \
 }
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 09/30] smb: client: let procfs copy in writes
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (7 preceding siblings ...)
  2026-11-04 19:51   ` [PATCH RESEND v2 08/30] smb: client: let procfs own the seq_file of the smbdirect files Christian Brauner
@ 2026-11-04 19:51   ` Christian Brauner
  2026-11-04 19:52   ` [PATCH RESEND v2 10/30] genirq/proc: let procfs copy in the affinity masks Christian Brauner
                     ` (21 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:51 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for the files in /proc/fs/cifs so procfs
copies in what is written before it pins the entry, and parse what was
written from that copy. Writes larger than PAGE_SIZE - 1 bytes fail with
-EINVAL now, SecurityFlags never took more than 11 bytes. Empty writes
to cifsFYI and dfscache fail with -EINVAL too, they looked at a byte
that wasn't written before. kstrtoint_from_user() looked at the first
34 bytes of a write to cifs_debug_dirs and the smbdirect files, now the
whole write is parsed.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 fs/smb/client/cifs_debug.c | 62 +++++++++++++++++++---------------------------
 fs/smb/client/dfs_cache.c  | 13 +++-------
 2 files changed, 29 insertions(+), 46 deletions(-)

diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 664072ab3342..fb1323bf44c7 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -359,12 +359,12 @@ static int cifs_debug_dirs_proc_show(struct seq_file *m, void *v)
 #ifdef CONFIG_CIFS_DEBUG
 
 /* Drop all cached directory entries across all CIFS mounts. */
-static ssize_t cifs_debug_dirs_proc_write(struct file *file, const char __user *buffer,
+static ssize_t cifs_debug_dirs_proc_write(struct file *file, char *buffer,
 					  size_t count, loff_t *ppos)
 {
 	int rc, v;
 
-	rc = kstrtoint_from_user(buffer, count, 10, &v);
+	rc = kstrtoint(buffer, 10, &v);
 	if (rc)
 		return rc;
 
@@ -390,7 +390,7 @@ static ssize_t cifs_debug_dirs_proc_write(struct file *file, const char __user *
 
 static const struct proc_ops cifs_debug_dirs_proc_ops = {
 	.proc_show	= cifs_debug_dirs_proc_show,
-	.proc_write	= cifs_debug_dirs_proc_write,
+	.proc_write_buf	= cifs_debug_dirs_proc_write,
 };
 #endif /* CONFIG_CIFS_DEBUG */
 
@@ -713,8 +713,8 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t cifs_stats_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *ppos)
+static ssize_t cifs_stats_proc_write(struct file *file, char *buffer,
+		size_t count, loff_t *ppos)
 {
 	bool bv;
 	int rc;
@@ -722,7 +722,7 @@ static ssize_t cifs_stats_proc_write(struct file *file,
 	struct cifs_ses *ses;
 	struct cifs_tcon *tcon;
 
-	rc = kstrtobool_from_user(buffer, count, &bv);
+	rc = kstrtobool(buffer, &bv);
 	if (rc == 0) {
 #ifdef CONFIG_CIFS_STATS2
 		int i;
@@ -854,16 +854,16 @@ static int cifs_stats_proc_show(struct seq_file *m, void *v)
 
 static const struct proc_ops cifs_stats_proc_ops = {
 	.proc_show	= cifs_stats_proc_show,
-	.proc_write	= cifs_stats_proc_write,
+	.proc_write_buf	= cifs_stats_proc_write,
 };
 
 #ifdef CONFIG_CIFS_SMB_DIRECT
 #define PROC_FILE_DEFINE(name) \
-static ssize_t name##_write(struct file *file, const char __user *buffer, \
+static ssize_t name##_write(struct file *file, char *buffer, \
 	size_t count, loff_t *ppos) \
 { \
 	int rc; \
-	rc = kstrtoint_from_user(buffer, count, 10, &name); \
+	rc = kstrtoint(buffer, 10, &name); \
 	if (rc) \
 		return rc; \
 	return count; \
@@ -876,7 +876,7 @@ static int name##_proc_show(struct seq_file *m, void *v) \
 \
 static const struct proc_ops cifs_##name##_proc_fops = { \
 	.proc_show	= name##_proc_show, \
-	.proc_write	= name##_write, \
+	.proc_write_buf	= name##_write, \
 }
 
 PROC_FILE_DEFINE(rdma_readwrite_threshold);
@@ -990,16 +990,12 @@ static int cifsFYI_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t cifsFYI_proc_write(struct file *file, const char __user *buffer,
+static ssize_t cifsFYI_proc_write(struct file *file, char *buffer,
 		size_t count, loff_t *ppos)
 {
-	char c[2] = { '\0' };
+	char c[2] = { buffer[0] };
 	bool bv;
-	int rc;
 
-	rc = get_user(c[0], buffer);
-	if (rc)
-		return rc;
 	if (kstrtobool(c, &bv) == 0)
 		cifsFYI = bv;
 	else if ((c[0] > '1') && (c[0] <= '9'))
@@ -1012,7 +1008,7 @@ static ssize_t cifsFYI_proc_write(struct file *file, const char __user *buffer,
 
 static const struct proc_ops cifsFYI_proc_ops = {
 	.proc_show	= cifsFYI_proc_show,
-	.proc_write	= cifsFYI_proc_write,
+	.proc_write_buf	= cifsFYI_proc_write,
 };
 
 static int cifs_linux_ext_proc_show(struct seq_file *m, void *v)
@@ -1021,12 +1017,12 @@ static int cifs_linux_ext_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t cifs_linux_ext_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *ppos)
+static ssize_t cifs_linux_ext_proc_write(struct file *file, char *buffer,
+		size_t count, loff_t *ppos)
 {
 	int rc;
 
-	rc = kstrtobool_from_user(buffer, count, &linuxExtEnabled);
+	rc = kstrtobool(buffer, &linuxExtEnabled);
 	if (rc)
 		return rc;
 
@@ -1035,7 +1031,7 @@ static ssize_t cifs_linux_ext_proc_write(struct file *file,
 
 static const struct proc_ops cifs_linux_ext_proc_ops = {
 	.proc_show	= cifs_linux_ext_proc_show,
-	.proc_write	= cifs_linux_ext_proc_write,
+	.proc_write_buf	= cifs_linux_ext_proc_write,
 };
 
 static int cifs_lookup_cache_proc_show(struct seq_file *m, void *v)
@@ -1044,12 +1040,12 @@ static int cifs_lookup_cache_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t cifs_lookup_cache_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *ppos)
+static ssize_t cifs_lookup_cache_proc_write(struct file *file, char *buffer,
+		size_t count, loff_t *ppos)
 {
 	int rc;
 
-	rc = kstrtobool_from_user(buffer, count, &lookupCacheEnabled);
+	rc = kstrtobool(buffer, &lookupCacheEnabled);
 	if (rc)
 		return rc;
 
@@ -1058,7 +1054,7 @@ static ssize_t cifs_lookup_cache_proc_write(struct file *file,
 
 static const struct proc_ops cifs_lookup_cache_proc_ops = {
 	.proc_show	= cifs_lookup_cache_proc_show,
-	.proc_write	= cifs_lookup_cache_proc_write,
+	.proc_write_buf	= cifs_lookup_cache_proc_write,
 };
 
 static int traceSMB_proc_show(struct seq_file *m, void *v)
@@ -1067,12 +1063,12 @@ static int traceSMB_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t traceSMB_proc_write(struct file *file, const char __user *buffer,
+static ssize_t traceSMB_proc_write(struct file *file, char *buffer,
 		size_t count, loff_t *ppos)
 {
 	int rc;
 
-	rc = kstrtobool_from_user(buffer, count, &traceSMB);
+	rc = kstrtobool(buffer, &traceSMB);
 	if (rc)
 		return rc;
 
@@ -1081,7 +1077,7 @@ static ssize_t traceSMB_proc_write(struct file *file, const char __user *buffer,
 
 static const struct proc_ops traceSMB_proc_ops = {
 	.proc_show	= traceSMB_proc_show,
-	.proc_write	= traceSMB_proc_write,
+	.proc_write_buf	= traceSMB_proc_write,
 };
 
 static int cifs_security_flags_proc_show(struct seq_file *m, void *v)
@@ -1111,21 +1107,15 @@ cifs_security_flags_handle_must_flags(unsigned int *flags)
 }
 
 static ssize_t cifs_security_flags_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *ppos)
+		char *flags_string, size_t count, loff_t *ppos)
 {
 	int rc;
 	unsigned int flags;
-	char flags_string[12];
 	bool bv;
 
 	if ((count < 1) || (count > 11))
 		return -EINVAL;
 
-	memset(flags_string, 0, sizeof(flags_string));
-
-	if (copy_from_user(flags_string, buffer, count))
-		return -EFAULT;
-
 	if (count < 3) {
 		/* single char or single char followed by null */
 		if (kstrtobool(flags_string, &bv) == 0) {
@@ -1176,7 +1166,7 @@ static ssize_t cifs_security_flags_proc_write(struct file *file,
 
 static const struct proc_ops cifs_security_flags_proc_ops = {
 	.proc_show	= cifs_security_flags_proc_show,
-	.proc_write	= cifs_security_flags_proc_write,
+	.proc_write_buf	= cifs_security_flags_proc_write,
 };
 
 /* To make it easier to debug, can help to show mount params */
diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c
index 58f16cf83d36..a6aa05430b15 100644
--- a/fs/smb/client/dfs_cache.c
+++ b/fs/smb/client/dfs_cache.c
@@ -191,17 +191,10 @@ static int dfscache_proc_show(struct seq_file *m, void *v)
 	return 0;
 }
 
-static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer,
+static ssize_t dfscache_proc_write(struct file *file, char *buffer,
 				   size_t count, loff_t *ppos)
 {
-	char c;
-	int rc;
-
-	rc = get_user(c, buffer);
-	if (rc)
-		return rc;
-
-	if (c != '0')
+	if (buffer[0] != '0')
 		return -EINVAL;
 
 	cifs_dbg(FYI, "clearing dfs cache\n");
@@ -215,7 +208,7 @@ static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer,
 
 const struct proc_ops dfscache_proc_ops = {
 	.proc_show	= dfscache_proc_show,
-	.proc_write	= dfscache_proc_write,
+	.proc_write_buf	= dfscache_proc_write,
 };
 
 #ifdef CONFIG_CIFS_DEBUG2

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 10/30] genirq/proc: let procfs copy in the affinity masks
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (8 preceding siblings ...)
  2026-11-04 19:51   ` [PATCH RESEND v2 09/30] smb: client: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:52   ` Christian Brauner
  2026-11-04 19:52   ` [PATCH RESEND v2 11/30] latencytop: let procfs copy in writes to /proc/latency_stats Christian Brauner
                     ` (20 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:52 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for smp_affinity, smp_affinity_list and
default_smp_affinity so procfs copies in what is written before it pins
the entry, and parse the masks from that copy. A list of CPUs can be
longer than a page on large machines, so the files take as much as
memdup_user_nul() could copy in before. Anything larger fails with
-EINVAL instead of -ENOMEM.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/irq/proc.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
index aa3d5544cfc1..fe683457a088 100644
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -9,6 +9,7 @@
 #include <linux/gfp.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
+#include <linux/slab.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/kernel_stat.h>
@@ -134,8 +135,11 @@ static inline int irq_select_affinity_usr(unsigned int irq)
 }
 #endif
 
+/* lists of CPUs can outgrow a page, take what memdup_user_nul() took */
+#define IRQ_AFFINITY_WRITE_SIZE	(KMALLOC_MAX_SIZE - 1)
+
 static ssize_t write_irq_affinity(int type, struct file *file,
-		const char __user *buffer, size_t count, loff_t *pos)
+		char *buffer, size_t count, loff_t *pos)
 {
 	unsigned int irq = (int)(long)pde_data(file_inode(file));
 	cpumask_var_t new_value;
@@ -148,9 +152,9 @@ static ssize_t write_irq_affinity(int type, struct file *file,
 		return -ENOMEM;
 
 	if (type)
-		err = cpumask_parselist_user(buffer, count, new_value);
+		err = cpulist_parse(buffer, new_value);
 	else
-		err = cpumask_parse_user(buffer, count, new_value);
+		err = cpumask_parse(buffer, new_value);
 	if (err)
 		goto free_cpumask;
 
@@ -177,25 +181,27 @@ free_cpumask:
 }
 
 static ssize_t irq_affinity_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *pos)
+		char *buffer, size_t count, loff_t *pos)
 {
 	return write_irq_affinity(0, file, buffer, count, pos);
 }
 
 static ssize_t irq_affinity_list_proc_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *pos)
+		char *buffer, size_t count, loff_t *pos)
 {
 	return write_irq_affinity(1, file, buffer, count, pos);
 }
 
 static const struct proc_ops irq_affinity_proc_ops = {
 	.proc_show	= irq_affinity_proc_show,
-	.proc_write	= irq_affinity_proc_write,
+	.proc_write_buf	= irq_affinity_proc_write,
+	.proc_write_size = IRQ_AFFINITY_WRITE_SIZE,
 };
 
 static const struct proc_ops irq_affinity_list_proc_ops = {
 	.proc_show	= irq_affinity_list_proc_show,
-	.proc_write	= irq_affinity_list_proc_write,
+	.proc_write_buf	= irq_affinity_list_proc_write,
+	.proc_write_size = IRQ_AFFINITY_WRITE_SIZE,
 };
 
 #ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
@@ -217,7 +223,7 @@ static int default_affinity_show(struct seq_file *m, void *v)
 }
 
 static ssize_t default_affinity_write(struct file *file,
-		const char __user *buffer, size_t count, loff_t *ppos)
+		char *buffer, size_t count, loff_t *ppos)
 {
 	cpumask_var_t new_value;
 	int err;
@@ -225,7 +231,7 @@ static ssize_t default_affinity_write(struct file *file,
 	if (!zalloc_cpumask_var(&new_value, GFP_KERNEL))
 		return -ENOMEM;
 
-	err = cpumask_parse_user(buffer, count, new_value);
+	err = cpumask_parse(buffer, new_value);
 	if (err)
 		goto out;
 
@@ -249,7 +255,8 @@ out:
 
 static const struct proc_ops default_affinity_proc_ops = {
 	.proc_show	= default_affinity_show,
-	.proc_write	= default_affinity_write,
+	.proc_write_buf	= default_affinity_write,
+	.proc_write_size = IRQ_AFFINITY_WRITE_SIZE,
 };
 
 static int irq_node_proc_show(struct seq_file *m, void *v)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 11/30] latencytop: let procfs copy in writes to /proc/latency_stats
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (9 preceding siblings ...)
  2026-11-04 19:52   ` [PATCH RESEND v2 10/30] genirq/proc: let procfs copy in the affinity masks Christian Brauner
@ 2026-11-04 19:52   ` Christian Brauner
  2026-11-04 19:52   ` [PATCH RESEND v2 12/30] locking/lockdep: let procfs own the seq_file of /proc/lock_stat Christian Brauner
                     ` (19 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:52 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry. The write only clears the statistics and doesn't look
at what was written anyway. Writes larger than PAGE_SIZE - 1 bytes fail
with -EINVAL now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/latencytop.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/kernel/latencytop.c b/kernel/latencytop.c
index b764f47606d3..35f73ca07656 100644
--- a/kernel/latencytop.c
+++ b/kernel/latencytop.c
@@ -267,8 +267,7 @@ static int lstats_show(struct seq_file *m, void *v)
 }
 
 static ssize_t
-lstats_write(struct file *file, const char __user *buf, size_t count,
-	     loff_t *offs)
+lstats_write(struct file *file, char *buf, size_t count, loff_t *offs)
 {
 	clear_global_latency_tracing();
 
@@ -277,7 +276,7 @@ lstats_write(struct file *file, const char __user *buf, size_t count,
 
 static const struct proc_ops lstats_proc_ops = {
 	.proc_show	= lstats_show,
-	.proc_write	= lstats_write,
+	.proc_write_buf	= lstats_write,
 };
 
 static int __init init_lstats_procfs(void)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 12/30] locking/lockdep: let procfs own the seq_file of /proc/lock_stat
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (10 preceding siblings ...)
  2026-11-04 19:52   ` [PATCH RESEND v2 11/30] latencytop: let procfs copy in writes to /proc/latency_stats Christian Brauner
@ 2026-11-04 19:52   ` Christian Brauner
  2026-11-04 19:52   ` [PATCH RESEND v2 13/30] locking/lockdep: let procfs copy in writes to /proc/lock_stat Christian Brauner
                     ` (18 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:52 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and let procfs open and free the seq_file.
->proc_open() only takes the snapshot of the statistics now and
->proc_release() frees it.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/locking/lockdep_proc.c | 48 +++++++++++++++++++------------------------
 1 file changed, 21 insertions(+), 27 deletions(-)

diff --git a/kernel/locking/lockdep_proc.c b/kernel/locking/lockdep_proc.c
index 1916db9aa46b..9b034b5b94bd 100644
--- a/kernel/locking/lockdep_proc.c
+++ b/kernel/locking/lockdep_proc.c
@@ -638,40 +638,35 @@ static const struct seq_operations lockstat_ops = {
 	.show	= ls_show,
 };
 
+/* procfs opens the seq_file */
 static int lock_stat_open(struct inode *inode, struct file *file)
 {
-	int res;
-	struct lock_class *class;
 	struct lock_stat_seq *data = vmalloc(sizeof(struct lock_stat_seq));
+	struct lock_stat_data *iter;
+	struct seq_file *m = file->private_data;
+	struct lock_class *class;
+	unsigned long idx;
 
 	if (!data)
 		return -ENOMEM;
 
-	res = seq_open(file, &lockstat_ops);
-	if (!res) {
-		struct lock_stat_data *iter = data->stats;
-		struct seq_file *m = file->private_data;
-		unsigned long idx;
-
-		iterate_lock_classes(idx, class) {
-			if (!test_bit(idx, lock_classes_in_use))
-				continue;
-			iter->class = class;
-			lock_stats(class, &iter->stats);
-			iter++;
-		}
-
-		data->iter_end = iter;
+	iter = data->stats;
+	iterate_lock_classes(idx, class) {
+		if (!test_bit(idx, lock_classes_in_use))
+			continue;
+		iter->class = class;
+		lock_stats(class, &iter->stats);
+		iter++;
+	}
 
-		sort(data->stats, data->iter_end - data->stats,
-				sizeof(struct lock_stat_data),
-				lock_stat_cmp, NULL);
+	data->iter_end = iter;
 
-		m->private = data;
-	} else
-		vfree(data);
+	sort(data->stats, data->iter_end - data->stats,
+			sizeof(struct lock_stat_data),
+			lock_stat_cmp, NULL);
 
-	return res;
+	m->private = data;
+	return 0;
 }
 
 static ssize_t lock_stat_write(struct file *file, const char __user *buf,
@@ -702,14 +697,13 @@ static int lock_stat_release(struct inode *inode, struct file *file)
 	struct seq_file *seq = file->private_data;
 
 	vfree(seq->private);
-	return seq_release(inode, file);
+	return 0;
 }
 
 static const struct proc_ops lock_stat_proc_ops = {
+	.proc_seq_ops	= &lockstat_ops,
 	.proc_open	= lock_stat_open,
 	.proc_write	= lock_stat_write,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
 	.proc_release	= lock_stat_release,
 };
 #endif /* CONFIG_LOCK_STAT */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 13/30] locking/lockdep: let procfs copy in writes to /proc/lock_stat
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (11 preceding siblings ...)
  2026-11-04 19:52   ` [PATCH RESEND v2 12/30] locking/lockdep: let procfs own the seq_file of /proc/lock_stat Christian Brauner
@ 2026-11-04 19:52   ` Christian Brauner
  2026-11-04 19:53   ` [PATCH RESEND v2 14/30] profiling: let procfs copy in writes to /proc/profile Christian Brauner
                     ` (17 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:52 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and look at the first character of that copy. Writes
larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/locking/lockdep_proc.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/kernel/locking/lockdep_proc.c b/kernel/locking/lockdep_proc.c
index 9b034b5b94bd..2a66ea09c2aa 100644
--- a/kernel/locking/lockdep_proc.c
+++ b/kernel/locking/lockdep_proc.c
@@ -669,18 +669,14 @@ static int lock_stat_open(struct inode *inode, struct file *file)
 	return 0;
 }
 
-static ssize_t lock_stat_write(struct file *file, const char __user *buf,
-			       size_t count, loff_t *ppos)
+static ssize_t lock_stat_write(struct file *file, char *buf, size_t count,
+			       loff_t *ppos)
 {
 	struct lock_class *class;
 	unsigned long idx;
-	char c;
 
 	if (count) {
-		if (get_user(c, buf))
-			return -EFAULT;
-
-		if (c != '0')
+		if (buf[0] != '0')
 			return count;
 
 		iterate_lock_classes(idx, class) {
@@ -703,7 +699,7 @@ static int lock_stat_release(struct inode *inode, struct file *file)
 static const struct proc_ops lock_stat_proc_ops = {
 	.proc_seq_ops	= &lockstat_ops,
 	.proc_open	= lock_stat_open,
-	.proc_write	= lock_stat_write,
+	.proc_write_buf	= lock_stat_write,
 	.proc_release	= lock_stat_release,
 };
 #endif /* CONFIG_LOCK_STAT */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 14/30] profiling: let procfs copy in writes to /proc/profile
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (12 preceding siblings ...)
  2026-11-04 19:52   ` [PATCH RESEND v2 13/30] locking/lockdep: let procfs copy in writes to /proc/lock_stat Christian Brauner
@ 2026-11-04 19:53   ` Christian Brauner
  2026-11-04 19:53   ` [PATCH RESEND v2 15/30] profiling: let procfs copy out reads of /proc/profile Christian Brauner
                     ` (16 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:53 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written before
it pins the entry, and take the multiplier from that copy. Writes
larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/profile.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/kernel/profile.c b/kernel/profile.c
index 820d5ec909d4..dc2db66a77d2 100644
--- a/kernel/profile.c
+++ b/kernel/profile.c
@@ -189,16 +189,14 @@ int __weak setup_profiling_timer(unsigned mult)
  * Writing a 'profiling multiplier' value into it also re-sets the profiling
  * interrupt frequency, on architectures that support this.
  */
-static ssize_t write_profile(struct file *file, const char __user *buf,
-			     size_t count, loff_t *ppos)
+static ssize_t write_profile(struct file *file, char *buf, size_t count,
+			     loff_t *ppos)
 {
 #ifdef CONFIG_SMP
 	if (count == sizeof(int)) {
 		unsigned int multiplier;
 
-		if (copy_from_user(&multiplier, buf, sizeof(int)))
-			return -EFAULT;
-
+		memcpy(&multiplier, buf, sizeof(int));
 		if (setup_profiling_timer(multiplier))
 			return -EINVAL;
 	}
@@ -209,7 +207,7 @@ static ssize_t write_profile(struct file *file, const char __user *buf,
 
 static const struct proc_ops profile_proc_ops = {
 	.proc_read	= read_profile,
-	.proc_write	= write_profile,
+	.proc_write_buf	= write_profile,
 	.proc_lseek	= default_llseek,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 15/30] profiling: let procfs copy out reads of /proc/profile
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (13 preceding siblings ...)
  2026-11-04 19:53   ` [PATCH RESEND v2 14/30] profiling: let procfs copy in writes to /proc/profile Christian Brauner
@ 2026-11-04 19:53   ` Christian Brauner
  2026-11-04 19:53   ` [PATCH RESEND v2 16/30] profiling: mark /proc/profile permanent Christian Brauner
                     ` (15 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:53 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Fill the buffer procfs hands to ->proc_read_buf() instead of reading
straight into userspace. readprofile reads the whole file in one go, so
ask for PROC_ENTRY_FULL_READS and procfs fills such a read in pieces.
The file is read through ->read_iter() now, so sendfile() and splice()
work on it like on the other converted entries.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/profile.c | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/kernel/profile.c b/kernel/profile.c
index dc2db66a77d2..64f6a45834b1 100644
--- a/kernel/profile.c
+++ b/kernel/profile.c
@@ -142,7 +142,6 @@ void profile_tick(int type)
 #ifdef CONFIG_PROC_FS
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
-#include <linux/uaccess.h>
 
 /*
  * This function accesses profiling information. The returned data is
@@ -151,7 +150,7 @@ void profile_tick(int type)
  * get meaningful info out of these data.
  */
 static ssize_t
-read_profile(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+read_profile(struct file *file, char *buf, size_t count, loff_t *ppos)
 {
 	unsigned long p = *ppos;
 	ssize_t read;
@@ -165,13 +164,11 @@ read_profile(struct file *file, char __user *buf, size_t count, loff_t *ppos)
 	read = 0;
 
 	while (p < sizeof(unsigned int) && count > 0) {
-		if (put_user(*((char *)(&sample_step)+p), buf))
-			return -EFAULT;
+		*buf = *((char *)(&sample_step)+p);
 		buf++; p++; count--; read++;
 	}
 	pnt = (char *)prof_buffer + p - sizeof(atomic_t);
-	if (copy_to_user(buf, (void *)pnt, count))
-		return -EFAULT;
+	memcpy(buf, pnt, count);
 	read += count;
 	*ppos += read;
 	return read;
@@ -206,7 +203,8 @@ static ssize_t write_profile(struct file *file, char *buf, size_t count,
 }
 
 static const struct proc_ops profile_proc_ops = {
-	.proc_read	= read_profile,
+	.proc_flags	= PROC_ENTRY_FULL_READS,
+	.proc_read_buf	= read_profile,
 	.proc_write_buf	= write_profile,
 	.proc_lseek	= default_llseek,
 };

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 16/30] profiling: mark /proc/profile permanent
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (14 preceding siblings ...)
  2026-11-04 19:53   ` [PATCH RESEND v2 15/30] profiling: let procfs copy out reads of /proc/profile Christian Brauner
@ 2026-11-04 19:53   ` Christian Brauner
  2026-11-04 19:53   ` [PATCH RESEND v2 17/30] sched/psi: let procfs own the seq_file of the pressure files Christian Brauner
                     ` (14 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:53 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

/proc/profile is created when profiling is turned on and never removed.
Say so with PROC_ENTRY_PERMANENT, procfs then calls into it without
pinning it.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/profile.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/profile.c b/kernel/profile.c
index 64f6a45834b1..0c2672adcbb5 100644
--- a/kernel/profile.c
+++ b/kernel/profile.c
@@ -203,7 +203,7 @@ static ssize_t write_profile(struct file *file, char *buf, size_t count,
 }
 
 static const struct proc_ops profile_proc_ops = {
-	.proc_flags	= PROC_ENTRY_FULL_READS,
+	.proc_flags	= PROC_ENTRY_PERMANENT | PROC_ENTRY_FULL_READS,
 	.proc_read_buf	= read_profile,
 	.proc_write_buf	= write_profile,
 	.proc_lseek	= default_llseek,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 17/30] sched/psi: let procfs own the seq_file of the pressure files
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (15 preceding siblings ...)
  2026-11-04 19:53   ` [PATCH RESEND v2 16/30] profiling: mark /proc/profile permanent Christian Brauner
@ 2026-11-04 19:53   ` Christian Brauner
  2026-11-04 19:54   ` [PATCH RESEND v2 18/30] sched/psi: let procfs copy in trigger writes Christian Brauner
                     ` (13 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:53 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the show functions and let procfs open and free the seq_file of
/proc/pressure/*. That makes the open functions unnecessary and
->proc_release() only destroys the trigger now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/sched/psi.c | 38 +++++---------------------------------
 1 file changed, 5 insertions(+), 33 deletions(-)

diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c
index 8b59635e95d4..9f1d9742bac9 100644
--- a/kernel/sched/psi.c
+++ b/kernel/sched/psi.c
@@ -1535,21 +1535,6 @@ static int psi_cpu_show(struct seq_file *m, void *v)
 	return psi_show(m, &psi_system, PSI_CPU);
 }
 
-static int psi_io_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, psi_io_show, NULL);
-}
-
-static int psi_memory_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, psi_memory_show, NULL);
-}
-
-static int psi_cpu_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, psi_cpu_show, NULL);
-}
-
 static ssize_t psi_write(struct file *file, const char __user *user_buf,
 			 size_t nbytes, enum psi_res res)
 {
@@ -1635,31 +1620,25 @@ static int psi_fop_release(struct inode *inode, struct file *file)
 	struct seq_file *seq = file->private_data;
 
 	psi_trigger_destroy(seq->private);
-	return single_release(inode, file);
+	return 0;
 }
 
 static const struct proc_ops psi_io_proc_ops = {
-	.proc_open	= psi_io_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= psi_io_show,
 	.proc_write	= psi_io_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,
 };
 
 static const struct proc_ops psi_memory_proc_ops = {
-	.proc_open	= psi_memory_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= psi_memory_show,
 	.proc_write	= psi_memory_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,
 };
 
 static const struct proc_ops psi_cpu_proc_ops = {
-	.proc_open	= psi_cpu_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= psi_cpu_show,
 	.proc_write	= psi_cpu_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,
@@ -1671,11 +1650,6 @@ static int psi_irq_show(struct seq_file *m, void *v)
 	return psi_show(m, &psi_system, PSI_IRQ);
 }
 
-static int psi_irq_open(struct inode *inode, struct file *file)
-{
-	return single_open(file, psi_irq_show, NULL);
-}
-
 static ssize_t psi_irq_write(struct file *file, const char __user *user_buf,
 			     size_t nbytes, loff_t *ppos)
 {
@@ -1683,9 +1657,7 @@ static ssize_t psi_irq_write(struct file *file, const char __user *user_buf,
 }
 
 static const struct proc_ops psi_irq_proc_ops = {
-	.proc_open	= psi_irq_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
+	.proc_show	= psi_irq_show,
 	.proc_write	= psi_irq_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 18/30] sched/psi: let procfs copy in trigger writes
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (16 preceding siblings ...)
  2026-11-04 19:53   ` [PATCH RESEND v2 17/30] sched/psi: let procfs own the seq_file of the pressure files Christian Brauner
@ 2026-11-04 19:54   ` Christian Brauner
  2026-11-04 19:54   ` [PATCH RESEND v2 19/30] sched/psi: mark the pressure files permanent Christian Brauner
                     ` (12 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:54 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for the files in /proc/pressure so procfs
copies in what is written before it pins the entry, and take the
trigger from that copy. Only the first 31 bytes were ever looked at.
Writes larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/sched/psi.c | 40 +++++++++++++++++++---------------------
 1 file changed, 19 insertions(+), 21 deletions(-)

diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c
index 9f1d9742bac9..9b99099017e1 100644
--- a/kernel/sched/psi.c
+++ b/kernel/sched/psi.c
@@ -1535,8 +1535,8 @@ static int psi_cpu_show(struct seq_file *m, void *v)
 	return psi_show(m, &psi_system, PSI_CPU);
 }
 
-static ssize_t psi_write(struct file *file, const char __user *user_buf,
-			 size_t nbytes, enum psi_res res)
+static ssize_t psi_write(struct file *file, const char *kbuf, size_t nbytes,
+			 enum psi_res res)
 {
 	char buf[32];
 	size_t buf_size;
@@ -1552,9 +1552,7 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
 		return -EINVAL;
 
 	buf_size = min(nbytes, sizeof(buf) - 1);
-	if (copy_from_user(buf, user_buf, buf_size))
-		return -EFAULT;
-
+	memcpy(buf, kbuf, buf_size);
 	buf[buf_size] = '\0';
 
 	seq = file->private_data;
@@ -1590,22 +1588,22 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
 	return nbytes;
 }
 
-static ssize_t psi_io_write(struct file *file, const char __user *user_buf,
-			    size_t nbytes, loff_t *ppos)
+static ssize_t psi_io_write(struct file *file, char *buf, size_t nbytes,
+			    loff_t *ppos)
 {
-	return psi_write(file, user_buf, nbytes, PSI_IO);
+	return psi_write(file, buf, nbytes, PSI_IO);
 }
 
-static ssize_t psi_memory_write(struct file *file, const char __user *user_buf,
-				size_t nbytes, loff_t *ppos)
+static ssize_t psi_memory_write(struct file *file, char *buf, size_t nbytes,
+				loff_t *ppos)
 {
-	return psi_write(file, user_buf, nbytes, PSI_MEM);
+	return psi_write(file, buf, nbytes, PSI_MEM);
 }
 
-static ssize_t psi_cpu_write(struct file *file, const char __user *user_buf,
-			     size_t nbytes, loff_t *ppos)
+static ssize_t psi_cpu_write(struct file *file, char *buf, size_t nbytes,
+			     loff_t *ppos)
 {
-	return psi_write(file, user_buf, nbytes, PSI_CPU);
+	return psi_write(file, buf, nbytes, PSI_CPU);
 }
 
 static __poll_t psi_fop_poll(struct file *file, poll_table *wait)
@@ -1625,21 +1623,21 @@ static int psi_fop_release(struct inode *inode, struct file *file)
 
 static const struct proc_ops psi_io_proc_ops = {
 	.proc_show	= psi_io_show,
-	.proc_write	= psi_io_write,
+	.proc_write_buf	= psi_io_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,
 };
 
 static const struct proc_ops psi_memory_proc_ops = {
 	.proc_show	= psi_memory_show,
-	.proc_write	= psi_memory_write,
+	.proc_write_buf	= psi_memory_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,
 };
 
 static const struct proc_ops psi_cpu_proc_ops = {
 	.proc_show	= psi_cpu_show,
-	.proc_write	= psi_cpu_write,
+	.proc_write_buf	= psi_cpu_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,
 };
@@ -1650,15 +1648,15 @@ static int psi_irq_show(struct seq_file *m, void *v)
 	return psi_show(m, &psi_system, PSI_IRQ);
 }
 
-static ssize_t psi_irq_write(struct file *file, const char __user *user_buf,
-			     size_t nbytes, loff_t *ppos)
+static ssize_t psi_irq_write(struct file *file, char *buf, size_t nbytes,
+			     loff_t *ppos)
 {
-	return psi_write(file, user_buf, nbytes, PSI_IRQ);
+	return psi_write(file, buf, nbytes, PSI_IRQ);
 }
 
 static const struct proc_ops psi_irq_proc_ops = {
 	.proc_show	= psi_irq_show,
-	.proc_write	= psi_irq_write,
+	.proc_write_buf	= psi_irq_write,
 	.proc_poll	= psi_fop_poll,
 	.proc_release	= psi_fop_release,
 };

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 19/30] sched/psi: mark the pressure files permanent
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (17 preceding siblings ...)
  2026-11-04 19:54   ` [PATCH RESEND v2 18/30] sched/psi: let procfs copy in trigger writes Christian Brauner
@ 2026-11-04 19:54   ` Christian Brauner
  2026-11-04 19:54   ` [PATCH RESEND v2 20/30] sched/psi: let procfs own the wait queue of the pressure files Christian Brauner
                     ` (11 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:54 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

/proc/pressure/* are created when psi initializes and never removed.
Say so with PROC_ENTRY_PERMANENT, procfs then calls into them without
pinning them, like it does for /proc/profile.

That also takes care of a rule the files bent. psi_write() takes the
seq_file's lock to serialize the triggers. A reader holds that lock
while it copies to userspace, and now that procfs owns the seq_file it
does that with the entry unpinned. So nothing that runs with the entry
pinned may wait for that lock, and psi_write() ran pinned. Nothing ever
waited for it since the files are never removed, but with the files
permanent nothing runs pinned at all.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/sched/psi.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c
index 9b99099017e1..cdb7a832333a 100644
--- a/kernel/sched/psi.c
+++ b/kernel/sched/psi.c
@@ -1622,6 +1622,7 @@ static int psi_fop_release(struct inode *inode, struct file *file)
 }
 
 static const struct proc_ops psi_io_proc_ops = {
+	.proc_flags	= PROC_ENTRY_PERMANENT,
 	.proc_show	= psi_io_show,
 	.proc_write_buf	= psi_io_write,
 	.proc_poll	= psi_fop_poll,
@@ -1629,6 +1630,7 @@ static const struct proc_ops psi_io_proc_ops = {
 };
 
 static const struct proc_ops psi_memory_proc_ops = {
+	.proc_flags	= PROC_ENTRY_PERMANENT,
 	.proc_show	= psi_memory_show,
 	.proc_write_buf	= psi_memory_write,
 	.proc_poll	= psi_fop_poll,
@@ -1636,6 +1638,7 @@ static const struct proc_ops psi_memory_proc_ops = {
 };
 
 static const struct proc_ops psi_cpu_proc_ops = {
+	.proc_flags	= PROC_ENTRY_PERMANENT,
 	.proc_show	= psi_cpu_show,
 	.proc_write_buf	= psi_cpu_write,
 	.proc_poll	= psi_fop_poll,
@@ -1655,6 +1658,7 @@ static ssize_t psi_irq_write(struct file *file, char *buf, size_t nbytes,
 }
 
 static const struct proc_ops psi_irq_proc_ops = {
+	.proc_flags	= PROC_ENTRY_PERMANENT,
 	.proc_show	= psi_irq_show,
 	.proc_write_buf	= psi_irq_write,
 	.proc_poll	= psi_fop_poll,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 20/30] sched/psi: let procfs own the wait queue of the pressure files
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (18 preceding siblings ...)
  2026-11-04 19:54   ` [PATCH RESEND v2 19/30] sched/psi: mark the pressure files permanent Christian Brauner
@ 2026-11-04 19:54   ` Christian Brauner
  2026-11-04 19:54   ` [PATCH RESEND v2 21/30] kallsyms: let procfs own the seq_file of /proc/kallsyms Christian Brauner
                     ` (10 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:54 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

A trigger on one of the /proc/pressure files has a wait queue of its
own for its poller, the cgroup pressure files use the queue of their
kernfs node through kernfs_generic_poll() and kernfs_notify(). procfs
owns the queue of its pressure files now, so use it the same way: the
poll_wait() is procfs', and a trigger that fires wakes the pollers of
its file with proc_poll_notify(). Like for the cgroup files every
poller of the file is woken when any trigger on it fires, and each
reports the state of its own trigger. psi_trigger_poll() doesn't need
the file anymore.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 include/linux/psi.h       |  3 +--
 include/linux/psi_types.h |  8 +++++---
 kernel/cgroup/cgroup.c    |  2 +-
 kernel/sched/psi.c        | 30 +++++++++++++++++-------------
 4 files changed, 24 insertions(+), 19 deletions(-)

diff --git a/include/linux/psi.h b/include/linux/psi.h
index 7966e3ac03b9..c6f536631411 100644
--- a/include/linux/psi.h
+++ b/include/linux/psi.h
@@ -30,8 +30,7 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
 int psi_trigger_create_rtpoll_worker(struct psi_group *group);
 void psi_trigger_destroy(struct psi_trigger *t);
 
-__poll_t psi_trigger_poll(void **trigger_ptr, struct file *file,
-			poll_table *wait);
+__poll_t psi_trigger_poll(void **trigger_ptr, poll_table *wait);
 
 #ifdef CONFIG_CGROUPS
 static inline struct psi_group *cgroup_psi(struct cgroup *cgrp)
diff --git a/include/linux/psi_types.h b/include/linux/psi_types.h
index dd10c22299ab..ce772575c5da 100644
--- a/include/linux/psi_types.h
+++ b/include/linux/psi_types.h
@@ -8,6 +8,8 @@
 #include <linux/kref.h>
 #include <linux/wait.h>
 
+struct proc_dir_entry;
+
 #ifdef CONFIG_PSI
 
 /* Tracked task states */
@@ -132,12 +134,12 @@ struct psi_trigger {
 	/* Backpointer needed during trigger destruction */
 	struct psi_group *group;
 
-	/* Wait queue for polling */
-	wait_queue_head_t event_wait;
-
 	/* Kernfs file for cgroup triggers */
 	struct kernfs_open_file *of;
 
+	/* /proc/pressure file for system triggers, see proc_poll_notify() */
+	struct proc_dir_entry *pde;
+
 	/* Pending event flag */
 	int event;
 
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index a284225f8dc9..c5da33495ae3 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -4232,7 +4232,7 @@ static __poll_t cgroup_pressure_poll(struct kernfs_open_file *of,
 {
 	struct cgroup_file_ctx *ctx = of->priv;
 
-	return psi_trigger_poll(&ctx->psi.trigger, of->file, pt);
+	return psi_trigger_poll(&ctx->psi.trigger, pt);
 }
 
 static void cgroup_pressure_release(struct kernfs_open_file *of)
diff --git a/kernel/sched/psi.c b/kernel/sched/psi.c
index cdb7a832333a..97b5e2488106 100644
--- a/kernel/sched/psi.c
+++ b/kernel/sched/psi.c
@@ -514,7 +514,7 @@ static void update_triggers(struct psi_group *group, u64 now,
 			if (t->of)
 				kernfs_notify(t->of->kn);
 			else
-				wake_up_interruptible(&t->event_wait);
+				proc_poll_notify(t->pde);
 		}
 		t->last_event_time = now;
 		/* Reset threshold breach flag once event got generated */
@@ -1327,6 +1327,9 @@ int psi_trigger_create_rtpoll_worker(struct psi_group *group)
 	return 0;
 }
 
+/* the /proc/pressure files, see psi_proc_init() */
+static struct proc_dir_entry *psi_proc_entry[NR_PSI_RESOURCES];
+
 struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
 				       enum psi_res res, struct file *file,
 				       struct kernfs_open_file *of,
@@ -1392,8 +1395,7 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
 	t->event = 0;
 	t->last_event_time = 0;
 	t->of = of;
-	if (!of)
-		init_waitqueue_head(&t->event_wait);
+	t->pde = of ? NULL : psi_proc_entry[res];
 	t->pending_event = false;
 	t->aggregator = privileged ? PSI_POLL : PSI_AVGS;
 
@@ -1441,7 +1443,7 @@ void psi_trigger_destroy(struct psi_trigger *t)
 	if (t->of)
 		kernfs_notify(t->of->kn);
 	else
-		wake_up_interruptible(&t->event_wait);
+		proc_poll_notify(t->pde);
 
 	if (t->aggregator == PSI_AVGS) {
 		mutex_lock(&group->avgs_lock);
@@ -1495,8 +1497,7 @@ void psi_trigger_destroy(struct psi_trigger *t)
 	kfree(t);
 }
 
-__poll_t psi_trigger_poll(void **trigger_ptr,
-				struct file *file, poll_table *wait)
+__poll_t psi_trigger_poll(void **trigger_ptr, poll_table *wait)
 {
 	__poll_t ret = DEFAULT_POLLMASK;
 	struct psi_trigger *t;
@@ -1508,10 +1509,9 @@ __poll_t psi_trigger_poll(void **trigger_ptr,
 	if (!t)
 		return DEFAULT_POLLMASK | EPOLLERR | EPOLLPRI;
 
+	/* a /proc/pressure file is polled on the queue procfs owns for it */
 	if (t->of)
 		kernfs_generic_poll(t->of, wait);
-	else
-		poll_wait(file, &t->event_wait, wait);
 
 	if (cmpxchg(&t->event, 1, 0) == 1)
 		ret |= EPOLLPRI;
@@ -1610,7 +1610,7 @@ static __poll_t psi_fop_poll(struct file *file, poll_table *wait)
 {
 	struct seq_file *seq = file->private_data;
 
-	return psi_trigger_poll(&seq->private, file, wait);
+	return psi_trigger_poll(&seq->private, wait);
 }
 
 static int psi_fop_release(struct inode *inode, struct file *file)
@@ -1670,11 +1670,15 @@ static int __init psi_proc_init(void)
 {
 	if (psi_enable) {
 		proc_mkdir("pressure", NULL);
-		proc_create("pressure/io", 0666, NULL, &psi_io_proc_ops);
-		proc_create("pressure/memory", 0666, NULL, &psi_memory_proc_ops);
-		proc_create("pressure/cpu", 0666, NULL, &psi_cpu_proc_ops);
+		psi_proc_entry[PSI_IO] = proc_create("pressure/io", 0666, NULL,
+						     &psi_io_proc_ops);
+		psi_proc_entry[PSI_MEM] = proc_create("pressure/memory", 0666,
+						      NULL, &psi_memory_proc_ops);
+		psi_proc_entry[PSI_CPU] = proc_create("pressure/cpu", 0666, NULL,
+						      &psi_cpu_proc_ops);
 #ifdef CONFIG_IRQ_TIME_ACCOUNTING
-		proc_create("pressure/irq", 0666, NULL, &psi_irq_proc_ops);
+		psi_proc_entry[PSI_IRQ] = proc_create("pressure/irq", 0666, NULL,
+						      &psi_irq_proc_ops);
 #endif
 	}
 	return 0;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 21/30] kallsyms: let procfs own the seq_file of /proc/kallsyms
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (19 preceding siblings ...)
  2026-11-04 19:54   ` [PATCH RESEND v2 20/30] sched/psi: let procfs own the wait queue of the pressure files Christian Brauner
@ 2026-11-04 19:54   ` Christian Brauner
  2026-11-04 19:55   ` [PATCH RESEND v2 22/30] module: procfs: let procfs own the seq_file of /proc/modules Christian Brauner
                     ` (9 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:54 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and the size of the iterator and let procfs
open and free the seq_file. ->proc_open() only initializes the iterator
now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/kallsyms.c | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index aec2f06858af..2d309fcee730 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -888,10 +888,9 @@ static int kallsyms_open(struct inode *inode, struct file *file)
 	 * s_start from where we left off, so we avoid doing
 	 * using get_symbol_offset for every symbol.
 	 */
-	struct kallsym_iter *iter;
-	iter = __seq_open_private(file, &kallsyms_op, sizeof(*iter));
-	if (!iter)
-		return -ENOMEM;
+	struct seq_file *m = file->private_data;
+	struct kallsym_iter *iter = m->private;
+
 	reset_iter(iter, 0);
 
 	/*
@@ -923,10 +922,9 @@ const char *kdb_walk_kallsyms(loff_t *pos)
 #endif	/* CONFIG_KGDB_KDB */
 
 static const struct proc_ops kallsyms_proc_ops = {
+	.proc_seq_ops	= &kallsyms_op,
+	.proc_state_size = sizeof(struct kallsym_iter),
 	.proc_open	= kallsyms_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= seq_release_private,
 };
 
 static int __init kallsyms_init(void)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 22/30] module: procfs: let procfs own the seq_file of /proc/modules
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (20 preceding siblings ...)
  2026-11-04 19:54   ` [PATCH RESEND v2 21/30] kallsyms: let procfs own the seq_file of /proc/kallsyms Christian Brauner
@ 2026-11-04 19:55   ` Christian Brauner
  2026-11-04 19:55   ` [PATCH RESEND v2 23/30] dyndbg: let procfs copy in writes to /proc/dynamic_debug/control Christian Brauner
                     ` (8 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:55 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and let procfs open and free the seq_file.
->proc_open() only decides whether to hide the addresses now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/module/procfs.c | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/kernel/module/procfs.c b/kernel/module/procfs.c
index 90712aa9dd13..a759bd916df7 100644
--- a/kernel/module/procfs.c
+++ b/kernel/module/procfs.c
@@ -117,31 +117,24 @@ static const struct seq_operations modules_op = {
 };
 
 /*
- * This also sets the "private" pointer to non-NULL if the
- * kernel pointers should be hidden (so you can just test
+ * procfs opens the seq_file. This sets the "private" pointer to non-NULL
+ * if the kernel pointers should be hidden (so you can just test
  * "m->private" to see if you should keep the values private).
  *
  * We use the same logic as for /proc/kallsyms.
  */
 static int modules_open(struct inode *inode, struct file *file)
 {
-	int err = seq_open(file, &modules_op);
+	struct seq_file *m = file->private_data;
 
-	if (!err) {
-		struct seq_file *m = file->private_data;
-
-		m->private = kallsyms_show_value(file->f_cred) ? NULL : (void *)8ul;
-	}
-
-	return err;
+	m->private = kallsyms_show_value(file->f_cred) ? NULL : (void *)8ul;
+	return 0;
 }
 
 static const struct proc_ops modules_proc_ops = {
 	.proc_flags	= PROC_ENTRY_PERMANENT,
+	.proc_seq_ops	= &modules_op,
 	.proc_open	= modules_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= seq_release,
 };
 
 static int __init proc_modules_init(void)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 23/30] dyndbg: let procfs copy in writes to /proc/dynamic_debug/control
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (21 preceding siblings ...)
  2026-11-04 19:55   ` [PATCH RESEND v2 22/30] module: procfs: let procfs own the seq_file of /proc/modules Christian Brauner
@ 2026-11-04 19:55   ` Christian Brauner
  2026-11-04 19:55   ` [PATCH RESEND v2 24/30] ipc: let procfs own the seq_file of the sysvipc files Christian Brauner
                     ` (7 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:55 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Move the parsing into ddebug_proc_write_buf() and implement
->proc_write_buf() with it so procfs copies in what is written before
it pins the entry. The debugfs file keeps copying in itself. Writes
larger than USER_BUF_PAGE - 1 bytes to the procfs file now fail with
-EINVAL from procfs instead of -E2BIG, and without the "expected <4096
bytes into control" warning.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 lib/dynamic_debug.c | 34 ++++++++++++++++++++++------------
 1 file changed, 22 insertions(+), 12 deletions(-)

diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index 64a211290872..1d6012c9eb41 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -1002,11 +1002,28 @@ __setup("dyndbg=", dyndbg_setup);
  * command text from userspace, parses and executes it.
  */
 #define USER_BUF_PAGE 4096
+
+static ssize_t ddebug_proc_write_buf(struct file *file, char *buf, size_t len,
+				     loff_t *offp)
+{
+	int ret;
+
+	if (len == 0)
+		return 0;
+	v2pr_info("read %zu bytes from userspace\n", len);
+
+	ret = ddebug_exec_queries(buf, NULL);
+	if (ret < 0)
+		return ret;
+
+	*offp += len;
+	return len;
+}
+
 static ssize_t ddebug_proc_write(struct file *file, const char __user *ubuf,
 				  size_t len, loff_t *offp)
 {
-	char *tmpbuf;
-	int ret;
+	char *tmpbuf __free(kfree) = NULL;
 
 	if (len == 0)
 		return 0;
@@ -1017,15 +1034,7 @@ static ssize_t ddebug_proc_write(struct file *file, const char __user *ubuf,
 	tmpbuf = memdup_user_nul(ubuf, len);
 	if (IS_ERR(tmpbuf))
 		return PTR_ERR(tmpbuf);
-	v2pr_info("read %zu bytes from userspace\n", len);
-
-	ret = ddebug_exec_queries(tmpbuf, NULL);
-	kfree(tmpbuf);
-	if (ret < 0)
-		return ret;
-
-	*offp += len;
-	return len;
+	return ddebug_proc_write_buf(file, tmpbuf, len, offp);
 }
 
 /*
@@ -1196,7 +1205,8 @@ static const struct file_operations ddebug_proc_fops = {
 static const struct proc_ops proc_fops = {
 	.proc_seq_ops = &ddebug_proc_seqops,
 	.proc_state_size = sizeof(struct ddebug_iter),
-	.proc_write = ddebug_proc_write
+	.proc_write_buf = ddebug_proc_write_buf,
+	.proc_write_size = USER_BUF_PAGE - 1,
 };
 
 static void ddebug_attach_module_classes(struct ddebug_table *dt,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 24/30] ipc: let procfs own the seq_file of the sysvipc files
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (22 preceding siblings ...)
  2026-11-04 19:55   ` [PATCH RESEND v2 23/30] dyndbg: let procfs copy in writes to /proc/dynamic_debug/control Christian Brauner
@ 2026-11-04 19:55   ` Christian Brauner
  2026-11-04 19:55   ` [PATCH RESEND v2 25/30] alloc_tag: let procfs own the seq_file of /proc/allocinfo Christian Brauner
                     ` (6 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:55 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and the size of the iterator and let procfs
open and free the seq_file of /proc/sysvipc/*. ->proc_open() only fills
in the iterator now and ->proc_release() drops the references it holds.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 ipc/util.c | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/ipc/util.c b/ipc/util.c
index 1737d776bc08..8ec3bc328657 100644
--- a/ipc/util.c
+++ b/ipc/util.c
@@ -894,13 +894,11 @@ static const struct seq_operations sysvipc_proc_seqops = {
 	.show  = sysvipc_proc_show,
 };
 
+/* procfs opened the seq_file and allocated @iter */
 static int sysvipc_proc_open(struct inode *inode, struct file *file)
 {
-	struct ipc_proc_iter *iter;
-
-	iter = __seq_open_private(file, &sysvipc_proc_seqops, sizeof(*iter));
-	if (!iter)
-		return -ENOMEM;
+	struct seq_file *m = file->private_data;
+	struct ipc_proc_iter *iter = m->private;
 
 	iter->iface = pde_data(inode);
 	iter->ns    = get_ipc_ns(current->nsproxy->ipc_ns);
@@ -915,14 +913,14 @@ static int sysvipc_proc_release(struct inode *inode, struct file *file)
 	struct ipc_proc_iter *iter = seq->private;
 	put_ipc_ns(iter->ns);
 	put_pid_ns(iter->pid_ns);
-	return seq_release_private(inode, file);
+	return 0;
 }
 
 static const struct proc_ops sysvipc_proc_ops = {
 	.proc_flags	= PROC_ENTRY_PERMANENT,
+	.proc_seq_ops	= &sysvipc_proc_seqops,
+	.proc_state_size = sizeof(struct ipc_proc_iter),
 	.proc_open	= sysvipc_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
 	.proc_release	= sysvipc_proc_release,
 };
 #endif /* CONFIG_PROC_FS */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 25/30] alloc_tag: let procfs own the seq_file of /proc/allocinfo
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (23 preceding siblings ...)
  2026-11-04 19:55   ` [PATCH RESEND v2 24/30] ipc: let procfs own the seq_file of the sysvipc files Christian Brauner
@ 2026-11-04 19:55   ` Christian Brauner
  2026-11-04 19:56   ` [PATCH RESEND v2 26/30] alloc_tag: let procfs copy the ioctl arguments " Christian Brauner
                     ` (5 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:55 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and the size of the private state and let
procfs open and free the seq_file. ->proc_open() and ->proc_release()
only set up and destroy the ioctl lock now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 mm/alloc_tag.c | 28 +++++++++++-----------------
 1 file changed, 11 insertions(+), 17 deletions(-)

diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index 7d7ad6704953..697e44468ebc 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -152,27 +152,21 @@ static const struct seq_operations allocinfo_seq_op = {
 };
 
 /*
- * Initializes seq_file operations and allocates private state when opening
- * the /proc/allocinfo procfs entry.
+ * Initializes the private state when opening the /proc/allocinfo procfs
+ * entry. procfs opened the seq_file and allocated the state.
  */
 static int allocinfo_open(struct inode *inode, struct file *file)
 {
-	int ret;
-
-	ret = seq_open_private(file, &allocinfo_seq_op,
-			       sizeof(struct allocinfo_private));
-	if (!ret) {
-		struct seq_file *m = file->private_data;
-		struct allocinfo_private *priv = m->private;
+	struct seq_file *m = file->private_data;
+	struct allocinfo_private *priv = m->private;
 
-		mutex_init(&priv->ioctl_lock);
-	}
-	return ret;
+	mutex_init(&priv->ioctl_lock);
+	return 0;
 }
 
 /*
- * Cleans up the seq_file state and frees up the private state allocated in
- * allocinfo_open() when closing the /proc/allocinfo file descriptor.
+ * Undoes allocinfo_open() when closing the /proc/allocinfo file descriptor.
+ * procfs frees the seq_file and the private state.
  */
 static int allocinfo_release(struct inode *inode, struct file *file)
 {
@@ -180,7 +174,7 @@ static int allocinfo_release(struct inode *inode, struct file *file)
 	struct allocinfo_private *priv = m->private;
 
 	mutex_destroy(&priv->ioctl_lock);
-	return seq_release_private(inode, file);
+	return 0;
 }
 
 /*
@@ -475,9 +469,9 @@ static long allocinfo_compat_ioctl(struct file *file, unsigned int cmd,
 #endif
 
 static const struct proc_ops allocinfo_proc_ops = {
+	.proc_seq_ops		= &allocinfo_seq_op,
+	.proc_state_size	= sizeof(struct allocinfo_private),
 	.proc_open		= allocinfo_open,
-	.proc_read_iter		= seq_read_iter,
-	.proc_lseek		= seq_lseek,
 	.proc_release		= allocinfo_release,
 	.proc_ioctl		= allocinfo_ioctl,
 #ifdef CONFIG_COMPAT

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 26/30] alloc_tag: let procfs copy the ioctl arguments of /proc/allocinfo
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (24 preceding siblings ...)
  2026-11-04 19:55   ` [PATCH RESEND v2 25/30] alloc_tag: let procfs own the seq_file of /proc/allocinfo Christian Brauner
@ 2026-11-04 19:56   ` Christian Brauner
  2026-11-04 19:56   ` [PATCH RESEND v2 27/30] mm/swapfile: let procfs own the seq_file of /proc/swaps Christian Brauner
                     ` (4 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:56 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_ioctl_buf() so procfs copies the arguments in before
it pins the entry and back out after unpinning it. The commands encode
their arguments. procfs converts the pointers of 32-bit tasks, so
allocinfo_compat_ioctl() goes away.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 mm/alloc_tag.c | 47 +++++++++++++++++------------------------------
 1 file changed, 17 insertions(+), 30 deletions(-)

diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c
index 697e44468ebc..b17457c8fb02 100644
--- a/mm/alloc_tag.c
+++ b/mm/alloc_tag.c
@@ -6,7 +6,6 @@
 #include <linux/kallsyms.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
-#include <linux/compat.h>
 #include <linux/page_ext.h>
 #include <linux/pgalloc_tag.h>
 #include <linux/proc_fs.h>
@@ -236,16 +235,14 @@ static void allocinfo_to_params(struct codetag *ct,
  * Retrieves the unique content ID representing the current allocation tag module
  * layout, allowing userspace to detect if modules were loaded / unloaded.
  */
-static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
+static int allocinfo_ioctl_get_content_id(struct seq_file *m, void *arg)
 {
 	struct allocinfo_content_id params;
 
 	codetag_lock_module_list(alloc_tag_cttype);
 	params.id = codetag_get_content_id(alloc_tag_cttype);
 	codetag_unlock_module_list(alloc_tag_cttype);
-	if (copy_to_user(arg, &params, sizeof(params)))
-		return -EFAULT;
-
+	memcpy(arg, &params, sizeof(params));
 	return 0;
 }
 
@@ -310,7 +307,7 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter,
  * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its
  * profiling data and returns it to userspace.
  */
-static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
+static int allocinfo_ioctl_get_at(struct seq_file *m, void *arg)
 {
 	struct allocinfo_private *priv;
 	struct codetag *ct;
@@ -319,8 +316,7 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
 	struct alloc_tag_counters counters;
 	bool fetched_counters;
 
-	if (copy_from_user(&params, arg, sizeof(params)))
-		return -EFAULT;
+	memcpy(&params, arg, sizeof(params));
 
 	if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)
 		return -EINVAL;
@@ -375,9 +371,7 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
 	if (!ct)
 		return -ENOENT;
 
-	if (copy_to_user(arg, &params, sizeof(params)))
-		return -EFAULT;
-
+	memcpy(arg, &params, sizeof(params));
 	return 0;
 }
 
@@ -385,7 +379,7 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
  * Advances the ioctl iterator to the next allocation tag in the sequence and
  * returns its profiling data to userspace.
  */
-static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
+static int allocinfo_ioctl_get_next(struct seq_file *m, void *arg)
 {
 	struct allocinfo_private *priv;
 	struct codetag *ct;
@@ -425,10 +419,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
 	codetag_unlock_module_list(alloc_tag_cttype);
 	mutex_unlock(&priv->ioctl_lock);
 
-	if (ret == 0) {
-		if (copy_to_user(arg, &params, sizeof(params)))
-			return -EFAULT;
-	}
+	if (ret == 0)
+		memcpy(arg, &params, sizeof(params));
 	return ret;
 }
 
@@ -436,10 +428,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
  * Entry point ioctl function for /proc/allocinfo routing requests to fetch the
  * layout content ID, seek to a specific tag, or read sequential tags.
  */
-static long allocinfo_ioctl(struct file *file, unsigned int cmd,
-			    unsigned long __arg)
+static long allocinfo_ioctl(struct file *file, unsigned int cmd, void *arg)
 {
-	void __user *arg = (void __user *)__arg;
 	int ret;
 
 	switch (cmd) {
@@ -460,23 +450,20 @@ static long allocinfo_ioctl(struct file *file, unsigned int cmd,
 	return ret;
 }
 
-#ifdef CONFIG_COMPAT
-static long allocinfo_compat_ioctl(struct file *file, unsigned int cmd,
-				   unsigned long arg)
-{
-	return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
-}
-#endif
+static const struct proc_ioctl_arg allocinfo_ioctl_args[] = {
+	PROC_IOCTL_IOC(ALLOCINFO_IOC_CONTENT_ID),
+	PROC_IOCTL_IOC(ALLOCINFO_IOC_GET_AT),
+	PROC_IOCTL_IOC(ALLOCINFO_IOC_GET_NEXT),
+};
 
 static const struct proc_ops allocinfo_proc_ops = {
 	.proc_seq_ops		= &allocinfo_seq_op,
 	.proc_state_size	= sizeof(struct allocinfo_private),
 	.proc_open		= allocinfo_open,
 	.proc_release		= allocinfo_release,
-	.proc_ioctl		= allocinfo_ioctl,
-#ifdef CONFIG_COMPAT
-	.proc_compat_ioctl	= allocinfo_compat_ioctl,
-#endif
+	.proc_ioctl_buf		= allocinfo_ioctl,
+	.proc_ioctl_args	= allocinfo_ioctl_args,
+	.proc_nr_ioctl_args	= ARRAY_SIZE(allocinfo_ioctl_args),
 };
 
 size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 27/30] mm/swapfile: let procfs own the seq_file of /proc/swaps
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (25 preceding siblings ...)
  2026-11-04 19:56   ` [PATCH RESEND v2 26/30] alloc_tag: let procfs copy the ioctl arguments " Christian Brauner
@ 2026-11-04 19:56   ` Christian Brauner
  2026-11-04 19:56   ` [PATCH RESEND v2 28/30] mm/swapfile: let procfs own the wait queue " Christian Brauner
                     ` (3 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:56 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and let procfs open and free the seq_file.
->proc_open() only records the event count for ->proc_poll() now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 mm/swapfile.c | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/mm/swapfile.c b/mm/swapfile.c
index 254ce86fa923..ee4868ecc3a7 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -3410,26 +3410,19 @@ static const struct seq_operations swaps_op = {
 	.show =		swap_show
 };
 
+/* procfs opens the seq_file */
 static int swaps_open(struct inode *inode, struct file *file)
 {
-	struct seq_file *seq;
-	int ret;
-
-	ret = seq_open(file, &swaps_op);
-	if (ret)
-		return ret;
+	struct seq_file *seq = file->private_data;
 
-	seq = file->private_data;
 	seq->poll_event = atomic_read(&proc_poll_event);
 	return 0;
 }
 
 static const struct proc_ops swaps_proc_ops = {
 	.proc_flags	= PROC_ENTRY_PERMANENT,
+	.proc_seq_ops	= &swaps_op,
 	.proc_open	= swaps_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
-	.proc_release	= seq_release,
 	.proc_poll	= swaps_poll,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 28/30] mm/swapfile: let procfs own the wait queue of /proc/swaps
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (26 preceding siblings ...)
  2026-11-04 19:56   ` [PATCH RESEND v2 27/30] mm/swapfile: let procfs own the seq_file of /proc/swaps Christian Brauner
@ 2026-11-04 19:56   ` Christian Brauner
  2026-11-04 19:56   ` [PATCH RESEND v2 29/30] kernel/configs: let procfs copy out reads of /proc/config.gz Christian Brauner
                     ` (2 subsequent siblings)
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:56 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

swaps_poll() sleeps the poller on a queue of its own. procfs owns the
queue poll() uses now, so leave the poll_wait() to it and wake its
pollers when a swap area comes or goes.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 mm/swapfile.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/mm/swapfile.c b/mm/swapfile.c
index ee4868ecc3a7..1d063a1cef43 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -111,7 +111,7 @@ static struct kmem_cache *swap_table_cachep;
 /* Protects si->swap_file for /proc/swaps usage */
 static DEFINE_MUTEX(swapon_mutex);
 
-static DECLARE_WAIT_QUEUE_HEAD(proc_poll_wait);
+static struct proc_dir_entry *proc_swaps;
 /* Activity counter to indicate that a swapon or swapoff has occurred */
 static atomic_t proc_poll_event = ATOMIC_INIT(0);
 
@@ -3307,7 +3307,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
 
 	err = 0;
 	atomic_inc(&proc_poll_event);
-	wake_up_interruptible(&proc_poll_wait);
+	proc_poll_notify(proc_swaps);
 
 out_dput:
 	filp_close(victim, NULL);
@@ -3319,8 +3319,6 @@ static __poll_t swaps_poll(struct file *file, poll_table *wait)
 {
 	struct seq_file *seq = file->private_data;
 
-	poll_wait(file, &proc_poll_wait, wait);
-
 	if (seq->poll_event != atomic_read(&proc_poll_event)) {
 		seq->poll_event = atomic_read(&proc_poll_event);
 		return EPOLLIN | EPOLLRDNORM | EPOLLERR | EPOLLPRI;
@@ -3428,7 +3426,7 @@ static const struct proc_ops swaps_proc_ops = {
 
 static int __init procswaps_init(void)
 {
-	proc_create("swaps", 0, NULL, &swaps_proc_ops);
+	proc_swaps = proc_create("swaps", 0, NULL, &swaps_proc_ops);
 	return 0;
 }
 __initcall(procswaps_init);
@@ -3907,7 +3905,7 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
 
 	mutex_unlock(&swapon_mutex);
 	atomic_inc(&proc_poll_event);
-	wake_up_interruptible(&proc_poll_wait);
+	proc_poll_notify(proc_swaps);
 
 	error = 0;
 	goto out;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 29/30] kernel/configs: let procfs copy out reads of /proc/config.gz
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (27 preceding siblings ...)
  2026-11-04 19:56   ` [PATCH RESEND v2 28/30] mm/swapfile: let procfs own the wait queue " Christian Brauner
@ 2026-11-04 19:56   ` Christian Brauner
  2026-11-04 19:57   ` [PATCH RESEND v2 30/30] selftests/proc: check that unloading configs doesn't wait for a reader Christian Brauner
  2026-11-04 20:05   ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Linus Torvalds
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:56 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read to userspace
after it unpinned the entry. Unloading configs.ko then no longer waits
for a reader that faults on its buffer. /proc/config.gz asks for
PROC_ENTRY_FULL_READS so that reading all of it at once keeps working.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 kernel/configs.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/kernel/configs.c b/kernel/configs.c
index a28c79c5f713..05b12c440168 100644
--- a/kernel/configs.c
+++ b/kernel/configs.c
@@ -38,17 +38,18 @@ extern char kernel_config_data;
 extern char kernel_config_data_end;
 
 static ssize_t
-ikconfig_read_current(struct file *file, char __user *buf,
+ikconfig_read_current(struct file *file, char *buf,
 		      size_t len, loff_t * offset)
 {
-	return simple_read_from_buffer(buf, len, offset,
+	return memory_read_from_buffer(buf, len, offset,
 				       &kernel_config_data,
 				       &kernel_config_data_end -
 				       &kernel_config_data);
 }
 
 static const struct proc_ops config_gz_proc_ops = {
-	.proc_read	= ikconfig_read_current,
+	.proc_flags	= PROC_ENTRY_FULL_READS,
+	.proc_read_buf	= ikconfig_read_current,
 	.proc_lseek	= default_llseek,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 30/30] selftests/proc: check that unloading configs doesn't wait for a reader
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (28 preceding siblings ...)
  2026-11-04 19:56   ` [PATCH RESEND v2 29/30] kernel/configs: let procfs copy out reads of /proc/config.gz Christian Brauner
@ 2026-11-04 19:57   ` Christian Brauner
  2026-11-04 20:05   ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Linus Torvalds
  30 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:57 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Read /proc/config.gz into a buffer whose first page is backed by
userfaultfd and unload configs.ko while procfs copies what was read to
userspace:

- removing the entry doesn't wait for the reader

- the reader still gets the page that was read before the removal, and
  nothing more

- and the next read fails with -EIO

The test loads configs.ko if it isn't there, skips if configs is built
in or can't be unloaded, and loads it again afterwards. It needs
CONFIG_IKCONFIG=m. kselftest-merge keeps the =y that other fragments
ask for, so it skips there.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore            |   1 +
 tools/testing/selftests/proc/Makefile              |   1 +
 tools/testing/selftests/proc/config                |   2 +
 .../proc/proc-remove-slow-config-reader.c          | 157 +++++++++++++++++++++
 4 files changed, 161 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index a1994bda676c..ed863db1688f 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -21,6 +21,7 @@
 /proc-uptime-001
 /proc-uptime-002
 /proc-pidns
+/proc-remove-slow-config-reader
 /proc-remove-slow-sysctl
 /proc-remove-slow-reader
 /proc-remove-mapped
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index 8fcab5af502b..95c9212fb670 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -33,6 +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-config-reader
 TEST_GEN_PROGS += proc-remove-slow-reader
 TEST_GEN_PROGS += proc-remove-mapped
 TEST_GEN_PROGS += proc-remove-slow-readlink
diff --git a/tools/testing/selftests/proc/config b/tools/testing/selftests/proc/config
index 992577d15295..15b307189392 100644
--- a/tools/testing/selftests/proc/config
+++ b/tools/testing/selftests/proc/config
@@ -11,3 +11,5 @@ CONFIG_SND=y
 CONFIG_SND_DUMMY=m
 CONFIG_MODULES=y
 CONFIG_MODULE_UNLOAD=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
diff --git a/tools/testing/selftests/proc/proc-remove-slow-config-reader.c b/tools/testing/selftests/proc/proc-remove-slow-config-reader.c
new file mode 100644
index 000000000000..5f7bcb27b315
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-remove-slow-config-reader.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A read that faults while procfs copies it to userspace must not hold up
+ * removal of the /proc entry. Unloading configs.ko removes /proc/config.gz,
+ * which isn't a seq_file.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/userfaultfd.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define CONFIG_PATH	"/proc/config.gz"
+#define CONFIG_MODULE	"/sys/module/configs"
+#define READ_PAGES	2
+#define TIMEOUT_MS	5000
+
+struct reader {
+	int fd;
+	char *buf;
+	size_t len;
+	ssize_t ret;
+};
+
+struct unloader {
+	int done;
+	int ret;
+};
+
+static void *read_thread(void *arg)
+{
+	struct reader *r = arg;
+
+	r->ret = read(r->fd, r->buf, r->len);
+	return NULL;
+}
+
+static void *unload_thread(void *arg)
+{
+	struct unloader *u = arg;
+	ssize_t ret;
+
+	u->ret = syscall(__NR_delete_module, "configs", O_NONBLOCK) ? -errno : 0;
+	ret = write(u->done, "x", 1);
+	return (void *)ret;
+}
+
+FIXTURE(configs) {
+	bool loaded;
+};
+
+FIXTURE_SETUP(configs)
+{
+	self->loaded = false;
+	if (access(CONFIG_PATH, R_OK) && system("modprobe -q configs"))
+		SKIP(return, "no " CONFIG_PATH);
+	if (access(CONFIG_PATH, R_OK))
+		SKIP(return, CONFIG_PATH ": %s", strerror(errno));
+	if (access(CONFIG_MODULE, F_OK))
+		SKIP(return, "configs isn't a module");
+	self->loaded = true;
+}
+
+FIXTURE_TEARDOWN(configs)
+{
+	/* an ASSERT may have fired with the module unloaded */
+	if (self->loaded && access(CONFIG_PATH, F_OK) &&
+	    system("modprobe -q configs"))
+		TH_LOG("couldn't load configs again");
+}
+
+TEST_F(configs, 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 unloader u = {};
+	struct reader r = {};
+	struct uffd_msg msg;
+	struct pollfd pfd;
+	pthread_t rt, ut;
+	int uffd, done[2];
+	char *buf, *ref;
+	ssize_t ref_len;
+
+	r.fd = open(CONFIG_PATH, O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(r.fd, 0);
+
+	/* what the first page of a read looks like */
+	ref = malloc(psz);
+	ASSERT_NE(ref, NULL);
+	ref_len = pread(r.fd, ref, psz, 0);
+	ASSERT_GT(ref_len, 0);
+
+	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, &reg), 0);
+
+	r.buf = buf;
+	r.len = READ_PAGES * psz;
+	ASSERT_EQ(pthread_create(&rt, NULL, read_thread, &r), 0);
+
+	/* The reader now sleeps in the 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);
+
+	/* Unloading configs.ko removes the file that is being read. */
+	ASSERT_EQ(pipe2(done, O_CLOEXEC), 0);
+	u.done = done[1];
+	ASSERT_EQ(pthread_create(&ut, NULL, unload_thread, &u), 0);
+	pfd = (struct pollfd){ .fd = done[0], .events = POLLIN };
+	EXPECT_EQ(poll(&pfd, 1, TIMEOUT_MS), 1) {
+		TH_LOG("removing " CONFIG_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(ut, NULL), 0);
+	if (u.ret)
+		SKIP(return, "unloading configs: %s", strerror(-u.ret));
+
+	/*
+	 * The page that was read before the removal still arrives and the
+	 * next piece isn't filled anymore, then nothing.
+	 */
+	EXPECT_EQ(r.ret, ref_len);
+	EXPECT_EQ(memcmp(buf, ref, ref_len), 0);
+	EXPECT_EQ(read(r.fd, buf, psz), -1);
+	EXPECT_EQ(errno, EIO);
+	free(ref);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7)
  2026-11-04 19:04 [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does Christian Brauner
                   ` (4 preceding siblings ...)
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
@ 2026-11-04 19:57 ` Christian Brauner
  2026-11-04 19:57   ` [PATCH RESEND v2 01/13] atm: let procfs copy out reads of the device files Christian Brauner
                     ` (7 more replies)
  5 siblings, 8 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:57 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Part 6/7 of "proc: move procfs to what kernfs does", on top of part 2
and independent of parts 3 to 5:

  https://patch.msgid.link/20261104-work-procfs-v2-0-b45cd5789ac5@brauner

This converts the proc entries of atm, l2tp, pktgen, xt_recent and
sunrpc, and the kfifo samples, to what "proc: let procfs own the data
path" set up. Afterwards the entries' code only runs with the entry
pinned and never touches userspace memory. One patch per subsystem and
method.

- procfs owns the seq_file of /proc/net/pppol2tp, the xt_recent tables
  and the sunrpc cache content files. Their open and release hooks only
  take and drop references now, and reading an xt_recent table or a
  cache file no longer holds up its removal.
- procfs copies in the writes of pktgen, the xt_recent tables, the
  sunrpc channel, flush and use-gss-proxy files and the kfifo samples.
  The sunrpc channel files take 32k downcalls, which is what
  ->proc_write_size is for. Where a write larger than anything the entry
  parses now fails with -EINVAL from procfs the commit says so.
- procfs copies out the reads of the atm device files, the sunrpc
  channel, flush, use-gss-proxy and gss_krb5_enctypes files and the
  kfifo samples. The sunrpc channel and the kfifo samples are streams,
  they hand out what is read before procfs copies it out, so a reader
  that passes a bad buffer loses that part.
- the ioctl of the sunrpc channel files goes away, do_vfs_ioctl()
  answers FIONREAD for regular files before the file's ioctl ever sees
  it.
- the sunrpc channel files move their pollers to the wait queue procfs
  owns for the entry, which is what the static queue in the bugfix
  series stood in for. The rpc_pipefs channel files keep their
  queue_wait, procfs has nothing to do with them.

The selftest polls the channel file of a sunrpc cache in a network
namespace of its own and lets the namespace die while the poller sleeps.
On a KASAN and lockdep kernel it fails without the series, nothing wakes
the poller and it times out after ten seconds, and passes with it. Build
tested per commit on x86_64 and with W=1 on the touched objects. The
pktgen selftest behaves the same with and without the series and so do
the entries I could get at in a VM.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Changes in v2:
- sunrpc lets procfs own the wait queue of the channel files, which is
  what the static queue_wait in the bugfix series stood in for
- new selftest: removing a polled entry wakes its pollers
- Acked-by from Chuck on the seq_file and ioctl patches
- the selftest commit applies on the core series alone
- rebased onto v7.4-rc1
- Link to v1: https://patch.msgid.link/20261102-work-procfs-net-v1-0-982104a4f592@kernel.org

---
Christian Brauner (13):
      atm: let procfs copy out reads of the device files
      l2tp: let procfs own the seq_file of /proc/net/pppol2tp
      net: pktgen: let procfs copy in commands
      netfilter: xt_recent: let procfs own the seq_file
      netfilter: xt_recent: let procfs copy in writes
      SUNRPC: let procfs own the seq_file of the cache content file
      SUNRPC: let procfs copy in writes
      SUNRPC: let procfs copy out reads
      SUNRPC: drop the ioctl of the procfs channel files
      SUNRPC: let procfs own the wait queue of the procfs channel files
      samples/kfifo: let procfs copy in writes
      samples/kfifo: let procfs copy out reads
      selftests/proc: check that removing a polled entry wakes its pollers

 include/linux/sunrpc/cache.h                      |   2 +
 net/atm/proc.c                                    |   9 +-
 net/core/pktgen.c                                 | 128 ++++++---------
 net/l2tp/l2tp_ppp.c                               |  20 +--
 net/netfilter/xt_recent.c                         |  19 +--
 net/sunrpc/auth_gss/svcauth_gss.c                 |  34 ++--
 net/sunrpc/cache.c                                | 151 ++++++++++-------
 samples/kfifo/bytestream-example.c                |  18 +--
 samples/kfifo/inttype-example.c                   |  20 +--
 samples/kfifo/record-example.c                    |  21 ++-
 tools/testing/selftests/proc/.gitignore           |   1 +
 tools/testing/selftests/proc/Makefile             |   1 +
 tools/testing/selftests/proc/config               |   2 +
 tools/testing/selftests/proc/proc-remove-polled.c | 189 ++++++++++++++++++++++
 14 files changed, 382 insertions(+), 233 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261101-work-procfs-net-bc8b21e3eff0
prerequisite-change-id: 20261101-work-procfs-core-8fa4a4f412cf:v2
prerequisite-patch-id: a81e8b894107e48d607f8257d2c9f56e0dd6f400
prerequisite-patch-id: 7edf7ca7ecd32c9e09085e4b65922af10097beaa
prerequisite-patch-id: 81f6893f79b4236637675553298a6f2892b603f3
prerequisite-patch-id: 598c2b1a389f47adcac88ebcf1c9f74e2511fbab
prerequisite-patch-id: 6f5a2172dacc5ff2491fa9832c48ccd0e1ce1eda
prerequisite-patch-id: 8947af6490288e4e2a3b2bf8844a974bedb1d975
prerequisite-patch-id: 8cbe756286fd95967d31b0c46cacfeb05f0a2c6b
prerequisite-patch-id: 3fa159f2b6ccd0ac51a2662e4eec5587ec18de89
prerequisite-patch-id: bfb4e4ffe9631cb58015fefe6bd3698cd53e2244
prerequisite-patch-id: 56ff6667a57a962264c07c0227c61aa2f1f4c22c
prerequisite-patch-id: bc506323e29944ec0c29eae84950c706a8592042
prerequisite-patch-id: 326c0ebb5accf0f40cbee1323fb4c3378f5be154
prerequisite-patch-id: d6eaf7690a54a0dfda7500936a048b769fa265dd
prerequisite-patch-id: 7c7b2535642b4d388e13eab518cbe0f9550eb1e6
prerequisite-patch-id: 0474af12959624910718795bf86684383664c685
prerequisite-patch-id: f6d4191e23c22e2d2cc90cc5d04781f913e98a8e
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: 43b92dfc1a98fbf0d14597a3e3ae99972844773b
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: d58c239fba616b366a725de6acba3f88734156d0
prerequisite-patch-id: dbe02fe3f784776fda5e7ae99f077bef0cd4b48d
prerequisite-patch-id: 53edc9a95afff57c4acba40095a8054924261ea2
prerequisite-patch-id: 5348d8957eb7eae7430ca8b101aa84ce77d3aa4f
prerequisite-patch-id: 36c96d781e2dd80b20932667bbe6e7fd14976069
prerequisite-patch-id: efeb2d6f7df71d5dede2c1d12fe999a28e38aab9
prerequisite-patch-id: 17f95c7fdf0bdc7c8aaaf7883bff1dcc19a0f529
prerequisite-patch-id: 117db27dcdef433de7c6be5e95973cac1e373927
prerequisite-patch-id: a0844aa26ffb65ccb362b88ef6c2f4758e15edbf
prerequisite-patch-id: 9c64355dfde62e399f2717d9208c4607834d4650
prerequisite-patch-id: 9644739854ddfe2876f17fda8727fd5119255957
prerequisite-patch-id: c61bba24c035f79a2a32a80ace0924ab58e97f46
prerequisite-patch-id: 143fb91bddf33068b6ee11e205818c4c2815e23d
prerequisite-patch-id: 9ce9be2ff05250f92d945c0084f4bb31d3d0b27d
prerequisite-patch-id: aee0961341523547095a43ae298cb8b551302f10
prerequisite-patch-id: 2f0fdb970ddd760d85ff5d2caad0b96348270182
prerequisite-patch-id: f2aa468ff5cbb1cabab3f70e1b8eeee0286cfb4a
prerequisite-patch-id: 5555895bcbca4d7c9888ecc9e82c0e4d76da8f40
prerequisite-patch-id: 487141c49e2548310bfb6c2a0964a78f08b285a9
prerequisite-patch-id: c3d3008819fa5629a18142ec9ba6d7024ade7fdb
prerequisite-patch-id: ca587c976690ac16e3d759fccab2116fef0508e8
prerequisite-patch-id: 9423f410b99853693db06c29f12f1c08278b050d
prerequisite-patch-id: e269815617a6324615a38916b8886f99d98c641e
prerequisite-patch-id: 3d24be1897a624d519ef144801f76d152c7528a9
prerequisite-patch-id: 1ea3fc8319d9012faaf3a182ac61d5da89aafdb7
prerequisite-patch-id: 7547b4e2c1b3588acf6caf90cc01fe7884fdb6bd
prerequisite-patch-id: 695d325349ebeb89bf0ea24bcf0c4f1c4edacab5
prerequisite-patch-id: e33502a202979aba0d6fdaf196e8471d906992a1
prerequisite-patch-id: 47bfe4f07677ad75fe8d9accc8923a1cbdf3afa6
prerequisite-patch-id: 9fc0739226572203fcb36f2c271be7b27f405ef2


^ permalink raw reply	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 01/13] atm: let procfs copy out reads of the device files
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
@ 2026-11-04 19:57   ` Christian Brauner
  2026-11-04 19:58   ` [PATCH RESEND v2 02/13] l2tp: let procfs own the seq_file of /proc/net/pppol2tp Christian Brauner
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:57 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() so procfs copies what is read from
/proc/net/atm/<dev> to userspace after it unpinned the entry. The
drivers' ->proc_read() keep filling their page.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/atm/proc.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/net/atm/proc.c b/net/atm/proc.c
index ca0fc6b90998..cea930b4b840 100644
--- a/net/atm/proc.c
+++ b/net/atm/proc.c
@@ -30,11 +30,11 @@
 #include "resources.h"
 #include "common.h" /* atm_proc_init prototype */
 
-static ssize_t proc_dev_atm_read(struct file *file, char __user *buf,
+static ssize_t proc_dev_atm_read(struct file *file, char *buf,
 				 size_t count, loff_t *pos);
 
 static const struct proc_ops atm_dev_proc_ops = {
-	.proc_read	= proc_dev_atm_read,
+	.proc_read_buf	= proc_dev_atm_read,
 	.proc_lseek	= noop_llseek,
 };
 
@@ -246,7 +246,7 @@ static const struct seq_operations vcc_seq_ops = {
 	.show	= vcc_seq_show,
 };
 
-static ssize_t proc_dev_atm_read(struct file *file, char __user *buf,
+static ssize_t proc_dev_atm_read(struct file *file, char *buf,
 				 size_t count, loff_t *pos)
 {
 	struct atm_dev *dev;
@@ -267,8 +267,7 @@ static ssize_t proc_dev_atm_read(struct file *file, char __user *buf,
 			length = -EINVAL;
 	}
 	if (length >= 0) {
-		if (copy_to_user(buf, (char *)page, length))
-			length = -EFAULT;
+		memcpy(buf, (char *)page, length);
 		(*pos)++;
 	}
 	free_page(page);

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 02/13] l2tp: let procfs own the seq_file of /proc/net/pppol2tp
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
  2026-11-04 19:57   ` [PATCH RESEND v2 01/13] atm: let procfs copy out reads of the device files Christian Brauner
@ 2026-11-04 19:58   ` Christian Brauner
  2026-11-04 19:58   ` [PATCH RESEND v2 03/13] net: pktgen: let procfs copy in commands Christian Brauner
                     ` (5 subsequent siblings)
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:58 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and the size of the iterator state and let
procfs open and free the seq_file. ->proc_open() only takes the
reference to the network namespace now and ->proc_release() drops that
and the references the iterator holds. Reading the file then no longer
holds up its removal.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/l2tp/l2tp_ppp.c | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/net/l2tp/l2tp_ppp.c b/net/l2tp/l2tp_ppp.c
index 4f311d473a64..499e37eb788a 100644
--- a/net/l2tp/l2tp_ppp.c
+++ b/net/l2tp/l2tp_ppp.c
@@ -1596,21 +1596,17 @@ static const struct seq_operations pppol2tp_seq_ops = {
 	.show		= pppol2tp_seq_show,
 };
 
+/* procfs opened the seq_file and allocated @pd */
 static int pppol2tp_proc_open(struct inode *inode, struct file *file)
 {
-	struct net *net = pde_data(inode);
-	struct pppol2tp_seq_data *pd;
+	struct seq_file *seq = file->private_data;
+	struct pppol2tp_seq_data *pd __maybe_unused = seq->private;
+	struct net *net;
 
-	net = maybe_get_net(net);
+	net = maybe_get_net(pde_data(inode));
 	if (!net)
 		return -ENXIO;
 
-	pd = __seq_open_private(file, &pppol2tp_seq_ops, sizeof(*pd));
-	if (!pd) {
-		put_net(net);
-		return -ENOMEM;
-	}
-
 #ifdef CONFIG_NET_NS
 	pd->p.net = net;
 	netns_tracker_alloc(net, &pd->p.ns_tracker, GFP_KERNEL);
@@ -1633,13 +1629,13 @@ static int pppol2tp_proc_release(struct inode *inode, struct file *file)
 #else
 	put_net(&init_net);
 #endif
-	return seq_release_private(inode, file);
+	return 0;
 }
 
 static const struct proc_ops pppol2tp_proc_ops = {
+	.proc_seq_ops	= &pppol2tp_seq_ops,
+	.proc_state_size = sizeof(struct pppol2tp_seq_data),
 	.proc_open	= pppol2tp_proc_open,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
 	.proc_release	= pppol2tp_proc_release,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 03/13] net: pktgen: let procfs copy in commands
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
  2026-11-04 19:57   ` [PATCH RESEND v2 01/13] atm: let procfs copy out reads of the device files Christian Brauner
  2026-11-04 19:58   ` [PATCH RESEND v2 02/13] l2tp: let procfs own the seq_file of /proc/net/pppol2tp Christian Brauner
@ 2026-11-04 19:58   ` Christian Brauner
  2026-11-04 19:58   ` [PATCH RESEND v2 04/13] netfilter: xt_recent: let procfs own the seq_file Christian Brauner
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:58 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for the files in /proc/net/pktgen so
procfs copies in what is written before it pins the entry, and parse
the commands from that copy. The parse helpers read the copy instead of
fetching every character with get_user(). Commands longer than
PAGE_SIZE - 1 bytes fail with -EINVAL now.

With debug on, the dump of the command went through strndup_user() and
failed the write with -EINVAL when there was no NUL right behind the
command in the writer's buffer. The copy is NUL-terminated, so that's
gone.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/core/pktgen.c | 128 +++++++++++++++++++-----------------------------------
 1 file changed, 45 insertions(+), 83 deletions(-)

diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index 12363a373fb8..84f4db86a5b0 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -516,11 +516,9 @@ static int pgctrl_show(struct seq_file *seq, void *v)
 	return 0;
 }
 
-static ssize_t pgctrl_write(struct file *file, const char __user *buf,
-			    size_t count, loff_t *ppos)
+static ssize_t pgctrl_write(struct file *file, char *buf, size_t count,
+			    loff_t *ppos)
 {
-	char data[128];
-	size_t max;
 	struct pktgen_net *pn = net_generic(current->nsproxy->net_ns, pg_net_id);
 
 	if (!capable(CAP_NET_ADMIN))
@@ -529,20 +527,15 @@ static ssize_t pgctrl_write(struct file *file, const char __user *buf,
 	if (count < 1)
 		return -EINVAL;
 
-	max = min(count, sizeof(data) - 1);
-	if (copy_from_user(data, buf, max))
-		return -EFAULT;
+	/* strip trailing '\n' */
+	if (buf[count - 1] == '\n')
+		buf[count - 1] = 0;
 
-	if (data[max - 1] == '\n')
-		data[max - 1] = 0; /* strip trailing '\n', terminate string */
-	else
-		data[max] = 0; /* terminate string */
-
-	if (!strcmp(data, "stop"))
+	if (!strcmp(buf, "stop"))
 		pktgen_stop_all_threads(pn);
-	else if (!strcmp(data, "start"))
+	else if (!strcmp(buf, "start"))
 		pktgen_run_all_threads(pn);
-	else if (!strcmp(data, "reset"))
+	else if (!strcmp(buf, "reset"))
 		pktgen_reset_all_threads(pn);
 	else
 		return -EINVAL;
@@ -552,7 +545,7 @@ static ssize_t pgctrl_write(struct file *file, const char __user *buf,
 
 static const struct proc_ops pktgen_proc_ops = {
 	.proc_show	= pgctrl_show,
-	.proc_write	= pgctrl_write,
+	.proc_write_buf	= pgctrl_write,
 };
 
 static int pktgen_if_show(struct seq_file *seq, void *v)
@@ -741,7 +734,7 @@ static int pktgen_if_show(struct seq_file *seq, void *v)
 }
 
 
-static ssize_t hex32_arg(const char __user *user_buffer, size_t maxlen,
+static ssize_t hex32_arg(const char *user_buffer, size_t maxlen,
 			 __u32 *num)
 {
 	size_t i = 0;
@@ -752,8 +745,7 @@ static ssize_t hex32_arg(const char __user *user_buffer, size_t maxlen,
 		int value;
 		char c;
 
-		if (get_user(c, &user_buffer[i]))
-			return -EFAULT;
+		c = user_buffer[i];
 		value = hex_to_bin(c);
 		if (value >= 0) {
 			*num <<= 4;
@@ -765,15 +757,14 @@ static ssize_t hex32_arg(const char __user *user_buffer, size_t maxlen,
 	return i;
 }
 
-static ssize_t count_trail_chars(const char __user *user_buffer, size_t maxlen)
+static ssize_t count_trail_chars(const char *user_buffer, size_t maxlen)
 {
 	size_t i;
 
 	for (i = 0; i < maxlen; i++) {
 		char c;
 
-		if (get_user(c, &user_buffer[i]))
-			return -EFAULT;
+		c = user_buffer[i];
 		switch (c) {
 		case '\"':
 		case '\n':
@@ -790,7 +781,7 @@ done:
 	return i;
 }
 
-static ssize_t num_arg(const char __user *user_buffer, size_t maxlen,
+static ssize_t num_arg(const char *user_buffer, size_t maxlen,
 		       unsigned long *num)
 {
 	size_t i;
@@ -799,8 +790,7 @@ static ssize_t num_arg(const char __user *user_buffer, size_t maxlen,
 	for (i = 0; i < maxlen; i++) {
 		char c;
 
-		if (get_user(c, &user_buffer[i]))
-			return -EFAULT;
+		c = user_buffer[i];
 		if ((c >= '0') && (c <= '9')) {
 			*num *= 10;
 			*num += c - '0';
@@ -810,15 +800,14 @@ static ssize_t num_arg(const char __user *user_buffer, size_t maxlen,
 	return i;
 }
 
-static ssize_t strn_len(const char __user *user_buffer, size_t maxlen)
+static ssize_t strn_len(const char *user_buffer, size_t maxlen)
 {
 	size_t i;
 
 	for (i = 0; i < maxlen; i++) {
 		char c;
 
-		if (get_user(c, &user_buffer[i]))
-			return -EFAULT;
+		c = user_buffer[i];
 		switch (c) {
 		case '\"':
 		case '\n':
@@ -840,7 +829,7 @@ done_str:
  * where each entry consists of size and weight delimited by commas.
  * "size1,weight_1 size2,weight_2 ... size_n,weight_n" for example.
  */
-static ssize_t get_imix_entries(const char __user *buffer,
+static ssize_t get_imix_entries(const char *buffer,
 				size_t maxlen,
 				struct pktgen_dev *pkt_dev)
 {
@@ -867,8 +856,7 @@ static ssize_t get_imix_entries(const char __user *buffer,
 		i += len;
 		if (i >= maxlen)
 			return -EINVAL;
-		if (get_user(c, &buffer[i]))
-			return -EFAULT;
+		c = buffer[i];
 		/* Check for comma between size_i and weight_i */
 		if (c != ',')
 			return -EINVAL;
@@ -894,15 +882,14 @@ static ssize_t get_imix_entries(const char __user *buffer,
 
 		if (i >= maxlen)
 			break;
-		if (get_user(c, &buffer[i]))
-			return -EFAULT;
+		c = buffer[i];
 		i++;
 	} while (c == ' ');
 
 	return i;
 }
 
-static ssize_t get_labels(const char __user *buffer,
+static ssize_t get_labels(const char *buffer,
 			  size_t maxlen, struct pktgen_dev *pkt_dev)
 {
 	unsigned int n = 0;
@@ -936,8 +923,7 @@ static ssize_t get_labels(const char __user *buffer,
 		n++;
 		if (i >= maxlen)
 			break;
-		if (get_user(c, &buffer[i]))
-			return -EFAULT;
+		c = buffer[i];
 		i++;
 	} while (c == ',');
 
@@ -974,9 +960,8 @@ static __u32 pktgen_read_flag(const char *f, bool *disable)
 	return 0;
 }
 
-static ssize_t pktgen_if_write(struct file *file,
-			       const char __user *user_buffer, size_t count,
-			       loff_t *offset)
+static ssize_t pktgen_if_write(struct file *file, char *user_buffer,
+			       size_t count, loff_t *offset)
 {
 	struct seq_file *seq = file->private_data;
 	struct pktgen_dev *pkt_dev = seq->private;
@@ -1009,8 +994,7 @@ static ssize_t pktgen_if_write(struct file *file,
 		return len;
 
 	memset(name, 0, sizeof(name));
-	if (copy_from_user(name, &user_buffer[i], len))
-		return -EFAULT;
+	memcpy(name, &user_buffer[i], len);
 	i += len;
 
 	max = count - i;
@@ -1020,16 +1004,9 @@ static ssize_t pktgen_if_write(struct file *file,
 
 	i += len;
 
-	if (debug) {
-		size_t copy = min_t(size_t, count + 1, 1024);
-		char *tp = strndup_user(user_buffer, copy);
-
-		if (IS_ERR(tp))
-			return PTR_ERR(tp);
-
-		pr_debug("%s,%zu  buffer -:%s:-\n", name, count, tp);
-		kfree(tp);
-	}
+	if (debug)
+		pr_debug("%s,%zu  buffer -:%.*s:-\n", name, count,
+			 (int)min_t(size_t, count, 1023), user_buffer);
 
 	if (!strcmp(name, "min_pkt_size")) {
 		max = min(10, count - i);
@@ -1322,8 +1299,7 @@ static ssize_t pktgen_if_write(struct file *file,
 			return len;
 
 		memset(f, 0, sizeof(f));
-		if (copy_from_user(f, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(f, &user_buffer[i], len);
 
 		if (strcmp(f, "start_xmit") == 0) {
 			pkt_dev->xmit_mode = M_START_XMIT;
@@ -1362,8 +1338,7 @@ static ssize_t pktgen_if_write(struct file *file,
 			return len;
 
 		memset(f, 0, 32);
-		if (copy_from_user(f, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(f, &user_buffer[i], len);
 
 		flag = pktgen_read_flag(f, &disable);
 		if (flag) {
@@ -1410,8 +1385,7 @@ static ssize_t pktgen_if_write(struct file *file,
 		if (len < 0)
 			return len;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 		if (strcmp(buf, pkt_dev->dst_min) != 0) {
 			strscpy_pad(pkt_dev->dst_min, buf);
@@ -1430,8 +1404,7 @@ static ssize_t pktgen_if_write(struct file *file,
 		if (len < 0)
 			return len;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 		if (strcmp(buf, pkt_dev->dst_max) != 0) {
 			strscpy_pad(pkt_dev->dst_max, buf);
@@ -1452,8 +1425,7 @@ static ssize_t pktgen_if_write(struct file *file,
 
 		pkt_dev->flags |= F_IPV6;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 
 		in6_pton(buf, -1, pkt_dev->in6_daddr.s6_addr, -1, NULL);
@@ -1475,8 +1447,7 @@ static ssize_t pktgen_if_write(struct file *file,
 
 		pkt_dev->flags |= F_IPV6;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 
 		in6_pton(buf, -1, pkt_dev->min_in6_daddr.s6_addr, -1, NULL);
@@ -1497,8 +1468,7 @@ static ssize_t pktgen_if_write(struct file *file,
 
 		pkt_dev->flags |= F_IPV6;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 
 		in6_pton(buf, -1, pkt_dev->max_in6_daddr.s6_addr, -1, NULL);
@@ -1518,8 +1488,7 @@ static ssize_t pktgen_if_write(struct file *file,
 
 		pkt_dev->flags |= F_IPV6;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 
 		in6_pton(buf, -1, pkt_dev->in6_saddr.s6_addr, -1, NULL);
@@ -1539,8 +1508,7 @@ static ssize_t pktgen_if_write(struct file *file,
 		if (len < 0)
 			return len;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 		if (strcmp(buf, pkt_dev->src_min) != 0) {
 			strscpy_pad(pkt_dev->src_min, buf);
@@ -1559,8 +1527,7 @@ static ssize_t pktgen_if_write(struct file *file,
 		if (len < 0)
 			return len;
 
-		if (copy_from_user(buf, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(buf, &user_buffer[i], len);
 		buf[len] = 0;
 		if (strcmp(buf, pkt_dev->src_max) != 0) {
 			strscpy_pad(pkt_dev->src_max, buf);
@@ -1580,8 +1547,7 @@ static ssize_t pktgen_if_write(struct file *file,
 			return len;
 
 		memset(valstr, 0, sizeof(valstr));
-		if (copy_from_user(valstr, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(valstr, &user_buffer[i], len);
 
 		if (!mac_pton(valstr, pkt_dev->dst_mac))
 			return -EINVAL;
@@ -1598,8 +1564,7 @@ static ssize_t pktgen_if_write(struct file *file,
 			return len;
 
 		memset(valstr, 0, sizeof(valstr));
-		if (copy_from_user(valstr, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(valstr, &user_buffer[i], len);
 
 		if (!mac_pton(valstr, pkt_dev->src_mac))
 			return -EINVAL;
@@ -1864,7 +1829,7 @@ static ssize_t pktgen_if_write(struct file *file,
 
 static const struct proc_ops pktgen_if_proc_ops = {
 	.proc_show	= pktgen_if_show,
-	.proc_write	= pktgen_if_write,
+	.proc_write_buf	= pktgen_if_write,
 };
 
 static int pktgen_thread_show(struct seq_file *seq, void *v)
@@ -1897,8 +1862,7 @@ static int pktgen_thread_show(struct seq_file *seq, void *v)
 	return 0;
 }
 
-static ssize_t pktgen_thread_write(struct file *file,
-				   const char __user *user_buffer,
+static ssize_t pktgen_thread_write(struct file *file, char *user_buffer,
 				   size_t count, loff_t *offset)
 {
 	struct seq_file *seq = file->private_data;
@@ -1927,8 +1891,7 @@ static ssize_t pktgen_thread_write(struct file *file,
 		return len;
 
 	memset(name, 0, sizeof(name));
-	if (copy_from_user(name, &user_buffer[i], len))
-		return -EFAULT;
+	memcpy(name, &user_buffer[i], len);
 	i += len;
 
 	max = count - i;
@@ -1959,8 +1922,7 @@ static ssize_t pktgen_thread_write(struct file *file,
 			ret = len;
 			goto out;
 		}
-		if (copy_from_user(f, &user_buffer[i], len))
-			return -EFAULT;
+		memcpy(f, &user_buffer[i], len);
 
 		mutex_lock(&pktgen_thread_lock);
 		ret = pktgen_add_device(t, f);
@@ -1996,7 +1958,7 @@ out:
 
 static const struct proc_ops pktgen_thread_proc_ops = {
 	.proc_show	= pktgen_thread_show,
-	.proc_write	= pktgen_thread_write,
+	.proc_write_buf	= pktgen_thread_write,
 };
 
 /* Think find or remove for NN */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 04/13] netfilter: xt_recent: let procfs own the seq_file
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
                     ` (2 preceding siblings ...)
  2026-11-04 19:58   ` [PATCH RESEND v2 03/13] net: pktgen: let procfs copy in commands Christian Brauner
@ 2026-11-04 19:58   ` Christian Brauner
  2026-11-04 19:58   ` [PATCH RESEND v2 05/13] netfilter: xt_recent: let procfs copy in writes Christian Brauner
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:58 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and the size of the iterator state and let
procfs open and free the seq_file. ->proc_open() only fills in the table
now. Reading /proc/net/xt_recent/<table> then no longer holds up the
removal of the table.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/netfilter/xt_recent.c | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/net/netfilter/xt_recent.c b/net/netfilter/xt_recent.c
index 55f1f2d89952..a43fff5308da 100644
--- a/net/netfilter/xt_recent.c
+++ b/net/netfilter/xt_recent.c
@@ -546,11 +546,7 @@ static const struct seq_operations recent_seq_ops = {
 
 static int recent_seq_open(struct inode *inode, struct file *file)
 {
-	struct recent_iter_state *st;
-
-	st = __seq_open_private(file, &recent_seq_ops, sizeof(*st));
-	if (st == NULL)
-		return -ENOMEM;
+	struct recent_iter_state *st = ((struct seq_file *)file->private_data)->private;
 
 	st->table    = pde_data(inode);
 	return 0;
@@ -626,11 +622,10 @@ recent_mt_proc_write(struct file *file, const char __user *input,
 }
 
 static const struct proc_ops recent_mt_proc_ops = {
+	.proc_seq_ops	= &recent_seq_ops,
+	.proc_state_size = sizeof(struct recent_iter_state),
 	.proc_open	= recent_seq_open,
-	.proc_read	= seq_read,
 	.proc_write	= recent_mt_proc_write,
-	.proc_release	= seq_release_private,
-	.proc_lseek	= seq_lseek,
 };
 
 static int __net_init recent_proc_net_init(struct net *net)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 05/13] netfilter: xt_recent: let procfs copy in writes
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
                     ` (3 preceding siblings ...)
  2026-11-04 19:58   ` [PATCH RESEND v2 04/13] netfilter: xt_recent: let procfs own the seq_file Christian Brauner
@ 2026-11-04 19:58   ` Christian Brauner
  2026-11-04 19:59   ` [PATCH RESEND v2 06/13] SUNRPC: let procfs own the seq_file of the cache content file Christian Brauner
                     ` (2 subsequent siblings)
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:58 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() so procfs copies in what is written to
/proc/net/xt_recent/<table> before it pins the entry, and take the
address from that copy. The removal of a table runs under recent_mutex
and doesn't wait for a writer that faults on its buffer anymore. Writes
larger than PAGE_SIZE - 1 bytes fail with -EINVAL now.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/netfilter/xt_recent.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/net/netfilter/xt_recent.c b/net/netfilter/xt_recent.c
index a43fff5308da..72407eeaa9a6 100644
--- a/net/netfilter/xt_recent.c
+++ b/net/netfilter/xt_recent.c
@@ -553,8 +553,7 @@ static int recent_seq_open(struct inode *inode, struct file *file)
 }
 
 static ssize_t
-recent_mt_proc_write(struct file *file, const char __user *input,
-		     size_t size, loff_t *loff)
+recent_mt_proc_write(struct file *file, char *input, size_t size, loff_t *loff)
 {
 	struct recent_table *t = pde_data(file_inode(file));
 	struct recent_entry *e;
@@ -568,8 +567,7 @@ recent_mt_proc_write(struct file *file, const char __user *input,
 		return 0;
 	if (size > sizeof(buf))
 		size = sizeof(buf);
-	if (copy_from_user(buf, input, size) != 0)
-		return -EFAULT;
+	memcpy(buf, input, size);
 
 	/* Strict protocol! */
 	if (*loff != 0)
@@ -625,7 +623,7 @@ static const struct proc_ops recent_mt_proc_ops = {
 	.proc_seq_ops	= &recent_seq_ops,
 	.proc_state_size = sizeof(struct recent_iter_state),
 	.proc_open	= recent_seq_open,
-	.proc_write	= recent_mt_proc_write,
+	.proc_write_buf	= recent_mt_proc_write,
 };
 
 static int __net_init recent_proc_net_init(struct net *net)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 06/13] SUNRPC: let procfs own the seq_file of the cache content file
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
                     ` (4 preceding siblings ...)
  2026-11-04 19:58   ` [PATCH RESEND v2 05/13] netfilter: xt_recent: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:59   ` Christian Brauner
  2026-11-04 19:59   ` [PATCH RESEND v2 07/13] SUNRPC: let procfs copy in writes Christian Brauner
  2026-11-04 19:59   ` [PATCH RESEND v2 08/13] SUNRPC: let procfs copy out reads Christian Brauner
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:59 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Declare the seq_operations and let procfs open and free the seq_file of
/proc/net/rpc/<cache>/content. ->proc_open() and ->proc_release() only
take and drop the module reference and set up ->private now. The
rpc_pipefs file is unchanged. Reading the procfs file then no longer
holds up its removal when the network namespace goes away.

Acked-by: Chuck Lever <cel@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/sunrpc/cache.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 1ceaea7e01b9..007521e8a57f 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -1606,24 +1606,28 @@ static const struct proc_ops cache_channel_proc_ops = {
 	.proc_release	= cache_release_procfs,
 };
 
+/* procfs opens the seq_file */
 static int content_open_procfs(struct inode *inode, struct file *filp)
 {
 	struct cache_detail *cd = pde_data(inode);
 
-	return content_open(inode, filp, cd);
+	if (!cd || !try_module_get(cd->owner))
+		return -EACCES;
+	((struct seq_file *)filp->private_data)->private = cd;
+	return 0;
 }
 
 static int content_release_procfs(struct inode *inode, struct file *filp)
 {
 	struct cache_detail *cd = pde_data(inode);
 
-	return content_release(inode, filp, cd);
+	module_put(cd->owner);
+	return 0;
 }
 
 static const struct proc_ops content_proc_ops = {
+	.proc_seq_ops	= &cache_content_op,
 	.proc_open	= content_open_procfs,
-	.proc_read	= seq_read,
-	.proc_lseek	= seq_lseek,
 	.proc_release	= content_release_procfs,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 07/13] SUNRPC: let procfs copy in writes
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
                     ` (5 preceding siblings ...)
  2026-11-04 19:59   ` [PATCH RESEND v2 06/13] SUNRPC: let procfs own the seq_file of the cache content file Christian Brauner
@ 2026-11-04 19:59   ` Christian Brauner
  2026-11-04 19:59   ` [PATCH RESEND v2 08/13] SUNRPC: let procfs copy out reads Christian Brauner
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:59 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_write_buf() for the procfs channel, flush and
use-gss-proxy files so procfs copies in what is written before it pins
the entry. The channel file parses the downcall from that copy and the
flush files share the parsing with rpc_pipefs in do_write_flush(). The
rpc_pipefs files keep copying in themselves.

CACHE_DOWNCALL_MAX and CACHE_FLUSH_MAX name the limits both sides
enforce, procfs through ->proc_write_size. The procfs files are removed
when the network namespace goes away and that no longer waits for a
writer that faults on its buffer.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/sunrpc/auth_gss/svcauth_gss.c | 10 +++----
 net/sunrpc/cache.c                | 63 +++++++++++++++++++++++++++------------
 2 files changed, 48 insertions(+), 25 deletions(-)

diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c
index 43839e805531..8d105cd900d4 100644
--- a/net/sunrpc/auth_gss/svcauth_gss.c
+++ b/net/sunrpc/auth_gss/svcauth_gss.c
@@ -1408,8 +1408,8 @@ svcauth_gss_proc_init(struct svc_rqst *rqstp, struct rpc_gss_wire_cred *gc)
 
 #ifdef CONFIG_PROC_FS
 
-static ssize_t write_gssp(struct file *file, const char __user *buf,
-			 size_t count, loff_t *ppos)
+static ssize_t write_gssp(struct file *file, char *buf, size_t count,
+			  loff_t *ppos)
 {
 	struct net *net = pde_data(file_inode(file));
 	char tbuf[20];
@@ -1418,9 +1418,7 @@ static ssize_t write_gssp(struct file *file, const char __user *buf,
 
 	if (*ppos || count > sizeof(tbuf)-1)
 		return -EINVAL;
-	if (copy_from_user(tbuf, buf, count))
-		return -EFAULT;
-
+	memcpy(tbuf, buf, count);
 	tbuf[count] = 0;
 	res = kstrtoul(tbuf, 0, &i);
 	if (res)
@@ -1468,7 +1466,7 @@ static ssize_t read_gssp(struct file *file, char __user *buf,
 
 static const struct proc_ops use_gss_proxy_proc_ops = {
 	.proc_open	= nonseekable_open,
-	.proc_write	= write_gssp,
+	.proc_write_buf	= write_gssp,
 	.proc_read	= read_gssp,
 };
 
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index 007521e8a57f..a257598768fa 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -924,6 +924,9 @@ static ssize_t cache_do_downcall(char *kaddr, const char __user *buf,
 	return ret;
 }
 
+/* 32k is max userland buffer, lets check anyway */
+#define CACHE_DOWNCALL_MAX	(32768 - 1)
+
 static ssize_t cache_downcall(struct address_space *mapping,
 			      const char __user *buf,
 			      size_t count, struct cache_detail *cd)
@@ -931,7 +934,7 @@ static ssize_t cache_downcall(struct address_space *mapping,
 	char *write_buf;
 	ssize_t ret = -ENOMEM;
 
-	if (count >= 32768) { /* 32k is max userland buffer, lets check anyway */
+	if (count > CACHE_DOWNCALL_MAX) {
 		ret = -EINVAL;
 		goto out;
 	}
@@ -1508,19 +1511,15 @@ static ssize_t read_flush(struct file *file, char __user *buf,
 	return simple_read_from_buffer(buf, count, ppos, tbuf, len);
 }
 
-static ssize_t write_flush(struct file *file, const char __user *buf,
-			   size_t count, loff_t *ppos,
-			   struct cache_detail *cd)
+/* longest number the flush files take */
+#define CACHE_FLUSH_MAX		19
+
+static ssize_t do_write_flush(char *tbuf, size_t count, loff_t *ppos,
+			      struct cache_detail *cd)
 {
-	char tbuf[20];
 	char *ep;
 	time64_t now;
 
-	if (*ppos || count > sizeof(tbuf)-1)
-		return -EINVAL;
-	if (copy_from_user(tbuf, buf, count))
-		return -EFAULT;
-	tbuf[count] = 0;
 	simple_strtoul(tbuf, &ep, 0);
 	if (*ep && *ep != '\n')
 		return -EINVAL;
@@ -1551,6 +1550,20 @@ static ssize_t write_flush(struct file *file, const char __user *buf,
 	return count;
 }
 
+static ssize_t write_flush(struct file *file, const char __user *buf,
+			   size_t count, loff_t *ppos,
+			   struct cache_detail *cd)
+{
+	char tbuf[CACHE_FLUSH_MAX + 1];
+
+	if (*ppos || count > CACHE_FLUSH_MAX)
+		return -EINVAL;
+	if (copy_from_user(tbuf, buf, count))
+		return -EFAULT;
+	tbuf[count] = 0;
+	return do_write_flush(tbuf, count, ppos, cd);
+}
+
 static ssize_t cache_read_procfs(struct file *filp, char __user *buf,
 				 size_t count, loff_t *ppos)
 {
@@ -1559,12 +1572,21 @@ static ssize_t cache_read_procfs(struct file *filp, char __user *buf,
 	return cache_read(filp, buf, count, ppos, cd);
 }
 
-static ssize_t cache_write_procfs(struct file *filp, const char __user *buf,
-				  size_t count, loff_t *ppos)
+/* procfs copied in what was written */
+static ssize_t cache_write_procfs(struct file *filp, char *buf, size_t count,
+				  loff_t *ppos)
 {
 	struct cache_detail *cd = pde_data(file_inode(filp));
+	struct inode *inode = file_inode(filp);
+	ssize_t ret;
 
-	return cache_write(filp, buf, count, ppos, cd);
+	if (!cd->cache_parse || !count)
+		return -EINVAL;
+
+	inode_lock(inode);
+	ret = cd->cache_parse(cd, buf, count);
+	inode_unlock(inode);
+	return ret ? ret : count;
 }
 
 static __poll_t cache_poll_procfs(struct file *filp, poll_table *wait)
@@ -1599,7 +1621,8 @@ static int cache_release_procfs(struct inode *inode, struct file *filp)
 
 static const struct proc_ops cache_channel_proc_ops = {
 	.proc_read	= cache_read_procfs,
-	.proc_write	= cache_write_procfs,
+	.proc_write_buf	= cache_write_procfs,
+	.proc_write_size = CACHE_DOWNCALL_MAX,
 	.proc_poll	= cache_poll_procfs,
 	.proc_ioctl	= cache_ioctl_procfs, /* for FIONREAD */
 	.proc_open	= cache_open_procfs,
@@ -1653,19 +1676,21 @@ static ssize_t read_flush_procfs(struct file *filp, char __user *buf,
 	return read_flush(filp, buf, count, ppos, cd);
 }
 
-static ssize_t write_flush_procfs(struct file *filp,
-				  const char __user *buf,
-				  size_t count, loff_t *ppos)
+static ssize_t write_flush_procfs(struct file *filp, char *buf, size_t count,
+				  loff_t *ppos)
 {
 	struct cache_detail *cd = pde_data(file_inode(filp));
 
-	return write_flush(filp, buf, count, ppos, cd);
+	if (*ppos)
+		return -EINVAL;
+	return do_write_flush(buf, count, ppos, cd);
 }
 
 static const struct proc_ops cache_flush_proc_ops = {
 	.proc_open	= open_flush_procfs,
 	.proc_read	= read_flush_procfs,
-	.proc_write	= write_flush_procfs,
+	.proc_write_buf	= write_flush_procfs,
+	.proc_write_size = CACHE_FLUSH_MAX,
 	.proc_release	= release_flush_procfs,
 };
 

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* [PATCH RESEND v2 08/13] SUNRPC: let procfs copy out reads
  2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
                     ` (6 preceding siblings ...)
  2026-11-04 19:59   ` [PATCH RESEND v2 07/13] SUNRPC: let procfs copy in writes Christian Brauner
@ 2026-11-04 19:59   ` Christian Brauner
  7 siblings, 0 replies; 169+ messages in thread
From: Christian Brauner @ 2026-11-04 19:59 UTC (permalink / raw)
  To: linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers,
	Christian Brauner (Amutable), Chuck Lever

Implement ->proc_read_buf() for the procfs channel, flush,
use-gss-proxy and gss_krb5_enctypes files so procfs copies what is read
to userspace after it unpinned the entry.

cache_read() takes an iov_iter now. The rpc_pipefs channel file passes
one for the user buffer and behaves like before. The procfs one passes
one for procfs' buffer, and a read still hands out at most one request.
A reader of the procfs channel file whose copy out fails, a readv()
with a bad later segment included, loses what was handed out, since the
request moves on before procfs copies it out. If that was only part of
a request the next read returns the rest of it. Both flush files share
flush_time_text().

The procfs files are removed when the network namespace goes away and
that no longer waits for a reader that faults on its buffer.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/sunrpc/auth_gss/svcauth_gss.c | 24 ++++++-------------
 net/sunrpc/cache.c                | 50 +++++++++++++++++++++++----------------
 2 files changed, 36 insertions(+), 38 deletions(-)

diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c
index 8d105cd900d4..67e086a7127c 100644
--- a/net/sunrpc/auth_gss/svcauth_gss.c
+++ b/net/sunrpc/auth_gss/svcauth_gss.c
@@ -1442,32 +1442,22 @@ static ssize_t write_gssp(struct file *file, char *buf, size_t count,
 	return count;
 }
 
-static ssize_t read_gssp(struct file *file, char __user *buf,
+static ssize_t read_gssp(struct file *file, char *buf,
 			 size_t count, loff_t *ppos)
 {
 	struct net *net = pde_data(file_inode(file));
 	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
-	unsigned long p = *ppos;
 	char tbuf[10];
 	size_t len;
 
-	snprintf(tbuf, sizeof(tbuf), "%d\n", sn->use_gss_proxy);
-	len = strlen(tbuf);
-	if (p >= len)
-		return 0;
-	len -= p;
-	if (len > count)
-		len = count;
-	if (copy_to_user(buf, (void *)(tbuf+p), len))
-		return -EFAULT;
-	*ppos += len;
-	return len;
+	len = snprintf(tbuf, sizeof(tbuf), "%d\n", sn->use_gss_proxy);
+	return memory_read_from_buffer(buf, count, ppos, tbuf, len);
 }
 
 static const struct proc_ops use_gss_proxy_proc_ops = {
 	.proc_open	= nonseekable_open,
 	.proc_write_buf	= write_gssp,
-	.proc_read	= read_gssp,
+	.proc_read_buf	= read_gssp,
 };
 
 static int create_use_gss_proxy_proc_entry(struct net *net)
@@ -1494,7 +1484,7 @@ static void destroy_use_gss_proxy_proc_entry(struct net *net)
 	}
 }
 
-static ssize_t read_gss_krb5_enctypes(struct file *file, char __user *buf,
+static ssize_t read_gss_krb5_enctypes(struct file *file, char *buf,
 				      size_t count, loff_t *ppos)
 {
 	struct rpcsec_gss_oid oid = {
@@ -1512,7 +1502,7 @@ static ssize_t read_gss_krb5_enctypes(struct file *file, char __user *buf,
 		return 0;
 	}
 
-	ret = simple_read_from_buffer(buf, count, ppos,
+	ret = memory_read_from_buffer(buf, count, ppos,
 				      mech->gm_upcall_enctypes,
 				      strlen(mech->gm_upcall_enctypes));
 	gss_mech_put(mech);
@@ -1521,7 +1511,7 @@ static ssize_t read_gss_krb5_enctypes(struct file *file, char __user *buf,
 
 static const struct proc_ops gss_krb5_enctypes_proc_ops = {
 	.proc_open	= nonseekable_open,
-	.proc_read	= read_gss_krb5_enctypes,
+	.proc_read_buf	= read_gss_krb5_enctypes,
 };
 
 static int create_krb5_enctypes_proc_entry(struct net *net)
diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c
index a257598768fa..81a3f0068f36 100644
--- a/net/sunrpc/cache.c
+++ b/net/sunrpc/cache.c
@@ -28,6 +28,7 @@
 #include <linux/workqueue.h>
 #include <linux/mutex.h>
 #include <linux/pagemap.h>
+#include <linux/uio.h>
 #include <asm/ioctls.h>
 #include <linux/sunrpc/types.h>
 #include <linux/sunrpc/cache.h>
@@ -837,9 +838,10 @@ cache_next_request(struct cache_detail *cd, u64 seqno)
 	return NULL;
 }
 
-static ssize_t cache_read(struct file *filp, char __user *buf, size_t count,
-			  loff_t *ppos, struct cache_detail *cd)
+static ssize_t cache_read(struct file *filp, struct iov_iter *to,
+			  struct cache_detail *cd)
 {
+	size_t count = iov_iter_count(to);
 	struct cache_reader *rp = filp->private_data;
 	struct cache_request *rq;
 	struct inode *inode = file_inode(filp);
@@ -878,7 +880,7 @@ static ssize_t cache_read(struct file *filp, char __user *buf, size_t count,
 		if (rp->offset + count > rq->len)
 			count = rq->len - rp->offset;
 		err = -EFAULT;
-		if (copy_to_user(buf, rq->buf + rp->offset, count))
+		if (copy_to_iter(rq->buf + rp->offset, count, to) != count)
 			goto out;
 		rp->offset += count;
 		if (rp->offset >= rq->len) {
@@ -1499,16 +1501,11 @@ static int release_flush(struct inode *inode, struct file *file,
 	return 0;
 }
 
-static ssize_t read_flush(struct file *file, char __user *buf,
-			  size_t count, loff_t *ppos,
-			  struct cache_detail *cd)
+/* what reading the flush files returns */
+static size_t flush_time_text(struct cache_detail *cd, char *tbuf, size_t size)
 {
-	char tbuf[22];
-	size_t len;
-
-	len = snprintf(tbuf, sizeof(tbuf), "%llu\n",
+	return snprintf(tbuf, size, "%llu\n",
 			convert_to_wallclock(cd->flush_time));
-	return simple_read_from_buffer(buf, count, ppos, tbuf, len);
 }
 
 /* longest number the flush files take */
@@ -1564,12 +1561,15 @@ static ssize_t write_flush(struct file *file, const char __user *buf,
 	return do_write_flush(tbuf, count, ppos, cd);
 }
 
-static ssize_t cache_read_procfs(struct file *filp, char __user *buf,
-				 size_t count, loff_t *ppos)
+static ssize_t cache_read_procfs(struct file *filp, char *buf, size_t count,
+				 loff_t *ppos)
 {
 	struct cache_detail *cd = pde_data(file_inode(filp));
+	struct kvec kvec = { .iov_base = buf, .iov_len = count };
+	struct iov_iter to;
 
-	return cache_read(filp, buf, count, ppos, cd);
+	iov_iter_kvec(&to, ITER_DEST, &kvec, 1, count);
+	return cache_read(filp, &to, cd);
 }
 
 /* procfs copied in what was written */
@@ -1620,7 +1620,7 @@ static int cache_release_procfs(struct inode *inode, struct file *filp)
 }
 
 static const struct proc_ops cache_channel_proc_ops = {
-	.proc_read	= cache_read_procfs,
+	.proc_read_buf	= cache_read_procfs,
 	.proc_write_buf	= cache_write_procfs,
 	.proc_write_size = CACHE_DOWNCALL_MAX,
 	.proc_poll	= cache_poll_procfs,
@@ -1668,12 +1668,15 @@ static int release_flush_procfs(struct inode *inode, struct file *filp)
 	return release_flush(inode, filp, cd);
 }
 
-static ssize_t read_flush_procfs(struct file *filp, char __user *buf,
-			    size_t count, loff_t *ppos)
+static ssize_t read_flush_procfs(struct file *filp, char *buf, size_t count,
+				 loff_t *ppos)
 {
 	struct cache_detail *cd = pde_data(file_inode(filp));
+	char tbuf[22];
+	size_t len;
 
-	return read_flush(filp, buf, count, ppos, cd);
+	len = flush_time_text(cd, tbuf, sizeof(tbuf));
+	return memory_read_from_buffer(buf, count, ppos, tbuf, len);
 }
 
 static ssize_t write_flush_procfs(struct file *filp, char *buf, size_t count,
@@ -1688,7 +1691,7 @@ static ssize_t write_flush_procfs(struct file *filp, char *buf, size_t count,
 
 static const struct proc_ops cache_flush_proc_ops = {
 	.proc_open	= open_flush_procfs,
-	.proc_read	= read_flush_procfs,
+	.proc_read_buf	= read_flush_procfs,
 	.proc_write_buf	= write_flush_procfs,
 	.proc_write_size = CACHE_FLUSH_MAX,
 	.proc_release	= release_flush_procfs,
@@ -1795,8 +1798,10 @@ static ssize_t cache_read_pipefs(struct file *filp, char __user *buf,
 				 size_t count, loff_t *ppos)
 {
 	struct cache_detail *cd = RPC_I(file_inode(filp))->private;
+	struct iov_iter to;
 
-	return cache_read(filp, buf, count, ppos, cd);
+	iov_iter_ubuf(&to, ITER_DEST, buf, count);
+	return cache_read(filp, &to, cd);
 }
 
 static ssize_t cache_write_pipefs(struct file *filp, const char __user *buf,
@@ -1886,8 +1891,11 @@ static ssize_t read_flush_pipefs(struct file *filp, char __user *buf,
 			    size_t count, loff_t *ppos)
 {
 	struct cache_detail *cd = RPC_I(file_inode(filp))->private;
+	char tbuf[22];
+	size_t len;
 
-	return read_flush(filp, buf, count, ppos, cd);
+	len = flush_time_text(cd, tbuf, sizeof(tbuf));
+	return simple_read_from_buffer(buf, count, ppos, tbuf, len);
 }
 
 static ssize_t write_flush_pipefs(struct file *filp,

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 169+ messages in thread

* Re: [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7)
  2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
                     ` (29 preceding siblings ...)
  2026-11-04 19:57   ` [PATCH RESEND v2 30/30] selftests/proc: check that unloading configs doesn't wait for a reader Christian Brauner
@ 2026-11-04 20:05   ` Linus Torvalds
  30 siblings, 0 replies; 169+ messages in thread
From: Linus Torvalds @ 2026-11-04 20:05 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Al Viro,
	Jonathan Corbet, Christoph Hellwig, Greg Kroah-Hartman,
	Joel Granados, Kees Cook, Jan Kara, Jan Kara, linux-kernel,
	linux-nfs, linux-acpi, linux-kselftest, linux-s390, linux-scsi,
	linux-um, linux-wireless, linuxppc-dev, netdev, ntfs3,
	platform-driver-x86, coreteam, kexec, linux-pci, linux-sound,
	netfilter-devel, dri-devel, ibm-acpi-devel, jfs-discussion,
	linux-alpha, linux-arm-kernel, linux-cifs, linux-fbdev,
	linux-hwmon, linux-mm, linux-sh, linux-usb, samba-technical,
	sparclinux, linux-m68k, linux-doc, linux-serial,
	linux-atm-general, Takashi Iwai, linux-input, linux-raid,
	linux-ext4, linux-modules, linux-debuggers, Chuck Lever

On Wed, 4 Nov 2026 at 11:49, Christian Brauner <brauner@kernel.org> wrote:
>
> Part 5/7 of "proc: move procfs to what kernfs does", on top of part 2

Christian - way too many patches, re-sent much too often, cc'd to way
too many people.

Nobody is going to read these repeated resends, and all you are doing
is clogging up peoples mailboxes.

             Linus

^ permalink raw reply	[flat|nested] 169+ messages in thread

end of thread, other threads:[~2026-11-04 20:12 UTC | newest]

Thread overview: 169+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 19:04 [PATCH RESEND v2 0/7] proc: move procfs to what kernfs does Christian Brauner
2026-11-04 19:05 ` [PATCH RESEND v2 00/51] proc: fix a few old bugs in /proc handlers (Part 1/7) Christian Brauner
2026-11-04 19:06   ` [PATCH RESEND v2 01/51] alloc_tag: don't remove /proc/allocinfo under the codetag module lock Christian Brauner
2026-11-04 19:16   ` [PATCH RESEND v2 02/51] scsi: esas2r: don't copy kernel pointers out in EXPRESS_IOCTL_GET_MOD_INFO Christian Brauner
2026-11-04 19:16   ` [PATCH RESEND v2 03/51] ALSA: cs46xx: hand the region to the region proc files Christian Brauner
2026-11-04 19:16   ` [PATCH RESEND v2 04/51] PNP: isapnp: don't go on when isapnp_cfg_begin() fails Christian Brauner
2026-11-04 19:17   ` [PATCH RESEND v2 05/51] xtensa: simdisk: remove the proc entry before the disk Christian Brauner
2026-11-04 19:17   ` [PATCH RESEND v2 06/51] powerpc/pseries: put the node that the ofdt property commands look up Christian Brauner
2026-11-04 19:17   ` [PATCH RESEND v2 07/51] lockd: don't hand out the terminator of nlm_end_grace Christian Brauner
2026-11-04 19:17   ` [PATCH RESEND v2 08/51] powerpc/pseries/lparcfg: don't drop the last byte of a write Christian Brauner
2026-11-04 19:18   ` [PATCH RESEND v2 09/51] profiling: report it when /proc/profile can't be created Christian Brauner
2026-11-04 19:18   ` [PATCH RESEND v2 10/51] zorro: size the proc files like what they serve Christian Brauner
2026-11-04 19:18   ` [PATCH RESEND v2 11/51] sparc: led: consume the whole write Christian Brauner
2026-11-04 19:18   ` [PATCH RESEND v2 12/51] powerpc/rtas: consume the whole write to /proc/powerpc/rtas/progress Christian Brauner
2026-11-04 19:19   ` [PATCH RESEND v2 13/51] sched/psi: don't drop the last byte of a trigger write Christian Brauner
2026-11-04 19:19   ` [PATCH RESEND v2 14/51] proc: sysctl: don't stop readdir at an entry that is being unregistered Christian Brauner
2026-11-04 19:19   ` [PATCH RESEND v2 15/51] PCI/proc: don't truncate the position of config space accesses Christian Brauner
2026-11-04 19:19   ` [PATCH RESEND v2 16/51] PNP: isapnp: don't read past the end of the buffer on a large pread() Christian Brauner
2026-11-04 19:20   ` [PATCH RESEND v2 17/51] s390/dasd: don't look in front of the buffer on an empty write Christian Brauner
2026-11-04 19:20   ` [PATCH RESEND v2 18/51] platform/x86: toshiba_acpi: reject malformed brightness and fan writes Christian Brauner
2026-11-04 19:20   ` [PATCH RESEND v2 19/51] xtensa: simdisk: don't read the file name without the lock Christian Brauner
2026-11-04 19:20   ` [PATCH RESEND v2 20/51] powerpc/rtas_flash: don't read past the validate message Christian Brauner
2026-11-04 19:21   ` [PATCH RESEND v2 21/51] powerpc/rtasd: zero the error log buffer Christian Brauner
2026-11-04 19:21   ` [PATCH RESEND v2 22/51] ALSA: info: don't write past the end of a data entry Christian Brauner
2026-11-04 19:21   ` [PATCH RESEND v2 23/51] scsi: BusLogic: don't return 0 from writes to the proc file Christian Brauner
2026-11-04 19:21   ` [PATCH RESEND v2 24/51] scsi: ips: " Christian Brauner
2026-11-04 19:22   ` [PATCH RESEND v2 25/51] wifi: libipw: don't return 0 from writes to debug_level Christian Brauner
2026-11-04 19:22   ` [PATCH RESEND v2 26/51] scsi: core: NUL-terminate what is written to the host proc files Christian Brauner
2026-11-04 19:22   ` [PATCH RESEND v2 27/51] um: NUL-terminate what is written to /proc/exitcode Christian Brauner
2026-11-04 19:22   ` [PATCH RESEND v2 28/51] ntfs3: don't warn when a huge label is written Christian Brauner
2026-11-04 19:23   ` [PATCH RESEND v2 29/51] proc: don't walk through a non-directory when resolving a name Christian Brauner
2026-11-04 19:23   ` [PATCH RESEND v2 30/51] net: pktgen: reject device names with a '/' Christian Brauner
2026-11-04 19:23   ` [PATCH RESEND v2 31/51] proc: don't WARN about a name that is already registered Christian Brauner
2026-11-04 19:23   ` [PATCH RESEND v2 32/51] proc: resume readdir after the last name it returned Christian Brauner
2026-11-04 19:24   ` [PATCH RESEND v2 33/51] selftests/proc: check a /proc listing while entries come and go Christian Brauner
2026-11-04 19:24   ` [PATCH RESEND v2 34/51] proc: let proc_remove() remove the entry it was given Christian Brauner
2026-11-04 19:24   ` [PATCH RESEND v2 35/51] proc: warn about entries left in /proc/net of a dying namespace Christian Brauner
2026-11-04 19:24   ` [PATCH RESEND v2 36/51] proc: don't let NOMMU map procfs files Christian Brauner
2026-11-04 19:25   ` [PATCH RESEND v2 37/51] proc/vmcore: don't let vmcore_cleanup() run twice at once Christian Brauner
2026-11-04 19:25   ` [PATCH RESEND v2 38/51] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() Christian Brauner
2026-11-04 19:25   ` [PATCH RESEND v2 39/51] powerpc/crash_dump: recover from a machine check " Christian Brauner
2026-11-04 19:25   ` [PATCH RESEND v2 40/51] sunrpc: make queue_wait a global wait queue again Christian Brauner
2026-11-04 19:26   ` [PATCH RESEND v2 41/51] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls Christian Brauner
2026-11-04 19:26   ` [PATCH RESEND v2 42/51] powerpc/pseries: bound the lengths in the ofdt property parser Christian Brauner
2026-11-04 19:26   ` [PATCH RESEND v2 43/51] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long Christian Brauner
2026-11-04 19:26   ` [PATCH RESEND v2 44/51] proc: sysctl: resume readdir after the last name it returned Christian Brauner
2026-11-04 19:27   ` [PATCH RESEND v2 45/51] selftests/proc: check a sysctl listing while entries come and go Christian Brauner
2026-11-04 19:27   ` [PATCH RESEND v2 46/51] proc: sysctl: copy to and from userspace without holding the table Christian Brauner
2026-11-04 19:27   ` [PATCH RESEND v2 47/51] proc: sysctl: look at the table under sysctl_lock when creating an inode Christian Brauner
2026-11-04 19:27   ` [PATCH RESEND v2 48/51] proc: sysctl: don't hold the tables while readdir copies to userspace Christian Brauner
2026-11-04 19:27   ` [PATCH RESEND v2 49/51] selftests/proc: check that faulting sysctl accesses don't hold up removal Christian Brauner
2026-11-04 19:28   ` [PATCH RESEND v2 50/51] proc: don't pin a symlink's entry while readlink() copies it out Christian Brauner
2026-11-04 19:28   ` [PATCH RESEND v2 51/51] selftests/proc: check that a faulting readlink() doesn't hold up removal Christian Brauner
2026-11-04 19:29 ` [PATCH RESEND v2 00/23] proc: let procfs own the data path (Part 2/7) Christian Brauner
2026-11-04 19:29   ` [PATCH RESEND v2 01/23] proc: drop the compat file operations Christian Brauner
2026-11-04 19:29   ` [PATCH RESEND v2 02/23] proc: add pde_pin() and pde_unpin() Christian Brauner
2026-11-04 19:30   ` [PATCH RESEND v2 03/23] proc: share the readdir cursor with sysctl Christian Brauner
2026-11-04 19:30   ` [PATCH RESEND v2 04/23] proc: wait for the last user of an entry with wait_var_event() Christian Brauner
2026-11-04 19:30   ` [PATCH RESEND v2 05/23] proc: keep the open files of an entry in an open node Christian Brauner
2026-11-04 19:30   ` [PATCH RESEND v2 06/23] proc: make room for more entry flags Christian Brauner
2026-11-04 19:30   ` [PATCH RESEND v2 07/23] proc: let procfs own the seq_file of seq_file entries Christian Brauner
2026-11-04 19:31   ` [PATCH RESEND v2 08/23] proc: let procfs own the seq_file of proc_create_seq() and friends Christian Brauner
2026-11-04 19:31   ` [PATCH RESEND v2 09/23] proc: let procfs own the seq_file of DEFINE_PROC_SHOW_ATTRIBUTE() files Christian Brauner
2026-11-04 19:31   ` [PATCH RESEND v2 10/23] treewide: let procfs own the seq_file of simple proc entries Christian Brauner
2026-11-04 19:31   ` [PATCH RESEND v2 11/23] selftests/proc: check that a faulting reader doesn't hold up removal Christian Brauner
2026-11-04 19:32   ` [PATCH RESEND v2 12/23] seq_file: add seq_preallocate() Christian Brauner
2026-11-04 19:32   ` [PATCH RESEND v2 13/23] proc: let procfs own the seq_file of /proc/stat Christian Brauner
2026-11-04 19:32   ` [PATCH RESEND v2 14/23] proc: let procfs copy in writes before pinning the entry Christian Brauner
2026-11-04 19:32   ` [PATCH RESEND v2 15/23] proc: copy in writes to the network namespace entries before pinning Christian Brauner
2026-11-04 19:33   ` [PATCH RESEND v2 16/23] proc: let procfs copy out reads that don't go through seq_file Christian Brauner
2026-11-04 19:33   ` [PATCH RESEND v2 17/23] fs/proc/vmcore: let procfs copy out reads Christian Brauner
2026-11-04 19:33   ` [PATCH RESEND v2 18/23] proc: let procfs copy ioctl arguments in and out Christian Brauner
2026-11-04 19:33   ` [PATCH RESEND v2 19/23] proc: pin the entry around the vm_operations of its mappings Christian Brauner
2026-11-04 19:34   ` [PATCH RESEND v2 20/23] selftests/proc: check that a mapped entry can be removed Christian Brauner
2026-11-04 19:34   ` [PATCH RESEND v2 21/23] proc: let lockdep check the pin Christian Brauner
2026-11-04 19:34   ` [PATCH RESEND v2 22/23] proc: own the wait queue of an entry with a ->proc_poll() Christian Brauner
2026-11-04 19:34   ` [PATCH RESEND v2 23/23] Documentation: seq_file: describe how a /proc file uses seq_file Christian Brauner
2026-11-04 19:35 ` [PATCH RESEND v2 00/23] proc: convert the arch and platform entries (Part 3/7) Christian Brauner
2026-11-04 19:35   ` [PATCH RESEND v2 01/23] alpha: srm_env: let procfs copy in writes Christian Brauner
2026-11-04 19:36   ` [PATCH RESEND v2 02/23] ARM: alignment: " Christian Brauner
2026-11-04 19:36   ` [PATCH RESEND v2 03/23] ARM: atags: let procfs copy out reads of /proc/atags Christian Brauner
2026-11-04 19:36   ` [PATCH RESEND v2 04/23] m68k/bootinfo: let procfs copy out reads of /proc/bootinfo Christian Brauner
2026-11-04 19:36   ` [PATCH RESEND v2 05/23] powerpc/rtas: let procfs copy in writes Christian Brauner
2026-11-04 19:37   ` [PATCH RESEND v2 06/23] powerpc/rtas: let procfs copy out reads Christian Brauner
2026-11-04 19:37   ` [PATCH RESEND v2 07/23] powerpc/rtasd: let procfs own the wait queue of the rtas error log Christian Brauner
2026-11-04 19:37   ` [PATCH RESEND v2 08/23] powerpc/pseries: let procfs copy in writes Christian Brauner
2026-11-04 19:37   ` [PATCH RESEND v2 09/23] powerpc/procfs: let procfs copy out reads of /proc/powerpc/systemcfg Christian Brauner
2026-11-04 19:38   ` [PATCH RESEND v2 10/23] sh: alignment: let procfs copy in writes Christian Brauner
2026-11-04 19:38   ` [PATCH RESEND v2 11/23] sparc: led: " Christian Brauner
2026-11-04 19:38   ` [PATCH RESEND v2 12/23] um: " Christian Brauner
2026-11-04 19:38   ` [PATCH RESEND v2 13/23] x86/mtrr: let procfs own the seq_file of /proc/mtrr Christian Brauner
2026-11-04 19:39   ` [PATCH RESEND v2 14/23] x86/mtrr: let procfs copy in writes to /proc/mtrr Christian Brauner
2026-11-04 19:39   ` [PATCH RESEND v2 15/23] x86/mtrr: let procfs copy the ioctl arguments of /proc/mtrr Christian Brauner
2026-11-04 19:39   ` [PATCH RESEND v2 16/23] xtensa: simdisk: let procfs copy in writes Christian Brauner
2026-11-04 19:39   ` [PATCH RESEND v2 17/23] xtensa: simdisk: let procfs copy out reads Christian Brauner
2026-11-04 19:40   ` [PATCH RESEND v2 18/23] ACPI: proc: let procfs copy in writes to /proc/acpi/wakeup Christian Brauner
2026-11-04 19:40   ` [PATCH RESEND v2 19/23] macintosh/via-pmu: let procfs copy in writes to the options file Christian Brauner
2026-11-04 19:40   ` [PATCH RESEND v2 20/23] platform/x86: thinkpad_acpi: let procfs copy in writes Christian Brauner
2026-11-04 19:40   ` [PATCH RESEND v2 21/23] platform/x86: toshiba_acpi: " Christian Brauner
2026-11-04 19:41   ` [PATCH RESEND v2 22/23] PNP: pnpbios: " Christian Brauner
2026-11-04 19:41   ` [PATCH RESEND v2 23/23] PNP: isapnp: let procfs copy out reads Christian Brauner
2026-11-04 19:42 ` [PATCH RESEND v2 00/26] proc: convert the driver entries (Part 4/7) Christian Brauner
2026-11-04 19:42   ` [PATCH RESEND v2 01/26] nubus: let procfs own the seq_file of the resource files Christian Brauner
2026-11-04 19:42   ` [PATCH RESEND v2 02/26] zorro: let procfs copy out reads Christian Brauner
2026-11-04 19:42   ` [PATCH RESEND v2 03/26] PCI/proc: let procfs copy in config space writes Christian Brauner
2026-11-04 19:43   ` [PATCH RESEND v2 04/26] PCI/proc: let procfs copy out config space reads Christian Brauner
2026-11-04 19:43   ` [PATCH RESEND v2 05/26] PCI/proc: let procfs handle the ioctls Christian Brauner
2026-11-04 19:43   ` [PATCH RESEND v2 06/26] md: let procfs own the seq_file of /proc/mdstat Christian Brauner
2026-11-04 19:43   ` [PATCH RESEND v2 07/26] md: let procfs own the wait queue " Christian Brauner
2026-11-04 19:44   ` [PATCH RESEND v2 08/26] s390/dasd: let procfs copy in writes to /proc/dasd/statistics Christian Brauner
2026-11-04 19:44   ` [PATCH RESEND v2 09/26] s390/cio: let procfs copy in writes Christian Brauner
2026-11-04 19:44   ` [PATCH RESEND v2 10/26] scsi: let procfs own the seq_file of the host files Christian Brauner
2026-11-04 19:44   ` [PATCH RESEND v2 11/26] scsi: core: let procfs copy in writes Christian Brauner
2026-11-04 19:45   ` [PATCH RESEND v2 12/26] selftests/proc: check that a faulting writer doesn't hold up removal Christian Brauner
2026-11-04 19:45   ` [PATCH RESEND v2 13/26] scsi: sg: let procfs own the seq_file of allow_dio and def_reserved_size Christian Brauner
2026-11-04 19:45   ` [PATCH RESEND v2 14/26] scsi: sg: let procfs copy in writes Christian Brauner
2026-11-04 19:45   ` [PATCH RESEND v2 15/26] scsi: esas2r: let procfs copy the ioctl argument of the ATTONode file Christian Brauner
2026-11-04 19:46   ` [PATCH RESEND v2 16/26] hwmon: (dell-smm) let procfs copy the i8k ioctl arguments Christian Brauner
2026-11-04 19:46   ` [PATCH RESEND v2 17/26] fbdev: viafb: let procfs copy in writes Christian Brauner
2026-11-04 19:46   ` [PATCH RESEND v2 18/26] usb: gadget: function: rndis: " Christian Brauner
2026-11-04 19:46   ` [PATCH RESEND v2 19/26] wifi: ipw2x00: let procfs copy in writes to the debug level Christian Brauner
2026-11-04 19:47   ` [PATCH RESEND v2 20/26] tty: sysrq: let procfs copy in writes to /proc/sysrq-trigger Christian Brauner
2026-11-04 19:47   ` [PATCH RESEND v2 21/26] Input: let procfs own the wait queue of /proc/bus/input/devices Christian Brauner
2026-11-04 19:47   ` [PATCH RESEND v2 22/26] ALSA: info: let procfs own the seq_file of text entries Christian Brauner
2026-11-04 19:47   ` [PATCH RESEND v2 23/26] ALSA: info: let procfs copy in writes Christian Brauner
2026-11-04 19:48   ` [PATCH RESEND v2 24/26] ALSA: info: let procfs copy out reads of data entries Christian Brauner
2026-11-04 19:48   ` [PATCH RESEND v2 25/26] ALSA: info: drop the ioctl hook " Christian Brauner
2026-11-04 19:48   ` [PATCH RESEND v2 26/26] ALSA: info: drop the poll_table from the info entry poll op Christian Brauner
2026-11-04 19:49 ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Christian Brauner
2026-11-04 19:49   ` [PATCH RESEND v2 01/30] jbd2: let procfs own the seq_file of the info file Christian Brauner
2026-11-04 19:50   ` [PATCH RESEND v2 02/30] jfs: let procfs copy in writes to the loglevel file Christian Brauner
2026-11-04 19:50   ` [PATCH RESEND v2 03/30] lockd: let procfs copy in writes to nlm_end_grace Christian Brauner
2026-11-04 19:50   ` [PATCH RESEND v2 04/30] lockd: let procfs copy out reads of nlm_end_grace Christian Brauner
2026-11-04 19:50   ` [PATCH RESEND v2 05/30] nfsd: factor out setting up the exports seq_file Christian Brauner
2026-11-04 19:51   ` [PATCH RESEND v2 06/30] nfsd: let procfs own the seq_file of /proc/fs/nfs/exports Christian Brauner
2026-11-04 19:51   ` [PATCH RESEND v2 07/30] ntfs3: let procfs copy in writes to the label file Christian Brauner
2026-11-04 19:51   ` [PATCH RESEND v2 08/30] smb: client: let procfs own the seq_file of the smbdirect files Christian Brauner
2026-11-04 19:51   ` [PATCH RESEND v2 09/30] smb: client: let procfs copy in writes Christian Brauner
2026-11-04 19:52   ` [PATCH RESEND v2 10/30] genirq/proc: let procfs copy in the affinity masks Christian Brauner
2026-11-04 19:52   ` [PATCH RESEND v2 11/30] latencytop: let procfs copy in writes to /proc/latency_stats Christian Brauner
2026-11-04 19:52   ` [PATCH RESEND v2 12/30] locking/lockdep: let procfs own the seq_file of /proc/lock_stat Christian Brauner
2026-11-04 19:52   ` [PATCH RESEND v2 13/30] locking/lockdep: let procfs copy in writes to /proc/lock_stat Christian Brauner
2026-11-04 19:53   ` [PATCH RESEND v2 14/30] profiling: let procfs copy in writes to /proc/profile Christian Brauner
2026-11-04 19:53   ` [PATCH RESEND v2 15/30] profiling: let procfs copy out reads of /proc/profile Christian Brauner
2026-11-04 19:53   ` [PATCH RESEND v2 16/30] profiling: mark /proc/profile permanent Christian Brauner
2026-11-04 19:53   ` [PATCH RESEND v2 17/30] sched/psi: let procfs own the seq_file of the pressure files Christian Brauner
2026-11-04 19:54   ` [PATCH RESEND v2 18/30] sched/psi: let procfs copy in trigger writes Christian Brauner
2026-11-04 19:54   ` [PATCH RESEND v2 19/30] sched/psi: mark the pressure files permanent Christian Brauner
2026-11-04 19:54   ` [PATCH RESEND v2 20/30] sched/psi: let procfs own the wait queue of the pressure files Christian Brauner
2026-11-04 19:54   ` [PATCH RESEND v2 21/30] kallsyms: let procfs own the seq_file of /proc/kallsyms Christian Brauner
2026-11-04 19:55   ` [PATCH RESEND v2 22/30] module: procfs: let procfs own the seq_file of /proc/modules Christian Brauner
2026-11-04 19:55   ` [PATCH RESEND v2 23/30] dyndbg: let procfs copy in writes to /proc/dynamic_debug/control Christian Brauner
2026-11-04 19:55   ` [PATCH RESEND v2 24/30] ipc: let procfs own the seq_file of the sysvipc files Christian Brauner
2026-11-04 19:55   ` [PATCH RESEND v2 25/30] alloc_tag: let procfs own the seq_file of /proc/allocinfo Christian Brauner
2026-11-04 19:56   ` [PATCH RESEND v2 26/30] alloc_tag: let procfs copy the ioctl arguments " Christian Brauner
2026-11-04 19:56   ` [PATCH RESEND v2 27/30] mm/swapfile: let procfs own the seq_file of /proc/swaps Christian Brauner
2026-11-04 19:56   ` [PATCH RESEND v2 28/30] mm/swapfile: let procfs own the wait queue " Christian Brauner
2026-11-04 19:56   ` [PATCH RESEND v2 29/30] kernel/configs: let procfs copy out reads of /proc/config.gz Christian Brauner
2026-11-04 19:57   ` [PATCH RESEND v2 30/30] selftests/proc: check that unloading configs doesn't wait for a reader Christian Brauner
2026-11-04 20:05   ` [PATCH RESEND v2 00/30] proc: convert the fs, kernel and mm entries (Part 5/7) Linus Torvalds
2026-11-04 19:57 ` [PATCH RESEND v2 00/13] proc: convert the net entries and the kfifo samples (Part 6/7) Christian Brauner
2026-11-04 19:57   ` [PATCH RESEND v2 01/13] atm: let procfs copy out reads of the device files Christian Brauner
2026-11-04 19:58   ` [PATCH RESEND v2 02/13] l2tp: let procfs own the seq_file of /proc/net/pppol2tp Christian Brauner
2026-11-04 19:58   ` [PATCH RESEND v2 03/13] net: pktgen: let procfs copy in commands Christian Brauner
2026-11-04 19:58   ` [PATCH RESEND v2 04/13] netfilter: xt_recent: let procfs own the seq_file Christian Brauner
2026-11-04 19:58   ` [PATCH RESEND v2 05/13] netfilter: xt_recent: let procfs copy in writes Christian Brauner
2026-11-04 19:59   ` [PATCH RESEND v2 06/13] SUNRPC: let procfs own the seq_file of the cache content file Christian Brauner
2026-11-04 19:59   ` [PATCH RESEND v2 07/13] SUNRPC: let procfs copy in writes Christian Brauner
2026-11-04 19:59   ` [PATCH RESEND v2 08/13] SUNRPC: let procfs copy out reads Christian Brauner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox