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> 44*d81b337dSJamie Gritton #include <sys/user.h> 45851dc7f8SJamie Gritton #include <sys/vnode.h> 46851dc7f8SJamie Gritton 47851dc7f8SJamie Gritton MALLOC_DEFINE(M_JAILDESC, "jaildesc", "jail descriptors"); 48851dc7f8SJamie Gritton 49851dc7f8SJamie Gritton static fo_stat_t jaildesc_stat; 50851dc7f8SJamie Gritton static fo_close_t jaildesc_close; 51851dc7f8SJamie Gritton static fo_fill_kinfo_t jaildesc_fill_kinfo; 52851dc7f8SJamie Gritton static fo_cmp_t jaildesc_cmp; 53851dc7f8SJamie Gritton 54851dc7f8SJamie Gritton static struct fileops jaildesc_ops = { 55851dc7f8SJamie Gritton .fo_read = invfo_rdwr, 56851dc7f8SJamie Gritton .fo_write = invfo_rdwr, 57851dc7f8SJamie Gritton .fo_truncate = invfo_truncate, 58851dc7f8SJamie Gritton .fo_ioctl = invfo_ioctl, 59851dc7f8SJamie Gritton .fo_poll = invfo_poll, 60851dc7f8SJamie Gritton .fo_kqfilter = invfo_kqfilter, 61851dc7f8SJamie Gritton .fo_stat = jaildesc_stat, 62851dc7f8SJamie Gritton .fo_close = jaildesc_close, 63*d81b337dSJamie Gritton .fo_chmod = invfo_chmod, 64*d81b337dSJamie Gritton .fo_chown = invfo_chown, 65851dc7f8SJamie Gritton .fo_sendfile = invfo_sendfile, 66851dc7f8SJamie Gritton .fo_fill_kinfo = jaildesc_fill_kinfo, 67851dc7f8SJamie Gritton .fo_cmp = jaildesc_cmp, 68851dc7f8SJamie Gritton .fo_flags = DFLAG_PASSABLE, 69851dc7f8SJamie Gritton }; 70851dc7f8SJamie Gritton 71851dc7f8SJamie Gritton /* 72*d81b337dSJamie Gritton * Given a jail descriptor number, return its prison and/or its 73*d81b337dSJamie Gritton * credential. They are returned held, and will need to be released 74*d81b337dSJamie Gritton * by the caller. 75851dc7f8SJamie Gritton */ 76851dc7f8SJamie Gritton int 77*d81b337dSJamie Gritton jaildesc_find(struct thread *td, int fd, struct prison **prp, 78*d81b337dSJamie Gritton struct ucred **ucredp) 79851dc7f8SJamie Gritton { 80851dc7f8SJamie Gritton struct file *fp; 81851dc7f8SJamie Gritton struct jaildesc *jd; 82851dc7f8SJamie Gritton struct prison *pr; 83851dc7f8SJamie Gritton int error; 84851dc7f8SJamie Gritton 85851dc7f8SJamie Gritton error = fget(td, fd, &cap_no_rights, &fp); 86851dc7f8SJamie Gritton if (error != 0) 87851dc7f8SJamie Gritton return (error); 88851dc7f8SJamie Gritton if (fp->f_type != DTYPE_JAILDESC) { 8916f600dcSJamie Gritton error = EINVAL; 90851dc7f8SJamie Gritton goto out; 91851dc7f8SJamie Gritton } 92851dc7f8SJamie Gritton jd = fp->f_data; 93851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 94851dc7f8SJamie Gritton pr = jd->jd_prison; 95851dc7f8SJamie Gritton if (pr == NULL || !prison_isvalid(pr)) { 96851dc7f8SJamie Gritton error = ENOENT; 97851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 98851dc7f8SJamie Gritton goto out; 99851dc7f8SJamie Gritton } 100*d81b337dSJamie Gritton if (prp != NULL) { 101851dc7f8SJamie Gritton prison_hold(pr); 102851dc7f8SJamie Gritton *prp = pr; 103*d81b337dSJamie Gritton } 104851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 105851dc7f8SJamie Gritton if (ucredp != NULL) 106851dc7f8SJamie Gritton *ucredp = crhold(fp->f_cred); 107851dc7f8SJamie Gritton out: 108851dc7f8SJamie Gritton fdrop(fp, td); 109851dc7f8SJamie Gritton return (error); 110851dc7f8SJamie Gritton } 111851dc7f8SJamie Gritton 112851dc7f8SJamie Gritton /* 113851dc7f8SJamie Gritton * Allocate a new jail decriptor, not yet associated with a prison. 114851dc7f8SJamie Gritton * Return the file pointer (with a reference held) and the descriptor 115851dc7f8SJamie Gritton * number. 116851dc7f8SJamie Gritton */ 117851dc7f8SJamie Gritton int 118851dc7f8SJamie Gritton jaildesc_alloc(struct thread *td, struct file **fpp, int *fdp, int owning) 119851dc7f8SJamie Gritton { 120851dc7f8SJamie Gritton struct file *fp; 121851dc7f8SJamie Gritton struct jaildesc *jd; 122851dc7f8SJamie Gritton int error; 123851dc7f8SJamie Gritton 124851dc7f8SJamie Gritton if (owning) { 125851dc7f8SJamie Gritton error = priv_check(td, PRIV_JAIL_REMOVE); 126851dc7f8SJamie Gritton if (error != 0) 127851dc7f8SJamie Gritton return (error); 128*d81b337dSJamie Gritton } 129851dc7f8SJamie Gritton jd = malloc(sizeof(*jd), M_JAILDESC, M_WAITOK | M_ZERO); 130851dc7f8SJamie Gritton error = falloc_caps(td, &fp, fdp, 0, NULL); 131851dc7f8SJamie Gritton if (error != 0) { 132851dc7f8SJamie Gritton free(jd, M_JAILDESC); 133851dc7f8SJamie Gritton return (error); 134851dc7f8SJamie Gritton } 135d8d5324eSJamie Gritton finit(fp, priv_check_cred(fp->f_cred, PRIV_JAIL_SET) == 0 ? 136d8d5324eSJamie Gritton FREAD | FWRITE : FREAD, DTYPE_JAILDESC, jd, &jaildesc_ops); 137851dc7f8SJamie Gritton JAILDESC_LOCK_INIT(jd); 138*d81b337dSJamie Gritton if (owning) 139*d81b337dSJamie Gritton jd->jd_flags |= JDF_OWNING; 140851dc7f8SJamie Gritton *fpp = fp; 141851dc7f8SJamie Gritton return (0); 142851dc7f8SJamie Gritton } 143851dc7f8SJamie Gritton 144851dc7f8SJamie Gritton /* 145851dc7f8SJamie Gritton * Assocate a jail descriptor with its prison. 146851dc7f8SJamie Gritton */ 147851dc7f8SJamie Gritton void 148851dc7f8SJamie Gritton jaildesc_set_prison(struct file *fp, struct prison *pr) 149851dc7f8SJamie Gritton { 150851dc7f8SJamie Gritton struct jaildesc *jd; 151851dc7f8SJamie Gritton 152851dc7f8SJamie Gritton mtx_assert(&pr->pr_mtx, MA_OWNED); 153851dc7f8SJamie Gritton jd = fp->f_data; 154851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 155851dc7f8SJamie Gritton jd->jd_prison = pr; 156851dc7f8SJamie Gritton LIST_INSERT_HEAD(&pr->pr_descs, jd, jd_list); 157851dc7f8SJamie Gritton prison_hold(pr); 158851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 159851dc7f8SJamie Gritton } 160851dc7f8SJamie Gritton 161851dc7f8SJamie Gritton /* 162d8d5324eSJamie Gritton * Detach all the jail descriptors from a prison. 163851dc7f8SJamie Gritton */ 164851dc7f8SJamie Gritton void 165851dc7f8SJamie Gritton jaildesc_prison_cleanup(struct prison *pr) 166851dc7f8SJamie Gritton { 167851dc7f8SJamie Gritton struct jaildesc *jd; 168851dc7f8SJamie Gritton 169851dc7f8SJamie Gritton mtx_assert(&pr->pr_mtx, MA_OWNED); 170851dc7f8SJamie Gritton while ((jd = LIST_FIRST(&pr->pr_descs))) { 171851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 172851dc7f8SJamie Gritton LIST_REMOVE(jd, jd_list); 173851dc7f8SJamie Gritton jd->jd_prison = NULL; 174851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 175851dc7f8SJamie Gritton prison_free(pr); 176851dc7f8SJamie Gritton } 177851dc7f8SJamie Gritton } 178851dc7f8SJamie Gritton 179851dc7f8SJamie Gritton static int 180851dc7f8SJamie Gritton jaildesc_close(struct file *fp, struct thread *td) 181851dc7f8SJamie Gritton { 182851dc7f8SJamie Gritton struct jaildesc *jd; 183851dc7f8SJamie Gritton struct prison *pr; 184851dc7f8SJamie Gritton 185851dc7f8SJamie Gritton jd = fp->f_data; 186851dc7f8SJamie Gritton fp->f_data = NULL; 187851dc7f8SJamie Gritton if (jd != NULL) { 188851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 189851dc7f8SJamie Gritton pr = jd->jd_prison; 190851dc7f8SJamie Gritton if (pr != NULL) { 191851dc7f8SJamie Gritton /* 192851dc7f8SJamie Gritton * Free or remove the associated prison. 193851dc7f8SJamie Gritton * This requires a second check after re- 194851dc7f8SJamie Gritton * ordering locks. This jaildesc can remain 195851dc7f8SJamie Gritton * unlocked once we have a prison reference, 196851dc7f8SJamie Gritton * because that prison is the only place that 197851dc7f8SJamie Gritton * still points back to it. 198851dc7f8SJamie Gritton */ 199851dc7f8SJamie Gritton prison_hold(pr); 200851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 201*d81b337dSJamie Gritton if (jd->jd_flags & JDF_OWNING) { 202851dc7f8SJamie Gritton sx_xlock(&allprison_lock); 203851dc7f8SJamie Gritton prison_lock(pr); 204851dc7f8SJamie Gritton if (jd->jd_prison != NULL) { 205851dc7f8SJamie Gritton /* 206851dc7f8SJamie Gritton * Unlink the prison, but don't free 207851dc7f8SJamie Gritton * it; that will be done as part of 208851dc7f8SJamie Gritton * of prison_remove. 209851dc7f8SJamie Gritton */ 210851dc7f8SJamie Gritton LIST_REMOVE(jd, jd_list); 211851dc7f8SJamie Gritton prison_remove(pr); 212851dc7f8SJamie Gritton } else { 213851dc7f8SJamie Gritton prison_unlock(pr); 214851dc7f8SJamie Gritton sx_xunlock(&allprison_lock); 215851dc7f8SJamie Gritton } 216851dc7f8SJamie Gritton } else { 217851dc7f8SJamie Gritton prison_lock(pr); 218851dc7f8SJamie Gritton if (jd->jd_prison != NULL) { 219851dc7f8SJamie Gritton LIST_REMOVE(jd, jd_list); 220851dc7f8SJamie Gritton prison_free(pr); 221851dc7f8SJamie Gritton } 222851dc7f8SJamie Gritton prison_unlock(pr); 223851dc7f8SJamie Gritton } 224851dc7f8SJamie Gritton prison_free(pr); 225851dc7f8SJamie Gritton } 226851dc7f8SJamie Gritton JAILDESC_LOCK_DESTROY(jd); 227851dc7f8SJamie Gritton free(jd, M_JAILDESC); 228851dc7f8SJamie Gritton } 229851dc7f8SJamie Gritton return (0); 230851dc7f8SJamie Gritton } 231851dc7f8SJamie Gritton 232851dc7f8SJamie Gritton static int 233851dc7f8SJamie Gritton jaildesc_stat(struct file *fp, struct stat *sb, struct ucred *active_cred) 234851dc7f8SJamie Gritton { 235851dc7f8SJamie Gritton struct jaildesc *jd; 236851dc7f8SJamie Gritton 237851dc7f8SJamie Gritton bzero(sb, sizeof(struct stat)); 238851dc7f8SJamie Gritton jd = fp->f_data; 239851dc7f8SJamie Gritton JAILDESC_LOCK(jd); 240851dc7f8SJamie Gritton if (jd->jd_prison != NULL) { 241*d81b337dSJamie Gritton sb->st_ino = jd->jd_prison->pr_id; 242*d81b337dSJamie Gritton sb->st_mode = S_IFREG | S_IRWXU; 243851dc7f8SJamie Gritton } else 244851dc7f8SJamie Gritton sb->st_mode = S_IFREG; 245851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd); 246851dc7f8SJamie Gritton return (0); 247851dc7f8SJamie Gritton } 248851dc7f8SJamie Gritton 249851dc7f8SJamie Gritton static int 250851dc7f8SJamie Gritton jaildesc_fill_kinfo(struct file *fp, struct kinfo_file *kif, 251851dc7f8SJamie Gritton struct filedesc *fdp) 252851dc7f8SJamie Gritton { 253*d81b337dSJamie Gritton struct jaildesc *jd; 254*d81b337dSJamie Gritton 255*d81b337dSJamie Gritton jd = fp->f_data; 256*d81b337dSJamie Gritton kif->kf_type = KF_TYPE_JAILDESC; 257*d81b337dSJamie Gritton kif->kf_un.kf_jail.kf_jid = jd->jd_prison ? jd->jd_prison->pr_id : 0; 258*d81b337dSJamie Gritton return (0); 259851dc7f8SJamie Gritton } 260851dc7f8SJamie Gritton 261851dc7f8SJamie Gritton static int 262851dc7f8SJamie Gritton jaildesc_cmp(struct file *fp1, struct file *fp2, struct thread *td) 263851dc7f8SJamie Gritton { 264851dc7f8SJamie Gritton struct jaildesc *jd1, *jd2; 265851dc7f8SJamie Gritton int jid1, jid2; 266851dc7f8SJamie Gritton 267851dc7f8SJamie Gritton if (fp2->f_type != DTYPE_JAILDESC) 268851dc7f8SJamie Gritton return (3); 269851dc7f8SJamie Gritton jd1 = fp1->f_data; 270851dc7f8SJamie Gritton JAILDESC_LOCK(jd1); 271851dc7f8SJamie Gritton jid1 = jd1->jd_prison ? (uintptr_t)jd1->jd_prison->pr_id : 0; 272851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd1); 273851dc7f8SJamie Gritton jd2 = fp2->f_data; 274851dc7f8SJamie Gritton JAILDESC_LOCK(jd2); 275851dc7f8SJamie Gritton jid2 = jd2->jd_prison ? (uintptr_t)jd2->jd_prison->pr_id : 0; 276851dc7f8SJamie Gritton JAILDESC_UNLOCK(jd2); 277851dc7f8SJamie Gritton return (kcmp_cmp(jid1, jid2)); 278851dc7f8SJamie Gritton } 279