Skip to content

Add two-phase seccomp-BPF sandbox for the VMM process#86

Open
perbu wants to merge 2 commits into
masterfrom
seccomp-host-sandbox
Open

Add two-phase seccomp-BPF sandbox for the VMM process#86
perbu wants to merge 2 commits into
masterfrom
seccomp-host-sandbox

Conversation

@perbu

@perbu perbu commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Kernel-enforced syscall allowlist behind the guest-facing emulation layer: if a syscall handler is compromised, the VMM process can no longer execve, ptrace, mount, or issue anything outside what the handlers need.

  • Two stacking phases: wide Init for guest initialization / warm-fork prepare, strict Runtime for request serving (KVM ioctls by type byte, thread-only clone, clone3 → ENOSYS fallback).
  • Hand-rolled classic BPF, kernel headers only — no new dependencies.
  • Opt-in: nothing changes unless the embedder calls install_seccomp_filter().
  • log_only soak mode reports allowlist gaps via the kernel audit log instead of killing.
  • 6 unit tests, green on x86-64 and aarch64. See docs/seccomp.md.

Notes for review:

  • Allowlist tables are derived from the handlers and test suite, not yet soaked against Varnish/fast-agent workloads — soak with log_only before enforcing.
  • Integration point (who installs the filters, on which threads) is left to embedders; filters are irrevocable per-thread, so thread pools need dedicated VM threads or process isolation.

🤖 Generated with Claude Code

Add a kernel-enforced syscall allowlist behind the guest-facing
emulation layer. If a syscall handler is ever compromised, the
attacker lands in a process that can no longer execve, ptrace,
mount, or issue any syscall outside what the handlers need.

Two stacking phases: a wide Init filter for guest initialization
and warm-fork prepare, and a strict Runtime filter for request
serving (KVM ioctls by type byte, translated-fd I/O, thread-only
clone, clone3 -> ENOSYS fallback). Hand-rolled classic BPF against
kernel headers only - no new dependencies. A log-only mode allows
soak-testing allowlists via the kernel audit log before enforcing.

The tables are derived from the handlers and validated against the
unit test suite; embedders should soak with log_only and extend via
SeccompOptions::extra_rules. See docs/seccomp.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in, host-side seccomp-BPF sandbox for the TinyKVM VMM process to constrain the VMM’s syscall surface area behind the guest-facing emulation layer, using a two-phase (Init → Runtime) tightening model plus a log-only soak mode.

Changes:

  • Introduces tinykvm::install_seccomp_filter() / seccomp_rules_for() API and a hand-rolled classic-BPF program generator.
  • Defines Init/Runtime allowlist tables (including KVM-ioctl arg filtering, clone3 → ENOSYS fallback, TSYNC option).
  • Adds unit tests and documentation for installing/soaking the sandbox.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/seccomp.cpp Adds unit tests validating Runtime enforcement, log-only mode, stacking intent, ioctl arg filtering, and extra rules.
tests/unit/CMakeLists.txt Registers the new seccomp unit test on AMD64 and ARM64.
lib/tinykvm/linux/seccomp.hpp Public API and data model for phases, rules, and options.
lib/tinykvm/linux/seccomp.cpp Implements allowlists, BPF emission, and filter installation via seccomp() + PR_SET_NO_NEW_PRIVS.
lib/CMakeLists.txt Links the new seccomp implementation into the library build.
docs/seccomp.md Documents threat model, two-phase usage, thread scope implications, and log-only soak workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to +29
#ifndef SYS_seccomp
#define SYS_seccomp 317
#endif
Comment on lines +150 to +152
R{SYS_set_tid_address}, R{SYS_prlimit64},
R{SYS_prctl}, R{SYS_getrlimit},
R{SYS_getuid}, R{SYS_geteuid}, R{SYS_getgid}, R{SYS_getegid},
Comment on lines +217 to +228
struct Check { uint32_t off; uint32_t mask; uint32_t value; };
Check checks[8];
unsigned num_checks = 0;
for (unsigned i = 0; i < rule.num_args; i++) {
const auto& arg = rule.args[i];
const uint32_t mask_lo = arg.mask, mask_hi = arg.mask >> 32;
const uint32_t val_lo = arg.value, val_hi = arg.value >> 32;
if (mask_lo != 0)
checks[num_checks++] = {seccomp_data_arg_lo(arg.index), mask_lo, val_lo};
if (mask_hi != 0)
checks[num_checks++] = {seccomp_data_arg_hi(arg.index), mask_hi, val_hi};
}
Comment thread tests/unit/seccomp.cpp
- allow seccomp(SECCOMP_SET_MODE_FILTER) in the Init phase so the
  Runtime filter can actually be installed on top of it; the stacking
  test passed spuriously because the SIGSYS it expected came from the
  blocked seccomp() call, not the banned syscall
- make the SYS_seccomp fallback arch-specific (317 x86_64, 277 aarch64)
- validate num_args and Arg::index before BPF emission so malformed
  extra_rules throw instead of reading out of bounds
- REQUIRE fork() success in the test harness and strengthen the
  stacking test to prove the child survives the Runtime install

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants