diff options
author | Jorge Lucangeli Obes <jorgelo@chromium.org> | 2012-07-31 16:25:56 -0700 |
---|---|---|
committer | Gerrit <chrome-bot@google.com> | 2012-08-10 18:07:49 -0700 |
commit | bda833cbcee330eab91561a9b50b6bc24c47f2e9 (patch) | |
tree | 0ee5665a5e09e2a58c376b52fa8eca77aa164280 | |
parent | a6b034dedfb1109adcd88eb1bcea15a29067824c (diff) | |
download | minijail-bda833cbcee330eab91561a9b50b6bc24c47f2e9.tar.gz |
Minijail: add logging for seccomp filter failures.
BUG=chromium-os:33361
TEST=unit tests
TEST=security_Minijail0, security_Minijail_seccomp, platform_CrosDisksArchive
Change-Id: I16cdb8fbcf1cb13f2dee5521f97fb8d0bdbdf93b
Reviewed-on: https://gerrit.chromium.org/gerrit/29053
Reviewed-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
Tested-by: Jorge Lucangeli Obes <jorgelo@chromium.org>
Commit-Ready: Jorge Lucangeli Obes <jorgelo@chromium.org>
-rw-r--r-- | Makefile | 15 | ||||
-rw-r--r-- | bpf.h | 5 | ||||
-rw-r--r-- | libminijail.c | 25 | ||||
-rw-r--r-- | libminijail.h | 1 | ||||
-rw-r--r-- | minijail0.c | 20 | ||||
-rw-r--r-- | signal.c | 70 | ||||
-rw-r--r-- | signal.h | 14 | ||||
-rw-r--r-- | syscall_filter.c | 55 | ||||
-rw-r--r-- | syscall_filter.h | 5 | ||||
-rw-r--r-- | syscall_filter_unittest.c | 50 | ||||
-rw-r--r-- | util.c | 12 | ||||
-rw-r--r-- | util.h | 3 |
12 files changed, 244 insertions, 31 deletions
@@ -11,11 +11,12 @@ all : minijail0 libminijail.so libminijailpreload.so tests : libminijail_unittest.wrapper syscall_filter_unittest -minijail0 : libsyscalls.gen.o libminijail.o syscall_filter.o bpf.o util.o \ - minijail0.c +minijail0 : libsyscalls.gen.o libminijail.o syscall_filter.o \ + signal.o bpf.o util.o minijail0.c $(CC) $(CFLAGS) -o $@ $^ -lcap -libminijail.so : libminijail.o syscall_filter.o bpf.o util.o libsyscalls.gen.o +libminijail.so : libminijail.o syscall_filter.o signal.o bpf.o util.o \ + libsyscalls.gen.o $(CC) $(CFLAGS) -shared -o $@ $^ -lcap # Allow unittests to access what are normally internal symbols. @@ -26,11 +27,11 @@ libminijail_unittest.wrapper : libminijail_unittest : CFLAGS := $(filter-out -fvisibility=%,$(CFLAGS)) libminijail_unittest : libminijail_unittest.o libminijail.o \ - syscall_filter.o bpf.o util.o libsyscalls.gen.o + syscall_filter.o signal.o bpf.o util.o libsyscalls.gen.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(filter-out $(CFLAGS_FILE),$^) -lcap libminijailpreload.so : libminijailpreload.c libminijail.o libsyscalls.gen.o \ - syscall_filter.o bpf.o util.o + syscall_filter.o signal.o bpf.o util.o $(CC) $(CFLAGS) -shared -o $@ $^ -ldl -lcap libminijail.o : libminijail.c libminijail.h @@ -49,6 +50,8 @@ syscall_filter_unittest.o : syscall_filter_unittest.c test_harness.h syscall_filter.o : syscall_filter.c syscall_filter.h +signal.o : signal.c signal.h + bpf.o : bpf.c bpf.h util.o : util.c util.h @@ -101,5 +104,5 @@ clean : test-clean @rm -f libminijail.so @rm -f libminijail_unittest @rm -f libsyscalls.gen.c - @rm -f syscall_filter.o bpf.o util.o + @rm -f syscall_filter.o signal.o bpf.o util.o @rm -f syscall_filter_unittest syscall_filter_unittest.o @@ -59,7 +59,7 @@ struct seccomp_data { #define ARCH_NR AUDIT_ARCH_X86_64 #elif defined(__arm__) /* - * <linux/audit.h> includes <linux/elf-em.h>, which does not include EM_ARM. + * <linux/audit.h> includes <linux/elf-em.h>, which does not define EM_ARM. * <linux/elf.h> only includes <asm/elf.h> if we're in the kernel. */ # ifndef EM_ARM @@ -147,6 +147,9 @@ inline size_t set_bpf_instr(struct sock_filter *instr, #define set_bpf_ret_kill(_block) \ set_bpf_stmt((_block), BPF_RET+BPF_K, SECCOMP_RET_KILL) +#define set_bpf_ret_trap(_block) \ + set_bpf_stmt((_block), BPF_RET+BPF_K, SECCOMP_RET_TRAP) + #define set_bpf_ret_errno(_block, _errno) \ set_bpf_stmt((_block), BPF_RET+BPF_K, \ SECCOMP_RET_ERRNO | ((_errno) & SECCOMP_RET_DATA)) diff --git a/libminijail.c b/libminijail.c index 2c3d5b3..4da1f66 100644 --- a/libminijail.c +++ b/libminijail.c @@ -35,6 +35,7 @@ #include "libminijail.h" #include "libminijail-private.h" +#include "signal.h" #include "syscall_filter.h" #include "util.h" @@ -71,6 +72,7 @@ struct minijail { int ptrace:1; int no_new_privs:1; int seccomp_filter:1; + int log_seccomp_filter:1; int chroot:1; } flags; uid_t uid; @@ -185,6 +187,11 @@ void API minijail_use_seccomp_filter(struct minijail *j) j->flags.seccomp_filter = 1; } +void API minijail_log_seccomp_filter_failures(struct minijail *j) +{ + j->flags.log_seccomp_filter = 1; +} + void API minijail_use_caps(struct minijail *j, uint64_t capmask) { j->caps = capmask; @@ -278,8 +285,9 @@ void API minijail_parse_seccomp_filters(struct minijail *j, const char *path) } struct sock_fprog *fprog = malloc(sizeof(struct sock_fprog)); - if (compile_filter(file, fprog)) { - die("failed to compile seccomp filter BPF program in '%s'", path); + if (compile_filter(file, fprog, j->flags.log_seccomp_filter)) { + die("failed to compile seccomp filter BPF program in '%s'", + path); } j->filter_len = fprog->len; @@ -334,7 +342,8 @@ void minijail_marshal_helper(struct marshal_state *state, for (b = j->bindings_head; b; b = b->next) { marshal_append(state, b->src, strlen(b->src) + 1); marshal_append(state, b->dest, strlen(b->dest) + 1); - marshal_append(state, (char *)&b->writeable, sizeof(b->writeable)); + marshal_append(state, (char *)&b->writeable, + sizeof(b->writeable)); } } @@ -640,6 +649,16 @@ void API minijail_enter(const struct minijail *j) } /* + * If we're logging seccomp filter failures, + * install the SIGSYS handler first. + */ + if (j->flags.seccomp_filter && j->flags.log_seccomp_filter) { + if (install_sigsys_handler()) + pdie("install SIGSYS handler"); + warn("logging seccomp filter failures"); + } + + /* * Install seccomp filter before dropping root and caps. * WARNING: this means that filter policies *must* allow * setgroups()/setresgid()/setresuid() for dropping root and diff --git a/libminijail.h b/libminijail.h index 13f9ab4..d83dc21 100644 --- a/libminijail.h +++ b/libminijail.h @@ -46,6 +46,7 @@ void minijail_use_seccomp(struct minijail *j); void minijail_no_new_privs(struct minijail *j); void minijail_use_seccomp_filter(struct minijail *j); void minijail_parse_seccomp_filters(struct minijail *j, const char *path); +void minijail_log_seccomp_filter_failures(struct minijail *j); void minijail_use_caps(struct minijail *j, uint64_t capmask); void minijail_namespace_vfs(struct minijail *j); /* Implies namespace_vfs and remount_readonly. diff --git a/minijail0.c b/minijail0.c index 9f2a765..9ab5195 100644 --- a/minijail0.c +++ b/minijail0.c @@ -11,6 +11,8 @@ #include "libminijail.h" #include "libsyscalls.h" +#include "util.h" + static void set_user(struct minijail *j, const char *arg) { char *end = NULL; @@ -69,6 +71,8 @@ static void add_binding(struct minijail *j, char *arg) { static void usage(const char *progn) { + size_t i; + printf("Usage: %s [-Ghnprsv] [-b <src>,<dest>[,<writeable>]] " "[-c <caps>] [-C <dir>] [-g <group>] [-S <file>] [-u <user>] " "<program> [args...]\n" @@ -80,14 +84,21 @@ static void usage(const char *progn) " -g <group>: change gid to <group>\n" " -h: help (this message)\n" " -H: seccomp filter help message\n" + " -L: log blocked syscalls when using seccomp filter. " + "Forces the following syscalls to be allowed:\n" + " ", progn); + for (i = 0; i < log_syscalls_len; i++) + printf("%s ", log_syscalls[i]); + + printf("\n" " -n: set no_new_privs\n" " -p: use pid namespace (implies -vr)\n" " -r: remount /proc readonly (implies -v)\n" " -s: use seccomp\n" - " -S <file>: set seccomp filters using <file>\n" + " -S <file>: set seccomp filter using <file>\n" " E.g., -S /usr/share/filters/<prog>.$(uname -m)\n" " -u <user>: change uid to <user>\n" - " -v: use vfs namespace\n", progn); + " -v: use vfs namespace\n"); } static void seccomp_filter_usage(const char *progn) @@ -105,7 +116,7 @@ int main(int argc, char *argv[]) struct minijail *j = minijail_new(); int opt; - while ((opt = getopt(argc, argv, "u:g:sS:c:C:b:vrGhHnp")) != -1) { + while ((opt = getopt(argc, argv, "u:g:sS:c:C:b:vrGhHnpL")) != -1) { switch (opt) { case 'u': set_user(j, optarg); @@ -123,6 +134,9 @@ int main(int argc, char *argv[]) minijail_parse_seccomp_filters(j, optarg); minijail_use_seccomp_filter(j); break; + case 'L': + minijail_log_seccomp_filter_failures(j); + break; case 'b': add_binding(j, optarg); break; diff --git a/signal.c b/signal.c new file mode 100644 index 0000000..85103c7 --- /dev/null +++ b/signal.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <asm/siginfo.h> +#define __have_siginfo_t 1 +#define __have_sigval_t 1 +#define __have_sigevent_t 1 + +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "signal.h" + +#include "util.h" + +struct local_sigsys { + void *ip; + int nr; + unsigned int arch; +}; + +void log_sigsys_handler(int nr, siginfo_t *info, void *void_context) +{ + struct local_sigsys sigsys; + const char *syscall_name; + memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); + syscall_name = lookup_syscall_name(sigsys.nr); + + if (syscall_name) + warn("blocked syscall: %s", syscall_name); + else + warn("blocked syscall: %d", nr); + + (void) void_context; + + /* + * We trapped on a syscall that should have killed the process. + * This should never ever return, but we're paranoid. + */ + for (;;) + _exit(1); +} + +int install_sigsys_handler() +{ + int ret = 0; + struct sigaction act; + sigset_t mask; + + memset(&act, 0, sizeof(act)); + act.sa_sigaction = &log_sigsys_handler; + act.sa_flags = SA_SIGINFO; + + sigemptyset(&mask); + sigaddset(&mask, SIGSYS); + + ret = sigaction(SIGSYS, &act, NULL); + if (ret < 0) + return ret; + + ret = sigprocmask(SIG_UNBLOCK, &mask, NULL); + if (ret < 0) + return ret; + + return 0; +} diff --git a/signal.h b/signal.h new file mode 100644 index 0000000..d68bbb2 --- /dev/null +++ b/signal.h @@ -0,0 +1,14 @@ +/* signal.h + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Signal handling functions. + */ + +#ifndef SIGNAL_H +#define SIGNAL_H + +int install_sigsys_handler(); + +#endif /* SIGNAL_H */ diff --git a/syscall_filter.c b/syscall_filter.c index e96ad60..b7dfb77 100644 --- a/syscall_filter.c +++ b/syscall_filter.c @@ -11,7 +11,9 @@ #include "util.h" -#define MAX_LINE_LENGTH 1024 +#define MAX_LINE_LENGTH 1024 +#define MAX_POLICY_LINE_LENGTH 1024 + #define ONE_INSTR 1 #define TWO_INSTRS 2 @@ -85,6 +87,13 @@ void append_ret_kill(struct filter_block *head) append_filter_block(head, filter, ONE_INSTR); } +void append_ret_trap(struct filter_block *head) +{ + struct sock_filter *filter = new_instr_buf(ONE_INSTR); + set_bpf_ret_trap(filter); + append_filter_block(head, filter, ONE_INSTR); +} + void append_ret_errno(struct filter_block *head, int errno_val) { struct sock_filter *filter = new_instr_buf(ONE_INSTR); @@ -92,6 +101,22 @@ void append_ret_errno(struct filter_block *head, int errno_val) append_filter_block(head, filter, ONE_INSTR); } +void append_allow_syscall(struct filter_block *head, int nr) { + struct sock_filter *filter = new_instr_buf(ALLOW_SYSCALL_LEN); + size_t len = bpf_allow_syscall(filter, nr); + if (len != ALLOW_SYSCALL_LEN) + die("error building syscall number comparison"); + + append_filter_block(head, filter, len); +} + +void allow_log_syscalls(struct filter_block *head) +{ + unsigned int i; + for (i = 0; i < log_syscalls_len; i++) + append_allow_syscall(head, lookup_syscall(log_syscalls[i])); +} + unsigned int get_label_id(struct bpf_labels *labels, const char *label_str) { int label_id = bpf_label_id(labels, label_str); @@ -147,11 +172,11 @@ struct filter_block *compile_section(int nr, const char *policy_line, int group_idx = 0; /* Checks for overly long policy lines. */ - if (strlen(policy_line) >= MAX_POLICY_LINE_LEN) + if (strlen(policy_line) >= MAX_POLICY_LINE_LENGTH) return NULL; /* strtok() modifies its first argument, so let's make a copy. */ - char *line = strndup(policy_line, MAX_POLICY_LINE_LEN); + char *line = strndup(policy_line, MAX_POLICY_LINE_LENGTH); if (!line) return NULL; @@ -311,7 +336,8 @@ struct filter_block *compile_section(int nr, const char *policy_line, return head; } -int compile_filter(FILE *policy, struct sock_fprog *prog) +int compile_filter(FILE *policy, struct sock_fprog *prog, + int log_failures) { char line[MAX_LINE_LENGTH]; int line_count = 0; @@ -334,11 +360,15 @@ int compile_filter(FILE *policy, struct sock_fprog *prog) size_t len = bpf_validate_arch(valid_arch); append_filter_block(head, valid_arch, len); - /* Loading syscall number. */ + /* Load syscall number. */ struct sock_filter *load_nr = new_instr_buf(ONE_INSTR); len = bpf_load_syscall_nr(load_nr); append_filter_block(head, load_nr, len); + /* If we're logging failures, allow the necessary syscalls first. */ + if (log_failures) + allow_log_syscalls(head); + /* * Loop through all the lines in the policy file. * Build a jump table for the syscall number. @@ -374,10 +404,7 @@ int compile_filter(FILE *policy, struct sock_fprog *prog) */ if (strcmp(policy, "1") == 0) { /* Add simple ALLOW. */ - struct sock_filter *nr_comp = - new_instr_buf(ALLOW_SYSCALL_LEN); - bpf_allow_syscall(nr_comp, nr); - append_filter_block(head, nr_comp, ALLOW_SYSCALL_LEN); + append_allow_syscall(head, nr); } else { /* * Create and jump to the label that will hold @@ -404,8 +431,14 @@ int compile_filter(FILE *policy, struct sock_fprog *prog) } } - /* If none of the syscalls match, fall back to KILL. */ - append_ret_kill(head); + /* + * If none of the syscalls match, either fall back to KILL, + * or return TRAP. + */ + if (!log_failures) + append_ret_kill(head); + else + append_ret_trap(head); /* Allocate the final buffer, now that we know its size. */ size_t final_filter_len = head->total_len + diff --git a/syscall_filter.h b/syscall_filter.h index 18ea415..a9d83d1 100644 --- a/syscall_filter.h +++ b/syscall_filter.h @@ -11,7 +11,8 @@ #include "bpf.h" -#define MAX_POLICY_LINE_LEN 1024 +#define NO_LOGGING 0 +#define USE_LOGGING 1 struct filter_block { struct sock_filter *instrs; @@ -26,7 +27,7 @@ struct bpf_labels; struct filter_block *compile_section(int nr, const char *policy_line, unsigned int label_id, struct bpf_labels *labels); -int compile_filter(FILE *policy, struct sock_fprog *prog); +int compile_filter(FILE *policy, struct sock_fprog *prog, int log_failures); int flatten_block_list(struct filter_block *head, struct sock_filter *filter, size_t index, size_t cap); diff --git a/syscall_filter_unittest.c b/syscall_filter_unittest.c index c6b11cb..404919b 100644 --- a/syscall_filter_unittest.c +++ b/syscall_filter_unittest.c @@ -14,6 +14,8 @@ #include "bpf.h" #include "syscall_filter.h" +#include "util.h" + /* BPF testing macros. */ #define EXPECT_EQ_BLOCK(_block, _code, _k, _jt, _jf) \ do { \ @@ -399,7 +401,7 @@ FIXTURE_TEARDOWN(filter) {} TEST_F(filter, seccomp_mode1) { struct sock_fprog actual; FILE *policy = fopen("test/seccomp.policy", "r"); - int res = compile_filter(policy, &actual); + int res = compile_filter(policy, &actual, NO_LOGGING); /* * Checks return value, filter length, and that the filter @@ -427,7 +429,7 @@ TEST_F(filter, seccomp_mode1) { TEST_F(filter, seccomp_read_write) { struct sock_fprog actual; FILE *policy = fopen("test/stdin_stdout.policy", "r"); - int res = compile_filter(policy, &actual); + int res = compile_filter(policy, &actual, NO_LOGGING); /* * Checks return value, filter length, and that the filter @@ -459,12 +461,12 @@ TEST_F(filter, invalid) { struct sock_fprog actual; FILE *policy = fopen("test/invalid_syscall_name.policy", "r"); - int res = compile_filter(policy, &actual); + int res = compile_filter(policy, &actual, NO_LOGGING); ASSERT_NE(res, 0); fclose(policy); policy = fopen("test/invalid_arg_filter.policy", "r"); - res = compile_filter(policy, &actual); + res = compile_filter(policy, &actual, NO_LOGGING); ASSERT_NE(res, 0); fclose(policy); } @@ -473,8 +475,46 @@ TEST_F(filter, nonexistent) { struct sock_fprog actual; FILE *policy = fopen("test/nonexistent-file.policy", "r"); - int res = compile_filter(policy, &actual); + int res = compile_filter(policy, &actual, NO_LOGGING); ASSERT_NE(res, 0); } TEST_HARNESS_MAIN + +TEST_F(filter, log) { + struct sock_fprog actual; + + FILE *policy = fopen("test/seccomp.policy", "r"); + int res = compile_filter(policy, &actual, USE_LOGGING); + + size_t i; + size_t index = 0; + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, only allows expected syscalls, + * and returns TRAP on failure. + * NOTE(jorgelo): the filter is longer since we add the syscalls needed + * for logging. + */ + ASSERT_EQ(res, 0); + EXPECT_EQ(actual.len, 13 + 2 * log_syscalls_len); + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD+BPF_W+BPF_ABS, syscall_nr); + + index = ARCH_VALIDATION_LEN + 1; + for (i = 0; i < log_syscalls_len; i++) + EXPECT_ALLOW_SYSCALL(actual.filter + (index + 2 * i), + lookup_syscall(log_syscalls[i])); + + index += 2 * log_syscalls_len; + + EXPECT_ALLOW_SYSCALL(actual.filter + index, __NR_read); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 2, __NR_write); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 4, __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 6, __NR_exit); + EXPECT_EQ_STMT(actual.filter + index + 8, BPF_RET+BPF_K, SECCOMP_RET_TRAP); + + free(actual.filter); + fclose(policy); +} @@ -10,6 +10,18 @@ #include "libsyscalls.h" +#if defined(__x86_64__) +const char *log_syscalls[] = { "connect", "sendto" }; +#elif defined(__i386__) +const char *log_syscalls[] = { "socketcall", "time" }; +#elif defined(__arm__) +const char *log_syscalls[] = { "connect", "gettimeofday", "send" }; +#else +#error "Unsupported platform" +#endif + +const size_t log_syscalls_len = sizeof(log_syscalls)/sizeof(log_syscalls[0]); + int lookup_syscall(const char *name) { const struct syscall_entry *entry = syscall_table; @@ -26,6 +26,9 @@ #define info(_msg, ...) \ syslog(LOG_INFO, "libminijail: " _msg, ## __VA_ARGS__) +extern const char *log_syscalls[]; +extern const size_t log_syscalls_len; + int lookup_syscall(const char *name); const char *lookup_syscall_name(int nr); char *strip(char *s); |