Apply a seccomp-BPF syscall allowlist by default
Derived from Podman's default profile, stripped of capability-conditional rules (we never grant capabilities), argument filters, and the explicit EPERM block. Dangerous syscalls (mount, unshare, ptrace, bpf, perf_event_open, io_uring_*, keyctl, kexec_*, ...) fall through to the default ENOSYS action, which also keeps glibc's clone3 -> clone fallback working. x86_64 and aarch64 are supported; other archs error out. Toggle with --seccomp / --no-seccomp or seccomp = <bool> in config.
This commit is contained in:
@@ -38,6 +38,12 @@ pub fn build(args: Args, file_config: Option<FileConfig>) -> Result<SandboxConfi
|
||||
profile.unshare_net,
|
||||
globals.unshare_net,
|
||||
),
|
||||
seccomp: merge_flag_with_default(
|
||||
merge_flag_pair(args.seccomp, args.no_seccomp),
|
||||
profile.seccomp,
|
||||
globals.seccomp,
|
||||
true,
|
||||
),
|
||||
dry_run: merge_flag(
|
||||
merge_flag_pair(args.dry_run, args.no_dry_run),
|
||||
profile.dry_run,
|
||||
@@ -86,6 +92,15 @@ fn merge_flag(cli: Option<bool>, profile: Option<bool>, globals: Option<bool>) -
|
||||
cli.or(profile).or(globals).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn merge_flag_with_default(
|
||||
cli: Option<bool>,
|
||||
profile: Option<bool>,
|
||||
globals: Option<bool>,
|
||||
default: bool,
|
||||
) -> bool {
|
||||
cli.or(profile).or(globals).unwrap_or(default)
|
||||
}
|
||||
|
||||
fn merge_flag_pair(enable: bool, disable: bool) -> Option<bool> {
|
||||
if enable {
|
||||
Some(true)
|
||||
@@ -244,6 +259,7 @@ pub struct Options {
|
||||
pub whitelist: Option<bool>,
|
||||
pub hardened: Option<bool>,
|
||||
pub unshare_net: Option<bool>,
|
||||
pub seccomp: Option<bool>,
|
||||
pub entrypoint: Option<CommandValue>,
|
||||
pub command: Option<CommandValue>,
|
||||
pub dry_run: Option<bool>,
|
||||
@@ -380,6 +396,7 @@ mod tests {
|
||||
const FULL_CONFIG_TOML: &str = r#"
|
||||
hardened = true
|
||||
unshare-net = true
|
||||
seccomp = false
|
||||
rw = ["/tmp/a", "/tmp/b"]
|
||||
command = "zsh"
|
||||
|
||||
@@ -403,6 +420,7 @@ mod tests {
|
||||
fn globals_scalars() {
|
||||
assert_eq!(CONFIG.options.hardened, Some(true));
|
||||
assert_eq!(CONFIG.options.unshare_net, Some(true));
|
||||
assert_eq!(CONFIG.options.seccomp, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -522,6 +540,59 @@ mod tests {
|
||||
assert!(config.unshare_net);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_seccomp_default_is_true() {
|
||||
let config = build(args_with_command(), None).unwrap();
|
||||
assert!(config.seccomp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_seccomp_disabled_via_config() {
|
||||
let file_config = FileConfig {
|
||||
options: Options {
|
||||
seccomp: Some(false),
|
||||
..Options::default()
|
||||
},
|
||||
..FileConfig::default()
|
||||
};
|
||||
let config = build(args_with_command(), Some(file_config)).unwrap();
|
||||
assert!(!config.seccomp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_cli_seccomp_overrides_profile() {
|
||||
let file_config = FileConfig {
|
||||
options: Options {
|
||||
seccomp: Some(false),
|
||||
..Options::default()
|
||||
},
|
||||
..FileConfig::default()
|
||||
};
|
||||
let args = Args {
|
||||
seccomp: true,
|
||||
..args_with_command()
|
||||
};
|
||||
let config = build(args, Some(file_config)).unwrap();
|
||||
assert!(config.seccomp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_cli_no_seccomp_overrides_profile() {
|
||||
let file_config = FileConfig {
|
||||
options: Options {
|
||||
seccomp: Some(true),
|
||||
..Options::default()
|
||||
},
|
||||
..FileConfig::default()
|
||||
};
|
||||
let args = Args {
|
||||
no_seccomp: true,
|
||||
..args_with_command()
|
||||
};
|
||||
let config = build(args, Some(file_config)).unwrap();
|
||||
assert!(!config.seccomp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_cli_no_hardened_overrides_profile() {
|
||||
let file_config = FileConfig {
|
||||
|
||||
Reference in New Issue
Block a user