Peer review fixes (copilot)

src/fdt.c, include/fdt.h
  - Propagate fdt_fixup_initrd error in fit_load_ramdisk so a /chosen
    patch failure no longer silently boots a kernel with no initrd.
  - Add fit_load_image_to(): decompress (or memcpy) directly to a
    caller-supplied destination buffer instead of going through the
    FIT-declared `load` address. fit_load_ramdisk now uses this when
    WOLFBOOT_LOAD_RAMDISK_ADDRESS is set, so the override is a real
    safety bound for compressed ramdisks (previously the gzip stream
    was still inflated to the FIT `load` and only memcpy'd afterward).
  - Refactor fit_load_image_ex into a shared inner helper.
  - Reword the WOLFBOOT_FIT_MAX_DECOMP comment: the cap is a sanity
    ceiling, not a per-destination memory-safety bound. Authenticity
    is provided by the outer wolfBoot signature; tighter bounds need
    fit_load_image_ex / _to with an explicit out_max / dst_max.
  - Add WOLFBOOT_FIT_MAX_RAMDISK (defaults to WOLFBOOT_FIT_MAX_DECOMP)
    so targets can pin a tighter ramdisk decompression bound.

src/update_ram.c, src/update_disk.c
  - Panic when fit_load_image() returns NULL for the kernel subimage
    instead of letting load_address=NULL propagate into do_boot().

tools/unit-tests/unit-gzip.c
  - Add deterministic stored / fixed-Huffman / dynamic-Huffman gzip
    fixtures so the inflater's BTYPE 00/01/10 paths are exercised
    independent of host gzip(1) heuristics.
  - Add FEXTRA / FNAME / FCOMMENT / FHCRC and combined-flag fixtures
    plus a truncated-FEXTRA negative case to cover the optional gzip
    header parser.

tools/unit-tests/unit-fit-gzip.c (new), tools/unit-tests/Makefile
  - New libcheck binary covering the FIT loader's compression
    branches: gzip success, gzip stream corruption, unknown
    compression, compression="none" baseline, and the no-load
    fail-closed path. Built twice from the same source - once with
    WOLFBOOT_GZIP for the success / runtime-failure paths, and once
    without it so the compile-time fail-closed branch is also tested.
pull/772/head
David Garske 2026-05-04 13:06:15 -07:00 committed by Daniele Lacamera
parent d92053037c
commit 9ca1d435b9
7 changed files with 632 additions and 28 deletions

View File

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

View File

@ -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 */

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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 <check.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -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;
}