From 5546559034333215ef9da58fe5068dda95e84a41 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 7 Aug 2022 16:18:33 -0700 Subject: [PATCH] Improve pledge() usability and consistency - We now kill the program on violations like OpenBSD - We now print a message explaining which promise is needed - This change also fixes a linkage bug with thread local storage - Your sigaction() handlers should now be more thread safe A new `__pledge_mode` global has been introduced to make pledge() more customizable on Linux. For example: __attribute__((__constructor__)) static void init(void) { __pledge_mode = SECCOMP_RET_ERRNO | EPERM; } Can be used to restore our old permissive pledge() behavior. --- ape/ape.lds | 3 + examples/statfs.c | 226 +++++++++++++++++++++++++++++ libc/calls/g_sighandrvas.c | 16 +- libc/calls/pledge.c | 179 +++++++++++++++++++---- libc/calls/sig2.c | 2 - libc/calls/sigaction.c | 6 +- libc/calls/siglock.c | 32 ++++ libc/calls/state.internal.h | 4 +- libc/calls/struct/statfs.h | 26 ++++ libc/calls/syscall-sysv.internal.h | 3 +- libc/intrin/promises.c | 1 + libc/log/getsicodename.c | 5 + libc/log/oncrashthunks.S | 4 +- libc/log/showcrashreports.c | 2 +- libc/runtime/fork-nt.c | 4 - libc/runtime/runtime.h | 5 + libc/sysv/calls/sys_syslog.s | 2 + libc/sysv/calls/syslog.s | 2 - libc/sysv/consts/kern.h | 21 +++ libc/sysv/consts/loglevel.h | 19 +++ libc/sysv/restorert.S | 5 +- libc/sysv/syscalls.sh | 2 +- test/libc/calls/pledge2_test.c | 134 +++++++++++++++++ test/libc/calls/pledge_test.c | 20 ++- test/libc/calls/unveil_test.c | 15 +- test/libc/runtime/tls_test.c | 18 +++ test/tool/net/lunix_test.lua | 15 +- third_party/lua/lunix.c | 1 + tool/net/help.txt | 20 ++- tool/net/redbean.c | 7 +- 30 files changed, 713 insertions(+), 86 deletions(-) create mode 100644 examples/statfs.c create mode 100644 libc/calls/siglock.c create mode 100644 libc/calls/struct/statfs.h create mode 100644 libc/sysv/calls/sys_syslog.s delete mode 100644 libc/sysv/calls/syslog.s create mode 100644 libc/sysv/consts/kern.h create mode 100644 libc/sysv/consts/loglevel.h create mode 100644 test/libc/calls/pledge2_test.c diff --git a/ape/ape.lds b/ape/ape.lds index 1db67d0e9..814b8510a 100644 --- a/ape/ape.lds +++ b/ape/ape.lds @@ -386,6 +386,7 @@ SECTIONS { _tdata_start = .; *(SORT_BY_ALIGNMENT(.tdata)) *(SORT_BY_ALIGNMENT(.tdata.*)) + . = ALIGN(16); _tdata_end = .; . = ALIGN(PAGESIZE); } :Tls @@ -398,6 +399,8 @@ SECTIONS { _tbss_start = .; *(SORT_BY_ALIGNMENT(.tbss)) *(SORT_BY_ALIGNMENT(.tbss.*)) + . = ALIGN(16); + /* the %fs register is based on this location */ _tbss_end = .; } :Tls diff --git a/examples/statfs.c b/examples/statfs.c new file mode 100644 index 000000000..9223bbf4d --- /dev/null +++ b/examples/statfs.c @@ -0,0 +1,226 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/calls/struct/statfs.h" +#include "libc/log/check.h" +#include "libc/stdio/stdio.h" + +const char *DescribeStatfsType(int64_t x) { + switch (x) { + case 0xadf5: + return "ADFS_SUPER_MAGIC"; + case 0xadff: + return "AFFS_SUPER_MAGIC"; + case 0x0187: + return "AUTOFS_SUPER_MAGIC"; + case 0x1373: + return "DEVFS_SUPER_MAGIC"; + case 0x1cd1: + return "DEVPTS_SUPER_MAGIC"; + case 0xf15f: + return "ECRYPTFS_SUPER_MAGIC"; + case 0x137d: + return "EXT_SUPER_MAGIC"; + case 0xef51: + return "EXT2_OLD_SUPER_MAGIC"; + case 0xef53: + return "EXT2/3/4_SUPER_MAGIC"; + case 0x4244: + return "HFS_SUPER_MAGIC"; + case 0x9660: + return "ISOFS_SUPER_MAGIC"; + case 0x72b6: + return "JFFS2_SUPER_MAGIC"; + case 0x137f: + return "MINIX_SUPER_MAGIC"; + case 0x138f: + return "MINIX_SUPER_MAGIC2"; + case 0x2468: + return "MINIX2_SUPER_MAGIC"; + case 0x2478: + return "MINIX2_SUPER_MAGIC2"; + case 0x4d5a: + return "MINIX3_SUPER_MAGIC"; + case 0x4d44: + return "MSDOS_SUPER_MAGIC"; + case 0x564c: + return "NCP_SUPER_MAGIC"; + case 0x6969: + return "NFS_SUPER_MAGIC"; + case 0x3434: + return "NILFS_SUPER_MAGIC"; + case 0x9fa1: + return "OPENPROM_SUPER_MAGIC"; + case 0x9fa0: + return "PROC_SUPER_MAGIC"; + case 0x002f: + return "QNX4_SUPER_MAGIC"; + case 0x7275: + return "ROMFS_MAGIC"; + case 0x517b: + return "SMB_SUPER_MAGIC"; + case 0x9fa2: + return "USBDEVICE_SUPER_MAGIC"; + case 0x27e0eb: + return "CGROUP_SUPER_MAGIC"; + case 0xbad1dea: + return "FUTEXFS_SUPER_MAGIC"; + case 0x5346414f: + return "AFS_SUPER_MAGIC"; + case 0x09041934: + return "ANON_INODE_FS_MAGIC"; + case 0x62646576: + return "BDEVFS_MAGIC"; + case 0x42465331: + return "BEFS_SUPER_MAGIC"; + case 0x1badface: + return "BFS_MAGIC"; + case 0x42494e4d: + return "BINFMTFS_MAGIC"; + case 0xcafe4a11: + return "BPF_FS_MAGIC"; + case 0x9123683e: + return "BTRFS_SUPER_MAGIC"; + case 0x73727279: + return "BTRFS_TEST_MAGIC"; + case 0x63677270: + return "CGROUP2_SUPER_MAGIC"; + case 0xff534d42: + return "CIFS_MAGIC_NUMBER"; + case 0x73757245: + return "CODA_SUPER_MAGIC"; + case 0x012ff7b7: + return "COH_SUPER_MAGIC"; + case 0x28cd3d45: + return "CRAMFS_MAGIC"; + case 0x64626720: + return "DEBUGFS_MAGIC"; + case 0xde5e81e4: + return "EFIVARFS_MAGIC"; + case 0x00414a53: + return "EFS_SUPER_MAGIC"; + case 0xf2f52010: + return "F2FS_SUPER_MAGIC"; + case 0x65735546: + return "FUSE_SUPER_MAGIC"; + case 0x00c0ffee: + return "HOSTFS_SUPER_MAGIC"; + case 0xf995e849: + return "HPFS_SUPER_MAGIC"; + case 0x958458f6: + return "HUGETLBFS_MAGIC"; + case 0x3153464a: + return "JFS_SUPER_MAGIC"; + case 0x19800202: + return "MQUEUE_MAGIC"; + case 0x11307854: + return "MTD_INODE_FS_MAGIC"; + case 0x6e736673: + return "NSFS_MAGIC"; + case 0x5346544e: + return "NTFS_SB_MAGIC"; + case 0x7461636f: + return "OCFS2_SUPER_MAGIC"; + case 0x794c7630: + return "OVERLAYFS_SUPER_MAGIC"; + case 0x50495045: + return "PIPEFS_MAGIC"; + case 0x6165676c: + return "PSTOREFS_MAGIC"; + case 0x68191122: + return "QNX6_SUPER_MAGIC"; + case 0x858458f6: + return "RAMFS_MAGIC"; + case 0x52654973: + return "REISERFS_SUPER_MAGIC"; + case 0x73636673: + return "SECURITYFS_MAGIC"; + case 0xf97cff8c: + return "SELINUX_MAGIC"; + case 0x43415d53: + return "SMACK_MAGIC"; + case 0x534f434b: + return "SOCKFS_MAGIC"; + case 0x73717368: + return "SQUASHFS_MAGIC"; + case 0x62656572: + return "SYSFS_MAGIC"; + case 0x012ff7b6: + return "SYSV2_SUPER_MAGIC"; + case 0x012ff7b5: + return "SYSV4_SUPER_MAGIC"; + case 0x01021994: + return "TMPFS_MAGIC"; + case 0x74726163: + return "TRACEFS_MAGIC"; + case 0x15013346: + return "UDF_SUPER_MAGIC"; + case 0x00011954: + return "UFS_MAGIC"; + case 0x01021997: + return "V9FS_MAGIC"; + case 0xa501fcf5: + return "VXFS_SUPER_MAGIC"; + case 0xabba1974: + return "XENFS_SUPER_MAGIC"; + case 0x012ff7b4: + return "XENIX_SUPER_MAGIC"; + case 0x58465342: + return "XFS_SUPER_MAGIC"; + case 0x012fd16d: + return "_XIAFS_SUPER_MAGIC"; + default: + break; + } + return "UNKNOWN"; +} + +void ShowIt(const char *path) { + struct statfs sf = {0}; + CHECK_NE(-1, statfs(path, &sf)); + + printf("filesystem %s\n", path); + printf("f_type = %#x (%s)\n", sf.f_type, DescribeStatfsType(sf.f_type)); + printf("f_bsize = %'zu (%s)\n", sf.f_bsize, "optimal transfer block size"); + printf("f_blocks = %'zu (%s)\n", sf.f_blocks, + "total data blocks in filesystem"); + printf("f_bfree = %'zu (%s)\n", sf.f_bfree, "free blocks in filesystem"); + printf("f_bavail = %'zu (%s)\n", sf.f_bavail, "free blocks available to"); + printf("f_files = %'zu (%s)\n", sf.f_files, + "total file nodes in filesystem"); + printf("f_ffree = %'zu (%s)\n", sf.f_ffree, + "free file nodes in filesystem"); + printf("f_fsid = %d:%d (%s)\n", (sf.f_fsid & 0xffff0000) >> 020, + (sf.f_fsid & 0x0000ffff) >> 000, "filesystem id"); + printf("f_namelen = %'zu (%s)\n", sf.f_namelen, + "maximum length of filenames"); + printf("f_frsize = %'zu (%s)\n", sf.f_frsize, "fragment size"); + + printf("f_flags = %#x", sf.f_flags); + if (sf.f_flags & 1) printf(" ST_RDONLY"); + if (sf.f_flags & 2) printf(" ST_NOSUID"); + if (sf.f_flags & 4) printf(" ST_NODEV"); + if (sf.f_flags & 8) printf(" ST_NOEXEC"); + if (sf.f_flags & 16) printf(" ST_SYNCHRONOUS"); + if (sf.f_flags & 64) printf(" ST_MANDLOCK"); + if (sf.f_flags & 128) printf(" ST_WRITE"); + if (sf.f_flags & 256) printf(" ST_APPEND"); + if (sf.f_flags & 512) printf(" ST_IMMUTABLE"); + if (sf.f_flags & 1024) printf(" ST_NOATIME"); + if (sf.f_flags & 2048) printf(" ST_NODIRATIME"); + if (sf.f_flags & 4096) printf(" ST_RELATIME"); + printf("\n"); + + printf("\n"); +} + +int main(int argc, char *argv[]) { + if (!IsLinux()) return 1; + ShowIt("/"); +} diff --git a/libc/calls/g_sighandrvas.c b/libc/calls/g_sighandrvas.c index bed767e56..51020cbe1 100644 --- a/libc/calls/g_sighandrvas.c +++ b/libc/calls/g_sighandrvas.c @@ -19,17 +19,5 @@ #include "libc/calls/state.internal.h" #include "libc/intrin/pthread.h" -unsigned __sighandrvas[NSIG]; -unsigned __sighandflags[NSIG]; - -static pthread_mutex_t __sig_lock_obj; - -void(__sig_lock)(void) { - __sig_lock_obj.attr = PTHREAD_MUTEX_RECURSIVE; - pthread_mutex_lock(&__sig_lock_obj); -} - -void(__sig_unlock)(void) { - __sig_lock_obj.attr = PTHREAD_MUTEX_RECURSIVE; - pthread_mutex_unlock(&__sig_lock_obj); -} +_Thread_local unsigned __sighandrvas[NSIG]; +_Thread_local unsigned __sighandflags[NSIG]; diff --git a/libc/calls/pledge.c b/libc/calls/pledge.c index e3c93e7c6..1968a28b1 100644 --- a/libc/calls/pledge.c +++ b/libc/calls/pledge.c @@ -23,18 +23,27 @@ #include "libc/calls/struct/bpf.h" #include "libc/calls/struct/filter.h" #include "libc/calls/struct/seccomp.h" +#include "libc/calls/struct/sigaction.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/promises.internal.h" +#include "libc/intrin/spinlock.h" #include "libc/limits.h" #include "libc/macros.internal.h" +#include "libc/nexgen32e/bsr.h" +#include "libc/nexgen32e/threaded.h" #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" #include "libc/str/str.h" #include "libc/sysv/consts/audit.h" +#include "libc/sysv/consts/kern.h" #include "libc/sysv/consts/nrlinux.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/pr.h" #include "libc/sysv/consts/prot.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #define SPECIAL 0xf000 @@ -63,6 +72,13 @@ #define PLEDGE(pledge) pledge, ARRAYLEN(pledge) +#define AbortPledge(reason) \ + do { \ + assert(!reason); \ + asm("hlt"); \ + unreachable; \ + } while (0) + struct Filter { size_t n; struct sock_filter p[700]; @@ -79,6 +95,7 @@ static const uint16_t kPledgeLinuxDefault[] = { // difference in the latency of sched_yield() if it's at the start of // the bpf script or the end. static const uint16_t kPledgeLinuxStdio[] = { + __NR_linux_sigreturn, // __NR_linux_exit_group, // __NR_linux_sched_yield, // __NR_linux_sched_getaffinity, // @@ -175,7 +192,6 @@ static const uint16_t kPledgeLinuxStdio[] = { __NR_linux_sigaltstack, // __NR_linux_sigprocmask, // __NR_linux_sigsuspend, // - __NR_linux_sigreturn, // __NR_linux_sigpending, // __NR_linux_socketpair, // __NR_linux_getrusage, // @@ -432,7 +448,7 @@ 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) + // forbid some system calls with ENOSYS (rather than EPERM) BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, __NR_linux_memfd_secret, 5, 0), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_rseq, 4, 0), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_memfd_create, 3, 0), @@ -442,15 +458,91 @@ static const struct sock_filter kFilterStart[] = { BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (38 & SECCOMP_RET_DATA)), }; -static const struct sock_filter kFilterEnd[] = { - // if syscall isn't whitelisted then have it return -EPERM (-1) +static const struct sock_filter kFilterIgnoreExitGroup[] = { + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_exit_group, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)), }; +static void Log(const char *s, ...) { + va_list va; + va_start(va, s); + do { + write(2, s, strlen(s)); + } while ((s = va_arg(va, const char *))); + va_end(va); +} + +static bool HasSyscall(struct Pledges *p, uint16_t n) { + int i; + for (i = 0; i < p->len; ++i) { + if ((p->syscalls[i] & 0x0fff) == n) { + return true; + } + } + return false; +} + +static char *FixCpy(char p[17], uint64_t x, uint8_t k) { + while (k > 0) *p++ = "0123456789abcdef"[(x >> (k -= 4)) & 15]; + *p = '\0'; + return p; +} + +static char *HexCpy(char p[17], uint64_t x) { + return FixCpy(p, x, ROUNDUP(x ? bsrl(x) + 1 : 1, 4)); +} + +static void OnSigSys(int sig, siginfo_t *si, ucontext_t *ctx) { + int i; + bool found; + char ord[17], rip[17]; + struct sigaction dfl = {.sa_sigaction = SIG_DFL}; + ctx->uc_mcontext.rax = -si->si_errno; + FixCpy(ord, si->si_syscall, 12); + HexCpy(rip, ctx->uc_mcontext.rip); + for (found = i = 0; i < ARRAYLEN(kPledgeLinux); ++i) { + if (HasSyscall(kPledgeLinux + i, si->si_syscall)) { + Log("error: has not pledged ", kPledgeLinux[i].name, // + " (ord=", ord, " rip=", rip, ")\n", 0); + found = true; + break; + } + } + if (!found) { + Log("error: unsupported syscall (ord=", ord, " rip=", rip, ")\n", 0); + } + switch (__pledge_mode) { + case SECCOMP_RET_KILL_PROCESS: + if (!sigaction(SIGABRT, &dfl, 0)) { + sys_kill(getpid(), SIGABRT, 1); + } + _Exit(128 + SIGABRT); + case SECCOMP_RET_KILL_THREAD: + if (!sigaction(SIGABRT, &dfl, 0)) { + sys_tgkill(getpid(), gettid(), SIGABRT); + } + _Exit1(128 + SIGABRT); + default: + break; + } +} + +static void MonitorSigSys(void) { + static _Thread_local bool once; + if (once) return; + once = true; + struct sigaction sa = { + .sa_sigaction = OnSigSys, + .sa_flags = SA_SIGINFO | SA_RESTART, + }; + if (sigaction(SIGSYS, &sa, 0)) { + AbortPledge("sigaction failed"); + } +} + static void AppendFilter(struct Filter *f, struct sock_filter *p, size_t n) { if (UNLIKELY(f->n + n > ARRAYLEN(f->p))) { - asm("hlt"); // need to increase array size - unreachable; + AbortPledge("need to increase array size"); } memcpy(f->p + f->n, p, n * sizeof(*f->p)); f->n += n; @@ -1093,20 +1185,22 @@ static void AllowSocketUnix(struct Filter *f) { // - PR_SET_SECCOMP (22) // - PR_SET_NO_NEW_PRIVS (38) // - PR_CAPBSET_READ (23) +// - PR_CAPBSET_DROP (24) // static void AllowPrctlStdio(struct Filter *f) { static const struct sock_filter fragment[] = { - /*L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_prctl, 0, 10 - 1), - /*L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[0])), - /*L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 15, 5, 0), - /*L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 16, 4, 0), - /*L4*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 21, 3, 0), - /*L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 22, 2, 0), - /*L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 23, 1, 0), - /*L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 38, 0, 1), - /*L7*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), - /*L8*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), - /*L9*/ /* next filter */ + /* L0*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_prctl, 0, 11 - 1), + /* L1*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[0])), + /* L2*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 15, 6, 0), + /* L3*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 16, 5, 0), + /* L4*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 21, 4, 0), + /* L5*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 22, 3, 0), + /* L6*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 23, 2, 0), + /* L7*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 24, 1, 0), + /* L8*/ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 38, 0, 1), + /* L9*/ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + /*L10*/ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)), + /*L11*/ /* next filter */ }; AppendFilter(f, PLEDGE(fragment)); } @@ -1220,8 +1314,7 @@ static void AppendPledge(struct Filter *f, const uint16_t *p, size_t len) { }; AppendFilter(f, PLEDGE(fragment)); } else { - asm("hlt"); // list of ordinals exceeds max displacement - unreachable; + AbortPledge("list of ordinals exceeds max displacement"); } } @@ -1311,8 +1404,7 @@ static void AppendPledge(struct Filter *f, const uint16_t *p, size_t len) { AllowPrlimitStdio(f); break; default: - asm("hlt"); // switch forgot to define a special ordinal - unreachable; + AbortPledge("switch forgot to define a special ordinal"); } } } @@ -1322,7 +1414,14 @@ int sys_pledge_linux(unsigned long ipromises) { struct Filter f; CheckLargeStackAllocation(&f, sizeof(f)); f.n = 0; + + // set up the seccomp filter AppendFilter(&f, PLEDGE(kFilterStart)); + if (ipromises == -1) { + // if we're pledging empty string, then avoid triggering a sigsys + // when _Exit() gets called since we need to fallback to _Exit1() + AppendFilter(&f, PLEDGE(kFilterIgnoreExitGroup)); + } if (!(~ipromises & (1ul << PROMISE_EXEC))) { AppendOriginVerification(&f); } @@ -1332,7 +1431,30 @@ int sys_pledge_linux(unsigned long ipromises) { AppendPledge(&f, kPledgeLinux[i].syscalls, kPledgeLinux[i].len); } } - AppendFilter(&f, PLEDGE(kFilterEnd)); + + // now determine the default seccomp action + // the __pledge_mode global could be set to + // - SECCOMP_RET_KILL + // - SECCOMP_RET_KILL_THREAD + // - SECCOMP_RET_KILL_PROCESS + // - SECCOMP_RET_ERRNO | EPERM + struct sock_filter filter[1] = {BPF_STMT(BPF_RET | BPF_K, 0)}; + if (~ipromises & (1ul << PROMISE_EXEC)) { + // our sigsys error message handler can't be inherited across + // execve() boundaries so if you've pledged exec then that'll + // mean no error messages for you. + filter[0].k = __pledge_mode; + AppendFilter(&f, PLEDGE(filter)); + } else { + // if we haven't pledged exec, then we can monitor SIGSYS + // and print a helpful error message when things do break + // the handler then decides what to do with __pledge_mode + MonitorSigSys(); + filter[0].k = SECCOMP_RET_TRAP | EPERM; + AppendFilter(&f, PLEDGE(filter)); + } + + // register our seccomp filter with the kernel if ((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); @@ -1388,11 +1510,13 @@ int ParsePromises(const char *promises, unsigned long *out) { * * Pledging causes most system calls to become unavailable. Your system * call policy is enforced by the kernel, which means it can propagate - * across execve() if permitted. This system call is supported on - * OpenBSD and Linux where it's polyfilled using SECCOMP BPF. The way it - * works on Linux is verboten system calls will raise EPERM whereas - * OpenBSD just kills the process while logging a helpful message to - * /var/log/messages explaining which promise category you needed. + * across execve() if permitted. Root is not required. This system call + * is supported on OpenBSD and Linux where it's polyfilled using SECCOMP + * BPF. The way it works on Linux is, if a forbidden system call is used + * then the kernel will will the process. On OpenBSD, a helpful message + * explaining which promise is needed should be emitted to your system + * log. On Linux, we log that to stderr with one exception: reporting is + * currently not possible if you pledge exec. * * Timing is everything with pledge. For example, if you're using * threads, then you may want to enable them explicitly *before* calling @@ -1548,6 +1672,7 @@ int ParsePromises(const char *promises, unsigned long *out) { * @raise ENOSYS if host os isn't Linux or OpenBSD * @raise EINVAL if `execpromises` on Linux isn't a subset of `promises` * @raise EINVAL if `promises` allows exec and `execpromises` is null + * @threadsafe */ int pledge(const char *promises, const char *execpromises) { int rc; diff --git a/libc/calls/sig2.c b/libc/calls/sig2.c index f25858a7e..47b009644 100644 --- a/libc/calls/sig2.c +++ b/libc/calls/sig2.c @@ -99,7 +99,6 @@ static bool __sig_deliver(bool restartable, int sig, int si_code, STRACE("delivering %G", sig); // enter the signal - __sig_lock(); rva = __sighandrvas[sig]; flags = __sighandflags[sig]; if ((~flags & SA_NODEFER) || (flags & SA_RESETHAND)) { @@ -110,7 +109,6 @@ static bool __sig_deliver(bool restartable, int sig, int si_code, // signal handler. in that case you must use SA_NODEFER. __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; } - __sig_unlock(); // setup the somewhat expensive information args // only if they're requested by the user in sigaction() diff --git a/libc/calls/sigaction.c b/libc/calls/sigaction.c index 4f8afaabc..096652a58 100644 --- a/libc/calls/sigaction.c +++ b/libc/calls/sigaction.c @@ -37,7 +37,6 @@ #include "libc/dce.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" -#include "libc/intrin/spinlock.h" #include "libc/limits.h" #include "libc/log/backtrace.internal.h" #include "libc/log/log.h" @@ -245,7 +244,7 @@ static int __sigaction(int sig, const struct sigaction *act, } /** - * Installs handler for kernel interrupt, e.g.: + * Installs handler for kernel interrupt to thread, e.g.: * * void GotCtrlC(int sig, siginfo_t *si, ucontext_t *ctx); * struct sigaction sa = {.sa_sigaction = GotCtrlC, @@ -445,6 +444,7 @@ static int __sigaction(int sig, const struct sigaction *act, * @return 0 on success or -1 w/ errno * @see xsigaction() for a much better api * @asyncsignalsafe + * @threadsafe * @vforksafe */ int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) { @@ -452,9 +452,7 @@ int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) { if (sig == SIGKILL || sig == SIGSTOP) { rc = einval(); } else { - __sig_lock(); rc = __sigaction(sig, act, oldact); - __sig_unlock(); } STRACE("sigaction(%G, %s, [%s]) → %d% m", sig, DescribeSigaction(0, act), DescribeSigaction(rc, oldact), rc); diff --git a/libc/calls/siglock.c b/libc/calls/siglock.c new file mode 100644 index 000000000..20f5a5611 --- /dev/null +++ b/libc/calls/siglock.c @@ -0,0 +1,32 @@ +/*-*- 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/state.internal.h" +#include "libc/intrin/pthread.h" + +static pthread_mutex_t __sig_lock_obj; + +void(__sig_lock)(void) { + __sig_lock_obj.attr = PTHREAD_MUTEX_RECURSIVE; + pthread_mutex_lock(&__sig_lock_obj); +} + +void(__sig_unlock)(void) { + __sig_lock_obj.attr = PTHREAD_MUTEX_RECURSIVE; + pthread_mutex_unlock(&__sig_lock_obj); +} diff --git a/libc/calls/state.internal.h b/libc/calls/state.internal.h index 0759ad91c..955d83b78 100644 --- a/libc/calls/state.internal.h +++ b/libc/calls/state.internal.h @@ -7,8 +7,8 @@ COSMOPOLITAN_C_START_ hidden extern int __vforked; hidden extern bool __time_critical; -hidden extern unsigned __sighandrvas[NSIG]; -hidden extern unsigned __sighandflags[NSIG]; +hidden _Thread_local extern unsigned __sighandrvas[NSIG]; +hidden _Thread_local extern unsigned __sighandflags[NSIG]; hidden extern const struct NtSecurityAttributes kNtIsInheritable; void __fds_lock(void); diff --git a/libc/calls/struct/statfs.h b/libc/calls/struct/statfs.h new file mode 100644 index 000000000..223ed26a1 --- /dev/null +++ b/libc/calls/struct/statfs.h @@ -0,0 +1,26 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_STATFS_H_ +#define COSMOPOLITAN_LIBC_CALLS_STRUCT_STATFS_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +struct statfs { + int64_t f_type; /* type of filesystem */ + int64_t f_bsize; /* optimal transfer block size */ + int64_t f_blocks; /* total data blocks in filesystem */ + int64_t f_bfree; /* free blocks in filesystem */ + int64_t f_bavail; /* free blocks available to */ + int64_t f_files; /* total file nodes in filesystem */ + int64_t f_ffree; /* free file nodes in filesystem */ + int64_t f_fsid; /* filesystem id */ + int64_t f_namelen; /* maximum length of filenames */ + int64_t f_frsize; /* fragment size */ + int64_t f_flags; /* mount flags of filesystem 2.6.36 */ + int64_t f_spare[4]; +}; + +int statfs(const char *, struct statfs *); +int fstatfs(int, struct statfs *); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_STATFS_H_ */ diff --git a/libc/calls/syscall-sysv.internal.h b/libc/calls/syscall-sysv.internal.h index 771613f61..186f35b60 100644 --- a/libc/calls/syscall-sysv.internal.h +++ b/libc/calls/syscall-sysv.internal.h @@ -68,7 +68,6 @@ i32 sys_mincore(void *, u64, unsigned char *) hidden; i32 sys_mkdirat(i32, const char *, u32) hidden; i32 sys_mkfifo(const char *, u32) hidden; i32 sys_mknod(const char *, u32, u64) hidden; -i32 sys_unmount(const char *, i32) hidden; i32 sys_mprotect(void *, u64, i32) hidden; i32 sys_msync(void *, u64, i32) hidden; i32 sys_munmap(void *, u64) hidden; @@ -97,11 +96,13 @@ i32 sys_sigaltstack(const void *, void *) hidden; i32 sys_symlinkat(const char *, i32, const char *) hidden; i32 sys_sync(void) hidden; i32 sys_sync_file_range(i32, i64, i64, u32) hidden; +i32 sys_syslog(i32, char *, i32) hidden; i32 sys_tgkill(i32, i32, i32) hidden; i32 sys_tkill(i32, i32, void *) hidden; i32 sys_truncate(const char *, u64, u64) hidden; i32 sys_uname(void *) hidden; i32 sys_unlinkat(i32, const char *, i32) hidden; +i32 sys_unmount(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; diff --git a/libc/intrin/promises.c b/libc/intrin/promises.c index 210dc8119..6707e9997 100644 --- a/libc/intrin/promises.c +++ b/libc/intrin/promises.c @@ -19,5 +19,6 @@ #include "libc/intrin/promises.internal.h" // XXX: should be inherited thread local +unsigned __pledge_mode; unsigned long __promises; unsigned long __execpromises; diff --git a/libc/log/getsicodename.c b/libc/log/getsicodename.c index c36ee2c12..493dbd9a6 100644 --- a/libc/log/getsicodename.c +++ b/libc/log/getsicodename.c @@ -158,6 +158,11 @@ const char *GetSiCodeName(int sig, int si_code) { } else if (si_code == POLL_HUP) { strcpy(b + 5, "HUP"); /* device disconnected */ } + } else if (sig == SIGSYS) { + NameIt(b, "SYS_", si_code); + if (si_code == SYS_SECCOMP) { + strcpy(b + 4, "SECCOMP"); + } } return b; } diff --git a/libc/log/oncrashthunks.S b/libc/log/oncrashthunks.S index 02e848af6..488c765b8 100644 --- a/libc/log/oncrashthunks.S +++ b/libc/log/oncrashthunks.S @@ -90,13 +90,13 @@ __oncrash_sigbus: .endfn __oncrash_sigbus,globl .org 11*7 -__oncrash_sigsys: +__oncrash_sigurg: push %rbp mov %rsp,%rbp call __oncrash pop %rbp ret - .endfn __oncrash_sigsys,globl + .endfn __oncrash_sigurg,globl // : showcrashreports.c, oncrashthunks.S, oncrash.c, internal.h diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c index 0351d8ce8..416e7bc04 100644 --- a/libc/log/showcrashreports.c +++ b/libc/log/showcrashreports.c @@ -111,7 +111,7 @@ void ShowCrashReports(void) { kCrashSigs[4] = SIGTRAP; /* bad system call */ kCrashSigs[5] = SIGABRT; /* abort() called */ kCrashSigs[6] = SIGBUS; /* misaligned, noncanonical ptr, etc. */ - kCrashSigs[7] = SIGSYS; /* bad system call */ + kCrashSigs[7] = SIGURG; /* placeholder */ /* : showcrashreports.c, oncrashthunks.S, oncrash.c */ if (!IsWindows()) { bzero(&ss, sizeof(ss)); diff --git a/libc/runtime/fork-nt.c b/libc/runtime/fork-nt.c index 8dbe9398b..24d96c3d6 100644 --- a/libc/runtime/fork-nt.c +++ b/libc/runtime/fork-nt.c @@ -55,10 +55,6 @@ STATIC_YOINK("_check_sigchld"); extern int64_t __wincrashearly; -extern unsigned char __data_start[]; /* αpε */ -extern unsigned char __data_end[]; /* αpε */ -extern unsigned char __bss_start[]; /* αpε */ -extern unsigned char __bss_end[]; /* αpε */ bool32 __onntconsoleevent_nt(uint32_t); static textwindows wontreturn void AbortFork(const char *func) { diff --git a/libc/runtime/runtime.h b/libc/runtime/runtime.h index 94f4f60c4..0159269b7 100644 --- a/libc/runtime/runtime.h +++ b/libc/runtime/runtime.h @@ -17,6 +17,7 @@ extern intptr_t __oldstack; /* CRT */ extern uint64_t __nosync; /* SYS */ extern _Atomic(int) __ftrace; /* SYS */ extern _Atomic(int) __strace; /* SYS */ +extern uint32_t __pledge_mode; /* SYS */ extern char *program_invocation_name; /* RII */ extern char *program_invocation_short_name; /* RII */ extern uint64_t __syscount; /* RII */ @@ -41,6 +42,10 @@ extern unsigned char *__relo_start[]; /* αpε */ extern unsigned char *__relo_end[]; /* αpε */ extern uint8_t __zip_start[]; /* αpε */ extern uint8_t __zip_end[]; /* αpε */ +extern uint8_t __data_start[]; /* αpε */ +extern uint8_t __data_end[]; /* αpε */ +extern uint8_t __bss_start[]; /* αpε */ +extern uint8_t __bss_end[]; /* αpε */ extern size_t __virtualmax; extern bool __isworker; diff --git a/libc/sysv/calls/sys_syslog.s b/libc/sysv/calls/sys_syslog.s new file mode 100644 index 000000000..8919eb2d5 --- /dev/null +++ b/libc/sysv/calls/sys_syslog.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.internal.inc" +.scall sys_syslog,0xfffffffffffff067,globl,hidden diff --git a/libc/sysv/calls/syslog.s b/libc/sysv/calls/syslog.s deleted file mode 100644 index 812e0ba59..000000000 --- a/libc/sysv/calls/syslog.s +++ /dev/null @@ -1,2 +0,0 @@ -.include "o/libc/sysv/macros.internal.inc" -.scall syslog,0xfffffffffffff067,globl diff --git a/libc/sysv/consts/kern.h b/libc/sysv/consts/kern.h new file mode 100644 index 000000000..b77e3d3f5 --- /dev/null +++ b/libc/sysv/consts/kern.h @@ -0,0 +1,21 @@ +#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_KERN_H_ +#define COSMOPOLITAN_LIBC_SYSV_CONSTS_KERN_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +#define KERN_SOH "\001" +#define KERN_SOH_ASCII '\001' +#define KERN_EMERG KERN_SOH "0" +#define KERN_ALERT KERN_SOH "1" +#define KERN_CRIT KERN_SOH "2" +#define KERN_ERR KERN_SOH "3" +#define KERN_WARNING KERN_SOH "4" +#define KERN_NOTICE KERN_SOH "5" +#define KERN_INFO KERN_SOH "6" +#define KERN_DEBUG KERN_SOH "7" +#define KERN_DEFAULT "" +#define KERN_CONT KERN_SOH "c" + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_KERN_H_ */ diff --git a/libc/sysv/consts/loglevel.h b/libc/sysv/consts/loglevel.h new file mode 100644 index 000000000..d0fa3e91f --- /dev/null +++ b/libc/sysv/consts/loglevel.h @@ -0,0 +1,19 @@ +#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_LOGLEVEL_H_ +#define COSMOPOLITAN_LIBC_SYSV_CONSTS_LOGLEVEL_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +#define LOGLEVEL_SCHED -2 +#define LOGLEVEL_DEFAULT -1 +#define LOGLEVEL_EMERG 0 +#define LOGLEVEL_ALERT 1 +#define LOGLEVEL_CRIT 2 +#define LOGLEVEL_ERR 3 +#define LOGLEVEL_WARNING 4 +#define LOGLEVEL_NOTICE 5 +#define LOGLEVEL_INFO 6 +#define LOGLEVEL_DEBUG 7 + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_LOGLEVEL_H_ */ diff --git a/libc/sysv/restorert.S b/libc/sysv/restorert.S index 42a16d079..19ed577bf 100644 --- a/libc/sysv/restorert.S +++ b/libc/sysv/restorert.S @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/sysv/consts/nrlinux.h" #include "libc/macros.internal.h" .privileged @@ -25,8 +26,8 @@ __restore_bt: .endfn __restore_bt,globl,hidden nop # gap so that __get_symbol(st, addr - 1) fails .align 16 -__restore_rt: # @see gdb/amd64-linux-tdep.c - mov $0x000f,%rax # [sic] +__restore_rt: # @see gdb/amd64-linux-tdep.c + mov $__NR_linux_sigreturn,%rax # [sic] syscall .align 16 .endfn __restore_rt,globl,hidden diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh index 55f7ee433..879330f0a 100755 --- a/libc/sysv/syscalls.sh +++ b/libc/sysv/syscalls.sh @@ -140,7 +140,7 @@ scall __sys_getrusage 0x1bd0130752075062 globl hidden scall sys_sysinfo 0xfffffffffffff063 globl hidden scall sys_times 0xfffffffffffff064 globl hidden scall sys_ptrace 0x01a01a01a201a065 globl hidden -scall syslog 0xfffffffffffff067 globl +scall sys_syslog 0xfffffffffffff067 globl hidden scall sys_getuid 0x0180180182018066 globl hidden scall sys_getgid 0x02f02f02f202f068 globl hidden scall sys_getppid 0xfff027027202706e globl hidden # see sys_getpid()→edx for netbsd diff --git a/test/libc/calls/pledge2_test.c b/test/libc/calls/pledge2_test.c new file mode 100644 index 000000000..cb70a8f0a --- /dev/null +++ b/test/libc/calls/pledge2_test.c @@ -0,0 +1,134 @@ +/*-*- 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/seccomp.h" +#include "libc/calls/syscall_support-sysv.internal.h" +#include "libc/dce.h" +#include "libc/intrin/kprintf.h" +#include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/ipproto.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/sock.h" +#include "libc/testlib/testlib.h" + +#define SPAWN(METHOD) \ + { \ + int ws, pid; \ + ASSERT_NE(-1, (pid = METHOD())); \ + if (!pid) { + +#define EXITS(rc) \ + _Exit(0); \ + } \ + ASSERT_NE(-1, wait(&ws)); \ + ASSERT_TRUE(WIFEXITED(ws)); \ + ASSERT_EQ(rc, WEXITSTATUS(ws)); \ + } + +#define TERMS(sig) \ + _Exit(0); \ + } \ + ASSERT_NE(-1, wait(&ws)); \ + ASSERT_TRUE(WIFSIGNALED(ws)); \ + ASSERT_EQ(sig, WTERMSIG(ws)); \ + } + +void SetUp(void) { + if (!__is_linux_2_6_23() && !IsOpenbsd()) exit(0); +} + +TEST(pledge, testSoftError) { + if (IsOpenbsd()) return; + SPAWN(fork); + __pledge_mode = SECCOMP_RET_ERRNO | EPERM; + ASSERT_SYS(0, 0, pledge("stdio", 0)); + ASSERT_SYS(EPERM, -1, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + _Exit(7); + EXITS(7); +} + +TEST(pledge, testKillThreadMode) { + SPAWN(fork); + __pledge_mode = SECCOMP_RET_KILL_THREAD; + ASSERT_SYS(0, 0, pledge("stdio", 0)); + socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + TERMS(IsOpenbsd() ? SIGABRT : SIGSYS); +} + +TEST(pledge, testKillProcessMode) { + SPAWN(fork); + __pledge_mode = SECCOMP_RET_KILL_PROCESS; + ASSERT_SYS(0, 0, pledge("stdio", 0)); + socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + TERMS(IsOpenbsd() ? SIGABRT : SIGSYS); +} + +TEST(pledge, testLogMessage_onSoftyMode) { + if (IsOpenbsd()) return; + int fds[2]; + char msg[64] = {0}; + ASSERT_SYS(0, 0, pipe(fds)); + SPAWN(fork); + __pledge_mode = SECCOMP_RET_ERRNO | EPERM; + ASSERT_SYS(0, 2, dup2(fds[1], 2)); + ASSERT_SYS(0, 0, pledge("stdio", 0)); + ASSERT_SYS(EPERM, -1, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + EXITS(0); + close(fds[1]); + read(fds[0], msg, sizeof(msg)); + close(fds[0]); + if (IsLinux()) { + ASSERT_STARTSWITH("error: has not pledged inet", msg); + } +} + +TEST(pledge, testLogMessage_onKillProcess) { + int fds[2]; + char msg[64] = {0}; + ASSERT_SYS(0, 0, pipe(fds)); + SPAWN(fork); + __pledge_mode = SECCOMP_RET_KILL; + ASSERT_SYS(0, 2, dup2(fds[1], 2)); + ASSERT_SYS(0, 0, pledge("stdio", 0)); + socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + TERMS(IsOpenbsd() ? SIGABRT : SIGSYS); + close(fds[1]); + read(fds[0], msg, sizeof(msg)); + close(fds[0]); + if (IsLinux()) { + ASSERT_STARTSWITH("error: has not pledged inet", msg); + } +} + +TEST(pledge, testNoLogPossibleSadly_becausePledgedExec) { + int fds[2]; + char msg[64] = {0}; + ASSERT_SYS(0, 0, pipe(fds)); + SPAWN(fork); + ASSERT_SYS(0, 2, dup2(fds[1], 2)); + ASSERT_SYS(0, 0, pledge("stdio exec", "stdio exec")); + socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + TERMS(IsOpenbsd() ? SIGABRT : SIGSYS); + close(fds[1]); + read(fds[0], msg, sizeof(msg)); + close(fds[0]); + ASSERT_STREQ("", msg); +} diff --git a/test/libc/calls/pledge_test.c b/test/libc/calls/pledge_test.c index 06f8ab88c..4bf314caf 100644 --- a/test/libc/calls/pledge_test.c +++ b/test/libc/calls/pledge_test.c @@ -60,6 +60,10 @@ STATIC_YOINK("zip_uri_support"); char testlib_enable_tmp_setup_teardown; +__attribute__((__constructor__)) static void init(void) { + __pledge_mode = SECCOMP_RET_ERRNO | EPERM; +} + void OnSig(int sig) { // do nothing } @@ -108,6 +112,20 @@ TEST(pledge, default_allowsExit) { EXPECT_SYS(0, 0, munmap(job, FRAMESIZE)); } +TEST(pledge, execpromises_notok) { + if (IsOpenbsd()) return; // b/c testing linux bpf + int ws, pid; + ASSERT_NE(-1, (pid = fork())); + if (!pid) { + ASSERT_SYS(0, 0, pledge("stdio rpath exec", "stdio")); + execl("sock.elf", "sock.elf", 0); + _Exit(127); + } + EXPECT_NE(-1, wait(&ws)); + EXPECT_TRUE(WIFEXITED(ws)); + EXPECT_EQ(129, WEXITSTATUS(ws)); +} + int Enclave(void *arg, int tid) { ASSERT_SYS(0, 0, pledge("", 0)); int *job = arg; // get job @@ -478,7 +496,7 @@ TEST(pledge, execpromises_ok) { EXPECT_EQ(42, WEXITSTATUS(ws)); } -TEST(pledge, execpromises_notok) { +TEST(pledge, execpromises_notok1) { if (IsOpenbsd()) return; // b/c testing linux bpf int ws, pid; ASSERT_NE(-1, (pid = fork())); diff --git a/test/libc/calls/unveil_test.c b/test/libc/calls/unveil_test.c index b13468127..8970a9a19 100644 --- a/test/libc/calls/unveil_test.c +++ b/test/libc/calls/unveil_test.c @@ -367,22 +367,13 @@ TEST(unveil, usedTwice_forbidden_worksWithPledge) { ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY)); // verify the first filter is still working *gotsome = true; - ASSERT_SYS(EPERM, -1, socket(AF_UNIX, SOCK_STREAM, 0)); - if (IsLinux()) { - ASSERT_SYS(0, 0, stat("garden/secret.txt", &st)); - ASSERT_EQ(5, st.st_size); // wut linux metadata is accessible - } + socket(AF_UNIX, SOCK_STREAM, 0); _Exit(0); } ASSERT_NE(-1, wait(&ws)); ASSERT_TRUE(*gotsome); - if (IsOpenbsd()) { - ASSERT_TRUE(WIFSIGNALED(ws)); - ASSERT_EQ(SIGABRT, WTERMSIG(ws)); - } else { - ASSERT_TRUE(WIFEXITED(ws)); - ASSERT_EQ(0, WEXITSTATUS(ws)); - } + ASSERT_TRUE(WIFSIGNALED(ws)); + ASSERT_EQ(IsOpenbsd() ? SIGABRT : SIGSYS, WTERMSIG(ws)); EXPECT_SYS(0, 0, munmap(gotsome, FRAMESIZE)); } diff --git a/test/libc/runtime/tls_test.c b/test/libc/runtime/tls_test.c index d752ee2f1..ae21ec0a0 100644 --- a/test/libc/runtime/tls_test.c +++ b/test/libc/runtime/tls_test.c @@ -16,6 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/kprintf.h" +#include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/testlib/testlib.h" @@ -23,6 +26,21 @@ _Thread_local int x; _Thread_local int y = 40; int z = 2; +void PrintInfo(void) { + kprintf("_tdata_size = %d\n", _tdata_size); + kprintf("_tls_size = %d\n", _tls_size); + kprintf("_tls_content = %d\n", _tls_content); + kprintf("__data_start = %p\n", __data_start); + kprintf("__data_end = %p\n", __data_end); + kprintf("_tdata_start = %p\n", _tdata_start); + kprintf("_tdata_end = %p\n", _tdata_end); + kprintf("_tbss_start = %p\n", _tbss_start); + kprintf("_tbss_end = %p\n", _tbss_end); + kprintf("&y = %p\n", &y); + kprintf("__bss_start = %p\n", __bss_start); + kprintf("__bss_end = %p\n", __bss_end); +} + TEST(tls, test) { EXPECT_EQ(42, x + y + z); } diff --git a/test/tool/net/lunix_test.lua b/test/tool/net/lunix_test.lua index 55b47de48..3f9531682 100644 --- a/test/tool/net/lunix_test.lua +++ b/test/tool/net/lunix_test.lua @@ -16,6 +16,10 @@ gotsigusr1 = false tmpdir = "o/tmp/lunix_test.%d" % {unix.getpid()} +function string.starts(String,Start) + return string.sub(String,1,string.len(Start))==Start +end + function OnSigUsr1(sig) gotsigusr1 = true end @@ -67,15 +71,18 @@ function UnixTest() -- 2. sandbox the process -- 3. then violate its security if GetHostOs() == "LINUX" then + reader, writer = assert(unix.pipe()) if assert(unix.fork()) == 0 then + assert(unix.dup(writer, 2)) assert(unix.pledge("stdio")) - _, err = unix.socket() - assert(err:errno() == unix.EPERM) + unix.socket() unix.exit(0) end + unix.close(writer) + unix.close(reader) pid, ws = assert(unix.wait()) - assert(unix.WIFEXITED(ws)) - assert(unix.WEXITSTATUS(ws) == 0) + assert(unix.WIFSIGNALED(ws)) + assert(unix.WTERMSIG(ws) == unix.SIGSYS) elseif GetHostOs() == "OPENBSD" then if assert(unix.fork()) == 0 then assert(unix.pledge("stdio")) diff --git a/third_party/lua/lunix.c b/third_party/lua/lunix.c index 5d5d26311..9fb852c25 100644 --- a/third_party/lua/lunix.c +++ b/third_party/lua/lunix.c @@ -1381,6 +1381,7 @@ static int LuaUnixSiocgifconf(lua_State *L) { // └─→ nil, unix.Errno static int LuaUnixPledge(lua_State *L) { int olderr = errno; + __pledge_mode = 0; return SysretBool(L, "pledge", olderr, pledge(luaL_checkstring(L, 1), luaL_optstring(L, 2, 0))); } diff --git a/tool/net/help.txt b/tool/net/help.txt index 2c6abe660..961d07c60 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -259,7 +259,7 @@ SECURITY -S (online policy) - This causes unix.pledge("stdio rpath inet dns") to be called on + This causes unix.pledge("stdio rpath inet dns id") to be called on workers after fork() is called. This permits read-only operations and APIs like Fetch() that let workers send and receive data with private and public Internet hosts. Access to the unix module is @@ -267,10 +267,12 @@ SECURITY -SS (offline policy) - This causes unix.pledge("stdio rpath") to be called on workers + This causes unix.pledge("stdio rpath id") to be called on workers after after fork() is called. This prevents workers from talking to the network (other than the client) and allows read-only file - system access (e.g. `-D DIR` flag). + system access (e.g. `-D DIR` flag). The `id` group helps you to + call other functions important to redbean security, such as the + unix.setrlimit() function. -SSS (contained policy) @@ -281,6 +283,11 @@ SECURITY should only be able to serve from its own zip file in this mode. Lua script access to the unix module is highly restricted. + Unlike the unix.pledge() function, these sandboxing flags use a more + permissive policy on Linux. Rather than killing the process, they'll + cause system calls to fail with EPERM instead. Therefore these flags + should be gentler when you want security errors to be recoverable. + See http://redbean.dev for further details. ──────────────────────────────────────────────────────────────────────────────── @@ -3834,8 +3841,11 @@ UNIX MODULE This can be used to sandbox your redbean workers. It allows finer customization compared to the `-S` flag. - Pledging causes most system calls to become unavailable. On Linux the - disabled calls will return EPERM whereas OpenBSD kills the process. + Pledging causes most system calls to become unavailable. If a + forbidden system call is used, then the process will be killed. In + that case, on OpenBSD, your system log will explain which promise + you need. On Linux, we report the promise to stderr, with one + exception: reporting is currently not possible if you pledge exec. Using pledge is irreversible. On Linux it causes PR_SET_NO_NEW_PRIVS to be set on your process. diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 36c5dd09b..1a52d6329 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -25,6 +25,7 @@ #include "libc/calls/struct/flock.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/rusage.h" +#include "libc/calls/struct/seccomp.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/stat.h" #include "libc/calls/struct/termios.h" @@ -32,6 +33,7 @@ #include "libc/dns/dns.h" #include "libc/dns/hoststxt.h" #include "libc/dos.h" +#include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/fmt/itoa.h" #include "libc/intrin/kprintf.h" @@ -6585,17 +6587,18 @@ static void UnveilRedbean(void) { } static int EnableSandbox(void) { + __pledge_mode = SECCOMP_RET_ERRNO | EPERM; switch (sandboxed) { case 0: return 0; case 1: // -S DEBUGF("(stat) applying '%s' sandbox policy", "online"); UnveilRedbean(); - return pledge("stdio rpath inet dns", 0); + return pledge("stdio rpath inet dns id", 0); case 2: // -SS DEBUGF("(stat) applying '%s' sandbox policy", "offline"); UnveilRedbean(); - return pledge("stdio rpath", 0); + return pledge("stdio rpath id", 0); default: // -SSS DEBUGF("(stat) applying '%s' sandbox policy", "contained"); UnveilRedbean();