From e81edf7b04d1401ee0e5cded1eee72d5c7fe1a37 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 18 Jul 2022 07:23:15 -0700 Subject: [PATCH] Improve pledge() and unveil() The pledge.com command now supports the new [WIP] unveil() support. For example, to strongly sandbox our command for listing directories. o//tool/build/assimilate.com o//examples/ls.com pledge.com -v /etc -p 'stdio rpath' o//examples/ls.com /etc This file system sandboxing is going to be perfect for us, because APE binaries are self-contained static executables that really don't use the filesystem that much. On the other hand, with non-static executables, sandboxing is going to be more difficult. For example, here's how to sandbox the `ls` command on the latest Alpine: pledge.com -v rx:/lib -v /usr/lib -v /etc -p 'stdio rpath exec' ls /etc This change fixes the `execpromises` API with pledge(). This change also adds unix.unveil() to redbean. Fixes #494 --- libc/calls/execve.c | 12 +- libc/calls/faccessat.c | 14 +- libc/calls/syscall-sysv.internal.h | 3 +- libc/intrin/promises.c | 1 + libc/intrin/promises.internal.h | 42 +++--- libc/mem/pledge.c | 225 +++++++++++++++++++---------- libc/mem/unveil.c | 14 +- libc/sysv/calls/faccessat2.s | 2 - libc/sysv/calls/openat2.s | 2 - libc/sysv/calls/sys_faccessat2.s | 2 + libc/sysv/calls/sys_openat2.s | 2 + libc/sysv/syscalls.sh | 4 +- test/libc/mem/pledge_test.c | 126 +++++++++++++++- test/libc/mem/{ => prog}/life.c | 0 test/libc/mem/prog/sock.c | 31 ++++ test/libc/mem/test.mk | 48 ++++-- third_party/lua/lunix.c | 15 +- tool/build/pledge.c | 85 ++++++++--- tool/net/help.txt | 57 +++++++- 19 files changed, 535 insertions(+), 150 deletions(-) delete mode 100644 libc/sysv/calls/faccessat2.s delete mode 100644 libc/sysv/calls/openat2.s create mode 100644 libc/sysv/calls/sys_faccessat2.s create mode 100644 libc/sysv/calls/sys_openat2.s rename test/libc/mem/{ => prog}/life.c (100%) create mode 100644 test/libc/mem/prog/sock.c diff --git a/libc/calls/execve.c b/libc/calls/execve.c index 1823eba87..cf87257d9 100644 --- a/libc/calls/execve.c +++ b/libc/calls/execve.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/likely.h" +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/strace.internal.h" #include "libc/calls/syscall-nt.internal.h" @@ -24,10 +25,13 @@ #include "libc/dce.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/kprintf.h" +#include "libc/intrin/promises.internal.h" #include "libc/log/libfatal.internal.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" +int sys_pledge_linux(unsigned long); + /** * Replaces current process with program. * @@ -66,7 +70,13 @@ int execve(const char *prog, char *const argv[], char *const envp[]) { } #endif if (!IsWindows()) { - rc = sys_execve(prog, argv, envp); + rc = 0; + if (IsLinux() && __execpromises && weaken(sys_pledge_linux)) { + rc = weaken(sys_pledge_linux)(__execpromises); + } + if (!rc) { + rc = sys_execve(prog, argv, envp); + } } else { rc = sys_execve_nt(prog, argv, envp); } diff --git a/libc/calls/faccessat.c b/libc/calls/faccessat.c index 875b3b885..e487dd5a2 100644 --- a/libc/calls/faccessat.c +++ b/libc/calls/faccessat.c @@ -22,6 +22,7 @@ #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" #include "libc/sysv/consts/at.h" @@ -36,18 +37,27 @@ * @param path is a filename or directory * @param mode can be R_OK, W_OK, X_OK, F_OK * @param flags can have AT_EACCESS, AT_SYMLINK_NOFOLLOW + * @note on Linux flags is only supported on Linux 5.8+ * @return 0 if ok, or -1 and sets errno * @asyncsignalsafe */ int faccessat(int dirfd, const char *path, int mode, uint32_t flags) { - int rc; + int e, rc; if (IsAsan() && !__asan_is_valid(path, 1)) { rc = efault(); } else if (weaken(__zipos_notat) && weaken(__zipos_notat)(dirfd, path) == -1) { rc = -1; /* TODO(jart): implement me */ } else if (!IsWindows()) { - rc = sys_faccessat(dirfd, path, mode, flags); + e = errno; + if (!flags) goto NoFlags; + if ((rc = sys_faccessat2(dirfd, path, mode, flags)) == -1) { + if (errno == ENOSYS) { + errno = e; + NoFlags: + rc = sys_faccessat(dirfd, path, mode, flags); + } + } } else { rc = sys_faccessat_nt(dirfd, path, mode, flags); } diff --git a/libc/calls/syscall-sysv.internal.h b/libc/calls/syscall-sysv.internal.h index 9561ac9cd..9ec822b8b 100644 --- a/libc/calls/syscall-sysv.internal.h +++ b/libc/calls/syscall-sysv.internal.h @@ -33,6 +33,7 @@ i32 sys_dup2(i32, i32) hidden; i32 sys_dup3(i32, i32, i32) hidden; i32 sys_execve(const char *, char *const[], char *const[]) hidden; i32 sys_faccessat(i32, const char *, i32, u32) hidden; +i32 sys_faccessat2(i32, const char *, i32, u32) hidden; i32 sys_fadvise(i32, i64, i64, i32) hidden; i32 sys_fchdir(i32) hidden; i32 sys_fchmod(i32, u32) hidden; @@ -95,6 +96,7 @@ i32 sys_tkill(i32, i32, void *) hidden; i32 sys_truncate(const char *, u64, u64) hidden; i32 sys_uname(char *) hidden; i32 sys_unlinkat(i32, const char *, i32) hidden; +i32 sys_unveil(const char *, const char *) hidden; i64 sys_copy_file_range(i32, long *, i32, long *, u64, u32) hidden; i64 sys_getrandom(void *, u64, u32) hidden; i64 sys_pread(i32, void *, u64, i64, i64) hidden; @@ -111,7 +113,6 @@ u32 sys_geteuid(void) hidden; u32 sys_getgid(void) hidden; u32 sys_getuid(void) hidden; u32 sys_umask(u32) hidden; -i32 sys_unveil(const char *, const char *) hidden; void *__sys_mmap(void *, u64, u32, u32, i64, i64, i64) hidden; void *sys_mremap(void *, u64, u64, i32, void *) hidden; void sys_exit(int) hidden; diff --git a/libc/intrin/promises.c b/libc/intrin/promises.c index 78519ed5d..19085b423 100644 --- a/libc/intrin/promises.c +++ b/libc/intrin/promises.c @@ -19,3 +19,4 @@ #include "libc/intrin/promises.internal.h" unsigned long __promises; +unsigned long __execpromises; diff --git a/libc/intrin/promises.internal.h b/libc/intrin/promises.internal.h index 67106985c..d421c8bba 100644 --- a/libc/intrin/promises.internal.h +++ b/libc/intrin/promises.internal.h @@ -1,33 +1,33 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_PROMISES_H_ #define COSMOPOLITAN_LIBC_INTRIN_PROMISES_H_ -#define PROMISE_DEFAULT 0 -#define PROMISE_STDIO 1 -#define PROMISE_RPATH 2 -#define PROMISE_WPATH 3 -#define PROMISE_CPATH 4 -#define PROMISE_DPATH 5 -#define PROMISE_FLOCK 6 -#define PROMISE_FATTR 7 -#define PROMISE_INET 8 -#define PROMISE_UNIX 9 -#define PROMISE_DNS 10 -#define PROMISE_TTY 11 -#define PROMISE_RECVFD 12 -#define PROMISE_PROC 13 -#define PROMISE_THREAD 14 -#define PROMISE_EXEC 15 -#define PROMISE_EXECNATIVE 16 -#define PROMISE_ID 17 -#define PROMISE_UNVEIL 18 -#define PROMISE_MAX 18 +#define PROMISE_STDIO 0 +#define PROMISE_RPATH 1 +#define PROMISE_WPATH 2 +#define PROMISE_CPATH 3 +#define PROMISE_DPATH 4 +#define PROMISE_FLOCK 5 +#define PROMISE_FATTR 6 +#define PROMISE_INET 7 +#define PROMISE_UNIX 8 +#define PROMISE_DNS 9 +#define PROMISE_TTY 10 +#define PROMISE_RECVFD 11 +#define PROMISE_PROC 12 +#define PROMISE_THREAD 13 +#define PROMISE_EXEC 14 +#define PROMISE_EXECNATIVE 15 +#define PROMISE_ID 16 +#define PROMISE_UNVEIL 17 +#define PROMISE_SENDFD 18 -#define PLEDGED(x) (~__promises & (1L << PROMISE_##x)) +#define PLEDGED(x) ((~__promises >> PROMISE_##x) & 1) #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ hidden extern unsigned long __promises; +hidden extern unsigned long __execpromises; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/mem/pledge.c b/libc/mem/pledge.c index 96e9f4b41..9a550f137 100644 --- a/libc/mem/pledge.c +++ b/libc/mem/pledge.c @@ -25,7 +25,6 @@ #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" -#include "libc/intrin/kprintf.h" #include "libc/intrin/promises.internal.h" #include "libc/limits.h" #include "libc/macros.internal.h" @@ -157,6 +156,7 @@ static const uint16_t kPledgeLinuxRpath[] = { __NR_linux_fstatat, // __NR_linux_access, // __NR_linux_faccessat, // + __NR_linux_faccessat2, // __NR_linux_readlink, // __NR_linux_readlinkat, // __NR_linux_statfs, // @@ -173,6 +173,7 @@ static const uint16_t kPledgeLinuxWpath[] = { __NR_linux_fstatat, // __NR_linux_access, // __NR_linux_faccessat, // + __NR_linux_faccessat2, // __NR_linux_readlinkat, // __NR_linux_chmod, // __NR_linux_fchmod, // @@ -254,6 +255,10 @@ static const uint16_t kPledgeLinuxRecvfd[] = { __NR_linux_recvmsg, // }; +static const uint16_t kPledgeLinuxSendfd[] = { + __NR_linux_sendmsg, // +}; + static const uint16_t kPledgeLinuxProc[] = { __NR_linux_fork, // __NR_linux_vfork, // @@ -268,8 +273,10 @@ static const uint16_t kPledgeLinuxProc[] = { }; static const uint16_t kPledgeLinuxThread[] = { - __NR_linux_clone, // - __NR_linux_futex, // + __NR_linux_clone, // + __NR_linux_futex, // + __NR_linux_set_robust_list, // + __NR_linux_get_robust_list, // }; static const uint16_t kPledgeLinuxId[] = { @@ -313,7 +320,6 @@ static const struct Pledges { const uint16_t *syscalls; const size_t len; } kPledgeLinux[] = { - [PROMISE_DEFAULT] = {"default", PLEDGE(kPledgeLinuxDefault)}, // [PROMISE_STDIO] = {"stdio", PLEDGE(kPledgeLinuxStdio)}, // [PROMISE_RPATH] = {"rpath", PLEDGE(kPledgeLinuxRpath)}, // [PROMISE_WPATH] = {"wpath", PLEDGE(kPledgeLinuxWpath)}, // @@ -326,13 +332,13 @@ static const struct Pledges { [PROMISE_DNS] = {"dns", PLEDGE(kPledgeLinuxDns)}, // [PROMISE_TTY] = {"tty", PLEDGE(kPledgeLinuxTty)}, // [PROMISE_RECVFD] = {"recvfd", PLEDGE(kPledgeLinuxRecvfd)}, // + [PROMISE_SENDFD] = {"sendfd", PLEDGE(kPledgeLinuxSendfd)}, // [PROMISE_PROC] = {"proc", PLEDGE(kPledgeLinuxProc)}, // [PROMISE_THREAD] = {"thread", PLEDGE(kPledgeLinuxThread)}, // [PROMISE_EXEC] = {"exec", PLEDGE(kPledgeLinuxExec)}, // [PROMISE_EXECNATIVE] = {"execnative", PLEDGE(kPledgeLinuxExec2)}, // [PROMISE_ID] = {"id", PLEDGE(kPledgeLinuxId)}, // [PROMISE_UNVEIL] = {"unveil", PLEDGE(kPledgeLinuxUnveil)}, // - [PROMISE_MAX + 1] = {0}, // }; static const struct sock_filter kFilterStart[] = { @@ -342,6 +348,13 @@ static const struct sock_filter kFilterStart[] = { BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), // each filter assumes ordinal is already loaded into accumulator BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), + // Forbid some system calls with ENOSYS (rather than EPERM) + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_openat2, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (38 & SECCOMP_RET_DATA)), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_clone3, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (38 & SECCOMP_RET_DATA)), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_statx, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (38 & SECCOMP_RET_DATA)), }; static const struct sock_filter kFilterEnd[] = { @@ -957,8 +970,8 @@ static bool AllowFchmodat(struct Filter *f) { return AppendFilter(f, PLEDGE(fragment)); } -static bool AppendPledge(struct Filter *f, const uint16_t *p, size_t len, bool needmapexec, - bool needmorphing) { +static bool AppendPledge(struct Filter *f, const uint16_t *p, size_t len, + bool needmapexec, bool needmorphing) { int i; for (i = 0; i < len; ++i) { switch (p[i]) { @@ -1045,83 +1058,100 @@ static bool AppendPledge(struct Filter *f, const uint16_t *p, size_t len, bool n return true; } -static int FindPromise(const struct Pledges *p, const char *name, size_t *len) { - int i; - for (i = 0; p[i].name; ++i) { - if (!strcasecmp(name, p[i].name)) { - *len = p[i].len; - return i; - } - } - return -1; -} - -static int sys_pledge_linux(const char *promises, const char *execpromises) { - bool ok; - int rc = -1; - size_t plen; - int promise; +int sys_pledge_linux(unsigned long ipromises) { + bool ok = true; + int i, rc = -1; bool needmapexec; - bool needexecnative; bool needmorphing; + bool needexecnative; struct Filter f = {0}; - const uint16_t *pledge; - unsigned long ipromises = -1; - char *s, *tok, *state, *start; - 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)) && + ipromises = ~ipromises; + needmapexec = (ipromises >> PROMISE_EXEC) & 1; + needmorphing = (ipromises >> PROMISE_THREAD) & 1; + needexecnative = (ipromises >> PROMISE_EXECNATIVE) & 1; + if (AppendFilter(&f, kFilterStart, ARRAYLEN(kFilterStart)) && (needmapexec || needexecnative || AppendOriginVerification(&f)) && - AppendPledge(&f, kPledgeLinuxDefault, ARRAYLEN(kPledgeLinuxDefault), needmapexec, - needmorphing)) { - for (ok = true; (tok = strtok_r(start, " \t\r\n", &state)); start = 0) { - if ((promise = FindPromise(kPledgeLinux, tok, &plen)) != -1) { - pledge = kPledgeLinux[promise].syscalls; - ipromises &= ~(1ULL << promise); - } else { - ok = false; - rc = einval(); - break; - } - if (!AppendPledge(&f, pledge, plen, needmapexec, needmorphing)) { - ok = false; - break; + AppendPledge(&f, kPledgeLinuxDefault, ARRAYLEN(kPledgeLinuxDefault), + needmapexec, needmorphing)) { + for (i = 0; i < ARRAYLEN(kPledgeLinux); ++i) { + if ((ipromises & (1ul << i)) && kPledgeLinux[i].name) { + ipromises &= ~(1ul << i); + if (!AppendPledge(&f, kPledgeLinux[i].syscalls, kPledgeLinux[i].len, + needmapexec, needmorphing)) { + ok = false; + rc = einval(); + break; + } } } + if (ipromises) { + ok = false; + rc = einval(); + } if (ok && AppendFilter(&f, kFilterEnd, ARRAYLEN(kFilterEnd)) && (rc = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) != -1) { struct sock_fprog sandbox = {.len = f.n, .filter = f.p}; rc = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &sandbox); } - if (!rc) { - __promises = ipromises; - } } free(f.p); - free(s); return rc; } -static void SetPromises(const char *promises) { - int promise; - size_t plen; - char *tok, *state, *start; - unsigned long ipromises = -1; - while ((tok = strtok_r(start, " \t\r\n", &state))) { - if ((promise = FindPromise(kPledgeLinux, tok, &plen)) != -1) { - ipromises &= ~(1ULL << promise); +static int FindPromise(const char *name) { + int i; + for (i = 0; i < ARRAYLEN(kPledgeLinux); ++i) { + if (!strcasecmp(name, kPledgeLinux[i].name)) { + return i; } - start = 0; } - __promises = ipromises; + STRACE("unknown promise %s", name); + return -1; +} + +static int ParsePromises(const char *promises, unsigned long *out) { + int rc = 0; + int promise; + unsigned long ipromises; + char *tok, *state, *start, *freeme; + if (promises) { + ipromises = -1; + freeme = start = strdup(promises); + while ((tok = strtok_r(start, " \t\r\n", &state))) { + if ((promise = FindPromise(tok)) != -1) { + ipromises &= ~(1ULL << promise); + } else { + rc = einval(); + break; + } + start = 0; + } + free(freeme); + } else { + ipromises = 0; + } + if (!rc) { + *out = ipromises; + } + return rc; +} + +static void FixupOpenbsdPromises(char *p) { + if (!p) return; + if ((p = strstr(p, "execnative"))) { + p[4] = ' '; + p[5] = ' '; + p[6] = ' '; + p[7] = ' '; + p[8] = ' '; + p[9] = ' '; + } } /** * Restricts system operations, e.g. * - * pledge("stdio tty", 0); + * pledge("stdio rfile tty", 0); * * Pledging causes most system calls to become unavailable. Your system * call policy is enforced by the kernel, which means it can propagate @@ -1185,12 +1215,13 @@ static void SetPromises(const char *promises) { * fcntl(F_GETFL), fcntl(F_SETFL). * * - "rpath" (read-only path ops) allows chdir, getcwd, open(O_RDONLY), - * openat(O_RDONLY), stat, fstat, lstat, fstatat, access, faccessat, - * readlink, readlinkat, statfs, fstatfs. + * openat(O_RDONLY), stat, fstat, lstat, fstatat, access, + * faccessat,faccessat2, readlink, readlinkat, statfs, fstatfs. * * - "wpath" (write path ops) allows getcwd, open(O_WRONLY), - * openat(O_WRONLY), stat, fstat, lstat, fstatat, access, faccessat, - * readlink, readlinkat, chmod, fchmod, fchmodat. + * openat(O_WRONLY), stat, fstat, lstat, fstatat, access, + * faccessat,faccessat2, readlink, readlinkat, chmod, fchmod, + * fchmodat. * * - "cpath" (create path ops) allows open(O_CREAT), openat(O_CREAT), * rename, renameat, renameat2, link, linkat, symlink, symlinkat, @@ -1204,7 +1235,9 @@ static void SetPromises(const char *promises) { * - "tty" allows ioctl(TIOCGWINSZ), ioctl(TCGETS), ioctl(TCSETS), * ioctl(TCSETSW), ioctl(TCSETSF). * - * - "recvfd" allows recvmsg(SCM_RIGHTS). + * - "recvfd" allows recvmsg in general (for SCM_RIGHTS). + * + * - "recvfd" allows sendmsg in general (for SCM_RIGHTS). * * - "fattr" allows chmod, fchmod, fchmodat, utime, utimes, futimens, * utimensat. @@ -1236,24 +1269,70 @@ static void SetPromises(const char *promises) { * 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. + * assimilate your APE binaries, then this should be preferred. On + * OpenBSD this will be rewritten to be "exec". * * - "unveil" allows unveil() to be called, as well as the underlying * landlock_create_ruleset, landlock_add_rule, landlock_restrict_self * calls on Linux. * + * `execpromises` only matters if "exec" or "execnative" are specified + * in `promises`. In that case, this specifies the promises that'll + * apply once execve() happens. If this is NULL then the default is + * used, which is unrestricted. OpenBSD allows child processes to escape + * the sandbox (so a pledged OpenSSH server process can do things like + * spawn a root shell). Linux however requires monotonically decreasing + * privileges. This function will will perform some validation on Linux + * to make sure that `execpromises` is a subset of `promises`. Your libc + * wrapper for execve() will then apply its SECCOMP BPF filter later. + * Since Linux has to do this before calling sys_execve(), the executed + * process will be weakened to have execute permissions too. + * * @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 + * @raise EINVAL if `execpromises` on Linux isn't a subset of `promises` */ int pledge(const char *promises, const char *execpromises) { int rc; - if (IsLinux()) { - rc = sys_pledge_linux(promises, execpromises); - } else { - rc = sys_pledge(promises, execpromises); + char *p, *q; + unsigned long ipromises, iexecpromises; + if (!(rc = ParsePromises(promises, &ipromises)) && + !(rc = ParsePromises(execpromises, &iexecpromises))) { + if (IsLinux()) { + // copy exec and execnative from promises to execpromises + iexecpromises = + ~(~iexecpromises | (~ipromises & ((1ul << PROMISE_EXEC) | // + (1ul << PROMISE_EXECNATIVE)))); + // if bits are missing in execpromises that exist in promises + // then execpromises wouldn't be a monotonic access reduction + // this check only matters when exec / execnative are allowed + if ((ipromises & ~iexecpromises) && + (~ipromises & + ((1ul << PROMISE_EXEC) | (1ul << PROMISE_EXECNATIVE)))) { + STRACE("execpromises must be a subset of promises"); + rc = einval(); + } else { + rc = sys_pledge_linux(ipromises); + } + } else { + // openbsd only supports execnative and calls it exec + if ((p = strdup(promises))) { + FixupOpenbsdPromises(p); + if ((q = execpromises ? strdup(execpromises) : 0) || !execpromises) { + FixupOpenbsdPromises(q); + rc = sys_pledge(p, q); + free(q); + } else { + rc = -1; + } + free(p); + } else { + rc = -1; + } + } if (!rc) { - SetPromises(promises); + __promises = ipromises; + __execpromises = iexecpromises; } } STRACE("pledge(%#s, %#s) → %d% m", promises, execpromises, rc); diff --git a/libc/mem/unveil.c b/libc/mem/unveil.c index 192ba2107..e18ab39a6 100644 --- a/libc/mem/unveil.c +++ b/libc/mem/unveil.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/internal.h" #include "libc/calls/landlock.h" #include "libc/calls/strace.internal.h" #include "libc/calls/struct/stat.h" @@ -25,7 +26,10 @@ #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/errno.h" #include "libc/mem/mem.h" +#include "libc/nexgen32e/threaded.h" +#include "libc/runtime/internal.h" #include "libc/str/str.h" +#include "libc/sysv/consts/at.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/pr.h" @@ -126,10 +130,13 @@ static int sys_unveil_linux(const char *path, const char *permissions) { if ((rc = sys_open(path, O_PATH | O_CLOEXEC, 0)) == -1) return rc; pb.parent_fd = rc; struct stat st; - if ((rc = fstat(pb.parent_fd, &st)) == -1) return err_close(rc, pb.parent_fd); - if (!S_ISDIR(st.st_mode)) pb.allowed_access &= FILE_BITS; - if ((rc = landlock_add_rule(State.fd, LANDLOCK_RULE_PATH_BENEATH, &pb, 0))) + if ((rc = sys_fstat(pb.parent_fd, &st)) == -1) { return err_close(rc, pb.parent_fd); + } + if (!S_ISDIR(st.st_mode)) pb.allowed_access &= FILE_BITS; + if ((rc = landlock_add_rule(State.fd, LANDLOCK_RULE_PATH_BENEATH, &pb, 0))) { + return err_close(rc, pb.parent_fd); + } sys_close(pb.parent_fd); return rc; } @@ -139,6 +146,7 @@ static int sys_unveil_linux(const char *path, const char *permissions) { */ int unveil(const char *path, const char *permissions) { int rc; + __enable_tls(); if (IsLinux()) { rc = sys_unveil_linux(path, permissions); } else { diff --git a/libc/sysv/calls/faccessat2.s b/libc/sysv/calls/faccessat2.s deleted file mode 100644 index aaccd0eda..000000000 --- a/libc/sysv/calls/faccessat2.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.internal.inc" -.scall faccessat2,0xfffffffffffff1b7,globl diff --git a/libc/sysv/calls/openat2.s b/libc/sysv/calls/openat2.s deleted file mode 100644 index 3522cd62e..000000000 --- a/libc/sysv/calls/openat2.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.internal.inc" -.scall openat2,0xfffffffffffff1b5,globl diff --git a/libc/sysv/calls/sys_faccessat2.s b/libc/sysv/calls/sys_faccessat2.s new file mode 100644 index 000000000..1d046a9b9 --- /dev/null +++ b/libc/sysv/calls/sys_faccessat2.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.internal.inc" +.scall sys_faccessat2,0xfffffffffffff1b7,globl,hidden diff --git a/libc/sysv/calls/sys_openat2.s b/libc/sysv/calls/sys_openat2.s new file mode 100644 index 000000000..33153dd39 --- /dev/null +++ b/libc/sysv/calls/sys_openat2.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.internal.inc" +.scall sys_openat2,0xfffffffffffff1b5,globl,hidden diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh index 86fbc666d..a5e12a0ff 100755 --- a/libc/sysv/syscalls.sh +++ b/libc/sysv/syscalls.sh @@ -385,9 +385,9 @@ scall fspick 0xfffffffffffff1b1 globl scall pidfd_open 0xfffffffffffff1b2 globl scall clone3 0xfffffffffffff1b3 globl scall close_range 0xfffffffffffff1b4 globl -scall openat2 0xfffffffffffff1b5 globl # Linux 5.6 +scall sys_openat2 0xfffffffffffff1b5 globl hidden # Linux 5.6 scall pidfd_getfd 0xfffffffffffff1b6 globl -scall faccessat2 0xfffffffffffff1b7 globl +scall sys_faccessat2 0xfffffffffffff1b7 globl hidden scall process_madvise 0xfffffffffffff1b8 globl scall epoll_pwait2 0xfffffffffffff1b9 globl scall mount_setattr 0xfffffffffffff1ba globl diff --git a/test/libc/mem/pledge_test.c b/test/libc/mem/pledge_test.c index 67b4ec27e..9e2a7f211 100644 --- a/test/libc/mem/pledge_test.c +++ b/test/libc/mem/pledge_test.c @@ -28,7 +28,9 @@ #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" +#include "libc/mem/io.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" @@ -48,16 +50,33 @@ #include "libc/testlib/testlib.h" #include "libc/thread/spawn.h" +STATIC_YOINK("zip_uri_support"); + char testlib_enable_tmp_setup_teardown; void OnSig(int sig) { // do nothing } -void SetUp(void) { - if (!__is_linux_2_6_23() && !IsOpenbsd()) { - exit(0); +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); +} + +void SetUp(void) { + if (!__is_linux_2_6_23() && !IsOpenbsd()) exit(0); + ASSERT_SYS(0, 0, extract("/zip/life.elf", "life.elf", 0755)); + ASSERT_SYS(0, 0, extract("/zip/sock.elf", "sock.elf", 0755)); } TEST(pledge, default_allowsExit) { @@ -149,8 +168,8 @@ TEST(pledge, multipleCalls_canOnlyBecomeMoreRestrictive2) { int ws, pid; ASSERT_NE(-1, (pid = fork())); if (!pid) { - ASSERT_SYS(0, 0, pledge("stdio", 0)); - ASSERT_SYS(EPERM, -1, pledge("stdio unix", 0)); + ASSERT_SYS(0, 0, pledge("stdio", "stdio")); + ASSERT_SYS(EPERM, -1, pledge("stdio unix", "stdio unix")); _Exit(0); } EXPECT_NE(-1, wait(&ws)); @@ -293,7 +312,7 @@ TEST(pledge, mmapExec) { int ws, pid; ASSERT_NE(-1, (pid = fork())); if (!pid) { - ASSERT_SYS(0, 0, pledge("stdio exec", 0)); + ASSERT_SYS(0, 0, pledge("stdio exec", "stdio")); ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0))); ASSERT_SYS(0, 0, mprotect(p, FRAMESIZE, PROT_READ)); @@ -414,3 +433,98 @@ TEST(pledge, sigaction_isFineButForbidsSigsys) { EXPECT_NE(-1, wait(&ws)); EXPECT_TRUE(WIFEXITED(ws) && !WEXITSTATUS(ws)); } + +TEST(pledge, execpromises_ok) { + if (IsOpenbsd()) return; // b/c testing linux bpf + int ws, pid; + struct stat st; + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio execnative", "stdio")); + execl("life.elf", "life.elf", 0); + _Exit(127); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFEXITED(ws)); + EXPECT_EQ(42, WEXITSTATUS(ws)); +} + +TEST(pledge, execpromises_notok) { + if (IsOpenbsd()) return; // b/c testing linux bpf + int ws, pid; + struct stat st; + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio execnative", "stdio")); + execl("sock.elf", "sock.elf", 0); + _Exit(127); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFEXITED(ws)); + EXPECT_EQ(128 + EPERM, WEXITSTATUS(ws)); +} + +TEST(pledge, execpromises_reducesAtExecOnLinux) { + if (IsOpenbsd()) return; // b/c testing linux bpf + int ws, pid; + struct stat st; + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio inet tty execnative", "stdio tty")); + execl("sock.elf", "sock.elf", 0); + _Exit(127); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFEXITED(ws)); + EXPECT_EQ(128 + EPERM, WEXITSTATUS(ws)); +} + +TEST(pledge_openbsd, execpromisesIsNull_letsItDoAnything) { + if (!IsOpenbsd()) return; + int ws, pid; + struct stat st; + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio execnative", 0)); + execl("sock.elf", "sock.elf", 0); + _Exit(127); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFEXITED(ws)); + EXPECT_EQ(3, WEXITSTATUS(ws)); +} + +TEST(pledge_openbsd, execpromisesIsSuperset_letsItDoAnything) { + if (!IsOpenbsd()) return; + int ws, pid; + struct stat st; + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio rpath execnative", "stdio rpath tty inet")); + execl("sock.elf", "sock.elf", 0); + _Exit(127); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFEXITED(ws)); + EXPECT_EQ(3, WEXITSTATUS(ws)); +} + +TEST(pledge_linux, execpromisesIsSuperset_notPossible) { + if (IsOpenbsd()) return; + ASSERT_SYS(EINVAL, -1, pledge("stdio execnative", "stdio inet execnative")); +} + +TEST(pledge_openbsd, execpromises_notok) { + if (!IsOpenbsd()) return; + int ws, pid; + struct stat st; + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio execnative", "stdio")); + execl("sock.elf", "sock.elf", 0); + _Exit(127); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFSIGNALED(ws)); + EXPECT_EQ(SIGABRT, WTERMSIG(ws)); +} diff --git a/test/libc/mem/life.c b/test/libc/mem/prog/life.c similarity index 100% rename from test/libc/mem/life.c rename to test/libc/mem/prog/life.c diff --git a/test/libc/mem/prog/sock.c b/test/libc/mem/prog/sock.c new file mode 100644 index 000000000..cfb3db0cb --- /dev/null +++ b/test/libc/mem/prog/sock.c @@ -0,0 +1,31 @@ +/*-*- 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/errno.h" +#include "libc/sock/sock.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/sock.h" + +int main(int argc, char *argv[]) { + int fd; + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) { + return fd; + } else { + return 128 + errno; + } +} diff --git a/test/libc/mem/test.mk b/test/libc/mem/test.mk index e547852ac..abbe03557 100644 --- a/test/libc/mem/test.mk +++ b/test/libc/mem/test.mk @@ -61,34 +61,64 @@ o/$(MODE)/test/libc/mem/%.com.dbg: \ $(TEST_LIBC_MEM_DEPS) \ o/$(MODE)/test/libc/mem/%.o \ o/$(MODE)/test/libc/mem/mem.pkg \ - o/$(MODE)/test/libc/mem/life.elf.zip.o \ + o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ + o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o \ $(LIBC_TESTMAIN) \ $(CRT) \ $(APE_NO_MODIFY_SELF) @$(APELINK) -o/$(MODE)/test/libc/mem/life.com.dbg: \ +################################################################################ + +o/$(MODE)/test/libc/mem/prog/life.com.dbg: \ $(LIBC_RUNTIME) \ - o/$(MODE)/test/libc/mem/life.o \ + o/$(MODE)/test/libc/mem/prog/life.o \ $(CRT) \ $(APE) @$(APELINK) -o/$(MODE)/test/libc/mem/life.elf: \ +o/$(MODE)/test/libc/mem/prog/life.elf: \ o/$(MODE)/tool/build/assimilate.com \ - o/$(MODE)/test/libc/mem/life.com + o/$(MODE)/test/libc/mem/prog/life.com @$(COMPILE) -ACP -T$@ \ build/bootstrap/cp.com \ - o/$(MODE)/test/libc/mem/life.com \ - o/$(MODE)/test/libc/mem/life.elf + o/$(MODE)/test/libc/mem/prog/life.com \ + o/$(MODE)/test/libc/mem/prog/life.elf @$(COMPILE) -AASSIMILATE -T$@ \ o/$(MODE)/tool/build/assimilate.com \ - o/$(MODE)/test/libc/mem/life.elf + o/$(MODE)/test/libc/mem/prog/life.elf -o/$(MODE)/test/libc/mem/life.elf.zip.o: \ +o/$(MODE)/test/libc/mem/prog/life.elf.zip.o: \ ZIPOBJ_FLAGS += \ -B +################################################################################ + +o/$(MODE)/test/libc/mem/prog/sock.com.dbg: \ + $(LIBC_RUNTIME) \ + $(LIBC_SOCK) \ + o/$(MODE)/test/libc/mem/prog/sock.o \ + $(CRT) \ + $(APE) + @$(APELINK) + +o/$(MODE)/test/libc/mem/prog/sock.elf: \ + o/$(MODE)/tool/build/assimilate.com \ + o/$(MODE)/test/libc/mem/prog/sock.com + @$(COMPILE) -ACP -T$@ \ + build/bootstrap/cp.com \ + o/$(MODE)/test/libc/mem/prog/sock.com \ + o/$(MODE)/test/libc/mem/prog/sock.elf + @$(COMPILE) -AASSIMILATE -T$@ \ + o/$(MODE)/tool/build/assimilate.com \ + o/$(MODE)/test/libc/mem/prog/sock.elf + +o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o: \ + ZIPOBJ_FLAGS += \ + -B + +################################################################################ + $(TEST_LIBC_MEM_OBJS): \ DEFAULT_CCFLAGS += \ -fno-builtin diff --git a/third_party/lua/lunix.c b/third_party/lua/lunix.c index 4d884ccb8..bfbd7cef4 100644 --- a/third_party/lua/lunix.c +++ b/third_party/lua/lunix.c @@ -1382,12 +1382,22 @@ static int LuaUnixSiocgifconf(lua_State *L) { return 1; } -// sandbox.pledge([promises:str]) +// sandbox.pledge([promises:str[, execpromises:str]]) // ├─→ true // └─→ nil, unix.Errno static int LuaUnixPledge(lua_State *L) { int olderr = errno; - return SysretBool(L, "pledge", olderr, pledge(luaL_checkstring(L, 1), 0)); + return SysretBool(L, "pledge", olderr, + pledge(luaL_checkstring(L, 1), luaL_optstring(L, 2, 0))); +} + +// sandbox.unveil(path:str, permissions:str) +// ├─→ true +// └─→ nil, unix.Errno +static int LuaUnixUnveil(lua_State *L) { + int olderr = errno; + return SysretBool(L, "unveil", olderr, + unveil(luaL_checkstring(L, 1), luaL_checkstring(L, 2))); } // unix.gethostname() @@ -2636,6 +2646,7 @@ static const luaL_Reg kLuaUnix[] = { {"truncate", LuaUnixTruncate}, // shrink or extend file medium {"umask", LuaUnixUmask}, // set default file mask {"unlink", LuaUnixUnlink}, // remove file + {"unveil", LuaUnixUnveil}, // filesystem sandboxing {"utimensat", LuaUnixUtimensat}, // change access/modified time {"wait", LuaUnixWait}, // wait for child to change status {"write", LuaUnixWrite}, // write to file or socket diff --git a/tool/build/pledge.c b/tool/build/pledge.c index b29df648a..c54676954 100644 --- a/tool/build/pledge.c +++ b/tool/build/pledge.c @@ -27,12 +27,14 @@ #include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" #include "libc/math.h" +#include "libc/mem/mem.h" #include "libc/nexgen32e/kcpuids.h" #include "libc/runtime/runtime.h" #include "libc/runtime/sysconf.h" #include "libc/sock/sock.h" #include "libc/sock/struct/pollfd.h" #include "libc/stdio/stdio.h" +#include "libc/stdio/strlist.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/ioprio.h" #include "libc/sysv/consts/o.h" @@ -49,17 +51,18 @@ STATIC_YOINK("strerror_wr"); #define USAGE \ "\ usage: pledge.com [-hnN] PROG ARGS...\n\ - -h show help\n\ - -g GID call setgid()\n\ - -u UID call setuid()\n\ - -c PATH call chroot()\n\ - -n maximum niceness\n\ - -N don't normalize file descriptors\n\ - -C SECS set cpu limit [default: inherited]\n\ - -M BYTES set virtual memory limit [default: 4gb]\n\ - -P PROCS set process limit [default: GetCpuCount()*2]\n\ - -F BYTES set individual file size limit [default: 4gb]\n\ - -p PLEDGE may contain any of following separated by spaces\n\ + -h show help\n\ + -g GID call setgid()\n\ + -u UID call setuid()\n\ + -c PATH call chroot()\n\ + -v [PERM:]PATH call unveil(PATH,PERM) where PERM can have rwxc\n\ + -n set maximum niceness\n\ + -N don't normalize file descriptors\n\ + -C SECS set cpu limit [default: inherited]\n\ + -M BYTES set virtual memory limit [default: 4gb]\n\ + -P PROCS set process limit [default: GetCpuCount()*2]\n\ + -F BYTES set individual file size limit [default: 4gb]\n\ + -p PLEDGE may contain any of following separated by spaces\n\ - stdio: allow stdio and benign system calls\n\ - rpath: read-only path ops\n\ - wpath: write path ops\n\ @@ -68,6 +71,7 @@ usage: pledge.com [-hnN] PROG ARGS...\n\ - flock: file locks\n\ - tty: terminal ioctls\n\ - recvfd: allow SCM_RIGHTS\n\ + - sendfd: allow SCM_RIGHTS\n\ - fattr: allow changing some struct stat bits\n\ - inet: allow IPv4 and IPv6\n\ - unix: allow local sockets\n\ @@ -75,9 +79,9 @@ usage: pledge.com [-hnN] PROG ARGS...\n\ - proc: allow fork, clone and friends\n\ - thread: allow clone\n\ - id: allow setuid and friends\n\ - - exec: allow executing ape binaries\n\ + - exec: make execution more permissive\n\ \n\ -pledge.com v1.o\n\ +pledge.com v1.1\n\ copyright 2022 justine alexandra roberts tunney\n\ https://twitter.com/justinetunney\n\ https://linkedin.com/in/jtunney\n\ @@ -102,6 +106,11 @@ long g_proquota; const char *g_chroot; const char *g_promises; +struct { + int n; + char **p; +} unveils; + static void GetOpts(int argc, char *argv[]) { int opt; struct sysinfo si; @@ -111,7 +120,7 @@ static void GetOpts(int argc, char *argv[]) { g_fszquota = 4 * 1000 * 1000 * 1000; g_memquota = 4L * 1024 * 1024 * 1024; if (!sysinfo(&si)) g_memquota = si.totalram; - while ((opt = getopt(argc, argv, "hnNp:u:g:c:C:P:M:F:")) != -1) { + while ((opt = getopt(argc, argv, "hnNp:u:g:c:C:P:M:F:v:")) != -1) { switch (opt) { case 'n': g_nice = true; @@ -147,6 +156,10 @@ static void GetOpts(int argc, char *argv[]) { g_promises = optarg; } break; + case 'v': + unveils.p = realloc(unveils.p, ++unveils.n * sizeof(*unveils.p)); + unveils.p[unveils.n - 1] = optarg; + break; case 'h': case '?': write(1, USAGE, sizeof(USAGE) - 1); @@ -157,9 +170,8 @@ static void GetOpts(int argc, char *argv[]) { } } if (!g_promises) { - g_promises = "stdio rpath execnative"; + g_promises = "stdio rpath"; } - g_promises = xstrcat(g_promises, ' ', "execnative"); } const char *prog; @@ -290,6 +302,7 @@ void MakeProcessNice(void) { } int main(int argc, char *argv[]) { + int i; bool hasfunbits; int useruid, usergid; int owneruid, ownergid; @@ -430,14 +443,50 @@ int main(int argc, char *argv[]) { } } + if (unveils.n) { + if (unveil(prog, "rx") == -1) { + kprintf("error: unveil(0, 0) failed: %m\n", prog, "rx"); + _Exit(20); + } + if (strstr(g_promises, "exec") && isexecutable("/usr/bin/ape")) { + if (unveil("/usr/bin/ape", "rx") == -1) { + kprintf("error: unveil(0, 0) failed: %m\n", "/usr/bin/ape", "rx"); + _Exit(20); + } + } + for (i = 0; i < unveils.n; ++i) { + char *s, *t; + const char *path; + const char *perm; + s = unveils.p[i]; + if ((t = strchr(s, ':'))) { + *t = 0; + perm = s; + path = t + 1; + } else { + perm = "r"; + path = s; + } + if (unveil(path, perm) == -1) { + kprintf("error: unveil(%#s, %#s) failed: %m\n", path, perm); + _Exit(20); + } + } + if (unveil(0, 0) == -1) { + kprintf("error: unveil(0, 0) failed: %m\n"); + _Exit(20); + } + } + // apply sandbox - if (pledge(g_promises, 0) == -1) { + g_promises = xstrcat(g_promises, ' ', "execnative"); + if (pledge(g_promises, g_promises) == -1) { kprintf("error: pledge(%#s) failed: %m\n", g_promises); _Exit(19); } // launch program - __sys_execve(prog, argv + optind, environ); + execve(prog, argv + optind, environ); kprintf("%s: execve failed: %m\n", prog); return 127; } diff --git a/tool/net/help.txt b/tool/net/help.txt index 89f8d70e1..2d63ec00d 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -3619,7 +3619,7 @@ UNIX MODULE See the unix.Rusage section below for details on returned fields. - unix.pledge([promises:str]) + unix.pledge([promises:str[, execpromises:str]]) ├─→ true └─→ nil, unix.Errno @@ -3654,11 +3654,15 @@ UNIX MODULE restrictions need to be loosened. `promises` is a string that may include any of the following groups - delimited by spaces. + delimited by spaces. This list has been curated to focus on the + system calls for which this module provides wrappers. See the + Cosmopolitan Libc pledge() documentation for a comprehensive and + authoritative list of raw system calls. Having the raw system call + list may be useful if you're executing foreign programs. stdio - Allows read, write, send, recv, recvfrom, recvmsg, close, + Allows read, write, send, recv, recvfrom, close, clock_getres, clock_gettime, dup, dup2, dup3, fchdir, fstat, fsync, fdatasync, ftruncate, getdents, getegid, getrandom, geteuid, getgid, getgroups, getitimer, getpgid, getpgrp, getpid, @@ -3721,12 +3725,49 @@ UNIX MODULE exec - Allows execve. + Allows execve, access. - If this is used then APE binaries should be assimilated in order - to work on OpenBSD. 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. + 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. On OpenBSD this will be rewritten to be + "exec". + + `execpromises` only matters if "exec" or "execnative" are specified + in `promises`. In that case, this specifies the promises that'll + apply once execve() happens. If this is NULL then the default is + used, which is unrestricted. OpenBSD allows child processes to escape + the sandbox (so a pledged OpenSSH server process can do things like + spawn a root shell). Linux however requires monotonically decreasing + privileges. This function will will perform some validation on Linux + to make sure that `execpromises` is a subset of `promises`. Your libc + wrapper for execve() will then apply its SECCOMP BPF filter later. + Since Linux has to do this before calling sys_execve(), the executed + process will be weakened to have execute permissions too. + + unix.unveil(path:str, permissions:str) + ├─→ true + └─→ nil, unix.Errno + + Unveil parts of a restricted filesystem view, e.g. + + unix.unveil(".", "r") + unix.unveil(nil, nil) + + This can be used for sandboxing file system access. + + Unveil support is a work in progress. unix.gmtime(unixts:int) ├─→ year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst:int,zone:str