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