Session.parent now comes from the actual birthing VM's own stadium_vm_id, not a hardcoded vm_uuid_hera(). Added a VMUuid parent parameter to capsule_birth_baby() and, one level up, to capsule_console_birth()/capsule_runcap_birth() (neither had a VM* in their own signature, but every caller did). Updated all 6 real call sites: BIRTH, CAPSULE-BIRTH, CONNECT-ARTEMIS, CONNECT-HERMES, RUNCAP-TEST, PAIR-TEST (mama_forth_words.c) and the console+user birth pair in capsule_wirebind_try_attach() (capsule_wirebind.c). Two functions had their vm parameter marked __attribute__((unused)), now genuinely used -- attribute removed. Steps 8 (Session.name from capsule name) and 9 (identity defaults to installed=0) were already satisfied by step 5's existing session_register() call and its identity-zeroing -- confirmed by inspection, no further code needed. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
179 lines
7.0 KiB
C
179 lines
7.0 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
Licensed under the StarForth License, Version 1.0
|
||
*/
|
||
|
||
#ifndef __STARKERNEL__
|
||
#error "capsule_wirebind.c is kernel-only"
|
||
#endif
|
||
|
||
#include "starkernel/capsule_wirebind.h"
|
||
#include "starkernel/capsule_birth.h"
|
||
#include "starkernel/capsule_runcap.h"
|
||
#include "starkernel/capsule_console.h"
|
||
#include "starkernel/vm_identity.h"
|
||
#include "starkernel/user_identity_seed.h"
|
||
#include "starkernel/console.h"
|
||
#include "blkio.h"
|
||
#include "block_subsystem.h" /* compute_crc64() */
|
||
#include "freestanding/stdio.h"
|
||
#include <string.h>
|
||
#include <stddef.h>
|
||
|
||
/* WIREBIND_CERT_MAX_DEVBLOCKS: a sane upper bound on how much cert
|
||
* content this reads, independent of whatever sig->cert_devblocks
|
||
* claims -- MINT (capsule_mint.c) only ever writes 1 devblock's worth
|
||
* (a real cert is ~150-250 bytes), so 4 devblocks (16KB) is already
|
||
* generous headroom, not a real constraint. */
|
||
#define WIREBIND_CERT_MAX_DEVBLOCKS 4u
|
||
|
||
/* Read exactly one devblock (4096 bytes) at devblock offset `devblock`
|
||
* -- same convention capsule_runcap.c/capsule_mint.c already use. */
|
||
static int read_devblock(struct blkio_dev *dev, uint32_t devblock, uint8_t *buf4096) {
|
||
uint32_t base = devblock * 4u;
|
||
for (uint32_t i = 0; i < 4u; i++) {
|
||
if (blkio_read((blkio_dev_t *)dev, base + i,
|
||
buf4096 + (size_t)i * BLKIO_FORTH_BLOCK_SIZE) != BLKIO_OK) {
|
||
return -1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int capsule_wirebind_verify_cert(struct blkio_dev *dev,
|
||
const homeblocks_sig_t *sig,
|
||
VM *mama_vm, VMIdentity *out) {
|
||
if (!dev || !sig || !mama_vm || !out) return -1;
|
||
|
||
/* Genesis-mode Zuse drives have no cert region -- capsule_zuse_boot_
|
||
* try_attach() owns that case, not this one. */
|
||
if (sig->cert_offset == 0 || sig->cert_devblocks == 0) return -1;
|
||
|
||
/* Nothing to verify a regular cert against until Zuse herself has
|
||
* authenticated this boot. */
|
||
if (!mama_vm->zuse_cert_installed) return -1;
|
||
|
||
/* Read the cert region into a local buffer. */
|
||
uint32_t n_devblocks = sig->cert_devblocks;
|
||
if (n_devblocks > WIREBIND_CERT_MAX_DEVBLOCKS) n_devblocks = WIREBIND_CERT_MAX_DEVBLOCKS;
|
||
uint8_t cert_der[WIREBIND_CERT_MAX_DEVBLOCKS * 4096];
|
||
for (uint32_t d = 0; d < n_devblocks; d++) {
|
||
if (read_devblock(dev, sig->cert_offset + d, cert_der + (size_t)d * 4096) != 0) {
|
||
return -1;
|
||
}
|
||
}
|
||
size_t cert_len = (size_t)n_devblocks * 4096;
|
||
|
||
memset(out, 0, sizeof(*out));
|
||
return vm_identity_from_cert(out, cert_der, cert_len,
|
||
mama_vm->zuse_cert_pubkey, sig->drive_uuid,
|
||
0 /* acl_caps: no bits assigned yet, §F.2 */);
|
||
}
|
||
|
||
void capsule_wirebind_try_attach(struct blkio_dev *dev,
|
||
const homeblocks_sig_t *sig,
|
||
VM *mama_vm) {
|
||
if (!dev || !sig || !mama_vm) return;
|
||
|
||
/* Genesis-mode Zuse drives have no cert region -- capsule_zuse_boot_
|
||
* try_attach() already owns that case. Check here too so the right
|
||
* "not applicable" case stays silent (matches capsule_wirebind_
|
||
* verify_cert()'s own -1, but avoids a misleading refusal message
|
||
* for a drive this function was never going to act on anyway). */
|
||
if (sig->cert_offset == 0 || sig->cert_devblocks == 0) return;
|
||
if (!mama_vm->zuse_cert_installed) return;
|
||
|
||
VMIdentity identity;
|
||
if (capsule_wirebind_verify_cert(dev, sig, mama_vm, &identity) != 0) {
|
||
console_println("WIREBIND: cert verification FAILED -- drive refused");
|
||
return;
|
||
}
|
||
|
||
/* Verified. Read the drive's own profile for its username. */
|
||
if (sig->identity_src_offset == 0 || sig->identity_src_devblocks < 1) {
|
||
console_println("WIREBIND: verified cert but no identity_src region -- refusing");
|
||
return;
|
||
}
|
||
user_identity_seed_t idrec;
|
||
if (read_devblock(dev, sig->identity_src_offset, (uint8_t *)&idrec) != 0) {
|
||
console_println("WIREBIND: verified cert but identity record unreadable -- refusing");
|
||
return;
|
||
}
|
||
/* Same magic -> version -> CRC-64 discipline as capsule_zuse_boot.c:
|
||
* the identity record is the same on-disk format, so a corrupt or
|
||
* format-mismatched record must be refused rather than trusted. */
|
||
if (idrec.magic != USER_IDENTITY_SEED_MAGIC ||
|
||
idrec.version != USER_IDENTITY_SEED_VERSION) {
|
||
console_println("WIREBIND: verified cert but identity record unreadable -- refusing");
|
||
return;
|
||
}
|
||
uint64_t want_crc = compute_crc64((const uint8_t *)&idrec,
|
||
offsetof(user_identity_seed_t, crc));
|
||
if (want_crc != idrec.crc) {
|
||
console_println("WIREBIND: verified cert but identity record corrupt -- refusing");
|
||
return;
|
||
}
|
||
|
||
char username[USER_IDENTITY_USERNAME_MAX];
|
||
memcpy(username, idrec.username, sizeof(username));
|
||
username[sizeof(username) - 1] = '\0'; /* defensive; MINT already NUL-terminates */
|
||
|
||
char user_vm_name[USER_IDENTITY_USERNAME_MAX + 8];
|
||
size_t ulen = strlen(username);
|
||
if (ulen == 0 || ulen + 6 > sizeof(user_vm_name)) {
|
||
console_println("WIREBIND: empty or oversized username -- refusing");
|
||
return;
|
||
}
|
||
memcpy(user_vm_name, username, ulen);
|
||
memcpy(user_vm_name + ulen, "~user", 6);
|
||
|
||
/* Idempotent: already bound this session (console VM already live
|
||
* under this username) -- nothing to do. Matches BIRTH's own
|
||
* idempotent convention. */
|
||
{
|
||
VMRegistryEntry existing;
|
||
if (capsule_vm_find_by_name(username, &existing) == 0 &&
|
||
existing.state == VM_STATE_LIVE) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
VMUuid console_id, user_id;
|
||
void *console_ctx = (void *)0;
|
||
void *user_ctx = (void *)0;
|
||
if (capsule_console_birth(username, mama_vm->stadium_vm_id, &console_id, &console_ctx)
|
||
!= CAPSULE_RUN_OK) {
|
||
console_println("WIREBIND: console VM birth FAILED");
|
||
return;
|
||
}
|
||
if (capsule_runcap_birth(dev, sig, user_vm_name, mama_vm->stadium_vm_id, &user_id,
|
||
&user_ctx) != CAPSULE_RUN_OK) {
|
||
console_println("WIREBIND: user VM birth FAILED");
|
||
return;
|
||
}
|
||
|
||
/* Install the verified identity onto the user VM -- the one BINDSTEP
|
||
* (§F.9) will later re-verify against on every USE. */
|
||
if (user_ctx) {
|
||
((VM *)user_ctx)->identity = identity;
|
||
}
|
||
|
||
/* Register the pairing in the console's own routing table, index 3
|
||
* -- the fixed convention sk_repl_dispatch_line() (repl.c) uses. */
|
||
{
|
||
char reg_cmd[sizeof(user_vm_name) + 32];
|
||
int n = snprintf(reg_cmd, sizeof(reg_cmd), "S\" %s\" 3 VM-NAME-REG", user_vm_name);
|
||
if (n > 0 && (size_t)n < sizeof(reg_cmd)) {
|
||
vm_interpret((VM *)console_ctx, reg_cmd);
|
||
}
|
||
}
|
||
|
||
console_puts("WIREBIND: ");
|
||
console_puts(username);
|
||
console_println(" attached and ready -- USE it to begin");
|
||
}
|