xref: /illumos-gate/usr/src/test/os-tests/tests/secure_getenv/secure_getenv.c (revision 7216809591be497087c98047f2a145b1dcc5873d)
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