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
305 lines
11 KiB
C
305 lines
11 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
This file is part of the StarForth project.
|
||
|
||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
|
||
You may obtain a copy of the License at:
|
||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||
|
||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
express or implied, including but not limited to the warranties of
|
||
merchantability, fitness for a particular purpose, and noninfringement.
|
||
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
*/
|
||
|
||
/**
|
||
* capsule.h - Init Capsule Architecture (M7.1)
|
||
*
|
||
* Content-addressed, immutable init capsules for VM birth.
|
||
* See docs/lithosananke/M7.1.md for full specification.
|
||
*
|
||
* Key invariants:
|
||
* - Exactly ONE production (p) INIT defines a baby VM
|
||
* - capsule_id == content_hash (content-addressed)
|
||
* - No shared/implicit base INITs
|
||
* - DOMAIN is Mama-only, PERSONALITY is baby-only
|
||
*/
|
||
|
||
#ifndef STARKERNEL_CAPSULE_H
|
||
#define STARKERNEL_CAPSULE_H
|
||
|
||
#include <stdint.h>
|
||
#include <stddef.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/*===========================================================================
|
||
* Constants
|
||
*===========================================================================*/
|
||
|
||
/** Magic signatures */
|
||
#define CAPSULE_DESC_MAGIC 0x53504143ULL /* 'CAPS' little-endian */
|
||
#define CAPSULE_DIR_MAGIC 0x44504143ULL /* 'CAPD' little-endian */
|
||
|
||
/** Version */
|
||
#define CAPSULE_VERSION_0 0
|
||
|
||
/** Limits */
|
||
#define CAPSULE_MAX_COUNT 256
|
||
|
||
/** Maximum capsule name length (colon-separated path, null-terminated) */
|
||
#define CAPSULE_NAME_MAX 512
|
||
|
||
/*===========================================================================
|
||
* Hash Algorithm Enum
|
||
*===========================================================================*/
|
||
|
||
typedef enum {
|
||
CAPSULE_HASH_XXHASH64 = 0,
|
||
CAPSULE_HASH_SHA256 = 1,
|
||
CAPSULE_HASH_BLAKE3 = 2,
|
||
} CapsuleHashAlg;
|
||
|
||
/*===========================================================================
|
||
* Flags
|
||
*===========================================================================*/
|
||
|
||
/** State flags */
|
||
#define CAPSULE_FLAG_ACTIVE 0x00000001 /* Eligible for use */
|
||
#define CAPSULE_FLAG_REVOKED 0x00000002 /* Birth-blocked forever */
|
||
#define CAPSULE_FLAG_DEPRECATED 0x00000004 /* Eligible but discouraged */
|
||
#define CAPSULE_FLAG_PINNED 0x00000008 /* Immune to GC */
|
||
|
||
/** Mode flags (D2: babies carry both) */
|
||
#define CAPSULE_FLAG_PRODUCTION 0x00000010 /* (p) truth-bearing */
|
||
#define CAPSULE_FLAG_EXPERIMENT 0x00000020 /* (e) workload only */
|
||
|
||
/** Mama init flag (exactly one capsule must have this) */
|
||
#define CAPSULE_FLAG_MAMA_INIT 0x00000040 /* (m) Mama's init */
|
||
|
||
/** Contributor capsule flag (FABRIC-3.md §I.5, 2026-09-04) -- path-match
|
||
* on capsules/contrib/, mirrors FLAG_MAMA_INIT's own exact-match pattern
|
||
* in mkcapsule.c's flags_from_name(). Trust-tier enforcement (QEMU-vs-
|
||
* real-hardware, decided in conversation) is a runtime check in
|
||
* capsule_validate()'s callers, not encoded in this bit itself -- the
|
||
* bit only marks "this capsule's provenance is a contributor, not this
|
||
* project's own source," same as CAPSULE_FLAG_PRODUCTION/_EXPERIMENT
|
||
* mark mode, not policy. */
|
||
#define CAPSULE_FLAG_CONTRIB 0x00000080 /* (c) contributor-submitted */
|
||
|
||
/** Validate mode flags.
|
||
* Mama: neither (p) nor (e) may be set.
|
||
* Babies: at least one of (p) or (e) must be set (both is fine — D2). */
|
||
#define CAPSULE_MODE_VALID(f) \
|
||
((((f) & CAPSULE_FLAG_MAMA_INIT) != 0) ? \
|
||
(!((f) & (CAPSULE_FLAG_PRODUCTION | CAPSULE_FLAG_EXPERIMENT))) : \
|
||
(((f) & CAPSULE_FLAG_PRODUCTION) || ((f) & CAPSULE_FLAG_EXPERIMENT)))
|
||
|
||
/** Check if capsule is Mama's init */
|
||
#define CAPSULE_IS_MAMA_INIT(f) \
|
||
(((f) & CAPSULE_FLAG_MAMA_INIT) && ((f) & CAPSULE_FLAG_ACTIVE))
|
||
|
||
/** Birth eligibility: active and not revoked (flag type irrelevant — D2) */
|
||
#define CAPSULE_BIRTH_ELIGIBLE(f) \
|
||
(((f) & CAPSULE_FLAG_ACTIVE) && \
|
||
!((f) & CAPSULE_FLAG_REVOKED))
|
||
|
||
/** DoE eligibility: experiment, active, not revoked */
|
||
#define CAPSULE_DOE_ELIGIBLE(f) \
|
||
(((f) & CAPSULE_FLAG_EXPERIMENT) && \
|
||
((f) & CAPSULE_FLAG_ACTIVE) && \
|
||
!((f) & CAPSULE_FLAG_REVOKED))
|
||
|
||
/*===========================================================================
|
||
* Magic Field Packing
|
||
*
|
||
* bits 0..31 : 'CAPS' (0x53504143 little-endian)
|
||
* bits 32..39 : version (0 for v0)
|
||
* bits 40..47 : hashAlg (enum CapsuleHashAlg)
|
||
* bits 48..63 : reserved (zero)
|
||
*===========================================================================*/
|
||
|
||
#define CAPSULE_MAGIC_PACK(ver, alg) \
|
||
(CAPSULE_DESC_MAGIC | ((uint64_t)(ver) << 32) | ((uint64_t)(alg) << 40))
|
||
|
||
#define CAPSULE_MAGIC_GET_SIG(m) ((uint32_t)((m) & 0xFFFFFFFFULL))
|
||
#define CAPSULE_MAGIC_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
|
||
#define CAPSULE_MAGIC_GET_HASHALG(m) ((uint8_t)(((m) >> 40) & 0xFF))
|
||
|
||
/*===========================================================================
|
||
* CapsuleDesc - Capsule Descriptor (64 bytes, cache-line aligned)
|
||
*===========================================================================*/
|
||
|
||
typedef struct __attribute__((aligned(64))) {
|
||
uint64_t magic; /* 0x00: 'CAPS' | ver | hashAlg | reserved */
|
||
uint64_t capsule_id; /* 0x08: == content_hash (content-addressed) */
|
||
uint64_t content_hash; /* 0x10: hash of payload bytes */
|
||
uint64_t offset; /* 0x18: byte offset into payload arena */
|
||
uint64_t length; /* 0x20: payload length in bytes */
|
||
uint32_t flags; /* 0x28: state + policy bits */
|
||
uint32_t owner_vm; /* 0x2C: 0 = mama, else child VM ID */
|
||
uint64_t birth_count; /* 0x30: how many VMs born from this */
|
||
uint64_t created_ns; /* 0x38: monotonic timestamp at registration */
|
||
} CapsuleDesc; /* 0x40 = 64 bytes */
|
||
|
||
/*===========================================================================
|
||
* CapsuleNameEntry - Capsule Name (parallel array to CapsuleDesc[])
|
||
*
|
||
* Indexed 1:1 with capsule_descriptors[]. Name is the full relative path
|
||
* from the capsule root with '/' replaced by ':', e.g.:
|
||
* "core:init.4th"
|
||
* "experiments:doe-l8:init-l8-diverse.4th"
|
||
* "production:myvm.4th"
|
||
*===========================================================================*/
|
||
|
||
typedef struct {
|
||
char name[CAPSULE_NAME_MAX]; /* null-terminated, colon-separated path */
|
||
} CapsuleNameEntry;
|
||
|
||
/*===========================================================================
|
||
* CapsuleSigEntry - Ed25519 signature (parallel array to CapsuleDesc[])
|
||
*
|
||
* Milestone 6 (Phase 8): each capsule's payload bytes (the same bytes
|
||
* content_hash already covers), signed by mkcapsule at build time with
|
||
* the snakeoil intermediate's private key. has_sig=0 for a capsule built
|
||
* before this feature existed or otherwise unsigned -- a real, distinct
|
||
* state, not "signature is all-zero bytes" (which sig[64] full of 0x00
|
||
* would otherwise look ambiguous with). Indexed 1:1 with
|
||
* capsule_descriptors[], same convention as CapsuleNameEntry.
|
||
*===========================================================================*/
|
||
|
||
typedef struct {
|
||
uint8_t sig[64]; /* raw Ed25519 R||S, see ed25519_sign()/ed25519_verify() */
|
||
uint8_t has_sig; /* 0 = no signature present, 1 = sig[] is real */
|
||
uint8_t _pad[7];
|
||
} CapsuleSigEntry;
|
||
|
||
/*===========================================================================
|
||
* CapsuleDirHeader - Directory Header
|
||
*===========================================================================*/
|
||
|
||
typedef struct {
|
||
uint64_t magic; /* 'CAPD' | ver | reserved */
|
||
uint64_t arena_base; /* phys or virt base of payload arena */
|
||
uint64_t arena_size; /* bytes */
|
||
uint32_t desc_count; /* current number of descriptors */
|
||
uint32_t desc_capacity; /* max (fixed at compile time for Phase A) */
|
||
uint32_t name_count; /* == desc_count, kept separate for validation */
|
||
uint32_t reserved; /* padding */
|
||
uint64_t dir_hash; /* hash of descriptor table (for parity) */
|
||
} CapsuleDirHeader;
|
||
|
||
/*===========================================================================
|
||
* Validation
|
||
*===========================================================================*/
|
||
|
||
typedef enum {
|
||
CAPSULE_VALID = 0,
|
||
CAPSULE_ERR_BAD_MAGIC,
|
||
CAPSULE_ERR_BAD_VERSION,
|
||
CAPSULE_ERR_BAD_HASH_ALG,
|
||
CAPSULE_ERR_BOUNDS,
|
||
CAPSULE_ERR_MODE_INVALID,
|
||
CAPSULE_ERR_REVOKED_ACTIVE,
|
||
CAPSULE_ERR_HASH_MISMATCH,
|
||
CAPSULE_ERR_NULL_PTR,
|
||
} CapsuleValidateResult;
|
||
|
||
/**
|
||
* capsule_validate - Validate a capsule descriptor
|
||
*
|
||
* @param desc Capsule descriptor to validate
|
||
* @param arena_base Base address of payload arena
|
||
* @param arena_size Size of payload arena in bytes
|
||
* @param verify_hash If true, recompute and compare content hash
|
||
* @return CAPSULE_VALID on success, error code otherwise
|
||
*/
|
||
CapsuleValidateResult capsule_validate(
|
||
const CapsuleDesc *desc,
|
||
const uint8_t *arena_base,
|
||
uint64_t arena_size,
|
||
int verify_hash
|
||
);
|
||
|
||
/**
|
||
* capsule_validate_result_str - Get string for validation result
|
||
*/
|
||
const char *capsule_validate_result_str(CapsuleValidateResult result);
|
||
|
||
/*===========================================================================
|
||
* Lookup
|
||
*===========================================================================*/
|
||
|
||
/**
|
||
* capsule_find_by_id - Find capsule by content hash ID
|
||
*
|
||
* @param dir Directory header
|
||
* @param descs Descriptor array
|
||
* @param id Capsule ID (content hash) to find
|
||
* @return Pointer to descriptor, or NULL if not found
|
||
*/
|
||
const CapsuleDesc *capsule_find_by_id(
|
||
const CapsuleDirHeader *dir,
|
||
const CapsuleDesc *descs,
|
||
uint64_t id
|
||
);
|
||
|
||
/**
|
||
* capsule_find_by_name - Find capsule by colon-separated name
|
||
*
|
||
* @param dir Directory header
|
||
* @param descs Descriptor array
|
||
* @param names Name entry array (parallel to descs)
|
||
* @param name Colon-separated capsule name, e.g. "core:init.4th"
|
||
* @return Pointer to descriptor, or NULL if not found
|
||
*/
|
||
const CapsuleDesc *capsule_find_by_name(
|
||
const CapsuleDirHeader *dir,
|
||
const CapsuleDesc *descs,
|
||
const CapsuleNameEntry *names,
|
||
const char *name
|
||
);
|
||
|
||
/**
|
||
* capsule_get_payload - Get pointer to capsule payload bytes
|
||
*
|
||
* @param desc Capsule descriptor
|
||
* @param arena_base Base address of payload arena
|
||
* @return Pointer to payload bytes, or NULL on error
|
||
*/
|
||
const uint8_t *capsule_get_payload(
|
||
const CapsuleDesc *desc,
|
||
const uint8_t *arena_base
|
||
);
|
||
|
||
/**
|
||
* capsule_find_mama_init - Find the Mama init capsule
|
||
*
|
||
* Searches the descriptor array for the capsule with CAPSULE_FLAG_MAMA_INIT.
|
||
* There must be exactly one such capsule.
|
||
*
|
||
* @param dir Directory header
|
||
* @param descs Descriptor array
|
||
* @return Pointer to Mama's init descriptor, or NULL if not found
|
||
*/
|
||
const CapsuleDesc *capsule_find_mama_init(
|
||
const CapsuleDirHeader *dir,
|
||
const CapsuleDesc *descs
|
||
);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* STARKERNEL_CAPSULE_H */
|