X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fprocd.git;a=blobdiff_plain;f=trace%2Ftrace.c;h=1c1e75753fc910fad78f4aec4c7a01f3a55d6f29;hp=b0005b8a8178dc4a738dd9c475d30ad7a731f43f;hb=67eb7e68165bfa29ab57eb82f3e40eca17662365;hpb=890ac34c460fbb753bd4303b201d11aebd27a570 diff --git a/trace/trace.c b/trace/trace.c index b0005b8..1c1e757 100644 --- a/trace/trace.c +++ b/trace/trace.c @@ -12,8 +12,10 @@ */ #define _GNU_SOURCE +#include #include #include +#include #include #include #include @@ -23,7 +25,15 @@ #include #include #include +#include +#ifndef PTRACE_EVENT_STOP +/* PTRACE_EVENT_STOP is defined in linux/ptrace.h, but this header + * collides with musl's sys/ptrace.h */ +#define PTRACE_EVENT_STOP 128 +#endif + +#include #include #include #include @@ -43,38 +53,54 @@ # 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 -#define INFO(fmt, ...) do { \ - fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \ -} while (0) +enum mode { + UTRACE, + SECCOMP_TRACE, +} mode = UTRACE; -#define ERROR(fmt, ...) do { \ - syslog(LOG_ERR, "utrace: "fmt, ## __VA_ARGS__); \ - fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \ -} while (0) +struct tracee { + struct uloop_process proc; + int in_syscall; +}; -static struct uloop_process tracer; -static int *syscall_count; +static struct tracee tracer; +static int syscall_count[SYSCALL_COUNT]; +static int violation_count; static struct blob_buf b; -static int syscall_max; -static int in_syscall; static int debug; - -static int max_syscall = ARRAY_SIZE(syscall_names); +char *json = NULL; +int ptrace_restart; static void set_syscall(const char *name, int val) { int i; - for (i = 0; i < max_syscall; i++) - if (syscall_names[i] && !strcmp(syscall_names[i], name)) { + for (i = 0; i < SYSCALL_COUNT; i++) { + int sc = syscall_index_to_number(i); + if (syscall_name(sc) && !strcmp(syscall_name(sc), name)) { syscall_count[i] = val; return; } + } +} + +struct syscall { + int syscall; + int count; +}; + +static int cmp_count(const void *a, const void *b) +{ + return ((struct syscall*)b)->count - ((struct syscall*)a)->count; } static void print_syscalls(int policy, const char *json) @@ -82,25 +108,37 @@ 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[SYSCALL_COUNT]; + + for (i = 0; i < SYSCALL_COUNT; i++) { + sorted[i].syscall = syscall_index_to_number(i); + sorted[i].count = syscall_count[i]; + } + + qsort(sorted, SYSCALL_COUNT, sizeof(sorted[0]), cmp_count); blob_buf_init(&b, 0); c = blobmsg_open_array(&b, "whitelist"); - for (i = 0; i < ARRAY_SIZE(syscall_names); i++) { - if (!syscall_count[i]) - continue; - if (syscall_names[i]) { + for (i = 0; i < SYSCALL_COUNT; i++) { + int sc = sorted[i].syscall; + if (!sorted[i].count) + break; + if (syscall_name(sc)) { if (debug) printf("syscall %d (%s) was called %d times\n", - i, syscall_names[i], syscall_count[i]); - blobmsg_add_string(&b, NULL, syscall_names[i]); + sc, syscall_name(sc), sorted[i].count); + blobmsg_add_string(&b, NULL, syscall_name(sc)); } else { - ERROR("no name found for syscall(%d)\n", i); + ULOG_ERR("no name found for syscall(%d)\n", sc); } } blobmsg_close_array(&b, c); @@ -110,9 +148,9 @@ static void print_syscalls(int policy, const char *json) if (fp) { fprintf(fp, "%s", blobmsg_format_json_indent(b.head, true, 0)); fclose(fp); - INFO("saving syscall trace to %s\n", json); + ULOG_INFO("saving syscall trace to %s\n", json); } else { - ERROR("failed to open %s\n", json); + ULOG_ERR("failed to open %s\n", json); } } else { printf("%s\n", @@ -121,35 +159,115 @@ static void print_syscalls(int policy, const char *json) } -static void tracer_cb(struct uloop_process *c, int ret) +static void report_seccomp_vialation(pid_t pid, unsigned syscall) { - if (WIFSTOPPED(ret) && WSTOPSIG(ret) & 0x80) { - if (!in_syscall) { - int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr); + 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++; + int i = syscall_index(syscall); + if (i >= 0) { + syscall_count[i]++; + ULOG_ERR("%s[%u] tried to call non-whitelisted syscall: %s (see %s)\n", + buf, pid, syscall_name(syscall), json); + } else { + ULOG_ERR("%s[%u] tried to call non-whitelisted syscall: %d (see %s)\n", + buf, pid, syscall, json); + } +} - if (syscall < syscall_max) { - syscall_count[syscall]++; - if (debug) - fprintf(stderr, "%s()\n", syscall_names[syscall]); - } else if (debug) { - fprintf(stderr, "syscal(%d)\n", syscall); +static void tracer_cb(struct uloop_process *c, int ret) +{ + struct tracee *tracee = container_of(c, struct tracee, proc); + int inject_signal = 0; + + /* We explicitely check for events in upper 16 bits, because + * musl (as opposed to glibc) does not report + * PTRACE_EVENT_STOP as WIFSTOPPED */ + if (WIFSTOPPED(ret) || (ret >> 16)) { + if (WSTOPSIG(ret) & 0x80) { + if (!tracee->in_syscall) { + int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr); + int i = syscall_index(syscall); + if (i >= 0) { + syscall_count[i]++; + if (debug) + fprintf(stderr, "%s()\n", syscall_name(syscall)); + } else if (debug) { + fprintf(stderr, "syscal(%d)\n", syscall); + } } + tracee->in_syscall = !tracee->in_syscall; + } else if ((ret >> 8) == (SIGTRAP | (PTRACE_EVENT_FORK << 8)) || + (ret >> 8) == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) || + (ret >> 8) == (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) { + struct tracee *child = calloc(1, sizeof(struct tracee)); + + ptrace(PTRACE_GETEVENTMSG, c->pid, 0, &child->proc.pid); + child->proc.cb = tracer_cb; + 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) + fprintf(stderr, "Injecting signal %d into pid %d\n", + inject_signal, tracee->proc.pid); + } + } else if (WIFEXITED(ret) || (WIFSIGNALED(ret) && WTERMSIG(ret))) { + if (tracee == &tracer) { + uloop_end(); /* Main process exit */ + } else { + if (debug) + fprintf(stderr, "Child %d exited\n", tracee->proc.pid); + free(tracee); } - in_syscall = !in_syscall; - } else if (WIFEXITED(ret)) { - uloop_end(); return; } - ptrace(PTRACE_SYSCALL, c->pid, 0, 0); - uloop_process_add(&tracer); + + ptrace(ptrace_restart, c->pid, 0, inject_signal); + uloop_process_add(c); +} + +static void sigterm_handler(int signum) +{ + /* When we receive SIGTERM, we forward it to the tracee. After + * the tracee exits, trace_cb() will be called and make us + * exit too. */ + kill(tracer.proc.pid, SIGTERM); } + 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': @@ -161,6 +279,9 @@ int main(int argc, char **argv, char **envp) } } + if (!json) + json = getenv("SECCOMP_FILE"); + argc -= optind; argv += optind; @@ -176,8 +297,10 @@ 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"; - int envc = 1; + char *preload = NULL; + const char *old_preload = getenv("LD_PRELOAD"); + int newenv = 0; + int envc = 0; int ret; memcpy(_argv, argv, argc * sizeof(char *)); @@ -185,41 +308,84 @@ int main(int argc, char **argv, char **envp) while (envp[envc++]) ; - _envp = calloc(envc, 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); + ULOG_ERR("failed to exec %s: %m\n", _argv[0]); - ret = execve(_argv[0], _argv, envp); - ERROR("failed to exec %s: %s\n", _argv[0], strerror(errno)); + free(_argv); + free(_envp); return ret; } if (child < 0) return -1; - syscall_max = ARRAY_SIZE(syscall_names); - syscall_count = calloc(syscall_max, sizeof(int)); - waitpid(child, &status, 0); + waitpid(child, &status, WUNTRACED); if (!WIFSTOPPED(status)) { - ERROR("failed to start %s\n", *argv); + ULOG_ERR("failed to start %s\n", *argv); return -1; } - ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD); - ptrace(PTRACE_SYSCALL, child, 0, 0); - + /* Initialize uloop to catch all ptrace stops from now on. */ uloop_init(); - tracer.pid = child; - tracer.cb = tracer_cb; - uloop_process_add(&tracer); + + 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; + } + if (ptrace(PTRACE_SEIZE, child, 0, ptrace_options) == -1) { + ULOG_ERR("PTRACE_SEIZE: %m\n"); + return -1; + } + if (ptrace(ptrace_restart, child, 0, SIGCONT) == -1) { + ULOG_ERR("ptrace_restart: %m\n"); + return -1; + } + + tracer.proc.pid = child; + tracer.proc.cb = tracer_cb; + uloop_process_add(&tracer.proc); + signal(SIGTERM, sigterm_handler); /* Override uloop's SIGTERM handler */ 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) + ULOG_ERR("failed to allocate output path: %m\n"); + 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; }