All of lore.kernel.org
 help / color / mirror / Atom feed
From: Christian Brauner <brauner@kernel.org>
To: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Jann Horn <jannh@google.com>, Jan Kara <jack@suse.cz>,
	 Amir Goldstein <amir73il@gmail.com>,
	linux-fsdevel@vger.kernel.org,
	 Alexander Viro <viro@zeniv.linux.org.uk>,
	 "Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH RFC v2 7/8] selftests/filesystems: check the two-step cycle over crossed loop images
Date: Fri, 25 Sep 2026 00:35:47 +0200	[thread overview]
Message-ID: <20260925-work-mount-knullfs-v2-7-c4aebaa186e9@kernel.org> (raw)
In-Reply-To: <20260925-work-mount-knullfs-v2-0-c4aebaa186e9@kernel.org>

Two tmpfs mounts in a child namespace, each carrying the image of the
loop mount below the other. Check that both loop devices are released
after:

- rmdir of the first tmpfs' mountpoint from the parent namespace, which
  leaves the loop mount below it connected while its image's mount lives

- rmdir of the second, which takes it down with the loop mount whose
  image is on the first, by then dead, tmpfs

Each dead tmpfs then owns a loop mount whose filesystem pins the other.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 .../filesystems/mount_cycle/loop_cycle_test.c      | 88 ++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c
index 67fc3e420c92..4f4c88397861 100644
--- a/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c
+++ b/tools/testing/selftests/filesystems/mount_cycle/loop_cycle_test.c
@@ -204,6 +204,34 @@ static int loop_mount(const char *img, const char *mp)
 	return n;
 }
 
+/*
+ * Two tmpfs mounts, each carrying the image of the loop mount below the
+ * other: the loop mount below /mnt_dir/vol has its image on /mnt_dir/vol2
+ * and the other way round.
+ */
+static int crossed_child(int to_parent, int from_parent)
+{
+	int n[2];
+	char c;
+
+	if (unshare(CLONE_NEWNS) || mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
+		return CHILD_NS;
+	if (mkdir("/mnt_dir/vol", 0755) || mount("tmpfs", "/mnt_dir/vol", "tmpfs", 0, NULL) ||
+	    mkdir("/mnt_dir/vol2", 0755) || mount("tmpfs", "/mnt_dir/vol2", "tmpfs", 0, NULL))
+		return CHILD_NS;
+	n[0] = loop_mount("/mnt_dir/vol2/img", "/mnt_dir/vol/mnt");
+	if (n[0] < 0)
+		return -n[0];
+	n[1] = loop_mount("/mnt_dir/vol/img", "/mnt_dir/vol2/mnt");
+	if (n[1] < 0)
+		return -n[1];
+	if (write(to_parent, n, sizeof(n)) != sizeof(n))
+		return CHILD_PIPE;
+	if (read(from_parent, &c, 1) != 1)
+		return CHILD_PIPE;
+	return CHILD_OK;
+}
+
 /*
  * In its own mount namespace the child mounts a tmpfs on /mnt_dir/vol,
  * puts a filesystem image on it, binds a loop device to the image and
@@ -374,4 +402,64 @@ TEST_F(loop_cycle, dissolved_tree_loop_mount_released)
 	assert_loop_released(_metadata, self);
 }
 
+/*
+ * The cycle in two steps: rmdir of /mnt_dir/vol leaves the loop mount
+ * below it connected while its image's mount, /mnt_dir/vol2, is alive;
+ * then rmdir of /mnt_dir/vol2 takes that one with the loop mount whose
+ * image is on the dead /mnt_dir/vol. Each dead mount now owns a loop
+ * mount whose filesystem pins the other.
+ */
+TEST_F(loop_cycle, crossed_images_released)
+{
+	int to_parent[2], to_child[2];
+	char sysfs[2][64], dev[2][32];
+	int status, n[2] = { -1, -1 };
+	pid_t pid;
+
+	ASSERT_EQ(pipe(to_parent), 0);
+	ASSERT_EQ(pipe(to_child), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		close(to_parent[0]);
+		close(to_child[1]);
+		_exit(crossed_child(to_parent[1], to_child[0]));
+	}
+	close(to_parent[1]);
+	close(to_child[0]);
+
+	if (read(to_parent[0], n, sizeof(n)) != sizeof(n)) {
+		waitpid(pid, &status, 0);
+		if (WIFEXITED(status) && WEXITSTATUS(status) == CHILD_MOUNT)
+			SKIP(return, "test requires a FAT filesystem");
+		ASSERT_EQ(status, 0);
+	}
+	for (int i = 0; i < 2; i++) {
+		snprintf(dev[i], sizeof(dev[i]), "/dev/loop%d", n[i]);
+		snprintf(sysfs[i], sizeof(sysfs[i]), "/sys/block/loop%d/loop/backing_file", n[i]);
+	}
+
+	/* step one: the mount with the first loop mount below it goes */
+	ASSERT_EQ(rmdir("/mnt_dir/vol"), 0);
+
+	/* step two: the other one, with the loop mount whose image is on the first */
+	ASSERT_EQ(rmdir("/mnt_dir/vol2"), 0);
+
+	/* the child leaves: its namespace and every reference it held are gone */
+	ASSERT_EQ(write(to_child[1], "", 1), 1);
+	ASSERT_EQ(waitpid(pid, &status, 0), pid);
+	ASSERT_EQ(status, 0);
+	close(to_parent[0]);
+	close(to_child[1]);
+
+	/* nothing can reach the four mounts any more */
+	for (int i = 0; i < 2; i++) {
+		ASSERT_FALSE(mounted_anywhere(dev[i]));
+		strcpy(self->dev, dev[i]);
+		strcpy(self->sysfs, sysfs[i]);
+		assert_loop_released(_metadata, self);
+	}
+}
+
 TEST_HARNESS_MAIN

-- 
2.53.0


  parent reply	other threads:[~2026-09-24 22:36 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-24 22:35 [PATCH RFC v2 0/8] namespace: prevent UMOUNT_CONNECTED reference count cycles Christian Brauner
2026-09-24 22:35 ` [PATCH RFC v2 1/8] fs: refuse fspick() on internal superblocks Christian Brauner
2026-09-24 22:35 ` [PATCH RFC v2 2/8] fsnotify: record the superblock a connector is accounted on Christian Brauner
2026-09-24 22:35 ` [PATCH RFC v2 3/8] fs: put the old fs_struct before the old namespaces in unshare() Christian Brauner
2026-09-24 22:35 ` [PATCH RFC v2 4/8] namespace: drop the file's reference first in dissolve_on_fput() Christian Brauner
2026-09-24 22:35 ` [PATCH RFC v2 5/8] namespace: prevent UMOUNT_CONNECTED reference count cycles Christian Brauner
2026-09-24 22:35 ` [PATCH RFC v2 6/8] selftests/filesystems: check that a loop mount below a dead mount is released Christian Brauner
2026-09-24 22:35 ` Christian Brauner [this message]
2026-09-24 22:35 ` [PATCH RFC v2 8/8] selftests/filesystems: check that the holders let go of a dead 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=20260925-work-mount-knullfs-v2-7-c4aebaa186e9@kernel.org \
    --to=brauner@kernel.org \
    --cc=amir73il@gmail.com \
    --cc=jack@suse.cz \
    --cc=jannh@google.com \
    --cc=linux-fsdevel@vger.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.