1851dc7f8SJamie Gritton /*- 2851dc7f8SJamie Gritton * SPDX-License-Identifier: BSD-2-Clause 3851dc7f8SJamie Gritton * 4851dc7f8SJamie Gritton * Copyright (c) 2025 James Gritton. 5851dc7f8SJamie Gritton * All rights reserved. 6851dc7f8SJamie Gritton * 7851dc7f8SJamie Gritton * Redistribution and use in source and binary forms, with or without 8851dc7f8SJamie Gritton * modification, are permitted provided that the following conditions 9851dc7f8SJamie Gritton * are met: 10851dc7f8SJamie Gritton * 1. Redistributions of source code must retain the above copyright 11851dc7f8SJamie Gritton * notice, this list of conditions and the following disclaimer. 12851dc7f8SJamie Gritton * 2. Redistributions in binary form must reproduce the above copyright 13851dc7f8SJamie Gritton * notice, this list of conditions and the following disclaimer in the 14851dc7f8SJamie Gritton * documentation and/or other materials provided with the distribution. 15851dc7f8SJamie Gritton * 16851dc7f8SJamie Gritton * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17851dc7f8SJamie Gritton * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18851dc7f8SJamie Gritton * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19851dc7f8SJamie Gritton * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20851dc7f8SJamie Gritton * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21851dc7f8SJamie Gritton * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22851dc7f8SJamie Gritton * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23851dc7f8SJamie Gritton * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24851dc7f8SJamie Gritton * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25851dc7f8SJamie Gritton * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26851dc7f8SJamie Gritton * SUCH DAMAGE. 27851dc7f8SJamie Gritton */ 28851dc7f8SJamie Gritton 29851dc7f8SJamie Gritton #include <sys/param.h> 30851dc7f8SJamie Gritton #include <sys/fcntl.h> 31851dc7f8SJamie Gritton #include <sys/file.h> 32851dc7f8SJamie Gritton #include <sys/filedesc.h> 33851dc7f8SJamie Gritton #include <sys/kernel.h> 34851dc7f8SJamie Gritton #include <sys/jail.h> 35851dc7f8SJamie Gritton #include <sys/jaildesc.h> 36851dc7f8SJamie Gritton #include <sys/lock.h> 37851dc7f8SJamie Gritton #include <sys/malloc.h> 38851dc7f8SJamie Gritton #include <sys/mutex.h> 39851dc7f8SJamie Gritton #include <sys/priv.h> 40851dc7f8SJamie Gritton #include <sys/stat.h> 41851dc7f8SJamie Gritton #include <sys/sysproto.h> 42851dc7f8SJamie Gritton #include <sys/systm.h> 43851dc7f8SJamie Gritton #include <sys/ucred.h> 44851dc7f8SJamie Gritton #include <sys/vnode.h> 45851dc7f8SJamie Gritton 46851dc7f8SJamie Gritton MALLOC_DEFINE(M_JAILDESC, "jaildesc", "jail descriptors"); 47851dc7f8SJamie Gritton 48851dc7f8SJamie Gritton static fo_stat_t jaildesc_stat; 49851dc7f8SJamie Gritton static fo_close_t jaildesc_close; 50851dc7f8SJamie Gritton static fo_chmod_t jaildesc_chmod; 51851dc7f8SJamie Gritton static fo_chown_t jaildesc_chown; 52851dc7f8SJamie Gritton static fo_fill_kinfo_t jaildesc_fill_kinfo; 53851dc7f8SJamie Gritton static fo_cmp_t jaildesc_cmp; 54851dc7f8SJamie Gritton 55851dc7f8SJamie Gritton static struct fileops jaildesc_ops = { 56851dc7f8SJamie Gritton .fo_read = invfo_rdwr, 57851dc7f8SJamie Gritton .fo_write = invfo_rdwr, 58851dc7f8SJamie Gritton .fo_truncate = invfo_truncate, 59851dc7f8SJamie Gritton .fo_ioctl = invfo_ioctl, 60851dc7f8SJamie Gritton .fo_poll = invfo_poll, 61851dc7f8SJamie Gritton .fo_kqfilter = invfo_kqfilter, 62851dc7f8SJamie Gritton .fo_stat = jaildesc_stat, 63851dc7f8SJamie Gritton .fo_close = jaildesc_close, 64851dc7f8SJamie Gritton .fo_chmod = jaildesc_chmod, 65851dc7f8SJamie Gritton .fo_chown = jaildesc_chown, 66851dc7f8SJamie Gritton .fo_sendfile = invfo_sendfile, 67851dc7f8SJamie Gritton .fo_fill_kinfo = jaildesc_fill_kinfo, 68851dc7f8SJamie Gritton .fo_cmp = jaildesc_cmp, 69851dc7f8SJamie Gritton .fo_flags = DFLAG_PASSABLE, 70851dc7f8SJamie Gritton }; 71851dc7f8SJamie Gritton 72851dc7f8SJamie Gritton /* 73851dc7f8SJamie Gritton * Given a jail descriptor number, return the jaildesc, its prison, 74851dc7f8SJamie Gritton * and its credential. The jaildesc will be returned locked, and 75851dc7f8SJamie Gritton * prison and the credential will be returned held. 76851dc7f8SJamie Gritton */ 77851dc7f8SJamie Gritton int 78851dc7f8SJamie Gritton jaildesc_find(struct thread *td, int fd, struct jaildesc **jdp, 79851dc7f8SJamie Gritton struct prison **prp, struct ucred **ucredp) 80851dc7f8SJamie Gritton { 81851dc7f8SJamie Gritton struct file *fp; 82851dc7f8SJamie Gritton struct jaildesc *jd; 83851dc7f8SJamie Gritton struct prison *pr; 84851dc7f8SJamie Gritton int error; 85851dc7f8SJamie Gritton 86851dc7f8SJamie Gritton error = fget(td, fd, &cap_no_rights, &fp); 87851dc7f8SJamie Gritton if (error != 0) 88851dc7f8SJamie Gritton return (error); 89851dc7f8SJamie Gritton if (fp->f_type != DTYPE_JAILDESC) { 9016f600dcSJamie Gritton error = EINVAL; 91851dc7f8SJamie Gritton goto out; 92851dc7f8SJamie Gritton } 93851dc7f8SJamie Gritton jd = fp->f_data; 94851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 95851dc7f8SJamie Gritton pr = jd->jd_prison; 96851dc7f8SJamie Gritton if (pr == NULL || !prison_isvalid(pr)) { 97851dc7f8SJamie Gritton error = ENOENT; 98851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 99851dc7f8SJamie Gritton goto out; 100851dc7f8SJamie Gritton } 101851dc7f8SJamie Gritton prison_hold(pr); 102851dc7f8SJamie Gritton *prp = pr; 103851dc7f8SJamie Gritton if (jdp != NULL) 104851dc7f8SJamie Gritton *jdp = jd; 105851dc7f8SJamie Gritton else 106851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 107851dc7f8SJamie Gritton if (ucredp != NULL) 108851dc7f8SJamie Gritton *ucredp = crhold(fp->f_cred); 109851dc7f8SJamie Gritton out: 110851dc7f8SJamie Gritton fdrop(fp, td); 111851dc7f8SJamie Gritton return (error); 112851dc7f8SJamie Gritton } 113851dc7f8SJamie Gritton 114851dc7f8SJamie Gritton /* 115851dc7f8SJamie Gritton * Allocate a new jail decriptor, not yet associated with a prison. 116851dc7f8SJamie Gritton * Return the file pointer (with a reference held) and the descriptor 117851dc7f8SJamie Gritton * number. 118851dc7f8SJamie Gritton */ 119851dc7f8SJamie Gritton int 120851dc7f8SJamie Gritton jaildesc_alloc(struct thread *td, struct file **fpp, int *fdp, int owning) 121851dc7f8SJamie Gritton { 122851dc7f8SJamie Gritton struct file *fp; 123851dc7f8SJamie Gritton struct jaildesc *jd; 124851dc7f8SJamie Gritton int error; 125851dc7f8SJamie Gritton mode_t mode; 126851dc7f8SJamie Gritton 127851dc7f8SJamie Gritton if (owning) { 128851dc7f8SJamie Gritton error = priv_check(td, PRIV_JAIL_REMOVE); 129851dc7f8SJamie Gritton if (error != 0) 130851dc7f8SJamie Gritton return (error); 131851dc7f8SJamie Gritton mode = S_ISTXT; 132851dc7f8SJamie Gritton } else 133851dc7f8SJamie Gritton mode = 0; 134851dc7f8SJamie Gritton jd = malloc(sizeof(*jd), M_JAILDESC, M_WAITOK | M_ZERO); 135851dc7f8SJamie Gritton error = falloc_caps(td, &fp, fdp, 0, NULL); 136851dc7f8SJamie Gritton if (error != 0) { 137851dc7f8SJamie Gritton free(jd, M_JAILDESC); 138851dc7f8SJamie Gritton return (error); 139851dc7f8SJamie Gritton } 140*d8d5324eSJamie Gritton finit(fp, priv_check_cred(fp->f_cred, PRIV_JAIL_SET) == 0 ? 141*d8d5324eSJamie Gritton FREAD | FWRITE : FREAD, DTYPE_JAILDESC, jd, &jaildesc_ops); 142851dc7f8SJamie Gritton JAILDESC_LOCK_INIT(jd); 143851dc7f8SJamie Gritton jd->jd_uid = fp->f_cred->cr_uid; 144851dc7f8SJamie Gritton jd->jd_gid = fp->f_cred->cr_gid; 145*d8d5324eSJamie Gritton jd->jd_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH | mode | 146*d8d5324eSJamie Gritton (priv_check(td, PRIV_JAIL_SET) == 0 ? S_IWUSR | S_IXUSR : 0) | 147*d8d5324eSJamie Gritton (priv_check(td, PRIV_JAIL_ATTACH) == 0 ? S_IXUSR : 0); 148851dc7f8SJamie Gritton *fpp = fp; 149851dc7f8SJamie Gritton return (0); 150851dc7f8SJamie Gritton } 151851dc7f8SJamie Gritton 152851dc7f8SJamie Gritton /* 153851dc7f8SJamie Gritton * Assocate a jail descriptor with its prison. 154851dc7f8SJamie Gritton */ 155851dc7f8SJamie Gritton void 156851dc7f8SJamie Gritton jaildesc_set_prison(struct file *fp, struct prison *pr) 157851dc7f8SJamie Gritton { 158851dc7f8SJamie Gritton struct jaildesc *jd; 159851dc7f8SJamie Gritton 160851dc7f8SJamie Gritton mtx_assert(&pr->pr_mtx, MA_OWNED); 161851dc7f8SJamie Gritton jd = fp->f_data; 162851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 163851dc7f8SJamie Gritton jd->jd_prison = pr; 164851dc7f8SJamie Gritton LIST_INSERT_HEAD(&pr->pr_descs, jd, jd_list); 165851dc7f8SJamie Gritton prison_hold(pr); 166851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 167851dc7f8SJamie Gritton } 168851dc7f8SJamie Gritton 169851dc7f8SJamie Gritton /* 170*d8d5324eSJamie Gritton * Detach all the jail descriptors from a prison. 171851dc7f8SJamie Gritton */ 172851dc7f8SJamie Gritton void 173851dc7f8SJamie Gritton jaildesc_prison_cleanup(struct prison *pr) 174851dc7f8SJamie Gritton { 175851dc7f8SJamie Gritton struct jaildesc *jd; 176851dc7f8SJamie Gritton 177851dc7f8SJamie Gritton mtx_assert(&pr->pr_mtx, MA_OWNED); 178851dc7f8SJamie Gritton while ((jd = LIST_FIRST(&pr->pr_descs))) { 179851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 180851dc7f8SJamie Gritton LIST_REMOVE(jd, jd_list); 181851dc7f8SJamie Gritton jd->jd_prison = NULL; 182851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 183851dc7f8SJamie Gritton prison_free(pr); 184851dc7f8SJamie Gritton } 185851dc7f8SJamie Gritton } 186851dc7f8SJamie Gritton 187851dc7f8SJamie Gritton static int 188851dc7f8SJamie Gritton jaildesc_close(struct file *fp, struct thread *td) 189851dc7f8SJamie Gritton { 190851dc7f8SJamie Gritton struct jaildesc *jd; 191851dc7f8SJamie Gritton struct prison *pr; 192851dc7f8SJamie Gritton 193851dc7f8SJamie Gritton jd = fp->f_data; 194851dc7f8SJamie Gritton fp->f_data = NULL; 195851dc7f8SJamie Gritton if (jd != NULL) { 196851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 197851dc7f8SJamie Gritton pr = jd->jd_prison; 198851dc7f8SJamie Gritton if (pr != NULL) { 199851dc7f8SJamie Gritton /* 200851dc7f8SJamie Gritton * Free or remove the associated prison. 201851dc7f8SJamie Gritton * This requires a second check after re- 202851dc7f8SJamie Gritton * ordering locks. This jaildesc can remain 203851dc7f8SJamie Gritton * unlocked once we have a prison reference, 204851dc7f8SJamie Gritton * because that prison is the only place that 205851dc7f8SJamie Gritton * still points back to it. 206851dc7f8SJamie Gritton */ 207851dc7f8SJamie Gritton prison_hold(pr); 208851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 209851dc7f8SJamie Gritton if (jd->jd_mode & S_ISTXT) { 210851dc7f8SJamie Gritton sx_xlock(&allprison_lock); 211851dc7f8SJamie Gritton prison_lock(pr); 212851dc7f8SJamie Gritton if (jd->jd_prison != NULL) { 213851dc7f8SJamie Gritton /* 214851dc7f8SJamie Gritton * Unlink the prison, but don't free 215851dc7f8SJamie Gritton * it; that will be done as part of 216851dc7f8SJamie Gritton * of prison_remove. 217851dc7f8SJamie Gritton */ 218851dc7f8SJamie Gritton LIST_REMOVE(jd, jd_list); 219851dc7f8SJamie Gritton prison_remove(pr); 220851dc7f8SJamie Gritton } else { 221851dc7f8SJamie Gritton prison_unlock(pr); 222851dc7f8SJamie Gritton sx_xunlock(&allprison_lock); 223851dc7f8SJamie Gritton } 224851dc7f8SJamie Gritton } else { 225851dc7f8SJamie Gritton prison_lock(pr); 226851dc7f8SJamie Gritton if (jd->jd_prison != NULL) { 227851dc7f8SJamie Gritton LIST_REMOVE(jd, jd_list); 228851dc7f8SJamie Gritton prison_free(pr); 229851dc7f8SJamie Gritton } 230851dc7f8SJamie Gritton prison_unlock(pr); 231851dc7f8SJamie Gritton } 232851dc7f8SJamie Gritton prison_free(pr); 233851dc7f8SJamie Gritton } 234851dc7f8SJamie Gritton JAILDESC_LOCK_DESTROY(jd); 235851dc7f8SJamie Gritton free(jd, M_JAILDESC); 236851dc7f8SJamie Gritton } 237851dc7f8SJamie Gritton return (0); 238851dc7f8SJamie Gritton } 239851dc7f8SJamie Gritton 240851dc7f8SJamie Gritton static int 241851dc7f8SJamie Gritton jaildesc_stat(struct file *fp, struct stat *sb, struct ucred *active_cred) 242851dc7f8SJamie Gritton { 243851dc7f8SJamie Gritton struct jaildesc *jd; 244851dc7f8SJamie Gritton 245851dc7f8SJamie Gritton bzero(sb, sizeof(struct stat)); 246851dc7f8SJamie Gritton jd = fp->f_data; 247851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 248851dc7f8SJamie Gritton if (jd->jd_prison != NULL) { 249851dc7f8SJamie Gritton sb->st_ino = jd->jd_prison ? jd->jd_prison->pr_id : 0; 250851dc7f8SJamie Gritton sb->st_uid = jd->jd_uid; 251851dc7f8SJamie Gritton sb->st_gid = jd->jd_gid; 252851dc7f8SJamie Gritton sb->st_mode = jd->jd_mode; 253851dc7f8SJamie Gritton } else 254851dc7f8SJamie Gritton sb->st_mode = S_IFREG; 255851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 256851dc7f8SJamie Gritton return (0); 257851dc7f8SJamie Gritton } 258851dc7f8SJamie Gritton 259851dc7f8SJamie Gritton static int 260851dc7f8SJamie Gritton jaildesc_chmod(struct file *fp, mode_t mode, struct ucred *active_cred, 261851dc7f8SJamie Gritton struct thread *td) 262851dc7f8SJamie Gritton { 263851dc7f8SJamie Gritton struct jaildesc *jd; 264851dc7f8SJamie Gritton int error; 265851dc7f8SJamie Gritton 266851dc7f8SJamie Gritton /* Reject permissions that the creator doesn't have. */ 267*d8d5324eSJamie Gritton if (((mode & (S_IWUSR | S_IWGRP | S_IWOTH)) && 268*d8d5324eSJamie Gritton priv_check_cred(fp->f_cred, PRIV_JAIL_SET) != 0) || 269*d8d5324eSJamie Gritton ((mode & (S_IXUSR | S_IXGRP | S_IXOTH)) && 270*d8d5324eSJamie Gritton priv_check_cred(fp->f_cred, PRIV_JAIL_ATTACH) != 0 && 271*d8d5324eSJamie Gritton priv_check_cred(fp->f_cred, PRIV_JAIL_SET) != 0) || 272*d8d5324eSJamie Gritton ((mode & S_ISTXT) && 273*d8d5324eSJamie Gritton priv_check_cred(fp->f_cred, PRIV_JAIL_REMOVE) != 0)) 274851dc7f8SJamie Gritton return (EPERM); 275851dc7f8SJamie Gritton if (mode & (S_ISUID | S_ISGID)) 276851dc7f8SJamie Gritton return (EINVAL); 277851dc7f8SJamie Gritton jd = fp->f_data; 278851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 279851dc7f8SJamie Gritton error = vaccess(VREG, jd->jd_mode, jd->jd_uid, jd->jd_gid, VADMIN, 280851dc7f8SJamie Gritton active_cred); 281851dc7f8SJamie Gritton if (error == 0) 282851dc7f8SJamie Gritton jd->jd_mode = S_IFREG | (mode & ALLPERMS); 283851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 284851dc7f8SJamie Gritton return (error); 285851dc7f8SJamie Gritton } 286851dc7f8SJamie Gritton 287851dc7f8SJamie Gritton static int 288851dc7f8SJamie Gritton jaildesc_chown(struct file *fp, uid_t uid, gid_t gid, struct ucred *active_cred, 289851dc7f8SJamie Gritton struct thread *td) 290851dc7f8SJamie Gritton { 291851dc7f8SJamie Gritton struct jaildesc *jd; 292851dc7f8SJamie Gritton int error; 293851dc7f8SJamie Gritton 294851dc7f8SJamie Gritton error = 0; 295851dc7f8SJamie Gritton jd = fp->f_data; 296851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 297851dc7f8SJamie Gritton if (uid == (uid_t)-1) 298851dc7f8SJamie Gritton uid = jd->jd_uid; 299851dc7f8SJamie Gritton if (gid == (gid_t)-1) 300851dc7f8SJamie Gritton gid = jd->jd_gid; 301851dc7f8SJamie Gritton if ((uid != jd->jd_uid && uid != active_cred->cr_uid) || 302851dc7f8SJamie Gritton (gid != jd->jd_gid && !groupmember(gid, active_cred))) 303851dc7f8SJamie Gritton error = priv_check_cred(active_cred, PRIV_VFS_CHOWN); 304851dc7f8SJamie Gritton if (error == 0) { 305851dc7f8SJamie Gritton jd->jd_uid = uid; 306851dc7f8SJamie Gritton jd->jd_gid = gid; 307851dc7f8SJamie Gritton } 308851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 309851dc7f8SJamie Gritton return (error); 310851dc7f8SJamie Gritton } 311851dc7f8SJamie Gritton 312851dc7f8SJamie Gritton static int 313851dc7f8SJamie Gritton jaildesc_fill_kinfo(struct file *fp, struct kinfo_file *kif, 314851dc7f8SJamie Gritton struct filedesc *fdp) 315851dc7f8SJamie Gritton { 316851dc7f8SJamie Gritton return (EINVAL); 317851dc7f8SJamie Gritton } 318851dc7f8SJamie Gritton 319851dc7f8SJamie Gritton static int 320851dc7f8SJamie Gritton jaildesc_cmp(struct file *fp1, struct file *fp2, struct thread *td) 321851dc7f8SJamie Gritton { 322851dc7f8SJamie Gritton struct jaildesc *jd1, *jd2; 323851dc7f8SJamie Gritton int jid1, jid2; 324851dc7f8SJamie Gritton 325851dc7f8SJamie Gritton if (fp2->f_type != DTYPE_JAILDESC) 326851dc7f8SJamie Gritton return (3); 327851dc7f8SJamie Gritton jd1 = fp1->f_data; 328851dc7f8SJamie Gritton JAILDESC_LOCK(jd1); 329851dc7f8SJamie Gritton jid1 = jd1->jd_prison ? (uintptr_t)jd1->jd_prison->pr_id : 0; 330851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd1); 331851dc7f8SJamie Gritton jd2 = fp2->f_data; 332851dc7f8SJamie Gritton JAILDESC_LOCK(jd2); 333851dc7f8SJamie Gritton jid2 = jd2->jd_prison ? (uintptr_t)jd2->jd_prison->pr_id : 0; 334851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd2); 335851dc7f8SJamie Gritton return (kcmp_cmp(jid1, jid2)); 336851dc7f8SJamie Gritton } 337