diff --git a/include/fdt.h b/include/fdt.h index c5efeb82..69941c03 100644 --- a/include/fdt.h +++ b/include/fdt.h @@ -172,6 +172,12 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ const char** pramdisk); void* fit_load_image(void* fdt, const char* image, int* lenp); void* fit_load_image_ex(void* fdt, const char* image, int* lenp, uint32_t out_max); +/* Load (and, if compressed, decompress) a FIT subimage directly to a + * caller-supplied destination buffer. Overrides the FIT image's + * `load` and `entry` properties - dst is both the destination and the + * value returned. dst_max bounds the decompressed size. */ +void* fit_load_image_to(void* fdt, const char* image, void* dst, + uint32_t dst_max, int* lenp); /* FDT initrd fixup: writes /chosen/linux,initrd-{start,end} as 64-bit * big-endian properties. Creates /chosen if missing. Returns 0 on success diff --git a/src/fdt.c b/src/fdt.c index 43cc78ac..b31feee9 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -34,10 +34,15 @@ #include "gzip.h" #endif -/* Default upper bound on a single FIT subimage's decompressed size. - * The outer wolfBoot signature already authenticates the FIT, but a - * concrete cap defends against a malformed-but-signed FIT scribbling - * across unrelated memory. Override per target via: +/* Coarse upper bound on a single FIT subimage's decompressed size. + * This is a sanity ceiling, not a per-destination memory-safety + * bound. Authenticity of the FIT bytes is provided by the outer + * wolfBoot signature; this cap is a belt-and-suspenders limit so a + * malformed-but-signed gzip stream cannot inflate without bound. + * Callers that need a tighter, RAM-window-aware bound should use + * fit_load_image_ex() (FIT-`load` destination + explicit out_max) + * or fit_load_image_to() (caller-supplied destination + dst_max). + * Override per target via: * CFLAGS+=-DWOLFBOOT_FIT_MAX_DECOMP=... */ #ifndef WOLFBOOT_FIT_MAX_DECOMP @@ -904,13 +909,22 @@ int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size) #define WOLFBOOT_LOAD_RAMDISK_ADDRESS 0 #endif +/* Upper bound on the (decompressed) ramdisk size. Defaults to the + * generic FIT decompression cap. Targets with a tighter known-safe + * RAM window for the ramdisk should override this. */ +#ifndef WOLFBOOT_FIT_MAX_RAMDISK +#define WOLFBOOT_FIT_MAX_RAMDISK WOLFBOOT_FIT_MAX_DECOMP +#endif + /* Load a FIT ramdisk subimage and patch the DTB's /chosen * linux,initrd-{start,end} to point at it. If - * WOLFBOOT_LOAD_RAMDISK_ADDRESS is nonzero, the ramdisk is relocated - * to that fixed address (overrides the FIT's `load` property); - * otherwise the address fit_load_image returned (FIT-specified or - * in-FIT pointer) is used as-is. Caller passes the DTB pointer for - * the initrd fixup, or NULL to skip the fixup. + * WOLFBOOT_LOAD_RAMDISK_ADDRESS is nonzero, the ramdisk is loaded + * directly to that fixed address - bypassing the FIT's `load` + * property entirely - so a gzip-compressed ramdisk is decompressed + * straight into the override (with the override capacity acting as + * the safety bound). Otherwise the address fit_load_image returned + * (FIT-specified or in-FIT pointer) is used as-is. Caller passes the + * DTB pointer for the initrd fixup, or NULL to skip the fixup. * * Returns 0 on success, -1 if the ramdisk node was found but the * load failed. The current callers ignore the return value @@ -926,41 +940,52 @@ int fit_load_ramdisk(void* fit, const char* ramdisk_node, void* dts_addr) return -1; } - rd_ptr = (uint8_t*)fit_load_image(fit, ramdisk_node, &rd_size); - if (rd_ptr == NULL || rd_size <= 0) { - wolfBoot_printf("FIT: ramdisk node present but load failed\n"); - return -1; - } - if (WOLFBOOT_LOAD_RAMDISK_ADDRESS != 0) { rd_dst = (uint8_t*)WOLFBOOT_LOAD_RAMDISK_ADDRESS; - if (rd_ptr != rd_dst) { - wolfBoot_printf("Loading ramdisk: %p -> %p (%d bytes)\n", - rd_ptr, rd_dst, rd_size); - memcpy(rd_dst, rd_ptr, rd_size); - } - else { - wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", - rd_dst, rd_size); + rd_ptr = (uint8_t*)fit_load_image_to(fit, ramdisk_node, + rd_dst, (uint32_t)WOLFBOOT_FIT_MAX_RAMDISK, &rd_size); + if (rd_ptr == NULL || rd_size <= 0) { + wolfBoot_printf("FIT: ramdisk node present but load failed\n"); + return -1; } + wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", + rd_dst, rd_size); } else { + rd_ptr = (uint8_t*)fit_load_image(fit, ramdisk_node, &rd_size); + if (rd_ptr == NULL || rd_size <= 0) { + wolfBoot_printf("FIT: ramdisk node present but load failed\n"); + return -1; + } rd_dst = rd_ptr; wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", rd_dst, rd_size); } if (dts_addr != NULL) { - (void)fdt_fixup_initrd(dts_addr, + int frc = fdt_fixup_initrd(dts_addr, (uint64_t)(uintptr_t)rd_dst, (uint64_t)rd_size); + if (frc != 0) { + wolfBoot_printf("FIT: fdt_fixup_initrd failed (rc=%d); " + "kernel will not see initrd\n", frc); + return -1; + } } return 0; } #endif /* WOLFBOOT_FIT_RAMDISK */ -void* fit_load_image_ex(void* fdt, const char* image, int* lenp, - uint32_t out_max) +/* Inner implementation shared by fit_load_image_ex and fit_load_image_to. + * When dst_override is non-NULL it replaces the FIT image's `load` + * property as the destination, so a compressed (gzip) payload is + * decompressed directly into the caller's buffer rather than being + * routed through the FIT-declared address. The `entry` property is + * also ignored when dst_override is in effect, since the caller wants + * the override address back. + */ +static void* fit_load_image_inner(void* fdt, const char* image, int* lenp, + uint32_t out_max, void* dst_override) { void *load, *entry, *data = NULL; int off, len = 0; @@ -976,6 +1001,12 @@ void* fit_load_image_ex(void* fdt, const char* image, int* lenp, data = (void*)fdt_getprop(fdt, off, "data", &len); load = fdt_getprop_address(fdt, off, "load"); entry = fdt_getprop_address(fdt, off, "entry"); + if (dst_override != NULL) { + /* Caller-supplied destination replaces the FIT load + * property and disables `entry` resolution. */ + load = dst_override; + entry = NULL; + } if (data != NULL) { int is_gzip = 0; int is_unknown_comp = 0; @@ -1073,9 +1104,24 @@ void* fit_load_image_ex(void* fdt, const char* image, int* lenp, } +void* fit_load_image_ex(void* fdt, const char* image, int* lenp, + uint32_t out_max) +{ + return fit_load_image_inner(fdt, image, lenp, out_max, NULL); +} + void* fit_load_image(void* fdt, const char* image, int* lenp) { return fit_load_image_ex(fdt, image, lenp, WOLFBOOT_FIT_MAX_DECOMP); } +void* fit_load_image_to(void* fdt, const char* image, void* dst, + uint32_t dst_max, int* lenp) +{ + if (dst == NULL) { + return NULL; + } + return fit_load_image_inner(fdt, image, lenp, dst_max, dst); +} + #endif /* (MMU || WOLFBOOT_FDT) && !BUILD_LOADER_STAGE1 */ diff --git a/src/update_disk.c b/src/update_disk.c index 975d727a..62631ae7 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -548,7 +548,13 @@ void RAMFUNCTION wolfBoot_start(void) (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk); if (kernel != NULL) { - load_address = fit_load_image(fit, kernel, NULL); + void *new_load = fit_load_image(fit, kernel, NULL); + if (new_load == NULL) { + wolfBoot_printf("FIT: failed to load kernel '%s'\r\n", + kernel); + wolfBoot_panic(); + } + load_address = new_load; } if (flat_dt != NULL) { uint8_t *dts_ptr = fit_load_image(fit, flat_dt, (int*)&dts_size); diff --git a/src/update_ram.c b/src/update_ram.c index 8092a10e..6a2409a0 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -375,7 +375,12 @@ backup_on_failure: (void)fit_find_images(fit, &kernel, &flat_dt, &ramdisk); if (kernel != NULL) { - load_address = fit_load_image(fit, kernel, NULL); + void *new_load = fit_load_image(fit, kernel, NULL); + if (new_load == NULL) { + wolfBoot_printf("FIT: failed to load kernel '%s'\n", kernel); + wolfBoot_panic(); + } + load_address = new_load; } if (flat_dt != NULL) { uint8_t *dts_ptr = fit_load_image(fit, flat_dt, (int*)&dts_size); diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index ac65d1bc..ee0b6398 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -59,6 +59,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-keygen-xmss-params TESTS+=unit-tpm-check-rot-auth TESTS+=unit-tpm-api-names +TESTS+=unit-fit-gzip unit-fit-nogzip include unit-sign-encrypted-output.mkfrag @@ -324,6 +325,19 @@ unit-delta: ../../include/target.h unit-delta.c unit-gzip: ../../include/target.h unit-gzip.c gcc -o $@ unit-gzip.c $(CFLAGS) -DWOLFBOOT_GZIP $(LDFLAGS) +# FIT-loader gzip / unsupported-compression branch coverage. Built twice +# from the same source: once with WOLFBOOT_GZIP (success + decompress +# failure paths) and once without (compile-time fail-closed path). +unit-fit-gzip: ../../include/target.h unit-fit-gzip.c + gcc -o $@ unit-fit-gzip.c $(CFLAGS) -DWOLFBOOT_FDT -DWOLFBOOT_GZIP \ + -DWOLFBOOT_NO_PRINTF \ + -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections + +unit-fit-nogzip: ../../include/target.h unit-fit-gzip.c + gcc -o $@ unit-fit-gzip.c $(CFLAGS) -DWOLFBOOT_FDT \ + -DWOLFBOOT_NO_PRINTF \ + -ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections + unit-update-flash: ../../include/target.h unit-update-flash.c gcc -o $@ unit-update-flash.c ../../src/image.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c $(CFLAGS) $(LDFLAGS) diff --git a/tools/unit-tests/unit-fit-gzip.c b/tools/unit-tests/unit-fit-gzip.c new file mode 100644 index 00000000..f5a3b54c --- /dev/null +++ b/tools/unit-tests/unit-fit-gzip.c @@ -0,0 +1,317 @@ +/* unit-fit-gzip.c + * + * Unit tests for the FIT-image loader's gzip / unsupported-compression + * branches in src/fdt.c. The tests drive fit_load_image_to() and + * fit_load_image_ex() against minimal hand-built FIT blobs so the new + * fail-closed paths (gzip success, gzip corruption, unknown + * compression, and the WOLFBOOT_GZIP-disabled build) all have + * deterministic coverage. + * + * The test binary is built twice from the same source: + * - unit-fit-gzip (WOLFBOOT_GZIP defined -> gzip path enabled) + * - unit-fit-nogzip (WOLFBOOT_GZIP undefined -> compile-time fail-closed) + * + * 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 + */ + +#include +#include +#include +#include +#include + +#include "../../include/fdt.h" + +/* fdt.c calls wolfBoot_printf; supply a silent stub. */ +void wolfBoot_printf(const char *fmt, ...) +{ + (void)fmt; +} + +/* Pull in the production code under test. fdt.c's body is gated on + * WOLFBOOT_FDT; the Makefile defines that for both build variants. + * gzip.c is only needed for the WOLFBOOT_GZIP build. */ +#include "../../src/fdt.c" +#ifdef WOLFBOOT_GZIP +#include "../../src/gzip.c" +#endif + +/* ------------------------------------------------------------------------- */ +/* Pre-built FIT fixtures (generated with python; see commit message) */ +/* ------------------------------------------------------------------------- */ +/* Each FIT contains a single /images/kernel-1 node. The `data` property */ +/* is either a gzip-compressed or plain copy of `fit_plain_payload`. The */ +/* `compression` property selects the path under test. `load` is set to */ +/* 0xC0001000 in most fixtures - tests use fit_load_image_to() which */ +/* overrides that with a real heap buffer, so the address never gets */ +/* dereferenced. */ + +static const char fit_plain_payload[] = "hello fit test payload\n"; +#define FIT_PLAIN_LEN 23 + +/* gzip-compressed kernel, load=0xC0001000 */ +static const uint8_t fit_with_gzip_kernel[] = { + 0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6b, 0x65, 0x72, 0x6e, + 0x65, 0x6c, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, + 0x48, 0xcb, 0x2c, 0x51, 0x28, 0x49, 0x2d, 0x2e, 0x51, 0x28, 0x48, 0xac, + 0xcc, 0xc9, 0x4f, 0x4c, 0xe1, 0x02, 0x00, 0x54, 0x03, 0xc1, 0x3a, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x05, 0x67, 0x7a, 0x69, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, + 0x64, 0x61, 0x74, 0x61, 0x00, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x6c, 0x6f, 0x61, 0x64, 0x00, +}; + +/* Same as above but one byte of the deflate body is flipped */ +static const uint8_t fit_with_corrupt_gzip[] = { + 0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6b, 0x65, 0x72, 0x6e, + 0x65, 0x6c, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0xa8, + 0x48, 0xcb, 0x2c, 0x51, 0x28, 0x49, 0x2d, 0x2e, 0x51, 0x28, 0x48, 0xac, + 0xcc, 0xc9, 0x4f, 0x4c, 0xe1, 0x02, 0x00, 0x54, 0x03, 0xc1, 0x3a, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x05, 0x67, 0x7a, 0x69, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, + 0x64, 0x61, 0x74, 0x61, 0x00, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x6c, 0x6f, 0x61, 0x64, 0x00, +}; + +/* compression="lzma" - unknown to the loader, must fail closed */ +static const uint8_t fit_with_lzma[] = { + 0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6b, 0x65, 0x72, 0x6e, + 0x65, 0x6c, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, + 0x6f, 0x20, 0x66, 0x69, 0x74, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x6c, 0x7a, 0x6d, 0x61, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x09, 0x64, 0x61, 0x74, 0x61, 0x00, 0x63, 0x6f, 0x6d, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x6c, 0x6f, 0x61, + 0x64, 0x00, +}; + +/* compression="none" - sanity baseline (plain memcpy) */ +static const uint8_t fit_with_none_comp[] = { + 0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6b, 0x65, 0x72, 0x6e, + 0x65, 0x6c, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, + 0x6f, 0x20, 0x66, 0x69, 0x74, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x6e, 0x6f, 0x6e, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x09, 0x64, 0x61, 0x74, 0x61, 0x00, 0x63, 0x6f, 0x6d, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x6c, 0x6f, 0x61, + 0x64, 0x00, +}; + +/* gzip-compressed kernel WITHOUT a `load` property -> exercises the + * "compression declared but no destination" fail-closed branch when + * called via fit_load_image_ex() (which has no override). */ +static const uint8_t fit_gzip_no_load[] = { + 0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x6b, 0x65, 0x72, 0x6e, + 0x65, 0x6c, 0x2d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, + 0x48, 0xcb, 0x2c, 0x51, 0x28, 0x49, 0x2d, 0x2e, 0x51, 0x28, 0x48, 0xac, + 0xcc, 0xc9, 0x4f, 0x4c, 0xe1, 0x02, 0x00, 0x54, 0x03, 0xc1, 0x3a, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x05, 0x67, 0x7a, 0x69, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x09, 0x64, 0x61, 0x74, 0x61, 0x00, 0x63, 0x6f, 0x6d, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x00, +}; + +/* ------------------------------------------------------------------------- */ +/* Test cases */ +/* ------------------------------------------------------------------------- */ + +#ifdef WOLFBOOT_GZIP + +START_TEST(test_fit_to_gzip_success) +{ + uint8_t buf[64]; + int len = -1; + void *ret; + + memset(buf, 0xa5, sizeof(buf)); + /* fit_with_gzip_kernel is read-only; copy into a writable scratch + * since fit_load_image_to() does not modify the FIT, but we still + * want a clean buffer per test. */ + static uint8_t fit_scratch[sizeof(fit_with_gzip_kernel)]; + memcpy(fit_scratch, fit_with_gzip_kernel, sizeof(fit_scratch)); + + ret = fit_load_image_to(fit_scratch, "kernel-1", + buf, (uint32_t)sizeof(buf), &len); + ck_assert_ptr_eq(ret, buf); + ck_assert_int_eq(len, FIT_PLAIN_LEN); + ck_assert_int_eq(memcmp(buf, fit_plain_payload, FIT_PLAIN_LEN), 0); +} +END_TEST + +START_TEST(test_fit_to_gzip_corrupt_returns_null) +{ + uint8_t buf[64]; + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_with_corrupt_gzip)]; + memcpy(fit_scratch, fit_with_corrupt_gzip, sizeof(fit_scratch)); + + ret = fit_load_image_to(fit_scratch, "kernel-1", + buf, (uint32_t)sizeof(buf), &len); + ck_assert_ptr_null(ret); +} +END_TEST + +START_TEST(test_fit_to_none_compression_copies_plain) +{ + uint8_t buf[64]; + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_with_none_comp)]; + memcpy(fit_scratch, fit_with_none_comp, sizeof(fit_scratch)); + + ret = fit_load_image_to(fit_scratch, "kernel-1", + buf, (uint32_t)sizeof(buf), &len); + ck_assert_ptr_eq(ret, buf); + ck_assert_int_eq(len, FIT_PLAIN_LEN); + ck_assert_int_eq(memcmp(buf, fit_plain_payload, FIT_PLAIN_LEN), 0); +} +END_TEST + +START_TEST(test_fit_ex_gzip_no_load_returns_null) +{ + /* fit_load_image_ex() with no override and no FIT `load` property + * must refuse rather than pass compressed bytes through as raw. */ + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_gzip_no_load)]; + memcpy(fit_scratch, fit_gzip_no_load, sizeof(fit_scratch)); + + ret = fit_load_image_ex(fit_scratch, "kernel-1", &len, 64 * 1024); + ck_assert_ptr_null(ret); +} +END_TEST + +#else /* !WOLFBOOT_GZIP */ + +START_TEST(test_fit_to_gzip_disabled_returns_null) +{ + /* With WOLFBOOT_GZIP undefined, even a perfectly valid gzip stream + * must fail closed - the loader has no inflater linked in. */ + uint8_t buf[64]; + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_with_gzip_kernel)]; + memcpy(fit_scratch, fit_with_gzip_kernel, sizeof(fit_scratch)); + + ret = fit_load_image_to(fit_scratch, "kernel-1", + buf, (uint32_t)sizeof(buf), &len); + ck_assert_ptr_null(ret); +} +END_TEST + +#endif /* WOLFBOOT_GZIP */ + +START_TEST(test_fit_to_lzma_unknown_returns_null) +{ + /* Independent of WOLFBOOT_GZIP - any unknown compression scheme is + * always rejected. */ + uint8_t buf[64]; + int len = -1; + void *ret; + static uint8_t fit_scratch[sizeof(fit_with_lzma)]; + memcpy(fit_scratch, fit_with_lzma, sizeof(fit_scratch)); + + ret = fit_load_image_to(fit_scratch, "kernel-1", + buf, (uint32_t)sizeof(buf), &len); + ck_assert_ptr_null(ret); +} +END_TEST + +/* ------------------------------------------------------------------------- */ + +static Suite *fit_gzip_suite(void) +{ + Suite *s = suite_create("fit_gzip"); + TCase *tc = tcase_create("fit_gzip"); + +#ifdef WOLFBOOT_GZIP + tcase_add_test(tc, test_fit_to_gzip_success); + tcase_add_test(tc, test_fit_to_gzip_corrupt_returns_null); + tcase_add_test(tc, test_fit_to_none_compression_copies_plain); + tcase_add_test(tc, test_fit_ex_gzip_no_load_returns_null); +#else + tcase_add_test(tc, test_fit_to_gzip_disabled_returns_null); +#endif + tcase_add_test(tc, test_fit_to_lzma_unknown_returns_null); + + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + SRunner *sr = srunner_create(fit_gzip_suite()); + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails ? 1 : 0; +} diff --git a/tools/unit-tests/unit-gzip.c b/tools/unit-tests/unit-gzip.c index c15862c2..a92166ab 100644 --- a/tools/unit-tests/unit-gzip.c +++ b/tools/unit-tests/unit-gzip.c @@ -325,6 +325,203 @@ START_TEST(test_neg_null_args) } END_TEST +/* ------------------------------------------------------------------------- */ +/* Deterministic byte-level fixtures */ +/* ------------------------------------------------------------------------- */ +/* These cover three concerns that round-tripping through host gzip(1) */ +/* cannot guarantee: */ +/* 1. block-type coverage (BTYPE 00/01/10 in the deflate stream) */ +/* 2. optional gzip header flags (FEXTRA / FNAME / FCOMMENT / FHCRC) */ +/* 3. malformed FEXTRA xlen handling */ +/* All fixtures were pre-generated with Python's gzip / zlib modules so the */ +/* exact byte layout (and therefore the BTYPE / FLG bits being exercised) */ +/* is independent of the host gzip(1) version. Any change in fixture */ +/* content should be regenerated and the BTYPE/FLG comments updated. */ + +/* ---------- Stored block (BTYPE = 00) ----------------------------------- */ +static const char stored_input[] = + "hello stored block, this is exactly verbatim.\n"; +static const uint8_t stored_gz[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01, 0x2e, + 0x00, 0xd1, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x64, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2c, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x65, 0x78, 0x61, 0x63, 0x74, + 0x6c, 0x79, 0x20, 0x76, 0x65, 0x72, 0x62, 0x61, 0x74, 0x69, 0x6d, 0x2e, + 0x0a, 0xe3, 0x07, 0x71, 0xdd, 0x2e, 0x00, 0x00, 0x00, +}; + +/* ---------- Fixed Huffman (BTYPE = 01), zlib Z_FIXED strategy ----------- */ +static const char fixed_input[] = "the lazy dog"; +static const uint8_t fixed_gz[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x2b, 0xc9, + 0x48, 0x55, 0xc8, 0x49, 0xac, 0xaa, 0x54, 0x48, 0xc9, 0x4f, 0x07, 0x00, + 0xe3, 0x57, 0x10, 0x29, 0x0c, 0x00, 0x00, 0x00, +}; + +/* ---------- Dynamic Huffman (BTYPE = 10) -------------------------------- */ +static const char dyn_input[] = + "alpha beta gamma delta epsilon zeta eta theta iota kappa lambda " + "mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega"; +static const uint8_t dyn_gz[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x2d, 0x4c, + 0x5b, 0x0a, 0xc0, 0x20, 0x0c, 0xbb, 0x4a, 0xae, 0x96, 0xa9, 0x68, 0x99, + 0xd5, 0xe2, 0x03, 0xc6, 0x4e, 0xbf, 0x0a, 0xfb, 0x48, 0x48, 0xc8, 0x83, + 0xd5, 0x0a, 0x71, 0xa5, 0x45, 0x64, 0xaa, 0x12, 0x31, 0x55, 0xd7, 0xc9, + 0xa6, 0xd4, 0xde, 0xf0, 0x9e, 0xe0, 0x60, 0x95, 0xc3, 0xd2, 0x9d, 0x6e, + 0x9a, 0x11, 0x95, 0x7a, 0x45, 0x42, 0x37, 0xda, 0xc6, 0x23, 0xe8, 0x2a, + 0x61, 0xf8, 0xc2, 0x04, 0xa3, 0x74, 0x4c, 0xc9, 0x7e, 0xb6, 0xb8, 0xb1, + 0xff, 0x2b, 0x2b, 0x82, 0xe0, 0x70, 0xeb, 0xe5, 0x94, 0xf9, 0x01, 0xc3, + 0x9e, 0x9a, 0x49, 0x7b, 0x00, 0x00, 0x00, +}; + +/* ---------- Optional gzip header flags ---------------------------------- */ +/* All five fixtures decompress to "hello flag-test" (15 bytes). They share */ +/* the same deflate body + trailer; only the gzip header varies. */ +static const char flag_payload[] = "hello flag-test"; +#define FLAG_PAYLOAD_LEN 15 + +/* FLG = 0x04 (FEXTRA) - 5 bytes of extra field */ +static const uint8_t gz_fextra[] = { + 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0x00, + 0xab, 0xcd, 0xef, 0x01, 0x02, 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x48, + 0xcb, 0x49, 0x4c, 0xd7, 0x2d, 0x49, 0x2d, 0x2e, 0x01, 0x00, 0x09, 0x37, + 0x1d, 0x37, 0x0f, 0x00, 0x00, 0x00, +}; + +/* FLG = 0x08 (FNAME) - "my-fname.bin\0" */ +static const uint8_t gz_fname[] = { + 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x6d, 0x79, + 0x2d, 0x66, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x62, 0x69, 0x6e, 0x00, 0xcb, + 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x48, 0xcb, 0x49, 0x4c, 0xd7, 0x2d, 0x49, + 0x2d, 0x2e, 0x01, 0x00, 0x09, 0x37, 0x1d, 0x37, 0x0f, 0x00, 0x00, 0x00, +}; + +/* FLG = 0x10 (FCOMMENT) - "some comment\0" */ +static const uint8_t gz_fcomment[] = { + 0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x73, 0x6f, + 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0xcb, + 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x48, 0xcb, 0x49, 0x4c, 0xd7, 0x2d, 0x49, + 0x2d, 0x2e, 0x01, 0x00, 0x09, 0x37, 0x1d, 0x37, 0x0f, 0x00, 0x00, 0x00, +}; + +/* FLG = 0x02 (FHCRC) - 2-byte header CRC (we do not validate it) */ +static const uint8_t gz_fhcrc[] = { + 0x1f, 0x8b, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xde, 0xad, + 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x48, 0xcb, 0x49, 0x4c, 0xd7, 0x2d, + 0x49, 0x2d, 0x2e, 0x01, 0x00, 0x09, 0x37, 0x1d, 0x37, 0x0f, 0x00, 0x00, + 0x00, +}; + +/* FLG = FEXTRA | FNAME | FCOMMENT | FHCRC - all four set at once */ +static const uint8_t gz_all_flags[] = { + 0x1f, 0x8b, 0x08, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0x00, + 0xab, 0xcd, 0xef, 0x01, 0x02, 0x6d, 0x79, 0x2d, 0x66, 0x6e, 0x61, 0x6d, + 0x65, 0x2e, 0x62, 0x69, 0x6e, 0x00, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x12, 0x34, 0xcb, 0x48, 0xcd, + 0xc9, 0xc9, 0x57, 0x48, 0xcb, 0x49, 0x4c, 0xd7, 0x2d, 0x49, 0x2d, 0x2e, + 0x01, 0x00, 0x09, 0x37, 0x1d, 0x37, 0x0f, 0x00, 0x00, 0x00, +}; + +/* FEXTRA xlen claims 100 bytes but only 3 are actually present -> truncated */ +static const uint8_t gz_trunc_fextra[] = { + 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x64, 0x00, + 0x61, 0x62, 0x63, +}; + +/* Helper: extract BTYPE from the first byte of the deflate stream right + * after a (no-flags) gzip header. The header is fixed at 10 bytes when + * FLG=0; the first deflate byte's bits 1-2 are BTYPE. */ +static unsigned gz_btype_after_plain_header(const uint8_t *gz) +{ + return (unsigned)((gz[10] >> 1) & 0x03); +} + +START_TEST(test_fixture_stored_block) +{ + uint8_t out[128]; uint32_t out_len = 0; + int rc; + ck_assert_uint_eq(gz_btype_after_plain_header(stored_gz), 0); + rc = wolfBoot_gunzip(stored_gz, sizeof(stored_gz), out, sizeof(out), + &out_len); + ck_assert_int_eq(rc, 0); + ck_assert_uint_eq(out_len, sizeof(stored_input) - 1); + ck_assert_int_eq(memcmp(out, stored_input, out_len), 0); +} +END_TEST + +START_TEST(test_fixture_fixed_huffman) +{ + uint8_t out[64]; uint32_t out_len = 0; + int rc; + ck_assert_uint_eq(gz_btype_after_plain_header(fixed_gz), 1); + rc = wolfBoot_gunzip(fixed_gz, sizeof(fixed_gz), out, sizeof(out), + &out_len); + ck_assert_int_eq(rc, 0); + ck_assert_uint_eq(out_len, sizeof(fixed_input) - 1); + ck_assert_int_eq(memcmp(out, fixed_input, out_len), 0); +} +END_TEST + +START_TEST(test_fixture_dynamic_huffman) +{ + uint8_t out[256]; uint32_t out_len = 0; + int rc; + ck_assert_uint_eq(gz_btype_after_plain_header(dyn_gz), 2); + rc = wolfBoot_gunzip(dyn_gz, sizeof(dyn_gz), out, sizeof(out), &out_len); + ck_assert_int_eq(rc, 0); + ck_assert_uint_eq(out_len, sizeof(dyn_input) - 1); + ck_assert_int_eq(memcmp(out, dyn_input, out_len), 0); +} +END_TEST + +static void check_flag_fixture(const uint8_t *gz, size_t gz_len) +{ + uint8_t out[64]; uint32_t out_len = 0; + int rc = wolfBoot_gunzip(gz, (uint32_t)gz_len, out, sizeof(out), &out_len); + ck_assert_int_eq(rc, 0); + ck_assert_uint_eq(out_len, FLAG_PAYLOAD_LEN); + ck_assert_int_eq(memcmp(out, flag_payload, out_len), 0); +} + +START_TEST(test_fixture_fextra) +{ + check_flag_fixture(gz_fextra, sizeof(gz_fextra)); +} +END_TEST + +START_TEST(test_fixture_fname) +{ + check_flag_fixture(gz_fname, sizeof(gz_fname)); +} +END_TEST + +START_TEST(test_fixture_fcomment) +{ + check_flag_fixture(gz_fcomment, sizeof(gz_fcomment)); +} +END_TEST + +START_TEST(test_fixture_fhcrc) +{ + check_flag_fixture(gz_fhcrc, sizeof(gz_fhcrc)); +} +END_TEST + +START_TEST(test_fixture_all_flags) +{ + check_flag_fixture(gz_all_flags, sizeof(gz_all_flags)); +} +END_TEST + +START_TEST(test_fixture_truncated_fextra) +{ + uint8_t out[16]; uint32_t out_len = 0; + int rc = wolfBoot_gunzip(gz_trunc_fextra, sizeof(gz_trunc_fextra), + out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_TRUNCATED); +} +END_TEST + /* ------------------------------------------------------------------------- */ /* Test runner */ /* ------------------------------------------------------------------------- */ @@ -334,10 +531,12 @@ static Suite *gzip_suite(void) Suite *s = suite_create("gzip"); TCase *tc_pos = tcase_create("roundtrip"); TCase *tc_neg = tcase_create("negative"); + TCase *tc_fix = tcase_create("fixtures"); /* The 2 MB test pushes past the default 4-second per-test budget */ tcase_set_timeout(tc_pos, 30); tcase_set_timeout(tc_neg, 10); + tcase_set_timeout(tc_fix, 10); tcase_add_test(tc_pos, test_roundtrip_empty); tcase_add_test(tc_pos, test_roundtrip_short_text); @@ -356,8 +555,19 @@ static Suite *gzip_suite(void) tcase_add_test(tc_neg, test_neg_output_overflow); tcase_add_test(tc_neg, test_neg_null_args); + tcase_add_test(tc_fix, test_fixture_stored_block); + tcase_add_test(tc_fix, test_fixture_fixed_huffman); + tcase_add_test(tc_fix, test_fixture_dynamic_huffman); + tcase_add_test(tc_fix, test_fixture_fextra); + tcase_add_test(tc_fix, test_fixture_fname); + tcase_add_test(tc_fix, test_fixture_fcomment); + tcase_add_test(tc_fix, test_fixture_fhcrc); + tcase_add_test(tc_fix, test_fixture_all_flags); + tcase_add_test(tc_fix, test_fixture_truncated_fextra); + suite_add_tcase(s, tc_pos); suite_add_tcase(s, tc_neg); + suite_add_tcase(s, tc_fix); return s; }