1*2b46a8cbSderaadt /* $OpenBSD: kern_unveil.c,v 1.55 2022/12/05 23:18:37 deraadt Exp $ */
28b23add8Sbeck
38b23add8Sbeck /*
41939e486Sbeck * Copyright (c) 2017-2019 Bob Beck <beck@openbsd.org>
58b23add8Sbeck *
68b23add8Sbeck * Permission to use, copy, modify, and distribute this software for any
78b23add8Sbeck * purpose with or without fee is hereby granted, provided that the above
88b23add8Sbeck * copyright notice and this permission notice appear in all copies.
98b23add8Sbeck *
108b23add8Sbeck * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
118b23add8Sbeck * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
128b23add8Sbeck * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
138b23add8Sbeck * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
148b23add8Sbeck * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
158b23add8Sbeck * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
168b23add8Sbeck * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
178b23add8Sbeck */
188b23add8Sbeck
198b23add8Sbeck #include <sys/param.h>
208b23add8Sbeck
21fa19a603Sbluhm #include <sys/acct.h>
228b23add8Sbeck #include <sys/mount.h>
232369e5b2Sbeck #include <sys/filedesc.h>
248b23add8Sbeck #include <sys/proc.h>
258b23add8Sbeck #include <sys/namei.h>
268b23add8Sbeck #include <sys/vnode.h>
278b23add8Sbeck #include <sys/types.h>
288b23add8Sbeck #include <sys/malloc.h>
298b23add8Sbeck #include <sys/tree.h>
30b077271dSderaadt #include <sys/lock.h>
318b23add8Sbeck
328b23add8Sbeck #include <sys/syscall.h>
338b23add8Sbeck #include <sys/syscallargs.h>
348b23add8Sbeck #include <sys/systm.h>
358b23add8Sbeck
368b23add8Sbeck #include <sys/pledge.h>
378b23add8Sbeck
38dd1a4184Santon struct unvname {
39dd1a4184Santon char *un_name;
40dd1a4184Santon size_t un_namesize;
41dd1a4184Santon u_char un_flags;
42dd1a4184Santon RBT_ENTRY(unvnmae) un_rbt;
43dd1a4184Santon };
44dd1a4184Santon
45dd1a4184Santon RBT_HEAD(unvname_rbt, unvname);
46dd1a4184Santon
47dd1a4184Santon struct unveil {
48dd1a4184Santon struct vnode *uv_vp;
49dd1a4184Santon ssize_t uv_cover;
50dd1a4184Santon struct unvname_rbt uv_names;
51dd1a4184Santon struct rwlock uv_lock;
52dd1a4184Santon u_char uv_flags;
53dd1a4184Santon };
54dd1a4184Santon
558b23add8Sbeck /* #define DEBUG_UNVEIL */
5662aa2271Ssemarie #ifdef DEBUG_UNVEIL
5762aa2271Ssemarie #define DPRINTF(x...) do { printf(x); } while (0)
5862aa2271Ssemarie #else
5962aa2271Ssemarie #define DPRINTF(x...)
6062aa2271Ssemarie #endif
618b23add8Sbeck
628b23add8Sbeck #define UNVEIL_MAX_VNODES 128
638b23add8Sbeck #define UNVEIL_MAX_NAMES 128
648b23add8Sbeck
658b23add8Sbeck static inline int
unvname_compare(const struct unvname * n1,const struct unvname * n2)668b23add8Sbeck unvname_compare(const struct unvname *n1, const struct unvname *n2)
678b23add8Sbeck {
688b23add8Sbeck if (n1->un_namesize == n2->un_namesize)
698b23add8Sbeck return (memcmp(n1->un_name, n2->un_name, n1->un_namesize));
708b23add8Sbeck else
718b23add8Sbeck return (n1->un_namesize - n2->un_namesize);
728b23add8Sbeck }
738b23add8Sbeck
748b23add8Sbeck struct unvname *
unvname_new(const char * name,size_t size,u_char flags)75e1a6e226Sbeck unvname_new(const char *name, size_t size, u_char flags)
768b23add8Sbeck {
778b23add8Sbeck struct unvname *ret = malloc(sizeof(struct unvname), M_PROC, M_WAITOK);
788b23add8Sbeck ret->un_name = malloc(size, M_PROC, M_WAITOK);
798b23add8Sbeck memcpy(ret->un_name, name, size);
808b23add8Sbeck ret->un_namesize = size;
818b23add8Sbeck ret->un_flags = flags;
828b23add8Sbeck return ret;
838b23add8Sbeck }
848b23add8Sbeck
858b23add8Sbeck void
unvname_delete(struct unvname * name)868b23add8Sbeck unvname_delete(struct unvname *name)
878b23add8Sbeck {
88ab8e3451Sderaadt free(name->un_name, M_PROC, name->un_namesize);
898b23add8Sbeck free(name, M_PROC, sizeof(struct unvname));
908b23add8Sbeck }
918b23add8Sbeck
928b23add8Sbeck RBT_PROTOTYPE(unvname_rbt, unvname, un_rbt, unvname_compare);
938b23add8Sbeck RBT_GENERATE(unvname_rbt, unvname, un_rbt, unvname_compare);
948b23add8Sbeck
958b23add8Sbeck int
unveil_delete_names(struct unveil * uv)968b23add8Sbeck unveil_delete_names(struct unveil *uv)
978b23add8Sbeck {
988b23add8Sbeck struct unvname *unvn, *next;
998b23add8Sbeck int ret = 0;
1008b23add8Sbeck
1018b23add8Sbeck rw_enter_write(&uv->uv_lock);
1028b23add8Sbeck RBT_FOREACH_SAFE(unvn, unvname_rbt, &uv->uv_names, next) {
1038b23add8Sbeck RBT_REMOVE(unvname_rbt, &uv->uv_names, unvn);
1048b23add8Sbeck unvname_delete(unvn);
1058b23add8Sbeck ret++;
1068b23add8Sbeck }
1078b23add8Sbeck rw_exit_write(&uv->uv_lock);
10862aa2271Ssemarie
10962aa2271Ssemarie DPRINTF("deleted %d names\n", ret);
1108b23add8Sbeck return ret;
1118b23add8Sbeck }
1128b23add8Sbeck
113a239dbafSanton int
unveil_add_name_unlocked(struct unveil * uv,char * name,u_char flags)1147d77064cSguenther unveil_add_name_unlocked(struct unveil *uv, char *name, u_char flags)
1158b23add8Sbeck {
1168b23add8Sbeck struct unvname *unvn;
1178b23add8Sbeck
1188b23add8Sbeck unvn = unvname_new(name, strlen(name) + 1, flags);
119a239dbafSanton if (RBT_INSERT(unvname_rbt, &uv->uv_names, unvn) != NULL) {
120a239dbafSanton /* Name already present. */
121a239dbafSanton unvname_delete(unvn);
122a239dbafSanton return 0;
123a239dbafSanton }
12462aa2271Ssemarie
12562aa2271Ssemarie DPRINTF("added name %s underneath vnode %p\n", name, uv->uv_vp);
126a239dbafSanton return 1;
1278b23add8Sbeck }
1288b23add8Sbeck
129a239dbafSanton int
unveil_add_name(struct unveil * uv,char * name,u_char flags)1307d77064cSguenther unveil_add_name(struct unveil *uv, char *name, u_char flags)
1317d77064cSguenther {
132a239dbafSanton int ret;
133a239dbafSanton
1347d77064cSguenther rw_enter_write(&uv->uv_lock);
135a239dbafSanton ret = unveil_add_name_unlocked(uv, name, flags);
1367d77064cSguenther rw_exit_write(&uv->uv_lock);
137a239dbafSanton return ret;
1387d77064cSguenther }
1397d77064cSguenther
1408b23add8Sbeck struct unvname *
unveil_namelookup(struct unveil * uv,char * name)1418b23add8Sbeck unveil_namelookup(struct unveil *uv, char *name)
1428b23add8Sbeck {
1438b23add8Sbeck struct unvname n, *ret = NULL;
1448b23add8Sbeck
1458b23add8Sbeck rw_enter_read(&uv->uv_lock);
1468b23add8Sbeck
14762aa2271Ssemarie DPRINTF("%s: looking up name %s (%p) in vnode %p\n",
14862aa2271Ssemarie __func__, name, name, uv->uv_vp);
1498b23add8Sbeck
1508b23add8Sbeck KASSERT(uv->uv_vp != NULL);
1518b23add8Sbeck
1528b23add8Sbeck n.un_name = name;
1538b23add8Sbeck n.un_namesize = strlen(name) + 1;
1548b23add8Sbeck
1558b23add8Sbeck ret = RBT_FIND(unvname_rbt, &uv->uv_names, &n);
1568b23add8Sbeck
1578b23add8Sbeck rw_exit_read(&uv->uv_lock);
1588b23add8Sbeck
15962aa2271Ssemarie DPRINTF("%s: %s name %s in vnode %p\n", __func__,
16062aa2271Ssemarie (ret == NULL) ? "no match for" : "matched",
1618b23add8Sbeck name, uv->uv_vp);
1628b23add8Sbeck return ret;
1638b23add8Sbeck }
1648b23add8Sbeck
1658b23add8Sbeck void
unveil_destroy(struct process * ps)1668b23add8Sbeck unveil_destroy(struct process *ps)
1678b23add8Sbeck {
1688b23add8Sbeck size_t i;
1698b23add8Sbeck
1708b23add8Sbeck for (i = 0; ps->ps_uvpaths != NULL && i < ps->ps_uvvcount; i++) {
1718b23add8Sbeck struct unveil *uv = ps->ps_uvpaths + i;
1728b23add8Sbeck
1738b23add8Sbeck struct vnode *vp = uv->uv_vp;
1748b23add8Sbeck /* skip any vnodes zapped by unveil_removevnode */
1758b23add8Sbeck if (vp != NULL) {
1768b23add8Sbeck vp->v_uvcount--;
17762aa2271Ssemarie
17862aa2271Ssemarie DPRINTF("unveil: %s(%d): removing vnode %p uvcount %d "
1798b23add8Sbeck "in position %ld\n",
1808b23add8Sbeck ps->ps_comm, ps->ps_pid, vp, vp->v_uvcount, i);
1818b23add8Sbeck vrele(vp);
1828b23add8Sbeck }
1838b23add8Sbeck ps->ps_uvncount -= unveil_delete_names(uv);
1848b23add8Sbeck uv->uv_vp = NULL;
1858b23add8Sbeck uv->uv_flags = 0;
1868b23add8Sbeck }
1878b23add8Sbeck
1888b23add8Sbeck KASSERT(ps->ps_uvncount == 0);
1898b23add8Sbeck free(ps->ps_uvpaths, M_PROC, UNVEIL_MAX_VNODES *
1908b23add8Sbeck sizeof(struct unveil));
1918b23add8Sbeck ps->ps_uvvcount = 0;
1928b23add8Sbeck ps->ps_uvpaths = NULL;
1938b23add8Sbeck }
1948b23add8Sbeck
195fe520198Sbeck void
unveil_copy(struct process * parent,struct process * child)196fe520198Sbeck unveil_copy(struct process *parent, struct process *child)
1978b23add8Sbeck {
1988b23add8Sbeck size_t i;
1998b23add8Sbeck
2009b678605Sclaudio child->ps_uvdone = parent->ps_uvdone;
201fe520198Sbeck if (parent->ps_uvvcount == 0)
202fe520198Sbeck return;
203fe520198Sbeck
204585157b0Sbeck child->ps_uvpaths = mallocarray(UNVEIL_MAX_VNODES,
205585157b0Sbeck sizeof(struct unveil), M_PROC, M_WAITOK|M_ZERO);
2068b23add8Sbeck
207fe520198Sbeck child->ps_uvncount = 0;
208fe520198Sbeck for (i = 0; parent->ps_uvpaths != NULL && i < parent->ps_uvvcount;
209fe520198Sbeck i++) {
210fe520198Sbeck struct unveil *from = parent->ps_uvpaths + i;
211fe520198Sbeck struct unveil *to = child->ps_uvpaths + i;
2128b23add8Sbeck struct unvname *unvn, *next;
2138b23add8Sbeck
214fe520198Sbeck to->uv_vp = from->uv_vp;
215fe520198Sbeck if (to->uv_vp != NULL) {
216fe520198Sbeck vref(to->uv_vp);
217fe520198Sbeck to->uv_vp->v_uvcount++;
2188b23add8Sbeck }
219fe520198Sbeck rw_init(&to->uv_lock, "unveil");
220fe520198Sbeck RBT_INIT(unvname_rbt, &to->uv_names);
221fe520198Sbeck rw_enter_read(&from->uv_lock);
222fe520198Sbeck RBT_FOREACH_SAFE(unvn, unvname_rbt, &from->uv_names, next) {
223a239dbafSanton if (unveil_add_name_unlocked(&child->ps_uvpaths[i],
224a239dbafSanton unvn->un_name, unvn->un_flags))
225fe520198Sbeck child->ps_uvncount++;
2268b23add8Sbeck }
227fe520198Sbeck rw_exit_read(&from->uv_lock);
228fe520198Sbeck to->uv_flags = from->uv_flags;
229585157b0Sbeck to->uv_cover = from->uv_cover;
2308b23add8Sbeck }
231fe520198Sbeck child->ps_uvvcount = parent->ps_uvvcount;
2328b23add8Sbeck }
2338b23add8Sbeck
234585157b0Sbeck /*
235585157b0Sbeck * Walk up from vnode dp, until we find a matching unveil, or the root vnode
23610478431Sclaudio * returns -1 if no unveil to be found above dp or if dp is the root vnode.
237585157b0Sbeck */
238585157b0Sbeck ssize_t
unveil_find_cover(struct vnode * dp,struct proc * p)2392369e5b2Sbeck unveil_find_cover(struct vnode *dp, struct proc *p)
240585157b0Sbeck {
2412369e5b2Sbeck struct vnode *vp = NULL, *parent = NULL, *root;
242585157b0Sbeck ssize_t ret = -1;
243585157b0Sbeck int error;
244585157b0Sbeck
2452369e5b2Sbeck /* use the correct root to stop at, chrooted or not.. */
2462369e5b2Sbeck root = p->p_fd->fd_rdir ? p->p_fd->fd_rdir : rootvnode;
247585157b0Sbeck vp = dp;
248585157b0Sbeck
24910478431Sclaudio while (vp != root) {
250585157b0Sbeck struct componentname cn = {
251585157b0Sbeck .cn_nameiop = LOOKUP,
252585157b0Sbeck .cn_flags = ISLASTCN | ISDOTDOT | RDONLY,
253585157b0Sbeck .cn_proc = p,
254585157b0Sbeck .cn_cred = p->p_ucred,
255585157b0Sbeck .cn_pnbuf = NULL,
256585157b0Sbeck .cn_nameptr = "..",
257585157b0Sbeck .cn_namelen = 2,
258585157b0Sbeck .cn_consume = 0
259585157b0Sbeck };
2602369e5b2Sbeck
2612369e5b2Sbeck /*
2621939e486Sbeck * If we are at the root of a filesystem, and we are
2631939e486Sbeck * still mounted somewhere, take the .. in the above
2641939e486Sbeck * filesystem.
2652369e5b2Sbeck */
2661939e486Sbeck if (vp != root && (vp->v_flag & VROOT)) {
2671939e486Sbeck if (vp->v_mount == NULL)
2681939e486Sbeck return -1;
2691939e486Sbeck vp = vp->v_mount->mnt_vnodecovered ?
2702369e5b2Sbeck vp->v_mount->mnt_vnodecovered : vp;
2711939e486Sbeck }
2722369e5b2Sbeck
273585157b0Sbeck if (vget(vp, LK_EXCLUSIVE|LK_RETRY) != 0)
274585157b0Sbeck return -1;
2752369e5b2Sbeck /* Get parent vnode of vp using lookup of '..' */
276585157b0Sbeck /* This returns with vp unlocked but ref'ed*/
277585157b0Sbeck error = VOP_LOOKUP(vp, &parent, &cn);
278585157b0Sbeck if (error) {
279585157b0Sbeck if (!(cn.cn_flags & PDIRUNLOCK))
280585157b0Sbeck vput(vp);
281585157b0Sbeck else {
282585157b0Sbeck /*
283585157b0Sbeck * This corner case should not happen because
284585157b0Sbeck * we have not set LOCKPARENT in the flags
285585157b0Sbeck */
28662aa2271Ssemarie DPRINTF("vnode %p PDIRUNLOCK on error\n", vp);
287585157b0Sbeck vrele(vp);
288585157b0Sbeck }
289585157b0Sbeck break;
290585157b0Sbeck }
291585157b0Sbeck
292585157b0Sbeck vrele(vp);
2937ac2b76bSguenther (void) unveil_lookup(parent, p->p_p, &ret);
294585157b0Sbeck vput(parent);
295585157b0Sbeck
2962369e5b2Sbeck if (ret >= 0)
2972369e5b2Sbeck break;
2982369e5b2Sbeck
299585157b0Sbeck if (vp == parent) {
300585157b0Sbeck ret = -1;
301585157b0Sbeck break;
302585157b0Sbeck }
303585157b0Sbeck vp = parent;
304585157b0Sbeck parent = NULL;
30510478431Sclaudio }
306585157b0Sbeck return ret;
307585157b0Sbeck }
308585157b0Sbeck
309585157b0Sbeck
3108b23add8Sbeck struct unveil *
unveil_lookup(struct vnode * vp,struct process * pr,ssize_t * position)3117ac2b76bSguenther unveil_lookup(struct vnode *vp, struct process *pr, ssize_t *position)
3128b23add8Sbeck {
3138b23add8Sbeck struct unveil *uv = pr->ps_uvpaths;
314f5e54ad9Sclaudio ssize_t i;
315f5e54ad9Sclaudio
316585157b0Sbeck if (position != NULL)
317585157b0Sbeck *position = -1;
3188b23add8Sbeck
3198b23add8Sbeck if (vp->v_uvcount == 0)
3208b23add8Sbeck return NULL;
3218b23add8Sbeck
322f5e54ad9Sclaudio for (i = 0; i < pr->ps_uvvcount; i++) {
323f5e54ad9Sclaudio if (vp == uv[i].uv_vp) {
324f5e54ad9Sclaudio KASSERT(uv[i].uv_vp->v_uvcount > 0);
325f5e54ad9Sclaudio KASSERT(uv[i].uv_vp->v_usecount > 0);
326585157b0Sbeck if (position != NULL)
327f5e54ad9Sclaudio *position = i;
328f5e54ad9Sclaudio return &uv[i];
3298b23add8Sbeck }
3308b23add8Sbeck }
3318b23add8Sbeck return NULL;
3328b23add8Sbeck }
3338b23add8Sbeck
3348b23add8Sbeck int
unveil_parsepermissions(const char * permissions,u_char * perms)335e1a6e226Sbeck unveil_parsepermissions(const char *permissions, u_char *perms)
3368b23add8Sbeck {
3378b23add8Sbeck size_t i = 0;
3388b23add8Sbeck char c;
3398b23add8Sbeck
3402abaea44Sclaudio *perms = UNVEIL_USERSET;
34104b561efSderaadt while ((c = permissions[i++]) != '\0') {
3428b23add8Sbeck switch (c) {
3438b23add8Sbeck case 'r':
344e1a6e226Sbeck *perms |= UNVEIL_READ;
3458b23add8Sbeck break;
3468b23add8Sbeck case 'w':
347e1a6e226Sbeck *perms |= UNVEIL_WRITE;
3488b23add8Sbeck break;
3498b23add8Sbeck case 'x':
350e1a6e226Sbeck *perms |= UNVEIL_EXEC;
3518b23add8Sbeck break;
3528b23add8Sbeck case 'c':
353e1a6e226Sbeck *perms |= UNVEIL_CREATE;
3548b23add8Sbeck break;
3558b23add8Sbeck default:
3568b23add8Sbeck return -1;
3578b23add8Sbeck }
3588b23add8Sbeck }
3598b23add8Sbeck return 0;
3608b23add8Sbeck }
3618b23add8Sbeck
3628b23add8Sbeck int
unveil_setflags(u_char * flags,u_char nflags)363e1a6e226Sbeck unveil_setflags(u_char *flags, u_char nflags)
3648b23add8Sbeck {
3658b23add8Sbeck #if 0
3668b23add8Sbeck if (((~(*flags)) & nflags) != 0) {
36762aa2271Ssemarie DPRINTF("Flags escalation %llX -> %llX\n", *flags, nflags);
3688b23add8Sbeck return 1;
3698b23add8Sbeck }
3708b23add8Sbeck #endif
3718b23add8Sbeck *flags = nflags;
3728b23add8Sbeck return 1;
3738b23add8Sbeck }
3748b23add8Sbeck
3758b23add8Sbeck struct unveil *
unveil_add_vnode(struct proc * p,struct vnode * vp)3767ac2b76bSguenther unveil_add_vnode(struct proc *p, struct vnode *vp)
3778b23add8Sbeck {
3787ac2b76bSguenther struct process *pr = p->p_p;
3798b23add8Sbeck struct unveil *uv = NULL;
3801bef2b77Sclaudio ssize_t i;
381a4b48aa3Sbeck
382a4b48aa3Sbeck KASSERT(pr->ps_uvvcount < UNVEIL_MAX_VNODES);
383a4b48aa3Sbeck
3841bef2b77Sclaudio uv = &pr->ps_uvpaths[pr->ps_uvvcount++];
3858b23add8Sbeck rw_init(&uv->uv_lock, "unveil");
3868b23add8Sbeck RBT_INIT(unvname_rbt, &uv->uv_names);
3878b23add8Sbeck uv->uv_vp = vp;
3885cab2156Sclaudio uv->uv_flags = 0;
389585157b0Sbeck
390585157b0Sbeck /* find out what we are covered by */
3917ac2b76bSguenther uv->uv_cover = unveil_find_cover(vp, p);
392585157b0Sbeck
393585157b0Sbeck /*
394585157b0Sbeck * Find anyone covered by what we are covered by
395585157b0Sbeck * and re-check what covers them (we could have
396585157b0Sbeck * interposed a cover)
397585157b0Sbeck */
3981bef2b77Sclaudio for (i = 0; i < pr->ps_uvvcount - 1; i++) {
399585157b0Sbeck if (pr->ps_uvpaths[i].uv_cover == uv->uv_cover)
4001bef2b77Sclaudio pr->ps_uvpaths[i].uv_cover =
4011bef2b77Sclaudio unveil_find_cover(pr->ps_uvpaths[i].uv_vp, p);
402585157b0Sbeck }
403585157b0Sbeck
4048b23add8Sbeck return (uv);
4058b23add8Sbeck }
4068b23add8Sbeck
4078b23add8Sbeck int
unveil_add(struct proc * p,struct nameidata * ndp,const char * permissions)40804b561efSderaadt unveil_add(struct proc *p, struct nameidata *ndp, const char *permissions)
4098b23add8Sbeck {
4108b23add8Sbeck struct process *pr = p->p_p;
4118b23add8Sbeck struct vnode *vp;
4128b23add8Sbeck struct unveil *uv;
4138b23add8Sbeck int directory_add;
4148b23add8Sbeck int ret = EINVAL;
415e1a6e226Sbeck u_char flags;
4168b23add8Sbeck
4178b23add8Sbeck KASSERT(ISSET(ndp->ni_cnd.cn_flags, HASBUF)); /* must have SAVENAME */
4188b23add8Sbeck
41904b561efSderaadt if (unveil_parsepermissions(permissions, &flags) == -1)
4208b23add8Sbeck goto done;
4218b23add8Sbeck
4228b23add8Sbeck if (pr->ps_uvpaths == NULL) {
4238b23add8Sbeck pr->ps_uvpaths = mallocarray(UNVEIL_MAX_VNODES,
4248b23add8Sbeck sizeof(struct unveil), M_PROC, M_WAITOK|M_ZERO);
4258b23add8Sbeck }
4268b23add8Sbeck
4270fdd9a14Sclaudio if (pr->ps_uvvcount >= UNVEIL_MAX_VNODES ||
4288b23add8Sbeck pr->ps_uvncount >= UNVEIL_MAX_NAMES) {
4298b23add8Sbeck ret = E2BIG;
4308b23add8Sbeck goto done;
4318b23add8Sbeck }
4328b23add8Sbeck
4338b23add8Sbeck /* Are we a directory? or something else */
4348b23add8Sbeck directory_add = ndp->ni_vp != NULL && ndp->ni_vp->v_type == VDIR;
4358b23add8Sbeck
4368b23add8Sbeck if (directory_add)
4378b23add8Sbeck vp = ndp->ni_vp;
4388b23add8Sbeck else
4398b23add8Sbeck vp = ndp->ni_dvp;
4408b23add8Sbeck
4418b23add8Sbeck KASSERT(vp->v_type == VDIR);
4428b23add8Sbeck vref(vp);
4438b23add8Sbeck vp->v_uvcount++;
4447ac2b76bSguenther if ((uv = unveil_lookup(vp, pr, NULL)) != NULL) {
4458b23add8Sbeck /*
4468b23add8Sbeck * We already have unveiled this directory
4478b23add8Sbeck * vnode
4488b23add8Sbeck */
4498b23add8Sbeck vp->v_uvcount--;
4508b23add8Sbeck vrele(vp);
4518b23add8Sbeck
4528b23add8Sbeck /*
4538b23add8Sbeck * If we are adding a directory which was already
4548b23add8Sbeck * unveiled containing only specific terminals,
4558b23add8Sbeck * unrestrict it.
4568b23add8Sbeck */
4578b23add8Sbeck if (directory_add) {
45862aa2271Ssemarie DPRINTF("unveil: %s(%d): updating directory vnode %p"
4598b23add8Sbeck " to unrestricted uvcount %d\n",
4608b23add8Sbeck pr->ps_comm, pr->ps_pid, vp, vp->v_uvcount);
46162aa2271Ssemarie
4628b23add8Sbeck if (!unveil_setflags(&uv->uv_flags, flags))
4638b23add8Sbeck ret = EPERM;
4648b23add8Sbeck else
4658b23add8Sbeck ret = 0;
4668b23add8Sbeck goto done;
4678b23add8Sbeck }
4688b23add8Sbeck
4698b23add8Sbeck /*
4708b23add8Sbeck * If we are adding a terminal that is already unveiled, just
4718b23add8Sbeck * replace the flags and we are done
4728b23add8Sbeck */
4738b23add8Sbeck if (!directory_add) {
4748b23add8Sbeck struct unvname *tname;
4758b23add8Sbeck if ((tname = unveil_namelookup(uv,
4768b23add8Sbeck ndp->ni_cnd.cn_nameptr)) != NULL) {
47762aa2271Ssemarie DPRINTF("unveil: %s(%d): changing flags for %s"
4788b23add8Sbeck "in vnode %p, uvcount %d\n",
4798b23add8Sbeck pr->ps_comm, pr->ps_pid, tname->un_name, vp,
4808b23add8Sbeck vp->v_uvcount);
48162aa2271Ssemarie
4828b23add8Sbeck if (!unveil_setflags(&tname->un_flags, flags))
4838b23add8Sbeck ret = EPERM;
4848b23add8Sbeck else
4858b23add8Sbeck ret = 0;
4868b23add8Sbeck goto done;
4878b23add8Sbeck }
4888b23add8Sbeck }
4898b23add8Sbeck
4908b23add8Sbeck } else {
4918b23add8Sbeck /*
4928b23add8Sbeck * New unveil involving this directory vnode.
4938b23add8Sbeck */
4947ac2b76bSguenther uv = unveil_add_vnode(p, vp);
4958b23add8Sbeck }
4968b23add8Sbeck
4978b23add8Sbeck /*
4988b23add8Sbeck * At this stage with have a unveil in uv with a vnode for a
4998b23add8Sbeck * directory. If the component we are adding is a directory,
5008b23add8Sbeck * we are done. Otherwise, we add the component name the name
5018b23add8Sbeck * list in uv.
5028b23add8Sbeck */
5038b23add8Sbeck
5048b23add8Sbeck if (directory_add) {
5058b23add8Sbeck uv->uv_flags = flags;
5068b23add8Sbeck ret = 0;
50762aa2271Ssemarie
50862aa2271Ssemarie DPRINTF("unveil: %s(%d): added unrestricted directory vnode %p"
5098b23add8Sbeck ", uvcount %d\n",
5108b23add8Sbeck pr->ps_comm, pr->ps_pid, vp, vp->v_uvcount);
5118b23add8Sbeck goto done;
5128b23add8Sbeck }
5138b23add8Sbeck
514a239dbafSanton if (unveil_add_name(uv, ndp->ni_cnd.cn_nameptr, flags))
5158b23add8Sbeck pr->ps_uvncount++;
5168b23add8Sbeck ret = 0;
5178b23add8Sbeck
51862aa2271Ssemarie DPRINTF("unveil: %s(%d): added name %s beneath %s vnode %p,"
5198b23add8Sbeck " uvcount %d\n",
5208b23add8Sbeck pr->ps_comm, pr->ps_pid, ndp->ni_cnd.cn_nameptr,
5218b23add8Sbeck uv->uv_flags ? "unrestricted" : "restricted",
5228b23add8Sbeck vp, vp->v_uvcount);
5238b23add8Sbeck
5248b23add8Sbeck done:
5258b23add8Sbeck return ret;
5268b23add8Sbeck }
5278b23add8Sbeck
5288b23add8Sbeck /*
5298b23add8Sbeck * XXX this will probably change.
530678831beSjsg * XXX collapse down later once debug surely unneeded
5318b23add8Sbeck */
5328b23add8Sbeck int
unveil_flagmatch(struct nameidata * ni,u_char flags)533e1a6e226Sbeck unveil_flagmatch(struct nameidata *ni, u_char flags)
5348b23add8Sbeck {
5358b23add8Sbeck if (flags == 0) {
53662aa2271Ssemarie DPRINTF("All operations forbidden for 0 flags\n");
5378b23add8Sbeck return 0;
5388b23add8Sbeck }
539e1a6e226Sbeck if (ni->ni_unveil & UNVEIL_READ) {
540e1a6e226Sbeck if ((flags & UNVEIL_READ) == 0) {
54162aa2271Ssemarie DPRINTF("unveil lacks UNVEIL_READ\n");
5428b23add8Sbeck return 0;
5438b23add8Sbeck }
5448b23add8Sbeck }
545e1a6e226Sbeck if (ni->ni_unveil & UNVEIL_WRITE) {
546e1a6e226Sbeck if ((flags & UNVEIL_WRITE) == 0) {
54762aa2271Ssemarie DPRINTF("unveil lacks UNVEIL_WRITE\n");
5488b23add8Sbeck return 0;
5498b23add8Sbeck }
5508b23add8Sbeck }
551e1a6e226Sbeck if (ni->ni_unveil & UNVEIL_EXEC) {
552e1a6e226Sbeck if ((flags & UNVEIL_EXEC) == 0) {
55362aa2271Ssemarie DPRINTF("unveil lacks UNVEIL_EXEC\n");
5548b23add8Sbeck return 0;
5558b23add8Sbeck }
5568b23add8Sbeck }
557e1a6e226Sbeck if (ni->ni_unveil & UNVEIL_CREATE) {
558e1a6e226Sbeck if ((flags & UNVEIL_CREATE) == 0) {
55962aa2271Ssemarie DPRINTF("unveil lacks UNVEIL_CREATE\n");
5608b23add8Sbeck return 0;
5618b23add8Sbeck }
5628b23add8Sbeck }
5638b23add8Sbeck return 1;
5648b23add8Sbeck }
5658b23add8Sbeck
56610478431Sclaudio /*
56710478431Sclaudio * When traversing up towards the root figure out the proper unveil for
56810478431Sclaudio * the parent directory.
56910478431Sclaudio */
570585157b0Sbeck struct unveil *
unveil_covered(struct unveil * uv,struct vnode * dvp,struct proc * p)57110478431Sclaudio unveil_covered(struct unveil *uv, struct vnode *dvp, struct proc *p)
57210478431Sclaudio {
573585157b0Sbeck if (uv && uv->uv_vp == dvp) {
57410478431Sclaudio /* if at the root, chrooted or not, return the current uv */
57510478431Sclaudio if (dvp == (p->p_fd->fd_rdir ? p->p_fd->fd_rdir : rootvnode))
57610478431Sclaudio return uv;
577585157b0Sbeck if (uv->uv_cover >=0) {
57810478431Sclaudio KASSERT(uv->uv_cover < p->p_p->ps_uvvcount);
57910478431Sclaudio return &p->p_p->ps_uvpaths[uv->uv_cover];
580585157b0Sbeck }
581585157b0Sbeck return NULL;
582585157b0Sbeck }
583585157b0Sbeck return uv;
584585157b0Sbeck }
585585157b0Sbeck
586585157b0Sbeck
587585157b0Sbeck /*
58893c275fdSbeck * Start a relative path lookup. Ensure we find whatever unveil covered
58993c275fdSbeck * where we start from, either by having a saved current working directory
59093c275fdSbeck * unveil, or by walking up and finding a cover the hard way if we are
59193c275fdSbeck * doing a non AT_FDCWD relative lookup. Caller passes a NULL dp
59293c275fdSbeck * if we are using AT_FDCWD.
593585157b0Sbeck */
594585157b0Sbeck void
unveil_start_relative(struct proc * p,struct nameidata * ni,struct vnode * dp)59593c275fdSbeck unveil_start_relative(struct proc *p, struct nameidata *ni, struct vnode *dp)
596585157b0Sbeck {
5977ac2b76bSguenther struct process *pr = p->p_p;
59893c275fdSbeck struct unveil *uv = NULL;
599dd490a11Sclaudio ssize_t uvi;
600585157b0Sbeck
601efacc20bSsemarie if (pr->ps_uvpaths == NULL)
602efacc20bSsemarie return;
603efacc20bSsemarie
6047ac2b76bSguenther uv = unveil_lookup(dp, pr, NULL);
60593c275fdSbeck if (uv == NULL) {
60693c275fdSbeck uvi = unveil_find_cover(dp, p);
60793c275fdSbeck if (uvi >= 0) {
6087ac2b76bSguenther KASSERT(uvi < pr->ps_uvvcount);
6097ac2b76bSguenther uv = &pr->ps_uvpaths[uvi];
61093c275fdSbeck }
61193c275fdSbeck }
61293c275fdSbeck
61393c275fdSbeck /*
614dbaaa455Sclaudio * Store this match for later use. Flags are checked at the end.
615585157b0Sbeck */
616dbaaa455Sclaudio if (uv) {
61762aa2271Ssemarie DPRINTF("unveil: %s(%d): relative unveil at %p matches",
6187ac2b76bSguenther pr->ps_comm, pr->ps_pid, uv);
61962aa2271Ssemarie
620585157b0Sbeck ni->ni_unveil_match = uv;
621585157b0Sbeck }
622585157b0Sbeck }
623585157b0Sbeck
6248b23add8Sbeck /*
6258b23add8Sbeck * unveil checking - for component directories in a namei lookup.
6268b23add8Sbeck */
6278b23add8Sbeck void
unveil_check_component(struct proc * p,struct nameidata * ni,struct vnode * dp)6288b23add8Sbeck unveil_check_component(struct proc *p, struct nameidata *ni, struct vnode *dp)
6298b23add8Sbeck {
6307ac2b76bSguenther struct process *pr = p->p_p;
6318b23add8Sbeck struct unveil *uv = NULL;
6328b23add8Sbeck
6330fdd9a14Sclaudio if (ni->ni_pledge == PLEDGE_UNVEIL || pr->ps_uvpaths == NULL)
634efacc20bSsemarie return;
635efacc20bSsemarie if (ni->ni_cnd.cn_flags & BYPASSUNVEIL)
636efacc20bSsemarie return;
637efacc20bSsemarie
638585157b0Sbeck if (ni->ni_cnd.cn_flags & ISDOTDOT) {
639585157b0Sbeck /*
640585157b0Sbeck * adjust unveil match as necessary
641585157b0Sbeck */
64210478431Sclaudio uv = unveil_covered(ni->ni_unveil_match, dp, p);
643efacc20bSsemarie
6441939e486Sbeck /* clear the match when we DOTDOT above it */
645dbaaa455Sclaudio if (ni->ni_unveil_match && ni->ni_unveil_match->uv_vp == dp)
6461939e486Sbeck ni->ni_unveil_match = NULL;
647efacc20bSsemarie } else
6487ac2b76bSguenther uv = unveil_lookup(dp, pr, NULL);
649585157b0Sbeck
650585157b0Sbeck if (uv != NULL) {
651dbaaa455Sclaudio /* update match */
6528b23add8Sbeck ni->ni_unveil_match = uv;
65362aa2271Ssemarie
65462aa2271Ssemarie DPRINTF("unveil: %s(%d): component directory match for "
655dbaaa455Sclaudio "vnode %p\n", pr->ps_comm, pr->ps_pid, dp);
6568b23add8Sbeck }
6578b23add8Sbeck }
6588b23add8Sbeck
6598b23add8Sbeck /*
6608b23add8Sbeck * unveil checking - only done after namei lookup has succeeded on
661048c2096Sjasper * the last component of a namei lookup.
6628b23add8Sbeck */
6638b23add8Sbeck int
unveil_check_final(struct proc * p,struct nameidata * ni)6648b23add8Sbeck unveil_check_final(struct proc *p, struct nameidata *ni)
6658b23add8Sbeck {
6667ac2b76bSguenther struct process *pr = p->p_p;
6677ed2ac75Sclaudio struct unveil *uv = NULL, *nuv;
6688b23add8Sbeck struct unvname *tname = NULL;
6698b23add8Sbeck
6707ac2b76bSguenther if (ni->ni_pledge == PLEDGE_UNVEIL || pr->ps_uvpaths == NULL)
6718b23add8Sbeck return (0);
6728b23add8Sbeck
6738b23add8Sbeck if (ni->ni_cnd.cn_flags & BYPASSUNVEIL) {
67462aa2271Ssemarie DPRINTF("unveil: %s(%d): BYPASSUNVEIL.\n",
6757ac2b76bSguenther pr->ps_comm, pr->ps_pid);
67662aa2271Ssemarie
6778b23add8Sbeck return (0);
6788b23add8Sbeck }
679dbaaa455Sclaudio
6808b23add8Sbeck if (ni->ni_vp != NULL && ni->ni_vp->v_type == VDIR) {
681585157b0Sbeck /* We are matching a directory terminal component */
6827ac2b76bSguenther uv = unveil_lookup(ni->ni_vp, pr, NULL);
6832abaea44Sclaudio if (uv == NULL || (uv->uv_flags & UNVEIL_USERSET) == 0) {
68462aa2271Ssemarie DPRINTF("unveil: %s(%d) no match for vnode %p\n",
6857ac2b76bSguenther pr->ps_comm, pr->ps_pid, ni->ni_vp);
68662aa2271Ssemarie
6872abaea44Sclaudio if (uv != NULL)
6882abaea44Sclaudio ni->ni_unveil_match = uv;
6898b23add8Sbeck goto done;
6908b23add8Sbeck }
6918b23add8Sbeck if (!unveil_flagmatch(ni, uv->uv_flags)) {
69262aa2271Ssemarie DPRINTF("unveil: %s(%d) flag mismatch for directory"
6938b23add8Sbeck " vnode %p\n",
6947ac2b76bSguenther pr->ps_comm, pr->ps_pid, ni->ni_vp);
69562aa2271Ssemarie
6967ac2b76bSguenther pr->ps_acflag |= AUNVEIL;
6972abaea44Sclaudio if (uv->uv_flags & UNVEIL_MASK)
6988b23add8Sbeck return EACCES;
699f3784bceSbeck else
700f3784bceSbeck return ENOENT;
701f3784bceSbeck
7028b23add8Sbeck }
703dbaaa455Sclaudio /* directory and flags match, success */
70462aa2271Ssemarie DPRINTF("unveil: %s(%d): matched directory \"%s\" at vnode %p\n",
705dbaaa455Sclaudio pr->ps_comm, pr->ps_pid, ni->ni_cnd.cn_nameptr,
706dbaaa455Sclaudio uv->uv_vp);
70762aa2271Ssemarie
708dbaaa455Sclaudio return (0);
709585157b0Sbeck }
710dbaaa455Sclaudio
711585157b0Sbeck /* Otherwise, we are matching a non-terminal component */
7127ac2b76bSguenther uv = unveil_lookup(ni->ni_dvp, pr, NULL);
7138b23add8Sbeck if (uv == NULL) {
71462aa2271Ssemarie DPRINTF("unveil: %s(%d) no match for directory vnode %p\n",
7157ac2b76bSguenther pr->ps_comm, pr->ps_pid, ni->ni_dvp);
71662aa2271Ssemarie
7178b23add8Sbeck goto done;
7188b23add8Sbeck }
719dbaaa455Sclaudio if ((tname = unveil_namelookup(uv, ni->ni_cnd.cn_nameptr)) == NULL) {
72062aa2271Ssemarie DPRINTF("unveil: %s(%d) no match for terminal '%s' in "
7218b23add8Sbeck "directory vnode %p\n",
7227ac2b76bSguenther pr->ps_comm, pr->ps_pid,
7238b23add8Sbeck ni->ni_cnd.cn_nameptr, ni->ni_dvp);
72462aa2271Ssemarie
725585157b0Sbeck /* no specific name, so check unveil directory flags */
726585157b0Sbeck if (!unveil_flagmatch(ni, uv->uv_flags)) {
72762aa2271Ssemarie DPRINTF("unveil: %s(%d) terminal "
728585157b0Sbeck "'%s' flags mismatch in directory "
729585157b0Sbeck "vnode %p\n",
7307ac2b76bSguenther pr->ps_comm, pr->ps_pid,
731585157b0Sbeck ni->ni_cnd.cn_nameptr, ni->ni_dvp);
73262aa2271Ssemarie
733585157b0Sbeck /*
7341939e486Sbeck * If dir has user set restrictions fail with
7352abaea44Sclaudio * EACCES or ENOENT. Otherwise, use any covering
7362abaea44Sclaudio * match that we found above this dir.
737585157b0Sbeck */
738fa19a603Sbluhm if (uv->uv_flags & UNVEIL_USERSET) {
7397ac2b76bSguenther pr->ps_acflag |= AUNVEIL;
7402abaea44Sclaudio if (uv->uv_flags & UNVEIL_MASK)
741585157b0Sbeck return EACCES;
7422abaea44Sclaudio else
7432abaea44Sclaudio return ENOENT;
744fa19a603Sbluhm }
745dbaaa455Sclaudio /* start backtrack from this node */
746585157b0Sbeck ni->ni_unveil_match = uv;
7478b23add8Sbeck goto done;
7488b23add8Sbeck }
749dbaaa455Sclaudio /* directory flags match, success */
75062aa2271Ssemarie DPRINTF("unveil: %s(%d): matched \"%s\" underneath vnode %p\n",
751dbaaa455Sclaudio pr->ps_comm, pr->ps_pid, ni->ni_cnd.cn_nameptr,
752dbaaa455Sclaudio uv->uv_vp);
75362aa2271Ssemarie
754dbaaa455Sclaudio return (0);
755dbaaa455Sclaudio }
7568b23add8Sbeck if (!unveil_flagmatch(ni, tname->un_flags)) {
757585157b0Sbeck /* do flags match for matched name */
75862aa2271Ssemarie DPRINTF("unveil: %s(%d) flag mismatch for terminal '%s'\n",
7597ac2b76bSguenther pr->ps_comm, pr->ps_pid, tname->un_name);
76062aa2271Ssemarie
7617ac2b76bSguenther pr->ps_acflag |= AUNVEIL;
7628b23add8Sbeck return EACCES;
7638b23add8Sbeck }
764dbaaa455Sclaudio /* name and flags match. success */
76562aa2271Ssemarie DPRINTF("unveil: %s(%d) matched terminal '%s'\n",
766dbaaa455Sclaudio pr->ps_comm, pr->ps_pid, tname->un_name);
76762aa2271Ssemarie
768dbaaa455Sclaudio return (0);
769585157b0Sbeck
7708b23add8Sbeck done:
771dbaaa455Sclaudio /*
772dbaaa455Sclaudio * last component did not match, check previous matches if
773dbaaa455Sclaudio * access is allowed or not.
774dbaaa455Sclaudio */
775dbaaa455Sclaudio for (uv = ni->ni_unveil_match; uv != NULL; uv = nuv) {
776dbaaa455Sclaudio if (unveil_flagmatch(ni, uv->uv_flags)) {
77762aa2271Ssemarie DPRINTF("unveil: %s(%d): matched \"%s\" underneath/at "
778dbaaa455Sclaudio "vnode %p\n", pr->ps_comm, pr->ps_pid,
779dbaaa455Sclaudio ni->ni_cnd.cn_nameptr, uv->uv_vp);
78062aa2271Ssemarie
7818b23add8Sbeck return (0);
7828b23add8Sbeck }
783dbaaa455Sclaudio /* if node has any flags set then this is an access violation */
784dbaaa455Sclaudio if (uv->uv_flags & UNVEIL_USERSET) {
78562aa2271Ssemarie DPRINTF("unveil: %s(%d) flag mismatch for vnode %p\n",
786dbaaa455Sclaudio pr->ps_comm, pr->ps_pid, uv->uv_vp);
78762aa2271Ssemarie
7887ac2b76bSguenther pr->ps_acflag |= AUNVEIL;
7892abaea44Sclaudio if (uv->uv_flags & UNVEIL_MASK)
7901939e486Sbeck return EACCES;
7912abaea44Sclaudio else
7922abaea44Sclaudio return ENOENT;
7931939e486Sbeck }
79462aa2271Ssemarie
79562aa2271Ssemarie DPRINTF("unveil: %s(%d) check cover for vnode %p, uv_cover %zd\n",
796dbaaa455Sclaudio pr->ps_comm, pr->ps_pid, uv->uv_vp, uv->uv_cover);
79762aa2271Ssemarie
798dbaaa455Sclaudio nuv = unveil_covered(uv, uv->uv_vp, p);
799dbaaa455Sclaudio if (nuv == uv)
800dbaaa455Sclaudio break;
801dbaaa455Sclaudio }
8027ac2b76bSguenther pr->ps_acflag |= AUNVEIL;
8038b23add8Sbeck return ENOENT;
8048b23add8Sbeck }
8058b23add8Sbeck
8068b23add8Sbeck /*
8078b23add8Sbeck * Scan all active processes to see if any of them have a unveil
8088b23add8Sbeck * to this vnode. If so, NULL the vnode in their unveil list,
8098b23add8Sbeck * vrele, drop the reference, and mark their unveil list
8108b23add8Sbeck * as needing to have the hole shrunk the next time the process
8118b23add8Sbeck * uses it for lookup.
8128b23add8Sbeck */
8138b23add8Sbeck void
unveil_removevnode(struct vnode * vp)8148b23add8Sbeck unveil_removevnode(struct vnode *vp)
8158b23add8Sbeck {
8168b23add8Sbeck struct process *pr;
8178b23add8Sbeck
8188b23add8Sbeck if (vp->v_uvcount == 0)
8198b23add8Sbeck return;
820413a2998Sbeck
82162aa2271Ssemarie DPRINTF("%s: found vnode %p with count %d\n",
82262aa2271Ssemarie __func__, vp, vp->v_uvcount);
82362aa2271Ssemarie
824413a2998Sbeck vref(vp); /* make sure it is held till we are done */
825413a2998Sbeck
8268b23add8Sbeck LIST_FOREACH(pr, &allprocess, ps_list) {
8278b23add8Sbeck struct unveil * uv;
8288b47f4acSderaadt
8297ac2b76bSguenther if ((uv = unveil_lookup(vp, pr, NULL)) != NULL &&
830413a2998Sbeck uv->uv_vp != NULL) {
8318b23add8Sbeck uv->uv_vp = NULL;
8328b23add8Sbeck uv->uv_flags = 0;
83362aa2271Ssemarie
83462aa2271Ssemarie DPRINTF("%s: vnode %p now count %d\n",
83562aa2271Ssemarie __func__, vp, vp->v_uvcount);
83662aa2271Ssemarie
837413a2998Sbeck if (vp->v_uvcount > 0) {
8388b23add8Sbeck vrele(vp);
839413a2998Sbeck vp->v_uvcount--;
840413a2998Sbeck } else
841413a2998Sbeck panic("vp %p, v_uvcount of %d should be 0",
842413a2998Sbeck vp, vp->v_uvcount);
843413a2998Sbeck }
844413a2998Sbeck }
845413a2998Sbeck KASSERT(vp->v_uvcount == 0);
846413a2998Sbeck
847413a2998Sbeck vrele(vp); /* release our ref */
8488b23add8Sbeck }
849