fix: repair master — the ABI asserts and the Ed25519 verifier both merged broken - #94
Conversation
…rged broken master (22d8f8b) does not compile. Two independent double-merges, both the same shape: two PRs fixing adjacent things landed on stale bases, each was green on its own branch, and the result was never rebuilt. 1. include/eos_image.h — embeddedos-org#93 replaced reserved[30] with tlv_len (2) + tlv_hash[28], preserving every offset. embeddedos-org#87 merged afterwards carrying asserts written against the older struct: error: no member named 'reserved' in 'eos_image_header_t' (x2) embeddedos-org#93 already asserts tlv_len at 62 and tlv_hash at 64, so the offset assert was a duplicate; the width assert had no replacement and is restored as two asserts covering both halves of the same 30-byte span. No offset moves and the wire format is unchanged. 2. core/ed25519_verify.c — embeddedos-org#86 and embeddedos-org#57 both landed a subgroup guard, so the file carried two byte-identical point_is_identity() definitions: error: redefinition of 'point_is_identity' Only embeddedos-org#57's public_key_is_valid_subgroup() is wired to the call site, so embeddedos-org#86's key_has_prime_order() was dead. Kept the live function, folded embeddedos-org#86's fuller rationale onto it, deleted the duplicate. 3. tests/unit/test_ed25519.c — collateral from the same merge. Two copies of test_ed25519_identity_key_forgery_rejected, main() calling it twice and two tests not at all, and test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery referencing k_low_order[] and messages[] that the merge had dropped. While restoring the corpus, corrected it (review finding on embeddedos-org#86): the array claimed to hold "the eight low-order point encodings" and held five. Every order here was computed rather than copied — decode y, recover x, add the point to itself until it reaches the identity — giving 1, 2, 4, 4, 8, 8, 8, 8. Missing before: y=0 with the sign bit set, and both sign-flipped order-8 encodings. D9FF..FF was in the array and is not a low-order point at all — no x satisfies the curve equation for that y — so it moves to a separate k_non_canonical[], with EDFF..FF7F (y=p) and EEFF..FF7F (y=p+1). tests_run was assigned a literal (11) in main() and never incremented, which is how the duplicate call and the two unregistered tests went unnoticed. The TEST macro now increments it, so the total cannot drift. Verified: cmake -DEBLDR_BUILD_TESTS=ON on master FAILS to build, 3 errors same with this commit builds clean ctest 21/21 PASS ctest -DEBLDR_SANITIZE=ON (ASan+UBSan) 21/21 PASS pytest tests/ 24 passed, 1 skipped test_ed25519 14/14 PASS (was 11 claimed, 12 run) discrimination, with `public_key_is_valid_subgroup` disabled: test_ed25519_low_order_keys_rejected FAILS, as it must test_ed25519_non_canonical_... still PASSES — those are refused by unpackneg() on canonicality, a different mechanism, which is the reason they are held in a separate array rather than counted among the eight.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… clash Three findings from the review on embeddedos-org#80, all in files this branch already owns. Finding 2 (Medium) -- core/fw_decrypt.c's file header still read "Falls back to HAL hw_aes_decrypt if available." The diff started at line 192, so that line survived and directly contradicted the twenty-line rationale this PR installs below it. A self-contradictory file is worse than the original. Finding 1 (Medium) -- after the removal, hw_aes_decrypt has zero call sites while eos_hal.h still advertises it under "software fallback used if NULL", which is now false in the other direction: a board author who implements the hook gets nothing, silently, with no diagnostic. That is the same class of failure this PR is fixing. Kept the member rather than deleting it -- removing it from a public struct is an ABI change for out-of-tree boards, and the hook is worth having once it can express the operation -- and documented it as reserved and currently unconsumed, with the reason and with what a streaming-capable replacement would need. Finding 3 (Low) -- this PR and embeddedos-org#82 both inserted their add_executable/add_test triple immediately after add_test(NAME test_keystore ...), so whichever landed second would have conflicted for no reason but placement. Re-anchored this one to the end of the registrations, with a comment saying why. Also rebased: this branch was 7 commits behind and its diff against current master would have reverted the test_recovery link-line fix. It is now stacked on embeddedos-org#94, which repairs master -- without that, every PR that builds the test suite is red on include/eos_image.h and core/ed25519_verify.c. Verified: cmake --build (EBLDR_BUILD_TESTS=ON) clean ctest 22/22 PASS test_fw_decrypt 8/8 PASS git grep hw_aes_decrypt header + this file's comment only Refs embeddedos-org#80
…hdr_size Answers the review on embeddedos-org#88. Both High findings had the same root, and master has since settled the question the reviewer said needed a decision. The original defect is real and unchanged: the tool emitted [header][TLV][payload] while stamping hdr_size as a fixed 156, so core/image_verify.c's `payload_addr = addr + hdr->hdr_size` landed on the TLV block and every image it produced failed integrity verification on-device. This branch fixed it by moving hdr_size to mean "offset to the payload". That was the wrong half to move. Finding 1 (High) -- eFirmware/src/efw_image.c:122 checks `hdr_size != EFW_IMAGE_HDR_SIZE` for exact equality against 156, so every TLV image would have been rejected by the other half of the format, which eFirmware's header documents as interchangeable. Finding 2 (High) -- core/rollback.c computes the TLV address as image_addr + hdr_size + image_size, i.e. it assumes the area follows the payload. Under either the old layout or this branch's, the anti-rollback EOS_TLV_MIN_SEC_VER could never be found, so the counter this repository gates downgrades on was unreachable for every image the tool produced. Master answers both, and it is the reviewer's option (b): embeddedos-org#93 landed tlv_len and tlv_hash in the header -- inside EOS_IMG_SIGNED_LEN -- and documents that "the TLV area sits after the payload". So this PR now emits [header][payload][TLV] with hdr_size always 156. eFirmware's equality check keeps working untouched, rollback.c's arithmetic is correct as written, and no field changes meaning. That reordering also resolves an ordering problem the old shape had: the TLV carried the signature, so its digest could not be computed before the signature existed. The signature now lives in the header's signature[] field and the TLV carries metadata only, which is what makes tlv_hash signable. Finding 4 (Medium) -- the two `assert`s in cmd_sign are removed by `python -O`. This is release-signing tooling, so the signature-length check is now a `raise SystemExit`. The TLV-length assert is gone with the code that needed it. Finding 5 (Medium) -- `pytest.importorskip("cryptography")` still skipped silently if the install ever failed, leaving "collected 19, ran 0" as a green run. Now gated: EOS_REQUIRE_SIGNING_TESTS is set in ci.yml, and under it the module is imported directly so a missing dependency is a collection error. Locally, without the variable, the skip still applies. Finding 6 (Low) -- cmd_verify bounds-checks the image length before unpacking, so a short file gets a FAIL: line rather than a struct.error traceback, and the 156 is hoisted to EOS_IMG_STRUCT_SIZE rather than re-derived. Finding 3 (Medium) needs no change here: master's include/eos_image.h already documents the layout this now emits. Rebased onto embeddedos-org#94 (the master repair) and reduced to the five files that are still this PR's own -- the branch was stale enough that its diff against master would have reverted core/rollback.c, include/eos_image.h, tests/unit/test_tlv_auth.c and the rest of embeddedos-org#93's TLV work. Verified: end to end, with the real tool: eos_sign.py keygen / sign / verify -> VERIFIED total 744 = 156 header + 512 payload + 76 TLV hdr_size 156, tlv_len 76, TLV magic 0x6907 at 156+512 sha256(tlv)[:28] == header bytes 64..92 True ctest 22/22 PASS test_eos_sign_boot_path (real parser over real tool output): current layout [hdr][payload][tlv]: verify_integrity -> 0 PASS old layout [hdr][tlv][payload]: verify_integrity -> -3 PASS TLV magic at addr + hdr_size + image_size: 0x6907 PASS that third line is finding 2's regression test -- it fails on the old layout, where the address lands past the end of the image. pytest tests/ (EOS_REQUIRE_SIGNING_TESTS=1) 44 passed the CI gate itself: with the variable set and the module absent, the import raises ImportError -> collection error -> job fails, rather than skipping. Refs embeddedos-org#88, embeddedos-org#93
srpatcha
left a comment
There was a problem hiding this comment.
Review — eBoot#94 "fix: repair master — the ABI asserts and the Ed25519 verifier both merged broken"
head: f704d87 author: Kartikey1306 ci: pass
Verdict: Correct, and every claim in the body checks out — I reproduced the breakage on master, reproduced the repair, and independently recomputed the low-order point orders rather than taking the table on trust. This is the base of the #84 → #85 and #89 stacks and blocks all of them. One finding, and it is about what the PR deliberately does not do.
Findings
| # | Severity | File:line | Finding | Recommended fix |
|---|---|---|---|---|
| 1 | Medium | org branch protection, not a file in this diff | The body diagnoses the cause precisely — "two PRs fixing adjacent things landed on stale bases, each green on its own branch, and the merged result was never rebuilt" — and then repairs only the damage. Nothing here stops the third occurrence, and this PR is already fixing two independent instances of it (#87/#93 and #86/#57). Every required check in checks.txt runs against the PR head; none runs against the merge result, so two green PRs can and did produce a red master. |
Not a code change: enable Require branches to be up to date before merging on master for the required checks, or turn on a merge queue. Worth stating in the PR so it is not lost with the fix. .github/STANDARDS.md §"Release model" defines the branch model but says nothing about merge preconditions — see the proposal appended to .ai/autoreview/proposals/2026-09.md. |
Nothing else. The diff does what it says, no check is weakened, nothing is deleted without explanation, and the two files it touches are the two that are broken.
What I verified
The breakage is real. origin/master at 22d8f8b:
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DEBLDR_BUILD_TESTS=ON && cmake --build build
include/eos_image.h:135:23: error: 'eos_image_header_t' has no member named 'reserved'
include/eos_image.h:142:55: error: 'eos_image_header_t' has no member named 'reserved'
gmake: *** [Makefile:101: all] Error 2
$ gcc -fsyntax-only -Iinclude core/ed25519_verify.c
core/ed25519_verify.c:338:12: error: redefinition of 'point_is_identity'
core/ed25519_verify.c:281:12: note: previous definition ...
The repair is complete. At this head, clean build and:
100% tests passed, 0 tests failed out of 21
14/14 tests passed # test_ed25519
Both numbers match the body exactly.
key_has_prime_order() really was dead. On master the only call site is line 496, if (!public_key_is_valid_subgroup(A)); key_has_prime_order is defined at 313 and never referenced. Deleting the right one of the two.
The retained guard fails closed and does both halves. public_key_is_valid_subgroup() returns point_is_identity(multiple) && !point_is_identity(public_key), and the caller maps a false to EOS_ERR_SIGNATURE. ORDER_L is the real group order — I decoded the 32 bytes at core/ed25519_verify.c:71 and compared against 2^252 + 27742317777372353535851937790883648493: equal.
I recomputed the low-order table rather than trusting it, since it is the regression record for a secure-boot bypass. Decoding each y, recovering x, and repeatedly adding the point to itself until it reaches the identity:
k_low_order[] k_non_canonical[]
order 1 identity order=1 canonical y = p decodes, order 4, NOT canonical
order 2 order=2 canonical y = p+1 decodes, order 1, NOT canonical
order 4 sign clear order=4 canonical D9FF..FF no x exists on the curve
order 4 sign set order=4 canonical
order 8 a order=8 canonical
order 8 b order=8 canonical
order 8 a' order=8 canonical
order 8 b' order=8 canonical
1, 2, 4, 4, 8, 8, 8, 8 is the whole 8-element torsion subgroup, so the array is now the class it names, and the split is justified on exactly the grounds the body gives: D9FF..FF has no x at all and was never a low-order point, while y = p and y = p+1 decode but are non-canonical and are refused earlier by unpackneg(). Every claim in the body's table holds.
One thing the body understates. It says tests_run "was assigned a literal (11) and never incremented, which is exactly how a duplicate call and two unregistered tests went unnoticed". It was worse than mis-labelling: master's main() makes 13 run_* calls (12 distinct, identity_key_forgery twice), so tests_passed reaches 13 against tests_run = 11 and the binary exits 1. And test_ed25519_low_order_R_with_a_valid_key_is_not_a_forgery is defined at tests/unit/test_ed25519.c:278 on master with zero call sites — a signature-forgery test that never ran. git log -S "tests_run = 11" puts both in 22d8f8b, the same merge, so the exposure is recent rather than long-standing. This PR restores the call and makes the count self-maintaining; that is a weakened check being repaired and belongs in the body.
Architecture conformance
Master design §5.1 and §8: conforms. No dependency changes, no new files, no tier movement. eBoot, Tier 1 – Foundation (§21). The change is confined to restoring the state the two merges destroyed.
.ai/security.md "Fail closed" and "Cryptography — reject invalid and low-order public keys explicitly": this PR is what puts eBoot back on the right side of both. Master today cannot build, so Host Build & Tests on every open PR is testing the PR's own repair rather than the tree.
The design-level gap is §28, "Status, Evidence and Claims Policy". It defines what evidence an artifact needs to claim a state — "Implemented: code and functional tests" — and is silent on the integration gate that keeps the trunk in that state. An org whose master does not compile still satisfies §28 as written, because §28 never asks the question of the merged result. That is a hole in the master design rather than in this PR; proposal appended to .ai/autoreview/proposals/2026-09.md.
Proposed changes
- Land this first. #84, #89 and (through #84) #85 are all based on
f704d87and cannot build without it. - Add finding 1 to the body as a follow-up item, or open an issue, so the branch-protection change is not lost once the tree is green again.
- Add the two facts above —
test_ed25519exiting 1, and the uncalled forgery test — to §3 of the body. They make the case for thetests_runchange stronger than it currently reads.
No code change requested.
Not checked
pytest tests/— "24 passed, 1 skipped". I did not run it; I ran the CMake/ctest path only. Unverified.- The disabled-guard experiment. The body shows
low_order_keys_rejected [FAIL]/non_canonical_encodings_rejected [PASS]withpublic_key_is_valid_subgroup()disabled. I did not reproduce it. It is consistent with my own computation — the non-canonical entries are refused byunpackneg()before the subgroup check runs — but consistency is not the same as having run it. - Sanitizer run at this head. Body claims 21/21 under
-DEBLDR_SANITIZE=ON. I ran ASan/UBSan on #84 (22/22, this commit plus the FDT work) and not separately here. - Host x86-64 only. The eight cross-compile and eleven EoSim jobs in
checks.txtare green; not reproduced. - Why
Host Build & Testswas green onmaster's own merge commits. Iftest_ed25519was exiting 1 from22d8f8bonward, something should have been red. I did not dig into the workflow definitions to find out whether the job runsctestat all on push, so I cannot tell you whether the signal was missing or merely ignored. Worth someone checking — it is the same question finding 1 asks.
Automated architecture review of f704d87fd445 — scheduled, model claude-opus-5, checked against the EmbeddedOS Master Design v2.0. Advisory only: this reviewer never approves, requests changes, or merges. Reply here to discuss or push back — a wrong finding is a bug worth reporting.
|
Worth flagging for merge priority: this blocks every open PR in
That is this PR's finding 1, reached from the other repository. It is the only failing check on eos #114, #116, #117, #118, #119, #122, #126 and #127 — every other job on all of them is green — and nothing can be done from the eos side, because the workflow builds The eBoot side is the same story: #80, #82, #84, #85, #88, #89 and #90 are all stacked on this branch because they cannot build otherwise. No review shortcut intended — the diff is three files and the evidence is in the body. Just noting that the blast radius is larger than the PR looks. |
… makes Answers the second review on embeddedos-org#82. Finding 1 (High) -- step 4, "Verify signing key against OTP root-of-trust", was the same fail-open this PR removes from step 7, two steps earlier. An `eos_hal_otp_read()` failure was discarded and boot continued; and when the read succeeded and the anchor was provisioned, the body of the `if` was `/* In a full implementation, extract key hash from TLV and compare */`. So a device whose root of trust *is* provisioned booted an image signed by any key the image carried, and step 8 recorded EOS_SBOOT_OK. Implementing the TLV comparison is out of scope, as the review says. The two things that are in scope are done: a non-EOS_OK otp_read now fails EOS_SBOOT_ERR_SIGNATURE, and a provisioned anchor that nothing compares against refuses the boot rather than proceeding. An unprovisioned board (all-zero anchor) is deliberately unchanged -- there is nothing to check against, and refusing would brick every board that has not been provisioned. The comment now states plainly that the step is planned rather than implemented, which §8.1 asks for and a comment inside an `if` was not. No test for those two refusals, deliberately, and the file says why rather than leaving it to be discovered. Reaching step 4 requires passing step 3 -- a real Ed25519 signature checked against the keystore. I wrote the obvious test first and it was worthless: with require_signature = true and an unsigned fixture the boot fails at step 3 and returns the same EOS_SBOOT_ERR_SIGNATURE step 4 returns, so it passed against the unfixed code too. I confirmed that by reverting step 4 and watching it still pass. A test that cannot fail is worse than none. What is there instead is the counter-check that the change does not refuse a boot it should allow. The fixture is buildable -- the keystore ships RFC 8032 TEST 1's public key and the matching private key is in the RFC -- but that machinery is embeddedos-org#88's (tools/gen_signed_image_fixture.py). Worth doing once embeddedos-org#88 lands. Finding 3 (Low) -- `TEST()` did not increment `tests_run` and `main()` hardcoded `tests_run = 6`, so a test added to the file but not wired into `main()` would have been skipped with a zero exit. embeddedos-org#94 removed exactly this from tests/unit/test_ed25519.c, where a hardcoded 11 was masking two uncalled tests. Same fix here. Finding 4 (Low) -- the Valgrind `foreach` is hand-maintained and missed 6 of 22 registered tests. Added test_secure_boot_policy, test_fdt_loader and test_fw_decrypt. The list being hand-maintained at all is the real defect and is not fixed here -- it is the same class as the hardcoded count, one level up. Finding 2 (Medium), that eos_secure_boot() has no production caller, stands and is not addressed here; finding 1 makes it sharper rather than resolving it. Finding 5 (Low) is a PR-body correction. Verified: ctest 22/22 PASS test_secure_boot_policy 7/7 PASS step 7 discrimination, still: reverting the step-7 branch fails test_secure_boot_refuses_when_the_debug_lock_cannot_be_taken Refs embeddedos-org#82
…harnesses Answers the second review on embeddedos-org#84. Finding 2 (Medium) -- eos_fdt_validate() and eos_fdt_get_prop() kept an out-of-bounds read by design. Both dereferenced hdr->totalsize before any length was known and handed that attacker-controlled value to the _sized form as its bound, so calling either on a short buffer read past it before a single check had run. The header called that a caller warrant; a warrant is not a check, and it is the exact bug this PR exists to fix. Neither had an in-tree caller. Removed rather than documented. There is now one form of each entry point and it always takes the length: int eos_fdt_validate(const void *fdt_blob, uint32_t avail); int eos_fdt_get_prop(const void *fdt, uint32_t fdt_len, ...); An exported unsafe twin in a TCB header is a future boot-path caller reintroducing the bug with no compiler complaint, which is worth more than the convenience of a one-argument call. eos_fdt_load() already passed max_size. The test that pinned the wrapper's behaviour is gone with the wrapper, and get_prop_sized_exact() collapsed into get_prop_exact() since they became the same function. Finding 3 (Medium) -- tests/fuzz/ was built by nothing. EBLDR_BUILD_FUZZ defaults OFF and no job set it, so the harness added here joined five others that no CI job compiles. A harness that is never built cannot fail to build, which is how fuzz_devicetree came to declare a function that did not exist and sit there unnoticed (eos#50). Adds a `fuzz-build` job: configure with clang, build every harness, and run each for five seconds over its own generated inputs. That is not a campaign -- it is enough to catch a harness that no longer compiles or crashes at once, which is the failure this repo has actually had. Note it needs EBLDR_BUILD_TESTS=ON as well: tests/fuzz/ is added from tests/CMakeLists.txt, so EBLDR_BUILD_FUZZ alone configures cleanly and builds no harness at all -- the job would have passed having compiled nothing. Found that locally before writing the job, not after. NOT RUN, and this is the honest limit: this host has no libFuzzer runtime (libclang_rt.fuzzer_osx.a is absent from the Xcode toolchain), so the link step cannot be reproduced here for any harness, old or new. What I verified is that CMake configures with both flags and reports all six targets -- "Fuzz targets: fuzz_image_verify, fuzz_recovery_protocol, fuzz_fw_update, fuzz_crypto, fuzz_bootctl, fuzz_fdt" -- and that fuzz_fdt.c passes `cc -fsyntax-only`. The link and the smoke run are CI's to show, and this job is what makes them visible. Coordination note: embeddedos-org#90 adds a `CI Gate` whose needs list is [test, build-arm, static-analysis]. Whichever of embeddedos-org#84 and embeddedos-org#90 lands second must add fuzz-build to that list -- and embeddedos-org#90's own test_gate_covers_every_job_that_runs_on_a_pull_request fails loudly if it is not, which is the guard working rather than a trap. Finding 1 (High) is a PR-body correction, made there: the diff carries embeddedos-org#94's master repair because this branch is stacked on it, and the body described only the FDT parser. Verified: ctest 22/22 PASS pytest tests/ 38 passed test_fdt_loader 13 tests PASS grep for a length-free entry point none remains in the header Refs embeddedos-org#84
|
This PR is blocking 11 open pull requests in The blast radius
Swept every open — eleven, all with the identical signature: None of those PRs touches Reproduced locally, both directionsThe failure is a So the fix in this PR is exactly the fix for the eos-side failure, and I can Why it happened, for the record
AskNothing to change here — this PR is If it helps triage: the only other recurring red on those eos PRs is the |
Rebuilt from the review on cf4bf4f. The FDT work is unchanged; everything else in this branch is a response to a finding. Finding 3 -- scope. core/ed25519_verify.c, include/eos_image.h and tests/unit/test_ed25519.c are gone from this branch. They were a master repair, and embeddedos-org#94 owns it. The branch is now stacked on embeddedos-org#94 rather than racing it; git dropped the duplicate as "patch contents already upstream", which is the confirmation that all three PRs were carrying the same fix. Finding 1 -- the Fuzz Harness Build job this PR adds was red, and it was red for a reason worth stating plainly: three of the five harnesses call functions that have never existed anywhere in the tree. fuzz_fw_update.c eos_fw_update_init, _process_chunk -- no such symbols fuzz_recovery_protocol.c eos_recovery_parse_packet -- no such symbol fuzz_bootctl.c eos_bootctl_parse -- no such symbol Deleted, with their add_executable blocks. Each declared its target with a local extern instead of including a header, so nothing ever checked that the function existed; and the job that would have caught it did not exist until this PR added it. Deleting a harness that has never compiled removes no coverage -- but it does remove the appearance of coverage, which is finding 2 and is the part that matters. eBoot has no fuzz coverage of firmware-update stream parsing or the recovery protocol, and now says so. fuzz_image_verify.c was worse than dead: it linked. It declared extern int eos_image_parse_header(const void *flash_base, size_t flash_len); against a function that is (uint32_t addr, eos_image_header_t *out), so it passed a pointer as a flash address and a size_t as an output-struct pointer. C does not check a declaration against a definition in another translation unit, so it built, ran, and fuzzed nothing. It now includes eos_image.h -- which is what makes the compiler check the call -- and drives the real parser through a flash backend registered with eos_hal_init(), the seam tests/unit/test_image_verify.c already uses. Finding 4 -- the "-8 the node path is deeper than FDT_MAX_PATH_DEPTH" line is gone from include/eos_fdt_loader.h. No path returned it and the constant is defined nowhere. Findings 5 and 6 -- the two fuzz_fdt.c comments now say what is true. The `size` justification no longer argues against unsized entry points this PR removed, and "/chosen" is described as the not-found probe it is until embeddedos-org#85 lands, rather than as coverage of the match path. Finding 7 is moot: the ed25519 comment rewrite that dropped the eBoot#57 attribution is no longer part of this branch. Verified: ctest 22/22, and 22/22 again under -DEBLDR_SANITIZE=ON. All three surviving harnesses link and run against a stub driver (no libFuzzer on this machine, so the smoke-run step is checked in CI, not here).
|
Root cause identified, and it is my own merged PR. Owning it plainly since it Which commit broke
|
|
Evidence for prioritizing this: merging it takes 12 currently-red eos PRs green in one step, with no change needed on any of them. Every open eos PR fails exactly one job —
Master carries a second, independent break this PR also fixes: Both defects are pairs of PRs that merged clean and broke the build together; eos PR #135 pins eos's eBoot checkout to Since eos PR CI runs on the merge commit with master, the 12 eos PRs go green on their next run after this lands — no rebases, no pushes. |
master(22d8f8b) does not compile. Two independent double-merges, both the same shape: two PRs fixing adjacent things landed on stale bases, each green on its own branch, and the merged result was never rebuilt.This blocks every open PR that builds the test suite.
1.
include/eos_image.h— #93 and #87#93 replaced
reserved[30]withtlv_len(2 bytes) +tlv_hash[28], preserving every offset. #87 merged afterwards carrying asserts written against the older struct.#93 already asserts
tlv_lenat 62 andtlv_hashat 64, so #87's offset assert was a duplicate. Its width assert had no replacement, and that is the one that matters — an offset assert cannot see a field growing into padding that keeps every later offset. Restored as two asserts covering both halves of the same 30-byte span. No offset moves; the wire format is unchanged.2.
core/ed25519_verify.c— #86 and #57Both landed a subgroup guard, so the file carried two byte-identical
point_is_identity()definitions. Only #57'spublic_key_is_valid_subgroup()is wired to the call site, so #86'skey_has_prime_order()was dead code. Kept the live function, folded #86's fuller rationale onto it, deleted the duplicate.3.
tests/unit/test_ed25519.c— collateral from the same mergeTwo copies of
test_ed25519_identity_key_forgery_rejected;main()calling it twice and two tests not at all; andtest_ed25519_low_order_R_with_a_valid_key_is_not_a_forgeryreferencingk_low_order[]andmessages[]that the merge had dropped.While restoring the corpus, corrected it — this is the review finding on #86. The array claimed to hold "the eight low-order point encodings" and held five. Every order below was computed, not copied: decode
y, recoverx, add the point to itself until it reaches the identity.01 00…00ECFF…FF7F00…0000…008026E8…6D53FC05C717…92AC037A26E8…6D53FC85C717…92AC03FAD9FF…FFD9FF…FFmoves to a separatek_non_canonical[]alongsideEDFF…FF7F(y = p) andEEFF…FF7F(y = p+1).[L](-A) = -[L]Aso the guard rejects a sign variant whether or not it is listed — the reason to list them is that this is the regression record for a Critical secure-boot bypass, and a claimed class has to be the class it claims.tests_runwas assigned a literal (11) inmain()and never incremented, which is exactly how a duplicate call and two unregistered tests went unnoticed. TheTESTmacro now increments it, so the total cannot drift from what actually ran.Verification
cmake --build(EBLDR_BUILD_TESTS=ON)ctestctestunder-DEBLDR_SANITIZE=ON(ASan+UBSan)pytest tests/test_ed25519The sweep still discriminates. With
public_key_is_valid_subgroup()disabled:That second line is why the two arrays are separate: the non-canonical encodings are refused by
unpackneg()on canonicality, not by the subgroup check, so nobody should delete that path believing the subgroup test now covers it.Refs #87, #93, #86, #57