#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../logger.h" #include "../utils.h" #include "../random.h" #include "sandbox-linux.h" /* TODO: make a whitelist approach. So far we simply blacklist * 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; } return success; } bool SandboxLinux::bindMountPaths(std::string target_root, std::initializer_list 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 failed! " << strerror(errno); return false; } } } return true; } bool SandboxLinux::isolateNamespaces(std::vector fsPaths) { 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; } for(std::string &path : fsPaths) { if(!bindMountPaths(rootpath, { path })) { Logger::error() << "Bind mount for " << path << " 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) })) { Logger::error() << "Failed to install blacklisting seccomp filter"; return false; } return true; } bool SandboxLinux::enablePreWorker(std::vector fsPaths) { if(!isolateNamespaces(fsPaths)) { Logger::error() << "Failed to isolate namespaces"; return false; } return true; } bool SandboxLinux::supported() { return true; } 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; } /* 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) })) { Logger::error() << "Sandbox: Activation of seccomp blacklist failed!"; return false; } return true; }