From a79450b68b2fa1655693ae830a8c7eb4795e4acf Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 26 Sep 2020 17:03:26 +0200 Subject: [PATCH] sandbox: First version using qssb.h --- qswiki.cpp | 2 - sandbox/sandbox-linux.cpp | 266 +++++++------------------------------- sandbox/sandbox-linux.h | 6 +- sandbox/sandbox.h | 4 +- submodules/qssb.h | 2 +- 5 files changed, 52 insertions(+), 228 deletions(-) diff --git a/qswiki.cpp b/qswiki.cpp index 8772ff9..99e2fc7 100644 --- a/qswiki.cpp +++ b/qswiki.cpp @@ -96,8 +96,6 @@ int main(int argc, char **argv) Config config = configreader.readConfig(); //TODO: config.connectiontring only works as long as we only support sqlite of course - - if(!sandbox->enablePreWorker({ config.configVarResolver.getConfig("cache_fs_dir"), config.templatepath, diff --git a/sandbox/sandbox-linux.cpp b/sandbox/sandbox-linux.cpp index 633973e..5b3e43d 100644 --- a/sandbox/sandbox-linux.cpp +++ b/sandbox/sandbox-linux.cpp @@ -1,5 +1,3 @@ -#include -#include #include #include #include @@ -14,6 +12,7 @@ #include #include #include +#include #include "../logger.h" #include "../utils.h" #include "../random.h" @@ -24,178 +23,50 @@ * obvious systemcalls. To whitelist, we need to analyse our * dependencies (http library, sqlite wrapper, sqlite lib etc.) */ -/* TODO: cleanup our sandboxing directory (unmount and delete folders) after exit */ -bool SandboxLinux::seccomp_blacklist(std::initializer_list syscalls) -{ - scmp_filter_ctx ctx; - ctx = seccomp_init(SCMP_ACT_ALLOW); - if(ctx == NULL) - { - Logger::error() << "failed to init seccomp_init"; - return false; - } - - for(auto sc : syscalls) - { - if(seccomp_rule_add(ctx, SCMP_ACT_KILL_PROCESS, sc, 0) < 0) - { - Logger::error() << "Failed to add a seccomp rule"; - return false; - } - } - - bool success = seccomp_load(ctx) == 0; - if(!success) - { - Logger::error() << "Failed to load seccomp filter"; - return false; - } - seccomp_release(ctx); - return success; -} - -bool SandboxLinux::bindMountPaths(std::string target_root, const std::vector &paths) -{ - for(const std::string &path : paths) - { - std::string chroot_target_path = target_root + path; - if(std::filesystem::exists(chroot_target_path)) - { - continue; - } - if(std::filesystem::is_regular_file(path)) - { - std::fstream f1; - f1.open(chroot_target_path, std::ios::out); - f1.close(); - } - else - { - std::error_code ec; - //TODO: fails if the stuff already exists, but it shouldn't according to doc? - if(!std::filesystem::create_directories(chroot_target_path, ec)) - { - Logger::error() << "Error while trying to duplicate structure for sandbox. Dir creation failed. Path: " << chroot_target_path << " Error: " << ec.message(); - return false; - } - - if(mount(path.c_str(), chroot_target_path.c_str(), NULL, MS_BIND, NULL) == -1) - { - Logger::error() << "Bind mount for " << path << " -> " << chroot_target_path << " failed! " << strerror(errno); - return false; - } - } - - } - return true; -} - -bool SandboxLinux::isolateNamespaces(std::vector fsPaths) -{ - std::sort(fsPaths.begin(), fsPaths.end(), [](const std::string &a, const std::string &b){ return a.length() < b.length(); }); - - auto current_uid = getuid(); - auto current_gid = getgid(); - if(unshare(CLONE_NEWUSER) == -1) - { - Logger::error() << "Failed to unshare user namespace: " << strerror(errno); - return false; - } - - std::fstream setgroups; - setgroups.open("/proc/self/setgroups", std::ios::out | std::ios::app); - setgroups << "deny"; - setgroups.flush(); - setgroups.close(); - - std::fstream uid_map; - uid_map.open("/proc/self/uid_map", std::ios::out | std::ios::app); - uid_map << "0 " << current_uid << " 1\n"; - uid_map.flush(); - uid_map.close(); - - std::fstream gid_map; - gid_map.open("/proc/self/gid_map", std::ios::out); - uid_map << "0 " << current_gid << " 1\n"; - gid_map.flush(); - gid_map.close(); - - if(unshare(CLONE_NEWNS) == -1) - { - Logger::error() << "Failed to unshare mount namespace: " << strerror(errno); - return false; - } - - /* - * TODO: breaks server. - * TODO: fork? - * if(unshare(CLONE_NEWPID) == -1) - { - Logger::error() << "Failed to unshare pid namespace: " << strerror(errno); - return false; - }*/ - - - //The purpose is to start with a clean sandbox dir. - //We maybe could work with mkdirat, and check whether it exists alrady, to avoid - //some attacks where an attacker gueses the dir, but in that case the system is already compromised - //TODO: still, check, whether this is something we must consider here or not... - Random random; - std::string rootpath = "/tmp/qswiki_sandbox_" + random.getRandomHexString(10) + "/"; - if(!std::filesystem::create_directory(rootpath)) - { - Logger::error() << "Failed to create chroot directory for sandbox"; - return false; - } - - - if(!bindMountPaths(rootpath, fsPaths )) - { - Logger::error() << "Bind mounting paths failed!"; - return false; - } - - - - if(chroot(rootpath.c_str()) == -1) - { - Logger::error() << "chroot to sandbox failed!"; - return false; - } - if(chdir("/") == -1) - { - Logger::error() << "chdir to sandbox failed!"; - return false; - } - - return true; -} - bool SandboxLinux::enableForInit() { umask(0027); - - //TODO. there is execv for SPARC. Sigh... - if(!seccomp_blacklist({ SCMP_SYS(execveat), SCMP_SYS(execve) })) + struct qssb_policy policy = {0}; + int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1}; + policy.blacklisted_syscalls = blacklisted_syscalls; + policy.no_new_privs = 1; + int result = qssb_enable_policy(&policy); + if(result != 0) { - Logger::error() << "Failed to install blacklisting seccomp filter"; + Logger::error() << "Failed to install sandboxing policy (init): " << result; return false; } - return true; - - } bool SandboxLinux::enablePreWorker(std::vector fsPaths) { - if(!isolateNamespaces(fsPaths)) + std::sort(fsPaths.begin(), fsPaths.end(), + [](const std::string &a, const std::string &b) { return a.length() < b.length(); }); + + struct qssb_path_policy *policies = new qssb_path_policy[fsPaths.size()]; + for(unsigned int i = 0; i < fsPaths.size(); i++) { - Logger::error() << "Failed to isolate namespaces"; + policies[i].next = policies + (i + 1); + policies[i].mountpoint = fsPaths[i].c_str(); + policies[i].policy = QSSB_MOUNT_ALLOW_READ | QSSB_MOUNT_ALLOW_WRITE; + } + policies[fsPaths.size() - 1].next = NULL; + + struct qssb_policy policy = {0}; + policy.path_policies = policies; + policy.namespace_options |= QSSB_UNSHARE_MOUNT; + policy.namespace_options |= QSSB_UNSHARE_USER; + int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1}; + policy.blacklisted_syscalls = blacklisted_syscalls; + int result = qssb_enable_policy(&policy); + if(result != 0) + { + Logger::error() << "Failed to install sandboxing policy (preworker): %i" << result; return false; } + delete[] policies; return true; - } bool SandboxLinux::supported() @@ -208,7 +79,8 @@ bool SandboxLinux::supported() stream >> str; if(str[0] == '0') { - Logger::error() << "Please write '1' to /proc/sys/kernel/unprivileged_userns_clone in order to enable sandboxing support on this system"; + Logger::error() << "Please write '1' to /proc/sys/kernel/unprivileged_userns_clone in order to enable " + "sandboxing support on this system"; return false; } } @@ -216,65 +88,26 @@ bool SandboxLinux::supported() } bool SandboxLinux::enableForWorker() { - int cap = 0; - int res = 0; - while((res = prctl(PR_CAPBSET_DROP, cap++, 0, 0, 0)) == 0); - if(res == -1 && errno != EINVAL) - { - Logger::error() << "Failed to drop the capability bounding set!"; - return false; - } - - __user_cap_header_struct h = { 0 }; - h.pid = 0; - h.version = _LINUX_CAPABILITY_VERSION_3; - __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) - { - Logger::error() << "Failed to drop capabilities: " << strerror(errno); - return false; - } - - - + struct qssb_policy policy = {0}; + policy.drop_caps = 1; + policy.not_dumpable = 1; + policy.no_new_privs = 1; /* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the * sense that more could be listed here and some critical ones are probably missing */ - if(! seccomp_blacklist({ - SCMP_SYS(setuid), - SCMP_SYS(setuid32), - SCMP_SYS(connect), - SCMP_SYS(chroot), - SCMP_SYS(pivot_root), - SCMP_SYS(mount), - SCMP_SYS(setns), - SCMP_SYS(unshare), - SCMP_SYS(ptrace), - SCMP_SYS(personality) - - })) - { - Logger::error() << "Sandbox: Activation of seccomp blacklist failed!"; - return false; - } - if(prctl(PR_SET_DUMPABLE, 0) == -1) - { - Logger::error() << "prctl: PR_SET_DUMPABLE failed"; - return false; - } - if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) - { - Logger::error() << "prctl: PR_SET_NO_NEW_PRIVS failed: " << strerror(errno); - return false; - } - - if(! seccomp_blacklist({ SCMP_SYS(prctl) })) + int blacklisted_syscalls[] = {QSSB_SYS(setuid), + QSSB_SYS(connect), + QSSB_SYS(chroot), + QSSB_SYS(pivot_root), + QSSB_SYS(mount), + QSSB_SYS(setns), + QSSB_SYS(unshare), + QSSB_SYS(ptrace), + QSSB_SYS(personality), + QSSB_SYS(prctl), + -1}; + policy.blacklisted_syscalls = blacklisted_syscalls; + if(qssb_enable_policy(&policy) != 0) { Logger::error() << "Sandbox: Activation of seccomp blacklist failed!"; return false; @@ -282,4 +115,3 @@ bool SandboxLinux::enableForWorker() return true; } - diff --git a/sandbox/sandbox-linux.h b/sandbox/sandbox-linux.h index be10f8f..dbd22b7 100644 --- a/sandbox/sandbox-linux.h +++ b/sandbox/sandbox-linux.h @@ -5,15 +5,11 @@ #include "sandbox.h" class SandboxLinux : public Sandbox { -public: + public: using Sandbox::Sandbox; bool supported() override; bool enableForInit() override; bool enablePreWorker(std::vector fsPaths) override; bool enableForWorker() override; -private : - bool isolateNamespaces(std::vector fsPaths); - bool seccomp_blacklist(std::initializer_list syscalls); - bool bindMountPaths(std::string target_root, const std::vector &paths); }; #endif diff --git a/sandbox/sandbox.h b/sandbox/sandbox.h index 1572e7b..eb7e00a 100644 --- a/sandbox/sandbox.h +++ b/sandbox/sandbox.h @@ -3,10 +3,9 @@ #include class Sandbox { -public: + public: Sandbox() { - } /* Whether the platform has everything required to active all sandbnox modes */ virtual bool supported() = 0; @@ -18,7 +17,6 @@ public: /* Activated after config has been read. Now we now which paths we need access to */ virtual bool enablePreWorker(std::vector fsPaths) = 0; - /* Activated after we have acquired resources (bound to ports etc.) * * This should allow us to further restrcit the process */ diff --git a/submodules/qssb.h b/submodules/qssb.h index 60776be..763c65c 160000 --- a/submodules/qssb.h +++ b/submodules/qssb.h @@ -1 +1 @@ -Subproject commit 60776be4160fe7e2178b3790cd43a76d412843b6 +Subproject commit 763c65c3fee87c500b149fab321d0d12eeccedde -- 2.47.1