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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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