Skip to content

Commit 6069760

Browse files
committed
feat(seccomp): add seccomp-bpf filter support with TSYNC
1 parent 776de30 commit 6069760

4 files changed

Lines changed: 64 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: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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
16+
/// `SECCOMP_FILTER_FLAG_TSYNC`.
17+
///
18+
/// Uses `seccomp(2)` instead of `prctl(PR_SET_SECCOMP)` to synchronize the
19+
/// filter across all threads via `SECCOMP_FILTER_FLAG_TSYNC`.
20+
///
21+
/// # Safety
22+
///
23+
/// Must be called after `prctl(PR_SET_NO_NEW_PRIVS, 1)` and before
24+
/// `execvpe()`. The caller must ensure the BPF program is valid.
25+
pub unsafe fn install(&self) -> std::io::Result<()> {
26+
let filters: Vec<libc::sock_filter> = self
27+
.instructions
28+
.iter()
29+
.map(|&(code, jt, jf, k)| libc::sock_filter { code, jt, jf, k })
30+
.collect();
31+
let prog = libc::sock_fprog {
32+
len: filters.len() as u16,
33+
filter: filters.as_ptr() as *mut _,
34+
};
35+
36+
let ret = unsafe {
37+
libc::syscall(
38+
libc::SYS_seccomp,
39+
1u64,
40+
1u64,
41+
&prog as *const _,
42+
)
43+
};
44+
if ret != 0 {
45+
return Err(std::io::Error::last_os_error());
46+
}
47+
Ok(())
48+
}
49+
}

src/wrap.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,13 @@ impl ExecutableSpec {
663663
self.set_no_new_privs()?;
664664
}
665665

666+
if let Some(filter) = &self.seccomp {
667+
if !self.no_new_privs {
668+
bail!("seccomp filter requires no_new_privs = true");
669+
}
670+
unsafe { filter.install()? };
671+
}
672+
666673
unsafe {
667674
if libc::execvpe(
668675
program_cstring.as_ptr(),

0 commit comments

Comments
 (0)