Skip to content

build: drop EBLDR_REQUIRE_SIGNATURES and EBLDR_RECOVERY_AUTH, switches wired to nothing - #133

Open
Kartikey1306 wants to merge 13 commits into
embeddedos-org:masterfrom
Kartikey1306:build/security-options-wired-to-nothing
Open

Kartikey1306 wants to merge 13 commits into
embeddedos-org:masterfrom
Kartikey1306:build/security-options-wired-to-nothing

Conversation

@Kartikey1306

@Kartikey1306 Kartikey1306 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #132

Stacked on #135 (→ #115); review abf0522..f9fa26c for this change alone -- two commits, six files. Merge order #115#135 → this: #135 is the Eisvogel toolchain fix for Build PDF, which this PR triggers by touching docs/book/ and which has been red on every run since 2026-04-25; stacking on it is what turns that check green here rather than leaving it as a declared-prerequisite red. (Restacked from e152d8e..25794f4 once #135's fix was proven green at abf0522; before that from a276016..88cb92e.) Merge #115 first. Independent of the other stacks on #115 (#116/#122, #123, #127/#131, #129): its CHANGELOG line goes at the end of ### Fixed, where none of them writes.

On the red Build & Release Book PDF check: this PR is the first one tonight to touch docs/book/, so it is the first to trigger that workflow, and the workflow has never succeeded in this repository -- every run since 2026-04-25 fails, including the last master push run (84bbef0, 2026-09-08), on the same ! LaTeX Error: Command \chead already defined. (pandoc 3.6.1 + Eisvogel 2.4.0). Not caused by the table edit here; the toolchain fix is #135 (issue #134), and this PR's PDF check goes green once that lands. The gate checks (CI — eBoot, eBoot Build & Test, CodeQL, Linked issue policy, Simulation Sanity Test) are green on this head.

The defect

CMakeLists.txt offered EBLDR_REQUIRE_SIGNATURES and EBLDR_RECOVERY_AUTH as options (default ON), forwarded each as a compile definition, and the README listed both under "Security options" as the switches that require Ed25519 signatures and recovery authentication. No source file tests either macro:

$ git grep -n "EBLDR_REQUIRE_SIGNATURES\|EBLDR_RECOVERY_AUTH" -- '*.c' '*.h'
(no output)

Measured rather than inferred: the same tree configured with -DCMAKE_C_FLAGS="-DEBLDR_REQUIRE_SIGNATURES -DEBLDR_RECOVERY_AUTH" and without produces byte-identical output for 41 of 41 non-test object files (to reproduce, build without -g — debug info embeds the command line, so a Debug configuration differs in every object for a reason that has nothing to do with the options) (cmp over CMakeFiles/**/*.c.o; the .a archives differ only in their timestamp fields). OFF built the verifying bootloader too -- fail-closed by accident, but a documented switch that changes nothing is a capability the tree does not have, presented as configuration. docs/book/book.md §24.1 went further and listed EBLDR_SECURE_BOOT, EBLDR_MULTICORE, EBLDR_RECOVERY and EBLDR_BOOT_MENU, none of which has ever existed in CMakeLists.txt.

The fix

  • CMakeLists.txt: the two option()s and their add_compile_definitions are removed. A configure that still passes either as OFF stops with FATAL_ERROR ("there is no build that skips signature verification or recovery authentication ... drop the flag") instead of silently producing the verifying firmware. Passing ON is inert and gets CMake's usual unused-variable warning.
  • README.md: the two rows are gone; a paragraph states that verification and recovery authentication are not options, and why the rows were there.
  • docs/book/book.md: the table lists the options that exist (EBLDR_BOARD, EBLDR_BUILD_TESTS, EBLDR_VERIFY_STAGE1, EBLDR_HARDENING, EBLDR_SANITIZE, EBLDR_BUILD_FUZZ) and names the four phantoms it used to carry.
  • tests/unit/test_build_options_are_read.py (new, runs under the existing pytest tests/ step in ci.yml): every EBLDR_ name that CMakeLists.txt forwards with add_compile_definitions must be referenced by a preprocessor line in some .c/.h under core hal include stage0 stage1 boards tests; every EBLDR_ name in a README or book option table must be declared by CMakeLists.txt; and neither removed option may be redeclared.
  • tests/simulate_tests.py: a correctness repair to a file nothing executes — not coverage. Its two checks asserted the option names appeared in CMakeLists.txt, which held because the switches did nothing, so each asserted the opposite of what its name claimed. They now assert the honest opposite, and they are exactly as unexecuted as before: no workflow, CMake target or Makefile runs this file (grep -rn simulate_tests .github/ CMakeLists.txt tests/CMakeLists.txt is empty), and run by hand it fails 8 unrelated stale checks. The guard that pytest actually runs is tests/unit/test_build_options_are_read.py, which the CHANGELOG names. The file's fate — register-and-repair, or delete — is a follow-up, not this PR. That script is run by no workflow and fails 9 unrelated checks on the base (3.0.1 version pins, CI job names); the failure set is unchanged, 9 before and 9 after.

Not done: EBLDR_VERIFY_STAGE1 is a real switch (stage0/jump_stage1.c:65) and stays one; whether a Release build of a real board should be allowed to turn it off is a policy question for #122's gate, not this PR.

Verification

Check Result
cmake -S . -B build -DEBLDR_BUILD_TESTS=ONcmake --buildctest 31/31
cmake ... -DEBLDR_REQUIRE_SIGNATURES=OFF / -DEBLDR_RECOVERY_AUTH=0 configure fails at the new guard with the message above
cmake ... -DEBLDR_REQUIRE_SIGNATURES=ON configures (exit 0)
pytest tests -q 84 passed (80 on the base + the 4 new)
Negative control: base CMakeLists.txt, README.md and docs/book/book.md restored, new guard run 3 of 4 fail: unread definitions ['EBLDR_RECOVERY_AUTH', 'EBLDR_REQUIRE_SIGNATURES']; phantom book options ['EBLDR_BOOT_MENU', 'EBLDR_MULTICORE', 'EBLDR_RECOVERY', 'EBLDR_SECURE_BOOT']; the removed options still declared
Object comparison, definitions forced back in vs. not 41/41 identical
Line endings CMakeLists.txt stays CRLF (464→471 CR bytes), CHANGELOG.md 135→136; README, book, and the Python files stay LF

@codecov-commenter

codecov-commenter commented Sep 14, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 98.01980% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
tests/unit/test_fw_update_test_sigs.py 93.33% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@srpatcha srpatcha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — eBoot#133 "build: drop EBLDR_REQUIRE_SIGNATURES and EBLDR_RECOVERY_AUTH, switches wired to nothing"

head: 88cb92e author: Kartikey1306 ci: failBuild PDF red, 25 green (Create GitHub Release, Attach PDF to Release, Update Dev PDF, assign skipped)

Verdict: First look. The claim this PR rests on is true and I reproduced it exactly: with both
macros forced back in via CMAKE_C_FLAGS, 41 of 41 objects are byte-identical to a build without
them, so the two options documented a capability the tree did not have. Removing them rather than
implementing them is the right call — the alternative is adding a supported way to build a
bootloader that does not verify. The FATAL_ERROR for a script that still passes =OFF is the
right shape, and it fires. Two findings, neither in the removal itself: the new guard catches one of
the three ordinary CMake spellings of the mistake it exists to prevent, and tests/simulate_tests.py
— which this PR carefully corrects — is executed by nothing.

The red check is not this PR's. Build PDF fails with LaTeX Error: Command \chead already defined at l.1560 of the template preamble, which is a pandoc/Eisvogel/fancyhdr incompatibility
that never reaches the document body. #135 in this same batch is the PR that fixes it. I read the
job log rather than inferring this, because this PR does edit docs/book/book.md and that was the
obvious thing to suspect.

Findings

# Severity File:line Finding Recommended fix
1 Medium (P2) tests/unit/test_build_options_are_read.py:52 The guard matches one of the three ordinary ways to forward a compile definition, so two of them re-introduce a dead switch silently. _forwarded_definitions() is re.findall(r"add_compile_definitions\(\s*(EBLDR_[A-Z0-9_]+)\s*\)", text) — the closing paren must follow the name immediately. Demonstrated by editing CMakeLists.txt and running the module: add_compile_definitions(EBLDR_DEAD_A=1)4 passed; target_compile_definitions(eboot_core PRIVATE EBLDR_DEAD_B)4 passed; the plain add_compile_definitions(EBLDR_DEAD_C)1 failed ✅. The =1 form is the more likely of the two to appear, because it is what someone writes when they want the macro to have a value, and target_compile_definitions is what they write when they want it scoped to one library. Neither is exotic. The guard is correct about the tree as it stands — CMakeLists.txt forwards exactly one EBLDR_ definition, EBLDR_VERIFY_STAGE1 at :56, and I confirmed there is no target_compile_definitions with an EBLDR_ name anywhere — so this is entirely future-facing, which is also where the whole value of this guard lies. Match the argument list rather than a single argument: collect every EBLDR_[A-Z0-9_]+ token inside add_compile_definitions(...) and target_compile_definitions(...), and strip anything after a = before comparing. Then add the two shapes above as negative cases, the way test_no_option_offers_to_skip_verification_or_authentication pins the two names. Secondary, same file: test_documented_options_exist checks documented ⊆ declared but not the reverse, so an option that exists and is documented nowhere passes. That direction is clean today — I enumerated it: declared is EBLDR_{BOARD,BUILD_FUZZ,BUILD_TESTS,HARDENING,SANITIZE,VERIFY_STAGE1}, docs/book/book.md lists all six and README.md lists five with EBLDR_BOARD covered in the prose above the table — so this is worth one more assertion, not a finding of its own.
2 Low (P3) tests/simulate_tests.py:169-181 This PR rewrites two assertions in a file that nothing executes, and the file has been failing for some time. The change itself is exactly right — the old test("Ed25519 signature requirement enabled by default", "EBLDR_REQUIRE_SIGNATURES" in cmake) passed because the dead option was present, so it asserted the opposite of what its name claimed, and the replacement asserts the honest thing. But grep -rn simulate_tests .github/ CMakeLists.txt tests/CMakeLists.txt Makefile* is empty: no workflow, no CMake target, no Makefile runs it. I ran it here: 103/111, 8 failures, exit code 1 — including three version-drift failures (build.yaml, CITATION.cff, CMakeLists.txt version is 3.0.1) and five about CI jobs it believes are missing. None of that is visible to anyone. So the two corrected assertions are as unexecuted as the two vacuous ones they replace, and the PR body does not say so — a reader would reasonably take "the honest claim is the opposite one" as a check that now runs. The real guard here is tests/unit/test_build_options_are_read.py, which pytest does run, and the CHANGELOG correctly names that one. Decide the file's fate in a follow-up rather than leaving it in between: either register it (add_test in tests/CMakeLists.txt, or a step in ci.yml) and fix or delete the eight stale assertions, or delete the file and move anything worth keeping into the pytest suite. Whichever way, say in this PR's body that the simulate_tests.py edit is a correctness repair to an unexecuted file, so the claim is not read as coverage.

What this PR gets right

The evidence is the strong part, and it is reproducible. The removal is justified by measurement
rather than by reading — a switch that produces identical objects is not a switch, and that is a
statement you can check, which I did. Choosing FATAL_ERROR over silently ignoring a stale
-DEBLDR_REQUIRE_SIGNATURES=OFF is the fail-closed reading: a caller passing that flag is asking for
something the bootloader does not do, and configuring as if the request had been granted is how the
original defect would have survived the cleanup. The if(DEFINED ... AND NOT ...) guard correctly
lets =ON through — asking for what you already get is not an error — and I confirmed =OFF and =0
both abort. docs/book/book.md losing EBLDR_SECURE_BOOT, EBLDR_MULTICORE, EBLDR_RECOVERY and
EBLDR_BOOT_MENU is the larger documentation repair: four options that never existed in
CMakeLists.txt were being presented to readers as configuration, and test_documented_options_exist
is what stops a fifth appearing. No stale references to either removed name survive outside the
three places that explain the removal.

Verification performed for this review

Detached worktree at .ai/autoreview/state/scratch/eBoot-133 on 88cb92e9. The user's eBoot
checkout is on fix/ed25519-low-order-keys; it was clean before and is clean after, and was not
touched. Nothing was committed or pushed. This review covers a276016..88cb92e9 (one commit); the
rest of the bundle diff belongs to #115, which this branch is stacked on.

Check Result
cmake -B build/host -DEBLDR_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debugcmake --build --parallel 4 PASS
ctest --test-dir build/host --output-on-failure --no-tests=error PASS — 31/31, 10.98s
pytest tests/ -q PASS — 84 passed, 1.79s
pytest tests/unit/test_build_options_are_read.py -v PASS — 4/4
The headline claim, reproduced. Two EBLDR_BOARD=none Release builds, the second with -DCMAKE_C_FLAGS="-DEBLDR_REQUIRE_SIGNATURES -DEBLDR_RECOVERY_AUTH", objects compared by SHA-256 41 identical, 0 differing — exactly the author's "41 of 41". Methodology note: the same comparison on a Debug build shows 41/41 differing, because -g records the compiler command line in DW_AT_producer, so the flags themselves change the object. The claim is about generated code and holds; it needs a no--g build to see. Worth a line in the body so the next person reproducing it does not conclude the opposite.
grep -rn "EBLDR_REQUIRE_SIGNATURES|EBLDR_RECOVERY_AUTH" --include=*.c --include=*.h empty — confirms no source ever read either macro
cmake -DEBLDR_REQUIRE_SIGNATURES=OFF -DEBLDR_BOARD=none CMake Error at CMakeLists.txt:67, configure aborts with the intended message
=ON accepted, =0 refused Both as intended — if(DEFINED ... AND NOT ...)
Guard blind-spot probes (finding 1) add_compile_definitions(EBLDR_DEAD_A=1)4 passed ❌ · target_compile_definitions(… EBLDR_DEAD_B)4 passed ❌ · add_compile_definitions(EBLDR_DEAD_C)1 failed ✅. CMakeLists.txt restored; git status --short empty.
Declared vs documented options, enumerated through the module's own helpers declared = {BOARD, BUILD_FUZZ, BUILD_TESTS, HARDENING, SANITIZE, VERIFY_STAGE1}; book.md lists all six; README.md lists five, EBLDR_BOARD in prose. No phantom, no undocumented.
tests/simulate_tests.py — is it run, and does it pass? Run by nothing; python3 tests/simulate_tests.py103/111, exit 1. import re is present at :12, so the new re.search calls resolve — I checked, because the diff adds the first re use in that block.
Leftover references to the removed options Only the three sites that explain the removal (CMakeLists.txt:60,65, README.md:74-75). No workflow or script still passes either flag.
Build PDF failure cause — job log read, not inferred ! LaTeX Error: Command \chead already defined. at l.1560 ...nel@ifstar {\@dblarg\@@@chead}{\@chead}}, exit code 43. A template/fancyhdr collision in the preamble, before any of this PR's book.md content is typeset. Not caused by this PR; #135 addresses it.

Architecture conformance

Conforms. §21: eBoot is Tier 1 Foundation; CMakeLists.txt, README.md, docs/, tests/ are all
inside the owning repo, and §21.1 is not engaged. §5.1 dependency direction is untouched — nothing
about the module graph changes, only which macros reach the compiler. The clause this PR serves is
§28, Status, Evidence and Claims Policy, and it serves it unusually directly: a build option
presented as ON by default, with the meaning "Require Ed25519 signatures for boot", is a
Implemented-grade claim (code and functional tests) made about something that had neither — the
definition reached the compiler and no code read it. §28's rule that a claim without evidence must be
labelled rather than asserted is what the README and book.md now comply with. §14.1's "keep
implemented, experimental and planned security features explicitly separated"
— restated in
.ai/security.md as "never let a planned feature read as implemented" — is the same point from the
security side, and four phantom options in book.md were the worst instance of it in the tree.
§5.1's "eBoot keeps the trusted computing base minimal and auditable" is served by the
FATAL_ERROR: a TCB whose build accepts a flag asking it to skip verification, and silently does not
skip it, is not auditable in either direction.

Proposed changes

In this PR, if convenient (both are small):
  test_build_options_are_read.py   match target_compile_definitions() too, and
                                   EBLDR_X=1 as well as EBLDR_X; the two shapes
                                   as negative cases                        (finding 1)
  PR body                          say the simulate_tests.py edit is a repair to a
                                   file nothing runs, and that the Debug-build object
                                   comparison needs -g off to reproduce      (finding 2)

Follow-up, not this PR:
  tests/simulate_tests.py          register it and fix its 8 stale assertions,
                                   or delete it and keep what is worth keeping
  test_build_options_are_read.py   assert declared subset documented, the other
                                   direction of test_documented_options_exist

This branch is stacked on #115 and cannot land before it. Build PDF is red and will stay red
until #135 merges; it is not a reason to hold this PR, but it does mean this PR cannot be judged
by an all-green tree.

No fix PR opened. tests/unit/test_build_options_are_read.py exists only on this branch — not on
origin/master — so a branch cut from the default branch, which is what fix-start.sh produces,
would have nothing to patch. Finding 2 is Low and outside the autofix rule regardless.

Not checked

  • The PDF build was NOT run locally. No pandoc or LaTeX toolchain here. The cause above is read
    from the failing job's log; that #135 fixes it is taken from its title and reviewed separately,
    not verified here.
  • Only the host EBLDR_BOARD=none build was compared. The 41/41 result is Verified for that
    configuration. That the same holds for a cross-compiled board build is Inferred — it follows
    from no source file containing either macro, which I did verify, but I did not rebuild a board
    twice to measure it.
  • EBLDR_HARDENING, EBLDR_SANITIZE, EBLDR_BUILD_FUZZ were not audited for the same defect.
    This PR's guard now covers them by construction for the add_compile_definitions form, and none of
    them is forwarded that way, but I did not check by hand whether each one actually changes the
    build. EBLDR_SANITIZE and EBLDR_HARDENING affect CMAKE_C_FLAGS rather than definitions, so the
    guard does not speak to them at all.
  • The #115 half of the diff was not reviewed here. This review covers a276016..88cb92e9 only.
  • Cross-compile, fuzz, ASan/UBSan, Valgrind, CodeQL, cppcheck/clang-tidy — NOT RUN locally. The
    25 green checks were not re-run and their logs were not read; only the one red job's log was.

Automated architecture review of 88cb92e9b262 — 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.

Twenty PRs were merged into master in ninety minutes on 09-07, each on the
base it was written against. Master has not configured, compiled, linked or
passed its tests since. Every nightly and every eos simulation run (which
builds eBoot master) has been red for the same reason.

Configure:
- tests/CMakeLists.txt registered eboot_test_fdt_loader twice. embeddedos-org#84 added it,
  and embeddedos-org#85 -- stacked on embeddedos-org#84 -- re-added it at a different anchor when it was
  replayed onto a master that already had embeddedos-org#84. Same hunk as embeddedos-org#111.

Compile (eboot_core):
- embeddedos-org#55 restored a core/sha512.c from 02b7dac that keeps its byte count in
  ctx->count; the eos_sha512_ctx_t master ships has bitlen[2]/buffer_len
  (embeddedos-org#69/embeddedos-org#93). Back to the pre-embeddedos-org#55 file, blob-identical (d9aa57c).
- embeddedos-org#55's source-list "correction" dropped core/boot_log.c, core/secure_boot.c
  and core/fdt_loader.c, which embeddedos-org#72/embeddedos-org#84 had added after embeddedos-org#55 was written.
  CMakeLists.txt is blob-identical to pre-embeddedos-org#55 again (f8fe6eb).
- embeddedos-org#55 replaced the eos_boot_log_get_head() declaration with a second copy of
  eos_boot_log_read(); embeddedos-org#91 had already fixed the prototype it meant to fix.
  Header restored (964ebb8).
- embeddedos-org#94 and embeddedos-org#105 each repaired the Ed25519 verifier and each added an identical
  static scalarbase(); both merged. One copy removed.
- The same pair each added k_low_order[]/messages[] to test_ed25519.c. The
  embeddedos-org#105 copy is removed; embeddedos-org#94's stays because it also carries k_non_canonical[].

Tests that stopped passing because two merged PRs disagree on behaviour:
- embeddedos-org#104 verifies the image signature at install unconditionally, before the
  anti-rollback check embeddedos-org#103 added, so embeddedos-org#103's unsigned images are refused as
  EOS_ERR_SIGNATURE before they can be refused as EOS_ERR_ANTI_ROLLBACK, and
  test_fw_transport's XMODEM install can no longer finalize. Both suites now
  stream genuinely signed images. eBoot has no Ed25519 signer in C, so
  tools/gen_fw_update_test_sigs.py signs the exact header prefixes those
  suites build under the RFC 8032 section 7.1 TEST 1 key and emits
  tests/vectors/fw_update_test_sigs.h; the suites serve that key from a
  simulated OTP slot 0. Negative control: one flipped signature byte fails
  test_write_streams_tlv_then_finalize_rejects_below_floor with
  EOS_ERR_SIGNATURE.
- embeddedos-org#103's step 5b reads the TLV counter through the HAL slot containing the
  image; test_secure_boot_policy (embeddedos-org#82) declared no slots, so eos_secure_boot()
  returned EOS_SBOOT_ERR_BAD_HEADER two steps before the one under test. The
  fixture now places its image in slot A.

Guards from embeddedos-org#95 that later merges walked back, never run until now because the
C configure step failed first:
- embeddedos-org#103 replayed the hand-written Valgrind foreach over the derived one.
  Restored foreach(TEST_NAME ${EBLDR_UNIT_TESTS}); eleven registered suites
  had no list(APPEND ...) and so no Valgrind run.
- Seven suites assign tests_run = <literal> and their TEST() does not count;
  four suites have no TEST() macro at all. Counted, and classified.
- embeddedos-org#101 added fuzz-build after embeddedos-org#90's gate; the gate did not wait for it.

CI plumbing:
- eosim-sanity.yml: the install-validate job is written in bash but ran under
  PowerShell on the Windows legs (no shell:), where SITE_PACKAGES=$(...) is an
  unknown command and `|| { exit 1 }` is an unexecuted script block.
- scorecard.yml: ossf/scorecard-action@v2.4.0 pulls gcr.io, which now demands
  GCP billing. v2.4.3 pulls ghcr.io; eos already pins it and is green.

Verified locally (macOS, clang): Release build clean, 31/31 ctest; the same
under -DEBLDR_SANITIZE=ON (ASan+UBSan); 78/78 pytest.

Not fixed here, reported separately: core/keystore.c's compiled-in
default_dev_key is described as the RFC 8032 TEST 1 public key but differs
from byte 21 on and is not a point on the curve, so nothing can verify
against it on any board without OTP. With embeddedos-org#104 that makes firmware update
refuse every image on such boards.
… -1.0.0 as an option

test_out_of_range_version_is_rejected[-1.0.0] passed locally (Python 3.14)
and failed in CI (ubuntu-22.04, Python 3.10) with argparse's own
"expected one argument": the older negative-number matcher does not accept
-1.0.0, so the token was read as an unknown option and imgpack.py's range
check -- the thing under test -- never ran. The joined form is unambiguous
on every interpreter and the test now reaches the tool's message.
tests/vectors/fw_update_test_sigs.h is the output of
tools/gen_fw_update_test_sigs.py, committed because eBoot has no Ed25519
signer in C. Nothing checked that the two agree: a generator edit without a
regeneration leaves test_fw_update and test_fw_transport verifying against
stale signatures, failing with EOS_ERR_SIGNATURE and nothing to say why.

tests/unit/test_fw_update_test_sigs.py runs the generator with the test's
own interpreter and compares its stdout to the committed header byte for
byte, so a line-ending change counts too. It follows the same dependency
rule as test_eos_sign_payload_offset.py: with EOS_REQUIRE_SIGNING_TESTS set
(the CI workflow sets it before the pytest step) a missing cryptography
module fails the job instead of skipping. Negative control: one flipped hex
byte in the header fails the test with a unified diff naming the line.

build_image() and build_container() now carry a comment naming the
coupling: the signed prefix is assembled both there and in the generator,
and changing any field in it means changing the generator's copy and
regenerating the header.
…epair

embeddedos-org#103 and embeddedos-org#104 were both merged and disagree on whether the anti-rollback
counter or the signature is checked first in eos_fw_update_finalize(). The
master design orders boot as verify image, then version policy (section
8.1), but its update flow (section 15) never places the anti-rollback
check, so the order the install path uses existed only in a PR body.
ADR-020 records it: the signature over the signed header prefix is
verified first, the TLV counter is read only after the prefix that binds
it is authenticated, and an image that fails verification is refused as
EOS_ERR_SIGNATURE without its counter being consulted. docs/adr/README.md
is added in the shape of the eos repository's index; 020 avoids reusing
001 through 019.

CHANGELOG.md gains the Unreleased entries for the repair: the configure,
compile and link breakage after the 09-07 batch merge, the settled check
ordering with the suites streaming signed images, the re-derived Valgrind
list, fuzz-build in the CI gate, counted tests_run, the EoSim Windows legs
running under bash, and the Scorecard action on its ghcr.io-hosted release.
The changelog said eleven suites had no Valgrind run. Eleven were missing
from EBLDR_UNIT_TESTS, but four of those were named in the hand-written
foreach and did run; seven had no run at all. Say which.

ADR-020: its design-document citation now says where it comes from (the
architecture review of embeddedos-org#115), and the sentence about the two PRs' bases is
replaced with what the history shows -- embeddedos-org#103's commits predate embeddedos-org#104's merge,
and embeddedos-org#104 was written without embeddedos-org#103's check in place.
The regeneration test's failure path -- the unified diff under the
regenerate command -- ran only when the header was stale, so a green run
never executed it and the coverage report said so. The report is now a
helper the match asserts with, and a second test drives the helper with
two byte strings that differ in one byte and checks the command and both
sides of the changed line appear.
…itself

No run of "Build & Release Book PDF" in this repository's history has
succeeded: every run since the step was added (ef0dc50, 2026-04-25) is a
failure, and every one whose log is still readable dies with

    ! LaTeX Error: Command \chead already defined.   (exit 43)

The workflow installs pandoc 3.6.1 from the release .deb and Eisvogel 2.4.0,
which predates pandoc 3.6. The 3.x line merges pandoc's default template
through 3.6.1 and later (its CHANGELOG names each merge) and states pandoc 3
compatibility. The LaTeX-level cause of the clash is not established here;
the run on this change is what shows whether the pair works.

Three changes to the one step and its trigger:

  Eisvogel 2.4.0 -> 3.5.1, as EISVOGEL_VERSION next to PANDOC_VERSION. The
  3.x tarball extracts into Eisvogel-<v>/ rather than flat, so the copy
  names the directory -- a bare version bump would have failed at the cp.
  Ran the new step verbatim against the real release: template installed,
  29,929 bytes; the old cp against the 3.x layout fails as predicted.

  No more `tar xzf ... 2>/dev/null || true`. That was fail-open on the exact
  step that breaks: a missing or reshaped tarball failed silently and the
  diagnostic landed on the cp after it.

  The workflow is added to its own paths: filter, for push and pull_request.
  It fired only on docs/book/**, so a workflow-only change could not get the
  run that proves it -- including this one.

Verified: parsed structure asserted, not parse success -- both paths lists,
push.tags ['v*'], branches, release types, workflow_dispatch and the job list
are unchanged apart from the two added path entries; the template step
contains no '|| true'. NOT RUN here: the PDF itself (no xelatex on this
host). The evidence is this PR's own run of the job, cited in the PR once it
exists -- and only if build-pdf ran rather than being skipped on has_book.

Closes embeddedos-org#134
…ships

The first run of this change got past the template clash -- no more
"\chead already defined" -- and failed on the next thing:

    ! LaTeX Error: File `sourcesans.sty' not found.   (exit 43)

Eisvogel 3.5.0 migrated its default font from the `sourcesanspro` package
to `sourcesans` (its CHANGELOG, 2026-06-28), and ubuntu-22.04's TeX Live
2021 has no sourcesans.sty. 3.4.0 is the last release on sourcesanspro,
which texlive-fonts-extra ships (checked against the package's file list),
and it already carries pandoc's default template merged through 3.9.

Install step re-run verbatim against the 3.4.0 release: template installed.
The PDF is still NOT RUN here; the PR's next run is the evidence.
The second run of this PR, on Eisvogel 3.4.0, failed exactly as master
does -- "Command \chead already defined" -- which means the first run's
apparent progress was an illusion: on 3.5.1 LaTeX died earlier, on a
missing font at line ~672 of the template, before it ever reached the
header setup at ~890. The clash was never in the Eisvogel version.

It is in this workflow. The pandoc step injected, through header-includes,

    \usepackage{fancyhdr} \pagestyle{fancy} \fancyhead[L]{...} ...

and Eisvogel builds its own header and footer with KOMA's scrlayer-scrpage,
which defines \chead. The template places $header-includes$ (line 541)
before its header block (~890), so fancyhdr defined \chead first and
scrlayer-scrpage's \newcommand failed. Two header packages, one command.
Both arrived in ef0dc50 (2026-04-25), which is why no run of this job has
ever succeeded, on any pandoc or Eisvogel version.

The header and footer now go through the template's own variables --
header-left=\leftmark, header-right=<version>, footer-center=<press line>,
footer-right left at the template's default page number -- and the
fancyhdr block is gone. The rest of header-includes (float, booktabs,
longtable, caption, graphicx) is unchanged. Eisvogel stays at 3.4.0 for
the fonts (3.5.x needs sourcesans.sty, absent from TeX Live 2021).

Asserted on the parsed workflow: no fancyhdr/fancyhead/pagestyle{fancy}/
*rulewidth on any non-comment line of the step; the four variables and
the remaining header-includes packages present; no comment or blank line
inside the backslash-continued pandoc command (a first draft put the
explanation inside it, where '#' would have swallowed the continuation);
trigger paths, tags and EISVOGEL_VERSION unchanged.

Still NOT RUN here: the PDF. Third run is the evidence.
Run 3 got through the preamble and died on content:

    ! Misplaced alignment tab character &.
    <argument> Srikanth Patchava & EmbeddedOS Contributors

The pandoc step passed -V "author=Srikanth Patchava & EmbeddedOS
Contributors". A -V value is inserted into the LaTeX verbatim, so the '&'
reached xelatex as an alignment tab. book.md's front matter already
declares the same author, and metadata goes through pandoc's writer, which
escapes it. The override is removed; the metadata is the one source.

Earlier runs never reached this line -- they died in the preamble first,
on \chead and on the font -- which is the same lesson as before: the first
error hides every later one.

Asserted on the parsed step: no -V author; every remaining literal -V value
is free of unescaped LaTeX specials (& % # _ ^ ~); header-includes is
exempt by design and values expanded at run time (title, version, date)
come from metadata or `date`, which carry none.

Still NOT RUN here. Fourth run is the evidence.
@Kartikey1306
Kartikey1306 force-pushed the build/security-options-wired-to-nothing branch 2 times, most recently from b5213df to 25794f4 Compare September 15, 2026 06:34
@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Both taken. Restacked onto #115's e152d8e first (master 682d005), then one commit on top; head is 25794f4, range e152d8e..25794f4.

Finding 1 (Medium) — the guard saw one of three shapes. Reproduced your three probes first, same result (=1 → 4 passed, target_compile_definitions → 4 passed, plain → 1 failed). _forwarded_definitions() now takes the whole argument list of add_ or target_compile_definitions(...), every EBLDR_ token in it, =value stripped. Five shapes pinned as collected — including multi-line and a mixed list — and a second test appends each shape with a dead name to the real CMakeLists.txt text and requires the guard to fail, which is how a dead switch would actually return. All three of your probes now fail it. Your secondary note is taken too: declared ⊆ documented is asserted alongside documented ⊆ declared, with EBLDR_BOARD exempted for the reason you gave.

Negative control: old one-shape collector, new tests kept → 5 of 11 fail.

Finding 2 (Low) — simulate_tests.py. Body now says it in the change list, in bold: a correctness repair to a file nothing executes, not coverage; the empty grep quoted; 8 stale failures by hand; the real guard is the pytest one. Its fate is a follow-up. The -g note is on the object-comparison line.

On the red Build PDF: pre-existing since 2026-04-25, as the body already said; the toolchain fix is #135 and this should land after it.

pytest tests/   93 passed
ctest           100% tests passed, 0 tests failed out of 31

(One correction visible in the history: the first push of this commit said pytest 91 in its message, from the wrong head; amended to the measured 93 within a minute, b5213df25794f4.) No approval existed at 88cb92e. Could you re-review at 25794f4.

The comment said the missing sourcesans.sty was ubuntu-22.04's TeX Live
2021. The job runs on ubuntu-latest, which the green run at 358009e shows
is ubuntu-24.04 (noble). The fact that matters -- the runner's
texlive-fonts-extra has no sourcesans.sty, so Eisvogel 3.5.x cannot be
used -- is unchanged and is what run 1 proved; the distro and TeX Live
version I attached to it were assumed, not read from the log.
…s wired to nothing

CMakeLists.txt offered both as options (default ON), forwarded each as a
compile definition, and the README listed both under "Security options".
No source file tests either macro: a configure with one of them OFF
produces byte-for-byte the same objects as ON (41 of 41 non-test object
files identical with the two definitions forced back in through
CMAKE_C_FLAGS). Signature verification and recovery authentication are
unconditional, and a switch that documents otherwise is a capability
the tree does not have, presented as configuration.

Remove the two options and their add_compile_definitions. A configure
that still passes either as OFF now stops with a message saying there
is no such build, instead of silently producing the verifying one. The
README says the two are not options. docs/book/book.md's option table
listed EBLDR_SECURE_BOOT, EBLDR_MULTICORE, EBLDR_RECOVERY and
EBLDR_BOOT_MENU, none of which has ever existed in CMakeLists.txt; it
now lists the real ones.

tests/unit/test_build_options_are_read.py pins both rules: every EBLDR_
name CMakeLists.txt forwards as a compile definition is referenced by a
preprocessor line in some C or header file, and every EBLDR_ name a
documentation table presents as an option is declared. Against the base
tree it fails on exactly the two dead options and the four phantom
book entries. tests/simulate_tests.py, which nothing runs, asserted the
option names were present in CMakeLists.txt; it now asserts the honest
opposite, and its failure set is unchanged (9 before, 9 after, all
unrelated).
…warded

Finding 1 from the review of 88cb92e. _forwarded_definitions() matched
add_compile_definitions(EBLDR_X) with the closing paren required to
follow the name, so of the three ordinary ways to forward a compile
definition it saw one. The reviewer showed the other two carrying a
name nothing reads straight past it:

    add_compile_definitions(EBLDR_DEAD_A=1)                      -> 4 passed
    target_compile_definitions(eboot_core PRIVATE EBLDR_DEAD_B)  -> 4 passed
    add_compile_definitions(EBLDR_DEAD_C)                        -> 1 failed

The =1 form is what someone writes when they want the macro to have a
value; target_compile_definitions is what they write when they want it
scoped to one library. Neither is exotic, and this guard exists only
for the future -- the tree forwards exactly one EBLDR_ name today and
it is read.

The collector now takes the whole argument list of add_ or
target_compile_definitions(...), every EBLDR_ token in it, with
anything after = stripped. Five shapes are pinned as collected,
including a multi-line call and a mixed list, and a second test appends
each shape to the real CMakeLists.txt text with a dead name and
requires the guard to fail on it -- which is exactly how a dead switch
would come back. All three of the reviewer's probes now fail the guard.

The secondary note is taken too: test_documented_options_exist checked
documented <= declared; the reverse direction is now asserted as well,
with EBLDR_BOARD exempted because the README covers it in prose above
the table rather than in it. Clean today, as the reviewer enumerated.

Negative control: with the old one-shape collector restored and the new
tests kept, 5 of 11 fail. At this head (restacked onto embeddedos-org#115's e152d8e,
itself on master 682d005): pytest tests/ 93 passed, ctest 31/31.
@Kartikey1306
Kartikey1306 force-pushed the build/security-options-wired-to-nothing branch from 25794f4 to f9fa26c Compare September 15, 2026 06:43
@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Restacked onto #135 at abf0522 — its Build PDF check just went green, proving the Eisvogel fix — so this PR's Build PDF can now pass instead of standing as a declared-prerequisite red. Head f9fa26c, range abf0522..f9fa26c, content unchanged (range-diff against 25794f4 empty). Merge order is now #115#135#133. At f9fa26c: ctest 31/31, pytest 93, 17 workflows parse.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EBLDR_REQUIRE_SIGNATURES and EBLDR_RECOVERY_AUTH are build options that no source file reads

3 participants