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