Files
LithosAnanake/include/starkernel/capsule_birth.h
T
Robert Allan JamesandClaude Sonnet 5 d9da82b065 Stage 4 increments 2+3: WIREBIND VMs as switch-signal participants + mark-and-defer tombstone reap (FABRIC-3.md §XXVIII Stage 4)
Increment 2: WIREBIND user VMs (the ones that actually run FORTH work;
console VMs are pure REPL proxies and never participate) register as
Stage 3 switch-signal participants at attach, unregister at teardown.
Slot table bumped 8 -> 16, matching messaging.4th's own VM-MAX -- a real,
already-agreed ceiling, not an invented number. Added
sk_vm_switch_signal_unregister() (compaction-based; Tripod VMs never
needed removal, WIREBIND VMs cycle constantly and would otherwise
exhaust the bounded table).

Increment 3: implements the plan's own ratified option (A) for the
async-detach UAF risk -- mark-and-defer via a new pending_reap flag on
VMRegistryEntry, deliberately not a new VMState (capsule_vm_kill()
already treats VM_STATE_DEAD as idempotent success, which would silently
swallow a reap attempt; SWITCHED_OUT still accurately describes a
tombstoned VM until the moment it's actually freed). unclean_detach()
sets it when capsule_vm_kill() refuses a SWITCHED_OUT target; the Stage 3
checkpoint (vm_core.c) checks it before ever attempting to resume a
pending switch target, and calls the new capsule_vm_force_reap() instead
-- the one caller allowed to bypass capsule_vm_kill()'s own refusal,
because it runs at the exact safe cooperative point the switcher itself
controls. A new idle-tick sweep cleans up the WIREBIND live-table entry
once the reap has actually happened.

Verified clean on all 3 architectures (baseline regression -- no
WIREBIND attach happens in a plain boot). The reap mechanism's own
correctness under a genuinely parked context is verified separately,
next, via a temporary deterministic probe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
2026-09-15 00:58:34 -04:00

347 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 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_birth.h - VM Birth Protocol (M7.1)
*
* Functions for birthing VMs from capsules:
* - Mama init: Execute core/init.4th to establish Mama's PERSONALITY
* - Baby birth: Create new VM from (p) capsule
* - Experiment run: Execute (e) capsule on Mama
*/
#ifndef STARKERNEL_CAPSULE_BIRTH_H
#define STARKERNEL_CAPSULE_BIRTH_H
#include <stdint.h>
#include "starkernel/capsule.h"
#include "starkernel/capsule_run.h"
#ifdef __cplusplus
extern "C" {
#endif
/*===========================================================================
* VM Execution Hook
*
* The birth protocol needs to execute FORTH code on a VM.
* This hook is provided by the VM layer.
*===========================================================================*/
/**
* VM execution function type
*
* @param vm_ctx Opaque pointer to VM context
* @param code FORTH source code to execute
* @param code_len Length of code in bytes
* @return 0 on success, non-zero on error
*/
typedef int (*CapsuleExecFn)(void *vm_ctx, const char *code, uint64_t code_len);
/**
* Dictionary hash function type
*
* @param vm_ctx Opaque pointer to VM context
* @return 64-bit hash of dictionary state
*/
typedef uint64_t (*CapsuleDictHashFn)(void *vm_ctx);
/**
* VM allocation function type (for baby birth)
*
* @return Opaque pointer to new VM context, or NULL on failure
*/
typedef void *(*CapsuleVMAllocFn)(void);
/**
* capsule_birth_set_hooks - Configure VM execution hooks
*
* Must be called before any birth operations.
*
* @param exec_fn Function to execute FORTH code on VM
* @param dict_hash_fn Function to compute dictionary hash
* @param vm_alloc_fn Function to allocate new VM (for babies)
*/
void capsule_birth_set_hooks(
CapsuleExecFn exec_fn,
CapsuleDictHashFn dict_hash_fn,
CapsuleVMAllocFn vm_alloc_fn
);
/*===========================================================================
* Mama Init
*===========================================================================*/
/**
* capsule_birth_mama - Execute Mama's init capsule
*
* Finds the MAMA_INIT capsule by flag, validates it, executes it on Mama's VM.
*
* @param mama_vm Mama's VM context
* @param dir Capsule directory header
* @param descs Capsule descriptor array
* @param names Capsule name entry array (parallel to descs)
* @param arena Capsule payload arena
* @return CAPSULE_RUN_OK on success, error code otherwise
*/
CapsuleRunResult capsule_birth_mama(
void *mama_vm,
const CapsuleDirHeader *dir,
const CapsuleDesc *descs,
const CapsuleNameEntry *names,
const uint8_t *arena
);
/*===========================================================================
* Baby Birth
*===========================================================================*/
/**
* capsule_birth_baby - Birth a new VM from a named (p) capsule
*
* Finds the capsule by colon-separated name, validates it, allocates a new
* VM, executes the capsule payload as IDENTITY, then (if present) executes
* unit.4th from the baby's block space as PERSONALITY.
*
* @param capsule_name Colon-separated capsule name, e.g. "production:myvm.4th"
* @param dir Capsule directory header
* @param descs Capsule descriptor array
* @param names Capsule name entry array (parallel to descs)
* @param arena Capsule payload arena
* @param parent Who is birthing this VM (FABRIC-2.md §H.12 step 7) --
* the caller's own VMUuid (e.g. vm->stadium_vm_id for
* a FORTH word handler), recorded on the new VM's
* Session.parent. Every current call site has one in
* scope, directly or one level up; traced live rather
* than assumed (checked all 6 call sites across
* mama_forth_words.c/capsule_console.c/
* capsule_runcap.c/capsule_wirebind.c).
* @param skip_pki_sig 0 for every build-time capsule (the normal case --
* checked against the compile-time-baked signature
* array via capsule_get_signatures()). Non-zero only
* for RUNCAP (FABRIC-2.md §F.6/F.18): a heap-built,
* single-entry directory sourced from a user's own
* thumbdrive has no entry in that array at all --
* index 0 would silently compare against whatever
* real capsule happens to occupy slot 0, which is
* not a security check, just a guaranteed-wrong one.
* Trust for that content comes from CERTVERIFY (a
* separate root, the user's own Zuse-signed cert)
* already having run before RUNCAP is ever called,
* not from this flag -- this only skips a check that
* was never meaningful for that content in the first
* place. Deliberately a plain flag, not a new entry
* point, so the policy is one call-site decision,
* trivially reversible.
* @param out_vm_id Output: assigned VM ID
* @param out_vm_ctx Output: new VM context
* @return CAPSULE_RUN_OK on success, error code otherwise
*/
CapsuleRunResult capsule_birth_baby(
const char *capsule_name,
const CapsuleDirHeader *dir,
const CapsuleDesc *descs,
const CapsuleNameEntry *names,
const uint8_t *arena,
VMUuid parent,
int skip_pki_sig,
VMUuid *out_vm_id,
void **out_vm_ctx
);
/*===========================================================================
* Experiment Execution
*===========================================================================*/
/**
* capsule_run_experiment - Execute a named (e) capsule on Mama
*
* Finds the experiment capsule by colon-separated name, validates it,
* executes it on Mama's VM without creating a new VM.
*
* @param mama_vm Mama's VM context
* @param capsule_name Colon-separated capsule name, e.g. "experiments:doe-l8:init-l8-stable.4th"
* @param dir Capsule directory header
* @param descs Capsule descriptor array
* @param names Capsule name entry array (parallel to descs)
* @param arena Capsule payload arena
* @param out_run_id Output: assigned run ID
* @return CAPSULE_RUN_OK on success, error code otherwise
*/
CapsuleRunResult capsule_run_experiment(
void *mama_vm,
const char *capsule_name,
const CapsuleDirHeader *dir,
const CapsuleDesc *descs,
const CapsuleNameEntry *names,
const uint8_t *arena,
uint64_t *out_run_id
);
/*===========================================================================
* VM Hook Registration
*===========================================================================*/
/**
* capsule_vm_hooks_register - Wire concrete VM hooks into the capsule subsystem
*
* Registers capsule_exec_hook, capsule_dict_hash_hook, and capsule_vm_alloc_hook.
* Must be called after vm_init() on Mama's VM and before any birth operations.
*/
void capsule_vm_hooks_register(void);
/*===========================================================================
* VM Registry
*===========================================================================*/
/**
* capsule_vm_registry_init - Initialize VM registry
*
* Allocates Mama's registry node (VM 0, name "Hera") via kmalloc and
* stores mama_vm_ptr so KILL can guard against destroying Mama.
* Must be called after kmalloc_init().
*
* @param mama_vm_ptr Pointer to Mama's VM object (e.g. &sk_mama_vm)
*/
void capsule_vm_registry_init(void *mama_vm_ptr);
/**
* capsule_vm_kill - Destroy a named VM and release all its resources
*
* Looks up the VM by name (case-insensitive). Hera (VM 0) cannot be
* killed. If the VM is already DEAD the call is a no-op.
* On success: vm_cleanup + sf_free, state → VM_STATE_DEAD, name cleared.
*
* @param name Symbolic name of the VM to kill (case-insensitive)
* @return 0 on success (or already dead), -1 if not found or refused
*/
int capsule_vm_kill(const char *name);
/**
* capsule_vm_registry_get - Get VM registry entry by ID
*
* @param vm_id VM ID to look up
* @param out Output: registry entry copy
* @return 0 on success, -1 if not found
*/
int capsule_vm_registry_get(VMUuid vm_id, VMRegistryEntry *out);
/**
* capsule_vm_registry_count - Get number of registered VMs
*/
uint32_t capsule_vm_registry_count(void);
/**
* capsule_vm_registry_get_by_index - Get registry entry by list position
* (birth order, stable within a boot session -- the registry is
* append-only). For enumeration (e.g. the idle-loop messaging pump,
* FABRIC-2.md Phase C, 2026-08-28), where no vm_id is known up front.
* Index range is [0, capsule_vm_registry_count()).
*
* @param index Zero-based position in birth order
* @param out Output: registry entry copy
* @return 0 on success, -1 if index is out of range
*/
int capsule_vm_registry_get_by_index(uint32_t index, VMRegistryEntry *out);
/**
* capsule_vm_find_by_name - Find VM registry entry by symbolic name
*
* Case-sensitive. Returns the first match.
*
* @param name Symbolic VM name, e.g. "Hermes"
* @param out Output: registry entry copy
* @return 0 if found, -1 if not found
*/
int capsule_vm_find_by_name(const char *name, VMRegistryEntry *out);
/**
* capsule_vm_find_by_name_nocase - Find VM registry entry by name (case-insensitive)
*
* Used by BIRTH for idempotency: prevents birthing a second VM with the
* same name regardless of case differences.
*
* @param name Symbolic VM name (compared case-insensitively)
* @param out Output: registry entry copy
* @return 0 if found, -1 if not found
*/
int capsule_vm_find_by_name_nocase(const char *name, VMRegistryEntry *out);
/**
* capsule_vm_set_state - Update a VM's state in the registry
*
* @param vm_id VM ID to update
* @param state New VMState value
*/
void capsule_vm_set_state(VMUuid vm_id, uint32_t state);
/**
* capsule_vm_set_pending_reap - FABRIC-3.md §XXVIII Stage 4 (2026-09-14):
* mark vm_id for deferred teardown once it is no longer worth resuming --
* see VMRegistryEntry.pending_reap's own doc comment for the full
* rationale. No-op if vm_id isn't registered.
*
* @param vm_id VM ID to mark.
* @param pending 1 to mark, 0 to clear (e.g. a re-attach of the same
* identity before the switcher ever reaped it).
*/
void capsule_vm_set_pending_reap(VMUuid vm_id, int pending);
/**
* capsule_vm_force_reap - FABRIC-3.md §XXVIII Stage 4 (2026-09-14):
* unconditionally tear down vm_id regardless of VM_STATE_SWITCHED_OUT --
* the one caller allowed to bypass capsule_vm_kill()'s own refusal there,
* because this is called *by* the Stage 3 switcher itself (vm_core.c's
* checkpoint, on noticing pending_reap set), at the one point that
* genuinely knows the parked native-stack context will never be resumed.
* Also releases the VM's own switch-signal slot
* (sk_vm_switch_signal_unregister()) -- generic cleanup, independent of
* whatever subsystem (WIREBIND today) set pending_reap in the first
* place. No-op if vm_id isn't registered or is already DEAD.
*
* @param vm_id VM ID to reap.
*/
void capsule_vm_force_reap(VMUuid vm_id);
/**
* capsule_vm_registry_set_name - Assign a symbolic name to a registered VM
*
* Truncates to VM_NAME_MAX-1 characters. No-op if vm_id not found.
*
* @param vm_id VM ID to name
* @param name Symbolic name string
*/
void capsule_vm_registry_set_name(VMUuid vm_id, const char *name);
/**
* capsule_vm_kill_all_nonmama - Kill every non-Mama VM in the registry.
*
* Sets halted, calls vm_cleanup + sf_free, marks state DEAD. Used by
* Hera's BYE immediately before arch_cold_reset() to reap all children.
*/
void capsule_vm_kill_all_nonmama(void);
#ifdef __cplusplus
}
#endif
#endif /* STARKERNEL_CAPSULE_BIRTH_H */