xref: /freebsd/sys/kern/kern_jaildesc.c (revision 8ec7a830f10bc0f3b421dfaf6967a1bc996d34fd)
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/priv.h>
40 #include <sys/stat.h>
41 #include <sys/sysproto.h>
42 #include <sys/systm.h>
43 #include <sys/ucred.h>
44 #include <sys/vnode.h>
45 
46 MALLOC_DEFINE(M_JAILDESC, "jaildesc", "jail descriptors");
47 
48 static fo_stat_t	jaildesc_stat;
49 static fo_close_t	jaildesc_close;
50 static fo_chmod_t	jaildesc_chmod;
51 static fo_chown_t	jaildesc_chown;
52 static fo_fill_kinfo_t	jaildesc_fill_kinfo;
53 static fo_cmp_t		jaildesc_cmp;
54 
55 static struct fileops jaildesc_ops = {
56 	.fo_read = invfo_rdwr,
57 	.fo_write = invfo_rdwr,
58 	.fo_truncate = invfo_truncate,
59 	.fo_ioctl = invfo_ioctl,
60 	.fo_poll = invfo_poll,
61 	.fo_kqfilter = invfo_kqfilter,
62 	.fo_stat = jaildesc_stat,
63 	.fo_close = jaildesc_close,
64 	.fo_chmod = jaildesc_chmod,
65 	.fo_chown = jaildesc_chown,
66 	.fo_sendfile = invfo_sendfile,
67 	.fo_fill_kinfo = jaildesc_fill_kinfo,
68 	.fo_cmp = jaildesc_cmp,
69 	.fo_flags = DFLAG_PASSABLE,
70 };
71 
72 /*
73  * Given a jail descriptor number, return the jaildesc, its prison,
74  * and its credential.  The jaildesc will be returned locked, and
75  * prison and the credential will be returned held.
76  */
77 int
jaildesc_find(struct thread * td,int fd,struct jaildesc ** jdp,struct prison ** prp,struct ucred ** ucredp)78 jaildesc_find(struct thread *td, int fd, struct jaildesc **jdp,
79     struct prison **prp, struct ucred **ucredp)
80 {
81 	struct file *fp;
82 	struct jaildesc *jd;
83 	struct prison *pr;
84 	int error;
85 
86 	error = fget(td, fd, &cap_no_rights, &fp);
87 	if (error != 0)
88 		return (error);
89 	if (fp->f_type != DTYPE_JAILDESC) {
90 		error = EBADF;
91 		goto out;
92 	}
93 	jd = fp->f_data;
94 	JAILDESC_LOCK(jd);
95 	pr = jd->jd_prison;
96 	if (pr == NULL || !prison_isvalid(pr)) {
97 		error = ENOENT;
98 		JAILDESC_UNLOCK(jd);
99 		goto out;
100 	}
101 	prison_hold(pr);
102 	*prp = pr;
103 	if (jdp != NULL)
104 		*jdp = jd;
105 	else
106 		JAILDESC_UNLOCK(jd);
107 	if (ucredp != NULL)
108 		*ucredp = crhold(fp->f_cred);
109  out:
110 	fdrop(fp, td);
111 	return (error);
112 }
113 
114 /*
115  * Allocate a new jail decriptor, not yet associated with a prison.
116  * Return the file pointer (with a reference held) and the descriptor
117  * number.
118  */
119 int
jaildesc_alloc(struct thread * td,struct file ** fpp,int * fdp,int owning)120 jaildesc_alloc(struct thread *td, struct file **fpp, int *fdp, int owning)
121 {
122 	struct file *fp;
123 	struct jaildesc *jd;
124 	int error;
125 	mode_t mode;
126 
127 	if (owning) {
128 		error = priv_check(td, PRIV_JAIL_REMOVE);
129 		if (error != 0)
130 			return (error);
131 		mode = S_ISTXT;
132 	} else
133 		mode = 0;
134 	jd = malloc(sizeof(*jd), M_JAILDESC, M_WAITOK | M_ZERO);
135 	error = falloc_caps(td, &fp, fdp, 0, NULL);
136 	if (error != 0) {
137 		free(jd, M_JAILDESC);
138 		return (error);
139 	}
140 	finit(fp, priv_check_cred(fp->f_cred, PRIV_JAIL_SET) == 0
141 	    ? FREAD | FWRITE : FREAD, DTYPE_JAILDESC, jd, &jaildesc_ops);
142 	JAILDESC_LOCK_INIT(jd);
143 	jd->jd_uid = fp->f_cred->cr_uid;
144 	jd->jd_gid = fp->f_cred->cr_gid;
145 	jd->jd_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH | mode
146 	    | (priv_check(td, PRIV_JAIL_SET) == 0 ? S_IWUSR | S_IXUSR : 0)
147 	    | (priv_check(td, PRIV_JAIL_ATTACH) == 0 ? S_IXUSR : 0);
148 	*fpp = fp;
149 	return (0);
150 }
151 
152 /*
153  * Assocate a jail descriptor with its prison.
154  */
155 void
jaildesc_set_prison(struct file * fp,struct prison * pr)156 jaildesc_set_prison(struct file *fp, struct prison *pr)
157 {
158 	struct jaildesc *jd;
159 
160 	mtx_assert(&pr->pr_mtx, MA_OWNED);
161 	jd = fp->f_data;
162 	JAILDESC_LOCK(jd);
163 	jd->jd_prison = pr;
164 	LIST_INSERT_HEAD(&pr->pr_descs, jd, jd_list);
165 	prison_hold(pr);
166 	JAILDESC_UNLOCK(jd);
167 }
168 
169 /*
170  * Detach the all jail descriptors from a prison.
171  */
172 void
jaildesc_prison_cleanup(struct prison * pr)173 jaildesc_prison_cleanup(struct prison *pr)
174 {
175 	struct jaildesc *jd;
176 
177 	mtx_assert(&pr->pr_mtx, MA_OWNED);
178 	while ((jd = LIST_FIRST(&pr->pr_descs))) {
179 		JAILDESC_LOCK(jd);
180 		LIST_REMOVE(jd, jd_list);
181 		jd->jd_prison = NULL;
182 		JAILDESC_UNLOCK(jd);
183 		prison_free(pr);
184 	}
185 }
186 
187 static int
jaildesc_close(struct file * fp,struct thread * td)188 jaildesc_close(struct file *fp, struct thread *td)
189 {
190 	struct jaildesc *jd;
191 	struct prison *pr;
192 
193 	jd = fp->f_data;
194 	fp->f_data = NULL;
195 	if (jd != NULL) {
196 		JAILDESC_LOCK(jd);
197 		pr = jd->jd_prison;
198 		if (pr != NULL) {
199 			/*
200 			 * Free or remove the associated prison.
201 			 * This requires a second check after re-
202 			 * ordering locks.  This jaildesc can remain
203 			 * unlocked once we have a prison reference,
204 			 * because that prison is the only place that
205 			 * still points back to it.
206 			 */
207 			prison_hold(pr);
208 			JAILDESC_UNLOCK(jd);
209 			if (jd->jd_mode & S_ISTXT) {
210 				sx_xlock(&allprison_lock);
211 				prison_lock(pr);
212 				if (jd->jd_prison != NULL) {
213 					/*
214 					 * Unlink the prison, but don't free
215 					 * it; that will be done as part of
216 					 * of prison_remove.
217 					 */
218 					LIST_REMOVE(jd, jd_list);
219 					prison_remove(pr);
220 				} else {
221 					prison_unlock(pr);
222 					sx_xunlock(&allprison_lock);
223 				}
224 			} else {
225 				prison_lock(pr);
226 				if (jd->jd_prison != NULL) {
227 					LIST_REMOVE(jd, jd_list);
228 					prison_free(pr);
229 				}
230 				prison_unlock(pr);
231 			}
232 			prison_free(pr);
233 		}
234 		JAILDESC_LOCK_DESTROY(jd);
235 		free(jd, M_JAILDESC);
236 	}
237 	return (0);
238 }
239 
240 static int
jaildesc_stat(struct file * fp,struct stat * sb,struct ucred * active_cred)241 jaildesc_stat(struct file *fp, struct stat *sb, struct ucred *active_cred)
242 {
243 	struct jaildesc *jd;
244 
245 	bzero(sb, sizeof(struct stat));
246 	jd = fp->f_data;
247 	JAILDESC_LOCK(jd);
248 	if (jd->jd_prison != NULL) {
249 		sb->st_ino = jd->jd_prison ? jd->jd_prison->pr_id : 0;
250 		sb->st_uid = jd->jd_uid;
251 		sb->st_gid = jd->jd_gid;
252 		sb->st_mode = jd->jd_mode;
253 	} else
254 		sb->st_mode = S_IFREG;
255 	JAILDESC_UNLOCK(jd);
256 	return (0);
257 }
258 
259 static int
jaildesc_chmod(struct file * fp,mode_t mode,struct ucred * active_cred,struct thread * td)260 jaildesc_chmod(struct file *fp, mode_t mode, struct ucred *active_cred,
261     struct thread *td)
262 {
263 	struct jaildesc *jd;
264 	int error;
265 
266 	/* Reject permissions that the creator doesn't have. */
267 	if (((mode & (S_IWUSR | S_IWGRP | S_IWOTH))
268 	    && priv_check_cred(fp->f_cred, PRIV_JAIL_SET) != 0)
269 	    || ((mode & (S_IXUSR | S_IXGRP | S_IXOTH))
270 	    && priv_check_cred(fp->f_cred, PRIV_JAIL_ATTACH) != 0
271 	    && priv_check_cred(fp->f_cred, PRIV_JAIL_SET) != 0)
272 	    || ((mode & S_ISTXT)
273 	    && priv_check_cred(fp->f_cred, PRIV_JAIL_REMOVE) != 0))
274 		return (EPERM);
275 	if (mode & (S_ISUID | S_ISGID))
276 		return (EINVAL);
277 	jd = fp->f_data;
278 	JAILDESC_LOCK(jd);
279 	error = vaccess(VREG, jd->jd_mode, jd->jd_uid, jd->jd_gid, VADMIN,
280 		active_cred);
281 	if (error == 0)
282 		jd->jd_mode = S_IFREG | (mode & ALLPERMS);
283 	JAILDESC_UNLOCK(jd);
284 	return (error);
285 }
286 
287 static int
jaildesc_chown(struct file * fp,uid_t uid,gid_t gid,struct ucred * active_cred,struct thread * td)288 jaildesc_chown(struct file *fp, uid_t uid, gid_t gid, struct ucred *active_cred,
289     struct thread *td)
290 {
291 	struct jaildesc *jd;
292 	int error;
293 
294 	error = 0;
295 	jd = fp->f_data;
296 	JAILDESC_LOCK(jd);
297 	if (uid == (uid_t)-1)
298 		uid = jd->jd_uid;
299 	if (gid == (gid_t)-1)
300 		gid = jd->jd_gid;
301 	if ((uid != jd->jd_uid && uid != active_cred->cr_uid) ||
302 	    (gid != jd->jd_gid && !groupmember(gid, active_cred)))
303 		error = priv_check_cred(active_cred, PRIV_VFS_CHOWN);
304 	if (error == 0) {
305 		jd->jd_uid = uid;
306 		jd->jd_gid = gid;
307 	}
308 	JAILDESC_UNLOCK(jd);
309 	return (error);
310 }
311 
312 static int
jaildesc_fill_kinfo(struct file * fp,struct kinfo_file * kif,struct filedesc * fdp)313 jaildesc_fill_kinfo(struct file *fp, struct kinfo_file *kif,
314     struct filedesc *fdp)
315 {
316 	return (EINVAL);
317 }
318 
319 static int
jaildesc_cmp(struct file * fp1,struct file * fp2,struct thread * td)320 jaildesc_cmp(struct file *fp1, struct file *fp2, struct thread *td)
321 {
322 	struct jaildesc *jd1, *jd2;
323 	int jid1, jid2;
324 
325 	if (fp2->f_type != DTYPE_JAILDESC)
326 		return (3);
327 	jd1 = fp1->f_data;
328 	JAILDESC_LOCK(jd1);
329 	jid1 = jd1->jd_prison ? (uintptr_t)jd1->jd_prison->pr_id : 0;
330 	JAILDESC_UNLOCK(jd1);
331 	jd2 = fp2->f_data;
332 	JAILDESC_LOCK(jd2);
333 	jid2 = jd2->jd_prison ? (uintptr_t)jd2->jd_prison->pr_id : 0;
334 	JAILDESC_UNLOCK(jd2);
335 	return (kcmp_cmp(jid1, jid2));
336 }
337