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