I wrote some patches to allow pledging across execs.
Currently, the exec pledge passes down the process tree.
The initial version simply inherited the current pledge when
execing with the `pledge("rexec")` promise, but after
discussing with Theo at EuroBSD, a better design was
suggested. Because directory pledges are going to be their
own system call, we can repurpose the second argument of
pledge as the "exec pledge".
My use is for sandboxing a bad idea, where arbitrary users can
submit code to a sandbox for my pet language, sitting on my
website, which gets compiled and run. In the base system, it
seems like many programs with pledge("exec") invoke arbitrary
binaries on behalf of the user, but there are others (eg, pax)
that expect specfic behavior from what they exec.
This also could have broader uses -- for example, pledging
entire shell scripts. It also helps mitigate the case where an
attacker might fool us into exec()ing the wrong binary.
This is split into four patches:
- Adding kernel+libc support and docs. This changes the
signature of pledge, but because everything calls
plege("promises", NULL), things keep working.
- Add a pledge(1) binary + docs. This allows enforcing
arbitrary pledges for a process tree. The minimal pledge for
dynamically linked executables is a bit broad ("stdio rpath
prot_exec"), due to ld.so, but this is still usefully
restrictive. For statically linked executables, we do even
better. 'pledge stdio echo hi' works just fine.
- The third patch adds the ability to pledge programs running
under slowcgi.
- The fourth patch exec-pledges pax. I'm not sure I got the
pledges right, so more careful review would be appreciated.
I grabbed the pledges from compress, which seem to be a
superset of the bzip2 pledges.
Regress tests would be good to add. I haven't done the work
yet, but it's mostly a matter of figuring out how to arrange
the makefiles to produce a binary that can be exec'ed. The
examples I see all seem to just produce one regress binary.
In an appropriate turn of events, this patch was written
while sitting in a capsicum talk at EuroBSD.
============
diff --git include/unistd.h include/unistd.h
index ffec1538f44..1faf2543f3d 100644
--- include/unistd.h
+++ include/unistd.h
@@ -522,7 +522,7 @@ int strtofflags(char **, u_int32_t *, u_int32_t *);
int swapctl(int cmd, const void *arg, int misc);
int syscall(int, ...);
int getentropy(void *, size_t);
-int pledge(const char *, const char **);
+int pledge(const char *, const char *);
pid_t __tfork_thread(const struct __tfork *, size_t, void (*)(void *),
void *);
#endif /* __BSD_VISIBLE */
diff --git lib/libc/sys/pledge.2 lib/libc/sys/pledge.2
index 89884352500..9bfedc3d14b 100644
--- lib/libc/sys/pledge.2
+++ lib/libc/sys/pledge.2
@@ -23,7 +23,7 @@
.Sh SYNOPSIS
.In unistd.h
.Ft int
-.Fn pledge "const char *promises" "const char *paths[]"
+.Fn pledge "const char *promises" "const char *execpromises"
.Sh DESCRIPTION
The current process is forced into a restricted-service operating mode.
A few subsets are available, roughly described as computation, memory
@@ -31,9 +31,16 @@ management, read-write operations on file descriptors,
opening of files,
networking.
In general, these modes were selected by studying the operation
of many programs using libc and other such interfaces, and setting
-.Ar promises
-or
-.Ar paths .
+.Ar promises.
+.Pp
+Specifying
+.Ar execpromises
+allow the programmer to set the default pledge set for all future
+processes process that are execed. This is allows for inheriting
+.Fn fork
+and
+.Fn exec
+calls.
.Pp
Use of
.Fn pledge
@@ -70,7 +77,7 @@ Passing
to
.Fa promises
or
-.Fa paths
+.Fa execpromises
specifies to not change the current value.
.Pp
Some system calls, when allowed, have restrictions applied to them:
@@ -143,9 +150,7 @@ support:
system sensor readings.
.Pp
.It Fn pledge
-Can only reduce permissions; can only set a list of
-.Pa paths
-once.
+Can only reduce permissions.
.El
.Pp
The
@@ -467,8 +472,8 @@ Allows a process to call
Coupled with the
.Va proc
promise, this allows a process to fork and execute another program.
-The new program starts running without pledge active and hopefully
-makes a new
+The new program starts running with the requested exec pledges active
+and hopefully makes a new
.Fn pledge .
.It Va prot_exec
Allows the use of
@@ -556,10 +561,6 @@ Allow
operation for statistics collection from a bpf device.
.El
.Pp
-A whitelist of permitted paths may be provided in
-.Ar paths .
-All other paths will return
-.Er ENOENT .
At least one promise is required to be pledged in order to activate a
whitelist.
.Sh RETURN VALUES
.Rv -std
diff --git regress/sys/kern/pledge/generic/manager.c
regress/sys/kern/pledge/generic/manager.c
index 451a3ecc088..e6af6b3d69e 100644
--- regress/sys/kern/pledge/generic/manager.c
+++ regress/sys/kern/pledge/generic/manager.c
@@ -175,7 +175,7 @@ drainfd(int rfd, int wfd)
void
_start_test(int *ret, const char *test_name, const char *request,
- const char *paths[], void (*test_func)(void))
+ const char *execrequest, const char *paths[], void (*test_func)(void))
{
int fildes[2];
pid_t pid;
@@ -235,7 +235,7 @@ _start_test(int *ret, const char *test_name, const char
*request,
setsid();
/* set pledge policy */
- if (request && pledge(request, paths) != 0)
+ if (request && pledge(request, execrequest) != 0)
err(errno, "pledge");
/* reset errno and launch test */
diff --git regress/sys/kern/pledge/generic/manager.h
regress/sys/kern/pledge/generic/manager.h
index 13c52eea75a..072406ed2e4 100644
--- regress/sys/kern/pledge/generic/manager.h
+++ regress/sys/kern/pledge/generic/manager.h
@@ -18,10 +18,10 @@
#define _MANAGER_H_
void _start_test(int *ret, const char *test_name, const char *request,
- const char *paths[], void (*test_func)(void));
+ const char *execrequest, const char *paths[], void (*test_func)(void));
-#define start_test(ret,req,paths,func) \
- _start_test(ret,#func,req,paths,func)
+#define start_test(ret,req, paths,func) \
+ _start_test(ret,#func,req,NULL,paths,func)
#define start_test1(ret,req,path,func) \
do { \
diff --git sys/kern/init_sysent.c sys/kern/init_sysent.c
index 2b46cda280b..1cbeb93edf8 100644
--- sys/kern/init_sysent.c
+++ sys/kern/init_sysent.c
@@ -1,10 +1,10 @@
-/* $OpenBSD: init_sysent.c,v 1.189 2017/08/12 00:04:06 tedu Exp $ */
+/* $OpenBSD$ */
/*
* System call switch table.
*
* DO NOT EDIT-- this file is automatically generated.
- * created from; OpenBSD: syscalls.master,v 1.177 2017/08/12 00:03:10
tedu Exp
+ * created from; OpenBSD: syscalls.master,v 1.178 2017/09/25 23:00:33
espie Exp
*/
#include <sys/param.h>
diff --git sys/kern/kern_exec.c sys/kern/kern_exec.c
index 3c65a3a9a44..056c346e22e 100644
--- sys/kern/kern_exec.c
+++ sys/kern/kern_exec.c
@@ -520,7 +520,20 @@ sys_execve(struct proc *p, void *v, register_t *retval)
else
atomic_clearbits_int(&pr->ps_flags, PS_SUGIDEXEC);
- atomic_clearbits_int(&pr->ps_flags, PS_PLEDGE);
+ /*
+ * If the process has a child pledge, this pledge
+ * is copied over. It is not cleared, so the pledge
+ * applies to all descendants, forever.
+ *
+ * Because it's possible to call pledge(NULL, "child"),
+ * which leaves pledge unset in the parent, the pledge bit
+ * needs to be set explicitly here
+ */
+ if (pr->ps_flags & PS_EPLEDGE) {
+ pr->ps_pledge = pr->ps_execpledge;
+ atomic_setbits_int(&pr->ps_flags, PS_PLEDGE);
+ } else
+ atomic_clearbits_int(&pr->ps_flags, PS_PLEDGE);
/*
* deal with set[ug]id.
diff --git sys/kern/kern_pledge.c sys/kern/kern_pledge.c
index 7cf81613b47..2ffe2dfb495 100644
--- sys/kern/kern_pledge.c
+++ sys/kern/kern_pledge.c
@@ -84,6 +84,7 @@
#endif
uint64_t pledgereq_flags(const char *req);
+int pledgereq_parse(struct proc *p, const char *user_flags, uint64_t *flagp);
int canonpath(const char *input, char *buf, size_t bufsize);
/* #define DEBUG_PLEDGE */
@@ -398,44 +399,16 @@ sys_pledge(struct proc *p, void *v, register_t *retval)
{
struct sys_pledge_args /* {
syscallarg(const char *)request;
- syscallarg(const char **)paths;
+ syscallarg(const char *)execrequest;
} */ *uap = v;
struct process *pr = p->p_p;
- uint64_t flags = 0;
+ uint64_t flags = 0, execflags = 0;
int error;
if (SCARG(uap, request)) {
- size_t rbuflen;
- char *rbuf, *rp, *pn;
- uint64_t f;
-
- rbuf = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
- error = copyinstr(SCARG(uap, request), rbuf, MAXPATHLEN,
- &rbuflen);
- if (error) {
- free(rbuf, M_TEMP, MAXPATHLEN);
+ error = pledgereq_parse(p, SCARG(uap, request), &flags);
+ if (error)
return (error);
- }
-#ifdef KTRACE
- if (KTRPOINT(p, KTR_STRUCT))
- ktrstruct(p, "pledgereq", rbuf, rbuflen-1);
-#endif
-
- for (rp = rbuf; rp && *rp && error == 0; rp = pn) {
- pn = strchr(rp, ' '); /* find terminator */
- if (pn) {
- while (*pn == ' ')
- *pn++ = '\0';
- }
-
- if ((f = pledgereq_flags(rp)) == 0) {
- free(rbuf, M_TEMP, MAXPATHLEN);
- return (EINVAL);
- }
- flags |= f;
- }
- free(rbuf, M_TEMP, MAXPATHLEN);
-
/*
* if we are already pledged, allow only promises reductions.
* flags doesn't contain flags outside _USERSET: they will be
@@ -446,13 +419,29 @@ sys_pledge(struct proc *p, void *v, register_t *retval)
return (EPERM);
}
- if (SCARG(uap, paths))
- return (EINVAL);
+ if (SCARG(uap, execrequest)) {
+ error = pledgereq_parse(p, SCARG(uap, execrequest), &execflags);
+ if (error)
+ return (error);
+ /* Same as above; Only allow reductions in promises. */
+ if (ISSET(pr->ps_flags, PS_PLEDGE) &&
+ (((flags | pr->ps_execpledge) != pr->ps_execpledge)))
+ return (EPERM);
+ }
if (SCARG(uap, request)) {
pr->ps_pledge = flags;
pr->ps_flags |= PS_PLEDGE;
}
+ /*
+ * if we call pledge(NULL, "childreq"), we do not want to
+ * set the pledge flag on the parent, so this needs to be
+ * set separately.
+ */
+ if (SCARG(uap, execrequest)) {
+ pr->ps_execpledge = execflags;
+ pr->ps_flags |= PS_EPLEDGE;
+ }
return (0);
}
@@ -1350,6 +1339,43 @@ pledge_swapctl(struct proc *p)
return (EPERM);
}
+int
+pledgereq_parse(struct proc *p, const char *user_req, uint64_t *flagp)
+{
+ size_t rbuflen;
+ char *rbuf, *rp, *pn;
+ uint64_t flags = 0;
+ uint64_t f;
+ int error;
+
+ rbuf = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
+ error = copyinstr(user_req, rbuf, MAXPATHLEN, &rbuflen);
+ if (error) {
+ free(rbuf, M_TEMP, MAXPATHLEN);
+ return (error);
+ }
+#ifdef KTRACE
+ if (KTRPOINT(p, KTR_STRUCT))
+ ktrstruct(p, "pledgereq", rbuf, rbuflen-1);
+#endif
+
+ for (rp = rbuf; rp && *rp && error == 0; rp = pn) {
+ pn = strchr(rp, ' '); /* find terminator */
+ if (pn) {
+ while (*pn == ' ')
+ *pn++ = '\0';
+ }
+
+ if ((f = pledgereq_flags(rp)) == 0) {
+ free(rbuf, M_TEMP, MAXPATHLEN);
+ return (EINVAL);
+ }
+ flags |= f;
+ }
+ free(rbuf, M_TEMP, MAXPATHLEN);
+ *flagp = flags;
+ return (0);
+}
/* bsearch over pledgereq. return flags value if found, 0 else */
uint64_t
pledgereq_flags(const char *req_name)
diff --git sys/kern/syscalls.c sys/kern/syscalls.c
index 0bd41a89b71..4786db3404e 100644
--- sys/kern/syscalls.c
+++ sys/kern/syscalls.c
@@ -1,10 +1,10 @@
-/* $OpenBSD: syscalls.c,v 1.188 2017/08/12 00:04:06 tedu Exp $ */
+/* $OpenBSD$ */
/*
* System call names.
*
* DO NOT EDIT-- this file is automatically generated.
- * created from; OpenBSD: syscalls.master,v 1.177 2017/08/12 00:03:10
tedu Exp
+ * created from; OpenBSD: syscalls.master,v 1.178 2017/09/25 23:00:33
espie Exp
*/
char *syscallnames[] = {
diff --git sys/kern/syscalls.master sys/kern/syscalls.master
index 9708ae86afb..44436aba279 100644
--- sys/kern/syscalls.master
+++ sys/kern/syscalls.master
@@ -227,7 +227,8 @@
106 STD { int sys_listen(int s, int backlog); }
107 STD { int sys_chflagsat(int fd, const char *path, \
u_int flags, int atflags); }
-108 STD { int sys_pledge(const char *request, const char
**paths); }
+108 STD { int sys_pledge(const char *request, \
+ const char *execrequest); }
109 STD { int sys_ppoll(struct pollfd *fds, \
u_int nfds, const struct timespec *ts, \
const sigset_t *mask); }
diff --git sys/sys/proc.h sys/sys/proc.h
index d8861f1d896..761479f9fbc 100644
--- sys/sys/proc.h
+++ sys/sys/proc.h
@@ -220,6 +220,7 @@ struct process {
u_short ps_acflag; /* Accounting flags. */
uint64_t ps_pledge;
+ uint64_t ps_execpledge;
int64_t ps_kbind_cookie;
u_long ps_kbind_addr;
@@ -262,6 +263,7 @@ struct process {
#define PS_NOBROADCASTKILL 0x00080000 /* Process excluded from kill
-1. */
#define PS_PLEDGE 0x00100000 /* Has called pledge(2) */
#define PS_WXNEEDED 0x00200000 /* Process may violate W^X */
+#define PS_EPLEDGE 0x00400000 /* Has called pledge(2) */
#define PS_BITS \
("\20" "\01CONTROLT" "\02EXEC" "\03INEXEC" "\04EXITING" "\05SUGID" \
diff --git sys/sys/syscall.h sys/sys/syscall.h
index efdb1c2d6f7..daacd2f9fc7 100644
--- sys/sys/syscall.h
+++ sys/sys/syscall.h
@@ -1,10 +1,10 @@
-/* $OpenBSD: syscall.h,v 1.188 2017/09/25 23:00:33 espie Exp $ */
+/* $OpenBSD$ */
/*
* System call numbers.
*
* DO NOT EDIT-- this file is automatically generated.
- * created from; OpenBSD: syscalls.master,v 1.177 2017/08/12 00:03:10
tedu Exp
+ * created from; OpenBSD: syscalls.master,v 1.178 2017/09/25 23:00:33
espie Exp
*/
/* syscall: "syscall" ret: "int" args: "int" "..." */
@@ -327,7 +327,7 @@
/* syscall: "chflagsat" ret: "int" args: "int" "const char *" "u_int" "int" */
#define SYS_chflagsat 107
-/* syscall: "pledge" ret: "int" args: "const char *" "const char **" */
+/* syscall: "pledge" ret: "int" args: "const char *" "const char *" */
#define SYS_pledge 108
/* syscall: "ppoll" ret: "int" args: "struct pollfd *" "u_int" "const struct
timespec *" "const sigset_t *" */
diff --git sys/sys/syscallargs.h sys/sys/syscallargs.h
index 2af8ebccc9b..91ffe411cf2 100644
--- sys/sys/syscallargs.h
+++ sys/sys/syscallargs.h
@@ -1,10 +1,10 @@
-/* $OpenBSD: syscallargs.h,v 1.191 2017/09/25 23:00:33 espie Exp $ */
+/* $OpenBSD$ */
/*
* System call argument lists.
*
* DO NOT EDIT-- this file is automatically generated.
- * created from; OpenBSD: syscalls.master,v 1.177 2017/08/12 00:03:10
tedu Exp
+ * created from; OpenBSD: syscalls.master,v 1.178 2017/09/25 23:00:33
espie Exp
*/
#ifdef syscallarg
@@ -539,7 +539,7 @@ struct sys_chflagsat_args {
struct sys_pledge_args {
syscallarg(const char *) request;
- syscallarg(const char **) paths;
+ syscallarg(const char *) execrequest;
};
struct sys_ppoll_args {