Proof-of-concept, default-permit seccomp-bpf sandbox

Can be disabled by adding -DCONFIG_NOSANDBOX to CXXFLAGS.
This commit is contained in:
dongresource 2021-11-04 03:12:11 +01:00
parent 05d6174351
commit 3c1e08372d
7 changed files with 138 additions and 2 deletions

View File

@ -48,6 +48,7 @@ CXXSRC=\
src/db/shard.cpp\
src/db/player.cpp\
src/db/email.cpp\
src/sandbox/seccomp.cpp\
src/Chat.cpp\
src/CustomCommands.cpp\
src/Entities.cpp\
@ -88,6 +89,7 @@ CXXHDR=\
src/servers/Monitor.hpp\
src/db/Database.hpp\
src/db/internal.hpp\
src/sandbox/Sandbox.hpp\
vendor/bcrypt/BCrypt.hpp\
vendor/INIReader.hpp\
vendor/JSON.hpp\

View File

@ -5,6 +5,9 @@
# 3 = print all packets
verbosity=1
# sandbox the process on supported platforms
sandbox=true
# Login Server configuration
[login]
# must be kept in sync with loginInfo.php

View File

@ -25,6 +25,7 @@
#include "Rand.hpp"
#include "settings.hpp"
#include "sandbox/Sandbox.hpp"
#include "../version.h"
@ -94,11 +95,13 @@ int main() {
#else
initsignals();
#endif
Rand::init(getTime());
settings::init();
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
Rand::init(getTime());
TableData::init();
PlayerManager::init();
PlayerMovement::init();
@ -138,6 +141,8 @@ int main() {
shardThread = new std::thread(startShard, (CNShardServer*)shardServer);
sandbox_start();
loginServer.start();
shardServer->kill();
@ -152,7 +157,7 @@ int main() {
// helper functions
std::string U16toU8(char16_t* src, size_t max) {
src[max-1] = '\0'; // force a NULL terminatorstd::string U16toU8(char16_t* src) {
src[max-1] = '\0'; // force a NULL terminator
try {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::string ret = convert.to_bytes(src);

21
src/sandbox/Sandbox.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
// use the sandbox on supported platforms, unless disabled
#if defined(__linux__) || defined(__OpenBSD__)
# if !defined(CONFIG_NOSANDBOX)
void sandbox_start();
# else
#include <iostream>
inline void sandbox_start() {
std::cout << "[WARN] Built without a sandbox" << std::endl;
}
# endif // CONFIG_NOSANDBOX
#else
// stub for unsupported platforms
inline void sandbox_start() {}
#endif

102
src/sandbox/seccomp.cpp Normal file
View File

@ -0,0 +1,102 @@
#if defined(__linux__) && !defined(CONFIG_NOSANDBOX)
#include "core/Core.hpp" // mostly for ARRLEN
#include "settings.hpp"
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <linux/unistd.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
// our own wrapper for the seccomp() syscall
// TODO: should this be conditional on a feature check or something?
static inline int seccomp(unsigned int operation, unsigned int flags, void *args) {
return syscall(__NR_seccomp, operation, flags, args);
}
/*
* Macros borrowed from from https://outflux.net/teach-seccomp/
* Relevant license:
* https://source.chromium.org/chromium/chromium/src/+/master:LICENSE
*/
#define syscall_nr (offsetof(struct seccomp_data, nr))
#define arch_nr (offsetof(struct seccomp_data, arch))
#if defined(__i386__)
# define ARCH_NR AUDIT_ARCH_I386
#elif defined(__x86_64__)
# define ARCH_NR AUDIT_ARCH_X86_64
#elif defined(__arm__)
# define ARCH_NR AUDIT_ARCH_ARM
#else
# error "Seccomp-bpf sandbox unsupported on this architecture"
#endif
#define VALIDATE_ARCHITECTURE \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define EXAMINE_SYSCALL \
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)
#define ALLOW_SYSCALL(name) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
#define KILL_PROCESS \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define DENY_SYSCALL(name) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS)
static sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
// examples of undesirable syscalls
DENY_SYSCALL(execve),
DENY_SYSCALL(fork),
DENY_SYSCALL(vfork),
DENY_SYSCALL(clone),
DENY_SYSCALL(connect),
DENY_SYSCALL(listen),
DENY_SYSCALL(bind),
DENY_SYSCALL(kill),
DENY_SYSCALL(settimeofday),
// etc
// default-permit mode
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
};
static sock_fprog prog = {
ARRLEN(filter), filter
};
void sandbox_start() {
if (!settings::SANDBOX) {
std::cout << "[WARN] Running without a sandbox" << std::endl;
return;
}
std::cout << "[INFO] Starting seccomp-bpf sandbox..." << std::endl;
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) {
perror("seccomp");
exit(1);
}
}
#endif // SANDBOX_SECCOMP

View File

@ -7,6 +7,7 @@
// defaults :)
int settings::VERBOSITY = 1;
bool settings::SANDBOX = true;
int settings::LOGINPORT = 23000;
bool settings::APPROVEALLNAMES = true;
@ -77,6 +78,7 @@ void settings::init() {
APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES);
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);

View File

@ -2,6 +2,7 @@
namespace settings {
extern int VERBOSITY;
extern bool SANDBOX;
extern int LOGINPORT;
extern bool APPROVEALLNAMES;
extern int DBSAVEINTERVAL;