Repository navigation
boot: 'Q' scrubs session secrets and returns to a working BASIC - #282
Merged
Merged
Conversation
'Q' banked BASIC ROM in and RTS'd into the SYS that started the image.
BASIC then runs CHRGET ($73-$8A) off TXTPTR ($7A/$7B) and trusts CURLIN
($39/$3A) and $2B-$38, all of which the crypto uses as scratch (X25519
fe_wide $40-$7F, nistcurves fp_mul_i/j $39/$3A, TLS/ECDSA $02-$3F). After
a handshake the 6510 executed a leftover field element as CHRGET:
"?SYNTAX ERROR IN <garbage>" at the end of every rig_https_banner run.
The session keys also stayed in RAM after the quit.
Now 'Q' (boot.s quit_to_basic):
- session_scrub (new src/session_scrub.s, CODE): zeroes 10 spans of
session secrets (ECDHE privkey/shared secret, hs/app keys+IVs, nonce,
HKDF PRK/OKM, early/handshake/master secrets, traffic + finished keys,
SHA/HMAC/DRBG/ChaCha20/Poly1305/AEAD state, tls_rec_buf incl. the
whole X25519 scratch, zp_save_buf, poly_prod). Multi-buffer spans are
link-asserted to their exact size. Then re-seeds the DRBG from
SID/CIA: HKDF shares hmac_key with DRBG K, and a zeroed K/V would hand
rigs that SYS back in all-zero "random" bytes.
- zeroes ZP $02-$7F, $FB-$FF and the stack page (under SEI);
- BASIC ROM in, then the cold start's INITV ($E453), INITCZ ($E3BF),
NEW ($A644), and READY via the warm-start vector ($A002 -> $E37B).
Screen kept. Program NEWed (the image is not re-entrant from RUN);
SYS re-entry still works. uci-m3 gets the ZP/stack/BASIC half only.
Red (master, uci-onchip 45bea88a..., VICE, new suite):
CHRGET after crypto: 33 E5 E5 79 07 A4 AF E3 7F 88 8B 78 1E C9 20 ...
(ROM: E6 7A D0 02 E6 7B AD 60 EA ...), TXTTAB=$FF01, VARTAB=$0080;
after Q: "?SYNTAX ERROR IN 7518.50335"; PRINT 6*7 never answers;
ECDSA re-entry hangs (1200 s). RESULTS 8/17 passed, 9 failed, exit 1.
Green (branch, VICE): uci-onchip da8a2fe7... 56/56, ip65-onchip
7c841663... 56/56: ZP/stack/secrets zero at quit_scrubbed, DRBG
re-seeded, READY., CHRGET == ROM, TXTTAB=$0801 VARTAB=$0803,
PRINT 6*7 -> 42, X25519 + P-256 verify correct after SYS re-entry.
Hardware: U64E 10.43.23.83 fw 3.15 3a1ff9ff FPGA 126 core 1.50, 48 MHz,
rig_https_banner PASS (uci-onchip, HTTPS_HOST=en.wikipedia.org
HTTPS_PATH=/robots.txt, PRG 4d6bf67c...): HTTP 200, 28,275 B ==
Content-Length; after 'Q': READY., CHRGET == ROM, PRINT 6*7 -> 42.
Margins: LOADER -139 B on all eight measured profiles, nothing else
moves; uci-m3 MAIN -38 B. All link incl. both ip65 wiki targets.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Amends 7d42f2e per adv-quit's review. 1. "SYS re-entry still works" was overclaimed. After NEW, BASIC owned $0803-$9FFF, which is the image: A=1:B=2:C=3 overwrote $080D on the U64E and DIM X(600) zero-filled $0803-$13DC. Now, after NEW, MEMSIZ and FRETOP are set to VARTAB ($0803), so a variable, a DIM, a string concatenation or a typed program line each give ?OUT OF MEMORY and leave the image untouched. CLR/RUN/NEW rebuild from MEMSIZ, so the cap holds until a reset. Direct-mode PRINT of numbers and SYS still work. The cost: until a reset, BASIC cannot run a program after 'Q'. 2. INITV ($E453) dropped: it is unnecessary and it would unhook a cartridge's or DOS wedge's $0300 vectors. 3. New SPAN mul_dma_lo/hi (512 B): the last multiply row, i.e. one operand byte, which is X25519's on a handshake that stops before CV. 4. Test: plants poly_prod and mul_dma; drbg_buf_idx is planted at 0, so "= 32 after Q" now proves the re-seed ran; new checks for MEMSIZ=FRETOP= $0803, four ?OUT OF MEMORY cases, and $0803-$9FFF byte-identical after them. 5. session_scrub.s "not scrubbed" list now names http_req_buf, http_resp_buf (the typed path stages there) and the HTTPS_BODY_TO_REU body. 6. CLAUDE.md: the six-line rig note became a two-line Known-issues bullet. Red (master uci-onchip 45bea88a, VICE): 7/21 passed, exit 1. Green (VICE): uci-onchip ac1129f3... 63/63; ip65-onchip 668efdf1... 63/63. uci-m3 (6b3e52af...): READY., CHRGET == ROM, PRINT 6*7 -> 42. Mutants (uci-onchip), all red: M1 no poly_prod span 62/63; M2 no INITCZ 53/63; M3 no NEW 55/62; M4 no reseed 60/63; M5 no memory cap 55/62 (2,841 B of the image overwritten, then ECDSA re-entry hangs); M6 no stack zero 62/63; M7 no zp_save span 62/63; M8 no mul_dma span 61/63. Hardware: U64E 10.43.23.83, fw 3.15 3a1ff9ff, FPGA 126, core 1.50, 48 MHz, dual lock. uci-onchip, HTTPS_HOST=en.wikipedia.org HTTPS_PATH=/robots.txt, PRG 0c92fac8...: rig_https_banner PASS (HTTP 200, 28,275 B == Content-Length). After 'Q': READY., CHRGET == ROM, MEMSIZ=FRETOP=$0803, PRINT 6*7 -> 42, A=1:B=2:C=3 and DIM X(600) -> ?OUT OF MEMORY, 0 bytes of $0803-$9FFF changed. Margins vs master: LOADER -152 B on all eight profiles (ip65 384, ip65-onchip 384, uci 613, uci-onchip 613, uci-comb 220, ip65-wiki 384, ip65-onchip-wiki 384, uci-comb-demo-store 144); nothing else moves. uci-m3 MAIN -47 B (32,700 free). PRG sha256: ip65 f776fbf91bd7c4219b355edcd2f3049db4c85aa7cb550fc36c17e9a8dfc60976 ip65-onchip 668efdf12f87c256f0c504f25d50e1f753440f0ef15f755677e44815cb725013 uci d1c9a99606d33d7aa3783cba5afcacdd242d7e58774225a164fda35830c8091b uci-onchip ac1129f3423ddb29101c8ef8c3d41ca802d8d930a77f8e5f2c9772d6ea790212 uci-comb 8a30a691ba0d26e44039f0386d29d36f5b54c109f12f92de0c2ab3bc9e41bb74 ip65-wiki 2a78e58c46cca557ca674a3adfb9f5b054efe9e8676669b90406f6a0d18fd320 ip65-onchip-wiki f8de73a4b528765b9685f00a8943cb6d1076c650c721dbd2c35239244a15488f demo-store 4e586b6f6bd574421ca4afebc00d6358f58ed0e1fe2691278c3f4f2c8cf8bc2f uci-m3 6b3e52af14807a77a6e90a6ba56259c342d83a21e787f3f04e547f1dcc2e2e9a Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rebased onto fix/drbg-own-key (f0c878f), where the DRBG's K moved out of hmac_key into drbg_k: private to hmac_drbg.s, unexported, and in none of session_scrub's spans. drbg_init_entropy, the scrub's tail call, re-instantiates K = 0 / V = 1 and updates both from SID/CIA, so it is the only thing that clears drbg_k on 'Q'. - session_scrub.s: the tail call is commented as the drbg_k scrub ("NOT redundant"); the header's DRBG paragraph no longer says HKDF shares K, and hmac_key is listed as the HMAC key input. - test_quit_basic_exit.py: drbg_k (address from labels.txt) is planted before 'Q'; at quit_scrubbed it, hmac_val and hmac_key must be neither the planted pattern nor a bare instantiate value (00.. / 01..). A build with no drbg_k label fails that check. - drbg_buf_idx: tls_connect now calls drbg_reseed, but no tls_connect runs between the plant (0) and 'Q', so "= 32 at quit_scrubbed" still proves the tail call ran. Red (master uci-onchip 45bea88a, VICE): 7/21 passed, exit 1. Green (VICE): uci-onchip 7964101b... 67/67; ip65-onchip 6fe69e79... 67/67. Mutants (uci-onchip), all red: M1 no poly_prod span 66/67; M2 no INITCZ 57/67; M3 no NEW 60/66; M4 no drbg_init_entropy tail call 63/67 (drbg_k still holds the planted pattern; hmac_val/hmac_key zero; drbg_buf_idx $00); M5 no memory cap 59/66; M6 no stack zero 66/67; M7 no zp_save span 66/67; M8 no mul_dma span 65/67. pytest 416 passed, 3 failed: test_device_prep x2 (_prg_load.py, also on master) and test_ip65_hw_checks_unit (needs an ip65 build/). test_drbg_state_owner: PASSED. Margins vs f0c878f: LOADER -152 B on all eight profiles (ip65 381, ip65-onchip 381, uci 610, uci-onchip 610, uci-comb 217, ip65-wiki 381, ip65-onchip-wiki 381, uci-comb-demo-store 141); nothing else moves. uci-m3 -47 B (32,747 -> 32,700). PRG sha256: ip65 5d16e8ac03d186f34813a9eabe85c51b93cf0ceb2fb7bd93a548bffb8e4fd35e ip65-onchip 6fe69e793ba64a691f045c67f86e98b35f5996e237ddf500c7bf5c59303af6f2 uci 85b7d9eaba23b0b03bb19b44994555d3a328f93c10053f3fb6fff88743b140db uci-onchip 7964101bd3b341e0b3819a53de1e1bb599d63560a49524b27a17c5ad497a9555 uci-comb 6dd9b352fa5b86841864d2fbb74cb5ce22fdb12e54df8633d9d90829ae429c68 ip65-wiki 1e35537c465710e7a050ebe72a6ce1fc9811edcd0ba7dcebb075b13cd4f9b4be ip65-onchip-wiki 3726dd1d98cc9082dba827a7ed7eb168d932b52dfbe7908f363ce1b90da598a2 demo-store d333577472c754b9edb9310ed25112d83f522dccaa0148845103d815367975cd uci-m3 6b3e52af14807a77a6e90a6ba56259c342d83a21e787f3f04e547f1dcc2e2e9a Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…SIC allocates adv-quit's re-attack (BLOCK) on e851c38/9f27f49's cap: with MEMSIZ = FRETOP = $0803 every quoted string in direct mode is ?OUT OF MEMORY -- LOAD"P",8, PRINT "HELLO", OPEN with a name -- so nothing could be loaded after 'Q' without a reset. And it did not hold: OPEN 2,2 (RS-232) copies KERNAL MEMTOP to MEMSIZ via $E0F9, after which DIM overwrote 2,819 B of the image. The cap is removed; NEW stays. After 'Q' BASIC owns $0801-$9FFF, which is the image. SYS re-entry into it is valid only before BASIC allocates anything (variables, strings, LOAD), and a re-entry that runs crypto clobbers BASIC's ZP again exactly as before 'Q'. The tools/uci rigs only type SYS after 'Q', so they are fine. boot.s and CLAUDE.md say exactly that. Test (test_quit_basic_exit.py): - OOM/cap checks dropped; MEMSIZ = $A000 checked instead. - [5] SYS-only re-entry: a typed SYS to a page-3 stub runs and leaves $0803-$9FFF byte-identical; X25519 + P-256 re-entry still correct. - [6] on a second, fresh boot (step 5's crypto re-clobbers BASIC's ZP): 'Q', then PRINT "AB"+"CD" -> ABCD, and LOAD"P",8 of "10 A=1:PRINT A" from a d64 minted with c1541 loads, and RUN prints a line "1". - Every post-'Q' step is tallied, never raised: a jammed 6510 (M3) now fails the remaining checks by name. - drbg_k wording: the check proves K was replaced by a one-way update at quit, not specifically by instantiate. Red (master uci-onchip 45bea88a, VICE): 10/22 passed, exit 1 (incl. PRINT "AB"+"CD", and LOAD -> ?OUT OF MEMORY: master leaves VARTAB past MEMSIZ). Green (VICE): uci-onchip 192157cb... 68/68; ip65-onchip 78a10748... 68/68. Mutants (uci-onchip), all red, all by tally: M1 no poly_prod span 67/68; M2 no INITCZ 59/68; M3 no NEW 66/68; M4 no drbg_init_entropy tail call 64/68; M6 no stack zero 67/68; M7 no zp_save span 67/68; M8 no mul_dma span 66/68. M5/M10/M11 (the cap) retired. test_drbg_state_owner and test_runner_coverage PASS; pytest 417 passed, 3 failed (test_device_prep x2, also on master; test_ip65_hw_checks_unit needs an ip65 build/). Hardware: U64E 10.43.23.83, fw 3.15 3a1ff9ff, FPGA 126, core 1.50, 48 MHz, dual lock. uci-onchip, HTTPS_HOST=en.wikipedia.org HTTPS_PATH=/robots.txt, PRG 4354278dd05d7269ba1e2360cecf69434bce7b5b4f5a2d92841dbb6fcdbb97b7: rig_https_banner PASS (HTTP 200, 28,275 B == Content-Length). After 'Q': READY. with no error, CHRGET == ROM, MEMSIZ=$A000; PRINT 6*7 -> 42; PRINT "AB"+"CD" -> ABCD; the same d64 mounted read-only on drive a (empty before, removed after): LOAD"P",8 -> SEARCHING/LOADING/READY., RUN -> 1. Margins vs f49d8ab: LOADER -140 B on all eight profiles (ip65 393, ip65-onchip 393, uci 622, uci-onchip 622, uci-comb 229, ip65-wiki 393, ip65-onchip-wiki 393, uci-comb-demo-store 153); nothing else moves. uci-m3 MAIN -35 B (32,712 free). PRG sha256: ip65 0a0b7a78660c298b510edac564a4c6757f89aaafd46369a7faf5f1c107a53c17 ip65-onchip 78a10748f42553417f5e8a5bfafab9cbc9e70a10fa2821d99dd2680c09753546 uci 3edcb6a61845cf53844136639781d721b37bcd463b15887720936eadd38b348a uci-onchip 192157cb6e326a4badfd49492f6ef83df1a9be2bdd1f7d5da3fa77f02cdb4c7e uci-comb 30cca7820ac528a7ab372dd78e5049ebd0ab65dbfc15879c4db08edb55c2475c ip65-wiki 7ca7eba5d946b2908b29d42014055e380572b6cd063f273bf725d63519206198 ip65-onchip-wiki a24d051552d5e395700c7dd0dd18eae43d28d02a9a5dd7555fe3e9eb18459638 demo-store d0c32ffcdf1347c4adc0fd0eb79f879d6c85cd90110c5a1fb544a41f6ff4e171 uci-m3 d2f0201c4e781194ee71679c2bf26abb7348682f2d26471d1e57622b999f6ba3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- boot.s / CLAUDE.md: after a crypto SYS re-entry returns BASIC is unusable; rigs park the CPU (JMP *) or type only further SYS lines. - boot.s: the "capping MEMSIZ was tried" story moves to docs/engineering-notes.md (Known issues); boot.s keeps a one-line "do not cap MEMSIZ" guard. - test_quit_basic_exit.py docstring: a missing c1541 is two counted FAILs by design (skip policy). PRG sha256 unchanged: uci-onchip 192157cb6e326a4badfd49492f6ef83df1a9be2bdd1f7d5da3fa77f02cdb4c7e ip65-onchip 78a10748f42553417f5e8a5bfafab9cbc9e70a10fa2821d99dd2680c09753546 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge after #279. This PR's base is
fix/drbg-own-key, so it shows only its own commits.Defect
'Q' banked BASIC ROM back in and did a bare RTS into the SYS that started the image. BASIC then runs CHRGET ($73-$8A) off TXTPTR ($7A/$7B) and trusts CURLIN ($39/$3A) and its pointers ($2B-$38). The crypto uses all of that zero page as scratch:
fe_wideat $40-$7Ffp_mul_i/fp_mul_jat $39/$3AAfter a handshake the 6510 executed a leftover field element as CHRGET and BASIC printed
?SYNTAX ERROR IN <garbage>;rig_https_bannerended every run that way. The session's keys also stayed in RAM after the quit.Fix (
boot.squit_to_basic,src/session_scrub.s)tls_rec_buf, which includes the X25519 scalarzp_save_buf,poly_prod,mul_dmadrbg_k, and it keeps rigs that SYS back in from drawing zeros.After 'Q', BASIC owns $0801-$9FFF, which is the image. SYS re-entry is valid only before BASIC allocates anything; the rigs only type SYS. uci-m3 gets the ZP/stack/BASIC half only.
Evidence
tools/test_quit_basic_exit.py(VICE, 68 checks):quit_scrubbed: ZP, stack and secrets are zero, and the DRBG state (includingdrbg_k) was replaced.PRINT 6*7gives 42.PRINT "AB"+"CD"andLOAD"P",8from a d64 followed by RUN.45bea88a…)?SYNTAX ERROR IN 7518.50335, CHRGET/TXTTAB clobbered, LOAD gives ?OUT OF MEMORY192157cb…)78a10748…)Mutants (uci-onchip), each red:
Hardware: U64E, fw 3.15 3a1ff9ff, FPGA 126, core 1.50, 48 MHz. Build: uci-onchip with
HTTPS_HOST=en.wikipedia.org HTTPS_PATH=/robots.txt, PRG4354278dd05d7269ba1e2360cecf69434bce7b5b4f5a2d92841dbb6fcdbb97b7.rig_https_bannerPASS: HTTP 200, 28,275 B equals Content-Length.READY.with no errorPRINT 6*7gives 42PRINT "AB"+"CD"gives ABCDLOAD"P",8thenRUNprints 1Size
LOADER is down 140 B on all eight measured profiles; the tightest, uci-comb-demo-store, has 153 B free. Nothing else moves. uci-m3 MAIN is down 35 B.
PRG sha256 at 3233821:
0a0b7a78…78a10748…3edcb6a6…192157cb…30cca782…d2f0201c…Stale commit messages
docs/engineering-notes.md, under Known issues.🤖 Generated with Claude Code