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