xref: /freebsd/sys/kern/kern_jaildesc.c (revision 38dd686b9336e2de5deadc5f8cb5e46a845b0dd9)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2025 James Gritton.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/param.h>
30 #include <sys/fcntl.h>
31 #include <sys/file.h>
32 #include <sys/filedesc.h>
33 #include <sys/kernel.h>
34 #include <sys/jail.h>
35 #include <sys/jaildesc.h>
36 #include <sys/lock.h>
37 #include <sys/malloc.h>
38 #include <sys/mutex.h>
39 #include <sys/poll.h>
40 #include <sys/priv.h>
41 #include <sys/stat.h>
42 #include <sys/sysproto.h>
43 #include <sys/systm.h>
44 #include <sys/ucred.h>
45 #include <sys/user.h>
46 #include <sys/vnode.h>
47 
48 MALLOC_DEFINE(M_JAILDESC, "jaildesc", "jail descriptors");
49 
50 static fo_poll_t	jaildesc_poll;
51 static fo_kqfilter_t	jaildesc_kqfilter;
52 static fo_stat_t	jaildesc_stat;
53 static fo_close_t	jaildesc_close;
54 static fo_fill_kinfo_t	jaildesc_fill_kinfo;
55 static fo_cmp_t		jaildesc_cmp;
56 
57 static const struct fileops jaildesc_ops = {
58 	.fo_read = invfo_rdwr,
59 	.fo_write = invfo_rdwr,
60 	.fo_truncate = invfo_truncate,
61 	.fo_ioctl = invfo_ioctl,
62 	.fo_poll = jaildesc_poll,
63 	.fo_kqfilter = jaildesc_kqfilter,
64 	.fo_stat = jaildesc_stat,
65 	.fo_close = jaildesc_close,
66 	.fo_chmod = invfo_chmod,
67 	.fo_chown = invfo_chown,
68 	.fo_sendfile = invfo_sendfile,
69 	.fo_fill_kinfo = jaildesc_fill_kinfo,
70 	.fo_cmp = jaildesc_cmp,
71 	.fo_flags = DFLAG_PASSABLE,
72 };
73 
74 /*
75  * Retrieve a prison from a jail descriptor.  If prp is not NULL, then the
76  * prison will be held and subsequently returned, and must be released by the
77  * caller.  This differs from jaildesc_get_prison in that it doesn't actually
78  * require the caller to take the struct prison, which we use internally when
79  * the caller doesn't necessarily need it- it might just want to check validity.
80  */
81 static int
jaildesc_get_prison_impl(struct file * fp,struct prison ** prp)82 jaildesc_get_prison_impl(struct file *fp, struct prison **prp)
83 {
84 	struct prison *pr;
85 	struct jaildesc *jd;
86 
87 	if (fp->f_type != DTYPE_JAILDESC)
88 		return (EINVAL);
89 
90 	jd = fp->f_data;
91 	JAILDESC_LOCK(jd);
92 	pr = jd->jd_prison;
93 	if (pr == NULL || !prison_isvalid(pr)) {
94 		JAILDESC_UNLOCK(jd);
95 		return (ENOENT);
96 	}
97 
98 	if (prp != NULL) {
99 		prison_hold(pr);
100 		*prp = pr;
101 	}
102 
103 	JAILDESC_UNLOCK(jd);
104 
105 	return (0);
106 }
107 
108 /*
109  * Given a jail descriptor number, return its prison and/or its
110  * credential.  They are returned held, and will need to be released
111  * by the caller.
112  */
113 int
jaildesc_find(struct thread * td,int fd,struct prison ** prp,struct ucred ** ucredp)114 jaildesc_find(struct thread *td, int fd, struct prison **prp,
115     struct ucred **ucredp)
116 {
117 	struct file *fp;
118 	int error;
119 
120 	error = fget(td, fd, &cap_no_rights, &fp);
121 	if (error != 0)
122 		return (error);
123 
124 	error = jaildesc_get_prison_impl(fp, prp);
125 	if (error == 0) {
126 		/*
127 		 * jaildesc_get_prison validated the file and held the prison
128 		 * for us if the caller wants it, so we just need to grab the
129 		 * ucred on the way out.
130 		 */
131 		if (ucredp != NULL)
132 			*ucredp = crhold(fp->f_cred);
133 	}
134 
135 	fdrop(fp, td);
136 	return (error);
137 }
138 
139 /*
140  * Allocate a new jail decriptor, not yet associated with a prison.
141  * Return the file pointer (with a reference held) and the descriptor
142  * number.
143  */
144 int
jaildesc_alloc(struct thread * td,struct file ** fpp,int * fdp,int owning)145 jaildesc_alloc(struct thread *td, struct file **fpp, int *fdp, int owning)
146 {
147 	struct file *fp;
148 	struct jaildesc *jd;
149 	int error;
150 
151 	if (owning) {
152 		error = priv_check(td, PRIV_JAIL_REMOVE);
153 		if (error != 0)
154 			return (error);
155 	}
156 	error = falloc_caps(td, &fp, fdp, 0, NULL);
157 	if (error != 0)
158 		return (error);
159 	jd = malloc(sizeof(*jd), M_JAILDESC, M_WAITOK | M_ZERO);
160 	JAILDESC_LOCK_INIT(jd);
161 	knlist_init_mtx(&jd->jd_selinfo.si_note, &jd->jd_lock);
162 	if (owning)
163 		jd->jd_flags |= JDF_OWNING;
164 	finit(fp, priv_check_cred(fp->f_cred, PRIV_JAIL_SET) == 0 ?
165 	    FREAD | FWRITE : FREAD, DTYPE_JAILDESC, jd, &jaildesc_ops);
166 	*fpp = fp;
167 	return (0);
168 }
169 
170 /*
171  * Retrieve a prison from a jail descriptor.  It will be returned held, and must
172  * be released by the caller.
173  */
174 int
jaildesc_get_prison(struct file * fp,struct prison ** prp)175 jaildesc_get_prison(struct file *fp, struct prison **prp)
176 {
177 	MPASS(prp != NULL);
178 	return (jaildesc_get_prison_impl(fp, prp));
179 }
180 
181 /*
182  * Assocate a jail descriptor with its prison.
183  */
184 void
jaildesc_set_prison(struct file * fp,struct prison * pr)185 jaildesc_set_prison(struct file *fp, struct prison *pr)
186 {
187 	struct jaildesc *jd;
188 
189 	mtx_assert(&pr->pr_mtx, MA_OWNED);
190 	jd = fp->f_data;
191 	JAILDESC_LOCK(jd);
192 	jd->jd_prison = pr;
193 	LIST_INSERT_HEAD(&pr->pr_descs, jd, jd_list);
194 	prison_hold(pr);
195 	JAILDESC_UNLOCK(jd);
196 }
197 
198 /*
199  * Detach all the jail descriptors from a prison.
200  */
201 void
jaildesc_prison_cleanup(struct prison * pr)202 jaildesc_prison_cleanup(struct prison *pr)
203 {
204 	struct jaildesc *jd;
205 
206 	mtx_assert(&pr->pr_mtx, MA_OWNED);
207 	while ((jd = LIST_FIRST(&pr->pr_descs))) {
208 		JAILDESC_LOCK(jd);
209 		LIST_REMOVE(jd, jd_list);
210 		jd->jd_prison = NULL;
211 		JAILDESC_UNLOCK(jd);
212 		prison_free(pr);
213 	}
214 }
215 
216 /*
217  * Pass a note to all listening kqueues.
218  */
219 void
jaildesc_knote(struct prison * pr,long hint)220 jaildesc_knote(struct prison *pr, long hint)
221 {
222 	struct jaildesc *jd;
223 	int prison_locked;
224 
225 	if (!LIST_EMPTY(&pr->pr_descs)) {
226 		prison_locked = mtx_owned(&pr->pr_mtx);
227 		if (!prison_locked)
228 			prison_lock(pr);
229 		LIST_FOREACH(jd, &pr->pr_descs, jd_list) {
230 			JAILDESC_LOCK(jd);
231 			if (hint == NOTE_JAIL_REMOVE) {
232 				jd->jd_flags |= JDF_REMOVED;
233 				selwakeup(&jd->jd_selinfo);
234 			}
235 			KNOTE_LOCKED(&jd->jd_selinfo.si_note, hint);
236 			JAILDESC_UNLOCK(jd);
237 		}
238 		if (!prison_locked)
239 			prison_unlock(pr);
240 	}
241 }
242 
243 static int
jaildesc_close(struct file * fp,struct thread * td)244 jaildesc_close(struct file *fp, struct thread *td)
245 {
246 	struct jaildesc *jd;
247 	struct prison *pr;
248 
249 	jd = fp->f_data;
250 	fp->f_data = NULL;
251 	if (jd != NULL) {
252 		JAILDESC_LOCK(jd);
253 		pr = jd->jd_prison;
254 		if (pr != NULL) {
255 			/*
256 			 * Free or remove the associated prison.
257 			 * This requires a second check after re-
258 			 * ordering locks.  This jaildesc can remain
259 			 * unlocked once we have a prison reference,
260 			 * because that prison is the only place that
261 			 * still points back to it.
262 			 */
263 			prison_hold(pr);
264 			JAILDESC_UNLOCK(jd);
265 			if (jd->jd_flags & JDF_OWNING) {
266 				sx_xlock(&allprison_lock);
267 				prison_lock(pr);
268 				if (jd->jd_prison != NULL) {
269 					/*
270 					 * Unlink the prison, but don't free
271 					 * it; that will be done as part of
272 					 * of prison_remove.
273 					 */
274 					LIST_REMOVE(jd, jd_list);
275 					prison_remove(pr);
276 				} else {
277 					prison_unlock(pr);
278 					sx_xunlock(&allprison_lock);
279 				}
280 			} else {
281 				prison_lock(pr);
282 				if (jd->jd_prison != NULL) {
283 					LIST_REMOVE(jd, jd_list);
284 					prison_free(pr);
285 				}
286 				prison_unlock(pr);
287 			}
288 			prison_free(pr);
289 		}
290 		seldrain(&jd->jd_selinfo);
291 		knlist_destroy(&jd->jd_selinfo.si_note);
292 		JAILDESC_LOCK_DESTROY(jd);
293 		free(jd, M_JAILDESC);
294 	}
295 	return (0);
296 }
297 
298 static int
jaildesc_poll(struct file * fp,int events,struct ucred * active_cred,struct thread * td)299 jaildesc_poll(struct file *fp, int events, struct ucred *active_cred,
300     struct thread *td)
301 {
302 	struct jaildesc *jd;
303 	int revents;
304 
305 	revents = 0;
306 	jd = fp->f_data;
307 	JAILDESC_LOCK(jd);
308 	if (jd->jd_flags & JDF_REMOVED)
309 		revents |= POLLHUP;
310 	else
311 		selrecord(td, &jd->jd_selinfo);
312 	JAILDESC_UNLOCK(jd);
313 	return (revents);
314 }
315 
316 static void
jaildesc_kqops_detach(struct knote * kn)317 jaildesc_kqops_detach(struct knote *kn)
318 {
319 	struct jaildesc *jd;
320 
321 	jd = kn->kn_fp->f_data;
322 	knlist_remove(&jd->jd_selinfo.si_note, kn, 0);
323 }
324 
325 static int
jaildesc_kqops_event(struct knote * kn,long hint)326 jaildesc_kqops_event(struct knote *kn, long hint)
327 {
328 	struct jaildesc *jd;
329 	u_int event;
330 
331 	jd = kn->kn_fp->f_data;
332 	if (hint == 0) {
333 		/*
334 		 * Initial test after registration. Generate a
335 		 * NOTE_JAIL_REMOVE in case the prison already died
336 		 * before registration.
337 		 */
338 		event = jd->jd_flags & JDF_REMOVED ? NOTE_JAIL_REMOVE : 0;
339 	} else {
340 		/*
341 		 * Mask off extra data.  In the NOTE_JAIL_CHILD case,
342 		 * that's everything except the NOTE_JAIL_CHILD bit
343 		 * itself, since a JID is any positive integer.
344 		 */
345 		event = ((u_int)hint & NOTE_JAIL_CHILD) ? NOTE_JAIL_CHILD :
346 		    (u_int)hint & NOTE_JAIL_CTRLMASK;
347 	}
348 
349 	/* If the user is interested in this event, record it. */
350 	if (kn->kn_sfflags & event) {
351 		kn->kn_fflags |= event;
352 		/* Report the created jail id or attached process id. */
353 		if (event == NOTE_JAIL_CHILD || event == NOTE_JAIL_ATTACH) {
354 			if (kn->kn_data != 0)
355 				kn->kn_fflags |= NOTE_JAIL_MULTI;
356 			kn->kn_data = (kn->kn_fflags & NOTE_JAIL_MULTI) ? 0U :
357 			    (u_int)hint & ~event;
358 		}
359 	}
360 
361 	/* Prison is gone, so flag the event as finished. */
362 	if (event == NOTE_JAIL_REMOVE) {
363 		kn->kn_flags |= EV_EOF | EV_ONESHOT;
364 		if (kn->kn_fflags == 0)
365 			kn->kn_flags |= EV_DROP;
366 		return (1);
367 	}
368 
369 	return (kn->kn_fflags != 0);
370 }
371 
372 static const struct filterops jaildesc_kqops = {
373 	.f_isfd = 1,
374 	.f_detach = jaildesc_kqops_detach,
375 	.f_event = jaildesc_kqops_event,
376 	.f_copy = knote_triv_copy,
377 };
378 
379 static int
jaildesc_kqfilter(struct file * fp,struct knote * kn)380 jaildesc_kqfilter(struct file *fp, struct knote *kn)
381 {
382 	struct jaildesc *jd;
383 
384 	jd = fp->f_data;
385 	switch (kn->kn_filter) {
386 	case EVFILT_JAILDESC:
387 		kn->kn_fop = &jaildesc_kqops;
388 		kn->kn_flags |= EV_CLEAR;
389 		knlist_add(&jd->jd_selinfo.si_note, kn, 0);
390 		return (0);
391 	default:
392 		return (EINVAL);
393 	}
394 }
395 
396 static int
jaildesc_stat(struct file * fp,struct stat * sb,struct ucred * active_cred)397 jaildesc_stat(struct file *fp, struct stat *sb, struct ucred *active_cred)
398 {
399 	struct jaildesc *jd;
400 
401 	bzero(sb, sizeof(struct stat));
402 	jd = fp->f_data;
403 	JAILDESC_LOCK(jd);
404 	if (jd->jd_prison != NULL) {
405 		sb->st_ino = jd->jd_prison->pr_id;
406 		sb->st_mode = S_IFREG | S_IRWXU;
407 	} else
408 		sb->st_mode = S_IFREG;
409 	JAILDESC_UNLOCK(jd);
410 	return (0);
411 }
412 
413 static int
jaildesc_fill_kinfo(struct file * fp,struct kinfo_file * kif,struct filedesc * fdp)414 jaildesc_fill_kinfo(struct file *fp, struct kinfo_file *kif,
415     struct filedesc *fdp)
416 {
417 	struct jaildesc *jd;
418 
419 	jd = fp->f_data;
420 	kif->kf_type = KF_TYPE_JAILDESC;
421 	kif->kf_un.kf_jail.kf_jid = jd->jd_prison ? jd->jd_prison->pr_id : 0;
422 	return (0);
423 }
424 
425 static int
jaildesc_cmp(struct file * fp1,struct file * fp2,struct thread * td)426 jaildesc_cmp(struct file *fp1, struct file *fp2, struct thread *td)
427 {
428 	struct jaildesc *jd1, *jd2;
429 	int jid1, jid2;
430 
431 	if (fp2->f_type != DTYPE_JAILDESC)
432 		return (3);
433 	jd1 = fp1->f_data;
434 	JAILDESC_LOCK(jd1);
435 	jid1 = jd1->jd_prison ? (uintptr_t)jd1->jd_prison->pr_id : 0;
436 	JAILDESC_UNLOCK(jd1);
437 	jd2 = fp2->f_data;
438 	JAILDESC_LOCK(jd2);
439 	jid2 = jd2->jd_prison ? (uintptr_t)jd2->jd_prison->pr_id : 0;
440 	JAILDESC_UNLOCK(jd2);
441 	return (kcmp_cmp(jid1, jid2));
442 }
443