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

nordisk/heimdall

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
}