Repository navigation
uci: union P-256 scratch with cert_buf (+1,312 B), with lifetime proof (L2+L3) - #292
Merged
Merged
Conversation
ip65 has time-shared cert_buf with the library's 1,312 B of verify scratch since #68 (SCRATCH_UNION). UCI kept the scratch in NET_BSS_TAIL, leaving that region 63 B free on every profile. The lifetime rule is the same on both backends (src/der_decode.s): cert_buf is dead once x509_extract_pubkey returns -- the key copy, the SAN check and the SPKI pin / trust hook all run inside it -- and ecdsa_verify runs only after. Every UCI cfg (uci, uci-onchip = c64-https-uci.cfg; comb = -onchip.cfg and its cold twin) now pins CERT_BUF_BSS at the head of CRYPTO_OVERLAY ($4200) and routes LIB_NISTCURVES_P256_BSS to a P256_SCRATCH_UNION area on top of it. The area is CERT_BUF_SIZE ($0800) long, so P-256 BSS growth past cert_buf is an ld65 memory-area overflow, and src/der_decode.s asserts (lderror) that the area starts at cert_buf and is no larger than it. The one consumer that breaks the rule: on comb the cold bank runs trust_bundle FROM cert_buf, and trust_bundle calls ecdsa_verify, whose scratch would overwrite the running code. comb + TRUST_BUNDLE=1 is now a Makefile $(error), with an lderror backstop in trust_bundle.s that fails the link if the bundle's code ever runs inside the union. TRUST_BUNDLE stays available on uci / uci-onchip, where it is resident and reads the TCP ring. test_trust_policy_6502.py drops its comb-bundle image (its verify is a Python stub, so it could not see the overwrite). test_cold_bank.py's cfg delta loses the cert_buf pin, which both comb cfgs now carry. NET_BSS_TAIL tail free, every UCI profile: 63 -> 1,375 B (+1,312); demo 43 -> 1,355. No other region moves (tools/measure_margins.py --build). ip65 PRGs byte-identical. Red-green: P256_SCRATCH_UNION start $4300 -> lderror "must start at cert_buf"; size $0900 -> lderror "larger than cert_buf"; size $0500 -> "overflows memory area 'P256_SCRATCH_UNION' by 32 bytes"; the Makefile guard removed, comb + TRUST_BUNDLE=1 -> lderror "trust_bundle runs inside P256_SCRATCH_UNION". All restored: green. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…3k]-[3n])
P256_SCRATCH_UNION (previous commit) puts the library's verify scratch on
cert_buf. It is safe only while nothing reads cert_buf once the scratch
is live. This group drives the image's own path, not a staged struct:
leaf into cert_buf -> tls_cert_stream_finish (key copy + SAN check) ->
tls_handle_cert_verify (CertificateVerify hash + ecdsa_verify), on any
UCI build that streams the Certificate:
[3k] the P-256 scratch symbols lie inside cert_buf (union linked)
[3l] the leaf is accepted, key extracted
[3m] the server signature verifies AND the verify overwrote the leaf
(else the overlap was not exercised)
[3n] a signature over another transcript is refused
ip65 runs no extra checks (its union is asserted in src/net/ip65/exports.s).
Mutation (onchip, C64_VICE_NO_REU=1), mutant M1 = defer the SAN check
from Certificate time to after the signature (a cert_buf read with the
scratch live):
union layout, unmutated 4298aecb... [3m] PASS C=0, 157/160 overwritten
union layout, M1 683faac8... [3m] FAIL C=1
master layout, M1 6a1fc605... [3m] C=0, 0/160 overwritten
So the red is the union's, not the mutant's. .o files and the PRG were
unlinked between builds, and the three hashes are distinct.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…oducts
Review finding (real defect): [3l]-[3n] failed on BOTH shipped products.
Under TRUST_STORE, x509_extract_pubkey tail-calls the trust hook
(cert_pin_check, src/tls_cert.s), which refuses while tp_mode is still
TP_M_NONE ("trust_pre never ran"). The earlier green run was on 4298aecb,
a plain uci-onchip image that ships nowhere.
- set tp_mode = TP_M_FIRST (read from src/trust_policy.inc) when the
label exists, as a first-use trust_pre would, before the extraction;
- seed ALL 2,048 B of cert_buf with junk before staging the leaf, and
re-seed it after the leaf is consumed and before each verify: every
verify now runs over arbitrary bytes in its whole scratch span, which
is the lifetime claim, not just over a 160 B leaf;
- say in the docstring what this does NOT cover: the deframer, a real
flight's interleaving, trust_pre/trust_post and the cold bank's calls,
and everything after CertificateVerify (Finished, keys, the GET).
Green (union group, VICE):
uci-onchip-store 6dcec600... (shipped flags, C64_VICE_NO_REU=1)
4/4, [3m] C=0, 1,240 of 2,048 cert_buf bytes overwritten
uci-comb-store 3726693a... (shipped flags, REU)
4/4, [3m] C=0, 1,242 of 2,048 overwritten
Red:
tp_mode write removed, comb-store 3726693a...: [3l] FAIL C=1 -> (1, 3),
the reviewer's defect reproduced
mutant M1 (SAN check after the signature), uci-onchip 683faac8...:
[3m] FAIL C=1 -> (3, 1); still red under the junk seeding
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CORRECTION to the docstring and to cea8459's commit message, which said mutant M1 (the SAN check moved to after the signature) "turns [3m] red on the union layout and leaves it green without it". That held for e0aab4c's test; it is false since cea8459 re-seeds all of cert_buf with junk before each verify. The deferred name check then reads junk on ANY layout, so M1 is red on master's layout too. Measured (uci-onchip, C64_VICE_NO_REU=1): master layout + M1, this test 6a1fc605... [3k] FAIL, [3m] FAIL C=1 with 0 of 2048 overwritten So M1's red shows only that a cert_buf read after the verify is caught. What shows the union is exercised is [3k] (the scratch symbols lie inside cert_buf) and [3m]'s `changed > 0` (the verify really wrote over cert_buf): on master's layout [3k] fails and [3m] reports 0 bytes overwritten. The docstring now says so. Text only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000
added a commit
that referenced
this pull request
Oct 12, 2026
The capture predated #292's P256_SCRATCH_UNION and #290's sha256 roll, so its five PRG hashes and labels were the pre-rebase links. Regenerated with test_rig_scratch.py --build --write-fixtures; --build re-run: matrix PASS, 17/17, hashes uci ed6871b2, uci-onchip eaa43a6a, uci-comb 8f8a0d05, uci-comb-trust 936884e9, uci-comb-demo-trust b66eaf0c. 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.
Layout passes L2 + L3.
LIB_NISTCURVES_P256_BSS(1,312 B) is unioned withcert_buf(P256_SCRATCH_UNION), as ip65 has done since feat(cfg): ip65 refit — un-break BACKEND=ip65 via cert_buf/verify-scratch union #68.NET_BSS_TAILgoes from 63 B to 1,375 B free on every UCI profile; this is the onchip product's home for the PQ hybrid glue.comb + TRUST_BUNDLE=1is now a$(error)+ ld65 error, becausetrust_bundlerunsecdsa_verifyout ofcert_buf(accepted trade-off; not a shipped combination).tools/test_ecdsa_kat_oracle.py[3k]-[3n]: all 2,048 B ofcert_bufjunk-seeded,tp_mode=TP_M_FIRST, green on both shipped store builds (onchip-store6dcec600…, comb-store3726693a…).Merge order: before the P-384 retirement PR, which conflicts in the three UCI cfgs and will be rebased onto this.
Commits (messages verbatim; each carries its red-green evidence)
961dcb2 uci: union LIB_NISTCURVES_P256_BSS with cert_buf (P256_SCRATCH_UNION)
ip65 has time-shared cert_buf with the library's 1,312 B of verify
scratch since #68 (SCRATCH_UNION). UCI kept the scratch in NET_BSS_TAIL,
leaving that region 63 B free on every profile. The lifetime rule is the
same on both backends (src/der_decode.s): cert_buf is dead once
x509_extract_pubkey returns -- the key copy, the SAN check and the SPKI
pin / trust hook all run inside it -- and ecdsa_verify runs only after.
Every UCI cfg (uci, uci-onchip = c64-https-uci.cfg; comb = -onchip.cfg and
its cold twin) now pins CERT_BUF_BSS at the head of CRYPTO_OVERLAY ($4200)
and routes LIB_NISTCURVES_P256_BSS to a P256_SCRATCH_UNION area on top of
it. The area is CERT_BUF_SIZE ($0800) long, so P-256 BSS growth past
cert_buf is an ld65 memory-area overflow, and src/der_decode.s asserts
(lderror) that the area starts at cert_buf and is no larger than it.
The one consumer that breaks the rule: on comb the cold bank runs
trust_bundle FROM cert_buf, and trust_bundle calls ecdsa_verify, whose
scratch would overwrite the running code. comb + TRUST_BUNDLE=1 is now a
Makefile $(error), with an lderror backstop in trust_bundle.s that fails
the link if the bundle's code ever runs inside the union. TRUST_BUNDLE
stays available on uci / uci-onchip, where it is resident and reads the
TCP ring. test_trust_policy_6502.py drops its comb-bundle image (its
verify is a Python stub, so it could not see the overwrite).
test_cold_bank.py's cfg delta loses the cert_buf pin, which both comb
cfgs now carry.
NET_BSS_TAIL tail free, every UCI profile: 63 -> 1,375 B (+1,312);
demo 43 -> 1,355. No other region moves (tools/measure_margins.py --build).
ip65 PRGs byte-identical.
Red-green: P256_SCRATCH_UNION start $4300 -> lderror "must start at
cert_buf"; size $0900 -> lderror "larger than cert_buf"; size $0500 ->
"overflows memory area 'P256_SCRATCH_UNION' by 32 bytes"; the Makefile
guard removed, comb + TRUST_BUNDLE=1 -> lderror "trust_bundle runs inside
P256_SCRATCH_UNION". All restored: green.
e0aab4c test_ecdsa_kat_oracle: prove the cert_buf union's lifetime in VICE ([3k]-[3n])
P256_SCRATCH_UNION (previous commit) puts the library's verify scratch on
cert_buf. It is safe only while nothing reads cert_buf once the scratch
is live. This group drives the image's own path, not a staged struct:
leaf into cert_buf -> tls_cert_stream_finish (key copy + SAN check) ->
tls_handle_cert_verify (CertificateVerify hash + ecdsa_verify), on any
UCI build that streams the Certificate:
[3k] the P-256 scratch symbols lie inside cert_buf (union linked)
[3l] the leaf is accepted, key extracted
[3m] the server signature verifies AND the verify overwrote the leaf
(else the overlap was not exercised)
[3n] a signature over another transcript is refused
ip65 runs no extra checks (its union is asserted in src/net/ip65/exports.s).
Mutation (onchip, C64_VICE_NO_REU=1), mutant M1 = defer the SAN check
from Certificate time to after the signature (a cert_buf read with the
scratch live):
union layout, unmutated 4298aecb... [3m] PASS C=0, 157/160 overwritten
union layout, M1 683faac8... [3m] FAIL C=1
master layout, M1 6a1fc605... [3m] C=0, 0/160 overwritten
So the red is the union's, not the mutant's. .o files and the PRG were
unlinked between builds, and the three hashes are distinct.
cea8459 test_ecdsa_kat_oracle: union group runs on the shipped TRUST_STORE products
Review finding (real defect): [3l]-[3n] failed on BOTH shipped products.
Under TRUST_STORE, x509_extract_pubkey tail-calls the trust hook
(cert_pin_check, src/tls_cert.s), which refuses while tp_mode is still
TP_M_NONE ("trust_pre never ran"). The earlier green run was on 4298aecb,
a plain uci-onchip image that ships nowhere.
label exists, as a first-use trust_pre would, before the extraction;
re-seed it after the leaf is consumed and before each verify: every
verify now runs over arbitrary bytes in its whole scratch span, which
is the lifetime claim, not just over a 160 B leaf;
flight's interleaving, trust_pre/trust_post and the cold bank's calls,
and everything after CertificateVerify (Finished, keys, the GET).
Green (union group, VICE):
uci-onchip-store 6dcec600... (shipped flags, C64_VICE_NO_REU=1)
4/4, [3m] C=0, 1,240 of 2,048 cert_buf bytes overwritten
uci-comb-store 3726693a... (shipped flags, REU)
4/4, [3m] C=0, 1,242 of 2,048 overwritten
Red:
tp_mode write removed, comb-store 3726693a...: [3l] FAIL C=1 -> (1, 3),
the reviewer's defect reproduced
mutant M1 (SAN check after the signature), uci-onchip 683faac8...:
[3m] FAIL C=1 -> (3, 1); still red under the junk seeding
8d9b7f9 test_ecdsa_kat_oracle: correct what the M1 mutant proves
CORRECTION to the docstring and to cea8459's commit message, which said
mutant M1 (the SAN check moved to after the signature) "turns [3m] red on
the union layout and leaves it green without it". That held for e0aab4c's
test; it is false since cea8459 re-seeds all of cert_buf with junk before
each verify. The deferred name check then reads junk on ANY layout, so M1
is red on master's layout too. Measured (uci-onchip, C64_VICE_NO_REU=1):
master layout + M1, this test 6a1fc605... [3k] FAIL, [3m] FAIL C=1
with 0 of 2048 overwritten
So M1's red shows only that a cert_buf read after the verify is caught.
What shows the union is exercised is [3k] (the scratch symbols lie inside
cert_buf) and [3m]'s
changed > 0(the verify really wrote over cert_buf):on master's layout [3k] fails and [3m] reports 0 bytes overwritten. The
docstring now says so. Text only.
🤖 Generated with Claude Code