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

nordisk/heimdall

crates/heimdall-container/tests/workspace_kernel.rs

// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB

//! The job workspace on a real kernel (owner decision 2026-11-01): writable,
//! capped, not executable unless asked, deleted after the job. Heimdall is a
//! defensive sandbox; every job here writes and runs only its own files.
//! `#[ignore]`d in plain CI: scripts/container-tests.sh runs them.

#[path = "../../heimdall-plain/tests/common/mod.rs"]
mod common;

use std::path::PathBuf;
use std::time::Duration;

use common::*;
use heimdall_container::*;
use heimdall_core::*;

struct Fx {
    base: PathBuf,
    image: ImageRef,
    runner: HeimdallRunner,
}

fn fx(name: &str, allow_exec: bool) -> Fx {
    let base = scratch(&format!("hc-ws-{name}"));
    let layout = Layout::new(&base.join("layout"));
    let image = busybox_image(&layout);
    let mut c = HeimdallConfig::new(&layout.dir, base.join("work"));
    c.allow_workspace_exec = allow_exec;
    Fx {
        runner: HeimdallRunner::new(c).expect("this kernel has every feature (strict)"),
        base,
        image,
    }
}

impl Fx {
    /// Run `script` with a workspace; the report it wrote to /out/r.
    fn run(&self, script: &str, bytes: u64, exec: bool) -> (String, JobResult) {
        let d = job_dirs(&self.base);
        let mut j = native_job(&self.image, &sh(script), &d);
        j.policy.workspace = Some(Workspace { bytes, exec });
        j.policy.limits.wall = Duration::from_secs(60);
        j.policy.limits.memory_bytes = 256 << 20;
        let s = self.runner.run(j).expect("a signed result");
        verify(&s, &self.runner.verifying_key()).expect("it verifies");
        let r = std::fs::read_to_string(d.output.join("r")).unwrap_or_default();
        (r, s.result)
    }
    fn workspaces_left(&self) -> usize {
        std::fs::read_dir(self.base.join("work/workspaces"))
            .map(|d| d.count())
            .unwrap_or(0)
    }
}

const MIB: u64 = 1 << 20;

#[test]
#[ignore = "needs user namespaces + delegated cgroup v2 + Landlock: scripts/container-tests.sh"]
fn a_tmpfs_workspace_is_writable_and_readable_and_capped() {
    let f = fx("tmpfs", false);
    let (r, res) = f.run(
        "echo hello > /work/a && cat /work/a > /out/r && \
         (dd if=/dev/zero of=/work/big bs=1M count=20 2>/dev/null && echo over >> /out/r || echo capped >> /out/r)",
        8 * MIB,
        false,
    );
    assert!(res.verdict.is_accepted(), "{:?} {r}", res.verdict);
    assert_eq!(r, "hello\ncapped\n");
}

#[test]
#[ignore = "needs user namespaces + delegated cgroup v2 + Landlock: scripts/container-tests.sh"]
fn nothing_in_a_workspace_runs_unless_exec_is_asked_and_allowed() {
    let script = "cp /bin/busybox /work/busybox && chmod +x /work/busybox && \
                  (/work/busybox true 2>/tmp/e && echo ran > /out/r || (echo refused; cat /tmp/e) > /out/r)";
    let f = fx("noexec", false);
    let (r, _) = f.run(script, 64 * MIB, false);
    assert!(r.starts_with("refused\n"), "{r}");
    let f = fx("exec", true);
    let (r, res) = f.run(script, 64 * MIB, true);
    assert!(res.verdict.is_accepted(), "{:?}", res.verdict);
    assert_eq!(r, "ran\n", "{r}");
    // A host-folder workspace the same.
    let (r, _) = fx("noexec-host", false).run(script, (1 << 30) + MIB, false);
    assert!(r.starts_with("refused\n"), "{r}");
    let (r, _) = fx("exec-host", true).run(script, (1 << 30) + MIB, true);
    assert_eq!(r, "ran\n", "{r}");
}

#[test]
#[ignore = "needs user namespaces + delegated cgroup v2 + Landlock: scripts/container-tests.sh"]
fn a_host_workspace_is_writable_and_deleted_when_the_job_ends() {
    let f = fx("host", false);
    let (r, res) = f.run(
        "dd if=/dev/zero of=/work/f bs=1M count=8 2>/dev/null && test -s /work/f && echo 1 > /out/r",
        (1 << 30) + MIB,
        false,
    );
    assert!(res.verdict.is_accepted(), "{:?} {r}", res.verdict);
    assert_eq!(r.trim(), "1");
    assert_eq!(f.workspaces_left(), 0, "the workspace outlived its job");
}

#[test]
#[ignore = "needs user namespaces + delegated cgroup v2 + Landlock: scripts/container-tests.sh"]
fn a_host_workspace_over_its_cap_kills_the_job() {
    let f = fx("hostcap", false);
    let cap = (1 << 30) + 16 * MIB;
    let (r, res) = f.run(
        "dd if=/dev/zero of=/work/f bs=1M count=1100 oflag=direct 2>/dev/null; sleep 30; echo survived > /out/r",
        cap,
        false,
    );
    assert_eq!(
        res.verdict.code(),
        Some(codes::LIMIT_WORKSPACE),
        "{:?} {r}",
        res.verdict
    );
    assert_eq!(res.exit.killed, Some(KillReason::Workspace));
    assert_eq!(f.workspaces_left(), 0);
}

// ==== a host workspace under with_host_ids (0.3.1; found by the k13 bench) ====

#[test]
#[ignore = "needs user namespaces + delegated cgroup v2 + Landlock: scripts/container-tests.sh"]
fn with_host_ids_a_host_workspace_is_the_jobs_and_writable() {
    use std::os::unix::fs::MetadataExt;
    assert_eq!(
        std::fs::metadata("/proc/self").unwrap().uid(),
        0,
        "the kernel tests run the runner as root"
    );
    let mut f = fx("host-ids", false);
    f.runner = f
        .runner
        .with_host_ids(10_000, 1000)
        .expect("a root runner takes the range");
    let (r, res) = f.run(
        "echo w > /work/f && stat -c %u /work/f > /out/r",
        (1 << 30) + MIB,
        false,
    );
    assert!(res.verdict.is_accepted(), "{:?} {r}", res.verdict);
    assert_eq!(r.trim(), "0", "the job wrote its workspace as its own root");
    assert_eq!(f.workspaces_left(), 0, "the workspace outlived its job");
}