From 977ffec509a95efeca2b7c508912d5d1df7cd677 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Wed, 22 Feb 2023 15:52:54 +0100 Subject: Rename actions to operations Replace all the action related names to operations to make them more generic. --- tests/unit/test_operations.c | 438 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 tests/unit/test_operations.c (limited to 'tests/unit/test_operations.c') diff --git a/tests/unit/test_operations.c b/tests/unit/test_operations.c new file mode 100644 index 0000000..6c5952f --- /dev/null +++ b/tests/unit/test_operations.c @@ -0,0 +1,438 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../gluten.h" +#include "../../operations.h" +#include "../../common.h" + +struct args_target { + long ret; + int err; + bool check_fd; + int fd; +}; + +struct seccomp_notif req; +int notifyfd; +struct args_target *at; +int pipefd[2]; +int nr = __NR_getpid; +pid_t pid; + +uint16_t tmp_data[TMP_DATA_SIZE]; + +static int install_notification_filter() +{ + int fd; + /* filter a single syscall for the tests */ + struct sock_filter filter[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, + (offsetof(struct seccomp_data, arch))), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SEITAN_AUDIT_ARCH, 0, 3), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, + (offsetof(struct seccomp_data, nr))), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF), + BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), + }; + struct sock_fprog prog; + + prog.filter = filter; + prog.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])); + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) { + perror("prctl"); + return -1; + } + if ((fd = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, + SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog)) < 0) { + perror("seccomp"); + return -1; + } + return fd; +} + +static int create_test_fd() +{ + return open("/tmp", O_RDWR | O_TMPFILE); +} + +static int target() +{ + int buf = 0; + if (install_notification_filter() < 0) { + return -1; + } + + at->ret = getpid(); + at->err = errno; + if (at->check_fd) + read(pipefd[0], &buf, 1); + + close(pipefd[0]); + + write(pipefd[1], &buf, 1); + close(pipefd[1]); + exit(0); +} + +static pid_t do_clone(int (*fn)(void *), void *arg) +{ + char stack[STACK_SIZE]; + pid_t child; + int flags = SIGCHLD; + + child = clone(fn, stack + sizeof(stack), flags, arg); + if (child == -1) { + perror("clone"); + return -1; + } + return child; +} + +int get_fd_notifier(pid_t pid) +{ + char path[PATH_MAX + 1]; + int pidfd, notifier; + int fd = -1; + + snprintf(path, sizeof(path), "/proc/%d/fd", pid); + while (fd < 0) { + sleep(2); + fd = find_fd_seccomp_notifier(path); + } + ck_assert_int_ge(fd, 0); + pidfd = syscall(SYS_pidfd_open, pid, 0); + ck_assert_msg(pidfd >= 0, strerror(errno)); + sleep(1); + notifier = syscall(SYS_pidfd_getfd, pidfd, fd, 0); + ck_assert_msg(notifier >= 0, strerror(errno)); + return notifier; +} + +static void check_target_result(long ret, int err, bool ignore_ret) +{ + int buf; + + read(pipefd[0], &buf, 1); + if (!ignore_ret) + ck_assert_msg(at->ret == ret, + "expect return value %ld to be equal to %ld", + at->ret, ret); + ck_assert_int_eq(at->err, err); + ck_assert_int_eq(close(pipefd[0]), 0); +} + +void target_exit() +{ + int status; + + waitpid(-1, &status, WUNTRACED | WNOHANG); + if (WEXITSTATUS(status) != 0) { + fprintf(stderr, "target process exited with an error\n"); + exit(-1); + } +} + +static bool has_fd(int pid, int fd) +{ + char path[PATH_MAX + 1]; + + snprintf(path, sizeof(path), "/proc/%d/fd/%d", pid, fd); + return access(path, F_OK) == 0; +} + +static void check_target_fd(int pid, int fd) +{ + int buf = 0; + + ck_assert(has_fd(pid, fd)); + write(pipefd[1], &buf, 1); + close(pipefd[1]); +} + +void setup(bool check_fd) +{ + int ret; + + signal(SIGCHLD, target_exit); + ck_assert_int_ne(pipe(pipefd), -1); + at = mmap(NULL, sizeof(struct args_target), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + at->check_fd = check_fd; + pid = do_clone(target, NULL); + ck_assert_int_ge(pid, 0); + + /* Use write pipe to sync the target for checking the existance of the fd */ + if (!check_fd) + ck_assert_int_ne(close(pipefd[1]), -1); + + notifyfd = get_fd_notifier(pid); + + memset(&req, 0, sizeof(req)); + ret = ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_RECV, &req); + ck_assert_msg(ret == 0, strerror(errno)); + ck_assert_msg((req.data).nr == nr, "filter syscall nr: %d", + (req.data).nr); +} + +void teardown() +{ + if (at != NULL) + munmap(at, sizeof(struct args_target)); +} + +void setup_without_fd() +{ + setup(false); +} +void setup_fd() +{ + setup(true); +} + +START_TEST(test_act_continue) +{ + struct op operations[] = { + { .type = OP_CONT }, + }; + int ret = do_operations(NULL, operations, + sizeof(operations) / sizeof(operations[0]), -1, notifyfd, + req.id); + ck_assert_msg(ret == 0, strerror(errno)); + ck_assert_int_eq(at->err, 0); +} +END_TEST + +START_TEST(test_act_block) +{ + struct op operations[] = { + { + .type = OP_BLOCK, + .block = { .error = -1 }, + }, + }; + int ret = do_operations(NULL, operations, + sizeof(operations) / sizeof(operations[0]), -1, notifyfd, + req.id); + ck_assert_msg(ret == 0, strerror(errno)); + check_target_result(-1, 0, false); +} +END_TEST + +START_TEST(test_act_return) +{ + struct op operations[] = { + { + .type = OP_RETURN, + .ret = { .type = IMMEDIATE, .value = 1 }, + }, + }; + int ret = do_operations(NULL, operations, + sizeof(operations) / sizeof(operations[0]), -1, notifyfd, + req.id); + ck_assert_msg(ret == 0, strerror(errno)); + check_target_result(1, 0, false); +} +END_TEST + +START_TEST(test_act_return_ref) +{ + int64_t v = 2; + uint16_t offset = 4; + struct op operations[] = { + { + .type = OP_RETURN, + .ret = { .type = REFERENCE, .value_off = offset }, + }, + }; + memcpy((uint16_t *)&tmp_data + offset, &v, sizeof(v)); + + int ret = do_operations(&tmp_data, operations, + sizeof(operations) / sizeof(operations[0]), -1, notifyfd, + req.id); + ck_assert_msg(ret == 0, strerror(errno)); + check_target_result(v, 0, false); +} +END_TEST + +START_TEST(test_act_call) +{ + struct op operations[] = { + { + .type = OP_CALL, + .call = { .nr = __NR_getppid, .has_ret = false }, + }, + { .type = OP_CONT }, + }; + int ret = do_operations(NULL, operations, + sizeof(operations) / sizeof(operations[0]), -1, notifyfd, + req.id); + ck_assert_msg(ret == 0, strerror(errno)); + check_target_result(1, 0, true); +} +END_TEST + +START_TEST(test_act_call_ret) +{ + struct op operations[] = { + { + .type = OP_CALL, + .call = { .nr = __NR_getppid, + .has_ret = true, + .ret_off = 2 }, + }, + { .type = OP_CONT }, + }; + int ret = do_operations(&tmp_data, operations, + sizeof(operations) / sizeof(operations[0]), -1, notifyfd, + req.id); + long r; + ck_assert_msg(ret == 0, strerror(errno)); + check_target_result(1, 0, true); + memcpy(&r, &tmp_data[2], sizeof(r)); + ck_assert_int_eq(r, getpid()); +} +END_TEST + +static void test_inject(struct op operations[], int n, bool reference) +{ + uint16_t new_off = 2, old_off = 4; + int fd_inj; + int test_fd = 3; + int ret; + + fd_inj = create_test_fd(); + ck_assert_int_ge(fd_inj, 0); + if (reference) { + memcpy((uint16_t *)&tmp_data + new_off, &fd_inj, + sizeof(fd_inj)); + memcpy((uint16_t *)&tmp_data + old_off, &test_fd, + sizeof(test_fd)); + + operations[0].inj.newfd.fd_off = new_off; + operations[0].inj.newfd.type = REFERENCE; + operations[0].inj.oldfd.fd_off = old_off; + operations[0].inj.oldfd.type = REFERENCE; + } else { + operations[0].inj.newfd.fd = fd_inj; + operations[0].inj.newfd.type = IMMEDIATE; + operations[0].inj.oldfd.fd = test_fd; + operations[0].inj.oldfd.type = IMMEDIATE; + } + + ret = do_operations(&tmp_data, operations, n, -1, notifyfd, req.id); + ck_assert_msg(ret == 0, strerror(errno)); + check_target_fd(pid, test_fd); +} + +START_TEST(test_act_inject_a) +{ + struct op operations[] = { { .type = OP_INJECT_A } }; + test_inject(operations, sizeof(operations) / sizeof(operations[0]), false); +} +END_TEST + +START_TEST(test_act_inject_a_ref) +{ + struct op operations[] = { { .type = OP_INJECT_A } }; + test_inject(operations, sizeof(operations) / sizeof(operations[0]), true); +} +END_TEST + +START_TEST(test_act_inject) +{ + struct op operations[] = { { .type = OP_INJECT } }; + test_inject(operations, sizeof(operations) / sizeof(operations[0]), false); +} +END_TEST + +START_TEST(test_act_inject_ref) +{ + struct op operations[] = { { .type = OP_INJECT } }; + test_inject(operations, sizeof(operations) / sizeof(operations[0]), true); +} +END_TEST + +Suite *op_call_suite(void) +{ + Suite *s; + int timeout = 30; + TCase *cont, *block, *ret, *call; + TCase *inject, *inject_a; + + s = suite_create("Perform operations"); + + cont = tcase_create("a_continue"); + tcase_add_checked_fixture(cont, setup_without_fd, teardown); + tcase_set_timeout(cont, timeout); + tcase_add_test(cont, test_act_continue); + suite_add_tcase(s, cont); + + ret = tcase_create("a_return"); + tcase_add_checked_fixture(ret, setup_without_fd, teardown); + tcase_set_timeout(ret, timeout); + tcase_add_test(ret, test_act_return); + tcase_add_test(ret, test_act_return_ref); + suite_add_tcase(s, ret); + + block = tcase_create("a_block"); + tcase_add_checked_fixture(block, setup_without_fd, teardown); + tcase_set_timeout(block, timeout); + tcase_add_test(block, test_act_block); + suite_add_tcase(s, block); + + call = tcase_create("a_call"); + tcase_add_checked_fixture(call, setup_without_fd, teardown); + tcase_set_timeout(call, timeout); + tcase_add_test(call, test_act_call); + tcase_add_test(call, test_act_call_ret); + suite_add_tcase(s, call); + + inject = tcase_create("a_inject"); + tcase_add_checked_fixture(inject, setup_fd, teardown); + tcase_set_timeout(inject, timeout); + tcase_add_test(inject, test_act_inject); + tcase_add_test(inject, test_act_inject_ref); + suite_add_tcase(s, inject); + + inject_a = tcase_create("a_inject_a"); + tcase_add_checked_fixture(inject_a, setup_fd, teardown); + tcase_set_timeout(inject_a, timeout); + tcase_add_test(inject_a, test_act_inject_a); + tcase_add_test(inject_a, test_act_inject_a_ref); + suite_add_tcase(s, inject_a); + + return s; +} + +int main(void) +{ + int no_failed = 0; + Suite *s; + SRunner *runner; + + s = op_call_suite(); + runner = srunner_create(s); + + srunner_run_all(runner, CK_VERBOSE); + no_failed = srunner_ntests_failed(runner); + srunner_free(runner); + return (no_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} -- cgit v1.2.3