/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright 2023 Red Hat GmbH * Author: Alice Frosi */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "operations.h" #include "common/common.h" #include "testutil.h" #define MAX_TEST_PATH 250 void setup_without_fd() { at = mmap(NULL, sizeof(struct args_target), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); at->check_fd = false; at->nr = __NR_getpid; at->install_filter = install_notification_filter; setup(); } void setup_fd() { at = mmap(NULL, sizeof(struct args_target), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); at->check_fd = true; at->nr = __NR_getpid; at->install_filter = install_notification_filter; setup(); } void setup_path() { unlink(path); at = mmap(NULL, sizeof(struct args_target), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); at->open_path = true; at->nr = __NR_getpid; at->install_filter = install_notification_filter; setup(); } void setup_target_connect() { struct sockaddr_un addr; int fd; fd = socket(AF_UNIX, SOCK_STREAM, 0); ck_assert_int_ge(fd, 0); memset(&addr, 0, sizeof(addr)); strcpy(addr.sun_path, "/tmp/test.sock"); addr.sun_family = AF_UNIX; at = mmap(NULL, sizeof(struct args_target), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); at->check_fd = false; at->nr = __NR_connect; at->targs[0] = (void *)(long)fd; at->targs[1] = (void *)&addr; at->targs[2] = (void *)(long)(sizeof(char) * 108); at->install_filter = install_notification_filter; setup(); } START_TEST(test_op_continue) { ck_assert_msg(op_continue(&req, notifyfd, NULL, NULL) == 0, strerror(errno)); ck_assert_int_eq(at->err, 0); } END_TEST START_TEST(test_op_block) { struct op_block op = { -1 }; ck_assert_msg(op_block(&req, notifyfd, NULL, &op) == 0, strerror(errno)); /* * The tests use getpid that returns the error with ret and it is always * successful */ check_target_result(op.error, 1, false); } END_TEST static void test_op_return(enum gluten_offset_type type, uint16_t offset) { struct op_return op = { { type, offset } }; int64_t v = 2; ck_write_gluten(gluten, op.val, v); ck_assert_msg(op_return(&req, notifyfd, &gluten, &op) == 0, strerror(errno)); check_target_result(v, 0, false); } START_TEST(test_op_return_ro_data) { test_op_return(OFFSET_RO_DATA, 4); } END_TEST START_TEST(test_op_return_data) { test_op_return(OFFSET_DATA, 4); } END_TEST START_TEST(test_op_call) { long nr = __NR_getppid; struct op ops[] = { { OP_CALL, { .call = { .nr = { OFFSET_DATA, 0 } } } }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY } }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.call.nr, nr); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result(1, 0, true); } END_TEST START_TEST(test_op_call_ret) { long nr = __NR_getppid; long r; struct op ops[] = { { OP_CALL, { .call = { .nr = { OFFSET_DATA, 0 }, .ret = { OFFSET_DATA, sizeof(nr) }, .has_ret = true } } }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY }, }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.call.nr, nr); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result(1, 0, true); ck_read_gluten(gluten, ops[0].op.call.ret, r); ck_assert(r == getpid()); } END_TEST static void test_inject(enum gluten_offset_type type, bool atomic) { struct op_inject op = { { type, 0 }, { type, 4 } }; int test_fd = 3; int fd_inj; fd_inj = create_test_fd(); ck_assert_int_ge(fd_inj, 0); ck_write_gluten(gluten, op.old_fd, test_fd); ck_write_gluten(gluten, op.new_fd, fd_inj); if (atomic) op_inject(&req, notifyfd, &gluten, &op); else op_inject_a(&req, notifyfd, &gluten, &op); check_target_fd(pid, test_fd); } START_TEST(test_op_inject_a) { test_inject(OFFSET_RO_DATA, true); } END_TEST START_TEST(test_op_inject_a_ref) { test_inject(OFFSET_DATA, true); } END_TEST START_TEST(test_op_inject) { test_inject(OFFSET_RO_DATA, false); } END_TEST START_TEST(test_op_inject_ref) { test_inject(OFFSET_DATA, false); } END_TEST START_TEST(test_op_load) { struct op ops[] = { { OP_LOAD, { .load = { { OFFSET_SECCOMP_DATA, 1 }, { OFFSET_DATA, 0 }, sizeof(struct sockaddr_un) } } }, { OP_RETURN, { .ret = { { OFFSET_DATA, sizeof(struct sockaddr_un) } } } }, { OP_END, OP_EMPTY }, }; struct sockaddr_un addr; int v = 2; write_instr(gluten, ops); ck_write_gluten(gluten, ops[1].op.ret.val, v); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result(v, 0, false); ck_read_gluten(gluten, ops[0].op.load.dst, addr); ck_assert_str_eq(addr.sun_path, "/tmp/test.sock"); ck_assert(addr.sun_family == AF_UNIX); } END_TEST struct top_cmp_int_data_t { int a; int b; enum op_cmp_type cmp; }; struct top_cmp_int_data_t top_cmp_int_data[] = { { 1, 1, CMP_EQ }, { 1, 2, CMP_NE }, { 2, 1, CMP_GT }, { 1, 1, CMP_GE }, { 1, 2, CMP_LT }, { 1, 1, CMP_LE }, }; START_TEST(test_op_cmp_int) { enum op_cmp_type cmp = top_cmp_int_data[_i].cmp; int a = top_cmp_int_data[_i].a; int b = top_cmp_int_data[_i].b; int jmp = 2; struct op ops[] = { { OP_CMP, { .cmp = { { OFFSET_DATA, 0 }, { OFFSET_DATA, 10 }, sizeof(int), cmp, { OFFSET_RO_DATA, 0 } } } }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY }, { OP_BLOCK, { .block = { -1 } } }, { OP_END, OP_EMPTY }, }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.cmp.x, a); ck_write_gluten(gluten, ops[0].op.cmp.y, b); ck_write_gluten(gluten, ops[0].op.cmp.jmp, jmp); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result_nonegative(); } START_TEST(test_op_cmp_string_eq) { char s1[30] = "Hello Test!!"; char s2[30] = "Hello Test!!"; int jmp = 3; struct op ops[] = { { OP_CMP, { .cmp = { { OFFSET_DATA, 0 }, { OFFSET_DATA, 30 }, sizeof(s1), CMP_EQ, { OFFSET_RO_DATA, 0 } } } }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY }, { OP_BLOCK, { .block = { -1 } } }, { OP_END, OP_EMPTY }, { 0 }, }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.cmp.x, s1); ck_write_gluten(gluten, ops[0].op.cmp.y, s2); ck_write_gluten(gluten, ops[0].op.cmp.jmp, jmp); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result_nonegative(); } END_TEST START_TEST(test_op_cmp_string_false) { char s1[30] = "Hello Test!!"; char s2[30] = "Hello Tost!!"; int jmp = 3; struct op ops[] = { { OP_CMP, { .cmp = { { OFFSET_DATA, 0 }, { OFFSET_DATA, 30 }, sizeof(s1), CMP_EQ, { OFFSET_RO_DATA, 0 } } } }, { OP_BLOCK, { .block = { -1 } } }, { OP_END, OP_EMPTY }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY }, { 0 }, }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.cmp.x, s1); ck_write_gluten(gluten, ops[0].op.cmp.y, s2); ck_write_gluten(gluten, ops[0].op.cmp.jmp, jmp); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result_nonegative(); } END_TEST START_TEST(test_op_resolvedfd_eq) { struct op ops[] = { { OP_RESOLVEDFD, { .resfd = { { OFFSET_DATA, 0 }, { OFFSET_DATA, 4 }, sizeof(path), 3 } } }, { OP_BLOCK, { .block = { -1 } } }, { OP_END, OP_EMPTY }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY }, { 0 }, }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.resfd.fd, at->fd); ck_write_gluten(gluten, ops[0].op.resfd.path, path); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result(-1, 1, false); } END_TEST START_TEST(test_op_resolvedfd_neq) { char path2[] = "/tmp/seitan-test-wrong"; struct op ops[] = { { OP_RESOLVEDFD, { .resfd = { { OFFSET_DATA, 0 }, { OFFSET_DATA, 4 }, sizeof(path), 3 } } }, { OP_BLOCK, { .block = { -1 } } }, { OP_END, OP_EMPTY }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY }, }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.resfd.fd, at->fd); ck_write_gluten(gluten, ops[0].op.resfd.path, path2); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result(-1, 1, false); } END_TEST START_TEST(test_op_nr) { long nr = __NR_getpid; int jmp = 3; struct op ops[] = { { OP_NR, { .nr = { { OFFSET_RO_DATA, 0 }, { OFFSET_RO_DATA, sizeof(nr) } } } }, { OP_BLOCK, { .block = { -1 } } }, { OP_END, OP_EMPTY }, { OP_CONT, OP_EMPTY }, { OP_END, OP_EMPTY }, }; write_instr(gluten, ops); ck_write_gluten(gluten, ops[0].op.nr.nr, nr); ck_write_gluten(gluten, ops[0].op.nr.no_match, jmp); ck_assert_int_eq(eval(&gluten, &req, notifyfd), 0); check_target_result_nonegative(); } START_TEST(test_op_copy) { int a[] = { 1, 2, 3, 4, 5, 6 }; int b[ARRAY_SIZE(a)]; struct op_copy op = { { OFFSET_DATA, 30 }, { OFFSET_DATA, 0 }, sizeof(a) }; ck_write_gluten(gluten, op.src, a); ck_assert_msg(op_copy(&req, notifyfd, &gluten, &op) == 0, strerror(errno)); ck_read_gluten(gluten, op.dst, b); ck_assert_mem_eq(a, b, sizeof(a)); } END_TEST Suite *op_call_suite(void) { Suite *s; int timeout = 30; TCase *cont, *block, *ret, *call, *resolvedfd; TCase *cmp, *cmpint; TCase *inject, *inject_a; TCase *load, *nr, *copy; s = suite_create("Perform ops"); cont = tcase_create("op_continue"); tcase_add_checked_fixture(cont, setup_without_fd, teardown); tcase_set_timeout(cont, timeout); tcase_add_test(cont, test_op_continue); suite_add_tcase(s, cont); block = tcase_create("op_block"); tcase_add_checked_fixture(block, setup_without_fd, teardown); tcase_set_timeout(block, timeout); tcase_add_test(block, test_op_block); suite_add_tcase(s, block); ret = tcase_create("op_return"); tcase_add_checked_fixture(ret, setup_without_fd, teardown); tcase_set_timeout(ret, timeout); tcase_add_test(ret, test_op_return_ro_data); tcase_add_test(ret, test_op_return_data); suite_add_tcase(s, ret); call = tcase_create("op_call"); tcase_add_checked_fixture(call, setup_without_fd, teardown); tcase_set_timeout(call, timeout); tcase_add_test(call, test_op_call); tcase_add_test(call, test_op_call_ret); suite_add_tcase(s, call); inject = tcase_create("op_inject"); tcase_add_checked_fixture(inject, setup_fd, teardown); tcase_set_timeout(inject, timeout); tcase_add_test(inject, test_op_inject); tcase_add_test(inject, test_op_inject_ref); suite_add_tcase(s, inject); inject_a = tcase_create("op_inject_a"); tcase_add_checked_fixture(inject_a, setup_fd, teardown); tcase_set_timeout(inject_a, timeout); tcase_add_test(inject_a, test_op_inject_a); tcase_add_test(inject_a, test_op_inject_a_ref); suite_add_tcase(s, inject_a); load = tcase_create("op_load"); tcase_add_checked_fixture(load, setup_target_connect, teardown); tcase_set_timeout(load, 120); tcase_add_test(load, test_op_load); suite_add_tcase(s, load); cmp = tcase_create("op_cmp"); tcase_add_checked_fixture(cmp, setup_without_fd, teardown); tcase_set_timeout(cmp, timeout); tcase_add_test(cmp, test_op_cmp_string_eq); tcase_add_test(cmp, test_op_cmp_string_false); suite_add_tcase(s, cmp); cmpint = tcase_create("op_cmp_int"); tcase_add_checked_fixture(cmpint, setup_without_fd, teardown); tcase_set_timeout(cmpint, timeout); tcase_add_loop_test(cmpint, test_op_cmp_int, 0, ARRAY_SIZE(top_cmp_int_data)); suite_add_tcase(s, cmpint); resolvedfd = tcase_create("op_resolvedfd"); tcase_add_checked_fixture(resolvedfd, setup_path, teardown); tcase_set_timeout(resolvedfd, timeout); tcase_add_test(resolvedfd, test_op_resolvedfd_eq); tcase_add_test(resolvedfd, test_op_resolvedfd_neq); suite_add_tcase(s, resolvedfd); nr = tcase_create("op_nr"); tcase_add_checked_fixture(nr, setup_without_fd, teardown); tcase_set_timeout(nr, timeout); tcase_add_test(nr, test_op_nr); suite_add_tcase(s, nr); copy = tcase_create("op_copy"); tcase_add_test(copy, test_op_copy); suite_add_tcase(s, copy); 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; }