Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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