xref: /freebsd/tests/sys/kern/jail_thread.c (revision d4e0f4dab2d7f4de46bb79db1ca7e6e8a2e34746)
1*d4e0f4daSJamie Gritton /*-
2*d4e0f4daSJamie Gritton  * SPDX-License-Identifier: BSD-2-Clause
3*d4e0f4daSJamie Gritton  *
4*d4e0f4daSJamie Gritton  * Copyright (c) 2026 James Gritton <jamie@FreeBSD.org>
5*d4e0f4daSJamie Gritton  */
6*d4e0f4daSJamie Gritton 
7*d4e0f4daSJamie Gritton #include <sys/param.h>
8*d4e0f4daSJamie Gritton #include <sys/jail.h>
9*d4e0f4daSJamie Gritton #include <sys/stat.h>
10*d4e0f4daSJamie Gritton 
11*d4e0f4daSJamie Gritton #include <err.h>
12*d4e0f4daSJamie Gritton #include <errno.h>
13*d4e0f4daSJamie Gritton #include <jail.h>
14*d4e0f4daSJamie Gritton #include <pthread.h>
15*d4e0f4daSJamie Gritton #include <stdio.h>
16*d4e0f4daSJamie Gritton #include <stdlib.h>
17*d4e0f4daSJamie Gritton #include <string.h>
18*d4e0f4daSJamie Gritton #include <unistd.h>
19*d4e0f4daSJamie Gritton 
20*d4e0f4daSJamie Gritton #include <atf-c.h>
21*d4e0f4daSJamie Gritton 
22*d4e0f4daSJamie Gritton #define NJAILS 3		/* One master jail and two to race. */
23*d4e0f4daSJamie Gritton #define NROUNDS 10000		/* Number of attempts to make race happen. */
24*d4e0f4daSJamie Gritton 
25*d4e0f4daSJamie Gritton struct jailinfo {
26*d4e0f4daSJamie Gritton 	int jfd;
27*d4e0f4daSJamie Gritton 	ino_t ino;
28*d4e0f4daSJamie Gritton 	char path[MAXPATHLEN];
29*d4e0f4daSJamie Gritton };
30*d4e0f4daSJamie Gritton 
31*d4e0f4daSJamie Gritton static pthread_barrier_t barrier;
32*d4e0f4daSJamie Gritton static struct jailinfo jinfo[NJAILS];
33*d4e0f4daSJamie Gritton 
34*d4e0f4daSJamie Gritton /* Attach a thread to a jail with jail_attach_jd. */
35*d4e0f4daSJamie Gritton static void *
thread_jail_attach_jd(void * arg)36*d4e0f4daSJamie Gritton thread_jail_attach_jd(void *arg)
37*d4e0f4daSJamie Gritton {
38*d4e0f4daSJamie Gritton 	int error;
39*d4e0f4daSJamie Gritton 
40*d4e0f4daSJamie Gritton 	/*
41*d4e0f4daSJamie Gritton 	 * Synchronize to get as close as possible to the same time,
42*d4e0f4daSJamie Gritton 	 * then attach to a jail.
43*d4e0f4daSJamie Gritton 	 */
44*d4e0f4daSJamie Gritton 	error = pthread_barrier_wait(&barrier);
45*d4e0f4daSJamie Gritton 	ATF_REQUIRE_MSG(error == 0 || error == PTHREAD_BARRIER_SERIAL_THREAD,
46*d4e0f4daSJamie Gritton 	    "pthread_barrier_wait: %s", strerror(errno));
47*d4e0f4daSJamie Gritton 	ATF_REQUIRE_MSG(jail_attach_jd(jinfo[(size_t)arg].jfd) == 0,
48*d4e0f4daSJamie Gritton 	    "jail_attach_jd: %s", strerror(errno));
49*d4e0f4daSJamie Gritton 	return (NULL);
50*d4e0f4daSJamie Gritton }
51*d4e0f4daSJamie Gritton 
52*d4e0f4daSJamie Gritton /* Attach a thread to a jail with jail_setv. */
53*d4e0f4daSJamie Gritton static void *
thread_jail_setv(void * arg)54*d4e0f4daSJamie Gritton thread_jail_setv(void *arg)
55*d4e0f4daSJamie Gritton {
56*d4e0f4daSJamie Gritton 	int error;
57*d4e0f4daSJamie Gritton 	char jdescstr[16];
58*d4e0f4daSJamie Gritton 
59*d4e0f4daSJamie Gritton 	error = pthread_barrier_wait(&barrier);
60*d4e0f4daSJamie Gritton 	ATF_REQUIRE_MSG(error == 0 || error == PTHREAD_BARRIER_SERIAL_THREAD,
61*d4e0f4daSJamie Gritton 	    "pthread_barrier_wait: %s", strerror(errno));
62*d4e0f4daSJamie Gritton 	snprintf(jdescstr, sizeof(jdescstr), "%d", jinfo[(size_t)arg].jfd);
63*d4e0f4daSJamie Gritton 	ATF_REQUIRE_MSG(jail_setv(JAIL_UPDATE | JAIL_ATTACH | JAIL_USE_DESC,
64*d4e0f4daSJamie Gritton 	    "desc", jdescstr, NULL) > 0,
65*d4e0f4daSJamie Gritton 	    "jail_setv: %s", jail_errmsg[0] ? jail_errmsg : strerror(errno));
66*d4e0f4daSJamie Gritton 	return (NULL);
67*d4e0f4daSJamie Gritton }
68*d4e0f4daSJamie Gritton 
69*d4e0f4daSJamie Gritton /* Attach a thread to a jail with chroot. */
70*d4e0f4daSJamie Gritton static void *
thread_chroot(void * arg)71*d4e0f4daSJamie Gritton thread_chroot(void *arg)
72*d4e0f4daSJamie Gritton {
73*d4e0f4daSJamie Gritton 	int error;
74*d4e0f4daSJamie Gritton 
75*d4e0f4daSJamie Gritton 	/* This is a race between jail_attach and chroot. */
76*d4e0f4daSJamie Gritton 	if ((size_t)arg > 1)
77*d4e0f4daSJamie Gritton 		return thread_jail_attach_jd(arg);
78*d4e0f4daSJamie Gritton 	error = pthread_barrier_wait(&barrier);
79*d4e0f4daSJamie Gritton 	ATF_REQUIRE_MSG(error == 0 || error == PTHREAD_BARRIER_SERIAL_THREAD,
80*d4e0f4daSJamie Gritton 	    "pthread_barrier_wait: %s", strerror(errno));
81*d4e0f4daSJamie Gritton 	ATF_REQUIRE_MSG(chroot(jinfo[(size_t)arg].path) == 0 || errno == ENOENT,
82*d4e0f4daSJamie Gritton 	    "chroot: %s", strerror(errno));
83*d4e0f4daSJamie Gritton 	return (NULL);
84*d4e0f4daSJamie Gritton }
85*d4e0f4daSJamie Gritton 
86*d4e0f4daSJamie Gritton static void
thread_attach_test(void * (* thread_handler)(void *),const char * jail_name,const char * syscall_name,bool jail_reset)87*d4e0f4daSJamie Gritton thread_attach_test(void *(*thread_handler)(void*), const char *jail_name,
88*d4e0f4daSJamie Gritton     const char *syscall_name, bool jail_reset)
89*d4e0f4daSJamie Gritton {
90*d4e0f4daSJamie Gritton 	int ri, spn, mixed_jails;
91*d4e0f4daSJamie Gritton 	size_t ji, ti, ji_hostname, ji_ino, jail_namelen;
92*d4e0f4daSJamie Gritton 	char *cwd;
93*d4e0f4daSJamie Gritton 	struct stat st;
94*d4e0f4daSJamie Gritton 	char jnamestr[64], jdescstr[16];
95*d4e0f4daSJamie Gritton 	pthread_t threads[NJAILS];
96*d4e0f4daSJamie Gritton 
97*d4e0f4daSJamie Gritton 	if (jinfo[0].jfd == 0) {
98*d4e0f4daSJamie Gritton 		/* Start with a master jail, so we can return to real root. */
99*d4e0f4daSJamie Gritton 		jdescstr[0] = '\0';
100*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(jail_setv(JAIL_CREATE | JAIL_OWN_DESC,
101*d4e0f4daSJamie Gritton 		    "name", jail_name,
102*d4e0f4daSJamie Gritton 		    "path", "/",
103*d4e0f4daSJamie Gritton 		    "desc", jdescstr,
104*d4e0f4daSJamie Gritton 		    "persist", "true",
105*d4e0f4daSJamie Gritton 		    NULL) > 0,
106*d4e0f4daSJamie Gritton 		    "jail_setv jail 0: %s",
107*d4e0f4daSJamie Gritton 		    jail_errmsg[0] ? jail_errmsg : strerror(errno));
108*d4e0f4daSJamie Gritton 		jinfo[0].jfd = strtol(jdescstr, NULL, 10);
109*d4e0f4daSJamie Gritton 		/* Make enough jails to cause contention. */
110*d4e0f4daSJamie Gritton 		cwd = getcwd(NULL, MAXPATHLEN);
111*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(cwd != NULL, "getcwd: %s", strerror(errno));
112*d4e0f4daSJamie Gritton 		for (ji = 1; ji < NJAILS; ++ji) {
113*d4e0f4daSJamie Gritton 			snprintf(jnamestr, sizeof(jnamestr),
114*d4e0f4daSJamie Gritton 			    "%s%zu", jail_name, ji);
115*d4e0f4daSJamie Gritton 			spn = snprintf(jinfo[ji].path, MAXPATHLEN,
116*d4e0f4daSJamie Gritton 			    "%s/jail%zu", cwd, ji);
117*d4e0f4daSJamie Gritton 			ATF_REQUIRE_MSG((size_t)spn < MAXPATHLEN,
118*d4e0f4daSJamie Gritton 			    "snprintf exceeded MAXPATHLEN: %d", spn);
119*d4e0f4daSJamie Gritton 			ATF_REQUIRE_MSG(
120*d4e0f4daSJamie Gritton 			    mkdir(jinfo[ji].path, 0755) == 0 || errno == EEXIST,
121*d4e0f4daSJamie Gritton 			    "mkdir %s: %s", jinfo[ji].path, strerror(errno));
122*d4e0f4daSJamie Gritton 			ATF_REQUIRE_MSG(stat(jinfo[ji].path, &st) == 0,
123*d4e0f4daSJamie Gritton 			    "stat %s: %s", jinfo[ji].path, strerror(errno));
124*d4e0f4daSJamie Gritton 			jinfo[ji].ino = st.st_ino;
125*d4e0f4daSJamie Gritton 			jdescstr[0] = '\0';
126*d4e0f4daSJamie Gritton 			ATF_REQUIRE_MSG(jail_setv(JAIL_CREATE | JAIL_OWN_DESC,
127*d4e0f4daSJamie Gritton 			    "name", jnamestr,
128*d4e0f4daSJamie Gritton 			    "host.hostname", jnamestr,
129*d4e0f4daSJamie Gritton 			    "path", jinfo[ji].path,
130*d4e0f4daSJamie Gritton 			    "desc", jdescstr,
131*d4e0f4daSJamie Gritton 			    "persist", "true",
132*d4e0f4daSJamie Gritton 			    NULL) > 0,
133*d4e0f4daSJamie Gritton 			    "jail_setv: %s",
134*d4e0f4daSJamie Gritton 			    jail_errmsg[0] ? jail_errmsg : strerror(errno));
135*d4e0f4daSJamie Gritton 			jinfo[ji].jfd = strtol(jdescstr, NULL, 10);
136*d4e0f4daSJamie Gritton 		}
137*d4e0f4daSJamie Gritton 	} else
138*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(jail_attach_jd(jinfo[0].jfd) == 0,
139*d4e0f4daSJamie Gritton 		    "jail_attach_jd: %s", strerror(errno));
140*d4e0f4daSJamie Gritton 
141*d4e0f4daSJamie Gritton 	/* Check the different system calls that can race. */
142*d4e0f4daSJamie Gritton 	jail_namelen = strlen(jail_name);
143*d4e0f4daSJamie Gritton 	mixed_jails = 0;
144*d4e0f4daSJamie Gritton 	for (ri = 0; ri < NROUNDS; ++ri) {
145*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(
146*d4e0f4daSJamie Gritton 		    pthread_barrier_init(&barrier, NULL, NJAILS - 1) == 0,
147*d4e0f4daSJamie Gritton 		    "pthread_barrier_init: %s", strerror(errno));
148*d4e0f4daSJamie Gritton 		for (ti = 1; ti < NJAILS; ++ti)
149*d4e0f4daSJamie Gritton 			ATF_REQUIRE_MSG(
150*d4e0f4daSJamie Gritton 			    pthread_create(&threads[ti], NULL, thread_handler,
151*d4e0f4daSJamie Gritton 			    (void*)ti) == 0,
152*d4e0f4daSJamie Gritton 			    "pthread_create: %s", strerror(errno));
153*d4e0f4daSJamie Gritton 		for (ti = 1; ti < NJAILS; ++ti)
154*d4e0f4daSJamie Gritton 			ATF_REQUIRE_MSG(
155*d4e0f4daSJamie Gritton 			    pthread_join(threads[ti], NULL) == 0,
156*d4e0f4daSJamie Gritton 			    "pthread_join: %s", strerror(errno));
157*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(pthread_barrier_destroy(&barrier) == 0,
158*d4e0f4daSJamie Gritton 		    "pthread_barrier_destroy: %s", strerror(errno));
159*d4e0f4daSJamie Gritton 		/*
160*d4e0f4daSJamie Gritton 		 * Find the current jail from the hostname, and also
161*d4e0f4daSJamie Gritton 		 * by the root inode.  They should be the same.
162*d4e0f4daSJamie Gritton 		 */
163*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(
164*d4e0f4daSJamie Gritton 		    gethostname(jnamestr, sizeof(jnamestr)) == 0,
165*d4e0f4daSJamie Gritton 		    "gethostname: %s", strerror(errno));
166*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(strncmp(jnamestr, jail_name, jail_namelen) == 0,
167*d4e0f4daSJamie Gritton 		    "unexpected jail hostname %s", jnamestr);
168*d4e0f4daSJamie Gritton 		ji_hostname = strtol(jnamestr + jail_namelen, NULL, 10);
169*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(stat("/", &st) == 0,
170*d4e0f4daSJamie Gritton 		    "stat /: %s", strerror(errno));
171*d4e0f4daSJamie Gritton 		for (ji_ino = 1; ji_ino < NJAILS; ++ji_ino)
172*d4e0f4daSJamie Gritton 			if (jinfo[ji_ino].ino == st.st_ino)
173*d4e0f4daSJamie Gritton 				break;
174*d4e0f4daSJamie Gritton 		ATF_REQUIRE_MSG(ji_ino < NJAILS,
175*d4e0f4daSJamie Gritton 		    "unexpected jail root inode %lu",
176*d4e0f4daSJamie Gritton 		    (unsigned long)st.st_ino);
177*d4e0f4daSJamie Gritton 		mixed_jails += ji_hostname != ji_ino;
178*d4e0f4daSJamie Gritton 		/* Reset to the master jail, required for chroot. */
179*d4e0f4daSJamie Gritton 		if (jail_reset)
180*d4e0f4daSJamie Gritton 			ATF_REQUIRE_MSG(jail_attach_jd(jinfo[0].jfd) == 0,
181*d4e0f4daSJamie Gritton 			    "jail_attach_jd: %s", strerror(errno));
182*d4e0f4daSJamie Gritton 	}
183*d4e0f4daSJamie Gritton 	/* It's an error if any of the rounds had a mismatch. */
184*d4e0f4daSJamie Gritton 	ATF_REQUIRE_MSG(mixed_jails == 0,
185*d4e0f4daSJamie Gritton 	    "%d of %d %s races with different root and "
186*d4e0f4daSJamie Gritton 	    "credentials", mixed_jails, NROUNDS, syscall_name);
187*d4e0f4daSJamie Gritton }
188*d4e0f4daSJamie Gritton 
189*d4e0f4daSJamie Gritton #define JAIL_NAME "jail_thread_attach_test"
190*d4e0f4daSJamie Gritton 
191*d4e0f4daSJamie Gritton 
192*d4e0f4daSJamie Gritton ATF_TC(jail_thread_attach);
ATF_TC_HEAD(jail_thread_attach,tc)193*d4e0f4daSJamie Gritton ATF_TC_HEAD(jail_thread_attach, tc)
194*d4e0f4daSJamie Gritton {
195*d4e0f4daSJamie Gritton 	atf_tc_set_md_var(tc, "require.user", "root");
196*d4e0f4daSJamie Gritton }
ATF_TC_BODY(jail_thread_attach,tc)197*d4e0f4daSJamie Gritton ATF_TC_BODY(jail_thread_attach, tc)
198*d4e0f4daSJamie Gritton {
199*d4e0f4daSJamie Gritton 	thread_attach_test(thread_jail_attach_jd,
200*d4e0f4daSJamie Gritton 	    "jail_thread_attach_test", "jail_attach_jd", false);
201*d4e0f4daSJamie Gritton }
202*d4e0f4daSJamie Gritton 
203*d4e0f4daSJamie Gritton ATF_TC(jail_thread_attach_jail_set);
ATF_TC_HEAD(jail_thread_attach_jail_set,tc)204*d4e0f4daSJamie Gritton ATF_TC_HEAD(jail_thread_attach_jail_set, tc)
205*d4e0f4daSJamie Gritton {
206*d4e0f4daSJamie Gritton 	atf_tc_set_md_var(tc, "require.user", "root");
207*d4e0f4daSJamie Gritton }
ATF_TC_BODY(jail_thread_attach_jail_set,tc)208*d4e0f4daSJamie Gritton ATF_TC_BODY(jail_thread_attach_jail_set, tc)
209*d4e0f4daSJamie Gritton {
210*d4e0f4daSJamie Gritton 	thread_attach_test(thread_jail_setv,
211*d4e0f4daSJamie Gritton 	    "jail_thread_attach_test_jail_set", "jail_set", false);
212*d4e0f4daSJamie Gritton }
213*d4e0f4daSJamie Gritton 
214*d4e0f4daSJamie Gritton ATF_TC(jail_thread_attach_chroot);
ATF_TC_HEAD(jail_thread_attach_chroot,tc)215*d4e0f4daSJamie Gritton ATF_TC_HEAD(jail_thread_attach_chroot, tc)
216*d4e0f4daSJamie Gritton {
217*d4e0f4daSJamie Gritton 	atf_tc_set_md_var(tc, "require.user", "root");
218*d4e0f4daSJamie Gritton }
ATF_TC_BODY(jail_thread_attach_chroot,tc)219*d4e0f4daSJamie Gritton ATF_TC_BODY(jail_thread_attach_chroot, tc)
220*d4e0f4daSJamie Gritton {
221*d4e0f4daSJamie Gritton 	thread_attach_test(thread_chroot,
222*d4e0f4daSJamie Gritton 	    "jail_thread_attach_test_chroot", "chroot", true);
223*d4e0f4daSJamie Gritton }
224*d4e0f4daSJamie Gritton 
ATF_TP_ADD_TCS(tp)225*d4e0f4daSJamie Gritton ATF_TP_ADD_TCS(tp)
226*d4e0f4daSJamie Gritton {
227*d4e0f4daSJamie Gritton 	ATF_TP_ADD_TC(tp, jail_thread_attach);
228*d4e0f4daSJamie Gritton 	ATF_TP_ADD_TC(tp, jail_thread_attach_jail_set);
229*d4e0f4daSJamie Gritton 	ATF_TP_ADD_TC(tp, jail_thread_attach_chroot);
230*d4e0f4daSJamie Gritton 	return (atf_no_error());
231*d4e0f4daSJamie Gritton }
232