From 197ccad0ebe6501e923fadee58aebd9aeb9ce0fd Mon Sep 17 00:00:00 2001 From: dongresource Date: Sat, 28 Sep 2024 14:14:49 +0200 Subject: [PATCH] [sandbox] Landlock support * Support disabling Landlock at compile time or runtime if unsupported, without disabling seccomp * Support older Landlock ABI versions * Support an extra arbitrary RW path, inteded for the coredump dir * Support database locations other than the working directory --- src/main.cpp | 4 + src/sandbox/Sandbox.hpp | 7 ++ src/sandbox/openbsd.cpp | 3 + src/sandbox/seccomp.cpp | 166 +++++++++++++++++++++++++++++++++++++++- src/settings.cpp | 2 + src/settings.hpp | 1 + 6 files changed, 180 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 288e537..68e93a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,6 +49,7 @@ CNShardServer *shardServer = nullptr; std::thread *shardThread = nullptr; void startShard(CNShardServer* server) { + sandbox_thread_start(); server->start(); } @@ -150,6 +151,8 @@ int main() { /* not reached */ } + sandbox_init(); + std::cout << "[INFO] Starting Server Threads..." << std::endl; CNLoginServer loginServer(settings::LOGINPORT); shardServer = new CNShardServer(settings::SHARDPORT); @@ -157,6 +160,7 @@ int main() { shardThread = new std::thread(startShard, (CNShardServer*)shardServer); sandbox_start(); + sandbox_thread_start(); loginServer.start(); diff --git a/src/sandbox/Sandbox.hpp b/src/sandbox/Sandbox.hpp index 7c115eb..90eb302 100644 --- a/src/sandbox/Sandbox.hpp +++ b/src/sandbox/Sandbox.hpp @@ -4,11 +4,16 @@ #if defined(__linux__) || defined(__OpenBSD__) # if !defined(CONFIG_NOSANDBOX) +void sandbox_init(); void sandbox_start(); +void sandbox_thread_start(); # else #include +inline void sandbox_init() {} +inline void sandbox_thread_start() {} + inline void sandbox_start() { std::cout << "[WARN] Built without a sandbox" << std::endl; } @@ -17,5 +22,7 @@ inline void sandbox_start() { #else // stub for unsupported platforms +inline void sandbox_init() {} inline void sandbox_start() {} +inline void sandbox_thread_start() {} #endif diff --git a/src/sandbox/openbsd.cpp b/src/sandbox/openbsd.cpp index 0b5ac9f..d9882cb 100644 --- a/src/sandbox/openbsd.cpp +++ b/src/sandbox/openbsd.cpp @@ -13,6 +13,9 @@ static void eunveil(const char *path, const char *permissions) { err(1, "unveil"); } +void sandbox_init() {} +void sandbox_thread_start() {} + void sandbox_start() { /* * There shouldn't ever be a reason to disable this one, but might as well diff --git a/src/sandbox/seccomp.cpp b/src/sandbox/seccomp.cpp index 6d930fd..ddeaedc 100644 --- a/src/sandbox/seccomp.cpp +++ b/src/sandbox/seccomp.cpp @@ -4,6 +4,9 @@ #include "settings.hpp" #include +#include + +#include #include #include @@ -17,6 +20,10 @@ #include #include // for socketcall() args +#ifndef CONFIG_NOLANDLOCK +#include +#endif + /* * Macros adapted from https://outflux.net/teach-seccomp/ * Relevant license: @@ -297,25 +304,178 @@ static sock_fprog prog = { ARRLEN(filter), filter }; -// our own wrapper for the seccomp() syscall +// Our own wrapper for the seccomp() syscall. int seccomp(unsigned int operation, unsigned int flags, void *args) { return syscall(__NR_seccomp, operation, flags, args); } -void sandbox_start() { +#ifndef CONFIG_NOLANDLOCK + +// Support compilation on systems that only have older Landlock headers. +#ifndef LANDLOCK_ACCESS_FS_REFER +#define LANDLOCK_ACCESS_FS_REFER 0 +#endif +#ifndef LANDLOCK_ACCESS_FS_TRUNCATE +#define LANDLOCK_ACCESS_FS_TRUNCATE 0 +#endif + +struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE + | LANDLOCK_ACCESS_FS_WRITE_FILE + | LANDLOCK_ACCESS_FS_READ_DIR + | LANDLOCK_ACCESS_FS_MAKE_REG + | LANDLOCK_ACCESS_FS_MAKE_DIR + | LANDLOCK_ACCESS_FS_MAKE_SYM + | LANDLOCK_ACCESS_FS_MAKE_SOCK + | LANDLOCK_ACCESS_FS_MAKE_FIFO + | LANDLOCK_ACCESS_FS_MAKE_BLOCK + | LANDLOCK_ACCESS_FS_REMOVE_FILE + | LANDLOCK_ACCESS_FS_REMOVE_DIR + | LANDLOCK_ACCESS_FS_TRUNCATE + | LANDLOCK_ACCESS_FS_REFER +}; + +uint64_t landlock_perms = LANDLOCK_ACCESS_FS_READ_FILE + | LANDLOCK_ACCESS_FS_WRITE_FILE + | LANDLOCK_ACCESS_FS_TRUNCATE + | LANDLOCK_ACCESS_FS_MAKE_REG + | LANDLOCK_ACCESS_FS_REMOVE_FILE; + +int landlock_fd; +bool landlock_supported; + +/* + * Our own wrappers for Landlock syscalls. + */ + +int landlock_create_ruleset(const struct landlock_ruleset_attr *attr, size_t size, uint32_t flags) { + return syscall(__NR_landlock_create_ruleset, attr, size, flags); +} + +int landlock_add_rule(int ruleset_fd, enum landlock_rule_type rule_type, const void *rule_attr, uint32_t flags) { + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags); +} + +int landlock_restrict_self(int ruleset_fd, uint32_t flags) { + return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); +} + +static void landlock_path(std::string path, uint32_t perms) { + struct landlock_path_beneath_attr path_beneath = { + .allowed_access = perms + }; + + path_beneath.parent_fd = open(path.c_str(), O_PATH|O_CLOEXEC); + if (path_beneath.parent_fd < 0) { + perror(path.c_str()); + exit(1); + } + + if (landlock_add_rule(landlock_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) { + perror("landlock_add_rule"); + exit(1); + } + + close(path_beneath.parent_fd); +} + +static bool landlock_detect() { + int abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); + + if (abi < 0) { + if (errno == ENOSYS || errno == EOPNOTSUPP) { + std::cout << "[WARN] No Landlock support on this system" << std::endl; + return false; + } + perror("landlock_create_ruleset"); + exit(1); + } + + std::cout << "[INFO] Detected Landlock ABI version: " << abi << std::endl; + + switch (abi) { + case 1: + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; + landlock_perms &= ~LANDLOCK_ACCESS_FS_REFER; + // fallthrough + case 2: + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; + landlock_perms &= ~LANDLOCK_ACCESS_FS_TRUNCATE; + } + + return true; +} + +static void landlock_init() { + std::cout << "[INFO] Setting up Landlock sandbox..." << std::endl; + + landlock_supported = landlock_detect(); + + if (!landlock_supported) + return; + + landlock_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + if (landlock_fd < 0) { + perror("landlock_create_ruleset"); + exit(1); + } + + std::string dbdir = std::filesystem::path(settings::DBPATH).parent_path(); + + // for the DB files (we can't rely on them being in the working directory) + landlock_path(dbdir == "" ? "." : dbdir, landlock_perms); + // for writing the gruntwork file + landlock_path(settings::TDATADIR, landlock_perms); + // for passowrd salting during account creation + landlock_path("/dev/urandom", LANDLOCK_ACCESS_FS_READ_FILE); + // for core dumps, optionally + if (settings::SANDBOXEXTRAPATH != "") + landlock_path(settings::SANDBOXEXTRAPATH, landlock_perms); +} + +#endif // !CONFIG_NOLANDLOCK + +void sandbox_init() { if (!settings::SANDBOX) { std::cout << "[WARN] Running without a sandbox" << std::endl; return; } +#ifndef CONFIG_NOLANDLOCK + landlock_init(); +#else + std::cout << "[WARN] Built without Landlock" << std::endl; +#endif +} + +void sandbox_start() { + if (!settings::SANDBOX) + return; + std::cout << "[INFO] Starting seccomp-bpf sandbox..." << std::endl; + // Sandboxing starts in sandbox_thread_start(). +} + +void sandbox_thread_start() { + if (!settings::SANDBOX) + return; + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { perror("prctl"); exit(1); } - if (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog) < 0) { +#ifndef CONFIG_NOLANDLOCK + if (landlock_supported) { + if (landlock_restrict_self(landlock_fd, 0)) { + perror("landlock_restrict_self"); + exit(1); + } + } +#endif + + if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog) < 0) { perror("seccomp"); exit(1); } diff --git a/src/settings.cpp b/src/settings.cpp index 9815a40..19c9b13 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -9,6 +9,7 @@ // defaults :) int settings::VERBOSITY = 1; bool settings::SANDBOX = true; +std::string settings::SANDBOXEXTRAPATH = ""; int settings::LOGINPORT = 23000; bool settings::APPROVEALLNAMES = true; @@ -85,6 +86,7 @@ void settings::init() { VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY); SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); + SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH); LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES); AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS); diff --git a/src/settings.hpp b/src/settings.hpp index 302c4f6..eb17c8d 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -5,6 +5,7 @@ namespace settings { extern int VERBOSITY; extern bool SANDBOX; + extern std::string SANDBOXEXTRAPATH; extern int LOGINPORT; extern bool APPROVEALLNAMES; extern bool AUTOCREATEACCOUNTS;