Authenticate the raw device tree against the signed firmware image - #852
Draft
dgarske wants to merge 1 commit into
Draft
Authenticate the raw device tree against the signed firmware image#852dgarske wants to merge 1 commit into
dgarske wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR strengthens secure boot for non-FIT MMU targets by cryptographically binding a raw DTB (loaded from a separate flash region) to the signed firmware image, preventing DTB tampering (e.g., /chosen/bootargs) while keeping the kernel image intact.
Changes:
- Adds a new signature-covered TLV (
HDR_DEVICE_TREE_DIGEST, tag0x35) and implementssign --dts <dtb>to hash/sign the DTB’s declaredfdt_totalsizespan. - Updates the MMU raw-DTB boot path to load the DTB safely (size clamp + checked reads) and verify its digest, with optional fail-closed enforcement via
WOLFBOOT_REQUIRE_SIGNED_DTB=1. - Adds/extends unit tests and documentation for the new signing and boot-time verification behavior.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/unit-tests/unit-sign-dts.py | New regression test for sign --dts DTB hashing/TLV emission and failure modes |
| tools/unit-tests/unit-image.c | Adds unit test for wolfBoot_verify_dts_digest() across supported hashes |
| tools/unit-tests/Makefile | Adds new unit-test targets for DTB digest verification and runs unit-sign-dts.py |
| tools/scripts/zynq7000/prepare_linux.sh | Updates RAW-DTB signing flow to use --dts and documents enforcement option |
| tools/keytools/sign.c | Implements --dts option and DTB hashing into a signature-covered TLV |
| src/update_ram.c | Loads raw DTB via HAL or ext-flash fallback and enforces digest verification policy |
| src/image.c | Adds wolfBoot_hash_buffer() + wolfBoot_verify_dts_digest() helper |
| options.mk | Adds WOLFBOOT_REQUIRE_SIGNED_DTB build option with warning |
| include/wolfboot/wolfboot.h | Defines HDR_DEVICE_TREE_DIGEST tag |
| include/image.h | Exposes DTS helper prototypes under `MMU |
| hal/zynq7000.c | Updates comments to reflect new DTB authentication behavior |
| docs/Targets.md | Documents raw-DTB authentication and enforcement behavior for relevant targets |
| docs/Signing.md | Documents new --dts option, behavior, and constraints |
| docs/compile.md | Documents WOLFBOOT_REQUIRE_SIGNED_DTB for raw-DTB targets |
| config/examples/zynq7000.config | Updates partition comments to reflect raw DTB + digest authentication |
| .gitignore | Ignores new unit-test binaries |
Suppressed comments (1)
src/update_ram.c:673
- The external-flash DTB path treats any positive fdt_totalsize as valid. Since fdt_check_header() doesn’t bound totalsize, a crafted header with totalsize < 40 would be accepted and only that short prefix would be loaded/forwarded. Enforce the same minimum header size used by the signer (40 bytes).
ret = ext_flash_read((uintptr_t)WOLFBOOT_DTS_BOOT_ADDRESS,
dts_hdr, (int)sizeof(dts_hdr));
if (ret == (int)sizeof(dts_hdr)) {
ret = wolfBoot_get_dts_size(dts_hdr);
if (ret > 0 && (uint32_t)ret <= WOLFBOOT_DTS_MAX_SIZE) {
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+1347
to
+1348
| fprintf(stderr, "Device tree file %s is %ld bytes but its FDT totalsize " | ||
| "is %u (truncated): %s\n", file, fsz, total, file); |
Comment on lines
+651
to
+657
| dts_addr = hal_get_dts_address(); | ||
| if (dts_addr != NULL) { | ||
| ret = wolfBoot_get_dts_size(dts_addr); | ||
| if (ret < 0 || (uint32_t)ret > WOLFBOOT_DTS_MAX_SIZE) { | ||
| wolfBoot_printf("DTB parse/size check failed - ignoring\n"); | ||
| dts_addr = NULL; /* never forward an unvalidated address */ | ||
| } |
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.
Fenrir 7998
Problem
For non-FIT MMU boots,
wolfBoot_start()loads a separate raw device tree (DTB) after the OS image is authenticated and hands it to the kernel with no check of its own -- only an FDT magic/version parse. An attacker who can write the DTS flash region can swap in a structurally valid tree that changes/chosen/bootargsor other kernel-visible policy while the signed kernel stays intact: a secure-boot bypass. A DTB inside a signed FIT is unaffected (it is covered by the FIT signature); this only concerns the rawPART_DTS_BOOT/hal_get_dts_address()path.Fix
Bind the raw DTB to the firmware image with a signature-covered digest and verify it before the tree reaches the kernel.
sign --dts <board.dtb>hashes exactly the firstfdt_totalsizebytes of the device tree with the image hash algorithm and stores the result as a new signature-covered TLV,HDR_DEVICE_TREE_DIGEST(tag0x35) -- mirroring the existing--cmdline/HDR_CMDLINEpattern. The signer applies at least the checks the bootloader'sfdt_check_header()applies.wolfBoot_start()snapshots the digest from the verified image header, loads the raw DTB, hashes it, and rejects a mismatch withwolfBoot_panic()(fault-hardened compare). The digest binds the tree to that firmware version, so an unrelated or rolled-back DTB cannot pair with it.Enforcement is backward compatible: a DTB that carries the digest is always verified and a mismatch always panics; a raw DTB with no digest only warns and boots unless the build opts in with
WOLFBOOT_REQUIRE_SIGNED_DTB=1(plumbed throughoptions.mkwith a$(warning)). Existing MMU targets that boot an unsigned raw DTB keep working until they adoptsign --dts.Raw-DTB load path: the pre-existing
PART_IS_EXT(&os_image)proxy left the raw DTS partition unreachable onEXT_FLASH + NO_XIP(RAMBOOT) targets.hal_get_dts_address()remains the primary source (unchanged for memory-mapped targets); when it has no usable address, the DTB is read directly from external flash atWOLFBOOT_DTS_BOOT_ADDRESS. Both paths clamp the DTB toWOLFBOOT_DTS_MAX_SIZEbefore copying and check everyext_flash_readlength, so an attacker-inflatedfdt_totalsizeor a short read cannot overflow or forward a partial tree.Usage
Stage the same
board.dtbin the raw DTS region (WOLFBOOT_DTS_BOOT_ADDRESS). Build withWOLFBOOT_REQUIRE_SIGNED_DTB=1to fail closed once every raw-DTB payload is signed. A DTB inside a signed FIT does not need--dts.Testing
Software (all passing):
unit-image-dts/-sha384/-sha3-384--wolfBoot_verify_dts_digest()andwolfBoot_hash_buffer()for each hash: match accepts, tamper/wrong/short/NULL reject.unit-sign-dts.py-- sign side across SHA256/384/3: correctHDR_DEVICE_TREE_DIGEST; bad-magic/truncated/unsupported-version/short-body rejected with a clean non-zero exit (guards the fixed double-free, previously SIGABRT);--dts+--deltarejected; missing-arg handled.unit-image,unit-update-ram*,unit-update-disk*,unit-fdtgreen; ZynqMP firmware builds clean for both SHA3 and SHA256.Notes for reviewers
hal_dts_fixup()runs; the helper takes a pre-captured digest because the DTS load may reuseos_image.