Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/heimdall

profiles/tests/enforce.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! Enforce each checked-in profile (`Mode::Enforce`, unknown call =
//! KILL_PROCESS) on its real release binary over its whole probe corpus:
//! every run must end with its `expect_exit`, never SIGSYS. Heimdall is a
//! defensive sandbox; the corpus attacks only Heimdall's own limits.
//! Run only with HEIMDALL_KERNEL_TESTS=1 (scripts/seccomp-tests.sh).

use std::path::Path;

use heimdall_probe::Workload;
use heimdall_profiles::ALL;
use heimdall_seccomp::bpf::Mode;
use heimdall_seccomp::launch::{Exit, Launch, Stdio};
use heimdall_seccomp::{Arch, Profile};

fn main() {
    if std::env::var_os("HEIMDALL_KERNEL_TESTS").is_none_or(|v| v != "1") {
        println!("enforce tests skipped: set HEIMDALL_KERNEL_TESTS=1 (scripts/seccomp-tests.sh)");
        return;
    }
    let profiles = Path::new(env!("CARGO_MANIFEST_DIR"));
    let mut failed = 0;
    let mut ran = 0;
    for e in ALL {
        let p = Profile::from(e.profile);
        if Some(p.arch) != Arch::native() {
            continue;
        }
        let w = Workload::load(profiles, &p.workload).unwrap();
        let bin = match w.binary(&heimdall_probe::ProbeOptions {
            libc: Some(p.libc),
            ..Default::default()
        }) {
            Ok((b, _)) => b,
            Err(err) => {
                println!("skip {} {} {}: {err}", p.workload, p.arch, p.libc);
                continue;
            }
        };
        for r in &w.manifest.runs {
            ran += 1;
            let tmp =
                std::env::temp_dir().join(format!("heim-enforce-{}-{ran}", std::process::id()));
            let _ = std::fs::remove_dir_all(&tmp);
            std::fs::create_dir_all(&tmp).unwrap();
            let expand = |s: &str| {
                s.replace("{corpus}", &w.dir.join("corpus").display().to_string())
                    .replace("{dir}", &w.dir.display().to_string())
                    .replace("{tmp}", &tmp.display().to_string())
            };
            let mut l = Launch::new(&bin)
                .args(r.args.iter().map(|a| expand(a)))
                .env_clear()
                .env("PATH", "/usr/bin:/bin")
                .cwd(&tmp)
                .stdin(Stdio::Null)
                .stdout(Stdio::Null)
                .stderr(Stdio::Null)
                .profile(&p, &Mode::Enforce);
            for (k, v) in w.manifest.env.iter().chain(r.env.iter()) {
                l = l.env(k, v);
            }
            let exit = l.spawn().unwrap().wait().unwrap();
            let want = Exit::Code(r.expect_exit.unwrap_or(0));
            let ok = exit == want;
            println!(
                "test {} {} {} {} ... {} ({exit})",
                p.workload,
                p.arch,
                p.libc,
                r.name,
                if ok { "ok" } else { "FAILED" }
            );
            if !ok {
                failed += 1;
            }
            let _ = std::fs::remove_dir_all(&tmp);
        }
    }
    println!("\nenforce tests: {} passed; {failed} failed", ran - failed);
    if failed > 0 {
        std::process::exit(1);
    }
}