diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-16 07:53:16 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-16 07:53:16 -0700 |
| commit | 1dbb7422053fa0f4fb5f9acc3931617fc0091939 (patch) | |
| tree | d45fbfba7e085a79fd93fa45113e72570be455f5 /tools | |
| parent | 519abe8d9acba75678a2bc91a2f6c855b9d1c54e (diff) | |
| parent | 214897acc8c2e5d6b63189d23ccaed6fa2af45a9 (diff) | |
Merge tag 'vfs-7.3-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs fixes from Christian Brauner:
- Fix a range of use-after-frees during rcu pathwalk caused by non-rcu
frees.
Whatever ->permission(), ->d_revalidate(), ->get_link(), ->d_hash()
and ->d_compare() read in rcu mode has to be freed after a grace
period. umount(2) hides most of it because namespace_unlock() waits
for one, but a chdir() out of a lazily unmounted directory or the
close() of the last file on it drops the last reference without.
* ext4_get_link() reads the sbi and the extent status counters for a
symlink stored in a data block. Both are freed after a grace
period now.
* The unicode map of casefolded ext4, f2fs and tmpfs was freed under
generic_ci_d_hash() and generic_ci_d_compare().
* ntfs3 with nocase freed its sbi and mount options under its dentry
hash and compare. A remount freed the old options right away too.
* eventfs freed an eventfs_inode after an SRCU grace period only
while tracefs_d_revalidate() reads it under rcu_read_lock().
* afs and 9p are only saved by the grace period their private bdi
gets in bdi_unregister(). They stop depending on it.
* create_ipc_ns() leaked the mqueue mount and its superblock on a
late failure. A long-term mount needs kern_unmount().
- Don't let idle threads pin the initramfs.
The idle threads of the secondary cpus are forked from PID 1 with a
private copy of its fs_struct, aren't on the task list so
pivot_root() never moves them and never exit. So the initramfs stayed
pinned forever on every machine with more than one cpu, 70 to 150 MB
in the reports. They share init_fs now like init_task does.
- A mount that copy_tree() leaves out can't take over a lock.
copy_tree() drops mounts of mount namespace files from the copies it
makes for unshare(CLONE_NEWNS) and propagation without looking at
MNT_LOCKED. So root in a user namespace could bind a child's
/proc/<pid>/ns/mnt beneath a locked cover, let it take the lock over,
unshare(CLONE_NEWNS) and read the file. copy_tree() refuses with
EPERM now and the move beneath a locked mount is refused up front.
- Revert "namei: don't reveal overmounted entries in refwalk" from the
rc7 fixes. The bug class is bigger than that patch.
- namei: lookup_fast_for_open() checked ->d_inode while
traverse_mounts() checks the type in d_flags, which is set second, so
a refwalk racing the instantiation could fail an O_CREAT open with
ENOENT. Use d_is_negative() in both places.
- Drop the smp_mb()s in mntput_no_expire_slowpath() and do_umount()
that mnt_get_count()'s own barrier made redundant.
- fsnotify: a mark added while fsnotify_destroy_marks() walks the
connector could be missed and the connector detached, so an fdinfo
read called igrab(NULL). New marks are refused during teardown.
- pipe: a failed second fasync enable on an O_RDWR FIFO removed the
existing reader entry and cleared FASYNC while the writer entry
stayed, a use-after-free in send_sigio() later.
- watch_queue: post_one_notification() could see nr_notes before the
note pool behind it and dereference a NULL array. An unprivileged
user can trigger that with a key watch. Publish nr_notes last.
- pid_namespace: zap_pid_ns_processes() waits without bound for a
tracer outside the namespace to reap its zombies and neither wait was
freezable, so every suspend attempt failed.
- exit: is_child_subreaper and has_child_subreaper shared a word with
other bitfields written without a common lock, so racing prctl()s
lost a bit. They are separate bools now.
- autofs: a notification write that failed on a pipe the daemon had
already replaced put the superblock into catatonic mode again and
closed the replacement. Only do that for the current pipe.
- ovl: reject a lowerdir ending with an unescaped backslash with EINVAL
instead of a confusing ENOENT later.
- ocfs2/dlm: take dlm_reco_state_lock when the debugfs state file walks
the recovery participant list.
- samples/pidfd: fix the userprogs typo that kept pidfd-metadata from
being built.
* tag 'vfs-7.3-rc8.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (24 commits)
Revert "namei: don't reveal overmounted entries in refwalk"
selftests/filesystems: test that a mntns file can't take over a lock
fs: let idle threads share init_fs
namespace: a mount that copy_tree() leaves out can't take over a lock
namei: test for a negative dentry the way traverse_mounts() does
fs: pass flags to copy_fs()
ovl: reject lowerdir ending with unescaped backslash
fsnotify: prevent mark additions during connector teardown
pid_namespace: make zap_pid_ns_processes() freezable
fs: drop the smp_mb()s that mnt_get_count() made redundant
autofs: ignore notification errors from a replaced pipe
ntfs3: free replaced mount options after a grace period
ntfs3: free the sbi after a grace period
unicode: free the map after a grace period
ipc: drop the mqueue mount with kern_unmount() on setup failure
eventfs: free the eventfs_inode after an rcu grace period too
9p: free the session after a grace period
watch_queue: Fix note pool publication race in watch_queue_set_size()
afs: free the super info after a grace period
pipe: preserve existing fasync readers on writer setup failure
...
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c index 0799b2ba545d..e9b2b874dd16 100644 --- a/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c +++ b/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c @@ -9,6 +9,8 @@ #include <errno.h> #include <fcntl.h> #include <sched.h> +#include <stdbool.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -27,6 +29,12 @@ #ifndef MOVE_MOUNT_BENEATH #define MOVE_MOUNT_BENEATH 0x00000200 #endif +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif #define DIR_LEN 64 #define PATH_LEN 192 @@ -120,6 +128,35 @@ static bool covered(const char *path) return fd < 0; } +/* What @path reads, "" if it can't be read. */ +static int read_word(const char *path, char *buf, size_t len) +{ + ssize_t n = 0; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + n = read(fd, buf, len - 1); + close(fd); + } + buf[n > 0 ? n : 0] = 0; + return fd < 0 ? -1 : 0; +} + +/* Put a clone of the mount at @src beneath the top mount at @where. */ +static int move_beneath(const char *src, const char *where) +{ + int fd, ret; + + fd = syscall(__NR_open_tree, AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (fd < 0) + return -1; + ret = syscall(__NR_move_mount, fd, "", AT_FDCWD, where, + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH); + close(fd); + return ret; +} + FIXTURE(locked_mount) { char base[DIR_LEN]; bool tracing; /* debugfs with the tracefs automount is there */ @@ -429,4 +466,89 @@ TEST_F(locked_mount, propagated_copy_keeps_the_cover_locked) EXPECT_EQ(ret, CHILD_OK); } +/* + * A mount of a mount namespace file is left out of the copy that + * unshare(CLONE_NEWNS) makes, together with everything mounted on it, so + * it can't take over the lock of a cover it is moved beneath. The child + * tries just that with the namespace file of a grandchild and gets EINVAL. + * A bind of an ordinary file takes the lock over, and the file is still + * covered in the next copy of the mount namespace. + */ +static int ns_file_child(const char *base) +{ + char secret[PATH_LEN], mine[PATH_LEN], nsfile[PATH_LEN], buf[32]; + int ret = CHILD_OK, pfd[2]; + pid_t pid; + + snprintf(secret, sizeof(secret), "%s/P/secret", base); + snprintf(mine, sizeof(mine), "%s/mine", base); + + if (enter_userns() || pipe(pfd)) + return CHILD_SETUP; + /* the cover is locked */ + if (!umount2(secret, 0) || errno != EINVAL) + return CHILD_STEP1; + + /* a mount namespace newer than our own, so that a bind of it is allowed */ + pid = fork(); + if (pid < 0) + return CHILD_SETUP; + if (pid == 0) { + if (unshare(CLONE_NEWNS) || send_byte(pfd[1])) + _exit(1); + pause(); + _exit(0); + } + if (wait_byte(pfd[0])) + return CHILD_SETUP; + snprintf(nsfile, sizeof(nsfile), "/proc/%d/ns/mnt", pid); + if (!move_beneath(nsfile, secret) || errno != EINVAL) + ret = CHILD_STEP2; + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + if (ret) + return ret; + + /* an ordinary file beneath the cover takes the lock over */ + if (move_beneath(mine, secret)) + return CHILD_STEP3; + if (umount2(secret, 0)) + return CHILD_STEP3; + if (!umount2(secret, 0) || errno != EINVAL) + return CHILD_STEP3; + /* and it is in the next copy of the namespace */ + if (unshare(CLONE_NEWNS)) + return CHILD_SETUP; + if (read_word(secret, buf, sizeof(buf)) || strcmp(buf, "mine")) + return CHILD_STEP4; + return CHILD_OK; +} + +TEST_F(locked_mount, mount_namespace_file_cant_take_the_lock) +{ + char p[PATH_LEN], q[PATH_LEN]; + pid_t pid; + int ret; + + snprintf(p, sizeof(p), "%s/P", self->base); + ASSERT_EQ(mkdir(p, 0755), 0); + ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0); + snprintf(p, sizeof(p), "%s/P/secret", self->base); + ASSERT_EQ(create_file(p, "covered-by-root\n"), 0); + snprintf(q, sizeof(q), "%s/P/empty", self->base); + ASSERT_EQ(create_file(q, ""), 0); + /* the cover: an empty file over the secret */ + ASSERT_EQ(mount(q, p, NULL, MS_BIND, NULL), 0); + snprintf(q, sizeof(q), "%s/mine", self->base); + ASSERT_EQ(create_file(q, "mine"), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + _exit(ns_file_child(self->base)); + ret = wait_child(pid); + TH_LOG("child exit code %d", ret); + EXPECT_EQ(ret, CHILD_OK); +} + TEST_HARNESS_MAIN |
