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()
}
}