diff options
-rw-r--r-- | doc/hacking.md | 22 | ||||
-rw-r--r-- | lib/chaseccomp/buffer.chasc | 2 | ||||
-rw-r--r-- | lib/chaseccomp/chaseccomp.c | 37 | ||||
-rw-r--r-- | lib/chaseccomp/common.chasc | 8 | ||||
-rwxr-xr-x | lib/chaseccomp/gen_defs | 6 | ||||
-rwxr-xr-x | lib/chaseccomp/gen_syscalls | 36 | ||||
-rw-r--r-- | lib/chaseccomp/network.chasc | 2 | ||||
-rw-r--r-- | src/utils/sandbox.nim | 3 |
8 files changed, 80 insertions, 36 deletions
diff --git a/doc/hacking.md b/doc/hacking.md index 99404064..12672fd1 100644 --- a/doc/hacking.md +++ b/doc/hacking.md @@ -212,18 +212,16 @@ Use `config.color.toml` and `my-test-case.color.expected` to preserve colors. ### Sandbox violations -First, note that a nil deref can also trigger a sandbox violation. Read -the stack trace to make sure you aren't dealing with that. - -Then, figure out if it's happening in a CGI process or a buffer -process. If your buffer was swapped out for the console, it's likely the -latter; otherwise, the former. - -Now change the appropriate sandbox handler from `SCMP_ACT_TRAP` to -`SCMP_ACT_KILL_PROCESS`. Run `strace -f ./cha -o start.console-buffer [...] 2>a`, -trigger the crash, then search for "killed by SIGSYS" in `a`. Copy the -logged PID, then search backwards once; you should now see the syscall -that got your process killed. +You got a syscall number (assuming you're on Linux); look it up in the +Linux syscall table for your architecture. + +To get more context on what happened, you can run +`strace -f ./cha -o start.console-buffer [...] 2>a`, trigger the crash, +then search for the last occurrence of "--- SIGSYS". Then search +backwards on the PID to see the last syscalls. + +(Incidentally, strace also shows the syscall name, so it may be easier +to check like that than looking it up in the syscall table.) ## Resources diff --git a/lib/chaseccomp/buffer.chasc b/lib/chaseccomp/buffer.chasc index 0a4aa555..0d14883f 100644 --- a/lib/chaseccomp/buffer.chasc +++ b/lib/chaseccomp/buffer.chasc @@ -33,6 +33,8 @@ ifeqdef SYS_sigreturn allow # called by signal trampoline : deny ret trap +: kill +ret kill : eperm ret errno EPERM : allow diff --git a/lib/chaseccomp/chaseccomp.c b/lib/chaseccomp/chaseccomp.c index 9001b973..29e39a6b 100644 --- a/lib/chaseccomp/chaseccomp.c +++ b/lib/chaseccomp/chaseccomp.c @@ -16,16 +16,35 @@ #include <sys/un.h> #include <fcntl.h> #include <stdint.h> +#include <signal.h> #include "chaseccomp.h" +static void sigsys_handler_buffer(int sig, siginfo_t *info, void *ucontext) +{ + fprintf(stderr, "Sandbox violation in buffer: syscall #%d\n", + info->si_syscall); + abort(); +} + int cha_enter_buffer_sandbox(void) { struct sock_filter filter[] = { #include "chasc_buffer.h" }; +#ifndef EXPECTED_COUNT +#error "network sandbox not built" +#elsif EXPECTED_COUNT != COUNTOF(filter) +#error "wrong network sandbox length" +#endif struct sock_fprog prog = { .len = COUNTOF(filter), .filter = filter }; + struct sigaction act = { + .sa_flags = SA_SIGINFO, + .sa_sigaction = sigsys_handler_buffer, + }; + if (sigaction(SIGSYS, &act, NULL) < 0) + return 0; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) return 0; if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) @@ -33,13 +52,31 @@ int cha_enter_buffer_sandbox(void) return 1; } +static void sigsys_handler_network(int sig, siginfo_t *info, void *ucontext) +{ + fprintf(stderr, "Sandbox violation in network: syscall #%d\n", + info->si_syscall); +} + int cha_enter_network_sandbox(void) { +#undef EXPECTED_COUNT struct sock_filter filter[] = { #include "chasc_network.h" }; +#ifndef EXPECTED_COUNT +#error "network sandbox not built" +#elsif EXPECTED_COUNT != COUNTOF(filter) +#error "wrong network sandbox length" +#endif struct sock_fprog prog = { .len = COUNTOF(filter), .filter = filter }; + struct sigaction act = { + .sa_flags = SA_SIGINFO, + .sa_sigaction = sigsys_handler_network, + }; + if (sigaction(SIGSYS, &act, NULL) < 0) + return 0; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) return 0; if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) diff --git a/lib/chaseccomp/common.chasc b/lib/chaseccomp/common.chasc index 37f886dc..15c40053 100644 --- a/lib/chaseccomp/common.chasc +++ b/lib/chaseccomp/common.chasc @@ -22,6 +22,7 @@ ifeqdef SYS_brk allow # less common socket/file i/o ifeqdef SYS_lseek allow ifeqdef SYS_close allow +ifeqdef SYS_shutdown allow # prevent glibc from getting our process murdered ifeqdef SYS_fstat eperm @@ -48,6 +49,13 @@ ifeqdef SYS_exit_group allow # for quit ifeqdef SYS_restart_syscall allow # for resuming poll on SIGCONT ifeqdef SYS_getrandom allow # glibc calls it when initializing its malloc +# so we don't trap on crash +ifeqdef SYS_sigaction eperm +ifeqdef SYS_rt_sigaction eperm +ifeqdef SYS_rt_sigprocmask eperm +ifeqdef SYS_gettid eperm +ifeqdef SYS_tgkill kill + # bionic-specific stuff ifdef __BIONIC__ ifeqdef SYS_rt_sigprocmap allow diff --git a/lib/chaseccomp/gen_defs b/lib/chaseccomp/gen_defs index f4932b71..6fdedac0 100755 --- a/lib/chaseccomp/gen_defs +++ b/lib/chaseccomp/gen_defs @@ -46,7 +46,7 @@ EOF f() { while read -r line - do case $line in + do case "$line" in 'include '*) <"${line#* }" f ;; *) printf '%s\n' "$line" ;; esac @@ -55,7 +55,9 @@ f() { f | while read -r line do line=${line%%#*} - case $line in + line=${line#"${line%%[![:space:]]*}"} + line=${line%"${line##*[![:space:]]}"} + case "$line" in '') ;; if??def' '*) inst=${line%%def*} diff --git a/lib/chaseccomp/gen_syscalls b/lib/chaseccomp/gen_syscalls index a4d7d5b5..401403d5 100755 --- a/lib/chaseccomp/gen_syscalls +++ b/lib/chaseccomp/gen_syscalls @@ -9,7 +9,7 @@ test "$1" || die "usage: gen_syscalls [file]" find_label() { printf '%s\n' "$labels" | while read -r line - do case $line in + do case "$line" in "$1 "*) printf '%d\n' "${line#* }" break ;; @@ -24,23 +24,19 @@ line_cut_next() { if test "$line" = "$oline"; then line= ; fi } -cut_label() { +put_cmp() { + line_cut_next + val=$next line_cut_next label=$(find_label "$next") test "$label" || die "missing label $next" label=$(($label - 1 - $ip)) -} - -put_cmp() { - line_cut_next - val=$next - cut_label - printf '\t%s(%s, %d),\n' "$inst" "$val" "$label" + printf '\t%s(%s, %d),\n' "$1" "$val" "$label" } put_load() { line_cut_next - printf '\t%s(%s),\n' "$inst" "$next" + printf '\tCHA_BPF_LOAD(%s),\n' "$next" } put_ret() { @@ -52,13 +48,13 @@ put_ret() { errno) val="SECCOMP_RET_ERRNO | ($line & SECCOMP_RET_DATA)" ;; *) die "wrong retval $line" ;; esac - printf '\t%s(%s),\n' "$inst" "$val" + printf '\tCHA_BPF_RET(%s),\n' "$val" } ip=0 while read -r line do line=${line%%#*} - case $line in + case "$line" in ''|define' '*) ;; ': '*) line_cut_next if test -n "$labels" @@ -76,13 +72,15 @@ while read -r line do line_cut_next case $next in :) continue ;; - define) printf '#%s %s\n' "$next $line" ; continue ;; - ret) inst=CHA_BPF_RET put_ret ;; - ifeq) inst=CHA_BPF_JE put_cmp ;; - ifne) inst=CHA_BPF_JNE put_cmp ;; - ifle) inst=CHA_BPF_JLE put_cmp ;; - load) inst=CHA_BPF_LOAD put_load ;; - *) die "unexpected instruction $inst" ;; + define) printf '#%s %s\n' "$next" "$line" ; continue ;; + ret) put_ret ;; + ifeq) put_cmp CHA_BPF_JE;; + ifne) put_cmp CHA_BPF_JNE;; + ifle) put_cmp CHA_BPF_JLE;; + load) put_load;; + *) die "unexpected instruction $next" ;; esac ip=$(($ip + 1)) done < "$1" + +printf '#define EXPECTED_COUNT %d\n' "$ip" diff --git a/lib/chaseccomp/network.chasc b/lib/chaseccomp/network.chasc index 8e7468d1..d6f86165 100644 --- a/lib/chaseccomp/network.chasc +++ b/lib/chaseccomp/network.chasc @@ -14,6 +14,8 @@ ifeqdef SYS_sendmsg allow ifeqdef SYS_getpid allow : deny +ret trap +: kill ret kill : eperm ret errno EPERM diff --git a/src/utils/sandbox.nim b/src/utils/sandbox.nim index d5c969de..b2e2c8b0 100644 --- a/src/utils/sandbox.nim +++ b/src/utils/sandbox.nim @@ -92,9 +92,6 @@ elif SandboxMode == stSeccomp: proc cha_enter_network_sandbox(): cint {.importc, cdecl.} proc enterBufferSandbox*(sockPath: string) = - onSignal SIGSYS: - discard sig - raise newException(Defect, "Sandbox violation in buffer") doAssert cha_enter_buffer_sandbox() == 1 proc enterNetworkSandbox*() = |