diff options
-rw-r--r-- | cooker/filter.c | 75 | ||||
-rw-r--r-- | cooker/filter.h | 36 | ||||
-rw-r--r-- | tests/unit/test_filter.c | 76 | ||||
-rw-r--r-- | tests/unit/test_filter_build.c | 21 | ||||
-rw-r--r-- | tests/unit/testutil.h | 7 | ||||
-rw-r--r-- | tests/unit/util.c | 14 |
6 files changed, 195 insertions, 34 deletions
diff --git a/cooker/filter.c b/cooker/filter.c index 350d6bc..29c2d64 100644 --- a/cooker/filter.c +++ b/cooker/filter.c @@ -9,6 +9,7 @@ #include "numbers.h" #include "filter.h" +#include "util.h" #define N_SYSCALL sizeof(numbers) / sizeof(numbers[0]) @@ -157,7 +158,7 @@ static unsigned get_n_args_syscall_entry(const struct bpf_call *entry) unsigned i, n = 0; for (i = 0; i < 6; i++) - if (entry->check_arg[i]) + if (entry->args[i].type != NO_CHECK) n++; return n; } @@ -181,22 +182,35 @@ static unsigned int get_n_args_syscall_instr(const struct syscall_entry *table) for (unsigned int i = 0; i < table->count; i++) { entry = table->entry + i; n = 0; - - /* For every argument there are 2 instructions, one to - * load the value and the second to evaluate the - * argument - */ for (unsigned int k = 0; k < 6; k++) { - if (entry->check_arg[k]) { + switch (entry->args[k].type) { + case U32: + /* For 32 bit arguments: 2 instructions, + * 1 for loading the value and + * 1 for evaluating the argument */ n += 2; + break; + case U64: + /* For 64 bit arguments: 4 instructions, for + * loading and evaluating the high and low 32 + * bits chuncks. + */ + n += 4; + break; + case NO_CHECK: + break; } } total_instr += n; + /* If there at least an argument, then there is the jump to the + * notification */ if (n > 0) { has_arg = true; total_instr++; } } + /* If there at least an argument for that syscall, then there is the jump to the + * accept */ if (has_arg) total_instr++; @@ -215,9 +229,12 @@ static unsigned int get_total_args_instr(const struct syscall_entry table[], } static bool check_args_syscall_entry(const struct bpf_call *entry){ - return entry->check_arg[0] || entry->check_arg[1] || - entry->check_arg[2] || entry->check_arg[3] || - entry->check_arg[4] || entry->check_arg[5]; + return entry->args[0].type != NO_CHECK || + entry->args[1].type != NO_CHECK || + entry->args[2].type != NO_CHECK || + entry->args[3].type != NO_CHECK || + entry->args[4].type != NO_CHECK || + entry->args[5].type != NO_CHECK; } static bool check_args_syscall(const struct syscall_entry *table) @@ -243,6 +260,21 @@ unsigned int create_bpf_program_log(struct sock_filter filter[]) return 4; } +static unsigned int eq_u64_filter(struct sock_filter filter[], int idx, + uint64_t v, unsigned int jtrue, + unsigned int jfalse) +{ + uint32_t hi = get_hi(v); + uint32_t lo = get_lo(v); + + filter[0] = (struct sock_filter)LOAD(LO_ARG(idx)); + filter[1] = (struct sock_filter)EQ(lo, 0, jfalse); + filter[2] = (struct sock_filter)LOAD(HI_ARG(idx)); + filter[3] = (struct sock_filter)EQ(hi, jtrue, jfalse); + + return 4; +} + unsigned int create_bfp_program(struct syscall_entry table[], struct sock_filter filter[], unsigned int n_syscall) @@ -320,16 +352,33 @@ unsigned int create_bfp_program(struct syscall_entry table[], entry = table[i].entry + j; next_args_off = get_n_args_syscall_entry(entry); for (k = 0; k < 6; k++) { - if (entry->check_arg[k]) { + offset = next_args_off - n_checks; + switch (entry->args[k].type) { + case NO_CHECK: + break; + case U64: + size += eq_u64_filter( + &filter[size], k, + entry->args[k].value.v64, 0, + offset); + n_checks++; + has_arg = true; + break; + case U32: filter[size++] = (struct sock_filter) LOAD((offsetof( struct seccomp_data, args[k]))); filter[size++] = (struct sock_filter)EQ( - (table[i].entry + j)->args[k], - 0, next_args_off - n_checks); + entry->args[k].value.v32, 0, + offset); n_checks++; has_arg = true; + break; + default: + fprintf(stderr, + "value for args not recognized\n"); + return -1; } } if (check_args_syscall_entry(table[i].entry)) diff --git a/cooker/filter.h b/cooker/filter.h index c8e74be..7705414 100644 --- a/cooker/filter.h +++ b/cooker/filter.h @@ -1,25 +1,53 @@ #ifndef FILTER_H_ #define FILTER_H_ +#include <stdint.h> + #include <linux/filter.h> #include <linux/audit.h> #include <linux/seccomp.h> +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define ENDIAN(_lo, _hi) _lo, _hi +#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) +#define HI_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + sizeof(__u32) +#define get_hi(x) ((uint32_t)((x) >> 32)) +#define get_lo(x) ((uint32_t)((x)&0xffffffff)) +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define ENDIAN(_lo, _hi) _hi, _lo +#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + sizeof(__u32) +#define HI_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) +#define get_lo(x) ((uint32_t)((x) >> 32)) +#define get_hi(x) ((uint32_t)((x)&0xffffffff)) +#else +#error "Unknown endianness" +#endif + #define JGE(nr, right, left) \ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, (nr), (right), (left)) #define JUMPA(jump) BPF_JUMP(BPF_JMP | BPF_JA, (jump), 0, 0) -#define EQ(nr, a1, a2) BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (nr), (a1), (a2)) +#define EQ(x, a1, a2) BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (x), (a1), (a2)) #define LOAD(x) BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (x)) - #define MAX_FILTER 1024 #define MAX_JUMPS 128 #define EMPTY -1 +enum arg_type { NO_CHECK, U32, U64 }; + +union arg_value { + uint32_t v32; + uint64_t v64; +}; + +struct arg { + enum arg_type type; + union arg_value value; +}; + struct bpf_call { char *name; - int args[6]; - bool check_arg[6]; + struct arg args[6]; }; struct syscall_entry { diff --git a/tests/unit/test_filter.c b/tests/unit/test_filter.c index c1e0949..4d7c9d8 100644 --- a/tests/unit/test_filter.c +++ b/tests/unit/test_filter.c @@ -6,6 +6,8 @@ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> +#include <stddef.h> +#include <fcntl.h> #include <sys/syscall.h> #include <sys/mman.h> #include <sys/resource.h> @@ -29,14 +31,26 @@ static int generate_install_filter(struct args_target *at) unsigned int size; for (i = 0; i < 6; i++) { - if (at->args[i] != NULL) { - calls[0].args[i] = (int)at->args[i]; - calls[0].check_arg[i] = true; - } else { - calls[0].check_arg[i] = false; + if (at->args[i] == NULL) { + calls[0].args[i].type = NO_CHECK; + continue; + } + switch (at->arg_type[i]) { + case U32: + calls[0].args[i].value.v32 = (uint32_t)at->args[i]; + calls[0].args[i].type = U32; + break; + case U64: + calls[0].args[i].value.v64 = (uint64_t)at->args[i]; + calls[0].args[i].type = U64; + break; + case NO_CHECK: + calls[0].args[i].type = NO_CHECK; + break; } } size = create_bfp_program(table, filter, 1); + bpf_disasm_all(filter, size); return install_filter(filter, size); } @@ -46,6 +60,7 @@ START_TEST(no_args) MAP_SHARED | MAP_ANONYMOUS, -1, 0); at->check_fd = false; at->nr = __NR_getpid; + set_args_no_check(at); at->install_filter = generate_install_filter; setup(); mock_syscall_target(); @@ -59,7 +74,9 @@ START_TEST(with_getsid) MAP_SHARED | MAP_ANONYMOUS, -1, 0); at->check_fd = false; at->nr = __NR_getsid; + set_args_no_check(at); at->args[0] = &id; + at->arg_type[0] = U32; at->install_filter = generate_install_filter; setup(); mock_syscall_target(); @@ -74,19 +91,59 @@ START_TEST(with_getpriority) MAP_SHARED | MAP_ANONYMOUS, -1, 0); at->check_fd = false; at->nr = __NR_getpriority; + set_args_no_check(at); at->args[0] = &which; + at->arg_type[0] = U32; at->args[1] = &who; + at->arg_type[0] = U32; + at->install_filter = generate_install_filter; + setup(); + mock_syscall_target(); +} +END_TEST + +static int target_lseek() +{ + int fd = open("/dev/zero", O_RDWR); + + /* Open the device on the target, but the arg0 isn't in the filter */ + ck_assert_int_ge(fd, 0); + at->args[0] = fd; + return target(); +} + +static void test_lseek(off_t offset) +{ + at = mmap(NULL, sizeof(struct args_target), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + at->check_fd = false; + at->nr = __NR_lseek; + at->target = target_lseek; + set_args_no_check(at); + at->args[1] = offset; + at->arg_type[1] = U64; at->install_filter = generate_install_filter; setup(); mock_syscall_target(); } + +START_TEST(with_lseek_lo) +{ + test_lseek(0x1); +} +END_TEST + +START_TEST(with_lseek_hi) +{ + test_lseek(0x0000000100000000); +} END_TEST Suite *op_call_suite(void) { Suite *s; int timeout = 30; - TCase *simple, *args32; + TCase *simple, *args32, *args64; s = suite_create("Test filter with target"); @@ -103,6 +160,13 @@ Suite *op_call_suite(void) tcase_add_test(args32, with_getpriority); suite_add_tcase(s, args32); + args64 = tcase_create("with args 64 bit"); + tcase_add_checked_fixture(args64, NULL, teardown); + tcase_set_timeout(args32, timeout); + tcase_add_test(args64, with_lseek_lo); + tcase_add_test(args64, with_lseek_hi); + suite_add_tcase(s, args64); + return s; } diff --git a/tests/unit/test_filter_build.c b/tests/unit/test_filter_build.c index 55e2a2b..4727e51 100644 --- a/tests/unit/test_filter_build.c +++ b/tests/unit/test_filter_build.c @@ -71,7 +71,8 @@ START_TEST(test_single_instr_two_args) { .name = "test1", .args = { 0, 123, 321, 0, 0, 0 }, - .check_arg = { false, true, true, false, false, false }, + .check_arg = { NO_CHECK, U32, U32, NO_CHECK, NO_CHECK, + NO_CHECK }, }, }; struct syscall_entry table[] = { @@ -198,12 +199,14 @@ START_TEST(test_multiple_instr_with_args) struct bpf_call calls[] = { { .name = "test1", .args = { 0, 123, 321, 0, 0, 0 }, - .check_arg = { false, true, true, false, false, false } }, + .check_arg = { NO_CHECK, U32, U32, NO_CHECK, NO_CHECK, + NO_CHECK } }, { .name = "test2" }, { .name = "test3" }, { .name = "test4", .args = { 0, 123, 321, 0, 0, 0 }, - .check_arg = { false, true, true, false, false, false } }, + .check_arg = { NO_CHECK, U32, U32, NO_CHECK, NO_CHECK, + NO_CHECK } }, { .name = "test5" }, }; struct syscall_entry table[] = { @@ -272,18 +275,22 @@ START_TEST(test_multiple_instance_same_instr) struct bpf_call calls[] = { { .name = "test1", .args = { 0, 123, 0, 0, 0, 0 }, - .check_arg = { false, true, false, false, false, false } }, + .check_arg = { NO_CHECK, U32, NO_CHECK, NO_CHECK, NO_CHECK, + NO_CHECK } }, { .name = "test1", .args = { 0, 0, 321, 0, 0, 0 }, - .check_arg = { false, false, true, false, false, false } }, + .check_arg = { NO_CHECK, NO_CHECK, U32, NO_CHECK, NO_CHECK, + NO_CHECK } }, { .name = "test2" }, { .name = "test3" }, { .name = "test4", .args = { 0, 123, 0, 0, 0, 0 }, - .check_arg = { false, true, false, false, false, false } }, + .check_arg = { NO_CHECK, U32, NO_CHECK, NO_CHECK, NO_CHECK, + NO_CHECK } }, { .name = "test4", .args = { 0, 0, 321, 0, 0, 0 }, - .check_arg = { false, false, true, false, false, false } }, + .check_arg = { NO_CHECK, NO_CHECK, U32, NO_CHECK, NO_CHECK, + NO_CHECK } }, { .name = "test5" }, }; struct syscall_entry table[] = { diff --git a/tests/unit/testutil.h b/tests/unit/testutil.h index dd4f1e9..7bb971a 100644 --- a/tests/unit/testutil.h +++ b/tests/unit/testutil.h @@ -5,8 +5,10 @@ #include <stdlib.h> #include <stdbool.h> #include <stddef.h> +#include <limits.h> #include <check.h> +#include "filter.h" #define STACK_SIZE (1024 * 1024 / 8) @@ -17,8 +19,10 @@ struct args_target { bool open_path; int fd; int nr; + enum arg_type arg_type[6]; void *args[6]; int (*install_filter)(struct args_target *at); + int (*target)(void *); }; extern struct seccomp_notif req; @@ -26,7 +30,7 @@ extern int notifyfd; extern struct args_target *at; extern int pipefd[2]; extern pid_t pid; -extern char path[100]; +extern char path[PATH_MAX]; extern uint16_t tmp_data[TMP_DATA_SIZE]; @@ -43,5 +47,6 @@ void teardown(); int install_notification_filter(struct args_target *at); void continue_target(); void mock_syscall_target(); +void set_args_no_check(struct args_target *at); #endif /* TESTUTIL_H */ diff --git a/tests/unit/util.c b/tests/unit/util.c index 5a1c5aa..7e5ec83 100644 --- a/tests/unit/util.c +++ b/tests/unit/util.c @@ -29,7 +29,7 @@ int notifyfd; struct args_target *at; int pipefd[2]; pid_t pid; -char path[] = "/tmp/test-seitan"; +char path[PATH_MAX] = "/tmp/test-seitan"; uint16_t tmp_data[TMP_DATA_SIZE]; int install_notification_filter(struct args_target *at) @@ -58,7 +58,7 @@ int target() at->ret = syscall(at->nr, at->args[0], at->args[1], at->args[2], at->args[3], at->args[4], at->args[5]); - at->err = errno; + at->err = errno; if (at->open_path) { if ((at->fd = open(path, O_CREAT | O_RDONLY)) < 0) { perror("open"); @@ -185,13 +185,21 @@ void mock_syscall_target() ck_assert_msg(ret == 0, strerror(errno)); } +void set_args_no_check(struct args_target *at) +{ + for (unsigned int i = 0; i < 6; i++) + at->arg_type[i] = NO_CHECK; +} + void setup() { int ret; signal(SIGCHLD, target_exit); ck_assert_int_ne(pipe(pipefd), -1); - pid = do_clone(target, NULL); + if (at->target == NULL) + at->target = target; + pid = do_clone(at->target, NULL); ck_assert_int_ge(pid, 0); /* Use write pipe to sync the target for checking the existance of the fd */ |