xref: /linux/tools/testing/selftests/filesystems/empty_mntns/overmount_chroot_test.c (revision 40286d6379aacfcc053253ef78dc78b09addffda)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Test: rootfs overmounted multiple times with chroot into topmost
4  *
5  * This test creates a scenario where:
6  * 1. A new mount namespace is created with a tmpfs root (via pivot_root)
7  * 2. A mountpoint is created and overmounted multiple times
8  * 3. The caller chroots into the topmost mount layer
9  *
10  * The test verifies that:
11  * - Multiple overmounts create separate mount layers
12  * - Each layer's files are isolated
13  * - chroot correctly sets the process's root to the topmost layer
14  * - After chroot, only the topmost layer's files are visible
15  *
16  * Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
17  */
18 
19 #define _GNU_SOURCE
20 #include <fcntl.h>
21 #include <linux/mount.h>
22 #include <linux/stat.h>
23 #include <sched.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <sys/mount.h>
27 #include <sys/stat.h>
28 #include <sys/syscall.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <unistd.h>
32 
33 #include "../utils.h"
34 #include "empty_mntns.h"
35 #include "kselftest_harness.h"
36 
37 #define NR_OVERMOUNTS 5
38 
39 /*
40  * Setup a proper root filesystem using pivot_root.
41  * This ensures we own the root directory in our user namespace.
42  */
43 static int setup_root(void)
44 {
45 	char tmpdir[] = "/tmp/overmount_test.XXXXXX";
46 	char oldroot[256];
47 
48 	if (!mkdtemp(tmpdir))
49 		return -1;
50 
51 	/* Mount tmpfs at the temporary directory */
52 	if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=10M"))
53 		return -1;
54 
55 	/* Create directory for old root */
56 	snprintf(oldroot, sizeof(oldroot), "%s/oldroot", tmpdir);
57 	if (mkdir(oldroot, 0755))
58 		return -1;
59 
60 	/* pivot_root to use the tmpfs as new root */
61 	if (syscall(SYS_pivot_root, tmpdir, oldroot))
62 		return -1;
63 
64 	if (chdir("/"))
65 		return -1;
66 
67 	/* Unmount old root */
68 	if (umount2("/oldroot", MNT_DETACH))
69 		return -1;
70 
71 	/* Remove oldroot directory */
72 	if (rmdir("/oldroot"))
73 		return -1;
74 
75 	return 0;
76 }
77 
78 /*
79  * Test scenario:
80  * 1. Enter a user namespace to gain CAP_SYS_ADMIN
81  * 2. Create a new mount namespace
82  * 3. Setup a tmpfs root via pivot_root
83  * 4. Create a mountpoint /newroot and overmount it multiple times
84  * 5. Create a marker file in each layer
85  * 6. Chroot into /newroot (the topmost overmount)
86  * 7. Verify we're in the topmost layer (only topmost marker visible)
87  */
88 TEST(overmount_chroot)
89 {
90 	pid_t pid;
91 
92 	pid = fork();
93 	ASSERT_GE(pid, 0);
94 
95 	if (pid == 0) {
96 		ssize_t nr_mounts;
97 		uint64_t mnt_ids[NR_OVERMOUNTS + 1];
98 		uint64_t root_id_before, root_id_after;
99 		struct statmount *sm;
100 		char marker[64];
101 		int fd, i;
102 
103 		/* Step 1: Enter user namespace for privileges */
104 		if (enter_userns())
105 			_exit(1);
106 
107 		/* Step 2: Create a new mount namespace */
108 		if (unshare(CLONE_NEWNS))
109 			_exit(2);
110 
111 		/* Step 3: Make the mount tree private */
112 		if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
113 			_exit(3);
114 
115 		/* Step 4: Setup a proper tmpfs root via pivot_root */
116 		if (setup_root())
117 			_exit(4);
118 
119 		/* Create the base mount point for overmounting */
120 		if (mkdir("/newroot", 0755))
121 			_exit(5);
122 
123 		/* Mount base tmpfs on /newroot */
124 		if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M"))
125 			_exit(6);
126 
127 		/* Record base mount ID */
128 		mnt_ids[0] = get_unique_mnt_id("/newroot");
129 		if (!mnt_ids[0])
130 			_exit(7);
131 
132 		/* Create marker in base layer */
133 		fd = open("/newroot/layer_0", O_CREAT | O_RDWR, 0644);
134 		if (fd < 0)
135 			_exit(8);
136 		if (write(fd, "layer_0", 7) != 7) {
137 			close(fd);
138 			_exit(9);
139 		}
140 		close(fd);
141 
142 		/* Step 5: Overmount /newroot multiple times with tmpfs */
143 		for (i = 0; i < NR_OVERMOUNTS; i++) {
144 			if (mount("tmpfs", "/newroot", "tmpfs", 0, "size=1M"))
145 				_exit(10);
146 
147 			/* Record mount ID for this layer */
148 			mnt_ids[i + 1] = get_unique_mnt_id("/newroot");
149 			if (!mnt_ids[i + 1])
150 				_exit(11);
151 
152 			/* Create a marker file in each layer */
153 			snprintf(marker, sizeof(marker), "/newroot/layer_%d", i + 1);
154 			fd = open(marker, O_CREAT | O_RDWR, 0644);
155 			if (fd < 0)
156 				_exit(12);
157 
158 			if (write(fd, marker, strlen(marker)) != (ssize_t)strlen(marker)) {
159 				close(fd);
160 				_exit(13);
161 			}
162 			close(fd);
163 		}
164 
165 		/* Verify mount count increased */
166 		nr_mounts = count_mounts();
167 		if (nr_mounts < NR_OVERMOUNTS + 2)
168 			_exit(14);
169 
170 		/* Record root mount ID before chroot */
171 		root_id_before = get_unique_mnt_id("/newroot");
172 
173 		/* Verify this is the topmost layer's mount */
174 		if (root_id_before != mnt_ids[NR_OVERMOUNTS])
175 			_exit(15);
176 
177 		/* Step 6: Chroot into /newroot (the topmost overmount) */
178 		if (chroot("/newroot"))
179 			_exit(16);
180 
181 		/* Change to root directory within the chroot */
182 		if (chdir("/"))
183 			_exit(17);
184 
185 		/* Step 7: Verify we're in the topmost layer */
186 		root_id_after = get_unique_mnt_id("/");
187 
188 		/* The mount ID should be the same as the topmost layer */
189 		if (root_id_after != mnt_ids[NR_OVERMOUNTS])
190 			_exit(18);
191 
192 		/* Verify the topmost layer's marker file exists */
193 		snprintf(marker, sizeof(marker), "/layer_%d", NR_OVERMOUNTS);
194 		if (access(marker, F_OK))
195 			_exit(19);
196 
197 		/* Verify we cannot see markers from lower layers (they're hidden) */
198 		for (i = 0; i < NR_OVERMOUNTS; i++) {
199 			snprintf(marker, sizeof(marker), "/layer_%d", i);
200 			if (access(marker, F_OK) == 0)
201 				_exit(20);
202 		}
203 
204 		/* Verify the root mount is tmpfs */
205 		sm = statmount_alloc(root_id_after, 0,
206 				     STATMOUNT_MNT_BASIC | STATMOUNT_MNT_ROOT |
207 				     STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE, 0);
208 		if (!sm)
209 			_exit(21);
210 
211 		if (sm->mask & STATMOUNT_FS_TYPE) {
212 			if (strcmp(sm->str + sm->fs_type, "tmpfs") != 0) {
213 				free(sm);
214 				_exit(22);
215 			}
216 		}
217 
218 		free(sm);
219 		_exit(0);
220 	}
221 
222 	ASSERT_EQ(wait_for_pid(pid), 0);
223 }
224 
225 TEST_HARNESS_MAIN
226