Repository navigation
http: bound the REU body sink to its region; cold-bank asserts and stash checks (adv-l5 on #266) - #273
Merged
Conversation
http_sink_blit STASHed each bounce to http_reu_body_base + cursor with no bound, and REU addresses wrap. On a 512 KB / 1 MB REU the default base $10:0000 is bank 0; on 16 MB the carry out of the bank byte was dropped. A server's body then reached the multiply rows (banks 0-1), comb's Lim-Lee table and the cold-code bank (bank 2): adv-l5's PoC runs server code at the next typed-target prompt, with the cold bank's marker and 8-bit XOR forged. The XOR is not authentication; bounding the writes is the fix. - reu_probe_size (boot, UCI, before anything else writes the REU) finds the REU's size by write/read-back/alias at bank offset 0; no REU or an expired DMA confirm leaves it unknown. - sink_room_init (per body): the region is [base, end), end = the probed size, or bank 6 below the P-384 banks; a base in banks 0-2 or 6-7, past the end, or with no size gets an empty region. - sink_fits: a blit that would cross the end (or wrap 24 bits) writes nothing and latches http_sink_full; every later blit is refused. http_recv_body stops at once and returns C=1; the timeout verdict says C=1; the menu prints BODY TOO BIG FOR THE REU instead of the viewer. - Assemble-time asserts: HTTP_REU_BODY_BASE outside banks 0-2 and 6-7; the cold bank and the reserved overlay slots inside them. - tools/test_reu_body_sink.py (6502 interpreter, 512 KB/1 MB/16 MB/no REU, adv-l5's attack); test_http gains a VICE over-region vector; the banner rig's body oracle reads http_sink_full. - uci-m3: HTTP_SINK_GUARD gets a home in its cfg, and the M3 receive loop stops on a refusal and is C=1 at completion when one happened. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…small REU Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Folding it into the one-DMA span widened the span to 20 KB on comb ($6113-$AF50) and tripped the span assert AFTER 'G', with a live socket (measured on the U64E, PRG 75be0262). The span is master's again; the optional symbol is resolved before any socket opens and read separately. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dv-l5 LOW 2) The old asserts compared COLD_REU with a literal floor, so they could not fail. They now import the code-read symbols reu_config.o exports (LIB_NISTCURVES_REU_BANK_COMB, _OFFSET_COMB_P256/_P384, _BANK_MUL) and the body sink's SINK_FLOOR_BANK, and check the images, both Lim-Lee tables and the multiply rows are disjoint and below the sink's banks. Red-green: rebuilding the comb archive with LIB_NISTCURVES_REU_OFFSET_COMB_P384=0xA000 (the P-384 table onto the cold home) links silently with the old asserts and fails this link with 'cold bank: images overlap the linked library's P-384 Lim-Lee table'. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(a) cold_call never read cold_stashed. With no stash this LOAD, cold_g_sum is 0 and stale or planted REU bytes carrying the marker and an XOR of 0 ran: case_no_stash_refuses ran a planted trust_store_load before this commit ($0334=$42) and is refused (cold_err FETCH) after it. (b) cold_bank_init checked only the UI group's marker, so a LOAD that stopped short of the TRUST group was stashed and summed: case_truncated_group_is_not_stashed was red; every group's marker is now checked at its load address first. The re-run case now forges every marker, so the once-per-LOAD rule stays separately tested. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two sizes: a 2,000 B body refused at its first blit must stop the loop mid-stream; a 100 B body refused at the final flush must not read as complete. Two mutants (stop removed; completion back to CLC), both killed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… (adv-273 F1)
A refusal can first happen in http_recv_{close,timeout}_verdict's own
final flush: every full bounce fit, the last partial one crosses the
region's end. Moving the http_sink_full check above php / jsr
http_body_finish stayed green everywhere. Now pinned twice:
- test_reu_body_sink case_verdict_flush (comb TS demo image): both
verdicts, unframed close and a satisfied Content-Length. Red on the
mutant (fd62f9f0: C=0 twice), green on 393360be.
- check_m3_client: the unframed + 01 variant; mutate_m3_client gains
sink-check-before-flush, killed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With HTTPS_BODY_TO_REU, m3_https_get.inc entered the viewer whatever http_sink_full said, so a refused body showed REU bytes it never wrote. It now prints BODY TOO BIG FOR THE REU and closes, as boot.s's TLS arm. check_m3_client gains VIEWER_TESTS, run on a BODY_TO_REU image (and named as not run otherwise): the menu path with a refused 100 B body. Red without the fix (b75fd5fb: do_https_get never returns, the viewer waits for keys); green with it (9e6ab506). The model attaches a response to the next Open (M3Device.on_write) so a menu fetch can carry a body. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…3 F3) reu_probe_size ran on every uci-m3 boot; with the REU disabled it spins ~3 s at 1 MHz, and only the REU body sink uses its result. A plain uci-m3 boot does no REU DMA again, as on master; every 6510-TLS UCI image and uci-m3 + HTTPS_BODY_TO_REU still probe. check_m3_client test_boot_reu_dma_only_for_the_sink runs `start` to the menu and counts REU register writes: 0 on plain uci-m3 (master b124ea9f 0, 3bf57344 27, now 0a826f7e 0), > 0 on a BODY_TO_REU image. Mutant boot-probe-ungated killed. (It also caught a .not/.or precedence slip in the first draft of the gate, which dropped the probe from BODY_TO_REU.) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ewer (adv-273 nits) viewer_enter is also linked by KEEP_VIEWER=1 and VIEWER_TEST_HELPERS=1, which do not stream the body to the REU: on a KEEP_VIEWER image the boot test demanded a probe and the viewer test ran, 49/51 (adv-273). The image's kind now comes off -D HTTPS_BODY_TO_REU in the flags.stamp beside its labels (KEEP_VIEWER: 50/50, the viewer test named as not run). mutate_m3_client refuses a BODY_TO_REU image (boot-probe-ungated is equivalent there) and copies the stamp into its scratch build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000
force-pushed
the
fix/reu-body-sink-bound
branch
from
October 6, 2026 02:34
a861278 to
56e29fd
Compare
JC-000
added a commit
that referenced
this pull request
Oct 6, 2026
…K_CODE) 1. MEDIUM-HIGH. The hook stored tp_bwarn from A after a failed tp_cmp_got, and A is then the bundle pin's byte where the hashes first differ (from byte 31 down). A $00 there left tp_bwarn clear: BUNDLE MISMATCH printed, RECORD KEY? was never asked, and trust_post recorded the server's key as TOFU -- free when the pin's byte 31 is $00, ~2^8 / 2^16 grinds for byte 30 / 29. Now `lda #1`. Regression: pins with byte 31 = $00, and byte 30 = $00 with 31 equal (red on 04af8b9: recorded without A); mutant. Audit: tp_bwarn was the only flag stored from an incidental A; the other stores are explicit, or clear (the safe way). 2. LOW. TRUST_POLICY_BSS is not boot-zeroed, so an accept armed before 'Q' survived RUN (or a reset and SYS). start now calls trust_state_init, which clears it and the bundle cache. Test + mutant (drop the call). 3. LOW, comb. A cold-bank refusal of trust_post (socket ERROR, latched DMA timeout) skipped its clears, keeping tp_ovr_armed and tp_mode. The resident stubs now clear both whenever the bank itself refuses. Red on 04af8b9 (armed=1 mode=1 survived); mutant. Under TRUST_STORE the hook's code is TRUST_HOOK_CODE, not CERT_PIN_CODE: the comb cfgs place it in NET_CODE (CRYPTO_HOT has no room for it beside #273's body-sink bound), the uci cfg where CERT_PIN_CODE already was. The plain pin keeps CERT_PIN_CODE, so non-trust pinned builds do not move. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 6, 2026
JC-000
added a commit
that referenced
this pull request
Oct 6, 2026
…K_CODE) 1. MEDIUM-HIGH. The hook stored tp_bwarn from A after a failed tp_cmp_got, and A is then the bundle pin's byte where the hashes first differ (from byte 31 down). A $00 there left tp_bwarn clear: BUNDLE MISMATCH printed, RECORD KEY? was never asked, and trust_post recorded the server's key as TOFU -- free when the pin's byte 31 is $00, ~2^8 / 2^16 grinds for byte 30 / 29. Now `lda #1`. Regression: pins with byte 31 = $00, and byte 30 = $00 with 31 equal (red on 04af8b9: recorded without A); mutant. Audit: tp_bwarn was the only flag stored from an incidental A; the other stores are explicit, or clear (the safe way). 2. LOW. TRUST_POLICY_BSS is not boot-zeroed, so an accept armed before 'Q' survived RUN (or a reset and SYS). start now calls trust_state_init, which clears it and the bundle cache. Test + mutant (drop the call). 3. LOW, comb. A cold-bank refusal of trust_post (socket ERROR, latched DMA timeout) skipped its clears, keeping tp_ovr_armed and tp_mode. The resident stubs now clear both whenever the bank itself refuses. Red on 04af8b9 (armed=1 mode=1 survived); mutant. Under TRUST_STORE the hook's code is TRUST_HOOK_CODE, not CERT_PIN_CODE: the comb cfgs place it in NET_CODE (CRYPTO_HOT has no room for it beside #273's body-sink bound), the uci cfg where CERT_PIN_CODE already was. The plain pin keeps CERT_PIN_CODE, so non-trust pinned builds do not move. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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-#282. This is based on
249f65d; if it conflicts after those merge, it gets one rebase.Follow-up to #266, fixing adv-l5's HIGH finding and two LOWs.
HIGH: the REU body sink wrote outside its region
http_sink_blitwrote each 512 B bounce tohttp_reu_body_base + cursorwith no length bound, and REU addresses wrap.$10:0000aliases bank 0.Consequence. A server's body reaches the multiply rows (banks 0-1), comb's Lim-Lee table and the cold-code bank (bank 2).
541a0613at 512 KB and 16 MB.Older exposure on the Lim-Lee table (before #266). Body offsets that land on bank 2
$0000-$3FFF:No attacker is needed. On master, the 751 KB Wikipedia demo body streamed on a 512 KB or 1 MB REU overwrites all 16,384 B of the Lim-Lee table and the cold image. Measured with master's own code in the interpreter.
Shipped images. No release (v0.2.0-v0.4.3) packaged
HTTPS_BODY_TO_REU(supervisor checkedtools/package/_common.shat each tag). Only demo and REU-body builds are exposed.Fix (
0cfda62)reu_probe_sizeruns at boot on UCI, before anything else writes the REU.sink_room_initsets each body's region to[base, end).endis the probed size, or bank 6 when the base is below the P-384 banks.sink_fits: a blit that would crossend, or wrap 24 bits, writes nothing and latcheshttp_sink_full. Every later blit is refused.http_recv_bodystops at once and returns C=1;BODY TOO BIG FOR THE REUinstead of entering the viewer.HTTP_REU_BODY_BASElies outside banks 0-2 and 6-7, and the reserved overlay slots and the cold bank lie inside them.HTTP_SINK_GUARDsegment in CRYPTO_HOT (279 B). LOADER and the overlay have no room for it.http_sink_full. It is an optional symbol, so a refused body cannot read as complete.LOW 2: the REU-home asserts could never fail (
883bd28)The asserts now import the library's code-read exports and
SINK_FLOOR_BANK:LIB_NISTCURVES_REU_BANK_COMB;_OFFSET_COMB_P256/_OFFSET_COMB_P384;_BANK_MUL.They check that the cold images, both Lim-Lee tables and the multiply rows are disjoint and below the sink's banks.
Red-green. I rebuilt the comb archive with
LIB_NISTCURVES_REU_OFFSET_COMB_P384=0xA000, which puts the table on the cold home.ld65: Error: cold bank: images overlap the linked library's P-384 Lim-Lee table.LOW 3: stash checks (
cbd804e)cold_callrefuses whilecold_stashed= 0. Before the fix, a planted REU image with the marker and XOR 0 ran with no stash ($0334=$42).cold_bank_initchecks every group's marker at its load address before stashing. Before the fix, a TRUST group whose tail never arrived was stashed and summed.Rig fix (
5d90e64)My first hardware run of
0cfda62crashed the banner rig AFTER 'G'.http_sink_full(in CRYPTO_HOT) into the rig's one-DMA span widened it to 20 KB and tripped the span assert.dhcp ok, so no lease poisoning was seen.Red-green
tools/test_reu_body_sink.py(new): 70/70. It runs on the 6502 interpreter with a modelled REU at 512 KB, 1 MB and 16 MB, and with no REU:test_cold_bank.py: 116/116. The two new LOW-3 cases are red before the fix; mutants L3a and L3b are killed; the original 8 mutants are still killed.test_http(VICE, uci): 72/72. New vector: the probe sees 8 banks, and a 1,500 B body against a 1 KB region writes the two bounces that fit, refuses the rest, and leaves bank 6 untouched.test_aead_fail_closed(uci, sink-abort case): pass;test_viewer7/7;test_trust_store_6502(comb) 128/128;test_prg_load11/11;mutate_http_body_checks90/90._prg_load.pywording. I reworded its new comment sotest_device_prep's rig discovery no longer selects the helper. comb: cold-code bank — no-connection code runs from the REU via cert_buf (#155 phase 2, L5) #266 had made that suite red.test_ip65_hw_checks_unitneeds an ip65 build inbuild/; it is build-state only.test_pytest_boundaryflagstools/test_rig_scratch.py:555, which arrived with rigs: low-RAM scratch for every rig that still carved a linked tail (#209) #272; this branch does not touch it.uci-m3 (folded in on the rebase onto 249f65d)
HTTP_SINK_GUARDhad no home incfg/c64-https-uci-m3.cfg, so the uci-m3 link failed. It now has one.http_recv_bodystops as soon as the sink refuses a write. At completion it returns C=1 if a refusal happened.check_m3_client.pytest_http_sink_refusal_stops(new). A 2,000 B body refused at its first blit must stop mid-stream. A 100 B body refused at the final flush must not read as complete.read on to the framing's end (2000 B)anda body the sink refused was reported complete.mutate_m3_client.py: 2 new mutants. 60/60 killed.http_recv_timeout_verdict: master now carries C throughphp/plp. The sink check sits after theplpand is anlda/bnethat leaves C alone.Suites at
074e422test_reu_body_sinktest_cold_banktest_trust_store_6502(default and comb)test_viewertest_prg_loadtest_http_body_checks_unitmutate_http_body_checkstest_http(VICE, uci)test_aead_fail_closed(uci)check_m3_clientmutate_m3_clientThe one root-pytest failure is
test_ip65_hw_checks_unit, which needs an ip65 build inbuild/. That is build state only.Hashes and margins (
tools/measure_margins.py --build, side by side with master249f65d)5f261d3c,69eeea39,93353af1,c9aa6a4e.34065085…90096a4e…b68679cb…393360be…0a826f7e…(masterb124ea9f…)HTTPS_BODY_TO_REU9e6ab506…adv-273 round 2 (
f5df8b8,a281ac0,d076a9a,56e29fd)f5df8b8, tests only). A refusal can first happen in a verdict's own final flush: every full bounce fits, and the last partial one crosses the region end.test_reu_body_sinkcase_verdict_flush, covering both verdicts.fd62f9f0), C=0 ×2. Green: 76/76 on393360be.check_m3_clientgets the unframed + 01 variant. Themutate_m3_clientmutantsink-check-before-flushis killed.a281ac0). uci-m3 withHTTPS_BODY_TO_REUentered the viewer even when the sink had refused the body.BODY TOO BIG FOR THE REUand closes, likeboot.s.VIEWER_TESTScase incheck_m3_client: the menu path with a refused 100 B body.b75fd5fb):do_https_getnever returns, because the viewer waits for keys. Green: 51/51 (9e6ab506).mutate_m3_client: the hook is assembled only on aBODY_TO_REUimage. adv-273's manual mutant is the evidence.d076a9a).reu_probe_sizeran on every uci-m3 boot; with the REU disabled that is about 3 s at 1 MHz.It now runs only on 6510-TLS UCI builds and on uci-m3 +
HTTPS_BODY_TO_REU.New
test_boot_reu_dma_only_for_the_sinkrunsstartto the menu in the model and counts REU register writes:b124ea9f3bf573440a826f7eBODY_TO_REUimageMutant
boot-probe-ungatedis killed.56e29fd).check_m3_clientnow tells aBODY_TO_REUimage by-D HTTPS_BODY_TO_REUin itsflags.stamp, not by the presence ofviewer_enter.KEEP_VIEWER=1image (c5c70a02) was 49/51 and is now 50/50.mutate_m3_clientrefuses aBODY_TO_REUimage, whereboot-probe-ungatedis equivalent.074e422, so the suites and the hardware run below still apply.check_m3_client: plain 50/50,BODY_TO_REU51/51,KEEP_VIEWER50/50.mutate_m3_client: 62/62.Hardware
3a1ff9ff, FPGA 126, core 1.50. Read through the harnessget_infounder DeviceLock.prepare_devicethenpreflight_reu.TRUST_STORE=1,rig_https_banner, RETURN ×2393360be044dbe92…🤖 Generated with Claude Code