diff --git a/Makefile b/Makefile index dce4a1f..b8dfbdd 100644 --- a/Makefile +++ b/Makefile @@ -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\ diff --git a/config.ini b/config.ini index 8c52a4a..e721dd2 100644 --- a/config.ini +++ b/config.ini @@ -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 diff --git a/src/main.cpp b/src/main.cpp index 79fd565..39bbabd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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,char16_t> convert; std::string ret = convert.to_bytes(src); diff --git a/src/sandbox/Sandbox.hpp b/src/sandbox/Sandbox.hpp new file mode 100644 index 0000000..7c115eb --- /dev/null +++ b/src/sandbox/Sandbox.hpp @@ -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 + +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 diff --git a/src/sandbox/seccomp.cpp b/src/sandbox/seccomp.cpp new file mode 100644 index 0000000..9caf33d --- /dev/null +++ b/src/sandbox/seccomp.cpp @@ -0,0 +1,102 @@ +#if defined(__linux__) && !defined(CONFIG_NOSANDBOX) + +#include "core/Core.hpp" // mostly for ARRLEN +#include "settings.hpp" + +#include + +#include +#include + +#include +#include +#include +#include + +// 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 diff --git a/src/settings.cpp b/src/settings.cpp index 79b999d..2885fbc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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); diff --git a/src/settings.hpp b/src/settings.hpp index 39de71e..cbbeeb4 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -2,6 +2,7 @@ namespace settings { extern int VERBOSITY; + extern bool SANDBOX; extern int LOGINPORT; extern bool APPROVEALLNAMES; extern int DBSAVEINTERVAL;