nordisk/heimdall
Commits
df02795d4784attestations: heimdall-0.3.0 (SLSA, YubiKey-signed; release key d24d3785…, statement 0f8ed717…; 12 crates on crates.io, check-published OK)685a826d978dMerge rel/0.3.0 into main: Heimdall 0.3.0 (Heimdall is a defensive sandbox): 12 crates on crates.io (core, seccomp, plain, wasm, run, probe, profiles, broker, container, trace, scan, cli), built from rel/0.3.0 6410ace on t14s by dwarves release-build (image nordisk-cargo-toolchain@sha256:2cf9b012), SLSA statement 0f8ed717 signed by the owner's YubiKey (release key d24d3785); gate at 6410ace on oden: ci 1299/0, plain 23/23, container 61/61, broker 24/24, workspace 4/4, seccomp 83 ok, trace 12/126410acebd81crelease prep 0.3.0: CHANGELOG summary names the fetch peer set54939af3267dMerge main (63ae945, fetch peer sets) into rel/0.3.063ae945f8ba4Merge allin/peers into main (lane broker, HEIMDALL-ALLIN, Heimdall is a defensive sandbox; rickard-e6's fetch conditions, blocks 0.3.0): policy.net is a declared set of up to 16 peers { host (SNI, certificate name, never resolved), addr (IP literal), port }; the broker connects only to declared addr:port pairs; the job gets HEIMDALL_NET_PEERS; bad sets refused by name; redirects are the caller's (a new job). Gate on this tree: ci.sh green, broker kernel 24/24, container kernel 61/61, workspace 4/4, plain 23/23, trace 12/12 in privileged rootless podman on oden (kernel 7.0)436ccc3e96baGREEN: Heimdall is a defensive sandbox — fetch jobs declare a set of peers: heimdall-core NetAllow { peers: Vec<NetPeer { host, addr, port }> }, MAX_NET_PEERS 16, NetAllow::check refuses an empty set, too many, a duplicate, a bad host, a non-IP addr, port 0 by name (InvalidJob); heimdall-broker BrokerPolicy::fetch is a Vec<SocketAddr> (socket of any declared family, connect only to a declared addr:port); heimdall-container hands the job HEIMDALL_NET_PEERS (the peers as JSON, host names for SNI and the certificate check). docs/API.md Fetch jobs (redirects: the job returns the target, the caller starts a new job), CHANGELOG. Gate: ci.sh green, broker kernel 24/24, container kernel 61/61, workspace 4/4, plain 23/23, trace 12/12 (oden, privileged rootless podman)b0de0324529aRED: Heimdall is a defensive sandbox — fetch jobs declare a SET of peers (rickard-e6, blocks 0.3.0): policy.net = NetAllow { peers: [NetPeer { host, addr, port }] }, host the name for the job's SNI and certificate check (never resolved by Heimdall), addr an IP literal; at most 16 peers; an empty set, a duplicate peer, a non-IP addr, port 0 or a bad host refused by name. Tests: core (the set, its checks), broker policy (several peers, each allowed, nothing else), container unit (policy and HEIMDALL_NET_PEERS from the set), fake shim (the fetch list reaches the shim; bad sets InvalidJob), kernel (two declared peers reached, an undeclared port and an undeclared address refused, the job reads its peers with their host names, 17 peers refused)4a686226744drelease prep 0.3.0: CHANGELOG [0.3.0] section (Heimdall is a defensive sandbox; the Holger line), 0.2.1 dated 2026-11-01 (published)f399181b8a47Merge main (d5be6fa, fetch jobs) into rel/0.3.0d5be6fa9567cMerge allin/broker-fetch into main (lane broker, HEIMDALL-ALLIN, Heimdall is a defensive sandbox): fetch jobs through the broker — policy.net = Some(NetAllow) with an IP literal and port gives the job one TCP peer: its network namespace stays empty, the broker creates the socket in the runner's namespace, connects it (pidfd_getfd) to exactly the checked address and injects it with ADDFD; every other socket, connect, bind or listen is refused and signed as a BrokerEvent; a name is RunError::Unsupported, port 0 InvalidJob, no broker in full mode refuses fetch jobs; Landlock ABI 4 keeps refusing the job's own TCP. Gate on this tree (main 6549e22): ci.sh green (heim-zlwasm), broker kernel 24/24, container kernel 59/59, workspace 4/4, plain 23/23, trace 12/12 in privileged rootless podman on oden (kernel 7.0)92e258826e3aMerge main (exec retry 6549e22) into allin/broker-fetch2af2cf73816dMerge main (6549e22) into rel/0.3.06549e22b414cMerge remote-tracking branch 'origin/main'be93c9417a2crelease prep 0.3.0: Heimdall is a defensive sandbox — every crate 0.3.0 (workspace version, inter-crate pins, locks of the workspace and the guests); new on crates.io with 0.3.0: nordisk-heimdall-profiles, -broker, -trace, -scan9310e6be04d6Merge main (exec-flake 2d3c35f) into allin/broker-fetch2d3c35fbe6e6Merge allin/exec-flake into main (lane HEIMDALL-ALLIN, Heimdall is a defensive sandbox): three load-dependent container-test flakes fixed. (1) ETXTBSY: a shim forked by one thread inherited the write fd of a program another thread was unpacking; heimdall_plain::forklock (unpacks hold writing(), both runners spawn their shim under spawning()); the shim reports execve errors with the errno chain. (2) The jobs' parent cgroup is created by the runner before the shim (libcgroups 0.7.0 exists()-then-mkdir raced: EEXIST). (3) The host_ids kernel test reads ids of the job's program past its execve. Gate on oden at e5dd033 (= main 7db81f8 + this): ci.sh 1291/0 (heim-zlwasm), plain 23/23, container-tests 19 runs in a row 56/56 (before: 2 of 8 and 1 of 12 runs red)96edd6f70c64Merge hgun/exec-flake into main (lane HEIM-GUNNAR): the job execve retried while the program is busy (ETXTBSY) — the parallel container tests failed 1 run in 3 under load (pre-existing: cf8b346 2/6, eb4d9b5 1/6), now 36/36 under 3 concurrent load loops; ci.sh greene5dd03385ec3rustfmt crates/heimdall-probe/tests/kernel.rs (main ced342e is fmt-red; the same one-file fix as lane broker's)e37555a97421fmt: heimdall-probe/tests/kernel.rs as cargo fmt writes it (main ced342e failed ci.sh at cargo fmt --check)656ea64dc41cMerge main (7db81f8, broker) into allin/exec-flake93b8151243deMerge main (broker 7db81f8) into allin/broker-fetch7db81f80178dMerge allin/broker into main (lane broker, HEIMDALL-ALLIN, Heimdall is a defensive sandbox): milestone 4, the syscall broker — heimdall-broker (USER_NOTIF supervisor on heimdall-seccomp: filter by its BPF builder, notif::Listener, ADDFD with FLAG_SEND; per-job policy of read / read-write / write-only prefixes, no magic links, no mount crossing, relative opens checked as absolute paths, exec logged) and every HeimdallRunner job under it (filter installed by the executor after Landlock, served by the shim; signed BrokerEvents; Features.broker only in full mode; check-only and deny-only fallbacks; files the broker creates belong to the job; /work like /tmp; trace runners run with BrokerSetting::Off); Landlock-alone tests for /in, /out, /tmp with controls; README rows Implemented. Gate on this tree (main 350b10b; ced342e touches only heimdall-probe tests): ci.sh green (heim-zlwasm), broker kernel 24/24, container kernel 56/56, workspace 4/4, plain 23/23, trace 12/12 in privileged rootless podman on oden (kernel 7.0)6a2702291dd8Merge remote-tracking branch 'origin/main' into allin/brokera8b50af0675bMerge fix: heimdall-trace kernel tests run their trace runner with BrokerSetting::Off (a trace job owns the job's seccomp listener; with the broker on by default the runner refused them by name). Gate on this tree (main 350b10b merged): ci.sh green, broker kernel 24/24, container kernel 56/56, workspace 4/4, plain 23/23, trace 12/12 (oden, privileged rootless podman)ced342ef7709Merge htool/probe-flake into main (lane HEIM-TOOLING): the probe kernel tests no longer depend on thread timing (futex flake seen by lane prof)c9ee8021c062GREEN: the heimdall-probe kernel test worker makes its futex calls always (a FUTEX_WAKE on an unwaited word), so a slow thread join is no drift; scripts/seccomp-tests.sh 83 ok1fc2579172a5GREEN: Heimdall is a defensive sandbox — the job's execve is retried while the program is busy: exec::retry_text_busy (ETXTBSY only, at most 50 x 20 ms; any other errno returned at once), used by the executor's own execvp of the spec's args (libcontainer DefaultExecutor's exec, retried). Unit 3/3; kernel: 3 concurrent loops x 12 runs of the parallel tests (incl. 4 rounds x 16 jobs) 36/36 with no failure (before: every loop failed within 2 runs, ETXTBSY); container 46/46 + workspace 4/4234938b0ee8fRED: Heimdall is a defensive sandbox — the job execve retried while the program is busy (ETXTBSY): the parallel container tests failed "failed to execute workload" 1 run in 3 under load on main (already before the workspace merge: cf8b346 2/6, eb4d9b5 1/6); the executor now reports the whole error chain, which names ETXTBSY: Text file busy (another thread's spawn holds a copy of the write fd of a root being unpacked until its exec). Tests: exec::retry_text_busy 3 unit, kernel 4 rounds x 16 parallel jobs83392c41a75dMerge remote-tracking branch 'origin/main' into allin/brokeraa1e0bf6462bMerge main (350b10b, dyntrace) into allin/exec-flake7169911e33a7GREEN: Heimdall is a defensive sandbox — heimdall-plain and heimdall-container create the jobs' parent cgroup (create_dir_all, race-free) before the shim starts; libcgroups still enables the controllers on it (it writes subtree_control for every existing intermediate component). CHANGELOG33f4bed2a49bRED: Heimdall is a defensive sandbox — heimdall-container: the jobs' parent cgroup must exist before the shim starts (container-tests on oden, 1 of 12 runs: a_changed_image_byte_is_refused_at_the_next_start failed 'failed to create dir /sys/fs/cgroup/heimdall: File exists': libcgroups 0.7.0 checks exists() then mkdirs, two parallel jobs race); fake-shim test the_runner_creates_the_jobs_parent_cgroup_before_the_shim_runs350b10b54acaMerge allin/dyntrace into main (lane dyntrace, H32-DYNAMIC-TRACE, Heimdall is a defensive sandbox): the dynamic-trace job — new crate heimdall-trace (in-job tracer as PID 1 under the probe learning filter: exec+argv, connect/bind refused unless AF_UNIX, file calls with paths/flags, writes outside /tmp and into /out refused, syscall set per program; canonical size-bounded trace.json heimdall.trace.v1 signed via the job SignedResult; TraceJob, run_trace, verify_trace fail closed to Unknown, diff against a baseline, image::with_tracer), heimdall-seccomp Launch::exec_by_path, heimdall trace run|verify|diff, fixed profile io_uring_setup EPERM (enter/register still kill), trace+broker refused by name (BROKER_REFUSAL). Gate on oden at the eb4d9b5 merge: ci.sh green (heim-zlwasm, registry mounts), trace-tests 12/12, container-tests 45/45 + workspace 4/4 (the load-dependent execve flake on main, failed to execute workload in parallel jobs, did not show this run; fix in allin/exec-flake); afb00c5 (prof: profiles and docs only) merged after the gate5b31977b7da4Merge remote-tracking branch 'origin/main' into allin/dyntrace6c030ac661b5Merge origin/main (prof afb00c5) into allin/broker: README keeps prof's seccomp-bpf row and the broker and Landlock rows Implementedd2214ccf59c1RED: heimdall-probe kernel test a_slow_thread_join_is_no_drift — the worker's calls depend on timing (a slow thread makes the join wait on a futex the fast probe missed; lane prof saw it once under load)afb00c5b5387Merge allin/prof into main (lane prof, HEIMDALL-ALLIN, Heimdall is a defensive sandbox): generated per-program seccomp profiles for Holger's three job kinds — heimdall-wasm-host (the verify-bundle worker) measured again without memfd_create (glibc 39 rules sha256:bd1a5bcd, musl 35 sha256:8369f2ec); Holger's scan worker (32 rules sha256:6d802b5e) and clamd (58 rules sha256:4a714c3d) documented in docs/SECCOMP.md (checked in at holger heimdall-allin/prof until 0.3.0); bench/znippy-unpack (owner-approved, 36 rules sha256:32dd9f28, enforce 12/12). README seccomp row Implemented; CHANGELOG Unreleased. Gate on oden: ci.sh green (heim-zlwasm), seccomp kernel 12/12 + enforce 54/54 + drift OK on the merged tree, container 45/45, plain 23/23 (privileged rootless podman, heim-m0)7f2154663aa5Merge main (eb4d9b5, workspace) into allin/exec-flake992b8a970f3dGREEN (test): Heimdall is a defensive sandbox — the host_ids kernel test reads the ids of the job's program only past its execve (not the executor, whose creds are host root until it sets them inside the mapped namespace) and keeps polling while it still reads host root; a job that really runs as host root stays root and still fails (1 of 24 runs on oden caught the executor after uid_map)c46cf8d11186Merge remote-tracking branch 'origin/main' into allin/dyntraceaadc5bf7e67bGREEN: Heimdall is a defensive sandbox — the job workspace (main eb4d9b5) through the broker: /work is a read-write prefix like /tmp (exec stays the mount's and Landlock's decision); O_DIRECT is an ordinary open flag (dd oflag=direct into a host workspace was refused, so the cap test never filled it). RED: main's a_host_workspace_over_its_cap_kills_the_job on the merged tree. workspace kernel tests 4/4, broker kernel tests 24/24 (oden), policy 21/216dfe13a7d5d5Merge remote-tracking branch 'origin/main' into allin/prof05adf9084f3bMerge remote-tracking branch 'origin/main' into allin/broker0ddef8a3d61dGREEN: Heimdall is a defensive sandbox — heimdall-plain forklock: one process-wide RwLock; image unpack holds writing(), PlainYoukiRunner and HeimdallRunner start their shim under spawning() (Command::spawn returns after the child execve), so no shim inherits a write fd of another job's program (the ETXTBSY flake on main cf8b346). The host_ids kernel test reads ids only from a job process whose user namespace is mapped (it caught the executor before uid_map: 0 0 0 0, 1 of 8 runs). CHANGELOG0d833759a5ecRED: Heimdall is a defensive sandbox — heimdall-plain forklock: no shim may be forked while a job root is being unpacked (container-tests flake on main cf8b346, 2 of 8 runs on oden: execve of /bin/sh in a parallel job failed ETXTBSY, a shim forked by another thread held the program open for writing); writing() shared, spawning() exclusive; tests a_spawn_waits_while_a_job_file_is_being_written, writers_do_not_wait_for_each_other; stub lockscefc20137a02heimdall-container: Heimdall is a defensive sandbox — the shim reports an execve failure with its whole error chain (libcontainer shows only "failed to execute workload"; the errno, e.g. ETXTBSY, is in its source). Found chasing a container-tests flake on main cf8b346eb4d9b5e0ee0Merge hgun/workspace into main (lane HEIM-GUNNAR, owner decision 2026-11-01): the job workspace — Policy.workspace {bytes, exec} at /work inside the job container; <= 1 GiB a capped tmpfs, above a per-job host folder (bind, shim-measured cap, deleted after); noexec unless exec AND HeimdallConfig.allow_workspace_exec; Landlock rw (+x only with exec); KillReason::Workspace (wire tag 5), heimdall-limit-workspace. ci.sh green (heim-zlwasm), kernel container 43/43 + workspace 4/47f7d72593bebGREEN: Heimdall is a defensive sandbox — a file the broker creates is the job's: under with_host_ids the broker (root in the shim) made /out files owned by host root (RED: main's with_host_ids_a_root_runners_job_root_is_not_host_root_seen_from_the_host on the merged tree). O_CREAT opens now try the file as it is first, create only with O_EXCL, and fchown what they created to the caller's fs uid/gid from /proc/<tid>/status; an existing file is never chowned. container kernel tests 56/56, broker kernel tests 24/24 (oden, privileged rootless podman); CHANGELOGd6fe08add7b4Merge remote-tracking branch 'origin/main' into allin/dyntrace04015eb8e787Merge origin/main (merge-fp-2 cf8b346: host_ids, wasm without memfd, io-max) into allin/broker: both sides' container and fake-shim tests kept