Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions config/examples/zynq7000.config
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 21 additions & 2 deletions docs/Signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-`

Expand Down
4 changes: 4 additions & 0 deletions docs/compile.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion hal/zynq.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 4 additions & 3 deletions hal/zynq7000.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions include/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions include/wolfboot/wolfboot.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
10 changes: 10 additions & 0 deletions options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,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)
Expand Down
86 changes: 86 additions & 0 deletions src/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading