sandbox: First version using qssb.h
This commit is contained in:
джерело
5abaaf67d0
коміт
75f76f58eb
@ -95,7 +95,6 @@ int main(int argc, char **argv)
|
|||||||
Config config = configreader.readConfig();
|
Config config = configreader.readConfig();
|
||||||
|
|
||||||
// TODO: config.connectiontring only works as long as we only support sqlite of course
|
// TODO: config.connectiontring only works as long as we only support sqlite of course
|
||||||
|
|
||||||
if(!sandbox->enablePreWorker({
|
if(!sandbox->enablePreWorker({
|
||||||
config.configVarResolver.getConfig("cache_fs_dir"),
|
config.configVarResolver.getConfig("cache_fs_dir"),
|
||||||
config.templatepath,
|
config.templatepath,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#include <sys/prctl.h>
|
|
||||||
#include <seccomp.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -14,6 +12,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
#include <sys/capability.h>
|
#include <sys/capability.h>
|
||||||
|
#include <qssb.h>
|
||||||
#include "../logger.h"
|
#include "../logger.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
#include "../random.h"
|
#include "../random.h"
|
||||||
@ -24,172 +23,49 @@
|
|||||||
* obvious systemcalls. To whitelist, we need to analyse our
|
* obvious systemcalls. To whitelist, we need to analyse our
|
||||||
* dependencies (http library, sqlite wrapper, sqlite lib etc.) */
|
* 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<int> 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<std::string> &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<std::string> 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()
|
bool SandboxLinux::enableForInit()
|
||||||
{
|
{
|
||||||
umask(0027);
|
umask(0027);
|
||||||
|
struct qssb_policy policy = {0};
|
||||||
// TODO. there is execv for SPARC. Sigh...
|
int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1};
|
||||||
if(!seccomp_blacklist({SCMP_SYS(execveat), SCMP_SYS(execve)}))
|
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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SandboxLinux::enablePreWorker(std::vector<std::string> fsPaths)
|
bool SandboxLinux::enablePreWorker(std::vector<std::string> 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
delete[] policies;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,55 +88,26 @@ bool SandboxLinux::supported()
|
|||||||
}
|
}
|
||||||
bool SandboxLinux::enableForWorker()
|
bool SandboxLinux::enableForWorker()
|
||||||
{
|
{
|
||||||
int cap = 0;
|
struct qssb_policy policy = {0};
|
||||||
int res = 0;
|
policy.drop_caps = 1;
|
||||||
while((res = prctl(PR_CAPBSET_DROP, cap++, 0, 0, 0)) == 0)
|
policy.not_dumpable = 1;
|
||||||
;
|
policy.no_new_privs = 1;
|
||||||
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
|
/* 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 */
|
* 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),
|
int blacklisted_syscalls[] = {QSSB_SYS(setuid),
|
||||||
SCMP_SYS(pivot_root), SCMP_SYS(mount), SCMP_SYS(setns), SCMP_SYS(unshare), SCMP_SYS(ptrace),
|
QSSB_SYS(connect),
|
||||||
SCMP_SYS(personality)
|
QSSB_SYS(chroot),
|
||||||
|
QSSB_SYS(pivot_root),
|
||||||
}))
|
QSSB_SYS(mount),
|
||||||
{
|
QSSB_SYS(setns),
|
||||||
Logger::error() << "Sandbox: Activation of seccomp blacklist failed!";
|
QSSB_SYS(unshare),
|
||||||
return false;
|
QSSB_SYS(ptrace),
|
||||||
}
|
QSSB_SYS(personality),
|
||||||
if(prctl(PR_SET_DUMPABLE, 0) == -1)
|
QSSB_SYS(prctl),
|
||||||
{
|
-1};
|
||||||
Logger::error() << "prctl: PR_SET_DUMPABLE failed";
|
policy.blacklisted_syscalls = blacklisted_syscalls;
|
||||||
return false;
|
if(qssb_enable_policy(&policy) != 0)
|
||||||
}
|
|
||||||
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!";
|
Logger::error() << "Sandbox: Activation of seccomp blacklist failed!";
|
||||||
return false;
|
return false;
|
||||||
|
@ -11,10 +11,5 @@ class SandboxLinux : public Sandbox
|
|||||||
bool enableForInit() override;
|
bool enableForInit() override;
|
||||||
bool enablePreWorker(std::vector<std::string> fsPaths) override;
|
bool enablePreWorker(std::vector<std::string> fsPaths) override;
|
||||||
bool enableForWorker() override;
|
bool enableForWorker() override;
|
||||||
|
|
||||||
private:
|
|
||||||
bool isolateNamespaces(std::vector<std::string> fsPaths);
|
|
||||||
bool seccomp_blacklist(std::initializer_list<int> syscalls);
|
|
||||||
bool bindMountPaths(std::string target_root, const std::vector<std::string> &paths);
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit dbdb35db371b79bb6a35cfbb9ef3815cdd5beff6
|
Subproject commit 763c65c3fee87c500b149fab321d0d12eeccedde
|
Завантаження…
Посилання в новій задачі
Block a user