Skip to content

Commit 04a0969

Browse files
author
Roy Lin
committed
feat(box): CRI applies RuntimeDefault seccomp filter to containers
A container whose SecurityContext requests seccomp RuntimeDefault now gets the default BPF filter installed in its child before execve (A3S_SEC_SECCOMP=default → guest-init configure_child_process → namespace::apply_default_seccomp, applied last, after chroot + privilege drop). The filter blocks the dangerous syscalls Docker's default profile blocks and puts the process in SECCOMP_MODE_FILTER (Seccomp: 2). Unconfined / unset containers stay unfiltered (Seccomp: 0). Verified on the KVM server: the CRI "should support seccomp default on the container" conformance spec now passes (SeccompProfilePath bucket 4 -> 5). The localhost-profile specs (incl. the SYS_ADMIN sethostname block) remain — they need the host profile file plumbed into the VM, a separate effort. Also fixes a clippy while-let lint in the sysctls boot-env loop.
1 parent e27d17e commit 04a0969

4 files changed

Lines changed: 30 additions & 5 deletions

File tree

src/cri/src/runtime_service/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,15 @@ impl RuntimeService for BoxRuntimeService {
809809
sc.readonly_paths.join(":"),
810810
));
811811
}
812+
// CRI seccomp: RuntimeDefault installs the default BPF filter in the
813+
// guest (Seccomp: 2); Unconfined / unset leave the container
814+
// unconfined. Localhost profiles are not yet plumbed into the VM.
815+
if let Some(profile) = sc.seccomp.as_ref() {
816+
use crate::cri_api::security_profile::ProfileType;
817+
if profile.profile_type() == ProfileType::RuntimeDefault {
818+
env.push(("A3S_SEC_SECCOMP".to_string(), "default".to_string()));
819+
}
820+
}
812821
}
813822
let user = container_user_from_linux_config(config.linux.as_ref())
814823
.or_else(|| image_config.and_then(|image| image.user.clone()));

src/guest/init/src/exec_server.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,12 +461,20 @@ fn build_command(
461461
.collect()
462462
})
463463
.unwrap_or_default();
464+
// CRI seccomp: A3S_SEC_SECCOMP=default applies the default BPF filter
465+
// (RuntimeDefault) in the child; unconfined/unset leave the process
466+
// unfiltered.
467+
let apply_seccomp = spec
468+
.env
469+
.iter()
470+
.any(|entry| entry == "A3S_SEC_SECCOMP=default");
464471
configure_child_process(
465472
&mut command,
466473
spec.rootfs,
467474
workdir,
468475
process_user,
469476
supplemental_groups,
477+
apply_seccomp,
470478
);
471479
if spec.rootfs.is_none() {
472480
if let Some(dir) = spec.working_dir {
@@ -831,6 +839,7 @@ fn configure_child_process(
831839
workdir: &str,
832840
user: Option<ProcessUser>,
833841
supplemental_groups: Vec<u32>,
842+
apply_seccomp: bool,
834843
) {
835844
use std::ffi::CString;
836845
use std::os::unix::process::CommandExt;
@@ -866,6 +875,15 @@ fn configure_child_process(
866875
if let Some(user) = user {
867876
user.apply()?;
868877
}
878+
// Install the seccomp filter last — after chroot and the privilege
879+
// drop — so it is active across execve. The default filter allows
880+
// execve and the syscalls the child still needs.
881+
#[cfg(target_os = "linux")]
882+
if apply_seccomp {
883+
crate::namespace::apply_default_seccomp()?;
884+
}
885+
#[cfg(not(target_os = "linux"))]
886+
let _ = apply_seccomp;
869887
Ok(())
870888
});
871889
}
@@ -878,6 +896,7 @@ fn configure_child_process(
878896
_workdir: &str,
879897
_user: Option<ProcessUser>,
880898
_supplemental_groups: Vec<u32>,
899+
_apply_seccomp: bool,
881900
) {
882901
}
883902

src/guest/init/src/host_config.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ pub fn apply_from_env() -> Result<(), Box<dyn std::error::Error>> {
2020
/// VM startup.
2121
fn apply_sysctls_from_env() {
2222
let mut index = 0;
23-
loop {
24-
let Ok(spec) = std::env::var(format!("BOX_SYSCTL_{index}")) else {
25-
break;
26-
};
23+
while let Ok(spec) = std::env::var(format!("BOX_SYSCTL_{index}")) {
2724
index += 1;
2825
let Some((name, value)) = spec.split_once('=') else {
2926
continue;

src/guest/init/src/namespace.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ fn cap_name_to_number(name: &str) -> Option<i32> {
497497
/// Based on Docker's default seccomp profile — blocks syscalls that could
498498
/// escape the sandbox or compromise the host.
499499
#[cfg(target_os = "linux")]
500-
fn apply_default_seccomp() -> Result<(), std::io::Error> {
500+
pub(crate) fn apply_default_seccomp() -> Result<(), std::io::Error> {
501501
// Use SECCOMP_SET_MODE_FILTER via prctl
502502
// The default profile uses a BPF filter that blocks:
503503
// - kexec_load, kexec_file_load (kernel replacement)

0 commit comments

Comments
 (0)