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 {

Reply via email to