cosmopolitan/tool/build/pledge.c
2024-08-04 12:52:25 -07:00

954 lines
26 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et 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 "ape/ape.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-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/ctype.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/fmt/itoa.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/promises.h"
#include "libc/intrin/safemacros.h"
#include "libc/limits.h"
#include "libc/macros.h"
#include "libc/math.h"
#include "libc/mem/alloca.h"
#include "libc/nexgen32e/kcpuids.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/serialize.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/sysparam.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("zipos");
#define USAGE \
"\
usage: pledge [-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\
Cosompolitan Pledge v1.9\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\
"
#ifdef __x86_64__
#define ARCH_NAME "x86_64"
#elif defined(__aarch64__)
#define ARCH_NAME "aarch64"
#else
#error "unsupported architecture"
#endif
enum Strategy {
kStrategyNull,
kStrategyStatic,
kStrategyDynamic,
kStrategyApe,
};
int g_gflag;
int g_uflag;
int g_kflag;
int g_hflag;
bool g_nice;
bool g_qflag;
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;
char pledgevar[64];
char g_promises[256];
char dsopath[PATH_MAX];
char tmppath[PATH_MAX];
char preloadvar[PATH_MAX];
struct {
int n;
char *p[10000];
} unveils;
unsigned long HasPromise(unsigned long ipromises, int promise) {
return ~ipromises & (1ul << promise);
}
long Atoi(const char *s) {
long i;
char *ep;
errno = 0;
i = strtol(s, &ep, 0);
if (*ep || errno) {
tinyprint(2, program_invocation_name, ": invalid integer: ", s, "\n", NULL);
exit(1);
}
return i;
}
long ParseSiSize(const char *s, long b) {
long i;
errno = 0;
i = sizetol(s, b);
if (errno) {
tinyprint(2, program_invocation_name, ": invalid size: ", s, "\n", NULL);
exit(1);
}
return i;
}
void AddPromise(const char *s) {
while (isspace(*s))
++s;
if (!*s)
return;
if (*g_promises) {
strlcat(g_promises, " ", sizeof(g_promises));
}
if (strlcat(g_promises, s, sizeof(g_promises)) >= sizeof(g_promises)) {
tinyprint(2, program_invocation_name, ": too many promises\n", NULL);
exit(1);
}
}
void GetOpts(int argc, char *argv[]) {
int opt;
struct sysinfo si;
bool got_promise_flag = false;
g_nfdquota = 64;
g_fszquota = 256 * 1000 * 1000;
if (!sysinfo(&si)) {
g_memquota = si.totalram;
g_proquota = __get_cpu_count() + si.procs;
} else {
g_proquota = __get_cpu_count() * 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':
g_fszquota = ParseSiSize(optarg, 1024);
break;
case 'M':
g_memquota = ParseSiSize(optarg, 1024);
break;
case 'p':
AddPromise(optarg);
got_promise_flag = true;
break;
case 'v':
if (unveils.n == ARRAYLEN(unveils.p)) {
tinyprint(2, program_invocation_name, ": too many unveils\n", NULL);
exit(1);
}
unveils.p[unveils.n++] = optarg;
break;
case 'h':
case '?':
write(1, USAGE, sizeof(USAGE) - 1);
exit(0);
default:
write(2, USAGE, sizeof(USAGE) - 1);
exit(64);
}
}
if (!got_promise_flag) {
stpcpy(g_promises, "stdio rpath");
}
}
const char *g_prog;
char pathbuf[PATH_MAX];
struct pollfd pfds[256];
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)) {
n = rl.rlim_cur;
} else {
n = 64;
}
return MIN(ARRAYLEN(pfds), MAX(3, n));
}
void NormalizeFileDescriptors(void) {
int i, n, fd;
closefrom(3); // faster and more secure if linux 5.9+ or bsd
n = GetPollMaxFds();
for (i = 0; i < n; ++i) {
pfds[i].fd = i;
pfds[i].events = POLLIN;
}
if (poll(pfds, n, 0) == -1) {
perror("poll");
exit(1);
}
for (i = 0; i < 3; ++i) {
if (pfds[i].revents & POLLNVAL) {
if ((fd = open("/dev/null", O_RDWR)) == -1) {
perror("/dev/null");
exit(2);
}
if (fd != i) {
tinyprint(2, program_invocation_name, ": poll() or open() is broken\n",
NULL);
exit(3);
}
}
}
for (i = 3; i < n; ++i) {
if (~pfds[i].revents & POLLNVAL) {
if (close(pfds[i].fd) == -1) {
perror("close");
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) {
#ifdef __x86_64__
return KCPUIDS(16H, EAX) & 0x7fff;
#else
return 0;
#endif
}
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) {
perror(path);
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 {
perror(path);
exit(20);
}
}
return -1;
}
enum Strategy GetStrategy(void) {
static enum Strategy strategy;
if (strategy == kStrategyNull) {
strategy = kStrategyStatic;
int fd;
if ((fd = open(g_prog, O_RDONLY)) != -1) {
union {
char magic[8];
Elf64_Ehdr ehdr;
} hdr = {0};
if (pread(fd, &hdr, sizeof(hdr), 0) == sizeof(hdr)) {
if (READ64LE(hdr.magic) == READ64LE("MZqFpD='") ||
READ64LE(hdr.magic) == READ64LE("jartsr='") ||
READ64LE(hdr.magic) == READ64LE("APEDBG='")) {
strategy = kStrategyApe;
} else if ((IsLinux() || IsFreebsd() || IsNetbsd() || IsOpenbsd()) &&
IsElf64Binary(&hdr.ehdr, sizeof(hdr))) {
if (hdr.ehdr.e_type == ET_DYN) {
strategy = kStrategyDynamic;
} else {
Elf64_Phdr phdrs[16];
int count = MIN(hdr.ehdr.e_phnum, ARRAYLEN(phdrs));
int bytes = count * sizeof(Elf64_Phdr);
if (pread(fd, phdrs, bytes, hdr.ehdr.e_phoff) == bytes) {
for (int i = 0; i < count; ++i) {
if (phdrs[i].p_type == PT_INTERP ||
phdrs[i].p_type == PT_DYNAMIC) {
strategy = kStrategyDynamic;
}
}
}
}
}
}
close(fd);
}
}
return strategy;
}
void ApplyFilesystemPolicy(unsigned long ipromises) {
const char *p;
if (g_dontunveil)
return;
if (!SupportsLandlock())
return;
Unveil(g_prog, "rx");
if (GetStrategy() == kStrategyDynamic) {
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-" ARCH_NAME ".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");
// in case musl is symlinked somewhere else
UnveilIfExists("/lib/ld-musl-" ARCH_NAME ".so.1", "rx");
}
if (HasPromise(ipromises, 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 (HasPromise(ipromises, PROMISE_INET)) {
UnveilIfExists("/etc/ssl/certs/ca-certificates.crt", "r");
}
if (HasPromise(ipromises, PROMISE_RPATH)) {
UnveilIfExists("/proc/filesystems", "r");
}
if (HasPromise(ipromises, PROMISE_DNS)) {
UnveilIfExists("/etc/hosts", "r");
UnveilIfExists("/etc/hostname", "r");
UnveilIfExists("/etc/services", "r");
UnveilIfExists("/etc/protocols", "r");
UnveilIfExists("/etc/resolv.conf", "r");
}
if (HasPromise(ipromises, 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 (GetStrategy() == kStrategyApe) {
if (UnveilIfExists("/usr/bin/ape", "rx") == -1) {
char buf[PATH_MAX];
if ((p = getenv("TMPDIR"))) {
UnveilIfExists(
__join_paths(buf, sizeof(buf), p, ".ape-" APE_VERSION_STR), "rx");
}
if ((p = getenv("HOME"))) {
UnveilIfExists(
__join_paths(buf, sizeof(buf), p, ".ape-" APE_VERSION_STR), "rx");
}
}
}
if (HasPromise(ipromises, 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 (HasPromise(ipromises, 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) {
perror("unveil");
exit(20);
}
}
void DropCapabilities(void) {
int e, i;
if (!IsLinux())
return;
for (e = errno, i = 0;; ++i) {
if (prctl(PR_CAPBSET_DROP, i) == -1) {
if (errno == EINVAL || errno == EPERM) {
errno = e;
break;
} else {
perror("prctl(PR_CAPBSET_DROP)");
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);
}
int CountEnviron(char **ep) {
int res = 0;
while (*ep++)
++res;
return res;
}
int main(int argc, char *argv[]) {
const char *s;
bool hasfunbits;
int useruid, usergid;
int owneruid, ownergid;
unsigned long ipromises;
int oldfsuid = 0, oldfsgid = 0;
// 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);
}
}
tinyprint(2, g_test, ": unknown test\n", NULL);
exit(2);
}
if (optind == argc) {
tinyprint(2, "error: missing command\n", NULL);
write(2, USAGE, sizeof(USAGE) - 1);
exit(64);
}
// perform process setup
if (!g_noclose) {
NormalizeFileDescriptors();
}
if (g_nice) {
verynice();
}
if (SetCpuLimit(g_cpuquota) == -1) {
perror("setrlimit(RLIMIT_CPU)");
}
if (SetLimit(RLIMIT_FSIZE, g_fszquota, g_fszquota * 1.5) == -1) {
perror("setrlimit(RLIMIT_FSIZE)");
}
if (SetLimit(RLIMIT_AS, g_memquota, g_memquota) == -1) {
perror("setrlimit(RLIMIT_AS)");
}
if (SetLimit(RLIMIT_NPROC, g_proquota, g_proquota) == -1) {
perror("setrlimit(RLIMIT_NPROC)");
}
// 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) {
tinyprint(2, program_invocation_name,
": setuid flags forbidden on setuid binaries\n", NULL);
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) {
perror(g_chroot);
exit(7);
}
setfsuid(oldfsuid);
setfsgid(oldfsgid);
}
// change root fs path
if (g_chroot) {
if (chdir(g_chroot) == -1) {
perror(g_chroot);
exit(8);
}
if (chroot(g_chroot) == -1) {
perror(g_chroot);
exit(9);
}
}
// find program
if (hasfunbits) {
oldfsuid = setfsuid(useruid);
oldfsgid = setfsgid(usergid);
}
if (!(g_prog = commandv(argv[optind], pathbuf, sizeof(pathbuf)))) {
perror(argv[optind]);
exit(10);
}
if (hasfunbits) {
setfsuid(oldfsuid);
setfsgid(oldfsgid);
}
// copy environment
// to setup child environment
// and remove variables we might create
unsetenv("_PLEDGE");
unsetenv("LD_PRELOAD");
int child_environ_count = CountEnviron(environ);
int child_environ_capacity = child_environ_count + 2 + 1;
int child_environ_bytes = child_environ_capacity * sizeof(char *);
char **child_environ = alloca(child_environ_bytes);
CheckLargeStackAllocation(child_environ, child_environ_bytes);
memcpy(child_environ, environ, child_environ_count * sizeof(char *));
bzero(child_environ + child_environ_count,
(child_environ_capacity - child_environ_count) * sizeof(char *));
// figure out where we want the dso
if (GetStrategy() == kStrategyDynamic) {
if ((s = getenv("TMPDIR")) || //
(s = getenv("HOME")) || //
(s = ".")) {
strlcpy(dsopath, s, sizeof(dsopath));
strlcat(dsopath, "/.pledge-sandbox.so", sizeof(dsopath));
if (!FileExistsAndIsNewerThan(dsopath, GetProgramExecutableName())) {
errno = 0;
char pidstr[21];
FormatInt64(pidstr, getpid());
strlcpy(tmppath, s, sizeof(tmppath));
strlcat(tmppath, "/.pledge-sandbox.so.", sizeof(tmppath));
strlcat(tmppath, pidstr, sizeof(tmppath));
if (Extract("/zip/sandbox-" ARCH_NAME ".so", tmppath, 0755) == -1) {
perror(tmppath);
exit(1);
}
if (rename(tmppath, dsopath) == -1) {
perror(dsopath);
exit(1);
}
}
stpcpy(preloadvar, "LD_PRELOAD=");
strlcat(preloadvar, dsopath, sizeof(preloadvar));
child_environ[child_environ_count++] = preloadvar;
}
}
if (g_dontdrop) {
if (hasfunbits) {
tinyprint(2, program_invocation_name,
": -D flag forbidden on setuid binaries\n", NULL);
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) {
perror("setgid");
exit(11);
}
if (getgid() != usergid || getegid() != usergid) {
tinyprint(2, "error: setgid() broken\n", NULL);
exit(12);
}
} else if (g_gflag) {
// otherwise we trust the gid flag
if (setgid(g_gflag) == -1) {
perror("setgid");
exit(13);
}
if (getgid() != g_gflag || getegid() != g_gflag) {
tinyprint(2, "error: setgid() broken\n", NULL);
exit(14);
}
}
// set user id
if (useruid != owneruid) {
// setuid binaries must use the uid of the user that ran it
if (setuid(useruid) == -1) {
perror("setuid");
exit(15);
}
if (getuid() != useruid || geteuid() != useruid) {
tinyprint(2, "error: setuid() broken\n", NULL);
exit(16);
}
} else if (g_uflag) {
// otherwise we trust the uid flag
if (setuid(g_uflag) == -1) {
perror("setuid");
exit(17);
}
if (getuid() != g_uflag || geteuid() != g_uflag) {
tinyprint(2, "error: setuid() broken\n", NULL);
exit(18);
}
}
// parse requested promises
// further changes to g_promises will be *effective* transient promises
if (ParsePromises(g_promises, &ipromises, 0) == -1) {
tinyprint(2, program_invocation_name, ": bad promises list: ", g_promises,
"\n", NULL);
exit(21);
}
// perform unveiling
ApplyFilesystemPolicy(ipromises);
// pledge 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;
}
// weaken system call policy to allow execution
//
// 1. we always need to pledge("exec") in order to use execve(). this
// is the primary disadvantage to using the `pledge` command to
// bolt security onto unsecured programs (as opposed to using
// pledge as it was intended, by having the program authors update
// their code to invoke the pledge() system call from within their
// programs at the appropriate moments)
//
// 2. we usually need to force pledge("rpath prot_exec") too; dynamic
// executables need it mmap() shared objects during initialization
// and actually portable executables need it so /usr/bin/ape can
// mmap() the ELF program headers. the only time we don't require
// `prot_exec` is when launching a native ELF PT_EXEC binaries,
// e.g. assimilated actually portable executables.
//
// 3. in some cases we can remove the `exec` and `prot_exec` promises
// later on in the loading process. on musl and glibc systems, we
// do that by injecting an LD_PRELOAD DSO which calls pledge()
// again with the requested promises.
if (!HasPromise(ipromises, PROMISE_EXEC)) {
AddPromise("exec");
if (!g_qflag) {
// TODO(jart): Fix me.
// __pledge_mode |= PLEDGE_STDERR_LOGGING;
}
}
if (GetStrategy() != kStrategyStatic) {
if (!HasPromise(ipromises, PROMISE_RPATH)) {
AddPromise("rpath");
}
if (!HasPromise(ipromises, PROMISE_PROT_EXEC)) {
AddPromise("prot_exec");
}
}
// pass parameters to injected dso
if (GetStrategy() == kStrategyDynamic) {
char *p = pledgevar;
p = stpcpy(p, "_PLEDGE=");
p = FormatInt64(p, ~ipromises);
p = stpcpy(p, ",");
p = FormatInt64(p, __pledge_mode);
child_environ[child_environ_count++] = pledgevar;
}
// this limit needs to come last since unveil() opens fds
if (SetLimit(RLIMIT_NOFILE, g_nfdquota, g_nfdquota) == -1) {
perror("setrlimit(RLIMIT_NOFILE)");
exit(1);
}
// apply sandbox
if (pledge(g_promises, g_promises) == -1) {
perror("pledge");
exit(19);
}
// launch program
if (!IsWindows()) {
sys_execve(g_prog, argv + optind, child_environ);
} else {
sys_execve_nt(g_prog, argv + optind, child_environ);
}
perror(g_prog);
return 127;
}