xref: /linux/tools/testing/selftests/filesystems/umount_propagation/locked_mount_test.c (revision 1dbb7422053fa0f4fb5f9acc3931617fc0091939)
1921c14fdSChristian Brauner // SPDX-License-Identifier: GPL-2.0
2921c14fdSChristian Brauner /*
3921c14fdSChristian Brauner  * MNT_LOCKED keeps the owner of a user namespace from revealing what a
4921c14fdSChristian Brauner  * mount covers. The lock has to be set on the copy in that namespace and
5921c14fdSChristian Brauner  * only there, it has to survive the expiry of a mount placed beneath the
6921c14fdSChristian Brauner  * locked one, and it has to survive the propagated umount of such a copy.
7921c14fdSChristian Brauner  */
8921c14fdSChristian Brauner #define _GNU_SOURCE
9921c14fdSChristian Brauner #include <errno.h>
10921c14fdSChristian Brauner #include <fcntl.h>
11921c14fdSChristian Brauner #include <sched.h>
12*eadfe727SChristian Brauner #include <stdbool.h>
13*eadfe727SChristian Brauner #include <signal.h>
14921c14fdSChristian Brauner #include <stdio.h>
15921c14fdSChristian Brauner #include <stdlib.h>
16921c14fdSChristian Brauner #include <string.h>
17921c14fdSChristian Brauner #include <unistd.h>
18921c14fdSChristian Brauner #include <sys/mount.h>
19921c14fdSChristian Brauner #include <sys/prctl.h>
20921c14fdSChristian Brauner #include <sys/stat.h>
21921c14fdSChristian Brauner #include <sys/syscall.h>
22921c14fdSChristian Brauner #include <sys/wait.h>
23921c14fdSChristian Brauner 
24921c14fdSChristian Brauner #include "../../kselftest_harness.h"
25921c14fdSChristian Brauner 
26921c14fdSChristian Brauner #ifndef MOVE_MOUNT_F_EMPTY_PATH
27921c14fdSChristian Brauner #define MOVE_MOUNT_F_EMPTY_PATH	0x00000004
28921c14fdSChristian Brauner #endif
29921c14fdSChristian Brauner #ifndef MOVE_MOUNT_BENEATH
30921c14fdSChristian Brauner #define MOVE_MOUNT_BENEATH	0x00000200
31921c14fdSChristian Brauner #endif
32*eadfe727SChristian Brauner #ifndef OPEN_TREE_CLONE
33*eadfe727SChristian Brauner #define OPEN_TREE_CLONE		1
34*eadfe727SChristian Brauner #endif
35*eadfe727SChristian Brauner #ifndef OPEN_TREE_CLOEXEC
36*eadfe727SChristian Brauner #define OPEN_TREE_CLOEXEC	O_CLOEXEC
37*eadfe727SChristian Brauner #endif
38921c14fdSChristian Brauner 
39921c14fdSChristian Brauner #define DIR_LEN		64
40921c14fdSChristian Brauner #define PATH_LEN	192
41921c14fdSChristian Brauner #define FLAGS		(MS_NOSUID | MS_NODEV | MS_NOEXEC)
42921c14fdSChristian Brauner 
43921c14fdSChristian Brauner /* exit codes of the children, each test says what they mean */
44921c14fdSChristian Brauner enum {
45921c14fdSChristian Brauner 	CHILD_OK,
46921c14fdSChristian Brauner 	CHILD_SETUP,
47921c14fdSChristian Brauner 	CHILD_STEP1,
48921c14fdSChristian Brauner 	CHILD_STEP2,
49921c14fdSChristian Brauner 	CHILD_STEP3,
50921c14fdSChristian Brauner 	CHILD_STEP4,
51921c14fdSChristian Brauner };
52921c14fdSChristian Brauner 
write_file(const char * path,const char * s)53921c14fdSChristian Brauner static int write_file(const char *path, const char *s)
54921c14fdSChristian Brauner {
55921c14fdSChristian Brauner 	ssize_t n = -1;
56921c14fdSChristian Brauner 	int fd;
57921c14fdSChristian Brauner 
58921c14fdSChristian Brauner 	fd = open(path, O_WRONLY | O_CLOEXEC);
59921c14fdSChristian Brauner 	if (fd >= 0) {
60921c14fdSChristian Brauner 		n = write(fd, s, strlen(s));
61921c14fdSChristian Brauner 		close(fd);
62921c14fdSChristian Brauner 	}
63921c14fdSChristian Brauner 	return n == (ssize_t)strlen(s) ? 0 : -1;
64921c14fdSChristian Brauner }
65921c14fdSChristian Brauner 
create_file(const char * path,const char * s)66921c14fdSChristian Brauner static int create_file(const char *path, const char *s)
67921c14fdSChristian Brauner {
68921c14fdSChristian Brauner 	ssize_t n = -1;
69921c14fdSChristian Brauner 	int fd;
70921c14fdSChristian Brauner 
71921c14fdSChristian Brauner 	fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
72921c14fdSChristian Brauner 	if (fd >= 0) {
73921c14fdSChristian Brauner 		n = write(fd, s, strlen(s));
74921c14fdSChristian Brauner 		close(fd);
75921c14fdSChristian Brauner 	}
76921c14fdSChristian Brauner 	return n == (ssize_t)strlen(s) ? 0 : -1;
77921c14fdSChristian Brauner }
78921c14fdSChristian Brauner 
79921c14fdSChristian Brauner /* Root in a new user namespace with a copy of the mount namespace. */
enter_userns(void)80921c14fdSChristian Brauner static int enter_userns(void)
81921c14fdSChristian Brauner {
82921c14fdSChristian Brauner 	uid_t uid = getuid();
83921c14fdSChristian Brauner 	gid_t gid = getgid();
84921c14fdSChristian Brauner 	char map[32];
85921c14fdSChristian Brauner 
86921c14fdSChristian Brauner 	prctl(PR_SET_DUMPABLE, 1);
87921c14fdSChristian Brauner 	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
88921c14fdSChristian Brauner 		return -1;
89921c14fdSChristian Brauner 	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
90921c14fdSChristian Brauner 		return -1;
91921c14fdSChristian Brauner 	snprintf(map, sizeof(map), "0 %d 1", uid);
92921c14fdSChristian Brauner 	if (write_file("/proc/self/uid_map", map))
93921c14fdSChristian Brauner 		return -1;
94921c14fdSChristian Brauner 	snprintf(map, sizeof(map), "0 %d 1", gid);
95921c14fdSChristian Brauner 	if (write_file("/proc/self/gid_map", map))
96921c14fdSChristian Brauner 		return -1;
97921c14fdSChristian Brauner 	return setgid(0) || setuid(0) ? -1 : 0;
98921c14fdSChristian Brauner }
99921c14fdSChristian Brauner 
wait_child(pid_t pid)100921c14fdSChristian Brauner static int wait_child(pid_t pid)
101921c14fdSChristian Brauner {
102921c14fdSChristian Brauner 	int status;
103921c14fdSChristian Brauner 
104921c14fdSChristian Brauner 	if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
105921c14fdSChristian Brauner 		return -1;
106921c14fdSChristian Brauner 	return WEXITSTATUS(status);
107921c14fdSChristian Brauner }
108921c14fdSChristian Brauner 
wait_byte(int fd)109921c14fdSChristian Brauner static int wait_byte(int fd)
110921c14fdSChristian Brauner {
111921c14fdSChristian Brauner 	char c;
112921c14fdSChristian Brauner 
113921c14fdSChristian Brauner 	return read(fd, &c, 1) == 1 ? 0 : -1;
114921c14fdSChristian Brauner }
115921c14fdSChristian Brauner 
send_byte(int fd)116921c14fdSChristian Brauner static int send_byte(int fd)
117921c14fdSChristian Brauner {
118921c14fdSChristian Brauner 	return write(fd, "x", 1) == 1 ? 0 : -1;
119921c14fdSChristian Brauner }
120921c14fdSChristian Brauner 
121921c14fdSChristian Brauner /* A read of @path fails: the file is covered. */
covered(const char * path)122921c14fdSChristian Brauner static bool covered(const char *path)
123921c14fdSChristian Brauner {
124921c14fdSChristian Brauner 	int fd = open(path, O_RDONLY | O_CLOEXEC);
125921c14fdSChristian Brauner 
126921c14fdSChristian Brauner 	if (fd >= 0)
127921c14fdSChristian Brauner 		close(fd);
128921c14fdSChristian Brauner 	return fd < 0;
129921c14fdSChristian Brauner }
130921c14fdSChristian Brauner 
131*eadfe727SChristian Brauner /* What @path reads, "" if it can't be read. */
read_word(const char * path,char * buf,size_t len)132*eadfe727SChristian Brauner static int read_word(const char *path, char *buf, size_t len)
133*eadfe727SChristian Brauner {
134*eadfe727SChristian Brauner 	ssize_t n = 0;
135*eadfe727SChristian Brauner 	int fd;
136*eadfe727SChristian Brauner 
137*eadfe727SChristian Brauner 	fd = open(path, O_RDONLY | O_CLOEXEC);
138*eadfe727SChristian Brauner 	if (fd >= 0) {
139*eadfe727SChristian Brauner 		n = read(fd, buf, len - 1);
140*eadfe727SChristian Brauner 		close(fd);
141*eadfe727SChristian Brauner 	}
142*eadfe727SChristian Brauner 	buf[n > 0 ? n : 0] = 0;
143*eadfe727SChristian Brauner 	return fd < 0 ? -1 : 0;
144*eadfe727SChristian Brauner }
145*eadfe727SChristian Brauner 
146*eadfe727SChristian Brauner /* Put a clone of the mount at @src beneath the top mount at @where. */
move_beneath(const char * src,const char * where)147*eadfe727SChristian Brauner static int move_beneath(const char *src, const char *where)
148*eadfe727SChristian Brauner {
149*eadfe727SChristian Brauner 	int fd, ret;
150*eadfe727SChristian Brauner 
151*eadfe727SChristian Brauner 	fd = syscall(__NR_open_tree, AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
152*eadfe727SChristian Brauner 	if (fd < 0)
153*eadfe727SChristian Brauner 		return -1;
154*eadfe727SChristian Brauner 	ret = syscall(__NR_move_mount, fd, "", AT_FDCWD, where,
155*eadfe727SChristian Brauner 		      MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH);
156*eadfe727SChristian Brauner 	close(fd);
157*eadfe727SChristian Brauner 	return ret;
158*eadfe727SChristian Brauner }
159*eadfe727SChristian Brauner 
FIXTURE(locked_mount)160921c14fdSChristian Brauner FIXTURE(locked_mount) {
161921c14fdSChristian Brauner 	char base[DIR_LEN];
162921c14fdSChristian Brauner 	bool tracing;		/* debugfs with the tracefs automount is there */
163921c14fdSChristian Brauner };
164921c14fdSChristian Brauner 
FIXTURE_SETUP(locked_mount)165921c14fdSChristian Brauner FIXTURE_SETUP(locked_mount)
166921c14fdSChristian Brauner {
167921c14fdSChristian Brauner 	char p[PATH_LEN];
168921c14fdSChristian Brauner 	struct stat st;
169921c14fdSChristian Brauner 
170921c14fdSChristian Brauner 	if (geteuid())
171921c14fdSChristian Brauner 		SKIP(return, "test requires root");
172921c14fdSChristian Brauner 
173921c14fdSChristian Brauner 	snprintf(self->base, sizeof(self->base), "/tmp/locked_mount.XXXXXX");
174921c14fdSChristian Brauner 	ASSERT_NE(mkdtemp(self->base), NULL);
175921c14fdSChristian Brauner 	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
176921c14fdSChristian Brauner 	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
177921c14fdSChristian Brauner 	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, "mode=0755"), 0);
178921c14fdSChristian Brauner 
179921c14fdSChristian Brauner 	/* an automount that a user can name: the tracefs below debugfs */
180921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/dbg", self->base);
181921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
182921c14fdSChristian Brauner 	self->tracing = !mount("debugfs", p, "debugfs", FLAGS, NULL);
183921c14fdSChristian Brauner 	if (self->tracing) {
184921c14fdSChristian Brauner 		/* the cases trigger it themselves */
185921c14fdSChristian Brauner 		snprintf(p, sizeof(p), "%s/dbg/tracing", self->base);
186921c14fdSChristian Brauner 		self->tracing = !fstatat(AT_FDCWD, p, &st, AT_NO_AUTOMOUNT) &&
187921c14fdSChristian Brauner 				S_ISDIR(st.st_mode);
188921c14fdSChristian Brauner 	}
189921c14fdSChristian Brauner }
190921c14fdSChristian Brauner 
FIXTURE_TEARDOWN(locked_mount)191921c14fdSChristian Brauner FIXTURE_TEARDOWN(locked_mount)
192921c14fdSChristian Brauner {
193921c14fdSChristian Brauner 	umount2(self->base, MNT_DETACH);
194921c14fdSChristian Brauner 	rmdir(self->base);
195921c14fdSChristian Brauner }
196921c14fdSChristian Brauner 
197921c14fdSChristian Brauner /*
198921c14fdSChristian Brauner  * The child keeps a directory descriptor on the host's debugfs mount, moves
199921c14fdSChristian Brauner  * to a user namespace of its own and triggers the automount through the
200921c14fdSChristian Brauner  * descriptor. The mount goes below the host's mount and propagates into the
201921c14fdSChristian Brauner  * child's copy. The child's copy has to be locked, the host's not.
202921c14fdSChristian Brauner  */
automount_child(const char * base,int dfd,int to_host,int from_host)203921c14fdSChristian Brauner static int automount_child(const char *base, int dfd, int to_host, int from_host)
204921c14fdSChristian Brauner {
205921c14fdSChristian Brauner 	char p[PATH_LEN];
206921c14fdSChristian Brauner 	struct stat st;
207921c14fdSChristian Brauner 
208921c14fdSChristian Brauner 	if (enter_userns())
209921c14fdSChristian Brauner 		return CHILD_SETUP;
210921c14fdSChristian Brauner 	if (fstatat(dfd, "tracing/.", &st, 0))
211921c14fdSChristian Brauner 		return CHILD_STEP1;
212921c14fdSChristian Brauner 	if (send_byte(to_host) || wait_byte(from_host))
213921c14fdSChristian Brauner 		return CHILD_SETUP;
214921c14fdSChristian Brauner 	/* the flags of the copy are locked: EPERM */
215921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/dbg/tracing", base);
216921c14fdSChristian Brauner 	if (!mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL) || errno != EPERM)
217921c14fdSChristian Brauner 		return CHILD_STEP2;
218921c14fdSChristian Brauner 	return CHILD_OK;
219921c14fdSChristian Brauner }
220921c14fdSChristian Brauner 
TEST_F(locked_mount,automount_locked_in_the_triggering_namespace)221921c14fdSChristian Brauner TEST_F(locked_mount, automount_locked_in_the_triggering_namespace)
222921c14fdSChristian Brauner {
223921c14fdSChristian Brauner 	int to_host[2], from_host[2], dfd, ret;
224921c14fdSChristian Brauner 	char p[PATH_LEN];
225921c14fdSChristian Brauner 	pid_t pid;
226921c14fdSChristian Brauner 
227921c14fdSChristian Brauner 	if (!self->tracing)
228921c14fdSChristian Brauner 		SKIP(return, "test requires debugfs with the tracefs automount");
229921c14fdSChristian Brauner 
230921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/dbg", self->base);
231921c14fdSChristian Brauner 	ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0);
232921c14fdSChristian Brauner 	dfd = open(p, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
233921c14fdSChristian Brauner 	ASSERT_GE(dfd, 0);
234921c14fdSChristian Brauner 	ASSERT_EQ(pipe(to_host), 0);
235921c14fdSChristian Brauner 	ASSERT_EQ(pipe(from_host), 0);
236921c14fdSChristian Brauner 
237921c14fdSChristian Brauner 	pid = fork();
238921c14fdSChristian Brauner 	ASSERT_GE(pid, 0);
239921c14fdSChristian Brauner 	if (pid == 0)
240921c14fdSChristian Brauner 		_exit(automount_child(self->base, dfd, to_host[1], from_host[0]));
241921c14fdSChristian Brauner 	close(dfd);
242921c14fdSChristian Brauner 	ASSERT_EQ(wait_byte(to_host[0]), 0);
243921c14fdSChristian Brauner 
244921c14fdSChristian Brauner 	/* the host's own mount isn't locked: the flags can go */
245921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/dbg/tracing", self->base);
246921c14fdSChristian Brauner 	EXPECT_EQ(mount(NULL, p, NULL, MS_REMOUNT | MS_BIND, NULL), 0);
247921c14fdSChristian Brauner 
248921c14fdSChristian Brauner 	ASSERT_EQ(send_byte(from_host[1]), 0);
249921c14fdSChristian Brauner 	ret = wait_child(pid);
250921c14fdSChristian Brauner 	TH_LOG("child exit code %d", ret);
251921c14fdSChristian Brauner 	EXPECT_EQ(ret, CHILD_OK);
252921c14fdSChristian Brauner }
253921c14fdSChristian Brauner 
254921c14fdSChristian Brauner /*
255921c14fdSChristian Brauner  * A copy of a tree with a locked cover. The child puts a shrinkable mount
256921c14fdSChristian Brauner  * beneath the cover, which hands the lock down, unmounts the cover and then
257921c14fdSChristian Brauner  * asks for the umount of an unlocked ancestor. That expires the shrinkable
258921c14fdSChristian Brauner  * mount on a kernel that doesn't look at the lock, and the covered
259921c14fdSChristian Brauner  * directory is bare.
260921c14fdSChristian Brauner  */
expiry_child(const char * base)261921c14fdSChristian Brauner static int expiry_child(const char *base)
262921c14fdSChristian Brauner {
263921c14fdSChristian Brauner 	char srv[PATH_LEN], shr[PATH_LEN], x[PATH_LEN], c[PATH_LEN];
264921c14fdSChristian Brauner 	char hidden[PATH_LEN], secret[PATH_LEN];
265921c14fdSChristian Brauner 
266921c14fdSChristian Brauner 	snprintf(srv, sizeof(srv), "%s/srv", base);
267921c14fdSChristian Brauner 	snprintf(shr, sizeof(shr), "%s/shr", base);
268921c14fdSChristian Brauner 	snprintf(x, sizeof(x), "%s/x", base);
269921c14fdSChristian Brauner 	snprintf(c, sizeof(c), "%s/c", base);
270921c14fdSChristian Brauner 	snprintf(hidden, sizeof(hidden), "%s/x/hidden", base);
271921c14fdSChristian Brauner 	snprintf(secret, sizeof(secret), "%s/x/hidden/secret", base);
272921c14fdSChristian Brauner 
273921c14fdSChristian Brauner 	if (enter_userns())
274921c14fdSChristian Brauner 		return CHILD_SETUP;
275921c14fdSChristian Brauner 	if (mount(srv, x, NULL, MS_BIND | MS_REC, NULL) ||
276921c14fdSChristian Brauner 	    mount(shr, c, NULL, MS_BIND, NULL))
277921c14fdSChristian Brauner 		return CHILD_SETUP;
278921c14fdSChristian Brauner 	/* the cover is locked */
279921c14fdSChristian Brauner 	if (!umount2(hidden, 0) || errno != EINVAL)
280921c14fdSChristian Brauner 		return CHILD_STEP1;
281921c14fdSChristian Brauner 	if (syscall(__NR_move_mount, AT_FDCWD, c, AT_FDCWD, hidden, MOVE_MOUNT_BENEATH))
282921c14fdSChristian Brauner 		return CHILD_STEP2;
283921c14fdSChristian Brauner 	/* the lock moved down, the cover may go */
284921c14fdSChristian Brauner 	if (umount2(hidden, 0))
285921c14fdSChristian Brauner 		return CHILD_STEP3;
286921c14fdSChristian Brauner 	/* the holder of the lock may not, in any way */
287921c14fdSChristian Brauner 	if (!umount2(hidden, 0) || errno != EINVAL)
288921c14fdSChristian Brauner 		return CHILD_STEP4;
289921c14fdSChristian Brauner 	if (chdir(x))
290921c14fdSChristian Brauner 		return CHILD_SETUP;
291921c14fdSChristian Brauner 	umount2(x, 0);
292921c14fdSChristian Brauner 	return covered(secret) ? CHILD_OK : CHILD_STEP4;
293921c14fdSChristian Brauner }
294921c14fdSChristian Brauner 
TEST_F(locked_mount,expiry_leaves_a_locked_mount_alone)295921c14fdSChristian Brauner TEST_F(locked_mount, expiry_leaves_a_locked_mount_alone)
296921c14fdSChristian Brauner {
297921c14fdSChristian Brauner 	char p[PATH_LEN], q[PATH_LEN];
298921c14fdSChristian Brauner 	pid_t pid;
299921c14fdSChristian Brauner 	int ret;
300921c14fdSChristian Brauner 
301921c14fdSChristian Brauner 	if (!self->tracing)
302921c14fdSChristian Brauner 		SKIP(return, "test requires debugfs with the tracefs automount");
303921c14fdSChristian Brauner 
304921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/dbg/tracing", self->base);
305921c14fdSChristian Brauner 	snprintf(q, sizeof(q), "%s/shr", self->base);
306921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(q, 0755), 0);
307921c14fdSChristian Brauner 	/* a bind of an automount is shrinkable as well */
308921c14fdSChristian Brauner 	ASSERT_EQ(mount(p, q, NULL, MS_BIND, NULL), 0);
309921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/srv", self->base);
310921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
311921c14fdSChristian Brauner 	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
312921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/srv/hidden", self->base);
313921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
314921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/srv/hidden/secret", self->base);
315921c14fdSChristian Brauner 	ASSERT_EQ(create_file(p, "covered-by-root\n"), 0);
316921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/srv/hidden", self->base);
317921c14fdSChristian Brauner 	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
318921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/x", self->base);
319921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
320921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/c", self->base);
321921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
322921c14fdSChristian Brauner 
323921c14fdSChristian Brauner 	pid = fork();
324921c14fdSChristian Brauner 	ASSERT_GE(pid, 0);
325921c14fdSChristian Brauner 	if (pid == 0)
326921c14fdSChristian Brauner 		_exit(expiry_child(self->base));
327921c14fdSChristian Brauner 	ret = wait_child(pid);
328921c14fdSChristian Brauner 	TH_LOG("child exit code %d", ret);
329921c14fdSChristian Brauner 	EXPECT_EQ(ret, CHILD_OK);
330921c14fdSChristian Brauner }
331921c14fdSChristian Brauner 
332921c14fdSChristian Brauner /*
333921c14fdSChristian Brauner  * The copy of the mount tree that a user namespace gets at its creation has
334921c14fdSChristian Brauner  * the automounts in it locked unless they are on an expiry list. A bind of
335921c14fdSChristian Brauner  * such a tree inside the namespace copies the lock to the automount but not
336921c14fdSChristian Brauner  * to the root of the bind, so the child may ask for the umount of that root.
337921c14fdSChristian Brauner  * That must not expire the locked automount below it: the root is busy, the
338921c14fdSChristian Brauner  * umount fails and the automount has to be there afterwards.
339921c14fdSChristian Brauner  */
copied_tree_child(const char * base)340921c14fdSChristian Brauner static int copied_tree_child(const char *base)
341921c14fdSChristian Brauner {
342921c14fdSChristian Brauner 	char dbg[PATH_LEN], x[PATH_LEN], tracing[PATH_LEN];
343921c14fdSChristian Brauner 	struct stat root, st;
344921c14fdSChristian Brauner 
345921c14fdSChristian Brauner 	snprintf(dbg, sizeof(dbg), "%s/dbg", base);
346921c14fdSChristian Brauner 	snprintf(x, sizeof(x), "%s/x", base);
347921c14fdSChristian Brauner 	snprintf(tracing, sizeof(tracing), "%s/x/tracing", base);
348921c14fdSChristian Brauner 
349921c14fdSChristian Brauner 	if (enter_userns())
350921c14fdSChristian Brauner 		return CHILD_SETUP;
351921c14fdSChristian Brauner 	if (mount(dbg, x, NULL, MS_BIND | MS_REC, NULL))
352921c14fdSChristian Brauner 		return CHILD_SETUP;
353921c14fdSChristian Brauner 	/* the copy of the automount carries the lock */
354921c14fdSChristian Brauner 	if (!umount2(tracing, 0) || errno != EINVAL)
355921c14fdSChristian Brauner 		return CHILD_STEP1;
356921c14fdSChristian Brauner 	/* the root of the bind doesn't; keep it busy */
357921c14fdSChristian Brauner 	if (chdir(x))
358921c14fdSChristian Brauner 		return CHILD_SETUP;
359921c14fdSChristian Brauner 	if (!umount2(x, 0) || errno != EBUSY)
360921c14fdSChristian Brauner 		return CHILD_STEP2;
361921c14fdSChristian Brauner 	/* the automount below it must not have gone */
362921c14fdSChristian Brauner 	if (stat(x, &root) || fstatat(AT_FDCWD, tracing, &st, AT_NO_AUTOMOUNT))
363921c14fdSChristian Brauner 		return CHILD_SETUP;
364921c14fdSChristian Brauner 	return st.st_dev != root.st_dev ? CHILD_OK : CHILD_STEP3;
365921c14fdSChristian Brauner }
366921c14fdSChristian Brauner 
TEST_F(locked_mount,umount_of_a_bind_leaves_a_locked_automount_alone)367921c14fdSChristian Brauner TEST_F(locked_mount, umount_of_a_bind_leaves_a_locked_automount_alone)
368921c14fdSChristian Brauner {
369921c14fdSChristian Brauner 	char p[PATH_LEN];
370921c14fdSChristian Brauner 	struct stat st;
371921c14fdSChristian Brauner 	pid_t pid;
372921c14fdSChristian Brauner 	int ret;
373921c14fdSChristian Brauner 
374921c14fdSChristian Brauner 	if (!self->tracing)
375921c14fdSChristian Brauner 		SKIP(return, "test requires debugfs with the tracefs automount");
376921c14fdSChristian Brauner 
377921c14fdSChristian Brauner 	/* the copy the child gets has to contain the automount */
378921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base);
379921c14fdSChristian Brauner 	ASSERT_EQ(stat(p, &st), 0);
380921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/x", self->base);
381921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
382921c14fdSChristian Brauner 
383921c14fdSChristian Brauner 	pid = fork();
384921c14fdSChristian Brauner 	ASSERT_GE(pid, 0);
385921c14fdSChristian Brauner 	if (pid == 0)
386921c14fdSChristian Brauner 		_exit(copied_tree_child(self->base));
387921c14fdSChristian Brauner 	ret = wait_child(pid);
388921c14fdSChristian Brauner 	TH_LOG("child exit code %d", ret);
389921c14fdSChristian Brauner 	EXPECT_EQ(ret, CHILD_OK);
390921c14fdSChristian Brauner }
391921c14fdSChristian Brauner 
392921c14fdSChristian Brauner /*
393921c14fdSChristian Brauner  * Z is a user namespace with a copy of a tree in which P/d is covered by a
394921c14fdSChristian Brauner  * locked mount. The host mounts X on P/d, which propagates beneath Z's
395921c14fdSChristian Brauner  * cover, and unmounts it again. Z's cover has to be locked afterwards as
396921c14fdSChristian Brauner  * it was before.
397921c14fdSChristian Brauner  */
propagation_child(const char * base,int to_host,int from_host)398921c14fdSChristian Brauner static int propagation_child(const char *base, int to_host, int from_host)
399921c14fdSChristian Brauner {
400921c14fdSChristian Brauner 	char d[PATH_LEN], secret[PATH_LEN];
401921c14fdSChristian Brauner 
402921c14fdSChristian Brauner 	snprintf(d, sizeof(d), "%s/P/d", base);
403921c14fdSChristian Brauner 	snprintf(secret, sizeof(secret), "%s/P/d/secret", base);
404921c14fdSChristian Brauner 
405921c14fdSChristian Brauner 	if (enter_userns())
406921c14fdSChristian Brauner 		return CHILD_SETUP;
407921c14fdSChristian Brauner 	/* the cover is locked */
408921c14fdSChristian Brauner 	if (!umount2(d, 0) || errno != EINVAL)
409921c14fdSChristian Brauner 		return CHILD_STEP1;
410921c14fdSChristian Brauner 	if (send_byte(to_host) || wait_byte(from_host))
411921c14fdSChristian Brauner 		return CHILD_SETUP;
412921c14fdSChristian Brauner 	/* X came and went beneath it: still locked */
413921c14fdSChristian Brauner 	if (!umount2(d, 0) || errno != EINVAL)
414921c14fdSChristian Brauner 		return CHILD_STEP2;
415921c14fdSChristian Brauner 	return covered(secret) ? CHILD_OK : CHILD_STEP3;
416921c14fdSChristian Brauner }
417921c14fdSChristian Brauner 
TEST_F(locked_mount,propagated_copy_keeps_the_cover_locked)418921c14fdSChristian Brauner TEST_F(locked_mount, propagated_copy_keeps_the_cover_locked)
419921c14fdSChristian Brauner {
420921c14fdSChristian Brauner 	int to_host[2], from_host[2], ret;
421921c14fdSChristian Brauner 	char p[PATH_LEN];
422921c14fdSChristian Brauner 	pid_t pid;
423921c14fdSChristian Brauner 
424921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/P", self->base);
425921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
426921c14fdSChristian Brauner 	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
427921c14fdSChristian Brauner 	ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0);
428921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/P/d", self->base);
429921c14fdSChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
430921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/P/d/secret", self->base);
431921c14fdSChristian Brauner 	ASSERT_EQ(create_file(p, "covered-by-root\n"), 0);
432921c14fdSChristian Brauner 	ASSERT_EQ(pipe(to_host), 0);
433921c14fdSChristian Brauner 	ASSERT_EQ(pipe(from_host), 0);
434921c14fdSChristian Brauner 
435921c14fdSChristian Brauner 	pid = fork();
436921c14fdSChristian Brauner 	ASSERT_GE(pid, 0);
437921c14fdSChristian Brauner 	if (pid == 0) {
438921c14fdSChristian Brauner 		/* M: a manager's namespace that covers P/d for Z */
439921c14fdSChristian Brauner 		pid_t z;
440921c14fdSChristian Brauner 
441921c14fdSChristian Brauner 		if (unshare(CLONE_NEWNS))
442921c14fdSChristian Brauner 			_exit(CHILD_SETUP);
443921c14fdSChristian Brauner 		snprintf(p, sizeof(p), "%s/P", self->base);
444921c14fdSChristian Brauner 		if (mount(NULL, p, NULL, MS_SLAVE, NULL))
445921c14fdSChristian Brauner 			_exit(CHILD_SETUP);
446921c14fdSChristian Brauner 		snprintf(p, sizeof(p), "%s/P/d", self->base);
447921c14fdSChristian Brauner 		if (mount("tmpfs", p, "tmpfs", 0, "mode=0755"))
448921c14fdSChristian Brauner 			_exit(CHILD_SETUP);
449921c14fdSChristian Brauner 		z = fork();
450921c14fdSChristian Brauner 		if (z < 0)
451921c14fdSChristian Brauner 			_exit(CHILD_SETUP);
452921c14fdSChristian Brauner 		if (z == 0)
453921c14fdSChristian Brauner 			_exit(propagation_child(self->base, to_host[1], from_host[0]));
454921c14fdSChristian Brauner 		_exit(wait_child(z));
455921c14fdSChristian Brauner 	}
456921c14fdSChristian Brauner 	ASSERT_EQ(wait_byte(to_host[0]), 0);
457921c14fdSChristian Brauner 
458921c14fdSChristian Brauner 	/* the host mounts on P/d and unmounts again; both propagate */
459921c14fdSChristian Brauner 	snprintf(p, sizeof(p), "%s/P/d", self->base);
460921c14fdSChristian Brauner 	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
461921c14fdSChristian Brauner 	ASSERT_EQ(umount2(p, 0), 0);
462921c14fdSChristian Brauner 
463921c14fdSChristian Brauner 	ASSERT_EQ(send_byte(from_host[1]), 0);
464921c14fdSChristian Brauner 	ret = wait_child(pid);
465921c14fdSChristian Brauner 	TH_LOG("child exit code %d", ret);
466921c14fdSChristian Brauner 	EXPECT_EQ(ret, CHILD_OK);
467921c14fdSChristian Brauner }
468921c14fdSChristian Brauner 
469*eadfe727SChristian Brauner /*
470*eadfe727SChristian Brauner  * A mount of a mount namespace file is left out of the copy that
471*eadfe727SChristian Brauner  * unshare(CLONE_NEWNS) makes, together with everything mounted on it, so
472*eadfe727SChristian Brauner  * it can't take over the lock of a cover it is moved beneath. The child
473*eadfe727SChristian Brauner  * tries just that with the namespace file of a grandchild and gets EINVAL.
474*eadfe727SChristian Brauner  * A bind of an ordinary file takes the lock over, and the file is still
475*eadfe727SChristian Brauner  * covered in the next copy of the mount namespace.
476*eadfe727SChristian Brauner  */
ns_file_child(const char * base)477*eadfe727SChristian Brauner static int ns_file_child(const char *base)
478*eadfe727SChristian Brauner {
479*eadfe727SChristian Brauner 	char secret[PATH_LEN], mine[PATH_LEN], nsfile[PATH_LEN], buf[32];
480*eadfe727SChristian Brauner 	int ret = CHILD_OK, pfd[2];
481*eadfe727SChristian Brauner 	pid_t pid;
482*eadfe727SChristian Brauner 
483*eadfe727SChristian Brauner 	snprintf(secret, sizeof(secret), "%s/P/secret", base);
484*eadfe727SChristian Brauner 	snprintf(mine, sizeof(mine), "%s/mine", base);
485*eadfe727SChristian Brauner 
486*eadfe727SChristian Brauner 	if (enter_userns() || pipe(pfd))
487*eadfe727SChristian Brauner 		return CHILD_SETUP;
488*eadfe727SChristian Brauner 	/* the cover is locked */
489*eadfe727SChristian Brauner 	if (!umount2(secret, 0) || errno != EINVAL)
490*eadfe727SChristian Brauner 		return CHILD_STEP1;
491*eadfe727SChristian Brauner 
492*eadfe727SChristian Brauner 	/* a mount namespace newer than our own, so that a bind of it is allowed */
493*eadfe727SChristian Brauner 	pid = fork();
494*eadfe727SChristian Brauner 	if (pid < 0)
495*eadfe727SChristian Brauner 		return CHILD_SETUP;
496*eadfe727SChristian Brauner 	if (pid == 0) {
497*eadfe727SChristian Brauner 		if (unshare(CLONE_NEWNS) || send_byte(pfd[1]))
498*eadfe727SChristian Brauner 			_exit(1);
499*eadfe727SChristian Brauner 		pause();
500*eadfe727SChristian Brauner 		_exit(0);
501*eadfe727SChristian Brauner 	}
502*eadfe727SChristian Brauner 	if (wait_byte(pfd[0]))
503*eadfe727SChristian Brauner 		return CHILD_SETUP;
504*eadfe727SChristian Brauner 	snprintf(nsfile, sizeof(nsfile), "/proc/%d/ns/mnt", pid);
505*eadfe727SChristian Brauner 	if (!move_beneath(nsfile, secret) || errno != EINVAL)
506*eadfe727SChristian Brauner 		ret = CHILD_STEP2;
507*eadfe727SChristian Brauner 	kill(pid, SIGKILL);
508*eadfe727SChristian Brauner 	waitpid(pid, NULL, 0);
509*eadfe727SChristian Brauner 	if (ret)
510*eadfe727SChristian Brauner 		return ret;
511*eadfe727SChristian Brauner 
512*eadfe727SChristian Brauner 	/* an ordinary file beneath the cover takes the lock over */
513*eadfe727SChristian Brauner 	if (move_beneath(mine, secret))
514*eadfe727SChristian Brauner 		return CHILD_STEP3;
515*eadfe727SChristian Brauner 	if (umount2(secret, 0))
516*eadfe727SChristian Brauner 		return CHILD_STEP3;
517*eadfe727SChristian Brauner 	if (!umount2(secret, 0) || errno != EINVAL)
518*eadfe727SChristian Brauner 		return CHILD_STEP3;
519*eadfe727SChristian Brauner 	/* and it is in the next copy of the namespace */
520*eadfe727SChristian Brauner 	if (unshare(CLONE_NEWNS))
521*eadfe727SChristian Brauner 		return CHILD_SETUP;
522*eadfe727SChristian Brauner 	if (read_word(secret, buf, sizeof(buf)) || strcmp(buf, "mine"))
523*eadfe727SChristian Brauner 		return CHILD_STEP4;
524*eadfe727SChristian Brauner 	return CHILD_OK;
525*eadfe727SChristian Brauner }
526*eadfe727SChristian Brauner 
TEST_F(locked_mount,mount_namespace_file_cant_take_the_lock)527*eadfe727SChristian Brauner TEST_F(locked_mount, mount_namespace_file_cant_take_the_lock)
528*eadfe727SChristian Brauner {
529*eadfe727SChristian Brauner 	char p[PATH_LEN], q[PATH_LEN];
530*eadfe727SChristian Brauner 	pid_t pid;
531*eadfe727SChristian Brauner 	int ret;
532*eadfe727SChristian Brauner 
533*eadfe727SChristian Brauner 	snprintf(p, sizeof(p), "%s/P", self->base);
534*eadfe727SChristian Brauner 	ASSERT_EQ(mkdir(p, 0755), 0);
535*eadfe727SChristian Brauner 	ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, "mode=0755"), 0);
536*eadfe727SChristian Brauner 	snprintf(p, sizeof(p), "%s/P/secret", self->base);
537*eadfe727SChristian Brauner 	ASSERT_EQ(create_file(p, "covered-by-root\n"), 0);
538*eadfe727SChristian Brauner 	snprintf(q, sizeof(q), "%s/P/empty", self->base);
539*eadfe727SChristian Brauner 	ASSERT_EQ(create_file(q, ""), 0);
540*eadfe727SChristian Brauner 	/* the cover: an empty file over the secret */
541*eadfe727SChristian Brauner 	ASSERT_EQ(mount(q, p, NULL, MS_BIND, NULL), 0);
542*eadfe727SChristian Brauner 	snprintf(q, sizeof(q), "%s/mine", self->base);
543*eadfe727SChristian Brauner 	ASSERT_EQ(create_file(q, "mine"), 0);
544*eadfe727SChristian Brauner 
545*eadfe727SChristian Brauner 	pid = fork();
546*eadfe727SChristian Brauner 	ASSERT_GE(pid, 0);
547*eadfe727SChristian Brauner 	if (pid == 0)
548*eadfe727SChristian Brauner 		_exit(ns_file_child(self->base));
549*eadfe727SChristian Brauner 	ret = wait_child(pid);
550*eadfe727SChristian Brauner 	TH_LOG("child exit code %d", ret);
551*eadfe727SChristian Brauner 	EXPECT_EQ(ret, CHILD_OK);
552*eadfe727SChristian Brauner }
553*eadfe727SChristian Brauner 
554921c14fdSChristian Brauner TEST_HARNESS_MAIN
555