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/facts.rs

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

//! Which argument facts the probe records per syscall (spec §3.1 step 2).
//! Heimdall is a defensive sandbox: these are the arguments that change what
//! a call can do; the rest (fds, buffers, lengths, pointers) vary freely.

use heimdall_seccomp::Width;

/// How the facts of a call are minimised into rules.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Kind {
    /// The observed value tuples are kept exactly while they are few
    /// (`exact_limit`), else the call becomes a bare allow (reported).
    Exact,
    /// A flag field: the union of the observed bits; no other bit may be set.
    Flags,
}

/// The recorded arguments of one syscall.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct CallSpec {
    pub syscall: &'static str,
    pub kind: Kind,
    /// (argument index, width).
    pub args: &'static [(u8, Width)],
    /// A protection field: `PROT_WRITE|PROT_EXEC` together is flagged and,
    /// unless the workload allows it, left out of the profile.
    pub prot: bool,
}

const W32: Width = Width::W32;
const W64: Width = Width::W64;

/// The calls whose arguments are recorded.
pub const SPECS: &[CallSpec] = &[
    CallSpec {
        syscall: "socket",
        kind: Kind::Exact,
        args: &[(0, W32), (1, W32), (2, W32)],
        prot: false,
    },
    CallSpec {
        syscall: "socketpair",
        kind: Kind::Exact,
        args: &[(0, W32), (1, W32), (2, W32)],
        prot: false,
    },
    CallSpec {
        syscall: "open",
        kind: Kind::Flags,
        args: &[(1, W32)],
        prot: false,
    },
    CallSpec {
        syscall: "openat",
        kind: Kind::Flags,
        args: &[(2, W32)],
        prot: false,
    },
    CallSpec {
        syscall: "ioctl",
        kind: Kind::Exact,
        args: &[(1, W32)],
        prot: false,
    },
    CallSpec {
        syscall: "mmap",
        kind: Kind::Exact,
        args: &[(2, W32)],
        prot: true,
    },
    CallSpec {
        syscall: "mprotect",
        kind: Kind::Exact,
        args: &[(2, W32)],
        prot: true,
    },
    CallSpec {
        syscall: "pkey_mprotect",
        kind: Kind::Exact,
        args: &[(2, W32)],
        prot: true,
    },
    CallSpec {
        syscall: "clone",
        kind: Kind::Flags,
        args: &[(0, W64)],
        prot: false,
    },
    CallSpec {
        syscall: "prctl",
        kind: Kind::Exact,
        args: &[(0, W32)],
        prot: false,
    },
    CallSpec {
        syscall: "fcntl",
        kind: Kind::Exact,
        args: &[(1, W32)],
        prot: false,
    },
];

/// The spec of a call, if its arguments are recorded.
pub fn spec(syscall: &str) -> Option<&'static CallSpec> {
    SPECS.iter().find(|s| s.syscall == syscall)
}

/// `PROT_WRITE|PROT_EXEC` both set.
pub fn is_wx(prot: u64) -> bool {
    let w = libc::PROT_WRITE as u64;
    let x = libc::PROT_EXEC as u64;
    prot & (w | x) == (w | x)
}

/// Calls the probe answers itself in learning mode, and the profile keeps
/// as fixed rules: `clone3` gets `ENOSYS`, so the libc falls back to `clone`,
/// whose flags a filter can check (`clone3` passes them in memory).
pub fn fixed_rules() -> Vec<heimdall_seccomp::Rule> {
    vec![heimdall_seccomp::Rule::bare(
        "clone3",
        heimdall_seccomp::Action::Errno(libc::ENOSYS as u16),
    )]
}

/// The note every generated profile carries about the vDSO.
pub const VDSO_NOTE: &str = "clock_gettime, clock_getres, gettimeofday, time and getcpu normally run in the vDSO and never reach seccomp; their syscall fallbacks are in the profile only if the probe saw them";