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
40char* add_uid_whitelist_policy_file = "/sys/kernel/security/safesetid/uid_allowlist_policy";
41char* add_gid_whitelist_policy_file = "/sys/kernel/security/safesetid/gid_allowlist_policy";
42
43static 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
52static 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
95static 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
107static 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
136static 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
163static 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
183static 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
207static 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
232static 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
266static 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
322static 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
378static 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
444static 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
453static 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
462static 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
476int 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