mirror of https://github.com/wolfSSL/wolfBoot.git
Authenticate raw device tree via signed HDR_DEVICE_TREE_DIGEST TLV (Fenrir #7998)
parent
a79bace772
commit
c8faa5adc2
|
|
@ -170,6 +170,9 @@ tools/unit-tests/unit-tpm-rsa-exp
|
|||
tools/unit-tests/unit-image-nopart
|
||||
tools/unit-tests/unit-image-sha3-384
|
||||
tools/unit-tests/unit-image-sha384
|
||||
tools/unit-tests/unit-image-dts
|
||||
tools/unit-tests/unit-image-dts-sha384
|
||||
tools/unit-tests/unit-image-dts-sha3-384
|
||||
tools/unit-tests/unit-store-sbrk
|
||||
tools/unit-tests/unit-tpm-blob
|
||||
tools/unit-tests/unit-update-disk
|
||||
|
|
|
|||
|
|
@ -46,9 +46,11 @@ NO_XIP=1
|
|||
# QSPI partition layout (16 MB total) - sized for a full Linux kernel + DTB
|
||||
# pair so the same layout also works for bare-metal payloads.
|
||||
# 0x000000 - 0x07FFFF BOOT.BIN (FSBL + wolfboot, 512 KB)
|
||||
# 0x080000 - 0x0FFFFF DTS_BOOT (signed DTB, 512 KB - Linux only)
|
||||
# 0x080000 - 0x0FFFFF DTS_BOOT (raw DTB, 512 KB - Linux only; authenticated
|
||||
# via the boot image HDR_DEVICE_TREE_DIGEST
|
||||
# TLV, see 'sign --dts')
|
||||
# 0x100000 - 0x6FFFFF BOOT_A (~6 MB primary)
|
||||
# 0x700000 - 0x77FFFF DTS_UPD (signed update DTB, 512 KB - Linux only)
|
||||
# 0x700000 - 0x77FFFF DTS_UPD (raw update DTB, 512 KB - Linux only)
|
||||
# 0x780000 - 0xDFFFFF UPDATE_B (~6.5 MB update)
|
||||
# 0xE00000 - 0xE0FFFF SWAP (64 KB scratch)
|
||||
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x00100000
|
||||
|
|
|
|||
|
|
@ -293,6 +293,25 @@ Provides a value to be set with a custom tag
|
|||
altered without breaking the image signature. Example:
|
||||
`--cmdline "root=/dev/mmcblk0p2 rw rootwait console=ttyTCU0,115200"`.
|
||||
|
||||
* `--dts filename`: Binds a raw (non-FIT) device tree blob to the firmware image. The sign
|
||||
tool hashes exactly the first `fdt_totalsize` bytes of the `.dtb` (validating the FDT magic
|
||||
and version the same way the bootloader does) with the image hash algorithm and stores the
|
||||
digest as a signature-covered TLV using the wolfBoot-reserved tag `HDR_DEVICE_TREE_DIGEST`
|
||||
(0x35). At boot, the non-FIT MMU path (`src/update_ram.c`) hashes the DTB it loads from the
|
||||
raw DTS partition or `hal_get_dts_address()` and compares it against this digest before
|
||||
handing the tree to the kernel; a mismatch triggers `wolfBoot_panic()`. This prevents an
|
||||
attacker who can write the DTS flash region from altering `/chosen/bootargs` or other
|
||||
kernel-visible policy while leaving the signed kernel intact. Example: `--dts board.dtb`.
|
||||
Note: the DTB and the kernel image are signed together, so re-signing the kernel is required
|
||||
whenever the device tree changes. A DTB delivered inside a signed FIT image is already
|
||||
covered by the FIT's signature and does not need `--dts`.
|
||||
|
||||
Enforcement of a *missing* digest is opt-in for backward compatibility: a firmware image that
|
||||
carries the digest is always verified, but a raw DTB with no `HDR_DEVICE_TREE_DIGEST` only
|
||||
warns and boots unless wolfBoot is built with `WOLFBOOT_REQUIRE_SIGNED_DTB=1` (see
|
||||
`options.mk`), which makes the missing digest a hard failure. Adopt `--dts` on every raw-DTB
|
||||
payload first, then set `WOLFBOOT_REQUIRE_SIGNED_DTB=1` to fail closed.
|
||||
|
||||
The 65524-byte maximum is the largest TLV value the wolfBoot header parser can walk
|
||||
past when locating the fields that follow it, such as the signature.
|
||||
|
||||
|
|
@ -301,8 +320,8 @@ Provides a value to be set with a custom tag
|
|||
must be built with a matching `IMAGE_HEADER_SIZE`, or it will fail to locate the firmware
|
||||
image at boot.
|
||||
|
||||
Note: all options, including `--cmdline` and the `--custom-tlv*` options, must appear **before**
|
||||
the positional `image key version` arguments.
|
||||
Note: all options, including `--cmdline`, `--dts`, and the `--custom-tlv*` options, must appear
|
||||
**before** the positional `image key version` arguments.
|
||||
|
||||
#### Three-steps signing using external provisioning tools
|
||||
|
||||
|
|
|
|||
|
|
@ -4332,7 +4332,7 @@ Key options in `config/examples/zynq7000.config`:
|
|||
- `MMU=1 ELF=1` - lets the same image boot Linux or bare-metal. `do_boot` always emits the ARM Linux boot ABI (`r0=0`, `r1=~0`, `r2=DTB_phys`, `r3=0`) on this target, which bare-metal apps simply ignore. `MMU=1` enables `update_ram.c`'s DTB-load codepath and pulls in `src/fdt.o`; wolfBoot does not manage page tables (it inherits FSBL's flat 1:1 DDR mapping). `ELF=1` lets wolfBoot understand ELF inputs (e.g. `vmlinux`) and load only their LOAD segments. Cost over a strictly bare-metal-only build: ~5 KB extra wolfBoot binary (31 KB -> 36 KB).
|
||||
- `EXT_FLASH=1` - QSPI as external flash via `XQspiPs`
|
||||
- `WOLFBOOT_LOAD_ADDRESS=0x10000000` - DDR offset 256 MB, where the verified app is staged before `do_boot`. Must be **above** wolfBoot's own region (`0x04000000`-`0x040FFFFF`) because `src/update_ram.c` enforces `dst > _end`.
|
||||
- `WOLFBOOT_LOAD_DTS_ADDRESS=0x11000000` - DDR offset 272 MB, where a DTB read out of `PART_DTS_BOOT` would be relocated. Ignored for bare-metal payloads and for the appended-DTB Linux flow (where the DTB lives at the end of the signed kernel image).
|
||||
- `WOLFBOOT_LOAD_DTS_ADDRESS=0x11000000` - DDR offset 272 MB, where a DTB read out of `PART_DTS_BOOT` would be relocated. Ignored for bare-metal payloads and for the appended-DTB Linux flow (where the DTB lives at the end of the signed kernel image). A DTB loaded this way (raw `PART_DTS_BOOT` or `hal_get_dts_address()`, i.e. not inside a signed FIT) is authenticated against the boot image's `HDR_DEVICE_TREE_DIGEST` TLV before the kernel sees it: sign the kernel with `sign --dts <board.dtb>` (see `docs/Signing.md`). When the image carries the digest it is always verified and a mismatch panics. A raw DTB with no digest only warns and boots by default (backward compatible); build with `WOLFBOOT_REQUIRE_SIGNED_DTB=1` to make a missing digest a hard failure once every raw-DTB payload is signed with `--dts`.
|
||||
- `WOLFBOOT_PARTITION_BOOT_ADDRESS=0x00100000` - 16 MB QSPI layout below
|
||||
- `CROSS_COMPILE=arm-none-eabi-`
|
||||
|
||||
|
|
|
|||
|
|
@ -280,6 +280,10 @@ downgrades, compile with `ALLOW_DOWNGRADE=1`.
|
|||
Warning: this option will disable version checking before the updates, thus exposing the system to potential
|
||||
forced downgrade attacks.
|
||||
|
||||
### Require an authenticated device tree (raw-DTB targets)
|
||||
|
||||
On non-FIT MMU targets that load a raw device tree from flash, wolfBoot authenticates the DTB against the `HDR_DEVICE_TREE_DIGEST` TLV bound to the signed kernel (`sign --dts <board.dtb>`, see `docs/Signing.md`). A DTB carrying the digest is always verified; a raw DTB with no digest only warns and boots by default. Compile with `WOLFBOOT_REQUIRE_SIGNED_DTB=1` to make a missing digest a hard failure (fail-closed) once every raw-DTB payload is signed with `--dts`.
|
||||
|
||||
### Enable optional support for external flash memory
|
||||
|
||||
WolfBoot can be compiled with the makefile option `EXT_FLASH=1`. When the external flash support is
|
||||
|
|
|
|||
|
|
@ -2591,7 +2591,12 @@ uint64_t hal_get_timer_us(void)
|
|||
|
||||
void* hal_get_dts_address(void)
|
||||
{
|
||||
#ifdef WOLFBOOT_DTS_BOOT_ADDRESS
|
||||
#ifdef NO_XIP
|
||||
/* QSPI is not memory-mapped: WOLFBOOT_DTS_BOOT_ADDRESS is a flash offset,
|
||||
* not a CPU-dereferenceable address. Return NULL so update_ram.c reads and
|
||||
* authenticates the DTB from external flash instead of dereferencing it. */
|
||||
return NULL;
|
||||
#elif defined(WOLFBOOT_DTS_BOOT_ADDRESS)
|
||||
return (void*)WOLFBOOT_DTS_BOOT_ADDRESS;
|
||||
#elif defined(WOLFBOOT_LOAD_DTS_ADDRESS)
|
||||
return (void*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
||||
|
|
|
|||
|
|
@ -826,9 +826,10 @@ void ext_flash_unlock(void) { }
|
|||
|
||||
#ifdef MMU
|
||||
/* Memory-mapped DTB fallback. Not used on Zynq-7000 with EXT_FLASH=1: the
|
||||
* signed DTB is opened as PART_DTS_BOOT in update_ram.c and read out of
|
||||
* QSPI via ext_flash_check_read. Return NULL so the fallback path is a
|
||||
* no-op when the DTS partition is missing. */
|
||||
* DTB is opened as PART_DTS_BOOT in update_ram.c and read out of QSPI via
|
||||
* ext_flash_check_read, then authenticated against the boot image's
|
||||
* HDR_DEVICE_TREE_DIGEST TLV before the kernel sees it. Return NULL so the
|
||||
* fallback path is a no-op when the DTS partition is missing. */
|
||||
void *hal_get_dts_address(void)
|
||||
{
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -1712,6 +1712,11 @@ int image_CT_compare(const uint8_t *expected, const uint8_t *actual,
|
|||
uint32_t len);
|
||||
int wolfBoot_hardened_CT_compare(const uint8_t *expected, const uint8_t *actual,
|
||||
uint32_t len);
|
||||
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
||||
int wolfBoot_get_dts_size(void *dts_addr);
|
||||
int wolfBoot_verify_dts_digest(const uint8_t *expected_digest,
|
||||
const void *dts_addr, uint32_t dts_size);
|
||||
#endif
|
||||
int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part);
|
||||
#ifdef EXT_FLASH
|
||||
int wolfBoot_open_image_external(struct wolfBoot_image* img, uint8_t part, uint8_t* addr);
|
||||
|
|
|
|||
|
|
@ -239,6 +239,9 @@ extern "C" {
|
|||
* (0x0030-0xFEFE). Signature-covered, unlike an external cmdline file; consumed
|
||||
* by the EFI targets and passed to the kernel via LoadOptions. */
|
||||
#define HDR_CMDLINE 0x0034
|
||||
/* Signature-covered digest of a raw (non-FIT) device tree, binding it to this
|
||||
* image. Length = image hash size (WOLFBOOT_SHA_DIGEST_SIZE). */
|
||||
#define HDR_DEVICE_TREE_DIGEST 0x35
|
||||
#define HDR_PADDING 0xFF
|
||||
|
||||
/* Auth Key types */
|
||||
|
|
|
|||
10
options.mk
10
options.mk
|
|
@ -919,6 +919,16 @@ ifeq ($(ALLOW_DOWNGRADE),1)
|
|||
CFLAGS+= -D"ALLOW_DOWNGRADE"
|
||||
endif
|
||||
|
||||
# Raw (non-FIT) device tree authentication (see docs/Signing.md '--dts'). A
|
||||
# device tree bound to the image via HDR_DEVICE_TREE_DIGEST is always verified;
|
||||
# WOLFBOOT_REQUIRE_SIGNED_DTB additionally makes a missing digest a hard failure
|
||||
# (fail-closed) instead of a warning, once every raw-DTB payload is signed with
|
||||
# 'sign --dts'.
|
||||
ifeq ($(WOLFBOOT_REQUIRE_SIGNED_DTB),1)
|
||||
$(warning WOLFBOOT_REQUIRE_SIGNED_DTB=1 makes wolfBoot panic on a raw device tree that carries no authenticated HDR_DEVICE_TREE_DIGEST; sign every raw DTB payload with 'sign --dts' first or the target will not boot)
|
||||
CFLAGS+= -D"WOLFBOOT_REQUIRE_SIGNED_DTB"
|
||||
endif
|
||||
|
||||
ifeq ($(WOLFBOOT_SKIP_BOOT_VERIFY),1)
|
||||
ifneq ($(WOLFBOOT_SELF_HEADER),1)
|
||||
$(error WOLFBOOT_SKIP_BOOT_VERIFY=1 requires WOLFBOOT_SELF_HEADER=1)
|
||||
|
|
|
|||
86
src/image.c
86
src/image.c
|
|
@ -1549,6 +1549,92 @@ int wolfBoot_get_dts_size(void *dts_addr)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Hash a raw buffer with the configured image hash (explicit per-algorithm API,
|
||||
* since the generic update_hash macro's SHA3 mapping is wrong). 0 on success. */
|
||||
static int wolfBoot_hash_buffer(const void *buf, uint32_t len, uint8_t *out)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)buf;
|
||||
int ret;
|
||||
|
||||
#if defined(WOLFBOOT_HASH_SHA256)
|
||||
wc_Sha256 ctx;
|
||||
ret = wc_InitSha256_ex(&ctx, NULL, WOLFBOOT_DEVID_HASH);
|
||||
if (ret == 0) {
|
||||
while (len > 0) {
|
||||
uint32_t sz = (len < WOLFBOOT_SHA_BLOCK_SIZE) ?
|
||||
len : (uint32_t)WOLFBOOT_SHA_BLOCK_SIZE;
|
||||
ret = wc_Sha256Update(&ctx, p, sz);
|
||||
if (ret != 0)
|
||||
break;
|
||||
p += sz;
|
||||
len -= sz;
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = wc_Sha256Final(&ctx, out);
|
||||
wc_Sha256Free(&ctx);
|
||||
}
|
||||
#elif defined(WOLFBOOT_HASH_SHA384)
|
||||
wc_Sha384 ctx;
|
||||
ret = wc_InitSha384_ex(&ctx, NULL, WOLFBOOT_DEVID_HASH);
|
||||
if (ret == 0) {
|
||||
while (len > 0) {
|
||||
uint32_t sz = (len < WOLFBOOT_SHA_BLOCK_SIZE) ?
|
||||
len : (uint32_t)WOLFBOOT_SHA_BLOCK_SIZE;
|
||||
ret = wc_Sha384Update(&ctx, p, sz);
|
||||
if (ret != 0)
|
||||
break;
|
||||
p += sz;
|
||||
len -= sz;
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = wc_Sha384Final(&ctx, out);
|
||||
wc_Sha384Free(&ctx);
|
||||
}
|
||||
#elif defined(WOLFBOOT_HASH_SHA3_384)
|
||||
wc_Sha3 ctx;
|
||||
ret = wc_InitSha3_384(&ctx, NULL, WOLFBOOT_DEVID_HASH);
|
||||
if (ret == 0) {
|
||||
while (len > 0) {
|
||||
uint32_t sz = (len < WOLFBOOT_SHA_BLOCK_SIZE) ?
|
||||
len : (uint32_t)WOLFBOOT_SHA_BLOCK_SIZE;
|
||||
ret = wc_Sha3_384_Update(&ctx, p, sz);
|
||||
if (ret != 0)
|
||||
break;
|
||||
p += sz;
|
||||
len -= sz;
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = wc_Sha3_384_Final(&ctx, out);
|
||||
wc_Sha3_384_Free(&ctx);
|
||||
}
|
||||
#else
|
||||
(void)p;
|
||||
ret = -1;
|
||||
#endif
|
||||
return (ret == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Verify a raw DTB against a firmware-bound digest (from the image's
|
||||
* HDR_DEVICE_TREE_DIGEST TLV, captured by the caller since the load may reuse
|
||||
* the image struct). Returns 0 on match, -1 on mismatch/bad args/hash error. */
|
||||
int wolfBoot_verify_dts_digest(const uint8_t *expected_digest,
|
||||
const void *dts_addr, uint32_t dts_size)
|
||||
{
|
||||
uint8_t calc[WOLFBOOT_SHA_DIGEST_SIZE];
|
||||
|
||||
if (expected_digest == NULL || dts_addr == NULL || dts_size == 0)
|
||||
return -1;
|
||||
|
||||
if (wolfBoot_hash_buffer(dts_addr, dts_size, calc) != 0)
|
||||
return -1;
|
||||
|
||||
if (wolfBoot_hardened_CT_compare(expected_digest, calc,
|
||||
WOLFBOOT_SHA_DIGEST_SIZE) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* MMU || WOLFBOOT_FDT */
|
||||
|
||||
#ifdef WOLFBOOT_FIXED_PARTITIONS
|
||||
|
|
|
|||
131
src/update_ram.c
131
src/update_ram.c
|
|
@ -46,11 +46,22 @@
|
|||
#endif
|
||||
|
||||
extern void hal_flash_dualbank_swap(void);
|
||||
extern int wolfBoot_get_dts_size(void *dts_addr);
|
||||
/* DTS helpers declared in include/image.h under (MMU || WOLFBOOT_FDT). */
|
||||
|
||||
extern uint32_t kernel_load_addr;
|
||||
extern uint32_t dts_load_addr;
|
||||
|
||||
#if defined(MMU) || defined(WOLFBOOT_FDT)
|
||||
/* Bounds for the attacker-influenced fdt_totalsize before relocating a DTB.
|
||||
* MIN is the FDT v17 header size (also enforced by the signer): fdt_check_header
|
||||
* validates magic/version but not totalsize, so a crafted header with a tiny
|
||||
* totalsize must be rejected rather than loaded/forwarded as a partial tree. */
|
||||
#ifndef WOLFBOOT_DTS_MAX_SIZE
|
||||
#define WOLFBOOT_DTS_MAX_SIZE (1024U * 1024U)
|
||||
#endif
|
||||
#define WOLFBOOT_DTS_MIN_SIZE (40U)
|
||||
#endif
|
||||
|
||||
#if defined(__WOLFBOOT) && defined(WOLFBOOT_LOAD_ADDRESS)
|
||||
extern uint8_t _end[]; /* linker symbol: end of wolfBoot BSS */
|
||||
#endif
|
||||
|
|
@ -292,6 +303,14 @@ void RAMFUNCTION wolfBoot_start(void)
|
|||
#endif
|
||||
#ifdef MMU
|
||||
uint32_t dts_size = 0;
|
||||
/* HDR_DEVICE_TREE_DIGEST snapshot, taken before the raw DTB is loaded. */
|
||||
uint8_t dts_digest[WOLFBOOT_SHA_DIGEST_SIZE];
|
||||
uint8_t *dts_tlv = NULL;
|
||||
uint16_t dts_tlv_len = 0;
|
||||
int dts_digest_present = 0; /* 0 absent, 1 valid, -1 malformed */
|
||||
#if defined(EXT_FLASH) && defined(WOLFBOOT_DTS_BOOT_ADDRESS)
|
||||
uint8_t dts_hdr[64]; /* FDT header peek (fdt_check_header needs >= 40) */
|
||||
#endif
|
||||
#endif
|
||||
#if defined(WOLFBOOT_ZYNQMP_FSBL) && defined(MMU)
|
||||
/* When wolfBoot is the FSBL, the boot FIT carries an "atf" (BL31)
|
||||
|
|
@ -573,6 +592,22 @@ backup_on_failure:
|
|||
#endif
|
||||
|
||||
#ifdef MMU
|
||||
/* Snapshot the digest from the verified header before os_image can be
|
||||
* reused for the DTS partition below. (FIT DTBs are covered by the FIT.) */
|
||||
dts_tlv_len = wolfBoot_get_header(&os_image, HDR_DEVICE_TREE_DIGEST,
|
||||
&dts_tlv);
|
||||
if (dts_tlv_len != 0 && dts_tlv != NULL) {
|
||||
if (dts_tlv_len == WOLFBOOT_SHA_DIGEST_SIZE) {
|
||||
memcpy(dts_digest, dts_tlv, WOLFBOOT_SHA_DIGEST_SIZE);
|
||||
dts_digest_present = 1; /* present and well-formed */
|
||||
}
|
||||
else {
|
||||
/* A present-but-malformed digest TLV must not silently downgrade
|
||||
* to an unauthenticated DTB boot; treat it as a hard failure. */
|
||||
dts_digest_present = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Is this a Flattened uImage Tree (FIT) image (FDT format) */
|
||||
if (wolfBoot_get_dts_size(load_address) > 0) {
|
||||
void* fit = (void*)load_address;
|
||||
|
|
@ -638,39 +673,77 @@ backup_on_failure:
|
|||
#endif
|
||||
}
|
||||
else {
|
||||
/* Load DTS to RAM */
|
||||
#ifdef EXT_FLASH
|
||||
if (PART_IS_EXT(&os_image) &&
|
||||
wolfBoot_open_image(&os_image, PART_DTS_BOOT) >= 0) {
|
||||
dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
||||
dts_size = (uint32_t)os_image.fw_size;
|
||||
|
||||
wolfBoot_printf("Loading DTS (size %lu) to RAM at %08lx\n",
|
||||
(long unsigned int)dts_size, (long unsigned int)dts_addr);
|
||||
ext_flash_check_read((uintptr_t)os_image.fw_base,
|
||||
(uint8_t*)dts_addr, dts_size);
|
||||
/* Prefer the HAL's memory-mapped DTB (unchanged for XIP targets); fall
|
||||
* back to external flash at WOLFBOOT_DTS_BOOT_ADDRESS when the HAL has
|
||||
* no usable address (NULL, or a flash offset on NO_XIP targets). */
|
||||
dts_addr = hal_get_dts_address();
|
||||
if (dts_addr != NULL) {
|
||||
ret = wolfBoot_get_dts_size(dts_addr);
|
||||
if (ret < (int)WOLFBOOT_DTS_MIN_SIZE ||
|
||||
(uint32_t)ret > WOLFBOOT_DTS_MAX_SIZE) {
|
||||
wolfBoot_printf("DTB parse/size check failed - ignoring\n");
|
||||
dts_addr = NULL; /* never forward an unvalidated address */
|
||||
}
|
||||
else {
|
||||
dts_size = (uint32_t)ret;
|
||||
memcpy((void*)WOLFBOOT_LOAD_DTS_ADDRESS, dts_addr, dts_size);
|
||||
dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif /* EXT_FLASH */
|
||||
{
|
||||
dts_addr = hal_get_dts_address();
|
||||
if (dts_addr) {
|
||||
ret = wolfBoot_get_dts_size(dts_addr);
|
||||
if (ret < 0) {
|
||||
wolfBoot_printf("Failed parsing DTB to load\n");
|
||||
/* Allow failure, continue booting */
|
||||
}
|
||||
else {
|
||||
/* relocate DTS to RAM */
|
||||
uint8_t* dts_dst = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
||||
#if defined(EXT_FLASH) && defined(WOLFBOOT_DTS_BOOT_ADDRESS)
|
||||
if (dts_addr == NULL) {
|
||||
/* Peek the FDT header for the size, clamp it, then read the body.
|
||||
* Each ext_flash_read length is checked so a short/failed read
|
||||
* never yields a partial or oversized tree. */
|
||||
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 >= (int)WOLFBOOT_DTS_MIN_SIZE &&
|
||||
(uint32_t)ret <= WOLFBOOT_DTS_MAX_SIZE) {
|
||||
dts_size = (uint32_t)ret;
|
||||
wolfBoot_printf("Loading DTB (size %d) from %p to RAM at %p\n",
|
||||
dts_size, dts_addr, (void*)WOLFBOOT_LOAD_DTS_ADDRESS);
|
||||
memcpy(dts_dst, dts_addr, dts_size);
|
||||
dts_addr = dts_dst;
|
||||
if (ext_flash_read((uintptr_t)WOLFBOOT_DTS_BOOT_ADDRESS,
|
||||
(uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS, (int)dts_size)
|
||||
== (int)dts_size)
|
||||
dts_addr = (uint8_t*)WOLFBOOT_LOAD_DTS_ADDRESS;
|
||||
else
|
||||
dts_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* EXT_FLASH && WOLFBOOT_DTS_BOOT_ADDRESS */
|
||||
|
||||
/* Authenticate the raw DTB before boot. dts_size == 0 with a non-NULL
|
||||
* address (e.g. a zeroed fdt totalsize) is rejected. A bound digest is
|
||||
* always enforced; a missing one only panics under
|
||||
* WOLFBOOT_REQUIRE_SIGNED_DTB, so unsigned raw-DTB targets keep booting
|
||||
* until they adopt 'sign --dts'. */
|
||||
if (dts_addr != NULL) {
|
||||
if (dts_size == 0) {
|
||||
wolfBoot_printf("DTB has zero size - rejecting\n");
|
||||
wolfBoot_panic();
|
||||
}
|
||||
if (dts_digest_present == 1) {
|
||||
if (wolfBoot_verify_dts_digest(dts_digest, dts_addr, dts_size)
|
||||
!= 0) {
|
||||
wolfBoot_printf("DTB digest mismatch - rejecting\n");
|
||||
wolfBoot_panic();
|
||||
}
|
||||
wolfBoot_printf("DTB digest verified\n");
|
||||
}
|
||||
else if (dts_digest_present < 0) {
|
||||
wolfBoot_printf("Malformed DTB digest TLV - rejecting\n");
|
||||
wolfBoot_panic();
|
||||
}
|
||||
else {
|
||||
#ifdef WOLFBOOT_REQUIRE_SIGNED_DTB
|
||||
wolfBoot_printf("No DTB digest - rejecting\n");
|
||||
wolfBoot_panic();
|
||||
#else
|
||||
wolfBoot_printf("Warning: DTB not authenticated (sign --dts)\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* MMU */
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,8 @@ static inline int fp_truncate(FILE *f, size_t len)
|
|||
#define HDR_POLICY_SIGNATURE 0x21
|
||||
#define HDR_SECONDARY_SIGNATURE 0x22
|
||||
#define HDR_CERT_CHAIN 0x23
|
||||
/* HDR_DEVICE_TREE_DIGEST comes from wolfboot/wolfboot.h (included above),
|
||||
* matching how HDR_CMDLINE is consumed. */
|
||||
|
||||
|
||||
#define HDR_SHA256_LEN 32
|
||||
|
|
@ -389,6 +391,7 @@ struct cmd_options {
|
|||
const char *encrypt_key_file;
|
||||
const char *delta_base_file;
|
||||
const char *cert_chain_file;
|
||||
const char *dts_file;
|
||||
int no_base_sha;
|
||||
char output_image_file[PATH_MAX];
|
||||
char output_diff_file[PATH_MAX];
|
||||
|
|
@ -1333,6 +1336,147 @@ static uint32_t header_digest_size(int hash_algo)
|
|||
}
|
||||
}
|
||||
|
||||
/* Hash the first fdt_totalsize bytes of a DTB with the image hash algorithm
|
||||
* (the same span the bootloader hashes). Applies at least fdt_check_header()'s
|
||||
* checks (magic, version range); intentionally stricter. Writes digest to out,
|
||||
* length to out_sz. Returns 0 on success, -1 on error. */
|
||||
static int dts_hash_file(const char *file, int hash_algo, uint8_t *out,
|
||||
uint32_t *out_sz)
|
||||
{
|
||||
/* FDT header (big-endian, 40B): magic@0, totalsize@4, version@0x14,
|
||||
* last_comp_version@0x18. */
|
||||
const uint32_t FDT_MAGIC = 0xd00dfeedU;
|
||||
const uint32_t FDT_HDR_SIZE = 40U;
|
||||
const uint32_t FDT_FIRST_VER = 0x10U;
|
||||
const uint32_t FDT_LAST_COMP_VER = 0x11U;
|
||||
FILE *f;
|
||||
uint8_t hdr[40];
|
||||
uint8_t rbuf[4096];
|
||||
uint32_t magic, total, version, last_comp, remain;
|
||||
long fsz;
|
||||
size_t rd, want;
|
||||
int ret = -1;
|
||||
|
||||
f = fopen(file, "rb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "Cannot open device tree file %s: %s\n",
|
||||
file, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fread(hdr, 1, sizeof(hdr), f) != sizeof(hdr)) {
|
||||
fprintf(stderr, "Device tree file %s too small for an FDT header\n",
|
||||
file);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
magic = ((uint32_t)hdr[0] << 24) | ((uint32_t)hdr[1] << 16) |
|
||||
((uint32_t)hdr[2] << 8) | (uint32_t)hdr[3];
|
||||
total = ((uint32_t)hdr[4] << 24) | ((uint32_t)hdr[5] << 16) |
|
||||
((uint32_t)hdr[6] << 8) | (uint32_t)hdr[7];
|
||||
version = ((uint32_t)hdr[0x14] << 24) | ((uint32_t)hdr[0x15] << 16) |
|
||||
((uint32_t)hdr[0x16] << 8) | (uint32_t)hdr[0x17];
|
||||
last_comp = ((uint32_t)hdr[0x18] << 24) | ((uint32_t)hdr[0x19] << 16) |
|
||||
((uint32_t)hdr[0x1A] << 8) | (uint32_t)hdr[0x1B];
|
||||
if (magic != FDT_MAGIC) {
|
||||
fprintf(stderr, "Not a valid device tree (bad FDT magic): %s\n", file);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (total < FDT_HDR_SIZE || version < FDT_FIRST_VER ||
|
||||
last_comp > FDT_LAST_COMP_VER) {
|
||||
fprintf(stderr, "Unsupported device tree (version %u, comp %u, "
|
||||
"totalsize %u): %s -- the bootloader would reject it\n",
|
||||
version, last_comp, total, file);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
/* A file shorter than fdt_totalsize can't be hashed as declared (reject);
|
||||
* a longer one (trailing padding) is hashed over the declared span (warn). */
|
||||
fseek(f, 0, SEEK_END);
|
||||
fsz = ftell(f);
|
||||
if (fsz >= 0 && (uint32_t)fsz < total) {
|
||||
fprintf(stderr, "Device tree file %s is %ld bytes but its FDT totalsize "
|
||||
"is %u (truncated)\n", file, fsz, total);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (fsz >= 0 && (uint32_t)fsz != total) {
|
||||
fprintf(stderr, "Warning: device tree file %s is %ld bytes but its FDT "
|
||||
"totalsize is %u; hashing the first %u bytes\n",
|
||||
file, fsz, total, total);
|
||||
}
|
||||
if (fseek(f, 0, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
remain = total;
|
||||
|
||||
if (hash_algo == HASH_SHA256) {
|
||||
#ifndef NO_SHA256
|
||||
wc_Sha256 sha;
|
||||
ret = wc_InitSha256_ex(&sha, NULL, INVALID_DEVID);
|
||||
if (ret == 0) {
|
||||
while (remain > 0) {
|
||||
want = (remain < sizeof(rbuf)) ? remain : sizeof(rbuf);
|
||||
rd = fread(rbuf, 1, want, f);
|
||||
if (rd == 0) { ret = -1; break; }
|
||||
ret = wc_Sha256Update(&sha, rbuf, (word32)rd);
|
||||
if (ret != 0) break;
|
||||
remain -= (uint32_t)rd;
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = wc_Sha256Final(&sha, out);
|
||||
wc_Sha256Free(&sha);
|
||||
}
|
||||
*out_sz = HDR_SHA256_LEN;
|
||||
#endif
|
||||
}
|
||||
else if (hash_algo == HASH_SHA384) {
|
||||
#ifndef NO_SHA384
|
||||
wc_Sha384 sha;
|
||||
ret = wc_InitSha384_ex(&sha, NULL, INVALID_DEVID);
|
||||
if (ret == 0) {
|
||||
while (remain > 0) {
|
||||
want = (remain < sizeof(rbuf)) ? remain : sizeof(rbuf);
|
||||
rd = fread(rbuf, 1, want, f);
|
||||
if (rd == 0) { ret = -1; break; }
|
||||
ret = wc_Sha384Update(&sha, rbuf, (word32)rd);
|
||||
if (ret != 0) break;
|
||||
remain -= (uint32_t)rd;
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = wc_Sha384Final(&sha, out);
|
||||
wc_Sha384Free(&sha);
|
||||
}
|
||||
*out_sz = HDR_SHA384_LEN;
|
||||
#endif
|
||||
}
|
||||
else if (hash_algo == HASH_SHA3) {
|
||||
#ifdef WOLFSSL_SHA3
|
||||
wc_Sha3 sha;
|
||||
ret = wc_InitSha3_384(&sha, NULL, INVALID_DEVID);
|
||||
if (ret == 0) {
|
||||
while (remain > 0) {
|
||||
want = (remain < sizeof(rbuf)) ? remain : sizeof(rbuf);
|
||||
rd = fread(rbuf, 1, want, f);
|
||||
if (rd == 0) { ret = -1; break; }
|
||||
ret = wc_Sha3_384_Update(&sha, rbuf, (word32)rd);
|
||||
if (ret != 0) break;
|
||||
remain -= (uint32_t)rd;
|
||||
}
|
||||
if (ret == 0)
|
||||
ret = wc_Sha3_384_Final(&sha, out);
|
||||
wc_Sha3_384_Free(&sha);
|
||||
}
|
||||
*out_sz = HDR_SHA3_384_LEN;
|
||||
#endif
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t header_required_size(int is_diff, uint32_t cert_chain_sz,
|
||||
uint32_t secondary_key_sz)
|
||||
{
|
||||
|
|
@ -1369,6 +1513,11 @@ static uint32_t header_required_size(int is_diff, uint32_t cert_chain_sz,
|
|||
header_size_append_tag(&idx, CMD.custom_tlv[i].len);
|
||||
}
|
||||
|
||||
if (CMD.dts_file != NULL && digest_sz > 0U) {
|
||||
header_size_align_8(&idx);
|
||||
header_size_append_tag(&idx, digest_sz);
|
||||
}
|
||||
|
||||
if (cert_chain_sz > 0U) {
|
||||
header_size_align_8(&idx);
|
||||
header_size_append_tag(&idx, cert_chain_sz);
|
||||
|
|
@ -1446,7 +1595,8 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
|
|||
|
||||
/* Check certificate chain file size before allocating header, and adjust
|
||||
* header size if needed */
|
||||
if ((CMD.cert_chain_file != NULL) || (CMD.custom_tlvs > 0)) {
|
||||
if ((CMD.cert_chain_file != NULL) || (CMD.custom_tlvs > 0) ||
|
||||
(CMD.dts_file != NULL)) {
|
||||
uint32_t hdr_cert_chain_sz = 0;
|
||||
uint32_t required_space;
|
||||
|
||||
|
|
@ -1517,7 +1667,7 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
|
|||
image_sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fclose(f);
|
||||
f = NULL;
|
||||
f = NULL; /* avoid a double fclose() if a later step jumps to 'failure' */
|
||||
|
||||
/* Append Magic header (spells 'WOLF') */
|
||||
header_append_u32(header, &header_idx, WOLFBOOT_MAGIC);
|
||||
|
|
@ -1626,6 +1776,21 @@ static int make_header_ex(int is_diff, uint8_t *pubkey, uint32_t pubkey_sz,
|
|||
}
|
||||
}
|
||||
|
||||
/* Signature-covered digest of a raw (non-FIT) device tree. */
|
||||
if (CMD.dts_file != NULL) {
|
||||
uint8_t dts_digest[48]; /* max digest */
|
||||
uint32_t dts_digest_sz = 0;
|
||||
if (dts_hash_file(CMD.dts_file, CMD.hash_algo, dts_digest,
|
||||
&dts_digest_sz) != 0 || dts_digest_sz == 0) {
|
||||
printf("Error hashing device tree file %s\n", CMD.dts_file);
|
||||
goto failure;
|
||||
}
|
||||
ALIGN_8(header_idx);
|
||||
header_append_tag(header, &header_idx, HDR_DEVICE_TREE_DIGEST,
|
||||
(uint16_t)dts_digest_sz, dts_digest);
|
||||
printf("Device tree digest (%u bytes) bound to image\n", dts_digest_sz);
|
||||
}
|
||||
|
||||
/* Read certificate chain if provided */
|
||||
if (CMD.cert_chain_file != NULL) {
|
||||
const size_t cert_chain_tlv_hdr_sz = 4;
|
||||
|
|
@ -3447,6 +3612,14 @@ int main(int argc, char** argv)
|
|||
CMD.custom_tlv[p].buffer = NULL;
|
||||
CMD.custom_tlvs++;
|
||||
i += 3;
|
||||
} else if (strcmp(argv[i], "--dts") == 0) {
|
||||
if (argc < (i + 2)) {
|
||||
fprintf(stderr, "Missing device tree file for --dts.\n");
|
||||
exit(16);
|
||||
}
|
||||
/* Hashed in make_header_ex() once the hash algo is known. */
|
||||
CMD.dts_file = argv[i + 1];
|
||||
i += 1;
|
||||
} else if (strcmp(argv[i], "--custom-tlv-buffer") == 0) {
|
||||
int p = CMD.custom_tlvs;
|
||||
uint16_t tag, len;
|
||||
|
|
@ -3705,6 +3878,13 @@ int main(int argc, char** argv)
|
|||
CMD.fw_version = argv[i+2];
|
||||
}
|
||||
|
||||
/* --dts can grow the header after patch_inv_off is computed, so the delta
|
||||
* inverse offset would be stale. Reject the combination. */
|
||||
if (CMD.dts_file != NULL && CMD.delta) {
|
||||
fprintf(stderr, "Error: --dts cannot be combined with --delta\n");
|
||||
exit(16);
|
||||
}
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
strncpy((char*)buf, CMD.image_file, sizeof(buf)-1);
|
||||
tmpstr = strrchr((char*)buf, '.');
|
||||
|
|
|
|||
|
|
@ -113,11 +113,15 @@ else
|
|||
exit 1
|
||||
fi
|
||||
|
||||
echo "Mode : RAW DTB (zImage signed alone, DTB staged separately at PART_DTS_BOOT)"
|
||||
echo "Mode : RAW DTB (zImage signed alone, DTB staged separately in flash"
|
||||
echo " at WOLFBOOT_DTS_BOOT_ADDRESS)"
|
||||
echo "zImage: $ZIMAGE ($KSIZE bytes)"
|
||||
echo "DTB : $DTB ($DSIZE bytes)"
|
||||
echo "Signing kernel as PART_BOOT v$VERSION ..."
|
||||
$SIGN_TOOL --ecc256 --sha256 "$ZIMAGE" "$KEY" "$VERSION"
|
||||
# --dts binds the raw DTB's digest to the signed kernel (HDR_DEVICE_TREE_DIGEST),
|
||||
# so wolfBoot authenticates the device tree before booting. Build wolfBoot with
|
||||
# WOLFBOOT_REQUIRE_SIGNED_DTB=1 to make a missing digest fatal.
|
||||
$SIGN_TOOL --ecc256 --sha256 --dts "$DTB" "$ZIMAGE" "$KEY" "$VERSION"
|
||||
|
||||
SIGNED_OUT="${ZIMAGE%.*}_v${VERSION}_signed.bin"
|
||||
[ -f "$SIGNED_OUT" ] || SIGNED_OUT="${ZIMAGE}_v${VERSION}_signed.bin"
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128
|
|||
unit-update-flash-self-update \
|
||||
unit-update-flash-enc unit-update-ram unit-update-ram-uboot unit-update-ram-enc unit-update-ram-enc-nopart unit-update-ram-nofixed unit-update-ram-noramboot unit-update-flash-hwswap unit-pkcs11_store unit-psa_store unit-wolfhsm_flash_hal unit-disk \
|
||||
unit-update-disk unit-update-disk-oob unit-update-disk-fit unit-multiboot unit-boot-x86-fsp unit-loader-tpm-init unit-qspi-flash unit-fwtpm-stub unit-tpm-rsa-exp \
|
||||
unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-store-sbrk \
|
||||
unit-image-nopart unit-image-sha384 unit-image-sha3-384 unit-image-dts \
|
||||
unit-image-dts-sha384 unit-image-dts-sha3-384 unit-store-sbrk \
|
||||
unit-tpm-blob unit-policy-create unit-policy-sign unit-rot-auth unit-sdhci-response-bits \
|
||||
unit-sdhci-disk-unaligned unit-sign-encrypted-output \
|
||||
unit-sign-hybrid-keyload \
|
||||
|
|
@ -147,6 +148,7 @@ run: $(TESTS)
|
|||
python3 unit-sign-custom-tlv-le.py || exit 1
|
||||
python3 unit-sign-custom-tlv-large.py || exit 1
|
||||
python3 unit-sign-custom-tlv-pubkey-der.py || exit 1
|
||||
python3 unit-sign-dts.py || exit 1
|
||||
|
||||
|
||||
WOLFCRYPT_SRC:=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha.c \
|
||||
|
|
@ -579,6 +581,26 @@ unit-image-sha3-384: ../../include/target.h unit-image.c unit-common.c
|
|||
$(CFLAGS) $(WOLFCRYPT_CFLAGS) -DUNIT_IMAGE_KEYHASH_ONLY \
|
||||
-DWOLFBOOT_HASH_SHA3_384 $(LDFLAGS)
|
||||
|
||||
# Exercises the raw-DTB authentication helper wolfBoot_verify_dts_digest()
|
||||
# (Fenrir #7998). WOLFBOOT_FDT compiles the DTS helpers in image.c, which pull
|
||||
# in fdt.c for wolfBoot_get_dts_size(). The sha384/sha3-384 variants cover the
|
||||
# corresponding wolfBoot_hash_buffer() branches used by real MMU targets.
|
||||
unit-image-dts: ../../include/target.h unit-image.c unit-common.c $(WOLFCRYPT_SRC)
|
||||
gcc -o $@ unit-image.c unit-common.c $(WOLFCRYPT_SRC) ../../src/fdt.c \
|
||||
$(CFLAGS) $(WOLFCRYPT_CFLAGS) -DWOLFBOOT_FDT $(LDFLAGS)
|
||||
|
||||
unit-image-dts-sha384: ../../include/target.h unit-image.c unit-common.c $(WOLFCRYPT_SRC)
|
||||
gcc -o $@ unit-image.c unit-common.c $(WOLFCRYPT_SRC) ../../src/fdt.c \
|
||||
$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha512.c \
|
||||
$(CFLAGS) $(WOLFCRYPT_CFLAGS) -DWOLFBOOT_FDT -DUNIT_IMAGE_DTS_ONLY \
|
||||
-DWOLFBOOT_HASH_SHA384 $(LDFLAGS)
|
||||
|
||||
unit-image-dts-sha3-384: ../../include/target.h unit-image.c unit-common.c $(WOLFCRYPT_SRC)
|
||||
gcc -o $@ unit-image.c unit-common.c $(WOLFCRYPT_SRC) ../../src/fdt.c \
|
||||
$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha3.c \
|
||||
$(CFLAGS) $(WOLFCRYPT_CFLAGS) -DWOLFBOOT_FDT -DUNIT_IMAGE_DTS_ONLY \
|
||||
-DWOLFBOOT_HASH_SHA3_384 $(LDFLAGS)
|
||||
|
||||
unit-image-rsa: CFLAGS += -DWOLFBOOT_SIGN_RSA2048
|
||||
unit-image-rsa: ../../include/target.h unit-image.c unit-common.c
|
||||
gcc -o $@ unit-image.c unit-common.c $(WOLFCRYPT_SRC) \
|
||||
|
|
|
|||
|
|
@ -1105,6 +1105,74 @@ START_TEST(test_open_image_address_without_partitions_rejects_oversized_fw_size)
|
|||
END_TEST
|
||||
#endif
|
||||
|
||||
#if defined(WOLFBOOT_FDT) || defined(MMU)
|
||||
/* Exercises wolfBoot_verify_dts_digest(), the raw-DTB authentication used by
|
||||
* the non-FIT MMU boot path in src/update_ram.c (Fenrir #7998). The reference
|
||||
* digest is computed with whichever WOLFBOOT_HASH the build selected, so the
|
||||
* SHA256, SHA384 and SHA3-384 variants of wolfBoot_hash_buffer() are all
|
||||
* covered (see the unit-image-dts* Makefile targets). */
|
||||
START_TEST(test_verify_dts_digest)
|
||||
{
|
||||
uint8_t dtb[256];
|
||||
uint8_t tampered[256];
|
||||
uint8_t good[WOLFBOOT_SHA_DIGEST_SIZE];
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < sizeof(dtb); i++)
|
||||
dtb[i] = (uint8_t)(i * 7U + 1U);
|
||||
|
||||
/* Reference digest computed the same way the signer does (image hash over
|
||||
* the DTB bytes), using the configured hash algorithm. */
|
||||
#if defined(WOLFBOOT_HASH_SHA256)
|
||||
{
|
||||
wc_Sha256 sha;
|
||||
ck_assert_int_eq(wc_InitSha256_ex(&sha, NULL, INVALID_DEVID), 0);
|
||||
ck_assert_int_eq(wc_Sha256Update(&sha, dtb, sizeof(dtb)), 0);
|
||||
ck_assert_int_eq(wc_Sha256Final(&sha, good), 0);
|
||||
wc_Sha256Free(&sha);
|
||||
}
|
||||
#elif defined(WOLFBOOT_HASH_SHA384)
|
||||
{
|
||||
wc_Sha384 sha;
|
||||
ck_assert_int_eq(wc_InitSha384_ex(&sha, NULL, INVALID_DEVID), 0);
|
||||
ck_assert_int_eq(wc_Sha384Update(&sha, dtb, sizeof(dtb)), 0);
|
||||
ck_assert_int_eq(wc_Sha384Final(&sha, good), 0);
|
||||
wc_Sha384Free(&sha);
|
||||
}
|
||||
#elif defined(WOLFBOOT_HASH_SHA3_384)
|
||||
{
|
||||
wc_Sha3 sha;
|
||||
ck_assert_int_eq(wc_InitSha3_384(&sha, NULL, INVALID_DEVID), 0);
|
||||
ck_assert_int_eq(wc_Sha3_384_Update(&sha, dtb, sizeof(dtb)), 0);
|
||||
ck_assert_int_eq(wc_Sha3_384_Final(&sha, good), 0);
|
||||
wc_Sha3_384_Free(&sha);
|
||||
}
|
||||
#else
|
||||
#error "test_verify_dts_digest: no supported WOLFBOOT_HASH selected"
|
||||
#endif
|
||||
|
||||
/* Matching digest -> accept. */
|
||||
ck_assert_int_eq(wolfBoot_verify_dts_digest(good, dtb, sizeof(dtb)), 0);
|
||||
|
||||
/* Tampered DTB (single flipped byte) -> reject. */
|
||||
memcpy(tampered, dtb, sizeof(dtb));
|
||||
tampered[100] ^= 0xFFU;
|
||||
ck_assert_int_eq(
|
||||
wolfBoot_verify_dts_digest(good, tampered, sizeof(tampered)), -1);
|
||||
|
||||
/* Wrong expected digest -> reject. */
|
||||
good[0] ^= 0xFFU;
|
||||
ck_assert_int_eq(wolfBoot_verify_dts_digest(good, dtb, sizeof(dtb)), -1);
|
||||
good[0] ^= 0xFFU;
|
||||
|
||||
/* Bad arguments -> reject. */
|
||||
ck_assert_int_eq(wolfBoot_verify_dts_digest(NULL, dtb, sizeof(dtb)), -1);
|
||||
ck_assert_int_eq(wolfBoot_verify_dts_digest(good, NULL, sizeof(dtb)), -1);
|
||||
ck_assert_int_eq(wolfBoot_verify_dts_digest(good, dtb, 0), -1);
|
||||
}
|
||||
END_TEST
|
||||
#endif /* WOLFBOOT_FDT || MMU */
|
||||
|
||||
|
||||
Suite *wolfboot_suite(void)
|
||||
{
|
||||
|
|
@ -1119,6 +1187,16 @@ Suite *wolfboot_suite(void)
|
|||
return s;
|
||||
#endif
|
||||
|
||||
#if defined(UNIT_IMAGE_DTS_ONLY) && (defined(WOLFBOOT_FDT) || defined(MMU))
|
||||
/* Only the raw-DTB digest test. Used by the sha384/sha3-384 variants,
|
||||
* whose non-SHA256 hash config would break the other unit-image tests. */
|
||||
TCase* tcase_dts_only = tcase_create("dts_digest");
|
||||
tcase_set_timeout(tcase_dts_only, 20);
|
||||
tcase_add_test(tcase_dts_only, test_verify_dts_digest);
|
||||
suite_add_tcase(s, tcase_dts_only);
|
||||
return s;
|
||||
#endif
|
||||
|
||||
#ifdef UNIT_IMAGE_HYBRID_ONLY
|
||||
TCase* tcase_hybrid_auth = tcase_create("hybrid_auth");
|
||||
tcase_set_timeout(tcase_hybrid_auth, 20);
|
||||
|
|
@ -1196,6 +1274,13 @@ Suite *wolfboot_suite(void)
|
|||
#endif
|
||||
suite_add_tcase(s, tcase_open_image);
|
||||
#endif
|
||||
|
||||
#if defined(WOLFBOOT_FDT) || defined(MMU)
|
||||
TCase* tcase_dts_digest = tcase_create("dts_digest");
|
||||
tcase_set_timeout(tcase_dts_digest, 20);
|
||||
tcase_add_test(tcase_dts_digest, test_verify_dts_digest);
|
||||
suite_add_tcase(s, tcase_dts_digest);
|
||||
#endif
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
#!/usr/bin/env python3
|
||||
# unit-sign-dts.py
|
||||
#
|
||||
# Regression test for the C sign tool "--dts" device-tree binding
|
||||
# (make_header_ex()/dts_hash_file() in tools/keytools/sign.c, Fenrir #7998).
|
||||
#
|
||||
# Covers:
|
||||
# 1. A valid DTB is bound to the image: the HDR_DEVICE_TREE_DIGEST TLV
|
||||
# (tag 0x35) holds the image-hash (SHA256) of exactly the first
|
||||
# fdt_totalsize bytes of the .dtb.
|
||||
# 2. A bad-magic file is rejected with a clean non-zero exit -- NOT a signal
|
||||
# (the dangling-FILE double free previously aborted with SIGABRT).
|
||||
# 3. A truncated FDT (valid magic, shorter than the 40-byte header) is
|
||||
# rejected cleanly, matching what the bootloader's fdt_check_header()
|
||||
# would reject.
|
||||
#
|
||||
# Copyright (C) 2026 wolfSSL Inc.
|
||||
#
|
||||
# This file is part of wolfBoot.
|
||||
#
|
||||
# wolfBoot is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# wolfBoot is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
HDR_PADDING = 0xFF
|
||||
HDR_DEVICE_TREE_DIGEST = 0x35
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(THIS_DIR, "..", ".."))
|
||||
SIGN = os.path.join(ROOT, "tools", "keytools", "sign")
|
||||
|
||||
|
||||
def skip(msg):
|
||||
print("SKIP unit-sign-dts: " + msg)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def fail(msg):
|
||||
print("FAIL unit-sign-dts: " + msg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def find_tlv_bytes(data, want_type, scan_end):
|
||||
"""Return raw value bytes for a TLV entry (mirrors wolfBoot_find_header)."""
|
||||
p = 8 # skip 4-byte magic + 4-byte image size
|
||||
while p + 4 <= scan_end:
|
||||
htype = data[p] | (data[p + 1] << 8)
|
||||
if htype == 0:
|
||||
break
|
||||
if data[p] == HDR_PADDING or (p & 1) != 0:
|
||||
p += 1
|
||||
continue
|
||||
length = data[p + 2] | (data[p + 3] << 8)
|
||||
if htype == want_type:
|
||||
if p + 4 + length > scan_end:
|
||||
return None
|
||||
return bytes(data[p + 4:p + 4 + length])
|
||||
p += 4 + length
|
||||
return None
|
||||
|
||||
|
||||
def ensure_sign():
|
||||
if os.path.exists(SIGN):
|
||||
return True
|
||||
try:
|
||||
subprocess.run(["make", "sign"],
|
||||
cwd=os.path.join(ROOT, "tools", "keytools"),
|
||||
check=True, capture_output=True, text=True)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return False
|
||||
return os.path.exists(SIGN)
|
||||
|
||||
|
||||
def make_ed25519_key(path):
|
||||
"""Write a 64-byte raw ed25519 key (seed + public) as expected by sign."""
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import \
|
||||
Ed25519PrivateKey
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
except Exception:
|
||||
return False
|
||||
seed = b"\x42" * 32
|
||||
sk = Ed25519PrivateKey.from_private_bytes(seed)
|
||||
pub = sk.public_key().public_bytes(serialization.Encoding.Raw,
|
||||
serialization.PublicFormat.Raw)
|
||||
with open(path, "wb") as f:
|
||||
f.write(seed + pub)
|
||||
return True
|
||||
|
||||
|
||||
def make_fdt(total):
|
||||
"""Build a minimal valid FDT blob of 'total' bytes (v17 header)."""
|
||||
hdr = bytearray(40)
|
||||
struct.pack_into(">I", hdr, 0x00, 0xd00dfeed) # magic
|
||||
struct.pack_into(">I", hdr, 0x04, total) # totalsize
|
||||
struct.pack_into(">I", hdr, 0x08, 0x38) # off_dt_struct
|
||||
struct.pack_into(">I", hdr, 0x0C, 0x38) # off_dt_strings
|
||||
struct.pack_into(">I", hdr, 0x10, 0x28) # off_mem_rsvmap
|
||||
struct.pack_into(">I", hdr, 0x14, 0x11) # version 17
|
||||
struct.pack_into(">I", hdr, 0x18, 0x10) # last_comp_version 16
|
||||
blob = bytes(hdr) + b"\x00" * (total - len(hdr))
|
||||
return blob
|
||||
|
||||
|
||||
# (hash flag, hashlib factory, digest length) for each supported algorithm.
|
||||
HASHES = [
|
||||
("--sha256", hashlib.sha256, 32),
|
||||
("--sha384", hashlib.sha384, 48),
|
||||
("--sha3", (lambda b: hashlib.sha3_384(b)), 48),
|
||||
]
|
||||
|
||||
|
||||
def run_dts(work, key, dtb_path, hash_flag="--sha256", image=None):
|
||||
"""Run 'sign --ed25519 <hash_flag> --dts <dtb> <image> <key> 1'."""
|
||||
if image is None:
|
||||
image = os.path.join(work, "fw.bin")
|
||||
with open(image, "wb") as f:
|
||||
f.write(b"\x11" * 2048)
|
||||
cmd = [SIGN, "--ed25519", hash_flag, "--dts", dtb_path, image, key, "1"]
|
||||
return subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True), image
|
||||
|
||||
|
||||
def main():
|
||||
if not ensure_sign():
|
||||
skip("sign tool not available")
|
||||
|
||||
with tempfile.TemporaryDirectory() as work:
|
||||
key = os.path.join(work, "ed25519.der")
|
||||
if not make_ed25519_key(key):
|
||||
skip("python 'cryptography' module unavailable")
|
||||
|
||||
# 1. Valid DTB -> digest TLV present and correct, for every hash
|
||||
# algorithm (covers dts_hash_file's sha256/sha384/sha3-384 branches).
|
||||
# A sign failure here is a real regression, so fail() (not skip()).
|
||||
total = 128
|
||||
dtb = os.path.join(work, "good.dtb")
|
||||
blob = make_fdt(total)
|
||||
with open(dtb, "wb") as f:
|
||||
f.write(blob + b"\xAA" * 16) # trailing padding beyond totalsize
|
||||
for hflag, hfn, hlen in HASHES:
|
||||
r, image = run_dts(work, key, dtb, hflag)
|
||||
if "unsupported" in (r.stderr or "").lower() and hflag == "--sha3":
|
||||
# SHA3 not compiled into this sign build; skip only this case.
|
||||
continue
|
||||
if r.returncode != 0:
|
||||
fail("sign %s --dts failed (rc=%d): %s"
|
||||
% (hflag, r.returncode, r.stderr.strip()))
|
||||
signed = image.replace(".bin", "_v1_signed.bin")
|
||||
if not os.path.exists(signed):
|
||||
fail("sign %s --dts produced no signed image" % hflag)
|
||||
with open(signed, "rb") as f:
|
||||
data = f.read(2048)
|
||||
got = find_tlv_bytes(data, HDR_DEVICE_TREE_DIGEST, len(data))
|
||||
expect = hfn(blob).digest() # first totalsize bytes only
|
||||
if got is None:
|
||||
fail("%s: HDR_DEVICE_TREE_DIGEST (0x35) TLV not found" % hflag)
|
||||
if len(got) != hlen:
|
||||
fail("%s: digest TLV len %d, expected %d"
|
||||
% (hflag, len(got), hlen))
|
||||
if got != expect:
|
||||
fail("%s: digest TLV mismatch: got %s expected %s"
|
||||
% (hflag, got.hex(), expect.hex()))
|
||||
|
||||
# 2. Bad magic -> clean non-zero exit, never a signal (SIGABRT was the
|
||||
# double-free symptom). subprocess returncode < 0 means killed by
|
||||
# signal -N.
|
||||
bad = os.path.join(work, "bad.dtb")
|
||||
with open(bad, "wb") as f:
|
||||
f.write(b"not-an-fdt-file-but-long-enough-to-fill-40-bytes!!!")
|
||||
r, _ = run_dts(work, key, bad)
|
||||
if r.returncode < 0:
|
||||
fail("bad-magic --dts died with signal %d (double free?)"
|
||||
% (-r.returncode))
|
||||
if r.returncode == 0:
|
||||
fail("bad-magic --dts unexpectedly succeeded")
|
||||
|
||||
# 3. Truncated FDT (valid magic, < 40-byte header) -> clean rejection.
|
||||
trunc = os.path.join(work, "trunc.dtb")
|
||||
with open(trunc, "wb") as f:
|
||||
f.write(struct.pack(">II", 0xd00dfeed, 0x10)) # 8 bytes only
|
||||
r, _ = run_dts(work, key, trunc)
|
||||
if r.returncode < 0:
|
||||
fail("truncated --dts died with signal %d" % (-r.returncode))
|
||||
if r.returncode == 0:
|
||||
fail("truncated --dts unexpectedly succeeded")
|
||||
|
||||
# 4. Unsupported FDT version (full 40B header, valid magic, version 15)
|
||||
# -> rejected (the version/last_comp check the bootloader also does).
|
||||
badver = os.path.join(work, "badver.dtb")
|
||||
vblob = bytearray(make_fdt(128))
|
||||
struct.pack_into(">I", vblob, 0x14, 0x0F) # version 15 < 0x10
|
||||
with open(badver, "wb") as f:
|
||||
f.write(bytes(vblob))
|
||||
r, _ = run_dts(work, key, badver)
|
||||
if r.returncode <= 0:
|
||||
fail("unsupported-version --dts should be rejected (rc=%d)"
|
||||
% r.returncode)
|
||||
|
||||
# 5. Truncated body: totalsize declares more than the file holds ->
|
||||
# rejected up front, not hashed.
|
||||
short = os.path.join(work, "short.dtb")
|
||||
with open(short, "wb") as f:
|
||||
f.write(make_fdt(4096)[:512]) # header says 4096, file is 512
|
||||
r, _ = run_dts(work, key, short)
|
||||
if r.returncode <= 0:
|
||||
fail("short-body --dts should be rejected (rc=%d)" % r.returncode)
|
||||
|
||||
# 6. Missing --dts argument -> clean usage error, never a crash.
|
||||
r = subprocess.run([SIGN, "--ed25519", "--sha256", "--dts"],
|
||||
cwd=ROOT, capture_output=True, text=True)
|
||||
if r.returncode < 0:
|
||||
fail("--dts with no argument died with signal %d" % (-r.returncode))
|
||||
if r.returncode == 0:
|
||||
fail("--dts with no argument unexpectedly succeeded")
|
||||
|
||||
print("PASS unit-sign-dts")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue