crates/heimdall-probe/src/codegen.rs
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB
//! Code generation (spec §3.1 step 4): `profiles/<workload>/<arch>-<libc>.rs`
//! (a `const` [`heimdall_seccomp::StaticProfile`]), the same profile as
//! reviewable `.json`, and the OCI seccomp `.oci.json`. Heimdall is a
//! defensive sandbox; the output is deterministic (no dates, sorted), so a
//! regenerated profile diffs only where the workload changed.
use std::fmt::Write as _;
use heimdall_seccomp::bpf::Mode;
use heimdall_seccomp::{Action, Arch, Libc, Width};
use crate::minimise::Generated;
fn action(a: Action) -> String {
match a {
Action::Allow => "Allow".into(),
Action::Log => "Log".into(),
Action::Errno(e) => format!("Errno({e})"),
Action::UserNotif => "UserNotif".into(),
Action::Trap => "Trap".into(),
Action::KillThread => "KillThread".into(),
Action::KillProcess => "KillProcess".into(),
}
}
fn arch(a: Arch) -> &'static str {
match a {
Arch::X86_64 => "X86_64",
Arch::Aarch64 => "Aarch64",
}
}
fn libc_name(l: Libc) -> &'static str {
match l {
Libc::Glibc => "Glibc",
Libc::Musl => "Musl",
}
}
fn comment_list(out: &mut String, title: &str, items: &[String]) {
if items.is_empty() {
return;
}
let _ = writeln!(out, "//\n// {title}:");
for i in items {
let _ = writeln!(out, "// {i}");
}
}
/// The first lines of every generated `.rs`. A generated profile is DATA
/// about the profiled program, not part of Heimdall: it is MIT-0 (OSI
/// approved, no attribution, accepted where code licences are checked; CC0
/// is not, for code, in some policies), so MIT, Apache and proprietary
/// repos can check it in next to the program it describes.
pub const GENERATED_HEADER: &str = "// SPDX-License-Identifier: MIT-0\n\
// This file is generated output of Heimdall (`heimdall profile gen`): data about the profiled\n\
// program, not covered by Heimdall's AGPL. Use it under any licence, including the program's own.\n";
/// The `.rs` file: plain `//` comments and fully qualified paths, so it can
/// be `include!`d anywhere.
pub fn to_rust(g: &Generated) -> String {
let p = &g.profile;
let r = &g.review;
let mut o = String::new();
o.push_str(GENERATED_HEADER);
let _ = writeln!(
o,
"// GENERATED by heimdall-probe (`heimdall profile gen {}`). Do not edit by hand:",
p.workload
);
let _ = writeln!(
o,
"// probe, regenerate and review the diff (spec §3.1). Heimdall is a defensive sandbox;"
);
let _ = writeln!(
o,
"// this is the whole syscall allowlist of `{}` on {} {}.",
p.workload, p.arch, p.libc
);
let _ = writeln!(
o,
"//\n// probed with {} run(s): {}",
r.runs.len(),
r.runs.join(", ")
);
let fmt = |f: &crate::minimise::Finding| format!("{} (by {})", f.call, f.runs.join(", "));
comment_list(
&mut o,
"near-miss, seen only on error paths",
&r.near_miss.iter().map(fmt).collect::<Vec<_>>(),
);
comment_list(
&mut o,
"left out: PROT_WRITE|PROT_EXEC",
&r.wx.iter().map(fmt).collect::<Vec<_>>(),
);
comment_list(&mut o, "bare allow (arguments varied)", &r.bare);
comment_list(
&mut o,
"unknown calls (never allowed)",
&r.unknown.iter().map(fmt).collect::<Vec<_>>(),
);
comment_list(&mut o, "fixed rules", &r.fixed);
comment_list(&mut o, "notes", &r.notes);
let _ = writeln!(o);
let _ = writeln!(
o,
"/// The seccomp profile of `{}` on {} {}.",
p.workload, p.arch, p.libc
);
let _ = writeln!(
o,
"pub const PROFILE: heimdall_seccomp::StaticProfile = heimdall_seccomp::StaticProfile {{"
);
let _ = writeln!(o, " workload: {:?},", p.workload);
let _ = writeln!(o, " arch: heimdall_seccomp::Arch::{},", arch(p.arch));
let _ = writeln!(
o,
" libc: heimdall_seccomp::Libc::{},",
libc_name(p.libc)
);
let _ = writeln!(o, " rules: &[");
for rule in &p.rules {
let _ = writeln!(o, " heimdall_seccomp::StaticRule {{");
let _ = writeln!(o, " syscall: {:?},", rule.syscall);
let _ = writeln!(
o,
" action: heimdall_seccomp::Action::{},",
action(rule.action)
);
if rule.when.is_empty() {
let _ = writeln!(o, " when: &[],");
} else {
let _ = writeln!(o, " when: &[");
for alt in &rule.when {
let _ = writeln!(o, " &[");
for c in alt {
let w = match c.width {
Width::W32 => "W32",
Width::W64 => "W64",
};
let _ = writeln!(
o,
" heimdall_seccomp::ArgCmp {{ arg: {}, width: heimdall_seccomp::Width::{w}, mask: {:#x}, value: {:#x} }},",
c.arg, c.mask, c.value
);
}
let _ = writeln!(o, " ],");
}
let _ = writeln!(o, " ],");
}
let _ = writeln!(o, " }},");
}
let _ = writeln!(o, " ],");
let _ = writeln!(o, "}};");
o
}
/// The `.oci.json` file (enforcing: default `SCMP_ACT_KILL_PROCESS`).
pub fn to_oci_json(g: &Generated) -> String {
let v = heimdall_seccomp::oci::to_oci(&g.profile, &Mode::Enforce);
serde_json::to_string_pretty(&v).expect("json") + "\n"
}
/// The three files for a generated profile: (`.rs`, `.json`, `.oci.json`).
pub fn files(g: &Generated) -> (String, String, String) {
(to_rust(g), g.to_json(), to_oci_json(g))
}