Commit Graph

1039 Commits

Author SHA1 Message Date
Josef Bacik
948e604aca
mount: refuse setns() with a shared fs_struct again
mntns_install() refuses to switch the mount namespace of a task that
shares its fs_struct, since root and cwd of the others would move to a
namespace they are not in.  It looks at nsset->fs for that.  If more
than the mount namespace is asked for, that is a private copy made by
prepare_nsset(), which has one user by definition; commit_nsset() then
writes root and cwd of the copy to the fs_struct of the task.

A thread of a process with two threads, pidfd of a task in another
mount namespace:

	setns(pidfd, CLONE_NEWNS)			-EINVAL
	setns(pidfd, CLONE_NEWNS | CLONE_NEWUTS)	0

and after the second call the other thread, still in the old mount
namespace, has lost its cwd (/tmp became /) and sees the mounts of the
other namespace under /.

Look at the fs_struct that is going to be changed.  The second call then
fails with -EINVAL like the first one.

Fixes: 303cc571d1 ("nsproxy: attach to namespaces via pidfds")
Cc: stable@vger.kernel.org # v5.8+
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-29 14:12:56 +01:00
Christian Brauner
95a5412022
listmount: don't wrap around with a U64_MAX cursor
do_listmount() takes the last mount id the caller saw and looks up the
first mount after it by searching for last_mnt_id + 1. With U64_MAX
that wraps around to zero and the search finds the first mount in the
namespace. So a cursor at the largest possible id restarts the listing
from the beginning instead of ending it. A cursor of U64_MAX - 1
correctly returns nothing.

No mount will ever have that id. Mount ids come from a counter that
starts at MNT_UNIQUE_ID_OFFSET + 1 and a caller that only feeds back
ids it got from us can't hit this. It's still wrong. Nothing can come
after the largest possible id so return no entries for it, just like
for any other cursor past the last mount.

The reverse direction isn't affected. A cursor of zero is special-cased
and anything else is larger than MNT_UNIQUE_ID_OFFSET so last_mnt_id - 1
can't underflow.

Reproducer, unprivileged:

    #define _GNU_SOURCE
    #include <errno.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <sys/syscall.h>
    #include <unistd.h>
    #define LSMT_ROOT 0xffffffffffffffffULL
    struct mnt_id_req {
    	uint32_t size, mnt_ns_fd;
    	uint64_t mnt_id, param, mnt_ns_id;
    };
    static void list(uint64_t cursor)
    {
    	struct mnt_id_req req = {
    		.size = sizeof(req), .mnt_id = LSMT_ROOT, .param = cursor,
    	};
    	uint64_t ids[8];
    	long ret;
    	errno = 0;
    	ret = syscall(__NR_listmount, &req, ids, 8, 0);
    	printf("cursor %llu: ret %ld errno %d", (unsigned long long)cursor,
    	       ret, errno);
    	for (long i = 0; i < ret && i < 1; i++)
    		printf("%s%llu", i ? "," : " ids ", (unsigned long long)ids[i]);
    	printf("%s\n", ret > 1 ? ",..." : "");
    }
    int main(void)
    {
    	list(0);
    	list(UINT64_MAX - 1);
    	list(UINT64_MAX);
    	return 0;
    }

Before, on 7.3.0-rc7-00252-g4e3259b08c54 (vfs.fixes) in a VM:

    cursor 0: ret 5 errno 0 ids 2147483651,...
    cursor 18446744073709551614: ret 0 errno 0
    cursor 18446744073709551615: ret 5 errno 0 ids 2147483651,...

After:

    cursor 0: ret 5 errno 0 ids 2147483651,...
    cursor 18446744073709551614: ret 0 errno 0
    cursor 18446744073709551615: ret 0 errno 0

Reported-by: shaos <shawnq1@163.com>
Link: https://lore.kernel.org/r/20261020110732.959552-1-shawnq1@163.com
Fixes: b4c2bea8ce ("add listmount(2) syscall")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20261021-work-mount-statmount_listns_fixes-v1-3-8917a4c1bd14@kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-22 18:50:19 +02:00
Christian Brauner
11eef75006
statmount: don't copy the reserved empty string past the buffer
statmount_string() reserves an empty string at the start of the seq
buffer before it calls the handler for the requested string. That's
what the offsets of unset strings point to since commit e52e97f09f
("statmount: let unset strings be empty").

If the handler emits nothing the early return from commit 75ead69a71
("fs: don't let statmount return empty strings") skips the size check
against the caller's buffer. Back then an empty emission meant an empty
seq buffer and there was nothing to copy. Now the reserved NUL byte is
there and copy_statmount_to_user() copies it to buf + sizeof(struct
statmount) no matter how small bufsize is.

So request a string that may turn out empty with a buffer that's
smaller than struct statmount and the kernel writes one zero byte past
the caller's buffer and reports a size that's larger than the buffer.
STATMOUNT_MNT_UIDMAP or STATMOUNT_MNT_GIDMAP on a mount that isn't
idmapped will do, so will STATMOUNT_FS_SUBTYPE on anything but fuse or
STATMOUNT_OPT_ARRAY on a filesystem without ->show_options(). None of
this needs privileges. The byte only lands in the caller's own address
space but that's still not ok.

prepare_kstatmount() already refuses a buffer of exactly sizeof(struct
statmount) for string requests. A smaller buffer can't hold a string
either and it can't even hold the reserved empty string. So refuse any
buffer that isn't larger than the fixed part. That's also what the uapi
header says: string requests need space after the fixed part of the
structure.

Reproducer, unprivileged, on any mount that isn't idmapped:

    #define _GNU_SOURCE
    #include <errno.h>
    #include <fcntl.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <sys/syscall.h>
    #include <unistd.h>
    #define STATMOUNT_FS_SUBTYPE	0x00000100U
    #define STATMOUNT_MNT_UIDMAP	0x00002000U
    #define STATX_MNT_ID_UNIQUE	0x00004000U
    #define SM_SIZE			512 /* sizeof(struct statmount) */
    struct mnt_id_req {
    	uint32_t size, mnt_ns_fd;
    	uint64_t mnt_id, param, mnt_ns_id;
    };
    static uint64_t root_mnt_id(void)
    {
    	unsigned char stx[256]; /* struct statx, stx_mnt_id at offset 144 */
    	uint64_t id;
    	if (syscall(__NR_statx, AT_FDCWD, "/", 0, STATX_MNT_ID_UNIQUE, stx))
    		_exit(1);
    	memcpy(&id, stx + 144, sizeof(id));
    	return id;
    }
    static void probe(uint64_t mnt_id, uint64_t mask, int guard)
    {
    	struct mnt_id_req req = {
    		.size = sizeof(req), .mnt_id = mnt_id, .param = mask,
    	};
    	long page = sysconf(_SC_PAGESIZE);
    	unsigned char *map, *buf;
    	uint32_t size;
    	long ret;
    	map = mmap(NULL, 2 * page, PROT_READ | PROT_WRITE,
    		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	memset(map, 0xaa, 2 * page);
    	/* 100 byte buffer ending right at the page boundary */
    	buf = map + page - 100;
    	if (guard)
    		mprotect(map + page, page, PROT_NONE);
    	errno = 0;
    	ret = syscall(__NR_statmount, &req, buf, 100, 0);
    	printf("mask 0x%04llx%s: ret %ld errno %d", (unsigned long long)mask,
    	       guard ? " guard page" : "", ret, errno);
    	if (!guard) {
    		memcpy(&size, buf, sizeof(size));
    		printf(", size 0x%x, byte[%d] 0x%02x", size, SM_SIZE,
    		       buf[SM_SIZE]);
    	}
    	printf("\n");
    	munmap(map, 2 * page);
    }
    int main(void)
    {
    	uint64_t id = root_mnt_id();
    	probe(id, STATMOUNT_MNT_UIDMAP, 0);
    	probe(id, STATMOUNT_FS_SUBTYPE, 0);
    	probe(id, STATMOUNT_MNT_UIDMAP, 1);
    	return 0;
    }

Before, on 7.3.0-rc7-00252-g4e3259b08c54 (vfs.fixes) in a VM, the
same on a distro 7.1.13:

    mask 0x2000: ret 0 errno 0, size 0x65, byte[512] 0x00
    mask 0x0100: ret 0 errno 0, size 0x65, byte[512] 0x00
    mask 0x2000 guard page: ret -1 errno 14

After:

    mask 0x2000: ret -1 errno 75, size 0xaaaaaaaa, byte[512] 0xaa
    mask 0x0100: ret -1 errno 75, size 0xaaaaaaaa, byte[512] 0xaa
    mask 0x2000 guard page: ret -1 errno 75

Reported-by: shaos <shawnq1@163.com>
Link: https://lore.kernel.org/r/20261020110747.959829-1-shawnq1@163.com
Fixes: e52e97f09f ("statmount: let unset strings be empty")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20261021-work-mount-statmount_listns_fixes-v1-1-8917a4c1bd14@kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-22 18:50:19 +02:00
Christian Brauner
c84de57efe
super: convert to plain hashtable
Convert the rhltable to a plain hashtable under a spinlock. The walks
keep their place with a cursor entry in the chain. A claim is unhashed
the moment it's dropped and a walk that finds a superblock that isn't
born yet waits for it to be born or to die and looks again. Since
hashing can't fail anymore the entry for the superblock's own s_dev is
embedded in struct super_block and the preallocation in alloc_super()
goes away. This all simplifies things quite a lot.

It also fixes a btrfs bug where fs_bdev_freeze() ended up freezing a
btrfs filesystem through a stale member that open_ctree() had already
released while the freeze was waiting for SB_BORN. The resume doesn't
find a holder for that device anymore so the filesystem stays frozen
for good.

The walks return superblocks instead of table entries now and
user_get_super() hands out the passive reference the walk took.

Reproducer, a three device btrfs with a stale copy of a removed member.
dm-delay under the remaining members stretches the window between
btrfs_open_devices() and btrfs_free_extra_devids():

  truncate -s 1G a1 a2 b
  A1=$(losetup -f --show a1); A2=$(losetup -f --show a2); B=$(losetup -f --show b)
  mkfs.btrfs -f -d single -m single $A1 $A2 $B
  cp b b.old
  mount $A1 /mnt && btrfs device remove 3 /mnt && umount /mnt
  OLD=$(losetup -f --show b.old)
  dmsetup create d1 --table "0 $(blockdev --getsz $A1) delay $A1 0 200"
  dmsetup create d2 --table "0 $(blockdev --getsz $A2) delay $A2 0 200"
  dmsetup create old --table "0 $(blockdev --getsz $OLD) linear $OLD 0"
  mount -o device=/dev/mapper/d2,device=/dev/mapper/old /dev/mapper/d1 /mnt &
  sleep 1; dmsetup suspend old; wait
  dmsetup resume old
  touch /mnt/foo     # hangs until fsfreeze -u /mnt

That left btrfs frozen in 5 of 5 rounds with the same tables issued
through raw dm ioctls. The -o degraded variant needs the mount held
between the insertion and the bd_fsfreeze_count check in
fs_bdev_register() with a kprobe.

Fixes: cdb5146f8d ("fs: look up superblocks via the device table in fs_holder_ops")
Link: https://patch.msgid.link/20261012-work-super-dev_hash-v1-2-305cf71cd334@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-22 18:50:18 +02:00
Linus Torvalds
a804c5742b lsm/stable-7.4 PR 20261016
-----BEGIN PGP SIGNATURE-----
 
 iQJIBAABCgAyFiEES0KozwfymdVUl37v6iDy2pc3iXMFAmrSkXAUHHBhdWxAcGF1
 bC1tb29yZS5jb20ACgkQ6iDy2pc3iXOXTQ/+LcghZOG72DOeZwDkXIzdD+Ex5Xkf
 kV3EiZ+l6LCy2sGePC5PdHJaX1wQE5N0bHCHh8TMIsfE5b0nbdKJsDiFJVkayTJz
 CE4SpAm8Tup+CBDkwMJ1x3aGYgh4RxbgTR9klsrH2zsehI80sGLE3SYp9+lO3BFp
 Eyf+INgF2GrcBNH3ozQBtME5b0086UC/uWif1vQ3HK4RU6QxWlQg5VB68+XJv9GY
 /wZyJ4bIet8QQ1S2boMQN/RGQHih1qYs8hOeuL+R0Inh6pk59ucgM3qfj9OILWbD
 x4u9ptO7ICvoKlkrzWIfvt+ydM5q9mHozN1qi2i+dy4IiDyBicZ75YCFWIeSOld+
 UGL73NB08g/KT1fOo7WE3lfm/ZAMoR8F0IUScDvMeVLv06NU/rCccPKl+z8bYBuo
 vSbctpHsnSPxpZew1U1FiTavwTUZVTHgPnuGrM28wfV1qsj+y9jebM35GNETolDi
 6fcsVh6Fw/8qCG/V1TVQzahqI0xuA2/BDQE7vFKO87Mmu7EJqNr1J3lgFdmMk7tD
 cZqHhHhSF7MgQz8qkuCB8t2edukXV1Fyzm8tUmA9MeurfQHrhLkwZuRPzKmEz0RB
 HssTyKhmo9cwgLgRFzspnoDIOXcSYqxUNSmz4BOVb9yUMi/d5bRhiZ6JFym19rRr
 9IjSU2ud7x3fXqw=
 =7Dpp
 -----END PGP SIGNATURE-----

Merge tag 'lsm-pr-20261016' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm

Pull lsm updates from Paul Moore:

 - Add LSM hooks, object blobs, and audit records for namespaces

   Add the necessary bits to enable LSMs to enforce access controls on
   the management of various kernel namespaces. While this pull request
   does not contain any in-tree users of these hooks, the Landlock pull
   request for Linux v7.4 will include Landlock support and the next
   merge window will likely include SELinux support as well. Considering
   the development behind these hooks, I expect that BPF LSM developers
   will also be making use of the namespace hooks, if they haven't
   already.

 - Update the inode LSM hooks to include mount idmaps

   This allows LSMs to incorporate the mount ID mapping into their
   access control decisions. While there are no current in-tree users of
   this, it was requested by those developing BPF LSM programs and this
   pull request includes a BPF LSM selftest.

 - Preserve the full 32-bit ioctl command in LSM audit records

   Previously only the low 16 bits were logged, ignoring the direction
   and size information in the upper 16 bits. The pull request also
   includes a Landlock based selftest to verify the full 32-bit ioctl
   value.

 - Ensure we don't call security_backing_file_free() multiple times

   Thankfully we don't have a problem with the current code base, but we
   want to fix this behavior now before it becomes a problem.

 - Clarify that only the current task may access current->cred

   The current code is a bit misleading around safely accessing the cred
   field in a task struct. Through comment and code corrections, this
   pull request clarifies that only the task itself may access its cred
   field.

 - Add SLAB_NO_MERGE to the credential object cache

   Harden the credential cache by ensuring credential objects live on
   dedicated slab pages.

 - Make use of safe memory zero'ing in the LSM Rust bindings

 - Update the docs for the current behavior of /sys/kernel/security/lsm

 - Update the docs for CONFIG_LSM and the "lsm=" command line option

 - Remove unnecessary code

* tag 'lsm-pr-20261016' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm:
  lsm: remove redundant NULL check in security_init()
  selftests/landlock: Check full ioctl commands in audit records
  lsm: Preserve full ioctl commands in audit records
  doc: fix LSM ordering description for /sys/kernel/security/lsm
  doc: describe CONFIG_LSM and lsm= as the selection mechanism
  selftests/bpf: verify mount idmaps reach inode hooks
  lsm: expose mount idmaps to inode hooks
  rust: lsm: replace `core::mem::zeroed` with `pin_init::zeroed`
  cred: prevent slab cache merging for cred_jar
  lsm: don't call security_backing_file_free() multiple times
  lsm: delete duplicate assignment in lsm_prepare()
  lsm: update the BUILD_BUG_ON() in audit_log_lsm_data()
  lsm: add LSM_AUDIT_DATA_NS for namespace audit records
  lsm: add LSM blob and hooks for namespaces
  ns: Free anonymous mount namespaces via ns_common_free()
  cred: clarify that task_struct::cred is only for the current task
2026-10-20 20:04:07 -10:00
Linus Torvalds
0cbcaf75a1 vfs-7.4-rc1.mount
Please consider pulling these changes from the signed vfs-7.4-rc1.mount tag.
 
 Thanks!
 Christian
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCatHrlwAKCRCRxhvAZXjc
 okTnAP42ymJnRxXT0sSaNQ8Sk9PIRQ+1evElkdr0LfGkBp5ApAEAjtRL+o4LCjd9
 WRE1+n+xwuQyPHUv9UghlPi8Qnr7ggU=
 =nfhQ
 -----END PGP SIGNATURE-----

Merge tag 'vfs-7.4-rc1.mount' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull mount updates from Christian Brauner:
 "This adds OPEN_TREE_DROP_MNTNS_MOUNTS.

  crun started using OPEN_TREE_NAMESPACE to set up the container's mount
  namespace and open_tree(OPEN_TREE_CLONE) for the sources of its bind
  mounts. That fails on any host with a mount namespace pinned below
  such a source. The recursive clone picks the pinned namespace up and
  move_mount() refuses to attach the tree with ELOOP. Only a younger
  mount namespace may ever be bound into an older one or mount namespace
  references could form a loop. A namespace created with
  OPEN_TREE_NAMESPACE is younger than everything else on the system so
  from inside it every pinned namespace is older. By the time this fails
  setns() has run and the host tree is out of reach, so the runtime has
  to predict the situation beforehand and lose the optimization. snapd
  pins mount namespaces under /run/snapd/ns so on Ubuntu that is any
  container bind mounting the host root.

  Every other path crossing a mount namespace boundary already leaves
  these mounts behind, copy_mnt_ns() and create_new_namespace() both do.
  Only get_detached_copy() asks for them.

  The new flag lets a caller get a clone without them. It stays opt-in
  because open_tree(OPEN_TREE_CLONE) plus move_mount() is mount --rbind
  through a file descriptor and dropping them by default would silently
  lose /run/snapd/ns/*.mnt on a live system. It requires AT_RECURSIVE,
  is a no-op with OPEN_TREE_NAMESPACE and skips locked nsfs mounts the
  way copy_mnt_ns() does, so nothing new is exposed. Selftests are
  included"

* tag 'vfs-7.4-rc1.mount' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  selftests/filesystems: test OPEN_TREE_DROP_MNTNS_MOUNTS
  mount: add OPEN_TREE_DROP_MNTNS_MOUNTS
2026-10-19 20:06:27 -10:00
Linus Torvalds
7ed37e9d5c vfs-7.4-rc1.misc
Please consider pulling these changes from the signed vfs-7.4-rc1.misc tag.
 
 Thanks!
 Christian
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCatHrlgAKCRCRxhvAZXjc
 oiocAQDQrd6xDocGA55sJ45KSw1tHYQh5CYH8zml+T9D1agMmwEA5oGUQWEJAoN3
 Y2zXG7WoZcdnKQv3FWRYWhjRmXNxUQk=
 =dbn3
 -----END PGP SIGNATURE-----

Merge tag 'vfs-7.4-rc1.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull misc vfs updates from Christian Brauner:
 "Fixes:

   - aio: a keyed poll completion on an epoll instance could recurse
     into the same epoll waitqueue lock when the result eventfd is
     watched by that very instance. An unprivileged process could make a
     CPU spin on the recursive lock. eventfd-backed poll completions are
     always deferred now.

   - fsnotify_inoderemove() is only called once the inode is really
     dead, no nlink and no aliases, and open_by_handle_at() can't add an
     alias to a dead inode anymore. The race could crash fsnotify or
     lose inode mark notifications.

   - devtmpfs: the conversion of the public instance to ->get_tree()
     broke reconfiguring the shared superblock's options on later mounts
     which made the boot time defaults permanent. A reconfigure context
     is kept in fc->fs_private so the parse ops can forward options to
     the underlying filesystem.

   - ufs truncated the 64-bit directory size to an unsigned int in
     ufs_last_byte(). A crafted image with a 4 GiB directory produced an
     out-of-folio read in ufs_find_entry() and, with
     CONFIG_UFS_FS_WRITE, a write into an adjacent page.

   - devpts rejects max=0 instead of silently lifting the per-instance
     pty limit. The value wrapped into ida_alloc_max() and became
     unlimited.

   - fs_pin publishes pins with the rcu list helpers so the kill paths
     reading the lists under rcu see the initialization.

   - dentry_kill() reports a Tasks-RCU quiescent state. Its
     cond_resched() is the only reschedule point on the dentry teardown
     path and cond_resched() isn't a quiescent state for Tasks-RCU, so
     Meta kept seeing rcu_tasks stalls in btrfs subvolume deletion.

   - writeback sizes foreign flushes by the target wb's dirty pages
     instead of the memcg-wide count which over or undersized a single
     bdi flush.

   - nfsd READ emitted two fsnotify access events on the iterator path
     since vfs_iocb_iter_read() already sends one. A READ on a krb5i or
     krb5p mount showed up twice in inotify.

   - pipe and splice pass a poll key when waking readers and writers so
     epoll, poll() and select() waiters only wake for the event they
     asked for.

   - the iso8859-14 Unicode to charset tables had page01 duplicated as
     page1e so 29 code points mapped to the wrong bytes and 22 that
     aren't in the charset were accepted.

   - orangefs doesn't wait for an unmount service operation that never
     returns on system shutdown and uses ssize_t for strscpy() errors.

   - __register_chrdev() checks the kobject_set_name() return value.

   - selftest fixes: the openat2 tests hardcoded O_LARGEFILE to 0x8000
     which is O_NOFOLLOW on powerpc, resolve_test makes /tmp a
     mountpoint so RESOLVE_NO_XDEV has a boundary to trip over,
     file_stressor moves into its own directory with a 30 minute
     timeout, the kernfs listxattr test skips when SELinux exposes
     security.selinux, the namespaces active reference tests wait for
     the thread via a PIDFD_THREAD pidfd since pthread_join() returns
     before exit_nsproxy_namespaces(), the idmapped_tmpfile test chowns
     its directory into the mapped range, the clone3 CLONE_CLEAR_SIGHAND
     test adds CLONE_VM so it fails for the intended reason, the pidfd
     PPID error message prints the values it compares and fscontext_ns
     gets a .gitignore.

  Cleanups:

   - vmcore prints the release of the crashed kernel from the VMCOREINFO
     note so a kdump that dies before the dump lands still says which
     kernel it was for.

   - initramfs allocates hardlink hash entries with the validated cpio
     name length instead of PATH_MAX, which put every entry in
     kmalloc-8k.

   - the unused dax_layout_busy_page() is removed,
     dax_layout_busy_page_range() becomes static and dax_folio_init()
     drops a redundant list initialization.

   - vfs_empty_path() is removed, it has had no callers since
     getname_maybe_null().

   - comment and documentation fixes in aio, bpf_fs_kfuncs,
     fs-writeback, inode, read_write, squashfs and the vfs.rst copy of
     struct file_system_type"

* tag 'vfs-7.4-rc1.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (38 commits)
  selftests/filesystems: Fix idmapped_tmpfile layer dir ownership
  selftests/namespaces: Wait for thread exit via pidfd in active-ref tests
  writeback: size foreign flushes by target wb dirty pages
  initramfs: Reduce hardlink hash allocation sizes
  fs: remove unused vfs_empty_path()
  dcache: report a Tasks-RCU quiescent state in dentry_kill()
  vmcore: report the release of the crashed kernel
  splice: pass a poll key when waking pipe readers and writers
  pipe: pass a poll key when waking writers after a resize
  orangefs: use ssize_t instead of size_t for error codes...
  orangefs_super: don't wait on service operation on system shutdown.
  docs/vfs: Fix the struct file_system_type copy
  nls: fix the Unicode to charset tables of iso8859-14
  selftests/filesystems/openat2: make /tmp a mountpoint inside private namespace
  selftests/filesystems/openat2: fix O_LARGEFILE definitions for non-arm64 architectures
  fs: fix misleading comments in read_write.c
  squashfs: fix inode number description in documentation
  fs: fix typos in comments
  fs/dax: Make dax_layout_busy_page_range() static
  fs/dax: Remove unused dax_layout_busy_page()
  ...
2026-10-19 19:30:45 -10:00
Linus Torvalds
00b04f4495 vfs-7.4-rc1.idmap
Please consider pulling these changes from the signed vfs-7.4-rc1.idmap tag.
 
 Thanks!
 Christian
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCatHrlgAKCRCRxhvAZXjc
 opFuAPwLL5l3D3GSRyD+SyOjKYThylgowvH9c2BcLWbSAIO6yQD9F05GX+rrcbUq
 9ijolxAaQZYpEWsemZIIHx6BBwdDBA4=
 =IH8Z
 -----END PGP SIGNATURE-----

Merge tag 'vfs-7.4-rc1.idmap' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull vfs idmap constification from Christian Brauner:
 "This makes struct mnt_idmap const throughout the tree.

  A mount's idmapping is immutable. The only thing modified after
  alloc_mnt_idmap() is the reference count and that is hidden behind
  mnt_idmap_get() and mnt_idmap_put(). Everything else only reads from
  it. That is the same model struct cred uses and the idmapping is
  sensitive enough to deserve it.

  The conversion goes bottom up through the vfs{g,u}id and fs{g,u}id
  helpers, the privilege checks, acl, xattr and quota and then every
  inode operation that takes an idmap, so ->permission(), ->getattr(),
  ->setattr(), ->create(), ->mkdir(), ->mknod(), ->symlink(),
  ->rename(), ->tmpfile(), ->get_acl(), ->set_acl() and ->fileattr_set()
  with their implementations in every filesystem, plus the LSM hooks.

  struct vfsmount, struct mount_kattr and struct kstatmount store a
  const pointer and nop_mnt_idmap and invalid_mnt_idmap live in rodata,
  so anything that tries to modify them faults instead of silently
  corrupting the initial idmapping"

* tag 'vfs-7.4-rc1.idmap' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (27 commits)
  fs: make nop_mnt_idmap and invalid_mnt_idmap const
  fs: port mnt_idmap() and file_mnt_idmap() to const mnt_idmap
  fs: port vfs_*() helpers to const mnt_idmap
  fs: port ->setattr() to pass const mnt_idmap
  fs: port ->getattr() to pass const mnt_idmap
  fs: port ->create() to pass const mnt_idmap
  fs: port ->symlink() to pass const mnt_idmap
  fs: port ->mkdir() to pass const mnt_idmap
  fs: port ->rename() to pass const mnt_idmap
  fs: port ->mknod() to pass const mnt_idmap
  fs: port ->tmpfile() to pass const mnt_idmap
  fs: port ->get_acl() to pass const mnt_idmap
  fs: port ->set_acl() to pass const mnt_idmap
  fs: port ->fileattr_set() to pass const mnt_idmap
  fs: port xattr to const mnt_idmap
  fs: port ->permission() to pass const mnt_idmap
  fs: port acl to const mnt_idmap
  fs: port inode_init_owner() to const mnt_idmap
  fs: port inode_owner_or_capable() to const mnt_idmap
  fs: port privilege checking helpers to const mnt_idmap
  ...
2026-10-19 17:57:49 -10:00
Linus Torvalds
a1d4585be1 vfs-7.4-rc1.file
Please consider pulling these changes from the signed vfs-7.4-rc1.file tag.
 
 Thanks!
 Christian
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCatHrlgAKCRCRxhvAZXjc
 osd6AP4nuqrNhsMzcp4zPk4rbMVhvJL9O3ccnoE6TLn8Mg7uLwD/WtjKIVtKAPkv
 gz58C2i14vDLLbKAAGK0P8c1EF0ZuAE=
 =jY1x
 -----END PGP SIGNATURE-----

Merge tag 'vfs-7.4-rc1.file' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull file table updates from Christian Brauner:
 "This annotates struct fdtable's fd array with __counted_by_ptr so
  KASAN and the fortified functions can bounds check it against max_fds
  and adds a KUnit suite for fdtable. A follow-up uses __struct_size()
  in the test since sparse doesn't know __builtin_dynamic_object_size().

  FD_PREPARE() is simplified. It was built as an ACQUIRE-style guard on
  infrastructure that was never needed. The ACQUIRE machinery and
  CLASS_INIT() go away, the variable is declared with __cleanup()
  directly, struct fd_prepare loses its err field since the fd either
  holds the descriptor or the error, and callers get a const pointer to
  the guard behind a UNIQUE_ID() name so they can't shadow it. Code
  generation is identical"

* tag 'vfs-7.4-rc1.file' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  file: make struct fd_prepare const and kill its err field
  cleanup: remove CLASS_INIT()
  file: declare the FD_PREPARE() variable with __cleanup() directly
  file: simplify FD_PREPARE()
  vfs: use __struct_size() in the fdtable KUnit test
  vfs: Add KUnit tests for fdtable
  vfs: Annotate struct fdtable's fd field with __counted_by_ptr
2026-10-19 17:35:50 -10:00
Linus Torvalds
e86d265826 namespace-7.4-rc1.misc
Please consider pulling these changes from the signed namespace-7.4-rc1.misc tag.
 
 Thanks!
 Christian
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCatHrlQAKCRCRxhvAZXjc
 okOqAP98o3UxyJOsSJfNJpr0OnHzk/WHbt8Qm7Lmk0Xgt2h6BgD/Ypf5v68wiCaS
 eg6X3g3wR4tLMbdTkEy5tekLG80Alg4=
 =36S+
 -----END PGP SIGNATURE-----

Merge tag 'namespace-7.4-rc1.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull namespace updates from Christian Brauner:

 - Reject uid_map, gid_map and projid_map values that don't fit in a
   u32.

   map_write() parsed with simple_strtoul() and truncated modulo 2^32 on
   64-bit so "4294967301 1000 1" was installed as "5 1000 1" without an
   error. Each field is parsed with kstrtou32() now and selftests cover
   the out-of-range, negative and in-range cases. This isn't a security
   fix. The truncated values were valid ids and every later check still
   applied to them.

 - Annotate struct uid_gid_map's forward and reverse pointers with
   __counted_by_ptr and add a KUnit suite for inserting and sorting
   extents.

   insert_extent() wrote to forward[nr_extents] before bumping the count
   which the annotation flags, so it bumps first now. Two follow-up
   fixes rename the now global helpers to uid_gid_map_insert_extent()
   and uid_gid_map_sort(), since the btrfs self-tests have their own
   insert_extent(), and guard the declarations on CONFIG_KUNIT.

 - Drop the BUG_ON() NULL checks in copy_mnt_ns() and copy_utsname().
   create_new_namespaces() always passes a valid pointer and the other
   copy_*_ns() helpers don't check either.

 - Remove a dead key_put() on the create_user_ns() error path.

* tag 'namespace-7.4-rc1.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  selftests/namespaces: add uid_map/gid_map range tests
  userns: reject uid_map/gid_map values that do not fit in u32
  userns: declare the KUnit-visible helpers whenever KUnit is enabled
  userns: rename insert_extent() and sort_idmaps()
  userns: Add KUnit test suite for uid_gid_map
  userns: Add __counted_by_ptr attribute to struct uid_gid_map
  fs: namespace: drop BUG_ON() in copy_mnt_ns()
  uts: drop BUG_ON() in copy_utsname()
  userns: remove dead key_put() on the create_user_ns() error path
2026-10-19 16:09:10 -10:00
Paul Moore
436485132d Automated merge of 'lsm/dev' into 'stable-7.4'
Manual merge fixup in ns_common_types.h:ns_common to work around a conflict
with the nsfs RCU grace period fix.

* lsm/dev:
  lsm: remove redundant NULL check in security_init()
  selftests/landlock: Check full ioctl commands in audit records
  lsm: Preserve full ioctl commands in audit records
  doc: fix LSM ordering description for /sys/kernel/security/lsm
  doc: describe CONFIG_LSM and lsm= as the selection mechanism
  selftests/bpf: verify mount idmaps reach inode hooks
  lsm: expose mount idmaps to inode hooks
  rust: lsm: replace `core::mem::zeroed` with `pin_init::zeroed`
  cred: prevent slab cache merging for cred_jar
  lsm: don't call security_backing_file_free() multiple times
  lsm: delete duplicate assignment in lsm_prepare()
  lsm: update the BUILD_BUG_ON() in audit_log_lsm_data()
  lsm: add LSM_AUDIT_DATA_NS for namespace audit records
  lsm: add LSM blob and hooks for namespaces
  ns: Free anonymous mount namespaces via ns_common_free()
  cred: clarify that task_struct::cred is only for the current task
2026-10-16 17:01:58 -04:00
Christian Brauner
69fbe94f80
namespace: a mount that copy_tree() leaves out can't take over a lock
Since commit c62a476693 ("move_mount: transfer MNT_LOCKED") a mount
can be moved beneath a locked mount. The lock moves from the old cover
to the new mount which takes over the job of hiding what's under it and
the old cover can be unmounted. That works as long as the new mount
stays. But the caller picks the new mount and copy_tree() leaves a
mount of a mount namespace file out of every copy made without
CL_COPY_MNT_NS_FILE, together with everything mounted on it, and never
looks at MNT_LOCKED. unshare(CLONE_NEWNS) and mount propagation make
such copies.

So a task that is root in its own user namespace gets rid of a locked
cover on a file without a race and without touching the filesystem
below it. It makes a child with a mount namespace of its own, binds the
child's /proc/<pid>/ns/mnt beneath the cover with MOVE_MOUNT_BENEATH,
which hands the lock to that bind, and calls unshare(CLONE_NEWNS). The
new mount namespace has nothing on the file. That's the usual way to
mask a single file, a bind of /dev/null or of an empty file. With a
bind of an ordinary file the file stays covered.

copy_tree() already refuses to leave out a locked unbindable mount with
-EPERM since commit df7342b240 ("mount: Don't allow copying
MNT_UNBINDABLE|MNT_LOCKED mounts"). Do the same for a locked mount of a
mount namespace file. That also covers a tree copied with
CL_COPY_MNT_NS_FILE and attached into a mount namespace of another user
namespace where lock_mnt_tree() locks it. Note that unshare(CLONE_NEWNS)
and clone(CLONE_NEWNS) fail with EPERM from such a mount namespace from
then on. The mount can't be left out and it can't be unmounted.

And refuse to move a mount of a mount namespace file beneath a locked
mount in the first place. Otherwise the task ends up with a mount it
can't unmount and an unshare() that fails for good.

Chris also found a second mount that goes away by itself, a clone of an
automounted mount that shrink_submounts() took along with its parent.
Commit f78ddcc872 ("namespace: never expire a locked mount") took
care of that one.

Chris's reproducer is appended. Mode nsfile reads SECRET after the
unshare() on 7.3-rc7, with this patch the move_mount() fails with
EINVAL and the file stays covered. nsfile-control uses an ordinary file
and reads MASK both ways.

  // SPDX-License-Identifier: GPL-2.0
  /*
   * Get rid of a locked cover with MOVE_MOUNT_BENEATH.
   *
   * The "admin" (root) makes a tmpfs W:
   *     W/D         file, holds SECRET, covered by a bind of a file with MASK
   *     W/Dd/inner  file, holds SECRET, Dd is covered by a bind of a directory
   *                 whose "inner" holds MASK
   *     W/own       directory of uid 1000 with the file "mine" and the
   *                 directory "minedir", whose "inner" holds MINE
   *     W/dbg       debugfs, mode 755. dbg/tracing is an automount point
   * The "attacker" is uid 1000. It enters a new user namespace with a new mount
   * namespace, so W and both covers are locked there.
   *
   * usage: prog MODE
   *   plain      put a bind of own/mine beneath the cover of D, unmount the cover
   *   nsfile     put a bind of the mount namespace file of a child beneath the
   *              cover of D, then unshare(CLONE_NEWNS)
   *   nsfile-control    the same with a bind of own/mine
   *   shrink     put a bind of the automounted tracefs beneath the cover of Dd,
   *              unmount the cover, put a tmpfs beneath W, call umount(W), which fails
   *   shrink-control    the same with a bind of own/minedir
   * The environment variable AUTOMOUNT_DIR names another automount point for "shrink".
   */
  #define _GNU_SOURCE
  #include <errno.h>
  #include <fcntl.h>
  #include <grp.h>
  #include <sched.h>
  #include <signal.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/mount.h>
  #include <sys/prctl.h>
  #include <sys/stat.h>
  #include <sys/wait.h>

  #ifndef MOVE_MOUNT_BENEATH
  #define MOVE_MOUNT_BENEATH 0x00000200
  #endif
  #define W "/run/x2"

  static void die(const char *what)
  {
  	fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno));
  	exit(2);
  }

  static void put_file(const char *name, const char *content)
  {
  	int fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, 0644);

  	if (fd < 0 || write(fd, content, strlen(content)) < 0)
  		die(name);
  	close(fd);
  }

  static void show(const char *path)
  {
  	char buf[32] = "";
  	int fd = open(path, O_RDONLY);

  	if (fd < 0) {
  		printf("attacker: open(%s) = %s\n", path, strerror(errno));
  		return;
  	}
  	if (read(fd, buf, sizeof(buf) - 1) < 0)
  		snprintf(buf, sizeof(buf), "<%s>", strerror(errno));
  	close(fd);
  	printf("attacker: read(%s) = \"%s\"%s\n", path, buf,
  	       strncmp(buf, "SECRET", 6) ? "" : "   <-- HIT");
  }

  static int say(const char *what, int ret)
  {
  	printf("attacker: %s: %s\n", what, ret ? strerror(errno) : "ok");
  	return ret;
  }

  static void write_str(const char *path, const char *s)
  {
  	int fd = open(path, O_WRONLY);

  	if (fd < 0 || write(fd, s, strlen(s)) < 0)
  		die(path);
  	close(fd);
  }

  static void mounts(void)
  {
  	if (system("grep x2 /proc/self/mountinfo"))
  		printf("attacker: no mounts listed\n");
  }

  static void admin_setup(void)
  {
  	if (unshare(CLONE_NEWNS) || mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
  		die("unshare");
  	mkdir(W, 0755);
  	if (mount("W", W, "tmpfs", 0, "mode=755"))
  		die("mount W");
  	if (mkdir(W "/Dd", 0755) || mkdir(W "/maskdir", 0755) || mkdir(W "/dbg", 0755) ||
  	    mkdir(W "/own", 0755) || mkdir(W "/own/minedir", 0755))
  		die("mkdir");
  	put_file(W "/D", "SECRET");
  	put_file(W "/Dd/inner", "SECRET");
  	put_file(W "/mask", "MASK");
  	put_file(W "/maskdir/inner", "MASK");
  	put_file(W "/own/mine", "MINE");
  	put_file(W "/own/minedir/inner", "MINE");
  	if (mount(W "/mask", W "/D", NULL, MS_BIND, NULL) ||
  	    mount(W "/maskdir", W "/Dd", NULL, MS_BIND, NULL))
  		die("covers");
  	if (mount("dbg", W "/dbg", "debugfs", 0, "mode=755"))
  		die("mount debugfs");
  }

  static void become_attacker(void)
  {
  	if (setgroups(0, NULL) || setresgid(1000, 1000, 1000) ||
  	    setresuid(1000, 1000, 1000) || prctl(PR_SET_DUMPABLE, 1))
  		die("drop to uid 1000");
  	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
  		die("unshare user and mount namespace");
  	write_str("/proc/self/setgroups", "deny");
  	write_str("/proc/self/uid_map", "0 1000 1");
  	write_str("/proc/self/gid_map", "0 1000 1");
  }

  /* clone the mount at @src and put the clone beneath the top mount at @where */
  static int beneath(const char *src, const char *where)
  {
  	char what[128];
  	int fd = open_tree(AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
  	int ret;

  	snprintf(what, sizeof(what), "open_tree(%s, OPEN_TREE_CLONE)", src);
  	if (say(what, fd < 0))
  		return -1;
  	ret = move_mount(fd, "", AT_FDCWD, where,
  			 MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH);
  	snprintf(what, sizeof(what), "move_mount(clone, %s, MOVE_MOUNT_BENEATH)", where);
  	say(what, ret);
  	close(fd);
  	return ret;
  }

  static void do_nsfile(int control)
  {
  	char path[64] = W "/own/mine";
  	int pfd[2];
  	pid_t pid;
  	char c;

  	if (pipe(pfd))
  		die("pipe");
  	pid = fork();
  	if (pid < 0)
  		die("fork");
  	if (!pid) {
  		if (unshare(CLONE_NEWNS) || write(pfd[1], "g", 1) != 1)
  			die("child");
  		pause();
  		exit(0);
  	}
  	if (read(pfd[0], &c, 1) != 1)
  		die("sync");
  	if (!control)
  		snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
  	beneath(path, W "/D");
  	mounts();
  	say("unshare(CLONE_NEWNS)", unshare(CLONE_NEWNS));
  	show(W "/D");
  	mounts();
  	kill(pid, SIGKILL);
  	waitpid(pid, NULL, 0);
  }

  static void do_shrink(int control)
  {
  	const char *automount = getenv("AUTOMOUNT_DIR");
  	int fd, mfd;

  	if (!automount)
  		automount = W "/dbg/tracing";
  	beneath(control ? W "/own/minedir" : automount, W "/Dd");
  	say("umount2(Dd, 0), the old cover", umount2(W "/Dd", 0));
  	say("umount2(Dd, 0), the mount that was put beneath", umount2(W "/Dd", 0));
  	show(W "/Dd/inner");
  	say("umount2(W, 0)", umount2(W, 0));
  	fd = fsopen("tmpfs", FSOPEN_CLOEXEC);
  	if (fd < 0 || fsconfig(fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0))
  		die("fsopen tmpfs");
  	mfd = fsmount(fd, FSMOUNT_CLOEXEC, 0);
  	if (mfd < 0)
  		die("fsmount");
  	say("move_mount(new tmpfs, W, MOVE_MOUNT_BENEATH)",
  	    move_mount(mfd, "", AT_FDCWD, W, MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH));
  	close(mfd);
  	mounts();
  	say("umount2(W, 0)", umount2(W, 0));
  	show(W "/Dd/inner");
  	mounts();
  }

  int main(int argc, char **argv)
  {
  	const char *mode = argc > 1 ? argv[1] : "plain";

  	setvbuf(stdout, NULL, _IOLBF, 0);
  	admin_setup();
  	become_attacker();

  	show(W "/D");
  	show(W "/Dd/inner");
  	say("umount2(D, MNT_DETACH)", umount2(W "/D", MNT_DETACH));
  	say("umount2(Dd, MNT_DETACH)", umount2(W "/Dd", MNT_DETACH));

  	if (!strcmp(mode, "plain")) {
  		beneath(W "/own/mine", W "/D");
  		show(W "/D");
  		say("umount2(D, MNT_DETACH), the old cover", umount2(W "/D", MNT_DETACH));
  		show(W "/D");
  		say("umount2(D, MNT_DETACH), the mount that was put beneath",
  		    umount2(W "/D", MNT_DETACH));
  		show(W "/D");
  		mounts();
  	} else if (!strncmp(mode, "nsfile", 6)) {
  		do_nsfile(!strcmp(mode, "nsfile-control"));
  	} else {
  		do_shrink(!strcmp(mode, "shrink-control"));
  	}
  	return 0;
  }

Fixes: c62a476693 ("move_mount: transfer MNT_LOCKED")
Reported-by: Chris Mason <mason@kernel.org>
Cc: stable@vger.kernel.org # v7.1+
Link: https://patch.msgid.link/20261014-work-namei-locked-mnt_ns_file-v1-1-24035bd5d8e9@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-14 22:08:06 +02:00
Christian Brauner
a291a5cf00
fs: drop the smp_mb()s that mnt_get_count() made redundant
Both mntput_no_expire_slowpath() and do_umount() have an smp_mb()
between taking mount_lock and summing up the reference count. They pair
with the smp_mb() in __legitimize_mnt().

A walker in __legitimize_mnt() increments the count and then re-reads
the seqcount of mount_lock. The unmounter writes the seqcount when it
takes mount_lock and then sums up the count. They must not both miss
each other's store. Otherwise the walker keeps a reference to a mount
that the unmounter finishes off. That's the store buffering pattern and
it needs a full barrier between the store and the load on both sides.

In commit 119e1ef80e ("fix __legitimize_mnt()/mntput() race") the
barrier in the slow path of mntput_no_expire() as added and the one in
do_umount() in commit 65781e19dc ("do_umount(): add missing barrier
before refcount checks in sync case"). Back then mnt_get_count() didn't
have a barrier of its own.

Since commit 7eb84d54fa ("fs: don't let a migrating task hide its
reference from do_umount()") it does. It sums up the puts, does an
smp_mb() and then sums up the gets. In program order that barrier sits
after the seqcount write in lock_mount_hash() and before the loads of
the gets. That's all the walker needs. If the sum misses the walker's
increment the walker sees the seqcount change and drops its reference
under mount_lock where it finds MNT_DOOMED or MNT_SYNC_UMOUNT.

The only thing the outer barrier orders on top of that is the seqcount
write against the loads of the puts and nothing relies on it:

(1) A put from the fast path of mntput_no_expire() never looks at the
    seqcount. If the sum misses such a put that's what the grace period
    is for.

(2) All other puts happen under mount_lock and are ordered by it.

(3) The slow path's own decrement right before the sum is a store on the
    same cpu and is read back in program order.

So the outer barriers don't order anything that the inner one doesn't.
That argument is made in the kernel memory model and every architecture
has to implement its barriers according to it. herd7 against
tools/memory-model agrees. Without the outer smp_mb() the walker against
the slow path and against the busy check in do_umount() is "Never". If
the inner smp_mb() goes as well it's "Sometimes". The litmus tests for
this are in the next patch.

Make the comments in __legitimize_mnt() and mnt_get_count() point at
each other.

Link: https://patch.msgid.link/20261011-work-mount-fixes-5-v1-1-a9b9a8ecf565@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-14 22:08:04 +02:00
Kir Kolyshkin
73b40395c7
mount: add OPEN_TREE_DROP_MNTNS_MOUNTS
A recursive open_tree(OPEN_TREE_CLONE) copies the nsfs mounts of any
mount namespaces pinned below the source.  When the clone is attached
inside a mount namespace younger than a pinned one, move_mount(2) fails
with ELOOP from check_for_nsfs_mounts(), per the rule from commit
8823c079ba ("vfs: Add setns support for the mount namespace") that
prevents mount namespace reference loops.

This breaks container runtimes that use OPEN_TREE_NAMESPACE, such as
crun [1].  The new namespace is younger than everything else, so bind
mounting e.g. the host root fails on any host that pins a mount namespace
(snapd does, under /run/snapd/ns).  By the time it fails, setns() has
already run and userspace cannot recover: the host tree is out of reach,
and the offending mounts cannot be unmounted from the detached copy.

copy_mnt_ns() and create_new_namespace() already leave these mounts
behind; only get_detached_copy() copies them.  Add an open_tree() flag to
drop them from the clone, as suggested by Aleksa [2].  Locked nsfs mounts
are dropped the same way copy_mnt_ns() does it, so nothing new is exposed.

Keep it opt-in: open_tree(OPEN_TREE_CLONE) plus move_mount(2) is how
mount --rbind works through a file descriptor, and in the caller's own
or an older namespace such mounts remain usable.

The flag requires AT_RECURSIVE, since a non-recursive clone has no
submounts to drop, and OPEN_TREE_CLONE or OPEN_TREE_NAMESPACE.  With the
latter it is a no-op, so a runtime can pass it with any recursive
open_tree() call.

Link: https://github.com/containers/crun/issues/2262 [1]
Link: https://lore.kernel.org/all/2026-01-07-oldest-grim-captions-spills-ywC2O3@cyphar.com/ [2]
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Link: https://patch.msgid.link/20261006-nsfs-prune-rfc-v3-1-adb97ea52868@gmail.com
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-12 11:33:19 +02:00
Christian Brauner
3d39922442
namespace: simplify disconnect_mount()
Rename disconnect_mount() and simplify it.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-07 00:45:50 +02:00
Christian Brauner
01cbebb420
namespace: rework connected mounts
UMOUNT_CONNECTED as implemented allows for the creation of reference
count cycles. Here's a simple example

  mkdir /x; mkfifo /ready /go
  unshare -m sh -c 'mount -t tmpfs tmpfs /x
                    truncate -s 8M /x/img; mkfs.ext4 -q /x/img
                    dev=$(losetup -f --show /x/img)
                    mkdir /x/mp; mount $dev /x/mp
                    echo $dev > /ready; read r < /go' &
  read dev < /ready
  rmdir /x
  echo > /go
  wait
  losetup -d $dev
  losetup -a

Take a directory /x on the host, create a new mount namespace, mount a
tmpfs on /x, use a file on that tmpfs as the backing file for a loop
device, mount that loop device on that tmpfs. Now rmdir /x on the host.
This will lazily unmount the mount on top of /x in the container with
UMOUNT_CONNECTED. Once the namespace exits nothing references the mount
anymore. Now the tmpfs is pinned by the backing file of the loop device
and the loop mount is owned by the tmpfs superblock.

Fun fact, such cycles can be formed by at least the following
subsystems and I have added reproducers for all of them:

(1) a loop mount P from an image on a tmpfs next to it, so that P's
    death shows as the loop device giving up its backing file

(2) autofs with a FIFO on P as its pipe, zram with a device node on P as
    its writeback device, both on a minix image since vfat has neither

(3) ecryptfs with its lower directory on P, under a passphrase token
    added to the session keyring

(4) binfmt_misc in a new user namespace with an 'F' interpreter on P

(5) a fuse server that answers FUSE_INIT with passthrough on and
    registers a file on P as a backing file

(6) zloop with its zone files in a directory on P

(7) a mass storage gadget on the dummy UDC with its LUN file on P,
    mounted from the SCSI disk the gadget shows up as

(8) md with a RAID1 of one loop device and its bitmap file on P, which
    skips while SET_BITMAP_FILE has no way to succeed

(9) rmdir of P's mountpoint from the parent, then the child exits, then
    the device must be free and LOOP_CLR_FD must release the file

The underlying mechanism is UMOUNT_CONNECTED (MNT_LOCKED falls into the
same class). With UMOUNT_CONNECTED an unmounted mount stays attached to
its parent. This is used to protect revealing the underlying mount and
is a non-negotiable security mechanism. So now the parent owns that
mount and is put on the parent's final mntput(). That moves it to
mnt_stuck_children and ultimately it's cleaned up by cleanup_mnt().

The fact that ownership of the child mount gets transferred to the
parent turns every reference from a child's superblock back to one of
its ancestors into a cycle.

Don't keep the child attached at all. What the parent needs is that a
lookup at the child's mountpoint keeps finding some mount, not the child
itself and it's not a guarantee we have given really.

When an unmounted mount would have stayed attached to its unmounted
parent disconnect it like every other unmounted mount and leave a
marker behind.

A lookup on the parent that misses the mount hash and hits a marker
finds knullfs. Either a file or a directory. Nothing leads from a marker
to any other mount.

The marker is owned by the parent and dropped by the parent's final
mntput() or by __detach_mounts() when the mountpoint is deleted from
under it. It is allocated together with the mount.

With that every unmounted mount is a root and holds only its own
reference which namespace_unlock() drops. No unmounted mount owns
another one. A superblock that pins an ancestor can't form a cycle.

It has a visible change. Mounts left connected (rmdir etc.) used to stay
traversable through the parent for as long as something held the parent.
Now it is detached with the umount. It lives as long as something
references it but it isn't reachable through the parent anymore and ".."
inside it leads nowhere which is the same as for every other lazily
unmounted mount.

Link: https://gist.github.com/mvo5/63ef46482349f3b1c3957d463a0c9c6f
Link: https://patch.msgid.link/20261002-work-mount-cover-v1-2-232a8f52b43c@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-07 00:45:49 +02:00
Christian Brauner
54867884fe
nullfs: add an empty immutable regular file
Add nullfs_new_file() to allocate an empty immutable regular file on a
nullfs instance as a dentry of its own. It is never hashed under the
root and so can't be found by lookup. Reads return nothing, changes are
refused, file locks, leases and delegations are refused as.

Link: https://patch.msgid.link/20261002-work-mount-cover-v1-1-232a8f52b43c@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-07 00:45:49 +02:00
Christian Brauner
e22e69c2ad
namespace: nothing is mounted on or written through knullfs
Mark the root of knullfs with dont_mount(). Nothing is ever mounted on
the root of a kernel thread and the following patches make that root
reachable from userspace, so say it on the dentry where it doesn't
depend on the mount being in no namespace. Let do_lock_mount() refuse
such a target before it takes the inode lock and namespace_sem. The
flag is sticky so the check needs no lock. The one under the locks
stays for a mountpoint that is being removed.

Make the mount read-only as well. Its one inode is immutable so nothing
could be changed through it anyway, but MNT_READONLY makes that visible
the usual way: EROFS instead of EPERM and ST_RDONLY in statvfs().

Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-15-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:40 +02:00
Christian Brauner
4209cd7f54
namespace: keep the private nullfs instance in knullfs
The private nullfs instance that kernel threads are confined to is only
ever reachable through init_task's root and pwd. The following patches
point mounts at it as well, so keep it in a global.

No functional changes.

Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-14-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:40 +02:00
Christian Brauner
67b69b8a6e
fhandle: decide the subtree check under mount_lock
may_decode_fh() lets a caller who is privileged over the mount
namespace of @root decode handles below @root->dentry as long as the
mount is mounted and no locked child covers something below it. The
three parts are read one after the other without a lock: is_mounted()
and capable_wrt_mount() read ->mnt_ns and has_locked_children() walks
->mnt_mounts under a mount_lock of its own.

Today the gaps are harmless. A lazy umount in between clears ->mnt_ns,
but the locked children stay attached to their unmounted parent, so the
walk still finds them and the decode is refused either way. The
following patches detach every unmounted mount from its parent. Then
an umount between is_mounted() and the walk makes the walk come back
empty and the caller decodes into what a locked child covered.

So take mount_lock once and answer all three questions under it.
is_mounted() is stable there, umount_tree() clears ->mnt_ns on the
write side, and a mounted mount still has its locked children on its
list. ns_capable() under the spinlock is fine, generic_permission()
calls it in RCU walk already. has_locked_children() loses its locking
wrapper, its other callers hold namespace_sem or mount_lock anyway.

Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-13-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:40 +02:00
Christian Brauner
d74d66aa70
namespace: keep the lock on a mount that a propagated copy is moved beneath
attach_recursive_mnt() transfers MNT_LOCKED from the top mount to the
mount that is moved beneath it with MOVE_MOUNT_BENEATH. This allows the
owner of a user namespace to replace its locked /proc or its root. The
mount beneath takes on the job of covering the underlying mountpoint
allowing the top mount to be unmounted.

Consider two mount namespaces:

(H) The host H has a shared mount P with a secret in P/d
(Z) Z is a user namespace made from M. Its copy of P receives
    propagation from the host's P and its copy of M's cover on P/d is
    locked Z's owner must not get to see P/d.

Now (H) mounts X on P/d. This propagates into (Z). The copy of X lands
beneath (Z)'s locked cover. The locked property is transfered from (Z)'s
cover to the copy of X propagated beneath it. The cover is now unlocked.

Now (H) unmounts X again. The copy of X in (Z) gets unmounted and the
covering mount is left unlocked on top of P/d. (Z) can now unmount it:

    Z: umount2(P/d)             = EINVAL            /* the cover is locked */
    H: mount X on P/d, umount X                     /* both propagate into Z /*
    Z: umount2(P/d)             = 0                 /* the cover is now unlocked */
    Z: read P/d/secret          = "covered-by-root" /* secret revealed */

So only transfer the locked property to the mount beneath for mounts the
caller has placed. A propagated copy that lands beneath a locked mount
is locked as well so that the mount at the bottom of the stack carries a
lock the way every check expects. The mount on top of it remains locked
to ensure that it keeps covering even if the propagated mount is
unmounted again.

Fixes: c62a476693 ("move_mount: transfer MNT_LOCKED")
Cc: stable@vger.kernel.org # v7.1+
Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-9-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:39 +02:00
Christian Brauner
f78ddcc872
namespace: never expire a locked mount
Locked mounts are special. They protect the underlying files and
directories from being revealed. do_umount() refuses to unmount locked
mounts but shrink_submounts() doesn't.

A shrinkable mount can become locked once the owner of a user namespace
puts it beneath a locked mount with MOVE_MOUNT_BENEATH. The locked
property now moves to the mount at the bottom making it possible to
unmount the top mount.

So umount() of an unlocked ancestor now expires the bottom mount first
and the covered directory is revealed:

  move_mount(c -> x/hidden, MOVE_MOUNT_BENEATH) = 0
  the cover after the lock moved:  umount2(x/hidden) = 0
  the holder of the lock:          umount2(x/hidden) = EINVAL
  the root of the copy, busy:      umount2(x) = EBUSY
  reads x/hidden/secret: "covered-by-root"

So leave a locked mount alone as it dies together with its parent. A
plain umount() of an unlocked mount with a locked one below it is EBUSY
from now on. It's the same for any other locked child. A lazy umount
still takes the whole tree.

mark_mounts_for_expiry() never sees a locked mount. lock_mnt_tree()
leaves a mount on an expiry list alone and nothing else puts a locked
one on such a list. Add an assert for this.

Fixes: 5ff9d8a65c ("vfs: Lock in place mounts from more privileged users")
Fixes: c62a476693 ("move_mount: transfer MNT_LOCKED")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-8-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:39 +02:00
Christian Brauner
33aadc7c72
namespace: handle mount locking for automounts correctly
If mounts are propagated across user namespaces, attach_recursive_mnt()
locks every copy of the source mount to protect overmounts from
vanishing and revealing the underlying files or directories.

The user namespace is taken from the caller's mount namespaces since
this is where the mounts end up. Except, that's not always true.
Automounts may legitimately get popped in by tasks located in a
different mount and user namespace during path lookup.

Then check doesn't make sense at that point. The copy in the namespace
of the parent - which may be the host's - is now locked and the host
cannot change the flags of its own mounts anymore.

Here's the reproducer:

- host has debugfs mounted nosuid,nodev,noexec and shared
- tracefs automount below it is not yet active
- hand a child process a directory descriptor on that mount
- child process enters auser and mount namespace
- child process' namespace now holds a locked copy of the host's debugfs
  mount that receives propagation from it
- child process stats "tracing/." through the descriptor
- lookup runs on the host's mount so the automount lands below the host's mount
- propagation puts a copy below the child's copy
- both try to clear the flags on the mount they got, with a bind remount:

    host, on its own automount:            MS_REMOUNT|MS_BIND = EPERM
    child, on the copy in its namespace:   MS_REMOUNT|MS_BIND = 0

So the lock landed on the host's mount instead of the child's copy.
Congrats. So we need to compare with the owner of the namespace the
mount actually gets mounted on. For all regular cases that is the
caller's mount namespace and so nothing changes.

Detached trees in anonymous mount namespaces by be handed over via
SCM_RIGHTS or inherited in other ways on purpose so the attaching task's
mount namespace is authoritative, not the creator of the detached tree.

Fixes: 132c94e31b ("vfs: Carefully propogate mounts across user namespaces")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-6-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:39 +02:00
Christian Brauner
ba985ce084
namespace: refuse an automount below a mount that is in no namespace
It's possible to add autmounts even when the parent mount isn't in the
mount namespace of the caller. The only requirement we have is that the
parent mount namespace must not be NULL, i.e., unmounted.

Problem is that clone_private_mount() has MNT_NS_INTERNAL which makes
that trivially true. So that passes the test and attach_recursive_mnt()
accepts that as a mount point and funny enough, count_mounts()
dereferences MNT_NS_INTERNAL. The problem is it is an error pointer...

So we can reach this in userspace via fanotify. A filesystem mark on the
lower filesystem of an overlay reports paths on the layer clone and
reading the event hands out a descriptor on it. For example with debugfs
as the lower layer it goes kaboom:

  openat(evfd, "tracing", O_DIRECTORY)

  Oops: general protection fault
  KASAN: null-ptr-deref in range [0x1d0-0x1d7]
  RIP: 0010:count_mounts+0x35/0x200
   attach_recursive_mnt
   finish_automount

And since that sleeping beauty happens under namespace_sem held for
writing every mount operation on the system blocks from then on.
Congrats.

Use is_mounted() instead which rejects unmounted and internal mounts
alike. The open fails with EINVAL just as it did before
clone_private_mount() used MNT_NS_INTERNAL.

Fixes: df820f8de4 ("ovl: make private mounts longterm")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-5-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:39 +02:00
Christian Brauner
528b383dc3
namespace: unhash a dentry before detaching the mounts on it
unlink(), rmdir() and rename() remove the entry from the filesystem,
call detach_mounts() on the entry with the inode locked and then call
d_delete() once the inode is unlocked.

The thing is that between detach_mounts() and d_delete() the dentry is
still hashed and positive. But after detach_mounts() nothing covers the
dentry anymore. A lookup that finds this dentry in the dcache can
uncover what the mounts hid.

That's a problem when unlinking files or directories that are
mountpoints in other mount namespaces. Everybody who had the underlying
entry covered can race the detach_mount() call until d_delete() has run.

The race window isn't all that small. It encompasses namespace_unlock()
with a full synchronize_rcu_expedited() grace period and the
inode_unlock() of the entry.

In my experiments three walkers that kept trying read an overmounted
file in 11938 of the 16140 unlinks that removed its mountpoint from a
bind mount of the filesystem within a minute.

Fun fact, d_invalidate() has the same ordering problem but gets it
right. It unhashes the dentry first and detaches the mounts afterwards.
Let's do the same in __detach_mounts():

- A lookup that hasn't found the dentry yet misses it in the dcache and
  waits for the directory lock that the caller holds until the name is
  gone for good.

- A lockless lookup that found it already fails the mount_lock check
  while the dentry still counts as a mountpoint and the d_seq check once
  it doesn't, and retries.

This fixes the lockless path. We still need to fix the reference count
lookup in a follow-up patch.

So d_drop() the dentry. The dentry stays positive and held but can't be
found anymore. Then proceed with the detach and unlink.

Reproducer:

The reads were counted with the program below, three walkers for 60 s
in a VM with 4 CPUs. The race window is stretched with the debug patch
pasted here. fs.detach_race_walk_us sleeps in lookup_fast() once it
found a mountpoint dentry and before its mounts are crossed.
fs.detach_race_unlink_us sleeps in vfs_unlink() between detach_mounts()
and d_delete().

  // SPDX-License-Identifier: GPL-2.0
  /*
   * unlink_covered: unlink a file that is a mountpoint in a detached copy of
   * its mount, against lookups that open it through that copy.
   *
   * A is a tmpfs with the file f1 (content SECRET_F) and the plain file
   * MARK_A. A' is an open_tree(OPEN_TREE_CLONE) copy of A with MARK_A bound
   * on A'/f1, held through an O_PATH fd on its root once the tree fd is
   * closed. Walkers open f1 through that fd while the driver unlinks A/f1,
   * where nothing is mounted on it. A walker may read MARK_A or get ENOENT.
   * A read of SECRET_F is a hit: the name was found after its mount was
   * gone. The lockless walks use openat2(RESOLVE_CACHED).
   *
   * usage: unlink_covered [-t seconds] [-w walkers]
   */
  #ifndef _GNU_SOURCE
  #define _GNU_SOURCE
  #endif
  #include <errno.h>
  #include <fcntl.h>
  #include <pthread.h>
  #include <stdatomic.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/mount.h>
  #include <sys/stat.h>
  #include <sys/syscall.h>
  #include <linux/openat2.h>

  #ifndef __NR_open_tree
  #define __NR_open_tree 428
  #endif
  #ifndef __NR_move_mount
  #define __NR_move_mount 429
  #endif
  #ifndef __NR_openat2
  #define __NR_openat2 437
  #endif
  #ifndef OPEN_TREE_CLONE
  #define OPEN_TREE_CLONE 1
  #endif
  #ifndef OPEN_TREE_CLOEXEC
  #define OPEN_TREE_CLOEXEC O_CLOEXEC
  #endif
  #ifndef MOVE_MOUNT_F_EMPTY_PATH
  #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
  #endif

  #define WORK "/tmp/uc"
  #define ADIR WORK "/A"

  static int duration = 60, nwalkers = 3;
  static atomic_int stop, writer_waiting;
  static pthread_rwlock_t cur_lock = PTHREAD_RWLOCK_INITIALIZER;
  static int cur_fd = -1;		/* the root of A', -1 while there is none */
  static atomic_long n_unlink, n_walk, n_mark, n_enoent, n_other, n_secret,
  		   n_secret_cached;

  static void die(const char *what)
  {
  	fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno));
  	exit(2);
  }

  static void put_file(int dfd, const char *name, const char *content)
  {
  	int fd = openat(dfd, name, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
  			0644);

  	if (fd < 0 || write(fd, content, strlen(content)) < 0)
  		die(name);
  	close(fd);
  }

  /* "plain/../" @n times, then f1: a longer walk that checks nothing on its way */
  static char *longpath(int n)
  {
  	char *p = malloc(n * 9 + 3), *q = p;

  	for (int i = 0; i < n; i++, q += 9)
  		memcpy(q, "plain/../", 9);
  	strcpy(q, "f1");
  	return p;
  }

  static void try_read(int dfd, const char *path, int cached)
  {
  	struct open_how how = { .flags = O_RDONLY | O_CLOEXEC,
  				.resolve = RESOLVE_CACHED };
  	char buf[32] = "";
  	long n;
  	int fd;

  	if (cached)
  		fd = syscall(__NR_openat2, dfd, path, &how, sizeof(how));
  	else
  		fd = openat(dfd, path, O_RDONLY | O_CLOEXEC);
  	atomic_fetch_add(&n_walk, 1);
  	if (fd < 0) {
  		if (errno == ENOENT)
  			atomic_fetch_add(&n_enoent, 1);
  		else if (!cached || errno != EAGAIN)
  			atomic_fetch_add(&n_other, 1);
  		return;
  	}
  	n = read(fd, buf, sizeof(buf) - 1);
  	close(fd);
  	if (n >= 6 && !strncmp(buf, "SECRET", 6)) {
  		if (cached)
  			atomic_fetch_add(&n_secret_cached, 1);
  		if (!atomic_fetch_add(&n_secret, 1))
  			printf("HIT: read \"%s\" through %s (%s walk)\n", buf,
  			       path, cached ? "lockless" : "any");
  	} else {
  		atomic_fetch_add(&n_mark, 1);
  	}
  }

  static void *walker(void *arg)
  {
  	unsigned int r = (long)arg * 2654435761u;

  	while (!atomic_load(&stop)) {
  		char *p_long, *p_short;
  		int dfd;

  		while (atomic_load(&writer_waiting) && !atomic_load(&stop))
  			usleep(20);
  		pthread_rwlock_rdlock(&cur_lock);
  		dfd = cur_fd;
  		if (dfd < 0) {
  			pthread_rwlock_unlock(&cur_lock);
  			usleep(100);
  			continue;
  		}
  		r = r * 1103515245u + 12345u;
  		p_long = longpath(1 + (r >> 8) % 400);
  		p_short = longpath(0);
  		try_read(dfd, p_long, 0);
  		try_read(dfd, p_short, 0);
  		try_read(dfd, p_long, 1);
  		pthread_rwlock_unlock(&cur_lock);
  		free(p_long);
  		free(p_short);
  	}
  	return NULL;
  }

  /* hand the walkers a new A' (or none), close the old one */
  static void publish(int fd)
  {
  	int old;

  	atomic_fetch_add(&writer_waiting, 1);
  	pthread_rwlock_wrlock(&cur_lock);
  	old = cur_fd;
  	cur_fd = fd;
  	pthread_rwlock_unlock(&cur_lock);
  	atomic_fetch_sub(&writer_waiting, 1);
  	if (old >= 0)
  		close(old);
  }

  static void *driver(void *arg __attribute__((unused)))
  {
  	int a;

  	if (mkdir(ADIR, 0755) && errno != EEXIST)
  		die("mkdir A");
  	if (mount("A", ADIR, "tmpfs", 0, "size=4M"))
  		die("mount A");
  	a = open(ADIR, O_PATH | O_DIRECTORY | O_CLOEXEC);
  	if (a < 0 || mkdirat(a, "plain", 0755))
  		die("A/plain");
  	put_file(a, "MARK_A", "MARK_A");
  	while (!atomic_load(&stop)) {
  		int t, m, fd_a;

  		put_file(a, "f1", "SECRET_F");
  		t = syscall(__NR_open_tree, a, "",
  			    OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_EMPTY_PATH);
  		if (t < 0)
  			die("open_tree A");
  		m = syscall(__NR_open_tree, a, "MARK_A",
  			    OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
  		if (m < 0)
  			die("open_tree MARK_A");
  		if (syscall(__NR_move_mount, m, "", t, "f1",
  			    MOVE_MOUNT_F_EMPTY_PATH))
  			die("move_mount");
  		close(m);
  		fd_a = openat(t, ".", O_PATH | O_DIRECTORY | O_CLOEXEC);
  		if (fd_a < 0)
  			die("open A'");
  		publish(fd_a);
  		usleep(200 + rand() % 1000);
  		close(t);			/* A' is unmounted, fd_a holds it */
  		usleep(200 + rand() % 1000);
  		if (unlinkat(a, "f1", 0))	/* through A, a plain file there */
  			die("unlink f1");
  		atomic_fetch_add(&n_unlink, 1);
  		usleep(rand() % 300);
  		publish(-1);
  	}
  	close(a);
  	umount2(ADIR, MNT_DETACH);
  	return NULL;
  }

  int main(int argc, char **argv)
  {
  	pthread_t d, *w;
  	int c, i;

  	setvbuf(stdout, NULL, _IOLBF, 0);
  	while ((c = getopt(argc, argv, "t:w:")) != -1) {
  		switch (c) {
  		case 't':
  			duration = atoi(optarg);
  			break;
  		case 'w':
  			nwalkers = atoi(optarg);
  			break;
  		default:
  			fprintf(stderr, "usage: unlink_covered [-t seconds] [-w walkers]\n");
  			return 2;
  		}
  	}
  	if (unshare(CLONE_NEWNS) ||
  	    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
  		die("unshare");
  	if (mkdir(WORK, 0755) && errno != EEXIST)
  		die("mkdir");
  	w = calloc(nwalkers, sizeof(*w));
  	for (i = 0; i < nwalkers; i++)
  		if (pthread_create(&w[i], NULL, walker, (void *)(long)i))
  			die("pthread_create");
  	if (pthread_create(&d, NULL, driver, NULL))
  		die("pthread_create");
  	sleep(duration);
  	atomic_store(&stop, 1);
  	pthread_join(d, NULL);
  	for (i = 0; i < nwalkers; i++)
  		pthread_join(w[i], NULL);
  	printf("unlink_covered: %d s, %d walkers: unlinks %ld walks %ld mark %ld enoent %ld other %ld SECRET %ld (lockless %ld)\n",
  	       duration, nwalkers, atomic_load(&n_unlink), atomic_load(&n_walk),
  	       atomic_load(&n_mark), atomic_load(&n_enoent),
  	       atomic_load(&n_other), atomic_load(&n_secret),
  	       atomic_load(&n_secret_cached));
  	return atomic_load(&n_secret) ? 1 : 0;
  }

  diff --git a/fs/namei.c b/fs/namei.c
  --- a/fs/namei.c
  +++ b/fs/namei.c
  @@ -35,6 +35,7 @@
   #include <linux/fcntl.h>
   #include <linux/device_cgroup.h>
   #include <linux/fs_struct.h>
  +#include <linux/delay.h>
   #include <linux/posix_acl.h>
   #include <linux/hash.h>
   #include <linux/bitops.h>
  @@ -1205,9 +1206,26 @@ static int sysctl_protected_symlinks __read_mostly;
   static int sysctl_protected_hardlinks __read_mostly;
   static int sysctl_protected_fifos __read_mostly;
   static int sysctl_protected_regular __read_mostly;
  +/* debug: widen the two windows of the detach_mounts() race */
  +static int sysctl_detach_race_walk_us __read_mostly;
  +static int sysctl_detach_race_unlink_us __read_mostly;

   #ifdef CONFIG_SYSCTL
   static const struct ctl_table namei_sysctls[] = {
  +	{
  +		.procname	= "detach_race_walk_us",
  +		.data		= &sysctl_detach_race_walk_us,
  +		.maxlen		= sizeof(int),
  +		.mode		= 0644,
  +		.proc_handler	= proc_dointvec,
  +	},
  +	{
  +		.procname	= "detach_race_unlink_us",
  +		.data		= &sysctl_detach_race_unlink_us,
  +		.maxlen		= sizeof(int),
  +		.mode		= 0644,
  +		.proc_handler	= proc_dointvec,
  +	},
   	{
   		.procname	= "protected_symlinks",
   		.data		= &sysctl_protected_symlinks,
  @@ -1878,6 +1896,10 @@ static struct dentry *lookup_fast(struct nameidata *nd)
   		dentry = __d_lookup(parent, &nd->last);
   		if (unlikely(!dentry))
   			return NULL;
  +		/* debug: between finding a mountpoint and crossing its mounts */
  +		if (unlikely(sysctl_detach_race_walk_us) && d_mountpoint(dentry))
  +			usleep_range(sysctl_detach_race_walk_us,
  +				     sysctl_detach_race_walk_us + 10);
   		status = d_revalidate(nd->inode, &nd->last, dentry, nd->flags);
   	}
   	if (unlikely(status <= 0)) {
  @@ -5693,6 +5715,10 @@ int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir,
   			if (!error) {
   				dont_mount(dentry);
   				detach_mounts(dentry);
  +				/* debug: the mounts are gone, d_delete() is still to come */
  +				if (unlikely(sysctl_detach_race_unlink_us))
  +					usleep_range(sysctl_detach_race_unlink_us,
  +						     sysctl_detach_race_unlink_us + 10);
   			}
   		}
   	}

Fixes: 8ed936b567 ("vfs: Lazily remove mounts on unlinked files and directories.")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20261002-work-mount-fixes-4-v1-1-dd44b89d44ce@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-04 12:31:38 +02:00
Christian Brauner
b138e16fb1
namespace: don't let a pseudo dentry become the root of a mount
d_alloc_pseudo() hands out dentries for pipes, sockets and other files
that are never anyone's child or parent. They carry DCACHE_NORCU and
dentry_free() frees them right away because no lockless path walk can
ever reach them. That holds as long as such a dentry isn't the root of
a mount. But bind mounting /proc/self/fd/<fd> of such a file does
exactly that.

Most callers of alloc_file_pseudo() put their files on kernel-internal
mounts and may_copy_tree() refuses those. bpf_token_create() doesn't. It
places the token file on the bpffs mount the caller handed it and that
mount is in the caller's mount namespace so the clone goes through:

  mount --bind /proc/self/fd/<token> <file>
  open_tree(tokfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE) + move_mount()

__follow_mount_rcu() then loads ->mnt_root of that mount, reads d_seq
and d_flags of the dentry and only then checks mount_lock. The dentry
can be gone by then. The final mntput() of an unmounted parent unhooks
a child that stayed attached to it under mount_lock alone. When the
child's holder does the final put right after that the root is dput()
and freed immediately while a walker that found the mount hashed still
looks at it.

Refuse to clone a mount with a DCACHE_NORCU dentry as its root. Nothing
sensible can be done with a bind mount of a bpf token anyway.

Fixes: 35f96de041 ("bpf: Introduce BPF token object")
Cc: stable@vger.kernel.org # v6.9+
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-17-be34c83956ae@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:20:08 +02:00
Christian Brauner
1a116222ac
namespace: remove the fsnotify marks of a mount namespace in process context
mnt_ns_release() drops the last passive reference of a mount namespace
and removes its fanotify marks via fsnotify_mntns_delete(). That takes
the mutex of every group with a mark on the namespace and the spinlock
of the connector. Fine from process context. But mnt_ns_tree_remove()
hands the reference the namespace was allocated with to call_rcu() and
so the marks are removed from the RCU softirq:

  BUG: sleeping function called from invalid context at kernel/locking/mutex.c:623
  in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 0, name: swapper/3
   __mutex_lock+0x113/0x24b0
   fsnotify_destroy_marks+0x11b/0x3d0
   mnt_ns_release_rcu+0x57/0xa0
   rcu_core+0x6b6/0x1e40

and lockdep complains about the connector lock being taken from softirq
context. A fanotify group with a FAN_MARK_MNTNS mark on the mount
namespace of another task is all that's needed. Root in a user
namespace can do that for a mount namespace it owns. The task exits and
the namespace is freed with the mark still on it.

Remove the marks in free_mnt_ns() before the namespace is handed to RCU.
That runs in process context once the last active reference is gone. A
mark is added through a file descriptor to the namespace which holds an
active reference so no mark can show up after that.

Fixes: bf630c4016 ("vfs: add notifications for mount attach and detach")
Cc: stable@vger.kernel.org # v6.15+
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-13-be34c83956ae@kernel.org
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:20:07 +02:00
Christian Brauner
6b7c001eb5
namespace: check the mounts before reading their parents in pivot_root()
path_pivot_root() reads the parents of new_root and of the caller's root
and checks whether they are shared before it checks that either mount is
in the caller's mount namespace. Only namespace_sem is held. That's fine
for a mount that is in the caller's mount namespace.

But new_root can be a file descriptor to a mount that has been unmounted
and that only the file descriptor keeps alive. If that mount stayed
attached to its parent when it was unmounted nothing pins the parent for
it. The final mntput() of the parent unhooks the children under
mount_lock alone and frees the parent afterwards:

  pivot_root()                     close(fd), last ref on the parent
  ------------                     ---------------------------------
  ex_parent = new_mnt->mnt_parent
                                   mntput_no_expire_slowpath()
                                     __umount_mnt(new_mnt)
                                   cleanup_mnt()
                                     call_rcu()
  IS_MNT_SHARED(ex_parent)

  BUG: KASAN: slab-use-after-free in path_pivot_root+0xf1a/0x1840
  Read of size 4 at addr ffff8881047382f8 by task pivot_widen/157
   path_pivot_root+0xf1a/0x1840
   __x64_sys_pivot_root+0x165/0x190

The same goes for the caller's root via chroot(). Both outcomes of the
check end in EINVAL so nothing but the read itself goes wrong. It's the
same thing commit bb46094057 ("statmount: read the parent of an
unmounted mount under mount_lock") fixed for statmount().

Check that both mounts are in the caller's namespace before their
parents are read. Every path returns EINVAL either way.

Fixes: e0c9c0afd2 ("mnt: Update detach_mounts to leave mounts connected")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-10-be34c83956ae@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:20:06 +02:00
Christian Brauner
8714b44ada
namespace: look at the topmost mount for a mount namespace file
attach_recursive_mnt() preallocates a mountpoint for the root of the
topmost mount of the source so that a mount that already sits at the
destination can be put on top of the source or on top of one of its
propagated copies.

The copies are made without CL_COPY_MNT_NS_FILE so their chain of
overmounts is shorter than the source's. It ends below the first bind
mount of a mount namespace file. So while the loop walks up the chain of
the source it remembers the mountpoint of that mount in "shorter" and
the existing mount is put there for the copies.

But the loop ends at the topmost mount without ever looking at it. If
the topmost mount is the only mount namespace file in the chain
"shorter" stays NULL and mnt_change_mountpoint() attaches the existing
mount of the copy below the root of the mount namespace file. That's a
dentry of nsfs. No path walk in the copy's mount namespace ever gets
there. The mount is gone until its parent goes and the copy that took
its place can't be unmounted synchronously because it has a child:

  S      bind mount of a file
  N      bind mount of a mount namespace file on top of S
  A      shared mount, B a slave of A, Q mounted on B/file

  move_mount(S, A/file)
  cat B/file          -> the content of S instead of Q
  umount B/file       -> EBUSY

Look at every mount of the chain including the topmost one.

Fixes: 96f5d2e051 ("attach_recursive_mnt(): unify the mnt_change_mountpoint() logics")
Cc: stable@vger.kernel.org # v6.17+
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-8-be34c83956ae@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:20:06 +02:00
Christian Brauner
0febe278ec
namespace: keep covered mounts covered in OPEN_TREE_NAMESPACE
open_tree(OPEN_TREE_NAMESPACE) creates a new mount namespace from a
copy of the tree at the given path. The caller needs to be privileged
over its current user namespace because the new mount namespace will be
owned by it. It doesn't need to be privileged over the mount namespace
the tree is copied from. An unprivileged user just needs to create a
user namespace first.

The copy follows the rules of a detached bind mount. Without
AT_RECURSIVE only the mount itself is cloned and its children are left
out. With AT_RECURSIVE unbindable mounts are skipped. Both is fine when
the caller has privileges over the source mount namespace because it
could unmount those mounts anyway. Not so for an unprivileged user in
that mount namespace. For them a mount namespace copy is what unshare()
does. copy_mnt_ns() copies everything including unbindable mounts and
lock_mnt_tree() makes sure nothing can be unmounted in the copy. Nothing
that was covered gets revealed.

create_new_namespace() only does the locking and so the covered content
is right there in the new mount namespace:

  # mount -t tmpfs none /tmp/otn; mkdir /tmp/otn/covered
  # echo hidden > /tmp/otn/covered/under.txt
  # mount -t tmpfs none /tmp/otn/covered

  uid 1000, after unshare(CLONE_NEWUSER):
  fd = open_tree(AT_FDCWD, "/tmp/otn", OPEN_TREE_NAMESPACE);
  setns(fd, CLONE_NEWNS);
  open("/covered/under.txt", O_RDONLY)  -> "hidden"

Covering paths with mounts is how container runtimes mask parts of
/proc and /sys and how admins hide things.

When the caller's user namespace doesn't own the source mount namespace
copy the way unshare() does and refuse a non-recursive copy of a mount
that has anything mounted below the requested directory and copy
unbindable mounts in a recursive copy. A caller that is privileged over
the source mount namespace sees no change.

Fixes: 9b8a0ba682 ("mount: add OPEN_TREE_NAMESPACE")
Cc: stable@vger.kernel.org # v7.0+
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-6-be34c83956ae@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:20:05 +02:00
Christian Brauner
c640a01a78
namespace: check a recursive bind mount for mount namespace loops
do_loopback() refuses to bind mount the file of a mount namespace that
is as old as the caller's or older because a mount namespace that holds
a mount of its own file, or of an ancestor's, would cause a cycle. But
it only checks the dentry the bind mount starts from. With MS_REC
everything below it is copied and do_loopback() passes
CL_COPY_MNT_NS_FILE so mount namespace files below the source are
copied.

That's fine for a source in the caller's own mount namespace. Every
mount namespace file in there passed the same check when it was
bind-mounted. It isn't fine for a source in another mount namespace.
may_copy_tree() accepts a bind mount of any nsfs or pidfs file no matter
what mount namespace it lives in so that /proc/<pid>/ns/<ns> and pidfds
can be bind mounted. If we create a bind-mount stack of mount namespaces
file descriptors the kernel will copy them irrespective of their
ancestoral relationship to the mount namespace in question:

  151 146 0:7 net:[4026531833] /tmp/nrc/y rw - nsfs nsfs rw
  152 151 0:7 mnt:[4026532293] /tmp/nrc/y rw - nsfs nsfs rw

Mount 152 is a mount of the mount namespace file of mount namespace
4026532293 inside mount namespace 4026532293. That namespace and every
mount in it are leaked... All it takes is a process in an older mount
namespace that stacks the file and repeating it leaks without limit:

  after control: Shmem:               380 kB
  child: mount(/proc/self/fd/6, MS_BIND|MS_REC): ok
  after cycle: Shmem:             65916 kB

do_move_mount() runs check_for_nsfs_mounts() over a detached tree for
exactly that reason. Do the same for the copy before it is grafted.

Fixes: e149ed2b80 ("take the targets of /proc/*/ns/* symlinks to separate fs")
Fixes: ef4144ac2d ("pidfs: allow bind-mounts")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-4-be34c83956ae@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:20:05 +02:00
Christian Brauner
32bf924166
namespace: check a submount for references right before unmounting it
shrink_submounts() and mark_mounts_for_expiry() first collect all the
mounts they are allowed to unmount and then unmount them.

Whether a mount is busy is decided by propagate_mount_busy() on the
tree. But unmounting the first mount changes the tree that the second
one propagates into. propagate_umount()
moves a surviving overmount off a copy it unmounts and mounts it where
the copy was mounted. If that's where the propagated copy of the second
victim is looked up the overmount becomes a candidate of the second
umount_tree(). It's childless and so trim_one() commits it without
looking at its reference count.

So a synchronous umount pulls out a busy mount that is in use somewhere
else and marks it MNT_SYNC_UMOUNT while it has users:

  umount2(/tmp/plshrink/p1, 0) = 0 errno 0
  cwd is (unreachable)

The same two umounts requested one after the other fail with EBUSY.
This needs a mount with MNT_SHRINKABLE, i.e., automounted submounts of
NFS, AFS and CIFS or the tracefs mount below debugfs. Bind mounts
inherit the flag.

Check each mount right before it is unmounted under the same hold of
namespace_sem and mount_lock as the umount itself. Make sure that the
algorithm stays linear.

Fixes: 1064f874ab ("mnt: Tuck mounts under others instead of creating shadow/side mounts.")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-2-be34c83956ae@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:18:27 +02:00
Christian Brauner
bd2feb7796
namespace: queue a mount only once for mount notifications
mnt_notify_add() puts a mount on notify_list. It doesn't check whether
the mount is on the list already. That's fine as long as every mount is
queued at most once per namespace_sem hold. It isn't.

propagate_umount() moves a surviving overmount off a stack of mounts
that are going away and queues it as moved. shrink_submounts() and
mark_mounts_for_expiry() call umount_tree() for several mounts under a
single namespace_sem hold. So the second umount_tree() can take down
exactly the mount the first one reparented, reparent it once more and so
end up queueing it a second time.

The second list_add_tail() cuts the mounts queued in between out of the
list while the head still points at the last of them. notify_mnt_list()
then only visits that one mount and the others are freed after the grace
period with notify_list still pointing at them. Every later mount
operation on the host walks freed memory:

  BUG: KASAN: slab-use-after-free in __list_add_valid_or_report
  Read of size 8 at addr ffff8881003d57e0 by task notify_dq/162
   __list_add_valid_or_report+0x15c/0x1a0
   mnt_add_to_ns+0x322/0x890
   attach_recursive_mnt.isra.0+0xf2a/0x1a00
   path_mount+0x139c/0x1d40

This needs a mount with MNT_SHRINKABLE to bind from. That's what
finish_automount() creates (submounts of NFS, AFS and CIFS and the
tracefs mount below debugfs). Bind mounts inherit the flag. A mount
namespace with a FAN_MARK_MNTNS mark does the rest and root in a user
namespace can have both.

Initialize to_notify in alloc_vfsmnt() and leave a mount alone that is
queued already. mnt_notify() looks at the state the mount has when the
list is drained so a single entry per mount is enough. The move event
for a mount that is unmounted under the same hold is lost. That's fine.
The detach is what matters.

Fixes: bf630c4016 ("vfs: add notifications for mount attach and detach")
Cc: stable@vger.kernel.org # v6.15+
Link: https://patch.msgid.link/20260930-work-mount-fixes-3-v1-1-be34c83956ae@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-01 14:18:24 +02:00
Christian Brauner
7253a6ff64
namespace: drop the file's reference first in dissolve_on_fput()
A detached mount tree is held alive by a file. dissolve_on_fput()
unmounts the detached tree attached to that file.

Currently dissolve_on_fput() leaves the file's reference to the root of
the tree to the caller. So either __fput(), open_detached_copy(), or
fsmount() drop the mount.

That's backwards. The reference keeps the tree around and is dropped
when the tree is already gone. Instead, let dissolve_on_fput() the mount
and drop it after umount_tree() with namespace_sem still held. After
umount_tree() the mntput() is never the last reference and so the root
gets unmounted before its children.

Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-8-f357abf3d17b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-26 15:47:41 +02:00
Christian Brauner
08f4288a37
namespace: check the target before walking it in do_mount_setattr()
When mount_setattr() changes the propagation type to MS_SHARED
do_mount_setattr() takes namespace_sem and calls invent_group_ids()
which walks the whole target tree with next_mnt() and allocates a peer
group id for every mount that doesn't have one.

Whether the caller may touch the target at all is only checked
afterwards under mount_lock. If it may not, cleanup_group_ids() walks
the tree a second time on the way out to free the ids again.

A detached tree that a file descriptor keeps alive, a mount in another
mount namespace or a mount below the root of an anonymous mount
namespace does a bunch of allocation work that is completely useless
because it will fail and revert right after.

Do the right thing and check the target right after taking namespace_sem
instead. A mount leaves its namespace only under namespace_sem so the
mount stays stable. The check under mount_lock stays for the case where
no propagation change is requested and namespace_sem isn't taken.
Callers get the same EINVAL as before.

Fixes: 2a1867219c ("fs: add mount_setattr()")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-5-f357abf3d17b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-26 15:47:41 +02:00
Christian Brauner
8bacbf6be9
namespace: don't copy an unmounted mount tree
may_copy_tree() accepts a mount whose root is an nsfs or pidfs file
before it checks whether that mount is mounted. This exception exists
for nsfs and pidfs mounts. They are part of no mount namespace so
neither check_mnt() nor is_mounted() would let them through and
bind-mounting a nsfsfd or a pidfd wouldn't work.

Problem is that exception also admits a bind mount of such a file when
it was lazily unmounted and is only kept alive by a file descriptor:

  mount --bind /proc/self/ns/net /tmp/x
  exec 3< /tmp/x
  umount -l /tmp/x
  mount --rbind /proc/self/fd/3 /tmp/y

With MS_REC or AT_RECURSIVE copy_tree() doesn't stop at the unmounted
mount. It copies every mount that stayed attached to it as well. So if
you have a bunch of overmounts over the nsfsfd or the pidfd the whole
unmounted stack is copied. That's a bug.

So refuse to walk an unmounted tree. Copying its root alone stays
possible. So a bind mount of a namespace that is gone can still be
recreated from a file descriptor on the old one. The file descriptor
pins that mount and its root and __has_locked_children() only reads the
direct children which don't leave the parent's list without
namespace_sem. The internal mounts carry MNT_NS_INTERNAL instead of NULL
and are still walked. Copying an unmounted tree recursively fails with
EINVAL.

Fixes: e149ed2b80 ("take the targets of /proc/*/ns/* symlinks to separate fs")
Fixes: ef4144ac2d ("pidfs: allow bind-mounts")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-4-f357abf3d17b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-26 15:47:41 +02:00
Christian Brauner
e2c6d32621
namespace: don't inherit MNT_UMOUNT in clone_mnt()
MNT_UMOUNT gets raised on a mount that umount_tree() or umount_one() has
taken out of its namespace. It's never cleared. __detach_mounts()
unhooks a mount that carries it instead of unmounting it.

disconnect_mount() lets an unmounted child stay attached only if its
parent carries it. propagate_umount() takes it as the sign that a mount
is going to be unmounted when it decides what else can be unmounted,
where a chain of locked mounts ends and where a for reparenting an
overmount.

clone_mnt() copies the flags of the source and only masks
MNT_INTERNAL_FLAGS which doesn't include MNT_UMOUNT. A mount that was
lazily unmounted but is kept alive by a file descriptor can still be
copied when its root is an nsfs or pidfs file because may_copy_tree()
accepts those before checking whether the source is mounted at all:

  mount --bind /proc/self/ns/net /tmp/x
  exec 3< /tmp/x
  umount -l /tmp/x
  mount --bind /proc/self/fd/3 /tmp/y

The mount on /tmp/y is attached to the caller's namespace with
MNT_UMOUNT already set and every copy of it inherits the flag again.

That causes a number of bugs:

- Unlink its mountpoint from a mount namespace where it isn't one.
  __detach_mounts() takes it for a mount that was unmounted earlier,
  unhooks it from its parent and drops the reference the namespace
  holds on it. It stays in the namespace with one reference too few
  and as its own parent, so it can't be unmounted through the file
  descriptor anymore and the namespace never finds it again. When the
  namespace goes away it's leaked and points to a freed struct
  mnt_namespace that may_change_propagation() and anon_ns_root() read
  on the next propagation change or mount_setattr() through the file
  descriptor.

- Make it shared and unmount a peer. trace_transfers() follows the
  flag from the peer into it and strips it from its peer group.

- Let a propagated umount reach it. gather_candidates() marks it with
  T_UMOUNT_CANDIDATE but never puts it on the candidate list, so the
  mark is never cleared and every later propagated umount skips it. If
  it's the parent of a locked mount handle_locked() takes it as the
  cutoff and commits the locked child that should have stayed and
  disconnect_mount() leaves that child attached to it, unmounted under
  a mounted parent, which is what disconnect_mount() exists to
  prevent. When the namespace goes away umount_tree() visits the child
  a second time. And once __detach_mounts() has made it its own parent
  reparent() never finds the end of the chain of unmounted parents.

A clone is a new mount that has never been unmounted. Add MNT_UMOUNT
to MNT_INTERNAL_FLAGS so clone_mnt() never copies it. And warn when
umount_tree() reaches a mount that already has it set.

Fixes: 590ce4bcbf ("mnt: Add MNT_UMOUNT flag")
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-3-f357abf3d17b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-26 15:47:41 +02:00
Christian Brauner
bb46094057
statmount: read the parent of an unmounted mount under mount_lock
statmount_mnt_basic() reads the ids of the parent through m->mnt_parent
with namespace_sem held for reading. That's fine for a mount that is
in a namespace. Everything above it is pinned by that namespace.

STATMOUNT_BY_FD also accepts a file descriptor on a mount that has
been unmounted on purpose and that only the file descriptor keeps alive.
If that mount stayed attached to its parent when it was unmounted the
parent is unmounted as well and nothing pins it for the child.

The final mntput() unmounts the children under mount_lock alone and
frees the parent afterwards. Concurrently, statmount_mnt_basic() reads
m->mnt_parent twice while that can happen and can easily dereference a
mount that's already gone.

Read the parent of an unmounted mount under mount_lock. The parent
can't be put while it is held and both ids come from the same mount. A
mounted mount's parent is pinned by the namespace and a mount can't
leave its namespace while namespace_sem is held, so the common case
takes no global lock.

Fixes: 0e5032237e ("statmount: accept fd as a parameter")
Cc: stable@vger.kernel.org # v7.0+
Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-2-f357abf3d17b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-26 15:47:40 +02:00
Christian Brauner
476ef286a7
namespace: reset the old parent's ->overmount in mnt_change_mountpoint()
__umount_mnt() resets ->overmount of the parent when it unmounts the
mount that overmounts it. mnt_change_mountpoint() doesn't. It moves the
mount to its new parent and leaves the old one pointing at it.

For a mount that is moved beneath in attach_recursive_mnt() that doesn't
matter. The new mount has already reset ->overmount of the old parent by
the time the old top mount is moved onto it.

It matters for reparent() though. propagate_umount() moves a surviving
overmount onto the first ancestor that isn't going to be unmounted. The
mount it was taken from is unmounted immediately afterwards. If a file
descriptor keeps that unmounted mount alive ->overmount points at the
moved mount. That's a dangling pointer and UAF as the moved mount is
unmounted and freed.

do_lock_mount() follows ->overmount from the target path under
mount_lock. With a file descriptor on such a mount as the target of a
mount beneath topmost_overmount() walks into the freed mount and
do_lock_mount() takes references on whatever it finds there.

So reset ->overmount of the old parent in mnt_change_mountpoint() the
same way __umount_mnt() does. A mount's ->overmount is then always a
mount that is attached to it.

Fixes: ffdc52fbbd ("prevent mount hash conflicts")
Cc: stable@vger.kernel.org # v6.17+
Link: https://patch.msgid.link/20260926-work-mount-fixes-2-v1-1-f357abf3d17b@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-26 15:47:40 +02:00
Christian Brauner
7eb84d54fa
fs: don't let a migrating task hide its reference from do_umount()
propagate_mount_busy() uses mnt_get_count() via do_refcount_check() to
figure out whether a synchronous umount may proceed or not by summing
the per-CPU mnt_count counter under mount_lock.

The fastpath for mntget() and mntput() doesn't require that lock. A task
that already holds a reference and takes another one on a cpu the loop
has summed can get migrated and drops that reference again on another
cpu. The loop might not have reached that cpu yet. That ends up
undercounting. The original reference of the task is missed and umount()
ends up succeeding with the file still open. The mount is destroyed
later when the task mntput()s so it degrades to a lazy umount.

That's basically the umount(2) variant of commit 9ea0a46ca2 ("fix
mntput/mntput race") closed for the final mntput(). The same
cpu-migration caused a UAF.

Luckily this isn't a UAF. It's just a hidden reference pinning the
mount but it's wrong nonetheless.

Like the other race it's a narrow window but I can reproduce it reliably
with some patching. The debug patch below prints the sum the loop
computed next to an immediate re-sum of the same counters. It reports
who drops the last reference to that mount later:

  --- a/fs/namespace.c
  +++ b/fs/namespace.c
  @@ -34,6 +34,8 @@
   #include <linux/mnt_idmapping.h>
   #include <linux/pidfs.h>
   #include <linux/nstree.h>
  +#include <linux/debugfs.h>
  +#include <linux/delay.h>

   #include "pnode.h"
   #include "internal.h"
  @@ -263,17 +265,48 @@
   #endif
   }

  +static u32 mnt_get_count_delay_ms;
  +static int mnt_get_count_debug_id;
  +
  +static int __init mnt_get_count_debug_init(void)
  +{
  +	debugfs_create_u32("mnt_get_count_delay_ms", 0600, NULL,
  +			   &mnt_get_count_delay_ms);
  +	return 0;
  +}
  +late_initcall(mnt_get_count_debug_init);
  +
   /*
    * vfsmount lock must be held for write
    */
   int mnt_get_count(struct mount *mnt)
   {
   #ifdef CONFIG_SMP
  -	int count = 0;
  +	static int seen[NR_CPUS];
  +	bool slow = mnt_get_count_delay_ms &&
  +		    !(mnt->mnt.mnt_flags & MNT_UMOUNT);
  +	int count = 0, again = 0;
   	int cpu;

   	for_each_possible_cpu(cpu) {
  -		count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count;
  +		seen[cpu] = per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count;
  +		count += seen[cpu];
  +		if (slow)
  +			mdelay(mnt_get_count_delay_ms);
  +	}
  +	if (slow) {
  +		for_each_possible_cpu(cpu) {
  +			int now;
  +
  +			now = per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count;
  +			again += now;
  +			pr_err("mount %d: cpu%d read %d, now %d\n",
  +			       mnt->mnt_id, cpu, seen[cpu], now);
  +		}
  +		mnt_get_count_debug_id = mnt->mnt_id;
  +		pr_err("mount %d: sum %d, re-sum %d, %ps\n",
  +		       mnt->mnt_id, count, again,
  +		       __builtin_return_address(0));
   	}

   	return count;
  @@ -1350,6 +1383,9 @@
   	smp_mb();
   	mnt_add_count(mnt, -1);
   	count = mnt_get_count(mnt);
  +	if (count == 0 && mnt->mnt_id == mnt_get_count_debug_id)
  +		pr_err("mount %d: last put by %s (%d)\n", mnt->mnt_id,
  +		       current->comm, task_pid_nr(current));
   	if (count != 0) {
   		WARN_ON(count < 0);
   		rcu_read_unlock();

A task holds open a directory fd on a tmpfs. A synchronous rumount() of
the tmpfs sums up. Concurrently a task calls fchdir() on that fd on
CPU 0 and fchdir()s back to / on CPU 3. This will cause a path_get() and
a path_put() with a migration in between without mount_lock held.

Adding a delay per CPU makes the count loop miss the get in a cpu it
already visited but count the put in one it hasn't.

The reproducer, run as root with debugfs mounted:

  /*
   * gcc -static -o umount-torn umount-torn.c
   */
  #define _GNU_SOURCE
  #include <errno.h>
  #include <fcntl.h>
  #include <sched.h>
  #include <stdio.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/mount.h>
  #include <sys/stat.h>
  #include <sys/wait.h>

  #define KNOB "/sys/kernel/debug/mnt_get_count_delay_ms"
  #define MNT "/mnt/x"

  static int kmsg;

  static void pin(int cpu)
  {
  	cpu_set_t s;

  	CPU_ZERO(&s);
  	CPU_SET(cpu, &s);
  	sched_setaffinity(0, sizeof(s), &s);
  }

  static void knob(const char *v)
  {
  	int fd = open(KNOB, O_WRONLY);

  	write(fd, v, strlen(v));
  	close(fd);
  }

  int main(void)
  {
  	int last = sysconf(_SC_NPROCESSORS_ONLN) - 1;
  	int p[2], fd, ret;
  	pid_t pid;
  	char c;

  	kmsg = open("/dev/kmsg", O_WRONLY);
  	mkdir(MNT, 0755);
  	mount("none", MNT, "tmpfs", 0, NULL);
  	fd = open(MNT "/f", O_WRONLY | O_CREAT, 0644);
  	write(fd, "still here\n", 11);
  	close(fd);

  	pipe(p);
  	pid = fork();
  	if (pid == 0) {
  		int dirfd = open(MNT, O_RDONLY | O_DIRECTORY);
  		int rootfd = open("/", O_RDONLY | O_DIRECTORY);
  		struct stat st;
  		char buf[16];

  		/* one get/put pair while the umount is summing */
  		usleep(150000);
  		pin(0);
  		fchdir(dirfd);		/* mntget() */
  		dprintf(kmsg, "holder %d: get on cpu %d\n", getpid(),
  			sched_getcpu());
  		pin(last);
  		fchdir(rootfd);		/* mntput() */
  		dprintf(kmsg, "holder %d: put on cpu %d\n", getpid(),
  			sched_getcpu());

  		/* the umount has returned, is the fd still good? */
  		read(p[0], &c, 1);
  		fd = openat(dirfd, "f", O_RDONLY);
  		ret = read(fd, buf, sizeof(buf));
  		dprintf(kmsg, "holder %d: fstat %d openat %d read %d\n",
  			getpid(), fstat(dirfd, &st), fd, ret);
  		_exit(0);
  	}

  	pin(1);
  	knob("100");
  	ret = umount2(MNT, 0);
  	knob("0");
  	if (ret)
  		dprintf(kmsg, "umount: %s\n", strerror(errno));
  	else
  		dprintf(kmsg, "umount: 0\n");
  	write(p[1], "x", 1);
  	waitpid(pid, NULL, 0);
  	return 0;
  }

The log shows the busy check summing 2 and when read right after it
reads 3. The loop read CPU 0 before the get landed there and CPU 3
after the put did. The umount succeeds while there's still someone
with an fd. The mount is then dropped when the fd goes:

  [    2.511944] holder 90: get on cpu 0
  [    2.512023] holder 90: put on cpu 3
  [    2.671388] mount 28: cpu0 read 1, now 2
  [    2.671398] mount 28: cpu1 read 2, now 2
  [    2.671401] mount 28: cpu2 read 0, now 0
  [    2.671404] mount 28: cpu3 read -1, now -1
  [    2.671407] mount 28: sum 2, re-sum 3, propagate_mount_busy
  [    2.671773] umount: 0
  [    2.672963] holder 90: fstat 0 openat 8 read 11
  [    2.673663] mount 28: last put by umount-torn (90)

Easy fix would be to order mntget() against the loop that sums up. But
that's a full barrier and a flag test in mntget() afaict. No bueno...
That would cost every path_get().

So learn from SRCU. Keep gets and puts in separate per-cpu counters and
make mnt_get_count() sum all puts first and all gets second with a full
barrier in between.

A put is always after its own get (hopefully...) independent of the cpu
the task is on. So a put the first pass counted must have its get
visible to the second pass. Ergo, we can't undercount.

This is the two pass sum srcu_readers_active_idx_check() does for
srcu_locks and srcu_unlocks. mntget() keeps a single
this_cpu_inc(). mntput() gains an smp_wmb() before its increment, a
compiler barrier on x86 and a store fence on the weakly ordered
architectures, since the two counters are two words and a put stored
after a get could otherwise become visible before it. The counters
are unsigned so the difference stays right after they wrap.

The count can still come out high when a get is counted whose put
the first pass did not see. We don't care. A task can only take a new
reference if:

(1) it holds one already
(2) through __legitimize_mnt() which the seqcount orders
    against the write side

So a get in this window means someone has a reference and -EBUSY is
correct. The final mntput() is unchanged. Once ->mnt_ns is NULL every
put takes the slow path under mount_lock and so no put is in flight
while it counts. Here's an attempted proof for that crap:

(i) A put never becomes visible before its get. Within one task the
    smp_wmb() in mntput_no_expire() orders every earlier store before the
    increment of the puts.

    Any task that migrated in between had its stores flushed by the
    scheduler. When that reference is handed to another task that's
    trivially true.

(ii) The double count in mnt_get_count() is ordered via smp_mb(). That
     keeps every read of the puts counter before every read of the gets
     counter.

(iii) We never undercount by (ii).

(iv) There are no spurious counts/differences. A get is counted only if
     it happened and a put is left out only if it hasn't happened when
     the counter was read. With (iii) this means it's equivalent to the
     old count whenever nothing is taken or dropped during the loop.

(v) do_umount() holds a reference and mount_lock hence. New references
    come from: (v.a) __legitimize_mnt() managed by the seqcount. Either
    it's smp_mb() made the get visible to the loop or it takes the lock
    and waits for the loop. By (iii) a mount with a additional
    references is refused. By (iv) refusing the umount just means that
    any additional reference existed, same as the old counter.

(vi) mntput_no_expire_slowpath() runs with ->mnt_ns already NULL. Hence,
     every other put must take the slowpath as well and hangs on mount_lock.

    The grace period in namespace_unlock() will make every fast path do
    a put that still saw ->mnt_ns.

    No put is in flight during the loop.

    A get that is in flight comes from a task holding another reference.
    That must be included by the count or from __legitimize_mnt() as in
    (v).

    Ergo, zero means the last reference and non-zero means a reference
    is live.

(vii) may_umount() uses propagate_mount_busy() as well and
      may_umount_tree() sums mnt_get_count() itself.

With this patch the race is dead:

  [    3.066786] mount 28: sum 3, re-sum 3, propagate_mount_busy
  [    3.105867] umount: Device or resource busy
  [    3.106047] holder 90: fstat 0 openat 8 read 11

Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-7-f424cf8d3242@kernel.org
Fixes: b3e19d924b ("fs: scale mntget/mntput")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-25 17:20:33 +02:00
Christian Brauner
a2b42babc9
mount: refuse MOVE_MOUNT_SET_GROUP on an unbindable mount
do_set_group() only accepts mounts as targets that are neither shared
nor a slave. That encompasses undindable mounts.

If the source mount is a slave mount the target mount will end up with
source's master as its master. It keeps T_UNBINDABLE. That means we get
a mount that is both unbindable and a slave:

  mount --bind /tmp/src /tmp/src
  mount --make-shared /tmp/src
  mount --bind /tmp/src /tmp/b
  mount --make-slave /tmp/b
  mount --bind /tmp/src /tmp/c
  mount --make-unbindable /tmp/c
  move_mount(b, "", c, "", MOVE_MOUNT_SET_GROUP)
  grep /tmp/c /proc/self/mountinfo
  ... /tmp/c ... master:646 unbindable ...

This property cannot be produced any other way. change_mnt_propagation()
drops the master when it makes a mount unbindable (iow, it becomes
private) and doesn't touch an unbindable mount when it is supposed to
turned into a slave mount.

End-result is that the unbindable-slave mount receives everything
propagated from its master's peer group while it can't be bind mounted
itself. Not sure what that's supposed to do.

On the other side: if the source mount is shared, the target mount drops
T_UNBINDABLE because because set_mnt_shared() clears T_SHARED_MASK.

So teach do_set_group() to refuse an unbindable target mount the same
way a shared or slave target mount is refused.

The main user of MOVE_MOUNT_SET_GROUP is CRIU which restores sharing
first and applies MS_UNBINDABLE afterwards. It never hits this.

Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-3-f424cf8d3242@kernel.org
Fixes: 9ffb14ef61 ("move_mount: allow to add a mount into an existing group")
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-25 17:20:32 +02:00
Christian Brauner
1ab8904ae6
mount: keep a copied mount unbindable
It's groundhog day.

MNT_UNBINDABLE used to be set in in mnt->mnt.mnt_flags and was not part
of MNT_INTERNAL_FLAGS. clone_mnt() copied it with all the other flags to
the new mount. This guaranteed that the copy of an unbindable mount
became unbindable as well. Unbindable copies that ended up as shared
lost the unbindable property.

But then commit 406fea7999 ("mount: separate the flags accessed only
under namespace_sem") moved MNT_UNBINDABLE from mnt->mnt.mnt_flags into
mnt->mnt_t_flags as T_UNBINDABLE.

clone_mnt() doesn't copy from mnt_t_flags and specifically doesn't copy
T_UNBINDABLE. The only place a mount gets marked unbindable is in
change_mnt_propagation() for MS_UNBINDABLE.

This reintroduced an earlier bug we had already fixed. It resurfaces in
copy_mnt_ns() which clones unbindable mounts via CL_COPY_UNBINDABLE. So
since v6.17 a mount namespace created via clone(CLONE_NEWNS) or
unshare(CLONE_NEWNS) contain private and bindable copies of every
unbindable mount. The following snippet:

  mount --make-unbindable /mnt
  unshare -m --propagation unchanged
  mount --bind /mnt /tmp/x

succeeds. This used to fail with EINVAL. This also applies to
recursively binding a tree that contains an unbindable the mount. Such
subtrees used to be pruned when they were moved onto a shared mount.

And Documentation/filesystems/sharedsubtree.rst still says that the copy
of an unbindable mount is unbindable.

So once again let's fix this. Copy T_UNBINDABLE in clone_mnt(). Let
set_mnt_shared() mask it off whenever a mount is made shared.
This restores what the old MNT_INTERNAL_FLAGS mask did.

Link: https://patch.msgid.link/20260923-work-mount-fixes-v1-1-f424cf8d3242@kernel.org
Fixes: 406fea7999 ("mount: separate the flags accessed only under namespace_sem")
Cc: stable@vger.kernel.org # v6.17+
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-25 17:20:32 +02:00
Christian Brauner
64cbb36448
file: make struct fd_prepare const and kill its err field
FD_PREPARE() releases the fd as soon as the file expression fails. The
fd either holds the descriptor or the error. The separate field is
redundant. Make fd and file plain members and struct fd_prepare simpler.

Callers of FD_PREPARE() get a const pointer to the guard. Simplify all
users as they can now easily access the trivial struct.

No functional changes.

Link: https://patch.msgid.link/20260917-work-file-fd_prepare-v1-4-b87534ca49f3@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-23 09:27:44 +02:00
Christian Brauner
025756ee7b
fs: port mnt_idmap() and file_mnt_idmap() to const mnt_idmap
Now that everything takes a mnt_idmap as const store a const pointer in
struct vfsmount, struct mount_kattr and struct kstatmount and return one
from mnt_idmap(), file_mnt_idmap() and ovl_upper_mnt_idmap(). Finally,
also make mnt_idmap_get() return a const pointer. Also convert the
remaining local variables that are initialized from the accessors.

alloc_mnt_idmap() keeps returning a non-const pointer. It is the only
place where an idmapping is actually written to.

Link: https://patch.msgid.link/20260901-work-idmap-const-v1-26-54ccd48e100b@kernel.org
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Seth Forshee <sforshee@kernel.org>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-23 09:19:51 +02:00
Christian Brauner
2e2142a35d Revert "put_mnt_ns(): leave mounts connected"
This reverts commit 0342482a4d.

Keeping every mount of a dying namespace attached to its parent changes
who owns it. An attached but unmounted mount is owned by its parent: the
parent's final mntput() is what drops the child's reference and takes
its superblock down. Before that commit only the namespace root was in
that position and every other mount dropped its own reference in
namespace_unlock().

That ownership rule turns any reference from a child's superblock back
to one of its ancestors into a cycle. The obvious case is a loop device:

    unshare -m sh -c 'mount -o loop /var/tmp/img /var/tmp/mp'

mount(8) opens the image from inside the new namespace, so the loop
device's backing file pins that namespace's copy of the root mount.
When the namespace dies the loop mount stays attached to that copy and
is owned by it. The copy can't reach zero because the backing file
holds it. The backing file is only put once the ext4 superblock
releases the block device and autoclear runs, and that needs the loop
mount to go first. Nothing breaks the cycle. The superblock, the loop
device and, since the root copy keeps all of its children.

systemd-sysext sets up its loop devices in a private mount namespace and
relies on autoclear when that namespace goes away, which is how this was
found. Anything that holds a file on an ancestor from a mounted
filesystem has the same problem. That includes ecryptfs lower paths,
erofs file-backed mounts, fuse passthrough backing files.

That's a bigger fix and not an -rc change. Revert.

Fixes: 0342482a4d ("put_mnt_ns(): leave mounts connected")
Reported-by: Michael Vogt <michael@amutable.com>
Link: https://gist.github.com/mvo5/63ef46482349f3b1c3957d463a0c9c6f
Link: https://patch.msgid.link/20260917-work-put_mnt_ns-revert-v1-2-34d9b8679e68@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-23 09:03:18 +02:00
Christian Brauner
5179241521
fs: don't create the private nullfs mount under namespace_sem
init_mount_tree() mounts the mutable rootfs on top of nullfs via
LOCK_MOUNT_EXACT(). That declares a pinned mountpoint with a cleanup
attribute in the scope of the whole function so the nullfs root inode
lock and namespace_sem are only dropped when init_mount_tree() returns.

This became a problem when the private nullfs instance for kthreads was
added. kern_mount() allocates a new superblock and alloc_super() takes
the new s_umount with SINGLE_DEPTH_NESTING and then shrinker_mutex via
shrinker_alloc(). Doing that with namespace_sem held teaches lockdep the
dependency

    namespace_sem -> s_umount/1 -> shrinker_mutex

With CONFIG_SHRINKER_DEBUG shrinker_debugfs_rename() takes the debugfs
directory inode lock under shrinker_mutex every time a block device is
mounted and lock_mount_exact() takes namespace_sem under the inode lock
of the mountpoint for every mount. So mounting anything on debugfs,
e.g. the tracefs automount on /sys/kernel/debug/tracing, closes the
cycle:

    WARNING: possible circular locking dependency detected
    7.3.0-rc3+ #17 Not tainted
    ------------------------------------------------------
    rasdaemon/4449 is trying to acquire lock:
    (namespace_sem){++++}-{4:4}, at: lock_mount_exact+0x4c/0x308

    but task is already holding lock:
    (&sb->s_type->i_mutex_key#17){++++}-{4:4}, at: lock_mount_exact+0x3c/0x308

    which lock already depends on the new lock.
    ...
    Chain exists of:
      namespace_sem --> shrinker_mutex --> &sb->s_type->i_mutex_key#17

This can't actually deadlock. init_mount_tree() runs single-threaded
during early boot before any other task exists and nothing allocates a
superblock under namespace_sem after that. But lockdep can't know that
and disables itself for the rest of the boot.

Move mounting the rootfs on top of nullfs into a helper so the locks
are dropped when it returns.

Fixes: 32750c77e8 ("fs: start all kthreads in nullfs")
Reported-by: Zenghui Yu <yuzenghui@huawei.com>
Closes: https://lore.kernel.org/15174353-3f4a-a1ca-5bd1-ea2a4c77828e@huawei.com
Link: https://patch.msgid.link/20260917-atemtechnik-bleichen-befassen-9a57db01baf0@brauner
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-18 09:42:05 +02:00
Hemanth Selam
21a62663d4
fs: fix typos in comments
Fix typos in comments, reported by scripts/checkpatch.pl using the
misspelling list in scripts/spelling.txt.  Only touches comments, no code
changes.

Signed-off-by: Hemanth Selam <hemanth.selam@gmail.com>
Link: https://patch.msgid.link/20260907063252.21433-1-hemanth.selam@gmail.com
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-10 10:06:16 +02:00
Kees Cook
3a2c4d55e3 treewide: refresh kmalloc_obj() conversions
This is another run of the Coccinelle script for converting kmalloc()
family of allocations to kmalloc_obj() via the existing rules in
scripts/coccinelle/api/kmalloc_objs.cocci

This catches both the set of kmalloc() uses added since the first
kmalloc_obj() conversions in v7.0 and adds a large group missed in the
first pass due to Coccinelle not interacting well with the cleanup.h
scoped_...() family of macros[1]. I worked around this with spatch's
"--macro-file" argument to a file with all the scoped_...() macros mapped
to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control
flow indicator I could find.

Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc,
riscv, and s390 with no new warnings.

Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1]
Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2]
Signed-off-by: Kees Cook <kees+treewide@kernel.org>
2026-09-04 21:37:00 -07:00
Shaojie Sun
4141e148e6
fs: namespace: drop BUG_ON() in copy_mnt_ns()
The sole caller, create_new_namespaces(), always passes a valid mount
namespace pointer from the task's nsproxy. The other copy_*_ns()
helpers, e.g. copy_time_ns() and copy_pid_ns(), don't perform such a
check either. Drop the BUG_ON() for consistency with them.

Signed-off-by: Shaojie Sun <sunshaojie@kylinos.cn>
Link: https://patch.msgid.link/20260903064828.126643-4-sunshaojie@kylinos.cn
Reviewed-by: Tao Cui <cuitao@kylinos.cn>
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-04 12:46:45 +02:00
Christian Brauner
4f238e6e1c ns: Free anonymous mount namespaces via ns_common_free()
free_mnt_ns() skipped ns_common_free() for anonymous mount namespaces
(the "if (!is_anon_ns(ns))" guard) because they carry the reserved inum
MNT_NS_ANON_INO, which proc_free_inum() must never release.

A following change needs ns_common_free() to run for every namespace, to
release per-namespace state attached during __ns_common_init().  Move
the reserved-inum decision into __ns_common_free() and let free_mnt_ns()
call ns_common_free() unconditionally.

__ns_common_free() is shared by all namespace types, so it gates
proc_free_inum() on ns->inum > MNT_NS_INO_SPECIAL_MAX (a new alias for
MNT_NS_ANON_INO) rather than the mount-specific is_anon_ns().  The
reserved inums (MNT_NS_ANON_INO and the *_NS_INIT_INO values just above
it) belong to namespaces that are never freed, except the anonymous
mount namespace; dynamically allocated inums are >= PROC_DYNAMIC_FIRST.
So the comparison frees every dynamic inum and skips exactly the
anonymous mount namespace, matching the previous guard.

Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
Co-developed-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Reviewed-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2026-08-31 16:14:10 -04:00