Repository navigation
Description
Found while assessing the sibling pins for #166. Filing it because it is the
part of the §8.2 story that no upstream bump can fix for us: we issue REU DMA
from our own code, and none of it is fenced.
Revised 2026-09-05 after adversarial review. The first version said the
http.s/viewer.sbody-sink sites "run on the wikipedia path at 48 MHz"
and folded them into the shipped surface. They are not reachable in any
shipped product — corrected below. It also saidreu_fetch_mul_rowis
"provided bysrc/boot.s:945", which is true only of the REU-profile default
build; in all three shipped maps the symbol does not exist. Both fixed.
The clause
c64-lib-contract §8.2 (upstream c64-lib-contract#144): on a turbo-clocked
Ultimate 64 at firmware 3.15, the REU's post-transfer restore outlasts the CPU,
so the REU register write that follows an execute can be lost or misapplied.
The mitigation is a DMA-completion confirm ($DF00 bit 6, end-of-block) plus a
post-execute settle before the next REU register access.
Both sibling libraries adopted it — c64-x25519 v0.12.0, c64-nist-curves v0.12.0
(we are still pinned below the latter; #190).
What we do
12 sta reu_command execute sites in src/, split 7 / 2 / 2 / 1, and zero
of them read $DF00 or observe a settle. reu_status = $df00 is defined in
src/constants.inc and referenced by no code path at all:
$ grep -rl "sta reu_command" src/
src/boot.s (7)
src/http.s (2)
src/viewer.s (2)
src/crypto/shared/crypto_swap.s (1)
Scope — what is actually reachable in a shipped product
This is much narrower than "12 sites", and the narrowing is the useful part.
Reachable in shipped images: the two reu_mul_init stashes in src/boot.s
(lines 894 and 919). That is all.
boot.s keeps the reu_mul_init call under both profiles because a REU-quiet
boot makes the C64U drop the first TCP_CONNECT. The stashed table is not read
back on the onchip profiles, but the hazard §8.2 describes is about the next
REU register write, not the transferred bytes — and on uci-comb the next REU
user is the comb boot precompute.
Everything else is compiled in but never entered:
reu_fetch_mul_row(boot.s:945) — not present in any shipped map.
boot.s:52-54gates the.exportoff underUSE_NISTCURVES_ONCHIP, and our
SHARED_REU_MUL_FETCHdefine suppresses the library's copy, so the symbol
simply does not exist in the three shipped links. The body survives as ~10 B
of unreferenced code containing onesta reu_command. Its only caller,
src/crypto/fe25519.s:439, sits in the.elseof the same guard and is
compiled out —fe25519.sdeliberately does not even.importthe symbol on
that profile. The site is live only in the REU row-fetch profile, which
packaging retired but which is still the defaultmake, i.e. what
tools/test_x25519.pyandtools/test_ecdsa_kat_oracle.pybuild against,
and CLAUDE.md notes it is onePACKAGE_VARIANTSline from returning.src/http.sandsrc/viewer.s(4 sites) — theHTTPS_BODY_TO_REUbody
sink and the viewer's page reads.http_body_sinkis a runtime flag,
.res 1defaulting to 0, written only under.ifdef HTTPS_BODY_TO_REU
(boot.s:742) or by a rig via DMA. NoPACKAGE_VARIANTSrow sets
HTTPS_BODY_TO_REU, so the code is resident but never entered in a shipped
product. These cover the wikipedia custom build, not a release image.
Worth noting thathttp.s:1255already re-latchesreu_fetch_mul_row's
register state after the sink clobbers it — precisely the "next REU register
write after an execute" shape the clause is about.src/crypto/shared/crypto_swap.s(1 site) — the overlay swap, driven by
the retiredEMBED_P256_OVERLAY/ P-384 lanes. Not reached by shipped
images.
Bounding it
Same caveat as #166 and #190: the §8.2 hardware row has since been measured on
our U64E (601A96) at core 1.4F and passes 22/22, including an unmitigated
control. The recorded failure was core 1.4E. So this is very likely not
observable on the device we run today, and a green run here would not
discriminate. The oracle that would settle it is in #190: the §8.2 probe on a
core 1.4E U64E or a real 17xx REU, mitigated against unmitigated.
It remains in scope for 1.4E-era devices, real 17xx REUs and other REU
implementations.
Priority: low. It is a correctness gap with no error path, and it is
currently invisible — the sibling advisories point at the libraries, and we had
nothing on file saying our own source has the same shape.
Suggested action
Not proposing a fix in this issue. Options, in rough cost order:
- Fence the two
reu_mul_initstashes (boot.s:894,boot.s:919). That
is the entire reachable shipped surface, and the smallest possible change. - Add the
http.s/viewer.ssites if we want theHTTPS_BODY_TO_REU
wikipedia build covered too — a custom build, not a release image. - Do all 12 for uniformity, which also covers the REU row-fetch profile if it
ever returns toPACKAGE_VARIANTS, and the deadreu_fetch_mul_rowbody. - Byte budget is the constraint, as always —
ip65-onchiphas 40 B free in
CRYPTO_RESIDENTand 22 B inCRYPTO_OVERLAYtoday, so a per-site inline
confirm is likely out and a singlejsr'd helper is the shape that fits.
Option 1 needs only one call site pair, so it is the cheapest by a wide
margin.
Cross-refs: #166 (x25519 pin, closed by PR #189), #190 (nistcurves — same
clause, shipped code, blocked on an APP_OWNED symbol collision), upstream
c64-lib-contract#144.
Activity
JC-000 commentedon Sep 5, 2026
Cross-reference: "the nistcurves pin issue" referenced above is #190. The x25519 half is #166, closed by PR #189.
reu: long confirm bound where a REU is required; guard the settle (#191)
fix(reu): confirm + settle every REU execute through one helper (#191)
reu: long confirm bound where a REU is required; guard the settle (#191)
fix(reu): confirm + settle every REU execute through one helper (#191)
reu: long confirm bound where a REU is required; guard the settle (#191)
fix(reu): confirm + settle every REU execute through one helper (#191)
reu: long confirm bound where a REU is required; guard the settle (#191)
Merge pull request #250 from JC-000/feat/ip65-bytes-204-191-235