All of lore.kernel.org
 help / color / mirror / Atom feed
From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
	 Chris Mason <mason@kernel.org>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	 Jan Kara <jack@suse.cz>, Jeff Layton <jlayton@kernel.org>,
	 Aleksa Sarai <cyphar@cyphar.com>,
	Amir Goldstein <amir73il@gmail.com>,
	 bpf@vger.kernel.org,
	"Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH 03/17] selftests/filesystems: check that a busy submount survives a synchronous umount
Date: Wed, 30 Sep 2026 15:31:55 +0200	[thread overview]
Message-ID: <20260930-work-mount-fixes-3-v1-3-be34c83956ae@kernel.org> (raw)
In-Reply-To: <20260930-work-mount-fixes-3-v1-0-be34c83956ae@kernel.org>

Add a test for the shrinkable submounts of a synchronous umount:

- P shared, P1 a slave that is shared in turn, P2 its peer
- B on P/options with copies on P1 and P2, R on top of the copy in P2
- P with B moved below P1, R is the working directory
- umount(P1) slides R to where the copy of B is looked up

The umount fails with EBUSY and R stays mounted. Needs the tracefs
automount below debugfs for the shrinkable mounts.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 .../filesystems/umount_propagation/Makefile        |   2 +-
 .../umount_propagation/shrink_submounts_test.c     | 205 +++++++++++++++++++++
 2 files changed, 206 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/filesystems/umount_propagation/Makefile b/tools/testing/selftests/filesystems/umount_propagation/Makefile
index fc0a0783018b..eb85612abf8d 100644
--- a/tools/testing/selftests/filesystems/umount_propagation/Makefile
+++ b/tools/testing/selftests/filesystems/umount_propagation/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-TEST_GEN_PROGS := umount_propagation_test
+TEST_GEN_PROGS := umount_propagation_test shrink_submounts_test
 
 CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
 
diff --git a/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c
new file mode 100644
index 000000000000..43efb7faf95c
--- /dev/null
+++ b/tools/testing/selftests/filesystems/umount_propagation/shrink_submounts_test.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A synchronous umount first unmounts the shrinkable submounts of the
+ * victim that aren't busy. Every one of them has to be checked right
+ * before it is unmounted: unmounting one can slide a busy mount to where
+ * the propagated copy of the next one is looked up.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/fanotify.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/vfs.h>
+#include <linux/magic.h>
+
+#include "../../kselftest_harness.h"
+
+#ifndef FAN_REPORT_MNT
+#define FAN_REPORT_MNT		0x00004000
+#endif
+#ifndef FAN_MARK_MNTNS
+#define FAN_MARK_MNTNS		0x00000110
+#endif
+#ifndef FAN_MNT_ATTACH
+#define FAN_MNT_ATTACH		0x01000000
+#endif
+#ifndef FAN_MNT_DETACH
+#define FAN_MNT_DETACH		0x02000000
+#endif
+
+#define DIR_LEN 64
+#define PATH_LEN 128
+
+FIXTURE(shrink_submounts) {
+	char base[DIR_LEN];
+	char automount[PATH_LEN];
+	bool mounted;
+	int fan;
+};
+
+/*
+ * Shrinkable mounts come from an automount. The tracefs mount below debugfs
+ * is one and bind mounts inherit the flag.
+ */
+FIXTURE_SETUP(shrink_submounts)
+{
+	struct stat st;
+	char p[PATH_LEN];
+
+	self->mounted = false;
+	self->fan = -1;
+
+	if (geteuid() != 0)
+		SKIP(return, "test requires CAP_SYS_ADMIN");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+
+	snprintf(self->base, sizeof(self->base), "/tmp/shrink_submounts.XXXXXX");
+	ASSERT_NE(mkdtemp(self->base), NULL);
+	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
+	self->mounted = true;
+	ASSERT_EQ(mount(NULL, self->base, NULL, MS_PRIVATE, NULL), 0);
+
+	snprintf(p, sizeof(p), "%s/dbg", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	if (mount("debugfs", p, "debugfs", 0, NULL))
+		SKIP(return, "test requires debugfs");
+	snprintf(self->automount, sizeof(self->automount), "%s/dbg/tracing",
+		 self->base);
+	snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base);
+	if (stat(p, &st))
+		SKIP(return, "test requires the tracefs automount");
+}
+
+FIXTURE_TEARDOWN(shrink_submounts)
+{
+	if (self->fan >= 0)
+		close(self->fan);
+	chdir("/");
+	if (self->mounted)
+		umount2(self->base, MNT_DETACH);
+	rmdir(self->base);
+}
+
+static bool mounted_tmpfs(const char *path)
+{
+	struct statfs st;
+
+	return !statfs(path, &st) && st.f_type == TMPFS_MAGIC;
+}
+
+/*
+ * P is a shared bind mount of the automount, P1 a slave of P that is shared
+ * in turn and P2 its peer. B, another bind mount of the automount, goes on
+ * P/options and propagates copies Bc1 and Bc2 onto P1/options and
+ * P2/options. R, a tmpfs and our working directory, sits on top of Bc2 which
+ * is made private first. P, with B on it, is moved to P1/instances.
+ *
+ * A synchronous umount of P1 unmounts the shrinkable submounts Bc1 and B
+ * first. Unmounting Bc1 takes Bc2 along and slides R to P2/options where
+ * the propagated copy of B is looked up next. R is busy, so B has to stay
+ * and the umount fails with EBUSY.
+ */
+TEST_F(shrink_submounts, busy_mount_moved_into_reach)
+{
+	char p[PATH_LEN], p1[PATH_LEN], p2[PATH_LEN], r[PATH_LEN], cwd[PATH_LEN];
+	int nsfd;
+
+	snprintf(p, sizeof(p), "%s/p", self->base);
+	snprintf(p1, sizeof(p1), "%s/p1", self->base);
+	snprintf(p2, sizeof(p2), "%s/p2", self->base);
+	ASSERT_EQ(mkdir(p, 0755), 0);
+	ASSERT_EQ(mkdir(p1, 0755), 0);
+	ASSERT_EQ(mkdir(p2, 0755), 0);
+
+	/* watch the mount namespace so that the detached mounts get queued */
+	self->fan = fanotify_init(FAN_REPORT_MNT, O_RDONLY);
+	if (self->fan >= 0) {
+		nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+		ASSERT_GE(nsfd, 0);
+		EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_MNTNS,
+					FAN_MNT_ATTACH | FAN_MNT_DETACH, nsfd, NULL), 0);
+		close(nsfd);
+	}
+
+	ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0);
+	ASSERT_EQ(mount(p, p1, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, p1, NULL, MS_SLAVE, NULL), 0);
+	ASSERT_EQ(mount(NULL, p1, NULL, MS_SHARED, NULL), 0);
+	ASSERT_EQ(mount(p1, p2, NULL, MS_BIND, NULL), 0);
+
+	/* B on P/options, copies on P1/options and P2/options */
+	snprintf(r, sizeof(r), "%s/p/options", self->base);
+	ASSERT_EQ(mount(self->automount, r, NULL, MS_BIND, NULL), 0);
+
+	/* R on top of Bc2 */
+	snprintf(r, sizeof(r), "%s/p2/options", self->base);
+	ASSERT_EQ(mount(NULL, r, NULL, MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(mount("R", r, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(chdir(r), 0);
+
+	/* P, with B on it, below the victim */
+	snprintf(cwd, sizeof(cwd), "%s/p1/instances", self->base);
+	ASSERT_EQ(mount(p, cwd, NULL, MS_MOVE, NULL), 0);
+
+	ASSERT_TRUE(mounted_tmpfs(r));
+	ASSERT_EQ(umount2(p1, 0), -1);
+	EXPECT_EQ(errno, EBUSY);
+
+	/* R is still mounted and still our working directory */
+	EXPECT_TRUE(mounted_tmpfs(r));
+	ASSERT_NE(getcwd(cwd, sizeof(cwd)), NULL);
+	EXPECT_STREQ(cwd, r);
+}
+
+/*
+ * T is shared and Q, a slave of T, has T moved into it, so Q receives
+ * propagation from its own child. M, another bind mount of the automount, is
+ * on T/options and that is the dentry T sits on in Q. Unmounting M makes T
+ * the propagated victim at that dentry in Q and takes T along. The shrink
+ * walk of V has just unmounted M and continues in the children of T.
+ */
+TEST_F(shrink_submounts, parent_goes_with_child)
+{
+	char v[PATH_LEN], t[PATH_LEN], q[PATH_LEN], p[PATH_LEN];
+	struct stat before, after;
+
+	snprintf(v, sizeof(v), "%s/v", self->base);
+	snprintf(t, sizeof(t), "%s/v/t", self->base);
+	snprintf(q, sizeof(q), "%s/v/q", self->base);
+	ASSERT_EQ(mkdir(v, 0755), 0);
+	ASSERT_EQ(mount("V", v, "tmpfs", 0, NULL), 0);
+	ASSERT_EQ(mount(NULL, v, NULL, MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(stat(v, &before), 0);
+	ASSERT_EQ(mkdir(t, 0755), 0);
+	ASSERT_EQ(mkdir(q, 0755), 0);
+
+	/* T shared, M on T/options before anything receives from T */
+	ASSERT_EQ(mount(self->automount, t, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, t, NULL, MS_SHARED, NULL), 0);
+	snprintf(p, sizeof(p), "%s/v/t/options", self->base);
+	ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0);
+
+	/* Q, a slave of T, and T moved into Q at the dentry M sits on */
+	ASSERT_EQ(mount(t, q, NULL, MS_BIND, NULL), 0);
+	ASSERT_EQ(mount(NULL, q, NULL, MS_SLAVE, NULL), 0);
+	snprintf(p, sizeof(p), "%s/v/q/options", self->base);
+	ASSERT_EQ(mount(t, p, NULL, MS_MOVE, NULL), 0);
+
+	/* M, T and then Q go, V is empty and can be unmounted */
+	ASSERT_EQ(umount2(v, 0), 0);
+	ASSERT_EQ(stat(v, &after), 0);
+	EXPECT_NE(before.st_dev, after.st_dev);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


  parent reply	other threads:[~2026-09-30 13:32 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30 13:31 [PATCH 00/17] mount: more bugfixes, the Oprah edition Christian Brauner
2026-09-30 13:31 ` [PATCH 01/17] namespace: queue a mount only once for mount notifications Christian Brauner
2026-09-30 13:31 ` [PATCH 02/17] namespace: check a submount for references right before unmounting it Christian Brauner
2026-09-30 13:31 ` Christian Brauner [this message]
2026-09-30 13:44   ` [PATCH 03/17] selftests/filesystems: check that a busy submount survives a synchronous umount sashiko-bot
2026-09-30 13:31 ` [PATCH 04/17] namespace: check a recursive bind mount for mount namespace loops Christian Brauner
2026-09-30 13:31 ` [PATCH 05/17] selftests/filesystems: check that a recursive bind mount can't pin the caller's mount namespace Christian Brauner
2026-09-30 13:31 ` [PATCH 06/17] namespace: keep covered mounts covered in OPEN_TREE_NAMESPACE Christian Brauner
2026-09-30 13:31 ` [PATCH 07/17] selftests/filesystems: check that OPEN_TREE_NAMESPACE keeps mounts covered Christian Brauner
2026-09-30 13:43   ` sashiko-bot
2026-09-30 13:32 ` [PATCH 08/17] namespace: look at the topmost mount for a mount namespace file Christian Brauner
2026-09-30 13:32 ` [PATCH 09/17] selftests/filesystems: check that a mount namespace file on top doesn't bury a mount Christian Brauner
2026-09-30 13:40   ` sashiko-bot
2026-09-30 13:32 ` [PATCH 10/17] namespace: check the mounts before reading their parents in pivot_root() Christian Brauner
2026-09-30 13:32 ` [PATCH 11/17] namespace: don't reconfigure internal superblocks via remount and umount Christian Brauner
2026-09-30 13:32 ` [PATCH 12/17] selftests/filesystems: check that the nullfs root can't be reconfigured Christian Brauner
2026-09-30 13:32 ` [PATCH 13/17] namespace: remove the fsnotify marks of a mount namespace in process context Christian Brauner
2026-09-30 15:07   ` Amir Goldstein
2026-09-30 13:32 ` [PATCH 14/17] fsnotify: detach the connector before destroying its marks Christian Brauner
2026-09-30 13:57   ` sashiko-bot
2026-10-01  9:31   ` Christian Brauner
2026-10-01 10:58     ` Amir Goldstein
2026-10-01 12:06       ` Christian Brauner
2026-09-30 13:32 ` [PATCH 15/17] dcache: don't put a mountpoint on a dentry that's being removed Christian Brauner
2026-09-30 13:32 ` [PATCH 16/17] unshare: don't drop active namespace references that were never taken Christian Brauner
2026-09-30 13:32 ` [PATCH 17/17] namespace: don't let a pseudo dentry become the root of a mount Christian Brauner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260930-work-mount-fixes-3-v1-3-be34c83956ae@kernel.org \
    --to=brauner@kernel.org \
    --cc=amir73il@gmail.com \
    --cc=bpf@vger.kernel.org \
    --cc=cyphar@cyphar.com \
    --cc=jack@suse.cz \
    --cc=jlayton@kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=mason@kernel.org \
    --cc=torvalds@linux-foundation.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.