1*72168095SRobert Mustacchi /* 2*72168095SRobert Mustacchi * This file and its contents are supplied under the terms of the 3*72168095SRobert Mustacchi * Common Development and Distribution License ("CDDL"), version 1.0. 4*72168095SRobert Mustacchi * You may only use this file in accordance with the terms of version 5*72168095SRobert Mustacchi * 1.0 of the CDDL. 6*72168095SRobert Mustacchi * 7*72168095SRobert Mustacchi * A full copy of the text of the CDDL should have accompanied this 8*72168095SRobert Mustacchi * source. A copy of the CDDL is also available via the Internet at 9*72168095SRobert Mustacchi * http://www.illumos.org/license/CDDL. 10*72168095SRobert Mustacchi */ 11*72168095SRobert Mustacchi 12*72168095SRobert Mustacchi /* 13*72168095SRobert Mustacchi * Copyright 2025 Oxide Computer Company 14*72168095SRobert Mustacchi */ 15*72168095SRobert Mustacchi 16*72168095SRobert Mustacchi /* 17*72168095SRobert Mustacchi * Test whether or not secure_getenv(3C) correctly ratchets off certain 18*72168095SRobert Mustacchi * privileges. In general, this happens if our uid/euid or gid/egid mismatch or 19*72168095SRobert Mustacchi * whether or not there was some kind of privilege escalation. We have a checker 20*72168095SRobert Mustacchi * program that we'll use to actually look at this. 21*72168095SRobert Mustacchi */ 22*72168095SRobert Mustacchi 23*72168095SRobert Mustacchi #include <stdlib.h> 24*72168095SRobert Mustacchi #include <err.h> 25*72168095SRobert Mustacchi #include <stdbool.h> 26*72168095SRobert Mustacchi #include <sys/sysmacros.h> 27*72168095SRobert Mustacchi #include <unistd.h> 28*72168095SRobert Mustacchi #include <sys/fork.h> 29*72168095SRobert Mustacchi #include <wait.h> 30*72168095SRobert Mustacchi #include <pwd.h> 31*72168095SRobert Mustacchi #include <limits.h> 32*72168095SRobert Mustacchi #include <libgen.h> 33*72168095SRobert Mustacchi #include <sys/debug.h> 34*72168095SRobert Mustacchi #include <priv.h> 35*72168095SRobert Mustacchi 36*72168095SRobert Mustacchi static const char *getenv_checker = "checker"; 37*72168095SRobert Mustacchi static char getenv_path[PATH_MAX]; 38*72168095SRobert Mustacchi static struct passwd *getenv_nobody; 39*72168095SRobert Mustacchi 40*72168095SRobert Mustacchi typedef struct { 41*72168095SRobert Mustacchi const char *gf_desc; 42*72168095SRobert Mustacchi bool gf_secure; 43*72168095SRobert Mustacchi void (*gf_forker)(const char *); 44*72168095SRobert Mustacchi } getenv_fork_t; 45*72168095SRobert Mustacchi 46*72168095SRobert Mustacchi /* 47*72168095SRobert Mustacchi * Set all of our effective IDs to nobody. 48*72168095SRobert Mustacchi */ 49*72168095SRobert Mustacchi static void 50*72168095SRobert Mustacchi getenv_fork_nobody(const char *desc) 51*72168095SRobert Mustacchi { 52*72168095SRobert Mustacchi if (setgid(getenv_nobody->pw_gid) != 0) { 53*72168095SRobert Mustacchi errx(EXIT_FAILURE, "TEST FAILED: %s: failed to setgid to " 54*72168095SRobert Mustacchi "nobody (%u)", desc, getenv_nobody->pw_gid); 55*72168095SRobert Mustacchi } 56*72168095SRobert Mustacchi 57*72168095SRobert Mustacchi if (setuid(getenv_nobody->pw_uid) != 0) { 58*72168095SRobert Mustacchi errx(EXIT_FAILURE, "TEST FAILED: %s: failed to setuid to " 59*72168095SRobert Mustacchi "nobody (%u)", desc, getenv_nobody->pw_uid); 60*72168095SRobert Mustacchi } 61*72168095SRobert Mustacchi } 62*72168095SRobert Mustacchi 63*72168095SRobert Mustacchi static void 64*72168095SRobert Mustacchi getenv_fork_seteuid(const char *desc) 65*72168095SRobert Mustacchi { 66*72168095SRobert Mustacchi if (seteuid(getenv_nobody->pw_uid) != 0) { 67*72168095SRobert Mustacchi errx(EXIT_FAILURE, "TEST FAILED: %s: failed to seteuid to " 68*72168095SRobert Mustacchi "nobody (%u)", desc, getenv_nobody->pw_uid); 69*72168095SRobert Mustacchi } 70*72168095SRobert Mustacchi } 71*72168095SRobert Mustacchi 72*72168095SRobert Mustacchi static void 73*72168095SRobert Mustacchi getenv_fork_setegid(const char *desc) 74*72168095SRobert Mustacchi { 75*72168095SRobert Mustacchi if (setegid(getenv_nobody->pw_gid) != 0) { 76*72168095SRobert Mustacchi errx(EXIT_FAILURE, "TEST FAILED: %s: failed to setegid to " 77*72168095SRobert Mustacchi "nobody (%u)", desc, getenv_nobody->pw_gid); 78*72168095SRobert Mustacchi } 79*72168095SRobert Mustacchi } 80*72168095SRobert Mustacchi 81*72168095SRobert Mustacchi static void 82*72168095SRobert Mustacchi getenv_fork_seteugid(const char *desc) 83*72168095SRobert Mustacchi { 84*72168095SRobert Mustacchi getenv_fork_setegid(desc); 85*72168095SRobert Mustacchi getenv_fork_seteuid(desc); 86*72168095SRobert Mustacchi } 87*72168095SRobert Mustacchi 88*72168095SRobert Mustacchi /* 89*72168095SRobert Mustacchi * An executing process is considered to have a privilege increase if the 90*72168095SRobert Mustacchi * inheritable set is larger than the permitted set. Because this is launched as 91*72168095SRobert Mustacchi * a privileged process we generally have a default inheritable set of 'basic', 92*72168095SRobert Mustacchi * but our permitted is 'all'. So we first increase our inheritable set and then 93*72168095SRobert Mustacchi * drop our permitted set. 94*72168095SRobert Mustacchi */ 95*72168095SRobert Mustacchi static void 96*72168095SRobert Mustacchi getenv_fork_privs(const char *desc) 97*72168095SRobert Mustacchi { 98*72168095SRobert Mustacchi priv_set_t *priv = priv_allocset(); 99*72168095SRobert Mustacchi 100*72168095SRobert Mustacchi if (priv == NULL) { 101*72168095SRobert Mustacchi err(EXIT_FAILURE, "TEST FAILED: %s: failed to allocate a " 102*72168095SRobert Mustacchi "priv_set_t", desc); 103*72168095SRobert Mustacchi } 104*72168095SRobert Mustacchi 105*72168095SRobert Mustacchi VERIFY0(priv_addset(priv, PRIV_PROC_CLOCK_HIGHRES)); 106*72168095SRobert Mustacchi if (setppriv(PRIV_ON, PRIV_INHERITABLE, priv) != 0) { 107*72168095SRobert Mustacchi err(EXIT_FAILURE, "TEST FAILED: %s: failed to add privs to " 108*72168095SRobert Mustacchi "the inheritable set", desc); 109*72168095SRobert Mustacchi } 110*72168095SRobert Mustacchi 111*72168095SRobert Mustacchi priv_basicset(priv); 112*72168095SRobert Mustacchi if (setppriv(PRIV_SET, PRIV_PERMITTED, priv) != 0) { 113*72168095SRobert Mustacchi err(EXIT_FAILURE, "TEST FAILED: %s: failed to set permitted " 114*72168095SRobert Mustacchi "set to the basic set", desc); 115*72168095SRobert Mustacchi } 116*72168095SRobert Mustacchi 117*72168095SRobert Mustacchi priv_freeset(priv); 118*72168095SRobert Mustacchi } 119*72168095SRobert Mustacchi 120*72168095SRobert Mustacchi static const getenv_fork_t getenv_tests[] = { { 121*72168095SRobert Mustacchi .gf_desc = "change all to nobody", 122*72168095SRobert Mustacchi .gf_secure = false, 123*72168095SRobert Mustacchi .gf_forker = getenv_fork_nobody 124*72168095SRobert Mustacchi }, { 125*72168095SRobert Mustacchi .gf_desc = "seteuid to nobody", 126*72168095SRobert Mustacchi .gf_secure = true, 127*72168095SRobert Mustacchi .gf_forker = getenv_fork_seteuid 128*72168095SRobert Mustacchi }, { 129*72168095SRobert Mustacchi .gf_desc = "setegid to nobody", 130*72168095SRobert Mustacchi .gf_secure = true, 131*72168095SRobert Mustacchi .gf_forker = getenv_fork_setegid 132*72168095SRobert Mustacchi }, { 133*72168095SRobert Mustacchi .gf_desc = "sete[ug]id to nobody", 134*72168095SRobert Mustacchi .gf_secure = true, 135*72168095SRobert Mustacchi .gf_forker = getenv_fork_seteugid 136*72168095SRobert Mustacchi }, { 137*72168095SRobert Mustacchi .gf_desc = "privilege increase", 138*72168095SRobert Mustacchi .gf_secure = true, 139*72168095SRobert Mustacchi .gf_forker = getenv_fork_privs 140*72168095SRobert Mustacchi } }; 141*72168095SRobert Mustacchi 142*72168095SRobert Mustacchi static bool 143*72168095SRobert Mustacchi getenv_fork(const getenv_fork_t *test) 144*72168095SRobert Mustacchi { 145*72168095SRobert Mustacchi pid_t child; 146*72168095SRobert Mustacchi siginfo_t cret; 147*72168095SRobert Mustacchi 148*72168095SRobert Mustacchi child = forkx(FORK_NOSIGCHLD | FORK_WAITPID); 149*72168095SRobert Mustacchi if (child == 0) { 150*72168095SRobert Mustacchi char *argv[4] = { (char *)getenv_checker, (char *)test->gf_desc, 151*72168095SRobert Mustacchi NULL, NULL }; 152*72168095SRobert Mustacchi if (test->gf_secure) { 153*72168095SRobert Mustacchi argv[2] = "secure"; 154*72168095SRobert Mustacchi } 155*72168095SRobert Mustacchi test->gf_forker(test->gf_desc); 156*72168095SRobert Mustacchi (void) execv(getenv_path, argv); 157*72168095SRobert Mustacchi warn("TEST FAILED: %s: failed to exec verifier %s", 158*72168095SRobert Mustacchi test->gf_desc, getenv_path); 159*72168095SRobert Mustacchi _exit(EXIT_FAILURE); 160*72168095SRobert Mustacchi } 161*72168095SRobert Mustacchi 162*72168095SRobert Mustacchi if (waitid(P_PID, child, &cret, WEXITED) < 0) { 163*72168095SRobert Mustacchi err(EXIT_FAILURE, "TEST FAILED: internal test failure waiting " 164*72168095SRobert Mustacchi "for forked child to report"); 165*72168095SRobert Mustacchi } 166*72168095SRobert Mustacchi 167*72168095SRobert Mustacchi if (cret.si_code != CLD_EXITED) { 168*72168095SRobert Mustacchi warnx("TEST FAILED: %s: child process did not successfully " 169*72168095SRobert Mustacchi "exit: found si_code: %d", test->gf_desc, cret.si_code); 170*72168095SRobert Mustacchi return (false); 171*72168095SRobert Mustacchi } else if (cret.si_status != 0) { 172*72168095SRobert Mustacchi warnx("TEST FAILED: %s: child process did not exit with code " 173*72168095SRobert Mustacchi "0: found %d", test->gf_desc, cret.si_status); 174*72168095SRobert Mustacchi return (false); 175*72168095SRobert Mustacchi } 176*72168095SRobert Mustacchi 177*72168095SRobert Mustacchi return (true); 178*72168095SRobert Mustacchi } 179*72168095SRobert Mustacchi 180*72168095SRobert Mustacchi static void 181*72168095SRobert Mustacchi getenv_getpath(void) 182*72168095SRobert Mustacchi { 183*72168095SRobert Mustacchi ssize_t ret; 184*72168095SRobert Mustacchi char dir[PATH_MAX]; 185*72168095SRobert Mustacchi 186*72168095SRobert Mustacchi ret = readlink("/proc/self/path/a.out", dir, sizeof (dir)); 187*72168095SRobert Mustacchi if (ret < 0) { 188*72168095SRobert Mustacchi err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to read our " 189*72168095SRobert Mustacchi "a.out path from /proc"); 190*72168095SRobert Mustacchi } else if (ret == 0) { 191*72168095SRobert Mustacchi errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: reading " 192*72168095SRobert Mustacchi "/proc/self/path/a.out returned 0 bytes"); 193*72168095SRobert Mustacchi } else if (ret == sizeof (dir)) { 194*72168095SRobert Mustacchi errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: Using " 195*72168095SRobert Mustacchi "/proc/self/path/a.out requires truncation"); 196*72168095SRobert Mustacchi } 197*72168095SRobert Mustacchi 198*72168095SRobert Mustacchi dir[ret] = '\0'; 199*72168095SRobert Mustacchi if (snprintf(getenv_path, sizeof (getenv_path), "%s/%s", dirname(dir), 200*72168095SRobert Mustacchi getenv_checker) >= sizeof (getenv_path)) { 201*72168095SRobert Mustacchi errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: constructing path " 202*72168095SRobert Mustacchi "for child process would overflow internal buffer"); 203*72168095SRobert Mustacchi } 204*72168095SRobert Mustacchi } 205*72168095SRobert Mustacchi 206*72168095SRobert Mustacchi int 207*72168095SRobert Mustacchi main(void) 208*72168095SRobert Mustacchi { 209*72168095SRobert Mustacchi int ret = EXIT_SUCCESS; 210*72168095SRobert Mustacchi 211*72168095SRobert Mustacchi (void) clearenv(); 212*72168095SRobert Mustacchi if (putenv("SECRET=keep it safe") != 0) { 213*72168095SRobert Mustacchi errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to put " 214*72168095SRobert Mustacchi "environment variable"); 215*72168095SRobert Mustacchi } 216*72168095SRobert Mustacchi VERIFY3P(getenv("SECRET"), !=, NULL); 217*72168095SRobert Mustacchi 218*72168095SRobert Mustacchi getenv_getpath(); 219*72168095SRobert Mustacchi if ((getenv_nobody = getpwnam("nobody")) == NULL) { 220*72168095SRobert Mustacchi err(EXIT_FAILURE, "failed to get passwd entry for nobody"); 221*72168095SRobert Mustacchi } 222*72168095SRobert Mustacchi 223*72168095SRobert Mustacchi for (size_t i = 0; i < ARRAY_SIZE(getenv_tests); i++) { 224*72168095SRobert Mustacchi if (!getenv_fork(&getenv_tests[i])) 225*72168095SRobert Mustacchi ret = EXIT_FAILURE; 226*72168095SRobert Mustacchi } 227*72168095SRobert Mustacchi 228*72168095SRobert Mustacchi if (ret == EXIT_SUCCESS) { 229*72168095SRobert Mustacchi (void) printf("All tests passed successfully\n"); 230*72168095SRobert Mustacchi } 231*72168095SRobert Mustacchi 232*72168095SRobert Mustacchi return (ret); 233*72168095SRobert Mustacchi } 234