From 7b284f6bda063b02fcab4575c712613cac0aa330 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 1 Nov 2023 06:08:58 -0700 Subject: [PATCH] Fix bugs and regressions in the pledge command This change gets the pledge (formerly pledge.com) command back in tip top shape for a 3.0.1 cosmos release. It now runs on all platforms, even though it's mostly a no-op on ones that lack the kernel security stuff. The binary footprint is now smaller, since it no longer needs to link malloc. It's also now able to be built as a fat binary. --- libc/calls/closefrom.c | 7 +- libc/calls/copy.c | 2 +- libc/calls/isapemagic.c | 3 +- libc/calls/parsepromises.c | 3 + libc/calls/pledge-linux.c | 5 +- libc/calls/stat2cosmo.c | 1 + libc/calls/struct/sysinfo.h | 2 +- libc/calls/sysinfo-nt.c | 50 +++- libc/calls/unveil.c | 52 +--- libc/integral/normalize.inc | 4 + libc/intrin/kprintf.greg.c | 6 + libc/str/joinpaths.c | 69 +++++ libc/str/str.h | 1 + test/tool/build/pledge_test.sh | 14 +- tool/build/build.mk | 6 +- tool/build/dso/sandbox.c | 6 +- tool/build/pledge.c | 473 ++++++++++++++++++++------------- tool/build/verynice.c | 61 +++++ 18 files changed, 493 insertions(+), 272 deletions(-) create mode 100644 libc/str/joinpaths.c create mode 100644 tool/build/verynice.c diff --git a/libc/calls/closefrom.c b/libc/calls/closefrom.c index b4bb8f5c4..791ce21ed 100644 --- a/libc/calls/closefrom.c +++ b/libc/calls/closefrom.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -37,13 +38,12 @@ * @raise EBADF on OpenBSD if `first` is greater than highest fd * @raise EINVAL if flags are bad or first is greater than last * @raise EMFILE if a weird race condition happens on Linux - * @raise ECANCELED if thread was cancelled in masked mode * @raise EINTR possibly on OpenBSD * @raise ENOMEM on Linux maybe */ int closefrom(int first) { - int rc, err; - (void)err; + int rc; + BLOCK_CANCELATION; if (first < 0) { // consistent with openbsd // freebsd allows this but it's dangerous @@ -58,6 +58,7 @@ int closefrom(int first) { } else { rc = enosys(); } + ALLOW_CANCELATION; STRACE("closefrom(%d) → %d% m", first, rc); return rc; } diff --git a/libc/calls/copy.c b/libc/calls/copy.c index b8bd92968..caf5f374f 100644 --- a/libc/calls/copy.c +++ b/libc/calls/copy.c @@ -44,7 +44,7 @@ ssize_t copyfd(int in, int out, size_t n) { if (dw != dr) { // POSIX requires atomic IO up to PIPE_BUF // The minimum permissible PIPE_BUF is 512 - abort(); + notpossible; } } return i; diff --git a/libc/calls/isapemagic.c b/libc/calls/isapemagic.c index a01946e8a..0da408fad 100644 --- a/libc/calls/isapemagic.c +++ b/libc/calls/isapemagic.c @@ -25,5 +25,6 @@ bool IsApeLoadable(char buf[8]) { return READ32LE(buf) == READ32LE("\177ELF") || READ64LE(buf) == READ64LE("MZqFpD='") || - READ64LE(buf) == READ64LE("JTqFpD='"); + READ64LE(buf) == READ64LE("jartsr='") || + READ64LE(buf) == READ64LE("APEDBG='"); } diff --git a/libc/calls/parsepromises.c b/libc/calls/parsepromises.c index c5de9e150..878c6e3a0 100644 --- a/libc/calls/parsepromises.c +++ b/libc/calls/parsepromises.c @@ -33,6 +33,9 @@ static int FindPromise(const char *name) { /** * Parses the arguments to pledge() into a bitmask. * + * @param out receives the integral promises mask, which zero is defined + * as the set of all promises, and -1 is defined as the empty set of + * promises, which is equivalent to `promises` being an empty string * @return 0 on success, or -1 if invalid */ int ParsePromises(const char *promises, unsigned long *out, diff --git a/libc/calls/pledge-linux.c b/libc/calls/pledge-linux.c index 2a36e362d..e59bd903c 100644 --- a/libc/calls/pledge-linux.c +++ b/libc/calls/pledge-linux.c @@ -535,7 +535,10 @@ static const struct thatispacked SyscallName { }; static const uint16_t kPledgeDefault[] = { - __NR_linux_exit, // thread return / exit() + __NR_linux_exit, // thread return / exit() +#ifdef __NR_linux_arch_prctl // + __NR_linux_arch_prctl, // or else launching musl process crashes (tls) +#endif // }; // stdio contains all the benign system calls. openbsd makes the diff --git a/libc/calls/stat2cosmo.c b/libc/calls/stat2cosmo.c index c97aba344..573683fbc 100644 --- a/libc/calls/stat2cosmo.c +++ b/libc/calls/stat2cosmo.c @@ -29,6 +29,7 @@ void __stat2cosmo(struct stat *restrict st, const union metastat *ms) { st->st_uid = ms->linux.st_uid; st->st_gid = ms->linux.st_gid; st->st_flags = 0; + st->st_gen = 0; st->st_rdev = ms->linux.st_rdev; st->st_size = ms->linux.st_size; st->st_blksize = ms->linux.st_blksize; diff --git a/libc/calls/struct/sysinfo.h b/libc/calls/struct/sysinfo.h index da90cf03a..3df62822b 100644 --- a/libc/calls/struct/sysinfo.h +++ b/libc/calls/struct/sysinfo.h @@ -12,7 +12,7 @@ struct sysinfo { uint64_t bufferram; /* lingering disk pages; see fadvise */ uint64_t totalswap; /* size of emergency memory */ uint64_t freeswap; /* hopefully equal to totalswap */ - int16_t procs; /* number of processes */ + uint16_t procs; /* number of processes */ int16_t __ignore1; /* padding */ int32_t __ignore2; /* padding */ uint64_t totalhigh; /* wut */ diff --git a/libc/calls/sysinfo-nt.c b/libc/calls/sysinfo-nt.c index da876d9a6..98e79da7e 100644 --- a/libc/calls/sysinfo-nt.c +++ b/libc/calls/sysinfo-nt.c @@ -16,27 +16,55 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/sysinfo.h" -#include "libc/calls/syscall_support-nt.internal.h" +#include "libc/calls/struct/sysinfo.internal.h" +#include "libc/calls/syscall-nt.internal.h" +#include "libc/intrin/weaken.h" +#include "libc/mem/mem.h" #include "libc/nt/accounting.h" +#include "libc/nt/process.h" #include "libc/nt/struct/memorystatusex.h" -#include "libc/nt/struct/systeminfo.h" #include "libc/nt/synchronization.h" -#include "libc/nt/systeminfo.h" + +static textwindows uint16_t GetProcessCount(void) { + uint16_t res; + uint32_t have, got, *pids; + uint32_t stack_memory[1000]; + have = 0xFFFF * 4; + if (!_weaken(malloc) || !(pids = _weaken(malloc)(have))) { + pids = stack_memory; + have = sizeof(stack_memory); + } + if (EnumProcesses(pids, have, &got)) { + res = got / 4; + } else { + res = 0; + } + if (pids != stack_memory && _weaken(free)) { + _weaken(free)(pids); + } + return res; +} textwindows int sys_sysinfo_nt(struct sysinfo *info) { + int i; + double load[3]; struct NtMemoryStatusEx memstat; - struct NtSystemInfo sysinfo; - GetSystemInfo(&sysinfo); + BLOCK_SIGNALS; + if (sys_getloadavg_nt(load, 3) != -1) { + for (i = 0; i < 3; ++i) { + info->loads[i] = load[i] * 65536; + } + } memstat.dwLength = sizeof(struct NtMemoryStatusEx); if (GlobalMemoryStatusEx(&memstat)) { - info->mem_unit = 1; info->totalram = memstat.ullTotalPhys; info->freeram = memstat.ullAvailPhys; - info->procs = sysinfo.dwNumberOfProcessors; - info->uptime = GetTickCount64() / 1000; - return 0; - } else { - return __winerr(); } + info->uptime = GetTickCount64() / 1000; + info->procs = GetProcessCount(); + info->mem_unit = 1; + ALLOW_SIGNALS; + return 0; } diff --git a/libc/calls/unveil.c b/libc/calls/unveil.c index 0942c8372..0616d603a 100644 --- a/libc/calls/unveil.c +++ b/libc/calls/unveil.c @@ -188,56 +188,6 @@ static int unveil_init(void) { return 0; } -/** - * Joins paths, e.g. - * - * 0 + 0 → 0 - * "" + "" → "" - * "a" + 0 → "a" - * "a" + "" → "a/" - * 0 + "b" → "b" - * "" + "b" → "b" - * "." + "b" → "./b" - * "b" + "." → "b/." - * "a" + "b" → "a/b" - * "a/" + "b" → "a/b" - * "a" + "b/" → "a/b/" - * "a" + "/b" → "/b" - * - * @return joined path, which may be `buf`, `path`, or `other`, or null - * if (1) `buf` didn't have enough space, or (2) both `path` and - * `other` were null - */ -static char *JoinPaths(char *buf, size_t size, const char *path, - const char *other) { - size_t pathlen, otherlen; - if (!other) return (char *)path; - if (!path) return (char *)other; - pathlen = strlen(path); - if (!pathlen || *other == '/') { - return (/*unconst*/ char *)other; - } - otherlen = strlen(other); - if (path[pathlen - 1] == '/') { - if (pathlen + otherlen + 1 <= size) { - memmove(buf, path, pathlen); - memmove(buf + pathlen, other, otherlen + 1); - return buf; - } else { - return 0; - } - } else { - if (pathlen + 1 + otherlen + 1 <= size) { - memmove(buf, path, pathlen); - buf[pathlen] = '/'; - memmove(buf + pathlen + 1, other, otherlen + 1); - return buf; - } else { - return 0; - } - } -} - int sys_unveil_linux(const char *path, const char *permissions) { #pragma GCC push_options #pragma GCC diagnostic ignored "-Wframe-larger-than=" @@ -302,7 +252,7 @@ int sys_unveil_linux(const char *path, const char *permissions) { // next = join(dirname(next), link) strcpy(b.buf2, next); dir = dirname(b.buf2); - if ((next = JoinPaths(b.buf3, PATH_MAX, dir, b.lbuf))) { + if ((next = __join_paths(b.buf3, PATH_MAX, dir, b.lbuf))) { // next now points to either: buf3, buf2, lbuf, rodata strcpy(b.buf4, next); next = b.buf4; diff --git a/libc/integral/normalize.inc b/libc/integral/normalize.inc index ef9834e93..5bc31157e 100644 --- a/libc/integral/normalize.inc +++ b/libc/integral/normalize.inc @@ -1,3 +1,7 @@ +#ifdef __COSMOPOLITAN__ +#undef __COSMOPOLITAN__ +#endif + #define __COSMOPOLITAN_MAJOR__ 3 #define __COSMOPOLITAN_MINOR__ 0 #define __COSMOPOLITAN_PATCH__ 0 diff --git a/libc/intrin/kprintf.greg.c b/libc/intrin/kprintf.greg.c index 09dbdf70f..1cefe2480 100644 --- a/libc/intrin/kprintf.greg.c +++ b/libc/intrin/kprintf.greg.c @@ -430,6 +430,9 @@ privileged void klog(const char *b, size_t n) { : "=a"(rax), "=D"(rdi), "=S"(rsi), "=d"(rdx) : "0"(__NR_write), "1"(h), "2"(b), "3"(n) : "rcx", "r8", "r9", "r10", "r11", "memory", "cc"); + if (rax < 0) { + __klog_handle = 0; + } } #elif defined(__aarch64__) // this isn't a cancelation point because we don't acknowledge eintr @@ -444,6 +447,9 @@ privileged void klog(const char *b, size_t n) { : "=r"(res_x0) : "r"(r0), "r"(r1), "r"(r2), "r"(r8), "r"(r16) : "memory"); + if (res_x0 < 0) { + __klog_handle = 0; + } #else #error "unsupported architecture" #endif diff --git a/libc/str/joinpaths.c b/libc/str/joinpaths.c new file mode 100644 index 000000000..af0265b1f --- /dev/null +++ b/libc/str/joinpaths.c @@ -0,0 +1,69 @@ +/*-*- 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 2023 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/str/str.h" + +/** + * Joins paths, e.g. + * + * 0 + 0 → 0 + * "" + "" → "" + * "a" + 0 → "a" + * "a" + "" → "a/" + * 0 + "b" → "b" + * "" + "b" → "b" + * "." + "b" → "./b" + * "b" + "." → "b/." + * "a" + "b" → "a/b" + * "a/" + "b" → "a/b" + * "a" + "b/" → "a/b/" + * "a" + "/b" → "/b" + * + * @return joined path, which may be `buf`, `path`, or `other`, or null + * if (1) `buf` didn't have enough space, or (2) both `path` and + * `other` were null + */ +char *__join_paths(char *buf, size_t size, const char *path, + const char *other) { + size_t pathlen, otherlen; + if (!other) return (char *)path; + if (!path) return (char *)other; + pathlen = strlen(path); + if (!pathlen || *other == '/') { + return (/*unconst*/ char *)other; + } + otherlen = strlen(other); + if (path[pathlen - 1] == '/') { + if (pathlen + otherlen + 1 <= size) { + memmove(buf, path, pathlen); + memmove(buf + pathlen, other, otherlen + 1); + return buf; + } else { + return 0; + } + } else { + if (pathlen + 1 + otherlen + 1 <= size) { + memmove(buf, path, pathlen); + buf[pathlen] = '/'; + memmove(buf + pathlen + 1, other, otherlen + 1); + return buf; + } else { + return 0; + } + } +} diff --git a/libc/str/str.h b/libc/str/str.h index b30486b04..7891a647a 100644 --- a/libc/str/str.h +++ b/libc/str/str.h @@ -217,6 +217,7 @@ axdx_t tprecode8to16(char16_t *, size_t, const char *); axdx_t tprecode16to8(char *, size_t, const char16_t *); bool wcsstartswith(const wchar_t *, const wchar_t *) strlenesque; bool wcsendswith(const wchar_t *, const wchar_t *) strlenesque; +char *__join_paths(char *, size_t, const char *, const char *) __wur; #endif /* _COSMO_SOURCE */ COSMOPOLITAN_C_END_ diff --git a/test/tool/build/pledge_test.sh b/test/tool/build/pledge_test.sh index 966ea6c99..92b1a6953 100755 --- a/test/tool/build/pledge_test.sh +++ b/test/tool/build/pledge_test.sh @@ -6,7 +6,7 @@ if [ $# = 0 ]; then if ! [ $(id -u) = 0 ]; then make -j16 MODE=fastbuild \ o/fastbuild/examples/ls.com \ - o/fastbuild/examples/curl.com \ + o/fastbuild/tool/curl/curl.com \ o/fastbuild/examples/life.com \ o/fastbuild/examples/hello.com \ o/fastbuild/examples/printargs.com \ @@ -14,7 +14,7 @@ if [ $# = 0 ]; then o/fastbuild/tool/build/pledge.com || exit make -j16 MODE=$m \ o/$m/examples/ls.com \ - o/$m/examples/curl.com \ + o/$m/tool/curl/curl.com \ o/$m/examples/life.com \ o/$m/examples/hello.com \ o/$m/examples/printargs.com \ @@ -76,7 +76,7 @@ elif [ "$1" = ape_binfmt_test_suite ]; then checkem startit ape binfmt curl.com - [ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ] + [ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ] checkem elif [ "$1" = ape_loader_test_suite ]; then @@ -93,7 +93,7 @@ elif [ "$1" = ape_loader_test_suite ]; then checkem startit ape loader curl.com - [ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ] + [ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ] checkem ape/apeinstall.sh >/dev/null 2>&1 @@ -116,7 +116,7 @@ elif [ "$1" = ape_assimilated_test_suite ]; then checkem startit ape assimilated curl.com - cp o/fastbuild/examples/curl.com $t/assimilated + cp o/fastbuild/tool/curl/curl.com $t/assimilated o/fastbuild/tool/build/assimilate.com $t/assimilated/curl.com [ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' $t/assimilated/curl.com https://justine.lol/hello.txt)" = "hello world" ] checkem @@ -133,7 +133,7 @@ elif [ "$1" = ape_native_test_suite ]; then checkem startit ape native curl.com - [ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ] + [ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' o/$m/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ] checkem elif [ "$1" = setuid_test_suite ]; then @@ -148,7 +148,7 @@ elif [ "$1" = setuid_test_suite ]; then checkem startit setuid curl.com - [ "$($t/pledge.com -p 'stdio rpath inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ] + [ "$($t/pledge.com -p 'stdio rpath inet dns' o/$m/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ] checkem startit setuid getuid diff --git a/tool/build/build.mk b/tool/build/build.mk index 46a801555..7e50d9c86 100644 --- a/tool/build/build.mk +++ b/tool/build/build.mk @@ -77,7 +77,7 @@ o/$(MODE)/tool/build/%.com.dbg: \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/tool/build/dso/sandbox.so.zip.o \ +o/$(MODE)/tool/build/dso/sandbox-$(ARCH).so.zip.o \ o/$(MODE)/tool/build/false.com.zip.o \ o/$(MODE)/tool/build/echo.com.zip.o \ o/$(MODE)/tool/build/cocmd.com.zip.o: private \ @@ -99,7 +99,7 @@ o/$(MODE)/tool/build/dso/sandbox.o: \ libc/intrin/promises.internal.h \ tool/build/build.mk -o/$(MODE)/tool/build/dso/sandbox.so: \ +o/$(MODE)/tool/build/dso/sandbox-$(ARCH).so: \ o/$(MODE)/tool/build/dso/sandbox.o \ o/$(MODE)/libc/calls/pledge-linux.o \ o/$(MODE)/libc/sysv/restorert.o @@ -118,7 +118,7 @@ o/$(MODE)/tool/build/dso/sandbox.so: \ o/$(MODE)/tool/build/pledge.com.dbg: \ $(TOOL_BUILD_DEPS) \ o/$(MODE)/tool/build/build.pkg \ - o/$(MODE)/tool/build/dso/sandbox.so.zip.o \ + o/$(MODE)/tool/build/dso/sandbox-$(ARCH).so.zip.o \ o/$(MODE)/tool/build/pledge.o \ $(CRT) \ $(APE_NO_MODIFY_SELF) diff --git a/tool/build/dso/sandbox.c b/tool/build/dso/sandbox.c index 57b98d168..25f2f2b92 100644 --- a/tool/build/dso/sandbox.c +++ b/tool/build/dso/sandbox.c @@ -22,10 +22,8 @@ #include "libc/intrin/promises.internal.h" #include "libc/runtime/runtime.h" -/* - * runs pledge at glibc executable load time, e.g. - * strace -vff bash -c '_PLEDGE=4194303,0 LD_PRELOAD=$HOME/sandbox.so ls' - */ +// runs pledge at glibc executable load time, e.g. +// strace -vff bash -c '_PLEDGE=4194303,0 LD_PRELOAD=$HOME/sandbox.so ls' __attribute__((__constructor__)) void init(void) { int c, i, j; diff --git a/tool/build/pledge.c b/tool/build/pledge.c index dfb44a3cf..05c90eee1 100644 --- a/tool/build/pledge.c +++ b/tool/build/pledge.c @@ -17,7 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/pledge.h" -#include "libc/assert.h" +#include "ape/ape.h" #include "libc/calls/calls.h" #include "libc/calls/landlock.h" #include "libc/calls/pledge.internal.h" @@ -26,6 +26,7 @@ #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" @@ -35,6 +36,7 @@ #include "libc/elf/struct/phdr.h" #include "libc/errno.h" #include "libc/fmt/conv.h" +#include "libc/fmt/itoa.h" #include "libc/intrin/bits.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/promises.internal.h" @@ -42,13 +44,14 @@ #include "libc/limits.h" #include "libc/macros.internal.h" #include "libc/math.h" -#include "libc/mem/gc.internal.h" -#include "libc/mem/mem.h" +#include "libc/mem/alloca.h" #include "libc/nexgen32e/kcpuids.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/stack.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" @@ -75,7 +78,7 @@ __static_yoink("zipos"); #define USAGE \ "\ -usage: pledge.com [-hnN] PROG ARGS...\n\ +usage: pledge [-hnN] PROG ARGS...\n\ -h show help\n\ -g GID call setgid()\n\ -u UID call setuid()\n\ @@ -116,7 +119,7 @@ usage: pledge.com [-hnN] PROG ARGS...\n\ - vminfo: allows /proc/stat, /proc/self/maps, etc.\n\ - tmppath: allows /tmp, $TMPPATH, lstat, unlink\n\ \n\ -pledge.com v1.8\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\ @@ -130,13 +133,27 @@ 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 isdynamic; bool g_noclose; long g_cpuquota; long g_fszquota; @@ -147,19 +164,60 @@ long g_dontdrop; long g_dontunveil; const char *g_test; const char *g_chroot; -const char *g_promises; +char pledgevar[64]; +char g_promises[256]; char dsopath[PATH_MAX]; char tmppath[PATH_MAX]; +char preloadvar[PATH_MAX]; struct { int n; - char **p; + char *p[10000]; } unveils; -static void GetOpts(int argc, char *argv[]) { +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; - g_promises = 0; + bool got_promise_flag = false; g_nfdquota = 64; g_fszquota = 256 * 1000 * 1000; if (!sysinfo(&si)) { @@ -196,46 +254,36 @@ static void GetOpts(int argc, char *argv[]) { g_chroot = optarg; break; case 'g': - g_gflag = atoi(optarg); + g_gflag = Atoi(optarg); break; case 'u': - g_uflag = atoi(optarg); + g_uflag = Atoi(optarg); break; case 'C': - g_cpuquota = atoi(optarg); + g_cpuquota = Atoi(optarg); break; case 'P': - g_proquota = atoi(optarg); + g_proquota = Atoi(optarg); break; case 'O': - g_nfdquota = atoi(optarg); + 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); - } + g_fszquota = ParseSiSize(optarg, 1024); break; case 'M': - errno = 0; - g_memquota = sizetol(optarg, 1024); - if (errno) { - kprintf("error: invalid size: -F %s\n", optarg); - exit(1); - } + g_memquota = ParseSiSize(optarg, 1024); break; case 'p': - if (g_promises) { - g_promises = xstrcat(g_promises, ' ', optarg); - } else { - g_promises = optarg; - } + AddPromise(optarg); + got_promise_flag = true; break; case 'v': - unveils.p = realloc(unveils.p, ++unveils.n * sizeof(*unveils.p)); - unveils.p[unveils.n - 1] = optarg; + 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 '?': @@ -246,16 +294,16 @@ static void GetOpts(int argc, char *argv[]) { exit(64); } } - if (!g_promises) { - g_promises = "stdio rpath"; + if (!got_promise_flag) { + stpcpy(g_promises, "stdio rpath"); } } -const char *prog; +const char *g_prog; char pathbuf[PATH_MAX]; struct pollfd pfds[256]; -static bool SupportsLandlock(void) { +bool SupportsLandlock(void) { int e = errno; bool r = landlock_create_ruleset(0, 0, LANDLOCK_CREATE_RULESET_VERSION) >= 0; errno = e; @@ -265,7 +313,7 @@ static bool SupportsLandlock(void) { int GetPollMaxFds(void) { int n; struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) != -1) { + if (!getrlimit(RLIMIT_NOFILE, &rl)) { n = rl.rlim_cur; } else { n = 64; @@ -274,27 +322,26 @@ int GetPollMaxFds(void) { } void NormalizeFileDescriptors(void) { - int e, i, n, fd; + int i, n, fd; + closefrom(3); // faster and more secure if linux 5.9+ or bsd 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"); + perror("poll"); 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"); + perror("/dev/null"); exit(2); } if (fd != i) { - kprintf("error: open() is broken: %d vs. %d\n", fd, i); + tinyprint(2, program_invocation_name, ": poll() or open() is broken\n", + NULL); exit(3); } } @@ -302,7 +349,7 @@ void NormalizeFileDescriptors(void) { 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); + perror("close"); exit(4); } } @@ -354,8 +401,8 @@ bool PathExists(const char *path) { 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); + perror(path); + exit(20); } } @@ -368,22 +415,62 @@ int UnveilIfExists(const char *path, const char *perm) { } else if (errno == ENOENT) { errno = err; } else { - kprintf("error: unveil(%#s, %#s) failed: %m\n", path, perm); - _Exit(20); + 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(prog, "rx"); + Unveil(g_prog, "rx"); - if (isdynamic) { + if (GetStrategy() == kStrategyDynamic) { Unveil(dsopath, "rx"); UnveilIfExists("/lib", "rx"); UnveilIfExists("/lib64", "rx"); @@ -391,14 +478,16 @@ void ApplyFilesystemPolicy(unsigned long ipromises) { 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-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 (~ipromises & (1ul << PROMISE_STDIO)) { + if (HasPromise(ipromises, PROMISE_STDIO)) { UnveilIfExists("/dev/fd", "r"); UnveilIfExists("/dev/log", "w"); UnveilIfExists("/dev/zero", "r"); @@ -422,15 +511,15 @@ void ApplyFilesystemPolicy(unsigned long ipromises) { UnveilIfExists("/proc/sys/vm/overcommit_memory", "r"); } - if (~ipromises & (1ul << PROMISE_INET)) { + if (HasPromise(ipromises, PROMISE_INET)) { UnveilIfExists("/etc/ssl/certs/ca-certificates.crt", "r"); } - if (~ipromises & (1ul << PROMISE_RPATH)) { + if (HasPromise(ipromises, PROMISE_RPATH)) { UnveilIfExists("/proc/filesystems", "r"); } - if (~ipromises & (1ul << PROMISE_DNS)) { + if (HasPromise(ipromises, PROMISE_DNS)) { UnveilIfExists("/etc/hosts", "r"); UnveilIfExists("/etc/hostname", "r"); UnveilIfExists("/etc/services", "r"); @@ -438,7 +527,7 @@ void ApplyFilesystemPolicy(unsigned long ipromises) { UnveilIfExists("/etc/resolv.conf", "r"); } - if (~ipromises & (1ul << PROMISE_TTY)) { + if (HasPromise(ipromises, PROMISE_TTY)) { UnveilIfExists(ttyname(0), "rw"); UnveilIfExists("/dev/tty", "rw"); UnveilIfExists("/dev/console", "rw"); @@ -447,18 +536,21 @@ void ApplyFilesystemPolicy(unsigned long ipromises) { UnveilIfExists("/usr/share/terminfo", "r"); } - if (~ipromises & (1ul << PROMISE_PROT_EXEC)) { + if (GetStrategy() == kStrategyApe) { if (UnveilIfExists("/usr/bin/ape", "rx") == -1) { + char buf[PATH_MAX]; if ((p = getenv("TMPDIR"))) { - UnveilIfExists(xjoinpaths(p, ".ape"), "rx"); + UnveilIfExists( + __join_paths(buf, sizeof(buf), p, ".ape-" APE_VERSION_STR), "rx"); } if ((p = getenv("HOME"))) { - UnveilIfExists(xjoinpaths(p, ".ape"), "rx"); + UnveilIfExists( + __join_paths(buf, sizeof(buf), p, ".ape-" APE_VERSION_STR), "rx"); } } } - if (~ipromises & (1ul << PROMISE_VMINFO)) { + if (HasPromise(ipromises, PROMISE_VMINFO)) { UnveilIfExists("/proc/stat", "r"); UnveilIfExists("/proc/meminfo", "r"); UnveilIfExists("/proc/cpuinfo", "r"); @@ -467,7 +559,7 @@ void ApplyFilesystemPolicy(unsigned long ipromises) { UnveilIfExists("/sys/devices/system/cpu", "r"); } - if (~ipromises & (1ul << PROMISE_TMPPATH)) { + if (HasPromise(ipromises, PROMISE_TMPPATH)) { UnveilIfExists("/tmp", "rwc"); UnveilIfExists(getenv("TMPPATH"), "rwc"); } @@ -489,21 +581,22 @@ void ApplyFilesystemPolicy(unsigned long ipromises) { } if (unveil(0, 0) == -1) { - kprintf("error: unveil(0, 0) failed: %m\n"); - _Exit(20); + 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 { - kprintf("error: prctl(PR_CAPBSET_DROP, %d) failed: %m\n", i); - _Exit(25); + perror("prctl(PR_CAPBSET_DROP)"); + exit(25); } } } @@ -533,68 +626,20 @@ int Extract(const char *from, const char *to, int mode) { 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; +int CountEnviron(char **ep) { + int res = 0; + while (*ep++) ++res; return res; } int main(int argc, char *argv[]) { const char *s; bool hasfunbits; - char buf[PATH_MAX]; 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) { @@ -612,41 +657,33 @@ int main(int argc, char *argv[]) { exit(1); } } - kprintf("error: unknown test: %s\n", g_test); + tinyprint(2, g_test, ": unknown test\n", NULL); exit(2); } if (optind == argc) { - kprintf("error: too few args\n"); + 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) { - kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_CPU"); - exit(1); + perror("setrlimit(RLIMIT_CPU)"); } - if (SetLimit(RLIMIT_FSIZE, g_fszquota, g_fszquota * 1.5) == -1) { - kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_FSIZE"); - exit(1); + perror("setrlimit(RLIMIT_FSIZE)"); } - if (SetLimit(RLIMIT_AS, g_memquota, g_memquota) == -1) { - kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_AS"); - exit(1); + perror("setrlimit(RLIMIT_AS)"); } - if (SetLimit(RLIMIT_NPROC, g_proquota, g_proquota) == -1) { - kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_NPROC"); - exit(1); + perror("setrlimit(RLIMIT_NPROC)"); } // test for weird chmod bits @@ -664,8 +701,9 @@ int main(int argc, char *argv[]) { // 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); + tinyprint(2, program_invocation_name, + ": setuid flags forbidden on setuid binaries\n", NULL); + exit(6); } } @@ -674,8 +712,8 @@ int main(int argc, char *argv[]) { oldfsuid = setfsuid(useruid); oldfsgid = setfsgid(usergid); if (access(g_chroot, R_OK) == -1) { - kprintf("error: access(%#s) failed: %m\n", g_chroot); - _Exit(7); + perror(g_chroot); + exit(7); } setfsuid(oldfsuid); setfsgid(oldfsgid); @@ -684,12 +722,12 @@ int main(int argc, char *argv[]) { // change root fs path if (g_chroot) { if (chdir(g_chroot) == -1) { - kprintf("error: chdir(%#s) failed: %m\n", g_chroot); - _Exit(8); + perror(g_chroot); + exit(8); } if (chroot(g_chroot) == -1) { - kprintf("error: chroot(%#s) failed: %m\n", g_chroot); - _Exit(9); + perror(g_chroot); + exit(9); } } @@ -698,42 +736,63 @@ int main(int argc, char *argv[]) { 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 (!(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 (IsDynamicExecutable(prog)) { - isdynamic = true; + if (GetStrategy() == kStrategyDynamic) { if ((s = getenv("TMPDIR")) || // (s = getenv("HOME")) || // (s = ".")) { - ksnprintf(dsopath, sizeof(dsopath), "%s/sandbox.so", s); + strlcpy(dsopath, s, sizeof(dsopath)); + strlcat(dsopath, "/.pledge-sandbox.so", sizeof(dsopath)); 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"); + 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) { - kprintf("error: rename dso failed: %m\n"); + perror(dsopath); exit(1); } } - ksnprintf(buf, sizeof(buf), "LD_PRELOAD=%s", dsopath); - putenv(buf); + stpcpy(preloadvar, "LD_PRELOAD="); + strlcat(preloadvar, dsopath, sizeof(preloadvar)); + child_environ[child_environ_count++] = preloadvar; } } if (g_dontdrop) { if (hasfunbits) { - kprintf("error: -D flag forbidden on setuid binaries\n"); - _Exit(6); + tinyprint(2, program_invocation_name, + ": -D flag forbidden on setuid binaries\n", NULL); + exit(6); } } else { DropCapabilities(); @@ -743,22 +802,22 @@ int main(int argc, char *argv[]) { 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); + perror("setgid"); + exit(11); } if (getgid() != usergid || getegid() != usergid) { - kprintf("error: setgid() broken\n"); - _Exit(12); + tinyprint(2, "error: setgid() broken\n", NULL); + 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); + perror("setgid"); + exit(13); } if (getgid() != g_gflag || getegid() != g_gflag) { - kprintf("error: setgid() broken\n"); - _Exit(14); + tinyprint(2, "error: setgid() broken\n", NULL); + exit(14); } } @@ -766,30 +825,34 @@ int main(int argc, char *argv[]) { 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); + perror("setuid"); + exit(15); } if (getuid() != useruid || geteuid() != useruid) { - kprintf("error: setuid() broken\n"); - _Exit(16); + tinyprint(2, "error: setuid() broken\n", NULL); + 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); + perror("setuid"); + exit(17); } if (getuid() != g_uflag || geteuid() != g_uflag) { - kprintf("error: setuid() broken\n"); - _Exit(18); + 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) { - kprintf("error: bad promises list: %s\n", g_promises); - _Exit(21); + tinyprint(2, program_invocation_name, ": bad promises list: ", g_promises, + "\n", NULL); + exit(21); } + // perform unveiling ApplyFilesystemPolicy(ipromises); // pledge.com uses the return eperm instead of killing the process @@ -802,38 +865,70 @@ int main(int argc, char *argv[]) { __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"); + // 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 (isdynamic) { - g_promises = xstrcat(g_promises, ' ', "prot_exec"); + if (GetStrategy() != kStrategyStatic) { + if (!HasPromise(ipromises, PROMISE_RPATH)) { + AddPromise("rpath"); + } + if (!HasPromise(ipromises, PROMISE_PROT_EXEC)) { + AddPromise("prot_exec"); + } } - // pass arguments to pledge() inside the dso - if (isdynamic) { - ksnprintf(buf, sizeof(buf), "_PLEDGE=%ld,%ld", ~ipromises, __pledge_mode); - putenv(buf); + // 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) { - kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_NOFILE"); + perror("setrlimit(RLIMIT_NOFILE)"); exit(1); } // apply sandbox if (pledge(g_promises, g_promises) == -1) { - kprintf("error: pledge(%#s) failed: %m\n", g_promises); - _Exit(19); + perror("pledge"); + exit(19); } // launch program - sys_execve(prog, argv + optind, environ); - kprintf("%s: execve failed: %m\n", prog); + 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; } diff --git a/tool/build/verynice.c b/tool/build/verynice.c new file mode 100644 index 000000000..a3205ba31 --- /dev/null +++ b/tool/build/verynice.c @@ -0,0 +1,61 @@ +/*-*- 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 2023 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/calls.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "third_party/getopt/getopt.internal.h" + +/** + * @fileoverview `verynice foo` launches `foo` as low priority as possible + * + * This is particularly useful on Linux systems with a spinning disk + * where the classic `nice` command doesn't do much. + */ + +const char *prog; + +static wontreturn void PrintUsage(int rc, int fd) { + tinyprint(fd, "Usage: ", prog, " COMMAND...\n", NULL); + exit(rc); +} + +int main(int argc, char *argv[]) { + + prog = argv[0]; + if (!prog) prog = "verynice"; + + int opt; + while ((opt = getopt(argc, argv, "h")) != -1) { + switch (opt) { + case 'h': + PrintUsage(0, 1); + default: + PrintUsage(1, 2); + } + } + if (optind == argc) { + tinyprint(2, prog, ": missing command\n", NULL); + exit(1); + } + + verynice(); + execvp(argv[optind], argv + optind); + perror(argv[optind]); + exit(127); +}