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

nordisk/heimdall

crates/heimdall-wasm/tests/no_memfd.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! The wasm host needs no `memfd_create`: Heimdall's own container seccomp
//! profile refuses it, and a guest run inside a HeimdallRunner job must still
//! load and run (Holger's split verify-bundle guest trapped with "cannot
//! create a memfd"). Heimdall is a defensive sandbox; this test filters only
//! its own child process.
//!
//! The parent test re-runs this test binary as a child with
//! `HEIMDALL_NO_MEMFD_CHILD=1`; the child installs a seccomp filter on every
//! thread that makes `memfd_create` fail with EPERM, proves the filter is on,
//! and runs a guest with data segments (the case that wasmtime's
//! copy-on-write memory images back with a memfd).

mod common;
use common::*;
use heimdall_wasm::{RunLimits, Verdict};

const CHILD: &str = "HEIMDALL_NO_MEMFD_CHILD";

#[cfg(target_arch = "x86_64")]
const AUDIT_ARCH: u32 = 0xC000_003E; // AUDIT_ARCH_X86_64
#[cfg(target_arch = "aarch64")]
const AUDIT_ARCH: u32 = 0xC000_00B7; // AUDIT_ARCH_AARCH64

/// seccomp: `memfd_create` fails with EPERM, every other call is allowed;
/// a foreign arch is killed. Applied to every thread (TSYNC).
fn refuse_memfd_create() {
    use libc::{sock_filter, sock_fprog};
    const LD_W_ABS: u16 = 0x20; // BPF_LD | BPF_W | BPF_ABS
    const JEQ_K: u16 = 0x15; // BPF_JMP | BPF_JEQ | BPF_K
    const RET_K: u16 = 0x06; // BPF_RET | BPF_K
    let f = |code, jt, jf, k| sock_filter { code, jt, jf, k };
    let prog = [
        f(LD_W_ABS, 0, 0, 4), // seccomp_data.arch
        f(JEQ_K, 1, 0, AUDIT_ARCH),
        f(RET_K, 0, 0, libc::SECCOMP_RET_KILL_PROCESS),
        f(LD_W_ABS, 0, 0, 0), // seccomp_data.nr
        f(JEQ_K, 0, 1, libc::SYS_memfd_create as u32),
        f(RET_K, 0, 0, libc::SECCOMP_RET_ERRNO | libc::EPERM as u32),
        f(RET_K, 0, 0, libc::SECCOMP_RET_ALLOW),
    ];
    let fprog = sock_fprog {
        len: prog.len() as u16,
        filter: prog.as_ptr() as *mut sock_filter,
    };
    // SAFETY: plain prctl/seccomp calls with a valid, live filter program.
    unsafe {
        assert_eq!(libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0);
        let r = libc::syscall(
            libc::SYS_seccomp,
            libc::SECCOMP_SET_MODE_FILTER,
            libc::SECCOMP_FILTER_FLAG_TSYNC,
            &fprog as *const sock_fprog,
        );
        assert_eq!(r, 0, "seccomp: {}", std::io::Error::last_os_error());
        // The filter is on: memfd_create now fails with EPERM.
        let name = b"probe\0";
        let fd = libc::syscall(libc::SYS_memfd_create, name.as_ptr(), 0u32);
        assert_eq!(fd, -1);
        assert_eq!(
            std::io::Error::last_os_error().raw_os_error(),
            Some(libc::EPERM)
        );
    }
}

#[test]
fn a_guest_with_data_segments_loads_and_runs_when_memfd_create_is_refused() {
    if std::env::var_os(CHILD).is_some() {
        refuse_memfd_create();
        let kit = Kit::new("no-memfd");
        let g = kit.guest("good", &good_wat());
        // Twice: the second instance would reuse a memory image if one existed.
        for _ in 0..2 {
            let o = g.run(b"hello bundle", &none(), &RunLimits::default());
            assert_eq!(
                o.verdict,
                Verdict::Accepted {
                    note: "accepted".into()
                },
                "{o:?}"
            );
            assert_eq!(o.files[0].bytes, b"hello bundle");
        }
        return;
    }
    let out = std::process::Command::new(std::env::current_exe().unwrap())
        .args([
            "--exact",
            "a_guest_with_data_segments_loads_and_runs_when_memfd_create_is_refused",
            "--nocapture",
            "--test-threads=1",
        ])
        .env(CHILD, "1")
        .output()
        .expect("the child test runs");
    let text = format!(
        "{}{}",
        String::from_utf8_lossy(&out.stdout),
        String::from_utf8_lossy(&out.stderr)
    );
    assert!(out.status.success(), "child failed:\n{text}");
    assert!(
        text.contains("1 passed"),
        "the child did not run the test:\n{text}"
    );
}