FABRIC-3.md §I.5: Milestone 7 trust tiers (QEMU-vs-real-hardware), closing it
Closes the contributor-capsule/trust-tier punch-list item. Decided direction: QEMU-vs-real-hardware conditional enforcement. Found before building on that decision: the obvious mechanism (expose TimerInfo.vm_mode) only works on amd64 -- aarch64 and riscv64 both had vm_mode hardcoded to 1 unconditionally, meaning they'd always report "running under QEMU" even on real hardware. Built real detection for both instead of shipping that: aarch64 checks the ACPI RSDP's OEM ID for QEMU's "BOCHS " SeaBIOS-heritage signature; riscv64 checks the devicetree root compatible property for "qemu". Confirmed vm_mode was otherwise unread anywhere else in either file first -- zero risk to existing timing behavior. CAPSULE_FLAG_CONTRIB (mkcapsule.c: FLAG_CONTRIB) path-matches on capsules/contrib/, mirroring FLAG_MAMA_INIT's exact-match pattern. contrib_capsule_refused() (capsule_birth.c) enforces: no additional check under QEMU (same WARN-only as everything else); on real hardware, a contrib capsule additionally requires CAPSULE_SIG_OK, since it has no other provenance to fall back on. Wired into capsule_birth_baby() and capsule_run_experiment(). Also updates §I.7 (Milestone 9): its stated precondition (Milestone 7 closing) is now met, flagged as stale rather than treated as a green light to design networking from nothing. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground) -- compile/boot verification only; the real-hardware enforcement branch is unverifiable from this environment, same as all of §I.6. logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5567d03c12
commit
eeceec21a5
@@ -16,9 +16,35 @@
|
||||
#include "q48_16.h"
|
||||
#include "uefi.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ─── ARM generic-timer helpers ─────────────────────────────────────── */
|
||||
|
||||
/* FABRIC-3.md §I.5, 2026-09-04: real hypervisor-vs-hardware detection.
|
||||
* s_cal.vm_mode was hardcoded to 1 unconditionally below (comment:
|
||||
* "QEMU SBSA always uses virtualised Generic Timer") -- true for the
|
||||
* *timing policy* this file cares about, but wrong to reuse as a
|
||||
* general "are we in QEMU" signal elsewhere (contrib-capsule trust-tier
|
||||
* enforcement, §I.5), which is exactly what happened before this was
|
||||
* caught: it would report "always QEMU" on real hardware too.
|
||||
*
|
||||
* ARM has no single CPUID-equivalent hypervisor-present bit the way
|
||||
* amd64's CPUID.1:ECX[31] is (arch/amd64/timer.c's own
|
||||
* running_under_hypervisor()). Checks the ACPI RSDP's OEM ID instead --
|
||||
* "BOCHS " is the SeaBIOS-heritage signature QEMU's ACPI table
|
||||
* generation has used since long before this project, on every machine
|
||||
* type it emulates, aarch64 virt included. Real hardware vendors set
|
||||
* their own OEMID, never this string. Mirrors pci.c's own private Rsdp2
|
||||
* struct layout (signature[8], checksum, oem_id[6]) -- duplicated
|
||||
* rather than shared, same "a few TLV-walking lines is simpler than a
|
||||
* new header" precedent tools/pkcs8_ed25519.c already set. */
|
||||
static int running_under_hypervisor(BootInfo *boot_info) {
|
||||
if (!boot_info || !boot_info->acpi_table) return 0;
|
||||
const uint8_t *rsdp = (const uint8_t *) boot_info->acpi_table;
|
||||
if (memcmp(rsdp, "RSD PTR ", 8) != 0) return 0;
|
||||
return memcmp(rsdp + 9, "BOCHS ", 6) == 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the AArch64 Physical System Counter with ISB serialisation.
|
||||
*
|
||||
@@ -101,15 +127,17 @@ static timer_calibration_record_t s_cal;
|
||||
* because the ARM Generic Timer is invariant and synchronised by
|
||||
* architecture — no drift-detection loop is needed.
|
||||
*
|
||||
* @param boot_info Kernel @c BootInfo (ACPI/memory map); unused on AArch64
|
||||
* for timer init (GIC base address will be used in a later
|
||||
* milestone for the interrupt controller).
|
||||
* @param boot_info Kernel @c BootInfo -- GIC base address will be used in a
|
||||
* later milestone for the interrupt controller; as of
|
||||
* 2026-09-04 also consulted here for @c acpi_table, so
|
||||
* @c s_cal.vm_mode can be a real hypervisor-vs-hardware
|
||||
* detection instead of the hardcoded-1 timing-policy
|
||||
* shortcut this field used to double as (see
|
||||
* running_under_hypervisor()'s own doc comment above).
|
||||
* @return 0 always.
|
||||
*/
|
||||
int timer_init(BootInfo *boot_info)
|
||||
{
|
||||
(void)boot_info;
|
||||
|
||||
uint64_t freq = cntfrq_read();
|
||||
if (freq == 0) {
|
||||
/* Firmware did not set CNTFRQ; assume 62.5 MHz (Cortex-A57 default) */
|
||||
@@ -127,7 +155,7 @@ int timer_init(BootInfo *boot_info)
|
||||
s_cal.hpet_hz = 0;
|
||||
s_cal.pit_hz_mean = 0;
|
||||
s_cal.converged = 1;
|
||||
s_cal.vm_mode = 1;
|
||||
s_cal.vm_mode = running_under_hypervisor(boot_info) ? 1 : 0;
|
||||
s_cal.trust = TIMER_TRUST_ABSOLUTE;
|
||||
|
||||
console_println("Timer: AArch64 generic timer initialised.");
|
||||
@@ -209,7 +237,11 @@ int timer_check_drift_now(void)
|
||||
* - @c hpet_hz = 0 (no HPET on AArch64)
|
||||
* - @c pit_hz_mean = 0 (no PIT on AArch64)
|
||||
* - @c converged = 1 (Generic Timer is already calibrated by firmware)
|
||||
* - @c vm_mode = 1 (QEMU SBSA always uses virtualised Generic Timer)
|
||||
* - @c vm_mode = real hypervisor-vs-hardware detection as of 2026-09-04
|
||||
* (ACPI RSDP OEM ID check, running_under_hypervisor() above) -- the
|
||||
* Generic Timer itself works identically either way, so this field's
|
||||
* *timing* meaning here is purely informational, unlike amd64 where
|
||||
* vm_mode gates a real calibration-path choice
|
||||
* - @c trust = @c TIMER_TRUST_ABSOLUTE
|
||||
*
|
||||
* The record is exposed to @c kernel_main() and @c timer.h consumers for
|
||||
|
||||
@@ -25,6 +25,37 @@
|
||||
#include "uefi.h"
|
||||
#include "starkernel/fdt.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* FABRIC-3.md §I.5, 2026-09-04: real hypervisor-vs-hardware detection.
|
||||
* s_cal.vm_mode was hardcoded to 1 unconditionally below -- see
|
||||
* aarch64/timer.c's own running_under_hypervisor() doc comment for why
|
||||
* that's wrong to reuse as a general "are we in QEMU" signal elsewhere
|
||||
* (contrib-capsule trust-tier enforcement, §I.5). RISC-V has no ACPI
|
||||
* here (this file's own devicetree-only timebase-frequency discovery
|
||||
* above is the proof) but does have a devicetree, already parsed for
|
||||
* exactly one other property -- the root node's "compatible" property
|
||||
* carries QEMU's own machine-model string ("qemu" appears in it for the
|
||||
* virt board) on every QEMU riscv64 target; real hardware vendors set
|
||||
* their own compatible strings, never this one. bytes_contain() is a
|
||||
* tiny local substring search -- no strstr dependency assumed available
|
||||
* in this translation unit. */
|
||||
static int bytes_contain(const uint8_t *hay, uint32_t haylen, const char *needle) {
|
||||
size_t nlen = strlen(needle);
|
||||
if (nlen == 0 || haylen < nlen) return 0;
|
||||
for (uint32_t i = 0; i + nlen <= haylen; i++) {
|
||||
if (memcmp(hay + i, needle, nlen) == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int running_under_hypervisor(BootInfo *boot_info) {
|
||||
if (!boot_info || !fdt_valid(boot_info->dtb)) return 0;
|
||||
uint32_t len = 0;
|
||||
const void *prop = fdt_find_prop(boot_info->dtb, "compatible", &len);
|
||||
if (!prop) return 0;
|
||||
return bytes_contain((const uint8_t *) prop, len, "qemu");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the RISC-V wall-clock counter (@c rdtime, CSR @c time 0xC01).
|
||||
@@ -145,7 +176,7 @@ int timer_init(BootInfo *boot_info)
|
||||
s_cal.hpet_hz = 0;
|
||||
s_cal.pit_hz_mean = 0;
|
||||
s_cal.converged = 1;
|
||||
s_cal.vm_mode = 1;
|
||||
s_cal.vm_mode = running_under_hypervisor(boot_info) ? 1 : 0;
|
||||
/* ABSOLUTE only when the rate came from firmware. On the fallback the
|
||||
* counter is still monotonic and invariant, but its scaling to real time
|
||||
* is a guess, which is exactly the RELATIVE case. */
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "starkernel/console.h"
|
||||
#include "starkernel/vm/stadium.h" /* item 4.1a -- stadium_grant_quota() */
|
||||
#include "starkernel/session.h" /* session_register()/session_set_pinned() -- FABRIC-3.md §H.12 step 5 */
|
||||
#include "starkernel/timer.h" /* timer_calibration_record()->vm_mode -- FABRIC-3.md §I.5 CONTRIB trust tier */
|
||||
#include "vm.h"
|
||||
#include "platform_alloc.h"
|
||||
/* No LOG_LINE_MAX include-order constraint anymore: vm.h's own
|
||||
@@ -450,6 +451,25 @@ void capsule_vm_kill_all_nonmama(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* FABRIC-3.md §I.5, 2026-09-04: contributor-capsule trust tier
|
||||
* (QEMU-vs-real-hardware conditional enforcement, decided in
|
||||
* conversation). CAPSULE_FLAG_CONTRIB capsules get the same WARN-only
|
||||
* treatment as everything else under QEMU (timer_calibration_record()->
|
||||
* vm_mode == 1) -- development/test is meant to run contrib capsules
|
||||
* freely. On real hardware (vm_mode == 0), a contrib capsule additionally
|
||||
* requires CAPSULE_SIG_OK -- MISSING/NO_ROOT_KEY, which stay WARN-only
|
||||
* for every other capsule (no offline signing key on most machines,
|
||||
* see capsule_birth_mama()'s own comment), are refused here specifically
|
||||
* because a contributor's capsule has no other provenance to fall back
|
||||
* on the way this project's own capsules do. Never touches the
|
||||
* CAPSULE_SIG_INVALID refusal already in place for every capsule --
|
||||
* additive, not a replacement. */
|
||||
static int contrib_capsule_refused(uint32_t flags, CapsuleSigResult sr) {
|
||||
if (!(flags & CAPSULE_FLAG_CONTRIB)) return 0;
|
||||
if (timer_calibration_record()->vm_mode) return 0; /* QEMU: relaxed */
|
||||
return sr != CAPSULE_SIG_OK; /* real hardware: must actually verify */
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* Mama Init
|
||||
*===========================================================================*/
|
||||
@@ -569,6 +589,13 @@ CapsuleRunResult capsule_birth_baby(
|
||||
names[idx].name, capsule_sig_result_str(sr));
|
||||
if (sr == CAPSULE_SIG_INVALID) return CAPSULE_RUN_ERR_INVALID;
|
||||
}
|
||||
/* FABRIC-3.md §I.5: contrib trust tier -- see contrib_capsule_
|
||||
* refused()'s own doc comment. */
|
||||
if (contrib_capsule_refused(cap->flags, sr)) {
|
||||
log_message(LOG_WARN, "capsule sig: %s: contrib capsule refused on real hardware (%s)",
|
||||
names[idx].name, capsule_sig_result_str(sr));
|
||||
return CAPSULE_RUN_ERR_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
if (vm_registry_live_count() >= stadium_max_vm_count()) {
|
||||
@@ -759,6 +786,13 @@ CapsuleRunResult capsule_run_experiment(
|
||||
names[idx].name, capsule_sig_result_str(sr));
|
||||
if (sr == CAPSULE_SIG_INVALID) return CAPSULE_RUN_ERR_INVALID;
|
||||
}
|
||||
/* FABRIC-3.md §I.5: contrib trust tier -- see contrib_capsule_
|
||||
* refused()'s own doc comment. */
|
||||
if (contrib_capsule_refused(cap->flags, sr)) {
|
||||
log_message(LOG_WARN, "capsule sig: %s: contrib capsule refused on real hardware (%s)",
|
||||
names[idx].name, capsule_sig_result_str(sr));
|
||||
return CAPSULE_RUN_ERR_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t pre_dict_hash = vm_dict_hash_fn(mama_vm);
|
||||
|
||||
Reference in New Issue
Block a user