From d7b2f6bf543157fc0aa0b36d43038f0ca94ed2da Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 18:20:09 +0000 Subject: [PATCH] fix(v4): the identity code wipes Zuse's seed after every drive it handles (#176, the identity code) sk_v4_genesis never wiped its seed or its read-back; sk_v4_drive_arrived never wiped the proxy's seed after her login; sk_v4_real_zuse_arrived wiped the seed only on a match. (The logged-in Zuse's own key stays in g_zuse_seed, by design.) Option 1: the three use one buffer the identity module owns, g_seeds (seed, then its read-back), and sk_v4_drive_arrived -- which every drive goes through, and which alone calls the other two -- runs the drive's handling (now handle_drive()) and then wipes it, whichever way it went. sk_v4_identity_seeds() lets the host tests read it. kernel/tests/test_sk_v4_identity.c, 10 checks, through the real drive-arrival path: a fake xHCI controller marks a slot attach-pending, the identity code's idle beat handles it, the real gpt.c reads the stick's table from the RAM disk; the block layer, signatures, key mint, marker and console faked. Genesis; a genesis not read back; the proxy logging in; a real Zuse named, then her stick logging in. Against the code before the wipe: 5 failures (every case's seeds left). Now: 0. The harness gains fake_disk_bind. Built for amd64, aarch64, riscv64. #176 done with 2bac2ac8 (the mint). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm --- .../plans/2026-11-12-kernel-host-tests.md | 4 +- kernel/include/starkernel/v4/sk_v4_identity.h | 4 + kernel/src/v4/sk_v4_identity.c | 32 +++- kernel/tests/Makefile | 4 +- kernel/tests/fakes.c | 9 ++ kernel/tests/fakes.h | 1 + kernel/tests/test_sk_v4_identity.c | 147 ++++++++++++++++++ .../kernel-tests/test_sk_v4_identity.log | 3 + 8 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 kernel/tests/test_sk_v4_identity.c create mode 100644 logs/20261112-182009/kernel-tests/test_sk_v4_identity.log diff --git a/docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md b/docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md index 238eb230..a7d87f35 100644 --- a/docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md +++ b/docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md @@ -32,7 +32,9 @@ fails half-way, a stop that spins) is still tested first and seen to fail. v4 ha - [x] 2. The xHCI configuration-descriptor walk moved out of the event loop (`usb_config.c`, `test_usb_config`), then #170 and #171 tested first (10 failures against the old decision) and fixed. -- [ ] 3. #176 (seeds wiped on every exit) -- the approach to rule. [x] #177 (the fatal stops halt: +- [x] 3. #176 (seeds wiped on every exit; option 1, Captain Bob 2026-11-12: "1 is great" -- the + caller owns the buffer: `test_sk_v4_mint`, 7 failures before; `test_sk_v4_identity`, through the + real drive-arrival path, 5 failures before). [x] #177 (the fatal stops halt: `sk_v4_stop.c`, `test_sk_v4_stop`, a spin seen failing). [x] #178 (the slot array: `sk_v4_slots.c`, `test_sk_v4_slots`, 4 failures against the old allocation). [x] #182 (the cap checked at compile time: `check_cap`, a 4 TiB cap seen building before). diff --git a/kernel/include/starkernel/v4/sk_v4_identity.h b/kernel/include/starkernel/v4/sk_v4_identity.h index 2d89ebd1..231a2783 100644 --- a/kernel/include/starkernel/v4/sk_v4_identity.h +++ b/kernel/include/starkernel/v4/sk_v4_identity.h @@ -56,6 +56,10 @@ void sk_v4_identity_set_marker(const sk_v4_marker_t *m); * written its GPT and identity (it is then recognised as the real Zuse's). */ void sk_v4_identity_recheck_offered(void); +/* The 64 bytes the identity code mints and reads Zuse's seed into while it handles a drive (seed, + * then its read-back): zero whenever no drive is being handled (#176). For the host tests. */ +const uint8_t *sk_v4_identity_seeds(void); + /* Headless: no prompt, and what is typed is read and dropped, until Zuse's * session begins (v3's sk_repl_headless_wait()). */ void sk_v4_identity_wait(void); diff --git a/kernel/src/v4/sk_v4_identity.c b/kernel/src/v4/sk_v4_identity.c index a0340708..b54439c7 100644 --- a/kernel/src/v4/sk_v4_identity.c +++ b/kernel/src/v4/sk_v4_identity.c @@ -66,6 +66,11 @@ static int g_zuse_session; static int g_zuse_cert_installed; static uint8_t g_zuse_seed[32], g_zuse_pubkey[32]; +/* The seed being minted or read, and its read-back, while a drive is handled: seed the first 32 + * bytes, rseed the next 32 (#176). The logged-in Zuse's own is g_zuse_seed, kept by design. */ +static uint8_t g_seeds[64]; + +const uint8_t *sk_v4_identity_seeds(void) { return g_seeds; } static uint8_t g_zuse_root_pubkey[32]; static int g_zuse_root_known; static struct blkio_dev *g_zuse_dev; @@ -242,7 +247,8 @@ static void sk_v4_login(blkio_dev_t *dev, const uint8_t seed[32], const uint8_t * system headless. Returns 1 when she is logged in. */ static int sk_v4_genesis(blkio_dev_t *dev, blkio_dev_t *art) { - uint8_t seed[32], pubkey[32], rseed[32], rpub[32]; + uint8_t pubkey[32], rpub[32]; + uint8_t *seed = g_seeds, *rseed = g_seeds + 32; homeblocks_sig_t sig; homeblocks_sig_result_t sig_rc; uint32_t lbn, nblk; @@ -289,7 +295,8 @@ static int sk_v4_real_zuse_arrived(sk_v4_drive_t *d, unsigned nparts) { homeblocks_sig_t sig; homeblocks_sig_result_t sig_rc; - uint8_t seed[32], pubkey[32]; + uint8_t pubkey[32]; + uint8_t *seed = g_seeds; int ii = gpt_find(g_gpt_parts, nparts, gpt_type_starship_identity); if (!g_marker.present || !g_marker.has_real || ii < 0) return 0; @@ -307,21 +314,23 @@ static int sk_v4_real_zuse_arrived(sk_v4_drive_t *d, unsigned nparts) g_real_drive = d; g_proxy_fallback = 0; sk_v4_login(&d->part, seed, pubkey, "Zuse: identity confirmed from attached thumbdrive"); - memset(seed, 0, sizeof seed); + memset(seed, 0, 32); return 1; } /* A drive that has arrived. This system's stick: its Artemis partition is * attached (the first disk slot, the block subsystem's), Zuse's public key is * read from its genesis marker, then its identity partition is attached and, - * if it is Zuse's, her session begins. Any other drive is not used. */ -static int sk_v4_drive_arrived(sk_v4_drive_t *d) + * if it is Zuse's, her session begins. Any other drive is not used. + * Called only by sk_v4_drive_arrived, which wipes g_seeds after it (#176). */ +static int handle_drive(sk_v4_drive_t *d) { blkio_dev_t *dev; homeblocks_sig_t sig; homeblocks_sig_result_t sig_rc; artemis_sig_t asig; - uint8_t seed[32], pubkey[32]; + uint8_t pubkey[32]; + uint8_t *seed = g_seeds; zuse_drive_t which; unsigned nparts; int gpt_rc; @@ -432,6 +441,17 @@ static int sk_v4_drive_arrived(sk_v4_drive_t *d) return 1; } +/* One place wipes the seeds, after every drive handled: genesis (seed and its read-back), the + * proxy's login and the real Zuse's (sk_v4_genesis, sk_v4_real_zuse_arrived, handle_drive) -- + * whichever way each went. The logged-in Zuse's own key stays in g_zuse_seed, by design; the + * working copies did not, and were left in memory (#176). */ +static int sk_v4_drive_arrived(sk_v4_drive_t *d) +{ + int rc = handle_drive(d); + memset(g_seeds, 0, sizeof g_seeds); + return rc; +} + /* A drive that has left: its identity partition is let go, then its Artemis * partition -- in that order, because blk_subsys_detach_device() removes * only the chain's tail, and the identity partition was attached last. */ diff --git a/kernel/tests/Makefile b/kernel/tests/Makefile index c6fb3f81..351896bc 100644 --- a/kernel/tests/Makefile +++ b/kernel/tests/Makefile @@ -19,7 +19,9 @@ CC ?= cc CFLAGS := -std=c99 -Wall -Wextra -Werror -O2 -g -I$(HERE) -I$(ROOT)/kernel/include \ -I$(ROOT)/kernel/include/starkernel -I$(ROOT)/v3/include -TESTS := test_gpt test_usb_config test_sk_v4_stop test_sk_v4_slots test_sk_v4_mint +TESTS := test_gpt test_usb_config test_sk_v4_stop test_sk_v4_slots test_sk_v4_mint test_sk_v4_identity +SRC_test_sk_v4_identity := $(ROOT)/kernel/src/v4/sk_v4_identity.c $(ROOT)/kernel/src/v4/sk_v4_slots.c $(ROOT)/kernel/src/gpt.c +EXTRA_test_sk_v4_identity := -D__STARKERNEL__ -I$(ROOT)/v4/include -DSTICK_GROW_MIN_BYTES=8000000000ULL -DSTICK_ARTEMIS_CAP_GIB=256ULL SRC_test_sk_v4_mint := $(ROOT)/kernel/src/v4/sk_v4_mint.c $(ROOT)/kernel/src/gpt.c EXTRA_test_sk_v4_mint := -D__STARKERNEL__ -I$(ROOT)/v4/include SRC_test_sk_v4_slots := $(ROOT)/kernel/src/v4/sk_v4_slots.c diff --git a/kernel/tests/fakes.c b/kernel/tests/fakes.c index e6ff31d6..d1cd29d4 100644 --- a/kernel/tests/fakes.c +++ b/kernel/tests/fakes.c @@ -98,6 +98,15 @@ int fake_disk_open(blkio_dev_t *dev, fake_disk *d, uint32_t blocks) p.opaque = d; return blkio_open(dev, &fake_vt, &p); } +int fake_disk_bind(blkio_dev_t *dev, fake_disk *d) +{ + blkio_params_t p; + memset(&p, 0, sizeof p); + p.forth_block_size = BLKIO_FORTH_BLOCK_SIZE; + p.total_blocks = d->blocks; + p.opaque = d; + return blkio_open(dev, &fake_vt, &p); +} void fake_disk_free(fake_disk *d) { free(d->bytes); d->bytes = 0; } void fake_disk_fail_read(fake_disk *d, uint32_t block) { if (d->n_fail_read < FAKE_DISK_MAX_FAULTS) d->fail_read[d->n_fail_read++] = block; } void fake_disk_fail_write(fake_disk *d, uint32_t block) { if (d->n_fail_write < FAKE_DISK_MAX_FAULTS) d->fail_write[d->n_fail_write++] = block; } diff --git a/kernel/tests/fakes.h b/kernel/tests/fakes.h index 4ee16c1f..7303b2e7 100644 --- a/kernel/tests/fakes.h +++ b/kernel/tests/fakes.h @@ -36,6 +36,7 @@ typedef struct { } fake_disk; int fake_disk_open(blkio_dev_t *dev, fake_disk *d, uint32_t blocks); /* zeroed; BLKIO_OK */ +int fake_disk_bind(blkio_dev_t *dev, fake_disk *d); /* open another handle on d */ void fake_disk_free(fake_disk *d); void fake_disk_fail_read(fake_disk *d, uint32_t block); void fake_disk_fail_write(fake_disk *d, uint32_t block); diff --git a/kernel/tests/test_sk_v4_identity.c b/kernel/tests/test_sk_v4_identity.c new file mode 100644 index 00000000..d232d7c7 --- /dev/null +++ b/kernel/tests/test_sk_v4_identity.c @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: LicenseRef-Starship-1.0 + * Copyright (c) 2023-2026 Robert A. James + */ +/* test_sk_v4_identity.c -- kernel/src/v4/sk_v4_identity.c on the host (#176): the seeds the identity + * code mints and reads while it handles a drive -- genesis, the proxy's login, the real Zuse's -- + * are wiped once the drive has been handled, whichever way it went. Drives arrive as on the + * machine: a fake xHCI controller marks a slot's drive attach-pending, sk_v4_identity_check_now() + * runs the identity code's idle beat, and the drive's GPT is read by the real gpt.c from the + * harness's RAM disk. The block layer, the signatures, the key mint, the marker and the console are + * faked. + */ +#define _POSIX_C_SOURCE 200809L +#include "harness.h" +#include "fakes.h" +#include "starkernel/gpt.h" +#include "starkernel/xhci_driver.h" +#include "starkernel/artemis_sig.h" +#include "starkernel/capsule_zuse_boot.h" +#include "starkernel/homeblocks_sig.h" +#include "starkernel/console.h" +#include "starkernel/v4/sk_v4_identity.h" +#include "starkernel/v4/sk_v4_marker.h" +#include "starkernel/v4/sk_v4_slots.h" +#include "block_subsystem.h" +#include +#include + +#define SLOTS 4u +static xhci_dev_t xdev; +static xhci_msc_slot_t ms[SLOTS + 1]; +static fake_disk disks[SLOTS + 1]; + +/* the scenario */ +static int sig_blank, key_hers; +static sk_v4_marker_t marker_on_stick; +static int marker_rc; + +/* ---- the fakes ---- */ +uint32_t xhci_dev_count(void) { return 1u; } +xhci_dev_t *xhci_dev_at(uint32_t i) { return i == 0 ? &xdev : 0; } +xhci_msc_slot_t *xhci_msc_slot_for(xhci_dev_t *dev, uint32_t slot) { (void)dev; return slot <= SLOTS ? &ms[slot] : 0; } +void xhci_poll_events(void) { } +int xhci_bringup(xhci_dev_t *dev) { (void)dev; return 0; } +int xhci_find_and_map(xhci_dev_t *dev, unsigned index) { (void)dev; (void)index; return -1; } +int blkio_usb_open_msc(blkio_dev_t *dev_out, xhci_dev_t *x, uint32_t slot) { (void)x; return fake_disk_bind(dev_out, &disks[slot]) == BLKIO_OK ? 0 : -1; } +void *kmalloc_aligned(size_t size, size_t align) { void *p = 0; return posix_memalign(&p, align, size) ? 0 : p; } + +artemis_sig_result_t artemis_sig_check(struct blkio_dev *dev, artemis_sig_t *out) { (void)dev; memset(out, 0, sizeof *out); return ARTEMIS_SIG_OK; } +int blk_subsys_attach_device(struct blkio_dev *dev) { (void)dev; return BLK_OK; } +int blk_subsys_detach_device(struct blkio_dev *dev) { (void)dev; return BLK_OK; } +int blk_get_device_range(struct blkio_dev *dev, uint32_t *start, uint32_t *count) { (void)dev; *start = 0; *count = 1; return BLK_OK; } +int blk_subsys_confirm_format(uint32_t lbn) { (void)lbn; return BLK_OK; } +int sk_v4_grow_artemis(blkio_dev_t *art) { (void)art; return 0; } +uint64_t sk_v4_grow_cap_sectors(blkio_dev_t *art, uint64_t cap) { (void)art; (void)cap; return 0; } +int sk_v4_marker_read(sk_v4_marker_t *m) { *m = marker_on_stick; return marker_on_stick.present ? 0 : -1; } +int sk_v4_marker_write(const sk_v4_marker_t *m) { marker_on_stick = *m; return marker_rc; } +int sk_v4_mint_zuse(void) { return -1; } + +homeblocks_sig_result_t homeblocks_sig_check(struct blkio_dev *dev, uint32_t start, homeblocks_sig_t *out) +{ (void)dev; (void)start; memset(out, 0, sizeof *out); return sig_blank ? HOMEBLOCKS_SIG_BLANK : HOMEBLOCKS_SIG_OK; } +int capsule_zuse_boot_genesis_mint(struct blkio_dev *dev, uint8_t seed[32], uint8_t pubkey[32]) +{ (void)dev; memset(seed, 0x5A, 32); memset(pubkey, 0x11, 32); return 0; } +zuse_drive_t capsule_zuse_boot_check_key(struct blkio_dev *dev, homeblocks_sig_result_t rc, const homeblocks_sig_t *sig, + const uint8_t want[32], uint8_t seed[32], uint8_t pubkey[32]) +{ (void)dev; (void)rc; (void)sig; memset(seed, 0x5A, 32); memcpy(pubkey, want, 32); + return key_hers ? ZUSE_DRIVE_HERS : (zuse_drive_t)(ZUSE_DRIVE_HERS + 1); } + +static uint64_t ticks; +uint64_t heartbeat_ticks(void) { return ticks += 1000u; } +void heartbeat_service(void) { } +void apic_timer_start(void) { } +void arch_halt(void) { } +void arch_enable_interrupts(void) { } +int sk_input_getc(void) { return -1; } +void sk_input_drop(void) { } +void console_cancel_deferred_line_start(void) { } +void console_ensure_line_start(void) { } +void console_fb_clear(void) { } +void console_fb_enable_ttf(void) { } +void console_fb_mirror(int on) { (void)on; } +void console_set_user_prefix_provider(console_user_prefix_fn fn) { (void)fn; } +void console_set_vm_name(const char *name) { (void)name; } +uint64_t console_tx_count(void) { return 0; } + +/* ---- the sticks ---- */ +static void put_le32(uint8_t *p, uint32_t v) { p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24); } +static void put_le64(uint8_t *p, uint64_t v) { put_le32(p, (uint32_t)v); put_le32(p + 4, (uint32_t)(v >> 32)); } +/* a GPT: the identity partition at 2048..4095, and Artemis after it when `artemis` */ +static void make_stick(fake_disk *d, int artemis) +{ + uint64_t sectors = (uint64_t)d->blocks * 2u, last_usable = sectors - 34u; + uint8_t *mbr = fake_disk_sector(d, 0), *h = fake_disk_sector(d, 1), *e = fake_disk_sector(d, 2); + memset(d->bytes, 0, 34u * 512u); + mbr[446 + 4] = 0xEE; put_le32(mbr + 446 + 8, 1u); put_le32(mbr + 446 + 12, (uint32_t)(sectors - 1u)); mbr[510] = 0x55; mbr[511] = 0xAA; + memcpy(e, gpt_type_starship_identity, 16); e[16] = 1; put_le64(e + 32, 2048u); put_le64(e + 40, 4095u); + if (artemis) { memcpy(e + 128, gpt_type_starship_artemis, 16); e[128 + 16] = 2; put_le64(e + 128 + 32, 4096u); put_le64(e + 128 + 40, 14335u); } + memcpy(h, "EFI PART", 8); put_le32(h + 8, 0x00010000u); put_le32(h + 12, 92u); + put_le64(h + 24, 1u); put_le64(h + 32, sectors - 1u); put_le64(h + 40, 34u); put_le64(h + 48, last_usable); + h[56] = (uint8_t)(0x40 + artemis); put_le64(h + 72, 2u); put_le32(h + 80, 128u); put_le32(h + 84, 128u); + put_le32(h + 88, gpt_crc32(e, 128u * 128u)); put_le32(h + 16, 0u); put_le32(h + 16, gpt_crc32(h, 92u)); +} +static void plug(unsigned slot) { ms[slot].bot_msc_attach_pending = 1; sk_v4_identity_check_now(); } +static void pull(unsigned slot) { ms[slot].bot_msc_detach_pending = 1; sk_v4_identity_check_now(); } +static int zero(void) { const uint8_t *p = sk_v4_identity_seeds(); unsigned i; for (i = 0; i < 64; i++) if (p[i]) return 0; return 1; } + +int main(void) +{ + unsigned k; + memset(&xdev, 0, sizeof xdev); xdev.max_slots = SLOTS; + for (k = 1; k <= SLOTS; k++) fake_disk_open(&(blkio_dev_t){0}, &disks[k], 8192u); + make_stick(&disks[1], 1); /* the system stick */ + make_stick(&disks[2], 0); /* a real Zuse's stick: identity only */ + fake_console_reset(); + + /* 1. a blank system stick: genesis mints the proxy, and she logs in */ + sig_blank = 1; key_hers = 1; marker_rc = 0; memset(&marker_on_stick, 0, sizeof marker_on_stick); + plug(1); + CHECK(fake_console_said("Zuse: genesis minted onto attached thumbdrive") && sk_v4_identity_present(), "1. genesis: minted, logged in"); + CHECK(zero(), "#176: 1. genesis minted -- the seed buffer is zero after"); + pull(1); + + /* 2. genesis whose read-back is not hers: not minted */ + memset(&marker_on_stick, 0, sizeof marker_on_stick); key_hers = 0; fake_console_reset(); + plug(1); + CHECK(fake_console_said("Zuse: genesis mint failed -- not read back") && !sk_v4_identity_present(), "2. genesis not read back: no login"); + CHECK(zero(), "#176: 2. genesis not read back -- the seed buffer is zero after"); + pull(1); + + /* 3. the system stick with its proxy Zuse: she logs in */ + marker_on_stick.present = 1; memset(marker_on_stick.proxy, 0x11, 32); marker_on_stick.has_real = 0; + sig_blank = 0; key_hers = 1; fake_console_reset(); + plug(1); + CHECK(fake_console_said("Zuse: identity confirmed from attached thumbdrive") && sk_v4_identity_present(), "3. the proxy: logged in"); + CHECK(zero(), "#176: 3. the proxy logged in -- the seed buffer is zero after"); + pull(1); + + /* 4. a real Zuse: the system stick in (no session, she exists), then her stick */ + marker_on_stick.has_real = 1; memset(marker_on_stick.real, 0x22, 32); fake_console_reset(); + plug(1); + CHECK(!sk_v4_identity_present(), "4. the system stick, a real Zuse named: the proxy gets no session"); + CHECK(zero(), "#176: 4. the proxy's key checked, no session -- the seed buffer is zero after"); + plug(2); + CHECK(fake_console_said("Zuse: identity confirmed from attached thumbdrive") && sk_v4_identity_present(), "4. her stick: logged in"); + CHECK(zero(), "#176: 4. the real Zuse logged in -- the seed buffer is zero after"); + + return DONE(); +} diff --git a/logs/20261112-182009/kernel-tests/test_sk_v4_identity.log b/logs/20261112-182009/kernel-tests/test_sk_v4_identity.log new file mode 100644 index 00000000..2f0cf236 --- /dev/null +++ b/logs/20261112-182009/kernel-tests/test_sk_v4_identity.log @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7e1b16f0450ac6995933953d1e4888659fa53dd9233fb3a7d719213deacfe4b +size 911