1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <stdio.h>
4 #include <errno.h>
5 #include <pwd.h>
6 #include <grp.h>
7 #include <string.h>
8 #include <syscall.h>
9 #include <sys/capability.h>
10 #include <sys/types.h>
11 #include <sys/mount.h>
12 #include <sys/prctl.h>
13 #include <sys/wait.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <stdbool.h>
18 #include <stdarg.h>
19 
20 /*
21  * NOTES about this test:
22  * - requries libcap-dev to be installed on test system
23  * - requires securityfs to me mounted at /sys/kernel/security, e.g.:
24  * mount -n -t securityfs -o nodev,noexec,nosuid securityfs /sys/kernel/security
25  * - needs CONFIG_SECURITYFS and CONFIG_SAFESETID to be enabled
26  */
27 
28 #ifndef CLONE_NEWUSER
29 # define CLONE_NEWUSER 0x10000000
30 #endif
31 
32 #define ROOT_UGID 0
33 #define RESTRICTED_PARENT_UGID 1
34 #define ALLOWED_CHILD1_UGID 2
35 #define ALLOWED_CHILD2_UGID 3
36 #define NO_POLICY_UGID 4
37 
38 #define UGID_POLICY_STRING "1:2\n1:3\n2:2\n3:3\n"
39 
40 char* add_uid_whitelist_policy_file = "/sys/kernel/security/safesetid/uid_allowlist_policy";
41 char* add_gid_whitelist_policy_file = "/sys/kernel/security/safesetid/gid_allowlist_policy";
42 
die(char * fmt,...)43 static void die(char *fmt, ...)
44 {
45 	va_list ap;
46 	va_start(ap, fmt);
47 	vfprintf(stderr, fmt, ap);
48 	va_end(ap);
49 	exit(EXIT_FAILURE);
50 }
51 
vmaybe_write_file(bool enoent_ok,char * filename,char * fmt,va_list ap)52 static bool vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap)
53 {
54 	char buf[4096];
55 	int fd;
56 	ssize_t written;
57 	int buf_len;
58 
59 	buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
60 	if (buf_len < 0) {
61 		printf("vsnprintf failed: %s\n",
62 		    strerror(errno));
63 		return false;
64 	}
65 	if (buf_len >= sizeof(buf)) {
66 		printf("vsnprintf output truncated\n");
67 		return false;
68 	}
69 
70 	fd = open(filename, O_WRONLY);
71 	if (fd < 0) {
72 		if ((errno == ENOENT) && enoent_ok)
73 			return true;
74 		return false;
75 	}
76 	written = write(fd, buf, buf_len);
77 	if (written != buf_len) {
78 		if (written >= 0) {
79 			printf("short write to %s\n", filename);
80 			return false;
81 		} else {
82 			printf("write to %s failed: %s\n",
83 				filename, strerror(errno));
84 			return false;
85 		}
86 	}
87 	if (close(fd) != 0) {
88 		printf("close of %s failed: %s\n",
89 			filename, strerror(errno));
90 		return false;
91 	}
92 	return true;
93 }
94 
write_file(char * filename,char * fmt,...)95 static bool write_file(char *filename, char *fmt, ...)
96 {
97 	va_list ap;
98 	bool ret;
99 
100 	va_start(ap, fmt);
101 	ret = vmaybe_write_file(false, filename, fmt, ap);
102 	va_end(ap);
103 
104 	return ret;
105 }
106 
ensure_user_exists(uid_t uid)107 static void ensure_user_exists(uid_t uid)
108 {
109 	struct passwd p;
110 
111 	FILE *fd;
112 	char name_str[10];
113 
114 	if (getpwuid(uid) == NULL) {
115 		memset(&p,0x00,sizeof(p));
116 		fd=fopen("/etc/passwd","a");
117 		if (fd == NULL)
118 			die("couldn't open file\n");
119 		if (fseek(fd, 0, SEEK_END))
120 			die("couldn't fseek\n");
121 		snprintf(name_str, 10, "user %d", uid);
122 		p.pw_name=name_str;
123 		p.pw_uid=uid;
124 		p.pw_gid=uid;
125 		p.pw_gecos="Test account";
126 		p.pw_dir="/dev/null";
127 		p.pw_shell="/bin/false";
128 		int value = putpwent(&p,fd);
129 		if (value != 0)
130 			die("putpwent failed\n");
131 		if (fclose(fd))
132 			die("fclose failed\n");
133 	}
134 }
135 
ensure_group_exists(gid_t gid)136 static void ensure_group_exists(gid_t gid)
137 {
138 	struct group g;
139 
140 	FILE *fd;
141 	char name_str[10];
142 
143 	if (getgrgid(gid) == NULL) {
144 		memset(&g,0x00,sizeof(g));
145 		fd=fopen("/etc/group","a");
146 		if (fd == NULL)
147 			die("couldn't open group file\n");
148 		if (fseek(fd, 0, SEEK_END))
149 			die("couldn't fseek group file\n");
150 		snprintf(name_str, 10, "group %d", gid);
151 		g.gr_name=name_str;
152 		g.gr_gid=gid;
153 		g.gr_passwd=NULL;
154 		g.gr_mem=NULL;
155 		int value = putgrent(&g,fd);
156 		if (value != 0)
157 			die("putgrent failed\n");
158 		if (fclose(fd))
159 			die("fclose failed\n");
160 	}
161 }
162 
ensure_securityfs_mounted(void)163 static void ensure_securityfs_mounted(void)
164 {
165 	int fd = open(add_uid_whitelist_policy_file, O_WRONLY);
166 	if (fd < 0) {
167 		if (errno == ENOENT) {
168 			// Need to mount securityfs
169 			if (mount("securityfs", "/sys/kernel/security",
170 						"securityfs", 0, NULL) < 0)
171 				die("mounting securityfs failed\n");
172 		} else {
173 			die("couldn't find securityfs for unknown reason\n");
174 		}
175 	} else {
176 		if (close(fd) != 0) {
177 			die("close of %s failed: %s\n",
178 				add_uid_whitelist_policy_file, strerror(errno));
179 		}
180 	}
181 }
182 
write_uid_policies()183 static void write_uid_policies()
184 {
185 	static char *policy_str = UGID_POLICY_STRING;
186 	ssize_t written;
187 	int fd;
188 
189 	fd = open(add_uid_whitelist_policy_file, O_WRONLY);
190 	if (fd < 0)
191 		die("can't open add_uid_whitelist_policy file\n");
192 	written = write(fd, policy_str, strlen(policy_str));
193 	if (written != strlen(policy_str)) {
194 		if (written >= 0) {
195 			die("short write to %s\n", add_uid_whitelist_policy_file);
196 		} else {
197 			die("write to %s failed: %s\n",
198 				add_uid_whitelist_policy_file, strerror(errno));
199 		}
200 	}
201 	if (close(fd) != 0) {
202 		die("close of %s failed: %s\n",
203 			add_uid_whitelist_policy_file, strerror(errno));
204 	}
205 }
206 
write_gid_policies()207 static void write_gid_policies()
208 {
209 	static char *policy_str = UGID_POLICY_STRING;
210 	ssize_t written;
211 	int fd;
212 
213 	fd = open(add_gid_whitelist_policy_file, O_WRONLY);
214 	if (fd < 0)
215 		die("can't open add_gid_whitelist_policy file\n");
216 	written = write(fd, policy_str, strlen(policy_str));
217 	if (written != strlen(policy_str)) {
218 		if (written >= 0) {
219 			die("short write to %s\n", add_gid_whitelist_policy_file);
220 		} else {
221 			die("write to %s failed: %s\n",
222 				add_gid_whitelist_policy_file, strerror(errno));
223 		}
224 	}
225 	if (close(fd) != 0) {
226 		die("close of %s failed: %s\n",
227 			add_gid_whitelist_policy_file, strerror(errno));
228 	}
229 }
230 
231 
test_userns(bool expect_success)232 static bool test_userns(bool expect_success)
233 {
234 	uid_t uid;
235 	char map_file_name[32];
236 	size_t sz = sizeof(map_file_name);
237 	pid_t cpid;
238 	bool success;
239 
240 	uid = getuid();
241 
242 	int clone_flags = CLONE_NEWUSER;
243 	cpid = syscall(SYS_clone, clone_flags, NULL);
244 	if (cpid == -1) {
245 	    printf("clone failed");
246 	    return false;
247 	}
248 
249 	if (cpid == 0) {	/* Code executed by child */
250 		// Give parent 1 second to write map file
251 		sleep(1);
252 		exit(EXIT_SUCCESS);
253 	} else {		/* Code executed by parent */
254 		if(snprintf(map_file_name, sz, "/proc/%d/uid_map", cpid) < 0) {
255 			printf("preparing file name string failed");
256 			return false;
257 		}
258 		success = write_file(map_file_name, "0 %d 1", uid);
259 		return success == expect_success;
260 	}
261 
262 	printf("should not reach here");
263 	return false;
264 }
265 
test_setuid(uid_t child_uid,bool expect_success)266 static void test_setuid(uid_t child_uid, bool expect_success)
267 {
268 	pid_t cpid, w;
269 	int wstatus;
270 
271 	cpid = fork();
272 	if (cpid == -1) {
273 		die("fork\n");
274 	}
275 
276 	if (cpid == 0) {	    /* Code executed by child */
277 		if (setuid(child_uid) < 0)
278 			exit(EXIT_FAILURE);
279 		if (getuid() == child_uid)
280 			exit(EXIT_SUCCESS);
281 		else
282 			exit(EXIT_FAILURE);
283 	} else {		 /* Code executed by parent */
284 		do {
285 			w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
286 			if (w == -1) {
287 				die("waitpid\n");
288 			}
289 
290 			if (WIFEXITED(wstatus)) {
291 				if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
292 					if (expect_success) {
293 						return;
294 					} else {
295 						die("unexpected success\n");
296 					}
297 				} else {
298 					if (expect_success) {
299 						die("unexpected failure\n");
300 					} else {
301 						return;
302 					}
303 				}
304 			} else if (WIFSIGNALED(wstatus)) {
305 				if (WTERMSIG(wstatus) == 9) {
306 					if (expect_success)
307 						die("killed unexpectedly\n");
308 					else
309 						return;
310 				} else {
311 					die("unexpected signal: %d\n", wstatus);
312 				}
313 			} else {
314 				die("unexpected status: %d\n", wstatus);
315 			}
316 		} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
317 	}
318 
319 	die("should not reach here\n");
320 }
321 
test_setgid(gid_t child_gid,bool expect_success)322 static void test_setgid(gid_t child_gid, bool expect_success)
323 {
324 	pid_t cpid, w;
325 	int wstatus;
326 
327 	cpid = fork();
328 	if (cpid == -1) {
329 		die("fork\n");
330 	}
331 
332 	if (cpid == 0) {	    /* Code executed by child */
333 		if (setgid(child_gid) < 0)
334 			exit(EXIT_FAILURE);
335 		if (getgid() == child_gid)
336 			exit(EXIT_SUCCESS);
337 		else
338 			exit(EXIT_FAILURE);
339 	} else {		 /* Code executed by parent */
340 		do {
341 			w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
342 			if (w == -1) {
343 				die("waitpid\n");
344 			}
345 
346 			if (WIFEXITED(wstatus)) {
347 				if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
348 					if (expect_success) {
349 						return;
350 					} else {
351 						die("unexpected success\n");
352 					}
353 				} else {
354 					if (expect_success) {
355 						die("unexpected failure\n");
356 					} else {
357 						return;
358 					}
359 				}
360 			} else if (WIFSIGNALED(wstatus)) {
361 				if (WTERMSIG(wstatus) == 9) {
362 					if (expect_success)
363 						die("killed unexpectedly\n");
364 					else
365 						return;
366 				} else {
367 					die("unexpected signal: %d\n", wstatus);
368 				}
369 			} else {
370 				die("unexpected status: %d\n", wstatus);
371 			}
372 		} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
373 	}
374 
375 	die("should not reach here\n");
376 }
377 
test_setgroups(gid_t * child_groups,size_t len,bool expect_success)378 static void test_setgroups(gid_t* child_groups, size_t len, bool expect_success)
379 {
380 	pid_t cpid, w;
381 	int wstatus;
382 	gid_t groupset[len];
383 	int i, j;
384 
385 	cpid = fork();
386 	if (cpid == -1) {
387 		die("fork\n");
388 	}
389 
390 	if (cpid == 0) {	    /* Code executed by child */
391 		if (setgroups(len, child_groups) != 0)
392 			exit(EXIT_FAILURE);
393 		if (getgroups(len, groupset) != len)
394 			exit(EXIT_FAILURE);
395 		for (i = 0; i < len; i++) {
396 			for (j = 0; j < len; j++) {
397 				if (child_groups[i] == groupset[j])
398 					break;
399 				if (j == len - 1)
400 					exit(EXIT_FAILURE);
401 			}
402 		}
403 		exit(EXIT_SUCCESS);
404 	} else {		 /* Code executed by parent */
405 		do {
406 			w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
407 			if (w == -1) {
408 				die("waitpid\n");
409 			}
410 
411 			if (WIFEXITED(wstatus)) {
412 				if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
413 					if (expect_success) {
414 						return;
415 					} else {
416 						die("unexpected success\n");
417 					}
418 				} else {
419 					if (expect_success) {
420 						die("unexpected failure\n");
421 					} else {
422 						return;
423 					}
424 				}
425 			} else if (WIFSIGNALED(wstatus)) {
426 				if (WTERMSIG(wstatus) == 9) {
427 					if (expect_success)
428 						die("killed unexpectedly\n");
429 					else
430 						return;
431 				} else {
432 					die("unexpected signal: %d\n", wstatus);
433 				}
434 			} else {
435 				die("unexpected status: %d\n", wstatus);
436 			}
437 		} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
438 	}
439 
440 	die("should not reach here\n");
441 }
442 
443 
ensure_users_exist(void)444 static void ensure_users_exist(void)
445 {
446 	ensure_user_exists(ROOT_UGID);
447 	ensure_user_exists(RESTRICTED_PARENT_UGID);
448 	ensure_user_exists(ALLOWED_CHILD1_UGID);
449 	ensure_user_exists(ALLOWED_CHILD2_UGID);
450 	ensure_user_exists(NO_POLICY_UGID);
451 }
452 
ensure_groups_exist(void)453 static void ensure_groups_exist(void)
454 {
455 	ensure_group_exists(ROOT_UGID);
456 	ensure_group_exists(RESTRICTED_PARENT_UGID);
457 	ensure_group_exists(ALLOWED_CHILD1_UGID);
458 	ensure_group_exists(ALLOWED_CHILD2_UGID);
459 	ensure_group_exists(NO_POLICY_UGID);
460 }
461 
drop_caps(bool setid_retained)462 static void drop_caps(bool setid_retained)
463 {
464 	cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID};
465 	cap_t caps;
466 
467 	caps = cap_get_proc();
468 	if (setid_retained)
469 		cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET);
470 	else
471 		cap_clear(caps);
472 	cap_set_proc(caps);
473 	cap_free(caps);
474 }
475 
main(int argc,char ** argv)476 int main(int argc, char **argv)
477 {
478 	ensure_groups_exist();
479 	ensure_users_exist();
480 	ensure_securityfs_mounted();
481 	write_uid_policies();
482 	write_gid_policies();
483 
484 	if (prctl(PR_SET_KEEPCAPS, 1L))
485 		die("Error with set keepcaps\n");
486 
487 	// First test to make sure we can write userns mappings from a non-root
488 	// user that doesn't have any restrictions (as long as it has
489 	// CAP_SETUID);
490 	if (setgid(NO_POLICY_UGID) < 0)
491 		die("Error with set gid(%d)\n", NO_POLICY_UGID);
492 	if (setuid(NO_POLICY_UGID) < 0)
493 		die("Error with set uid(%d)\n", NO_POLICY_UGID);
494 	// Take away all but setid caps
495 	drop_caps(true);
496 	// Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map
497 	// from non-root parent process.
498 	if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0))
499 		die("Error with set dumpable\n");
500 	if (!test_userns(true)) {
501 		die("test_userns failed when it should work\n");
502 	}
503 
504 	// Now switch to a user/group with restrictions
505 	if (setgid(RESTRICTED_PARENT_UGID) < 0)
506 		die("Error with set gid(%d)\n", RESTRICTED_PARENT_UGID);
507 	if (setuid(RESTRICTED_PARENT_UGID) < 0)
508 		die("Error with set uid(%d)\n", RESTRICTED_PARENT_UGID);
509 
510 	test_setuid(ROOT_UGID, false);
511 	test_setuid(ALLOWED_CHILD1_UGID, true);
512 	test_setuid(ALLOWED_CHILD2_UGID, true);
513 	test_setuid(NO_POLICY_UGID, false);
514 
515 	test_setgid(ROOT_UGID, false);
516 	test_setgid(ALLOWED_CHILD1_UGID, true);
517 	test_setgid(ALLOWED_CHILD2_UGID, true);
518 	test_setgid(NO_POLICY_UGID, false);
519 
520 	gid_t allowed_supp_groups[2] = {ALLOWED_CHILD1_UGID, ALLOWED_CHILD2_UGID};
521 	gid_t disallowed_supp_groups[2] = {ROOT_UGID, NO_POLICY_UGID};
522 	test_setgroups(allowed_supp_groups, 2, true);
523 	test_setgroups(disallowed_supp_groups, 2, false);
524 
525 	if (!test_userns(false)) {
526 		die("test_userns worked when it should fail\n");
527 	}
528 
529 	// Now take away all caps
530 	drop_caps(false);
531 	test_setuid(2, false);
532 	test_setuid(3, false);
533 	test_setuid(4, false);
534 	test_setgid(2, false);
535 	test_setgid(3, false);
536 	test_setgid(4, false);
537 
538 	// NOTE: this test doesn't clean up users that were created in
539 	// /etc/passwd or flush policies that were added to the LSM.
540 	printf("test successful!\n");
541 	return EXIT_SUCCESS;
542 }
543