/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8                                :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2022 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or 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 "libc/calls/pledge.h"
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/landlock.h"
#include "libc/calls/pledge.internal.h"
#include "libc/calls/struct/rlimit.h"
#include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/seccomp.internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/sysinfo.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/elf/def.h"
#include "libc/elf/elf.h"
#include "libc/elf/struct/ehdr.h"
#include "libc/elf/struct/phdr.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/promises.internal.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/macros.internal.h"
#include "libc/math.h"
#include "libc/mem/gc.internal.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/kcpuids.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ioprio.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/pr.h"
#include "libc/sysv/consts/prio.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/rlim.h"
#include "libc/sysv/consts/rlimit.h"
#include "libc/sysv/consts/sched.h"
#include "libc/sysv/errfuns.h"
#include "libc/x/x.h"
#include "third_party/getopt/getopt.internal.h"

// MANUALLY TESTED BY RUNNING
//
//     test/tool/build/pledge_test.sh
//

__static_yoink("strerror_wr");
__static_yoink("zipos");

#define USAGE \
  "\
usage: pledge.com [-hnN] PROG ARGS...\n\
  -h              show help\n\
  -g GID          call setgid()\n\
  -u UID          call setuid()\n\
  -c PATH         call chroot()\n\
  -v [PERM:]PATH  call unveil(PATH, PERM[rwxc])\n\
  -V              disable unveiling (only pledge)\n\
  -q              disable stderr violation logging\n\
  -k              kill process rather than eperm'ing\n\
  -n              set maximum niceness\n\
  -D              don't drop capabilities\n\
  -N              don't normalize file descriptors\n\
  -C SECS         set cpu limit [default: inherited]\n\
  -M BYTES        set virtual memory limit [default: 4gb]\n\
  -O FILES        set file descriptor limit [default: 64]\n\
  -P PROCS        set process limit [default: preexisting + cpus]\n\
  -F BYTES        set individual file size limit [default: 4gb]\n\
  -T pledge       exits 0 if pledge() is supported by host system\n\
  -T unveil       exits 0 if unveil() is supported by host system\n\
  -p PLEDGE       may contain any of following separated by spaces\n\
     - stdio: allow stdio and benign system calls\n\
     - rpath: read-only path ops\n\
     - wpath: write path ops\n\
     - cpath: create path ops\n\
     - dpath: create special files\n\
     - chown: allows file ownership changes\n\
     - flock: file locks\n\
     - tty: terminal ioctls\n\
     - recvfd: allow SCM_RIGHTS\n\
     - sendfd: allow SCM_RIGHTS\n\
     - fattr: allow changing some struct stat bits\n\
     - inet: allow IPv4 and IPv6\n\
     - unix: allow local sockets\n\
     - id: allow setuid and friends\n\
     - dns: allow dns and related files\n\
     - proc: allow process and thread creation\n\
     - exec: implied by default\n\
     - prot_exec: allow creating executable memory\n\
     - vminfo: allows /proc/stat, /proc/self/maps, etc.\n\
     - tmppath: allows /tmp, $TMPPATH, lstat, unlink\n\
\n\
pledge.com v1.8\n\
copyright 2022 justine alexandra roberts tunney\n\
notice licenses are embedded in the binary\n\
https://twitter.com/justinetunney\n\
https://linkedin.com/in/jtunney\n\
https://justine.lol/pledge/\n\
https://github.com/jart\n\
\n\
this program lets you launch linux commands in a sandbox that's\n\
inspired by the design of openbsd's pledge() system call. Visit\n\
the https://justine.lol/pledge/ page for online documentation.\n\
\n\
"

int g_gflag;
int g_uflag;
int g_kflag;
int g_hflag;
bool g_nice;
bool g_qflag;
bool isdynamic;
bool g_noclose;
long g_cpuquota;
long g_fszquota;
long g_nfdquota;
long g_memquota;
long g_proquota;
long g_dontdrop;
long g_dontunveil;
const char *g_test;
const char *g_chroot;
const char *g_promises;
char dsopath[PATH_MAX];
char tmppath[PATH_MAX];

struct {
  int n;
  char **p;
} unveils;

static void GetOpts(int argc, char *argv[]) {
  int opt;
  struct sysinfo si;
  g_promises = 0;
  g_nfdquota = 64;
  g_fszquota = 256 * 1000 * 1000;
  if (!sysinfo(&si)) {
    g_memquota = si.totalram;
    g_proquota = _getcpucount() + si.procs;
  } else {
    g_proquota = _getcpucount() * 100;
    g_memquota = 4L * 1024 * 1024 * 1024;
  }
  while ((opt = getopt(argc, argv, "hnqkNVT:p:u:g:c:C:D:P:M:F:O:v:")) != -1) {
    switch (opt) {
      case 'n':
        g_nice = true;
        break;
      case 'q':
        g_qflag = true;
        break;
      case 'k':
        g_kflag = true;
        break;
      case 'N':
        g_noclose = true;
        break;
      case 'D':
        g_dontdrop = true;
        break;
      case 'V':
        g_dontunveil = true;
        break;
      case 'T':
        g_test = optarg;
        break;
      case 'c':
        g_chroot = optarg;
        break;
      case 'g':
        g_gflag = atoi(optarg);
        break;
      case 'u':
        g_uflag = atoi(optarg);
        break;
      case 'C':
        g_cpuquota = atoi(optarg);
        break;
      case 'P':
        g_proquota = atoi(optarg);
        break;
      case 'O':
        g_nfdquota = atoi(optarg);
        break;
      case 'F':
        errno = 0;
        g_fszquota = sizetol(optarg, 1000);
        if (errno) {
          kprintf("error: invalid size: -F %s\n", optarg);
          exit(1);
        }
        break;
      case 'M':
        errno = 0;
        g_memquota = sizetol(optarg, 1024);
        if (errno) {
          kprintf("error: invalid size: -F %s\n", optarg);
          exit(1);
        }
        break;
      case 'p':
        if (g_promises) {
          g_promises = xstrcat(g_promises, ' ', optarg);
        } else {
          g_promises = optarg;
        }
        break;
      case 'v':
        unveils.p = realloc(unveils.p, ++unveils.n * sizeof(*unveils.p));
        unveils.p[unveils.n - 1] = optarg;
        break;
      case 'h':
      case '?':
        write(1, USAGE, sizeof(USAGE) - 1);
        exit(0);
      default:
        write(2, USAGE, sizeof(USAGE) - 1);
        exit(64);
    }
  }
  if (!g_promises) {
    g_promises = "stdio rpath";
  }
}

const char *prog;
char pathbuf[PATH_MAX];
struct pollfd pfds[256];

static bool SupportsLandlock(void) {
  int e = errno;
  bool r = landlock_create_ruleset(0, 0, LANDLOCK_CREATE_RULESET_VERSION) >= 0;
  errno = e;
  return r;
}

int GetPollMaxFds(void) {
  int n;
  struct rlimit rl;
  if (getrlimit(RLIMIT_NOFILE, &rl) != -1) {
    n = rl.rlim_cur;
  } else {
    n = 64;
  }
  return MIN(ARRAYLEN(pfds), MAX(3, n));
}

void NormalizeFileDescriptors(void) {
  int e, i, n, fd;
  n = GetPollMaxFds();
  e = errno;
  closefrom(3);  // more secure if linux 5.9+
  errno = e;
  for (i = 0; i < n; ++i) {
    pfds[i].fd = i;
    pfds[i].events = POLLIN;
  }
  if (poll(pfds, n, 0) == -1) {
    kprintf("error: poll() failed: %m\n");
    exit(1);
  }
  for (i = 0; i < 3; ++i) {
    if (pfds[i].revents & POLLNVAL) {
      if ((fd = open("/dev/null", O_RDWR)) == -1) {
        kprintf("error: open(\"/dev/null\") failed: %m\n");
        exit(2);
      }
      if (fd != i) {
        kprintf("error: open() is broken: %d vs. %d\n", fd, i);
        exit(3);
      }
    }
  }
  for (i = 3; i < n; ++i) {
    if (~pfds[i].revents & POLLNVAL) {
      if (close(pfds[i].fd) == -1) {
        kprintf("error: close(%d) failed: %m\n", pfds[i].fd);
        exit(4);
      }
    }
  }
}

int SetLimit(int r, long lo, long hi) {
  struct rlimit old;
  struct rlimit lim = {lo, hi};
  if (r < 0 || r >= RLIM_NLIMITS) return 0;
  if (!setrlimit(r, &lim)) return 0;
  if (getrlimit(r, &old)) return -1;
  lim.rlim_cur = MIN(lim.rlim_cur, old.rlim_max);
  lim.rlim_max = MIN(lim.rlim_max, old.rlim_max);
  return setrlimit(r, &lim);
}

static int GetBaseCpuFreqMhz(void) {
  return KCPUIDS(16H, EAX) & 0x7fff;
}

int SetCpuLimit(int secs) {
#ifdef __x86_64__
  int mhz, lim;
  if (secs <= 0) return 0;
  if (!(mhz = GetBaseCpuFreqMhz())) return eopnotsupp();
  lim = ceil(3100. / mhz * secs);
  return SetLimit(RLIMIT_CPU, lim, lim);
#else
  return 0;
#endif
}

bool PathExists(const char *path) {
  int err;
  struct stat st;
  if (path) {
    err = errno;
    if (!stat(path, &st)) {
      return true;
    } else {
      errno = err;
      return false;
    }
  } else {
    return false;
  }
}

void Unveil(const char *path, const char *perm) {
  if (unveil(path, perm) == -1) {
    kprintf("error: unveil(%#s, %#s) failed: %m\n", path, perm);
    _Exit(20);
  }
}

int UnveilIfExists(const char *path, const char *perm) {
  int err;
  if (path) {
    err = errno;
    if (unveil(path, perm) != -1) {
      return 0;
    } else if (errno == ENOENT) {
      errno = err;
    } else {
      kprintf("error: unveil(%#s, %#s) failed: %m\n", path, perm);
      _Exit(20);
    }
  }
  return -1;
}

void ApplyFilesystemPolicy(unsigned long ipromises) {
  const char *p;

  if (g_dontunveil) return;
  if (!SupportsLandlock()) return;

  Unveil(prog, "rx");

  if (isdynamic) {
    Unveil(dsopath, "rx");
    UnveilIfExists("/lib", "rx");
    UnveilIfExists("/lib64", "rx");
    UnveilIfExists("/usr/lib", "rx");
    UnveilIfExists("/usr/lib64", "rx");
    UnveilIfExists("/usr/local/lib", "rx");
    UnveilIfExists("/usr/local/lib64", "rx");
    UnveilIfExists("/etc/ld-musl-x86_64.path", "r");
    UnveilIfExists("/etc/ld.so.conf", "r");
    UnveilIfExists("/etc/ld.so.cache", "r");
    UnveilIfExists("/etc/ld.so.conf.d", "r");
    UnveilIfExists("/etc/ld.so.preload", "r");
  }

  if (~ipromises & (1ul << PROMISE_STDIO)) {
    UnveilIfExists("/dev/fd", "r");
    UnveilIfExists("/dev/log", "w");
    UnveilIfExists("/dev/zero", "r");
    UnveilIfExists("/dev/null", "rw");
    UnveilIfExists("/dev/full", "rw");
    UnveilIfExists("/dev/stdin", "rw");
    UnveilIfExists("/dev/stdout", "rw");
    UnveilIfExists("/dev/stderr", "rw");
    UnveilIfExists("/dev/urandom", "r");
    UnveilIfExists("/etc/localtime", "r");
    UnveilIfExists("/proc/self/fd", "rw");
    UnveilIfExists("/proc/self/stat", "r");
    UnveilIfExists("/proc/self/status", "r");
    UnveilIfExists("/usr/share/locale", "r");
    UnveilIfExists("/proc/self/cmdline", "r");
    UnveilIfExists("/usr/share/zoneinfo", "r");
    UnveilIfExists("/proc/sys/kernel/version", "r");
    UnveilIfExists("/usr/share/common-licenses", "r");
    UnveilIfExists("/proc/sys/kernel/ngroups_max", "r");
    UnveilIfExists("/proc/sys/kernel/cap_last_cap", "r");
    UnveilIfExists("/proc/sys/vm/overcommit_memory", "r");
  }

  if (~ipromises & (1ul << PROMISE_INET)) {
    UnveilIfExists("/etc/ssl/certs/ca-certificates.crt", "r");
  }

  if (~ipromises & (1ul << PROMISE_RPATH)) {
    UnveilIfExists("/proc/filesystems", "r");
  }

  if (~ipromises & (1ul << PROMISE_DNS)) {
    UnveilIfExists("/etc/hosts", "r");
    UnveilIfExists("/etc/hostname", "r");
    UnveilIfExists("/etc/services", "r");
    UnveilIfExists("/etc/protocols", "r");
    UnveilIfExists("/etc/resolv.conf", "r");
  }

  if (~ipromises & (1ul << PROMISE_TTY)) {
    UnveilIfExists(ttyname(0), "rw");
    UnveilIfExists("/dev/tty", "rw");
    UnveilIfExists("/dev/console", "rw");
    UnveilIfExists("/etc/terminfo", "r");
    UnveilIfExists("/usr/lib/terminfo", "r");
    UnveilIfExists("/usr/share/terminfo", "r");
  }

  if (~ipromises & (1ul << PROMISE_PROT_EXEC)) {
    if (UnveilIfExists("/usr/bin/ape", "rx") == -1) {
      if ((p = getenv("TMPDIR"))) {
        UnveilIfExists(xjoinpaths(p, ".ape"), "rx");
      }
      if ((p = getenv("HOME"))) {
        UnveilIfExists(xjoinpaths(p, ".ape"), "rx");
      }
    }
  }

  if (~ipromises & (1ul << PROMISE_VMINFO)) {
    UnveilIfExists("/proc/stat", "r");
    UnveilIfExists("/proc/meminfo", "r");
    UnveilIfExists("/proc/cpuinfo", "r");
    UnveilIfExists("/proc/diskstats", "r");
    UnveilIfExists("/proc/self/maps", "r");
    UnveilIfExists("/sys/devices/system/cpu", "r");
  }

  if (~ipromises & (1ul << PROMISE_TMPPATH)) {
    UnveilIfExists("/tmp", "rwc");
    UnveilIfExists(getenv("TMPPATH"), "rwc");
  }

  for (int i = 0; i < unveils.n; ++i) {
    char *s, *t;
    const char *path;
    const char *perm;
    s = unveils.p[i];
    if ((t = strchr(s, ':'))) {
      *t = 0;
      perm = s;
      path = t + 1;
    } else {
      perm = "r";
      path = s;
    }
    UnveilIfExists(path, perm);
  }

  if (unveil(0, 0) == -1) {
    kprintf("error: unveil(0, 0) failed: %m\n");
    _Exit(20);
  }
}

void DropCapabilities(void) {
  int e, i;
  for (e = errno, i = 0;; ++i) {
    if (prctl(PR_CAPBSET_DROP, i) == -1) {
      if (errno == EINVAL || errno == EPERM) {
        errno = e;
        break;
      } else {
        kprintf("error: prctl(PR_CAPBSET_DROP, %d) failed: %m\n", i);
        _Exit(25);
      }
    }
  }
}

bool FileExistsAndIsNewerThan(const char *filepath, const char *thanpath) {
  struct stat st1, st2;
  if (stat(filepath, &st1) == -1) return false;
  if (stat(thanpath, &st2) == -1) return false;
  if (st1.st_mtim.tv_sec < st2.st_mtim.tv_sec) return false;
  if (st1.st_mtim.tv_sec > st2.st_mtim.tv_sec) return true;
  return st1.st_mtim.tv_nsec >= st2.st_mtim.tv_nsec;
}

int Extract(const char *from, const char *to, int mode) {
  int fdin, fdout;
  if ((fdin = open(from, O_RDONLY)) == -1) return -1;
  if ((fdout = creat(to, mode)) == -1) {
    close(fdin);
    return -1;
  }
  if (copyfd(fdin, fdout, -1) == -1) {
    close(fdout);
    close(fdin);
    return -1;
  }
  return close(fdout) | close(fdin);
}

/**
 * Returns true if ELF executable uses dynamic loading magic.
 */
static bool IsDynamicExecutable(const char *prog) {
  bool res;
  Elf64_Ehdr *e;
  Elf64_Phdr *p;
  struct stat st;
  int i, fd, err;
  fd = -1;
  err = errno;
  e = MAP_FAILED;
  if ((fd = open(prog, O_RDONLY)) == -1) {
    res = false;
    goto Finish;
  }
  if (fstat(fd, &st) == -1 || st.st_size < 64) {
    res = false;
    goto Finish;
  }
  if ((e = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
    res = false;
    goto Finish;
  }
  if (READ32LE(e->e_ident) != READ32LE(ELFMAG)) {
    res = false;
    goto Finish;
  }
  if (e->e_type == ET_DYN) {
    res = true;
    goto Finish;
  }
  for (i = 0; i < e->e_phnum; ++i) {
    p = GetElfProgramHeaderAddress(e, st.st_size, i);
    if (p->p_type == PT_INTERP || p->p_type == PT_DYNAMIC) {
      res = true;
      goto Finish;
    }
  }
  res = false;
  goto Finish;
Finish:
  if (e != MAP_FAILED) munmap(e, st.st_size);
  if (fd != -1) close(fd);
  errno = err;
  return res;
}

int main(int argc, char *argv[]) {
  const char *s;
  bool hasfunbits;
  int fdin, fdout;
  char buf[PATH_MAX];
  int e, zipfd, memfd;
  int useruid, usergid;
  int owneruid, ownergid;
  int oldfsuid, oldfsgid;
  unsigned long ipromises;

  if (!IsLinux()) {
    kprintf("error: this program is only intended for linux\n");
    exit(5);
  }

  // parse flags
  GetOpts(argc, argv);
  if (g_test) {
    if (!strcmp(g_test, "pledge")) {
      if (IsOpenbsd() || (IsLinux() && __is_linux_2_6_23())) {
        exit(0);
      } else {
        exit(1);
      }
    }
    if (!strcmp(g_test, "unveil")) {
      if (IsOpenbsd() || (IsLinux() && SupportsLandlock())) {
        exit(0);
      } else {
        exit(1);
      }
    }
    kprintf("error: unknown test: %s\n", g_test);
    exit(2);
  }
  if (optind == argc) {
    kprintf("error: too few args\n");
    write(2, USAGE, sizeof(USAGE) - 1);
    exit(64);
  }

  if (!g_noclose) {
    NormalizeFileDescriptors();
  }

  if (g_nice) {
    verynice();
  }

  if (SetCpuLimit(g_cpuquota) == -1) {
    kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_CPU");
    exit(1);
  }

  if (SetLimit(RLIMIT_FSIZE, g_fszquota, g_fszquota * 1.5) == -1) {
    kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_FSIZE");
    exit(1);
  }

  if (SetLimit(RLIMIT_AS, g_memquota, g_memquota) == -1) {
    kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_AS");
    exit(1);
  }

  if (SetLimit(RLIMIT_NPROC, g_proquota, g_proquota) == -1) {
    kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_NPROC");
    exit(1);
  }

  // test for weird chmod bits
  usergid = getgid();
  ownergid = getegid();
  useruid = getuid();
  owneruid = geteuid();
  hasfunbits = usergid != ownergid || useruid != owneruid;

  if (hasfunbits) {
    setuid(owneruid);
    setgid(ownergid);
  }

  // some flags can't be allowed if binary has setuid bits
  if (hasfunbits) {
    if (g_uflag || g_gflag) {
      kprintf("error: setuid flags forbidden on setuid binaries\n");
      _Exit(6);
    }
  }

  // check if user has permission to chroot directory
  if (hasfunbits && g_chroot) {
    oldfsuid = setfsuid(useruid);
    oldfsgid = setfsgid(usergid);
    if (access(g_chroot, R_OK) == -1) {
      kprintf("error: access(%#s) failed: %m\n", g_chroot);
      _Exit(7);
    }
    setfsuid(oldfsuid);
    setfsgid(oldfsgid);
  }

  // change root fs path
  if (g_chroot) {
    if (chdir(g_chroot) == -1) {
      kprintf("error: chdir(%#s) failed: %m\n", g_chroot);
      _Exit(8);
    }
    if (chroot(g_chroot) == -1) {
      kprintf("error: chroot(%#s) failed: %m\n", g_chroot);
      _Exit(9);
    }
  }

  // find program
  if (hasfunbits) {
    oldfsuid = setfsuid(useruid);
    oldfsgid = setfsgid(usergid);
  }
  if (!(prog = commandv(argv[optind], pathbuf, sizeof(pathbuf)))) {
    kprintf("error: command not found: %m\n", argv[optind]);
    _Exit(10);
  }
  if (hasfunbits) {
    setfsuid(oldfsuid);
    setfsgid(oldfsgid);
  }

  // figure out where we want the dso
  if (IsDynamicExecutable(prog)) {
    isdynamic = true;
    if ((s = getenv("TMPDIR")) ||  //
        (s = getenv("HOME")) ||    //
        (s = ".")) {
      ksnprintf(dsopath, sizeof(dsopath), "%s/sandbox.so", s);
      if (!FileExistsAndIsNewerThan(dsopath, GetProgramExecutableName())) {
        ksnprintf(tmppath, sizeof(tmppath), "%s/sandbox.so.%d", s, getpid());
        if (Extract("/zip/sandbox.so", tmppath, 0755) == -1) {
          kprintf("error: extract dso failed: %m\n");
          exit(1);
        }
        if (rename(tmppath, dsopath) == -1) {
          kprintf("error: rename dso failed: %m\n");
          exit(1);
        }
      }
      ksnprintf(buf, sizeof(buf), "LD_PRELOAD=%s", dsopath);
      putenv(buf);
    }
  }

  if (g_dontdrop) {
    if (hasfunbits) {
      kprintf("error: -D flag forbidden on setuid binaries\n");
      _Exit(6);
    }
  } else {
    DropCapabilities();
  }

  // set group id
  if (usergid != ownergid) {
    // setgid binaries must use the gid of the user that ran it
    if (setgid(usergid) == -1) {
      kprintf("error: setgid(%d) failed: %m\n", usergid);
      _Exit(11);
    }
    if (getgid() != usergid || getegid() != usergid) {
      kprintf("error: setgid() broken\n");
      _Exit(12);
    }
  } else if (g_gflag) {
    // otherwise we trust the gid flag
    if (setgid(g_gflag) == -1) {
      kprintf("error: setgid(%d) failed: %m\n", g_gflag);
      _Exit(13);
    }
    if (getgid() != g_gflag || getegid() != g_gflag) {
      kprintf("error: setgid() broken\n");
      _Exit(14);
    }
  }

  // set user id
  if (useruid != owneruid) {
    // setuid binaries must use the uid of the user that ran it
    if (setuid(useruid) == -1) {
      kprintf("error: setuid(%d) failed: %m\n", useruid);
      _Exit(15);
    }
    if (getuid() != useruid || geteuid() != useruid) {
      kprintf("error: setuid() broken\n");
      _Exit(16);
    }
  } else if (g_uflag) {
    // otherwise we trust the uid flag
    if (setuid(g_uflag) == -1) {
      kprintf("error: setuid(%d) failed: %m\n", g_uflag);
      _Exit(17);
    }
    if (getuid() != g_uflag || geteuid() != g_uflag) {
      kprintf("error: setuid() broken\n");
      _Exit(18);
    }
  }

  if (ParsePromises(g_promises, &ipromises) == -1) {
    kprintf("error: bad promises list: %s\n", g_promises);
    _Exit(21);
  }

  ApplyFilesystemPolicy(ipromises);

  // pledge.com uses the return eperm instead of killing the process
  // model. we do this becasue it's only possible to have sigsys print
  // crash messages if we're not pledging exec, which is what this tool
  // always has to do currently.
  if (g_kflag) {
    __pledge_mode = PLEDGE_PENALTY_KILL_PROCESS;
  } else {
    __pledge_mode = PLEDGE_PENALTY_RETURN_EPERM;
  }

  // we need to be able to call execv and mmap the dso
  // it'll be pledged away once/if the dso gets loaded
  if (!(~ipromises & (1ul << PROMISE_EXEC))) {
    g_promises = xstrcat(g_promises, ' ', "exec");
    if (!g_qflag) {
      // TODO(jart): Fix me.
      // __pledge_mode |= PLEDGE_STDERR_LOGGING;
    }
  }
  if (isdynamic) {
    g_promises = xstrcat(g_promises, ' ', "prot_exec");
  }

  // pass arguments to pledge() inside the dso
  if (isdynamic) {
    ksnprintf(buf, sizeof(buf), "_PLEDGE=%ld,%ld", ~ipromises, __pledge_mode);
    putenv(buf);
  }

  if (SetLimit(RLIMIT_NOFILE, g_nfdquota, g_nfdquota) == -1) {
    kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_NOFILE");
    exit(1);
  }

  // apply sandbox
  if (pledge(g_promises, g_promises) == -1) {
    kprintf("error: pledge(%#s) failed: %m\n", g_promises);
    _Exit(19);
  }

  // launch program
  sys_execve(prog, argv + optind, environ);
  kprintf("%s: execve failed: %m\n", prog);
  return 127;
}