1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * may_decode_fh() refuses a file handle below a mount with locked children
4 * to root in a user namespace. That has to hold while the mount is lazily
5 * unmounted and its children, the locked ones too, are taken off it.
6 */
7 #define _GNU_SOURCE
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <pthread.h>
11 #include <sched.h>
12 #include <stdatomic.h>
13 #include <stdbool.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 #include <sys/mount.h>
19 #include <sys/prctl.h>
20 #include <sys/stat.h>
21 #include <sys/syscall.h>
22 #include <sys/wait.h>
23
24 #include "../../kselftest_harness.h"
25
26 #ifndef OPEN_TREE_CLONE
27 #define OPEN_TREE_CLONE 1
28 #endif
29 #ifndef OPEN_TREE_CLOEXEC
30 #define OPEN_TREE_CLOEXEC O_CLOEXEC
31 #endif
32 #ifndef AT_RECURSIVE
33 #define AT_RECURSIVE 0x8000
34 #endif
35
36 #define DIR_LEN 64
37 #define PATH_LEN 128
38
39 #define ROUNDS 100 /* lazy umounts raced per test */
40 #define RACERS 3 /* threads in open_by_handle_at() */
41 #define EXTRA_MOUNTS 64 /* make umount_tree() hold mount_lock longer */
42 #define MAX_DELAY_US 3000 /* before the umount */
43 #define TAIL_US 2000 /* after it */
44
45 struct handle {
46 struct file_handle fh;
47 unsigned char buf[MAX_HANDLE_SZ];
48 };
49
50 /* exit codes of the child */
51 enum {
52 CHILD_OK,
53 CHILD_SETUP,
54 CHILD_DECODED, /* decoded past a locked child */
55 CHILD_MOUNTED, /* not refused while mounted */
56 CHILD_UNMOUNTED, /* not refused once unmounted */
57 CHILD_ALLOWED, /* refused where nothing is locked */
58 CHILD_NOUSERNS, /* no user namespace to be had */
59 };
60
write_file(const char * path,const char * s)61 static int write_file(const char *path, const char *s)
62 {
63 ssize_t n = -1;
64 int fd;
65
66 fd = open(path, O_WRONLY | O_CLOEXEC);
67 if (fd >= 0) {
68 n = write(fd, s, strlen(s));
69 close(fd);
70 }
71 return n == (ssize_t)strlen(s) ? 0 : -1;
72 }
73
74 /* Become root in a new user namespace with a private mount namespace. */
enter_userns(void)75 static int enter_userns(void)
76 {
77 uid_t uid = getuid();
78 gid_t gid = getgid();
79 char map[32];
80
81 prctl(PR_SET_DUMPABLE, 1);
82 /* EINVAL: no USER_NS, ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM */
83 if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
84 return errno == EINVAL || errno == ENOSPC || errno == EPERM ?
85 CHILD_NOUSERNS : CHILD_SETUP;
86 if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
87 return CHILD_SETUP;
88 snprintf(map, sizeof(map), "0 %d 1", uid);
89 if (write_file("/proc/self/uid_map", map))
90 return CHILD_SETUP;
91 snprintf(map, sizeof(map), "0 %d 1", gid);
92 if (write_file("/proc/self/gid_map", map))
93 return CHILD_SETUP;
94 if (setgid(0) || setuid(0))
95 return CHILD_SETUP;
96 if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL))
97 return CHILD_SETUP;
98 return CHILD_OK;
99 }
100
get_handle(const char * path,struct handle * h)101 static int get_handle(const char *path, struct handle *h)
102 {
103 int mntid;
104
105 h->fh.handle_bytes = MAX_HANDLE_SZ;
106 return name_to_handle_at(AT_FDCWD, path, &h->fh, &mntid, 0);
107 }
108
decode(int dfd,struct handle * h)109 static int decode(int dfd, struct handle *h)
110 {
111 return open_by_handle_at(dfd, &h->fh, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
112 }
113
114 struct race {
115 int dfd;
116 struct handle *h;
117 atomic_int go;
118 atomic_int stop;
119 atomic_int won; /* the first decoded descriptor */
120 };
121
racer(void * arg)122 static void *racer(void *arg)
123 {
124 struct race *r = arg;
125
126 while (!atomic_load(&r->go))
127 ;
128 while (!atomic_load(&r->stop)) {
129 int none = -1;
130 int fd;
131
132 fd = decode(r->dfd, r->h);
133 if (fd < 0)
134 continue;
135 if (!atomic_compare_exchange_strong(&r->won, &none, fd))
136 close(fd);
137 }
138 return NULL;
139 }
140
141 /* stop the @n racers started so far and wait for them, they spin on @r */
stop_racers(struct race * r,pthread_t * th,int n)142 static void stop_racers(struct race *r, pthread_t *th, int n)
143 {
144 int i;
145
146 atomic_store(&r->stop, 1);
147 atomic_store(&r->go, 1); /* one still waiting for the start sees stop next */
148 for (i = 0; i < n; i++)
149 pthread_join(th[i], NULL);
150 }
151
152 /*
153 * Bind @h recursively at @w, or take a detached copy of it, and race
154 * open_by_handle_at() of @inner against the lazy umount. The decoded
155 * descriptor, if there was one, is left in @won.
156 */
race_round(const char * h,const char * w,struct handle * inner,bool dissolve,int * won)157 static int race_round(const char *h, const char *w, struct handle *inner,
158 bool dissolve, int *won)
159 {
160 struct race r = { .h = inner, .won = -1 };
161 pthread_t th[RACERS];
162 int treefd = -1, fd, i;
163
164 if (dissolve) {
165 treefd = syscall(__NR_open_tree, AT_FDCWD, h,
166 OPEN_TREE_CLONE | AT_RECURSIVE | OPEN_TREE_CLOEXEC);
167 if (treefd < 0)
168 return CHILD_SETUP;
169 /* an ordinary descriptor on the copy, treefd's close dissolves it */
170 r.dfd = openat(treefd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
171 } else {
172 if (mount(h, w, NULL, MS_BIND | MS_REC, NULL))
173 return CHILD_SETUP;
174 r.dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
175 }
176 if (r.dfd < 0)
177 return CHILD_SETUP;
178
179 fd = decode(r.dfd, inner);
180 if (fd >= 0 || errno != EPERM) {
181 if (fd >= 0)
182 close(fd);
183 return CHILD_MOUNTED;
184 }
185
186 for (i = 0; i < RACERS; i++) {
187 if (pthread_create(&th[i], NULL, racer, &r)) {
188 stop_racers(&r, th, i);
189 return CHILD_SETUP;
190 }
191 }
192 atomic_store(&r.go, 1);
193 usleep(rand() % MAX_DELAY_US);
194 if (dissolve)
195 close(treefd);
196 else if (umount2(w, MNT_DETACH)) {
197 stop_racers(&r, th, RACERS);
198 return CHILD_SETUP;
199 }
200 usleep(TAIL_US);
201 stop_racers(&r, th, RACERS);
202
203 fd = decode(r.dfd, inner);
204 if (fd >= 0) {
205 close(fd);
206 return CHILD_UNMOUNTED;
207 }
208 close(r.dfd);
209 *won = atomic_load(&r.won);
210 return CHILD_OK;
211 }
212
race_child(const char * h,const char * w,struct handle * inner,bool dissolve)213 static int race_child(const char *h, const char *w, struct handle *inner,
214 bool dissolve)
215 {
216 int ret, won, i;
217
218 srand(getpid());
219 ret = enter_userns();
220 if (ret)
221 return ret;
222 for (i = 0; i < ROUNDS; i++) {
223 ret = race_round(h, w, inner, dissolve, &won);
224 if (ret)
225 return ret;
226 if (won >= 0)
227 return CHILD_DECODED;
228 }
229 return CHILD_OK;
230 }
231
232 /* a bind without locked children decodes while mounted and not after */
allowed_child(const char * plain,const char * w,struct handle * h)233 static int allowed_child(const char *plain, const char *w, struct handle *h)
234 {
235 int dfd, fd, ret;
236
237 ret = enter_userns();
238 if (ret)
239 return ret;
240 if (mount(plain, w, NULL, MS_BIND | MS_REC, NULL))
241 return CHILD_SETUP;
242 dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
243 if (dfd < 0)
244 return CHILD_SETUP;
245 fd = decode(dfd, h);
246 if (fd < 0)
247 return CHILD_ALLOWED;
248 close(fd);
249 if (umount2(w, MNT_DETACH))
250 return CHILD_SETUP;
251 fd = decode(dfd, h);
252 if (fd >= 0) {
253 close(fd);
254 return CHILD_UNMOUNTED;
255 }
256 return errno == EPERM ? CHILD_OK : CHILD_UNMOUNTED;
257 }
258
FIXTURE(locked_handle)259 FIXTURE(locked_handle) {
260 char base[DIR_LEN];
261 char h[PATH_LEN]; /* the tree with the covered directory */
262 char plain[PATH_LEN]; /* a tree with no mount in it */
263 char w[PATH_LEN]; /* where the child binds either */
264 struct handle inner; /* h/top/secret/inner, covered */
265 struct handle uncovered; /* plain/inner */
266 };
267
FIXTURE_SETUP(locked_handle)268 FIXTURE_SETUP(locked_handle)
269 {
270 char p[PATH_LEN], e[PATH_LEN];
271 int i;
272
273 if (geteuid())
274 SKIP(return, "test requires root");
275
276 snprintf(self->base, sizeof(self->base), "/tmp/locked_handle.XXXXXX");
277 ASSERT_NE(mkdtemp(self->base), NULL);
278 ASSERT_EQ(unshare(CLONE_NEWNS), 0);
279 ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
280 ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
281
282 snprintf(self->h, sizeof(self->h), "%s/h", self->base);
283 snprintf(self->plain, sizeof(self->plain), "%s/plain", self->base);
284 snprintf(self->w, sizeof(self->w), "%s/w", self->base);
285 ASSERT_EQ(mkdir(self->h, 0755), 0);
286 ASSERT_EQ(mkdir(self->plain, 0755), 0);
287 ASSERT_EQ(mkdir(self->w, 0755), 0);
288 snprintf(p, sizeof(p), "%s/plain/inner", self->base);
289 ASSERT_EQ(mkdir(p, 0755), 0);
290 ASSERT_EQ(get_handle(p, &self->uncovered), 0);
291
292 /* the handle is for this filesystem */
293 ASSERT_EQ(mount("tmpfs", self->h, "tmpfs", 0, NULL), 0);
294 snprintf(p, sizeof(p), "%s/h/top", self->base);
295 ASSERT_EQ(mkdir(p, 0755), 0);
296 snprintf(p, sizeof(p), "%s/h/top/secret", self->base);
297 ASSERT_EQ(mkdir(p, 0755), 0);
298 snprintf(p, sizeof(p), "%s/h/top/secret/inner", self->base);
299 ASSERT_EQ(mkdir(p, 0755), 0);
300 ASSERT_EQ(get_handle(p, &self->inner), 0);
301 snprintf(e, sizeof(e), "%s/h/empty", self->base);
302 ASSERT_EQ(mkdir(e, 0755), 0);
303
304 /* cover it, and some more so the umount takes longer */
305 snprintf(p, sizeof(p), "%s/h/top/secret", self->base);
306 ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), 0);
307 for (i = 0; i < EXTRA_MOUNTS; i++) {
308 snprintf(p, sizeof(p), "%s/h/c%d", self->base, i);
309 ASSERT_EQ(mkdir(p, 0755), 0);
310 ASSERT_EQ(mount(e, p, NULL, MS_BIND, NULL), 0);
311 }
312 }
313
FIXTURE_TEARDOWN(locked_handle)314 FIXTURE_TEARDOWN(locked_handle)
315 {
316 umount2(self->base, MNT_DETACH);
317 rmdir(self->base);
318 }
319
wait_child(pid_t pid)320 static int wait_child(pid_t pid)
321 {
322 int status;
323
324 if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
325 return -1;
326 return WEXITSTATUS(status);
327 }
328
TEST_F(locked_handle,lazy_umount)329 TEST_F(locked_handle, lazy_umount)
330 {
331 pid_t pid;
332 int ret;
333
334 pid = fork();
335 ASSERT_GE(pid, 0);
336 if (pid == 0)
337 _exit(race_child(self->h, self->w, &self->inner, false));
338 ret = wait_child(pid);
339 TH_LOG("child exit code %d", ret);
340 if (ret == CHILD_NOUSERNS)
341 SKIP(return, "no user namespaces");
342 EXPECT_EQ(ret, CHILD_OK);
343 }
344
TEST_F(locked_handle,dissolve)345 TEST_F(locked_handle, dissolve)
346 {
347 pid_t pid;
348 int ret;
349
350 pid = fork();
351 ASSERT_GE(pid, 0);
352 if (pid == 0)
353 _exit(race_child(self->h, self->w, &self->inner, true));
354 ret = wait_child(pid);
355 TH_LOG("child exit code %d", ret);
356 if (ret == CHILD_NOUSERNS)
357 SKIP(return, "no user namespaces");
358 EXPECT_EQ(ret, CHILD_OK);
359 }
360
TEST_F(locked_handle,allowed_use)361 TEST_F(locked_handle, allowed_use)
362 {
363 pid_t pid;
364 int ret;
365
366 pid = fork();
367 ASSERT_GE(pid, 0);
368 if (pid == 0)
369 _exit(allowed_child(self->plain, self->w, &self->uncovered));
370 ret = wait_child(pid);
371 TH_LOG("child exit code %d", ret);
372 if (ret == CHILD_NOUSERNS)
373 SKIP(return, "no user namespaces");
374 EXPECT_EQ(ret, CHILD_OK);
375 }
376
377 TEST_HARNESS_MAIN
378