Skip to content

Commit 3fda71b

Browse files
committed
feat(seccomp): add seccomp-bpf filter support with TSYNC
Add optional seccomp-bpf filter installation to ExecutableSpec. The caller provides a filter program as BPF instruction tuples and styrolite installs it at the correct point in the execution sequence -- after PR_SET_NO_NEW_PRIVS and capability setup, but before execvpe(). Uses seccomp(2) with SECCOMP_FILTER_FLAG_TSYNC instead of prctl(PR_SET_SECCOMP) to synchronize the filter across all threads, preventing a race where a pre-existing thread could call a blocked syscall before the filter is applied. The seccomp field on ExecutableSpec is Optional and serde(default), so existing configs without seccomp continue to work unchanged.
1 parent 328198e commit 3fda71b

4 files changed

Lines changed: 67 additions & 0 deletions

File tree

src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::caps::CapabilityBit;
22
use crate::namespace::Namespace;
3+
use crate::seccomp::SeccompFilter;
34
use anyhow::{Result, bail};
45
use libc::{gid_t, pid_t, uid_t};
56
use serde::{Deserialize, Serialize};
@@ -74,6 +75,12 @@ pub struct ExecutableSpec {
7475
/// If `true`, sets `PR_SET_NO_NEW_PRIVS` before
7576
/// spawning the target executable.
7677
pub no_new_privs: bool,
78+
79+
/// An optional seccomp-bpf filter program. Applied after capabilities
80+
/// are set and `PR_SET_NO_NEW_PRIVS` is enabled, but before `execvpe()`.
81+
/// Requires `no_new_privs = true`.
82+
#[serde(default)]
83+
pub seccomp: Option<SeccompFilter>,
7784
}
7885

7986
#[derive(Default, Debug, Serialize, Deserialize)]

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod config;
44
pub mod mount;
55
pub mod namespace;
66
pub mod runner;
7+
pub mod seccomp;
78
pub mod signal;
89
pub mod unshare;
910
pub mod wrap;

src/seccomp.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/// A seccomp-bpf filter program.
2+
///
3+
/// The caller builds the BPF program as a list of (code, jt, jf, k)
4+
/// instructions. Styrolite installs it via `seccomp(2)` after
5+
/// capabilities are set but before `execvpe()`.
6+
///
7+
/// Requires `no_new_privs = true` on the `ExecutableSpec`.
8+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
9+
pub struct SeccompFilter {
10+
/// BPF instructions as (code, jt, jf, k) tuples.
11+
pub instructions: Vec<(u16, u8, u8, u32)>,
12+
}
13+
14+
impl SeccompFilter {
15+
/// Install the seccomp filter via `seccomp(2)` with `SECCOMP_FILTER_FLAG_TSYNC`.
16+
///
17+
/// Uses `seccomp(2)` instead of `prctl(PR_SET_SECCOMP)` to synchronize the
18+
/// filter across all threads via `SECCOMP_FILTER_FLAG_TSYNC`.
19+
///
20+
/// # Safety
21+
///
22+
/// Must be called after `prctl(PR_SET_NO_NEW_PRIVS, 1)` and before `execvpe()`.
23+
/// The caller must ensure the BPF program is valid.
24+
pub unsafe fn install(&self) -> std::io::Result<()> {
25+
let filters: Vec<libc::sock_filter> = self
26+
.instructions
27+
.iter()
28+
.map(|&(code, jt, jf, k)| libc::sock_filter { code, jt, jf, k })
29+
.collect();
30+
let prog = libc::sock_fprog {
31+
len: filters.len() as u16,
32+
filter: filters.as_ptr() as *mut _,
33+
};
34+
35+
// Use seccomp(2) with TSYNC to synchronize filter across all threads.
36+
// SECCOMP_SET_MODE_FILTER = 1, SECCOMP_FILTER_FLAG_TSYNC = 1
37+
let ret = libc::syscall(
38+
libc::SYS_seccomp,
39+
1u64, // SECCOMP_SET_MODE_FILTER
40+
1u64, // SECCOMP_FILTER_FLAG_TSYNC
41+
&prog as *const _,
42+
);
43+
if ret != 0 {
44+
return Err(std::io::Error::last_os_error());
45+
}
46+
Ok(())
47+
}
48+
}

src/wrap.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,17 @@ impl ExecutableSpec {
628628
self.set_no_new_privs()?;
629629
}
630630

631+
// Install seccomp-bpf filter if provided.
632+
// Must be after set_no_new_privs (required for unprivileged seccomp)
633+
// and before execvpe (filter applies to the exec'd process).
634+
if let Some(ref seccomp) = self.seccomp {
635+
unsafe {
636+
if let Err(e) = seccomp.install() {
637+
bail!("failed to install seccomp filter: {e}");
638+
}
639+
}
640+
}
641+
631642
unsafe {
632643
if libc::execvpe(
633644
program_cstring.as_ptr(),

0 commit comments

Comments
 (0)