18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#define _GNU_SOURCE
38c2ecf20Sopenharmony_ci#include <stdio.h>
48c2ecf20Sopenharmony_ci#include <errno.h>
58c2ecf20Sopenharmony_ci#include <pwd.h>
68c2ecf20Sopenharmony_ci#include <string.h>
78c2ecf20Sopenharmony_ci#include <syscall.h>
88c2ecf20Sopenharmony_ci#include <sys/capability.h>
98c2ecf20Sopenharmony_ci#include <sys/types.h>
108c2ecf20Sopenharmony_ci#include <sys/mount.h>
118c2ecf20Sopenharmony_ci#include <sys/prctl.h>
128c2ecf20Sopenharmony_ci#include <sys/wait.h>
138c2ecf20Sopenharmony_ci#include <stdlib.h>
148c2ecf20Sopenharmony_ci#include <unistd.h>
158c2ecf20Sopenharmony_ci#include <fcntl.h>
168c2ecf20Sopenharmony_ci#include <stdbool.h>
178c2ecf20Sopenharmony_ci#include <stdarg.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#ifndef CLONE_NEWUSER
208c2ecf20Sopenharmony_ci# define CLONE_NEWUSER 0x10000000
218c2ecf20Sopenharmony_ci#endif
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define ROOT_USER 0
248c2ecf20Sopenharmony_ci#define RESTRICTED_PARENT 1
258c2ecf20Sopenharmony_ci#define ALLOWED_CHILD1 2
268c2ecf20Sopenharmony_ci#define ALLOWED_CHILD2 3
278c2ecf20Sopenharmony_ci#define NO_POLICY_USER 4
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cichar* add_whitelist_policy_file = "/sys/kernel/security/safesetid/add_whitelist_policy";
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic void die(char *fmt, ...)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	va_list ap;
348c2ecf20Sopenharmony_ci	va_start(ap, fmt);
358c2ecf20Sopenharmony_ci	vfprintf(stderr, fmt, ap);
368c2ecf20Sopenharmony_ci	va_end(ap);
378c2ecf20Sopenharmony_ci	exit(EXIT_FAILURE);
388c2ecf20Sopenharmony_ci}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic bool vmaybe_write_file(bool enoent_ok, char *filename, char *fmt, va_list ap)
418c2ecf20Sopenharmony_ci{
428c2ecf20Sopenharmony_ci	char buf[4096];
438c2ecf20Sopenharmony_ci	int fd;
448c2ecf20Sopenharmony_ci	ssize_t written;
458c2ecf20Sopenharmony_ci	int buf_len;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	buf_len = vsnprintf(buf, sizeof(buf), fmt, ap);
488c2ecf20Sopenharmony_ci	if (buf_len < 0) {
498c2ecf20Sopenharmony_ci		printf("vsnprintf failed: %s\n",
508c2ecf20Sopenharmony_ci		    strerror(errno));
518c2ecf20Sopenharmony_ci		return false;
528c2ecf20Sopenharmony_ci	}
538c2ecf20Sopenharmony_ci	if (buf_len >= sizeof(buf)) {
548c2ecf20Sopenharmony_ci		printf("vsnprintf output truncated\n");
558c2ecf20Sopenharmony_ci		return false;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	fd = open(filename, O_WRONLY);
598c2ecf20Sopenharmony_ci	if (fd < 0) {
608c2ecf20Sopenharmony_ci		if ((errno == ENOENT) && enoent_ok)
618c2ecf20Sopenharmony_ci			return true;
628c2ecf20Sopenharmony_ci		return false;
638c2ecf20Sopenharmony_ci	}
648c2ecf20Sopenharmony_ci	written = write(fd, buf, buf_len);
658c2ecf20Sopenharmony_ci	if (written != buf_len) {
668c2ecf20Sopenharmony_ci		if (written >= 0) {
678c2ecf20Sopenharmony_ci			printf("short write to %s\n", filename);
688c2ecf20Sopenharmony_ci			return false;
698c2ecf20Sopenharmony_ci		} else {
708c2ecf20Sopenharmony_ci			printf("write to %s failed: %s\n",
718c2ecf20Sopenharmony_ci				filename, strerror(errno));
728c2ecf20Sopenharmony_ci			return false;
738c2ecf20Sopenharmony_ci		}
748c2ecf20Sopenharmony_ci	}
758c2ecf20Sopenharmony_ci	if (close(fd) != 0) {
768c2ecf20Sopenharmony_ci		printf("close of %s failed: %s\n",
778c2ecf20Sopenharmony_ci			filename, strerror(errno));
788c2ecf20Sopenharmony_ci		return false;
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci	return true;
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic bool write_file(char *filename, char *fmt, ...)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	va_list ap;
868c2ecf20Sopenharmony_ci	bool ret;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	va_start(ap, fmt);
898c2ecf20Sopenharmony_ci	ret = vmaybe_write_file(false, filename, fmt, ap);
908c2ecf20Sopenharmony_ci	va_end(ap);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	return ret;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic void ensure_user_exists(uid_t uid)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	struct passwd p;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	FILE *fd;
1008c2ecf20Sopenharmony_ci	char name_str[10];
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	if (getpwuid(uid) == NULL) {
1038c2ecf20Sopenharmony_ci		memset(&p,0x00,sizeof(p));
1048c2ecf20Sopenharmony_ci		fd=fopen("/etc/passwd","a");
1058c2ecf20Sopenharmony_ci		if (fd == NULL)
1068c2ecf20Sopenharmony_ci			die("couldn't open file\n");
1078c2ecf20Sopenharmony_ci		if (fseek(fd, 0, SEEK_END))
1088c2ecf20Sopenharmony_ci			die("couldn't fseek\n");
1098c2ecf20Sopenharmony_ci		snprintf(name_str, 10, "%d", uid);
1108c2ecf20Sopenharmony_ci		p.pw_name=name_str;
1118c2ecf20Sopenharmony_ci		p.pw_uid=uid;
1128c2ecf20Sopenharmony_ci		p.pw_gecos="Test account";
1138c2ecf20Sopenharmony_ci		p.pw_dir="/dev/null";
1148c2ecf20Sopenharmony_ci		p.pw_shell="/bin/false";
1158c2ecf20Sopenharmony_ci		int value = putpwent(&p,fd);
1168c2ecf20Sopenharmony_ci		if (value != 0)
1178c2ecf20Sopenharmony_ci			die("putpwent failed\n");
1188c2ecf20Sopenharmony_ci		if (fclose(fd))
1198c2ecf20Sopenharmony_ci			die("fclose failed\n");
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_cistatic void ensure_securityfs_mounted(void)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	int fd = open(add_whitelist_policy_file, O_WRONLY);
1268c2ecf20Sopenharmony_ci	if (fd < 0) {
1278c2ecf20Sopenharmony_ci		if (errno == ENOENT) {
1288c2ecf20Sopenharmony_ci			// Need to mount securityfs
1298c2ecf20Sopenharmony_ci			if (mount("securityfs", "/sys/kernel/security",
1308c2ecf20Sopenharmony_ci						"securityfs", 0, NULL) < 0)
1318c2ecf20Sopenharmony_ci				die("mounting securityfs failed\n");
1328c2ecf20Sopenharmony_ci		} else {
1338c2ecf20Sopenharmony_ci			die("couldn't find securityfs for unknown reason\n");
1348c2ecf20Sopenharmony_ci		}
1358c2ecf20Sopenharmony_ci	} else {
1368c2ecf20Sopenharmony_ci		if (close(fd) != 0) {
1378c2ecf20Sopenharmony_ci			die("close of %s failed: %s\n",
1388c2ecf20Sopenharmony_ci				add_whitelist_policy_file, strerror(errno));
1398c2ecf20Sopenharmony_ci		}
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic void write_policies(void)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	static char *policy_str =
1468c2ecf20Sopenharmony_ci		"1:2\n"
1478c2ecf20Sopenharmony_ci		"1:3\n"
1488c2ecf20Sopenharmony_ci		"2:2\n"
1498c2ecf20Sopenharmony_ci		"3:3\n";
1508c2ecf20Sopenharmony_ci	ssize_t written;
1518c2ecf20Sopenharmony_ci	int fd;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	fd = open(add_whitelist_policy_file, O_WRONLY);
1548c2ecf20Sopenharmony_ci	if (fd < 0)
1558c2ecf20Sopenharmony_ci		die("cant open add_whitelist_policy file\n");
1568c2ecf20Sopenharmony_ci	written = write(fd, policy_str, strlen(policy_str));
1578c2ecf20Sopenharmony_ci	if (written != strlen(policy_str)) {
1588c2ecf20Sopenharmony_ci		if (written >= 0) {
1598c2ecf20Sopenharmony_ci			die("short write to %s\n", add_whitelist_policy_file);
1608c2ecf20Sopenharmony_ci		} else {
1618c2ecf20Sopenharmony_ci			die("write to %s failed: %s\n",
1628c2ecf20Sopenharmony_ci				add_whitelist_policy_file, strerror(errno));
1638c2ecf20Sopenharmony_ci		}
1648c2ecf20Sopenharmony_ci	}
1658c2ecf20Sopenharmony_ci	if (close(fd) != 0) {
1668c2ecf20Sopenharmony_ci		die("close of %s failed: %s\n",
1678c2ecf20Sopenharmony_ci			add_whitelist_policy_file, strerror(errno));
1688c2ecf20Sopenharmony_ci	}
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic bool test_userns(bool expect_success)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	uid_t uid;
1748c2ecf20Sopenharmony_ci	char map_file_name[32];
1758c2ecf20Sopenharmony_ci	size_t sz = sizeof(map_file_name);
1768c2ecf20Sopenharmony_ci	pid_t cpid;
1778c2ecf20Sopenharmony_ci	bool success;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	uid = getuid();
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	int clone_flags = CLONE_NEWUSER;
1828c2ecf20Sopenharmony_ci	cpid = syscall(SYS_clone, clone_flags, NULL);
1838c2ecf20Sopenharmony_ci	if (cpid == -1) {
1848c2ecf20Sopenharmony_ci	    printf("clone failed");
1858c2ecf20Sopenharmony_ci	    return false;
1868c2ecf20Sopenharmony_ci	}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	if (cpid == 0) {	/* Code executed by child */
1898c2ecf20Sopenharmony_ci		// Give parent 1 second to write map file
1908c2ecf20Sopenharmony_ci		sleep(1);
1918c2ecf20Sopenharmony_ci		exit(EXIT_SUCCESS);
1928c2ecf20Sopenharmony_ci	} else {		/* Code executed by parent */
1938c2ecf20Sopenharmony_ci		if(snprintf(map_file_name, sz, "/proc/%d/uid_map", cpid) < 0) {
1948c2ecf20Sopenharmony_ci			printf("preparing file name string failed");
1958c2ecf20Sopenharmony_ci			return false;
1968c2ecf20Sopenharmony_ci		}
1978c2ecf20Sopenharmony_ci		success = write_file(map_file_name, "0 0 1", uid);
1988c2ecf20Sopenharmony_ci		return success == expect_success;
1998c2ecf20Sopenharmony_ci	}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	printf("should not reach here");
2028c2ecf20Sopenharmony_ci	return false;
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic void test_setuid(uid_t child_uid, bool expect_success)
2068c2ecf20Sopenharmony_ci{
2078c2ecf20Sopenharmony_ci	pid_t cpid, w;
2088c2ecf20Sopenharmony_ci	int wstatus;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	cpid = fork();
2118c2ecf20Sopenharmony_ci	if (cpid == -1) {
2128c2ecf20Sopenharmony_ci		die("fork\n");
2138c2ecf20Sopenharmony_ci	}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	if (cpid == 0) {	    /* Code executed by child */
2168c2ecf20Sopenharmony_ci		if (setuid(child_uid) < 0)
2178c2ecf20Sopenharmony_ci			exit(EXIT_FAILURE);
2188c2ecf20Sopenharmony_ci		if (getuid() == child_uid)
2198c2ecf20Sopenharmony_ci			exit(EXIT_SUCCESS);
2208c2ecf20Sopenharmony_ci		else
2218c2ecf20Sopenharmony_ci			exit(EXIT_FAILURE);
2228c2ecf20Sopenharmony_ci	} else {		 /* Code executed by parent */
2238c2ecf20Sopenharmony_ci		do {
2248c2ecf20Sopenharmony_ci			w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
2258c2ecf20Sopenharmony_ci			if (w == -1) {
2268c2ecf20Sopenharmony_ci				die("waitpid\n");
2278c2ecf20Sopenharmony_ci			}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci			if (WIFEXITED(wstatus)) {
2308c2ecf20Sopenharmony_ci				if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
2318c2ecf20Sopenharmony_ci					if (expect_success) {
2328c2ecf20Sopenharmony_ci						return;
2338c2ecf20Sopenharmony_ci					} else {
2348c2ecf20Sopenharmony_ci						die("unexpected success\n");
2358c2ecf20Sopenharmony_ci					}
2368c2ecf20Sopenharmony_ci				} else {
2378c2ecf20Sopenharmony_ci					if (expect_success) {
2388c2ecf20Sopenharmony_ci						die("unexpected failure\n");
2398c2ecf20Sopenharmony_ci					} else {
2408c2ecf20Sopenharmony_ci						return;
2418c2ecf20Sopenharmony_ci					}
2428c2ecf20Sopenharmony_ci				}
2438c2ecf20Sopenharmony_ci			} else if (WIFSIGNALED(wstatus)) {
2448c2ecf20Sopenharmony_ci				if (WTERMSIG(wstatus) == 9) {
2458c2ecf20Sopenharmony_ci					if (expect_success)
2468c2ecf20Sopenharmony_ci						die("killed unexpectedly\n");
2478c2ecf20Sopenharmony_ci					else
2488c2ecf20Sopenharmony_ci						return;
2498c2ecf20Sopenharmony_ci				} else {
2508c2ecf20Sopenharmony_ci					die("unexpected signal: %d\n", wstatus);
2518c2ecf20Sopenharmony_ci				}
2528c2ecf20Sopenharmony_ci			} else {
2538c2ecf20Sopenharmony_ci				die("unexpected status: %d\n", wstatus);
2548c2ecf20Sopenharmony_ci			}
2558c2ecf20Sopenharmony_ci		} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	die("should not reach here\n");
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic void ensure_users_exist(void)
2628c2ecf20Sopenharmony_ci{
2638c2ecf20Sopenharmony_ci	ensure_user_exists(ROOT_USER);
2648c2ecf20Sopenharmony_ci	ensure_user_exists(RESTRICTED_PARENT);
2658c2ecf20Sopenharmony_ci	ensure_user_exists(ALLOWED_CHILD1);
2668c2ecf20Sopenharmony_ci	ensure_user_exists(ALLOWED_CHILD2);
2678c2ecf20Sopenharmony_ci	ensure_user_exists(NO_POLICY_USER);
2688c2ecf20Sopenharmony_ci}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistatic void drop_caps(bool setid_retained)
2718c2ecf20Sopenharmony_ci{
2728c2ecf20Sopenharmony_ci	cap_value_t cap_values[] = {CAP_SETUID, CAP_SETGID};
2738c2ecf20Sopenharmony_ci	cap_t caps;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	caps = cap_get_proc();
2768c2ecf20Sopenharmony_ci	if (setid_retained)
2778c2ecf20Sopenharmony_ci		cap_set_flag(caps, CAP_EFFECTIVE, 2, cap_values, CAP_SET);
2788c2ecf20Sopenharmony_ci	else
2798c2ecf20Sopenharmony_ci		cap_clear(caps);
2808c2ecf20Sopenharmony_ci	cap_set_proc(caps);
2818c2ecf20Sopenharmony_ci	cap_free(caps);
2828c2ecf20Sopenharmony_ci}
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ciint main(int argc, char **argv)
2858c2ecf20Sopenharmony_ci{
2868c2ecf20Sopenharmony_ci	ensure_users_exist();
2878c2ecf20Sopenharmony_ci	ensure_securityfs_mounted();
2888c2ecf20Sopenharmony_ci	write_policies();
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	if (prctl(PR_SET_KEEPCAPS, 1L))
2918c2ecf20Sopenharmony_ci		die("Error with set keepcaps\n");
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	// First test to make sure we can write userns mappings from a user
2948c2ecf20Sopenharmony_ci	// that doesn't have any restrictions (as long as it has CAP_SETUID);
2958c2ecf20Sopenharmony_ci	if (setuid(NO_POLICY_USER) < 0)
2968c2ecf20Sopenharmony_ci		die("Error with set uid(%d)\n", NO_POLICY_USER);
2978c2ecf20Sopenharmony_ci	if (setgid(NO_POLICY_USER) < 0)
2988c2ecf20Sopenharmony_ci		die("Error with set gid(%d)\n", NO_POLICY_USER);
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	// Take away all but setid caps
3018c2ecf20Sopenharmony_ci	drop_caps(true);
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci	// Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map
3048c2ecf20Sopenharmony_ci	// from non-root parent process.
3058c2ecf20Sopenharmony_ci	if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0))
3068c2ecf20Sopenharmony_ci		die("Error with set dumpable\n");
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	if (!test_userns(true)) {
3098c2ecf20Sopenharmony_ci		die("test_userns failed when it should work\n");
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	if (setuid(RESTRICTED_PARENT) < 0)
3138c2ecf20Sopenharmony_ci		die("Error with set uid(%d)\n", RESTRICTED_PARENT);
3148c2ecf20Sopenharmony_ci	if (setgid(RESTRICTED_PARENT) < 0)
3158c2ecf20Sopenharmony_ci		die("Error with set gid(%d)\n", RESTRICTED_PARENT);
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	test_setuid(ROOT_USER, false);
3188c2ecf20Sopenharmony_ci	test_setuid(ALLOWED_CHILD1, true);
3198c2ecf20Sopenharmony_ci	test_setuid(ALLOWED_CHILD2, true);
3208c2ecf20Sopenharmony_ci	test_setuid(NO_POLICY_USER, false);
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci	if (!test_userns(false)) {
3238c2ecf20Sopenharmony_ci		die("test_userns worked when it should fail\n");
3248c2ecf20Sopenharmony_ci	}
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	// Now take away all caps
3278c2ecf20Sopenharmony_ci	drop_caps(false);
3288c2ecf20Sopenharmony_ci	test_setuid(2, false);
3298c2ecf20Sopenharmony_ci	test_setuid(3, false);
3308c2ecf20Sopenharmony_ci	test_setuid(4, false);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	// NOTE: this test doesn't clean up users that were created in
3338c2ecf20Sopenharmony_ci	// /etc/passwd or flush policies that were added to the LSM.
3348c2ecf20Sopenharmony_ci	return EXIT_SUCCESS;
3358c2ecf20Sopenharmony_ci}
336