From 837fed8a5e1583e6a947a22fa8cc946d980f8aa2 Mon Sep 17 00:00:00 2001 From: kartikey1306 Date: Tue, 1 Sep 2026 20:48:34 +0530 Subject: [PATCH] fix(image): pin the whole .efw header, not four fields of it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #66 (parts 1 and 2). eos_image_header_t is a wire format — eFirmware writes these bytes, eBoot reads them, and the signing tools address fields by absolute offset. Four of its fourteen fields were pinned: sizeof, hash, sig_type, signature. Transposing two adjacent same-width fields moves neither sizeof nor any of those four offsets. With load_addr and entry_addr swapped, all four existing asserts still pass and the header compiles clean; the bootloader then loads an image at its entry point and jumps to its load address. No constant's *value* was pinned on this side either. EOS_IMG_MAGIC, EOS_HASH_SIZE, EOS_SIG_MAX_SIZE, EOS_IMG_SIGNED_LEN, EOS_IMAGE_HDR_ VERSION and the five eos_sig_type_t values all travel inside the image. Changing EOS_SIG_ED25519 from 3 to 4 compiled cleanly, passed every assert, and would make eBoot misread the signature type of every image already in the field. eFirmware pins EFW_IMAGE_MAGIC and EFW_SIG_ED25519 on its side; this is the matching half, so the two definitions can no longer drift apart in silence. Adds the ten missing offsets, three field widths (an offset assert cannot see a field growing into padding that keeps every later offset — reserved[] absorbs exactly that), and eleven constant values. Thirty asserts total. Header only. No struct member, constant, or line of logic changes, so every image on disk today parses exactly as before. Verified: compiles clean, all 30 pass load_addr/entry_addr transposed -> 2 asserts fire (master: 0) EOS_SIG_ED25519 = 3 -> 4 -> 1 assert fires (master: 0) cmake -DEBLDR_BUILD_TESTS=ON + ctest -> 20/20 passed Part 3 of #66 — eFirmware stamping v1 while eBoot supports only v2 — is a security-policy call and is deliberately not touched here. --- include/eos_image.h | 59 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/include/eos_image.h b/include/eos_image.h index a2a9d18..d13d23e 100644 --- a/include/eos_image.h +++ b/include/eos_image.h @@ -80,6 +80,65 @@ EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, sig_type) == 60, EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, signature) == 92, "signature[] must stay at offset 92"); +/* Every remaining field, pinned. + * + * Four of the fourteen fields were asserted. Transposing two adjacent + * same-width fields moves neither sizeof nor any of those four offsets, so it + * compiled clean: with load_addr and entry_addr swapped, all four existing + * asserts still passed and the bootloader would load an image at its entry + * point and jump to its load address. A wire format needs every field pinned, + * not a representative sample. */ +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, magic) == 0, + "magic must stay at offset 0"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, hdr_version) == 4, + "hdr_version must stay at offset 4"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, hdr_size) == 6, + "hdr_size must stay at offset 6"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, image_size) == 8, + "image_size must stay at offset 8"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, load_addr) == 12, + "load_addr must stay at offset 12"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, entry_addr) == 16, + "entry_addr must stay at offset 16"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, image_version) == 20, + "image_version must stay at offset 20"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, flags) == 24, + "flags must stay at offset 24"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, sig_len) == 61, + "sig_len must stay at offset 61"); +EOS_IMG_STATIC_ASSERT(offsetof(eos_image_header_t, reserved) == 62, + "reserved[] must stay at offset 62"); + +/* Field widths. An offset assert cannot see a field growing into padding that + * happens to keep every later offset -- reserved[] absorbs exactly that. */ +EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->hash) == 32, + "hash[] is 32 bytes on the wire"); +EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->reserved) == 30, + "reserved[] is 30 bytes on the wire"); +EOS_IMG_STATIC_ASSERT(sizeof(((eos_image_header_t *)0)->signature) == 64, + "signature[] is 64 bytes on the wire"); + +/* Constant values. These travel inside the image, so they are wire format too + * and an offset assert says nothing about them. Changing EOS_SIG_ED25519 from + * 3 to 4, or reordering the enum, compiled cleanly and passed every assert + * above while making eBoot misread the signature type of every image already + * in the field. eFirmware pins the same numbers on its side + * (EFW_IMAGE_MAGIC == 0x454F5349u, EFW_SIG_ED25519 == 3); these are the + * matching half, so the two definitions can no longer drift apart in silence. */ +EOS_IMG_STATIC_ASSERT(EOS_IMG_MAGIC == 0x454F5349, + "magic is \"EOSI\" and eFirmware stamps the same value"); +EOS_IMG_STATIC_ASSERT(EOS_HASH_SIZE == 32, "hash is SHA-256, 32 bytes"); +EOS_IMG_STATIC_ASSERT(EOS_SIG_MAX_SIZE == 64, "signature area is 64 bytes"); +EOS_IMG_STATIC_ASSERT(EOS_IMG_SIGNED_LEN == 92, + "the signed prefix is the 92 bytes before signature[]"); +EOS_IMG_STATIC_ASSERT(EOS_IMAGE_HDR_VERSION == 2, + "eBoot writes and expects header format v2"); +EOS_IMG_STATIC_ASSERT((int)EOS_SIG_NONE == 0, "EOS_SIG_NONE is 0 on the wire"); +EOS_IMG_STATIC_ASSERT((int)EOS_SIG_CRC32 == 1, "EOS_SIG_CRC32 is 1 on the wire"); +EOS_IMG_STATIC_ASSERT((int)EOS_SIG_SHA256 == 2, "EOS_SIG_SHA256 is 2 on the wire"); +EOS_IMG_STATIC_ASSERT((int)EOS_SIG_ED25519 == 3, "EOS_SIG_ED25519 is 3 on the wire"); +EOS_IMG_STATIC_ASSERT((int)EOS_SIG_ECDSA == 4, "EOS_SIG_ECDSA is 4 on the wire"); + /* ---------------- Image Validation API ---------------- */ /**