xref: /linux/tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c (revision 7c8a4671dc3247a26a702e5f5996e9f453d7070d)
132f54f2bSChristian Brauner // SPDX-License-Identifier: GPL-2.0-or-later
232f54f2bSChristian Brauner /*
332f54f2bSChristian Brauner  * Test: rootfs overmounted multiple times with chroot into topmost
432f54f2bSChristian Brauner  *
532f54f2bSChristian Brauner  * This test creates a scenario where:
632f54f2bSChristian Brauner  * 1. A new mount namespace is created with a tmpfs root (via pivot_root)
732f54f2bSChristian Brauner  * 2. A mountpoint is created and overmounted multiple times
832f54f2bSChristian Brauner  * 3. The caller chroots into the topmost mount layer
932f54f2bSChristian Brauner  *
1032f54f2bSChristian Brauner  * The test verifies that:
1132f54f2bSChristian Brauner  * - Multiple overmounts create separate mount layers
1232f54f2bSChristian Brauner  * - Each layer's files are isolated
1332f54f2bSChristian Brauner  * - chroot correctly sets the process's root to the topmost layer
1432f54f2bSChristian Brauner  * - After chroot, only the topmost layer's files are visible
1532f54f2bSChristian Brauner  *
1632f54f2bSChristian Brauner  * Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
1732f54f2bSChristian Brauner  */
1832f54f2bSChristian Brauner 
1932f54f2bSChristian Brauner #define _GNU_SOURCE
2032f54f2bSChristian Brauner #include <fcntl.h>
2132f54f2bSChristian Brauner #include <linux/mount.h>
2232f54f2bSChristian Brauner #include <linux/stat.h>
2332f54f2bSChristian Brauner #include <sched.h>
2432f54f2bSChristian Brauner #include <stdio.h>
2532f54f2bSChristian Brauner #include <string.h>
2632f54f2bSChristian Brauner #include <sys/mount.h>
2732f54f2bSChristian Brauner #include <sys/stat.h>
2832f54f2bSChristian Brauner #include <sys/syscall.h>
2932f54f2bSChristian Brauner #include <sys/types.h>
3032f54f2bSChristian Brauner #include <sys/wait.h>
3132f54f2bSChristian Brauner #include <unistd.h>
3232f54f2bSChristian Brauner 
3332f54f2bSChristian Brauner #include "../utils.h"
3432f54f2bSChristian Brauner #include "empty_mntns.h"
3532f54f2bSChristian Brauner #include "kselftest_harness.h"
3632f54f2bSChristian Brauner 
3732f54f2bSChristian Brauner #define NR_OVERMOUNTS 5
3832f54f2bSChristian Brauner 
3932f54f2bSChristian Brauner /*
4032f54f2bSChristian Brauner  * Setup a proper root filesystem using pivot_root.
4132f54f2bSChristian Brauner  * This ensures we own the root directory in our user namespace.
4232f54f2bSChristian Brauner  */
4332f54f2bSChristian Brauner static int setup_root(void)
4432f54f2bSChristian Brauner {
4532f54f2bSChristian Brauner 	char tmpdir[] = "/tmp/overmount_test.XXXXXX";
4632f54f2bSChristian Brauner 	char oldroot[256];
4732f54f2bSChristian Brauner 
4832f54f2bSChristian Brauner 	if (!mkdtemp(tmpdir))
4932f54f2bSChristian Brauner 		return -1;
5032f54f2bSChristian Brauner 
5132f54f2bSChristian Brauner 	/* Mount tmpfs at the temporary directory */
5232f54f2bSChristian Brauner 	if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=10M"))
5332f54f2bSChristian Brauner 		return -1;
5432f54f2bSChristian Brauner 
5532f54f2bSChristian Brauner 	/* Create directory for old root */
5632f54f2bSChristian Brauner 	snprintf(oldroot, sizeof(oldroot), "%s/oldroot", tmpdir);
5732f54f2bSChristian Brauner 	if (mkdir(oldroot, 0755))
5832f54f2bSChristian Brauner 		return -1;
5932f54f2bSChristian Brauner 
6032f54f2bSChristian Brauner 	/* pivot_root to use the tmpfs as new root */
6132f54f2bSChristian Brauner 	if (syscall(SYS_pivot_root, tmpdir, oldroot))
6232f54f2bSChristian Brauner 		return -1;
6332f54f2bSChristian Brauner 
6432f54f2bSChristian Brauner 	if (chdir("/"))
6532f54f2bSChristian Brauner 		return -1;
6632f54f2bSChristian Brauner 
6732f54f2bSChristian Brauner 	/* Unmount old root */
6832f54f2bSChristian Brauner 	if (umount2("/oldroot", MNT_DETACH))
6932f54f2bSChristian Brauner 		return -1;
7032f54f2bSChristian Brauner 
7132f54f2bSChristian Brauner 	/* Remove oldroot directory */
7232f54f2bSChristian Brauner 	if (rmdir("/oldroot"))
7332f54f2bSChristian Brauner 		return -1;
7432f54f2bSChristian Brauner 
7532f54f2bSChristian Brauner 	return 0;
7632f54f2bSChristian Brauner }
7732f54f2bSChristian Brauner 
7832f54f2bSChristian Brauner /*
7932f54f2bSChristian Brauner  * Test scenario:
8032f54f2bSChristian Brauner  * 1. Enter a user namespace to gain CAP_SYS_ADMIN
8132f54f2bSChristian Brauner  * 2. Create a new mount namespace
8232f54f2bSChristian Brauner  * 3. Setup a tmpfs root via pivot_root
8332f54f2bSChristian Brauner  * 4. Create a mountpoint /newroot and overmount it multiple times
8432f54f2bSChristian Brauner  * 5. Create a marker file in each layer
8532f54f2bSChristian Brauner  * 6. Chroot into /newroot (the topmost overmount)
8632f54f2bSChristian Brauner  * 7. Verify we're in the topmost layer (only topmost marker visible)
8732f54f2bSChristian Brauner  */
8832f54f2bSChristian Brauner TEST(overmount_chroot)
8932f54f2bSChristian Brauner {
9032f54f2bSChristian Brauner 	pid_t pid;
9132f54f2bSChristian Brauner 
9232f54f2bSChristian Brauner 	pid = fork();
9332f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
9432f54f2bSChristian Brauner 
9532f54f2bSChristian Brauner 	if (pid == 0) {
9632f54f2bSChristian Brauner 		ssize_t nr_mounts;
9732f54f2bSChristian Brauner 		uint64_t mnt_ids[NR_OVERMOUNTS + 1];
9832f54f2bSChristian Brauner 		uint64_t root_id_before, root_id_after;
9932f54f2bSChristian Brauner 		struct statmount *sm;
10032f54f2bSChristian Brauner 		char marker[64];
10132f54f2bSChristian Brauner 		int fd, i;
10232f54f2bSChristian Brauner 
10332f54f2bSChristian Brauner 		/* Step 1: Enter user namespace for privileges */
10432f54f2bSChristian Brauner 		if (enter_userns())
10532f54f2bSChristian Brauner 			_exit(1);
10632f54f2bSChristian Brauner 
10732f54f2bSChristian Brauner 		/* Step 2: Create a new mount namespace */
10832f54f2bSChristian Brauner 		if (unshare(CLONE_NEWNS))
10932f54f2bSChristian Brauner 			_exit(2);
11032f54f2bSChristian Brauner 
11132f54f2bSChristian Brauner 		/* Step 3: Make the mount tree private */
11232f54f2bSChristian Brauner 		if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
11332f54f2bSChristian Brauner 			_exit(3);
11432f54f2bSChristian Brauner 
11532f54f2bSChristian Brauner 		/* Step 4: Setup a proper tmpfs root via pivot_root */
11632f54f2bSChristian Brauner 		if (setup_root())
11732f54f2bSChristian Brauner 			_exit(4);
11832f54f2bSChristian Brauner 
11932f54f2bSChristian Brauner 		/* Create the base mount point for overmounting */
12032f54f2bSChristian Brauner 		if (mkdir("/newroot", 0755))
12132f54f2bSChristian Brauner 			_exit(5);
12232f54f2bSChristian Brauner 
12332f54f2bSChristian Brauner 		/* Mount base tmpfs on /newroot */
12432f54f2bSChristian Brauner 		if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M"))
12532f54f2bSChristian Brauner 			_exit(6);
12632f54f2bSChristian Brauner 
12732f54f2bSChristian Brauner 		/* Record base mount ID */
12832f54f2bSChristian Brauner 		mnt_ids[0] = get_unique_mnt_id("/newroot");
12932f54f2bSChristian Brauner 		if (!mnt_ids[0])
13032f54f2bSChristian Brauner 			_exit(7);
13132f54f2bSChristian Brauner 
13232f54f2bSChristian Brauner 		/* Create marker in base layer */
13332f54f2bSChristian Brauner 		fd = open("/newroot/layer_0", O_CREAT | O_RDWR, 0644);
13432f54f2bSChristian Brauner 		if (fd < 0)
13532f54f2bSChristian Brauner 			_exit(8);
13632f54f2bSChristian Brauner 		if (write(fd, "layer_0", 7) != 7) {
13732f54f2bSChristian Brauner 			close(fd);
13832f54f2bSChristian Brauner 			_exit(9);
13932f54f2bSChristian Brauner 		}
14032f54f2bSChristian Brauner 		close(fd);
14132f54f2bSChristian Brauner 
14232f54f2bSChristian Brauner 		/* Step 5: Overmount /newroot multiple times with tmpfs */
14332f54f2bSChristian Brauner 		for (i = 0; i < NR_OVERMOUNTS; i++) {
14432f54f2bSChristian Brauner 			if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M"))
14532f54f2bSChristian Brauner 				_exit(10);
14632f54f2bSChristian Brauner 
14732f54f2bSChristian Brauner 			/* Record mount ID for this layer */
14832f54f2bSChristian Brauner 			mnt_ids[i + 1] = get_unique_mnt_id("/newroot");
14932f54f2bSChristian Brauner 			if (!mnt_ids[i + 1])
15032f54f2bSChristian Brauner 				_exit(11);
15132f54f2bSChristian Brauner 
15232f54f2bSChristian Brauner 			/* Create a marker file in each layer */
15332f54f2bSChristian Brauner 			snprintf(marker, sizeof(marker), "/newroot/layer_%d", i + 1);
15432f54f2bSChristian Brauner 			fd = open(marker, O_CREAT | O_RDWR, 0644);
15532f54f2bSChristian Brauner 			if (fd < 0)
15632f54f2bSChristian Brauner 				_exit(12);
15732f54f2bSChristian Brauner 
15832f54f2bSChristian Brauner 			if (write(fd, marker, strlen(marker)) != (ssize_t)strlen(marker)) {
15932f54f2bSChristian Brauner 				close(fd);
16032f54f2bSChristian Brauner 				_exit(13);
16132f54f2bSChristian Brauner 			}
16232f54f2bSChristian Brauner 			close(fd);
16332f54f2bSChristian Brauner 		}
16432f54f2bSChristian Brauner 
16532f54f2bSChristian Brauner 		/* Verify mount count increased */
16632f54f2bSChristian Brauner 		nr_mounts = count_mounts();
16732f54f2bSChristian Brauner 		if (nr_mounts < NR_OVERMOUNTS + 2)
16832f54f2bSChristian Brauner 			_exit(14);
16932f54f2bSChristian Brauner 
17032f54f2bSChristian Brauner 		/* Record root mount ID before chroot */
17132f54f2bSChristian Brauner 		root_id_before = get_unique_mnt_id("/newroot");
17232f54f2bSChristian Brauner 
17332f54f2bSChristian Brauner 		/* Verify this is the topmost layer's mount */
17432f54f2bSChristian Brauner 		if (root_id_before != mnt_ids[NR_OVERMOUNTS])
17532f54f2bSChristian Brauner 			_exit(15);
17632f54f2bSChristian Brauner 
17732f54f2bSChristian Brauner 		/* Step 6: Chroot into /newroot (the topmost overmount) */
17832f54f2bSChristian Brauner 		if (chroot("/newroot"))
17932f54f2bSChristian Brauner 			_exit(16);
18032f54f2bSChristian Brauner 
18132f54f2bSChristian Brauner 		/* Change to root directory within the chroot */
18232f54f2bSChristian Brauner 		if (chdir("/"))
18332f54f2bSChristian Brauner 			_exit(17);
18432f54f2bSChristian Brauner 
18532f54f2bSChristian Brauner 		/* Step 7: Verify we're in the topmost layer */
18632f54f2bSChristian Brauner 		root_id_after = get_unique_mnt_id("/");
18732f54f2bSChristian Brauner 
18832f54f2bSChristian Brauner 		/* The mount ID should be the same as the topmost layer */
18932f54f2bSChristian Brauner 		if (root_id_after != mnt_ids[NR_OVERMOUNTS])
19032f54f2bSChristian Brauner 			_exit(18);
19132f54f2bSChristian Brauner 
19232f54f2bSChristian Brauner 		/* Verify the topmost layer's marker file exists */
19332f54f2bSChristian Brauner 		snprintf(marker, sizeof(marker), "/layer_%d", NR_OVERMOUNTS);
19432f54f2bSChristian Brauner 		if (access(marker, F_OK))
19532f54f2bSChristian Brauner 			_exit(19);
19632f54f2bSChristian Brauner 
19732f54f2bSChristian Brauner 		/* Verify we cannot see markers from lower layers (they're hidden) */
19832f54f2bSChristian Brauner 		for (i = 0; i < NR_OVERMOUNTS; i++) {
19932f54f2bSChristian Brauner 			snprintf(marker, sizeof(marker), "/layer_%d", i);
20032f54f2bSChristian Brauner 			if (access(marker, F_OK) == 0)
20132f54f2bSChristian Brauner 				_exit(20);
20232f54f2bSChristian Brauner 		}
20332f54f2bSChristian Brauner 
20432f54f2bSChristian Brauner 		/* Verify the root mount is tmpfs */
20532f54f2bSChristian Brauner 		sm = statmount_alloc(root_id_after, 0,
20632f54f2bSChristian Brauner 				     STATMOUNT_MNT_BASIC | STATMOUNT_MNT_ROOT |
207*1a398a23SChristian Brauner 				     STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE, 0);
20832f54f2bSChristian Brauner 		if (!sm)
20932f54f2bSChristian Brauner 			_exit(21);
21032f54f2bSChristian Brauner 
21132f54f2bSChristian Brauner 		if (sm->mask & STATMOUNT_FS_TYPE) {
21232f54f2bSChristian Brauner 			if (strcmp(sm->str + sm->fs_type, "tmpfs") != 0) {
21332f54f2bSChristian Brauner 				free(sm);
21432f54f2bSChristian Brauner 				_exit(22);
21532f54f2bSChristian Brauner 			}
21632f54f2bSChristian Brauner 		}
21732f54f2bSChristian Brauner 
21832f54f2bSChristian Brauner 		free(sm);
21932f54f2bSChristian Brauner 		_exit(0);
22032f54f2bSChristian Brauner 	}
22132f54f2bSChristian Brauner 
22232f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
22332f54f2bSChristian Brauner }
22432f54f2bSChristian Brauner 
22532f54f2bSChristian Brauner TEST_HARNESS_MAIN
226