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 
write_nointr(int fd,const void * buf,size_t count)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 
write_file(const char * path,const void * buf,size_t count)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 
create_and_enter_userns(void)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 
prepare_unpriv_mountns(void)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 
get_field(char * src,int nfields)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 
null_endofword(char * word)142 static void null_endofword(char *word)
143 {
144 	while (*word && *word != ' ' && *word != '\t')
145 		word++;
146 	*word = '\0';
147 }
148 
is_shared_mount(const char * path)149 static bool is_shared_mount(const char *path)
150 {
151 	size_t len = 0;
152 	char *line = NULL;
153 	FILE *f = NULL;
154 
155 	f = fopen("/proc/self/mountinfo", "re");
156 	if (!f)
157 		return false;
158 
159 	while (getline(&line, &len, f) != -1) {
160 		char *opts, *target;
161 
162 		target = get_field(line, 4);
163 		if (!target)
164 			continue;
165 
166 		opts = get_field(target, 2);
167 		if (!opts)
168 			continue;
169 
170 		null_endofword(target);
171 
172 		if (strcmp(target, path) != 0)
173 			continue;
174 
175 		null_endofword(opts);
176 		if (strstr(opts, "shared:"))
177 			return true;
178 	}
179 
180 	free(line);
181 	fclose(f);
182 
183 	return false;
184 }
185 
186 /* Attempt to de-conflict with the selftests tree. */
187 #ifndef SKIP
188 #define SKIP(s, ...)	XFAIL(s, ##__VA_ARGS__)
189 #endif
190 
191 #define SET_GROUP_FROM	"/tmp/move_mount_set_group_supported_from"
192 #define SET_GROUP_TO	"/tmp/move_mount_set_group_supported_to"
193 
move_mount_set_group_supported(void)194 static bool move_mount_set_group_supported(void)
195 {
196 	int ret;
197 
198 	if (mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
199 		  "size=100000,mode=700"))
200 		return -1;
201 
202 	if (mount(NULL, "/tmp", NULL, MS_PRIVATE, 0))
203 		return -1;
204 
205 	if (mkdir(SET_GROUP_FROM, 0777))
206 		return -1;
207 
208 	if (mkdir(SET_GROUP_TO, 0777))
209 		return -1;
210 
211 	if (mount("testing", SET_GROUP_FROM, "tmpfs", MS_NOATIME | MS_NODEV,
212 		  "size=100000,mode=700"))
213 		return -1;
214 
215 	if (mount(SET_GROUP_FROM, SET_GROUP_TO, NULL, MS_BIND, NULL))
216 		return -1;
217 
218 	if (mount(NULL, SET_GROUP_FROM, NULL, MS_SHARED, 0))
219 		return -1;
220 
221 	ret = syscall(SYS_move_mount, AT_FDCWD, SET_GROUP_FROM,
222 		      AT_FDCWD, SET_GROUP_TO, MOVE_MOUNT_SET_GROUP);
223 	umount2("/tmp", MNT_DETACH);
224 
225 	return ret >= 0;
226 }
227 
FIXTURE(move_mount_set_group)228 FIXTURE(move_mount_set_group) {
229 };
230 
231 #define SET_GROUP_A "/tmp/A"
232 
FIXTURE_SETUP(move_mount_set_group)233 FIXTURE_SETUP(move_mount_set_group)
234 {
235 	bool ret;
236 
237 	ASSERT_EQ(prepare_unpriv_mountns(), 0);
238 
239 	ret = move_mount_set_group_supported();
240 	ASSERT_GE(ret, 0);
241 	if (!ret)
242 		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
243 
244 	umount2("/tmp", MNT_DETACH);
245 
246 	ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
247 			"size=100000,mode=700"), 0);
248 
249 	ASSERT_EQ(mkdir(SET_GROUP_A, 0777), 0);
250 
251 	ASSERT_EQ(mount("testing", SET_GROUP_A, "tmpfs", MS_NOATIME | MS_NODEV,
252 			"size=100000,mode=700"), 0);
253 }
254 
FIXTURE_TEARDOWN(move_mount_set_group)255 FIXTURE_TEARDOWN(move_mount_set_group)
256 {
257 	bool ret;
258 
259 	ret = move_mount_set_group_supported();
260 	ASSERT_GE(ret, 0);
261 	if (!ret)
262 		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
263 
264 	umount2("/tmp", MNT_DETACH);
265 }
266 
267 #define __STACK_SIZE (8 * 1024 * 1024)
do_clone(int (* fn)(void *),void * arg,int flags)268 static pid_t do_clone(int (*fn)(void *), void *arg, int flags)
269 {
270 	void *stack;
271 
272 	stack = malloc(__STACK_SIZE);
273 	if (!stack)
274 		return -ENOMEM;
275 
276 #ifdef __ia64__
277 	return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL);
278 #else
279 	return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL);
280 #endif
281 }
282 
wait_for_pid(pid_t pid)283 static int wait_for_pid(pid_t pid)
284 {
285 	int status, ret;
286 
287 again:
288 	ret = waitpid(pid, &status, 0);
289 	if (ret == -1) {
290 		if (errno == EINTR)
291 			goto again;
292 
293 		return -1;
294 	}
295 
296 	if (!WIFEXITED(status))
297 		return -1;
298 
299 	return WEXITSTATUS(status);
300 }
301 
302 struct child_args {
303 	int unsfd;
304 	int mntnsfd;
305 	bool shared;
306 	int mntfd;
307 };
308 
get_nestedns_mount_cb(void * data)309 static int get_nestedns_mount_cb(void *data)
310 {
311 	struct child_args *ca = (struct child_args *)data;
312 	int ret;
313 
314 	ret = prepare_unpriv_mountns();
315 	if (ret)
316 		return 1;
317 
318 	if (ca->shared) {
319 		ret = mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0);
320 		if (ret)
321 			return 1;
322 	}
323 
324 	ret = open("/proc/self/ns/user", O_RDONLY);
325 	if (ret < 0)
326 		return 1;
327 	ca->unsfd = ret;
328 
329 	ret = open("/proc/self/ns/mnt", O_RDONLY);
330 	if (ret < 0)
331 		return 1;
332 	ca->mntnsfd = ret;
333 
334 	ret = open(SET_GROUP_A, O_RDONLY);
335 	if (ret < 0)
336 		return 1;
337 	ca->mntfd = ret;
338 
339 	return 0;
340 }
341 
TEST_F(move_mount_set_group,complex_sharing_copying)342 TEST_F(move_mount_set_group, complex_sharing_copying)
343 {
344 	struct child_args ca_from = {
345 		.shared = true,
346 	};
347 	struct child_args ca_to = {
348 		.shared = false,
349 	};
350 	pid_t pid;
351 	bool ret;
352 
353 	ret = move_mount_set_group_supported();
354 	ASSERT_GE(ret, 0);
355 	if (!ret)
356 		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
357 
358 	pid = do_clone(get_nestedns_mount_cb, (void *)&ca_from, CLONE_VFORK |
359 		       CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
360 	ASSERT_EQ(wait_for_pid(pid), 0);
361 
362 	pid = do_clone(get_nestedns_mount_cb, (void *)&ca_to, CLONE_VFORK |
363 		       CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
364 	ASSERT_EQ(wait_for_pid(pid), 0);
365 
366 	ASSERT_EQ(syscall(SYS_move_mount, ca_from.mntfd, "",
367 			  ca_to.mntfd, "", MOVE_MOUNT_SET_GROUP
368 			  | MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH),
369 		  0);
370 
371 	ASSERT_EQ(setns(ca_to.mntnsfd, CLONE_NEWNS), 0);
372 	ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1);
373 }
374 
375 TEST_HARNESS_MAIN
376