xref: /linux/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c (revision 7541865c260f9bef9fecf212ec39538a21daaf77)
1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <sched.h>
4 #include <stdio.h>
5 #include <errno.h>
6 #include <string.h>
7 #include <sys/stat.h>
8 #include <sys/types.h>
9 #include <sys/mount.h>
10 #include <sys/wait.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <stdbool.h>
15 #include <stdarg.h>
16 #include <sys/syscall.h>
17 
18 #include "kselftest_harness.h"
19 
20 #ifndef CLONE_NEWNS
21 #define CLONE_NEWNS 0x00020000
22 #endif
23 
24 #ifndef CLONE_NEWUSER
25 #define CLONE_NEWUSER 0x10000000
26 #endif
27 
28 #ifndef MS_SHARED
29 #define MS_SHARED (1 << 20)
30 #endif
31 
32 #ifndef MS_PRIVATE
33 #define MS_PRIVATE (1<<18)
34 #endif
35 
36 #ifndef MOVE_MOUNT_SET_GROUP
37 #define MOVE_MOUNT_SET_GROUP 0x00000100
38 #endif
39 
40 #ifndef MOVE_MOUNT_F_EMPTY_PATH
41 #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
42 #endif
43 
44 #ifndef MOVE_MOUNT_T_EMPTY_PATH
45 #define MOVE_MOUNT_T_EMPTY_PATH 0x00000040
46 #endif
47 
48 static ssize_t write_nointr(int fd, const void *buf, size_t count)
49 {
50 	ssize_t ret;
51 
52 	do {
53 		ret = write(fd, buf, count);
54 	} while (ret < 0 && errno == EINTR);
55 
56 	return ret;
57 }
58 
59 static int write_file(const char *path, const void *buf, size_t count)
60 {
61 	int fd;
62 	ssize_t ret;
63 
64 	fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW);
65 	if (fd < 0)
66 		return -1;
67 
68 	ret = write_nointr(fd, buf, count);
69 	close(fd);
70 	if (ret < 0 || (size_t)ret != count)
71 		return -1;
72 
73 	return 0;
74 }
75 
76 static int create_and_enter_userns(void)
77 {
78 	uid_t uid;
79 	gid_t gid;
80 	char map[100];
81 
82 	uid = getuid();
83 	gid = getgid();
84 
85 	if (unshare(CLONE_NEWUSER))
86 		return -1;
87 
88 	if (write_file("/proc/self/setgroups", "deny", sizeof("deny") - 1) &&
89 	    errno != ENOENT)
90 		return -1;
91 
92 	snprintf(map, sizeof(map), "0 %d 1", uid);
93 	if (write_file("/proc/self/uid_map", map, strlen(map)))
94 		return -1;
95 
96 
97 	snprintf(map, sizeof(map), "0 %d 1", gid);
98 	if (write_file("/proc/self/gid_map", map, strlen(map)))
99 		return -1;
100 
101 	if (setgid(0))
102 		return -1;
103 
104 	if (setuid(0))
105 		return -1;
106 
107 	return 0;
108 }
109 
110 static int prepare_unpriv_mountns(void)
111 {
112 	if (create_and_enter_userns())
113 		return -1;
114 
115 	if (unshare(CLONE_NEWNS))
116 		return -1;
117 
118 	if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0))
119 		return -1;
120 
121 	return 0;
122 }
123 
124 static char *get_field(char *src, int nfields)
125 {
126 	int i;
127 	char *p = src;
128 
129 	for (i = 0; i < nfields; i++) {
130 		while (*p && *p != ' ' && *p != '\t')
131 			p++;
132 
133 		if (!*p)
134 			break;
135 
136 		p++;
137 	}
138 
139 	return p;
140 }
141 
142 static void null_endofword(char *word)
143 {
144 	while (*word && *word != ' ' && *word != '\t')
145 		word++;
146 	*word = '\0';
147 }
148 
149 /* Does the mount on @path carry the optional field @field in mountinfo? */
150 static bool mount_has_field(const char *path, const char *field)
151 {
152 	size_t len = 0;
153 	char *line = NULL;
154 	FILE *f = NULL;
155 	bool found = false;
156 
157 	f = fopen("/proc/self/mountinfo", "re");
158 	if (!f)
159 		return false;
160 
161 	while (!found && getline(&line, &len, f) != -1) {
162 		char *opts, *target;
163 
164 		target = get_field(line, 4);
165 		if (!target)
166 			continue;
167 
168 		opts = get_field(target, 2);
169 		if (!opts)
170 			continue;
171 
172 		null_endofword(target);
173 
174 		if (strcmp(target, path) != 0)
175 			continue;
176 
177 		/* the optional fields end at the "-" separator */
178 		while (opts && *opts != '-') {
179 			char *next = strchr(opts, ' ');
180 
181 			if (next)
182 				*next++ = '\0';
183 			if (!strncmp(opts, field, strlen(field))) {
184 				found = true;
185 				break;
186 			}
187 			opts = next;
188 		}
189 	}
190 
191 	free(line);
192 	fclose(f);
193 
194 	return found;
195 }
196 
197 static bool is_shared_mount(const char *path)
198 {
199 	return mount_has_field(path, "shared:");
200 }
201 
202 /* Attempt to de-conflict with the selftests tree. */
203 #ifndef SKIP
204 #define SKIP(s, ...)	XFAIL(s, ##__VA_ARGS__)
205 #endif
206 
207 #define SET_GROUP_FROM	"/tmp/move_mount_set_group_supported_from"
208 #define SET_GROUP_TO	"/tmp/move_mount_set_group_supported_to"
209 
210 static bool move_mount_set_group_supported(void)
211 {
212 	int ret;
213 
214 	if (mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
215 		  "size=100000,mode=700"))
216 		return -1;
217 
218 	if (mount(NULL, "/tmp", NULL, MS_PRIVATE, 0))
219 		return -1;
220 
221 	if (mkdir(SET_GROUP_FROM, 0777))
222 		return -1;
223 
224 	if (mkdir(SET_GROUP_TO, 0777))
225 		return -1;
226 
227 	if (mount("testing", SET_GROUP_FROM, "tmpfs", MS_NOATIME | MS_NODEV,
228 		  "size=100000,mode=700"))
229 		return -1;
230 
231 	if (mount(SET_GROUP_FROM, SET_GROUP_TO, NULL, MS_BIND, NULL))
232 		return -1;
233 
234 	if (mount(NULL, SET_GROUP_FROM, NULL, MS_SHARED, 0))
235 		return -1;
236 
237 	ret = syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_FROM,
238 		      AT_FDCWD, SET_GROUP_TO, MOVE_MOUNT_SET_GROUP);
239 	umount2("/tmp", MNT_DETACH);
240 
241 	return ret >= 0;
242 }
243 
244 FIXTURE(move_mount_set_group) {
245 };
246 
247 #define SET_GROUP_A "/tmp/A"
248 
249 FIXTURE_SETUP(move_mount_set_group)
250 {
251 	bool ret;
252 
253 	ASSERT_EQ(prepare_unpriv_mountns(), 0);
254 
255 	ret = move_mount_set_group_supported();
256 	ASSERT_GE(ret, 0);
257 	if (!ret)
258 		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
259 
260 	umount2("/tmp", MNT_DETACH);
261 
262 	ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
263 			"size=100000,mode=700"), 0);
264 
265 	ASSERT_EQ(mkdir(SET_GROUP_A, 0777), 0);
266 
267 	ASSERT_EQ(mount("testing", SET_GROUP_A, "tmpfs", MS_NOATIME | MS_NODEV,
268 			"size=100000,mode=700"), 0);
269 }
270 
271 FIXTURE_TEARDOWN(move_mount_set_group)
272 {
273 	bool ret;
274 
275 	ret = move_mount_set_group_supported();
276 	ASSERT_GE(ret, 0);
277 	if (!ret)
278 		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
279 
280 	umount2("/tmp", MNT_DETACH);
281 }
282 
283 #define __STACK_SIZE (8 * 1024 * 1024)
284 static pid_t do_clone(int (*fn)(void *), void *arg, int flags)
285 {
286 	void *stack;
287 
288 	stack = malloc(__STACK_SIZE);
289 	if (!stack)
290 		return -ENOMEM;
291 
292 #ifdef __ia64__
293 	return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL);
294 #else
295 	return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL);
296 #endif
297 }
298 
299 static int wait_for_pid(pid_t pid)
300 {
301 	int status, ret;
302 
303 again:
304 	ret = waitpid(pid, &status, 0);
305 	if (ret == -1) {
306 		if (errno == EINTR)
307 			goto again;
308 
309 		return -1;
310 	}
311 
312 	if (!WIFEXITED(status))
313 		return -1;
314 
315 	return WEXITSTATUS(status);
316 }
317 
318 struct child_args {
319 	int unsfd;
320 	int mntnsfd;
321 	bool shared;
322 	int mntfd;
323 };
324 
325 static int get_nestedns_mount_cb(void *data)
326 {
327 	struct child_args *ca = (struct child_args *)data;
328 	int ret;
329 
330 	ret = prepare_unpriv_mountns();
331 	if (ret)
332 		return 1;
333 
334 	if (ca->shared) {
335 		ret = mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0);
336 		if (ret)
337 			return 1;
338 	}
339 
340 	ret = open("/proc/self/ns/user", O_RDONLY);
341 	if (ret < 0)
342 		return 1;
343 	ca->unsfd = ret;
344 
345 	ret = open("/proc/self/ns/mnt", O_RDONLY);
346 	if (ret < 0)
347 		return 1;
348 	ca->mntnsfd = ret;
349 
350 	ret = open(SET_GROUP_A, O_RDONLY);
351 	if (ret < 0)
352 		return 1;
353 	ca->mntfd = ret;
354 
355 	return 0;
356 }
357 
358 TEST_F(move_mount_set_group, complex_sharing_copying)
359 {
360 	struct child_args ca_from = {
361 		.shared = true,
362 	};
363 	struct child_args ca_to = {
364 		.shared = false,
365 	};
366 	pid_t pid;
367 	bool ret;
368 
369 	ret = move_mount_set_group_supported();
370 	ASSERT_GE(ret, 0);
371 	if (!ret)
372 		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
373 
374 	pid = do_clone(get_nestedns_mount_cb, (void *)&ca_from, CLONE_VFORK |
375 		       CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
376 	ASSERT_EQ(wait_for_pid(pid), 0);
377 
378 	pid = do_clone(get_nestedns_mount_cb, (void *)&ca_to, CLONE_VFORK |
379 		       CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
380 	ASSERT_EQ(wait_for_pid(pid), 0);
381 
382 	ASSERT_EQ(syscall(__NR_move_mount, ca_from.mntfd, "",
383 			  ca_to.mntfd, "", MOVE_MOUNT_SET_GROUP
384 			  | MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH),
385 		  0);
386 
387 	ASSERT_EQ(setns(ca_to.mntnsfd, CLONE_NEWNS), 0);
388 	ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1);
389 }
390 
391 #define SET_GROUP_B "/tmp/B"
392 #define SET_GROUP_C "/tmp/C"
393 
394 /*
395  * An unbindable mount is neither shared nor a slave, so it must not be
396  * accepted as the target: with a slave source it would end up unbindable
397  * and a slave at the same time.
398  */
399 TEST_F(move_mount_set_group, unbindable_target)
400 {
401 	bool ret;
402 
403 	ret = move_mount_set_group_supported();
404 	ASSERT_GE(ret, 0);
405 	if (!ret)
406 		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
407 
408 	ASSERT_EQ(mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0), 0);
409 
410 	/* B: a slave of A's peer group */
411 	ASSERT_EQ(mkdir(SET_GROUP_B, 0777), 0);
412 	ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_B, NULL, MS_BIND, NULL), 0);
413 	ASSERT_EQ(mount(NULL, SET_GROUP_B, NULL, MS_SLAVE, 0), 0);
414 	ASSERT_TRUE(mount_has_field(SET_GROUP_B, "master:"));
415 
416 	/* C: unbindable */
417 	ASSERT_EQ(mkdir(SET_GROUP_C, 0777), 0);
418 	ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_C, NULL, MS_BIND, NULL), 0);
419 	ASSERT_EQ(mount(NULL, SET_GROUP_C, NULL, MS_UNBINDABLE, 0), 0);
420 	ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable"));
421 
422 	/* from a slave */
423 	ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_B,
424 			  AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1);
425 	ASSERT_EQ(errno, EINVAL);
426 	ASSERT_FALSE(mount_has_field(SET_GROUP_C, "master:"));
427 	ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable"));
428 
429 	/* from a shared mount */
430 	ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_A,
431 			  AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1);
432 	ASSERT_EQ(errno, EINVAL);
433 	ASSERT_FALSE(mount_has_field(SET_GROUP_C, "shared:"));
434 	ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable"));
435 }
436 
437 TEST_HARNESS_MAIN
438