xref: /freebsd/tests/sys/kern/jaildesc.c (revision f95da272b8b7138008542022af3dc7ead3ff2ce2)
1*f95da272SMark Johnston /*
2*f95da272SMark Johnston  * Copyright (c) 2026 Mark Johnston <markj@FreeBSD.org>
3*f95da272SMark Johnston  *
4*f95da272SMark Johnston  * SPDX-License-Identifier: BSD-2-Clause
5*f95da272SMark Johnston  */
6*f95da272SMark Johnston 
7*f95da272SMark Johnston #include <sys/param.h>
8*f95da272SMark Johnston #include <sys/jail.h>
9*f95da272SMark Johnston #include <sys/uio.h>
10*f95da272SMark Johnston 
11*f95da272SMark Johnston #include <atf-c.h>
12*f95da272SMark Johnston #include <errno.h>
13*f95da272SMark Johnston #include <poll.h>
14*f95da272SMark Johnston #include <pthread.h>
15*f95da272SMark Johnston #include <pwd.h>
16*f95da272SMark Johnston #include <string.h>
17*f95da272SMark Johnston #include <unistd.h>
18*f95da272SMark Johnston 
19*f95da272SMark Johnston /*
20*f95da272SMark Johnston  * Create a persistent jail and return an owning descriptor for it.
21*f95da272SMark Johnston  * The jail is removed when the returned descriptor is closed.
22*f95da272SMark Johnston  */
23*f95da272SMark Johnston static int
create_jail(const char * name)24*f95da272SMark Johnston create_jail(const char *name)
25*f95da272SMark Johnston {
26*f95da272SMark Johnston 	struct iovec iov[8];
27*f95da272SMark Johnston 	int desc, jid, n;
28*f95da272SMark Johnston 
29*f95da272SMark Johnston 	desc = -1;
30*f95da272SMark Johnston 	n = 0;
31*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, "name");
32*f95da272SMark Johnston 	iov[n++].iov_len = strlen("name") + 1;
33*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, name);
34*f95da272SMark Johnston 	iov[n++].iov_len = strlen(name) + 1;
35*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, "path");
36*f95da272SMark Johnston 	iov[n++].iov_len = strlen("path") + 1;
37*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, "/");
38*f95da272SMark Johnston 	iov[n++].iov_len = strlen("/") + 1;
39*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, "persist");
40*f95da272SMark Johnston 	iov[n++].iov_len = strlen("persist") + 1;
41*f95da272SMark Johnston 	iov[n].iov_base = NULL;
42*f95da272SMark Johnston 	iov[n++].iov_len = 0;
43*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, "desc");
44*f95da272SMark Johnston 	iov[n++].iov_len = strlen("desc") + 1;
45*f95da272SMark Johnston 	iov[n].iov_base = &desc;
46*f95da272SMark Johnston 	iov[n++].iov_len = sizeof(desc);
47*f95da272SMark Johnston 	jid = jail_set(iov, n, JAIL_CREATE | JAIL_OWN_DESC);
48*f95da272SMark Johnston 	ATF_REQUIRE_MSG(jid >= 0, "jail_set: %s", strerror(errno));
49*f95da272SMark Johnston 	return (desc);
50*f95da272SMark Johnston }
51*f95da272SMark Johnston 
52*f95da272SMark Johnston static void *
poll_jaildesc(void * arg)53*f95da272SMark Johnston poll_jaildesc(void *arg)
54*f95da272SMark Johnston {
55*f95da272SMark Johnston 	struct pollfd pfd;
56*f95da272SMark Johnston 
57*f95da272SMark Johnston 	pfd.fd = *(int *)arg;
58*f95da272SMark Johnston 	pfd.events = POLLHUP;
59*f95da272SMark Johnston 	(void)poll(&pfd, 1, 5000);
60*f95da272SMark Johnston 	return ((void *)(uintptr_t)pfd.revents);
61*f95da272SMark Johnston }
62*f95da272SMark Johnston 
63*f95da272SMark Johnston /*
64*f95da272SMark Johnston  * Regression test for the case where a jail descriptor is closed while a
65*f95da272SMark Johnston  * thread is blocking in poll(2) on it.
66*f95da272SMark Johnston  */
67*f95da272SMark Johnston ATF_TC(poll_close_race);
ATF_TC_HEAD(poll_close_race,tc)68*f95da272SMark Johnston ATF_TC_HEAD(poll_close_race, tc)
69*f95da272SMark Johnston {
70*f95da272SMark Johnston 	atf_tc_set_md_var(tc, "require.user", "root");
71*f95da272SMark Johnston }
ATF_TC_BODY(poll_close_race,tc)72*f95da272SMark Johnston ATF_TC_BODY(poll_close_race, tc)
73*f95da272SMark Johnston {
74*f95da272SMark Johnston 	pthread_t thr;
75*f95da272SMark Johnston 	uintptr_t revents;
76*f95da272SMark Johnston 	int error, jd;
77*f95da272SMark Johnston 
78*f95da272SMark Johnston 	jd = create_jail("jaildesc_poll_close_race");
79*f95da272SMark Johnston 
80*f95da272SMark Johnston 	error = pthread_create(&thr, NULL, poll_jaildesc, &jd);
81*f95da272SMark Johnston 	ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error));
82*f95da272SMark Johnston 
83*f95da272SMark Johnston 	/* Wait for the thread to block in poll(2). */
84*f95da272SMark Johnston 	usleep(250000);
85*f95da272SMark Johnston 
86*f95da272SMark Johnston 	ATF_REQUIRE_MSG(close(jd) == 0, "close: %s", strerror(errno));
87*f95da272SMark Johnston 
88*f95da272SMark Johnston 	error = pthread_join(thr, (void *)&revents);
89*f95da272SMark Johnston 	ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error));
90*f95da272SMark Johnston 	ATF_REQUIRE_EQ(revents, POLLNVAL);
91*f95da272SMark Johnston }
92*f95da272SMark Johnston 
93*f95da272SMark Johnston /*
94*f95da272SMark Johnston  * Verify that poll(2) of a jail descriptor returns POLLHUP when the jail
95*f95da272SMark Johnston  * is removed.
96*f95da272SMark Johnston  */
97*f95da272SMark Johnston ATF_TC(poll_remove_wakeup);
ATF_TC_HEAD(poll_remove_wakeup,tc)98*f95da272SMark Johnston ATF_TC_HEAD(poll_remove_wakeup, tc)
99*f95da272SMark Johnston {
100*f95da272SMark Johnston 	atf_tc_set_md_var(tc, "require.user", "root");
101*f95da272SMark Johnston }
ATF_TC_BODY(poll_remove_wakeup,tc)102*f95da272SMark Johnston ATF_TC_BODY(poll_remove_wakeup, tc)
103*f95da272SMark Johnston {
104*f95da272SMark Johnston 	pthread_t thr;
105*f95da272SMark Johnston 	uintptr_t revents;
106*f95da272SMark Johnston 	int error, jd;
107*f95da272SMark Johnston 
108*f95da272SMark Johnston 	jd = create_jail("jaildesc_poll_remove_wakeup");
109*f95da272SMark Johnston 
110*f95da272SMark Johnston 	error = pthread_create(&thr, NULL, poll_jaildesc, &jd);
111*f95da272SMark Johnston 	ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error));
112*f95da272SMark Johnston 
113*f95da272SMark Johnston 	/* Wait for the thread to block in poll(2). */
114*f95da272SMark Johnston 	usleep(250000);
115*f95da272SMark Johnston 
116*f95da272SMark Johnston 	ATF_REQUIRE_MSG(jail_remove_jd(jd) == 0,
117*f95da272SMark Johnston 	    "jail_remove_jd: %s", strerror(errno));
118*f95da272SMark Johnston 
119*f95da272SMark Johnston 	error = pthread_join(thr, (void *)&revents);
120*f95da272SMark Johnston 	ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error));
121*f95da272SMark Johnston 	ATF_REQUIRE_EQ(revents, POLLHUP);
122*f95da272SMark Johnston 
123*f95da272SMark Johnston 	ATF_REQUIRE_MSG(close(jd) == 0, "close: %s", strerror(errno));
124*f95da272SMark Johnston }
125*f95da272SMark Johnston 
126*f95da272SMark Johnston static int
get_jaildesc(const char * name)127*f95da272SMark Johnston get_jaildesc(const char *name)
128*f95da272SMark Johnston {
129*f95da272SMark Johnston 	struct iovec iov[4];
130*f95da272SMark Johnston 	char namebuf[MAXHOSTNAMELEN];
131*f95da272SMark Johnston 	int desc, jid, n;
132*f95da272SMark Johnston 
133*f95da272SMark Johnston 	strlcpy(namebuf, name, sizeof(namebuf));
134*f95da272SMark Johnston 	desc = -1;
135*f95da272SMark Johnston 	n = 0;
136*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, "name");
137*f95da272SMark Johnston 	iov[n++].iov_len = strlen("name") + 1;
138*f95da272SMark Johnston 	iov[n].iov_base = namebuf;
139*f95da272SMark Johnston 	iov[n++].iov_len = sizeof(namebuf);
140*f95da272SMark Johnston 	iov[n].iov_base = __DECONST(void *, "desc");
141*f95da272SMark Johnston 	iov[n++].iov_len = strlen("desc") + 1;
142*f95da272SMark Johnston 	iov[n].iov_base = &desc;
143*f95da272SMark Johnston 	iov[n++].iov_len = sizeof(desc);
144*f95da272SMark Johnston 	jid = jail_get(iov, n, JAIL_GET_DESC);
145*f95da272SMark Johnston 	ATF_REQUIRE_MSG(jid >= 0, "jail_get: %s", strerror(errno));
146*f95da272SMark Johnston 	return (desc);
147*f95da272SMark Johnston }
148*f95da272SMark Johnston 
149*f95da272SMark Johnston /*
150*f95da272SMark Johnston  * Regression test for the same use-after-free as poll_close_race, but with a
151*f95da272SMark Johnston  * non-owning JAIL_GET_DESC descriptor obtained without root privileges.
152*f95da272SMark Johnston  */
153*f95da272SMark Johnston ATF_TC(poll_close_race_get_desc);
ATF_TC_HEAD(poll_close_race_get_desc,tc)154*f95da272SMark Johnston ATF_TC_HEAD(poll_close_race_get_desc, tc)
155*f95da272SMark Johnston {
156*f95da272SMark Johnston 	atf_tc_set_md_var(tc, "require.user", "root");
157*f95da272SMark Johnston }
ATF_TC_BODY(poll_close_race_get_desc,tc)158*f95da272SMark Johnston ATF_TC_BODY(poll_close_race_get_desc, tc)
159*f95da272SMark Johnston {
160*f95da272SMark Johnston 	struct passwd *pw;
161*f95da272SMark Johnston 	pthread_t thr;
162*f95da272SMark Johnston 	uintptr_t revents;
163*f95da272SMark Johnston 	int error, jd, owning_jd;
164*f95da272SMark Johnston 
165*f95da272SMark Johnston 	/* Create the jail as root; keep the owning descriptor for cleanup. */
166*f95da272SMark Johnston 	owning_jd = create_jail("jaildesc_poll_close_get_desc");
167*f95da272SMark Johnston 
168*f95da272SMark Johnston 	/*
169*f95da272SMark Johnston 	 * Drop root privileges.  jail_get(2) with JAIL_GET_DESC does not
170*f95da272SMark Johnston 	 * require PRIV_JAIL_REMOVE, so a non-root process in the host prison
171*f95da272SMark Johnston 	 * can obtain a read-only descriptor for any visible jail.
172*f95da272SMark Johnston 	 */
173*f95da272SMark Johnston 	pw = getpwnam("nobody");
174*f95da272SMark Johnston 	ATF_REQUIRE_MSG(pw != NULL, "getpwnam: %s", strerror(errno));
175*f95da272SMark Johnston 	ATF_REQUIRE_MSG(setuid(pw->pw_uid) == 0, "setuid: %s", strerror(errno));
176*f95da272SMark Johnston 
177*f95da272SMark Johnston 	jd = get_jaildesc("jaildesc_poll_close_get_desc");
178*f95da272SMark Johnston 
179*f95da272SMark Johnston 	error = pthread_create(&thr, NULL, poll_jaildesc, &jd);
180*f95da272SMark Johnston 	ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error));
181*f95da272SMark Johnston 
182*f95da272SMark Johnston 	/* Wait for the thread to block in poll(2). */
183*f95da272SMark Johnston 	usleep(250000);
184*f95da272SMark Johnston 
185*f95da272SMark Johnston 	ATF_REQUIRE_MSG(close(jd) == 0, "close: %s", strerror(errno));
186*f95da272SMark Johnston 
187*f95da272SMark Johnston 	error = pthread_join(thr, (void *)&revents);
188*f95da272SMark Johnston 	ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error));
189*f95da272SMark Johnston 	ATF_REQUIRE_EQ(revents, POLLNVAL);
190*f95da272SMark Johnston 
191*f95da272SMark Johnston 	ATF_REQUIRE_MSG(close(owning_jd) == 0, "close: %s", strerror(errno));
192*f95da272SMark Johnston }
193*f95da272SMark Johnston 
ATF_TP_ADD_TCS(tp)194*f95da272SMark Johnston ATF_TP_ADD_TCS(tp)
195*f95da272SMark Johnston {
196*f95da272SMark Johnston 	ATF_TP_ADD_TC(tp, poll_close_race);
197*f95da272SMark Johnston 	ATF_TP_ADD_TC(tp, poll_remove_wakeup);
198*f95da272SMark Johnston 	ATF_TP_ADD_TC(tp, poll_close_race_get_desc);
199*f95da272SMark Johnston 
200*f95da272SMark Johnston 	return (atf_no_error());
201*f95da272SMark Johnston }
202