diff --git a/ape/apeinstall.sh b/ape/apeinstall.sh index 0881c638d..4f1c50948 100755 --- a/ape/apeinstall.sh +++ b/ape/apeinstall.sh @@ -99,6 +99,15 @@ if [ x"$(uname -s)" = xLinux ]; then $SUDO sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register" || exit echo done >&2 + if [ x"$(cat /proc/sys/fs/binfmt_misc/status)" = xdisabled ]; then + echo >&2 + echo enabling binfmt_misc >&2 + echo you may need to edit configs to persist across reboot >&2 + echo $SUDO sh -c 'echo 1 >/proc/sys/fs/binfmt_misc/status' >&2 + $SUDO sh -c 'echo 1 >/proc/sys/fs/binfmt_misc/status' || exit + echo done >&2 + fi + fi ################################################################################ diff --git a/libc/mem/pledge.c b/libc/mem/pledge.c index 921e78807..4d58dbccb 100644 --- a/libc/mem/pledge.c +++ b/libc/mem/pledge.c @@ -42,12 +42,13 @@ #include "libc/sysv/consts/prot.h" #include "libc/sysv/errfuns.h" -#define READONLY 0x8000 -#define INET 0x8000 -#define UNIX 0x4000 -#define ADDRLESS 0x2000 -#define LOCK 0x8000 -#define TTY 0x8000 +#define READONLY 0x8000 +#define WRITEONLY 0x4000 +#define INET 0x8000 +#define UNIX 0x4000 +#define ADDRLESS 0x2000 +#define LOCK 0x8000 +#define TTY 0x8000 #define OFF(f) offsetof(struct seccomp_data, f) #define PLEDGE(pledge) pledge, ARRAYLEN(pledge) @@ -157,25 +158,29 @@ static const uint16_t kPledgeLinuxRpath[] = { __NR_linux_faccessat, // __NR_linux_readlink, // __NR_linux_readlinkat, // + __NR_linux_statfs, // + __NR_linux_fstatfs, // }; static const uint16_t kPledgeLinuxWpath[] = { - __NR_linux_getcwd, // - __NR_linux_open, // - __NR_linux_openat, // - __NR_linux_stat, // - __NR_linux_fstat, // - __NR_linux_lstat, // - __NR_linux_fstatat, // - __NR_linux_access, // - __NR_linux_faccessat, // - __NR_linux_readlinkat, // - __NR_linux_chmod, // - __NR_linux_fchmod, // - __NR_linux_fchmodat, // + __NR_linux_getcwd, // + __NR_linux_open | WRITEONLY, // + __NR_linux_openat | WRITEONLY, // + __NR_linux_stat, // + __NR_linux_fstat, // + __NR_linux_lstat, // + __NR_linux_fstatat, // + __NR_linux_access, // + __NR_linux_faccessat, // + __NR_linux_readlinkat, // + __NR_linux_chmod, // + __NR_linux_fchmod, // + __NR_linux_fchmodat, // }; static const uint16_t kPledgeLinuxCpath[] = { + __NR_linux_open, // + __NR_linux_openat, // __NR_linux_rename, // __NR_linux_renameat, // __NR_linux_renameat2, // @@ -278,9 +283,20 @@ static const uint16_t kPledgeLinuxId[] = { __NR_linux_setrlimit, // __NR_linux_getpriority, // __NR_linux_setpriority, // + __NR_linux_setfsuid, // + __NR_linux_setfsgid, // }; static const uint16_t kPledgeLinuxExec[] = { + __NR_linux_execve, // + __NR_linux_execveat, // + __NR_linux_access, // + __NR_linux_faccessat, // + __NR_linux_open | READONLY, // + __NR_linux_openat | READONLY, // +}; + +static const uint16_t kPledgeLinuxExecnative[] = { __NR_linux_execve, // __NR_linux_execveat, // }; @@ -290,24 +306,25 @@ static const struct Pledges { const uint16_t *syscalls; const size_t len; } kPledgeLinux[] = { - {"default", PLEDGE(kPledgeLinuxDefault)}, // - {"stdio", PLEDGE(kPledgeLinuxStdio)}, // - {"rpath", PLEDGE(kPledgeLinuxRpath)}, // - {"wpath", PLEDGE(kPledgeLinuxWpath)}, // - {"cpath", PLEDGE(kPledgeLinuxCpath)}, // - {"dpath", PLEDGE(kPledgeLinuxDpath)}, // - {"flock", PLEDGE(kPledgeLinuxFlock)}, // - {"fattr", PLEDGE(kPledgeLinuxFattr)}, // - {"inet", PLEDGE(kPledgeLinuxInet)}, // - {"unix", PLEDGE(kPledgeLinuxUnix)}, // - {"dns", PLEDGE(kPledgeLinuxDns)}, // - {"tty", PLEDGE(kPledgeLinuxTty)}, // - {"recvfd", PLEDGE(kPledgeLinuxRecvfd)}, // - {"proc", PLEDGE(kPledgeLinuxProc)}, // - {"thread", PLEDGE(kPledgeLinuxThread)}, // - {"exec", PLEDGE(kPledgeLinuxExec)}, // - {"id", PLEDGE(kPledgeLinuxId)}, // - {0}, // + {"default", PLEDGE(kPledgeLinuxDefault)}, // + {"stdio", PLEDGE(kPledgeLinuxStdio)}, // + {"rpath", PLEDGE(kPledgeLinuxRpath)}, // + {"wpath", PLEDGE(kPledgeLinuxWpath)}, // + {"cpath", PLEDGE(kPledgeLinuxCpath)}, // + {"dpath", PLEDGE(kPledgeLinuxDpath)}, // + {"flock", PLEDGE(kPledgeLinuxFlock)}, // + {"fattr", PLEDGE(kPledgeLinuxFattr)}, // + {"inet", PLEDGE(kPledgeLinuxInet)}, // + {"unix", PLEDGE(kPledgeLinuxUnix)}, // + {"dns", PLEDGE(kPledgeLinuxDns)}, // + {"tty", PLEDGE(kPledgeLinuxTty)}, // + {"recvfd", PLEDGE(kPledgeLinuxRecvfd)}, // + {"proc", PLEDGE(kPledgeLinuxProc)}, // + {"thread", PLEDGE(kPledgeLinuxThread)}, // + {"exec", PLEDGE(kPledgeLinuxExec)}, // + {"execnative", PLEDGE(kPledgeLinuxExecnative)}, // + {"id", PLEDGE(kPledgeLinuxId)}, // + {0}, // }; static const struct sock_filter kFilterStart[] = { @@ -397,23 +414,25 @@ static bool AllowIoctl(struct Filter *f) { // - TIOCGPGRP (0x540f) // - TIOCSWINSZ (0x5414) // - TIOCSBRK (0x5427) +// - TCFLSH (0x540b) // static bool AllowIoctlTty(struct Filter *f) { static const struct sock_filter fragment[] = { - /* L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_ioctl, 0, 13 - 1), + /* L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_ioctl, 0, 14 - 1), /* L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[1])), - /* L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5401, 11 - 3, 0), - /* L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5402, 11 - 4, 0), - /* L4*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5403, 11 - 5, 0), - /* L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5404, 11 - 6, 0), - /* L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5413, 11 - 7, 0), - /* L7*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5410, 11 - 8, 0), - /* L8*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x540f, 11 - 9, 0), - /* L9*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5414, 11 - 10, 0), - /*L10*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5427, 0, 12 - 11), - /*L11*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), - /*L12*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), - /*L13*/ /* next filter */ + /* L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5401, 12 - 3, 0), + /* L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5402, 12 - 4, 0), + /* L4*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5403, 12 - 5, 0), + /* L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5404, 12 - 6, 0), + /* L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5413, 12 - 7, 0), + /* L7*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5410, 12 - 8, 0), + /* L8*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x540f, 12 - 9, 0), + /* L9*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5414, 12 - 10, 0), + /*L10*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x540b, 12 - 11, 0), + /*L11*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x5427, 0, 13 - 12), + /*L12*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + /*L13*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), + /*L14*/ /* next filter */ }; return AppendFilter(f, PLEDGE(fragment)); } @@ -593,6 +612,42 @@ static bool AllowOpenatReadonly(struct Filter *f) { return AppendFilter(f, PLEDGE(fragment)); } +// The open() flags parameter must not contain +// +// - O_CREAT (000000100) +// - O_TMPFILE (020200000) +// +static bool AllowOpenWriteonly(struct Filter *f) { + static const struct sock_filter fragment[] = { + /*L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_open, 0, 6 - 1), + /*L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[1])), + /*L2*/ BPF_STMT(BPF_ALU | BPF_AND | BPF_K, 020200100), + /*L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 5 - 4), + /*L4*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + /*L5*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), + /*L6*/ /* next filter */ + }; + return AppendFilter(f, PLEDGE(fragment)); +} + +// The openat() flags parameter must not contain +// +// - O_CREAT (000000100) +// - O_TMPFILE (020200000) +// +static bool AllowOpenatWriteonly(struct Filter *f) { + static const struct sock_filter fragment[] = { + /*L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_openat, 0, 6 - 1), + /*L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[2])), + /*L2*/ BPF_STMT(BPF_ALU | BPF_AND | BPF_K, 020200100), + /*L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 5 - 4), + /*L4*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + /*L5*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), + /*L6*/ /* next filter */ + }; + return AppendFilter(f, PLEDGE(fragment)); +} + // If the flags parameter of open() has one of: // // - O_CREAT (000000100) @@ -810,23 +865,21 @@ static bool AllowSocketUnix(struct Filter *f) { // The first parameter of prctl() can be any of // -// - PR_SET_NO_NEW_PRIVS (38) +// - PR_SET_NAME (15) +// - PR_GET_NAME (16) +// - PR_GET_SECCOMP (21) // - PR_SET_SECCOMP (22) -// -// The second parameter of prctl() can be any of -// -// - true (1) -// - SECCOMP_MODE_FILTER (2) +// - PR_SET_NO_NEW_PRIVS (38) // static bool AllowPrctl(struct Filter *f) { static const struct sock_filter fragment[] = { /*L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_prctl, 0, 9 - 1), /*L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[0])), - /*L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 38, 4 - 3, 0), - /*L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 22, 0, 8 - 4), - /*L4*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[1])), - /*L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 7 - 6, 0), - /*L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 0, 8 - 7), + /*L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 15, 7 - 3, 0), + /*L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 16, 7 - 4, 0), + /*L4*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 21, 7 - 5, 0), + /*L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 22, 7 - 6, 0), + /*L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 38, 0, 8 - 7), /*L7*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), /*L8*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), /*L9*/ /* next filter */ @@ -937,6 +990,12 @@ static bool AppendPledge(struct Filter *f, const uint16_t *p, size_t len, case __NR_linux_openat | READONLY: if (!AllowOpenatReadonly(f)) return false; break; + case __NR_linux_open | WRITEONLY: + if (!AllowOpenWriteonly(f)) return false; + break; + case __NR_linux_openat | WRITEONLY: + if (!AllowOpenatWriteonly(f)) return false; + break; case __NR_linux_setsockopt: if (!AllowSetsockopt(f)) return false; break; @@ -990,6 +1049,7 @@ static int sys_pledge_linux(const char *promises, const char *execpromises) { int rc = -1; size_t plen; bool needmapexec; + bool needexecnative; bool needmorphing; struct Filter f = {0}; const uint16_t *pledge; @@ -997,9 +1057,10 @@ static int sys_pledge_linux(const char *promises, const char *execpromises) { if (execpromises) return einval(); needmapexec = strstr(promises, "exec"); needmorphing = strstr(promises, "thread"); + needexecnative = strstr(promises, "execnative"); if ((start = s = strdup(promises)) && AppendFilter(&f, kFilterStart, ARRAYLEN(kFilterStart)) && - (needmapexec || AppendOriginVerification(&f)) && + (needmapexec || needexecnative || AppendOriginVerification(&f)) && AppendPledge(&f, kPledgeLinuxDefault, ARRAYLEN(kPledgeLinuxDefault), needmapexec, needmorphing)) { for (ok = true; (tok = strtok_r(start, " \t\r\n", &state)); start = 0) { @@ -1088,15 +1149,15 @@ static int sys_pledge_linux(const char *promises, const char *execpromises) { * * - "rpath" (read-only path ops) allows chdir, getcwd, open(O_RDONLY), * openat(O_RDONLY), stat, fstat, lstat, fstatat, access, faccessat, - * readlink, readlinkat. + * readlink, readlinkat, statfs, fstatfs. * - * - "wpath" (write path ops) allows getcwd, open, openat, stat, fstat, - * lstat, fstatat, access, faccessat, readlink, readlinkat, chmod, - * fchmod, fchmodat. + * - "wpath" (write path ops) allows getcwd, open(O_WRONLY), + * openat(O_WRONLY), stat, fstat, lstat, fstatat, access, faccessat, + * readlink, readlinkat, chmod, fchmod, fchmodat. * - * - "cpath" (create path ops) allows rename, renameat, renameat2, link, - * linkat, symlink, symlinkat, unlink, rmdir, unlinkat, mkdir, - * mkdirat. + * - "cpath" (create path ops) allows open(O_CREAT), openat(O_CREAT), + * rename, renameat, renameat2, link, linkat, symlink, symlinkat, + * unlink, rmdir, unlinkat, mkdir, mkdirat. * * - "dpath" (create special path ops) allows mknod, mknodat, mkfifo. * @@ -1125,13 +1186,21 @@ static int sys_pledge_linux(const char *promises, const char *execpromises) { * - "thread" allows clone, futex, and permits PROT_EXEC in mprotect. * * - "id" allows setuid, setreuid, setresuid, setgid, setregid, - * setresgid, setgroups, prlimit, setrlimit, getpriority, setpriority. + * setresgid, setgroups, prlimit, setrlimit, getpriority, setpriority, + * setfsuid, setfsgid. * - * - "exec" allows execve, execveat. If this is used then APE binaries - * should be assimilated in order to work on OpenBSD. On Linux, mmap() + * - "exec" allows execve, execveat, access, faccessat. On Linux this + * also weakens some security to permit running APE binaries. However + * on OpenBSD they must be assimilate beforehand. On Linux, mmap() * will be loosened up to allow creating PROT_EXEC memory (for APE * loader) and system call origin verification won't be activated. * + * - "execnative" allows execve, execveat. Can only be used to run + * native executables; you won't be able to run APE binaries. mmap() + * and mprotect() are still prevented from creating executable memory. + * System call origin verification can't be enabled. If you always + * assimilate your APE binaries, then this should be preferred. + * * @return 0 on success, or -1 w/ errno * @raise ENOSYS if host os isn't Linux or OpenBSD * @raise EINVAL if `execpromises` is used on Linux diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 926645a0f..7f8e93419 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -1536,7 +1536,6 @@ syscon termios EXTA 14 0x4b00 0x4b00 0x4b00 0x4b00 0 # bsd conse syscon termios EXTB 15 0x9600 0x9600 0x9600 0x9600 0 # bsd consensus syscon termios ERA 0x02002c 45 45 0 0 0 syscon termios EMPTY 0 0 0 0 0 0 # consensus -syscon termios TCFLSH 0x540b 0 0 0 0 0 syscon termios TCFLSH 0x540b 0x80047410 0x80047410 0x80047410 0x80047410 0 # see tcflush; TIOCFLUSH on BSD syscon termios TIOCFLUSH 0x540b 0x80047410 0x80047410 0x80047410 0x80047410 0 # see tcflush; TCFLSH on Linux diff --git a/libc/sysv/consts/pr.h b/libc/sysv/consts/pr.h index 8fd8d471a..3c2f2ba91 100644 --- a/libc/sysv/consts/pr.h +++ b/libc/sysv/consts/pr.h @@ -11,7 +11,7 @@ #define PR_GET_NO_NEW_PRIVS 39 #define PR_SET_NAME 15 -#define PR_GET_NAME 0x10 +#define PR_GET_NAME 16 #define PR_GET_TSC 25 #define PR_SET_TSC 26 diff --git a/test/libc/mem/pledge_test.c b/test/libc/mem/pledge_test.c index ae904f4ad..85f63aa85 100644 --- a/test/libc/mem/pledge_test.c +++ b/test/libc/mem/pledge_test.c @@ -108,9 +108,9 @@ TEST(pledge, multipleCalls_canOnlyBecomeMoreRestrictive1) { ASSERT_SYS(0, 0, pledge("stdio unix", 0)); ASSERT_SYS(0, 3, dup(2)); ASSERT_SYS(EPERM, -1, socket(AF_UNIX, SOCK_STREAM, 0)); - ASSERT_SYS(EPERM, -1, prctl(PR_SET_NO_NEW_PRIVS, 0, 0, 0, 0)); - ASSERT_SYS(EPERM, -1, prctl(PR_GET_SECCOMP, SECCOMP_MODE_FILTER, 0)); - ASSERT_SYS(EPERM, -1, prctl(PR_SET_SECCOMP, 33, 0)); + ASSERT_SYS(0, 2, prctl(PR_GET_SECCOMP, 0, 0)); + ASSERT_SYS(0, 0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + ASSERT_SYS(EINVAL, -1, prctl(PR_SET_NO_NEW_PRIVS, 0, 0, 0, 0)); _Exit(0); } EXPECT_NE(-1, wait(&ws)); @@ -347,8 +347,23 @@ TEST(pledge, open_wpath) { ASSERT_SYS(0, 0, pledge("stdio wpath", 0)); ASSERT_SYS(0, 3, open("foo", O_RDONLY)); ASSERT_SYS(EPERM, -1, open(".", O_RDWR | O_TMPFILE, 07644)); - ASSERT_SYS(EPERM, -1, open("foo", O_WRONLY | O_TRUNC | O_CREAT, 07644)); - ASSERT_SYS(0, 4, open("foo", O_WRONLY | O_TRUNC | O_CREAT, 0644)); + ASSERT_SYS(0, 4, open("foo", O_WRONLY | O_TRUNC, 07644)); + ASSERT_SYS(EPERM, -1, open("foo", O_WRONLY | O_TRUNC | O_CREAT, 0644)); + _Exit(0); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFEXITED(ws) && !WEXITSTATUS(ws)); +} + +TEST(pledge, open_cpath) { + if (IsOpenbsd()) return; // b/c testing linux bpf + int ws, pid; + struct stat st; + ASSERT_SYS(0, 0, touch("foo", 0644)); + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio cpath", 0)); + ASSERT_SYS(0, 3, open("foo", O_WRONLY | O_TRUNC | O_CREAT, 0644)); _Exit(0); } EXPECT_NE(-1, wait(&ws)); diff --git a/tool/build/jail.c b/tool/build/jail.c deleted file mode 100644 index f0a008d4a..000000000 --- a/tool/build/jail.c +++ /dev/null @@ -1,217 +0,0 @@ -/*-*- 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/calls.h" -#include "libc/calls/struct/filter.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/calls/struct/siginfo.h" -#include "libc/calls/struct/sigset.h" -#include "libc/calls/struct/user_regs_struct.h" -#include "libc/calls/ucontext.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/intrin/kprintf.h" -#include "libc/log/check.h" -#include "libc/log/log.h" -#include "libc/runtime/runtime.h" -#include "libc/sysv/consts/pr.h" -#include "libc/sysv/consts/ptrace.h" -#include "libc/sysv/consts/sa.h" -#include "libc/sysv/consts/sig.h" -#include "tool/net/sandbox.h" - -#define __WALL 0x40000000 - -static const struct sock_filter kSandboxFilter[] = { - _SECCOMP_MACHINE(AUDIT_ARCH_X86_64), // - _SECCOMP_LOAD_SYSCALL_NR(), // - _SECCOMP_ALLOW_SYSCALL(0x000), // read - _SECCOMP_ALLOW_SYSCALL(0x001), // write - _SECCOMP_ALLOW_SYSCALL(0x013), // readv - _SECCOMP_ALLOW_SYSCALL(0x014), // writev - _SECCOMP_ALLOW_SYSCALL(0x005), // fstat - _SECCOMP_ALLOW_SYSCALL(0x007), // poll - _SECCOMP_ALLOW_SYSCALL(0x008), // lseek - _SECCOMP_ALLOW_SYSCALL(0x009), // mmap - _SECCOMP_ALLOW_SYSCALL(0x00b), // munmap - _SECCOMP_ALLOW_SYSCALL(0x04f), // getcwd - _SECCOMP_ALLOW_SYSCALL(0x003), // close - _SECCOMP_ALLOW_SYSCALL(0x010), // ioctl todo - _SECCOMP_ALLOW_SYSCALL(0x016), // pipe - _SECCOMP_ALLOW_SYSCALL(0x125), // pipe2 - _SECCOMP_ALLOW_SYSCALL(0x035), // socketpair - _SECCOMP_ALLOW_SYSCALL(0x020), // dup - _SECCOMP_ALLOW_SYSCALL(0x021), // dup2 - _SECCOMP_ALLOW_SYSCALL(0x124), // dup3 - _SECCOMP_ALLOW_SYSCALL(0x039), // fork - _SECCOMP_ALLOW_SYSCALL(0x03a), // vfork - _SECCOMP_ALLOW_SYSCALL(0x011), // pread - _SECCOMP_ALLOW_SYSCALL(0x012), // pwrite - _SECCOMP_ALLOW_SYSCALL(0x127), // preadv - _SECCOMP_ALLOW_SYSCALL(0x128), // pwritev - _SECCOMP_ALLOW_SYSCALL(0x0d9), // getdents - _SECCOMP_ALLOW_SYSCALL(0x027), // getpid - _SECCOMP_ALLOW_SYSCALL(0x066), // getuid - _SECCOMP_ALLOW_SYSCALL(0x068), // getgid - _SECCOMP_ALLOW_SYSCALL(0x06e), // getppid - _SECCOMP_ALLOW_SYSCALL(0x06f), // getpgrp - _SECCOMP_ALLOW_SYSCALL(0x07c), // getsid - _SECCOMP_ALLOW_SYSCALL(0x06b), // geteuid - _SECCOMP_ALLOW_SYSCALL(0x06c), // getegid - _SECCOMP_ALLOW_SYSCALL(0x061), // getrlimit - _SECCOMP_ALLOW_SYSCALL(0x028), // sendfile - _SECCOMP_ALLOW_SYSCALL(0x02d), // recvfrom - _SECCOMP_ALLOW_SYSCALL(0x033), // getsockname - _SECCOMP_ALLOW_SYSCALL(0x034), // getpeername - _SECCOMP_ALLOW_SYSCALL(0x00f), // rt_sigreturn - _SECCOMP_ALLOW_SYSCALL(0x082), // rt_sigsuspend - _SECCOMP_ALLOW_SYSCALL(0x0e4), // clock_gettime - _SECCOMP_ALLOW_SYSCALL(0x060), // gettimeofday - _SECCOMP_ALLOW_SYSCALL(0x03f), // uname - _SECCOMP_ALLOW_SYSCALL(0x03c), // exit - _SECCOMP_ALLOW_SYSCALL(0x0e7), // exit_group - _SECCOMP_TRACE_SYSCALL(0x03e, 0), // kill - _SECCOMP_TRACE_SYSCALL(0x101, 0), // openat - _SECCOMP_TRACE_SYSCALL(0x106, 0), // newfstatat - _SECCOMP_TRACE_SYSCALL(0x029, 0), // socket - _SECCOMP_TRACE_SYSCALL(0x031, 0), // bind - _SECCOMP_TRACE_SYSCALL(0x02a, 0), // connect - _SECCOMP_TRACE_SYSCALL(0x02c, 0), // sendto - _SECCOMP_TRACE_SYSCALL(0x036, 0), // setsockopt - _SECCOMP_TRACE_SYSCALL(0x048, 0), // fcntl - _SECCOMP_TRACE_SYSCALL(0x03b, 0), // execve - _SECCOMP_TRACE_SYSCALL(0x102, 0), // mkdirat - _SECCOMP_TRACE_SYSCALL(0x104, 0), // chownat - _SECCOMP_TRACE_SYSCALL(0x107, 0), // unlinkat - _SECCOMP_TRACE_SYSCALL(0x108, 0), // renameat - _SECCOMP_TRACE_SYSCALL(0x109, 0), // linkat - _SECCOMP_TRACE_SYSCALL(0x10a, 0), // symlinkat - _SECCOMP_TRACE_SYSCALL(0x10b, 0), // readlinkat - _SECCOMP_TRACE_SYSCALL(0x10c, 0), // fchmodat - _SECCOMP_TRACE_SYSCALL(0x10d, 0), // faccessat - _SECCOMP_TRACE_SYSCALL(0x0eb, 0), // utimes - _SECCOMP_TRACE_SYSCALL(0x105, 0), // futimesat - _SECCOMP_TRACE_SYSCALL(0x118, 0), // utimensat - _SECCOMP_LOG_AND_RETURN_ERRNO(1), // EPERM -}; - -static const struct sock_fprog kSandbox = { - .len = ARRAYLEN(kSandboxFilter), - .filter = kSandboxFilter, -}; - -void OnSys(int sig, siginfo_t *si, ucontext_t *ctx) { - kprintf("Got SIGSYS%n"); -} - -int main(int argc, char *argv[]) { - sigset_t mask, origmask; - struct user_regs_struct regs; - int child, evpid, signal, wstatus; - - if (!IsLinux()) { - kprintf("error: %s is only supported on linux right now%n", argv[0]); - return 1; - } - if (argc < 2) { - kprintf("Usage: %s PROGRAM [ARGS...]%n", argv[0]); - return 1; - } - /* ShowCrashReports(); */ - - sigaction(SIGINT, &(struct sigaction){.sa_handler = SIG_IGN}, 0); - sigaction(SIGQUIT, &(struct sigaction){.sa_handler = SIG_IGN}, 0); - sigaction(SIGSYS, - &((struct sigaction){ - .sa_sigaction = OnSys, - .sa_flags = SA_SIGINFO, - }), - 0); - - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - sigprocmask(SIG_BLOCK, &mask, &origmask); - - CHECK_NE(-1, (child = fork())); - if (!child) { - sigaction(SIGINT, &(struct sigaction){.sa_handler = SIG_DFL}, 0); - sigaction(SIGQUIT, &(struct sigaction){.sa_handler = SIG_DFL}, 0); - kprintf("CHILD ptrace(PTRACE_TRACEME)%n"); - if (ptrace(PTRACE_TRACEME) == -1) { - kprintf("CHILD ptrace(PTRACE_TRACEME) failed %m%n"); - _Exit(124); - } - kprintf("CHILD prctl(PR_SET_NO_NEW_PRIVS)%n"); - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { - kprintf("CHILD prctl(PR_SET_NO_NEW_PRIVS) failed %m%n"); - _Exit(125); - } - kprintf("CHILD prctl(PR_SET_SECCOMP)%n"); - if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &kSandbox) == -1) { - kprintf("CHILD prctl(PR_SET_SECCOMP) failed %m%n"); - _Exit(126); - } - kprintf("CHILD sigsuspend()%n"); - if (sigsuspend(0) == -1) { - kprintf("CHILD sigsuspend() failed %m%n"); - } - sigaction(SIGSYS, &(struct sigaction){.sa_handler = SIG_DFL}, 0); - sigprocmask(SIG_SETMASK, &origmask, 0); - execv(argv[1], argv + 1); - kprintf("CHILD execve(%#s) failed %m%n", argv[1]); - _Exit(127); - } - - // wait for ptrace(PTRACE_TRACEME) to be called - kprintf("PARENT waitpid(child, &wstatus)%n"); - CHECK_EQ(child, waitpid(child, &wstatus, 0)); - - // configure linux process tracing - kprintf("PARENT ptrace(PTRACE_SETOPTIONS)%n"); - CHECK_NE(-1, ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESECCOMP)); - - // continue child process - kprintf("PARENT ptrace(PTRACE_CONT)%n"); - CHECK_NE(-1, ptrace(PTRACE_CONT, child, 0, 0)); - - kprintf("PARENT kill(child, SIGSYS)%n"); - kill(child, SIGSYS); - - for (;;) { - kprintf("PARENT waitpid()%n"); - CHECK_NE(-1, (evpid = waitpid(-1, &wstatus, __WALL))); - if (WIFSTOPPED(wstatus)) { - signal = (wstatus >> 8) & 0xffff; - if (signal == SIGTRAP | PTRACE_EVENT_SECCOMP) { - // CHECK_NE(-1, ptrace(PTRACE_GETEVENTMSG, evpid, 0, &msg)); - CHECK_NE(-1, ptrace(PTRACE_GETREGS, evpid, 0, regs)); - regs.rax = -EPERM; - CHECK_NE(-1, ptrace(PTRACE_GETREGS, evpid, 0, regs)); - ptrace(PTRACE_CONT, evpid, 0, 0); - } else { - ptrace(PTRACE_CONT, evpid, 0, signal & 127); - } - } else if (WIFEXITED(wstatus)) { - exit(WEXITSTATUS(wstatus)); - } else { - exit(128 + WTERMSIG(wstatus)); - } - } - - return 0; -} diff --git a/tool/build/pledge.c b/tool/build/pledge.c new file mode 100644 index 000000000..7478da640 --- /dev/null +++ b/tool/build/pledge.c @@ -0,0 +1,136 @@ +/*-*- 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/calls.h" +#include "libc/errno.h" +#include "libc/fmt/conv.h" +#include "libc/intrin/kprintf.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "third_party/getopt/getopt.h" + +// options used: hpugc +// letters not used: ABCDEFGHIJKLMNOPQRSTUVWXYZabdefijklmnoqrstvwxyz +// digits not used: 0123456789 +// puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~ +// letters duplicated: none +#define GETOPTS "hp: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\ + -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\ + - flock: file locks\n\ + - tty: terminal ioctls\n\ + - recvfd: allow SCM_RIGHTS\n\ + - fattr: allow changing some struct stat bits\n\ + - inet: allow IPv4 and IPv6\n\ + - unix: allow local sockets\n\ + - dns: allow dns\n\ + - proc: allow fork, clone and friends\n\ + - thread: allow clone\n\ + - id: allow setuid and friends\n\ + - exec: allow executing ape binaries\n\ +" + +int g_gflag; +int g_uflag; +int g_hflag; +const char *g_pflag; +const char *g_cflag; + +static void GetOpts(int argc, char *argv[]) { + int opt; + g_pflag = ""; + while ((opt = getopt(argc, argv, GETOPTS)) != -1) { + switch (opt) { + case 'p': + g_pflag = optarg; + break; + case 'c': + g_cflag = optarg; + break; + case 'g': + g_gflag = atoi(optarg); + break; + case 'u': + g_uflag = atoi(optarg); + break; + case 'h': + case '?': + write(1, USAGE, sizeof(USAGE) - 1); + exit(0); + default: + write(2, USAGE, sizeof(USAGE) - 1); + exit(64); + } + } +} + +const char *prog; +char pledges[1024]; +char pathbuf[PATH_MAX]; + +int main(int argc, char *argv[]) { + GetOpts(argc, argv); + if (optind == argc) { + kprintf("error: too few args\n", g_pflag); + write(2, USAGE, sizeof(USAGE) - 1); + exit(64); + } + if (g_cflag) { + if (chroot(g_cflag) == -1) { + kprintf("error: chroot(%`'s) failed: %s\n", g_cflag, strerror(errno)); + return 1; + } + } + if (!(prog = commandv(argv[optind], pathbuf, sizeof(pathbuf)))) { + kprintf("error: command not found: %s\n", argv[optind]); + return 2; + } + if (g_gflag) { + if (setgid(g_gflag) == -1) { + kprintf("error: setgid(%d) failed: %s\n", g_gflag, strerror(errno)); + return 3; + } + } + if (g_uflag) { + if (setuid(g_uflag) == -1) { + kprintf("error: setuid(%d) failed: %s\n", g_uflag, strerror(errno)); + return 4; + } + } + ksnprintf(pledges, sizeof(pledges), "%s execnative", g_pflag); + if (pledge(pledges, 0) == -1) { + kprintf("error: pledge(%`'s) failed: %s\n", pledges, strerror(errno)); + return 5; + } + execv(prog, argv + optind); + kprintf("error: execve(%`'s) failed: %s\n", prog, strerror(errno)); + return 127; +} diff --git a/tool/net/help.txt b/tool/net/help.txt index a80a11e92..a76d061c2 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -765,7 +765,7 @@ FUNCTIONS If the raw length of a table is reported as zero, then we check for the magic element `[0]=false`. If it's present, then - your table will be serialized as empty array `[]`. That entry + your table will be serialized as empty array `[]`. An entry is inserted by DecodeJson() automatically, only when encountering empty arrays, and it's necessary in order to make empty arrays round-trip. If raw length is zero and `[0]=false` is absent,