summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authorbeck <beck@openbsd.org>2018-07-13 09:25:22 +0000
committerbeck <beck@openbsd.org>2018-07-13 09:25:22 +0000
commit8b23add8c74b86d0da67de43302cf21b97b028be (patch)
treef1c0090b4f820a76d34793eb08027661681a4d3c /sys
parentno longer interpret 0.192.168.4 in hosts(5) as 192.168.4/24 (diff)
downloadwireguard-openbsd-8b23add8c74b86d0da67de43302cf21b97b028be.tar.xz
wireguard-openbsd-8b23add8c74b86d0da67de43302cf21b97b028be.zip
Unveiling unveil(2).
This brings unveil into the tree, disabled by default - Currently this will return EPERM on all attempts to use it until we are fully certain it is ready for people to start using, but this now allows for others to do more tweaking and experimentation. Still needs to send the unveil's across forks and execs before fully enabling. Many thanks to robert@ and deraadt@ for extensive testing. ok deraadt@
Diffstat (limited to 'sys')
-rw-r--r--sys/conf/files3
-rw-r--r--sys/kern/init_sysent.c6
-rw-r--r--sys/kern/kern_exec.c10
-rw-r--r--sys/kern/kern_exit.c5
-rw-r--r--sys/kern/kern_fork.c16
-rw-r--r--sys/kern/kern_pledge.c85
-rw-r--r--sys/kern/kern_unveil.c727
-rw-r--r--sys/kern/syscalls.c4
-rw-r--r--sys/kern/syscalls.master5
-rw-r--r--sys/kern/vfs_lookup.c55
-rw-r--r--sys/kern/vfs_subr.c5
-rw-r--r--sys/kern/vfs_syscalls.c108
-rw-r--r--sys/sys/namei.h9
-rw-r--r--sys/sys/pledge.h6
-rw-r--r--sys/sys/proc.h30
-rw-r--r--sys/sys/syscall.h6
-rw-r--r--sys/sys/syscallargs.h8
-rw-r--r--sys/sys/vnode.h3
18 files changed, 1044 insertions, 47 deletions
diff --git a/sys/conf/files b/sys/conf/files
index b0bdd63777a..e0b49cc8445 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1,4 +1,4 @@
-# $OpenBSD: files,v 1.663 2018/07/10 20:30:31 claudio Exp $
+# $OpenBSD: files,v 1.664 2018/07/13 09:25:22 beck Exp $
# $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $
# @(#)files.newconf 7.5 (Berkeley) 5/10/93
@@ -683,6 +683,7 @@ file kern/kern_proc.c
file kern/kern_prot.c
file kern/kern_resource.c
file kern/kern_pledge.c
+file kern/kern_unveil.c
file kern/kern_sched.c
file kern/kern_sensors.c
file kern/kern_sig.c
diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c
index f96a42ae89b..dcb26836061 100644
--- a/sys/kern/init_sysent.c
+++ b/sys/kern/init_sysent.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: init_sysent.c,v 1.196 2018/07/05 15:31:51 mpi Exp $ */
+/* $OpenBSD: init_sysent.c,v 1.197 2018/07/13 09:25:23 beck Exp $ */
/*
* System call switch table.
@@ -260,8 +260,8 @@ struct sysent sysent[] = {
sys_sendsyslog }, /* 112 = sendsyslog */
{ 0, 0, 0,
sys_nosys }, /* 113 = unimplemented fktrace */
- { 0, 0, 0,
- sys_nosys }, /* 114 = obsolete osendmsg */
+ { 2, s(struct sys_unveil_args), 0,
+ sys_unveil }, /* 114 = unveil */
{ 0, 0, 0,
sys_nosys }, /* 115 = obsolete vtrace */
{ 0, 0, 0,
diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c
index 7ccbab6a374..98a30c4aee0 100644
--- a/sys/kern/kern_exec.c
+++ b/sys/kern/kern_exec.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kern_exec.c,v 1.198 2018/06/18 09:15:05 mpi Exp $ */
+/* $OpenBSD: kern_exec.c,v 1.199 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: kern_exec.c,v 1.75 1996/02/09 18:59:28 christos Exp $ */
/*-
@@ -64,6 +64,8 @@
#include <uvm/uvm_extern.h>
#include <machine/tcb.h>
+void unveil_destroy(struct process *ps);
+
const struct kmem_va_mode kv_exec = {
.kv_wait = 1,
.kv_map = &exec_map
@@ -532,6 +534,12 @@ sys_execve(struct proc *p, void *v, register_t *retval)
} else {
atomic_clearbits_int(&pr->ps_flags, PS_PLEDGE);
pr->ps_pledge = 0;
+ /* XXX XXX XXX XXX */
+ /* Clear our unveil paths out so the child
+ * starts afresh
+ */
+ unveil_destroy(pr);
+ pr->ps_uvdone = 0;
}
/*
diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c
index dfd4e38078f..6bbf5fb2258 100644
--- a/sys/kern/kern_exit.c
+++ b/sys/kern/kern_exit.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kern_exit.c,v 1.164 2018/02/10 10:32:51 mpi Exp $ */
+/* $OpenBSD: kern_exit.c,v 1.165 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: kern_exit.c,v 1.39 1996/04/22 01:38:25 christos Exp $ */
/*
@@ -73,6 +73,7 @@
void proc_finish_wait(struct proc *, struct proc *);
void process_zap(struct process *);
void proc_free(struct proc *);
+void unveil_destroy(struct process *ps);
/*
* exit --
@@ -606,6 +607,8 @@ process_zap(struct process *pr)
*/
(void)chgproccnt(pr->ps_ucred->cr_ruid, -1);
+ unveil_destroy(pr);
+
/*
* Release reference to text vnode
*/
diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c
index 291be50d21c..2e2349ea54c 100644
--- a/sys/kern/kern_fork.c
+++ b/sys/kern/kern_fork.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kern_fork.c,v 1.203 2018/06/17 08:22:02 anton Exp $ */
+/* $OpenBSD: kern_fork.c,v 1.204 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: kern_fork.c,v 1.29 1996/02/09 18:59:34 christos Exp $ */
/*
@@ -75,6 +75,8 @@ pid_t alloctid(void);
pid_t allocpid(void);
int ispidtaken(pid_t);
+struct unveil *unveil_copy(struct process *s, size_t *count);
+
struct proc *thread_new(struct proc *_parent, vaddr_t _uaddr);
struct process *process_new(struct proc *, struct process *, int);
int fork_check_maxthread(uid_t _uid);
@@ -235,6 +237,18 @@ process_new(struct proc *p, struct process *parent, int flags)
pr->ps_textvp = parent->ps_textvp;
if (pr->ps_textvp)
vref(pr->ps_textvp);
+#if 0 /* XXX Fix this */
+ /* copy unveil if unveil is active */
+ if (parent->ps_uvvcount) {
+ pr->ps_uvpaths = unveil_copy(parent, &pr->ps_uvncount);
+ if (parent->ps_uvpcwd)
+ pr->ps_uvpcwd = pr->ps_uvpaths +
+ (parent->ps_uvpcwd - parent->ps_uvpaths);
+ pr->ps_uvpcwdgone = parent->ps_uvpcwdgone;
+ pr->ps_uvdone = parent->ps_uvdone;
+ pr->ps_uvshrink = 1;
+ }
+#endif
pr->ps_flags = parent->ps_flags &
(PS_SUGID | PS_SUGIDEXEC | PS_PLEDGE | PS_EXECPLEDGE | PS_WXNEEDED);
diff --git a/sys/kern/kern_pledge.c b/sys/kern/kern_pledge.c
index c5f223e8b1b..a057f5f1348 100644
--- a/sys/kern/kern_pledge.c
+++ b/sys/kern/kern_pledge.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kern_pledge.c,v 1.235 2018/07/12 01:23:38 cheloha Exp $ */
+/* $OpenBSD: kern_pledge.c,v 1.236 2018/07/13 09:25:23 beck Exp $ */
/*
* Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
@@ -25,6 +25,7 @@
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/namei.h>
+#include <sys/pool.h>
#include <sys/socketvar.h>
#include <sys/vnode.h>
#include <sys/mbuf.h>
@@ -87,6 +88,7 @@ uint64_t pledgereq_flags(const char *req);
int parsepledges(struct proc *p, const char *kname,
const char *promises, u_int64_t *fp);
int canonpath(const char *input, char *buf, size_t bufsize);
+void unveil_destroy(struct process *ps);
/* #define DEBUG_PLEDGE */
#ifdef DEBUG_PLEDGE
@@ -287,6 +289,8 @@ const uint64_t pledge_syscalls[SYS_MAXSYSCALL] = {
[SYS_setgroups] = PLEDGE_ID,
[SYS_setlogin] = PLEDGE_ID,
+ [SYS_unveil] = PLEDGE_UNVEIL,
+
[SYS_execve] = PLEDGE_EXEC,
[SYS_chdir] = PLEDGE_RPATH,
@@ -391,6 +395,7 @@ static const struct {
{ "tmppath", PLEDGE_TMPPATH },
{ "tty", PLEDGE_TTY },
{ "unix", PLEDGE_UNIX },
+ { "unveil", PLEDGE_UNVEIL },
{ "vminfo", PLEDGE_VMINFO },
{ "vmm", PLEDGE_VMM },
{ "wpath", PLEDGE_WPATH },
@@ -477,6 +482,14 @@ sys_pledge(struct proc *p, void *v, register_t *retval)
if (SCARG(uap, promises)) {
pr->ps_pledge = promises;
pr->ps_flags |= PS_PLEDGE;
+ /*
+ * Kill off unveil and drop unveil vnode refs if we no
+ * longer are holding any path-accessing pledge
+ */
+ if ((pr->ps_pledge & (PLEDGE_RPATH | PLEDGE_WPATH |
+ PLEDGE_CPATH | PLEDGE_DPATH | PLEDGE_TMPPATH | PLEDGE_EXEC |
+ PLEDGE_UNIX | PLEDGE_UNVEIL)) == 0)
+ unveil_destroy(pr);
}
if (SCARG(uap, execpromises)) {
pr->ps_execpledge = execpromises;
@@ -558,6 +571,11 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
if (!ni || (ni->ni_pledge == 0))
panic("ni_pledge");
+ /*
+ * We set the BYPASSUNVEIL flag to skip unveil checks
+ * as necessary
+ */
+
/* Doing a permitted execve() */
if ((ni->ni_pledge & PLEDGE_EXEC) &&
(p->p_p->ps_pledge & PLEDGE_EXEC))
@@ -572,6 +590,7 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
(p->p_pledge_syscall == SYS_open) &&
(ni->ni_pledge & PLEDGE_CPATH) &&
strncmp(path, "/tmp/", sizeof("/tmp/") - 1) == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
}
@@ -581,6 +600,7 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
if ((p->p_p->ps_pledge & PLEDGE_TMPPATH) &&
(p->p_pledge_syscall == SYS_unlink) &&
strncmp(path, "/tmp/", sizeof("/tmp/") - 1) == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
}
@@ -589,22 +609,25 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
case SYS_access:
/* tzset() needs this. */
if ((ni->ni_pledge == PLEDGE_RPATH) &&
- strcmp(path, "/etc/localtime") == 0)
+ strcmp(path, "/etc/localtime") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
/* when avoiding YP mode, getpw* functions touch this */
if (ni->ni_pledge == PLEDGE_RPATH &&
strcmp(path, "/var/run/ypbind.lock") == 0) {
- if (p->p_p->ps_pledge & PLEDGE_GETPW)
+ if (p->p_p->ps_pledge & PLEDGE_GETPW) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
- else
+ } else
return (pledge_fail(p, error, PLEDGE_GETPW));
}
- break;
case SYS_open:
/* daemon(3) or other such functions */
if ((ni->ni_pledge & ~(PLEDGE_RPATH | PLEDGE_WPATH)) == 0 &&
strcmp(path, "/dev/null") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
}
@@ -612,6 +635,7 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
if ((p->p_p->ps_pledge & PLEDGE_TTY) &&
(ni->ni_pledge & ~(PLEDGE_RPATH | PLEDGE_WPATH)) == 0 &&
strcmp(path, "/dev/tty") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
}
@@ -620,23 +644,35 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
(p->p_p->ps_pledge & PLEDGE_GETPW)) {
if (strcmp(path, "/etc/spwd.db") == 0)
return (EPERM); /* don't call pledge_fail */
- if (strcmp(path, "/etc/pwd.db") == 0)
+ if (strcmp(path, "/etc/pwd.db") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
- if (strcmp(path, "/etc/group") == 0)
+ }
+ if (strcmp(path, "/etc/group") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
- if (strcmp(path, "/etc/netid") == 0)
+ }
+ if (strcmp(path, "/etc/netid") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
}
/* DNS needs /etc/{resolv.conf,hosts,services}. */
if ((ni->ni_pledge == PLEDGE_RPATH) &&
(p->p_p->ps_pledge & PLEDGE_DNS)) {
- if (strcmp(path, "/etc/resolv.conf") == 0)
+ if (strcmp(path, "/etc/resolv.conf") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
- if (strcmp(path, "/etc/hosts") == 0)
+ }
+ if (strcmp(path, "/etc/hosts") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
- if (strcmp(path, "/etc/services") == 0)
+ }
+ if (strcmp(path, "/etc/services") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
}
if ((ni->ni_pledge == PLEDGE_RPATH) &&
@@ -651,38 +687,53 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
* progress, needing a clever design.
*/
p->p_p->ps_pledge |= PLEDGE_YPACTIVE;
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
}
if (strncmp(path, "/var/yp/binding/",
- sizeof("/var/yp/binding/") - 1) == 0)
+ sizeof("/var/yp/binding/") - 1) == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
}
/* tzset() needs these. */
if ((ni->ni_pledge == PLEDGE_RPATH) &&
strncmp(path, "/usr/share/zoneinfo/",
- sizeof("/usr/share/zoneinfo/") - 1) == 0)
+ sizeof("/usr/share/zoneinfo/") - 1) == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
if ((ni->ni_pledge == PLEDGE_RPATH) &&
- strcmp(path, "/etc/localtime") == 0)
+ strcmp(path, "/etc/localtime") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
break;
case SYS_readlink:
/* Allow /etc/malloc.conf for malloc(3). */
if ((ni->ni_pledge == PLEDGE_RPATH) &&
- strcmp(path, "/etc/malloc.conf") == 0)
+ strcmp(path, "/etc/malloc.conf") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
break;
case SYS_stat:
/* DNS needs /etc/resolv.conf. */
if ((ni->ni_pledge == PLEDGE_RPATH) &&
(p->p_p->ps_pledge & PLEDGE_DNS) &&
- strcmp(path, "/etc/resolv.conf") == 0)
+ strcmp(path, "/etc/resolv.conf") == 0) {
+ ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
+ }
break;
}
+ /* Doing a stat */
+ if (ni->ni_pledge & PLEDGE_STAT)
+ return(0);
+
/*
* Ensure each flag of p_pledgenote has counterpart allowing it in
* ps_pledge
@@ -690,6 +741,7 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
if (ni->ni_pledge & ~p->p_p->ps_pledge)
return (pledge_fail(p, EPERM, (ni->ni_pledge & ~p->p_p->ps_pledge)));
+ /* continue, and check unveil if present */
return (0);
}
@@ -716,7 +768,6 @@ pledge_recvfd(struct proc *p, struct file *fp)
if (vp->v_type != VDIR)
return (0);
- break;
}
return pledge_fail(p, EINVAL, PLEDGE_RECVFD);
}
diff --git a/sys/kern/kern_unveil.c b/sys/kern/kern_unveil.c
new file mode 100644
index 00000000000..9a1f293b6a6
--- /dev/null
+++ b/sys/kern/kern_unveil.c
@@ -0,0 +1,727 @@
+/* $OpenBSD: kern_unveil.c,v 1.1 2018/07/13 09:25:23 beck Exp $ */
+
+/*
+ * Copyright (c) 2017-2018 Bob Beck <beck@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+
+#include <sys/mount.h>
+#include <sys/proc.h>
+#include <sys/namei.h>
+#include <sys/pool.h>
+#include <sys/vnode.h>
+#include <sys/ktrace.h>
+#include <sys/types.h>
+#include <sys/malloc.h>
+#include <sys/tree.h>
+
+#include <sys/conf.h>
+#include <sys/syscall.h>
+#include <sys/syscallargs.h>
+#include <sys/systm.h>
+
+#define PLEDGENAMES
+#include <sys/pledge.h>
+
+/* #define DEBUG_UNVEIL */
+
+#define UNVEIL_MAX_VNODES 128
+#define UNVEIL_MAX_NAMES 128
+
+static inline int
+unvname_compare(const struct unvname *n1, const struct unvname *n2)
+{
+ if (n1->un_namesize == n2->un_namesize)
+ return (memcmp(n1->un_name, n2->un_name, n1->un_namesize));
+ else
+ return (n1->un_namesize - n2->un_namesize);
+}
+
+struct unvname *
+unvname_new(const char *name, size_t size, int flags)
+{
+ struct unvname *ret = malloc(sizeof(struct unvname), M_PROC, M_WAITOK);
+ ret->un_name = malloc(size, M_PROC, M_WAITOK);
+ memcpy(ret->un_name, name, size);
+ ret->un_namesize = size;
+ ret->un_flags = flags;
+ return ret;
+}
+
+void
+unveil_free_traversed_vnodes(struct nameidata *ndp) {
+ if (ndp->ni_tvpsize) {
+ size_t i;
+ for (i = 0; i < ndp->ni_tvpend; i++)
+ vrele(ndp->ni_tvp[i]); /* ref for being in list */
+ free(ndp->ni_tvp, M_PROC, ndp->ni_tvpsize * sizeof(struct vnode *));
+ ndp->ni_tvpsize = 0;
+ ndp->ni_tvpend = 0;
+ }
+}
+
+void
+unveil_save_traversed_vnode(struct nameidata *ndp, struct vnode *vp) {
+ if (ndp->ni_tvpsize == 0) {
+ ndp->ni_tvp = mallocarray(MAXPATHLEN, sizeof(struct vnode *),
+ M_PROC, M_WAITOK);
+ ndp->ni_tvpsize = MAXPATHLEN;
+ }
+ /* This should be limited by MAXPATHLEN on a single lookup */
+ KASSERT(ndp->ni_tvpsize > ndp->ni_tvpend);
+ vref(vp); /* ref for being in the list */
+ ndp->ni_tvp[ndp->ni_tvpend++] = vp;
+}
+
+void
+unvname_delete(struct unvname *name)
+{
+ free(name->un_name, M_PROC, name->un_namesize);;
+ free(name, M_PROC, sizeof(struct unvname));
+}
+
+RBT_PROTOTYPE(unvname_rbt, unvname, un_rbt, unvname_compare);
+RBT_GENERATE(unvname_rbt, unvname, un_rbt, unvname_compare);
+
+int
+unveil_delete_names(struct unveil *uv)
+{
+ struct unvname *unvn, *next;
+ int ret = 0;
+
+ rw_enter_write(&uv->uv_lock);
+ RBT_FOREACH_SAFE(unvn, unvname_rbt, &uv->uv_names, next) {
+ RBT_REMOVE(unvname_rbt, &uv->uv_names, unvn);
+ unvname_delete(unvn);
+ ret++;
+ }
+ rw_exit_write(&uv->uv_lock);
+ return ret;
+}
+
+void
+unveil_add_name(struct unveil *uv, char *name, uint64_t flags)
+{
+ struct unvname *unvn;
+
+ rw_enter_write(&uv->uv_lock);
+ unvn = unvname_new(name, strlen(name) + 1, flags);
+ RBT_INSERT(unvname_rbt, &uv->uv_names, unvn);
+ rw_exit_write(&uv->uv_lock);
+#ifdef DEBUG_UNVEIL
+ printf("added name %s\n", name);
+#endif
+}
+
+struct unvname *
+unveil_namelookup(struct unveil *uv, char *name)
+{
+ struct unvname n, *ret = NULL;
+
+ rw_enter_read(&uv->uv_lock);
+
+#ifdef DEBUG_UNVEIL
+ printf("unveil_namelookup: looking up name %s (%p) in vnode %p\n",
+ name, name, uv->uv_vp);
+#endif
+
+ KASSERT(uv->uv_vp != NULL);
+
+ n.un_name = name;
+ n.un_namesize = strlen(name) + 1;
+
+ ret = RBT_FIND(unvname_rbt, &uv->uv_names, &n);
+
+ rw_exit_read(&uv->uv_lock);
+
+#ifdef DEBUG_UNVEIL
+ if (ret == NULL)
+ printf("unveil_namelookup: no match for name %s in vnode %p\n",
+ name, uv->uv_vp);
+ else
+ printf("unveil_namelookup: matched name %s in vnode %p\n",
+ name, uv->uv_vp);
+#endif
+ return ret;
+}
+
+void
+unveil_destroy(struct process *ps)
+{
+ size_t i;
+
+ for (i = 0; ps->ps_uvpaths != NULL && i < ps->ps_uvvcount; i++) {
+ struct unveil *uv = ps->ps_uvpaths + i;
+
+ struct vnode *vp = uv->uv_vp;
+ /* skip any vnodes zapped by unveil_removevnode */
+ if (vp != NULL) {
+ vp->v_uvcount--;
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): removing vnode %p uvcount %d "
+ "in position %ld\n",
+ ps->ps_comm, ps->ps_pid, vp, vp->v_uvcount, i);
+#endif
+ vrele(vp);
+ }
+ ps->ps_uvncount -= unveil_delete_names(uv);
+ uv->uv_vp = NULL;
+ uv->uv_flags = 0;
+ }
+
+ KASSERT(ps->ps_uvncount == 0);
+ free(ps->ps_uvpaths, M_PROC, UNVEIL_MAX_VNODES *
+ sizeof(struct unveil));
+ ps->ps_uvvcount = 0;
+ ps->ps_uvpaths = NULL;
+}
+
+struct unveil *
+unveil_copy(struct process *ps, size_t *count)
+{
+ struct unveil *ret;
+ size_t i;
+
+ ret = mallocarray(UNVEIL_MAX_VNODES, sizeof(struct unveil),
+ M_PROC, M_WAITOK|M_ZERO);
+
+ *count = 0;
+ for (i = 0; ps->ps_uvpaths != NULL && i < ps->ps_uvvcount; i++) {
+ struct unveil *uv = ps->ps_uvpaths + i;
+ struct unvname *unvn, *next;
+
+ ret[i].uv_vp = uv->uv_vp;
+ if (ret[i].uv_vp != NULL) {
+ vref(ret[i].uv_vp);
+ ret[i].uv_vp->v_uvcount++;
+ }
+ rw_init(&ret[i].uv_lock, "unveil");
+ RBT_INIT(unvname_rbt, &ret[i].uv_names);
+ rw_enter_read(&uv->uv_lock);
+ RBT_FOREACH_SAFE(unvn, unvname_rbt, &uv->uv_names, next) {
+ unveil_add_name(&ret[i], unvn->un_name, unvn->un_flags);
+ (*count)++;
+ }
+ printf("count now %ld\n", *count);
+ rw_exit_read(&uv->uv_lock);
+ ret[i].uv_flags = uv->uv_flags;
+ }
+ return(ret);
+}
+
+
+struct unveil *
+unveil_lookup(struct vnode *vp, struct proc *p)
+{
+ struct process *pr = p->p_p;
+ struct unveil *uv = pr->ps_uvpaths;
+ ssize_t l, r;
+
+ if (vp->v_uvcount == 0)
+ return NULL;
+
+ /*
+ * shrink if told to do so to remove dead vnodes.
+ */
+ if (pr->ps_uvshrink) {
+ size_t i = 0, j;
+ while (i < pr->ps_uvvcount) {
+ if (uv[i].uv_vp == NULL) {
+ pr->ps_uvncount -= unveil_delete_names(&uv[i]);
+ for (j = i + 1; j < pr->ps_uvvcount; j++)
+ uv[j - 1] = uv[j];
+ pr->ps_uvvcount--;
+ }
+ i++;
+ }
+ pr->ps_uvshrink = 0;
+ }
+
+ if (pr->ps_uvvcount == 0)
+ return NULL;
+
+ /* clear the cwd unveil when we .. past it */
+ if (pr->ps_uvpcwd && (vp == pr->ps_uvpcwd->uv_vp)) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): nuking cwd traversing vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, vp);
+#endif
+ p->p_p->ps_uvpcwd = NULL;
+ p->p_p->ps_uvpcwdgone = 0;
+ }
+#ifdef DEBUG_UNVEIL
+ else {
+ if (pr->ps_uvpcwd) {
+ printf("unveil: %s(%d): did not nuke cwd because %p != %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, vp, pr->ps_uvpcwd->uv_vp);
+ }
+ else
+ printf("unveil: %s(%d): cwd is null\n",
+ p->p_p->ps_comm, p->p_p->ps_pid);
+
+ }
+#endif
+
+ l = 0;
+ r = pr->ps_uvvcount - 1;
+ while (l <= r) {
+ size_t m = l + (r - l)/2;
+#ifdef DEBUG_UNVEIL
+ printf("unveil: checking vnode %p vs. unveil vnode %p\n",
+ vp, uv[m].uv_vp);
+#endif
+ if (vp == uv[m].uv_vp) {
+ KASSERT(uv[m].uv_vp->v_uvcount > 0);
+ KASSERT(uv[m].uv_vp->v_usecount > 0);
+ return &uv[m];
+ }
+ if (vp > uv[m].uv_vp)
+ l = m + 1;
+ else
+ r = m - 1;
+ }
+ return NULL;
+}
+
+int
+unveil_parseflags(const char *cflags, uint64_t *flags)
+{
+ size_t i = 0;
+ char c;
+
+ *flags = 0;
+ while ((c = cflags[i++]) != '\0') {
+ switch (c) {
+ case 'r':
+ *flags |= PLEDGE_RPATH;
+ break;
+ case 'w':
+ *flags |= PLEDGE_WPATH;
+ break;
+ case 'x':
+ *flags |= PLEDGE_EXEC;
+ break;
+ case 'c':
+ *flags |= PLEDGE_CPATH;
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+unveil_setflags(uint64_t *flags, uint64_t nflags)
+{
+#if 0
+ if (((~(*flags)) & nflags) != 0) {
+#ifdef DEBUG_UNVEIL
+ printf("Flags escalation %llX -> %llX\n", *flags, nflags);
+#endif
+ return 1;
+ }
+#endif
+ *flags = nflags;
+ return 1;
+}
+
+struct unveil *
+unveil_add_vnode(struct process *pr, struct vnode *vp)
+{
+ struct unveil *uv = NULL;
+ ssize_t i;
+ for (i = pr->ps_uvvcount;
+ i > 0 && pr->ps_uvpaths[i - 1].uv_vp > vp;
+ i--)
+ pr->ps_uvpaths[i] = pr->ps_uvpaths[i - 1];
+
+ uv = &pr->ps_uvpaths[i];
+ rw_init(&uv->uv_lock, "unveil");
+ RBT_INIT(unvname_rbt, &uv->uv_names);
+ uv->uv_vp = vp;
+ uv->uv_flags = 0;
+ pr->ps_uvvcount++;
+ return (uv);
+}
+
+void
+unveil_add_traversed_vnodes(struct proc *p, struct nameidata *ndp)
+{
+ /*
+ * add the traversed vnodes with 0 flags if they
+ * are not already present.
+ */
+ if (ndp->ni_tvpsize) {
+ size_t i;
+ for (i = 0; i < ndp->ni_tvpend; i++) {
+ struct vnode *vp = ndp->ni_tvp[i];
+ if (unveil_lookup(vp, p) == NULL) {
+ vref(vp);
+ vp->v_uvcount++;
+ unveil_add_vnode(p->p_p, vp);
+ }
+ }
+ }
+}
+
+int
+unveil_add(struct proc *p, struct nameidata *ndp, const char *cflags)
+{
+ struct process *pr = p->p_p;
+ struct vnode *vp;
+ struct unveil *uv;
+ int directory_add;
+ int ret = EINVAL;
+ u_int64_t flags;
+
+ KASSERT(ISSET(ndp->ni_cnd.cn_flags, HASBUF)); /* must have SAVENAME */
+
+ if (unveil_parseflags(cflags, &flags) == -1)
+ goto done;
+
+ if (pr->ps_uvpaths == NULL) {
+ pr->ps_uvpaths = mallocarray(UNVEIL_MAX_VNODES,
+ sizeof(struct unveil), M_PROC, M_WAITOK|M_ZERO);
+ }
+
+ if (pr->ps_uvvcount >= UNVEIL_MAX_VNODES ||
+ pr->ps_uvncount >= UNVEIL_MAX_NAMES) {
+ ret = E2BIG;
+ goto done;
+ }
+
+ /* Are we a directory? or something else */
+ directory_add = ndp->ni_vp != NULL && ndp->ni_vp->v_type == VDIR;
+
+ if (directory_add)
+ vp=ndp->ni_vp;
+ else
+ vp=ndp->ni_dvp;
+
+ KASSERT(vp->v_type == VDIR);
+ vref(vp);
+ vp->v_uvcount++;
+ if ((uv = unveil_lookup(vp, p)) != NULL) {
+ /*
+ * We already have unveiled this directory
+ * vnode
+ */
+ vp->v_uvcount--;
+ vrele(vp);
+
+ /*
+ * If we are adding a directory which was already
+ * unveiled containing only specific terminals,
+ * unrestrict it.
+ */
+ if (directory_add) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): updating directory vnode %p"
+ " to unrestricted uvcount %d\n",
+ pr->ps_comm, pr->ps_pid, vp, vp->v_uvcount);
+#endif
+ if (!unveil_setflags(&uv->uv_flags, flags))
+ ret = EPERM;
+ else
+ ret = 0;
+ goto done;
+ }
+
+ /*
+ * If we are adding a terminal that is already unveiled, just
+ * replace the flags and we are done
+ */
+ if (!directory_add) {
+ struct unvname *tname;
+ if ((tname = unveil_namelookup(uv,
+ ndp->ni_cnd.cn_nameptr)) != NULL) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): changing flags for %s"
+ "in vnode %p, uvcount %d\n",
+ pr->ps_comm, pr->ps_pid, tname->un_name, vp,
+ vp->v_uvcount);
+#endif
+ if (!unveil_setflags(&tname->un_flags, flags))
+ ret = EPERM;
+ else
+ ret = 0;
+ goto done;
+ }
+ }
+
+ } else {
+ /*
+ * New unveil involving this directory vnode.
+ */
+ uv = unveil_add_vnode(pr, vp);
+ }
+
+ /*
+ * At this stage with have a unveil in uv with a vnode for a
+ * directory. If the component we are adding is a directory,
+ * we are done. Otherwise, we add the component name the name
+ * list in uv.
+ */
+
+ if (directory_add) {
+ uv->uv_flags = flags;
+ ret = 0;
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): added unrestricted directory vnode %p"
+ ", uvcount %d\n",
+ pr->ps_comm, pr->ps_pid, vp, vp->v_uvcount);
+#endif
+ goto done;
+ }
+
+ unveil_add_name(uv, ndp->ni_cnd.cn_nameptr, flags);
+ pr->ps_uvncount++;
+ ret = 0;
+
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): added name %s beneath %s vnode %p,"
+ " uvcount %d\n",
+ pr->ps_comm, pr->ps_pid, ndp->ni_cnd.cn_nameptr,
+ uv->uv_flags ? "unrestricted" : "restricted",
+ vp, vp->v_uvcount);
+#endif
+
+ done:
+ if (ret == 0)
+ unveil_add_traversed_vnodes(p, ndp);
+ unveil_free_traversed_vnodes(ndp);
+ pool_put(&namei_pool, ndp->ni_cnd.cn_pnbuf);
+ return ret;
+}
+
+/*
+ * XXX this will probably change.
+ * XXX collapse down later once debug surely unneded
+ */
+int
+unveil_flagmatch(struct nameidata *ni, uint64_t flags)
+{
+ if (flags == 0) {
+ if (ni->ni_pledge & PLEDGE_STAT) {
+#ifdef DEBUG_UNVEIL
+ printf("allowing stat/accesss for 0 flags");
+#endif
+ SET(ni->ni_pledge, PLEDGE_STATLIE);
+ return 1;
+ }
+#ifdef DEBUG_UNVEIL
+ printf("All operations forbidden for 0 flags\n");
+#endif
+ return 0;
+ }
+ if (ni->ni_pledge & PLEDGE_STAT) {
+#ifdef DEBUG_UNVEIL
+ printf("Allowing stat for nonzero flags\n");
+#endif
+ CLR(ni->ni_pledge, PLEDGE_STATLIE);
+ return 1;
+ }
+ if (ni->ni_pledge & PLEDGE_RPATH) {
+ if ((flags & PLEDGE_RPATH) == 0) {
+#ifdef DEBUG_UNVEIL
+ printf("Pledge wants read but disallowed\n");
+#endif
+ return 0;
+ }
+ }
+ if (ni->ni_pledge & PLEDGE_WPATH) {
+ if ((flags & PLEDGE_WPATH) == 0) {
+#ifdef DEBUG_UNVEIL
+ printf("Pledge wants write but disallowed\n");
+#endif
+ return 0;
+ }
+ }
+ if (ni->ni_pledge & PLEDGE_EXEC) {
+ if ((flags & PLEDGE_EXEC) == 0) {
+#ifdef DEBUG_UNVEIL
+ printf("Pledge wants exec but disallowed\n");
+#endif
+ return 0;
+ }
+ }
+ if (ni->ni_pledge & PLEDGE_CPATH) {
+ if ((flags & PLEDGE_CPATH) == 0) {
+#ifdef DEBUG_UNVEIL
+ printf("Pledge wants cpath but disallowed\n");
+#endif
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/*
+ * unveil checking - for component directories in a namei lookup.
+ */
+void
+unveil_check_component(struct proc *p, struct nameidata *ni, struct vnode *dp )
+{
+ struct unveil *uv = NULL;
+
+ if (ni->ni_pledge != PLEDGE_UNVEIL) {
+ if ((ni->ni_cnd.cn_flags & BYPASSUNVEIL) == 0 &&
+ (uv = unveil_lookup(dp, p)) != NULL) {
+ /* if directory flags match, it's a match */
+ if (unveil_flagmatch(ni, uv->uv_flags)) {
+ if (uv->uv_flags) {
+ ni->ni_unveil_match = uv;
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): component directory match"
+ " for vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, dp);
+
+#endif
+ }
+ }
+ }
+ }
+ else
+ unveil_save_traversed_vnode(ni, dp);
+}
+
+/*
+ * unveil checking - only done after namei lookup has succeeded on
+ * the last compoent of a namei lookup.
+ */
+int
+unveil_check_final(struct proc *p, struct nameidata *ni)
+{
+ struct unveil *uv;
+ struct unvname *tname = NULL;
+
+ if (ni->ni_pledge == PLEDGE_UNVEIL ||
+ p->p_p->ps_uvpaths == NULL)
+ return (0);
+
+ if (ni->ni_cnd.cn_flags & BYPASSUNVEIL) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): BYPASSUNVEIL.\n",
+ p->p_p->ps_comm, p->p_p->ps_pid);
+#endif
+ CLR(ni->ni_pledge, PLEDGE_STATLIE);
+ return (0);
+ }
+ if (ni->ni_vp != NULL && ni->ni_vp->v_type == VDIR) {
+ uv = unveil_lookup(ni->ni_vp, p);
+ if (uv == NULL) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d) no match for vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, ni->ni_vp);
+#endif
+ goto done;
+ }
+ if (!unveil_flagmatch(ni, uv->uv_flags)) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d) flag mismatch for directory"
+ " vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, ni->ni_vp);
+#endif
+ return EACCES;
+ }
+ } else {
+ uv = unveil_lookup(ni->ni_dvp, p);
+ if (uv == NULL) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d) no match for directory"
+ " vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, ni->ni_dvp);
+#endif
+ goto done;
+ }
+ if ((tname = unveil_namelookup(uv, ni->ni_cnd.cn_nameptr))
+ == NULL) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d) no match for terminal '%s' in "
+ "directory vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid,
+ ni->ni_cnd.cn_nameptr, ni->ni_dvp);
+#endif
+ uv = NULL;
+ goto done;
+ }
+ if (!unveil_flagmatch(ni, tname->un_flags)) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d) flag mismatch for terminal '%s'\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, tname->un_name);
+#endif
+ return EACCES;
+ }
+ }
+ ni->ni_unveil_match = uv;
+done:
+ if (ni->ni_unveil_match) {
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): matched \"%s\" underneath/at vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, ni->ni_cnd.cn_nameptr,
+ ni->ni_unveil_match->uv_vp);
+#endif
+ return (0);
+ }
+ else if (p->p_p->ps_uvpcwd) {
+ ni->ni_unveil_match = p->p_p->ps_uvpcwd;
+#ifdef DEBUG_UNVEIL
+ printf("unveil: %s(%d): used cwd unveil vnode from vnode %p\n",
+ p->p_p->ps_comm, p->p_p->ps_pid, ni->ni_unveil_match->uv_vp);
+#endif
+ return (0);
+ } else if (p->p_p->ps_uvpcwdgone) {
+ printf("Corner cases make Bob cry in a corner\n");
+ }
+ return ENOENT;
+}
+
+/*
+ * Scan all active processes to see if any of them have a unveil
+ * to this vnode. If so, NULL the vnode in their unveil list,
+ * vrele, drop the reference, and mark their unveil list
+ * as needing to have the hole shrunk the next time the process
+ * uses it for lookup.
+ */
+void
+unveil_removevnode(struct vnode *vp)
+{
+ struct process *pr;
+ int count = 0;
+
+ if (vp->v_uvcount == 0)
+ return;
+#ifdef DEBUG_UNVEIL
+ printf("unveil_removevnode found vnode %p with count %d", vp, vp->v_uvcount);
+#endif
+ LIST_FOREACH(pr, &allprocess, ps_list) {
+ struct unveil * uv;
+ if ((uv = unveil_lookup(vp, pr->ps_mainproc)) != NULL) {
+ uv->uv_vp = NULL;
+ uv->uv_flags = 0;
+#ifdef DEBUG_UNVEIL
+ printf("unveil_removevnode vnode %p now count %d", vp, vp->v_uvcount);
+#endif
+ pr->ps_uvshrink = 1;
+ count++;
+ }
+ }
+ KASSERT(vp->v_uvcount == count);
+
+ while (vp->v_uvcount--)
+ vrele(vp);
+}
diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c
index 8c2004ccbbd..174cfbd20a8 100644
--- a/sys/kern/syscalls.c
+++ b/sys/kern/syscalls.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: syscalls.c,v 1.195 2018/07/05 15:31:51 mpi Exp $ */
+/* $OpenBSD: syscalls.c,v 1.196 2018/07/13 09:25:23 beck Exp $ */
/*
* System call names.
@@ -134,7 +134,7 @@ char *syscallnames[] = {
"sigsuspend", /* 111 = sigsuspend */
"sendsyslog", /* 112 = sendsyslog */
"#113 (unimplemented fktrace)", /* 113 = unimplemented fktrace */
- "#114 (obsolete osendmsg)", /* 114 = obsolete osendmsg */
+ "unveil", /* 114 = unveil */
"#115 (obsolete vtrace)", /* 115 = obsolete vtrace */
"#116 (obsolete t32_gettimeofday)", /* 116 = obsolete t32_gettimeofday */
"#117 (obsolete t32_getrusage)", /* 117 = obsolete t32_getrusage */
diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master
index 0dedfc15685..2f4396a2171 100644
--- a/sys/kern/syscalls.master
+++ b/sys/kern/syscalls.master
@@ -1,4 +1,4 @@
-; $OpenBSD: syscalls.master,v 1.185 2018/07/05 15:31:04 mpi Exp $
+; $OpenBSD: syscalls.master,v 1.186 2018/07/13 09:25:23 beck Exp $
; $NetBSD: syscalls.master,v 1.32 1996/04/23 10:24:21 mycroft Exp $
; @(#)syscalls.master 8.2 (Berkeley) 1/13/94
@@ -239,7 +239,8 @@
112 STD { int sys_sendsyslog(const char *buf, size_t nbyte, \
int flags); }
113 UNIMPL fktrace
-114 OBSOL osendmsg
+114 STD { int sys_unveil(const char *path, \
+ const char *flags); }
115 OBSOL vtrace
116 OBSOL t32_gettimeofday
117 OBSOL t32_getrusage
diff --git a/sys/kern/vfs_lookup.c b/sys/kern/vfs_lookup.c
index 93de7775c5a..484889c7cd2 100644
--- a/sys/kern/vfs_lookup.c
+++ b/sys/kern/vfs_lookup.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: vfs_lookup.c,v 1.70 2018/07/05 00:00:18 bluhm Exp $ */
+/* $OpenBSD: vfs_lookup.c,v 1.71 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: vfs_lookup.c,v 1.17 1996/02/09 19:00:59 christos Exp $ */
/*
@@ -57,6 +57,9 @@
#include <sys/ktrace.h>
#endif
+void unveil_check_component(struct proc *p, struct nameidata *ni, struct vnode *dp );
+int unveil_check_final(struct proc *p, struct nameidata *ni);
+
void
ndinitat(struct nameidata *ndp, u_long op, u_long flags,
enum uio_seg segflg, int dirfd, const char *namep, struct proc *p)
@@ -179,11 +182,14 @@ fail:
* Check if starting from root directory or current directory.
*/
if (cnp->cn_pnbuf[0] == '/') {
+ curproc->p_p->ps_uvpcwd = NULL;
+ curproc->p_p->ps_uvpcwdgone = 0;
dp = ndp->ni_rootdir;
vref(dp);
} else if (ndp->ni_dirfd == AT_FDCWD) {
dp = fdp->fd_cdir;
vref(dp);
+ unveil_check_component(p, ndp, dp);
} else {
struct file *fp = fd_getfile(fdp, ndp->ni_dirfd);
if (fp == NULL) {
@@ -197,6 +203,7 @@ fail:
return (ENOTDIR);
}
vref(dp);
+ unveil_check_component(p, ndp, dp);
FRELE(fp, p);
}
for (;;) {
@@ -217,6 +224,20 @@ fail:
* If not a symbolic link, return search result.
*/
if ((cnp->cn_flags & ISSYMLINK) == 0) {
+ if ((error = unveil_check_final(p, ndp))) {
+ pool_put(&namei_pool, cnp->cn_pnbuf);
+ if ((cnp->cn_flags & LOCKPARENT) &&
+ (cnp->cn_flags & ISLASTCN))
+ VOP_UNLOCK(ndp->ni_dvp);
+ if (ndp->ni_vp) {
+ if ((cnp->cn_flags & LOCKLEAF))
+ vput(ndp->ni_vp);
+ else
+ vrele(ndp->ni_vp);
+ }
+ ndp->ni_vp = NULL;
+ return (error);
+ }
if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0)
pool_put(&namei_pool, cnp->cn_pnbuf);
else
@@ -274,7 +295,17 @@ badlink:
vrele(dp);
dp = ndp->ni_rootdir;
vref(dp);
+ ndp->ni_unveil_match = NULL;
+ curproc->p_p->ps_uvpcwd = NULL;
+ unveil_check_component(p, ndp, dp);
+ } else {
+ /*
+ * this is a relative link, so remember our
+ * unveil match from this point
+ */
+ curproc->p_p->ps_uvpcwd = ndp->ni_unveil_match;
}
+
}
pool_put(&namei_pool, cnp->cn_pnbuf);
vrele(ndp->ni_dvp);
@@ -455,6 +486,7 @@ dirloop:
* Handle "..": two special cases.
* 1. If at root directory (e.g. after chroot)
* or at absolute root directory
+ * or we are under unveil restrictions
* then ignore it so can't get out.
* 2. If this vnode is the root of a mounted
* filesystem, then replace it with the
@@ -463,10 +495,20 @@ dirloop:
*/
if (cnp->cn_flags & ISDOTDOT) {
for (;;) {
+ if (curproc->p_p->ps_uvvcount > 0) {
+#if 0
+ error = ENOENT;
+ goto bad;
+#else
+ ndp->ni_unveil_match = NULL;
+#endif
+ }
if (dp == ndp->ni_rootdir || dp == rootvnode) {
ndp->ni_dvp = dp;
ndp->ni_vp = dp;
vref(dp);
+ curproc->p_p->ps_uvpcwd = NULL;
+ curproc->p_p->ps_uvpcwdgone = 0;
goto nextname;
}
if ((dp->v_flag & VROOT) == 0 ||
@@ -476,6 +518,7 @@ dirloop:
dp = dp->v_mount->mnt_vnodecovered;
vput(tdp);
vref(dp);
+ unveil_check_component(curproc, ndp, dp);
vn_lock(dp, LK_EXCLUSIVE | LK_RETRY);
}
}
@@ -486,6 +529,7 @@ dirloop:
ndp->ni_dvp = dp;
ndp->ni_vp = NULL;
cnp->cn_flags &= ~PDIRUNLOCK;
+ unveil_check_component(curproc, ndp, dp);
if ((error = VOP_LOOKUP(dp, &ndp->ni_vp, cnp)) != 0) {
#ifdef DIAGNOSTIC
@@ -493,8 +537,15 @@ dirloop:
panic("leaf should be empty");
#endif
#ifdef NAMEI_DIAGNOSTIC
- printf("not found\n");
+ printf("not found\n");
#endif
+ /*
+ * Allow for unveiling of a file in a directory
+ * where we don't have access to create it ourselves
+ */
+ if (ndp->ni_pledge == PLEDGE_UNVEIL && error == EACCES)
+ error = EJUSTRETURN;
+
if (error != EJUSTRETURN)
goto bad;
/*
diff --git a/sys/kern/vfs_subr.c b/sys/kern/vfs_subr.c
index 353d94e3c76..fa9e541abec 100644
--- a/sys/kern/vfs_subr.c
+++ b/sys/kern/vfs_subr.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: vfs_subr.c,v 1.276 2018/07/02 20:56:22 bluhm Exp $ */
+/* $OpenBSD: vfs_subr.c,v 1.277 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: vfs_subr.c,v 1.53 1996/04/22 01:39:13 christos Exp $ */
/*
@@ -722,6 +722,7 @@ vput(struct vnode *vp)
}
#endif
vp->v_usecount--;
+ KASSERT(vp->v_usecount > 0 || vp->v_uvcount == 0);
if (vp->v_usecount > 0) {
VOP_UNLOCK(vp);
return;
@@ -1067,6 +1068,8 @@ vgonel(struct vnode *vp, struct proc *p)
struct vnode *vq;
struct vnode *vx;
+ KASSERT(vp->v_uvcount == 0);
+
/*
* If a vgone (or vclean) is already in progress,
* wait until it is done and return.
diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c
index da74978c8da..4a66952294f 100644
--- a/sys/kern/vfs_syscalls.c
+++ b/sys/kern/vfs_syscalls.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: vfs_syscalls.c,v 1.292 2018/07/03 20:40:25 kettenis Exp $ */
+/* $OpenBSD: vfs_syscalls.c,v 1.293 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: vfs_syscalls.c,v 1.71 1996/04/23 10:29:02 mycroft Exp $ */
/*
@@ -90,6 +90,8 @@ int doutimensat(struct proc *, int, const char *, struct timespec [2], int);
int dovutimens(struct proc *, struct vnode *, struct timespec [2]);
int dofutimens(struct proc *, int, struct timespec [2]);
int dounmount_leaf(struct mount *, int, struct proc *);
+int unveil_add(struct proc *, struct nameidata *, const char *);
+void unveil_removevnode(struct vnode *vp);
/*
* Virtual File System System Calls
@@ -350,6 +352,9 @@ checkdirs(struct vnode *olddp)
vref(newdp);
fdp->fd_rdir = newdp;
}
+ pr->ps_uvpcwd = NULL;
+ /* XXX */
+ pr->ps_uvpcwdgone = 1;
}
if (rootvnode == olddp) {
free_count++;
@@ -483,6 +488,7 @@ int
dounmount_leaf(struct mount *mp, int flags, struct proc *p)
{
struct vnode *coveredvp;
+ struct vnode *vp, *nvp;
int error;
int hadsyncer = 0;
@@ -493,6 +499,15 @@ dounmount_leaf(struct mount *mp, int flags, struct proc *p)
vgone(mp->mnt_syncer);
mp->mnt_syncer = NULL;
}
+
+ /*
+ * Before calling file system unmount, make sure
+ * all unveils to vnodes in here are dropped.
+ */
+ LIST_FOREACH_SAFE(vp , &mp->mnt_vnodelist, v_mntvnodes, nvp) {
+ unveil_removevnode(vp);
+ }
+
if (((mp->mnt_flag & MNT_RDONLY) ||
(error = VFS_SYNC(mp, MNT_WAIT, 0, p->p_ucred, p)) == 0) ||
(flags & MNT_FORCE))
@@ -623,6 +638,7 @@ sys_statfs(struct proc *p, void *v, register_t *retval)
NDINIT(&nd, LOOKUP, FOLLOW, UIO_USERSPACE, SCARG(uap, path), p);
nd.ni_pledge = PLEDGE_RPATH;
+ nd.ni_cnd.cn_flags |= BYPASSUNVEIL;
if ((error = namei(&nd)) != 0)
return (error);
mp = nd.ni_vp->v_mount;
@@ -795,6 +811,8 @@ sys_chdir(struct proc *p, void *v, register_t *retval)
nd.ni_pledge = PLEDGE_RPATH;
if ((error = change_dir(&nd, p)) != 0)
return (error);
+ p->p_p->ps_uvpcwd = nd.ni_unveil_match;
+ p->p_p->ps_uvpcwdgone = 0;
old_cdir = fdp->fd_cdir;
fdp->fd_cdir = nd.ni_vp;
vrele(old_cdir);
@@ -860,6 +878,78 @@ change_dir(struct nameidata *ndp, struct proc *p)
return (error);
}
+int
+sys_unveil(struct proc *p, void *v, register_t *retval)
+{
+ struct sys_unveil_args /* {
+ syscallarg(const char *) path;
+ syscallarg(const char *) flags;
+ } */ *uap = v;
+ char pathname[MAXPATHLEN];
+ struct nameidata nd;
+ size_t pathlen;
+ char cflags[5];
+ int error;
+
+ if (SCARG(uap, path) == NULL && SCARG(uap, flags) == NULL) {
+ p->p_p->ps_uvdone = 1;
+ return (0);
+ }
+
+ if (p->p_p->ps_uvdone != 0)
+ return EINVAL;
+
+ error = copyinstr(SCARG(uap, flags), cflags, sizeof(cflags), NULL);
+ if (error)
+ return(error);
+ error = copyinstr(SCARG(uap, path), pathname, sizeof(pathname), &pathlen);
+ if (error)
+ return(error);
+
+#ifdef KTRACE
+ if (KTRPOINT(p, KTR_STRUCT))
+ ktrstruct(p, "unveil", cflags, strlen(cflags));
+#endif
+ if (pathlen < 2)
+ return EINVAL;
+
+ /* XXX unveil is disabled for now */
+ return EPERM;
+
+ if (pathlen == 2 && pathname[0] == '/')
+ NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | SAVENAME,
+ UIO_SYSSPACE, pathname, p);
+ else
+ NDINIT(&nd, CREATE, FOLLOW | LOCKLEAF | LOCKPARENT | SAVENAME,
+ UIO_SYSSPACE, pathname, p);
+
+ nd.ni_pledge = PLEDGE_UNVEIL;
+ if ((error = namei(&nd)) != 0)
+ return (error);
+
+ /*
+ * XXX Any access to the file or directory will allow us to
+ * pledge path it
+ */
+ if ((nd.ni_vp &&
+ (VOP_ACCESS(nd.ni_vp, VREAD, p->p_ucred, p) == 0 ||
+ VOP_ACCESS(nd.ni_vp, VWRITE, p->p_ucred, p) == 0 ||
+ VOP_ACCESS(nd.ni_vp, VEXEC, p->p_ucred, p) == 0)) ||
+ VOP_ACCESS(nd.ni_dvp, VREAD, p->p_ucred, p) == 0 ||
+ VOP_ACCESS(nd.ni_dvp, VWRITE, p->p_ucred, p) == 0 ||
+ VOP_ACCESS(nd.ni_dvp, VEXEC, p->p_ucred, p) == 0)
+ error = unveil_add(p, &nd, cflags);
+ else
+ error = EPERM;
+
+ /* release vref and lock from namei, but not vref from ppath_add */
+ if (nd.ni_vp)
+ vput(nd.ni_vp);
+ if (nd.ni_dvp)
+ vput(nd.ni_dvp);
+ return (error);
+}
+
/*
* Check permissions, allocate an open file structure,
* and call the device open routine if any.
@@ -1706,7 +1796,7 @@ dofaccessat(struct proc *p, int fd, const char *path, int amode, int flag)
}
NDINITAT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE, fd, path, p);
- nd.ni_pledge = PLEDGE_RPATH;
+ nd.ni_pledge = PLEDGE_RPATH | PLEDGE_STAT;
if ((error = namei(&nd)) != 0)
goto out;
vp = nd.ni_vp;
@@ -1776,7 +1866,7 @@ dofstatat(struct proc *p, int fd, const char *path, struct stat *buf, int flag)
follow = (flag & AT_SYMLINK_NOFOLLOW) ? NOFOLLOW : FOLLOW;
NDINITAT(&nd, LOOKUP, follow | LOCKLEAF, UIO_USERSPACE, fd, path, p);
- nd.ni_pledge = PLEDGE_RPATH;
+ nd.ni_pledge = PLEDGE_RPATH | PLEDGE_STAT;
if ((error = namei(&nd)) != 0)
return (error);
error = vn_stat(nd.ni_vp, &sb, p);
@@ -1784,11 +1874,11 @@ dofstatat(struct proc *p, int fd, const char *path, struct stat *buf, int flag)
if (error)
return (error);
if (nd.ni_pledge & PLEDGE_STATLIE) {
- if (S_ISDIR(sb.st_mode)) {
- sb.st_mode &= ~ALLPERMS;
- sb.st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
- sb.st_uid = 0;
- sb.st_gid = 0;
+ if (S_ISDIR(sb.st_mode) || S_ISLNK(sb.st_mode)) {
+ if (sb.st_uid >= 1000) {
+ sb.st_uid = p->p_ucred->cr_uid;
+ sb.st_gid = p->p_ucred->cr_gid;;
+ }
sb.st_gen = 0;
} else
return (ENOENT);
@@ -1883,7 +1973,7 @@ doreadlinkat(struct proc *p, int fd, const char *path, char *buf,
struct nameidata nd;
NDINITAT(&nd, LOOKUP, NOFOLLOW | LOCKLEAF, UIO_USERSPACE, fd, path, p);
- nd.ni_pledge = PLEDGE_RPATH;
+ nd.ni_pledge = PLEDGE_RPATH | PLEDGE_STAT;
if ((error = namei(&nd)) != 0)
return (error);
vp = nd.ni_vp;
diff --git a/sys/sys/namei.h b/sys/sys/namei.h
index 4db0a86c734..a7f8accdc3e 100644
--- a/sys/sys/namei.h
+++ b/sys/sys/namei.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: namei.h,v 1.34 2017/08/29 02:51:27 deraadt Exp $ */
+/* $OpenBSD: namei.h,v 1.35 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: namei.h,v 1.11 1996/02/09 18:25:20 christos Exp $ */
/*
@@ -64,12 +64,18 @@ struct nameidata {
*/
struct vnode *ni_vp; /* vnode of result */
struct vnode *ni_dvp; /* vnode of intermediate directory */
+
/*
* Shared between namei and lookup/commit routines.
*/
size_t ni_pathlen; /* remaining chars in path */
char *ni_next; /* next location in pathname */
u_long ni_loopcnt; /* count of symlinks encountered */
+ struct unveil *ni_unveil_match; /* last matching unveil component */
+ struct vnode **ni_tvp; /* traversed vnodes */
+ size_t ni_tvpend; /* end of traversed vnode list */
+ size_t ni_tvpsize; /* size of traversed vnode list */
+
/*
* Lookup parameters: this structure describes the subset of
* information from the nameidata structure that is passed
@@ -138,6 +144,7 @@ struct nameidata {
#define REQUIREDIR 0x080000 /* must be a directory */
#define STRIPSLASHES 0x100000 /* strip trailing slashes */
#define PDIRUNLOCK 0x200000 /* vfs_lookup() unlocked parent dir */
+#define BYPASSUNVEIL 0x400000 /* bypass pledgepath check */
/*
* Initialization of an nameidata structure.
diff --git a/sys/sys/pledge.h b/sys/sys/pledge.h
index 28ca79901f1..910cc351ea3 100644
--- a/sys/sys/pledge.h
+++ b/sys/sys/pledge.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: pledge.h,v 1.36 2018/06/16 15:37:00 florian Exp $ */
+/* $OpenBSD: pledge.h,v 1.37 2018/07/13 09:25:23 beck Exp $ */
/*
* Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
@@ -61,12 +61,14 @@
#define PLEDGE_BPF 0x0000000200000000ULL /* bpf ioctl */
#define PLEDGE_ERROR 0x0000000400000000ULL /* ENOSYS instead of kill */
#define PLEDGE_WROUTE 0x0000000800000000ULL /* interface address ioctls */
+#define PLEDGE_UNVEIL 0x0000001000000000ULL /* allow unveil() */
/*
* Bits outside PLEDGE_USERSET are used by the kernel itself
* to track program behaviours which have been observed.
*/
#define PLEDGE_USERSET 0x0fffffffffffffffULL
+#define PLEDGE_STAT 0x2000000000000000ULL /* XXX this is a stat */
#define PLEDGE_STATLIE 0x4000000000000000ULL
#define PLEDGE_YPACTIVE 0x8000000000000000ULL /* YP use detected and allowed */
@@ -109,6 +111,7 @@ static struct {
{ PLEDGE_BPF, "bpf" },
{ PLEDGE_ERROR, "error" },
{ PLEDGE_WROUTE, "wroute" },
+ { PLEDGE_UNVEIL, "unveil" },
{ 0, NULL },
};
#endif
@@ -137,6 +140,7 @@ int pledge_fcntl(struct proc *p, int cmd);
int pledge_swapctl(struct proc *p);
int pledge_kill(struct proc *p, pid_t pid);
int pledge_protexec(struct proc *p, int prot);
+void ppath_destroy(struct process *ps);
#endif /* _KERNEL */
diff --git a/sys/sys/proc.h b/sys/sys/proc.h
index f05d70ee7d5..a797838c3f3 100644
--- a/sys/sys/proc.h
+++ b/sys/sys/proc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: proc.h,v 1.251 2018/07/12 01:23:38 cheloha Exp $ */
+/* $OpenBSD: proc.h,v 1.252 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: proc.h,v 1.44 1996/04/22 01:23:21 christos Exp $ */
/*-
@@ -48,6 +48,8 @@
#include <sys/event.h> /* For struct klist */
#include <sys/mutex.h> /* For struct mutex */
#include <sys/resource.h> /* For struct rusage */
+#include <sys/rwlock.h> /* For struct rwlock */
+#include <sys/tree.h>
#ifdef _KERNEL
#include <sys/atomic.h>
@@ -129,6 +131,15 @@ struct tusage {
uint64_t tu_iticks; /* Statclock hits processing intr. */
};
+struct unvname {
+ char *un_name;
+ size_t un_namesize;
+ uint64_t un_flags;
+ RBT_ENTRY(unvnmae) un_rbt;
+};
+
+RBT_HEAD(unvname_rbt, unvname);
+
/*
* Description of a process.
*
@@ -142,6 +153,7 @@ struct tusage {
* run-time information needed by threads.
*/
#ifdef __need_process
+struct unveil;
struct process {
/*
* ps_mainproc is the original thread in the process.
@@ -191,6 +203,15 @@ struct process {
u_int64_t ps_wxcounter;
+ struct unveil *ps_uvpaths; /* unveil vnodes and names */
+ struct unveil *ps_uvpcwd; /* pointer to unveil of cwd, NULL if none */
+ size_t ps_uvvcount; /* count of unveil vnodes held */
+ size_t ps_uvncount; /* count of unveil names allocated */
+ int ps_uvshrink; /* do we need to shrink vnode list */
+ int ps_uvactive; /* is unveil active */
+ int ps_uvdone; /* no more unveil is permitted */
+ int ps_uvpcwdgone; /* need to reevaluate cwd unveil */
+
/* End area that is zeroed on creation. */
#define ps_endzero ps_startcopy
@@ -404,6 +425,13 @@ struct proc {
#ifdef _KERNEL
+struct unveil {
+ struct vnode *uv_vp;
+ struct unvname_rbt uv_names;
+ struct rwlock uv_lock;
+ u_int64_t uv_flags;
+};
+
struct uidinfo {
LIST_ENTRY(uidinfo) ui_hash;
uid_t ui_uid;
diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h
index 9e80b413f26..b6e0feff8cf 100644
--- a/sys/sys/syscall.h
+++ b/sys/sys/syscall.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: syscall.h,v 1.195 2018/07/05 15:31:51 mpi Exp $ */
+/* $OpenBSD: syscall.h,v 1.196 2018/07/13 09:25:23 beck Exp $ */
/*
* System call numbers.
@@ -342,7 +342,9 @@
/* syscall: "sendsyslog" ret: "int" args: "const char *" "size_t" "int" */
#define SYS_sendsyslog 112
- /* 114 is obsolete osendmsg */
+/* syscall: "unveil" ret: "int" args: "const char *" "const char *" */
+#define SYS_unveil 114
+
/* 115 is obsolete vtrace */
/* 116 is obsolete t32_gettimeofday */
/* 117 is obsolete t32_getrusage */
diff --git a/sys/sys/syscallargs.h b/sys/sys/syscallargs.h
index ee697973432..ca0d5f4d311 100644
--- a/sys/sys/syscallargs.h
+++ b/sys/sys/syscallargs.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: syscallargs.h,v 1.198 2018/07/05 15:31:51 mpi Exp $ */
+/* $OpenBSD: syscallargs.h,v 1.199 2018/07/13 09:25:23 beck Exp $ */
/*
* System call argument lists.
@@ -568,6 +568,11 @@ struct sys_sendsyslog_args {
syscallarg(int) flags;
};
+struct sys_unveil_args {
+ syscallarg(const char *) path;
+ syscallarg(const char *) flags;
+};
+
struct sys_getsockopt_args {
syscallarg(int) s;
syscallarg(int) level;
@@ -1219,6 +1224,7 @@ int sys_ppoll(struct proc *, void *, register_t *);
int sys_pselect(struct proc *, void *, register_t *);
int sys_sigsuspend(struct proc *, void *, register_t *);
int sys_sendsyslog(struct proc *, void *, register_t *);
+int sys_unveil(struct proc *, void *, register_t *);
int sys_getsockopt(struct proc *, void *, register_t *);
int sys_thrkill(struct proc *, void *, register_t *);
int sys_readv(struct proc *, void *, register_t *);
diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h
index 6c2bac007ba..404a50b05e2 100644
--- a/sys/sys/vnode.h
+++ b/sys/sys/vnode.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: vnode.h,v 1.146 2018/05/27 06:02:15 visa Exp $ */
+/* $OpenBSD: vnode.h,v 1.147 2018/07/13 09:25:23 beck Exp $ */
/* $NetBSD: vnode.h,v 1.38 1996/02/29 20:59:05 cgd Exp $ */
/*
@@ -93,6 +93,7 @@ struct vnode {
enum vtagtype v_tag; /* type of underlying data */
u_int v_flag; /* vnode flags (see below) */
u_int v_usecount; /* reference count of users */
+ u_int v_uvcount; /* unveil references */
/* reference count of writers */
u_int v_writecount;
/* Flags that can be read/written in interrupts */