From 1d744ea11b7803d98ba442927043a0ee0e9a1e5f Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Thu, 14 Jul 2022 04:32:33 -0700 Subject: [PATCH] Improve upon the new pledge command --- libc/calls/calls.h | 2 + libc/intrin/exit.c | 5 + libc/mem/pledge.c | 6 +- libc/runtime/stack.h | 17 +++ libc/sysv/consts/ioprio.h | 30 ++++ test/libc/mem/pledge_test.c | 30 +++- test/libc/mem/test.mk | 1 + test/libc/nexgen32e/stackrw_test.c | 59 ++++++++ test/libc/nexgen32e/stackrwx_test.c | 41 ++++++ test/libc/nexgen32e/test.mk | 11 +- tool/build/assimilate.c | 36 ++--- tool/build/compile.c | 2 +- tool/build/pledge.c | 206 ++++++++++++++++++++++------ 13 files changed, 382 insertions(+), 64 deletions(-) create mode 100644 libc/sysv/consts/ioprio.h create mode 100644 test/libc/nexgen32e/stackrw_test.c create mode 100644 test/libc/nexgen32e/stackrwx_test.c diff --git a/libc/calls/calls.h b/libc/calls/calls.h index ee6d80308..24f5db396 100644 --- a/libc/calls/calls.h +++ b/libc/calls/calls.h @@ -111,6 +111,8 @@ int getresuid(uint32_t *, uint32_t *, uint32_t *); int getsid(int) nosideeffect libcesque; int gettid(void) libcesque; int getuid(void) nosideeffect libcesque; +int ioprio_get(int, int); +int ioprio_set(int, int, int); int kill(int, int); int killpg(int, int); int link(const char *, const char *) dontthrow; diff --git a/libc/intrin/exit.c b/libc/intrin/exit.c index 2c2f23d57..2a0702885 100644 --- a/libc/intrin/exit.c +++ b/libc/intrin/exit.c @@ -43,6 +43,11 @@ privileged wontreturn void _Exit(int exitcode) { : /* no outputs */ : "a"(__NR_exit_group), "D"(exitcode) : "rcx", "r11", "memory"); + // this should only be possible on Linux in a pledge ultra sandbox + asm volatile("syscall" + : /* no outputs */ + : "a"(__NR_exit), "D"(exitcode) + : "rcx", "r11", "memory"); } else if (IsWindows()) { ExitProcess(exitcode); } diff --git a/libc/mem/pledge.c b/libc/mem/pledge.c index 4d58dbccb..b30cc8727 100644 --- a/libc/mem/pledge.c +++ b/libc/mem/pledge.c @@ -59,11 +59,11 @@ struct Filter { }; static const uint16_t kPledgeLinuxDefault[] = { - __NR_linux_exit, // - __NR_linux_exit_group, // + __NR_linux_exit, // }; static const uint16_t kPledgeLinuxStdio[] = { + __NR_linux_exit_group, // __NR_linux_clock_getres, // __NR_linux_clock_gettime, // __NR_linux_clock_nanosleep, // @@ -1132,7 +1132,7 @@ static int sys_pledge_linux(const char *promises, const char *execpromises) { * `promises` is a string that may include any of the following groups * delimited by spaces. * - * - "stdio" allows close, dup, dup2, dup3, fchdir, fstat, fsync, + * - "stdio" allows exit, close, dup, dup2, dup3, fchdir, fstat, fsync, * fdatasync, ftruncate, getdents, getegid, getrandom, geteuid, * getgid, getgroups, getitimer, getpgid, getpgrp, getpid, getppid, * getresgid, getresuid, getrlimit, getsid, wait4, gettimeofday, diff --git a/libc/runtime/stack.h b/libc/runtime/stack.h index c0a321e19..bd4525ea9 100644 --- a/libc/runtime/stack.h +++ b/libc/runtime/stack.h @@ -48,6 +48,23 @@ #define STATIC_STACK_ADDR(ADDR) \ STATIC_SYMBOL("ape_stack_vaddr", _STACK_STRINGIFY(ADDR)) +/** + * Makes program stack executable if declared, e.g. + * + * STATIC_EXEC_STACK(); + * int main() { + * char code[16] = { + * 0x55, // push %rbp + * 0xb8, 0007, 0x00, 0x00, 0x00, // mov $7,%eax + * 0x5d, // push %rbp + * 0xc3, // ret + * }; + * int (*func)(void) = (void *)code; + * printf("result %d should be 7\n", func()); + * } + */ +#define STATIC_EXEC_STACK() STATIC_SYMBOL("ape_stack_pf", "7") + #define _STACK_STRINGIFY(ADDR) #ADDR #if IsAsan() diff --git a/libc/sysv/consts/ioprio.h b/libc/sysv/consts/ioprio.h new file mode 100644 index 000000000..e639d937b --- /dev/null +++ b/libc/sysv/consts/ioprio.h @@ -0,0 +1,30 @@ +#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_IOPRIO_H_ +#define COSMOPOLITAN_LIBC_SYSV_CONSTS_IOPRIO_H_ + +#define IOPRIO_WHO_PROCESS 1 +#define IOPRIO_WHO_PGRP 2 +#define IOPRIO_WHO_USER 3 + +#define IOPRIO_CLASS_SHIFT 13 +#define IOPRIO_CLASS_MASK 0x07 +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(ioprio) \ + (((ioprio) >> IOPRIO_CLASS_SHIFT) & IOPRIO_CLASS_MASK) +#define IOPRIO_PRIO_DATA(ioprio) ((ioprio)&IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) \ + ((((class) & IOPRIO_CLASS_MASK) << IOPRIO_CLASS_SHIFT) | \ + ((data)&IOPRIO_PRIO_MASK)) + +#define IOPRIO_CLASS_NONE 0 +#define IOPRIO_CLASS_RT 1 +#define IOPRIO_CLASS_BE 2 +#define IOPRIO_CLASS_IDLE 3 + +#define IOPRIO_NR_LEVELS 8 +#define IOPRIO_BE_NR IOPRIO_NR_LEVELS + +#define IOPRIO_NORM 4 +#define IOPRIO_BE_NORM IOPRIO_NORM + +#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_IOPRIO_H_ */ diff --git a/test/libc/mem/pledge_test.c b/test/libc/mem/pledge_test.c index 85f63aa85..6675ac226 100644 --- a/test/libc/mem/pledge_test.c +++ b/test/libc/mem/pledge_test.c @@ -46,6 +46,7 @@ #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/sock.h" #include "libc/testlib/testlib.h" +#include "libc/thread/spawn.h" char testlib_enable_tmp_setup_teardown; @@ -60,15 +61,40 @@ void SetUp(void) { } TEST(pledge, default_allowsExit) { + int *job; int ws, pid; - ASSERT_NE(-1, (pid = fork())); + // create small shared memory region + ASSERT_NE(-1, (job = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0))); + job[0] = 2; // create workload + job[1] = 2; + ASSERT_NE(-1, (pid = fork())); // create enclaved worker if (!pid) { ASSERT_SYS(0, 0, pledge("", 0)); + job[0] = job[0] + job[1]; // do work _Exit(0); } - EXPECT_NE(-1, wait(&ws)); + EXPECT_NE(-1, wait(&ws)); // wait for worker EXPECT_TRUE(WIFEXITED(ws)); EXPECT_EQ(0, WEXITSTATUS(ws)); + EXPECT_EQ(4, job[0]); // check result + EXPECT_SYS(0, 0, munmap(job, FRAMESIZE)); +} + +int Enclave(void *arg, int tid) { + ASSERT_SYS(0, 0, pledge("", 0)); + int *job = arg; // get job + job[0] = job[0] + job[1]; // do work + return 0; // exit +} + +TEST(pledge, withThreadMemory) { + if (IsOpenbsd()) return; // openbsd doesn't allow it, wisely + struct spawn worker; + int job[2] = {2, 2}; // create workload + ASSERT_SYS(0, 0, _spawn(Enclave, job, &worker)); // create worker + ASSERT_SYS(0, 0, _join(&worker)); // wait for exit + EXPECT_EQ(4, job[0]); // check result } TEST(pledge, stdio_forbidsOpeningPasswd1) { diff --git a/test/libc/mem/test.mk b/test/libc/mem/test.mk index 56ae82568..72bd64e96 100644 --- a/test/libc/mem/test.mk +++ b/test/libc/mem/test.mk @@ -42,6 +42,7 @@ TEST_LIBC_MEM_DIRECTDEPS = \ LIBC_STR \ LIBC_STUBS \ LIBC_SYSV \ + LIBC_THREAD \ LIBC_TESTLIB \ THIRD_PARTY_DLMALLOC \ THIRD_PARTY_LIBCXX diff --git a/test/libc/nexgen32e/stackrw_test.c b/test/libc/nexgen32e/stackrw_test.c new file mode 100644 index 000000000..56c8b11af --- /dev/null +++ b/test/libc/nexgen32e/stackrw_test.c @@ -0,0 +1,59 @@ +/*-*- 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/struct/sigaction.h" +#include "libc/intrin/kprintf.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/testlib/testlib.h" + +/** + * @fileoverview test non-executable stack is default + */ + +jmp_buf jb; + +void EscapeSegfault(int sig) { + longjmp(jb, 666); +} + +TEST(xstack, test) { + struct sigaction old[2]; + struct sigaction sa = { + .sa_handler = EscapeSegfault, + .sa_flags = SA_NODEFER, + }; + sigaction(SIGSEGV, &sa, old + 0); + sigaction(SIGBUS, &sa, old + 1); + char code[16] = { + 0x55, // push %rbp + 0xb8, 0007, 0x00, 0x00, 0x00, // mov $7,%eax + 0x5d, // push %rbp + 0xc3, // ret + }; + int (*func)(void) = (void *)code; + int rc; + if (!(rc = setjmp(jb))) { + func(); + abort(); + } + ASSERT_EQ(666, rc); + sigaction(SIGBUS, old + 1, 0); + sigaction(SIGSEGV, old + 0, 0); +} diff --git a/test/libc/nexgen32e/stackrwx_test.c b/test/libc/nexgen32e/stackrwx_test.c new file mode 100644 index 000000000..2f1fd9f2c --- /dev/null +++ b/test/libc/nexgen32e/stackrwx_test.c @@ -0,0 +1,41 @@ +/*-*- 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/runtime/runtime.h" +#include "libc/runtime/stack.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/testlib/testlib.h" + +/** + * @fileoverview tests executable stack + */ + +STATIC_EXEC_STACK(); + +TEST(xstack, test) { + char code[16] = { + 0x55, // push %rbp + 0xb8, 0007, 0x00, 0x00, 0x00, // mov $7,%eax + 0x5d, // push %rbp + 0xc3, // ret + }; + int (*func)(void) = (void *)code; + ASSERT_EQ(7, func()); +} diff --git a/test/libc/nexgen32e/test.mk b/test/libc/nexgen32e/test.mk index 5cc459028..ff95a48a6 100644 --- a/test/libc/nexgen32e/test.mk +++ b/test/libc/nexgen32e/test.mk @@ -37,10 +37,12 @@ TEST_LIBC_NEXGEN32E_DIRECTDEPS = \ LIBC_STDIO \ LIBC_STR \ LIBC_STUBS \ + LIBC_SYSV \ LIBC_TESTLIB \ LIBC_UNICODE \ LIBC_X \ - TOOL_VIZ_LIB + TOOL_VIZ_LIB \ + THIRD_PARTY_XED TEST_LIBC_NEXGEN32E_DEPS := \ $(call uniq,$(foreach x,$(TEST_LIBC_NEXGEN32E_DIRECTDEPS),$($(x)))) @@ -58,6 +60,13 @@ o/$(MODE)/test/libc/nexgen32e/%.com.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) +# we can't run this test on openbsd because rwx memory isn't allowed +o/$(MODE)/test/libc/nexgen32e/stackrwx_test.com.ok: \ + o/$(MODE)/tool/build/runit.com \ + o/$(MODE)/tool/build/runitd.com \ + o/$(MODE)/test/libc/nexgen32e/stackrwx_test.com + @$(COMPILE) -ATEST -tT$@ $^ $(filter-out openbsd,$(HOSTS)) + $(TEST_LIBC_NEXGEN32E_OBJS): \ DEFAULT_CCFLAGS += \ -fno-builtin diff --git a/tool/build/assimilate.c b/tool/build/assimilate.c index aa2ffab84..06ab70324 100644 --- a/tool/build/assimilate.c +++ b/tool/build/assimilate.c @@ -126,21 +126,21 @@ void GetElfHeader(char ehdr[hasatleast 64], const char *image) { ehdr[i++] = c; } else { kprintf("%s: ape printf elf header too long\n", prog); - exit(__COUNTER__); + exit(1); } } if (i != 64) { kprintf("%s: ape printf elf header too short\n", prog); - exit(__COUNTER__); + exit(2); } if (READ32LE(ehdr) != READ32LE("\177ELF")) { kprintf("%s: ape printf elf header didn't have elf magic\n", prog); - exit(__COUNTER__); + exit(3); } return; } kprintf("%s: printf statement not found in first 4096 bytes\n", prog); - exit(__COUNTER__); + exit(4); } void GetMachoPayload(const char *image, size_t imagesize, int *out_offset, @@ -151,7 +151,7 @@ void GetMachoPayload(const char *image, size_t imagesize, int *out_offset, int rc, skip, count, bs, offset, size; if (!(script = memmem(image, imagesize, "'\n#'\"\n", 6))) { kprintf("%s: ape shell script not found\n", prog); - exit(__COUNTER__); + exit(5); } script += 6; DCHECK_EQ(REG_OK, regcomp(&rx, @@ -163,11 +163,11 @@ void GetMachoPayload(const char *image, size_t imagesize, int *out_offset, if (rc != REG_OK) { if (rc == REG_NOMATCH) { kprintf("%s: ape macho dd command not found\n", prog); - exit(__COUNTER__); + exit(6); } regerror(rc, &rx, errstr, sizeof(errstr)); kprintf("%s: ape macho dd regex failed: %s\n", prog, errstr); - exit(__COUNTER__); + exit(7); } bs = atoi(script + rm[1].rm_so); skip = atoi(script + rm[2].rm_so); @@ -175,23 +175,23 @@ void GetMachoPayload(const char *image, size_t imagesize, int *out_offset, if (__builtin_mul_overflow(skip, bs, &offset) || __builtin_mul_overflow(count, bs, &size)) { kprintf("%s: integer overflow parsing macho\n"); - exit(__COUNTER__); + exit(8); } if (offset < 64) { kprintf("%s: ape macho dd offset should be ≥64: %d\n", prog, offset); - exit(__COUNTER__); + exit(9); } if (offset >= imagesize) { kprintf("%s: ape macho dd offset is outside file: %d\n", prog, offset); - exit(__COUNTER__); + exit(10); } if (size < 32) { kprintf("%s: ape macho dd size should be ≥32: %d\n", prog, size); - exit(__COUNTER__); + exit(11); } if (size > imagesize - offset) { kprintf("%s: ape macho dd size is outside file: %d\n", prog, size); - exit(__COUNTER__); + exit(12); } *out_offset = offset; *out_size = size; @@ -218,20 +218,20 @@ void Assimilate(void) { struct stat st; if ((fd = open(prog, O_RDWR)) == -1) { kprintf("%s: open(O_RDWR) failed: %m\n", prog); - exit(__COUNTER__); + exit(13); } if (fstat(fd, &st) == -1) { kprintf("%s: fstat() failed: %m\n", prog); - exit(__COUNTER__); + exit(14); } if (st.st_size < 8192) { kprintf("%s: ape binaries must be at least 4096 bytes\n", prog); - exit(__COUNTER__); + exit(15); } if ((p = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { kprintf("%s: mmap failed: %m\n", prog); - exit(__COUNTER__); + exit(16); } if (READ32LE(p) == READ32LE("\177ELF")) { if (!g_force) { @@ -253,7 +253,7 @@ void Assimilate(void) { } if (READ64LE(p) != READ64LE("MZqFpD='")) { kprintf("%s: this file is not an actually portable executable\n", prog); - exit(__COUNTER__); + exit(17); } if (g_mode == MODE_ELF) { AssimilateElf(p, st.st_size); @@ -263,7 +263,7 @@ void Assimilate(void) { Finish: if (munmap(p, st.st_size) == -1) { kprintf("%s: munmap() failed: %m\n", prog); - exit(__COUNTER__); + exit(18); } } diff --git a/tool/build/compile.c b/tool/build/compile.c index 1c68a5858..2a3a1a21a 100644 --- a/tool/build/compile.c +++ b/tool/build/compile.c @@ -777,7 +777,7 @@ int main(int argc, char *argv[]) { ccversion = atoi(optarg); break; case 'P': - proquota = sizetol(optarg, 1024); + proquota = atoi(optarg); break; case 'F': fszquota = sizetol(optarg, 1000); diff --git a/tool/build/pledge.c b/tool/build/pledge.c index b89c0b249..05de37623 100644 --- a/tool/build/pledge.c +++ b/tool/build/pledge.c @@ -18,35 +18,47 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/struct/rlimit.h" +#include "libc/calls/struct/sched_param.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" #include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" +#include "libc/math.h" +#include "libc/nexgen32e/kcpuids.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/sysconf.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/o.h" #include "libc/sysv/consts/ok.h" #include "libc/sysv/consts/poll.h" +#include "libc/sysv/consts/prio.h" #include "libc/sysv/consts/rlimit.h" +#include "libc/sysv/consts/sched.h" +#include "libc/x/x.h" #include "third_party/getopt/getopt.h" STATIC_YOINK("strerror_wr"); -#define GETOPTS "hFp:u:g:c:" - #define USAGE \ "\ -usage: pledge.com [-h] PROG ARGS...\n\ - -h show help\n\ - -g GID call setgid()\n\ - -u UID call setuid()\n\ - -c PATH call chroot()\n\ - -F don't normalize file descriptors\n\ - -p PLEDGE may contain any of following separated by spaces\n\ +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\ + -n maximum niceness\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\ + -P PROCS set process limit [default: GetCpuCount()*2]\n\ + -F BYTES set individual file size limit [default: 4gb]\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\ @@ -80,17 +92,29 @@ the https://justine.lol/pledge/ page for online documentation.\n\ int g_gflag; int g_uflag; int g_hflag; +bool g_nice; bool g_noclose; -const char *g_pflag; +long g_cpuquota; +long g_fszquota; +long g_memquota; +long g_proquota; const char *g_chroot; +const char *g_promises; static void GetOpts(int argc, char *argv[]) { int opt; - g_pflag = ""; - while ((opt = getopt(argc, argv, GETOPTS)) != -1) { + g_promises = 0; + g_proquota = GetCpuCount() * 2; + g_fszquota = 256 * 1000 * 1000; + g_fszquota = 4 * 1000 * 1000 * 1000; + g_memquota = 4L * 1024 * 1024 * 1024; + while ((opt = getopt(argc, argv, "hnNp:u:g:c:C:P:M:F:")) != -1) { switch (opt) { - case 'p': - g_pflag = optarg; + case 'n': + g_nice = true; + break; + case 'N': + g_noclose = true; break; case 'c': g_chroot = optarg; @@ -101,8 +125,24 @@ static void GetOpts(int argc, char *argv[]) { case 'u': g_uflag = atoi(optarg); break; + case 'C': + g_cpuquota = atoi(optarg); + break; + case 'P': + g_proquota = atoi(optarg); + break; + case 'M': + g_memquota = sizetol(optarg, 1024); + break; case 'F': - g_noclose = true; + g_fszquota = sizetol(optarg, 1000); + break; + case 'p': + if (g_promises) { + g_promises = xstrcat(g_promises, ' ', optarg); + } else { + g_promises = optarg; + } break; case 'h': case '?': @@ -113,13 +153,20 @@ static void GetOpts(int argc, char *argv[]) { exit(64); } } + if (!g_promises) { + g_promises = "stdio rpath execnative"; + } + g_promises = xstrcat(g_promises, ' ', "execnative"); } const char *prog; -char pledges[1024]; char pathbuf[PATH_MAX]; struct pollfd pfds[256]; +int GetBaseCpuFreqMhz(void) { + return KCPUIDS(16H, EAX) & 0x7fff; +} + int GetPollMaxFds(void) { int n; struct rlimit rl; @@ -140,17 +187,17 @@ void NormalizeFileDescriptors(void) { } if (poll(pfds, n, 0) == -1) { kprintf("error: poll() failed: %m\n"); - exit(__COUNTER__); + 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(__COUNTER__); + exit(2); } if (fd != i) { kprintf("error: open() is broken: %d vs. %d\n", fd, i); - exit(__COUNTER__); + exit(3); } } } @@ -158,12 +205,87 @@ void NormalizeFileDescriptors(void) { if (~pfds[i].revents & POLLNVAL) { if (close(pfds[i].fd) == -1) { kprintf("error: close(%d) failed: %m\n", pfds[i].fd); - exit(__COUNTER__); + exit(4); } } } } +void SetCpuLimit(int secs) { + int mhz, lim; + struct rlimit rlim; + if (secs <= 0) return; + if (!(mhz = GetBaseCpuFreqMhz())) return; + lim = ceil(3100. / mhz * secs); + rlim.rlim_cur = lim; + rlim.rlim_max = lim + 1; + if (setrlimit(RLIMIT_CPU, &rlim) != -1) { + return; + } else if (getrlimit(RLIMIT_CPU, &rlim) != -1) { + if (lim < rlim.rlim_cur) { + rlim.rlim_cur = lim; + if (setrlimit(RLIMIT_CPU, &rlim) != -1) { + return; + } + } + } + kprintf("error: setrlimit(RLIMIT_CPU) failed: %m\n"); + exit(20); +} + +void SetFszLimit(long n) { + struct rlimit rlim; + if (n <= 0) return; + rlim.rlim_cur = n; + rlim.rlim_max = n << 1; + if (setrlimit(RLIMIT_FSIZE, &rlim) != -1) { + return; + } else if (getrlimit(RLIMIT_FSIZE, &rlim) != -1) { + rlim.rlim_cur = n; + if (setrlimit(RLIMIT_FSIZE, &rlim) != -1) { + return; + } + } + kprintf("error: setrlimit(RLIMIT_FSIZE) failed: %m\n"); + exit(21); +} + +void SetMemLimit(long n) { + struct rlimit rlim = {n, n}; + if (n <= 0) return; + if (setrlimit(RLIMIT_AS, &rlim) == -1) { + kprintf("error: setrlimit(RLIMIT_AS) failed: %m\n"); + exit(22); + } +} + +void SetProLimit(long n) { + struct rlimit rlim = {n, n}; + if (n <= 0) return; + if (setrlimit(RLIMIT_NPROC, &rlim) == -1) { + kprintf("error: setrlimit(RLIMIT_NPROC) failed: %m\n"); + exit(22); + } +} + +void MakeProcessNice(void) { + if (!g_nice) return; + if (setpriority(PRIO_PROCESS, 0, 19) == -1) { + kprintf("error: setpriority(PRIO_PROCESS, 0, 19) failed: %m\n"); + exit(23); + } + if (ioprio_set(IOPRIO_WHO_PROCESS, 0, + IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)) == -1) { + kprintf("error: ioprio_set() failed: %m\n"); + exit(23); + } + struct sched_param p = {sched_get_priority_min(SCHED_IDLE)}; + if (sched_setscheduler(0, SCHED_IDLE, &p) == -1) { + kprintf("error: sched_setscheduler(SCHED_IDLE) failed: %m\n"); + exit(23); + } +} + int main(int argc, char *argv[]) { bool hasfunbits; int useruid, usergid; @@ -172,13 +294,13 @@ int main(int argc, char *argv[]) { if (!IsLinux()) { kprintf("error: this program is only intended for linux\n"); - exit(__COUNTER__); + exit(5); } // parse flags GetOpts(argc, argv); if (optind == argc) { - kprintf("error: too few args\n", g_pflag); + kprintf("error: too few args\n"); write(2, USAGE, sizeof(USAGE) - 1); exit(64); } @@ -187,6 +309,13 @@ int main(int argc, char *argv[]) { NormalizeFileDescriptors(); } + // set resource limits + MakeProcessNice(); + SetCpuLimit(g_cpuquota); + SetFszLimit(g_fszquota); + SetMemLimit(g_memquota); + SetProLimit(g_proquota); + // test for weird chmod bits usergid = getgid(); ownergid = getegid(); @@ -203,7 +332,7 @@ int main(int argc, char *argv[]) { if (hasfunbits) { if (g_uflag || g_gflag) { kprintf("error: setuid flags forbidden on setuid binaries\n"); - _Exit(__COUNTER__); + _Exit(6); } } @@ -213,7 +342,7 @@ int main(int argc, char *argv[]) { oldfsgid = setfsgid(usergid); if (access(g_chroot, R_OK) == -1) { kprintf("error: access(%#s) failed: %m\n", g_chroot); - _Exit(__COUNTER__); + _Exit(7); } setfsuid(oldfsuid); setfsgid(oldfsgid); @@ -230,11 +359,11 @@ int main(int argc, char *argv[]) { if (g_chroot) { if (chdir(g_chroot) == -1) { kprintf("error: chdir(%#s) failed: %m\n", g_chroot); - _Exit(__COUNTER__); + _Exit(8); } if (chroot(g_chroot) == -1) { kprintf("error: chroot(%#s) failed: %m\n", g_chroot); - _Exit(__COUNTER__); + _Exit(9); } } @@ -245,7 +374,7 @@ int main(int argc, char *argv[]) { } if (!(prog = commandv(argv[optind], pathbuf, sizeof(pathbuf)))) { kprintf("error: command not found: %m\n", argv[optind]); - _Exit(__COUNTER__); + _Exit(10); } if (hasfunbits) { setfsuid(oldfsuid); @@ -257,21 +386,21 @@ int main(int argc, char *argv[]) { // 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(__COUNTER__); + _Exit(11); } if (getgid() != usergid || getegid() != usergid) { kprintf("error: setgid() broken\n"); - _Exit(__COUNTER__); + _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(__COUNTER__); + _Exit(13); } if (getgid() != g_gflag || getegid() != g_gflag) { kprintf("error: setgid() broken\n"); - _Exit(__COUNTER__); + _Exit(14); } } @@ -280,29 +409,28 @@ int main(int argc, char *argv[]) { // 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(__COUNTER__); + _Exit(15); } if (getuid() != useruid || geteuid() != useruid) { kprintf("error: setuid() broken\n"); - _Exit(__COUNTER__); + _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(__COUNTER__); + _Exit(17); } if (getuid() != g_uflag || geteuid() != g_uflag) { kprintf("error: setuid() broken\n"); - _Exit(__COUNTER__); + _Exit(18); } } // apply sandbox - ksnprintf(pledges, sizeof(pledges), "%s execnative", g_pflag); - if (pledge(pledges, 0) == -1) { - kprintf("error: pledge(%#s) failed: %m\n", pledges); - _Exit(__COUNTER__); + if (pledge(g_promises, 0) == -1) { + kprintf("error: pledge(%#s) failed: %m\n", g_promises); + _Exit(19); } // launch program