Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::caps::CapabilityBit;
use crate::namespace::Namespace;
use crate::seccomp::SeccompFilter;
use anyhow::{Result, bail};
use libc::{gid_t, pid_t, uid_t};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -74,6 +75,12 @@ pub struct ExecutableSpec {
/// If `true`, sets `PR_SET_NO_NEW_PRIVS` before
/// spawning the target executable.
pub no_new_privs: bool,

/// An optional seccomp-bpf filter program. Applied after capabilities
/// are set and `PR_SET_NO_NEW_PRIVS` is enabled, but before `execvpe()`.
/// Requires `no_new_privs = true`.
#[serde(default)]
pub seccomp: Option<SeccompFilter>,
}

#[derive(Default, Debug, Serialize, Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod config;
pub mod mount;
pub mod namespace;
pub mod runner;
pub mod seccomp;
pub mod signal;
pub mod unshare;
pub mod wrap;
48 changes: 48 additions & 0 deletions src/seccomp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// A seccomp-bpf filter program.
///
/// The caller builds the BPF program as a list of (code, jt, jf, k)
/// instructions. Styrolite installs it via `seccomp(2)` after
/// capabilities are set but before `execvpe()`.
///
/// Requires `no_new_privs = true` on the `ExecutableSpec`.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct SeccompFilter {
/// BPF instructions as (code, jt, jf, k) tuples.
pub instructions: Vec<(u16, u8, u8, u32)>,
}

impl SeccompFilter {
/// Install the seccomp filter via `seccomp(2)` with `SECCOMP_FILTER_FLAG_TSYNC`.
///
/// Uses `seccomp(2)` instead of `prctl(PR_SET_SECCOMP)` to synchronize the
/// filter across all threads via `SECCOMP_FILTER_FLAG_TSYNC`.
///
/// # Safety
///
/// Must be called after `prctl(PR_SET_NO_NEW_PRIVS, 1)` and before `execvpe()`.
/// The caller must ensure the BPF program is valid.
pub unsafe fn install(&self) -> std::io::Result<()> {
let filters: Vec<libc::sock_filter> = self
.instructions
.iter()
.map(|&(code, jt, jf, k)| libc::sock_filter { code, jt, jf, k })
.collect();
let prog = libc::sock_fprog {
len: filters.len() as u16,
filter: filters.as_ptr() as *mut _,
};

// Use seccomp(2) with TSYNC to synchronize filter across all threads.
// SECCOMP_SET_MODE_FILTER = 1, SECCOMP_FILTER_FLAG_TSYNC = 1
let ret = libc::syscall(
libc::SYS_seccomp,
1u64, // SECCOMP_SET_MODE_FILTER
1u64, // SECCOMP_FILTER_FLAG_TSYNC
&prog as *const _,
);
if ret != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
}
11 changes: 11 additions & 0 deletions src/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,17 @@ impl ExecutableSpec {
self.set_no_new_privs()?;
}

// Install seccomp-bpf filter if provided.
// Must be after set_no_new_privs (required for unprivileged seccomp)
// and before execvpe (filter applies to the exec'd process).
if let Some(ref seccomp) = self.seccomp {
unsafe {
if let Err(e) = seccomp.install() {
bail!("failed to install seccomp filter: {e}");
}
}
}

unsafe {
if libc::execvpe(
program_cstring.as_ptr(),
Expand Down