From 6b3d257588afed39837ac98d049b9947ff5aba3e Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 8 Aug 2022 21:23:37 -0700 Subject: [PATCH] Use LD_PRELOAD to inject pledge() in glibc progs We're now able to drop both `exec` and `prot_exec` privileges automatically when launching glibc dynamic executables. We also have really outstanding standard error logging now, that explains which promises are needed, even in cases where `exec` is used. --- libc/calls/pledge-linux.c | 307 ++++++++++++++++----------------- test/libc/calls/pledge2_test.c | 10 +- tool/build/build.mk | 36 +++- tool/build/{ => dso}/sandbox.c | 30 +++- tool/build/pledge.c | 81 ++++++++- 5 files changed, 286 insertions(+), 178 deletions(-) rename tool/build/{ => dso}/sandbox.c (74%) diff --git a/libc/calls/pledge-linux.c b/libc/calls/pledge-linux.c index bb316bc2d..ad54ff580 100644 --- a/libc/calls/pledge-linux.c +++ b/libc/calls/pledge-linux.c @@ -68,7 +68,6 @@ #define TTY 0x8000 #define UNIX 0x4000 #define NOBITS 0x8000 -#define NOSIGSYS 0x8000 #define RESTRICT 0x1000 #define PLEDGE(pledge) pledge, ARRAYLEN(pledge) @@ -96,117 +95,117 @@ static const uint16_t kPledgeDefault[] = { // difference in the latency of sched_yield() if it's at the start of // the bpf script or the end. static const uint16_t kPledgeStdio[] = { - __NR_linux_sigreturn, // - __NR_linux_restart_syscall, // - __NR_linux_exit_group, // - __NR_linux_sched_yield, // - __NR_linux_sched_getaffinity, // - __NR_linux_clock_getres, // - __NR_linux_clock_gettime, // - __NR_linux_clock_nanosleep, // - __NR_linux_close_range, // - __NR_linux_close, // - __NR_linux_write, // - __NR_linux_writev, // - __NR_linux_pwrite, // - __NR_linux_pwritev, // - __NR_linux_pwritev2, // - __NR_linux_read, // - __NR_linux_readv, // - __NR_linux_pread, // - __NR_linux_preadv, // - __NR_linux_preadv2, // - __NR_linux_dup, // - __NR_linux_dup2, // - __NR_linux_dup3, // - __NR_linux_fchdir, // - __NR_linux_fcntl | STDIO, // - __NR_linux_fstat, // - __NR_linux_fsync, // - __NR_linux_sysinfo, // - __NR_linux_fdatasync, // - __NR_linux_ftruncate, // - __NR_linux_getdents, // - __NR_linux_getrandom, // - __NR_linux_getgroups, // - __NR_linux_getpgid, // - __NR_linux_getpgrp, // - __NR_linux_getpid, // - __NR_linux_gettid, // - __NR_linux_getuid, // - __NR_linux_getgid, // - __NR_linux_getsid, // - __NR_linux_getppid, // - __NR_linux_geteuid, // - __NR_linux_getegid, // - __NR_linux_getrlimit, // - __NR_linux_getresgid, // - __NR_linux_getresuid, // - __NR_linux_getitimer, // - __NR_linux_setitimer, // - __NR_linux_timerfd_create, // - __NR_linux_timerfd_settime, // - __NR_linux_timerfd_gettime, // - __NR_linux_copy_file_range, // - __NR_linux_gettimeofday, // - __NR_linux_sendfile, // - __NR_linux_vmsplice, // - __NR_linux_splice, // - __NR_linux_lseek, // - __NR_linux_tee, // - __NR_linux_brk, // - __NR_linux_msync, // - __NR_linux_mmap | NOEXEC, // - __NR_linux_mremap, // - __NR_linux_munmap, // - __NR_linux_mincore, // - __NR_linux_madvise, // - __NR_linux_fadvise, // - __NR_linux_mprotect | NOEXEC, // - __NR_linux_arch_prctl, // - __NR_linux_migrate_pages, // - __NR_linux_sync_file_range, // - __NR_linux_set_tid_address, // - __NR_linux_nanosleep, // - __NR_linux_pipe, // - __NR_linux_pipe2, // - __NR_linux_poll, // - __NR_linux_ppoll, // - __NR_linux_select, // - __NR_linux_pselect6, // - __NR_linux_epoll_create, // - __NR_linux_epoll_create1, // - __NR_linux_epoll_ctl, // - __NR_linux_epoll_wait, // - __NR_linux_epoll_pwait, // - __NR_linux_epoll_pwait2, // - __NR_linux_recvfrom, // - __NR_linux_sendto | ADDRLESS, // - __NR_linux_ioctl | RESTRICT, // - __NR_linux_alarm, // - __NR_linux_pause, // - __NR_linux_shutdown, // - __NR_linux_eventfd, // - __NR_linux_eventfd2, // - __NR_linux_signalfd, // - __NR_linux_signalfd4, // - __NR_linux_sigaction | NOSIGSYS, // - __NR_linux_sigaltstack, // - __NR_linux_sigprocmask, // - __NR_linux_sigsuspend, // - __NR_linux_sigpending, // - __NR_linux_socketpair, // - __NR_linux_getrusage, // - __NR_linux_times, // - __NR_linux_umask, // - __NR_linux_wait4, // - __NR_linux_uname, // - __NR_linux_prctl | STDIO, // - __NR_linux_clone | THREAD, // - __NR_linux_futex, // - __NR_linux_set_robust_list, // - __NR_linux_get_robust_list, // - __NR_linux_prlimit | STDIO, // + __NR_linux_sigreturn, // + __NR_linux_restart_syscall, // + __NR_linux_exit_group, // + __NR_linux_sched_yield, // + __NR_linux_sched_getaffinity, // + __NR_linux_clock_getres, // + __NR_linux_clock_gettime, // + __NR_linux_clock_nanosleep, // + __NR_linux_close_range, // + __NR_linux_close, // + __NR_linux_write, // + __NR_linux_writev, // + __NR_linux_pwrite, // + __NR_linux_pwritev, // + __NR_linux_pwritev2, // + __NR_linux_read, // + __NR_linux_readv, // + __NR_linux_pread, // + __NR_linux_preadv, // + __NR_linux_preadv2, // + __NR_linux_dup, // + __NR_linux_dup2, // + __NR_linux_dup3, // + __NR_linux_fchdir, // + __NR_linux_fcntl | STDIO, // + __NR_linux_fstat, // + __NR_linux_fsync, // + __NR_linux_sysinfo, // + __NR_linux_fdatasync, // + __NR_linux_ftruncate, // + __NR_linux_getdents, // + __NR_linux_getrandom, // + __NR_linux_getgroups, // + __NR_linux_getpgid, // + __NR_linux_getpgrp, // + __NR_linux_getpid, // + __NR_linux_gettid, // + __NR_linux_getuid, // + __NR_linux_getgid, // + __NR_linux_getsid, // + __NR_linux_getppid, // + __NR_linux_geteuid, // + __NR_linux_getegid, // + __NR_linux_getrlimit, // + __NR_linux_getresgid, // + __NR_linux_getresuid, // + __NR_linux_getitimer, // + __NR_linux_setitimer, // + __NR_linux_timerfd_create, // + __NR_linux_timerfd_settime, // + __NR_linux_timerfd_gettime, // + __NR_linux_copy_file_range, // + __NR_linux_gettimeofday, // + __NR_linux_sendfile, // + __NR_linux_vmsplice, // + __NR_linux_splice, // + __NR_linux_lseek, // + __NR_linux_tee, // + __NR_linux_brk, // + __NR_linux_msync, // + __NR_linux_mmap | NOEXEC, // + __NR_linux_mremap, // + __NR_linux_munmap, // + __NR_linux_mincore, // + __NR_linux_madvise, // + __NR_linux_fadvise, // + __NR_linux_mprotect | NOEXEC, // + __NR_linux_arch_prctl, // + __NR_linux_migrate_pages, // + __NR_linux_sync_file_range, // + __NR_linux_set_tid_address, // + __NR_linux_nanosleep, // + __NR_linux_pipe, // + __NR_linux_pipe2, // + __NR_linux_poll, // + __NR_linux_ppoll, // + __NR_linux_select, // + __NR_linux_pselect6, // + __NR_linux_epoll_create, // + __NR_linux_epoll_create1, // + __NR_linux_epoll_ctl, // + __NR_linux_epoll_wait, // + __NR_linux_epoll_pwait, // + __NR_linux_epoll_pwait2, // + __NR_linux_recvfrom, // + __NR_linux_sendto | ADDRLESS, // + __NR_linux_ioctl | RESTRICT, // + __NR_linux_alarm, // + __NR_linux_pause, // + __NR_linux_shutdown, // + __NR_linux_eventfd, // + __NR_linux_eventfd2, // + __NR_linux_signalfd, // + __NR_linux_signalfd4, // + __NR_linux_sigaction, // + __NR_linux_sigaltstack, // + __NR_linux_sigprocmask, // + __NR_linux_sigsuspend, // + __NR_linux_sigpending, // + __NR_linux_socketpair, // + __NR_linux_getrusage, // + __NR_linux_times, // + __NR_linux_umask, // + __NR_linux_wait4, // + __NR_linux_uname, // + __NR_linux_prctl | STDIO, // + __NR_linux_clone | THREAD, // + __NR_linux_futex, // + __NR_linux_set_robust_list, // + __NR_linux_get_robust_list, // + __NR_linux_prlimit | STDIO, // }; static const uint16_t kPledgeFlock[] = { @@ -585,18 +584,21 @@ static privileged void KillThisThread(void) { : "rcx", "r11", "memory"); } -static privileged bool HasSyscall(struct Pledges *p, uint16_t n) { +static privileged int HasSyscall(struct Pledges *p, uint16_t n) { int i; for (i = 0; i < p->len; ++i) { - if ((p->syscalls[i] & 0x0fff) == n) { - return true; + if (p->syscalls[i] == n) { + return 1; + } + if ((p->syscalls[i] & 0xfff) == n) { + return 2; } } - return false; + return 0; } static privileged void OnSigSys(int sig, siginfo_t *si, ucontext_t *ctx) { - int i; + int i, ok; bool found; char ord[17], rip[17]; enum PledgeMode mode = si->si_errno; @@ -604,15 +606,23 @@ static privileged void OnSigSys(int sig, siginfo_t *si, ucontext_t *ctx) { FixCpy(ord, si->si_syscall, 12); HexCpy(rip, ctx->uc_mcontext.rip); for (found = i = 0; i < ARRAYLEN(kPledge); ++i) { - if (HasSyscall(kPledge + i, si->si_syscall)) { - Log("error: has not pledged ", kPledge[i].name, // - " (ord=", ord, " rip=", rip, ")\n", 0); - found = true; - break; + switch (HasSyscall(kPledge + i, si->si_syscall)) { + case 1: + Log("error: should pledge ", kPledge[i].name, // + " (ord=", ord, " rip=", rip, ")\n", 0); + found = true; + break; + case 2: + Log("error: maybe pledge ", kPledge[i].name, // + " (ord=", ord, " rip=", rip, ")\n", 0); + found = true; + break; + default: + break; } } if (!found) { - Log("error: unsupported syscall (ord=", ord, " rip=", rip, ")\n", 0); + Log("error: bad syscall (ord=", ord, " rip=", rip, ")\n", 0); } switch (mode) { case kPledgeModeKillProcess: @@ -876,6 +886,7 @@ static privileged void AllowSetsockoptRestrict(struct Filter *f) { // The optname argument of getsockopt() must be one of: // // - SO_TYPE (0x03) +// - SO_ERROR (0x04) // - SO_REUSEPORT (0x0f) // - SO_REUSEADDR (0x02) // - SO_KEEPALIVE (0x09) @@ -885,20 +896,21 @@ static privileged void AllowSetsockoptRestrict(struct Filter *f) { static privileged void AllowGetsockoptRestrict(struct Filter *f) { static const int nr = __NR_linux_getsockopt; static const struct sock_filter fragment[] = { - /* L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, nr, 0, 13 - 1), + /* L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, nr, 0, 14 - 1), /* L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[1])), /* L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 1, 0), - /* L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 6, 0, 12 - 4), + /* L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 6, 0, 13 - 4), /* L4*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[2])), - /* L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x03, 5, 0), - /* L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x0f, 4, 0), - /* L7*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x02, 3, 0), - /* L8*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x09, 2, 0), - /* L9*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x14, 1, 0), - /*L10*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x15, 0, 1), - /*L11*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), - /*L12*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), - /*L13*/ /* next filter */ + /* L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x03, 6, 0), + /* L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x04, 5, 0), + /* L7*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x0f, 4, 0), + /* L8*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x02, 3, 0), + /* L9*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x09, 2, 0), + /*L10*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x14, 1, 0), + /*L11*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x15, 0, 1), + /*L12*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + /*L13*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), + /*L14*/ /* next filter */ }; AppendFilter(f, PLEDGE(fragment)); } @@ -1215,24 +1227,6 @@ static privileged void AllowSendtoAddrless(struct Filter *f) { AppendFilter(f, PLEDGE(fragment)); } -// The sig parameter of sigaction() must NOT be -// -// - SIGSYS (31) [always eperm] -// -static privileged void AllowSigactionNosigsys(struct Filter *f) { - static const int nr = __NR_linux_sigaction; - static const struct sock_filter fragment[] = { - /*L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, nr, 0, 6 - 1), - /*L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[0])), - /*L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 31, 0, 1), - /*L3*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | Eperm), - /*L4*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), - /*L5*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), - /*L6*/ /* next filter */ - }; - AppendFilter(f, PLEDGE(fragment)); -} - // The family parameter of socket() must be one of: // // - AF_INET (0x02) @@ -1479,9 +1473,6 @@ static privileged void AppendPledge(struct Filter *f, // case __NR_linux_fchmodat | NOBITS: AllowFchmodatNobits(f); break; - case __NR_linux_sigaction | NOSIGSYS: - AllowSigactionNosigsys(f); - break; case __NR_linux_prctl | STDIO: AllowPrctlStdio(f); break; @@ -1582,7 +1573,11 @@ privileged int sys_pledge_linux(unsigned long ipromises, // AppendPledge(&f, PLEDGE(kPledgeDefault)); for (i = 0; i < ARRAYLEN(kPledge); ++i) { if (~ipromises & (1ul << i)) { - AppendPledge(&f, kPledge[i].syscalls, kPledge[i].len); + if (kPledge[i].len) { + AppendPledge(&f, kPledge[i].syscalls, kPledge[i].len); + } else { + AbortPledge("bad ipromises"); + } } } diff --git a/test/libc/calls/pledge2_test.c b/test/libc/calls/pledge2_test.c index 3d828301a..9464ad88c 100644 --- a/test/libc/calls/pledge2_test.c +++ b/test/libc/calls/pledge2_test.c @@ -86,7 +86,7 @@ TEST(pledge, testKillProcessMode) { TEST(pledge, testLogMessage_inSoftyMode) { if (IsOpenbsd()) return; int fds[2]; - char msg[64] = {0}; + char msg[256] = {0}; ASSERT_SYS(0, 0, pipe(fds)); SPAWN(fork); __pledge_mode = kPledgeModeErrno; @@ -98,13 +98,13 @@ TEST(pledge, testLogMessage_inSoftyMode) { read(fds[0], msg, sizeof(msg)); close(fds[0]); if (IsLinux()) { - ASSERT_STARTSWITH("error: has not pledged inet", msg); + ASSERT_STARTSWITH("error: maybe pledge inet", msg); } } TEST(pledge, testLogMessage_onKillProcess) { int fds[2]; - char msg[64] = {0}; + char msg[256] = {0}; ASSERT_SYS(0, 0, pipe(fds)); SPAWN(fork); __pledge_mode = kPledgeModeKillThread; @@ -116,13 +116,13 @@ TEST(pledge, testLogMessage_onKillProcess) { read(fds[0], msg, sizeof(msg)); close(fds[0]); if (IsLinux()) { - ASSERT_STARTSWITH("error: has not pledged inet", msg); + ASSERT_STARTSWITH("error: maybe pledge inet", msg); } } TEST(pledge, testNoLogOrAbrtsignoPossibleSadly_becausePledgedExec) { int fds[2]; - char msg[64] = {0}; + char msg[256] = {0}; ASSERT_SYS(0, 0, pipe(fds)); SPAWN(fork); ASSERT_SYS(0, 2, dup2(fds[1], 2)); diff --git a/tool/build/build.mk b/tool/build/build.mk index 5e65f707f..87c93c92c 100644 --- a/tool/build/build.mk +++ b/tool/build/build.mk @@ -119,23 +119,45 @@ o/$(MODE)/tool/build/dd.zip.o: o/$(MODE)/tool/build/dd # we need pic because: # so it can be an LD_PRELOAD payload -o/$(MODE)/tool/build/sandbox.o: \ +o/$(MODE)/tool/build/dso/sandbox.o: \ OVERRIDE_CFLAGS += \ -fPIC -o/$(MODE)/tool/build/sandbox.so: \ - o/$(MODE)/tool/build/sandbox.o \ +o/$(MODE)/tool/build/dso/sandbox.o: \ + libc/calls/calls.h \ + tool/build/dso/sandbox.c \ + libc/calls/pledge.h \ + libc/runtime/runtime.h \ + libc/calls/pledge.internal.h \ + libc/intrin/promises.internal.h \ + tool/build/build.mk + +o/$(MODE)/tool/build/dso/sandbox.so: \ + o/$(MODE)/tool/build/dso/sandbox.o \ o/$(MODE)/libc/calls/pledge-linux.o \ o/$(MODE)/libc/sysv/restorert.o - @$(COMPILE) -ALINK.so \ - $(CC) \ - -s \ + @$(CC) -s \ -shared \ -nostdlib \ -Wl,--gc-sections \ - $(LINKARGS) \ + o/$(MODE)/tool/build/dso/sandbox.o \ + o/$(MODE)/libc/calls/pledge-linux.o \ + o/$(MODE)/libc/sysv/restorert.o \ $(OUTPUT_OPTION) +o/$(MODE)/tool/build/dso/sandbox.so.zip.o: \ + ZIPOBJ_FLAGS += \ + -B + +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/pledge.o \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + .PHONY: o/$(MODE)/tool/build o/$(MODE)/tool/build: \ o/$(MODE)/tool/build/emucrt \ diff --git a/tool/build/sandbox.c b/tool/build/dso/sandbox.c similarity index 74% rename from tool/build/sandbox.c rename to tool/build/dso/sandbox.c index 4f1152526..d2fbfb2a6 100644 --- a/tool/build/sandbox.c +++ b/tool/build/dso/sandbox.c @@ -16,13 +16,35 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/calls/pledge.h" #include "libc/calls/pledge.internal.h" #include "libc/intrin/promises.internal.h" +#include "libc/runtime/runtime.h" -hidden char __privileged_start; -hidden char __privileged_end; +/* + * runs pledge at glibc executable load time, e.g. + * strace -vff bash -c '_PLEDGE=4194303,0,1 LD_PRELOAD=$HOME/sandbox.so ls' + */ -__attribute__((__constructor__)) void InitializeSandbox(void) { - sys_pledge_linux(~(1ul << PROMISE_STDIO), kPledgeModeErrno, false); +hidden uint8_t __privileged_start[1]; +hidden uint8_t __privileged_end[1]; + +__attribute__((__constructor__)) void init(void) { + int c, i, j; + const char *s; + uint64_t arg[3] = {0}; + s = getenv("_PLEDGE"); + for (i = j = 0; i < 3; ++i) { + while ((c = s[j] & 255)) { + ++j; + if ('0' <= c & c <= '9') { + arg[i] *= 10; + arg[i] += c - '0'; + } else { + break; + } + } + } + sys_pledge_linux(~arg[0], arg[1], arg[2]); } diff --git a/tool/build/pledge.c b/tool/build/pledge.c index e5d47041c..70236ab97 100644 --- a/tool/build/pledge.c +++ b/tool/build/pledge.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/bits/bits.h" #include "libc/bits/safemacros.internal.h" #include "libc/calls/calls.h" @@ -36,6 +37,7 @@ #include "libc/intrin/promises.internal.h" #include "libc/macros.internal.h" #include "libc/math.h" +#include "libc/mem/io.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/kcpuids.h" #include "libc/runtime/gc.internal.h" @@ -65,6 +67,7 @@ // STATIC_YOINK("strerror_wr"); +STATIC_YOINK("zip_uri_support"); #define USAGE \ "\ @@ -123,6 +126,7 @@ int g_uflag; int g_kflag; int g_hflag; bool g_nice; +bool isdynamic; bool g_noclose; long g_cpuquota; long g_fszquota; @@ -131,6 +135,8 @@ long g_proquota; long g_dontdrop; const char *g_chroot; const char *g_promises; +char dsopath[PATH_MAX]; +char tmppath[PATH_MAX]; struct { int n; @@ -393,7 +399,8 @@ void ApplyFilesystemPolicy(unsigned long ipromises) { Unveil(prog, "rx"); - if (IsDynamicExecutable(prog)) { + if (isdynamic) { + Unveil(dsopath, "rx"); UnveilIfExists("/lib", "rx"); UnveilIfExists("/lib64", "rx"); UnveilIfExists("/usr/lib", "rx"); @@ -518,8 +525,36 @@ void DropCapabilities(void) { } } +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 main(int argc, char *argv[]) { + const char *s; bool hasfunbits; + int fdin, fdout; + char buf[PATH_MAX]; + int e, zipfd, memfd; int useruid, usergid; int owneruid, ownergid; int oldfsuid, oldfsgid; @@ -607,6 +642,29 @@ int main(int argc, char *argv[]) { setfsgid(oldfsgid); } + // figure out where we want the dso + if (IsDynamicExecutable(prog)) { + isdynamic = true; + if ((s = getenv("TMPDIR")) || // + (s = getenv("HOME")) || // + (s = ".")) { + ksnprintf(dsopath, sizeof(dsopath), "%s/sandbox.so", s); + if (!FileExistsAndIsNewerThan(dsopath, GetProgramExecutableName())) { + ksnprintf(tmppath, sizeof(tmppath), "%s/sandbox.so.%d", s, getpid()); + if (Extract("/zip/sandbox.so", tmppath, 0755) == -1) { + kprintf("error: extract dso failed: %m\n"); + exit(1); + } + if (rename(tmppath, dsopath) == -1) { + kprintf("error: rename dso failed: %m\n"); + exit(1); + } + } + ksnprintf(buf, sizeof(buf), "LD_PRELOAD=%s", dsopath); + putenv(buf); + } + } + if (g_dontdrop) { if (hasfunbits) { kprintf("error: -D flag forbidden on setuid binaries\n"); @@ -669,11 +727,6 @@ int main(int argc, char *argv[]) { ApplyFilesystemPolicy(ipromises); - // we always need exec which is a weakness of this model - if (!(~ipromises & (1ul << PROMISE_EXEC))) { - g_promises = xstrcat(g_promises, ' ', "exec"); - } - // pledge.com uses the return eperm instead of killing the process // model. we do this becasue it's only possible to have sigsys print // crash messages if we're not pledging exec, which is what this tool @@ -684,6 +737,22 @@ int main(int argc, char *argv[]) { __pledge_mode = kPledgeModeErrno; } + // we need to be able to call execv and mmap the dso + // it'll be pledged away once/if the dso gets loaded + if (!(~ipromises & (1ul << PROMISE_EXEC))) { + g_promises = xstrcat(g_promises, ' ', "exec"); + } + if (isdynamic) { + g_promises = xstrcat(g_promises, ' ', "prot_exec"); + } + + // pass arguments to pledge() inside the dso + if (isdynamic) { + ksnprintf(buf, sizeof(buf), "_PLEDGE=%ld,%ld,%ld", ~ipromises, + __pledge_mode, false); + putenv(buf); + } + // apply sandbox if (pledge(g_promises, g_promises) == -1) { kprintf("error: pledge(%#s) failed: %m\n", g_promises);