aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
authorW. J. van der Laan <laanwj@protonmail.com>2021-10-04 22:37:24 +0200
committerW. J. van der Laan <laanwj@protonmail.com>2021-10-04 22:45:43 +0200
commit9e530c6352c3e3d4f2936bbbb1bcb34ff9ca6378 (patch)
tree22ae15a6ea031cbe7ba34c419f902282a9c902cd /test/functional
parent42fedb4acd3cfa813059fcc3f96b2a41f78d9074 (diff)
parent4747da3a5b639b5a336b737e7e3cbf060cf2efcf (diff)
downloadbitcoin-9e530c6352c3e3d4f2936bbbb1bcb34ff9ca6378.tar.xz
Merge bitcoin/bitcoin#20487: Add syscall sandboxing using seccomp-bpf (Linux secure computing mode)
4747da3a5b639b5a336b737e7e3cbf060cf2efcf Add syscall sandboxing (seccomp-bpf) (practicalswift) Pull request description: Add experimental syscall sandboxing using seccomp-bpf (Linux secure computing mode). Enable filtering of system calls using seccomp-bpf: allow only explicitly allowlisted (expected) syscalls to be called. The syscall sandboxing implemented in this PR is an experimental feature currently available only under Linux x86-64. To enable the experimental syscall sandbox the `-sandbox=<mode>` option must be passed to `bitcoind`: ``` -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. ``` The allowed syscalls are defined on a per thread basis. I've used this feature since summer 2020 and I find it to be a helpful testing/debugging addition which makes it much easier to reason about the actual capabilities required of each type of thread in Bitcoin Core. --- Quick start guide: ``` $ ./configure $ src/bitcoind -regtest -debug=util -sandbox=log-and-abort … 2021-06-09T12:34:56Z Experimental syscall sandbox enabled (-sandbox=log-and-abort): bitcoind will terminate if an unexpected (not allowlisted) syscall is invoked. … 2021-06-09T12:34:56Z Syscall filter installed for thread "addcon" 2021-06-09T12:34:56Z Syscall filter installed for thread "dnsseed" 2021-06-09T12:34:56Z Syscall filter installed for thread "net" 2021-06-09T12:34:56Z Syscall filter installed for thread "msghand" 2021-06-09T12:34:56Z Syscall filter installed for thread "opencon" 2021-06-09T12:34:56Z Syscall filter installed for thread "init" … # A simulated execve call to show the sandbox in action: 2021-06-09T12:34:56Z ERROR: The syscall "execve" (syscall number 59) is not allowed by the syscall sandbox in thread "msghand". Please report. … Aborted (core dumped) $ ``` --- [About seccomp and seccomp-bpf](https://en.wikipedia.org/wiki/Seccomp): > In computer security, seccomp (short for secure computing mode) is a facility in the Linux kernel. seccomp allows a process to make a one-way transition into a "secure" state where it cannot make any system calls except exit(), sigreturn(), and read() and write() to already-open file descriptors. Should it attempt any other system calls, the kernel will terminate the process with SIGKILL or SIGSYS. In this sense, it does not virtualize the system's resources but isolates the process from them entirely. > > […] > > seccomp-bpf is an extension to seccomp that allows filtering of system calls using a configurable policy implemented using Berkeley Packet Filter rules. It is used by OpenSSH and vsftpd as well as the Google Chrome/Chromium web browsers on Chrome OS and Linux. (In this regard seccomp-bpf achieves similar functionality, but with more flexibility and higher performance, to the older systrace—which seems to be no longer supported for Linux.) ACKs for top commit: laanwj: Code review and lightly tested ACK 4747da3a5b639b5a336b737e7e3cbf060cf2efcf Tree-SHA512: e1c28e323eb4409a46157b7cc0fc29a057ba58d1ee2de268962e2ade28ebd4421b5c2536c64a3af6e9bd3f54016600fec88d016adb49864b63edea51ad838e17
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/feature_notifications.py3
-rwxr-xr-xtest/functional/feature_syscall_sandbox.py34
-rwxr-xr-xtest/functional/feature_versionbits_warning.py3
-rwxr-xr-xtest/functional/rpc_misc.py2
-rwxr-xr-xtest/functional/rpc_signer.py3
-rwxr-xr-xtest/functional/test_framework/test_framework.py11
-rwxr-xr-xtest/functional/test_runner.py1
-rwxr-xr-xtest/functional/wallet_signer.py3
8 files changed, 59 insertions, 1 deletions
diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py
index 4382022a7a..2a507c75c4 100755
--- a/test/functional/feature_notifications.py
+++ b/test/functional/feature_notifications.py
@@ -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)
diff --git a/test/functional/feature_syscall_sandbox.py b/test/functional/feature_syscall_sandbox.py
new file mode 100755
index 0000000000..caf7f1e7fc
--- /dev/null
+++ b/test/functional/feature_syscall_sandbox.py
@@ -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()
diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py
index 311d871d49..d74ef5e088 100755
--- a/test/functional/feature_versionbits_warning.py
+++ b/test/functional/feature_versionbits_warning.py
@@ -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")
diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py
index 13f33c321f..ac2a7a309b 100755
--- a/test/functional/rpc_misc.py
+++ b/test/functional/rpc_misc.py
@@ -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)
diff --git a/test/functional/rpc_signer.py b/test/functional/rpc_signer.py
index 9e963eba57..5c3722ef8f 100755
--- a/test/functional/rpc_signer.py
+++ b/test/functional/rpc_signer.py
@@ -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 = [
[],
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 3aca93fab3..d87d0cacfd 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -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:
@@ -887,3 +894,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")
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index bb84962b75..c5f08b27f2 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -171,6 +171,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',
diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py
index 7b77755d64..c6c1cc8784 100755
--- a/test/functional/wallet_signer.py
+++ b/test/functional/wallet_signer.py
@@ -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 = [
[],