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

nordisk/holger

jobs/runner/src/in_process.rs

//! **`InProcessRunner`: a test runner.** The job's program runs in the caller's own
//! process, over the job's own directories, with the same answer, output check and
//! signature as [`crate::ContainerRunner`]. It is compiled only with the
//! `test-runner` feature (server-lib's `sandbox-off`, a test build); no shipped build
//! names it, and a release build refuses `sandbox: off` at boot (`holger-sandbox-6`).

use std::sync::Arc;

use heimdall_core::{
    ExitInfo, Features, Job, JobResult, JobRunner, Protection, RunError, SignedResult, Verdict,
    VerifyingKey,
};

use crate::collect::{collect, empty};
use crate::container::{check_dirs, native_argv, now_unix};
use crate::key::ResultKey;
use crate::wire::{Dirs, Program, WireJob, JOB_SCHEMA};

/// The test runner.
pub struct InProcessRunner {
    program: Arc<Program>,
    key: ResultKey,
    /// A job's one network peer is allowed (the `fetch` job's tests).
    declared_peers: bool,
}

impl InProcessRunner {
    pub fn new(program: Arc<Program>) -> Self {
        Self {
            program,
            key: ResultKey::per_boot(),
            declared_peers: false,
        }
    }

    /// **The job's declared peers allowed** (lane cds4, the `fetch` job's tests): a job whose
    /// `policy.net` declares its set of peers runs in-process, where Heimdall's broker would hand it the
    /// one socket. The test runner cannot hold the job to that peer (Heimdall's broker does);
    /// a writable path besides the output is still refused.
    #[must_use]
    pub fn with_declared_peers(mut self) -> Self {
        self.declared_peers = true;
        self
    }

    /// The runner's features: no isolation at all.
    pub fn features_of() -> Features {
        Features {
            runner: "holger-in-process-test".into(),
            container: false,
            seccomp: Protection::None,
            landlock: false,
            broker: false,
            wasm: false,
            image_verified_each_start: false,
        }
    }
}

impl JobRunner for InProcessRunner {
    fn run(&self, job: Job) -> Result<SignedResult, RunError> {
        let argv = native_argv(&job)?;
        if (job.policy.net.is_some() && !self.declared_peers) || !job.policy.write.is_empty() {
            return Err(RunError::Unsupported {
                what: "a network allowance or a writable path besides the output directory".into(),
            });
        }
        check_dirs(&job)?;
        let wire = WireJob {
            schema: JOB_SCHEMA.into(),
            job_id: job.id.to_hex(),
            argv,
            params: job.params.clone(),
            reads: job.policy.read.len(),
            answer: None,
        };
        let dirs = Dirs {
            input: job.input_dir.clone(),
            read: job.policy.read.clone(),
            out: job.output_dir.clone(),
        };
        let started = now_unix();
        let program = Arc::clone(&self.program);
        let (verdict, outputs, exit) = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(
            || program(&wire, &dirs),
        )) {
            Ok(a) => {
                let (v, outs) = collect(&a, &job.output_dir);
                (v, outs, ExitInfo::exited(0))
            }
            Err(p) => {
                empty(&job.output_dir);
                let why = p
                    .downcast_ref::<&str>()
                    .map(|s| s.to_string())
                    .or_else(|| p.downcast_ref::<String>().cloned())
                    .unwrap_or_else(|| "a panic".into());
                let c = &holger_errcode::SANDBOX_WORKER_FAILED;
                (
                    Verdict::Refused {
                        code: c.code.into(),
                        reason: c.tag(format!("the job's program panicked: {why}")),
                    },
                    Vec::new(),
                    ExitInfo::exited(101),
                )
            }
        };
        let result = JobResult {
            job_id: job.id,
            nonce: job.nonce,
            verdict,
            outputs,
            exit,
            started_unix: started,
            finished_unix: now_unix().max(started),
            runner: Self::features_of(),
            events: Vec::new(),
            // This runner probes no kernel (Heimdall 0.2's `JobResult.kernel`).
            kernel: None,
        };
        self.key.sign(result)
    }

    fn verifying_key(&self) -> VerifyingKey {
        self.key.verifying_key()
    }

    fn features(&self) -> Features {
        Self::features_of()
    }
}