543 lines · c
1// SPDX-License-Identifier: GPL-2.02#define _GNU_SOURCE3#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 system23 * - requires securityfs to me mounted at /sys/kernel/security, e.g.:24 * mount -n -t securityfs -o nodev,noexec,nosuid securityfs /sys/kernel/security25 * - needs CONFIG_SECURITYFS and CONFIG_SAFESETID to be enabled26 */27 28#ifndef CLONE_NEWUSER29# define CLONE_NEWUSER 0x1000000030#endif31 32#define ROOT_UGID 033#define RESTRICTED_PARENT_UGID 134#define ALLOWED_CHILD1_UGID 235#define ALLOWED_CHILD2_UGID 336#define NO_POLICY_UGID 437 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 securityfs169 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 file251 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 else282 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 else309 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 else338 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 else365 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 else430 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 else471 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-root488 // user that doesn't have any restrictions (as long as it has489 // 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 caps495 drop_caps(true);496 // Need PR_SET_DUMPABLE flag set so we can write /proc/[pid]/uid_map497 // 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 restrictions505 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 caps530 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 in539 // /etc/passwd or flush policies that were added to the LSM.540 printf("test successful!\n");541 return EXIT_SUCCESS;542}543