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