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.s body-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 said reu_fetch_mul_row is
"provided by src/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-54 gates the .export off under USE_NISTCURVES_ONCHIP, and our
SHARED_REU_MUL_FETCH define 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 one sta reu_command. Its only caller,
src/crypto/fe25519.s:439, sits in the .else of the same guard and is
compiled out — fe25519.s deliberately does not even .import the symbol on
that profile. The site is live only in the REU row-fetch profile, which
packaging retired but which is still the default make, i.e. what
tools/test_x25519.py and tools/test_ecdsa_kat_oracle.py build against,
and CLAUDE.md notes it is one PACKAGE_VARIANTS line from returning.
src/http.s and src/viewer.s (4 sites) — the HTTPS_BODY_TO_REU body
sink and the viewer's page reads. http_body_sink is a runtime flag,
.res 1 defaulting to 0, written only under .ifdef HTTPS_BODY_TO_REU
(boot.s:742) or by a rig via DMA. No PACKAGE_VARIANTS row 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 that http.s:1255 already re-latches reu_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 retired EMBED_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_init stashes (boot.s:894, boot.s:919). That
is the entire reachable shipped surface, and the smallest possible change.
- Add the
http.s / viewer.s sites if we want the HTTPS_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 to PACKAGE_VARIANTS, and the dead reu_fetch_mul_row body.
- Byte budget is the constraint, as always —
ip65-onchip has 40 B free in
CRYPTO_RESIDENT and 22 B in CRYPTO_OVERLAY today, so a per-site inline
confirm is likely out and a single jsr'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.
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.
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 (
$DF00bit 6, end-of-block) plus apost-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_commandexecute sites insrc/, split 7 / 2 / 2 / 1, and zeroof them read
$DF00or observe a settle.reu_status = $df00is defined insrc/constants.incand referenced by no code path at all: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_initstashes insrc/boot.s(lines 894 and 919). That is all.
boot.skeeps thereu_mul_initcall under both profiles because a REU-quietboot makes the C64U drop the first
TCP_CONNECT. The stashed table is not readback on the onchip profiles, but the hazard §8.2 describes is about the next
REU register write, not the transferred bytes — and on
uci-combthe next REUuser 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 ourSHARED_REU_MUL_FETCHdefine suppresses the library's copy, so the symbolsimply does not exist in the three shipped links. The body survives as ~10 B
of unreferenced code containing one
sta reu_command. Its only caller,src/crypto/fe25519.s:439, sits in the.elseof the same guard and iscompiled out —
fe25519.sdeliberately does not even.importthe symbol onthat profile. The site is live only in the REU row-fetch profile, which
packaging retired but which is still the default
make, i.e. whattools/test_x25519.pyandtools/test_ecdsa_kat_oracle.pybuild against,and CLAUDE.md notes it is one
PACKAGE_VARIANTSline from returning.src/http.sandsrc/viewer.s(4 sites) — theHTTPS_BODY_TO_REUbodysink 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 setsHTTPS_BODY_TO_REU, so the code is resident but never entered in a shippedproduct. These cover the wikipedia custom build, not a release image.
Worth noting that
http.s:1255already re-latchesreu_fetch_mul_row'sregister 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 bythe retired
EMBED_P256_OVERLAY/ P-384 lanes. Not reached by shippedimages.
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 unmitigatedcontrol. 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:
reu_mul_initstashes (boot.s:894,boot.s:919). Thatis the entire reachable shipped surface, and the smallest possible change.
http.s/viewer.ssites if we want theHTTPS_BODY_TO_REUwikipedia build covered too — a custom build, not a release image.
ever returns to
PACKAGE_VARIANTS, and the deadreu_fetch_mul_rowbody.ip65-onchiphas 40 B free inCRYPTO_RESIDENTand 22 B inCRYPTO_OVERLAYtoday, so a per-site inlineconfirm is likely out and a single
jsr'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.