From 98a21da73421080327251b209ddf033f8fc49167 Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 11 Mar 2026 15:34:21 +0100 Subject: [PATCH] feat(runtime): seccomp blocklist filter via optional seccomp feature Add seccomp feature flag (seccompiler + libc, Linux-only, optional). When compiled with --features seccomp, weft-runtime installs a SECCOMP_MODE_FILTER immediately after argument parsing, before any package resolution or WASM execution. Filter strategy: default-allow with explicit KillProcess rules for high-risk syscalls a WASM runtime process has no legitimate need for: ptrace, process_vm_readv/writev, kexec_load, personality, syslog, reboot, mount/umount2, setuid/setgid/setreuid/setregid/setresuid/ setresgid, chroot, pivot_root, init_module/finit_module/delete_module, bpf, perf_event_open, acct. The feature is off by default so the standard build and tests are unaffected. Enable in production service builds with --features seccomp. --- crates/weft-runtime/Cargo.toml | 3 ++ crates/weft-runtime/src/main.rs | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/crates/weft-runtime/Cargo.toml b/crates/weft-runtime/Cargo.toml index 572eeb2..c5bd8b1 100644 --- a/crates/weft-runtime/Cargo.toml +++ b/crates/weft-runtime/Cargo.toml @@ -11,6 +11,7 @@ path = "src/main.rs" [features] default = [] wasmtime-runtime = ["dep:wasmtime", "dep:wasmtime-wasi", "dep:cap-std"] +seccomp = ["dep:seccompiler", "dep:libc"] [dependencies] anyhow = "1.0" @@ -19,3 +20,5 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } wasmtime = { version = "30", optional = true } wasmtime-wasi = { version = "30", optional = true } cap-std = { version = "3", optional = true } +seccompiler = { version = "0.4", optional = true } +libc = { version = "0.2", optional = true } diff --git a/crates/weft-runtime/src/main.rs b/crates/weft-runtime/src/main.rs index 8fb03bd..1c01673 100644 --- a/crates/weft-runtime/src/main.rs +++ b/crates/weft-runtime/src/main.rs @@ -50,6 +50,9 @@ fn main() -> anyhow::Result<()> { i += 1; } + #[cfg(feature = "seccomp")] + apply_seccomp_filter().context("apply seccomp filter")?; + tracing::info!(session_id, %app_id, "weft-runtime starting"); let pkg_dir = resolve_package(app_id)?; @@ -168,6 +171,59 @@ fn run_module( .map_err(|()| anyhow::anyhow!("wasm component run exited with error")) } +#[cfg(feature = "seccomp")] +fn apply_seccomp_filter() -> anyhow::Result<()> { + use seccompiler::{BpfProgram, SeccompAction, SeccompFilter, SeccompRule}; + use std::collections::BTreeMap; + use std::convert::TryInto; + + #[cfg(target_arch = "x86_64")] + let arch = seccompiler::TargetArch::x86_64; + #[cfg(target_arch = "aarch64")] + let arch = seccompiler::TargetArch::aarch64; + + let blocked: &[i64] = &[ + libc::SYS_ptrace, + libc::SYS_process_vm_readv, + libc::SYS_process_vm_writev, + libc::SYS_kexec_load, + libc::SYS_personality, + libc::SYS_syslog, + libc::SYS_reboot, + libc::SYS_mount, + libc::SYS_umount2, + libc::SYS_setuid, + libc::SYS_setgid, + libc::SYS_setreuid, + libc::SYS_setregid, + libc::SYS_setresuid, + libc::SYS_setresgid, + libc::SYS_chroot, + libc::SYS_pivot_root, + libc::SYS_init_module, + libc::SYS_finit_module, + libc::SYS_delete_module, + libc::SYS_bpf, + libc::SYS_perf_event_open, + libc::SYS_acct, + ]; + + let mut rules: BTreeMap> = BTreeMap::new(); + for &syscall in blocked { + rules.insert(syscall, vec![SeccompRule::new(vec![])?]); + } + + let filter = SeccompFilter::new( + rules, + SeccompAction::Allow, + SeccompAction::KillProcess, + arch, + )?; + let bpf: BpfProgram = filter.try_into()?; + seccompiler::apply_filter(&bpf)?; + Ok(()) +} + fn package_store_roots() -> Vec { if let Ok(explicit) = std::env::var("WEFT_APP_STORE") { return vec![PathBuf::from(explicit)];