[patch] default promises for unprivileged processes

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

[patch] default promises for unprivileged processes

Srikant T
Hi,

Sharing my patch in case someone finds it useful. Didn't have much to offer besides donations and CD purchases all these years.

Tested on amd64 6.5-stable (Changes are not arch-dependent) : base + Xorg + vmd + ntpd + sshd + chromium : work fine
Patch is against current src tree as on 2019-06-15.

The current thoroughly done pledge(2) mechanism goes deep in denying dangerous functionality to pledged processes. Depth is apparent in sysctl(2), ioctl(2) and namei(9) scrutiny.

The only issue is that it is voluntary :
1. Attacker's code will not make a pledge.
2. Unpledged software is missing out on this extra security.
3. The model is that the program does all of it's prep-work early and then makes a pledge. But during the prep-work no restrictions are imposed. This is needed for processes which start as root and daemons, but for the rest of unprivileged processes, leaves a window of unrestricted behaviour.


This patch is a kernel change which enforces a default set of promises and a syscall whitelist in the form of promises on all unprivileged processes.


This is how it works :
+ The root processes are left untouched. This is to provide a way via doas(1) to gain restricted functionality. Also ensures that boot and init scripts are not interfered with. Of course, privsep'ed daemons will cotinue to work.

+ We have a default promises list which includes all the functionality needed for Xorg (_x11), chromium, vmd, ntpd and all /bin /sbin tools. This list looks big but reflects reality. Still, better to have additional checks and hard-coded enforcements such as ACCESSPERMS, than not.

+ All subsequent pledge(2) calls are enforced to be subsets of the default promises. This is enforced silently to let over-pledged programs go past pledge(2) calls into program flows where they may not exercise any restricted functionality, which is caught anyway. In short, allow more programs to work for more command line options or control flows without modification. pledge(2) itself is not the end, syscall is, hence.

+ execve(2), set.*uid(2) syscall handlers set the default promises when uid changes to or is non-zero.

+ A syscall whitelist (same as default promises) is enforced early in the chain for unprivileged porcesses. Independant of pledge(2) mechanism, this effectively ensures all unprivileged software is prevented from dangerous operations early. This was optional, but I wanted it this way.

+ Default promises are immutable right now since they are already very comprehensive. For more secure server/firmware requirements, sys admins may want to further tighten it. If folks are interested, this can be exposed as a sysctl setting which can be set early on boot before securelevel is raised to 1 or 2.

+ Removed PLEDGE_ERROR or "error" promise to ensure malicious/compromised process doesn't get to live. Detailed error message gives enough information to know why it aborted. This whole patch is about enforcement rather than voluntary restrictions.


Advantages of using this patch :
+ Everything runs pledged whether explicitly or otherwise (root can be discounted since trying to contain root is futile). Hence, more security than the current default, especially for ports and software from non-OpenBSD sources.

+ Default promises set provides a single knob to control what you want to permit on your machine based on the intended functionality.

+ Slightly more informative pledge violation error messages


Disadvantages of using this patch :
+ Available syscalls list has shrunk a little and they have become more restrictive. The kernel contract with software changed since this is now default for all unprivileged processes.

+ Current assumptions of programmers are not exactly correct anymore. Those who use pledge(2) understand what they are getting into, but default promises changes that for those who didn't pledge(2) or don't care or can't pledge(2).

+ May induce people to open up their doas.conf too much or turn on setuid bit more.

+ Of course, compiling your own kernel may reduce chances of getting help on mailing lists.


Do let me know if modifications are needed or there are any questions.
Also have the same patch ready against 6.5-stable if needed.

Srikant T.
--


--- ./sys/arch/amd64/amd64/vmm.c.orig Sat Jun 15 13:49:35 2019
+++ ./sys/arch/amd64/amd64/vmm.c Sat Jun 15 15:26:42 2019
@@ -763,7 +763,7 @@ vm_find(uint32_t id, struct vm **res)
  if (((p->p_p->ps_pledge &
     (PLEDGE_VMM|PLEDGE_PROC)) == PLEDGE_VMM) &&
     (vm->vm_creator_pid != p->p_p->ps_pid))
- return (pledge_fail(p, EPERM, PLEDGE_VMM));
+ return (pledge_fail(p, EPERM, PLEDGE_VMM, "vm_find"));
  *res = vm;
  return (0);
  }
--- ./sys/kern/kern_pledge.c.orig Sat Jun 15 13:50:39 2019
+++ ./sys/kern/kern_pledge.c Sat Jun 15 15:26:42 2019
@@ -212,6 +212,7 @@ const uint64_t pledge_syscalls[SYS_MAXSYSCALL] = {
 
  [SYS_fstat] = PLEDGE_STDIO,
  [SYS_fsync] = PLEDGE_STDIO,
+ [SYS_sync] = PLEDGE_STDIO,
 
  [SYS_setsockopt] = PLEDGE_STDIO, /* narrow whitelist */
  [SYS_getsockopt] = PLEDGE_STDIO, /* narrow whitelist */
@@ -225,6 +226,7 @@ const uint64_t pledge_syscalls[SYS_MAXSYSCALL] = {
  [SYS_dup3] = PLEDGE_STDIO,
  [SYS_closefrom] = PLEDGE_STDIO,
  [SYS_shutdown] = PLEDGE_STDIO,
+ [SYS_reboot] = PLEDGE_STDIO,
  [SYS_fchdir] = PLEDGE_STDIO, /* XXX consider tightening */
 
  [SYS_pipe] = PLEDGE_STDIO,
@@ -258,6 +260,7 @@ const uint64_t pledge_syscalls[SYS_MAXSYSCALL] = {
  [SYS_adjtime] = PLEDGE_STDIO,   /* setting requires "settime" */
  [SYS_adjfreq] = PLEDGE_SETTIME,
  [SYS_settimeofday] = PLEDGE_SETTIME,
+ [SYS_clock_settime] = PLEDGE_SETTIME,
 
  /*
  * Needed by threaded programs
@@ -291,6 +294,7 @@ const uint64_t pledge_syscalls[SYS_MAXSYSCALL] = {
  [SYS_setresgid] = PLEDGE_ID,
  [SYS_setgroups] = PLEDGE_ID,
  [SYS_setlogin] = PLEDGE_ID,
+ [SYS_chroot] = PLEDGE_ID,
 
  [SYS_unveil] = PLEDGE_UNVEIL,
 
@@ -360,10 +364,16 @@ const uint64_t pledge_syscalls[SYS_MAXSYSCALL] = {
  [SYS_accept4] = PLEDGE_INET | PLEDGE_UNIX,
  [SYS_accept] = PLEDGE_INET | PLEDGE_UNIX,
  [SYS_getpeername] = PLEDGE_INET | PLEDGE_UNIX,
+
+ [SYS_setrtable] = PLEDGE_WROUTE,
 
  [SYS_flock] = PLEDGE_FLOCK | PLEDGE_YPACTIVE,
 
- [SYS_swapctl] = PLEDGE_VMINFO, /* XXX should limit to "get" operations */
+ [SYS_swapctl] = PLEDGE_VMINFO, /* XXX should limit to "get" ops */
+
+ [SYS_mount] = PLEDGE_MOUNT,
+ [SYS_unmount] = PLEDGE_MOUNT,
+
 };
 
 static const struct {
@@ -406,9 +416,41 @@ static const struct {
  { "vmm", PLEDGE_VMM },
  { "wpath", PLEDGE_WPATH },
  { "wroute", PLEDGE_WROUTE },
+ { "mount", PLEDGE_MOUNT },
 };
 
 int
+is_unpledged_superuser(struct proc *p) {
+ return ((0 == (p->p_p->ps_flags & PS_PLEDGE))
+ && (0 == p->p_ucred->cr_uid));
+}
+
+int
+is_unpledged_regular_user(struct proc *p) {
+ return ((0 == (p->p_p->ps_flags & PS_PLEDGE))
+ && (p->p_ucred->cr_uid != 0));
+}
+
+int
+is_unpledged_xorg(struct proc *p) {
+ return ((0 == (p->p_p->ps_flags & PS_PLEDGE))
+ && (35 == p->p_ucred->cr_uid));
+}
+
+void
+set_default_promises(struct proc *p) {
+ /* XXX cludge to let Xorg function */
+ if (35 == p->p_ucred->cr_uid)
+ return;
+
+ if (p->p_ucred->cr_uid != 0) {
+ p->p_p->ps_pledge = DEFAULT_PROMISES;
+ p->p_p->ps_flags |= PS_PLEDGE;
+ }
+ return;
+}
+
+int
 parsepledges(struct proc *p, const char *kname, const char *promises, u_int64_t *fp)
 {
  size_t rbuflen;
@@ -462,16 +504,15 @@ sys_pledge(struct proc *p, void *v, register_t *retval
  if (error)
  return (error);
 
- /* In "error" mode, ignore promise increase requests,
- * but accept promise decrease requests */
- if (ISSET(pr->ps_flags, PS_PLEDGE) &&
-    (pr->ps_pledge & PLEDGE_ERROR))
- promises &= (pr->ps_pledge & PLEDGE_USERSET);
-
  /* Only permit reductions */
- if (ISSET(pr->ps_flags, PS_PLEDGE) &&
-    (((promises | pr->ps_pledge) != pr->ps_pledge)))
- return (EPERM);
+/*
+ * Being lenient with over-pledged software. We enforce DEFAULT_PROMISES next
+ * Uncomment this if you remove DEFAULT_PROMISES
+ *
+ * if (ISSET(pr->ps_flags, PS_PLEDGE) &&
+ *    (((promises | pr->ps_pledge) != pr->ps_pledge)))
+ * return (EPERM);
+ */
  }
  if (SCARG(uap, execpromises)) {
  error = parsepledges(p, "pledgeexecreq",
@@ -480,13 +521,21 @@ sys_pledge(struct proc *p, void *v, register_t *retval
  return (error);
 
  /* Only permit reductions */
- if (ISSET(pr->ps_flags, PS_EXECPLEDGE) &&
-    (((execpromises | pr->ps_execpledge) != pr->ps_execpledge)))
- return (EPERM);
+/*
+ * Being lenient with over-pledged software. We enforce DEFAULT_PROMISES next
+ * Uncomment this if you remove DEFAULT_PROMISES
+ *
+ * if (ISSET(pr->ps_flags, PS_EXECPLEDGE) &&
+ *    (((execpromises | pr->ps_execpledge) != pr->ps_execpledge)))
+ * return (EPERM);
+ */
  }
 
  if (SCARG(uap, promises)) {
  pr->ps_pledge = promises;
+ /* Enforce DEFAULT_PROMISES silently */
+ if (suser(p) != 0)
+ p->p_p->ps_pledge &= DEFAULT_PROMISES;
  pr->ps_flags |= PS_PLEDGE;
  /*
  * Kill off unveil and drop unveil vnode refs if we no
@@ -499,6 +548,9 @@ sys_pledge(struct proc *p, void *v, register_t *retval
  }
  if (SCARG(uap, execpromises)) {
  pr->ps_execpledge = execpromises;
+ /* Enforce DEFAULT_PROMISES silently */
+ if (suser(p) != 0)
+ p->p_p->ps_execpledge &= DEFAULT_PROMISES;
  pr->ps_flags |= PS_EXECPLEDGE;
  }
  return (0);
@@ -516,15 +568,35 @@ pledge_syscall(struct proc *p, int code, uint64_t *tva
  if (pledge_syscalls[code] == PLEDGE_ALWAYS)
  return (0);
 
- if (p->p_p->ps_pledge & pledge_syscalls[code])
+ /* We don't try to contain root processes */
+ if (is_unpledged_superuser(p))
  return (0);
 
- *tval = pledge_syscalls[code];
- return (EPERM);
+ /* XXX cludge to let Xorg function */
+ if (is_unpledged_xorg(p))
+ return (0);
+
+ /*
+ * syscall whitelist in the form of promises
+ * Has nothing to do with the pledge mechanism
+ */
+ if ((suser(p) != 0)
+ && (0 == (pledge_syscalls[code] & DEFAULT_PROMISES))) {
+ *tval = pledge_syscalls[code];
+ return (EPERM);
+ }
+
+ if (ISSET(p->p_p->ps_flags, PS_PLEDGE)
+ && (0 == (p->p_p->ps_pledge & pledge_syscalls[code]))) {
+ *tval = pledge_syscalls[code];
+ return (EPERM);
+ }
+
+ return (0);
 }
 
 int
-pledge_fail(struct proc *p, int error, uint64_t code)
+pledge_fail(struct proc *p, int error, uint64_t code, char *caller)
 {
  char *codes = "";
  int i;
@@ -540,12 +612,17 @@ pledge_fail(struct proc *p, int error, uint64_t code)
  if (KTRPOINT(p, KTR_PLEDGE))
  ktrpledge(p, error, code, p->p_pledge_syscall);
 #endif
- if (p->p_p->ps_pledge & PLEDGE_ERROR)
- return (ENOSYS);
 
  KERNEL_LOCK();
- log(LOG_ERR, "%s[%d]: pledge \"%s\", syscall %d\n",
-    p->p_p->ps_comm, p->p_p->ps_pid, codes, p->p_pledge_syscall);
+ if (ISSET(p->p_p->ps_flags, PS_PLEDGE)) {
+ log(LOG_ERR, "%s[uid:%d][pid:%d][pledged]: \"%s\", syscall %d, promises 0x%llx, caller %s\n",
+ p->p_p->ps_comm, p->p_ucred->cr_uid, p->p_p->ps_pid,
+ codes, p->p_pledge_syscall, p->p_p->ps_pledge, caller);
+ } else {
+ log(LOG_ERR, "%s[uid:%d][pid:%d][NOT pledged]: \"%s\", syscall %d, promises 0x%llx, caller %s\n",
+ p->p_p->ps_comm, p->p_ucred->cr_uid, p->p_p->ps_pid,
+ codes, p->p_pledge_syscall, p->p_p->ps_pledge, caller);
+ }
  p->p_p->ps_acflag |= APLEDGE;
 
  /* Send uncatchable SIGABRT for coredump */
@@ -562,7 +639,9 @@ pledge_fail(struct proc *p, int error, uint64_t code)
 
 /*
  * Need to make it more obvious that one cannot get through here
- * without the right flags set
+ * without the right flags set.
+ *
+ * This gets called from namei() to handle pledge related checks
  */
 int
 pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
@@ -570,12 +649,18 @@ pledge_namei(struct proc *p, struct nameidata *ni, cha
  char path[PATH_MAX];
  int error;
 
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0 ||
-    (p->p_p->ps_flags & PS_COREDUMP))
+ if (ISSET(p->p_p->ps_flags, PS_COREDUMP)
+ || !ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
+ /*
+ * ni_pledge is not populated for initial procs : swapper and init
+ * So, we ignore pledge checks for these
+ */
+ if (p->p_p->ps_pid <= 1)
+ return (0);
  if (ni->ni_pledge == 0)
- panic("pledge_namei: ni_pledge");
+ panic("pledge_namei: ni_pledge is not initialized");
 
  /*
  * We set the BYPASSUNVEIL flag to skip unveil checks
@@ -628,7 +713,7 @@ pledge_namei(struct proc *p, struct nameidata *ni, cha
  ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
  return (0);
  } else
- return (pledge_fail(p, error, PLEDGE_GETPW));
+ return (pledge_fail(p, error, PLEDGE_GETPW, "pledge_namei:getpw"));
  }
  break;
  case SYS_open:
@@ -743,7 +828,7 @@ pledge_namei(struct proc *p, struct nameidata *ni, cha
  * ps_pledge.
  */
  if (ni->ni_pledge & ~p->p_p->ps_pledge)
- return (pledge_fail(p, EPERM, (ni->ni_pledge & ~p->p_p->ps_pledge)));
+ return (pledge_fail(p, EPERM, (ni->ni_pledge & ~p->p_p->ps_pledge), "pledge_namei:ni_pledge==ps_pledge"));
 
  /* continue, and check unveil if present */
  return (0);
@@ -757,10 +842,11 @@ pledge_recvfd(struct proc *p, struct file *fp)
 {
  struct vnode *vp;
 
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
+
  if ((p->p_p->ps_pledge & PLEDGE_RECVFD) == 0)
- return pledge_fail(p, EPERM, PLEDGE_RECVFD);
+ return pledge_fail(p, EPERM, PLEDGE_RECVFD, "pledge_recvfd:pledge_chk");
 
  switch (fp->f_type) {
  case DTYPE_SOCKET:
@@ -773,7 +859,7 @@ pledge_recvfd(struct proc *p, struct file *fp)
  if (vp->v_type != VDIR)
  return (0);
  }
- return pledge_fail(p, EINVAL, PLEDGE_RECVFD);
+ return pledge_fail(p, EINVAL, PLEDGE_RECVFD, "pledge_recvfd:default_deny");
 }
 
 /*
@@ -784,10 +870,11 @@ pledge_sendfd(struct proc *p, struct file *fp)
 {
  struct vnode *vp;
 
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
+
  if ((p->p_p->ps_pledge & PLEDGE_SENDFD) == 0)
- return pledge_fail(p, EPERM, PLEDGE_SENDFD);
+ return pledge_fail(p, EPERM, PLEDGE_SENDFD, "pledge_sendfd:pledge_chk");
 
  switch (fp->f_type) {
  case DTYPE_SOCKET:
@@ -801,7 +888,7 @@ pledge_sendfd(struct proc *p, struct file *fp)
  return (0);
  break;
  }
- return pledge_fail(p, EINVAL, PLEDGE_SENDFD);
+ return pledge_fail(p, EINVAL, PLEDGE_SENDFD, "pledge_sendfd:default_deny");
 }
 
 int
@@ -810,11 +897,11 @@ pledge_sysctl(struct proc *p, int miblen, int *mib, vo
  char buf[80];
  int i;
 
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
  if (new)
- return pledge_fail(p, EFAULT, 0);
+ return pledge_fail(p, EFAULT, 0, "pledge_sysctl:new");
 
  /* routing table observation */
  if ((p->p_p->ps_pledge & PLEDGE_ROUTE)) {
@@ -988,13 +1075,13 @@ pledge_sysctl(struct proc *p, int miblen, int *mib, vo
  }
  log(LOG_ERR, "%s\n", buf);
 
- return pledge_fail(p, EINVAL, 0);
+ return pledge_fail(p, EINVAL, 0, "pledge_sysctl:default_deny");
 }
 
 int
 pledge_chown(struct proc *p, uid_t uid, gid_t gid)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
  if (p->p_p->ps_pledge & PLEDGE_CHOWNUID)
@@ -1012,7 +1099,7 @@ pledge_adjtime(struct proc *p, const void *v)
 {
  const struct timeval *delta = v;
 
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
  if ((p->p_p->ps_pledge & PLEDGE_SETTIME))
@@ -1025,14 +1112,14 @@ pledge_adjtime(struct proc *p, const void *v)
 int
 pledge_sendit(struct proc *p, const void *to)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
  if ((p->p_p->ps_pledge & (PLEDGE_INET | PLEDGE_UNIX | PLEDGE_DNS | PLEDGE_YPACTIVE)))
  return (0); /* may use address */
  if (to == NULL)
  return (0); /* behaves just like write */
- return pledge_fail(p, EPERM, PLEDGE_INET);
+ return pledge_fail(p, EPERM, PLEDGE_INET, "pledge_sendit");
 }
 
 int
@@ -1041,7 +1128,7 @@ pledge_ioctl(struct proc *p, long com, struct file *fp
  struct vnode *vp = NULL;
  int error = EPERM;
 
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
  /*
@@ -1308,13 +1395,13 @@ pledge_ioctl(struct proc *p, long com, struct file *fp
  }
 #endif
 
- return pledge_fail(p, error, PLEDGE_TTY);
+ return pledge_fail(p, error, PLEDGE_TTY, "pledge_ioctl:default_deny");
 }
 
 int
 pledge_sockopt(struct proc *p, int set, int level, int optname)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
  /* Always allow these, which are too common to reject */
@@ -1329,7 +1416,7 @@ pledge_sockopt(struct proc *p, int set, int level, int
  }
 
  if ((p->p_p->ps_pledge & (PLEDGE_INET|PLEDGE_UNIX|PLEDGE_DNS|PLEDGE_YPACTIVE)) == 0)
- return pledge_fail(p, EPERM, PLEDGE_INET);
+ return pledge_fail(p, EPERM, PLEDGE_INET, "pledge_sockopt:pledge_chk1");
  /* In use by some service libraries */
  switch (level) {
  case SOL_SOCKET:
@@ -1372,18 +1459,18 @@ pledge_sockopt(struct proc *p, int set, int level, int
  }
 
  if ((p->p_p->ps_pledge & (PLEDGE_INET|PLEDGE_UNIX)) == 0)
- return pledge_fail(p, EPERM, PLEDGE_INET);
+ return pledge_fail(p, EPERM, PLEDGE_INET, "pledge_sockopt:pledge_chk2");
  switch (level) {
  case SOL_SOCKET:
  switch (optname) {
  case SO_RTABLE:
- return pledge_fail(p, EINVAL, PLEDGE_INET);
+ return pledge_fail(p, EINVAL, PLEDGE_INET, "pledge_sockopt:SO_RTABLE");
  }
  return (0);
  }
 
  if ((p->p_p->ps_pledge & PLEDGE_INET) == 0)
- return pledge_fail(p, EPERM, PLEDGE_INET);
+ return pledge_fail(p, EPERM, PLEDGE_INET, "pledge_sockopt:pledge_chk3");
  switch (level) {
  case IPPROTO_TCP:
  switch (optname) {
@@ -1445,19 +1532,19 @@ pledge_sockopt(struct proc *p, int set, int level, int
  case IPPROTO_ICMPV6:
  break;
  }
- return pledge_fail(p, EPERM, PLEDGE_INET);
+ return pledge_fail(p, EPERM, PLEDGE_INET, "pledge_sockopt:default_deny");
 }
 
 int
 pledge_socket(struct proc *p, int domain, unsigned int state)
 {
- if (! ISSET(p->p_p->ps_flags, PS_PLEDGE))
- return 0;
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
+ return (0);
 
  if (ISSET(state, SS_DNS)) {
  if (ISSET(p->p_p->ps_pledge, PLEDGE_DNS))
  return 0;
- return pledge_fail(p, EPERM, PLEDGE_DNS);
+ return pledge_fail(p, EPERM, PLEDGE_DNS, "pledge_socket:SS_DNS");
  }
 
  switch (domain) {
@@ -1468,33 +1555,34 @@ pledge_socket(struct proc *p, int domain, unsigned int
  if (ISSET(p->p_p->ps_pledge, PLEDGE_INET) ||
     ISSET(p->p_p->ps_pledge, PLEDGE_YPACTIVE))
  return 0;
- return pledge_fail(p, EPERM, PLEDGE_INET);
+ return pledge_fail(p, EPERM, PLEDGE_INET, "pledge_socket:pledge_chk1");
 
  case AF_UNIX:
  if (ISSET(p->p_p->ps_pledge, PLEDGE_UNIX))
  return 0;
- return pledge_fail(p, EPERM, PLEDGE_UNIX);
+ return pledge_fail(p, EPERM, PLEDGE_UNIX, "pledge_socket:pledge_chk2");
  }
 
- return pledge_fail(p, EINVAL, PLEDGE_INET);
+ return pledge_fail(p, EINVAL, PLEDGE_INET, "pledge_socket:default_deny");
 }
 
 int
 pledge_flock(struct proc *p)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
 
  if ((p->p_p->ps_pledge & PLEDGE_FLOCK))
  return (0);
- return (pledge_fail(p, EPERM, PLEDGE_FLOCK));
+ return (pledge_fail(p, EPERM, PLEDGE_FLOCK, "pledge_flock"));
 }
 
 int
 pledge_swapctl(struct proc *p)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
+
  return (EPERM);
 }
 
@@ -1520,35 +1608,38 @@ pledgereq_flags(const char *req_name)
 int
 pledge_fcntl(struct proc *p, int cmd)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
  return (0);
+
  if ((p->p_p->ps_pledge & PLEDGE_PROC) == 0 && cmd == F_SETOWN)
- return pledge_fail(p, EPERM, PLEDGE_PROC);
+ return pledge_fail(p, EPERM, PLEDGE_PROC, "pledge_fcntl");
  return (0);
 }
 
 int
 pledge_kill(struct proc *p, pid_t pid)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
- return 0;
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
+ return (0);
+
  if (p->p_p->ps_pledge & PLEDGE_PROC)
  return 0;
  if (pid == 0 || pid == p->p_p->ps_pid)
  return 0;
- return pledge_fail(p, EPERM, PLEDGE_PROC);
+ return pledge_fail(p, EPERM, PLEDGE_PROC, "pledge_kill");
 }
 
 int
 pledge_protexec(struct proc *p, int prot)
 {
- if ((p->p_p->ps_flags & PS_PLEDGE) == 0)
- return 0;
+ if (!ISSET(p->p_p->ps_flags, PS_PLEDGE))
+ return (0);
+
  /* Before kbind(2) call, ld.so and crt may create EXEC mappings */
  if (p->p_p->ps_kbind_addr == 0 && p->p_p->ps_kbind_cookie == 0)
  return 0;
  if (!(p->p_p->ps_pledge & PLEDGE_PROTEXEC) && (prot & PROT_EXEC))
- return pledge_fail(p, EPERM, PLEDGE_PROTEXEC);
+ return pledge_fail(p, EPERM, PLEDGE_PROTEXEC, "pledge_protexec");
  return 0;
 }
 
--- ./sys/kern/vfs_syscalls.c.orig Sat Jun 15 13:50:40 2019
+++ ./sys/kern/vfs_syscalls.c Sat Jun 15 15:26:42 2019
@@ -136,6 +136,7 @@ sys_mount(struct proc *p, void *v, register_t *retval)
  * Get vnode to be covered
  */
  NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, fspath, p);
+ nd.ni_pledge = PLEDGE_MOUNT;
  if ((error = namei(&nd)) != 0)
  goto fail;
  vp = nd.ni_vp;
@@ -379,6 +380,7 @@ sys_unmount(struct proc *p, void *v, register_t *retva
 
  NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE,
     SCARG(uap, path), p);
+ nd.ni_pledge = PLEDGE_MOUNT;
  if ((error = namei(&nd)) != 0)
  return (error);
  vp = nd.ni_vp;
@@ -826,6 +828,7 @@ sys_chroot(struct proc *p, void *v, register_t *retval
  return (error);
  NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE,
     SCARG(uap, path), p);
+ nd.ni_pledge = PLEDGE_ID;
  if ((error = change_dir(&nd, p)) != 0)
  return (error);
  if (fdp->fd_rdir != NULL) {
@@ -1125,7 +1128,7 @@ doopenat(struct proc *p, int fd, const char *path, int
  }
 
  cmode = ((mode &~ fdp->fd_cmask) & ALLPERMS) &~ S_ISTXT;
- if ((p->p_p->ps_flags & PS_PLEDGE))
+ if (ISSET(p->p_p->ps_flags, PS_PLEDGE))
  cmode &= ACCESSPERMS;
  NDINITAT(&nd, LOOKUP, FOLLOW, UIO_USERSPACE, fd, path, p);
  nd.ni_pledge = ni_pledge;
@@ -2250,7 +2253,7 @@ dofchmodat(struct proc *p, int fd, const char *path, m
 
  if (mode & ~(S_IFMT | ALLPERMS))
  return (EINVAL);
- if ((p->p_p->ps_flags & PS_PLEDGE))
+ if (ISSET(p->p_p->ps_flags, PS_PLEDGE))
  mode &= ACCESSPERMS;
  if (flag & ~AT_SYMLINK_NOFOLLOW)
  return (EINVAL);
@@ -2292,7 +2295,7 @@ sys_fchmod(struct proc *p, void *v, register_t *retval
 
  if (mode & ~(S_IFMT | ALLPERMS))
  return (EINVAL);
- if ((p->p_p->ps_flags & PS_PLEDGE))
+ if (ISSET(p->p_p->ps_flags, PS_PLEDGE))
  mode &= ACCESSPERMS;
 
  if ((error = getvnode(p, SCARG(uap, fd), &fp)) != 0)
--- ./sys/kern/kern_prot.c.orig Sat Jun 15 13:50:39 2019
+++ ./sys/kern/kern_prot.c Sat Jun 15 15:26:42 2019
@@ -49,6 +49,7 @@
 #include <sys/proc.h>
 #include <sys/filedesc.h>
 #include <sys/pool.h>
+#include <sys/pledge.h>
 
 #include <sys/mount.h>
 #include <sys/syscallargs.h>
@@ -413,6 +414,8 @@ sys_setresuid(struct proc *p, void *v, register_t *ret
  pr->ps_ucred = newcred;
  atomic_setbits_int(&p->p_p->ps_flags, PS_SUGID);
 
+ set_default_promises(p);
+
  /* now that we can sleep, transfer proc count to new user */
  if (ruid != (uid_t)-1 && ruid != pruc->cr_ruid) {
  chgproccnt(pruc->cr_ruid, -1);
@@ -680,6 +683,8 @@ sys_setreuid(struct proc *p, void *v, register_t *retv
  pr->ps_ucred = newcred;
  atomic_setbits_int(&p->p_p->ps_flags, PS_SUGID);
 
+ set_default_promises(p);
+
  /* now that we can sleep, transfer proc count to new user */
  if (ruid != (uid_t)-1 && ruid != pruc->cr_ruid) {
  chgproccnt(pruc->cr_ruid, -1);
@@ -736,6 +741,8 @@ sys_setuid(struct proc *p, void *v, register_t *retval
  pr->ps_ucred = newcred;
  atomic_setbits_int(&p->p_p->ps_flags, PS_SUGID);
 
+ set_default_promises(p);
+
  /*
  * Transfer proc count to new user.
  */
@@ -778,6 +785,9 @@ sys_seteuid(struct proc *p, void *v, register_t *retva
  newcred->cr_uid = euid;
  pr->ps_ucred = newcred;
  atomic_setbits_int(&p->p_p->ps_flags, PS_SUGID);
+
+ set_default_promises(p);
+
  crfree(pruc);
  return (0);
 }
--- ./sys/kern/kern_exec.c.orig Sat Jun 15 13:50:39 2019
+++ ./sys/kern/kern_exec.c Sat Jun 15 15:26:42 2019
@@ -537,6 +537,7 @@ sys_execve(struct proc *p, void *v, register_t *retval
  } else {
  atomic_clearbits_int(&pr->ps_flags, PS_PLEDGE);
  pr->ps_pledge = 0;
+ pr->ps_execpledge = 0;
  /* XXX XXX XXX XXX */
  /* Clear our unveil paths out so the child
  * starts afresh
@@ -648,6 +649,8 @@ sys_execve(struct proc *p, void *v, register_t *retval
  pr->ps_ucred = cred;
  crfree(ocred);
  }
+
+ set_default_promises(p);
 
  if (pr->ps_flags & PS_SUGIDEXEC) {
  int i, s = splclock();
--- ./sys/kern/kern_event.c.orig Sat Jun 15 13:50:39 2019
+++ ./sys/kern/kern_event.c Sat Jun 15 15:26:42 2019
@@ -214,9 +214,9 @@ filt_procattach(struct knote *kn)
 {
  struct process *pr;
 
- if ((curproc->p_p->ps_flags & PS_PLEDGE) &&
-    (curproc->p_p->ps_pledge & PLEDGE_PROC) == 0)
- return pledge_fail(curproc, EPERM, PLEDGE_PROC);
+ if (!ISSET(curproc->p_p->ps_pledge, PLEDGE_PROC)
+ && ISSET(curproc->p_p->ps_flags, PS_PLEDGE))
+ return pledge_fail(curproc, EPERM, PLEDGE_PROC, "filt_procattach");
 
  if (kn->kn_id > PID_MAX)
  return ESRCH;
--- ./sys/kern/uipc_syscalls.c.orig Sat Jun 15 13:50:40 2019
+++ ./sys/kern/uipc_syscalls.c Sat Jun 15 15:26:42 2019
@@ -150,8 +150,8 @@ dns_portcheck(struct proc *p, struct socket *so, void
  error = 0;
 #endif
  }
- if (error && p->p_p->ps_flags & PS_PLEDGE)
- return (pledge_fail(p, EPERM, PLEDGE_DNS));
+ if (error && ISSET(p->p_p->ps_flags, PS_PLEDGE))
+ return (pledge_fail(p, EPERM, PLEDGE_DNS, "dns_portcheck"));
  return error;
 }
 
--- ./sys/sys/pledge.h.orig Sat Jun 15 13:50:45 2019
+++ ./sys/sys/pledge.h Sat Jun 15 15:26:42 2019
@@ -63,6 +63,7 @@
 #define PLEDGE_WROUTE 0x0000000800000000ULL /* interface address ioctls */
 #define PLEDGE_UNVEIL 0x0000001000000000ULL /* allow unveil() */
 #define PLEDGE_VIDEO 0x0000002000000000ULL /* video ioctls */
+#define PLEDGE_MOUNT 0x0000004000000000ULL /* mount(2) or unmount(2) */
 
 /*
  * Bits outside PLEDGE_USERSET are used by the kernel itself
@@ -113,14 +114,48 @@ static struct {
  { PLEDGE_WROUTE, "wroute" },
  { PLEDGE_UNVEIL, "unveil" },
  { PLEDGE_VIDEO, "video" },
+ { PLEDGE_MOUNT, "mount" },
  { 0, NULL },
 };
 #endif
 
 #ifdef _KERNEL
 
+#define DEFAULT_PROMISES \
+ ( PLEDGE_STDIO \
+ | PLEDGE_PROC \
+ | PLEDGE_EXEC \
+ | PLEDGE_PROTEXEC \
+ | PLEDGE_UNVEIL \
+ | PLEDGE_TMPPATH \
+ | PLEDGE_TTY \
+ | PLEDGE_RPATH \
+ | PLEDGE_WPATH \
+ | PLEDGE_CPATH \
+ | PLEDGE_FATTR \
+ | PLEDGE_FLOCK \
+ | PLEDGE_ID \
+ | PLEDGE_PS \
+ | PLEDGE_VMINFO \
+ | PLEDGE_CHOWN \
+ | PLEDGE_CHOWNUID \
+ | PLEDGE_INET \
+ | PLEDGE_UNIX \
+ | PLEDGE_DNS \
+ | PLEDGE_MCAST \
+ | PLEDGE_ROUTE \
+ | PLEDGE_RECVFD \
+ | PLEDGE_SENDFD \
+ | PLEDGE_VMM \
+ | PLEDGE_GETPW)
+
+int     is_unpledged_superuser(struct proc *);
+int     is_unpledged_regular_user(struct proc *);
+int is_unpledged_xorg(struct proc *);
+void    set_default_promises(struct proc *);
+
 int pledge_syscall(struct proc *, int, uint64_t *);
-int pledge_fail(struct proc *, int, uint64_t);
+int pledge_fail(struct proc *, int, uint64_t, char *);
 
 struct mbuf;
 struct nameidata;
--- ./sys/sys/syscall_mi.h.orig Sat Jun 15 13:50:46 2019
+++ ./sys/sys/syscall_mi.h Sat Jun 15 15:26:42 2019
@@ -81,11 +81,10 @@ mi_syscall(struct proc *p, register_t code, const stru
 
  if (lock)
  KERNEL_LOCK();
- pledged = (p->p_p->ps_flags & PS_PLEDGE);
- if (pledged && (error = pledge_syscall(p, code, &tval))) {
+ if ((error = pledge_syscall(p, code, &tval))) {
  if (!lock)
  KERNEL_LOCK();
- error = pledge_fail(p, error, tval);
+ error = pledge_fail(p, error, tval, "mi_syscall");
  KERNEL_UNLOCK();
  return (error);
  }
--- ./lib/libc/sys/pledge.2.orig Sat Jun 15 15:45:40 2019
+++ ./lib/libc/sys/pledge.2 Sat Jun 15 18:00:31 2019
@@ -75,6 +75,71 @@ or
 .Ar execpromises
 specifies to not change the current value.
 .Pp
+When
+.Nm pledge
+is called with higher
+.Ar promises
+or
+.Ar execpromises ,
+those changes will be ignored and return success.
+This is useful when a parent enforces
+.Ar execpromises
+but an execve'd child has a different idea.
+.Pp
+All new unprivileged processes are automatically pledged to a set of
+.Ar default promises
+upon creation via
+.Xr execve 2 .
+When a process changes it's
+.Ar effective uid
+to an unprivileged
+.Ar uid
+via system calls:
+.Xr setuid 2 ,
+.Xr seteuid 2 ,
+.Xr setreuid 2 or
+.Xr setresuid 2 ,
+it too is automatically assigned
+.Ar default promises .
+Subsequent calls to
+.Nm pledge
+are only allowed to be a subset of
+.Ar default promises .
+.Bl -ohang -offset indent
+.It Xo
+The current set of
+.Ar default promises
+consists of:
+.Xc
+.It Xo
+.Va stdio ,
+.Va proc ,
+.Va exec ,
+.Va prot_exec ,
+.Va unveil ,
+.Va tmppath ,
+.Va tty ,
+.Va rpath ,
+.Va wpath ,
+.Va cpath ,
+.Va fattr ,
+.Va flock ,
+.Va id ,
+.Va ps ,
+.Va vminfo ,
+.Va chown ,
+.Va inet ,
+.Va unix ,
+.Va dns ,
+.Va mcast ,
+.Va route ,
+.Va recvfd ,
+.Va sendfd ,
+.Va vmm and
+.Va getpw .
+.Xc
+.El
+.Pp
 Some system calls, when allowed, have restrictions applied to them:
 .Bl -ohang -offset indent
 .It Xr access 2 :
@@ -579,20 +644,12 @@ device.
 Allow
 .Xr unveil 2
 to be called.
-.It Va error
-Rather than killing the process upon violation, indicate error with
-.Er ENOSYS .
-.Pp
-Also when
-.Nm pledge
-is called with higher
-.Ar promises
-or
-.Ar execpromises ,
-those changes will be ignored and return success.
-This is useful when a parent enforces
-.Ar execpromises
-but an execve'd child has a different idea.
+.It Va mount
+Allow
+.Xr mount 2
+and
+.Xr unmount 2
+to be called.
 .El
 .Sh RETURN VALUES
 .Rv -std


---
END.

Reply | Threaded
Open this post in threaded view
|

Re: [patch] default promises for unprivileged processes

Theo de Raadt-2
Your diff isn't a change to pledge -- it is completely tearing up
the POSIX rulebook for all programs.

I am not interested in the way it goes.

Reply | Threaded
Open this post in threaded view
|

Re: [patch] default promises for unprivileged processes

Bryan Steele-2
In reply to this post by Srikant T
On Sat, Jun 15, 2019 at 04:05:14PM +0200, Srikant T wrote:
> + /* XXX cludge to let Xorg function */
> + if (35 == p->p_ucred->cr_uid)
> + return;

This is completely unacceptable.

Kludge is spelled with a K.

> ---
> END.
>
>