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