mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
6e6fc38935
Commit bc6c183
introduced a bunch of discrepancies between what files
look like in the repo and what clang-format says they should look like.
However, there were already a few discrepancies prior to that. Most of
these discrepancies seemed to be unintentional, but a few of them were
load-bearing (e.g., a #include that violated header ordering needing
something to have been #defined by a 'later' #include.)
I opted to take what I hope is a relatively smooth-brained approach: I
reverted the .clang-format change, ran clang-format on the whole repo,
reapplied the .clang-format change, reran clang-format again, and then
reverted the commit that contained the first run. Thus the full effect
of this PR should only be to apply the changed formatting rules to the
repo, and from skimming the results, this seems to be the case.
My work can be checked by applying the short, manual commits, and then
rerunning the command listed in the autogenerated commits (those whose
messages I have prefixed auto:) and seeing if your results agree.
It might be that the other diffs should be fixed at some point but I'm
leaving that aside for now.
fd '\.c(c|pp)?$' --print0| xargs -0 clang-format -i
949 lines
26 KiB
C
949 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/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.internal.h"
|
|
#include "libc/intrin/safemacros.internal.h"
|
|
#include "libc/limits.h"
|
|
#include "libc/macros.internal.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) {
|
|
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) {
|
|
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;
|
|
int oldfsuid, oldfsgid;
|
|
unsigned long ipromises;
|
|
|
|
// 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;
|
|
}
|