Files
LithosAnanke/kernel/src/v4/sk_v4_identity.c
T
rajamesandClaude Opus 5.5 2e71f7aa0d fix(kernel): a GPT disk with a bad header, or an unreadable first block, is not used
The increment 2 final review's minors 1-4 (Captain Bob, 2026-10-27: "fix 1-4"):
1. A protective MBR with no "EFI PART" header is GPT_BAD, not GPT_NONE: the
   MBR says it is a GPT disk, and a GPT disk is never attached whole.
2. A disk whose block 0 cannot be read is GPT_EIO: "GPT: a USB drive could
   not be read -- not used", nothing attached (it was read raw, attached
   whole, and a raw Zuse drive behind it logged her in).
3. The entry array's LBA is checked against the disk's size directly.
4. gpt_bad_entries: the entry array changed, the header valid -- refused.
Harness modes gpt_no_header, gpt_read_error (blkdebug EIO on sector 0),
gpt_bad_entries. The plan carries the amendment.

Red (amd64): gpt_no_header 070335, gpt_read_error 070717 FAIL;
gpt_bad_entries 070048 PASS (existing check, now pinned). Green: every GPT
mode on amd64 071113-072933; gpt_zuse, gpt_read_error, gpt_no_header on
aarch64 073222-074047 and riscv64 074526-075535; the identity gate's and
increment 1's sessions 080036-082345, emergency 083130 (082635 was run
without EMERGENCY_CONSOLE_ENABLED=1 -- a harness invocation error).
Accepted: suite EXIT 0; 093121, 093352, 093750, POST 538/538, dict_hash
0xfec8c47cfc6f437f; test-32, 0 failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:17:00 -04:00

278 lines
9.8 KiB
C

/* sk_v4_identity.c -- the identity gate for the v4 boot: no identity drive,
* headless; Zuse's minted thumbdrive, a console.
* docs/v4.0.0/plans/2026-10-26-engine-step4-identity-gate.md, ENGINE.md 2.
*
* As v3 (kernel_main.c, repl.c's sk_repl_headless_wait()/sk_repl_idle(),
* capsule_zuse_boot.c), done by the kernel with no v3 VM behind it: Artemis
* and Hera are the kernel's (ENGINE.md 2, Q1), so a drive that arrives is
* given to the block subsystem here, and Zuse's session is kept here, for
* the first node, Hera. The check that the drive is hers is v3's own
* (capsule_zuse_boot_check()).
*
* Not here (the plan's "not in this gate"): WIREBIND identities, EJECT,
* ACL-ZUSE-BOOT, and genesis -- which writes Artemis's disk, and the v4 boot
* does not write it yet (Phase 5).
*/
#include "starkernel/v4/sk_v4_identity.h"
#include "starkernel/console.h"
#include "starkernel/arch.h"
#include "starkernel/apic.h"
#include "starkernel/timer.h"
#include "starkernel/kmalloc.h"
#include "starkernel/xhci_driver.h"
#include "starkernel/blkio_usb.h"
#include "starkernel/homeblocks_sig.h"
#include "starkernel/capsule_zuse_boot.h"
#include "starkernel/kbd_input.h"
#include "starkernel/gpt.h"
#ifdef ARCH_AMD64
#include "starkernel/ioapic.h"
#include "starkernel/i8042.h"
#endif
#include "block_subsystem.h"
#include <string.h>
#define SK_V4_IDLE_BEAT 100u /* ticks between idle beats, v3's SK_IDLE_BEAT_INTERVAL (1 s at 100 Hz) */
/* ZUSE'S STATE, for Hera. The cert is installed once and never replaced
* (v3's vm_zuse_cert_install(), one-way); the session ends when her drive
* leaves and begins again when it comes back. */
static int g_zuse_session;
static int g_zuse_cert_installed;
static uint8_t g_zuse_seed[32], g_zuse_pubkey[32];
static uint8_t g_zuse_root_pubkey[32];
static int g_zuse_root_known;
static struct blkio_dev *g_zuse_dev;
/* A drive in a USB slot: the raw disk; its StarshipOS identity partition as
* a device of its own, when it has a GPT with one; and the one of the two
* given to the block subsystem, if either was (a GPT disk is never given
* whole: docs/v4.0.0/plans/2026-10-26-stick-2-gpt-reader.md, ruled). */
typedef struct {
blkio_dev_t raw;
blkio_dev_t part;
gpt_part_dev_t part_state;
blkio_dev_t *used;
} sk_v4_drive_t;
/* One per slot, per xHCI controller, as repl.c's g_usb_ctrl. */
static struct {
sk_v4_drive_t *slots;
uint32_t count;
} g_usb[XHCI_MAX_CONTROLLERS];
#define SK_V4_GPT_PARTS 16u
static gpt_part_t g_gpt_parts[SK_V4_GPT_PARTS];
static uint64_t g_last_beat;
static int g_said_no_genesis;
static const char *sk_v4_user_prefix(void)
{
return g_zuse_session ? "zuse" : (const char *)0;
}
int sk_v4_identity_present(void)
{
return g_zuse_session;
}
void sk_v4_identity_boot(void)
{
unsigned xi;
static xhci_dev_t xhci_devs[XHCI_MAX_CONTROLLERS];
/* Zuse's public key, from the genesis marker on Artemis's disk */
g_zuse_root_known = capsule_zuse_boot_root_pubkey(g_zuse_root_pubkey) == 0;
/* every xHCI controller, as kernel_main.c's v3 path: a drive already in
* a port is found by the bring-up's own port scan */
for (xi = 0; xi < XHCI_MAX_CONTROLLERS; xi++) {
int rc = xhci_find_and_map(&xhci_devs[xi], xi);
if (rc == 1) break;
if (rc == 0) (void)xhci_bringup(&xhci_devs[xi]);
}
/* the heartbeat, as kernel_main.c's v3 path starts it */
console_println("Starting heartbeat...");
apic_timer_start();
#ifdef ARCH_AMD64
i8042_drain_stale();
ioapic_unmask_legacy_irq(1);
console_println("I/O APIC: keyboard IRQ1 unmasked");
#endif
arch_enable_interrupts();
console_println("Heartbeat running.");
g_last_beat = heartbeat_ticks();
}
/* A drive that has arrived: given to the block subsystem (Artemis's, the
* kernel's), then, if it is Zuse's, her session begins. */
static int sk_v4_drive_arrived(sk_v4_drive_t *d)
{
blkio_dev_t *dev = &d->raw;
homeblocks_sig_t sig;
homeblocks_sig_result_t sig_rc;
uint8_t seed[32], pubkey[32];
zuse_drive_t which;
unsigned nparts;
int gpt_rc, idx;
/* GPT first (docs/v4.0.0/STICK.md 4): a disk with a valid GPT is used
* through its StarshipOS identity partition only; one with no such
* partition, a damaged table or an unreadable first block is not used at
* all; a disk with no GPT is read raw, as an identity drive always has
* been. */
d->used = (blkio_dev_t *)0;
gpt_rc = gpt_read(&d->raw, g_gpt_parts, SK_V4_GPT_PARTS, &nparts);
if (gpt_rc == GPT_BAD) {
console_println("GPT: a USB drive has a damaged partition table -- not used");
return 1;
}
if (gpt_rc == GPT_EIO) {
console_println("GPT: a USB drive could not be read -- not used");
return 1;
}
if (gpt_rc == GPT_OK) {
idx = gpt_find(g_gpt_parts, nparts, gpt_type_starship_identity);
if (idx < 0) return 1; /* no StarshipOS partition: not ours to use */
if (gpt_part_open(&d->part, &d->part_state, &d->raw, &g_gpt_parts[idx]) != BLKIO_OK) {
console_println("GPT: a USB drive's identity partition is not on 1 KiB blocks -- not used");
return 1;
}
dev = &d->part;
}
sig_rc = homeblocks_sig_check(dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
if (blk_subsys_attach_device(dev) != BLK_OK) {
console_println("xhci: USB MSC block-subsystem attach failed");
return 0;
}
d->used = dev;
if (g_zuse_session) return 1;
which = capsule_zuse_boot_check(dev, sig_rc, &sig, seed, pubkey);
if (which == ZUSE_DRIVE_GENESIS) {
if (!g_said_no_genesis) {
console_println("Zuse: no genesis on this disk -- the system stays headless");
g_said_no_genesis = 1;
}
return 1;
}
if (which != ZUSE_DRIVE_HERS) return 1;
console_set_vm_name("Hera");
console_set_user_prefix_provider(sk_v4_user_prefix);
/* the screen is the console from here (docs/v4.0.0/STICK.md 2) */
console_fb_mirror(1);
console_fb_enable_ttf();
console_println("Zuse: identity confirmed from attached thumbdrive");
if (!g_zuse_cert_installed) {
memcpy(g_zuse_seed, seed, 32);
memcpy(g_zuse_pubkey, pubkey, 32);
g_zuse_cert_installed = 1;
}
memcpy(g_zuse_root_pubkey, pubkey, 32); /* as v3's install_and_activate() */
g_zuse_root_known = 1;
g_zuse_dev = dev;
g_zuse_session = 1;
return 1;
}
static void sk_v4_drive_left(sk_v4_drive_t *d)
{
blkio_dev_t *dev = d->used;
if (!dev) return; /* never given to the block subsystem */
d->used = (blkio_dev_t *)0;
blk_subsys_detach_device(dev);
if (dev != g_zuse_dev) return;
g_zuse_session = 0;
g_zuse_dev = (struct blkio_dev *)0;
/* nothing of her session is left on the screen; it is dark again --
* except in a build with EMERGENCY_CONSOLE_ENABLED, whose console needs
* no login and goes on running (the stick's increment 1 final review) */
#if !EMERGENCY_CONSOLE_ENABLED
console_fb_clear();
console_fb_mirror(0);
#endif
console_println("Zuse: session ended -- reattach to re-authenticate");
}
/* One idle beat: the xHCI event ring, then every drive that has arrived or
* left since the last (repl.c's sk_repl_idle(), its USB part). */
static void sk_v4_idle(void)
{
uint32_t xc, slot;
console_ensure_line_start();
{
uint64_t tx_before = console_tx_count();
xhci_poll_events();
for (xc = 0; xc < xhci_dev_count() && xc < XHCI_MAX_CONTROLLERS; xc++) {
xhci_dev_t *xdev = xhci_dev_at(xc);
if (!xdev) continue;
if (!g_usb[xc].slots || g_usb[xc].count < xdev->max_slots + 1) {
size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(sk_v4_drive_t);
sk_v4_drive_t *fresh = (sk_v4_drive_t *)kmalloc_aligned(bytes, 64);
if (!fresh) continue;
memset(fresh, 0, bytes);
g_usb[xc].slots = fresh;
g_usb[xc].count = xdev->max_slots + 1;
}
for (slot = 1; slot <= xdev->max_slots; slot++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot);
if (!ms || !ms->bot_msc_attach_pending) continue;
ms->bot_msc_attach_pending = 0;
if (blkio_usb_open_msc(&g_usb[xc].slots[slot].raw, xdev, slot) == 0 &&
sk_v4_drive_arrived(&g_usb[xc].slots[slot]))
ms->bot_msc_attached = 1;
}
for (slot = 1; slot <= xdev->max_slots; slot++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot);
if (!ms || !ms->bot_msc_detach_pending) continue;
ms->bot_msc_detach_pending = 0;
sk_v4_drive_left(&g_usb[xc].slots[slot]);
}
}
if (console_tx_count() == tx_before) console_cancel_deferred_line_start();
}
}
void sk_v4_identity_beat(void)
{
uint64_t now;
heartbeat_service();
now = heartbeat_ticks();
if (now - g_last_beat >= SK_V4_IDLE_BEAT) {
g_last_beat = now;
sk_v4_idle();
}
}
void sk_v4_identity_check_now(void)
{
g_last_beat = heartbeat_ticks();
sk_v4_idle();
}
/* What was typed at a headless machine -- on the serial line or any
* keyboard -- is no one's: it is read and dropped, so that it does not run
* as whoever logs in next (the identity gate's final review, item 4,
* 2026-10-26). */
static void sk_v4_discard_input(void)
{
sk_input_drop();
}
void sk_v4_identity_wait(void)
{
while (!g_zuse_session) {
sk_v4_discard_input();
sk_v4_identity_beat();
arch_relax();
}
sk_v4_discard_input();
}