Skip to content

Our own 12 REU execute sites have no §8.2 completion confirm or post-execute settle #191

Closed

Description

@JC-000

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions