crates/heimdall-probe/src/check.rs
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB
//! The drift check (spec §3.1 step 6): every call a fresh probe saw must be
//! allowed by the checked-in profile. Heimdall is a defensive sandbox: a
//! failure names the new call and the test that made it; the fix is a new
//! probe + review, never a hand edit.
use std::fmt;
use heimdall_seccomp::{Action, Profile};
use crate::observe::Observations;
/// A call the checked-in profile does not allow.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Violation {
/// `openat arg2=0x241`, or an unknown number.
pub call: String,
/// The syscall name (empty for an unknown number).
pub syscall: String,
/// What the enforcing filter would do (kill).
pub action: Action,
/// The tests (runs) that made it.
pub runs: Vec<String>,
}
impl fmt::Display for Violation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"new call {} ({}), made by test {}",
self.call,
self.action,
self.runs.join(", ")
)
}
}
/// Every observed call the profile does not allow, sorted.
pub fn check(obs: &Observations, profile: &Profile) -> Vec<Violation> {
let mut v = Vec::new();
if (obs.arch, obs.libc) != (profile.arch, profile.libc) {
v.push(Violation {
call: format!(
"probe is {} {}, profile is {} {}",
obs.arch, obs.libc, profile.arch, profile.libc
),
syscall: String::new(),
action: Action::KillProcess,
runs: Vec::new(),
});
return v;
}
for (c, runs) in &obs.calls {
let Some(nr) = profile.arch.syscall_nr(&c.syscall) else {
continue;
};
let mut args = [0u64; 6];
for (i, a) in &c.args {
args[usize::from(*i)] = *a;
}
let a = profile.evaluate(nr, &args, Action::KillProcess);
if matches!(a, Action::KillProcess | Action::KillThread | Action::Trap) {
v.push(Violation {
call: c.describe(),
syscall: c.syscall.clone(),
action: a,
runs: runs.iter().cloned().collect(),
});
}
}
for (u, runs) in &obs.unknown {
v.push(Violation {
call: u.clone(),
syscall: String::new(),
action: Action::KillProcess,
runs: runs.iter().cloned().collect(),
});
}
v
}