From 0a4e4850f9f17a6b67caa62700ade0f2053e2701 Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 17 Jan 2022 22:42:26 +0100 Subject: [PATCH 01/16] Introduce exile_vows_from_str() --- exile.h | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.c | 13 ++++++++++ 2 files changed, 88 insertions(+) diff --git a/exile.h b/exile.h index d732c76..66895c3 100644 --- a/exile.h +++ b/exile.h @@ -245,6 +245,12 @@ struct syscall_vow_map uint64_t vowmask; }; +struct str_to_vow_map +{ + char *str; + uint64_t value; +}; + struct exile_path_policy { const char *path; @@ -644,6 +650,73 @@ static struct syscall_vow_map exile_vow_map[] = {EXILE_SYS(futex_waitv), EXILE_SYSCALL_VOW_THREAD} }; +struct str_to_vow_map str_to_vow_map[] = +{ + { "chown", EXILE_SYSCALL_VOW_CHOWN}, + { "clone", EXILE_SYSCALL_VOW_CLONE}, + { "cpath", EXILE_SYSCALL_VOW_CPATH}, + { "dpath", EXILE_SYSCALL_VOW_DPATH}, + { "exec", EXILE_SYSCALL_VOW_EXEC}, + { "fattr", EXILE_SYSCALL_VOW_FATTR}, + { "fsnotify", EXILE_SYSCALL_VOW_FSNOTIFY}, + { "id", EXILE_SYSCALL_VOW_ID}, + { "inet", EXILE_SYSCALL_VOW_INET}, + { "ioctl", EXILE_SYSCALL_VOW_IOCTL}, + { "prctl", EXILE_SYSCALL_VOW_PRCTL}, + { "proc", EXILE_SYSCALL_VOW_PROC}, + { "prot_exec", EXILE_SYSCALL_VOW_PROT_EXEC}, + { "rpath", EXILE_SYSCALL_VOW_RPATH}, + { "sched", EXILE_SYSCALL_VOW_SCHED}, + { "seccomp_install", EXILE_SYSCALL_VOW_SECCOMP_INSTALL}, + { "shm", EXILE_SYSCALL_VOW_SHM}, + { "stdio", EXILE_SYSCALL_VOW_STDIO}, + { "thread", EXILE_SYSCALL_VOW_THREAD}, + { "unix", EXILE_SYSCALL_VOW_UNIX}, + { "wpath", EXILE_SYSCALL_VOW_WPATH}, + { "error", EXILE_SYSCALL_VOW_DENY_ERROR} +}; + +/* Converts the whitespace separated vows strings to vows flags + * + * This mainly helps readability, as lots of flags ORed together is not + * very readable. + * + * If an unkown string is found, abort() is called. + */ +uint64_t exile_vows_from_str(const char *str) +{ + uint64_t result = 0; + char current[64] = { 0 }; + char *ptr = current; + const char *end = ptr + sizeof(current)-1; + do + { + while(ptr <= end && *str != '\0' && *str != ' ') + { + *ptr = *str; + ++ptr; + ++str; + } + int found = 0; + for(int i = 0; i < sizeof(str_to_vow_map)/sizeof(str_to_vow_map[0]); i++) + { + if(strcmp(str_to_vow_map[i].str, current) == 0) + { + result |= str_to_vow_map[i].value; + found = 1; + break; + } + } + if(!found) + { + EXILE_LOG_ERROR("No such vow: %s\n", current); + abort(); + } + memset(current, 0, sizeof(current)); + ptr = current; + } while(*str++ != '\0'); + return result; +} static int is_valid_syscall_policy(unsigned int policy) { @@ -1999,6 +2072,8 @@ int exile_vow(uint64_t promises) return ret; } + + struct exile_launch_params { struct exile_policy *policy; /* Policy to activate before jumping to func */ diff --git a/test.c b/test.c index ad58f1b..3bcb065 100644 --- a/test.c +++ b/test.c @@ -625,6 +625,18 @@ int test_launch_get() return 0; } +int test_vows_from_str() +{ + uint64_t expected = EXILE_SYSCALL_VOW_CHOWN | EXILE_SYSCALL_VOW_WPATH | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_DENY_ERROR; + uint64_t actual = exile_vows_from_str("chown wpath inet error"); + if(expected != actual) + { + printf("Masks don't match: %lu vs %lu\n", expected, actual); + return 1; + } + return 0; +} + struct dispatcher { char *name; @@ -651,6 +663,7 @@ struct dispatcher dispatchers[] = { { "failflags", &test_fail_flags}, { "launch", &test_launch}, { "launch-get", &test_launch_get}, + { "vow_from_str", &test_vows_from_str}, }; int main(int argc, char *argv[]) -- 2.46.2 From 29b5864dd3f6ee554e28e6662b0b23871d2b983f Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 17 Jan 2022 22:48:29 +0100 Subject: [PATCH 02/16] test: Introduce LOG(), avoid inconsistent printf/fprintf --- test.c | 88 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/test.c b/test.c index 3bcb065..58a099a 100644 --- a/test.c +++ b/test.c @@ -6,12 +6,14 @@ #include #include +#define LOG(...) do { fprintf(stdout, "%s(): ", __func__); fprintf(stdout, __VA_ARGS__); } while(0) + int xexile_enable_policy(struct exile_policy *policy) { int ret = exile_enable_policy(policy); if(ret != 0) { - fprintf(stderr, "exile_enable_policy() failed: %i\n", ret); + LOG("failed: %i\n", ret); exit(EXIT_FAILURE); } return 0; @@ -38,16 +40,16 @@ static int test_expected_kill(int (*f)()) int c = WTERMSIG(status); if(c == SIGSYS) { - printf("Got expected signal\n"); + LOG("Got expected signal\n"); return 0; } - printf("Unexpected status code: %i\n", c); + LOG("Unexpected status code: %i\n", c); return 1; } else { int c = WEXITSTATUS(status); - printf("Process was not killed, test fails. Status code of exit: %i\n", c); + LOG("Process was not killed, test fails. Status code of exit: %i\n", c); return 1; } return 0; @@ -67,7 +69,7 @@ static int test_successful_exit(int (*f)()) if(WIFSIGNALED(status)) { int c = WTERMSIG(status); - printf("Received signal, which was not expected. Signal was: %i\n", c); + LOG("Received signal, which was not expected. Signal was: %i\n", c); return 1; } else @@ -75,11 +77,11 @@ static int test_successful_exit(int (*f)()) int c = WEXITSTATUS(status); if(c != 0) { - printf("Process failed to exit properly. Status code is: %i\n", c); + LOG("Process failed to exit properly. Status code is: %i\n", c); } return c; } - printf("Process exited sucessfully as expected"); + LOG("Process exited sucessfully as expected"); return 0; } @@ -153,7 +155,7 @@ int test_seccomp_require_last_matchall() int status = exile_enable_policy(policy); if(status == 0) { - printf("Failed. Should not have been enabled!"); + LOG("Failed. Should not have been enabled!"); return 1; } return 0; @@ -170,7 +172,7 @@ static int do_test_seccomp_errno() uid_t id = syscall(EXILE_SYS(getuid)); int fd = syscall(EXILE_SYS(close), 0); - printf("close() return code: %i, errno: %s\n", fd, strerror(errno)); + LOG("close() return code: %i, errno: %s\n", fd, strerror(errno)); return fd == -1 ? 0 : 1; } @@ -254,14 +256,14 @@ int test_seccomp_argfilter_mixed() int s = (int) syscall(EXILE_SYS(stat), "/dev/urandom", &statbuf); if(s != -1) { - printf("Failed: stat was expected to fail, but returned %i\n", s); + LOG("Failed: stat was expected to fail, but returned %i\n", s); return 1; } pid_t p = (pid_t) syscall(EXILE_SYS(getpid)); if(p != -1) { - printf("Failed: getpid was expected to fail, but returned %i\n", p); + LOG("Failed: getpid was expected to fail, but returned %i\n", p); return 1; } @@ -269,13 +271,13 @@ int test_seccomp_argfilter_mixed() int ret = (int) syscall(EXILE_SYS(open),t, O_WRONLY); if(ret != -1) { - printf("Failed: open was expected to fail, but returned %i\n", ret); + LOG("Failed: open was expected to fail, but returned %i\n", ret); return 1; } ret = (int) syscall(EXILE_SYS(open), t, O_RDONLY); if(ret == -1) { - printf("Failed: open with O_RDONLY was expected to succeed, but returned %i\n", ret); + LOG("Failed: open with O_RDONLY was expected to succeed, but returned %i\n", ret); return 1; } return 0; @@ -291,13 +293,13 @@ int do_test_seccomp_vow_socket() int s = socket(AF_INET, SOCK_STREAM, 0); if(s == -1) { - printf("Failed: socket was expected to succeed, but returned %i\n", s); + LOG("Failed: socket was expected to succeed, but returned %i\n", s); return 1; } s = socket(AF_UNIX, SOCK_DGRAM, 0); if(s != -1) { - printf("Failed: socket was expected to fail, but returned %i\n", s); + LOG("Failed: socket was expected to fail, but returned %i\n", s); return 1; } return 0; @@ -312,19 +314,19 @@ int do_test_seccomp_vow_open() int ret = open("/dev/urandom", O_WRONLY | O_APPEND); if(ret != -1) { - printf("Failed: open was expected to fail, but returned %i\n", ret); + LOG("Failed: open was expected to fail, but returned %i\n", ret); return 1; } ret = open("/dev/urandom", O_RDWR); if(ret != -1) { - printf("Failed: open O_RDWR was expected to fail, but returned %i\n", ret); + LOG("Failed: open O_RDWR was expected to fail, but returned %i\n", ret); return 1; } ret = open("/dev/urandom", O_RDONLY); if(ret == -1) { - printf("Failed: open was expected to succceed, but returned %i\n", ret); + LOG("Failed: open was expected to succceed, but returned %i\n", ret); return 1; } return 0; @@ -335,13 +337,13 @@ int test_seccomp_vow() int ret = test_successful_exit(&do_test_seccomp_vow_open); if(ret != 0) { - printf("Failed: do_test_seccomp_vow_open()\n"); + LOG("Failed: do_test_seccomp_vow_open()\n"); return 1; } ret = test_successful_exit(&do_test_seccomp_vow_socket); if(ret != 0) { - printf("Failed: do_test_seccomp_vow_socket()\n"); + LOG("Failed: do_test_seccomp_vow_socket()\n"); return 1; } return 0; @@ -353,13 +355,13 @@ int test_seccomp_exile_vow_multiple() int ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_UNIX | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR); if(ret != 0) { - printf("Failed: exile_vow() call 1 failed\n"); + LOG("Failed: exile_vow() call 1 failed\n"); return 1; } int s = socket(AF_UNIX, SOCK_STREAM, 0); if(s == -1) { - printf("Failed: socket was expected to succeed, but returned %i\n", s); + LOG("Failed: socket was expected to succeed, but returned %i\n", s); return 1; } @@ -367,13 +369,13 @@ int test_seccomp_exile_vow_multiple() ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR); if(ret != 0) { - printf("Failed: exile_vow() call 2 failed\n"); + LOG("Failed: exile_vow() call 2 failed\n"); return 1; } s = socket(AF_UNIX, SOCK_STREAM, 0); if(s != -1) { - printf("Failed: socket was expected to fail, but returned %i\n", s); + LOG("Failed: socket was expected to fail, but returned %i\n", s); return 1; } @@ -381,13 +383,13 @@ int test_seccomp_exile_vow_multiple() ret = exile_vow(EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_UNIX | EXILE_SYSCALL_VOW_SECCOMP_INSTALL | EXILE_SYSCALL_VOW_DENY_ERROR); if(ret != 0) { - printf("Failed: exile_vow() call 3 failed\n"); + LOG("Failed: exile_vow() call 3 failed\n"); return 1; } s = socket(AF_UNIX, SOCK_STREAM, 0); if(s != -1) { - printf("Failed: socket was still expected to fail, but returned %i\n", s); + LOG("Failed: socket was still expected to fail, but returned %i\n", s); return 1; } @@ -400,7 +402,7 @@ int test_landlock() { if(!exile_landlock_is_available()) { - printf("landlock not available, so cannot test\n"); + LOG("landlock not available, so cannot test\n"); return 1; } struct exile_policy *policy = exile_init_policy(); @@ -449,14 +451,14 @@ int test_nofs() int s = socket(AF_INET,SOCK_STREAM,0); if(s == -1) { - fprintf(stderr, "Failed to open socket but this was not requested by policy\n"); + LOG("Failed to open socket but this was not requested by policy\n"); return 1; } /* Expect seccomp to take care of this */ if(open("/test", O_CREAT | O_WRONLY) >= 0) { - fprintf(stderr, "Failed: We do not expect write access\n"); + LOG("Failed: We do not expect write access\n"); return 1; } @@ -472,14 +474,14 @@ int test_no_new_fds() if(open("/tmp/test", O_CREAT | O_WRONLY) >= 0) { - fprintf(stderr, "Failed: Could open new file descriptor\n"); + LOG("Failed: Could open new file descriptor\n"); return -1; } int s = socket(AF_INET,SOCK_STREAM,0); if(s >= 0) { - fprintf(stderr, "Failed: socket got opened but policy denied\n"); + LOG("Failed: socket got opened but policy denied\n"); return -1; } @@ -495,13 +497,13 @@ int test_mkpath() int ret = mkpath(filepath, 0700, 1); if(ret != 0) { - fprintf(stderr, "Failed: mkpath(file) returned: %i\n", ret); + LOG("Failed: mkpath(file) returned: %i\n", ret); return 1; } ret = mkpath(dirpath, 0700, 0); if(ret != 0) { - fprintf(stderr, "Failed: mkpath(dirpath) returned: %i\n", ret); + LOG("Failed: mkpath(dirpath) returned: %i\n", ret); return 1; } @@ -509,23 +511,23 @@ int test_mkpath() ret = stat(filepath, &statbuf); if(ret != 0) { - fprintf(stderr, "Failed: stat on filepath returned: %i\n", ret); + LOG("Failed: stat on filepath returned: %i\n", ret); return 1; } if(!S_ISREG(statbuf.st_mode)) { - fprintf(stderr, "Failed: mkpath did not create a file: %i\n", ret); + LOG("Failed: mkpath did not create a file: %i\n", ret); return 1; } ret = stat(dirpath, &statbuf); if(ret != 0) { - fprintf(stderr, "Failed: stat on dirpath returned: %i\n", ret); + LOG("Failed: stat on dirpath returned: %i\n", ret); return 1; } if(!S_ISDIR(statbuf.st_mode)) { - fprintf(stderr, "Failed: mkpath did not create a directory: %i\n", ret); + LOG("Failed: mkpath did not create a directory: %i\n", ret); return 1; } system("rm -rf /tmp/.exile.h/"); @@ -569,7 +571,7 @@ int test_launch() int launchfd = exile_launch(¶ms, &res); if(launchfd < 0) { - printf("Failed to launch\n"); + LOG("Failed to launch\n"); return 1; } @@ -577,11 +579,11 @@ int test_launch() write(res.write_fd, "1234", 4); int s = read(res.read_fd, buffer, sizeof(buffer)-1); write(1, buffer, s); - printf("Before wait, got: %i\n", s); + LOG("Before wait, got: %i\n", s); fflush(stdout); if(strstr(buffer, "Echoing: 1234") == NULL) { - printf("Failed: Did not get back what we wrote\n"); + LOG("Failed: Did not get back what we wrote\n"); } int status = 0; waitpid(res.tid, &status, __WALL); @@ -614,12 +616,12 @@ int test_launch_get() unsigned int len = strlen(LAUNCH_GET_TEST_STR); if(n != strlen(LAUNCH_GET_TEST_STR)) { - printf("Lenght does does not match: %lu vs %u\n", n, len); + LOG("Lenght does does not match: %lu vs %u\n", n, len); return 1; } if(strcmp(content, LAUNCH_GET_TEST_STR) != 0) { - printf("Received content differs\n"); + LOG("Received content differs\n"); return 1; } return 0; @@ -631,7 +633,7 @@ int test_vows_from_str() uint64_t actual = exile_vows_from_str("chown wpath inet error"); if(expected != actual) { - printf("Masks don't match: %lu vs %lu\n", expected, actual); + LOG("Masks don't match: %lu vs %lu\n", expected, actual); return 1; } return 0; -- 2.46.2 From 5ef54a08b470ce4be0f7c18becb3122cae6ed5bd Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 29 Jan 2022 23:10:24 +0100 Subject: [PATCH 03/16] struct syscall_vow_map: change 'str' to const char* --- exile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exile.h b/exile.h index 66895c3..a79bd10 100644 --- a/exile.h +++ b/exile.h @@ -247,7 +247,7 @@ struct syscall_vow_map struct str_to_vow_map { - char *str; + const char *str; uint64_t value; }; -- 2.46.2 From 278ae31e2e3f73f1141efd7f92e17fde6ea79931 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 30 Jan 2022 10:45:05 +0100 Subject: [PATCH 04/16] fixup! Introduce exile_vows_from_str() --- exile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exile.h b/exile.h index a79bd10..55dd5bf 100644 --- a/exile.h +++ b/exile.h @@ -698,7 +698,7 @@ uint64_t exile_vows_from_str(const char *str) ++str; } int found = 0; - for(int i = 0; i < sizeof(str_to_vow_map)/sizeof(str_to_vow_map[0]); i++) + for(size_t i = 0; i < sizeof(str_to_vow_map)/sizeof(str_to_vow_map[0]); i++) { if(strcmp(str_to_vow_map[i].str, current) == 0) { -- 2.46.2 From f13cff754c2384c19c6f9fcdfa65e650ba78306a Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 29 Jan 2022 23:05:27 +0100 Subject: [PATCH 05/16] Begin C++ API: Add exile.hpp with exile_launch() wrappers --- exile.hpp | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 exile.hpp diff --git a/exile.hpp b/exile.hpp new file mode 100644 index 0000000..0f83661 --- /dev/null +++ b/exile.hpp @@ -0,0 +1,175 @@ +#include "exile.h" +#include +#include +#include +#include +#include +#include + +#ifndef EXILE_MMAP_SIZE +#define EXILE_MMAP_SIZE 128 * 1024 * 1024 //128MB +#endif + + +template +class launch_arg +{ + static_assert(std::is_trivially_copyable_v); + static_assert(!std::is_pointer_v); + +public: + struct exile_policy *policy; + T *result_shm; + U fn; + std::tuple args; + + launch_arg(struct exile_policy *policy, T *result_shm, U fn, Args && ... args) : policy(policy), + result_shm(result_shm), fn(fn), args(std::forward(args)...) {} + +}; + +template +class launch_arg_serializer +{ + static_assert(std::is_copy_constructible_v); + +public: + struct exile_policy *policy; + char *serialize_buffer; + size_t n; + U fn; + std::tuple args; + + const std::function &serializer; + const std::function &deserializer; + + launch_arg_serializer(struct exile_policy *policy, char *serialize_buffer, size_t n, const std::function &serializer, const std::function &deserializer, U fn, Args && ... args) : policy(policy), serialize_buffer(serialize_buffer), n(n), fn(fn), args(std::forward(args)...), serializer(serializer), deserializer(deserializer) {} +}; + +template +int exile_clone_handle_trivial(void * arg) +{ + static_assert(std::is_trivially_copyable_v); + static_assert(!std::is_pointer_v); + + launch_arg *launchargs = (launch_arg *) arg; + int ret = exile_enable_policy(launchargs->policy); + if(ret != 0) + { + EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno)); + return 1; + } + T result = std::apply(launchargs->fn, launchargs->args); + std::cout << result; + memcpy(launchargs->result_shm, &result, sizeof(T)); + return 0; +} + +template +int exile_clone_handle_serializer(void * arg) +{ + static_assert(std::is_copy_constructible_v); + + launch_arg_serializer *launchargs = (launch_arg_serializer *) arg; + int ret = exile_enable_policy(launchargs->policy); + if(ret != 0) + { + EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno)); + return 1; + } + T result = std::apply(launchargs->fn, launchargs->args); + /* TODO: exception handling */ + /* TODO: ugly :S */ + char *target = launchargs->serialize_buffer + sizeof(size_t); + size_t n = launchargs->n - sizeof(size_t); + + size_t size = launchargs->serializer(result, target, n); + memcpy(launchargs->serialize_buffer, &size, sizeof(size_t)); + + return 0; +} + +static int do_clone(int (*clonefn)(void *), void *launcharg) +{ + struct rlimit rlimit; + int ret = getrlimit(RLIMIT_STACK, &rlimit); + if(ret != 0) + { + EXILE_LOG_ERROR("Failed to get stack size: %s\n", strerror(errno)); + return ret; + } + size_t size = rlimit.rlim_cur; + char *stack = (char *) calloc(1, size); + if(stack == NULL) + { + EXILE_LOG_ERROR("Failed to allocate stack memory for child\n"); + return 1; + } + stack += size; + + ret = clone(clonefn, stack, 17 /* SIGCHLD */, launcharg); + int status = 0; + waitpid(ret, &status, __WALL); + if(WIFEXITED(status)) + { + return WEXITSTATUS(status); + } + /* TODO: exception or what? */ + return 23; +} + +template +T exile_launch_trivial(struct exile_policy *policy, U fn, Args && ... args) +{ + size_t mapsize = sizeof(T); + T * sharedbuf = (T *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); + if(sharedbuf == NULL) + { + throw std::runtime_error(std::string("mmap failed: ") + strerror(errno)); + } + + std::shared_ptr deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); }); + launch_arg launcharg(policy, sharedbuf, fn, std::forward(args)...); + + int (*clonefn)(void *) = &exile_clone_handle_trivial; + /* TODO: exception or what? */ + int ret = do_clone(clonefn, &launcharg); + if(ret == 0) + { + return *sharedbuf; + } + throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret))); + return T(); +} + + + +template +T exile_launch(struct exile_policy *policy, const std::function &serializer, const std::function &deserializer, U fn, Args && ... args) +{ + size_t mapsize = EXILE_MMAP_SIZE; + char *sharedbuf = (char *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); + if(sharedbuf == NULL) + { + throw std::runtime_error(std::string("mmap failed: ") + strerror(errno)); + } + std::shared_ptr deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); }); + + + launch_arg_serializer launcharg(policy, sharedbuf, mapsize, serializer, deserializer, fn, std::forward(args)...); + + int (*clonefn)(void *) = &exile_clone_handle_serializer; + /* TODO: exception or what? */ + int ret = do_clone(clonefn, &launcharg); + if(ret == 0) + { + size_t size = 0; + memcpy(&size, sharedbuf, sizeof(size)); + + return deserializer(sharedbuf + sizeof(size_t), size); + } + throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret))); + return T(); + +} + -- 2.46.2 From 99d26480d7cb060e2b1ea5daf1b469ff0d787725 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 29 Jan 2022 23:28:55 +0100 Subject: [PATCH 06/16] Add test.cpp to test C++ API --- Makefile | 6 ++++- test.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 test.cpp diff --git a/Makefile b/Makefile index f7e56b4..8850976 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,20 @@ prefix = /usr/local bindir = $(prefix)/bin CFLAGS = -std=c99 -Wall -Wextra -pedantic +CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic .DEFAULT_GOAL := test clean: - rm -f test + rm -f test testcpp test: test.c $(CC) test.c -g $(CFLAGS) -o test +testcpp: test.cpp + $(CXX) test.cpp -g $(CXXFLAGS) -o testcpp + check: test ./test.sh diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..1d037e4 --- /dev/null +++ b/test.cpp @@ -0,0 +1,80 @@ +#include "exile.hpp" +#include "assert.h" +#include + +std::string sandboxed_reverse(std::string str) +{ + std::reverse(str.begin(), str.end()); + return str; +} + +std::string deserialize_stdstring(const char *buf, size_t n) +{ + return std::string { buf, n }; +} + +size_t serialize_stdstring(const std::string &t, char *buf, size_t n) +{ + if(n < t.size()) + { + return 0; + } + memcpy(buf, t.data(), t.size()); + return t.size(); +} + +size_t stdstrlen(const std::string &str) +{ + return str.size(); +} + +int incrementer(int arg) +{ + return ++arg; +} +int test_exile_launch_trivial() +{ + int u = 22; + int result = exile_launch_trivial(exile_init_policy(), &incrementer, u); + assert(result == 23); + return 0; +} + +int test_exile_launch() +{ + std::string str = "abc123"; + std::string reversed = exile_launch(exile_init_policy(), &serialize_stdstring, &deserialize_stdstring, &sandboxed_reverse, str); + + assert(reversed == "321cba"); + return 0; +} + +int main(int argc, char *argv[]) +{ + if(argc < 2) + { + std::cerr << "Missing test" << std::endl; + return 1; + } + std::map map = { + { "launch-trivial-cpp", &test_exile_launch_trivial} , + { "launch-cpp", &test_exile_launch } + }; + + std::string test = argv[1]; + if(test == "--dumptests") + { + for(auto &entry : map) + { + std::cout << entry.first << std::endl; + } + return 0; + } + int (*fn)() = map[test]; + if(fn != nullptr) + { + return fn(); + } + std::cerr << "Unknown test" << std::endl; + return 1; +} -- 2.46.2 From 6f19c53acfd8c5786363dedba06f88144d655fde Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 29 Jan 2022 23:36:30 +0100 Subject: [PATCH 07/16] test.sh: Also run C++ tests --- test.sh | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test.sh b/test.sh index d1600b7..b9b5488 100755 --- a/test.sh +++ b/test.sh @@ -44,14 +44,15 @@ function runtest_skipped() function runtest() { - testname="$1" - test_log_file="$2" + testbin="$1" + testname="$2" + test_log_file="$3" echo "Running: $testname. Date: $(date)" > "${test_log_file}" - echo -n "Running $1... " + echo -n "Running $testname... " #exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call" - (./test $1 || exit $?) &>> "${test_log_file}" + (./$testbin "$testname" || exit $?) &>> "${test_log_file}" ret=$? SUCCESS="no" if [ $ret -eq 0 ] ; then @@ -64,7 +65,7 @@ function runtest() runtest_fail fi - echo "Finished: ${testname}. Date: $(date). Success: $SUCCESS" >> "${test_log_file}" + echo "Finished: ${testname} (${testbin}). Date: $(date). Success: $SUCCESS" >> "${test_log_file}" } GIT_ID=$( git log --pretty="format:%h" -n1 ) @@ -79,7 +80,12 @@ LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/exile_test_${GIT_ID}_${TIMESTAMP}" for test in $( ./test --dumptests ) ; do testname=$( echo $test ) - runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}" + runtest test "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}" +done + +for test in $( ./testcpp --dumptests ) ; do + testname=$( echo $test ) + runtest testcpp "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}" done echo echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})" -- 2.46.2 From c57ba807d7960df34907f8eb4e6af6f8abf65f78 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 29 Jan 2022 23:39:36 +0100 Subject: [PATCH 08/16] Makefile: Add 'tests' target, depend on headers too to rebuild on changes of those --- Makefile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8850976..90cce91 100644 --- a/Makefile +++ b/Makefile @@ -3,19 +3,21 @@ bindir = $(prefix)/bin CFLAGS = -std=c99 -Wall -Wextra -pedantic CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic -.DEFAULT_GOAL := test +.DEFAULT_GOAL := tests clean: rm -f test testcpp -test: test.c +test: test.c exile.h $(CC) test.c -g $(CFLAGS) -o test -testcpp: test.cpp +testcpp: test.cpp exile.h exile.hpp $(CXX) test.cpp -g $(CXXFLAGS) -o testcpp -check: test +tests: test testcpp + +check: tests ./test.sh .PHONY: check -- 2.46.2 From 72a3b041d9a87ede38acd297f5c3c2868c92bc09 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 30 Jan 2022 10:39:40 +0100 Subject: [PATCH 09/16] c++: Retire exile_launch_trivial(), use std::enable_if --- exile.hpp | 5 +++-- test.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/exile.hpp b/exile.hpp index 0f83661..7cc86a9 100644 --- a/exile.hpp +++ b/exile.hpp @@ -119,7 +119,7 @@ static int do_clone(int (*clonefn)(void *), void *launcharg) } template -T exile_launch_trivial(struct exile_policy *policy, U fn, Args && ... args) +typename std::enable_if_t, T> exile_launch(struct exile_policy *policy, U fn, Args && ... args) { size_t mapsize = sizeof(T); T * sharedbuf = (T *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); @@ -145,7 +145,8 @@ T exile_launch_trivial(struct exile_policy *policy, U fn, Args && ... args) template -T exile_launch(struct exile_policy *policy, const std::function &serializer, const std::function &deserializer, U fn, Args && ... args) +typename std::enable_if_t && std::is_copy_constructible_v, T> + exile_launch(struct exile_policy *policy, const std::function &serializer, const std::function &deserializer, U fn, Args && ... args) { size_t mapsize = EXILE_MMAP_SIZE; char *sharedbuf = (char *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); diff --git a/test.cpp b/test.cpp index 1d037e4..1b14c44 100644 --- a/test.cpp +++ b/test.cpp @@ -35,7 +35,7 @@ int incrementer(int arg) int test_exile_launch_trivial() { int u = 22; - int result = exile_launch_trivial(exile_init_policy(), &incrementer, u); + int result = exile_launch(exile_init_policy(), &incrementer, u); assert(result == 23); return 0; } -- 2.46.2 From dcfbe641f98c2f0f451cc5c7256a3c57ad4b0bbb Mon Sep 17 00:00:00 2001 From: Albert S Date: Fri, 4 Feb 2022 21:46:41 +0100 Subject: [PATCH 10/16] c++: Add explicit exile_launch() std::basic_string variant --- exile.hpp | 23 +++++++++++++++++++++++ test.cpp | 50 +++++++++++++++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/exile.hpp b/exile.hpp index 7cc86a9..fb3d98f 100644 --- a/exile.hpp +++ b/exile.hpp @@ -174,3 +174,26 @@ typename std::enable_if_t && std::is_copy_const } +template +std::basic_string deserialize_stdstring(const char *buf, size_t n) +{ + return std::basic_string { buf, n }; +} + +template +size_t serialize_stdstring(const std::basic_string &t, char *buf, size_t n) +{ + if(n < t.size()) + { + return 0; + } + memcpy(buf, t.data(), t.size()); + return t.size(); +} + + +template +std::basic_string exile_launch(struct exile_policy *policy, U fn, Args && ... args) +{ + return exile_launch(policy, &serialize_stdstring, &deserialize_stdstring, fn, std::forward(args) ...); +} diff --git a/test.cpp b/test.cpp index 1b14c44..bce4007 100644 --- a/test.cpp +++ b/test.cpp @@ -8,21 +8,6 @@ std::string sandboxed_reverse(std::string str) return str; } -std::string deserialize_stdstring(const char *buf, size_t n) -{ - return std::string { buf, n }; -} - -size_t serialize_stdstring(const std::string &t, char *buf, size_t n) -{ - if(n < t.size()) - { - return 0; - } - memcpy(buf, t.data(), t.size()); - return t.size(); -} - size_t stdstrlen(const std::string &str) { return str.size(); @@ -40,15 +25,41 @@ int test_exile_launch_trivial() return 0; } -int test_exile_launch() +int test_exile_launch_stdstring() { std::string str = "abc123"; - std::string reversed = exile_launch(exile_init_policy(), &serialize_stdstring, &deserialize_stdstring, &sandboxed_reverse, str); - + std::string reversed = exile_launch(exile_init_policy(), &sandboxed_reverse, str); assert(reversed == "321cba"); return 0; } +struct not_trivially_copyable +{ +public: + std::string somecontent; +}; + +int test_exile_launch_serializer() +{ + static_assert(! std::is_trivially_copyable_v); + + auto serializer = [](const not_trivially_copyable &obj, char *buf, size_t n){ + serialize_stdstring(obj.somecontent, buf, n); + return obj.somecontent.size(); + }; + + auto deserializer = [](const char *buffer, size_t n) { + not_trivially_copyable obj; + obj.somecontent = deserialize_stdstring(buffer, n); + return obj; + }; + + not_trivially_copyable result = exile_launch(exile_init_policy(), serializer, deserializer, []() {not_trivially_copyable obj; obj.somecontent = "Just something"; return obj;}); + + assert(result.somecontent == "Just something"); + return 0; +} + int main(int argc, char *argv[]) { if(argc < 2) @@ -58,7 +69,8 @@ int main(int argc, char *argv[]) } std::map map = { { "launch-trivial-cpp", &test_exile_launch_trivial} , - { "launch-cpp", &test_exile_launch } + { "launch-stdstring-cpp", &test_exile_launch_stdstring }, + { "launch-serializer-cpp", &test_exile_launch_serializer }, }; std::string test = argv[1]; -- 2.46.2 From 732623fc6f8e7e05de59e660934d6556da76f19f Mon Sep 17 00:00:00 2001 From: Albert S Date: Sun, 13 Mar 2022 20:23:15 +0100 Subject: [PATCH 11/16] exile.h: Add extern "C" guards --- exile.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/exile.h b/exile.h index 55dd5bf..7b6ef6b 100644 --- a/exile.h +++ b/exile.h @@ -97,6 +97,11 @@ //don't mount recursive #define EXILE_MOUNT_NOT_REC 1<<5 +#ifdef __cplusplus +extern "C" { +#endif + + /* Fine-granular approach available with landlock */ #if HAVE_LANDLOCK == 1 #define EXILE_FS_ALLOW_REMOVE_DIR (1 << 7) @@ -2235,3 +2240,6 @@ char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n) *n = size; return result; } +#ifdef __cplusplus +} +#endif -- 2.46.2 From 7f083909e6eea9497658a6aab95c2977637c91cb Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 14 Mar 2022 21:31:56 +0100 Subject: [PATCH 12/16] exile.h: Move definitions to new file exile.c Especially with exile_launch(), we will be included from more than one translation unit. Thus, ODR becomes a headache now. So move definitions to exile.c. --- Makefile | 2 +- exile.c | 1841 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ exile.h | 1746 +-------------------------------------------------- test.c | 1 + 4 files changed, 1864 insertions(+), 1726 deletions(-) create mode 100644 exile.c diff --git a/Makefile b/Makefile index 90cce91..090dd03 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ clean: rm -f test testcpp test: test.c exile.h - $(CC) test.c -g $(CFLAGS) -o test + $(CC) test.c exile.c -g $(CFLAGS) -o test testcpp: test.cpp exile.h exile.hpp $(CXX) test.cpp -g $(CXXFLAGS) -o testcpp diff --git a/exile.c b/exile.c new file mode 100644 index 0000000..79309c0 --- /dev/null +++ b/exile.c @@ -0,0 +1,1841 @@ +/* + * Copyright (c) 2019-2022 Albert Schwarzkopf + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "exile.h" + +static struct syscall_vow_map exile_vow_map[] = +{ + {EXILE_SYS(read), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(write), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(open), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH}, + {EXILE_SYS(close), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(stat), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(fstat), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(lstat), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(poll), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(lseek), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mmap), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PROT_EXEC}, + {EXILE_SYS(mprotect), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PROT_EXEC}, + {EXILE_SYS(munmap), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(brk), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(rt_sigaction), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(rt_sigprocmask), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(rt_sigreturn), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(ioctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_IOCTL}, + {EXILE_SYS(pread64), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(pwrite64), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(readv), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(writev), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(access), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(pipe), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(select), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(sched_yield), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mremap), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(msync), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mincore), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(madvise), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(shmget), EXILE_SYSCALL_VOW_SHM}, + {EXILE_SYS(shmat), EXILE_SYSCALL_VOW_SHM}, + {EXILE_SYS(shmctl), EXILE_SYSCALL_VOW_SHM}, + {EXILE_SYS(dup), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(dup2), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(pause), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(nanosleep), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getitimer), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(alarm), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setitimer), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getpid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(sendfile), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(socket), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(connect), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(accept), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(sendto), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(recvfrom), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(sendmsg), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(recvmsg), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(shutdown), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(bind), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(listen), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(getsockname), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(getpeername), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(socketpair), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setsockopt), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(getsockopt), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, + {EXILE_SYS(clone), EXILE_SYSCALL_VOW_CLONE|EXILE_SYSCALL_VOW_THREAD}, + {EXILE_SYS(fork), EXILE_SYSCALL_VOW_CLONE}, + {EXILE_SYS(vfork), EXILE_SYSCALL_VOW_CLONE}, + {EXILE_SYS(execve), EXILE_SYSCALL_VOW_EXEC}, + {EXILE_SYS(exit), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(wait4), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(kill), EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(uname), EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(semget), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(semop), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(semctl), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(shmdt), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(msgget), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(msgsnd), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(msgrcv), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(msgctl), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(fcntl), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(flock), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(fsync), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(fdatasync), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(truncate), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(ftruncate), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getdents), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(getcwd), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(chdir), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(fchdir), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(rename), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(mkdir), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(rmdir), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(creat), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(link), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(unlink), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(symlink), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(readlink), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(chmod), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(fchmod), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(chown), EXILE_SYSCALL_VOW_CHOWN}, + {EXILE_SYS(fchown), EXILE_SYSCALL_VOW_CHOWN}, + {EXILE_SYS(lchown), EXILE_SYSCALL_VOW_CHOWN}, + {EXILE_SYS(umask), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(gettimeofday), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getrlimit), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getrusage), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(sysinfo), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(times), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getuid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getgid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setuid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(setgid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(geteuid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getegid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setpgid), EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(getppid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getpgrp), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setsid), EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(setreuid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(setregid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(getgroups), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setgroups), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(setresuid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(getresuid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setresgid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(getresgid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getpgid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setfsuid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(setfsgid), EXILE_SYSCALL_VOW_ID}, + {EXILE_SYS(getsid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(capget), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(rt_sigpending), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(rt_sigtimedwait), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(rt_sigqueueinfo), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(rt_sigsuspend), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(utime), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(mknod), EXILE_SYSCALL_VOW_DPATH}, + {EXILE_SYS(uselib), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(ustat), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(statfs), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(fstatfs), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(getpriority), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(setpriority), EXILE_SYSCALL_VOW_SCHED|EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(sched_setparam), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(sched_getparam), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(sched_setscheduler), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(sched_getscheduler), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(sched_get_priority_max), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(sched_get_priority_min), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(sched_rr_get_interval), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(mlock), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(munlock), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mlockall), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(munlockall), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(vhangup), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(prctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PRCTL|EXILE_SYSCALL_VOW_SECCOMP_INSTALL}, + {EXILE_SYS(arch_prctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PRCTL}, + {EXILE_SYS(setrlimit), EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(sync), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(gettid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(readahead), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(setxattr), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(lsetxattr), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(fsetxattr), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(getxattr), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(lgetxattr), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(fgetxattr), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(listxattr), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(llistxattr), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(flistxattr), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(removexattr), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(lremovexattr), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(fremovexattr), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(tkill), EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(time), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(futex), EXILE_SYSCALL_VOW_THREAD}, + {EXILE_SYS(sched_getaffinity), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(set_thread_area), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(get_thread_area), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(lookup_dcookie), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(epoll_create), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(epoll_ctl_old), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(epoll_wait_old), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(remap_file_pages), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getdents64), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(set_tid_address), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(semtimedop), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(fadvise64), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(timer_create), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(timer_settime), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(timer_gettime), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(timer_getoverrun), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(timer_delete), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(clock_gettime), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(clock_getres), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(clock_nanosleep), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(exit_group), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(epoll_wait), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(epoll_ctl), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(tgkill), EXILE_SYSCALL_VOW_PROC}, + {EXILE_SYS(utimes), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(mbind), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(get_mempolicy), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mq_open), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mq_unlink), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mq_timedsend), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mq_timedreceive), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mq_notify), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(mq_getsetattr), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(waitid), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(inotify_init), EXILE_SYSCALL_VOW_FSNOTIFY}, + {EXILE_SYS(inotify_add_watch), EXILE_SYSCALL_VOW_FSNOTIFY}, + {EXILE_SYS(inotify_rm_watch), EXILE_SYSCALL_VOW_FSNOTIFY}, + {EXILE_SYS(openat), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH}, + {EXILE_SYS(mkdirat), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(mknodat), EXILE_SYSCALL_VOW_DPATH}, + {EXILE_SYS(fchownat), EXILE_SYSCALL_VOW_CHOWN}, + {EXILE_SYS(futimesat), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(newfstatat), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(unlinkat), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(renameat), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(linkat), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(symlinkat), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(readlinkat), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(fchmodat), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(faccessat), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(pselect6), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(ppoll), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(set_robust_list), EXILE_SYSCALL_VOW_THREAD}, + {EXILE_SYS(get_robust_list), EXILE_SYSCALL_VOW_THREAD}, + {EXILE_SYS(splice), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(tee), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(sync_file_range), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(vmsplice), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(move_pages), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(utimensat), EXILE_SYSCALL_VOW_FATTR}, + {EXILE_SYS(epoll_pwait), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(signalfd), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(timerfd_create), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(eventfd), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(fallocate), EXILE_SYSCALL_VOW_WPATH|EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(timerfd_settime), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(timerfd_gettime), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(accept4), EXILE_SYSCALL_VOW_UNIX|EXILE_SYSCALL_VOW_INET}, + {EXILE_SYS(signalfd4), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(eventfd2), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(epoll_create1), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(dup3), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(pipe2), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(inotify_init1), EXILE_SYSCALL_VOW_FSNOTIFY}, + {EXILE_SYS(preadv), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(pwritev), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(recvmmsg), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(fanotify_init), EXILE_SYSCALL_VOW_FSNOTIFY}, + {EXILE_SYS(fanotify_mark), EXILE_SYSCALL_VOW_FSNOTIFY}, + {EXILE_SYS(prlimit64), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(open_by_handle_at), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(sendmmsg), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(getcpu), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(sched_setattr), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(sched_getattr), EXILE_SYSCALL_VOW_SCHED}, + {EXILE_SYS(renameat2), EXILE_SYSCALL_VOW_CPATH}, + {EXILE_SYS(getrandom), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(execveat), EXILE_SYSCALL_VOW_EXEC}, + {EXILE_SYS(mlock2), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(copy_file_range), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(statx), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(clone3), EXILE_SYSCALL_VOW_CLONE}, + {EXILE_SYS(close_range), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(openat2), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH}, + {EXILE_SYS(faccessat2), EXILE_SYSCALL_VOW_RPATH}, + {EXILE_SYS(process_madvise), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(epoll_pwait2), EXILE_SYSCALL_VOW_STDIO}, + {EXILE_SYS(futex_waitv), EXILE_SYSCALL_VOW_THREAD} +}; + +struct str_to_vow_map str_to_vow_map[] = +{ + { "chown", EXILE_SYSCALL_VOW_CHOWN}, + { "clone", EXILE_SYSCALL_VOW_CLONE}, + { "cpath", EXILE_SYSCALL_VOW_CPATH}, + { "dpath", EXILE_SYSCALL_VOW_DPATH}, + { "exec", EXILE_SYSCALL_VOW_EXEC}, + { "fattr", EXILE_SYSCALL_VOW_FATTR}, + { "fsnotify", EXILE_SYSCALL_VOW_FSNOTIFY}, + { "id", EXILE_SYSCALL_VOW_ID}, + { "inet", EXILE_SYSCALL_VOW_INET}, + { "ioctl", EXILE_SYSCALL_VOW_IOCTL}, + { "prctl", EXILE_SYSCALL_VOW_PRCTL}, + { "proc", EXILE_SYSCALL_VOW_PROC}, + { "prot_exec", EXILE_SYSCALL_VOW_PROT_EXEC}, + { "rpath", EXILE_SYSCALL_VOW_RPATH}, + { "sched", EXILE_SYSCALL_VOW_SCHED}, + { "seccomp_install", EXILE_SYSCALL_VOW_SECCOMP_INSTALL}, + { "shm", EXILE_SYSCALL_VOW_SHM}, + { "stdio", EXILE_SYSCALL_VOW_STDIO}, + { "thread", EXILE_SYSCALL_VOW_THREAD}, + { "unix", EXILE_SYSCALL_VOW_UNIX}, + { "wpath", EXILE_SYSCALL_VOW_WPATH}, + { "error", EXILE_SYSCALL_VOW_DENY_ERROR} +}; + +/* Converts the whitespace separated vows strings to vows flags + * + * This mainly helps readability, as lots of flags ORed together is not + * very readable. + * + * If an unkown string is found, abort() is called. + */ +uint64_t exile_vows_from_str(const char *str) +{ + uint64_t result = 0; + char current[64] = { 0 }; + char *ptr = current; + const char *end = ptr + sizeof(current)-1; + do + { + while(ptr <= end && *str != '\0' && *str != ' ') + { + *ptr = *str; + ++ptr; + ++str; + } + int found = 0; + for(size_t i = 0; i < sizeof(str_to_vow_map)/sizeof(str_to_vow_map[0]); i++) + { + if(strcmp(str_to_vow_map[i].str, current) == 0) + { + result |= str_to_vow_map[i].value; + found = 1; + break; + } + } + if(!found) + { + EXILE_LOG_ERROR("No such vow: %s\n", current); + abort(); + } + memset(current, 0, sizeof(current)); + ptr = current; + } while(*str++ != '\0'); + return result; +} + +inline int exile_landlock_is_available() +{ + #if HAVE_LANDLOCK == 1 + int ruleset = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); + return ruleset == 1; + #endif + return 0; +} +int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n) +{ + struct exile_syscall_policy *newpolicy = (struct exile_syscall_policy *) calloc(1, sizeof(struct exile_syscall_policy)); + if(newpolicy == NULL) + { + EXILE_LOG_ERROR("Failed to allocate memory for syscall policy\n"); + exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL; + return -1; + } + newpolicy->policy = syscall_policy; + newpolicy->syscall = syscall; + newpolicy->argfilterscount = n; + if(n > EXILE_ARGFILTERS_COUNT) + { + EXILE_LOG_ERROR("Too many argfilters supplied\n"); + exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL; + return -1; + } + for(size_t i = 0; i < n; i++) + { + newpolicy->argfilters[i] = argfilters[i]; + } + newpolicy->next = NULL; + + *(exile_policy->syscall_policies_tail) = newpolicy; + exile_policy->syscall_policies_tail = &(newpolicy->next); + + exile_policy->disable_syscall_filter = 0; + return 0; +} + +int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy) +{ + return exile_append_syscall_policy(exile_policy, EXILE_SYSCALL_MATCH_ALL, default_policy, NULL, 0); +} + +#define COUNT_EXILE_SYSCALL_FILTER(f) \ + sizeof(f)/sizeof(f[0]) + +#define EXILE_SYSCALL_FILTER_LOAD_ARG(val) \ +{ 0, EXILE_BPF_LOAD_SECCOMP_ARG(val), 0} + +/* Returns, for the specific syscall, the correct sock_filter struct for the provided vow_promises + * + * Returns: 0 if none copied, otherwise the number of entries in "filter". + */ +int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy) +{ + + /* How to read this: + * Keep in mind our default action is to deny, unless it's a syscall from a vow promise. Then it will be + * accepted if the argument values are good (if we care about them at all). + * EXILE_BPF_MATCH() means the argument value is good, and the syscall can be accepted without further checks + * EXILE_BPF_NO_MATCH() means the syscall won't be allowed because the value is illegal + * + * First field (vowmask): The mask to check + * Last field (whenset): If mask is set in vow_promises, then add this filter, otherwise don't. + */ + + struct exile_syscall_filter mmap_filter[] = { + EXILE_SYSCALL_FILTER_LOAD_ARG(2), + { EXILE_SYSCALL_VOW_PROT_EXEC, EXILE_BPF_NO_MATCH_SET(PROT_EXEC), 0}, + }; + + + struct exile_syscall_filter ioctl_filter[] = { + EXILE_SYSCALL_FILTER_LOAD_ARG(1), + { EXILE_SYSCALL_VOW_IOCTL, EXILE_BPF_RETURN_MATCHING, 1 }, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONBIO), 1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIOCLEX), 1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONCLEX), 1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_RETURN_NOT_MATCHING, 1} + }; + + struct exile_syscall_filter open_filter[] = { + EXILE_SYSCALL_FILTER_LOAD_ARG(1), + { EXILE_SYSCALL_VOW_CPATH, EXILE_BPF_NO_MATCH_SET(O_CREAT), 0 }, + { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_TMPFILE),0 }, + { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_WRONLY),0 }, + { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_RDWR),0 }, + { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_APPEND),0 }, + }; + + struct exile_syscall_filter socket_filter[] = { + EXILE_SYSCALL_FILTER_LOAD_ARG(0), + { EXILE_SYSCALL_VOW_UNIX, EXILE_BPF_MATCH(AF_UNIX), 1 }, + { EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET), 1 }, + { EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET6), 1 }, + { 0, EXILE_BPF_RETURN_NOT_MATCHING, 0} + }; + + struct exile_syscall_filter setsockopt_filter[] = { + EXILE_SYSCALL_FILTER_LOAD_ARG(2), + { 0, EXILE_BPF_NO_MATCH(SO_DEBUG), 0 }, + { 0, EXILE_BPF_NO_MATCH(SO_SNDBUFFORCE), 0 } + }; + + + struct exile_syscall_filter clone_filter[] = { + /* It's the first (0) argument for x86_64 */ + EXILE_SYSCALL_FILTER_LOAD_ARG(0), + { EXILE_SYSCALL_VOW_CLONE, EXILE_BPF_RETURN_MATCHING, 1 }, + { EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_VM, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1}, + { EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_THREAD, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1}, + { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWCGROUP), 0}, + { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWIPC),0}, + { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNET),0}, + { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNS),0}, + { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWPID),0}, + { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUSER),0}, + { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUTS),0}, + }; + + + struct exile_syscall_filter prctl_filter[] ={ + EXILE_SYSCALL_FILTER_LOAD_ARG(0), + { EXILE_SYSCALL_VOW_PRCTL, EXILE_BPF_RETURN_MATCHING, 1}, + { EXILE_SYSCALL_VOW_SECCOMP_INSTALL, EXILE_BPF_MATCH(PR_SET_SECCOMP), 1 }, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NO_NEW_PRIVS),1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NO_NEW_PRIVS),1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NAME),1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NAME),1}, + { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_CAPBSET_READ), 1}, + { 0, EXILE_BPF_RETURN_NOT_MATCHING, 0} + }; + + struct exile_syscall_filter *current_filter = NULL; + size_t current_count = 0; + + *policy = EXILE_SYSCALL_ALLOW; + switch(syscall) + { + case EXILE_SYS(mmap): + case EXILE_SYS(mprotect): + current_filter = mmap_filter; + current_count = COUNT_EXILE_SYSCALL_FILTER(mmap_filter); + break; + case EXILE_SYS(ioctl): + current_filter = ioctl_filter; + current_count = COUNT_EXILE_SYSCALL_FILTER(ioctl_filter); + break; + case EXILE_SYS(open): + case EXILE_SYS(openat): + case EXILE_SYS(open_by_handle_at): + if(syscall == EXILE_SYS(openat) || syscall == EXILE_SYS(open_by_handle_at)) + { + /* for openat, it's the third arg */ + open_filter[0] = (struct exile_syscall_filter) EXILE_SYSCALL_FILTER_LOAD_ARG(2); + } + current_filter = open_filter; + current_count = COUNT_EXILE_SYSCALL_FILTER(open_filter); + break; + case EXILE_SYS(openat2): + *policy = EXILE_SYSCALL_DENY_RET_ERROR; + return 0; + break; + case EXILE_SYS(socket): + current_filter = socket_filter; + current_count = COUNT_EXILE_SYSCALL_FILTER(socket_filter); + break; + case EXILE_SYS(setsockopt): + current_filter = setsockopt_filter; + current_count = COUNT_EXILE_SYSCALL_FILTER(setsockopt_filter); + break; + case EXILE_SYS(clone): + current_filter = clone_filter; + current_count = COUNT_EXILE_SYSCALL_FILTER(clone_filter); + break; + case EXILE_SYS(clone3): + if((vow_promises & EXILE_SYSCALL_VOW_CLONE) == 0) + { + *policy = EXILE_SYSCALL_DENY_RET_ERROR; + return 0; + } + break; + case EXILE_SYS(prctl): + current_filter = prctl_filter; + current_count = COUNT_EXILE_SYSCALL_FILTER(prctl_filter); + break; + } + + int out_filter_index = 0; + for(size_t i = 0; i < current_count; i++) + { + struct exile_syscall_filter *c = ¤t_filter[i]; + int set = 0; + if(c->vowmask & vow_promises) + { + set = 1; + } + if(c->whenset == set || c->vowmask == 0) + { + filter[out_filter_index++] = c->filter; + } + } + return out_filter_index; +} + +int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises) +{ + for(unsigned int i = 0; i < sizeof(exile_vow_map)/sizeof(exile_vow_map[0]); i++) + { + struct syscall_vow_map *current_map = &exile_vow_map[i]; + if(current_map->vowmask & vow_promises) + { + struct sock_filter filter[EXILE_ARGFILTERS_COUNT]; + long syscall = current_map->syscall; + int syscall_policy = EXILE_SYSCALL_ALLOW; + int argfilters = get_vow_argfilter(syscall, vow_promises, filter, &syscall_policy); + int ret = exile_append_syscall_policy(policy, syscall, syscall_policy, filter, argfilters); + if(ret != 0) + { + EXILE_LOG_ERROR("Failed adding syscall policy from vow while processing %li\n", syscall); + return ret; + } + } + } + int vow_policy = (vow_promises & EXILE_SYSCALL_VOW_DENY_ERROR) ? EXILE_SYSCALL_DENY_RET_ERROR : EXILE_SYSCALL_DENY_KILL_PROCESS; + return exile_append_syscall_default_policy(policy, vow_policy); +} + +/* Creates an empty policy struct without opinionated defaults. + * + * Must be freed using exile_free_policy() + * @returns: empty policy + */ +struct exile_policy *exile_create_policy() +{ + struct exile_policy *result = (struct exile_policy *) calloc(1, sizeof(struct exile_policy)); + if(result == NULL) + { + EXILE_LOG_ERROR("Failed to allocate memory for policy\n"); + return NULL; + } + result->path_policies_tail = &(result->path_policies); + result->syscall_policies_tail = &(result->syscall_policies); + return result; +} + +/* Creates the default policy + * Must be freed using exile_free_policy() + * + * @returns: default policy + */ +struct exile_policy *exile_init_policy() +{ + struct exile_policy *result = exile_create_policy(); + if(result == NULL) + { + return NULL; + } + result->drop_caps = 1; + result->not_dumpable = 1; + result->no_new_privs = 1; + result->namespace_options = EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_USER; + return result; +} + + +/* Appends path policies to the exile_policy object + * The last paramater must be NULL + * + * This function does not copy parameters. All passed paths + * MUST NOT be freed until exile_enable_policy() is called! + * + * @returns: 0 on success, -1 on failure */ +int (exile_append_path_policies)(struct exile_policy *exile_policy, unsigned int path_policy, ...) +{ + va_list args; + const char *path; + va_start(args, path_policy); + + path = va_arg(args, char*); + while(path != NULL) + { + int fd = open(path, O_PATH); + if(fd == -1) + { + EXILE_LOG_ERROR("Failed to open the specified path: %s\n", strerror(errno)); + exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL; + return -1; + } + close(fd); + struct exile_path_policy *newpolicy = (struct exile_path_policy *) calloc(1, sizeof(struct exile_path_policy)); + if(newpolicy == NULL) + { + EXILE_LOG_ERROR("Failed to allocate memory for path policy\n"); + exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL; + return -1; + } + newpolicy->path = path; + newpolicy->policy = path_policy; + newpolicy->next = NULL; + + *(exile_policy->path_policies_tail) = newpolicy; + exile_policy->path_policies_tail = &(newpolicy->next); + path = va_arg(args, char*); + } + + va_end(args); + + return 0; +} + +/* + * Fills buffer with random characters a-z. + * The string will be null terminated. + * + * @returns: number of written chars (excluding terminating null byte) on success + */ +int random_string(char *buffer, size_t buffer_length) +{ + int r = getrandom(buffer, buffer_length-1, GRND_NONBLOCK); + if(r != -1 && (size_t) r == buffer_length-1) + { + int i = 0; + while(i < r) + { + buffer[i] = 'a' + ((unsigned int)buffer[i] % 26); + ++i; + } + buffer[buffer_length-1] = '\0'; + return i; + } + return 0; +} + + +/* Creates a directory/file and all necessary parent directories +* @returns: 0 on success, -ERRNO on failure +*/ +int mkpath(const char *p, mode_t mode, int baseisfile) +{ + char path[PATH_MAX + 1] = {0}; + int ret = snprintf(path, sizeof(path), "%s%c", p, (baseisfile) ? '\0' : '/'); + if(ret < 0) + { + EXILE_LOG_ERROR("error during path concatination\n"); + return -EINVAL; + } + if((size_t)ret >= sizeof(path)) + { + EXILE_LOG_ERROR("path concatination truncated\n"); + return -EINVAL; + } + + char *begin = path; + char *end = begin + 1; + + while(*end) + { + if(*end == '/') + { + *end = 0; + if(mkdir(begin, mode) < 0) + { + if(errno != EEXIST) + { + EXILE_LOG_ERROR("Failed to create directory: %s\n", begin); + return -1; + } + } + *end = '/'; + while(*end == '/') + { + ++end; + } + } + else + { + ++end; + } + } + if(baseisfile) + { + ret = creat(p, mode); + if(ret == -1) + { + EXILE_LOG_ERROR("Failed to create file: %s\n", begin); + return ret; + } + close(ret); + return 0; + } + return 0; +} + +/* @returns: argument for mount(2) flags */ +static int get_policy_mount_flags(struct exile_path_policy *policy) +{ + int result = 0; + + if( (policy->policy & EXILE_FS_ALLOW_DEV) == 0) + { + result |= MS_NODEV; + } + + if( (policy->policy & EXILE_FS_ALLOW_EXEC) == 0) + { + result |= MS_NOEXEC; + } + + if( (policy->policy & EXILE_FS_ALLOW_SETUID) == 0) + { + result |= MS_NOSUID; + } + + if( (policy->policy & EXILE_FS_ALLOW_ALL_WRITE) == 0) + { + result |= MS_RDONLY; + } + + if( (policy->policy & EXILE_MOUNT_NOT_REC) == 0) + { + result |= MS_REC; + } + return result; +} + +int path_policy_needs_landlock(struct exile_path_policy *path_policy) +{ + unsigned int policy = path_policy->policy; +#if HAVE_LANDLOCK == 1 + if(policy >= EXILE_FS_ALLOW_REMOVE_DIR) + { + return 1; + } +#endif + //Can't need it if we don't have support at compile time + return 0; +} + +/* TODO: we can do va_args */ +char *concat_path(const char *first, const char *second) +{ + char *result = (char *) calloc(1, PATH_MAX); + if(result == NULL) + { + EXILE_LOG_ERROR("calloc failed\n"); + return NULL; + } + //TODO: We can strip multiple redundant slashes + int written = snprintf(result, PATH_MAX, "%s/%s", first, second); + if(written < 0) + { + EXILE_LOG_ERROR("Error during path concatination\n"); + return NULL; + } + if(written >= PATH_MAX) + { + EXILE_LOG_ERROR("path concatination truncated\n"); + return NULL; + } + return result; +} + + +/* Creates the file system hierarchy for the chroot + * @returns: 0 on sucess, -ERRNO on failure */ +static int create_chroot_dirs(const char *chroot_target_path, struct exile_path_policy *path_policy) +{ + while(path_policy != NULL) + { + struct stat sb; + int ret = stat(path_policy->path, &sb); + if(ret < 0) + { + EXILE_LOG_ERROR("stat failed\n"); + return ret; + } + + int baseisfile = 0; + if(S_ISREG(sb.st_mode)) + { + baseisfile = 1; + } + + char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path); + if(path_inside_chroot == NULL) + { + return 1; + } + + ret = mkpath(path_inside_chroot, 0700, baseisfile); + if(ret < 0) + { + EXILE_LOG_ERROR("Error creating directory structure while mounting paths to chroot. %s\n", strerror(errno)); + free(path_inside_chroot); + return ret; + } + path_policy = path_policy->next; + free(path_inside_chroot); + } + + return 0; +} + +static int perform_mounts(const char *chroot_target_path, struct exile_path_policy *path_policy) +{ + while(path_policy != NULL) + { + int mount_flags = get_policy_mount_flags(path_policy); + + char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path); + if(path_inside_chroot == NULL) + { + return 1; + } + //all we do is bind mounts + mount_flags |= MS_BIND; + + if(path_policy->policy & EXILE_FS_ALLOW_ALL_READ || path_policy->policy & EXILE_FS_ALLOW_ALL_WRITE) + { + int ret = mount(path_policy->path, path_inside_chroot, NULL, mount_flags, NULL); + if(ret < 0 ) + { + EXILE_LOG_ERROR("Failed to mount %s to %s: %s\n", path_policy->path, path_inside_chroot, strerror(errno)); + free(path_inside_chroot); + return ret; + } + + //remount so noexec, readonly etc. take effect + ret = mount(NULL, path_inside_chroot, NULL, mount_flags | MS_REMOUNT, NULL); + if(ret < 0 ) + { + EXILE_LOG_ERROR("Failed to remount %s: %s\n", path_inside_chroot, strerror(errno)); + free(path_inside_chroot); + return ret; + } + path_policy = path_policy->next; + free(path_inside_chroot); + } + } + return 0; +} + + + +/* + * Frees the memory taken by a exile_policy object + */ +void exile_free_policy(struct exile_policy *ctxt) +{ + if(ctxt != NULL) + { + struct exile_path_policy *current = ctxt->path_policies; + while(current != NULL) + { + struct exile_path_policy *tmp = current; + current = current->next; + free(tmp); + } + + struct exile_syscall_policy *sc_policy = ctxt->syscall_policies; + while(sc_policy != NULL) + { + struct exile_syscall_policy *tmp = sc_policy; + sc_policy = sc_policy->next; + free(tmp); + } + free(ctxt); + } +} + +/* Enters the specified namespaces */ +static int enter_namespaces(int namespace_options) +{ + if(namespace_options & EXILE_UNSHARE_USER) + { + int ret = unshare(CLONE_NEWUSER); + if(ret == -1) + { + EXILE_LOG_ERROR("Failed to unshare user namespaces: %s\n", strerror(errno)); + return ret; + } + + uid_t current_uid = getuid(); + gid_t current_gid = getgid(); + + FILE *fp = fopen("/proc/self/setgroups", "w"); + if(fp == NULL) + { + EXILE_LOG_ERROR("fopen failed while trying to deny setgroups\n"); + return -1; + } + if(fprintf(fp, "deny") < 0) + { + EXILE_LOG_ERROR("fprintf failed while trying to write setgroups\n"); + return -1; + } + fclose(fp); + + fp = fopen("/proc/self/uid_map", "w"); + if(fp == NULL) + { + EXILE_LOG_ERROR("fopen failed while trying to write uid_map\n"); + return -1; + } + if(fprintf(fp, "0 %i", current_uid) < 0) + { + EXILE_LOG_ERROR("fprintf failed while trying to write uid_map\n"); + return -1; + } + fclose(fp); + + fp = fopen("/proc/self/gid_map", "w"); + if(fp == NULL) + { + EXILE_LOG_ERROR("fopen failed while trying to write gid_map\n"); + return -1; + } + if(fprintf(fp, "0 %i", current_gid) < 0) + { + EXILE_LOG_ERROR("fprintf failed while trying to write gid_map\n"); + return -1; + } + fclose(fp); + } + + if(namespace_options & EXILE_UNSHARE_MOUNT) + { + int ret = unshare(CLONE_NEWNS); + if(ret == -1) + { + EXILE_LOG_ERROR("Failed to unshare mount namespaces: %s\n", strerror(errno)); + return ret; + } + } + + if(namespace_options & EXILE_UNSHARE_NETWORK) + { + int ret = unshare(CLONE_NEWNET); + if(ret == -1) + { + EXILE_LOG_ERROR("Failed to unshare network namespace: %s\n", strerror(errno)); + return ret; + } + } + + return 0; +} + +/* Drops all capabiltiies held by the process + * + * @returns: 0 on sucess, -1 on error +*/ +static int drop_caps() +{ + int cap = 0; + int res = 0; + while((res = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) == 0) + { + ++cap; + } + + if(res == -1 && errno != EINVAL) + { + EXILE_LOG_ERROR("Failed to drop the capability bounding set!\n"); + return -errno; + } + + //TODO: systems that are not 64 bit + struct __user_cap_header_struct h = { 0 }; + h.pid = 0; + h.version = _LINUX_CAPABILITY_VERSION_3; + struct __user_cap_data_struct drop[2]; + drop[0].effective = 0; + drop[0].permitted = 0; + drop[0].inheritable = 0; + drop[1].effective = 0; + drop[1].permitted = 0; + drop[1].inheritable = 0; + if(capset(&h, drop) == -1) + { + EXILE_LOG_ERROR("Failed to drop capabilities: %s\n", strerror(errno)); + return -errno; + } + return 0; +} + + + +static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, struct sock_filter *filter, unsigned short int *start_index) +{ + unsigned int action = syscallpolicy->policy; + if(action == EXILE_SYSCALL_ALLOW) + { + action = SECCOMP_RET_ALLOW; + } + if(action == EXILE_SYSCALL_DENY_KILL_PROCESS) + { + action = SECCOMP_RET_KILL_PROCESS; + } + if(action == EXILE_SYSCALL_DENY_RET_ERROR) + { + action = SECCOMP_RET_ERRNO|EACCES; + } + long syscall = syscallpolicy->syscall; + + struct sock_filter syscall_load = BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)); + filter[(*start_index)++] = syscall_load; + if(syscall != EXILE_SYSCALL_MATCH_ALL) + { + /* How many steps forward to jump when we don't match. This is either the last statement, + * i. e. the default action or the next syscall policy */ + __u8 next_syscall_pc = 1; + if(__builtin_add_overflow(next_syscall_pc, syscallpolicy->argfilterscount, &next_syscall_pc)) + { + EXILE_LOG_ERROR("Overflow while trying to calculate jump offset\n"); + /* TODO: Return error */ + return; + } + struct sock_filter syscall_check = EXILE_BPF_CMP_EQ((unsigned int) syscall, 0, next_syscall_pc); + filter[(*start_index)++] = syscall_check; + --next_syscall_pc; + + struct sock_filter return_matching = EXILE_BPF_RETURN_MATCHING; + struct sock_filter return_not_matching = EXILE_BPF_RETURN_NOT_MATCHING; + + for(size_t i = 0; i < syscallpolicy->argfilterscount; i++) + { + filter[*start_index] = syscallpolicy->argfilters[i]; + struct sock_filter *current = &filter[*start_index]; + __u8 jump_count_next_syscall = next_syscall_pc; + __u8 jump_count_return = jump_count_next_syscall - 1; + if(current->jt == EXILE_SYSCALL_EXIT_BPF_NO_MATCH) + { + current->jt = jump_count_next_syscall; + } + if(current->jt == EXILE_SYSCALL_EXIT_BPF_RETURN) + { + current->jt = jump_count_return; + } + if(current->jf == EXILE_SYSCALL_EXIT_BPF_NO_MATCH) + { + current->jf = jump_count_next_syscall; + } + if(current->jf == EXILE_SYSCALL_EXIT_BPF_RETURN) + { + current->jf = jump_count_return; + } + if(current->code == return_matching.code && current->k == return_matching.k) + { + current->k = jump_count_return; + } + if(current->code == return_not_matching.code && current->k == return_not_matching.k) + { + current->k = jump_count_next_syscall; + } + --next_syscall_pc; + ++*start_index; + } + } + struct sock_filter syscall_action = BPF_STMT(BPF_RET+BPF_K, action); + /* TODO: we can do better than adding this below every jump */ + filter[(*start_index)++] = syscall_action; + +} + +static int is_valid_syscall_policy(unsigned int policy) +{ + return policy == EXILE_SYSCALL_ALLOW || policy == EXILE_SYSCALL_DENY_RET_ERROR || policy == EXILE_SYSCALL_DENY_KILL_PROCESS; +} + +/* + * Enables the seccomp policy + * + * policy: exile policy object + * + * @returns: 0 on success, -1 on error + */ + +int exile_enable_syscall_policy(struct exile_policy *policy) +{ + struct sock_filter filter[1024] = + { + BPF_STMT(BPF_LD+BPF_W+BPF_ABS,offsetof(struct seccomp_data, arch)), + BPF_JUMP (BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_AUDIT_ARCH, 1, 0), + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), + BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, __X32_SYSCALL_BIT, 0, 1), + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), + }; + + unsigned short int current_filter_index = 6; + + struct exile_syscall_policy *current_policy = policy->syscall_policies; + while(current_policy) + { + if(!is_valid_syscall_policy(current_policy->policy)) + { + EXILE_LOG_ERROR("invalid syscall policy specified\n"); + return -1; + } + /* TODO: reintroduce overflow checks */ + append_syscall_to_bpf(current_policy, filter, ¤t_filter_index); + current_policy = current_policy->next; + } + + struct sock_fprog prog = { + .len = current_filter_index , + .filter = filter, + }; + + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) + { + EXILE_LOG_ERROR("prctl SET_SECCOMP %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +#if HAVE_LANDLOCK == 1 +static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode) +{ + unsigned int result = 0; + if(flags & EXILE_FS_ALLOW_ALL_READ) + { + result |= LANDLOCK_ACCESS_FS_READ_FILE; + if(S_ISDIR(statmode)) + { + result |= LANDLOCK_ACCESS_FS_READ_DIR; + } + } + if(flags & EXILE_FS_ALLOW_ALL_WRITE) + { + result |= LANDLOCK_ACCESS_FS_WRITE_FILE; + if(S_ISDIR(statmode)) + { + result |= LANDLOCK_ACCESS_FS_REMOVE_FILE; + result |= LANDLOCK_ACCESS_FS_MAKE_REG; + result |= LANDLOCK_ACCESS_FS_REMOVE_DIR; + result |= LANDLOCK_ACCESS_FS_MAKE_SYM; + } + } + if(flags & EXILE_FS_ALLOW_EXEC) + { + result |= LANDLOCK_ACCESS_FS_EXECUTE; + } + if(flags & EXILE_FS_ALLOW_WRITE_FILE) + { + result |= LANDLOCK_ACCESS_FS_WRITE_FILE; + } + if(S_ISDIR(statmode)) + { + if(flags & EXILE_FS_ALLOW_DEV) + { + result |= LANDLOCK_ACCESS_FS_MAKE_BLOCK; + result |= LANDLOCK_ACCESS_FS_MAKE_CHAR; + } + if(flags & EXILE_FS_ALLOW_MAKE_BLOCK) + { + result |= LANDLOCK_ACCESS_FS_MAKE_BLOCK; + } + if(flags & EXILE_FS_ALLOW_MAKE_CHAR) + { + result |= LANDLOCK_ACCESS_FS_MAKE_CHAR; + } + if(flags & EXILE_FS_ALLOW_MAKE_DIR) + { + result |= LANDLOCK_ACCESS_FS_MAKE_DIR; + } + if(flags & EXILE_FS_ALLOW_MAKE_FIFO) + { + result |= LANDLOCK_ACCESS_FS_MAKE_FIFO; + } + if(flags & EXILE_FS_ALLOW_MAKE_REG) + { + result |= LANDLOCK_ACCESS_FS_MAKE_REG; + } + if(flags & EXILE_FS_ALLOW_MAKE_SOCK) + { + result |= LANDLOCK_ACCESS_FS_MAKE_SOCK; + } + if(flags & EXILE_FS_ALLOW_MAKE_SYM) + { + result |= LANDLOCK_ACCESS_FS_MAKE_SYM; + } + if(flags & EXILE_FS_ALLOW_REMOVE) + { + result |= LANDLOCK_ACCESS_FS_REMOVE_DIR; + result |= LANDLOCK_ACCESS_FS_REMOVE_FILE; + } + if(flags & EXILE_FS_ALLOW_REMOVE_DIR) + { + result |= LANDLOCK_ACCESS_FS_REMOVE_DIR; + } + if(flags & EXILE_FS_ALLOW_REMOVE_FILE) + { + result |= LANDLOCK_ACCESS_FS_REMOVE_FILE; + } + if(flags & EXILE_FS_ALLOW_READ_DIR) + { + result |= LANDLOCK_ACCESS_FS_READ_DIR; + } + } + return result; +} + +static int landlock_prepare_ruleset(struct exile_path_policy *policies) +{ + int ruleset_fd = -1; + struct landlock_ruleset_attr ruleset_attr; + /* We here want the maximum possible ruleset, so set the var to the max possible bitmask. + Stolen/Adapted from: [linux src]/security/landlock/limits.h + */ + ruleset_attr.handled_access_fs = ((LANDLOCK_ACCESS_FS_MAKE_SYM << 1) - 1); + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + if (ruleset_fd < 0) + { + EXILE_LOG_ERROR("Failed to create landlock ruleset\n"); + return -1; + } + struct exile_path_policy *policy = policies; + while(policy != NULL) + { + struct landlock_path_beneath_attr path_beneath; + path_beneath.parent_fd = open(policy->path, O_PATH | O_CLOEXEC); + if(path_beneath.parent_fd < 0) + { + EXILE_LOG_ERROR("Failed to open policy path %s while preparing landlock ruleset\n", policy->path); + close(ruleset_fd); + return path_beneath.parent_fd; + } + struct stat sb; + int ret = fstat(path_beneath.parent_fd, &sb); + if(ret) + { + EXILE_LOG_ERROR("fstat failed %s\n", strerror(errno)); + close(ruleset_fd); + return ret; + } + path_beneath.allowed_access = exile_flags_to_landlock(policy->policy, sb.st_mode); + ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0); + if(ret) + { + EXILE_LOG_ERROR("Failed to update ruleset while processsing policy path %s\n", policy->path); + close(ruleset_fd); + return ret; + } + policy = policy->next; + } + return ruleset_fd; +} +#endif + + +/* Checks for illogical or dangerous combinations */ +static int check_policy_sanity(struct exile_policy *policy) +{ + if(policy->no_new_privs != 1) + { + if(policy->syscall_policies != NULL) + { + EXILE_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n"); + return -1; + } + } + + int can_use_landlock = exile_landlock_is_available(); + if(!can_use_landlock) + { + struct exile_path_policy *path_policy = policy->path_policies; + while(path_policy) + { + if(path_policy_needs_landlock(path_policy)) + { + EXILE_LOG_ERROR("A path policy needs landlock, but landlock is not available. Fallback not possible\n"); + return -1; + } + path_policy = path_policy->next; + } + } + + /* TODO: check if we have ALLOWED, but no default deny */ + + if(policy->mount_path_policies_to_chroot == 1) + { + if(policy->path_policies == NULL) + { + EXILE_LOG_ERROR("Cannot mount path policies to chroot if none are given\n"); + return -1; + } + if(!(policy->namespace_options & EXILE_UNSHARE_MOUNT)) + { + EXILE_LOG_ERROR("mount_path_policies_to_chroot = 1 requires unsharing mount namespace\n"); + return -1; + } + } + + + if(policy->path_policies != NULL) + { + + if(policy->mount_path_policies_to_chroot != 1) + { + #if HAVE_LANDLOCK != 1 + EXILE_LOG_ERROR("Path policies cannot be enforced! System needs landlock support or set mount_path_policies_to_chroot = 1\n"); + return -1; + #endif + } + if(policy->no_fs == 1) + { + EXILE_LOG_ERROR("If path_policies are specified, no_fs cannot be set to 1\n"); + return -1; + } + } + + struct exile_syscall_policy *syscall_policy = policy->syscall_policies; + if(syscall_policy != NULL) + { + /* A few sanitiy checks... but we cannot check overall whether it's reasonable */ + int i = 0; + int last_match_all = -1; + int match_all_policy = 0; + int last_policy = 0; + while(syscall_policy) + { + if(syscall_policy->syscall == EXILE_SYSCALL_MATCH_ALL) + { + last_match_all = i; + match_all_policy = syscall_policy->policy; + } + else + { + last_policy = syscall_policy->policy; + } + syscall_policy = syscall_policy->next; + ++i; + } + if(last_match_all == -1 || i - last_match_all != 1) + { + EXILE_LOG_ERROR("The last entry in the syscall policy list must match all syscalls (default rule)\n"); + return -1; + } + /* Most likely a mistake and not intended */ + if(last_policy == match_all_policy) + { + EXILE_LOG_ERROR("Last policy for a syscall matches default policy\n"); + return -1; + } + } + + return 0; +} + +static void close_file_fds() +{ + long max_files = sysconf(_SC_OPEN_MAX); + for(long i = 3; i <= max_files; i++) + { + close((int)i); + } +} + +/* Takes away file system access from the process + * + * We use this when "no_fs" is given in the policy. + * + * This is useful for restricted subprocesses that do some computational work + * and do not require filesystem access + * + * @returns: 0 on success, < 0 on error + */ +static int enable_no_fs(struct exile_policy *policy) +{ + close_file_fds(); + + if(chdir("/proc/self/fdinfo") != 0) + { + EXILE_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno)); + return -1; + } + + if(chroot(".") != 0) + { + EXILE_LOG_ERROR("Failed to chroot into safe directory: %s\n", strerror(errno)); + return -1; + } + + if(chdir("/") != 0) + { + EXILE_LOG_ERROR("Failed to chdir into safe directory inside chroot: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +/* Enables the specified exile_policy. + * + * This function is not atomic (and can't be). This means some + * policies can apply, while others may fail. + * + * This function returns success only if all policies applied. + * + * The state is undefined if this function fails. The process generally + * should exit. + * + * @returns: 0 on success (all policies applied), < 0 on error (none or some policies dit not apply) + */ +int exile_enable_policy(struct exile_policy *policy) +{ + if((policy->exile_flags & EXILE_FLAG_ADD_PATH_POLICY_FAIL) || (policy->exile_flags & EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL)) + { + EXILE_LOG_ERROR("At least one syscall or path policy was not successfully added!\n"); + return -1; + } + if(check_policy_sanity(policy) != 0) + { + EXILE_LOG_ERROR("Policy sanity check failed. Cannot apply policy!\n"); + return -EINVAL; + } + + if(enter_namespaces(policy->namespace_options) < 0) + { + EXILE_LOG_ERROR("Error while trying to enter namespaces\n"); + return -1; + } + + int can_use_landlock = exile_landlock_is_available(); + + + /* Fallback to chroot mechanism to enforce policies. Ignore mount_path_policies_to_chroot + * if we have no other option (so no landlock) */ + if((policy->mount_path_policies_to_chroot || !can_use_landlock) && policy->path_policies != NULL) + { + if(*policy->chroot_target_path == '\0') + { + char random_str[17]; + if(random_string(random_str, sizeof(random_str)) == 16) + { + int res = snprintf(policy->chroot_target_path, sizeof(policy->chroot_target_path), "%s/.sandbox_%" PRIdMAX "_%s", EXILE_TEMP_DIR, (intmax_t)getpid(), random_str); + if(res < 0) + { + EXILE_LOG_ERROR("error during path concatination\n"); + return -EINVAL; + } + if(res >= PATH_MAX) + { + EXILE_LOG_ERROR("path concatination truncated\n"); + return -EINVAL; + } + } + else + { + EXILE_LOG_ERROR("Error creating random sandbox directory name\n"); + return -1; + } + } + + if(create_chroot_dirs(policy->chroot_target_path, policy->path_policies) < 0) + { + EXILE_LOG_ERROR("bind mounting of path policies failed\n"); + return -1; + } + + if(perform_mounts(policy->chroot_target_path, policy->path_policies) < 0) + { + EXILE_LOG_ERROR("Failed to remount\n"); + return -1; + } + } + + if(*policy->chroot_target_path != '\0') + { + if(chroot(policy->chroot_target_path) < 0) + { + EXILE_LOG_ERROR("failed to enter %s\n", policy->chroot_target_path); + return -1; + } + const char *chdir_target_path = policy->chdir_path; + if(chdir_target_path == NULL) + { + chdir_target_path = "/"; + } + + if(chdir(chdir_target_path) < 0) + { + EXILE_LOG_ERROR("chdir to %s failed\n", policy->chdir_path); + return -1; + } + } + +#if HAVE_LANDLOCK == 1 + int landlock_ruleset_fd = -1; + if(can_use_landlock && policy->path_policies != NULL) + { + landlock_ruleset_fd = landlock_prepare_ruleset(policy->path_policies); + if(landlock_ruleset_fd < 0) + { + EXILE_LOG_ERROR("Failed to prepare landlock ruleset: %s\n", strerror(errno)); + return -1; + } + } +#endif + + if(policy->no_fs) + { + if(enable_no_fs(policy) != 0) + { + EXILE_LOG_ERROR("Failed to take away filesystem access of process\n"); + return -1; + } + } + + if(policy->no_new_fds) + { + const struct rlimit nofile = {0, 0}; + if (setrlimit(RLIMIT_NOFILE, &nofile) == -1) + { + EXILE_LOG_ERROR("setrlimit: Failed to set rlimit: %s\n", strerror(errno)); + return -1; + } + } + + if(policy->drop_caps) + { + if(drop_caps() < 0) + { + EXILE_LOG_ERROR("failed to drop capabilities\n"); + return -1; + } + } + + if(policy->not_dumpable) + { + if(prctl(PR_SET_DUMPABLE, 0) == -1) + { + EXILE_LOG_ERROR("prctl: PR_SET_DUMPABLE failed\n"); + return -1; + } + } + + if(policy->no_new_privs) + { + if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) + { + EXILE_LOG_ERROR("prctl: PR_SET_NO_NEW_PRIVS failed: %s\n", strerror(errno)); + return -1; + } + } + +#if HAVE_LANDLOCK == 1 + if (can_use_landlock && policy->path_policies != NULL && landlock_restrict_self(landlock_ruleset_fd, 0) != 0) + { + perror("Failed to enforce ruleset"); + close(landlock_ruleset_fd); + return -1; + } + close(landlock_ruleset_fd); +#endif + + if(policy->vow_promises != 0) + { + int ret = exile_append_vow_promises(policy, policy->vow_promises); + if(ret != 0) + { + EXILE_LOG_ERROR("exile_append_vow_promises() failed: %i\n", ret); + return ret; + } + } + + if(policy->syscall_policies != NULL) + { + return exile_enable_syscall_policy(policy); + } + + + return 0; +} + +/* Convenience wrapper for the vow-related subset of exile.h + * + * Only installs seccomp filters for the specified vow promises. + * + * Useful if only vow is required from exile.h, but nothing else + * + * Comparable with OpenBSD's pledge(), subsequent calls can only reduce allowed syscalls. + * + * Here, adding more promises than a previous call set may return success, but + * won't be allowed during execution. + * + * Due to the nature of seccomp, it's furthermore required the EXILE_SYSCALL_VOW_SECCOMP_INSTALL promise + * is set if further calls are expected. Generally, it's reasonable for the last call to + * exile_vow() a program makes to not set EXILE_SYSCALL_VOW_SECCOMP_INSTALL. + * + * There are no seperate exec_promises. All children of the process inherit the filter. + * . + * Return value: 0 on success, any other value on failure. + */ +int exile_vow(uint64_t promises) +{ + struct __user_cap_header_struct h = { 0 }; + h.pid = 0; + h.version = _LINUX_CAPABILITY_VERSION_3; + struct __user_cap_data_struct cap[2]; + cap[0].effective = 0; + cap[0].permitted = 0; + cap[0].inheritable = 0; + cap[1].effective = 0; + cap[1].permitted = 0; + cap[1].inheritable = 0; + if(capget(&h, cap) == -1) + { + EXILE_LOG_ERROR("Failed to get capabilities: %s\n", strerror(errno)); + return -errno; + } + + struct exile_policy *policy = exile_create_policy(); + if(policy == NULL) + { + EXILE_LOG_ERROR("Failed to create policy\n"); + return 1; + } + + policy->vow_promises = promises; + if((cap[0].effective & (1<no_new_privs = 1; + } + int ret = exile_enable_policy(policy); + exile_free_policy(policy); + return ret; +} + +int exile_clone_handle(void *arg) +{ + struct exile_launch_params *params = (struct exile_launch_params *) arg; + struct exile_policy *policy = (struct exile_policy *) params->policy; + + int ret = exile_enable_policy(policy); + if(ret != 0) + { + EXILE_LOG_ERROR("Failed to enable policy\n"); + close(child_read_pipe[1]); + close(child_write_pipe[0]); + return 1; + } + ret = dup2(child_read_pipe[1], 1); + if(ret == -1) + { + EXILE_LOG_ERROR("Failed to redirect stdout to pipe\n"); + return 1; + } + ret = params->func(params->funcarg); + fclose(stdout); + close(child_read_pipe[1]); + close(child_write_pipe[0]); + return ret; +} + + + +/* Helper to easily execute a single function sandboxed. + * + * Creates a child-process, then activates the policy contained in launch_params, + * and jumps to the specified function, passing the specified argument to it. + * Returns a fd connected to stdout in the child process, as well as a fd allowing to write + * to the child. + * + * if cloneflags is 0, the default ones are passed to clone(), otherwise the value of cloneflags + * + * Return value: Negative on error, otherwise the file descriptor to read from*/ +int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_result *launch_result) +{ + int ret = pipe(child_read_pipe); + if(ret != 0) + { + EXILE_LOG_ERROR("read pipe creation failed\n"); + return ret; + } + + ret = pipe(child_write_pipe); + if(ret != 0) + { + EXILE_LOG_ERROR("write pipe creation failed\n"); + return ret; + } + + struct rlimit rlimit; + ret = getrlimit(RLIMIT_STACK, &rlimit); + if(ret != 0) + { + EXILE_LOG_ERROR("Failed to get stack size: %s\n", strerror(errno)); + return ret; + } + size_t size = rlimit.rlim_cur; + char *stack = (char *) calloc(1, size); + if(stack == NULL) + { + EXILE_LOG_ERROR("Failed to allocate stack memory for child\n"); + return 1; + } + stack += size; + ret = clone(&exile_clone_handle, stack, 17 /* SIGCHLD */, launch_params); + if(ret == -1) + { + EXILE_LOG_ERROR("clone failed(): %s\n", strerror(errno)); + return ret; + } + close(child_read_pipe[1]); + close(child_write_pipe[0]); + + launch_result->tid = ret; + launch_result->read_fd = child_read_pipe[0]; + launch_result->write_fd = child_write_pipe[1]; + return 0; +} + +/* Helper for exile_launch, to easily read all output from a function +* This function will read all output from a sandboxed function. It's up to the caller to ensure +* that enough memory will be available. +* +* The result is \0 terminated. The "n" parameter contains the size of the result, not including the \0. +* +* Return value: All data written by the function. The result should be passed to free() once not needed. NULL will +* be returned on error. +*/ +char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n) +{ + *n = 0; + struct exile_launch_result launch_result; + int launch = exile_launch(launch_params, &launch_result); + if(launch < 0) + { + return NULL; + } + char *result = NULL; + size_t size = 0; + FILE *stream = open_memstream(&result, &size); + while(1) + { + char buffer[4096]; + int ret = read(launch_result.read_fd, buffer, sizeof(buffer)); + if(ret == 0) + { + break; + } + if(ret == -1) + { + if(errno == EINTR) + { + continue; + } + EXILE_LOG_ERROR("Failed to read from read file descriptor\n"); + close(launch_result.read_fd); + fclose(stream); + return NULL; + } + size_t written = fwrite(buffer, 1, ret, stream); + if(written != (size_t) ret) + { + EXILE_LOG_ERROR("Short item write"); + /* TODO: can we seek and free? */ + close(launch_result.read_fd); + fclose(stream); + return NULL; + } + } + fclose(stream); + int seek = fseek(stream, 0, SEEK_SET); + if(seek == -1) + { + EXILE_LOG_ERROR("fseek failed\n"); + close(launch_result.read_fd); + return NULL; + } + close(launch_result.read_fd); + *n = size; + return result; +} diff --git a/exile.h b/exile.h index 7b6ef6b..3809f54 100644 --- a/exile.h +++ b/exile.h @@ -383,304 +383,6 @@ struct exile_policy uint32_t exile_flags; }; - -static struct syscall_vow_map exile_vow_map[] = -{ - {EXILE_SYS(read), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(write), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(open), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH}, - {EXILE_SYS(close), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(stat), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(fstat), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(lstat), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(poll), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(lseek), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mmap), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PROT_EXEC}, - {EXILE_SYS(mprotect), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PROT_EXEC}, - {EXILE_SYS(munmap), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(brk), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(rt_sigaction), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(rt_sigprocmask), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(rt_sigreturn), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(ioctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_IOCTL}, - {EXILE_SYS(pread64), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(pwrite64), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(readv), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(writev), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(access), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(pipe), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(select), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(sched_yield), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mremap), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(msync), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mincore), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(madvise), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(shmget), EXILE_SYSCALL_VOW_SHM}, - {EXILE_SYS(shmat), EXILE_SYSCALL_VOW_SHM}, - {EXILE_SYS(shmctl), EXILE_SYSCALL_VOW_SHM}, - {EXILE_SYS(dup), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(dup2), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(pause), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(nanosleep), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getitimer), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(alarm), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setitimer), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getpid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(sendfile), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(socket), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(connect), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(accept), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(sendto), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(recvfrom), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(sendmsg), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(recvmsg), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(shutdown), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(bind), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(listen), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(getsockname), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(getpeername), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(socketpair), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setsockopt), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(getsockopt), EXILE_SYSCALL_VOW_INET|EXILE_SYSCALL_VOW_UNIX}, - {EXILE_SYS(clone), EXILE_SYSCALL_VOW_CLONE|EXILE_SYSCALL_VOW_THREAD}, - {EXILE_SYS(fork), EXILE_SYSCALL_VOW_CLONE}, - {EXILE_SYS(vfork), EXILE_SYSCALL_VOW_CLONE}, - {EXILE_SYS(execve), EXILE_SYSCALL_VOW_EXEC}, - {EXILE_SYS(exit), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(wait4), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(kill), EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(uname), EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(semget), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(semop), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(semctl), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(shmdt), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(msgget), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(msgsnd), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(msgrcv), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(msgctl), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(fcntl), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(flock), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(fsync), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(fdatasync), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(truncate), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(ftruncate), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getdents), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(getcwd), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(chdir), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(fchdir), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(rename), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(mkdir), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(rmdir), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(creat), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(link), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(unlink), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(symlink), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(readlink), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(chmod), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(fchmod), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(chown), EXILE_SYSCALL_VOW_CHOWN}, - {EXILE_SYS(fchown), EXILE_SYSCALL_VOW_CHOWN}, - {EXILE_SYS(lchown), EXILE_SYSCALL_VOW_CHOWN}, - {EXILE_SYS(umask), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(gettimeofday), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getrlimit), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getrusage), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(sysinfo), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(times), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getuid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getgid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setuid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(setgid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(geteuid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getegid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setpgid), EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(getppid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getpgrp), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setsid), EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(setreuid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(setregid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(getgroups), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setgroups), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(setresuid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(getresuid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setresgid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(getresgid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getpgid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setfsuid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(setfsgid), EXILE_SYSCALL_VOW_ID}, - {EXILE_SYS(getsid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(capget), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(rt_sigpending), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(rt_sigtimedwait), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(rt_sigqueueinfo), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(rt_sigsuspend), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(utime), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(mknod), EXILE_SYSCALL_VOW_DPATH}, - {EXILE_SYS(uselib), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(ustat), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(statfs), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(fstatfs), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(getpriority), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(setpriority), EXILE_SYSCALL_VOW_SCHED|EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(sched_setparam), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(sched_getparam), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(sched_setscheduler), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(sched_getscheduler), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(sched_get_priority_max), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(sched_get_priority_min), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(sched_rr_get_interval), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(mlock), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(munlock), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mlockall), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(munlockall), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(vhangup), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(prctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PRCTL|EXILE_SYSCALL_VOW_SECCOMP_INSTALL}, - {EXILE_SYS(arch_prctl), EXILE_SYSCALL_VOW_STDIO|EXILE_SYSCALL_VOW_PRCTL}, - {EXILE_SYS(setrlimit), EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(sync), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(gettid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(readahead), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(setxattr), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(lsetxattr), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(fsetxattr), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(getxattr), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(lgetxattr), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(fgetxattr), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(listxattr), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(llistxattr), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(flistxattr), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(removexattr), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(lremovexattr), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(fremovexattr), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(tkill), EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(time), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(futex), EXILE_SYSCALL_VOW_THREAD}, - {EXILE_SYS(sched_getaffinity), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(set_thread_area), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(get_thread_area), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(lookup_dcookie), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(epoll_create), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(epoll_ctl_old), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(epoll_wait_old), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(remap_file_pages), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getdents64), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(set_tid_address), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(semtimedop), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(fadvise64), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(timer_create), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(timer_settime), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(timer_gettime), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(timer_getoverrun), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(timer_delete), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(clock_gettime), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(clock_getres), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(clock_nanosleep), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(exit_group), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(epoll_wait), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(epoll_ctl), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(tgkill), EXILE_SYSCALL_VOW_PROC}, - {EXILE_SYS(utimes), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(mbind), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(get_mempolicy), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mq_open), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mq_unlink), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mq_timedsend), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mq_timedreceive), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mq_notify), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(mq_getsetattr), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(waitid), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(inotify_init), EXILE_SYSCALL_VOW_FSNOTIFY}, - {EXILE_SYS(inotify_add_watch), EXILE_SYSCALL_VOW_FSNOTIFY}, - {EXILE_SYS(inotify_rm_watch), EXILE_SYSCALL_VOW_FSNOTIFY}, - {EXILE_SYS(openat), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH}, - {EXILE_SYS(mkdirat), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(mknodat), EXILE_SYSCALL_VOW_DPATH}, - {EXILE_SYS(fchownat), EXILE_SYSCALL_VOW_CHOWN}, - {EXILE_SYS(futimesat), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(newfstatat), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(unlinkat), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(renameat), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(linkat), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(symlinkat), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(readlinkat), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(fchmodat), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(faccessat), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(pselect6), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(ppoll), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(set_robust_list), EXILE_SYSCALL_VOW_THREAD}, - {EXILE_SYS(get_robust_list), EXILE_SYSCALL_VOW_THREAD}, - {EXILE_SYS(splice), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(tee), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(sync_file_range), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(vmsplice), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(move_pages), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(utimensat), EXILE_SYSCALL_VOW_FATTR}, - {EXILE_SYS(epoll_pwait), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(signalfd), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(timerfd_create), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(eventfd), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(fallocate), EXILE_SYSCALL_VOW_WPATH|EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(timerfd_settime), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(timerfd_gettime), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(accept4), EXILE_SYSCALL_VOW_UNIX|EXILE_SYSCALL_VOW_INET}, - {EXILE_SYS(signalfd4), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(eventfd2), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(epoll_create1), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(dup3), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(pipe2), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(inotify_init1), EXILE_SYSCALL_VOW_FSNOTIFY}, - {EXILE_SYS(preadv), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(pwritev), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(recvmmsg), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(fanotify_init), EXILE_SYSCALL_VOW_FSNOTIFY}, - {EXILE_SYS(fanotify_mark), EXILE_SYSCALL_VOW_FSNOTIFY}, - {EXILE_SYS(prlimit64), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(open_by_handle_at), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(sendmmsg), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(getcpu), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(sched_setattr), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(sched_getattr), EXILE_SYSCALL_VOW_SCHED}, - {EXILE_SYS(renameat2), EXILE_SYSCALL_VOW_CPATH}, - {EXILE_SYS(getrandom), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(execveat), EXILE_SYSCALL_VOW_EXEC}, - {EXILE_SYS(mlock2), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(copy_file_range), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(statx), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(clone3), EXILE_SYSCALL_VOW_CLONE}, - {EXILE_SYS(close_range), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(openat2), EXILE_SYSCALL_VOW_RPATH|EXILE_SYSCALL_VOW_WPATH}, - {EXILE_SYS(faccessat2), EXILE_SYSCALL_VOW_RPATH}, - {EXILE_SYS(process_madvise), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(epoll_pwait2), EXILE_SYSCALL_VOW_STDIO}, - {EXILE_SYS(futex_waitv), EXILE_SYSCALL_VOW_THREAD} -}; - -struct str_to_vow_map str_to_vow_map[] = -{ - { "chown", EXILE_SYSCALL_VOW_CHOWN}, - { "clone", EXILE_SYSCALL_VOW_CLONE}, - { "cpath", EXILE_SYSCALL_VOW_CPATH}, - { "dpath", EXILE_SYSCALL_VOW_DPATH}, - { "exec", EXILE_SYSCALL_VOW_EXEC}, - { "fattr", EXILE_SYSCALL_VOW_FATTR}, - { "fsnotify", EXILE_SYSCALL_VOW_FSNOTIFY}, - { "id", EXILE_SYSCALL_VOW_ID}, - { "inet", EXILE_SYSCALL_VOW_INET}, - { "ioctl", EXILE_SYSCALL_VOW_IOCTL}, - { "prctl", EXILE_SYSCALL_VOW_PRCTL}, - { "proc", EXILE_SYSCALL_VOW_PROC}, - { "prot_exec", EXILE_SYSCALL_VOW_PROT_EXEC}, - { "rpath", EXILE_SYSCALL_VOW_RPATH}, - { "sched", EXILE_SYSCALL_VOW_SCHED}, - { "seccomp_install", EXILE_SYSCALL_VOW_SECCOMP_INSTALL}, - { "shm", EXILE_SYSCALL_VOW_SHM}, - { "stdio", EXILE_SYSCALL_VOW_STDIO}, - { "thread", EXILE_SYSCALL_VOW_THREAD}, - { "unix", EXILE_SYSCALL_VOW_UNIX}, - { "wpath", EXILE_SYSCALL_VOW_WPATH}, - { "error", EXILE_SYSCALL_VOW_DENY_ERROR} -}; - /* Converts the whitespace separated vows strings to vows flags * * This mainly helps readability, as lots of flags ORed together is not @@ -688,95 +390,16 @@ struct str_to_vow_map str_to_vow_map[] = * * If an unkown string is found, abort() is called. */ -uint64_t exile_vows_from_str(const char *str) -{ - uint64_t result = 0; - char current[64] = { 0 }; - char *ptr = current; - const char *end = ptr + sizeof(current)-1; - do - { - while(ptr <= end && *str != '\0' && *str != ' ') - { - *ptr = *str; - ++ptr; - ++str; - } - int found = 0; - for(size_t i = 0; i < sizeof(str_to_vow_map)/sizeof(str_to_vow_map[0]); i++) - { - if(strcmp(str_to_vow_map[i].str, current) == 0) - { - result |= str_to_vow_map[i].value; - found = 1; - break; - } - } - if(!found) - { - EXILE_LOG_ERROR("No such vow: %s\n", current); - abort(); - } - memset(current, 0, sizeof(current)); - ptr = current; - } while(*str++ != '\0'); - return result; -} - -static int is_valid_syscall_policy(unsigned int policy) -{ - return policy == EXILE_SYSCALL_ALLOW || policy == EXILE_SYSCALL_DENY_RET_ERROR || policy == EXILE_SYSCALL_DENY_KILL_PROCESS; -} +uint64_t exile_vows_from_str(const char *str); /* * If we can use landlock, return 1, otherwise 0 */ -int exile_landlock_is_available() -{ - #if HAVE_LANDLOCK == 1 - int ruleset = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); - return ruleset == 1; - #endif - return 0; -} +int exile_landlock_is_available(); -int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n) -{ - struct exile_syscall_policy *newpolicy = (struct exile_syscall_policy *) calloc(1, sizeof(struct exile_syscall_policy)); - if(newpolicy == NULL) - { - EXILE_LOG_ERROR("Failed to allocate memory for syscall policy\n"); - exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL; - return -1; - } - newpolicy->policy = syscall_policy; - newpolicy->syscall = syscall; - newpolicy->argfilterscount = n; - if(n > EXILE_ARGFILTERS_COUNT) - { - EXILE_LOG_ERROR("Too many argfilters supplied\n"); - exile_policy->exile_flags |= EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL; - return -1; - } - for(size_t i = 0; i < n; i++) - { - newpolicy->argfilters[i] = argfilters[i]; - } - newpolicy->next = NULL; - - *(exile_policy->syscall_policies_tail) = newpolicy; - exile_policy->syscall_policies_tail = &(newpolicy->next); - - exile_policy->disable_syscall_filter = 0; - return 0; -} - - -int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy) -{ - return exile_append_syscall_policy(exile_policy, EXILE_SYSCALL_MATCH_ALL, default_policy, NULL, 0); -} +int exile_append_syscall_policy(struct exile_policy *exile_policy, long syscall, unsigned int syscall_policy, struct sock_filter *argfilters, size_t n); +int exile_append_syscall_default_policy(struct exile_policy *exile_policy, unsigned int default_policy); struct exile_syscall_filter { @@ -795,219 +418,26 @@ struct exile_syscall_filter * * Returns: 0 if none copied, otherwise the number of entries in "filter". */ -static int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy) -{ - - /* How to read this: - * Keep in mind our default action is to deny, unless it's a syscall from a vow promise. Then it will be - * accepted if the argument values are good (if we care about them at all). - * EXILE_BPF_MATCH() means the argument value is good, and the syscall can be accepted without further checks - * EXILE_BPF_NO_MATCH() means the syscall won't be allowed because the value is illegal - * - * First field (vowmask): The mask to check - * Last field (whenset): If mask is set in vow_promises, then add this filter, otherwise don't. - */ - - struct exile_syscall_filter mmap_filter[] = { - EXILE_SYSCALL_FILTER_LOAD_ARG(2), - { EXILE_SYSCALL_VOW_PROT_EXEC, EXILE_BPF_NO_MATCH_SET(PROT_EXEC), 0}, - }; +int get_vow_argfilter(long syscall, uint64_t vow_promises, struct sock_filter *filter , int *policy); - struct exile_syscall_filter ioctl_filter[] = { - EXILE_SYSCALL_FILTER_LOAD_ARG(1), - { EXILE_SYSCALL_VOW_IOCTL, EXILE_BPF_RETURN_MATCHING, 1 }, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONBIO), 1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONREAD), 1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIOCLEX), 1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(FIONCLEX), 1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_RETURN_NOT_MATCHING, 1} - }; +int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises); - struct exile_syscall_filter open_filter[] = { - EXILE_SYSCALL_FILTER_LOAD_ARG(1), - { EXILE_SYSCALL_VOW_CPATH, EXILE_BPF_NO_MATCH_SET(O_CREAT), 0 }, - { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_TMPFILE),0 }, - { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_WRONLY),0 }, - { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_RDWR),0 }, - { EXILE_SYSCALL_VOW_WPATH, EXILE_BPF_NO_MATCH_SET(O_APPEND),0 }, - }; - - struct exile_syscall_filter socket_filter[] = { - EXILE_SYSCALL_FILTER_LOAD_ARG(0), - { EXILE_SYSCALL_VOW_UNIX, EXILE_BPF_MATCH(AF_UNIX), 1 }, - { EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET), 1 }, - { EXILE_SYSCALL_VOW_INET, EXILE_BPF_MATCH(AF_INET6), 1 }, - { 0, EXILE_BPF_RETURN_NOT_MATCHING, 0} - }; - - struct exile_syscall_filter setsockopt_filter[] = { - EXILE_SYSCALL_FILTER_LOAD_ARG(2), - { 0, EXILE_BPF_NO_MATCH(SO_DEBUG), 0 }, - { 0, EXILE_BPF_NO_MATCH(SO_SNDBUFFORCE), 0 } - }; - - - struct exile_syscall_filter clone_filter[] = { - /* It's the first (0) argument for x86_64 */ - EXILE_SYSCALL_FILTER_LOAD_ARG(0), - { EXILE_SYSCALL_VOW_CLONE, EXILE_BPF_RETURN_MATCHING, 1 }, - { EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_VM, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1}, - { EXILE_SYSCALL_VOW_THREAD, EXILE_BPF_CMP_SET(CLONE_THREAD, 0, EXILE_SYSCALL_EXIT_BPF_NO_MATCH), 1}, - { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWCGROUP), 0}, - { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWIPC),0}, - { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNET),0}, - { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWNS),0}, - { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWPID),0}, - { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUSER),0}, - { 0, EXILE_BPF_NO_MATCH_SET(CLONE_NEWUTS),0}, - }; - - - struct exile_syscall_filter prctl_filter[] ={ - EXILE_SYSCALL_FILTER_LOAD_ARG(0), - { EXILE_SYSCALL_VOW_PRCTL, EXILE_BPF_RETURN_MATCHING, 1}, - { EXILE_SYSCALL_VOW_SECCOMP_INSTALL, EXILE_BPF_MATCH(PR_SET_SECCOMP), 1 }, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NO_NEW_PRIVS),1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NO_NEW_PRIVS),1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_GET_NAME),1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_SET_NAME),1}, - { EXILE_SYSCALL_VOW_STDIO, EXILE_BPF_MATCH(PR_CAPBSET_READ), 1}, - { 0, EXILE_BPF_RETURN_NOT_MATCHING, 0} - }; - - struct exile_syscall_filter *current_filter = NULL; - size_t current_count = 0; - - *policy = EXILE_SYSCALL_ALLOW; - switch(syscall) - { - case EXILE_SYS(mmap): - case EXILE_SYS(mprotect): - current_filter = mmap_filter; - current_count = COUNT_EXILE_SYSCALL_FILTER(mmap_filter); - break; - case EXILE_SYS(ioctl): - current_filter = ioctl_filter; - current_count = COUNT_EXILE_SYSCALL_FILTER(ioctl_filter); - break; - case EXILE_SYS(open): - case EXILE_SYS(openat): - case EXILE_SYS(open_by_handle_at): - if(syscall == EXILE_SYS(openat) || syscall == EXILE_SYS(open_by_handle_at)) - { - /* for openat, it's the third arg */ - open_filter[0] = (struct exile_syscall_filter) EXILE_SYSCALL_FILTER_LOAD_ARG(2); - } - current_filter = open_filter; - current_count = COUNT_EXILE_SYSCALL_FILTER(open_filter); - break; - case EXILE_SYS(openat2): - *policy = EXILE_SYSCALL_DENY_RET_ERROR; - return 0; - break; - case EXILE_SYS(socket): - current_filter = socket_filter; - current_count = COUNT_EXILE_SYSCALL_FILTER(socket_filter); - break; - case EXILE_SYS(setsockopt): - current_filter = setsockopt_filter; - current_count = COUNT_EXILE_SYSCALL_FILTER(setsockopt_filter); - break; - case EXILE_SYS(clone): - current_filter = clone_filter; - current_count = COUNT_EXILE_SYSCALL_FILTER(clone_filter); - break; - case EXILE_SYS(clone3): - if((vow_promises & EXILE_SYSCALL_VOW_CLONE) == 0) - { - *policy = EXILE_SYSCALL_DENY_RET_ERROR; - return 0; - } - break; - case EXILE_SYS(prctl): - current_filter = prctl_filter; - current_count = COUNT_EXILE_SYSCALL_FILTER(prctl_filter); - break; - } - - int out_filter_index = 0; - for(size_t i = 0; i < current_count; i++) - { - struct exile_syscall_filter *c = ¤t_filter[i]; - int set = 0; - if(c->vowmask & vow_promises) - { - set = 1; - } - if(c->whenset == set || c->vowmask == 0) - { - filter[out_filter_index++] = c->filter; - } - } - return out_filter_index; -} - -int exile_append_vow_promises(struct exile_policy *policy, uint64_t vow_promises) -{ - for(unsigned int i = 0; i < sizeof(exile_vow_map)/sizeof(exile_vow_map[0]); i++) - { - struct syscall_vow_map *current_map = &exile_vow_map[i]; - if(current_map->vowmask & vow_promises) - { - struct sock_filter filter[EXILE_ARGFILTERS_COUNT]; - long syscall = current_map->syscall; - int syscall_policy = EXILE_SYSCALL_ALLOW; - int argfilters = get_vow_argfilter(syscall, vow_promises, filter, &syscall_policy); - int ret = exile_append_syscall_policy(policy, syscall, syscall_policy, filter, argfilters); - if(ret != 0) - { - EXILE_LOG_ERROR("Failed adding syscall policy from vow while processing %li\n", syscall); - return ret; - } - } - } - int vow_policy = (vow_promises & EXILE_SYSCALL_VOW_DENY_ERROR) ? EXILE_SYSCALL_DENY_RET_ERROR : EXILE_SYSCALL_DENY_KILL_PROCESS; - return exile_append_syscall_default_policy(policy, vow_policy); -} /* Creates an empty policy struct without opinionated defaults. * * Must be freed using exile_free_policy() * @returns: empty policy */ -struct exile_policy *exile_create_policy() -{ - struct exile_policy *result = (struct exile_policy *) calloc(1, sizeof(struct exile_policy)); - if(result == NULL) - { - EXILE_LOG_ERROR("Failed to allocate memory for policy\n"); - return NULL; - } - result->path_policies_tail = &(result->path_policies); - result->syscall_policies_tail = &(result->syscall_policies); - return result; -} +struct exile_policy *exile_create_policy(); + /* Creates the default policy * Must be freed using exile_free_policy() * * @returns: default policy */ -struct exile_policy *exile_init_policy() -{ - struct exile_policy *result = exile_create_policy(); - if(result == NULL) - { - return NULL; - } - result->drop_caps = 1; - result->not_dumpable = 1; - result->no_new_privs = 1; - result->namespace_options = EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_USER; - return result; -} +struct exile_policy *exile_init_policy(); /* Appends path policies to the exile_policy object @@ -1017,500 +447,17 @@ struct exile_policy *exile_init_policy() * MUST NOT be freed until exile_enable_policy() is called! * * @returns: 0 on success, -1 on failure */ -int exile_append_path_policies(struct exile_policy *exile_policy, unsigned int path_policy, ...) -{ - va_list args; - const char *path; - va_start(args, path_policy); - - path = va_arg(args, char*); - while(path != NULL) - { - int fd = open(path, O_PATH); - if(fd == -1) - { - EXILE_LOG_ERROR("Failed to open the specified path: %s\n", strerror(errno)); - exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL; - return -1; - } - close(fd); - struct exile_path_policy *newpolicy = (struct exile_path_policy *) calloc(1, sizeof(struct exile_path_policy)); - if(newpolicy == NULL) - { - EXILE_LOG_ERROR("Failed to allocate memory for path policy\n"); - exile_policy->exile_flags |= EXILE_FLAG_ADD_PATH_POLICY_FAIL; - return -1; - } - newpolicy->path = path; - newpolicy->policy = path_policy; - newpolicy->next = NULL; - - *(exile_policy->path_policies_tail) = newpolicy; - exile_policy->path_policies_tail = &(newpolicy->next); - path = va_arg(args, char*); - } - - va_end(args); - - return 0; -} - +int exile_append_path_policies(struct exile_policy *exile_policy, unsigned int path_policy, ...); #define exile_append_path_policies(e, p, ...) exile_append_path_policies(e, p, __VA_ARGS__, NULL) -/* - * Fills buffer with random characters a-z. - * The string will be null terminated. - * - * @returns: number of written chars (excluding terminating null byte) on success - */ -int random_string(char *buffer, size_t buffer_length) -{ - int r = getrandom(buffer, buffer_length-1, GRND_NONBLOCK); - if(r != -1 && (size_t) r == buffer_length-1) - { - int i = 0; - while(i < r) - { - buffer[i] = 'a' + ((unsigned int)buffer[i] % 26); - ++i; - } - buffer[buffer_length-1] = '\0'; - return i; - } - return 0; -} - - -/* Creates a directory/file and all necessary parent directories -* @returns: 0 on success, -ERRNO on failure -*/ -static int mkpath(const char *p, mode_t mode, int baseisfile) -{ - char path[PATH_MAX + 1] = {0}; - int ret = snprintf(path, sizeof(path), "%s%c", p, (baseisfile) ? '\0' : '/'); - if(ret < 0) - { - EXILE_LOG_ERROR("error during path concatination\n"); - return -EINVAL; - } - if((size_t)ret >= sizeof(path)) - { - EXILE_LOG_ERROR("path concatination truncated\n"); - return -EINVAL; - } - - char *begin = path; - char *end = begin + 1; - - while(*end) - { - if(*end == '/') - { - *end = 0; - if(mkdir(begin, mode) < 0) - { - if(errno != EEXIST) - { - EXILE_LOG_ERROR("Failed to create directory: %s\n", begin); - return -1; - } - } - *end = '/'; - while(*end == '/') - { - ++end; - } - } - else - { - ++end; - } - } - if(baseisfile) - { - ret = creat(p, mode); - if(ret == -1) - { - EXILE_LOG_ERROR("Failed to create file: %s\n", begin); - return ret; - } - close(ret); - return 0; - } - return 0; -} - -/* @returns: argument for mount(2) flags */ -static int get_policy_mount_flags(struct exile_path_policy *policy) -{ - int result = 0; - - if( (policy->policy & EXILE_FS_ALLOW_DEV) == 0) - { - result |= MS_NODEV; - } - - if( (policy->policy & EXILE_FS_ALLOW_EXEC) == 0) - { - result |= MS_NOEXEC; - } - - if( (policy->policy & EXILE_FS_ALLOW_SETUID) == 0) - { - result |= MS_NOSUID; - } - - if( (policy->policy & EXILE_FS_ALLOW_ALL_WRITE) == 0) - { - result |= MS_RDONLY; - } - - if( (policy->policy & EXILE_MOUNT_NOT_REC) == 0) - { - result |= MS_REC; - } - return result; -} - -static int path_policy_needs_landlock(struct exile_path_policy *path_policy) -{ - unsigned int policy = path_policy->policy; -#if HAVE_LANDLOCK == 1 - if(policy >= EXILE_FS_ALLOW_REMOVE_DIR) - { - return 1; - } -#endif - //Can't need it if we don't have support at compile time - return 0; -} - -/* TODO: we can do va_args */ -char *concat_path(const char *first, const char *second) -{ - char *result = (char *) calloc(1, PATH_MAX); - if(result == NULL) - { - EXILE_LOG_ERROR("calloc failed\n"); - return NULL; - } - //TODO: We can strip multiple redundant slashes - int written = snprintf(result, PATH_MAX, "%s/%s", first, second); - if(written < 0) - { - EXILE_LOG_ERROR("Error during path concatination\n"); - return NULL; - } - if(written >= PATH_MAX) - { - EXILE_LOG_ERROR("path concatination truncated\n"); - return NULL; - } - return result; -} - - -/* Creates the file system hierarchy for the chroot - * @returns: 0 on sucess, -ERRNO on failure */ -static int create_chroot_dirs(const char *chroot_target_path, struct exile_path_policy *path_policy) -{ - while(path_policy != NULL) - { - struct stat sb; - int ret = stat(path_policy->path, &sb); - if(ret < 0) - { - EXILE_LOG_ERROR("stat failed\n"); - return ret; - } - - int baseisfile = 0; - if(S_ISREG(sb.st_mode)) - { - baseisfile = 1; - } - - char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path); - if(path_inside_chroot == NULL) - { - return 1; - } - - ret = mkpath(path_inside_chroot, 0700, baseisfile); - if(ret < 0) - { - EXILE_LOG_ERROR("Error creating directory structure while mounting paths to chroot. %s\n", strerror(errno)); - free(path_inside_chroot); - return ret; - } - path_policy = path_policy->next; - free(path_inside_chroot); - } - - return 0; -} - -static int perform_mounts(const char *chroot_target_path, struct exile_path_policy *path_policy) -{ - while(path_policy != NULL) - { - int mount_flags = get_policy_mount_flags(path_policy); - - char *path_inside_chroot = concat_path(chroot_target_path, path_policy->path); - if(path_inside_chroot == NULL) - { - return 1; - } - //all we do is bind mounts - mount_flags |= MS_BIND; - - if(path_policy->policy & EXILE_FS_ALLOW_ALL_READ || path_policy->policy & EXILE_FS_ALLOW_ALL_WRITE) - { - int ret = mount(path_policy->path, path_inside_chroot, NULL, mount_flags, NULL); - if(ret < 0 ) - { - EXILE_LOG_ERROR("Failed to mount %s to %s: %s\n", path_policy->path, path_inside_chroot, strerror(errno)); - free(path_inside_chroot); - return ret; - } - - //remount so noexec, readonly etc. take effect - ret = mount(NULL, path_inside_chroot, NULL, mount_flags | MS_REMOUNT, NULL); - if(ret < 0 ) - { - EXILE_LOG_ERROR("Failed to remount %s: %s\n", path_inside_chroot, strerror(errno)); - free(path_inside_chroot); - return ret; - } - path_policy = path_policy->next; - free(path_inside_chroot); - } - } - return 0; -} - - +int path_policy_needs_landlock(struct exile_path_policy *path_policy); /* * Frees the memory taken by a exile_policy object */ -void exile_free_policy(struct exile_policy *ctxt) -{ - if(ctxt != NULL) - { - struct exile_path_policy *current = ctxt->path_policies; - while(current != NULL) - { - struct exile_path_policy *tmp = current; - current = current->next; - free(tmp); - } - - struct exile_syscall_policy *sc_policy = ctxt->syscall_policies; - while(sc_policy != NULL) - { - struct exile_syscall_policy *tmp = sc_policy; - sc_policy = sc_policy->next; - free(tmp); - } - free(ctxt); - } -} - -/* Enters the specified namespaces */ -static int enter_namespaces(int namespace_options) -{ - if(namespace_options & EXILE_UNSHARE_USER) - { - int ret = unshare(CLONE_NEWUSER); - if(ret == -1) - { - EXILE_LOG_ERROR("Failed to unshare user namespaces: %s\n", strerror(errno)); - return ret; - } - - uid_t current_uid = getuid(); - gid_t current_gid = getgid(); - - FILE *fp = fopen("/proc/self/setgroups", "w"); - if(fp == NULL) - { - EXILE_LOG_ERROR("fopen failed while trying to deny setgroups\n"); - return -1; - } - if(fprintf(fp, "deny") < 0) - { - EXILE_LOG_ERROR("fprintf failed while trying to write setgroups\n"); - return -1; - } - fclose(fp); - - fp = fopen("/proc/self/uid_map", "w"); - if(fp == NULL) - { - EXILE_LOG_ERROR("fopen failed while trying to write uid_map\n"); - return -1; - } - if(fprintf(fp, "0 %i", current_uid) < 0) - { - EXILE_LOG_ERROR("fprintf failed while trying to write uid_map\n"); - return -1; - } - fclose(fp); - - fp = fopen("/proc/self/gid_map", "w"); - if(fp == NULL) - { - EXILE_LOG_ERROR("fopen failed while trying to write gid_map\n"); - return -1; - } - if(fprintf(fp, "0 %i", current_gid) < 0) - { - EXILE_LOG_ERROR("fprintf failed while trying to write gid_map\n"); - return -1; - } - fclose(fp); - } - - if(namespace_options & EXILE_UNSHARE_MOUNT) - { - int ret = unshare(CLONE_NEWNS); - if(ret == -1) - { - EXILE_LOG_ERROR("Failed to unshare mount namespaces: %s\n", strerror(errno)); - return ret; - } - } - - if(namespace_options & EXILE_UNSHARE_NETWORK) - { - int ret = unshare(CLONE_NEWNET); - if(ret == -1) - { - EXILE_LOG_ERROR("Failed to unshare network namespace: %s\n", strerror(errno)); - return ret; - } - } - - return 0; -} - -/* Drops all capabiltiies held by the process - * - * @returns: 0 on sucess, -1 on error -*/ -static int drop_caps() -{ - int cap = 0; - int res = 0; - while((res = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) == 0) - { - ++cap; - } - - if(res == -1 && errno != EINVAL) - { - EXILE_LOG_ERROR("Failed to drop the capability bounding set!\n"); - return -errno; - } - - //TODO: systems that are not 64 bit - struct __user_cap_header_struct h = { 0 }; - h.pid = 0; - h.version = _LINUX_CAPABILITY_VERSION_3; - struct __user_cap_data_struct drop[2]; - drop[0].effective = 0; - drop[0].permitted = 0; - drop[0].inheritable = 0; - drop[1].effective = 0; - drop[1].permitted = 0; - drop[1].inheritable = 0; - if(capset(&h, drop) == -1) - { - EXILE_LOG_ERROR("Failed to drop capabilities: %s\n", strerror(errno)); - return -errno; - } - return 0; -} +void exile_free_policy(struct exile_policy *ctxt); - -static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, struct sock_filter *filter, unsigned short int *start_index) -{ - unsigned int action = syscallpolicy->policy; - if(action == EXILE_SYSCALL_ALLOW) - { - action = SECCOMP_RET_ALLOW; - } - if(action == EXILE_SYSCALL_DENY_KILL_PROCESS) - { - action = SECCOMP_RET_KILL_PROCESS; - } - if(action == EXILE_SYSCALL_DENY_RET_ERROR) - { - action = SECCOMP_RET_ERRNO|EACCES; - } - long syscall = syscallpolicy->syscall; - - struct sock_filter syscall_load = BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)); - filter[(*start_index)++] = syscall_load; - if(syscall != EXILE_SYSCALL_MATCH_ALL) - { - /* How many steps forward to jump when we don't match. This is either the last statement, - * i. e. the default action or the next syscall policy */ - __u8 next_syscall_pc = 1; - if(__builtin_add_overflow(next_syscall_pc, syscallpolicy->argfilterscount, &next_syscall_pc)) - { - EXILE_LOG_ERROR("Overflow while trying to calculate jump offset\n"); - /* TODO: Return error */ - return; - } - struct sock_filter syscall_check = EXILE_BPF_CMP_EQ((unsigned int) syscall, 0, next_syscall_pc); - filter[(*start_index)++] = syscall_check; - --next_syscall_pc; - - struct sock_filter return_matching = EXILE_BPF_RETURN_MATCHING; - struct sock_filter return_not_matching = EXILE_BPF_RETURN_NOT_MATCHING; - - for(size_t i = 0; i < syscallpolicy->argfilterscount; i++) - { - filter[*start_index] = syscallpolicy->argfilters[i]; - struct sock_filter *current = &filter[*start_index]; - __u8 jump_count_next_syscall = next_syscall_pc; - __u8 jump_count_return = jump_count_next_syscall - 1; - if(current->jt == EXILE_SYSCALL_EXIT_BPF_NO_MATCH) - { - current->jt = jump_count_next_syscall; - } - if(current->jt == EXILE_SYSCALL_EXIT_BPF_RETURN) - { - current->jt = jump_count_return; - } - if(current->jf == EXILE_SYSCALL_EXIT_BPF_NO_MATCH) - { - current->jf = jump_count_next_syscall; - } - if(current->jf == EXILE_SYSCALL_EXIT_BPF_RETURN) - { - current->jf = jump_count_return; - } - if(current->code == return_matching.code && current->k == return_matching.k) - { - current->k = jump_count_return; - } - if(current->code == return_not_matching.code && current->k == return_not_matching.k) - { - current->k = jump_count_next_syscall; - } - --next_syscall_pc; - ++*start_index; - } - } - struct sock_filter syscall_action = BPF_STMT(BPF_RET+BPF_K, action); - /* TODO: we can do better than adding this below every jump */ - filter[(*start_index)++] = syscall_action; - -} /* * Enables the seccomp policy * @@ -1518,509 +465,10 @@ static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, st * * @returns: 0 on success, -1 on error */ - -static int exile_enable_syscall_policy(struct exile_policy *policy) -{ - struct sock_filter filter[1024] = - { - BPF_STMT(BPF_LD+BPF_W+BPF_ABS,offsetof(struct seccomp_data, arch)), - BPF_JUMP (BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_AUDIT_ARCH, 1, 0), - BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), - BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), - BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, __X32_SYSCALL_BIT, 0, 1), - BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), - }; - - unsigned short int current_filter_index = 6; - - struct exile_syscall_policy *current_policy = policy->syscall_policies; - while(current_policy) - { - if(!is_valid_syscall_policy(current_policy->policy)) - { - EXILE_LOG_ERROR("invalid syscall policy specified\n"); - return -1; - } - /* TODO: reintroduce overflow checks */ - append_syscall_to_bpf(current_policy, filter, ¤t_filter_index); - current_policy = current_policy->next; - } - - struct sock_fprog prog = { - .len = current_filter_index , - .filter = filter, - }; - - if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) - { - EXILE_LOG_ERROR("prctl SET_SECCOMP %s\n", strerror(errno)); - return -1; - } - - return 0; -} - -#if HAVE_LANDLOCK == 1 -static unsigned int exile_flags_to_landlock(unsigned int flags, int statmode) -{ - unsigned int result = 0; - if(flags & EXILE_FS_ALLOW_ALL_READ) - { - result |= LANDLOCK_ACCESS_FS_READ_FILE; - if(S_ISDIR(statmode)) - { - result |= LANDLOCK_ACCESS_FS_READ_DIR; - } - } - if(flags & EXILE_FS_ALLOW_ALL_WRITE) - { - result |= LANDLOCK_ACCESS_FS_WRITE_FILE; - if(S_ISDIR(statmode)) - { - result |= LANDLOCK_ACCESS_FS_REMOVE_FILE; - result |= LANDLOCK_ACCESS_FS_MAKE_REG; - result |= LANDLOCK_ACCESS_FS_REMOVE_DIR; - result |= LANDLOCK_ACCESS_FS_MAKE_SYM; - } - } - if(flags & EXILE_FS_ALLOW_EXEC) - { - result |= LANDLOCK_ACCESS_FS_EXECUTE; - } - if(flags & EXILE_FS_ALLOW_WRITE_FILE) - { - result |= LANDLOCK_ACCESS_FS_WRITE_FILE; - } - if(S_ISDIR(statmode)) - { - if(flags & EXILE_FS_ALLOW_DEV) - { - result |= LANDLOCK_ACCESS_FS_MAKE_BLOCK; - result |= LANDLOCK_ACCESS_FS_MAKE_CHAR; - } - if(flags & EXILE_FS_ALLOW_MAKE_BLOCK) - { - result |= LANDLOCK_ACCESS_FS_MAKE_BLOCK; - } - if(flags & EXILE_FS_ALLOW_MAKE_CHAR) - { - result |= LANDLOCK_ACCESS_FS_MAKE_CHAR; - } - if(flags & EXILE_FS_ALLOW_MAKE_DIR) - { - result |= LANDLOCK_ACCESS_FS_MAKE_DIR; - } - if(flags & EXILE_FS_ALLOW_MAKE_FIFO) - { - result |= LANDLOCK_ACCESS_FS_MAKE_FIFO; - } - if(flags & EXILE_FS_ALLOW_MAKE_REG) - { - result |= LANDLOCK_ACCESS_FS_MAKE_REG; - } - if(flags & EXILE_FS_ALLOW_MAKE_SOCK) - { - result |= LANDLOCK_ACCESS_FS_MAKE_SOCK; - } - if(flags & EXILE_FS_ALLOW_MAKE_SYM) - { - result |= LANDLOCK_ACCESS_FS_MAKE_SYM; - } - if(flags & EXILE_FS_ALLOW_REMOVE) - { - result |= LANDLOCK_ACCESS_FS_REMOVE_DIR; - result |= LANDLOCK_ACCESS_FS_REMOVE_FILE; - } - if(flags & EXILE_FS_ALLOW_REMOVE_DIR) - { - result |= LANDLOCK_ACCESS_FS_REMOVE_DIR; - } - if(flags & EXILE_FS_ALLOW_REMOVE_FILE) - { - result |= LANDLOCK_ACCESS_FS_REMOVE_FILE; - } - if(flags & EXILE_FS_ALLOW_READ_DIR) - { - result |= LANDLOCK_ACCESS_FS_READ_DIR; - } - } - return result; -} - -static int landlock_prepare_ruleset(struct exile_path_policy *policies) -{ - int ruleset_fd = -1; - struct landlock_ruleset_attr ruleset_attr; - /* We here want the maximum possible ruleset, so set the var to the max possible bitmask. - Stolen/Adapted from: [linux src]/security/landlock/limits.h - */ - ruleset_attr.handled_access_fs = ((LANDLOCK_ACCESS_FS_MAKE_SYM << 1) - 1); - - ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); - if (ruleset_fd < 0) - { - EXILE_LOG_ERROR("Failed to create landlock ruleset\n"); - return -1; - } - struct exile_path_policy *policy = policies; - while(policy != NULL) - { - struct landlock_path_beneath_attr path_beneath; - path_beneath.parent_fd = open(policy->path, O_PATH | O_CLOEXEC); - if(path_beneath.parent_fd < 0) - { - EXILE_LOG_ERROR("Failed to open policy path %s while preparing landlock ruleset\n", policy->path); - close(ruleset_fd); - return path_beneath.parent_fd; - } - struct stat sb; - int ret = fstat(path_beneath.parent_fd, &sb); - if(ret) - { - EXILE_LOG_ERROR("fstat failed %s\n", strerror(errno)); - close(ruleset_fd); - return ret; - } - path_beneath.allowed_access = exile_flags_to_landlock(policy->policy, sb.st_mode); - ret = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0); - if(ret) - { - EXILE_LOG_ERROR("Failed to update ruleset while processsing policy path %s\n", policy->path); - close(ruleset_fd); - return ret; - } - policy = policy->next; - } - return ruleset_fd; -} -#endif +int exile_enable_syscall_policy(struct exile_policy *policy); -/* Checks for illogical or dangerous combinations */ -static int check_policy_sanity(struct exile_policy *policy) -{ - if(policy->no_new_privs != 1) - { - if(policy->syscall_policies != NULL) - { - EXILE_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n"); - return -1; - } - } - - int can_use_landlock = exile_landlock_is_available(); - if(!can_use_landlock) - { - struct exile_path_policy *path_policy = policy->path_policies; - while(path_policy) - { - if(path_policy_needs_landlock(path_policy)) - { - EXILE_LOG_ERROR("A path policy needs landlock, but landlock is not available. Fallback not possible\n"); - return -1; - } - path_policy = path_policy->next; - } - } - - /* TODO: check if we have ALLOWED, but no default deny */ - - if(policy->mount_path_policies_to_chroot == 1) - { - if(policy->path_policies == NULL) - { - EXILE_LOG_ERROR("Cannot mount path policies to chroot if none are given\n"); - return -1; - } - if(!(policy->namespace_options & EXILE_UNSHARE_MOUNT)) - { - EXILE_LOG_ERROR("mount_path_policies_to_chroot = 1 requires unsharing mount namespace\n"); - return -1; - } - } - - - if(policy->path_policies != NULL) - { - - if(policy->mount_path_policies_to_chroot != 1) - { - #if HAVE_LANDLOCK != 1 - EXILE_LOG_ERROR("Path policies cannot be enforced! System needs landlock support or set mount_path_policies_to_chroot = 1\n"); - return -1; - #endif - } - if(policy->no_fs == 1) - { - EXILE_LOG_ERROR("If path_policies are specified, no_fs cannot be set to 1\n"); - return -1; - } - } - - struct exile_syscall_policy *syscall_policy = policy->syscall_policies; - if(syscall_policy != NULL) - { - /* A few sanitiy checks... but we cannot check overall whether it's reasonable */ - int i = 0; - int last_match_all = -1; - int match_all_policy = 0; - int last_policy = 0; - while(syscall_policy) - { - if(syscall_policy->syscall == EXILE_SYSCALL_MATCH_ALL) - { - last_match_all = i; - match_all_policy = syscall_policy->policy; - } - else - { - last_policy = syscall_policy->policy; - } - syscall_policy = syscall_policy->next; - ++i; - } - if(last_match_all == -1 || i - last_match_all != 1) - { - EXILE_LOG_ERROR("The last entry in the syscall policy list must match all syscalls (default rule)\n"); - return -1; - } - /* Most likely a mistake and not intended */ - if(last_policy == match_all_policy) - { - EXILE_LOG_ERROR("Last policy for a syscall matches default policy\n"); - return -1; - } - } - - return 0; -} - -static void close_file_fds() -{ - long max_files = sysconf(_SC_OPEN_MAX); - for(long i = 3; i <= max_files; i++) - { - close((int)i); - } -} - -/* Takes away file system access from the process - * - * We use this when "no_fs" is given in the policy. - * - * This is useful for restricted subprocesses that do some computational work - * and do not require filesystem access - * - * @returns: 0 on success, < 0 on error - */ -static int enable_no_fs(struct exile_policy *policy) -{ - close_file_fds(); - - if(chdir("/proc/self/fdinfo") != 0) - { - EXILE_LOG_ERROR("Failed to change to safe directory: %s\n", strerror(errno)); - return -1; - } - - if(chroot(".") != 0) - { - EXILE_LOG_ERROR("Failed to chroot into safe directory: %s\n", strerror(errno)); - return -1; - } - - if(chdir("/") != 0) - { - EXILE_LOG_ERROR("Failed to chdir into safe directory inside chroot: %s\n", strerror(errno)); - return -1; - } - - return 0; -} - -/* Enables the specified exile_policy. - * - * This function is not atomic (and can't be). This means some - * policies can apply, while others may fail. - * - * This function returns success only if all policies applied. - * - * The state is undefined if this function fails. The process generally - * should exit. - * - * @returns: 0 on success (all policies applied), < 0 on error (none or some policies dit not apply) - */ -int exile_enable_policy(struct exile_policy *policy) -{ - if((policy->exile_flags & EXILE_FLAG_ADD_PATH_POLICY_FAIL) || (policy->exile_flags & EXILE_FLAG_ADD_SYSCALL_POLICY_FAIL)) - { - EXILE_LOG_ERROR("At least one syscall or path policy was not successfully added!\n"); - return -1; - } - if(check_policy_sanity(policy) != 0) - { - EXILE_LOG_ERROR("Policy sanity check failed. Cannot apply policy!\n"); - return -EINVAL; - } - - if(enter_namespaces(policy->namespace_options) < 0) - { - EXILE_LOG_ERROR("Error while trying to enter namespaces\n"); - return -1; - } - - int can_use_landlock = exile_landlock_is_available(); - - - /* Fallback to chroot mechanism to enforce policies. Ignore mount_path_policies_to_chroot - * if we have no other option (so no landlock) */ - if((policy->mount_path_policies_to_chroot || !can_use_landlock) && policy->path_policies != NULL) - { - if(*policy->chroot_target_path == '\0') - { - char random_str[17]; - if(random_string(random_str, sizeof(random_str)) == 16) - { - int res = snprintf(policy->chroot_target_path, sizeof(policy->chroot_target_path), "%s/.sandbox_%" PRIdMAX "_%s", EXILE_TEMP_DIR, (intmax_t)getpid(), random_str); - if(res < 0) - { - EXILE_LOG_ERROR("error during path concatination\n"); - return -EINVAL; - } - if(res >= PATH_MAX) - { - EXILE_LOG_ERROR("path concatination truncated\n"); - return -EINVAL; - } - } - else - { - EXILE_LOG_ERROR("Error creating random sandbox directory name\n"); - return -1; - } - } - - if(create_chroot_dirs(policy->chroot_target_path, policy->path_policies) < 0) - { - EXILE_LOG_ERROR("bind mounting of path policies failed\n"); - return -1; - } - - if(perform_mounts(policy->chroot_target_path, policy->path_policies) < 0) - { - EXILE_LOG_ERROR("Failed to remount\n"); - return -1; - } - } - - if(*policy->chroot_target_path != '\0') - { - if(chroot(policy->chroot_target_path) < 0) - { - EXILE_LOG_ERROR("failed to enter %s\n", policy->chroot_target_path); - return -1; - } - const char *chdir_target_path = policy->chdir_path; - if(chdir_target_path == NULL) - { - chdir_target_path = "/"; - } - - if(chdir(chdir_target_path) < 0) - { - EXILE_LOG_ERROR("chdir to %s failed\n", policy->chdir_path); - return -1; - } - } - -#if HAVE_LANDLOCK == 1 - int landlock_ruleset_fd = -1; - if(can_use_landlock && policy->path_policies != NULL) - { - landlock_ruleset_fd = landlock_prepare_ruleset(policy->path_policies); - if(landlock_ruleset_fd < 0) - { - EXILE_LOG_ERROR("Failed to prepare landlock ruleset: %s\n", strerror(errno)); - return -1; - } - } -#endif - - if(policy->no_fs) - { - if(enable_no_fs(policy) != 0) - { - EXILE_LOG_ERROR("Failed to take away filesystem access of process\n"); - return -1; - } - } - - if(policy->no_new_fds) - { - const struct rlimit nofile = {0, 0}; - if (setrlimit(RLIMIT_NOFILE, &nofile) == -1) - { - EXILE_LOG_ERROR("setrlimit: Failed to set rlimit: %s\n", strerror(errno)); - return -1; - } - } - - if(policy->drop_caps) - { - if(drop_caps() < 0) - { - EXILE_LOG_ERROR("failed to drop capabilities\n"); - return -1; - } - } - - if(policy->not_dumpable) - { - if(prctl(PR_SET_DUMPABLE, 0) == -1) - { - EXILE_LOG_ERROR("prctl: PR_SET_DUMPABLE failed\n"); - return -1; - } - } - - if(policy->no_new_privs) - { - if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) - { - EXILE_LOG_ERROR("prctl: PR_SET_NO_NEW_PRIVS failed: %s\n", strerror(errno)); - return -1; - } - } - -#if HAVE_LANDLOCK == 1 - if (can_use_landlock && policy->path_policies != NULL && landlock_restrict_self(landlock_ruleset_fd, 0) != 0) - { - perror("Failed to enforce ruleset"); - close(landlock_ruleset_fd); - return -1; - } - close(landlock_ruleset_fd); -#endif - - if(policy->vow_promises != 0) - { - int ret = exile_append_vow_promises(policy, policy->vow_promises); - if(ret != 0) - { - EXILE_LOG_ERROR("exile_append_vow_promises() failed: %i\n", ret); - return ret; - } - } - - if(policy->syscall_policies != NULL) - { - return exile_enable_syscall_policy(policy); - } - - - return 0; -} -#endif +int exile_enable_policy(struct exile_policy *policy); /* Convenience wrapper for the vow-related subset of exile.h @@ -2042,42 +490,7 @@ int exile_enable_policy(struct exile_policy *policy) * . * Return value: 0 on success, any other value on failure. */ -int exile_vow(uint64_t promises) -{ - struct __user_cap_header_struct h = { 0 }; - h.pid = 0; - h.version = _LINUX_CAPABILITY_VERSION_3; - struct __user_cap_data_struct cap[2]; - cap[0].effective = 0; - cap[0].permitted = 0; - cap[0].inheritable = 0; - cap[1].effective = 0; - cap[1].permitted = 0; - cap[1].inheritable = 0; - if(capget(&h, cap) == -1) - { - EXILE_LOG_ERROR("Failed to get capabilities: %s\n", strerror(errno)); - return -errno; - } - - struct exile_policy *policy = exile_create_policy(); - if(policy == NULL) - { - EXILE_LOG_ERROR("Failed to create policy\n"); - return 1; - } - - policy->vow_promises = promises; - if((cap[0].effective & (1<no_new_privs = 1; - } - int ret = exile_enable_policy(policy); - exile_free_policy(policy); - return ret; -} - - +int exile_vow(uint64_t promises); struct exile_launch_params { @@ -2096,32 +509,7 @@ struct exile_launch_result static int child_read_pipe[2]; static int child_write_pipe[2]; -static int exile_clone_handle(void *arg) -{ - struct exile_launch_params *params = (struct exile_launch_params *) arg; - struct exile_policy *policy = (struct exile_policy *) params->policy; - - int ret = exile_enable_policy(policy); - if(ret != 0) - { - EXILE_LOG_ERROR("Failed to enable policy\n"); - close(child_read_pipe[1]); - close(child_write_pipe[0]); - return 1; - } - ret = dup2(child_read_pipe[1], 1); - if(ret == -1) - { - EXILE_LOG_ERROR("Failed to redirect stdout to pipe\n"); - return 1; - } - ret = params->func(params->funcarg); - fclose(stdout); - close(child_read_pipe[1]); - close(child_write_pipe[0]); - return ret; -} - +int exile_clone_handle(void *arg); /* Helper to easily execute a single function sandboxed. * * Creates a child-process, then activates the policy contained in launch_params, @@ -2132,51 +520,8 @@ static int exile_clone_handle(void *arg) * if cloneflags is 0, the default ones are passed to clone(), otherwise the value of cloneflags * * Return value: Negative on error, otherwise the file descriptor to read from*/ -int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_result *launch_result) -{ - int ret = pipe(child_read_pipe); - if(ret != 0) - { - EXILE_LOG_ERROR("read pipe creation failed\n"); - return ret; - } +int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_result *launch_result); - ret = pipe(child_write_pipe); - if(ret != 0) - { - EXILE_LOG_ERROR("write pipe creation failed\n"); - return ret; - } - - struct rlimit rlimit; - ret = getrlimit(RLIMIT_STACK, &rlimit); - if(ret != 0) - { - EXILE_LOG_ERROR("Failed to get stack size: %s\n", strerror(errno)); - return ret; - } - size_t size = rlimit.rlim_cur; - char *stack = (char *) calloc(1, size); - if(stack == NULL) - { - EXILE_LOG_ERROR("Failed to allocate stack memory for child\n"); - return 1; - } - stack += size; - ret = clone(&exile_clone_handle, stack, 17 /* SIGCHLD */, launch_params); - if(ret == -1) - { - EXILE_LOG_ERROR("clone failed(): %s\n", strerror(errno)); - return ret; - } - close(child_read_pipe[1]); - close(child_write_pipe[0]); - - launch_result->tid = ret; - launch_result->read_fd = child_read_pipe[0]; - launch_result->write_fd = child_write_pipe[1]; - return 0; -} /* Helper for exile_launch, to easily read all output from a function * This function will read all output from a sandboxed function. It's up to the caller to ensure @@ -2187,59 +532,10 @@ int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_ * Return value: All data written by the function. The result should be passed to free() once not needed. NULL will * be returned on error. */ -char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n) -{ - *n = 0; - struct exile_launch_result launch_result; - int launch = exile_launch(launch_params, &launch_result); - if(launch < 0) - { - return NULL; - } - char *result = NULL; - size_t size = 0; - FILE *stream = open_memstream(&result, &size); - while(1) - { - char buffer[4096]; - int ret = read(launch_result.read_fd, buffer, sizeof(buffer)); - if(ret == 0) - { - break; - } - if(ret == -1) - { - if(errno == EINTR) - { - continue; - } - EXILE_LOG_ERROR("Failed to read from read file descriptor\n"); - close(launch_result.read_fd); - fclose(stream); - return NULL; - } - size_t written = fwrite(buffer, 1, ret, stream); - if(written != (size_t) ret) - { - EXILE_LOG_ERROR("Short item write"); - /* TODO: can we seek and free? */ - close(launch_result.read_fd); - fclose(stream); - return NULL; - } - } - fclose(stream); - int seek = fseek(stream, 0, SEEK_SET); - if(seek == -1) - { - EXILE_LOG_ERROR("fseek failed\n"); - close(launch_result.read_fd); - return NULL; - } - close(launch_result.read_fd); - *n = size; - return result; -} +char *exile_launch_get(struct exile_launch_params *launch_params, size_t *n); + #ifdef __cplusplus } #endif + +#endif diff --git a/test.c b/test.c index 58a099a..b2167e3 100644 --- a/test.c +++ b/test.c @@ -489,6 +489,7 @@ int test_no_new_fds() } +extern int mkpath(const char *p, mode_t mode, int baseisfile); int test_mkpath() { system("rm -rf /tmp/.exile.h/"); -- 2.46.2 From 41bd6e8f10f9d0f7de2d2c5fd9b2443d56b1824f Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 14 Mar 2022 22:26:22 +0100 Subject: [PATCH 13/16] exile.h: Retire static child_read/write_pipe vars --- exile.c | 22 +++++++++++----------- exile.h | 5 ++--- test.c | 5 ++++- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/exile.c b/exile.c index 79309c0..130d650 100644 --- a/exile.c +++ b/exile.c @@ -1702,11 +1702,11 @@ int exile_clone_handle(void *arg) if(ret != 0) { EXILE_LOG_ERROR("Failed to enable policy\n"); - close(child_read_pipe[1]); - close(child_write_pipe[0]); + close(params->child_read_pipe[1]); + close(params->child_write_pipe[0]); return 1; } - ret = dup2(child_read_pipe[1], 1); + ret = dup2(params->child_read_pipe[1], 1); if(ret == -1) { EXILE_LOG_ERROR("Failed to redirect stdout to pipe\n"); @@ -1714,8 +1714,8 @@ int exile_clone_handle(void *arg) } ret = params->func(params->funcarg); fclose(stdout); - close(child_read_pipe[1]); - close(child_write_pipe[0]); + close(params->child_read_pipe[1]); + close(params->child_write_pipe[0]); return ret; } @@ -1733,14 +1733,14 @@ int exile_clone_handle(void *arg) * Return value: Negative on error, otherwise the file descriptor to read from*/ int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_result *launch_result) { - int ret = pipe(child_read_pipe); + int ret = pipe(launch_params->child_read_pipe); if(ret != 0) { EXILE_LOG_ERROR("read pipe creation failed\n"); return ret; } - ret = pipe(child_write_pipe); + ret = pipe(launch_params->child_write_pipe); if(ret != 0) { EXILE_LOG_ERROR("write pipe creation failed\n"); @@ -1768,12 +1768,12 @@ int exile_launch(struct exile_launch_params *launch_params, struct exile_launch_ EXILE_LOG_ERROR("clone failed(): %s\n", strerror(errno)); return ret; } - close(child_read_pipe[1]); - close(child_write_pipe[0]); + close(launch_params->child_read_pipe[1]); + close(launch_params->child_write_pipe[0]); launch_result->tid = ret; - launch_result->read_fd = child_read_pipe[0]; - launch_result->write_fd = child_write_pipe[1]; + launch_result->read_fd = launch_params->child_read_pipe[0]; + launch_result->write_fd = launch_params->child_write_pipe[1]; return 0; } diff --git a/exile.h b/exile.h index 3809f54..d395851 100644 --- a/exile.h +++ b/exile.h @@ -497,6 +497,8 @@ struct exile_launch_params struct exile_policy *policy; /* Policy to activate before jumping to func */ int (*func)(void *); /* Function to be sandboxed */ void *funcarg; /* Arg to be passed */ + int child_read_pipe[2]; + int child_write_pipe[2]; }; struct exile_launch_result @@ -506,9 +508,6 @@ struct exile_launch_result int write_fd; }; -static int child_read_pipe[2]; -static int child_write_pipe[2]; - int exile_clone_handle(void *arg); /* Helper to easily execute a single function sandboxed. * diff --git a/test.c b/test.c index b2167e3..592a174 100644 --- a/test.c +++ b/test.c @@ -548,12 +548,14 @@ int test_fail_flags() return 0; } + +static int *read_pipe = NULL; int do_launch_test(void *arg) { int num = *(int *)(arg); num += 1; char buffer[512] = { 0 }; - read(child_write_pipe[0], buffer, sizeof(buffer)-1); + read(*read_pipe, buffer, sizeof(buffer)-1); printf("Sandboxed +1: %i\n", num); printf("Echoing: %s\n", buffer); fflush(stdout); @@ -569,6 +571,7 @@ int test_launch() params.func = &do_launch_test; params.funcarg = # params.policy = policy; + read_pipe = ¶ms.child_write_pipe[0]; int launchfd = exile_launch(¶ms, &res); if(launchfd < 0) { -- 2.46.2 From 0f39ee7061d72dffa4d80561c2f41becb40f7dc9 Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 14 Mar 2022 22:30:53 +0100 Subject: [PATCH 14/16] Makefile: Build exile.o separately, link it in all tests --- Makefile | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 090dd03..e277dd6 100644 --- a/Makefile +++ b/Makefile @@ -7,13 +7,17 @@ CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic clean: - rm -f test testcpp + rm -f test exile.o testcpp -test: test.c exile.h - $(CC) test.c exile.c -g $(CFLAGS) -o test -testcpp: test.cpp exile.h exile.hpp - $(CXX) test.cpp -g $(CXXFLAGS) -o testcpp +exile.o: exile.c exile.h + $(CC) -c exile.c -g $(CFLAGS) -o exile.o + +test: test.c exile.h exile.o + $(CC) test.c exile.o -g $(CFLAGS) -o test + +testcpp: test.cpp exile.h exile.hpp exile.o + $(CXX) test.cpp exile.o -g $(CXXFLAGS) -o testcpp tests: test testcpp -- 2.46.2 From f2ca26010a2bb6d9e270d6ade2e8789c02ac3b31 Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 14 Mar 2022 22:45:06 +0100 Subject: [PATCH 15/16] exile.hpp: Mark do_clone inline, not static --- exile.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exile.hpp b/exile.hpp index fb3d98f..61f1eb3 100644 --- a/exile.hpp +++ b/exile.hpp @@ -89,7 +89,7 @@ int exile_clone_handle_serializer(void * arg) return 0; } -static int do_clone(int (*clonefn)(void *), void *launcharg) +inline int do_clone(int (*clonefn)(void *), void *launcharg) { struct rlimit rlimit; int ret = getrlimit(RLIMIT_STACK, &rlimit); -- 2.46.2 From 73dae3a1026b94a176712b9c3145eca11dc02182 Mon Sep 17 00:00:00 2001 From: Albert S Date: Thu, 17 Mar 2022 15:17:28 +0100 Subject: [PATCH 16/16] append_syscall_to_bpf(): Check for unlikely case of too many sock_filters --- exile.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/exile.c b/exile.c index 130d650..c06903f 100644 --- a/exile.c +++ b/exile.c @@ -1046,8 +1046,18 @@ static int drop_caps() } +static void assign_filter(struct sock_filter *left, struct sock_filter *right, struct sock_filter *endfilter) +{ + if(left <= endfilter) + { + *left = *right; + return; + } + EXILE_LOG_ERROR("Too many syscall filters installed! Aborting.\n"); + abort(); +} -static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, struct sock_filter *filter, unsigned short int *start_index) +static struct sock_filter *append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, struct sock_filter *filter, struct sock_filter *endfilter) { unsigned int action = syscallpolicy->policy; if(action == EXILE_SYSCALL_ALLOW) @@ -1065,7 +1075,9 @@ static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, st long syscall = syscallpolicy->syscall; struct sock_filter syscall_load = BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)); - filter[(*start_index)++] = syscall_load; + assign_filter(filter, &syscall_load, endfilter); + ++filter; + if(syscall != EXILE_SYSCALL_MATCH_ALL) { /* How many steps forward to jump when we don't match. This is either the last statement, @@ -1074,11 +1086,11 @@ static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, st if(__builtin_add_overflow(next_syscall_pc, syscallpolicy->argfilterscount, &next_syscall_pc)) { EXILE_LOG_ERROR("Overflow while trying to calculate jump offset\n"); - /* TODO: Return error */ - return; + abort(); } struct sock_filter syscall_check = EXILE_BPF_CMP_EQ((unsigned int) syscall, 0, next_syscall_pc); - filter[(*start_index)++] = syscall_check; + assign_filter(filter, &syscall_check, endfilter); + ++filter; --next_syscall_pc; struct sock_filter return_matching = EXILE_BPF_RETURN_MATCHING; @@ -1086,8 +1098,8 @@ static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, st for(size_t i = 0; i < syscallpolicy->argfilterscount; i++) { - filter[*start_index] = syscallpolicy->argfilters[i]; - struct sock_filter *current = &filter[*start_index]; + assign_filter(filter, &syscallpolicy->argfilters[i], endfilter); + struct sock_filter *current = filter; __u8 jump_count_next_syscall = next_syscall_pc; __u8 jump_count_return = jump_count_next_syscall - 1; if(current->jt == EXILE_SYSCALL_EXIT_BPF_NO_MATCH) @@ -1115,13 +1127,13 @@ static void append_syscall_to_bpf(struct exile_syscall_policy *syscallpolicy, st current->k = jump_count_next_syscall; } --next_syscall_pc; - ++*start_index; + ++filter; } } struct sock_filter syscall_action = BPF_STMT(BPF_RET+BPF_K, action); /* TODO: we can do better than adding this below every jump */ - filter[(*start_index)++] = syscall_action; - + assign_filter(filter, &syscall_action, endfilter); + return ++filter; } static int is_valid_syscall_policy(unsigned int policy) @@ -1149,7 +1161,8 @@ int exile_enable_syscall_policy(struct exile_policy *policy) BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), }; - unsigned short int current_filter_index = 6; + struct sock_filter *current_target = &filter[6]; + struct sock_filter *end = &filter[1023]; struct exile_syscall_policy *current_policy = policy->syscall_policies; while(current_policy) @@ -1159,13 +1172,13 @@ int exile_enable_syscall_policy(struct exile_policy *policy) EXILE_LOG_ERROR("invalid syscall policy specified\n"); return -1; } - /* TODO: reintroduce overflow checks */ - append_syscall_to_bpf(current_policy, filter, ¤t_filter_index); + current_target = append_syscall_to_bpf(current_policy, current_target, end); current_policy = current_policy->next; } + unsigned short len = (current_target - &filter[0]); struct sock_fprog prog = { - .len = current_filter_index , + .len = len , .filter = filter, }; -- 2.46.2