From 5e4ad0270bedf98da1d47e3e1306f7b66b6b29c7 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Mon, 25 Sep 2017 00:56:33 +0200 Subject: [PATCH] seccomp: Log seccomp violations with utrace Older kernel version shipped by LEDE/OpenWrt contained patch target/linux/generic/patches-3.18/999-seccomp_log.patch that logged seccomp violations. For some reason, newer kernels do not have this patch. Without this kind of logging, it is very hard to setup seccomp whitelist properly, so this commit modifies utrace to serve as a logger for seccomp violations. With this patch, when utrace is executed via seccomp-trace symlink, it does not trace normal syscalls but only seccomp violations and logs them to syslog. For example: seccomp-trace: uci[3955] tried to call non-whitelisted syscall: ftruncate64 (see /etc/seccomp/myservice.json) Compared to the kernel-based logging, this approach gives users more information - which json whitelist needs to be extended. This is especially useful for services, which fork many diverse children such as shell scripts. Signed-off-by: Michal Sojka --- jail/seccomp-bpf.h | 1 + jail/seccomp.c | 4 +- trace/trace.c | 143 +++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 121 insertions(+), 27 deletions(-) diff --git a/jail/seccomp-bpf.h b/jail/seccomp-bpf.h index 82c0669..fc3ffe7 100644 --- a/jail/seccomp-bpf.h +++ b/jail/seccomp-bpf.h @@ -41,6 +41,7 @@ #define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ #define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */ #define SECCOMP_RET_LOG 0x00070000U +#define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */ #define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ #define SECCOMP_RET_ERROR(x) (SECCOMP_RET_ERRNO | ((x) & 0x0000ffffU)) #define SECCOMP_RET_LOGGER(x) (SECCOMP_RET_LOG | ((x) & 0x0000ffffU)) diff --git a/jail/seccomp.c b/jail/seccomp.c index dcd19ec..1a2bb27 100644 --- a/jail/seccomp.c +++ b/jail/seccomp.c @@ -118,8 +118,8 @@ int install_syscall_filter(const char *argv, const char *file) } if (default_policy) - /* return -1 and set errno */ - set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_LOGGER(default_policy)); + /* notify tracer; without tracer return -1 and set errno to ENOSYS */ + set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_TRACE); else /* kill the process */ set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); diff --git a/trace/trace.c b/trace/trace.c index 9d3cd0a..5189cfb 100644 --- a/trace/trace.c +++ b/trace/trace.c @@ -12,8 +12,10 @@ */ #define _GNU_SOURCE +#include #include #include +#include #include #include #include @@ -49,18 +51,33 @@ # endif #define reg_syscall_nr (EF_REG2 / 4) #elif defined(__arm__) +#include /* for PTRACE_SET_SYSCALL */ #define reg_syscall_nr _offsetof(struct user, regs.uregs[7]) +# if defined(__ARM_EABI__) +# define reg_retval_nr _offsetof(struct user, regs.uregs[0]) +# endif #else #error tracing is not supported on this architecture #endif +enum mode { + UTRACE, + SECCOMP_TRACE, +} mode = UTRACE; + +#define PROC_NAME(mode) (mode == UTRACE ? "utrace" : "seccomp-trace") + #define INFO(fmt, ...) do { \ - fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \ + fprintf(stderr, "%s: "fmt, PROC_NAME(mode), ## __VA_ARGS__); \ } while (0) #define ERROR(fmt, ...) do { \ - syslog(LOG_ERR, "utrace: "fmt, ## __VA_ARGS__); \ - fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \ + syslog(LOG_ERR, "%s: "fmt, PROC_NAME(mode), ## __VA_ARGS__); \ + fprintf(stderr, "%s: "fmt, PROC_NAME(mode), ## __VA_ARGS__); \ +} while (0) + +#define LOGERR(fmt, ...) do { \ + syslog(LOG_ERR, "%s: "fmt, PROC_NAME(mode), ## __VA_ARGS__); \ } while (0) struct tracee { @@ -70,9 +87,12 @@ struct tracee { static struct tracee tracer; static int *syscall_count; +static int violation_count; static struct blob_buf b; static int syscall_max; static int debug; +char *json = NULL; +int ptrace_restart; static int max_syscall = ARRAY_SIZE(syscall_names); @@ -102,11 +122,13 @@ static void print_syscalls(int policy, const char *json) void *c; int i; - set_syscall("rt_sigaction", 1); - set_syscall("sigreturn", 1); - set_syscall("rt_sigreturn", 1); - set_syscall("exit_group", 1); - set_syscall("exit", 1); + if (mode == UTRACE) { + set_syscall("rt_sigaction", 1); + set_syscall("sigreturn", 1); + set_syscall("rt_sigreturn", 1); + set_syscall("exit_group", 1); + set_syscall("exit", 1); + } struct syscall sorted[ARRAY_SIZE(syscall_names)]; @@ -151,6 +173,30 @@ static void print_syscalls(int policy, const char *json) } +static void report_seccomp_vialation(pid_t pid, unsigned syscall) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid); + int f = open(buf, O_RDONLY); + int r = read(f, buf, sizeof(buf) - 1); + if (r >= 0) + buf[r] = 0; + else + strcpy(buf, "unknown?"); + close(f); + + if (violation_count < INT_MAX) + violation_count++; + if (syscall < ARRAY_SIZE(syscall_names)) { + syscall_count[syscall]++; + LOGERR("%s[%u] tried to call non-whitelisted syscall: %s (see %s)\n", + buf, pid, syscall_names[syscall], json); + } else { + LOGERR("%s[%u] tried to call non-whitelisted syscall: %d (see %s)\n", + buf, pid, syscall, json); + } +} + static void tracer_cb(struct uloop_process *c, int ret) { struct tracee *tracee = container_of(c, struct tracee, proc); @@ -180,12 +226,21 @@ static void tracer_cb(struct uloop_process *c, int ret) ptrace(PTRACE_GETEVENTMSG, c->pid, 0, &child->proc.pid); child->proc.cb = tracer_cb; - ptrace(PTRACE_SYSCALL, child->proc.pid, 0, 0); + ptrace(ptrace_restart, child->proc.pid, 0, 0); uloop_process_add(&child->proc); if (debug) fprintf(stderr, "Tracing new child %d\n", child->proc.pid); } else if ((ret >> 16) == PTRACE_EVENT_STOP) { /* Nothing special to do here */ + } else if ((ret >> 8) == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) { + int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr); +#if defined(__arm__) + ptrace(PTRACE_SET_SYSCALL, c->pid, 0, -1); + ptrace(PTRACE_POKEUSER, c->pid, reg_retval_nr, -ENOSYS); +#else + ptrace(PTRACE_POKEUSER, c->pid, reg_syscall_nr, -1); +#endif + report_seccomp_vialation(c->pid, syscall); } else { inject_signal = WSTOPSIG(ret); if (debug) @@ -203,16 +258,20 @@ static void tracer_cb(struct uloop_process *c, int ret) return; } - ptrace(PTRACE_SYSCALL, c->pid, 0, inject_signal); + ptrace(ptrace_restart, c->pid, 0, inject_signal); uloop_process_add(c); } int main(int argc, char **argv, char **envp) { - char *json = NULL; int status, ch, policy = EPERM; pid_t child; + /* When invoked via seccomp-trace symlink, work as seccomp + * violation logger rather than as syscall tracer */ + if (strstr(argv[0], "seccomp-trace")) + mode = SECCOMP_TRACE; + while ((ch = getopt(argc, argv, "f:p:")) != -1) { switch (ch) { case 'f': @@ -224,6 +283,9 @@ int main(int argc, char **argv, char **envp) } } + if (!json) + json = getenv("SECCOMP_FILE"); + argc -= optind; argv += optind; @@ -239,7 +301,9 @@ int main(int argc, char **argv, char **envp) if (child == 0) { char **_argv = calloc(argc + 1, sizeof(char *)); char **_envp; - char *preload = "LD_PRELOAD=/lib/libpreload-trace.so"; + char *preload = NULL; + const char *old_preload = getenv("LD_PRELOAD"); + int newenv = 0; int envc = 0; int ret; @@ -248,9 +312,23 @@ int main(int argc, char **argv, char **envp) while (envp[envc++]) ; - _envp = calloc(envc + 1, sizeof(char *)); - memcpy(&_envp[1], envp, envc * sizeof(char *)); - *_envp = preload; + _envp = calloc(envc + 2, sizeof(char *)); + switch (mode) { + case UTRACE: + preload = "/lib/libpreload-trace.so"; + newenv = 1; + break; + case SECCOMP_TRACE: + preload = "/lib/libpreload-seccomp.so"; + newenv = 2; + asprintf(&_envp[1], "SECCOMP_FILE=%s", json ? json : ""); + kill(getpid(), SIGSTOP); + break; + } + asprintf(&_envp[0], "LD_PRELOAD=%s%s%s", preload, + old_preload ? ":" : "", + old_preload ? old_preload : ""); + memcpy(&_envp[newenv], envp, envc * sizeof(char *)); ret = execve(_argv[0], _argv, _envp); ERROR("failed to exec %s: %s\n", _argv[0], strerror(errno)); @@ -271,12 +349,19 @@ int main(int argc, char **argv, char **envp) return -1; } - ptrace(PTRACE_SEIZE, child, 0, - PTRACE_O_TRACESYSGOOD | - PTRACE_O_TRACEFORK | - PTRACE_O_TRACEVFORK | - PTRACE_O_TRACECLONE); - ptrace(PTRACE_SYSCALL, child, 0, SIGCONT); + int ptrace_options = PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE; + switch (mode) { + case UTRACE: + ptrace_options |= PTRACE_O_TRACESYSGOOD; + ptrace_restart = PTRACE_SYSCALL; + break; + case SECCOMP_TRACE: + ptrace_options |= PTRACE_O_TRACESECCOMP; + ptrace_restart = PTRACE_CONT; + break; + } + ptrace(PTRACE_SEIZE, child, 0, ptrace_options); + ptrace(ptrace_restart, child, 0, SIGCONT); uloop_init(); tracer.proc.pid = child; @@ -285,11 +370,19 @@ int main(int argc, char **argv, char **envp) uloop_run(); uloop_done(); - if (!json) - if (asprintf(&json, "/tmp/%s.%u.json", basename(*argv), child) < 0) - ERROR("failed to allocate output path: %s\n", strerror(errno)); + switch (mode) { + case UTRACE: + if (!json) + if (asprintf(&json, "/tmp/%s.%u.json", basename(*argv), child) < 0) + ERROR("failed to allocate output path: %s\n", strerror(errno)); + break; + case SECCOMP_TRACE: + if (!violation_count) + return 0; + asprintf(&json, "/tmp/%s.%u.violations.json", basename(*argv), child); + break; + } print_syscalls(policy, json); - return 0; } -- 2.11.0