Linux kernel -stable discussions
 help / color / mirror / Atom feed
* [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers
       [not found] <20261102-defizit-jobaussicht-kurios-7016c6e67442@brauner>
@ 2026-11-02 14:21 ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses Christian Brauner
                     ` (27 more replies)
  0 siblings, 28 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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

Two of them matter for the conversion itself:

- proc_bus_pci_write() truncates the file position to an int. A
  pwrite() at an offset like 0xfffffc00 makes it negative and the loop
  then writes a lot more than it was handed into config space at
  negative offsets. Once procfs hands it a kernel copy of the write
  that's a heap out-of-bounds read, so this has to be fixed before the
  PCI conversion:

    BUG: KASAN: slab-out-of-bounds in proc_bus_pci_write+0x528/0x5c0
    Read of size 4 at addr ff11000112103b20 by task pci-check/241
    Call Trace:
     proc_bus_pci_write+0x528/0x5c0
     proc_reg_write+0x2d7/0x730
     vfs_write+0x1cd/0xfa0
     __x64_sys_pwrite64+0x191/0x250

  isapnp has the same bug on the read side.

- xt_recent removes the proc entries of its tables with a spinlock held
  when a network namespace goes away. remove_proc_entry() waits for the
  readers and a reader takes that very lock. That's a deadlock.

/proc/sys readdir resumes a listing at a counted position. The entries
are sorted by name, so a device that comes or goes in front of that
position shifts them and the next getdents64() repeats an entry or
skips one. Even ls needs a second call to find the end of a directory.
It resumes after the name of the last entry it returned now, the way
kernfs resumes after its last node, and a selftest lists a directory in
two pieces around a device coming and going. That one is first, it
goes to stable on its own.

/proc/sys also has the bug the conversion fixes for the rest of /proc.
proc_sys_call_handler() copies what is read and written and
proc_sys_readdir() copies the names of the entries to and from userspace
while the sysctl tables are in use. Unregistering a table waits until
nobody uses it anymore. So a read(), write() or getdents64() whose
buffer faults for as long as userspace wants keeps
unregister_sysctl_table() waiting. addrconf unregisters the ipv6 sysctls
of a device under RTNL, so that stalls every RTNL user. The handler
copies in before it grabs the table and out after letting go of it.
readdir doesn't take the tables into use at all anymore. It pins the
memory of the entry it is at, which unregistering doesn't wait for,
finds the next one under sysctl_lock and emits with nothing held, the
way kernfs_fop_readdir() and proc_readdir_de() work. That's the same
one trip through sysctl_lock per entry as before. A selftest covers all
three.

/proc symlinks have it as well. proc_get_link() pins the entry and
vfs_readlink() copies the link to userspace before the delayed call
unpins it. ALSA removes /proc/asound/<id> with snd_card_mutex and
info_mutex held when a card changes its id, so a faulting readlink()
holds those up. The pin keeps nothing alive, the inode holds
the entry, so it goes. A selftest covers that too.

Chris' review of the conversion turned up more old bugs, in procfs
itself and in its users. Those are here as well:

- __xlate_proc_name() walks through whatever it finds for the
  components of a name, regular files included, and pktgen hands it the
  names that the writer of "add_device" picks. So an unprivileged user
  in namespaces of its own can hang an entry below another file and get
  it freed behind pktgen's back. procfs doesn't walk through anything
  but directories anymore and pktgen refuses names with a '/'.

- SUNRPC moved the waitqueue that poll() and epoll use for its channel
  files into the cache_detail of a network namespace. That's freed when
  the namespace goes away, but procfs leaves the waiters of a removed
  file queued. That's a use-after-free an unprivileged user can trigger
  too. It goes back to a single static waitqueue. procfs should really
  own the waitqueues of its files like kernfs does. I'll do that
  separately, it's nothing for stable.

- proc_register() WARNs when a name is already registered, and pktgen
  lets userspace pick names that collide with its own files. It reports
  the duplicate with pr_warn() and a stack dump now, like sysfs does.

- proc_readdir_de() resumes at a counted position just like /proc/sys
  and on top of that ends a listing early when the entry it is emitting
  goes away. It resumes after the last name now too, and a selftest
  covers both.

- proc_remove() removes by name, so removing an entry that is already
  gone can take out a different one. It removes the entry it's given
  now. And procfs warns about entries that a dying network namespace
  leaves in /proc/net instead of silently freeing their parent.

- On NOMMU every /proc file can be mapped privately, which reads it with
  nommu_region_sem held, and reading /proc/maps takes that lock too. Any
  user can hang the machine with that. procfs files can't be mapped on
  NOMMU anymore.

- esas2r takes the lengths of its HBA, SMP and firmware ioctls from
  userspace without checking them against the buffer they're copied
  into, through a file everyone can open.

- vmcore_cleanup() can run twice at once. powerpc's copy_oldmem_page()
  doesn't check ioremap_cache() and only recovers from a machine check
  for read(), not for splice() or vmcore's own reads. The pseries ofdt
  parser can read past its input and lparcfg reports a failed hcall as
  a huge write.

The rest is small: dasd looks in front of its buffer on an empty write,
toshiba_acpi's input checks can't fail, simdisk reads the file name
without the lock that protects it, rtas_flash can read past its message
buffer, rtasd hands out whatever vmalloc() left in its log buffer,
ALSA's info layer doesn't clamp writes past the end of a data entry,
BusLogic, ips and libipw return 0 from writes so stdio writers spin
forever, the scsi host files and um's exitcode parse what is written
without NUL-terminating it, and ntfs3 warns when a huge label is
written.

The PCI, xt_recent, sysctl, readdir, readlink, pktgen, SUNRPC,
duplicate name and /proc readdir bugs reproduce on a KASAN and lockdep
kernel in a VM and are gone with the series. The rest is build tested
only.

The procfs conversion is on top of this, starting with "proc: let
procfs own the data path".

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Christian Brauner (38):
      PCI/proc: don't truncate the position of config space accesses
      PNP: isapnp: don't read past the end of the buffer on a large pread()
      netfilter: xt_recent: don't remove the proc entries under a spinlock
      s390/dasd: don't look in front of the buffer on an empty write
      platform/x86: toshiba_acpi: reject malformed brightness and fan writes
      xtensa: simdisk: don't read the file name without the lock
      powerpc/rtas_flash: don't read past the validate message
      powerpc/rtasd: zero the error log buffer
      ALSA: info: don't write past the end of a data entry
      scsi: BusLogic: don't return 0 from writes to the proc file
      scsi: ips: don't return 0 from writes to the proc file
      wifi: libipw: don't return 0 from writes to debug_level
      scsi: core: NUL-terminate what is written to the host proc files
      um: NUL-terminate what is written to /proc/exitcode
      ntfs3: don't warn when a huge label is written
      proc: don't walk through a non-directory when resolving a name
      net: pktgen: reject device names with a '/'
      proc: don't WARN about a name that is already registered
      proc: resume readdir after the last name it returned
      selftests/proc: check a /proc listing while entries come and go
      proc: let proc_remove() remove the entry it was given
      proc: warn about entries left in /proc/net of a dying namespace
      proc: don't let NOMMU map procfs files
      proc/vmcore: don't let vmcore_cleanup() run twice at once
      powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page()
      powerpc/crash_dump: recover from a machine check in copy_oldmem_page()
      sunrpc: make queue_wait a global wait queue again
      scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
      powerpc/pseries: bound the lengths in the ofdt property parser
      powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long
      proc: sysctl: resume readdir after the last name it returned
      selftests/proc: check a sysctl listing while entries come and go
      proc: sysctl: copy to and from userspace without holding the table
      proc: sysctl: look at the table under sysctl_lock when creating an inode
      proc: sysctl: don't hold the tables while readdir copies to userspace
      selftests/proc: check that faulting sysctl accesses don't hold up removal
      proc: don't pin a symlink's entry while readlink() copies it out
      selftests/proc: check that a faulting readlink() doesn't hold up removal

 arch/powerpc/kernel/crash_dump.c                   |   6 +-
 arch/powerpc/kernel/rtas_flash.c                   |   2 +-
 arch/powerpc/kernel/rtasd.c                        |   2 +-
 arch/powerpc/platforms/pseries/lparcfg.c           |   2 +-
 arch/powerpc/platforms/pseries/reconfig.c          |  18 +-
 arch/um/kernel/exitcode.c                          |   3 +-
 arch/xtensa/platforms/iss/simdisk.c                |  25 +-
 drivers/net/wireless/intel/ipw2x00/libipw_module.c |   2 +-
 drivers/pci/proc.c                                 |  15 +-
 drivers/platform/x86/toshiba_acpi.c                |   8 +-
 drivers/pnp/isapnp/proc.c                          |   6 +-
 drivers/s390/block/dasd.c                          |   2 +-
 drivers/scsi/BusLogic.c                            |   2 +-
 drivers/scsi/esas2r/esas2r.h                       |   2 +-
 drivers/scsi/esas2r/esas2r_ioctl.c                 |  41 ++-
 drivers/scsi/esas2r/esas2r_main.c                  |   3 +-
 drivers/scsi/ips.c                                 |   2 +-
 drivers/scsi/scsi_proc.c                           |   1 +
 fs/ntfs3/super.c                                   |  11 +-
 fs/proc/generic.c                                  | 174 ++++++---
 fs/proc/inode.c                                    |  38 +-
 fs/proc/internal.h                                 |   1 +
 fs/proc/proc_net.c                                 |   7 +
 fs/proc/proc_sysctl.c                              | 389 ++++++++++++++-------
 fs/proc/root.c                                     |   1 +
 fs/proc/vmcore.c                                   |   5 +
 include/linux/sunrpc/cache.h                       |   1 -
 net/core/pktgen.c                                  |   4 +
 net/netfilter/xt_recent.c                          |   4 +-
 net/sunrpc/cache.c                                 |  12 +-
 sound/core/info.c                                  |   2 +
 tools/testing/selftests/proc/.gitignore            |   5 +
 tools/testing/selftests/proc/Makefile              |   5 +
 tools/testing/selftests/proc/config                |   6 +
 tools/testing/selftests/proc/proc-readdir-churn.c  | 236 +++++++++++++
 .../selftests/proc/proc-remove-slow-readlink.c     | 191 ++++++++++
 .../selftests/proc/proc-remove-slow-sysctl.c       | 213 +++++++++++
 .../selftests/proc/proc-sysctl-readdir-churn.c     | 147 ++++++++
 38 files changed, 1342 insertions(+), 252 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261101-work-procfs-bugfixes-054d00028491


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

* [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 17:58     ` Linus Torvalds
  2026-11-02 14:21   ` [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread() Christian Brauner
                     ` (26 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread()
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:51     ` Rafael J. Wysocki (Intel)
  2026-11-02 14:21   ` [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock Christian Brauner
                     ` (25 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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")
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 c1f6e6bd0eb2..8197c7f29743 100644
--- a/drivers/pnp/isapnp/proc.c
+++ b/drivers/pnp/isapnp/proc.c
@@ -23,14 +23,12 @@ static ssize_t isapnp_proc_bus_read(struct file *file, char __user * buf,
 				    size_t nbytes, loff_t * ppos)
 {
 	struct pnp_dev *dev = pde_data(file_inode(file));
-	int pos = *ppos;
+	loff_t pos = *ppos;
 	int cnt, size = 256;
 
 	if (pos >= size)
 		return 0;
-	if (nbytes >= size)
-		nbytes = size;
-	if (pos + nbytes > size)
+	if (nbytes > size - pos)
 		nbytes = size - pos;
 	cnt = nbytes;
 

-- 
2.53.0


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

* [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread() Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:41     ` Florian Westphal
  2026-11-02 14:21   ` [PATCH RFC 04/38] s390/dasd: don't look in front of the buffer on an empty write Christian Brauner
                     ` (24 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

recent_proc_net_exit() removes the proc entries of all tables with
recent_lock held. remove_proc_entry() waits until nobody calls into the
entry anymore, and reading one of them takes recent_lock in
recent_seq_start() while procfs has the entry pinned. So when a reader
gets there while the network namespace goes away, the exit path sleeps
with a spinlock held and bh disabled, waiting for a reader that spins
on that very lock:

  BUG: sleeping function called from invalid context at kernel/sched/completion.c:116
  in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 12, name: kworker/u16:0
  locks held by kworker/u16:0/12: 4, last CPU#2:
   #2: (pernet_ops_rwsem){++++}-{4:4}, at: cleanup_net+0xbf/0x860
   #3: (recent_lock){+...}-{3:3}, at: recent_net_exit+0x3d/0x170 [xt_recent]
  Workqueue: netns cleanup_net
  Call Trace:
   wait_for_completion+0x8e/0x400
   proc_entry_rundown+0x1ab/0x1f0
   remove_proc_entry+0x1ab/0x530
   recent_net_exit+0x9f/0x170 [xt_recent]
   ops_undo_list+0x271/0x8d0
   cleanup_net+0x3f3/0x860
   process_one_work+0x946/0x1820
  BUG: scheduling while atomic: kworker/u16:0/12/0x00000202

  watchdog: BUG: soft lockup - CPU#0 stuck for 26s! [rloop:252]
  Call Trace:
   _raw_spin_lock_bh+0x7a/0x80
   recent_seq_start+0x86/0x1e0 [xt_recent]
   seq_read_iter+0x26d/0x1230
   seq_read+0x2a9/0x560
   proc_reg_read+0x1c2/0x3c0
   vfs_read+0x176/0xb40
   __x64_sys_pread64+0x191/0x250

Tables are added and removed with recent_mutex held and that's also
what recent_mt_destroy() looks at ->xt_recent under. Nothing that runs
with a proc entry pinned takes it. So take recent_mutex instead.

Fixes: 665e205c16c1 ("netfilter: xt_recent: fix namespace destroy path")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/netfilter/xt_recent.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/net/netfilter/xt_recent.c b/net/netfilter/xt_recent.c
index 55f1f2d89952..11acb9a50d4b 100644
--- a/net/netfilter/xt_recent.c
+++ b/net/netfilter/xt_recent.c
@@ -652,12 +652,12 @@ static void __net_exit recent_proc_net_exit(struct net *net)
 	 * that the parent xt_recent proc entry is empty before trying to
 	 * remove it.
 	 */
-	spin_lock_bh(&recent_lock);
+	mutex_lock(&recent_mutex);
 	list_for_each_entry(t, &recent_net->tables, list)
 	        remove_proc_entry(t->name, recent_net->xt_recent);
 
 	recent_net->xt_recent = NULL;
-	spin_unlock_bh(&recent_lock);
+	mutex_unlock(&recent_mutex);
 
 	remove_proc_entry("xt_recent", net->proc_net);
 }

-- 
2.53.0


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

* [PATCH RFC 04/38] s390/dasd: don't look in front of the buffer on an empty write
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (2 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 05/38] platform/x86: toshiba_acpi: reject malformed brightness and fan writes Christian Brauner
                     ` (23 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 05/38] platform/x86: toshiba_acpi: reject malformed brightness and fan writes
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (3 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 04/38] s390/dasd: don't look in front of the buffer on an empty write Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 06/38] xtensa: simdisk: don't read the file name without the lock Christian Brauner
                     ` (22 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 06/38] xtensa: simdisk: don't read the file name without the lock
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (4 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 05/38] platform/x86: toshiba_acpi: reject malformed brightness and fan writes Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 07/38] powerpc/rtas_flash: don't read past the validate message Christian Brauner
                     ` (21 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 a2ddb49c269d..7b115e023de9 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] 50+ messages in thread

* [PATCH RFC 07/38] powerpc/rtas_flash: don't read past the validate message
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (5 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 06/38] xtensa: simdisk: don't read the file name without the lock Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 08/38] powerpc/rtasd: zero the error log buffer Christian Brauner
                     ` (20 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 08/38] powerpc/rtasd: zero the error log buffer
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (6 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 07/38] powerpc/rtas_flash: don't read past the validate message Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry Christian Brauner
                     ` (19 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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] 50+ messages in thread

* [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (7 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 08/38] powerpc/rtasd: zero the error log buffer Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-03 10:41     ` Takashi Iwai
  2026-11-02 14:21   ` [PATCH RFC 13/38] scsi: core: NUL-terminate what is written to the host proc files Christian Brauner
                     ` (18 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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")
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 c4d57d0809ad..982087bc8575 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] 50+ messages in thread

* [PATCH RFC 13/38] scsi: core: NUL-terminate what is written to the host proc files
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (8 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 14/38] um: NUL-terminate what is written to /proc/exitcode Christian Brauner
                     ` (17 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 14/38] um: NUL-terminate what is written to /proc/exitcode
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (9 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 13/38] scsi: core: NUL-terminate what is written to the host proc files Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 15/38] ntfs3: don't warn when a huge label is written Christian Brauner
                     ` (16 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 15/38] ntfs3: don't warn when a huge label is written
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (10 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 14/38] um: NUL-terminate what is written to /proc/exitcode Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name Christian Brauner
                     ` (15 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 58ef6be3286b..b4479cd6bd65 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -552,15 +552,10 @@ static ssize_t ntfs3_label_write(struct file *file, const char __user *buffer,
 	if (sb_rdonly(sb))
 		return -EROFS;
 
-	label = kmalloc(count, GFP_NOFS);
+	label = memdup_user(buffer, count);
+	if (IS_ERR(label))
+		return PTR_ERR(label);
 
-	if (!label)
-		return -ENOMEM;
-
-	if (copy_from_user(label, buffer, ret)) {
-		ret = -EFAULT;
-		goto out;
-	}
 	while (ret > 0 && label[ret - 1] == '\n')
 		ret -= 1;
 

-- 
2.53.0


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

* [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (11 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 15/38] ntfs3: don't warn when a huge label is written Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-05 11:41     ` Jan Kara
  2026-11-02 14:21   ` [PATCH RFC 17/38] net: pktgen: reject device names with a '/' Christian Brauner
                     ` (14 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

__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 2b1971da4a85..c560c96a64eb 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] 50+ messages in thread

* [PATCH RFC 17/38] net: pktgen: reject device names with a '/'
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (12 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 18/38] proc: don't WARN about a name that is already registered Christian Brauner
                     ` (13 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

"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] 50+ messages in thread

* [PATCH RFC 18/38] proc: don't WARN about a name that is already registered
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (13 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 17/38] net: pktgen: reject device names with a '/' Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-04 18:43     ` Alexey Dobriyan
  2026-11-05 11:52     ` Jan Kara
  2026-11-02 14:21   ` [PATCH RFC 19/38] proc: resume readdir after the last name it returned Christian Brauner
                     ` (12 subsequent siblings)
  27 siblings, 2 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 c560c96a64eb..ef804cd3ec45 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -401,9 +401,10 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
 	write_lock(&proc_subdir_lock);
 	dp->parent = dir;
 	if (pde_subdir_insert(dir, dp) == false) {
-		WARN(1, "proc_dir_entry '%s/%s' already registered\n",
-		     dir->name, dp->name);
 		write_unlock(&proc_subdir_lock);
+		pr_warn("proc_dir_entry '%s/%s' already registered\n",
+			dir->name, dp->name);
+		dump_stack();
 		goto out_free_inum;
 	}
 	write_unlock(&proc_subdir_lock);

-- 
2.53.0


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

* [PATCH RFC 19/38] proc: resume readdir after the last name it returned
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (14 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 18/38] proc: don't WARN about a name that is already registered Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-05 12:00     ` Jan Kara
  2026-11-05 16:44     ` Jann Horn
  2026-11-02 14:21   ` [PATCH RFC 23/38] proc: don't let NOMMU map procfs files Christian Brauner
                     ` (11 subsequent siblings)
  27 siblings, 2 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 ef804cd3ec45..47830d6af255 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)
 {
@@ -277,6 +299,40 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
 	return proc_lookup_de(dir, dentry, PDE(dir));
 }
 
+/*
+ * Where the next readdir resumes. The entries are counted, so one that is
+ * added or removed in front of the position would shift what the next call
+ * returns. The name of the last entry emitted doesn't move.
+ */
+struct proc_dir_cursor {
+	loff_t pos;			/* the position behind the entry */
+	unsigned int len;
+	char name[NAME_MAX + 1];
+};
+
+/* If this can't remember the entry the next call counts from the start. */
+static void proc_dir_cursor_set(struct file *file, struct proc_dir_entry *de,
+				loff_t pos)
+{
+	struct proc_dir_cursor *cursor = file->private_data;
+
+	if (!cursor) {
+		cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
+		if (!cursor)
+			return;
+		file->private_data = cursor;
+	}
+	cursor->pos = pos;
+	cursor->len = de->namelen;
+	memcpy(cursor->name, de->name, de->namelen);
+}
+
+int proc_dir_release(struct inode *inode, struct file *file)
+{
+	kfree(file->private_data);
+	return 0;
+}
+
 /*
  * This returns non-zero if at EOF, so that the /proc
  * root directory can use this and check if it should
@@ -289,23 +345,26 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
 int proc_readdir_de(struct file *file, struct dir_context *ctx,
 		    struct proc_dir_entry *de)
 {
+	struct proc_dir_cursor *cursor = file->private_data;
+	struct proc_dir_entry *dir = de;
 	int i;
 
 	if (!dir_emit_dots(file, ctx))
 		return 0;
 
-	i = ctx->pos - 2;
 	read_lock(&proc_subdir_lock);
-	de = pde_subdir_first(de);
-	for (;;) {
-		if (!de) {
-			read_unlock(&proc_subdir_lock);
-			return 0;
-		}
-		if (!i)
-			break;
-		de = pde_subdir_next(de);
-		i--;
+	if (cursor && cursor->pos == ctx->pos) {
+		/* Resume after the last entry emitted, wherever it is now. */
+		de = pde_subdir_after(dir, cursor->name, cursor->len);
+	} else {
+		/* Moved by a seek, count from the start like always. */
+		de = pde_subdir_first(dir);
+		for (i = ctx->pos - 2; de && i; i--)
+			de = pde_subdir_next(de);
+	}
+	if (!de) {
+		read_unlock(&proc_subdir_lock);
+		return 1;
 	}
 
 	do {
@@ -318,8 +377,13 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
 			return 0;
 		}
 		ctx->pos++;
+		proc_dir_cursor_set(file, de, ctx->pos);
 		read_lock(&proc_subdir_lock);
-		next = pde_subdir_next(de);
+		/* Removed in the meantime, go on after its name. */
+		if (RB_EMPTY_NODE(&de->subdir_node))
+			next = pde_subdir_after(dir, de->name, de->namelen);
+		else
+			next = pde_subdir_next(de);
 		pde_put(de);
 		de = next;
 	} while (de);
@@ -347,6 +411,7 @@ static const struct file_operations proc_dir_operations = {
 	.llseek			= generic_file_llseek,
 	.read			= generic_read_dir,
 	.iterate_shared		= proc_readdir,
+	.release		= proc_dir_release,
 };
 
 static int proc_net_d_revalidate(struct inode *dir, const struct qstr *name,
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] 50+ messages in thread

* [PATCH RFC 23/38] proc: don't let NOMMU map procfs files
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (15 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 19/38] proc: resume readdir after the last name it returned Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once Christian Brauner
                     ` (10 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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. 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] 50+ messages in thread

* [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (16 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 23/38] proc: don't let NOMMU map procfs files Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-05 12:11     ` Jan Kara
  2026-11-02 14:21   ` [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() Christian Brauner
                     ` (9 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page()
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (17 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-11 11:54     ` Breno Leitao
  2026-11-02 14:21   ` [PATCH RFC 26/38] powerpc/crash_dump: recover from a machine check " Christian Brauner
                     ` (8 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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] 50+ messages in thread

* [PATCH RFC 26/38] powerpc/crash_dump: recover from a machine check in copy_oldmem_page()
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (18 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again Christian Brauner
                     ` (7 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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.

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: 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] 50+ messages in thread

* [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (19 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 26/38] powerpc/crash_dump: recover from a machine check " Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 15:12     ` Chuck Lever
  2026-11-02 14:21   ` [PATCH RFC 28/38] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls Christian Brauner
                     ` (6 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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")
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 @@ static ssize_t cache_write(struct file *filp, const char __user *buf,
 	return ret;
 }
 
+/*
+ * Not in struct cache_detail: poll and epoll waiters stay queued until
+ * their file is closed, but a channel file in procfs can stay open after
+ * the cache_detail of its dying network namespace has been freed.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(queue_wait);
+
 static __poll_t cache_poll(struct file *filp, poll_table *wait,
 			       struct cache_detail *cd)
 {
 	__poll_t mask;
 	struct cache_reader *rp = filp->private_data;
 
-	poll_wait(filp, &cd->queue_wait, wait);
+	poll_wait(filp, &queue_wait, wait);
 
 	/* alway allow write */
 	mask = EPOLLOUT | EPOLLWRNORM;
@@ -1236,7 +1242,7 @@ static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h)
 	spin_unlock(&detail->queue_lock);
 	if (ret != -EAGAIN && detail->cache_notify)
 		detail->cache_notify(detail, h);
-	wake_up(&detail->queue_wait);
+	wake_up(&queue_wait);
 	if (ret == -EAGAIN) {
 		kfree(buf);
 		kfree(crq);

-- 
2.53.0


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

* [PATCH RFC 28/38] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (20 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 29/38] powerpc/pseries: bound the lengths in the ofdt property parser Christian Brauner
                     ` (5 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 3f7c1d131ec3..f99318f61c93 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] 50+ messages in thread

* [PATCH RFC 29/38] powerpc/pseries: bound the lengths in the ofdt property parser
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (21 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 28/38] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 30/38] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long Christian Brauner
                     ` (4 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 18e3f1a036e3..b41c5db726c9 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] 50+ messages in thread

* [PATCH RFC 30/38] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (22 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 29/38] powerpc/pseries: bound the lengths in the ofdt property parser Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-02 14:21   ` [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned Christian Brauner
                     ` (3 subsequent siblings)
  27 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 and an unknown one is at least negative.
Before commit 11529396ea31 ("powerpc/pseries: Split processor
entitlement retrieval and gathering to helper routines") a failed
H_GET_PPP turned into -EIO.

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 3280b7968cab..aa9b5b141434 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] 50+ messages in thread

* [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (23 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 30/38] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-05 12:16     ` Jan Kara
  2026-11-02 14:21   ` [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table Christian Brauner
                     ` (2 subsequent siblings)
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 6c0859c66563..38b61887b59c 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 @@ static __poll_t proc_sys_poll(struct file *filp, poll_table *wait)
 	return ret;
 }
 
+/*
+ * Where the next readdir resumes. The entries are counted, so one that is
+ * registered or unregistered in front of the position would shift what
+ * the next call returns. The name of the last entry emitted doesn't move.
+ */
+struct sysctl_cursor {
+	loff_t pos;			/* the position behind the entry */
+	int len;
+	char name[NAME_MAX + 1];
+};
+
+/* If this can't remember the entry the next call counts from the start. */
+static void sysctl_cursor_set(struct file *file, const char *name, int len,
+			      loff_t pos)
+{
+	struct sysctl_cursor *cursor = file->private_data;
+
+	if (len > NAME_MAX)
+		return;
+	if (!cursor) {
+		cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
+		if (!cursor)
+			return;
+		file->private_data = cursor;
+	}
+	cursor->pos = pos;
+	cursor->len = len;
+	memcpy(cursor->name, name, len);
+}
+
 static bool proc_sys_fill_cache(struct file *file,
 				struct dir_context *ctx,
 				struct ctl_table_header *head,
@@ -753,8 +831,11 @@ static int scan(struct ctl_table_header *head, const struct ctl_table *table,
 	else
 		res = proc_sys_fill_cache(file, ctx, head, table);
 
-	if (res)
+	if (res) {
 		ctx->pos = *pos;
+		sysctl_cursor_set(file, table->procname, strlen(table->procname),
+				  ctx->pos);
+	}
 
 	return res;
 }
@@ -762,6 +843,7 @@ static int scan(struct ctl_table_header *head, const struct ctl_table *table,
 static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 {
 	struct ctl_table_header *head = grab_header(file_inode(file));
+	struct sysctl_cursor *cursor = file->private_data;
 	struct ctl_table_header *h = NULL;
 	const struct ctl_table *entry;
 	struct ctl_dir *ctl_dir;
@@ -775,9 +857,20 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 	if (!dir_emit_dots(file, ctx))
 		goto out;
 
-	pos = 2;
+	/*
+	 * Resume after the last entry the previous call emitted. Only a seek
+	 * that moved the position away from it counts from the start, like
+	 * seeking to an arbitrary position always did.
+	 */
+	if (cursor && cursor->pos == ctx->pos) {
+		entry_after(ctl_dir, cursor->name, cursor->len, &h, &entry);
+		pos = ctx->pos;
+	} else {
+		first_entry(ctl_dir, &h, &entry);
+		pos = 2;
+	}
 
-	for (first_entry(ctl_dir, &h, &entry); h; next_entry(&h, &entry)) {
+	for (; h; next_entry(&h, &entry)) {
 		if (!scan(h, entry, &pos, file, ctx)) {
 			sysctl_head_finish(h);
 			break;
@@ -788,6 +881,12 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 	return 0;
 }
 
+static int proc_sys_dir_release(struct inode *inode, struct file *file)
+{
+	kfree(file->private_data);
+	return 0;
+}
+
 static int proc_sys_permission(const struct mnt_idmap *idmap,
 			       struct inode *inode, int mask)
 {
@@ -867,6 +966,7 @@ static const struct file_operations proc_sys_dir_file_operations = {
 	.read		= generic_read_dir,
 	.iterate_shared	= proc_sys_readdir,
 	.llseek		= generic_file_llseek,
+	.release	= proc_sys_dir_release,
 };
 
 static const struct inode_operations proc_sys_inode_operations = {

-- 
2.53.0


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

* [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (24 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-05 12:21     ` Jan Kara
  2026-11-02 14:21   ` [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace Christian Brauner
  2026-11-02 14:43   ` [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out Christian Brauner
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 38b61887b59c..2150cf842919 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] 50+ messages in thread

* [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (25 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table Christian Brauner
@ 2026-11-02 14:21   ` Christian Brauner
  2026-11-05 12:55     ` Jan Kara
  2026-11-02 14:43   ` [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out Christian Brauner
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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

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 and mode, follows a link to the entry it stands for and
pins that. Then it looks the dentry up, creates the inode if there is
none, which checks for itself under sysctl_lock that the table is still
registered, and emits the entry with nothing held at all. The next
entry is the first live one after that name, which is where a listing
resumes between calls since "proc: sysctl: resume readdir after the
last name it returned" anyway. kernfs_fop_readdir() and
proc_readdir_de() work the same way, a reference on the entry and the
lock only to find the next one.

While the entry is still registered its node is still in the tree and
the next one is right behind it, like kernfs_dir_next_pos() continues
from the node it holds. Only once it went away, and to resume, is the
next entry found by name. That's one trip through sysctl_lock per entry
like before, and the link entries of /proc/sys/net no longer take two
more. The directory isn't
taken into use either, an entry keeps its directory registered while
it is used and readdir looks at the directory under the lock. So the
dots don't wait for anything, a directory that went away lists them and
then fails with -ENOENT. A seek to an arbitrary position counts the
entries from the start like before, one trip per entry.

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 | 307 +++++++++++++++++++++++++++-----------------------
 1 file changed, 166 insertions(+), 141 deletions(-)

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index a1242247f492..0b7a1e487ed6 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,97 @@ static const struct ctl_table *lookup_entry(struct ctl_table_header **phead,
 	return entry;
 }
 
-static struct ctl_node *first_usable_entry(struct rb_node *node)
-{
-	struct ctl_node *ctl_node;
+/*
+ * What readdir takes along for an entry between the trips through
+ * sysctl_lock: the header pinned, which keeps its memory and nothing else,
+ * the entry, its name and mode. For a link the header and entry are the
+ * ones it stands for in the caller's set.
+ */
+struct sysctl_dirent {
+	struct ctl_table_header *head;
+	const struct ctl_table *table;
+	struct ctl_node *node;		/* the entry's node in the directory, NULL for a link */
+	ino_t ino;
+	unsigned int type;
+	umode_t mode;
+	bool skip;
+	int len;
+	char name[NAME_MAX + 1];
+};
 
-	for (;node; node = rb_next(node)) {
-		ctl_node = rb_entry(node, struct ctl_node, node);
-		if (use_table(ctl_node->header))
-			return ctl_node;
-	}
-	return NULL;
+/* A reference on the memory of @head, like an inode made for it holds. */
+static void sysctl_pin(struct ctl_table_header *head)
+{
+	lockdep_assert_held(&sysctl_lock);
+	head->count++;
 }
 
-static void first_entry(struct ctl_dir *dir,
-	struct ctl_table_header **phead, const struct ctl_table **pentry)
+static void sysctl_unpin(struct ctl_table_header *head)
 {
-	struct ctl_table_header *head = NULL;
-	const struct ctl_table *entry = NULL;
-	struct ctl_node *ctl_node;
-
-	spin_lock(&sysctl_lock);
-	ctl_node = first_usable_entry(rb_first(&dir->root));
-	spin_unlock(&sysctl_lock);
-	if (ctl_node) {
-		head = ctl_node->header;
-		entry = &head->ctl_table[ctl_node - head->node];
-	}
-	*phead = head;
-	*pentry = entry;
+	lockdep_assert_held(&sysctl_lock);
+	if (!--head->count)
+		kfree_rcu(head, rcu);
 }
 
-static void next_entry(struct ctl_table_header **phead, const struct ctl_table **pentry)
+/* the first entry from @node on whose table isn't being unregistered */
+static struct ctl_node *first_live_entry(struct rb_node *node)
 {
-	struct ctl_table_header *head = *phead;
-	const struct ctl_table *entry = *pentry;
-	struct ctl_node *ctl_node = &head->node[entry - head->ctl_table];
-
-	spin_lock(&sysctl_lock);
-	unuse_table(head);
+	struct ctl_node *ctl_node;
 
-	ctl_node = first_usable_entry(rb_next(&ctl_node->node));
-	spin_unlock(&sysctl_lock);
-	head = NULL;
-	if (ctl_node) {
-		head = ctl_node->header;
-		entry = &head->ctl_table[ctl_node - head->node];
+	lockdep_assert_held(&sysctl_lock);
+	for (; node; node = rb_next(node)) {
+		ctl_node = rb_entry(node, struct ctl_node, node);
+		if (!ctl_node->header->unregistering)
+			return ctl_node;
 	}
-	*phead = head;
-	*pentry = entry;
+	return NULL;
 }
 
 /*
- * The first usable entry of @dir after @name, or the first one at all for a
- * NULL @name. The entries are sorted by name, so that is where a listing
- * resumes when entries in front of it came or went in between. -ENOENT if
- * the directory is being unregistered.
+ * Fill @d with the entry of @dir behind the one it holds, letting go of that
+ * one, or with the first live entry after @name, or the first one at all for
+ * a NULL @name. While the entry @d holds is still registered its node is
+ * still in the tree and the next one is right behind it. Once it is gone the
+ * entries are found by name, they are sorted by it, so that is also where a
+ * listing resumes when entries in front of it came or went in between.
+ * 1 with the next entry pinned in @d, 0 at the end of the directory and
+ * -ENOENT once @dir itself is being unregistered.
+ *
+ * Everything that reads the tables happens under sysctl_lock, where an entry
+ * is erased when it is unregistered, since nothing holds the tables in use.
  */
-static int entry_after(struct ctl_dir *dir, const char *name, int namelen,
-		       struct ctl_table_header **phead,
-		       const struct ctl_table **pentry)
+static int sysctl_dir_next(struct ctl_dir *dir, struct sysctl_dirent *d,
+			   const char *name, int namelen)
 {
-	struct ctl_table_header *head = NULL;
-	const struct ctl_table *entry = NULL;
 	struct rb_node *node, *after = NULL;
+	struct ctl_table_header *head;
+	const struct ctl_table *entry;
 	struct ctl_node *ctl_node;
+	bool by_name = true;
+	int ret = 0;
 
 	spin_lock(&sysctl_lock);
+	if (d->head) {
+		if (d->node && !d->head->unregistering) {
+			after = rb_next(&d->node->node);
+			by_name = false;
+		}
+		sysctl_unpin(d->head);
+		d->head = NULL;
+	}
 	if (unlikely(dir->header.unregistering)) {
-		spin_unlock(&sysctl_lock);
-		return -ENOENT;
+		ret = -ENOENT;
+		goto out;
 	}
-	if (!name) {
+	if (!by_name) {
+		/* behind the entry @d held */
+	} else if (!name) {
 		after = rb_first(&dir->root);
 	} else for (node = dir->root.rb_node; node;) {
-		const struct ctl_table *table;
-		struct ctl_table_header *h;
-
 		ctl_node = rb_entry(node, struct ctl_node, node);
-		h = ctl_node->header;
-		table = &h->ctl_table[ctl_node - h->node];
-		if (namecmp(table->procname, strlen(table->procname),
+		head = ctl_node->header;
+		entry = &head->ctl_table[ctl_node - head->node];
+		if (namecmp(entry->procname, strlen(entry->procname),
 			    name, namelen) > 0) {
 			after = node;
 			node = node->rb_left;
@@ -454,15 +467,56 @@ static int entry_after(struct ctl_dir *dir, const char *name, int namelen,
 			node = node->rb_right;
 		}
 	}
-	ctl_node = first_usable_entry(after);
-	spin_unlock(&sysctl_lock);
-	if (ctl_node) {
-		head = ctl_node->header;
-		entry = &head->ctl_table[ctl_node - head->node];
+	ctl_node = first_live_entry(after);
+	if (!ctl_node)
+		goto out;
+	head = ctl_node->header;
+	entry = &head->ctl_table[ctl_node - head->node];
+
+	d->len = strlen(entry->procname);
+	if (d->len > NAME_MAX) {
+		ret = -ENAMETOOLONG;
+		goto out;
 	}
-	*phead = head;
-	*pentry = entry;
-	return 0;
+	memcpy(d->name, entry->procname, d->len);
+	d->mode = entry->mode;
+	d->skip = false;
+	d->node = ctl_node;
+	if (unlikely(S_ISLNK(entry->mode))) {
+		/* the entry it stands for in the caller's set, or nothing */
+		struct ctl_table_root *root = entry->data;
+		struct ctl_table_set *set = lookup_header_set(root);
+		struct ctl_dir *xdir = xlate_dir(set, head->parent);
+		struct ctl_table_header *xhead = NULL;
+		const struct ctl_table *xentry = NULL;
+
+		if (!IS_ERR(xdir))
+			xentry = find_entry(&xhead, xdir, d->name, d->len);
+		if (!xentry || xhead->unregistering) {
+			d->skip = true;
+		} else {
+			head = xhead;
+			entry = xentry;
+		}
+		d->node = NULL;
+	}
+	sysctl_pin(head);
+	d->head = head;
+	d->table = entry;
+	ret = 1;
+out:
+	spin_unlock(&sysctl_lock);
+	return ret;
+}
+
+static void sysctl_dir_done(struct sysctl_dirent *d)
+{
+	if (!d->head)
+		return;
+	spin_lock(&sysctl_lock);
+	sysctl_unpin(d->head);
+	spin_unlock(&sysctl_lock);
+	d->head = NULL;
 }
 
 /*
@@ -765,29 +819,29 @@ static void sysctl_cursor_set(struct file *file, const char *name, int len,
 	memcpy(cursor->name, name, len);
 }
 
-static bool proc_sys_fill_cache(struct file *file,
-				struct dir_context *ctx,
-				struct ctl_table_header *head,
-				const struct ctl_table *table)
+/*
+ * The inode number and type of the entry @d holds, instantiating its dentry
+ * if there is none. Needs the header pinned, proc_sys_make_inode() checks
+ * for itself that the table is still registered.
+ */
+static int proc_sys_fill_dirent(struct file *file, struct sysctl_dirent *d)
 {
 	struct dentry *child, *dir = file->f_path.dentry;
 	struct inode *inode;
 	struct qstr qname;
-	ino_t ino = 0;
-	unsigned type = DT_UNKNOWN;
 
-	qname.name = table->procname;
-	qname.len  = strlen(table->procname);
+	qname.name = d->name;
+	qname.len  = d->len;
 	qname.hash = full_name_hash(dir, qname.name, qname.len);
 
 	child = d_lookup(dir, &qname);
 	if (!child) {
 		child = d_alloc_parallel(dir, &qname);
 		if (IS_ERR(child))
-			return false;
+			return PTR_ERR(child);
 		if (d_in_lookup(child)) {
 			struct dentry *res;
-			inode = proc_sys_make_inode(dir->d_sb, head, table);
+			inode = proc_sys_make_inode(dir->d_sb, d->head, d->table);
 			res = d_splice_alias_ops(inode, child,
 						 &proc_sys_dentry_operations);
 			d_lookup_done(child);
@@ -795,79 +849,41 @@ static bool proc_sys_fill_cache(struct file *file,
 				dput(child);
 
 				if (IS_ERR(res))
-					return false;
+					return PTR_ERR(res);
 
 				child = res;
 			}
 		}
 	}
 	inode = d_inode(child);
-	ino  = inode->i_ino;
-	type = inode->i_mode >> 12;
+	d->ino = inode->i_ino;
+	d->type = inode->i_mode >> 12;
 	dput(child);
-	return dir_emit(ctx, qname.name, qname.len, ino, type);
-}
-
-static bool proc_sys_link_fill_cache(struct file *file,
-				    struct dir_context *ctx,
-				    struct ctl_table_header *head,
-				    const struct ctl_table *table)
-{
-	bool ret = true;
-
-	head = sysctl_head_grab(head);
-	if (IS_ERR(head))
-		return false;
-
-	/* It is not an error if we can not follow the link ignore it */
-	if (sysctl_follow_link(&head, &table))
-		goto out;
-
-	ret = proc_sys_fill_cache(file, ctx, head, table);
-out:
-	sysctl_head_finish(head);
-	return ret;
-}
-
-static int scan(struct ctl_table_header *head, const struct ctl_table *table,
-		unsigned long *pos, struct file *file,
-		struct dir_context *ctx)
-{
-	bool res;
-
-	if ((*pos)++ < ctx->pos)
-		return true;
-
-	if (unlikely(S_ISLNK(table->mode)))
-		res = proc_sys_link_fill_cache(file, ctx, head, table);
-	else
-		res = proc_sys_fill_cache(file, ctx, head, table);
-
-	if (res) {
-		ctx->pos = *pos;
-		sysctl_cursor_set(file, table->procname, strlen(table->procname),
-				  ctx->pos);
-	}
-
-	return res;
+	return 0;
 }
 
+/*
+ * Unregistering a table waits until nobody uses it anymore and a copy to
+ * userspace can take as long as userspace wants. So a listing doesn't take
+ * any table into use. It pins the memory of the entry it is at, finds the
+ * next one under sysctl_lock and emits with nothing held, like
+ * kernfs_fop_readdir() and proc_readdir_de() do. Between calls a listing
+ * resumes after the name of the last entry it emitted.
+ */
 static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 {
-	struct ctl_table_header *head = grab_header(file_inode(file));
+	struct ctl_table_header *head = PROC_I(file_inode(file))->sysctl;
 	struct sysctl_cursor *cursor = file->private_data;
-	struct ctl_table_header *h = NULL;
-	const struct ctl_table *entry;
+	struct sysctl_dirent d = {};
 	struct ctl_dir *ctl_dir;
-	unsigned long pos;
-
-	if (IS_ERR(head))
-		return PTR_ERR(head);
+	int err;
 
+	if (!head)
+		head = &sysctl_table_root.default_set.dir.header;
 	ctl_dir = container_of(head, struct ctl_dir, header);
 
 	if (!dir_emit_dots(file, ctx))
-		goto out;
+		return 0;
 
 	/*
 	 * Resume after the last entry the previous call emitted. Only a seek
@@ -875,21 +891,30 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 	 * seeking to an arbitrary position always did.
 	 */
 	if (cursor && cursor->pos == ctx->pos) {
-		entry_after(ctl_dir, cursor->name, cursor->len, &h, &entry);
-		pos = ctx->pos;
+		err = sysctl_dir_next(ctl_dir, &d, cursor->name, cursor->len);
 	} else {
-		first_entry(ctl_dir, &h, &entry);
-		pos = 2;
+		loff_t pos = 2;
+
+		err = sysctl_dir_next(ctl_dir, &d, NULL, 0);
+		while (err > 0 && pos++ < ctx->pos)
+			err = sysctl_dir_next(ctl_dir, &d, d.name, d.len);
 	}
+	if (err < 0)
+		return err;
 
-	for (; h; next_entry(&h, &entry)) {
-		if (!scan(h, entry, &pos, file, ctx)) {
-			sysctl_head_finish(h);
-			break;
+	while (err > 0) {
+		if (!d.skip) {
+			err = proc_sys_fill_dirent(file, &d);
+			if (err)
+				break;
+			if (!dir_emit(ctx, d.name, d.len, d.ino, d.type))
+				break;
 		}
+		ctx->pos++;
+		sysctl_cursor_set(file, d.name, d.len, ctx->pos);
+		err = sysctl_dir_next(ctl_dir, &d, d.name, d.len);
 	}
-out:
-	sysctl_head_finish(head);
+	sysctl_dir_done(&d);
 	return 0;
 }
 

-- 
2.53.0


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

* Re: [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock
  2026-11-02 14:21   ` [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock Christian Brauner
@ 2026-11-02 14:41     ` Florian Westphal
  2026-11-04  9:15       ` Christian Brauner
  0 siblings, 1 reply; 50+ messages in thread
From: Florian Westphal @ 2026-11-02 14:41 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

Christian Brauner <brauner@kernel.org> wrote:
> recent_proc_net_exit() removes the proc entries of all tables with
> recent_lock held. remove_proc_entry() waits until nobody calls into the
> entry anymore, and reading one of them takes recent_lock in
> recent_seq_start() while procfs has the entry pinned. So when a reader
> gets there while the network namespace goes away, the exit path sleeps
> with a spinlock held and bh disabled, waiting for a reader that spins
> on that very lock:

Yes, but note that this change is already part of a pending nf -> net
merge request.

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

* [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out
  2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
                     ` (26 preceding siblings ...)
  2026-11-02 14:21   ` [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace Christian Brauner
@ 2026-11-02 14:43   ` Christian Brauner
  2026-11-05 12:36     ` Jan Kara
  27 siblings, 1 reply; 50+ messages in thread
From: Christian Brauner @ 2026-11-02 14: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, 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] 50+ messages in thread

* Re: [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread()
  2026-11-02 14:21   ` [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread() Christian Brauner
@ 2026-11-02 14:51     ` Rafael J. Wysocki (Intel)
  0 siblings, 0 replies; 50+ messages in thread
From: Rafael J. Wysocki (Intel) @ 2026-11-02 14:51 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon, Nov 2, 2026 at 3:22 PM Christian Brauner <brauner@kernel.org> wrote:
>
> 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")
> Cc: stable@vger.kernel.org
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>

Reviewed-by: Rafael J. Wysocki (Intel) <rafael@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 c1f6e6bd0eb2..8197c7f29743 100644
> --- a/drivers/pnp/isapnp/proc.c
> +++ b/drivers/pnp/isapnp/proc.c
> @@ -23,14 +23,12 @@ static ssize_t isapnp_proc_bus_read(struct file *file, char __user * buf,
>                                     size_t nbytes, loff_t * ppos)
>  {
>         struct pnp_dev *dev = pde_data(file_inode(file));
> -       int pos = *ppos;
> +       loff_t pos = *ppos;
>         int cnt, size = 256;
>
>         if (pos >= size)
>                 return 0;
> -       if (nbytes >= size)
> -               nbytes = size;
> -       if (pos + nbytes > size)
> +       if (nbytes > size - pos)
>                 nbytes = size - pos;
>         cnt = nbytes;
>
>
> --
> 2.53.0
>
>

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

* Re: [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again
  2026-11-02 14:21   ` [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again Christian Brauner
@ 2026-11-02 15:12     ` Chuck Lever
  2026-11-03 16:26       ` Christian Brauner
  0 siblings, 1 reply; 50+ messages in thread
From: Chuck Lever @ 2026-11-02 15:12 UTC (permalink / raw)
  To: Christian Brauner, linux-fsdevel
  Cc: Linus Torvalds, Jann Horn, Josef Bacik, Chris Mason, Jeff Layton,
	NeilBrown, Eric Dumazet, Thomas Gleixner, Alexander 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@vger.kernel.org, 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, stable



On Mon, Nov 2, 2026, at 9:21 AM, Christian Brauner wrote:
> 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")
> Cc: stable@vger.kernel.org
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>

Acked-by: Chuck Lever <cel@kernel.org>


-- 
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)

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

* Re: [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses
  2026-11-02 14:21   ` [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses Christian Brauner
@ 2026-11-02 17:58     ` Linus Torvalds
  2026-11-04  9:17       ` Christian Brauner
  0 siblings, 1 reply; 50+ messages in thread
From: Linus Torvalds @ 2026-11-02 17:58 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, stable

On Mon, 2 Nov 2026 at 06:22, Christian Brauner <brauner@kernel.org> wrote:
>
> proc_bus_pci_write() truncates the file position to an int. A pwrite()
> at an offset like 0xfffff000 turns it negative [..]

I think this would actually be better with rw_verify_area() just
checking a per-file maxbytes.

We already have that logic for lseek and friends - using that fairly
hacky combination of s_maxbytes and !unsigned_offsets().

We should combine s_maxbytes and unsigned_offsets() at file open time
into f_maxbytes and check that not just at lseek but in
rw_verify_area.

And then things like proc_bus_pci could set it's size limit in one single place.

Hmm?

                   Linus

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

* Re: [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry
  2026-11-02 14:21   ` [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry Christian Brauner
@ 2026-11-03 10:41     ` Takashi Iwai
  0 siblings, 0 replies; 50+ messages in thread
From: Takashi Iwai @ 2026-11-03 10:41 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon, 02 Nov 2026 15:21:24 +0100,
Christian Brauner wrote:
> 
> 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")
> Cc: stable@vger.kernel.org
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>

Reviewed-by: Takashi Iwai <tiwai@suse.de>


thanks,

Takashi

> ---
>  sound/core/info.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/sound/core/info.c b/sound/core/info.c
> index c4d57d0809ad..982087bc8575 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	[flat|nested] 50+ messages in thread

* Re: [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again
  2026-11-02 15:12     ` Chuck Lever
@ 2026-11-03 16:26       ` Christian Brauner
  0 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-03 16:26 UTC (permalink / raw)
  To: Chuck Lever
  Cc: linux-fsdevel, Linus Torvalds, Jann Horn, Josef Bacik,
	Chris Mason, Jeff Layton, NeilBrown, Eric Dumazet,
	Thomas Gleixner, Alexander 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@vger.kernel.org, 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, stable

On Mon, Nov 02, 2026 at 10:12:12AM -0500, Chuck Lever wrote:
> 
> 
> On Mon, Nov 2, 2026, at 9:21 AM, Christian Brauner wrote:
> > 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")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
> 
> Acked-by: Chuck Lever <cel@kernel.org>

Fwiw, I have a patch series on top of this that makes procfs own the
waitqueue and then this whole UAF issues goes away structurally and you
get to elide that global thing here.

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

* Re: [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock
  2026-11-02 14:41     ` Florian Westphal
@ 2026-11-04  9:15       ` Christian Brauner
  0 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-04  9:15 UTC (permalink / raw)
  To: Florian Westphal
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon, Nov 02, 2026 at 03:41:03PM +0100, Florian Westphal wrote:
> Yes, but note that this change is already part of a pending nf -> net
> merge request.

Dropped.

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

* Re: [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses
  2026-11-02 17:58     ` Linus Torvalds
@ 2026-11-04  9:17       ` Christian Brauner
  0 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-04  9:17 UTC (permalink / raw)
  To: Linus Torvalds
  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, stable

On Mon, Nov 02, 2026 at 09:58:51AM -0800, Linus Torvalds wrote:
> I think this would actually be better with rw_verify_area() just
> checking a per-file maxbytes.
>
> We already have that logic for lseek and friends - using that fairly
> hacky combination of s_maxbytes and !unsigned_offsets().
>
> We should combine s_maxbytes and unsigned_offsets() at file open time
> into f_maxbytes and check that not just at lseek but in
> rw_verify_area.
>
> And then things like proc_bus_pci could set it's size limit in one single place.

Yeah, I like that.

Hm, maybe we should keep this and the isapnp thing small and do that
change on top.

This also might mean a change in behavior. Because a read past the end
returns 0 for proc_bus_pci and for a regular file past s_maxbytes. So
that would potentially need some more changes.

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

* Re: [PATCH RFC 18/38] proc: don't WARN about a name that is already registered
@ 2026-11-04 18:43     ` Alexey Dobriyan
  2026-11-05 21:31       ` Christian Brauner
  0 siblings, 1 reply; 50+ messages in thread
From: Alexey Dobriyan @ 2026-11-04 18:43 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, Linus Torvalds, Chris Mason, Al Viro,
	Christoph Hellwig, linux-kernel, stable, akpm

> --- a/fs/proc/generic.c
> +++ b/fs/proc/generic.c
> @@ -401,9 +401,10 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
>  	write_lock(&proc_subdir_lock);
>  	dp->parent = dir;
>  	if (pde_subdir_insert(dir, dp) == false) {
> -		WARN(1, "proc_dir_entry '%s/%s' already registered\n",
> -		     dir->name, dp->name);
>  		write_unlock(&proc_subdir_lock);
> +		pr_warn("proc_dir_entry '%s/%s' already registered\n",
> +			dir->name, dp->name);
> +		dump_stack();
>  		goto out_free_inum;
>  	}
>  	write_unlock(&proc_subdir_lock);

I don't like this patch.

WARN is there to make bugs noticeable, /proc entries aren't supposed to be
created randomly unlike regular files so deleting non-existent name or
registering twice is probably a bug.

Now, some names do come from userspace but it is up to subsystems to
find duplicates and return errors (and sanitize for dots/slashes!).

In userspace mkdir;mkdir returns error second time, as well as
unlink;unlink. In kernel error is BUG/WARN (like it is now) and it is up
to subsystem to evade it.

	/proc/alexey

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

* Re: [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name
  2026-11-02 14:21   ` [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name Christian Brauner
@ 2026-11-05 11:41     ` Jan Kara
  0 siblings, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 11:41 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:21:31, Christian Brauner wrote:
> __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>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  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 2b1971da4a85..c560c96a64eb 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
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 18/38] proc: don't WARN about a name that is already registered
  2026-11-02 14:21   ` [PATCH RFC 18/38] proc: don't WARN about a name that is already registered Christian Brauner
  2026-11-04 18:43     ` Alexey Dobriyan
@ 2026-11-05 11:52     ` Jan Kara
  1 sibling, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 11:52 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:21:33, Christian Brauner wrote:
> 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>

Makes sense. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  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 c560c96a64eb..ef804cd3ec45 100644
> --- a/fs/proc/generic.c
> +++ b/fs/proc/generic.c
> @@ -401,9 +401,10 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
>  	write_lock(&proc_subdir_lock);
>  	dp->parent = dir;
>  	if (pde_subdir_insert(dir, dp) == false) {
> -		WARN(1, "proc_dir_entry '%s/%s' already registered\n",
> -		     dir->name, dp->name);
>  		write_unlock(&proc_subdir_lock);
> +		pr_warn("proc_dir_entry '%s/%s' already registered\n",
> +			dir->name, dp->name);
> +		dump_stack();
>  		goto out_free_inum;
>  	}
>  	write_unlock(&proc_subdir_lock);
> 
> -- 
> 2.53.0
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
  2026-11-02 14:21   ` [PATCH RFC 19/38] proc: resume readdir after the last name it returned Christian Brauner
@ 2026-11-05 12:00     ` Jan Kara
  2026-11-05 16:44     ` Jann Horn
  1 sibling, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 12:00 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:21:34, Christian Brauner wrote:
> 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>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  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 ef804cd3ec45..47830d6af255 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)
>  {
> @@ -277,6 +299,40 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
>  	return proc_lookup_de(dir, dentry, PDE(dir));
>  }
>  
> +/*
> + * Where the next readdir resumes. The entries are counted, so one that is
> + * added or removed in front of the position would shift what the next call
> + * returns. The name of the last entry emitted doesn't move.
> + */
> +struct proc_dir_cursor {
> +	loff_t pos;			/* the position behind the entry */
> +	unsigned int len;
> +	char name[NAME_MAX + 1];
> +};
> +
> +/* If this can't remember the entry the next call counts from the start. */
> +static void proc_dir_cursor_set(struct file *file, struct proc_dir_entry *de,
> +				loff_t pos)
> +{
> +	struct proc_dir_cursor *cursor = file->private_data;
> +
> +	if (!cursor) {
> +		cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
> +		if (!cursor)
> +			return;
> +		file->private_data = cursor;
> +	}
> +	cursor->pos = pos;
> +	cursor->len = de->namelen;
> +	memcpy(cursor->name, de->name, de->namelen);
> +}
> +
> +int proc_dir_release(struct inode *inode, struct file *file)
> +{
> +	kfree(file->private_data);
> +	return 0;
> +}
> +
>  /*
>   * This returns non-zero if at EOF, so that the /proc
>   * root directory can use this and check if it should
> @@ -289,23 +345,26 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
>  int proc_readdir_de(struct file *file, struct dir_context *ctx,
>  		    struct proc_dir_entry *de)
>  {
> +	struct proc_dir_cursor *cursor = file->private_data;
> +	struct proc_dir_entry *dir = de;
>  	int i;
>  
>  	if (!dir_emit_dots(file, ctx))
>  		return 0;
>  
> -	i = ctx->pos - 2;
>  	read_lock(&proc_subdir_lock);
> -	de = pde_subdir_first(de);
> -	for (;;) {
> -		if (!de) {
> -			read_unlock(&proc_subdir_lock);
> -			return 0;
> -		}
> -		if (!i)
> -			break;
> -		de = pde_subdir_next(de);
> -		i--;
> +	if (cursor && cursor->pos == ctx->pos) {
> +		/* Resume after the last entry emitted, wherever it is now. */
> +		de = pde_subdir_after(dir, cursor->name, cursor->len);
> +	} else {
> +		/* Moved by a seek, count from the start like always. */
> +		de = pde_subdir_first(dir);
> +		for (i = ctx->pos - 2; de && i; i--)
> +			de = pde_subdir_next(de);
> +	}
> +	if (!de) {
> +		read_unlock(&proc_subdir_lock);
> +		return 1;
>  	}
>  
>  	do {
> @@ -318,8 +377,13 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
>  			return 0;
>  		}
>  		ctx->pos++;
> +		proc_dir_cursor_set(file, de, ctx->pos);
>  		read_lock(&proc_subdir_lock);
> -		next = pde_subdir_next(de);
> +		/* Removed in the meantime, go on after its name. */
> +		if (RB_EMPTY_NODE(&de->subdir_node))
> +			next = pde_subdir_after(dir, de->name, de->namelen);
> +		else
> +			next = pde_subdir_next(de);
>  		pde_put(de);
>  		de = next;
>  	} while (de);
> @@ -347,6 +411,7 @@ static const struct file_operations proc_dir_operations = {
>  	.llseek			= generic_file_llseek,
>  	.read			= generic_read_dir,
>  	.iterate_shared		= proc_readdir,
> +	.release		= proc_dir_release,
>  };
>  
>  static int proc_net_d_revalidate(struct inode *dir, const struct qstr *name,
> 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
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once
  2026-11-02 14:21   ` [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once Christian Brauner
@ 2026-11-05 12:11     ` Jan Kara
  0 siblings, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 12:11 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:21:39, Christian Brauner wrote:
> 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>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  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
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned
  2026-11-02 14:21   ` [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned Christian Brauner
@ 2026-11-05 12:16     ` Jan Kara
  0 siblings, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 12:16 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:21:46, Christian Brauner wrote:
> 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>

Just one coding style nit below. Otherwise feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

> +	if (!name) {
> +		after = rb_first(&dir->root);
> +	} else for (node = dir->root.rb_node; node;) {

This indentation looks too inventive to me :) I'd just use
	} else {
		for (...) {

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table
  2026-11-02 14:21   ` [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table Christian Brauner
@ 2026-11-05 12:21     ` Jan Kara
  0 siblings, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 12:21 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:21:48, Christian Brauner wrote:
> 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>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  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 38b61887b59c..2150cf842919 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
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out
  2026-11-02 14:43   ` [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out Christian Brauner
@ 2026-11-05 12:36     ` Jan Kara
  0 siblings, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 12:36 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:43:28, Christian Brauner wrote:
> 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>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  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
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace
  2026-11-02 14:21   ` [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace Christian Brauner
@ 2026-11-05 12:55     ` Jan Kara
  0 siblings, 0 replies; 50+ messages in thread
From: Jan Kara @ 2026-11-05 12:55 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon 02-11-26 15:21:50, Christian Brauner wrote:
> 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 and mode, follows a link to the entry it stands for and
> pins that. Then it looks the dentry up, creates the inode if there is
> none, which checks for itself under sysctl_lock that the table is still
> registered, and emits the entry with nothing held at all. The next
> entry is the first live one after that name, which is where a listing
> resumes between calls since "proc: sysctl: resume readdir after the
> last name it returned" anyway. kernfs_fop_readdir() and
> proc_readdir_de() work the same way, a reference on the entry and the
> lock only to find the next one.
> 
> While the entry is still registered its node is still in the tree and
> the next one is right behind it, like kernfs_dir_next_pos() continues
> from the node it holds. Only once it went away, and to resume, is the
> next entry found by name. That's one trip through sysctl_lock per entry
> like before, and the link entries of /proc/sys/net no longer take two
> more. The directory isn't
> taken into use either, an entry keeps its directory registered while
> it is used and readdir looks at the directory under the lock. So the
> dots don't wait for anything, a directory that went away lists them and
> then fails with -ENOENT. A seek to an arbitrary position counts the
> entries from the start like before, one trip per entry.
> 
> Fixes: 77b14db502cb ("[PATCH] sysctl: reimplement the sysctl proc support")
> Cc: stable@vger.kernel.org
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>

So I didn't really carefully check all the details but overall this looks
sane to me. Just one thing that was confusing me is: Why is sysctl_dirent
separate from sysctl_cursor? Wouldn't it be more logical for sysctl_cursor
itself to start what it needs to pin the entry it wants to restart from?

								Honza

> ---
>  fs/proc/proc_sysctl.c | 307 +++++++++++++++++++++++++++-----------------------
>  1 file changed, 166 insertions(+), 141 deletions(-)
> 
> diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
> index a1242247f492..0b7a1e487ed6 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,97 @@ static const struct ctl_table *lookup_entry(struct ctl_table_header **phead,
>  	return entry;
>  }
>  
> -static struct ctl_node *first_usable_entry(struct rb_node *node)
> -{
> -	struct ctl_node *ctl_node;
> +/*
> + * What readdir takes along for an entry between the trips through
> + * sysctl_lock: the header pinned, which keeps its memory and nothing else,
> + * the entry, its name and mode. For a link the header and entry are the
> + * ones it stands for in the caller's set.
> + */
> +struct sysctl_dirent {
> +	struct ctl_table_header *head;
> +	const struct ctl_table *table;
> +	struct ctl_node *node;		/* the entry's node in the directory, NULL for a link */
> +	ino_t ino;
> +	unsigned int type;
> +	umode_t mode;
> +	bool skip;
> +	int len;
> +	char name[NAME_MAX + 1];
> +};
>  
> -	for (;node; node = rb_next(node)) {
> -		ctl_node = rb_entry(node, struct ctl_node, node);
> -		if (use_table(ctl_node->header))
> -			return ctl_node;
> -	}
> -	return NULL;
> +/* A reference on the memory of @head, like an inode made for it holds. */
> +static void sysctl_pin(struct ctl_table_header *head)
> +{
> +	lockdep_assert_held(&sysctl_lock);
> +	head->count++;
>  }
>  
> -static void first_entry(struct ctl_dir *dir,
> -	struct ctl_table_header **phead, const struct ctl_table **pentry)
> +static void sysctl_unpin(struct ctl_table_header *head)
>  {
> -	struct ctl_table_header *head = NULL;
> -	const struct ctl_table *entry = NULL;
> -	struct ctl_node *ctl_node;
> -
> -	spin_lock(&sysctl_lock);
> -	ctl_node = first_usable_entry(rb_first(&dir->root));
> -	spin_unlock(&sysctl_lock);
> -	if (ctl_node) {
> -		head = ctl_node->header;
> -		entry = &head->ctl_table[ctl_node - head->node];
> -	}
> -	*phead = head;
> -	*pentry = entry;
> +	lockdep_assert_held(&sysctl_lock);
> +	if (!--head->count)
> +		kfree_rcu(head, rcu);
>  }
>  
> -static void next_entry(struct ctl_table_header **phead, const struct ctl_table **pentry)
> +/* the first entry from @node on whose table isn't being unregistered */
> +static struct ctl_node *first_live_entry(struct rb_node *node)
>  {
> -	struct ctl_table_header *head = *phead;
> -	const struct ctl_table *entry = *pentry;
> -	struct ctl_node *ctl_node = &head->node[entry - head->ctl_table];
> -
> -	spin_lock(&sysctl_lock);
> -	unuse_table(head);
> +	struct ctl_node *ctl_node;
>  
> -	ctl_node = first_usable_entry(rb_next(&ctl_node->node));
> -	spin_unlock(&sysctl_lock);
> -	head = NULL;
> -	if (ctl_node) {
> -		head = ctl_node->header;
> -		entry = &head->ctl_table[ctl_node - head->node];
> +	lockdep_assert_held(&sysctl_lock);
> +	for (; node; node = rb_next(node)) {
> +		ctl_node = rb_entry(node, struct ctl_node, node);
> +		if (!ctl_node->header->unregistering)
> +			return ctl_node;
>  	}
> -	*phead = head;
> -	*pentry = entry;
> +	return NULL;
>  }
>  
>  /*
> - * The first usable entry of @dir after @name, or the first one at all for a
> - * NULL @name. The entries are sorted by name, so that is where a listing
> - * resumes when entries in front of it came or went in between. -ENOENT if
> - * the directory is being unregistered.
> + * Fill @d with the entry of @dir behind the one it holds, letting go of that
> + * one, or with the first live entry after @name, or the first one at all for
> + * a NULL @name. While the entry @d holds is still registered its node is
> + * still in the tree and the next one is right behind it. Once it is gone the
> + * entries are found by name, they are sorted by it, so that is also where a
> + * listing resumes when entries in front of it came or went in between.
> + * 1 with the next entry pinned in @d, 0 at the end of the directory and
> + * -ENOENT once @dir itself is being unregistered.
> + *
> + * Everything that reads the tables happens under sysctl_lock, where an entry
> + * is erased when it is unregistered, since nothing holds the tables in use.
>   */
> -static int entry_after(struct ctl_dir *dir, const char *name, int namelen,
> -		       struct ctl_table_header **phead,
> -		       const struct ctl_table **pentry)
> +static int sysctl_dir_next(struct ctl_dir *dir, struct sysctl_dirent *d,
> +			   const char *name, int namelen)
>  {
> -	struct ctl_table_header *head = NULL;
> -	const struct ctl_table *entry = NULL;
>  	struct rb_node *node, *after = NULL;
> +	struct ctl_table_header *head;
> +	const struct ctl_table *entry;
>  	struct ctl_node *ctl_node;
> +	bool by_name = true;
> +	int ret = 0;
>  
>  	spin_lock(&sysctl_lock);
> +	if (d->head) {
> +		if (d->node && !d->head->unregistering) {
> +			after = rb_next(&d->node->node);
> +			by_name = false;
> +		}
> +		sysctl_unpin(d->head);
> +		d->head = NULL;
> +	}
>  	if (unlikely(dir->header.unregistering)) {
> -		spin_unlock(&sysctl_lock);
> -		return -ENOENT;
> +		ret = -ENOENT;
> +		goto out;
>  	}
> -	if (!name) {
> +	if (!by_name) {
> +		/* behind the entry @d held */
> +	} else if (!name) {
>  		after = rb_first(&dir->root);
>  	} else for (node = dir->root.rb_node; node;) {
> -		const struct ctl_table *table;
> -		struct ctl_table_header *h;
> -
>  		ctl_node = rb_entry(node, struct ctl_node, node);
> -		h = ctl_node->header;
> -		table = &h->ctl_table[ctl_node - h->node];
> -		if (namecmp(table->procname, strlen(table->procname),
> +		head = ctl_node->header;
> +		entry = &head->ctl_table[ctl_node - head->node];
> +		if (namecmp(entry->procname, strlen(entry->procname),
>  			    name, namelen) > 0) {
>  			after = node;
>  			node = node->rb_left;
> @@ -454,15 +467,56 @@ static int entry_after(struct ctl_dir *dir, const char *name, int namelen,
>  			node = node->rb_right;
>  		}
>  	}
> -	ctl_node = first_usable_entry(after);
> -	spin_unlock(&sysctl_lock);
> -	if (ctl_node) {
> -		head = ctl_node->header;
> -		entry = &head->ctl_table[ctl_node - head->node];
> +	ctl_node = first_live_entry(after);
> +	if (!ctl_node)
> +		goto out;
> +	head = ctl_node->header;
> +	entry = &head->ctl_table[ctl_node - head->node];
> +
> +	d->len = strlen(entry->procname);
> +	if (d->len > NAME_MAX) {
> +		ret = -ENAMETOOLONG;
> +		goto out;
>  	}
> -	*phead = head;
> -	*pentry = entry;
> -	return 0;
> +	memcpy(d->name, entry->procname, d->len);
> +	d->mode = entry->mode;
> +	d->skip = false;
> +	d->node = ctl_node;
> +	if (unlikely(S_ISLNK(entry->mode))) {
> +		/* the entry it stands for in the caller's set, or nothing */
> +		struct ctl_table_root *root = entry->data;
> +		struct ctl_table_set *set = lookup_header_set(root);
> +		struct ctl_dir *xdir = xlate_dir(set, head->parent);
> +		struct ctl_table_header *xhead = NULL;
> +		const struct ctl_table *xentry = NULL;
> +
> +		if (!IS_ERR(xdir))
> +			xentry = find_entry(&xhead, xdir, d->name, d->len);
> +		if (!xentry || xhead->unregistering) {
> +			d->skip = true;
> +		} else {
> +			head = xhead;
> +			entry = xentry;
> +		}
> +		d->node = NULL;
> +	}
> +	sysctl_pin(head);
> +	d->head = head;
> +	d->table = entry;
> +	ret = 1;
> +out:
> +	spin_unlock(&sysctl_lock);
> +	return ret;
> +}
> +
> +static void sysctl_dir_done(struct sysctl_dirent *d)
> +{
> +	if (!d->head)
> +		return;
> +	spin_lock(&sysctl_lock);
> +	sysctl_unpin(d->head);
> +	spin_unlock(&sysctl_lock);
> +	d->head = NULL;
>  }
>  
>  /*
> @@ -765,29 +819,29 @@ static void sysctl_cursor_set(struct file *file, const char *name, int len,
>  	memcpy(cursor->name, name, len);
>  }
>  
> -static bool proc_sys_fill_cache(struct file *file,
> -				struct dir_context *ctx,
> -				struct ctl_table_header *head,
> -				const struct ctl_table *table)
> +/*
> + * The inode number and type of the entry @d holds, instantiating its dentry
> + * if there is none. Needs the header pinned, proc_sys_make_inode() checks
> + * for itself that the table is still registered.
> + */
> +static int proc_sys_fill_dirent(struct file *file, struct sysctl_dirent *d)
>  {
>  	struct dentry *child, *dir = file->f_path.dentry;
>  	struct inode *inode;
>  	struct qstr qname;
> -	ino_t ino = 0;
> -	unsigned type = DT_UNKNOWN;
>  
> -	qname.name = table->procname;
> -	qname.len  = strlen(table->procname);
> +	qname.name = d->name;
> +	qname.len  = d->len;
>  	qname.hash = full_name_hash(dir, qname.name, qname.len);
>  
>  	child = d_lookup(dir, &qname);
>  	if (!child) {
>  		child = d_alloc_parallel(dir, &qname);
>  		if (IS_ERR(child))
> -			return false;
> +			return PTR_ERR(child);
>  		if (d_in_lookup(child)) {
>  			struct dentry *res;
> -			inode = proc_sys_make_inode(dir->d_sb, head, table);
> +			inode = proc_sys_make_inode(dir->d_sb, d->head, d->table);
>  			res = d_splice_alias_ops(inode, child,
>  						 &proc_sys_dentry_operations);
>  			d_lookup_done(child);
> @@ -795,79 +849,41 @@ static bool proc_sys_fill_cache(struct file *file,
>  				dput(child);
>  
>  				if (IS_ERR(res))
> -					return false;
> +					return PTR_ERR(res);
>  
>  				child = res;
>  			}
>  		}
>  	}
>  	inode = d_inode(child);
> -	ino  = inode->i_ino;
> -	type = inode->i_mode >> 12;
> +	d->ino = inode->i_ino;
> +	d->type = inode->i_mode >> 12;
>  	dput(child);
> -	return dir_emit(ctx, qname.name, qname.len, ino, type);
> -}
> -
> -static bool proc_sys_link_fill_cache(struct file *file,
> -				    struct dir_context *ctx,
> -				    struct ctl_table_header *head,
> -				    const struct ctl_table *table)
> -{
> -	bool ret = true;
> -
> -	head = sysctl_head_grab(head);
> -	if (IS_ERR(head))
> -		return false;
> -
> -	/* It is not an error if we can not follow the link ignore it */
> -	if (sysctl_follow_link(&head, &table))
> -		goto out;
> -
> -	ret = proc_sys_fill_cache(file, ctx, head, table);
> -out:
> -	sysctl_head_finish(head);
> -	return ret;
> -}
> -
> -static int scan(struct ctl_table_header *head, const struct ctl_table *table,
> -		unsigned long *pos, struct file *file,
> -		struct dir_context *ctx)
> -{
> -	bool res;
> -
> -	if ((*pos)++ < ctx->pos)
> -		return true;
> -
> -	if (unlikely(S_ISLNK(table->mode)))
> -		res = proc_sys_link_fill_cache(file, ctx, head, table);
> -	else
> -		res = proc_sys_fill_cache(file, ctx, head, table);
> -
> -	if (res) {
> -		ctx->pos = *pos;
> -		sysctl_cursor_set(file, table->procname, strlen(table->procname),
> -				  ctx->pos);
> -	}
> -
> -	return res;
> +	return 0;
>  }
>  
> +/*
> + * Unregistering a table waits until nobody uses it anymore and a copy to
> + * userspace can take as long as userspace wants. So a listing doesn't take
> + * any table into use. It pins the memory of the entry it is at, finds the
> + * next one under sysctl_lock and emits with nothing held, like
> + * kernfs_fop_readdir() and proc_readdir_de() do. Between calls a listing
> + * resumes after the name of the last entry it emitted.
> + */
>  static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
>  {
> -	struct ctl_table_header *head = grab_header(file_inode(file));
> +	struct ctl_table_header *head = PROC_I(file_inode(file))->sysctl;
>  	struct sysctl_cursor *cursor = file->private_data;
> -	struct ctl_table_header *h = NULL;
> -	const struct ctl_table *entry;
> +	struct sysctl_dirent d = {};
>  	struct ctl_dir *ctl_dir;
> -	unsigned long pos;
> -
> -	if (IS_ERR(head))
> -		return PTR_ERR(head);
> +	int err;
>  
> +	if (!head)
> +		head = &sysctl_table_root.default_set.dir.header;
>  	ctl_dir = container_of(head, struct ctl_dir, header);
>  
>  	if (!dir_emit_dots(file, ctx))
> -		goto out;
> +		return 0;
>  
>  	/*
>  	 * Resume after the last entry the previous call emitted. Only a seek
> @@ -875,21 +891,30 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
>  	 * seeking to an arbitrary position always did.
>  	 */
>  	if (cursor && cursor->pos == ctx->pos) {
> -		entry_after(ctl_dir, cursor->name, cursor->len, &h, &entry);
> -		pos = ctx->pos;
> +		err = sysctl_dir_next(ctl_dir, &d, cursor->name, cursor->len);
>  	} else {
> -		first_entry(ctl_dir, &h, &entry);
> -		pos = 2;
> +		loff_t pos = 2;
> +
> +		err = sysctl_dir_next(ctl_dir, &d, NULL, 0);
> +		while (err > 0 && pos++ < ctx->pos)
> +			err = sysctl_dir_next(ctl_dir, &d, d.name, d.len);
>  	}
> +	if (err < 0)
> +		return err;
>  
> -	for (; h; next_entry(&h, &entry)) {
> -		if (!scan(h, entry, &pos, file, ctx)) {
> -			sysctl_head_finish(h);
> -			break;
> +	while (err > 0) {
> +		if (!d.skip) {
> +			err = proc_sys_fill_dirent(file, &d);
> +			if (err)
> +				break;
> +			if (!dir_emit(ctx, d.name, d.len, d.ino, d.type))
> +				break;
>  		}
> +		ctx->pos++;
> +		sysctl_cursor_set(file, d.name, d.len, ctx->pos);
> +		err = sysctl_dir_next(ctl_dir, &d, d.name, d.len);
>  	}
> -out:
> -	sysctl_head_finish(head);
> +	sysctl_dir_done(&d);
>  	return 0;
>  }
>  
> 
> -- 
> 2.53.0
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
  2026-11-02 14:21   ` [PATCH RFC 19/38] proc: resume readdir after the last name it returned Christian Brauner
  2026-11-05 12:00     ` Jan Kara
@ 2026-11-05 16:44     ` Jann Horn
  2026-11-06 14:22       ` Christian Brauner
  1 sibling, 1 reply; 50+ messages in thread
From: Jann Horn @ 2026-11-05 16:44 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, Josef Bacik, Al Viro, Jan Kara, linux-kernel,
	stable

On Mon, Nov 2, 2026 at 3:26 PM Christian Brauner <brauner@kernel.org> wrote:
> 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>

Reviewed-by: Jann Horn <jannh@google.com>

> +static void proc_dir_cursor_set(struct file *file, struct proc_dir_entry *de,
> +                               loff_t pos)
> +{
> +       struct proc_dir_cursor *cursor = file->private_data;
> +
> +       if (!cursor) {
> +               cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
> +               if (!cursor)
> +                       return;

(For both this and the proc_sys version, you could instead do the
cursor allocation at the start of readdir() so that you can return a
proper error if it fails, but I guess it doesn't really matter and
that would complicate the code a bit more...)


> +               file->private_data = cursor;
> +       }
> +       cursor->pos = pos;
> +       cursor->len = de->namelen;
> +       memcpy(cursor->name, de->name, de->namelen);
> +}

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

* Re: [PATCH RFC 18/38] proc: don't WARN about a name that is already registered
  2026-11-04 18:43     ` Alexey Dobriyan
@ 2026-11-05 21:31       ` Christian Brauner
  0 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-05 21:31 UTC (permalink / raw)
  To: Alexey Dobriyan
  Cc: linux-fsdevel, Linus Torvalds, Chris Mason, Al Viro,
	Christoph Hellwig, linux-kernel, stable, akpm

On Wed, Nov 04, 2026 at 09:43:43PM +0300, Alexey Dobriyan wrote:
> Now, some names do come from userspace but it is up to subsystems to
> find duplicates and return errors (and sanitize for dots/slashes!).

Agreed. pktgen seems to be the only case suffering from this currently.
I dropped this patch and added. Tested and the WARN goes away.

-- >8 --
From e919a2c72296008ea07b5930bc9e02d59743bbbd Mon Sep 17 00:00:00 2001
From: Christian Brauner <brauner@kernel.org>
Date: Thu, 5 Nov 2026 18:02:42 +0100
Subject: [PATCH] net: pktgen: don't register a device file under a name that
The device files of pktgen share /proc/net/pktgen with pgctrl and the
thread files, and a device can be named like either of them:

  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 same happens from the netdev notifier when a device that pktgen has
a file for is renamed to pgctrl, and when a device that two threads
have under different "@" suffixes is renamed, both files move to the
bare device name then. The files of a network namespace belong to the
root of its user namespace and none of this needs 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.

Remember the name a device file is registered under and refuse a name
that pgctrl, a thread file or another device file has, both for
"add_device" and before the file of a renamed device moves, which then
keeps its old name. The thread files go by the prefix of their names,
the threads of a namespace are created one after the other while its
first thread file can be written already.

Reported-by: Chris Mason <mason@kernel.org>
Suggested-by: Alexey Dobriyan <adobriyan@gmail.com>
Link: https://lore.kernel.org/r/9cb48eaf-72f2-4e9f-b979-1ead58deb22f@p183
Fixes: 6c2f91e077f1 ("proc: use WARN() rather than printk+backtrace")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 net/core/pktgen.c | 51 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 48 insertions(+), 3 deletions(-)

diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index 02af70d360bc..4dd5920d5769 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -240,6 +240,7 @@ static char *pkt_flag_names[] = {
 #define PKTGEN_MAGIC 0xbe9be955
 #define PG_PROC_DIR "pktgen"
 #define PGCTRL	    "pgctrl"
+#define PG_THREAD_PREFIX "kpktgend_"
 
 #define MAX_CFLOWS  65536
 
@@ -416,6 +417,7 @@ struct pktgen_dev {
 				  */
 	netdevice_tracker dev_tracker;
 	char odevname[32];
+	char procname[32];	/* the name of ->entry in /proc/net/pktgen */
 	struct flow_state *flows;
 	unsigned int cflows;	/* Concurrent flows (config) */
 	unsigned int lflow;		/* Flow length  (config) */
@@ -2044,6 +2046,34 @@ static bool __pktgen_NN_threads(const struct pktgen_net *pn,
 	return false;
 }
 
+/*
+ * A device can be named like pgctrl or a thread file. Threads are still
+ * being created while the first thread file is writable, so match thread
+ * names by prefix. Called with pktgen_thread_lock held.
+ */
+static bool pktgen_proc_name_taken(const struct pktgen_net *pn,
+				   const char *name)
+{
+	const struct pktgen_thread *t;
+	const struct pktgen_dev *pkt_dev;
+
+	lockdep_assert_held(&pktgen_thread_lock);
+
+	if (!strcmp(name, PGCTRL) || str_has_prefix(name, PG_THREAD_PREFIX))
+		return true;
+	rcu_read_lock();
+	list_for_each_entry(t, &pn->pktgen_threads, th_list) {
+		list_for_each_entry_rcu(pkt_dev, &t->if_list, list) {
+			if (pkt_dev->entry && !strcmp(name, pkt_dev->procname)) {
+				rcu_read_unlock();
+				return true;
+			}
+		}
+	}
+	rcu_read_unlock();
+	return false;
+}
+
 /*
  * mark a device for removal
  */
@@ -2091,13 +2121,22 @@ static void pktgen_change_name(const struct pktgen_net *pn, struct net_device *d
 			if (pkt_dev->odev != dev)
 				continue;
 
+			/* renamed back to the name the file kept */
+			if (pkt_dev->entry && !strcmp(pkt_dev->procname, dev->name))
+				break;
+			if (pktgen_proc_name_taken(pn, dev->name)) {
+				pr_err("can't move proc entry for '%s', the name is taken\n",
+				       dev->name);
+				break;
+			}
 			proc_remove(pkt_dev->entry);
-
 			pkt_dev->entry = proc_create_data(dev->name, 0600,
 							  pn->proc_dir,
 							  &pktgen_if_proc_ops,
 							  pkt_dev);
-			if (!pkt_dev->entry)
+			if (pkt_dev->entry)
+				strscpy(pkt_dev->procname, dev->name);
+			else
 				pr_err("can't move proc entry for '%s'\n",
 				       dev->name);
 			break;
@@ -3841,6 +3880,10 @@ static int pktgen_add_device(struct pktgen_thread *t, const char *ifname)
 		pr_err("ERROR: interface already used\n");
 		return -EBUSY;
 	}
+	if (pktgen_proc_name_taken(t->net, ifname)) {
+		pr_err("ERROR: %s/%s is taken\n", PG_PROC_DIR, ifname);
+		return -EEXIST;
+	}
 
 	pkt_dev = kzalloc_node(sizeof(struct pktgen_dev), GFP_KERNEL, node);
 	if (!pkt_dev)
@@ -3888,6 +3931,7 @@ static int pktgen_add_device(struct pktgen_thread *t, const char *ifname)
 		err = -EINVAL;
 		goto out2;
 	}
+	strscpy(pkt_dev->procname, ifname);
 #ifdef CONFIG_XFRM
 	pkt_dev->ipsmode = XFRM_MODE_TRANSPORT;
 	pkt_dev->ipsproto = IPPROTO_ESP;
@@ -3937,7 +3981,8 @@ static int __net_init pktgen_create_thread(int cpu, struct pktgen_net *pn)
 	list_add_tail(&t->th_list, &pn->pktgen_threads);
 	init_completion(&t->start_done);
 
-	p = kthread_create_on_cpu(pktgen_thread_worker, t, cpu, "kpktgend_%d");
+	p = kthread_create_on_cpu(pktgen_thread_worker, t, cpu,
+				  PG_THREAD_PREFIX "%d");
 	if (IS_ERR(p)) {
 		pr_err("kthread_create_on_node() failed for cpu %d\n", t->cpu);
 		list_del(&t->th_list);
-- 
2.53.0


-- 

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

* Re: [PATCH RFC 19/38] proc: resume readdir after the last name it returned
  2026-11-05 16:44     ` Jann Horn
@ 2026-11-06 14:22       ` Christian Brauner
  0 siblings, 0 replies; 50+ messages in thread
From: Christian Brauner @ 2026-11-06 14:22 UTC (permalink / raw)
  To: Jann Horn
  Cc: linux-fsdevel, Josef Bacik, Al Viro, Jan Kara, linux-kernel,
	stable

On Thu, 5 Nov 2026 17:44:23 +0100, Jann Horn wrote:
> On Mon, Nov 2, 2026 at 3:26 PM Christian Brauner <brauner@kernel.org> wrote:
> > [...]
>
> Reviewed-by: Jann Horn <jannh@google.com>
>
> > [...]
>
> (For both this and the proc_sys version, you could instead do the
> cursor allocation at the start of readdir() so that you can return a
> proper error if it fails, but I guess it doesn't really matter and
> that would complicate the code a bit more...)

I did consider this and didn't think it was worth it. If we can allocate
a cursor we just start from the beginning.

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

* Re: [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page()
  2026-11-02 14:21   ` [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() Christian Brauner
@ 2026-11-11 11:54     ` Breno Leitao
  0 siblings, 0 replies; 50+ messages in thread
From: Breno Leitao @ 2026-11-11 11:54 UTC (permalink / raw)
  To: Christian Brauner
  Cc: linux-fsdevel, 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, Chuck Lever, stable

On Mon, Nov 02, 2026 at 03:21:40PM +0100, Christian Brauner wrote:
> 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>

Reviewed-by: Breno Leitao <leitao@debian.org>

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

end of thread, other threads:[~2026-11-11 11:54 UTC | newest]

Thread overview: 50+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20261102-defizit-jobaussicht-kurios-7016c6e67442@brauner>
2026-11-02 14:21 ` [PATCH RFC 00/38] proc: fix a few old bugs in /proc handlers Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 01/38] PCI/proc: don't truncate the position of config space accesses Christian Brauner
2026-11-02 17:58     ` Linus Torvalds
2026-11-04  9:17       ` Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 02/38] PNP: isapnp: don't read past the end of the buffer on a large pread() Christian Brauner
2026-11-02 14:51     ` Rafael J. Wysocki (Intel)
2026-11-02 14:21   ` [PATCH RFC 03/38] netfilter: xt_recent: don't remove the proc entries under a spinlock Christian Brauner
2026-11-02 14:41     ` Florian Westphal
2026-11-04  9:15       ` Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 04/38] s390/dasd: don't look in front of the buffer on an empty write Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 05/38] platform/x86: toshiba_acpi: reject malformed brightness and fan writes Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 06/38] xtensa: simdisk: don't read the file name without the lock Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 07/38] powerpc/rtas_flash: don't read past the validate message Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 08/38] powerpc/rtasd: zero the error log buffer Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 09/38] ALSA: info: don't write past the end of a data entry Christian Brauner
2026-11-03 10:41     ` Takashi Iwai
2026-11-02 14:21   ` [PATCH RFC 13/38] scsi: core: NUL-terminate what is written to the host proc files Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 14/38] um: NUL-terminate what is written to /proc/exitcode Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 15/38] ntfs3: don't warn when a huge label is written Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 16/38] proc: don't walk through a non-directory when resolving a name Christian Brauner
2026-11-05 11:41     ` Jan Kara
2026-11-02 14:21   ` [PATCH RFC 17/38] net: pktgen: reject device names with a '/' Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 18/38] proc: don't WARN about a name that is already registered Christian Brauner
2026-11-04 18:43     ` Alexey Dobriyan
2026-11-05 21:31       ` Christian Brauner
2026-11-05 11:52     ` Jan Kara
2026-11-02 14:21   ` [PATCH RFC 19/38] proc: resume readdir after the last name it returned Christian Brauner
2026-11-05 12:00     ` Jan Kara
2026-11-05 16:44     ` Jann Horn
2026-11-06 14:22       ` Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 23/38] proc: don't let NOMMU map procfs files Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 24/38] proc/vmcore: don't let vmcore_cleanup() run twice at once Christian Brauner
2026-11-05 12:11     ` Jan Kara
2026-11-02 14:21   ` [PATCH RFC 25/38] powerpc/crash_dump: check ioremap_cache() in copy_oldmem_page() Christian Brauner
2026-11-11 11:54     ` Breno Leitao
2026-11-02 14:21   ` [PATCH RFC 26/38] powerpc/crash_dump: recover from a machine check " Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 27/38] sunrpc: make queue_wait a global wait queue again Christian Brauner
2026-11-02 15:12     ` Chuck Lever
2026-11-03 16:26       ` Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 28/38] scsi: esas2r: bound the lengths of the HBA, SMP and firmware ioctls Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 29/38] powerpc/pseries: bound the lengths in the ofdt property parser Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 30/38] powerpc/pseries/lparcfg: return h_get_ppp()'s status as a long Christian Brauner
2026-11-02 14:21   ` [PATCH RFC 31/38] proc: sysctl: resume readdir after the last name it returned Christian Brauner
2026-11-05 12:16     ` Jan Kara
2026-11-02 14:21   ` [PATCH RFC 33/38] proc: sysctl: copy to and from userspace without holding the table Christian Brauner
2026-11-05 12:21     ` Jan Kara
2026-11-02 14:21   ` [PATCH RFC 35/38] proc: sysctl: don't hold the tables while readdir copies to userspace Christian Brauner
2026-11-05 12:55     ` Jan Kara
2026-11-02 14:43   ` [PATCH RFC 37/38] proc: don't pin a symlink's entry while readlink() copies it out Christian Brauner
2026-11-05 12:36     ` Jan Kara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox