Skip to content

Commit

Permalink
Add syscall sandboxing (seccomp-bpf)
Browse files Browse the repository at this point in the history
  • Loading branch information
practicalswift committed Oct 1, 2021
1 parent e69cbac commit 4747da3
Show file tree
Hide file tree
Showing 27 changed files with 1,125 additions and 1 deletion.
1 change: 1 addition & 0 deletions ci/test/00_setup_env_i686_multiprocess.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export DEP_OPTS="DEBUG=1 MULTIPROCESS=1"
export GOAL="install"
export BITCOIN_CONFIG="--enable-debug CC='clang -m32' CXX='clang++ -m32' LDFLAGS='--rtlib=compiler-rt -lgcc_s'"
export TEST_RUNNER_ENV="BITCOIND=bitcoin-node"
export TEST_RUNNER_EXTRA="--nosandbox"
export PIP_PACKAGES="lief"
37 changes: 37 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ case $host in
;;
esac

AC_ARG_WITH([seccomp],
[AS_HELP_STRING([--with-seccomp],
[enable experimental syscall sandbox feature (-sandbox), default is yes if seccomp-bpf is detected under Linux x86_64])],
[seccomp_found=$withval],
[seccomp_found=auto])

dnl Require C++17 compiler (no GNU extensions)
AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])

Expand Down Expand Up @@ -1443,6 +1449,36 @@ if test "x$use_external_signer" != xno; then
fi
AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "x$use_external_signer" = "xyes"])

dnl Do not compile with syscall sandbox support when compiling under the sanitizers.
dnl The sanitizers introduce use of syscalls that are not typically used in bitcoind
dnl (such as execve when the sanitizers execute llvm-symbolizer).
if test x$use_sanitizers != x; then
AC_MSG_WARN(Specifying --with-sanitizers forces --without-seccomp since the sanitizers introduce use of syscalls not allowed by the bitcoind syscall sandbox (-sandbox=<mode>).)
seccomp_found=no
fi
if test "x$seccomp_found" != "xno"; then
AC_MSG_CHECKING([for seccomp-bpf (Linux x86-64)])
AC_PREPROC_IFELSE([AC_LANG_PROGRAM([[
@%:@include <linux/seccomp.h>
]], [[
#if !defined(__x86_64__)
# error Syscall sandbox is an experimental feature currently available only under Linux x86-64.
#endif
]])],[
AC_MSG_RESULT(yes)
seccomp_found="yes"
AC_DEFINE(USE_SYSCALL_SANDBOX, 1, [Define this symbol to build with syscall sandbox support.])
],[
AC_MSG_RESULT(no)
seccomp_found="no"
])
fi
dnl Currently only enable -sandbox=<mode> feature if seccomp is found.
dnl In the future, sandboxing could be also be supported with other
dnl sandboxing mechanisms besides seccomp.
use_syscall_sandbox=$seccomp_found
AM_CONDITIONAL([ENABLE_SYSCALL_SANDBOX], [test "x$use_syscall_sandbox" != "xno"])

dnl Check for reduced exports
if test x$use_reduce_exports = xyes; then
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[CXXFLAGS="$CXXFLAGS -fvisibility=hidden"],
Expand Down Expand Up @@ -1933,6 +1969,7 @@ echo
echo "Options used to compile and link:"
echo " external signer = $use_external_signer"
echo " multiprocess = $build_multiprocess"
echo " with experimental syscall sandbox support = $use_syscall_sandbox"
echo " with libs = $build_bitcoin_libs"
echo " with wallet = $enable_wallet"
if test "x$enable_wallet" != "xno"; then
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ BITCOIN_CORE_H = \
util/sock.h \
util/spanparsing.h \
util/string.h \
util/syscall_sandbox.h \
util/system.h \
util/thread.h \
util/threadnames.h \
Expand Down Expand Up @@ -611,6 +612,7 @@ libbitcoin_util_a_SOURCES = \
util/spanparsing.cpp \
util/strencodings.cpp \
util/string.cpp \
util/syscall_sandbox.cpp \
util/time.cpp \
util/tokenpipe.cpp \
$(BITCOIN_CORE_H)
Expand Down
2 changes: 2 additions & 0 deletions src/bitcoind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <shutdown.h>
#include <util/check.h>
#include <util/strencodings.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>
#include <util/threadnames.h>
#include <util/tokenpipe.h>
Expand Down Expand Up @@ -238,6 +239,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
daemon_ep.Close();
}
#endif
SetSyscallSandboxPolicy(SyscallSandboxPolicy::SHUTOFF);
if (fRet) {
WaitForShutdown();
}
Expand Down
2 changes: 2 additions & 0 deletions src/checkqueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <sync.h>
#include <tinyformat.h>
#include <util/syscall_sandbox.h>
#include <util/threadnames.h>

#include <algorithm>
Expand Down Expand Up @@ -151,6 +152,7 @@ class CCheckQueue
for (int n = 0; n < threads_num; ++n) {
m_worker_threads.emplace_back([this, n]() {
util::ThreadRename(strprintf("scriptch.%i", n));
SetSyscallSandboxPolicy(SyscallSandboxPolicy::VALIDATION_SCRIPT_CHECK);
Loop(false /* worker thread */);
});
}
Expand Down
3 changes: 3 additions & 0 deletions src/httpserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <shutdown.h>
#include <sync.h>
#include <util/strencodings.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>
#include <util/threadnames.h>
#include <util/translation.h>
Expand Down Expand Up @@ -279,6 +280,7 @@ static void http_reject_request_cb(struct evhttp_request* req, void*)
static bool ThreadHTTP(struct event_base* base)
{
util::ThreadRename("http");
SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_HTTP_SERVER);
LogPrint(BCLog::HTTP, "Entering http event loop\n");
event_base_dispatch(base);
// Event loop will be interrupted by InterruptHTTPServer()
Expand Down Expand Up @@ -332,6 +334,7 @@ static bool HTTPBindAddresses(struct evhttp* http)
static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue, int worker_num)
{
util::ThreadRename(strprintf("httpworker.%i", worker_num));
SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_HTTP_SERVER_WORKER);
queue->Run();
}

Expand Down
2 changes: 2 additions & 0 deletions src/index/base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <node/ui_interface.h>
#include <shutdown.h>
#include <tinyformat.h>
#include <util/syscall_sandbox.h>
#include <util/thread.h>
#include <util/translation.h>
#include <validation.h> // For g_chainman
Expand Down Expand Up @@ -123,6 +124,7 @@ static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev, CChain&

void BaseIndex::ThreadSync()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::TX_INDEX);
const CBlockIndex* pindex = m_best_block_index.load();
if (!m_synced) {
auto& consensus_params = Params().GetConsensus();
Expand Down
36 changes: 36 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include <util/check.h>
#include <util/moneystr.h>
#include <util/string.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>
#include <util/thread.h>
#include <util/threadnames.h>
Expand Down Expand Up @@ -562,6 +563,10 @@ void SetupServerArgs(ArgsManager& argsman)
hidden_args.emplace_back("-daemonwait");
#endif

#if defined(USE_SYSCALL_SANDBOX)
argsman.AddArg("-sandbox=<mode>", "Use the experimental syscall sandbox in the specified mode (-sandbox=log-and-abort or -sandbox=abort). Allow only expected syscalls to be used by bitcoind. Note that this is an experimental new feature that may cause bitcoind to exit or crash unexpectedly: use with caution. In the \"log-and-abort\" mode the invocation of an unexpected syscall results in a debug handler being invoked which will log the incident and terminate the program (without executing the unexpected syscall). In the \"abort\" mode the invocation of an unexpected syscall results in the entire process being killed immediately by the kernel without executing the unexpected syscall.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif // USE_SYSCALL_SANDBOX

// Add the hidden options
argsman.AddHiddenArgs(hidden_args);
}
Expand Down Expand Up @@ -1018,6 +1023,37 @@ bool AppInitParameterInteraction(const ArgsManager& args)
return InitError(_("No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>."));
}

#if defined(USE_SYSCALL_SANDBOX)
if (args.IsArgSet("-sandbox") && !args.IsArgNegated("-sandbox")) {
const std::string sandbox_arg{args.GetArg("-sandbox", "")};
bool log_syscall_violation_before_terminating{false};
if (sandbox_arg == "log-and-abort") {
log_syscall_violation_before_terminating = true;
} else if (sandbox_arg == "abort") {
// log_syscall_violation_before_terminating is false by default.
} else {
return InitError(Untranslated("Unknown syscall sandbox mode (-sandbox=<mode>). Available modes are \"log-and-abort\" and \"abort\"."));
}
// execve(...) is not allowed by the syscall sandbox.
const std::vector<std::string> features_using_execve{
"-alertnotify",
"-blocknotify",
"-signer",
"-startupnotify",
"-walletnotify",
};
for (const std::string& feature_using_execve : features_using_execve) {
if (!args.GetArg(feature_using_execve, "").empty()) {
return InitError(Untranslated(strprintf("The experimental syscall sandbox feature (-sandbox=<mode>) is incompatible with %s (which uses execve).", feature_using_execve)));
}
}
if (!SetupSyscallSandbox(log_syscall_violation_before_terminating)) {
return InitError(Untranslated("Installation of the syscall sandbox failed."));
}
LogPrintf("Experimental syscall sandbox enabled (-sandbox=%s): bitcoind will terminate if an unexpected (not allowlisted) syscall is invoked.\n", sandbox_arg);
}
#endif // USE_SYSCALL_SANDBOX

return true;
}

Expand Down
1 change: 1 addition & 0 deletions src/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const CLogCategoryDesc LogCategories[] =
{BCLog::I2P, "i2p"},
{BCLog::IPC, "ipc"},
{BCLog::LOCK, "lock"},
{BCLog::UTIL, "util"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},
};
Expand Down
1 change: 1 addition & 0 deletions src/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace BCLog {
I2P = (1 << 22),
IPC = (1 << 23),
LOCK = (1 << 24),
UTIL = (1 << 25),
ALL = ~(uint32_t)0,
};

Expand Down
2 changes: 2 additions & 0 deletions src/mapport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <netaddress.h>
#include <netbase.h>
#include <threadinterrupt.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>
#include <util/thread.h>

Expand Down Expand Up @@ -222,6 +223,7 @@ static bool ProcessUpnp()

static void ThreadMapPort()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION_MAP_PORT);
bool ok;
do {
ok = false;
Expand Down
6 changes: 6 additions & 0 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <scheduler.h>
#include <util/sock.h>
#include <util/strencodings.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>
#include <util/thread.h>
#include <util/trace.h>
Expand Down Expand Up @@ -1615,6 +1616,7 @@ void CConnman::SocketHandler()

void CConnman::ThreadSocketHandler()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET);
while (!interruptNet)
{
DisconnectNodes();
Expand All @@ -1634,6 +1636,7 @@ void CConnman::WakeMessageHandler()

void CConnman::ThreadDNSAddressSeed()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION_DNS_SEED);
FastRandomContext rng;
std::vector<std::string> seeds = Params().DNSSeeds();
Shuffle(seeds.begin(), seeds.end(), rng);
Expand Down Expand Up @@ -1816,6 +1819,7 @@ int CConnman::GetExtraBlockRelayCount() const

void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_OPEN_CONNECTION);
// Connect to specific addresses
if (!connect.empty())
{
Expand Down Expand Up @@ -2155,6 +2159,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const

void CConnman::ThreadOpenAddedConnections()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_ADD_CONNECTION);
while (true)
{
CSemaphoreGrant grant(*semAddnode);
Expand Down Expand Up @@ -2218,6 +2223,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai

void CConnman::ThreadMessageHandler()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::MESSAGE_HANDLER);
FastRandomContext rng;
while (!flagInterruptMsgProc)
{
Expand Down
2 changes: 2 additions & 0 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <signet.h>
#include <streams.h>
#include <undo.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>
#include <validation.h>

Expand Down Expand Up @@ -489,6 +490,7 @@ struct CImportingNow {

void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args)
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION_LOAD_BLOCKS);
ScheduleBatchPriority();

{
Expand Down
25 changes: 25 additions & 0 deletions src/rpc/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <util/check.h>
#include <util/message.h> // For MessageSign(), MessageVerify()
#include <util/strencodings.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>

#include <optional>
Expand Down Expand Up @@ -417,6 +418,27 @@ static RPCHelpMan setmocktime()
};
}

#if defined(USE_SYSCALL_SANDBOX)
static RPCHelpMan invokedisallowedsyscall()
{
return RPCHelpMan{
"invokedisallowedsyscall",
"\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
if (!Params().IsTestChain()) {
throw std::runtime_error("invokedisallowedsyscall is used for testing only.");
}
TestDisallowedSandboxCall();
return NullUniValue;
},
};
}
#endif // USE_SYSCALL_SANDBOX

static RPCHelpMan mockscheduler()
{
return RPCHelpMan{"mockscheduler",
Expand Down Expand Up @@ -777,6 +799,9 @@ static const CRPCCommand commands[] =
{ "hidden", &echo, },
{ "hidden", &echojson, },
{ "hidden", &echoipc, },
#if defined(USE_SYSCALL_SANDBOX)
{ "hidden", &invokedisallowedsyscall, },
#endif // USE_SYSCALL_SANDBOX
};
// clang-format on
for (const auto& c : commands) {
Expand Down
2 changes: 2 additions & 0 deletions src/scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <scheduler.h>

#include <random.h>
#include <util/syscall_sandbox.h>
#include <util/time.h>

#include <assert.h>
Expand All @@ -24,6 +25,7 @@ CScheduler::~CScheduler()

void CScheduler::serviceQueue()
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::SCHEDULER);
WAIT_LOCK(newTaskMutex, lock);
++nThreadsServicingQueue;

Expand Down
2 changes: 2 additions & 0 deletions src/torcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <netbase.h>
#include <util/readwritefile.h>
#include <util/strencodings.h>
#include <util/syscall_sandbox.h>
#include <util/system.h>
#include <util/thread.h>
#include <util/time.h>
Expand Down Expand Up @@ -585,6 +586,7 @@ static std::thread torControlThread;

static void TorControlThread(CService onion_service_target)
{
SetSyscallSandboxPolicy(SyscallSandboxPolicy::TOR_CONTROL);
TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL), onion_service_target);

event_base_dispatch(gBase);
Expand Down
Loading

0 comments on commit 4747da3

Please sign in to comment.