Baleen

A static-partitioning separation kernel for AArch64/EL2, built so that its claims can be checked rather than believed.

View the Project on GitHub via-balaena/baleen

Architecture Audit #1 — the hv-hal fence

The centerpiece of M3 Arc 3, and the first point where the ∀-N model proofs make contact with real hardware. The proofs cover the hv-core model; they hold on the metal only insofar as the metal honors the southbound trait surface (hv-hal) exactly as the model assumes. This audit enumerates that surface, asks of each element “is it architecture-neutral, and does the ARM metal honor it — or must we name an assumption?”, and records the verdict. A clean audit is a valid result and the confidence artifact (design-lesson #17); a named assumption is an honest debt carried forward, not a gap swept under the rug.

The charter — what hv-core trusts the HAL to guarantee

hv-core is no_std, zero-unsafe, and reaches the outside world only through hv-hal (hv-hal/src/lib.rs). It never touches a register, a page table, or a device. So the entire attack surface between the proven model and the metal is these three traits and two type aliases. Two properties must both hold for the proofs to mean anything on hardware:

  1. Neutrality. The surface names no CPU architecture. If a signature carried a VMCS field, an ept_* type, or a GIC redistributor, the “same brain on ARM and x86” promise would be a fiction and the proofs would be entangled with one ISA. This is the standing constraint from [[baleen-arm-target]] — ARM-first, x86 co-equal.
  2. Fidelity. Each trait the ARM metal implements must behave as the model assumes — a TimeSource that runs backwards, or a GuestMemory::read that returns the wrong bytes, would silently falsify the proof at the seam. Where a trait is not yet implemented on ARM, the assumption is named and deferred to the arc that realizes it.

The surface, enumerated

The whole fence, as of Arc 3 (hv-hal/src/lib.rs):

element kind Arc-3 status
Gpa = u64 type alias guest-physical address; a plain integer
Ticks = u64 type alias opaque monotonic time; a plain integer
MemError enum { OutOfBounds }
GuestMemory::read(&self, Gpa, &mut [u8]) -> Result<(), MemError> trait method deferred (M4/Arc 5)
GuestMemory::write(&mut self, Gpa, &[u8]) -> Result<(), MemError> trait method deferred (M4/Arc 5)
TimeSource::now(&self) -> Ticks trait method realized on ARM this arc
VcpuOps::inject_interrupt(&mut self, u8) trait method deferred (M4)
VcpuOps::set_entry(&mut self, u64) trait method deferred (M4)

Neutrality verdict — per element

Overall neutrality verdict: ✅ the fence is architecture-neutral. No signature carries a VMCS field, an ept_*/Stage-2 descriptor type, a GIC/LAPIC concept, or a VMSA register. x86 plugs in behind exactly these traits. The two leaks found were both cosmetic (a doc claim and a parameter name), below the soundness bar, and are fixed in this same change.

Findings (both fixed in this PR)

Neither finding is a soundness issue. That the audit’s only findings are a doc line and an identifier is itself the result: the fence was built neutral and stayed neutral through three arcs.

Fidelity — realized vs. named-assumption, per trait

TimeSource — REALIZED on ARM this arc ✅

hv-metal/src/time.rs implements TimeSource::now over the generic timer’s physical count (CNTPCT_EL0). The contract is exactly “does not run backwards”; the ARM realization honors it:

GuestMemory — named assumption, deferred to M4/Arc 5 ⏳

No guest memory exists until the first EL1 guest and its Stage-2 tables (M4). Assumption named: the ARM impl will realize read/write as accesses through the guest’s Stage-2 translation such that they move exactly the bytes at the given Gpa and return OutOfBounds for any address outside the guest’s physical space — no more, no less. This is precisely the refinement Architecture Audit #2 (M4/Arc 5) is chartered to verify (the model→page-table bridge + the negative-isolation test). Recorded here so the debt is explicit, not discovered later.

VcpuOps — named assumption, deferred to M4 ⏳

No vCPU is run until M4’s trap-and-service loop. Assumption named: inject_interrupt will queue vector for delivery on the next guest entry (via the GIC), and set_entry will set the guest PC for the next ERET (via ELR_EL2). Nothing in Arc 3 relies on either; they are defined now only to fix the shape of the fence before hardware exists behind them.

Two cross-cutting assumptions Arc 3 introduces

Method — three-way convergence

Per the arc-0–2 discipline (design-lesson #24), the register-level claims this arc makes were established three independent ways, and they agree:

  1. Spec-derived code — the HCR_EL2 fields and the generic-timer registers were read from the Arm ARM (register descriptions, section D1) and encoded in el2.rs / time.rs.
  2. A spec-blind auditor — an independent re-derivation from the Arm ARM, with no sight of the code, confirmed: HCR_EL2.RW = bit 31; reset is architecturally UNKNOWN (so write the full value — which also pins E2H=0, without which RW loses its non-VHE meaning); the guest-trap bits (VM, TGE, IMO/FMO/AMO, the trap group) are safe left 0 pre-guest; CNTPCT_EL0 is the right EL2 clock, readable at EL2 with no enable, monotonic; CNTFRQ_EL0 is a firmware label; and an isb should precede the count read against speculative reordering. Every claim converged with the code; two refinements it surfaced (full-write over RMW; the isb) were folded in.
  3. The running emulator — QEMU booted the image and printed HCR_EL2 = 0x0000000080000000 (exactly bit 31, all else 0), a live monotonic count, and the dispatched HvCall returning balance=100. QEMU is architecturally faithful about system registers, the exception model, and functional dispatch, so it is a valid third oracle for these mechanisms (docs/QEMU-AND-METAL.md).

Verdict

The hv-hal fence is architecture-neutral, and the one trait Arc 3 realizes on ARM (TimeSource) honors its contract — witnessed on every boot. The remaining traits (GuestMemory, VcpuOps) are unimplemented on ARM by design; their assumptions are named above and carried to the arcs that realize them (Audit #2 for GuestMemory). Two cosmetic neutrality leaks were found and fixed. No soundness defect. Arc 3 refines the proof (the HAL realizes the model’s southbound assumptions) and is QEMU-sound for the functional dispatch — the honest scope, per the ledger in docs/ROADMAP.md.

The M3 HAL ledger

trait / type neutral? ARM metal (Arc 3) fidelity check verified scope
Gpa, Ticks, MemError ✅ — (plain types) — neutral by inspection
GuestMemory ✅ ⏳ deferred (M4/Arc 5 Stage-2) Audit #2 + negative-isolation test assumption named
TimeSource ✅ ✅ realized (CNTPCT_EL0, isb-ordered) witness_advance on every boot refines — honored
VcpuOps ✅ (after F2 fix) ⏳ deferred (M4 run loop) when the run loop lands assumption named
global allocator n/a ✅ bump over .bss (heap.rs) constructs Hypervisor + dispatches on every boot plumbing — no reclaim