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