projects/linux·commit f99005fb

Merge tag 'vfs-7.3-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

f99005fb · Linus Torvalds <[email protected]> · 2026-10-09 23:37:44 +02:00

Pull vfs fixes from Christian Brauner:
 "This contains fixes for the current development cycle.

  All of them came out of a review of the mount code that started with a
  bug report. The review modeled the corner cases of mount propagation,
  unmounting and mount reference counting and turned up a lot of bugs.
  Most of them years old. Most fixes come with a selftest.

   - Rework connected mounts.

     A mount that is unmounted together with its parent can stay
     attached to the parent to keep its mountpoint covered. That happens
     when the mountpoint is removed with rmdir(), unlink() or rename(),
     when a detached tree is dissolved, and for locked mounts in any
     umount that isn't synchronous, including the teardown of their
     mount namespace. The parent then owns the child and drops it on its
     own final mntput(). So any reference from the child's superblock
     back to one of its ancestors becomes a cycle that is never freed.

     A loop device backed by an image on a tmpfs and mounted on that
     same tmpfs is enough. Remove the directory the tmpfs is mounted on
     from the host, let the container's mount namespace exit, and the
     loop device, the tmpfs and the filesystem on the loop device are
     leaked for good. The same works with autofs, zram, ecryptfs,
     binfmt_misc, fuse passthrough, zloop, a mass storage gadget and md,
     and the selftests have reproducers for them. This has been possible
     since v4.1. It is also why "put_mnt_ns(): leave mounts connected"
     was reverted in -rc5. Keeping every mount of a dying mount
     namespace connected made these cycles trivial to create.

     Every unmounted mount is now detached from its parent. Where the
     mountpoint has to stay covered the mount leaves a cover on the
     parent instead, allocated together with the mount. A lookup on the
     unmounted parent that hits a cover finds an empty immutable
     directory or file on the private nullfs instance. Nothing leads
     from a cover to another mount, so no unmounted mount owns another
     one and no cycle can form.

     This is visible to userspace. A formerly connected mount can no
     longer be reached through its unmounted parent and ".." inside it
     leads nowhere, as for every other lazily unmounted mount.

     With that the private nullfs instance becomes reachable from
     userspace, so it now refuses mounts on top, is mounted read-only,
     and refuses fsnotify marks and file locks. Its inodes are shared by
     every holder and a watch or a lock would otherwise reach across
     users. may_decode_fh() now also decides its subtree check under a
     single mount_lock hold, as a racing umount could otherwise let it
     decode into what a locked child covered.

   - umount:

     * Don't silently unmount busy mounts.

       Since v4.13 propagate_umount() takes down propagated copies of
       the victim with children as long as each child is an overmount or
       another copy of the victim, but propagate_mount_busy() only ever
       checked copies without children or with just an overmount. A
       container that moved a tree beneath its copy of a host mount lost
       that tree from under its open file descriptor to a plain umount()
       on the host. propagate_mount_busy() now applies the same rules,
       walking each chain of copies once.

     * Don't let a migrating task hide its reference from umount().

       mnt_get_count() sums the per-cpu counters under mount_lock but
       the mntget() and mntput() fast paths don't take it. A task that
       takes a reference on a cpu the sum has already passed and drops
       it after migrating to one the sum hasn't reached yet hides the
       reference it held to begin with, and umount() succeeds with the
       file still open. Gets and puts now live in separate per-cpu
       counters and all puts are summed before all gets with a full
       barrier in between, the way srcu_readers_active_idx_check() does
       it. mntget() is unchanged and mntput() gains an smp_wmb().

     * Check each submount for references right before unmounting it.

       shrink_submounts() and mark_mounts_for_expiry() checked all their
       victims up front. Unmounting the first could move a busy
       overmount to where the next victim's propagated copy is looked up
       and it was then unmounted without a check.

     * Never expire a locked mount.

       A shrinkable mount moved beneath a locked mount with
       MOVE_MOUNT_BENEATH takes over the lock, and umount() of an
       unlocked ancestor expired it and revealed what it covered. That
       umount() now fails with EBUSY as it does for any other locked
       child. A lazy umount still takes the whole tree.

   - Overmounts and locked mounts:

     * Unhash a dentry before detaching the mounts on it.

       unlink(), rmdir() and rename() detach the mounts on the victim
       but only d_delete() it once its inode is unlocked, a window that
       includes an expedited RCU grace period. In between, a lookup from
       a mount namespace in which the dentry is a mountpoint found it
       hashed, positive and uncovered. Drop the dentry first, as
       d_invalidate() already does.

     * Don't reveal overmounted entries in refwalk.

       A refwalk that had grabbed the dentry before the unlink never
       rechecked it the way rcuwalk does with d_seq and mount_lock.
       Without any artificial widening three walkers read the covered
       file 27 times in a minute. step_into() now fails an unhashed
       dentry marked DCACHE_CANT_MOUNT with -ESTALE and the walk is
       retried.

     * Keep covered mounts covered in OPEN_TREE_NAMESPACE.

       Creating such a mount namespace only takes a user namespace and
       the copy followed bind mount rules: no children without
       AT_RECURSIVE and no unbindable mounts with it. An unprivileged
       user could see what mounts covered in the source, such as the
       parts of /proc and /sys that container runtimes mask. If the
       caller doesn't own the source mount namespace a non-recursive
       copy of a mount with something mounted below the requested
       directory is now refused and a recursive copy includes unbindable
       mounts, the way unshare() copies.

     * Keep the lock on a mount that a propagated copy is moved beneath.

       MNT_LOCKED moved to any mount that ended up beneath a locked
       mount, propagated copies included. A host mount and umount on a
       directory covered by a locked mount in a less privileged mount
       namespace left that cover unlocked for the namespace's owner to
       remove. Only mounts the caller places beneath take over the lock
       now.

     * Handle mount locking for automounts correctly. Which copies to
       lock was decided by the mount namespace of the task that
       triggered the automount. A task in a user namespace that
       triggered one on a host mount through a file descriptor got the
       host's own automount locked while its own copy stayed unlocked
       and could have nosuid, nodev and noexec cleared. Use the owner of
       the mount namespace the mount lands in.

   - Use-after-free and crashes:

     * Refuse an automount below a mount that is in no namespace.

       The private clones overlayfs uses for its layers have the
       MNT_NS_INTERNAL error pointer as their namespace, which
       finish_automount() let through and count_mounts() dereferenced. A
       fanotify filesystem mark on an overlayfs lower layer hands out
       file descriptors on such a clone. With debugfs as the lower layer
       opening "tracing" oopses with namespace_sem held for writing and
       every mount operation on the system blocks from then on.

     * Reset the old parent's ->overmount in mnt_change_mountpoint().
       When propagate_umount() moved an overmount off a mount that a
       file descriptor kept alive, MOVE_MOUNT_BENEATH through that
       descriptor later followed the stale pointer into the freed
       overmount.

     * statmount() with STATMOUNT_BY_FD and pivot_root() read the parent
       of a mount that may be unmounted and only held by a file
       descriptor, while the parent's final mntput() can free it.
       statmount() now reads it under mount_lock and pivot_root() first
       checks that both mounts are in the caller's mount namespace.

     * Queue a mount only once for mount notifications. A mount
       reparented by one umount_tree() and taken down by the next under
       the same namespace_sem hold, as in shrink_submounts(), was queued
       twice. That cut the mounts queued in between out of notify_list
       while it still pointed at them, and once they were freed every
       later mount operation walked freed memory.

     * Don't let a pseudo dentry become the root of a mount. A bind
       mount of a bpf token file did that with a DCACHE_NORCU dentry,
       which is freed without an RCU grace period while lockless path
       walks may still look at it. Refuse to clone such a mount.

     * Don't inherit MNT_UMOUNT in clone_mnt(). A bind mount of a lazily
       unmounted nsfs or pidfs mount through its file descriptor started
       out flagged as unmounted. Among other things __detach_mounts()
       then dropped the namespace's reference on it, the mount outlived
       its namespace and mount_setattr() through the descriptor read the
       freed namespace. A recursive bind mount of such a mount also
       copied the unmounted stack still attached to it. That now fails
       with EINVAL, copying the mount itself still works.

     * Remove the fsnotify marks of a mount namespace in free_mnt_ns()
       instead of the RCU callback that frees the namespace, where
       taking the group mutexes meant sleeping in softirq context.

   - Propagation and copies:

     * Keep a copied mount unbindable. Since v6.17 clone_mnt() didn't
       copy the unbindable flag, so every mount namespace created with
       CLONE_NEWNS had bindable copies of all unbindable mounts. This
       had been fixed once before.

     * Refuse MOVE_MOUNT_SET_GROUP on an unbindable mount. It made the
       mount an unbindable slave, a state nothing else can produce, or
       silently dropped the unbindable flag. CRIU applies MS_UNBINDABLE
       after restoring sharing and isn't affected.

     * Check a recursive bind mount for mount namespace loops.

       Recursively bind mounting a tree from another mount namespace
       could put a mount of a namespace's file inside that same
       namespace, which then pins itself and all its mounts. Repeating
       it leaks without limit, the reproducer took Shmem from 380 kB to
       65916 kB. The copy is now checked with check_for_nsfs_mounts()
       before it is grafted, as move_mount() does.

     * Look at the topmost mount for a mount namespace file.

       attach_recursive_mnt() never looked at the topmost mount of the
       source's chain of overmounts. If that was the chain's only mount
       namespace file an existing mount at a propagated destination got
       buried below the root of the nsfs file where no path walk reaches
       it.

     * Don't put a mountpoint on a dentry that's being removed.

       attach_recursive_mnt() makes a mountpoint of the source's root
       without its inode lock, so a racing rmdir() of that directory
       could leave a mount on it that nothing ever detaches.
       d_set_mounted() now checks cant_mount() as well.

   - nullfs:

     * Take no inode lock for readdir of an immutable directory.

       The root of every empty mount namespace is the same nullfs
       directory and iterate_dir() held its i_rwsem across
       ->iterate_shared(). A reader whose buffer faults on a FUSE mount
       of its own holds it for as long as its server wants, and with an
       exclusive locker queued behind it every lookup that misses the
       dcache, every create and every mount in that directory waits. One
       user of an empty mount namespace stalls all others. Directories
       with the new FOP_IMMUTABLE flag skip the lock.

     * Refuse to reconfigure internal superblocks through fspick(),
       MS_REMOUNT or the read-only remount that a synchronous umount()
       of the root does. For nullfs only root in the initial user
       namespace could do it, but the superblock is shared by every
       mount namespace and the flags showed up in statfs() for all of
       them.

     * Don't update the access time on nullfs and refuse F_SET_RW_HINT
       on an immutable inode.

   - unshare: Free an nsproxy that was never installed with
     nsproxy_free() when set_cred_ucounts() fails. put_nsproxy() dropped
     active references that were never taken, which triggered a warning
     and hid the caller's own namespaces from listns().

   - Smaller changes: mount_setattr() checks the target before it walks
     the tree to allocate peer group ids, unshare() puts the old
     fs_struct before the old namespaces, dissolve_on_fput() drops the
     file's reference to the tree itself, disconnect_mount() is
     simplified and the documentation of the propagated unmount rule is
     brought up to date"

* tag 'vfs-7.3-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (58 commits)
  namespace: simplify disconnect_mount()
  selftests/filesystems: test covered mounts
  namespace: rework connected mounts
  nullfs: add an empty immutable regular file
  selftests/filesystems: check that reading the root of an empty mount namespace stalls nobody
  selftests/filesystems: add a helper that holds a readdir in a page fault
  readdir: take no inode lock on an immutable directory
  nullfs: refuse file locks
  fsnotify: let a filesystem refuse marks on its objects
  namespace: nothing is mounted on or written through knullfs
  namespace: keep the private nullfs instance in knullfs
  fhandle: decide the subtree check under mount_lock
  selftests/filesystems: check that an automount below an overlay layer is refused
  selftests/filesystems: check the atime of the empty mount namespace root
  selftests/filesystems: check that a lock lands on the right mount and stays
  namespace: keep the lock on a mount that a propagated copy is moved beneath
  namespace: never expire a locked mount
  nullfs: don't update the access time
  namespace: handle mount locking for automounts correctly
  namespace: refuse an automount below a mount that is in no namespace
  ...
Commit
f99005fbed4e8071110faa350cc5fa453afa1455
Parents
9515f634 · 3d399224

Merge commit. Changes below are compared with the first parent: 9515f634.

Changes

53 files changed · +6462 −195

FileStatusAddDelContent
Documentation/filesystems/propagate_umount.txtmodified+6−6View file
Documentation/filesystems/sharedsubtree.rstmodified+14−6View file
fs/dcache.cmodified+3−1View file
fs/fcntl.cmodified+3−0View file
fs/fhandle.cmodified+17−3View file
fs/file_table.cmodified+2−1View file
fs/fs_context.cmodified+4−0View file
fs/mount.hmodified+14−4View file
fs/namei.cmodified+29−0View file
fs/namespace.cmodified+327−134View file
fs/notify/mark.cmodified+4−0View file
fs/nullfs.cmodified+121−2View file
fs/pnode.cmodified+92−20View file
fs/readdir.cmodified+9−4View file
include/linux/fs.hmodified+3−0View file
include/linux/mount.hmodified+1−1View file
include/linux/nsproxy.hmodified+1−0View file
kernel/fork.cmodified+8−4View file
kernel/nsproxy.cmodified+1−1View file
tools/testing/selftests/Makefilemodified+3−0View file
tools/testing/selftests/filesystems/.gitignoremodified+1−0View file
tools/testing/selftests/filesystems/Makefilemodified+1−1View file
tools/testing/selftests/filesystems/empty_mntns/.gitignoremodified+3−0View file
tools/testing/selftests/filesystems/empty_mntns/Makefilemodified+5−0View file
tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.cadded+108−0View file
tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.cadded+129−0View file
tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.cadded+45−0View file
tools/testing/selftests/filesystems/mntns_unbindable/Makefileadded+6−0View file
tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.cadded+227−0View file
tools/testing/selftests/filesystems/mount_cycle/.gitignoreadded+8−0View file
tools/testing/selftests/filesystems/mount_cycle/Makefileadded+12−0View file
tools/testing/selftests/filesystems/mount_cycle/configadded+39−0View file
tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.cadded+377−0View file
tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.cadded+1542−0View file
tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.cadded+579−0View file
tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.cadded+193−0View file
tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.cadded+190−0View file
tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.cadded+181−0View file
tools/testing/selftests/filesystems/mount_cycle/settingsadded+1−0View file
tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.cadded+484−0View file
tools/testing/selftests/filesystems/open_tree_ns/.gitignoremodified+1−0View file
tools/testing/selftests/filesystems/open_tree_ns/Makefilemodified+1−1View file
tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.cadded+195−0View file
tools/testing/selftests/filesystems/overlayfs/.gitignoremodified+1−0View file
tools/testing/selftests/filesystems/overlayfs/Makefilemodified+1−0View file
tools/testing/selftests/filesystems/overlayfs/automount_in_layer.cadded+174−0View file
tools/testing/selftests/filesystems/readdir_hold.hadded+224−0View file
tools/testing/selftests/filesystems/rw_hint_test.cadded+129−0View file
tools/testing/selftests/filesystems/umount_propagation/Makefileadded+6−0View file
tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.cadded+432−0View file
tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.cadded+211−0View file
tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.cadded+226−0View file
tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.cmodified+68−6View file

Only the first 600 diff lines are shown. Download the complete patch.

Documentation/filesystems/propagate_umount.txt

View file
diff --git a/Documentation/filesystems/propagate_umount.txt b/Documentation/filesystems/propagate_umount.txt
index 9a7eb96..de58ce8 100644
--- a/Documentation/filesystems/propagate_umount.txt
+++ b/Documentation/filesystems/propagate_umount.txt
@@ -32,12 +32,12 @@ of set. They can be reparented to the place where the bottom of
stack is attached to a mount that will survive. NOTE: doing that
will violate a constraint on having no more than one mount with
the same parent/mountpoint pair; however, the caller (umount_tree())
-will immediately remedy that - it may keep unmounted element attached
-to parent, but only if the parent itself is unmounted. Since all
-conflicts created by reparenting have common parent *not* in the
-set and one side of the conflict (bottom of the stack of overmounts)
-is in the set, it will be resolved. However, we rely upon umount_tree()
-doing that pretty much immediately after the call of propagate_umount().
+will immediately remedy that - it detaches every element of the set
+from its parent. Since all conflicts created by reparenting have
+common parent *not* in the set and one side of the conflict (bottom
+of the stack of overmounts) is in the set, it will be resolved.
+However, we rely upon umount_tree() doing that pretty much immediately
+after the call of propagate_umount().
Algorithm is based on two statements:
1) for any set S, there is a maximal non-shifting subset of S

Documentation/filesystems/sharedsubtree.rst

View file
diff --git a/Documentation/filesystems/sharedsubtree.rst b/Documentation/filesystems/sharedsubtree.rst
index 8b7dc91..8bae6d9 100644
--- a/Documentation/filesystems/sharedsubtree.rst
+++ b/Documentation/filesystems/sharedsubtree.rst
@@ -564,8 +564,8 @@ f) Unmount semantics
where 'A' is a mount mounted on mount 'B' at dentry 'b'.
If mount 'B' is shared, then all most-recently-mounted mounts at dentry
- 'b' on mounts that receive propagation from mount 'B' and does not have
- sub-mounts within them are unmounted.
+ 'b' on mounts that receive propagation from mount 'B' are unmounted as
+ well, if every mount below them is also unmounted.
Example: Let's say 'B1', 'B2', 'B3' are shared mounts that propagate to
each other.
@@ -584,10 +584,18 @@ f) Unmount semantics
So all 'C1', 'C2' and 'C3' should be unmounted.
- If any of 'C2' or 'C3' has some child mounts, then that mount is not
- unmounted, but all other mounts are unmounted. However if 'C1' is told
- to be unmounted and 'C1' has some sub-mounts, the umount operation is
- failed entirely.
+ If any of 'C2' or 'C3' has a child mount that cannot be unmounted
+ then that mount is not unmounted. But all other mounts are unmounted.
+ A child mount that is itself unmounted by the same unmount
+ propagation does not keep its parent mounted. However if 'C1' is
+ supposed to be unmounted and 'C1' has some sub-mounts, the unmount
+ fails.
+
+ A lazy umount (MNT_DETACH) takes a whole tree. Every mount of the
+ tree then propagates its unmount from its own parent as described
+ above, so the mounts that receive propagation lose the corresponding
+ trees as well. Documentation/filesystems/propagate_umount.txt has the
+ precise rules, including the ones for locked mounts.
g) Clone Namespace

fs/dcache.c

View file
diff --git a/fs/dcache.c b/fs/dcache.c
index a66be85..f44c7b3 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1583,6 +1583,8 @@ EXPORT_SYMBOL(path_has_submounts);
*
* Only one of d_invalidate() and d_set_mounted() must succeed. For
* this reason take rename_lock and d_lock on dentry and ancestors.
+ * Likewise for dont_mount() which marks a dentry that is being removed
+ * under d_lock.
*/
int d_set_mounted(struct dentry *dentry)
{
@@ -1599,7 +1601,7 @@ int d_set_mounted(struct dentry *dentry)
spin_unlock(&p->d_lock);
}
spin_lock(&dentry->d_lock);
- if (!d_unlinked(dentry)) {
+ if (!d_unlinked(dentry) && !cant_mount(dentry)) {
ret = -EBUSY;
if (!d_mountpoint(dentry)) {
dentry->d_flags |= DCACHE_MOUNTED;

fs/fcntl.c

View file
diff --git a/fs/fcntl.c b/fs/fcntl.c
index c158f08..9b71bc9 100644
--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -372,6 +372,9 @@ static long fcntl_set_rw_hint(struct file *file, unsigned long arg)
u64 __user *argp = (u64 __user *)arg;
u64 hint;
+ /* nothing is ever written to it */
+ if (IS_IMMUTABLE(inode))
+ return -EPERM;
if (!inode_owner_or_capable(file_mnt_idmap(file), inode))
return -EPERM;

fs/fhandle.c

View file
diff --git a/fs/fhandle.c b/fs/fhandle.c
index f882923..d22f2e0 100644
--- a/fs/fhandle.c
+++ b/fs/fhandle.c
@@ -298,6 +298,22 @@ static bool capable_wrt_mount(struct mount *mount)
return mnt_ns && ns_capable(mnt_ns->user_ns, CAP_SYS_ADMIN);
}
+/*
+ * Does the caller have an unobstructed way to everything below @root? Only
+ * if the mount is mounted, the caller is privileged over its mount namespace
+ * and no locked child covers something below @root->dentry. One answer from
+ * under mount_lock: an umount in between clears ->mnt_ns and takes the
+ * children off the mount, the locked ones too.
+ */
+static bool subtree_unobstructed(const struct path *root)
+{
+ struct mount *mnt = real_mount(root->mnt);
+
+ guard(mount_locked_reader)();
+ return is_mounted(root->mnt) && capable_wrt_mount(mnt) &&
+ !has_locked_children(mnt, root->dentry);
+}
+
static inline int may_decode_fh(struct handle_to_path_ctx *ctx,
unsigned int o_flags)
{
@@ -332,9 +348,7 @@ static inline int may_decode_fh(struct handle_to_path_ctx *ctx,
if (ns_capable(root->mnt->mnt_sb->s_user_ns, CAP_SYS_ADMIN))
ctx->flags = HANDLE_CHECK_PERMS;
- else if (is_mounted(root->mnt) &&
- capable_wrt_mount(real_mount(root->mnt)) &&
- !has_locked_children(real_mount(root->mnt), root->dentry))
+ else if (subtree_unobstructed(root))
ctx->flags = HANDLE_CHECK_PERMS | HANDLE_CHECK_SUBTREE;
else
return -EPERM;

fs/file_table.c

View file
diff --git a/fs/file_table.c b/fs/file_table.c
index c68b8c0..e2665ea 100644
--- a/fs/file_table.c
+++ b/fs/file_table.c
@@ -520,7 +520,8 @@ static void __fput(struct file *file)
dput(dentry);
if (unlikely(mode & FMODE_NEED_UNMOUNT))
dissolve_on_fput(mnt);
- mntput(mnt);
+ else
+ mntput(mnt);
out:
file_free(file);
}

fs/fs_context.c

View file
diff --git a/fs/fs_context.c b/fs/fs_context.c
index 23ad66c..a50584d 100644
--- a/fs/fs_context.c
+++ b/fs/fs_context.c
@@ -315,6 +315,10 @@ struct fs_context *fs_context_for_reconfigure(struct dentry *dentry,
unsigned int sb_flags,
unsigned int sb_flags_mask)
{
+ /* kernel-internal superblocks are nobody's to reconfigure */
+ if (dentry->d_sb->s_flags & SB_NOUSER)
+ return ERR_PTR(-EINVAL);
+
return alloc_fs_context(dentry->d_sb->s_type, dentry, sb_flags,
sb_flags_mask, FS_CONTEXT_FOR_RECONFIGURE);
}

fs/mount.h

View file
diff --git a/fs/mount.h b/fs/mount.h
index 94fcc30..4d23cad 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -6,6 +6,8 @@
#include <linux/fs_pin.h>
extern struct file_system_type nullfs_fs_type;
+extern struct dentry *nullfs_new_file(struct super_block *sb);
+extern struct vfsmount *knullfs;
extern struct list_head notify_list;
struct mnt_namespace {
@@ -33,16 +35,20 @@ struct mnt_namespace {
} __randomize_layout;
struct mnt_pcp {
- int mnt_count;
+ unsigned int mnt_gets;
+ unsigned int mnt_puts;
int mnt_writers;
};
struct mountpoint {
struct hlist_node m_hash;
struct dentry *m_dentry;
- struct hlist_head m_list;
+ struct hlist_head m_list; /* mounts on it and pins */
+ struct hlist_head m_covers; /* covers of unmounted parents */
};
+struct mnt_cover;
+
struct mount {
struct hlist_node mnt_hash;
struct mount *mnt_parent;
@@ -84,7 +90,7 @@ struct mount {
struct mountpoint *mnt_mp; /* where is it mounted */
union {
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
- struct hlist_node mnt_umount;
+ struct hlist_node mnt_umount; /* on the unmounted list */
};
#ifdef CONFIG_FSNOTIFY
struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
@@ -98,7 +104,8 @@ struct mount {
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
struct hlist_head mnt_pins;
- struct hlist_head mnt_stuck_children;
+ struct mnt_cover *mnt_cover; /* the one it may leave behind */
+ struct hlist_head mnt_covers; /* left behind by its unmounted children */
struct hlist_node mnt_ns_visible; /* link in ns->mnt_visible_mounts */
struct mount *overmount; /* mounted on ->mnt_root */
} __randomize_layout;
@@ -233,6 +240,9 @@ static inline struct mnt_namespace *to_mnt_ns(struct ns_common *ns)
#ifdef CONFIG_FSNOTIFY
static inline void mnt_notify_add(struct mount *m)
{
+ /* queued already under this namespace_sem hold */
+ if (!list_empty(&m->to_notify))
+ return;
/* Optimize the case where there are no watches */
if ((m->mnt_ns && m->mnt_ns->n_fsnotify_marks) ||
(m->prev_ns && m->prev_ns->n_fsnotify_marks))

fs/namei.c

View file
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534..c87c14e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2087,6 +2087,33 @@ all_done: // pure jump
}
/*
+ * Be careful in case the dentry is unlinked or renamed. Any mounts
+ * stacked on top of it are going away. We need to make sure that we
+ * don't reveal the underyling dentry during refwalk. In rcuwalk we
+ * catch this via d_seq and another lookup for the name. Give the same
+ * guarantee in refwalk.
+ */
+static bool unlink_may_reveal(struct nameidata *nd, int flags,
+ struct path *path, struct dentry *dentry)
+{
+ /* ".." and LOOKUP_DOWN may land on an unhashed directory */
+ if (flags & WALK_NOFOLLOW)
+ return false;
+ if (nd->flags & LOOKUP_REVAL)
+ return false;
+ /* We crossed onto a mount and the name led us here while it still existed */
+ if (path->mnt != nd->path.mnt)
+ return false;
+ /* only a name on its way out is flagged */
+ if (likely(!cant_mount(dentry)))
+ return false;
+ if (!d_unlinked(dentry))
+ return false;
+ dput(no_free_ptr(path->dentry));
+ return true;
+}
+
+/*
* Do we need to follow links? We _really_ want to be able
* to do this check without having to look at inode->i_op,
* so we keep a cache of "no, this doesn't need follow_link"
@@ -2115,6 +2142,8 @@ static noinline const char *step_into_slowpath(struct nameidata *nd, int flags,
if (unlikely(!inode))
return ERR_PTR(-ENOENT);
} else {
+ if (unlikely(unlink_may_reveal(nd, flags, &path, dentry)))
+ return ERR_PTR(-ESTALE);
dput(nd->path.dentry);
if (nd->path.mnt != path.mnt)
mntput(nd->path.mnt);

fs/namespace.c

View file
diff --git a/fs/namespace.c b/fs/namespace.c
index 580877e..3ed3766 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -80,6 +80,8 @@ static u64 mnt_id_ctr = MNT_UNIQUE_ID_OFFSET;
static struct hlist_head *mount_hashtable __ro_after_init;
static struct hlist_head *mountpoint_hashtable __ro_after_init;
static struct kmem_cache *mnt_cache __ro_after_init;
+struct vfsmount *knullfs __ro_after_init; /* private nullfs instance */
+static struct vfsmount *knullfs_file __ro_after_init; /* its regular file */
static DECLARE_RWSEM(namespace_sem);
static HLIST_HEAD(unmounted); /* protected by namespace_sem */
static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */
@@ -130,7 +132,6 @@ static void mnt_ns_release(struct mnt_namespace *ns)
{
/* keep alive for {list,stat}mount() */
if (ns && refcount_dec_and_test(&ns->passive)) {
- fsnotify_mntns_delete(ns);
put_user_ns(ns->user_ns);
kfree(ns);
}
@@ -210,6 +211,19 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry)
return &mountpoint_hashtable[tmp & mp_hash_mask];
}
+/*
+ * What an unmounted mount leaves behind at its unmounted parent instead of
+ * staying attached to it. A lookup on the parent at the mountpoint finds
+ * a stand-in for as long as the cover is there. The parent frees it.
+ */
+struct mnt_cover {
+ struct hlist_node node; /* parent->mnt_covers, RCU */
+ struct hlist_node pin; /* mp->m_covers, keeps the mountpoint */
+ struct dentry *dentry;
+ struct mountpoint *mp;
+ struct rcu_head rcu;
+};
+
static int mnt_alloc_id(struct mount *mnt)
{
int res;
@@ -249,16 +263,24 @@ void mnt_release_group_id(struct mount *mnt)
mnt->mnt_group_id = 0;
}
-/*
- * vfsmount lock must be held for read
- */
-static inline void mnt_add_count(struct mount *mnt, int n)
+static inline void mnt_inc_count(struct mount *mnt)
+{
+#ifdef CONFIG_SMP
+ this_cpu_inc(mnt->mnt_pcp->mnt_gets);
+#else
+ preempt_disable();
+ mnt->mnt_count++;
+ preempt_enable();
+#endif
+}
+
+static inline void mnt_dec_count(struct mount *mnt)
{
#ifdef CONFIG_SMP
- this_cpu_add(mnt->mnt_pcp->mnt_count, n);
+ this_cpu_inc(mnt->mnt_pcp->mnt_puts);
#else
preempt_disable();
- mnt->mnt_count += n;
+ mnt->mnt_count--;
preempt_enable();
#endif
}
@@ -269,14 +291,17 @@ static inline void mnt_add_count(struct mount *mnt, int n)
int mnt_get_count(struct mount *mnt)
{
#ifdef CONFIG_SMP
- int count = 0;
+ unsigned int gets = 0, puts = 0;
int cpu;
- for_each_possible_cpu(cpu) {
- count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count;
- }
+ /* puts first, so a put counted here has its get counted below */
+ for_each_possible_cpu(cpu)
+ puts += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_puts;
+ smp_mb(); /* pairs with the smp_wmb() in mntput_no_expire() */
+ for_each_possible_cpu(cpu)
+ gets += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_gets;
- return count;
+ return gets - puts;
#else
return mnt->mnt_count;
#endif
@@ -288,6 +313,10 @@ static struct mount *alloc_vfsmnt(const char *name)
if (mnt) {
int err;
+ mnt->mnt_cover = kzalloc_obj(struct mnt_cover, GFP_KERNEL_ACCOUNT);
+ if (!mnt->mnt_cover)
+ goto out_free_cache;
+
err = mnt_alloc_id(mnt);
if (err)
goto out_free_cache;
@@ -305,7 +334,7 @@ static struct mount *alloc_vfsmnt(const char *name)
if (!mnt->mnt_pcp)
goto out_free_devname;
- this_cpu_add(mnt->mnt_pcp->mnt_count, 1);
+ this_cpu_inc(mnt->mnt_pcp->mnt_gets);
#else
mnt->mnt_count = 1;
mnt->mnt_writers = 0;
@@ -320,8 +349,11 @@ static struct mount *alloc_vfsmnt(const char *name)
INIT_HLIST_HEAD(&mnt->mnt_slave_list);
INIT_HLIST_NODE(&mnt->mnt_slave);
INIT_HLIST_NODE(&mnt->mnt_mp_list);
- INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
+ INIT_HLIST_HEAD(&mnt->mnt_covers);
INIT_HLIST_NODE(&mnt->mnt_ns_visible);
+#ifdef CONFIG_FSNOTIFY
+ INIT_LIST_HEAD(&mnt->to_notify);
+#endif
RB_CLEAR_NODE(&mnt->mnt_node);
mnt->mnt.mnt_idmap = &nop_mnt_idmap;
}
@@ -334,6 +366,7 @@ out_free_devname:
out_free_id:
mnt_free_id(mnt);
out_free_cache:
+ kfree(mnt->mnt_cover);
kmem_cache_free(mnt_cache, mnt);
return NULL;
}
@@ -725,6 +758,8 @@ int sb_prepare_remount_readonly(struct super_block *sb)
static void free_vfsmnt(struct mount *mnt)
{
mnt_idmap_put(mnt_idmap(&mnt->mnt));
+ /* NULL if it left it behind */
+ kfree(mnt->mnt_cover);
kfree_const(mnt->mnt_devname);
#ifdef CONFIG_SMP
free_percpu(mnt->mnt_pcp);
@@ -746,13 +781,13 @@ int __legitimize_mnt(struct vfsmount *bastard, unsigned seq)
if (bastard == NULL)
return 0;
mnt = real_mount(bastard);
- mnt_add_count(mnt, 1);
- smp_mb(); // see mntput_no_expire() and do_umount()
+ mnt_inc_count(mnt);
+ smp_mb(); /* see mntput_no_expire_slowpath() and do_umount() */
if (likely(!read_seqretry(&mount_lock, seq)))
return 0;
lock_mount_hash();
if (unlikely(bastard->mnt_flags & (MNT_SYNC_UMOUNT | MNT_DOOMED))) {
- mnt_add_count(mnt, -1);
+ mnt_dec_count(mnt);
unlock_mount_hash();
return 1;
}
@@ -781,20 +816,31 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
* @dentry: dentry of mountpoint
*
* If @mnt has a child mount @c mounted on @dentry find and return it.
+ * If @mnt is unmounted and a child that was unmounted with it left its
+ * cover behind at @dentry, return the stand-in for it instead: knullfs
+ * for a directory, its regular file for anything else.
* Caller must either hold the spinlock component of @mount_lock or
* hold rcu_read_lock(), sample the seqcount component before the call
* and recheck it afterwards.
*
- * Return: The child of @mnt mounted on @dentry or %NULL.
+ * Return: The child of @mnt mounted on @dentry, a stand-in or %NULL.
*/
struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
{
struct hlist_head *head = m_hash(mnt, dentry);
+ struct mnt_cover *cover;
struct mount *p;
hlist_for_each_entry_rcu(p, head, mnt_hash)
if (&p->mnt_parent->mnt == mnt && p->mnt_mountpoint == dentry)
return p;
+ /* an unmounted mount keeps the covers its unmounted children left */
+ /* a lockless caller rechecks mount_lock after a miss, a stale flag is harmless */
+ if (unlikely(data_race(mnt->mnt_flags) & MNT_UMOUNT)) {
+ hlist_for_each_entry_rcu(cover, &real_mount(mnt)->mnt_covers, node)
+ if (cover->dentry == dentry)
+ return real_mount(d_is_dir(dentry) ? knullfs : knullfs_file);
+ }
return NULL;
}
@@ -910,6 +956,7 @@ mountpoint:
mp->m_dentry = dget(dentry);
hlist_add_head(&mp->m_hash, mp_hash(dentry));
INIT_HLIST_HEAD(&mp->m_list);
+ INIT_HLIST_HEAD(&mp->m_covers);
hlist_add_head(&m->node, &mp->m_list);
m->mp = no_free_ptr(mp);
read_sequnlock_excl(&mount_lock);
@@ -922,7 +969,7 @@ mountpoint:
*/
static void maybe_free_mountpoint(struct mountpoint *mp, struct list_head *list)
{
- if (hlist_empty(&mp->m_list)) {
+ if (hlist_empty(&mp->m_list) && hlist_empty(&mp->m_covers)) {
struct dentry *dentry = mp->m_dentry;
spin_lock(&dentry->d_lock);
dentry->d_flags &= ~DCACHE_MOUNTED;
@@ -1010,6 +1057,36 @@ static void umount_mnt(struct mount *mnt)
}
/*
+ * @mnt is unmounted together with its parent and would have stayed attached
+ * to it. Leave its cover behind before it is detached so that a lookup on the
+ * parent at the mountpoint keeps finding a mount instead of what @mnt covered.
+ *
+ * locks: mount_lock[write_seqlock]
+ */
+static void leave_cover(struct mount *mnt)
+{
+ struct mnt_cover *cover = mnt->mnt_cover;
+
+ mnt->mnt_cover = NULL;
+ cover->dentry = mnt->mnt_mountpoint;
+ cover->mp = mnt->mnt_mp;
+ /* keeps the mountpoint once @mnt has let go of it */
+ hlist_add_head(&cover->pin, &cover->mp->m_covers);
+ hlist_add_head_rcu(&cover->node, &mnt->mnt_parent->mnt_covers);
+}
+
+/*
+ * locks: mount_lock[write_seqlock]
+ */
+static void drop_cover(struct mnt_cover *cover, struct list_head *shrink_list)
+{
+ hlist_del_rcu(&cover->node);
+ hlist_del(&cover->pin);
+ maybe_free_mountpoint(cover->mp, shrink_list);
+ kfree_rcu(cover, rcu);
+}
+
+/*
* vfsmount lock must be held for write
*/
void mnt_set_mountpoint(struct mount *mnt,
@@ -1058,7 +1135,10 @@ static void attach_mnt(struct mount *mnt, struct mount *parent,
void mnt_change_mountpoint(struct mount *parent, struct mountpoint *mp, struct mount *mnt)
{
struct mountpoint *old_mp = mnt->mnt_mp;
+ struct mount *old_parent = mnt->mnt_parent;
+ if (old_parent->overmount == mnt)
+ old_parent->overmount = NULL;
list_del_init(&mnt->mnt_child);
hlist_del_init(&mnt->mnt_mp_list);
hlist_del_init_rcu(&mnt->mnt_hash);
@@ -1254,6 +1334,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
mnt->mnt.mnt_flags = READ_ONCE(old->mnt.mnt_flags) &
~MNT_INTERNAL_FLAGS;
+ mnt->mnt_t_flags = old->mnt_t_flags & T_UNBINDABLE;
if (flag & (CL_SLAVE | CL_PRIVATE))
mnt->mnt_group_id = 0; /* not a peer of original */
@@ -1296,8 +1377,6 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
static void cleanup_mnt(struct mount *mnt)
{
- struct hlist_node *p;
- struct mount *m;
/*
* The warning here probably indicates that somebody messed
* up a mnt_want/drop_write() pair. If this happens, the
@@ -1308,10 +1387,6 @@ static void cleanup_mnt(struct mount *mnt)
WARN_ON(mnt_get_writers(mnt));
if (unlikely(mnt->mnt_pins.first))
mnt_pin_kill(mnt);
- hlist_for_each_entry_safe(m, p, &mnt->mnt_stuck_children, mnt_umount) {
- hlist_del(&m->mnt_umount);
- mntput(&m->mnt);
- }
fsnotify_vfsmount_delete(&mnt->mnt);
dput(mnt->mnt.mnt_root);
deactivate_super(mnt->mnt.mnt_sb);
@@ -1337,6 +1412,8 @@ static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput);
static void noinline mntput_no_expire_slowpath(struct mount *mnt)
{
+ struct mnt_cover *cover;
+ struct hlist_node *n;
LIST_HEAD(list);
int count;
@@ -1347,7 +1424,7 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt)
* mount_lock, we'll see their refcount increment here.
*/
smp_mb();
- mnt_add_count(mnt, -1);
+ mnt_dec_count(mnt);
count = mnt_get_count(mnt);
if (count != 0) {
WARN_ON(count < 0);
@@ -1367,13 +1444,10 @@ static void noinline mntput_no_expire_slowpath(struct mount *mnt)
if (unlikely(!list_empty(&mnt->mnt_expire)))
list_del(&mnt->mnt_expire);
- if (unlikely(!list_empty(&mnt->mnt_mounts))) {
- struct mount *p, *tmp;
- list_for_each_entry_safe(p, tmp, &mnt->mnt_mounts, mnt_child) {
- __umount_mnt(p, &list);
- hlist_add_head(&p->mnt_umount, &mnt->mnt_stuck_children);
- }
- }
+ /* nothing stays attached to an unmounted mount */
+ VFS_WARN_ON_ONCE(!list_empty(&mnt->mnt_mounts));
+ hlist_for_each_entry_safe(cover, n, &mnt->mnt_covers, node)
+ drop_cover(cover, &list);
unlock_mount_hash();
shrink_dentry_list(&list);

This diff is incomplete. Download the complete patch.

fs/notify/mark.c

View file

This diff is incomplete. Download the complete patch.

fs/nullfs.c

View file

This diff is incomplete. Download the complete patch.

fs/pnode.c

View file

This diff is incomplete. Download the complete patch.

fs/readdir.c

View file

This diff is incomplete. Download the complete patch.

include/linux/fs.h

View file

This diff is incomplete. Download the complete patch.

include/linux/mount.h

View file

This diff is incomplete. Download the complete patch.

include/linux/nsproxy.h

View file

This diff is incomplete. Download the complete patch.

kernel/fork.c

View file

This diff is incomplete. Download the complete patch.

kernel/nsproxy.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/.gitignore

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/empty_mntns/.gitignore

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/empty_mntns/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/empty_mntns/internal_sb_reconfigure_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/empty_mntns/nullfs_atime_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/empty_mntns/root_readdir_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mntns_unbindable/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/.gitignore

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/config

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/locked_handle_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/mount_cover_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/nsfs_rbind_loop_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/overmount_ns_file_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/settings

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/open_tree_ns/.gitignore

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/open_tree_ns/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_covered_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/overlayfs/.gitignore

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/overlayfs/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/overlayfs/automount_in_layer.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/readdir_hold.h

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/rw_hint_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/umount_propagation/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c

View file

This diff is incomplete. Download the complete patch.