Add syscall sandboxing (seccomp-bpf)

This commit is contained in:
practicalswift
2021-10-01 13:53:59 +00:00
parent e69cbac628
commit 4747da3a5b
27 changed files with 1125 additions and 1 deletions

View File

@@ -24,3 +24,4 @@ RPCAUTH=@abs_top_srcdir@/share/rpcauth/rpcauth.py
@ENABLE_FUZZ_TRUE@ENABLE_FUZZ=true
@ENABLE_ZMQ_TRUE@ENABLE_ZMQ=true
@ENABLE_EXTERNAL_SIGNER_TRUE@ENABLE_EXTERNAL_SIGNER=true
@ENABLE_SYSCALL_SANDBOX_TRUE@ENABLE_SYSCALL_SANDBOX=true

View File

@@ -27,6 +27,9 @@ class NotificationsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
# The experimental syscall sandbox feature (-sandbox) is not compatible with -alertnotify,
# -blocknotify or -walletnotify (which all invoke execve).
self.disable_syscall_sandbox = True
def setup_network(self):
self.wallet = ''.join(chr(i) for i in range(FILE_CHAR_START, FILE_CHAR_END) if chr(i) not in FILE_CHARS_DISALLOWED)

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test bitcoind aborts if a disallowed syscall is used when compiled with the syscall sandbox."""
from test_framework.test_framework import BitcoinTestFramework, SkipTest
class SyscallSandboxTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
def skip_test_if_missing_module(self):
if not self.is_syscall_sandbox_compiled():
raise SkipTest("bitcoind has not been built with syscall sandbox enabled.")
if self.options.nosandbox:
raise SkipTest("--nosandbox passed to test runner.")
def run_test(self):
disallowed_syscall_terminated_bitcoind = False
expected_log_entry = 'ERROR: The syscall "getgroups" (syscall number 115) is not allowed by the syscall sandbox'
with self.nodes[0].assert_debug_log([expected_log_entry]):
self.log.info("Invoking disallowed syscall")
try:
self.nodes[0].invokedisallowedsyscall()
except ConnectionError:
disallowed_syscall_terminated_bitcoind = True
assert disallowed_syscall_terminated_bitcoind
self.nodes = []
if __name__ == "__main__":
SyscallSandboxTest().main()

View File

@@ -28,6 +28,9 @@ class VersionBitsWarningTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
# The experimental syscall sandbox feature (-sandbox) is not compatible with -alertnotify
# (which invokes execve).
self.disable_syscall_sandbox = True
def setup_network(self):
self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")

View File

@@ -57,7 +57,7 @@ class RpcMiscTest(BitcoinTestFramework):
self.log.info("test logging rpc and help")
# Test logging RPC returns the expected number of logging categories.
assert_equal(len(node.logging()), 25)
assert_equal(len(node.logging()), 26)
# Test toggling a logging category on/off/on with the logging RPC.
assert_equal(node.logging()['qt'], True)

View File

@@ -27,6 +27,9 @@ class RPCSignerTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
# The experimental syscall sandbox feature (-sandbox) is not compatible with -signer (which
# invokes execve).
self.disable_syscall_sandbox = True
self.extra_args = [
[],

View File

@@ -101,6 +101,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.supports_cli = True
self.bind_to_localhost_only = True
self.parse_args()
self.disable_syscall_sandbox = self.options.nosandbox
self.default_wallet_name = "default_wallet" if self.options.descriptors else ""
self.wallet_data_filename = "wallet.dat"
# Optional list of wallet names that can be set in set_test_params to
@@ -159,6 +160,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
parser = argparse.ArgumentParser(usage="%(prog)s [options]")
parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true",
help="Leave bitcoinds and test.* datadir on exit or error")
parser.add_argument("--nosandbox", dest="nosandbox", default=False, action="store_true",
help="Don't use the syscall sandbox")
parser.add_argument("--noshutdown", dest="noshutdown", default=False, action="store_true",
help="Don't stop bitcoinds after the test execution")
parser.add_argument("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"),
@@ -468,6 +471,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
extra_args = [[]] * num_nodes
if versions is None:
versions = [None] * num_nodes
if self.is_syscall_sandbox_compiled() and not self.disable_syscall_sandbox:
for i in range(len(extra_args)):
if versions[i] is None or versions[i] >= 219900:
extra_args[i] = extra_args[i] + ["-sandbox=log-and-abort"]
if binary is None:
binary = [get_bin_from_version(v, 'bitcoind', self.options.bitcoind) for v in versions]
if binary_cli is None:
@@ -886,3 +893,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def is_bdb_compiled(self):
"""Checks whether the wallet module was compiled with BDB support."""
return self.config["components"].getboolean("USE_BDB")
def is_syscall_sandbox_compiled(self):
"""Checks whether the syscall sandbox was compiled."""
return self.config["components"].getboolean("ENABLE_SYSCALL_SANDBOX")

View File

@@ -170,6 +170,7 @@ BASE_SCRIPTS = [
'rpc_users.py',
'rpc_whitelist.py',
'feature_proxy.py',
'feature_syscall_sandbox.py',
'rpc_signrawtransaction.py --legacy-wallet',
'rpc_signrawtransaction.py --descriptors',
'rpc_rawtransaction.py --legacy-wallet',

View File

@@ -27,6 +27,9 @@ class WalletSignerTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
# The experimental syscall sandbox feature (-sandbox) is not compatible with -signer (which
# invokes execve).
self.disable_syscall_sandbox = True
self.extra_args = [
[],