diff --git a/.gitignore b/.gitignore index 77ef20818e..8096ea3c40 100644 --- a/.gitignore +++ b/.gitignore @@ -187,9 +187,7 @@ tools/unit-tests/unit-update-ram-nofixed tools/unit-tests/unit-max-space tools/unit-tests/unit-sdhci-disk-unaligned tools/unit-tests/unit-fwtpm-stub - - - +tools/unit-tests/unit-gzip # Elf preprocessing tools @@ -380,4 +378,3 @@ image.ub system-default.dtb test_output/ sdcard.img - diff --git a/Makefile b/Makefile index 2986d8db51..327c2b98cb 100644 --- a/Makefile +++ b/Makefile @@ -638,6 +638,7 @@ include/target.h: $(TARGET_H_TEMPLATE) FORCE sed -e "s/@WOLFBOOT_DTS_UPDATE_ADDRESS@/$(WOLFBOOT_DTS_UPDATE_ADDRESS)/g" | \ sed -e "s/@WOLFBOOT_LOAD_ADDRESS@/$(WOLFBOOT_LOAD_ADDRESS)/g" | \ sed -e "s/@WOLFBOOT_LOAD_DTS_ADDRESS@/$(WOLFBOOT_LOAD_DTS_ADDRESS)/g" | \ + sed -e "s/@WOLFBOOT_LOAD_RAMDISK_ADDRESS@/$(WOLFBOOT_LOAD_RAMDISK_ADDRESS)/g" | \ sed -e "s|@WOLFBOOT_RAMBOOT_MAX_SIZE_DEFINE@|$(if $(strip $(WOLFBOOT_RAMBOOT_MAX_SIZE)),#define WOLFBOOT_RAMBOOT_MAX_SIZE $(WOLFBOOT_RAMBOOT_MAX_SIZE),/* WOLFBOOT_RAMBOOT_MAX_SIZE undefined */)|g" | \ sed -e "s/@WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS@/$(WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS)/g" \ > $@ diff --git a/config/examples/polarfire_mpfs250.config b/config/examples/polarfire_mpfs250.config index 083623a689..1b04b46cde 100644 --- a/config/examples/polarfire_mpfs250.config +++ b/config/examples/polarfire_mpfs250.config @@ -46,6 +46,9 @@ WOLFTPM?=0 ELF?=1 #DEBUG_ELF?=1 +# Native gzip decompression for FIT subimages (set GZIP=0 to disable) +GZIP?=1 + # Use RISC-V assembly version of ECDSA and SHA NO_ASM?=0 diff --git a/config/examples/polarfire_mpfs250_qspi.config b/config/examples/polarfire_mpfs250_qspi.config index b12b3dc503..604aea911a 100644 --- a/config/examples/polarfire_mpfs250_qspi.config +++ b/config/examples/polarfire_mpfs250_qspi.config @@ -35,6 +35,9 @@ WOLFTPM?=0 ELF?=1 #DEBUG_ELF?=1 +# Native gzip decompression for FIT subimages (set GZIP=0 to disable) +GZIP?=1 + # Use RISC-V assembly version of ECDSA and SHA NO_ASM?=0 diff --git a/config/examples/versal_vmk180.config b/config/examples/versal_vmk180.config index dff81a72dd..e65d9e6500 100644 --- a/config/examples/versal_vmk180.config +++ b/config/examples/versal_vmk180.config @@ -59,6 +59,9 @@ NO_XIP=1 # ELF loading support ELF?=1 +# Native gzip decompression for FIT subimages (set GZIP=0 to disable) +GZIP?=1 + # Toolchain USE_GCC=1 CROSS_COMPILE=aarch64-none-elf- diff --git a/config/examples/versal_vmk180_sdcard.config b/config/examples/versal_vmk180_sdcard.config index 0db3ab2d93..64ee17d8cb 100644 --- a/config/examples/versal_vmk180_sdcard.config +++ b/config/examples/versal_vmk180_sdcard.config @@ -33,6 +33,9 @@ NO_XIP=1 # ELF loading support ELF?=1 +# Native gzip decompression for FIT subimages (set GZIP=0 to disable) +GZIP?=1 + # Boot Benchmarking (optional) BOOT_BENCHMARK?=1 diff --git a/config/examples/zynqmp.config b/config/examples/zynqmp.config index 70dc8cb241..114a9eab42 100644 --- a/config/examples/zynqmp.config +++ b/config/examples/zynqmp.config @@ -58,6 +58,9 @@ USE_GCC=1 ELF?=1 #DEBUG_ELF?=1 +# Native gzip decompression for FIT subimages (set GZIP=0 to disable) +GZIP?=1 + # Flash Sector Size WOLFBOOT_SECTOR_SIZE=0x20000 # Application Partition Size diff --git a/config/examples/zynqmp_sdcard.config b/config/examples/zynqmp_sdcard.config index f2401021f3..e5b0c666a0 100644 --- a/config/examples/zynqmp_sdcard.config +++ b/config/examples/zynqmp_sdcard.config @@ -43,6 +43,9 @@ NO_XIP=1 ELF?=1 #DEBUG_ELF?=1 +# Native gzip decompression for FIT subimages (set GZIP=0 to disable) +GZIP?=1 + # Boot Exception Level: leave wolfBoot at EL2 for handoff to Linux (matches # the standard PetaLinux U-Boot flow and preserves KVM/hypervisor use of # EL2). The EL2 Linux-cleanup path in do_boot() will clean dcache/disable @@ -100,6 +103,21 @@ CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000 # Check `ls /sys/class/mmc_host/` on your running target to confirm. CFLAGS_EXTRA+=-DLINUX_BOOTARGS_ROOT=\"/dev/mmcblk0p4\" +# ============================================================================ +# Optional: FIT-bundled initramfs (ramdisk) instead of an on-disk rootfs +# ============================================================================ +# Expects the PetaLinux INITRAMFS_IMAGE_BUNDLE=0 layout (FIT contains kernel +# + DTB + ramdisk). wolfBoot extracts the ramdisk to +# WOLFBOOT_LOAD_RAMDISK_ADDRESS and patches the loaded DTB with +# /chosen/linux,initrd-{start,end} so the kernel can find it. Compressed +# (gzip) ramdisks decompress automatically when GZIP=1. +# +# To enable: uncomment the three lines below and comment out the +# LINUX_BOOTARGS_ROOT line above (root= is supplied by the cpio's /init). +#FIT_RAMDISK?=1 +#WOLFBOOT_LOAD_RAMDISK_ADDRESS?=0x40000000 +#CFLAGS_EXTRA+=-DLINUX_BOOTARGS='"earlycon console=ttyPS0,115200 init_fatal_sh=1"' + # ============================================================================ # Boot Memory Layout # ============================================================================ diff --git a/docs/Targets.md b/docs/Targets.md index bbce15e5cb..0beb7ae5c6 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -1257,7 +1257,48 @@ MACHINE=mpfs-video-kit bitbake mchp-base-image-sdk Build images are output to: `./tmp-glibc/deploy/images/mpfs-video-kit/` -#### Custom FIT image, signing and coping to SDCard +#### Custom FIT image, signing and copying to SDCard + +wolfBoot can either decompress a gzipped kernel at boot time (`GZIP=1`, +the default for `polarfire_mpfs250.config` and `polarfire_mpfs250_qspi.config`) +or accept a pre-decompressed kernel inside the FIT (`GZIP=0`). Pick one path. + +##### Option A - Compressed FIT (`GZIP=1`, default) + +Set `compression = "gzip"` and point `data` at the gzipped kernel directly +in `hal/mpfs250.its`: + +```dts +images { + kernel-1 { + data = /incbin/("../yocto-dev-polarfire/build/tmp-glibc/work/mpfs_video_kit-oe-linux/linux-mchp/6.12.22+git/build/linux.bin.gz"); + compression = "gzip"; + load = <0x80200000>; + entry = <0x80200000>; + hash-1 { algo = "sha256"; }; + ... + }; +}; +``` + +Then build the FIT directly - no manual `gzip -cdvk` step: + +```sh +sudo dd if=wolfboot.bin of=/dev/sdc1 bs=512 && sudo cmp wolfboot.bin /dev/sdc1 +mkimage -f hal/mpfs250.its fitImage +``` + +At boot, wolfBoot decompresses the kernel into `0x80200000` directly out of +the FIT `data` blob. Image integrity is provided by the outer wolfBoot +signature over the entire FIT (which covers the compressed `data` bytes per +the FIT spec), and post-decompress integrity by gzip's CRC32 + ISIZE +trailer; per-image `hash-1` subnodes are not re-verified at runtime since +they would be redundant with the outer signature. + +##### Option B - Uncompressed FIT (`GZIP=0`) + +Build wolfBoot with `GZIP=0` and pre-decompress the kernel on the host. +Keep `compression = "none"` in `hal/mpfs250.its`: ```sh # Copy wolfBoot to "BIOS" partition @@ -3390,6 +3431,113 @@ Application running successfully! Entering idle loop... ``` +**Booting Linux via FIT image** + +wolfBoot is a drop-in replacement for U-Boot's FIT image loader: it parses +the same `mkimage`-produced multi-component FIT (kernel + DTB + optional +ramdisk), honors per-subimage `load`/`entry`/`compression` properties, and +hands off to the kernel the same way - with the added value of +authenticating the entire FIT against a wolfBoot signature before any +subimage is touched. + +ZynqMP can chain into a Linux kernel (PetaLinux, Yocto, or any other +producer) using the same FIT mechanism as the Versal target. wolfBoot's +FIT-using configs (`zynqmp.config` and `zynqmp_sdcard.config`) default to +`GZIP=1`, which lets you point the FIT at a gzipped kernel (`Image.gz`) +directly: + +```dts +images { + kernel-1 { + data = /incbin/("Image.gz"); + compression = "gzip"; + load = <0x10000000>; + entry = <0x10000000>; + hash-1 { algo = "sha256"; }; + }; +}; +``` + +`mkimage -f your-zynqmp.its fitImage` then produces a single signed FIT +that wolfBoot decompresses straight to the kernel load address at boot. +See the [Versal "Booting Linux via FIT image"](#versal-gen-1-vmk180) +section for a full walkthrough - the flow is identical apart from the +load addresses and the `bl31`/`fsbl` versus `bl31`/`plm` boot chain. Set +`GZIP=0` in +`.config` if you want to keep using an uncompressed `Image` plus +`compression = "none"`. + +The decompressed-output bound for any single FIT subimage defaults to +`WOLFBOOT_FIT_MAX_DECOMP = 256 MB`. Override per target via +`CFLAGS+=-DWOLFBOOT_FIT_MAX_DECOMP=...` if a kernel/ramdisk legitimately +expands beyond that. The outer wolfBoot signature still authenticates the +entire FIT; this cap is defense-in-depth against a malformed-but-signed +stream. + +**FIT ramdisk (initramfs)** + +When PetaLinux is built with `INITRAMFS_IMAGE_BUNDLE = "0"` the rootfs cpio +ships as a separate `ramdisk` node in the FIT alongside the kernel and DTB. +wolfBoot can extract it, copy it to a configurable RAM address, and patch the +loaded DTB with `/chosen/linux,initrd-{start,end}` so the kernel finds it. +Enable this with `FIT_RAMDISK=1`: + +```sh +cp config/examples/zynqmp_sdcard.config .config +# Uncomment the FIT_RAMDISK / WOLFBOOT_LOAD_RAMDISK_ADDRESS / LINUX_BOOTARGS +# block under "Optional: FIT-bundled initramfs" and comment out the +# LINUX_BOOTARGS_ROOT line above it. +make +``` + +Key options (in `config/examples/zynqmp_sdcard.config`): +- `FIT_RAMDISK=1` - enables FIT ramdisk extraction (`-DWOLFBOOT_FIT_RAMDISK`). +- `WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40000000` - destination address. Pick a + region clear of the kernel image (`~0x80000` + tens of MB) and clear of + FIT staging (`WOLFBOOT_LOAD_ADDRESS=0x10000000` + FIT size). The default + `0x40000000` leaves ~1 GB of headroom on a 4 GB ZCU102. Set to `0` to + honor the FIT's own `load = <...>` property verbatim instead. +- `LINUX_BOOTARGS` should drop `root=...` since the ramdisk is the rootfs. + +Compressed (gzip) ramdisks are supported transparently when `GZIP=1` is set +(the same gzip path used for the kernel handles `compression = "gzip"` on +the ramdisk node). The outer wolfBoot signature already authenticates the +entire FIT, so the ramdisk inherits authentication without per-image +hashing. Per-image `hash-1` subnodes (if present) are not re-verified at +runtime - per the FIT spec they hash the in-FIT `data` bytes, which the +outer wolfBoot signature already covers. + +Example FIT layout: + +```dts +images { + kernel-1 { ... }; + fdt-1 { ... }; + ramdisk-1 { + data = /incbin/("rootfs.cpio.gz"); + type = "ramdisk"; + compression = "gzip"; /* or "none" */ + load = <0x40000000>; /* required for decompression / relocation */ + hash-1 { algo = "sha256"; }; + }; +}; +configurations { + default = "conf-zcu102"; + conf-zcu102 { + kernel = "kernel-1"; + fdt = "fdt-1"; + ramdisk = "ramdisk-1"; + }; +}; +``` + +Successful boot prints: +``` +Loading ramdisk: 0x... -> 0x40000000 (N bytes) +FDT: Set chosen (...), linux,initrd-start=1073741824 +FDT: Set chosen (...), linux,initrd-end=... +``` + ## Versal Gen 1 VMK180 @@ -3544,7 +3692,7 @@ Application running successfully! Entering idle loop... ``` -**Booting PetaLinux (QSPI)** +**Booting Linux via FIT image (QSPI)** wolfBoot can boot a signed Linux kernel on the Versal VMK180, replacing U-Boot entirely for a secure boot chain. @@ -3552,6 +3700,7 @@ Prerequisites: 1. **PetaLinux 2024.2** (or compatible version) built for VMK180 2. **Pre-built Linux images** from your PetaLinux build: - `Image` - Uncompressed Linux kernel (ARM64) + - `Image.gz` - gzip-compressed kernel (used with `GZIP=1`, see below) - `system-default.dtb` - Device tree blob for VMK180 3. **SD card** with root filesystem (PetaLinux rootfs.ext4 written to partition 2) @@ -3560,7 +3709,53 @@ wolfBoot uses a FIT (Flattened Image Tree) image containing the kernel and devic - DTB load address: `0x00001000` - SHA256 hashes for integrity -Create and sign the FIT image, then flash to QSPI: +`config/examples/versal_vmk180.config` and `config/examples/versal_vmk180_sdcard.config` +default to `GZIP=1`, so wolfBoot can decompress a gzipped kernel at boot +time. Pick one of the two flows below. + +##### Option A - Compressed kernel (`GZIP=1`, default) + +Edit `hal/versal.its` to point the kernel `data` at the gzipped kernel +and set `compression = "gzip"`: + +```dts +images { + kernel-1 { + data = /incbin/("Image.gz"); + compression = "gzip"; + load = <0x00200000>; + entry = <0x00200000>; + hash-1 { algo = "sha256"; }; + ... + }; +}; +``` + +Then build and flash the FIT directly - no manual `gunzip` step: + +```sh +cp /path/to/petalinux/images/linux/Image.gz . +cp /path/to/petalinux/images/linux/system-default.dtb . +mkimage -f hal/versal.its fitImage +./tools/keytools/sign --ecc384 --sha384 fitImage wolfboot_signing_private_key.der 1 + +tftp ${loadaddr} fitImage_v1_signed.bin +sf probe 0 +sf erase 0x800000 +${filesize} +sf write ${loadaddr} 0x800000 ${filesize} +``` + +The compressed FIT is roughly half the size of the uncompressed equivalent +on a typical PetaLinux ARM64 kernel, which lets a larger kernel fit in the +existing 44 MB QSPI partition. wolfBoot decompresses to `0x00200000` at boot. +Integrity is provided by the outer wolfBoot signature over the entire FIT +plus gzip's CRC32 + ISIZE trailer on the decompressed payload; per-image +`hash-1` subnodes are not re-verified at runtime. + +##### Option B - Uncompressed kernel (`GZIP=0`) + +Build wolfBoot with `GZIP=0` and use the uncompressed `Image` directly. +Keep `compression = "none"` in `hal/versal.its`: ```sh cp /path/to/petalinux/images/linux/Image . diff --git a/hal/mpfs250.c b/hal/mpfs250.c index 461a0e10ed..152724ccd8 100644 --- a/hal/mpfs250.c +++ b/hal/mpfs250.c @@ -327,8 +327,10 @@ int hal_dts_fixup(void* dts_addr) wolfBoot_printf("FDT: Version %d, Size %d\n", fdt_version(fdt), fdt_totalsize(fdt)); - /* Expand total size to allow adding/modifying properties */ - fdt_set_totalsize(fdt, fdt_totalsize(fdt) + 512); + /* Expand total size to allow adding/modifying properties. + * Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h. */ + fdt_set_totalsize(fdt, + fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM); /* Find /chosen node */ off = fdt_find_node_offset(fdt, -1, "chosen"); diff --git a/hal/versal.c b/hal/versal.c index 46d64d46fc..8be09fc5a7 100644 --- a/hal/versal.c +++ b/hal/versal.c @@ -1276,8 +1276,11 @@ int hal_dts_fixup(void* dts_addr) wolfBoot_printf("FDT: Version %d, Size %d\n", fdt_version(fdt), fdt_totalsize(fdt)); - /* Expand total size to allow adding/modifying properties */ - fdt_set_totalsize(fdt, fdt_totalsize(fdt) + 512); + /* Expand total size to allow adding/modifying properties (bootargs and, + * when WOLFBOOT_FIT_RAMDISK is in play, linux,initrd-{start,end}). + * Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM in include/fdt.h. */ + fdt_set_totalsize(fdt, + fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM); /* Find /chosen node; create it only if genuinely missing. Any other * negative return (malformed FDT, etc.) is surfaced directly rather diff --git a/hal/zynq.c b/hal/zynq.c index 2105d7fa5a..fb1262345d 100644 --- a/hal/zynq.c +++ b/hal/zynq.c @@ -1927,11 +1927,14 @@ int hal_dts_fixup(void* dts_addr) fdt_version(fdt), fdt_totalsize(fdt)); /* Expand totalsize so fdt_setprop() has in-blob free space to place - * a new/larger bootargs property. Physical headroom is already - * guaranteed by the load-address layout (DTB at WOLFBOOT_LOAD_DTS_ADDRESS, - * kernel loaded much higher), so growing the header is safe. Matches - * the pattern used in hal/versal.c:hal_dts_fixup. */ - fdt_set_totalsize(fdt, fdt_totalsize(fdt) + 512); + * a new/larger bootargs property and (when WOLFBOOT_FIT_RAMDISK is in + * play) the linux,initrd-{start,end} properties. Physical headroom is + * already guaranteed by the load-address layout (DTB at + * WOLFBOOT_LOAD_DTS_ADDRESS, kernel loaded much higher), so growing + * the header is safe. Sizing comes from WOLFBOOT_FDT_FIXUP_HEADROOM + * in include/fdt.h - same constant as hal/versal.c. */ + fdt_set_totalsize(fdt, + fdt_totalsize(fdt) + WOLFBOOT_FDT_FIXUP_HEADROOM); /* Find /chosen node; create it only if genuinely missing. Any other * negative return (malformed FDT, etc.) is surfaced directly rather diff --git a/include/fdt.h b/include/fdt.h index ff0fb44991..69941c03ac 100644 --- a/include/fdt.h +++ b/include/fdt.h @@ -127,6 +127,16 @@ uint64_t fdt64_to_cpu(uint64_t x); #define fdt_set_size_dt_strings(fdt, val) (fdt_set_header(fdt, size_dt_strings, (val))) #define fdt_set_size_dt_struct(fdt, val) (fdt_set_header(fdt, size_dt_struct, (val))) +/* Headroom (bytes) appended to fdt_totalsize() before wolfBoot inserts + * /chosen properties. Sized to comfortably hold a full LINUX_BOOTARGS + * plus, when WOLFBOOT_FIT_RAMDISK is enabled, two 64-bit + * linux,initrd-{start,end} cells with property-name overhead. A target + * whose hal_dts_fixup() inserts more chosen entries can override this + * with -DWOLFBOOT_FDT_FIXUP_HEADROOM=. */ +#ifndef WOLFBOOT_FDT_FIXUP_HEADROOM +#define WOLFBOOT_FDT_FIXUP_HEADROOM 768 +#endif + int fdt_check_header(const void *fdt); int fdt_next_node(const void *fdt, int offset, int *depth); int fdt_first_property_offset(const void *fdt, int nodeoffset); @@ -158,8 +168,29 @@ int fdt_fixup_val64(void* fdt, int off, const char* node, const char* name, uint int fdt_shrink(void* fdt); /* FIT */ -const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt); +const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt, + 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 + * or a negative FDT_ERR_*. */ +int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size); + +#ifdef WOLFBOOT_FIT_RAMDISK +/* Load a FIT ramdisk subimage (optionally relocated to + * WOLFBOOT_LOAD_RAMDISK_ADDRESS) and patch /chosen/linux,initrd-* + * in the supplied DTB. Returns 0 on success, -1 on load failure. + * Callers typically ignore the return value (log-and-continue). */ +int fit_load_ramdisk(void* fit, const char* ramdisk_node, void* dts_addr); +#endif #ifdef __cplusplus } diff --git a/include/gzip.h b/include/gzip.h new file mode 100644 index 0000000000..af16835121 --- /dev/null +++ b/include/gzip.h @@ -0,0 +1,75 @@ +/* gzip.h + * + * Native gzip decompression for wolfBoot FIT subimages. + * + * Clean-room implementation of RFC 1951 (DEFLATE) and RFC 1952 (gzip). + * + * Compile with GZIP=1. + * + * + * 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 + */ +#ifndef WOLFBOOT_GZIP_H +#define WOLFBOOT_GZIP_H + +#include + +/* error codes */ +#define WOLFBOOT_GZIP_E_FORMAT -1 /* bad magic / method / reserved bits */ +#define WOLFBOOT_GZIP_E_TRUNCATED -2 /* input ended mid-stream */ +#define WOLFBOOT_GZIP_E_OUTPUT -3 /* output would exceed out_max */ +#define WOLFBOOT_GZIP_E_HUFFMAN -4 /* invalid Huffman tree / code */ +#define WOLFBOOT_GZIP_E_DISTANCE -5 /* back-ref distance > bytes written */ +#define WOLFBOOT_GZIP_E_CRC32 -6 /* trailer CRC32 mismatch */ +#define WOLFBOOT_GZIP_E_ISIZE -7 /* trailer ISIZE mismatch */ +#define WOLFBOOT_GZIP_E_PARAM -8 /* invalid parameter */ + +/* RFC 1952 gzip wrapper constants (format-defining; useful to callers + * that pre-validate the gzip magic/method before invoking the inflater). + * All other DEFLATE-internal constants live in src/gzip.c. */ +#define GZIP_MAGIC_ID1 0x1FU /* first magic byte */ +#define GZIP_MAGIC_ID2 0x8BU /* second magic byte */ +#define GZIP_CM_DEFLATE 8 /* CM = DEFLATE */ +#define GZIP_HEADER_MIN_SIZE 10 /* magic+CM+FLG+MTIME+XFL+OS */ +#define GZIP_TRAILER_SIZE 8 /* CRC32 + ISIZE */ + +/* RFC 1952 Sec. 2.3.1 header flag bits (FLG byte) */ +#define GZIP_FLG_FTEXT 0x01 +#define GZIP_FLG_FHCRC 0x02 +#define GZIP_FLG_FEXTRA 0x04 +#define GZIP_FLG_FNAME 0x08 +#define GZIP_FLG_FCOMMENT 0x10 +#define GZIP_FLG_RESERVED 0xE0 + +/* Decompress a gzip stream. + * + * in - pointer to gzip stream (RFC 1952 wrapper around RFC 1951 DEFLATE) + * in_len - length of gzip stream in bytes + * out - destination buffer; also used as the DEFLATE sliding window, + * so the output region must be readable as well as writable. + * out_max - maximum bytes that may be written to out + * out_len - on success, set to the number of bytes written + * + * Returns 0 on success, negative WOLFBOOT_GZIP_E_* on error. + */ +int wolfBoot_gunzip(const uint8_t *in, uint32_t in_len, + uint8_t *out, uint32_t out_max, + uint32_t *out_len); + +#endif /* WOLFBOOT_GZIP_H */ diff --git a/include/target.h.in b/include/target.h.in index 56aeca0cc7..59fbe910de 100644 --- a/include/target.h.in +++ b/include/target.h.in @@ -157,5 +157,10 @@ @WOLFBOOT_RAMBOOT_MAX_SIZE_DEFINE@ #define WOLFBOOT_LOAD_DTS_ADDRESS @WOLFBOOT_LOAD_DTS_ADDRESS@ +/* Load address for FIT ramdisk extraction (used when WOLFBOOT_FIT_RAMDISK + * is set; otherwise unused). 0 means "use the FIT image's `load` property + * verbatim — do not relocate". */ +#define WOLFBOOT_LOAD_RAMDISK_ADDRESS @WOLFBOOT_LOAD_RAMDISK_ADDRESS@ + #endif /* !H_TARGETS_TARGET_ */ diff --git a/options.mk b/options.mk index b70cb4e6b8..e84b1d1c8c 100644 --- a/options.mk +++ b/options.mk @@ -900,6 +900,22 @@ ifeq ($(DELTA_UPDATES),1) endif endif +# GZIP=1 enables native gzip decompression of FIT subimages +# (RFC 1951 + RFC 1952). Enabled by default in FIT-using example configs. +GZIP ?= 0 +ifeq ($(GZIP),1) + OBJS += src/gzip.o + CFLAGS+=-DWOLFBOOT_GZIP +endif + +# FIT_RAMDISK=1 enables FIT ramdisk (initramfs) extraction and DTB +# /chosen/linux,initrd-{start,end} fixup. Compressed (gzip) ramdisks +# decompress through the same path when GZIP=1. +FIT_RAMDISK ?= 0 +ifeq ($(FIT_RAMDISK),1) + CFLAGS+=-DWOLFBOOT_FIT_RAMDISK +endif + ifeq ($(ARMORED),1) CFLAGS+=-DWOLFBOOT_ARMORED endif diff --git a/src/fdt.c b/src/fdt.c index 86b6afadff..b31feee971 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -30,6 +30,25 @@ #include "string.h" #include +#ifdef WOLFBOOT_GZIP +#include "gzip.h" +#endif + +/* 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 +#define WOLFBOOT_FIT_MAX_DECOMP (256U * 1024U * 1024U) +#endif + uint32_t cpu_to_fdt32(uint32_t x) { #ifdef BIG_ENDIAN_ORDER @@ -787,10 +806,11 @@ int fdt_fixup_val64(void* fdt, int off, const char* node, const char* name, /* FIT Specific */ -const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt) +const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_dt, + const char** pramdisk) { const void* val; - const char *conf = NULL, *kernel = NULL, *flat_dt = NULL; + const char *conf = NULL, *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; int off, len = 0; /* Find the default configuration (optional) */ @@ -806,6 +826,7 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ if (off > 0) { kernel = fdt_getprop(fdt, off, "kernel", &len); flat_dt = fdt_getprop(fdt, off, "fdt", &len); + ramdisk = fdt_getprop(fdt, off, "ramdisk", &len); } } if (kernel == NULL) { @@ -828,19 +849,151 @@ const char* fit_find_images(void* fdt, const char** pkernel, const char** pflat_ } } } + if (ramdisk == NULL) { + /* find node with "type" == ramdisk */ + off = fdt_find_prop_offset(fdt, -1, "type", "ramdisk"); + if (off > 0) { + val = fdt_get_name(fdt, off, &len); + if (val != NULL && len > 0) { + ramdisk = (const char*)val; + } + } + } if (pkernel) *pkernel = kernel; if (pflat_dt) *pflat_dt = flat_dt; + if (pramdisk) + *pramdisk = ramdisk; return conf; } -void* fit_load_image(void* fdt, const char* image, int* lenp) +int fdt_fixup_initrd(void* fdt, uint64_t start, uint64_t size) +{ + int off, ret; + uint64_t end; + + if (fdt == NULL) { + return -1; + } + + end = start + size; + + off = fdt_find_node_offset(fdt, -1, "chosen"); + if (off == -FDT_ERR_NOTFOUND) { + off = fdt_add_subnode(fdt, 0, "chosen"); + } + if (off < 0) { + return off; + } + + ret = fdt_fixup_val64(fdt, off, "chosen", "linux,initrd-start", start); + if (ret < 0) { + return ret; + } + ret = fdt_fixup_val64(fdt, off, "chosen", "linux,initrd-end", end); + if (ret < 0) { + return ret; + } + + return 0; +} + +#ifdef WOLFBOOT_FIT_RAMDISK +/* Defensive fallback: targets without a fixed relocation address + * leave WOLFBOOT_LOAD_RAMDISK_ADDRESS at 0, in which case the + * ramdisk is used in place. */ +#ifndef WOLFBOOT_LOAD_RAMDISK_ADDRESS +#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 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 + * (log-and-continue), so a missing/failed ramdisk does not abort + * the boot. */ +int fit_load_ramdisk(void* fit, const char* ramdisk_node, void* dts_addr) +{ + int rd_size = 0; + uint8_t *rd_ptr; + uint8_t *rd_dst; + + if (fit == NULL || ramdisk_node == NULL) { + return -1; + } + + if (WOLFBOOT_LOAD_RAMDISK_ADDRESS != 0) { + rd_dst = (uint8_t*)WOLFBOOT_LOAD_RAMDISK_ADDRESS; + 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) { + 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 */ + +/* 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; + const char *comp; + int complen = 0; +#ifndef WOLFBOOT_GZIP + (void)out_max; +#endif off = fdt_find_node_offset(fdt, -1, image); if (off > 0) { @@ -848,13 +1001,96 @@ void* fit_load_image(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 (data != NULL && load != NULL && data != load) { - wolfBoot_printf("Loading Image %s: %p -> %p (%d bytes)\n", - image, data, load, len); - memcpy(load, data, len); - - /* load should always have entry, but if not use load address */ - data = (entry != NULL) ? entry : load; + 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; + /* Detect compression unconditionally (independent of whether + * a valid distinct load destination is available) so we can + * fail closed when the build lacks support, when the scheme + * is unknown, or when there is no place to decompress to - + * instead of silently passing compressed bytes through as + * raw. */ + comp = (const char*)fdt_getprop(fdt, off, "compression", + &complen); + if (comp != NULL && complen > 0) { + if (strcmp(comp, "gzip") == 0) { + is_gzip = 1; + } + else if (strcmp(comp, "none") != 0) { + is_unknown_comp = 1; + } + } + if (load != NULL && data != load) { + if (is_gzip) { +#ifdef WOLFBOOT_GZIP + uint32_t out_len = 0; + int rc; + wolfBoot_printf("Decompressing Image %s (gzip): " + "%p -> %p (%d bytes)\n", image, data, load, len); + rc = wolfBoot_gunzip((const uint8_t*)data, + (uint32_t)len, (uint8_t*)load, out_max, &out_len); + if (rc != 0) { + wolfBoot_printf("FIT gunzip failed for %s: rc=%d " + "(wrote %u bytes)\n", image, rc, out_len); + return NULL; + } + len = (int)out_len; + wolfBoot_printf("Decompressed %s: %u bytes\n", image, + out_len); +#else + wolfBoot_printf("FIT: subimage '%s' has compression=" + "\"gzip\" but WOLFBOOT_GZIP is not enabled in " + "this build (rebuild with GZIP=1)\n", image); + return NULL; +#endif + } + else if (is_unknown_comp) { + /* Unknown compression scheme; fail closed rather + * than silently memcpy compressed bytes as raw. */ + wolfBoot_printf("FIT: subimage '%s' has unsupported " + "compression=\"%s\"\n", image, comp); + return NULL; + } + else { + wolfBoot_printf("Loading Image %s: %p -> %p " + "(%d bytes)\n", image, data, load, len); + memcpy(load, data, len); + } + + /* No per-image hash-1 re-verification here. Per the + * FIT spec (and U-Boot's reference implementation), a + * hash-N subnode's value is computed over the image + * node's `data` property bytes verbatim - which means + * the compressed bytes when compression="gzip". The + * outer wolfBoot signature + * (wolfBoot_verify_authenticity) already authenticates + * the entire FIT, including those data bytes, so a + * runtime per-image hash check would be redundant. + * Inflater bugs on the decompressed payload are + * caught by gzip's own CRC32 + ISIZE trailer inside + * wolfBoot_gunzip. */ + + /* load should always have entry, but if not use load + * address */ + data = (entry != NULL) ? entry : load; + } + else if (is_gzip || is_unknown_comp) { + /* Compression declared but no distinct destination to + * decompress into. Refuse rather than hand the caller + * a pointer to still-compressed bytes. */ + wolfBoot_printf("FIT: subimage '%s' declares " + "compression=\"%s\" but has no distinct load " + "destination (load=%p, data=%p); refusing to pass " + "compressed bytes through as raw\n", + image, comp, load, data); + return NULL; + } } wolfBoot_printf("Image %s: %p (%d bytes)\n", image, data, len); } @@ -868,4 +1104,24 @@ 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) +{ + 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/gzip.c b/src/gzip.c new file mode 100644 index 0000000000..994ab368ac --- /dev/null +++ b/src/gzip.c @@ -0,0 +1,799 @@ +/* gzip.c + * + * Clean-room implementation of RFC 1951 (DEFLATE) and RFC 1952 (gzip) + * decompression for wolfBoot. Written from the RFC text only; no derivative + * work from zlib, miniz, or other implementations. + * + * Design notes: + * - Single-pass inflate. The output buffer doubles as the LZ77 sliding + * window, so back-references read from out[out_pos - distance]. + * - Canonical Huffman decode using counts[] / symbols[] tables. Slightly + * slower than a fast lookup table but ~10x smaller in code size, which + * matters for the bootloader. + * - No dynamic allocation; state lives on the caller's stack (~6 KB peak). + * - CRC32 IEEE 802.3 polynomial computed on-the-fly during output. + * + * + * 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 + */ +#ifdef WOLFBOOT_GZIP + +#include "gzip.h" +#include +#include + +/* RFC 1951/1952 implementation-detail constants. These were previously + * in include/gzip.h but are not part of the public API of this module + * (no WOLFBOOT_ prefix and no out-of-file references). */ + +/* RFC 1952 CRC32 (IEEE 802.3 reflected) */ +#define GZIP_CRC32_INIT 0xFFFFFFFFU +#define GZIP_CRC32_FINAL_XOR 0xFFFFFFFFU +#define GZIP_CRC32_POLY 0xEDB88320U + +/* RFC 1951 DEFLATE - alphabet sizes */ +#define GZIP_MAX_HUFF_BITS 15 /* max Huffman code length */ +#define GZIP_CL_CODES 19 /* code-length alphabet */ +#define GZIP_LITLEN_CODES 288 /* literal/length alphabet */ +#define GZIP_DIST_CODES 32 /* distance alphabet */ + +/* RFC 1951 DEFLATE - fixed Huffman boundaries (Sec. 3.2.6) */ +#define GZIP_FIXED_LIT_END_8BIT 144 /* 0..143 -> 8 bits */ +#define GZIP_FIXED_LIT_END_9BIT 256 /* 144..255 -> 9 bits */ +#define GZIP_FIXED_LIT_END_7BIT 280 /* 256..279 -> 7 bits */ +#define GZIP_FIXED_LIT_END 288 /* 280..287 -> 8 bits */ +#define GZIP_FIXED_DIST_COUNT 30 /* 0..29 -> 5 bits */ + +/* RFC 1951 DEFLATE - alphabet bounds (Sec. 3.2.4 / 3.2.5) */ +#define GZIP_EOB_SYMBOL 256 /* end-of-block marker */ +#define GZIP_LENGTH_CODE_BASE 257 /* first length code */ +#define GZIP_LENGTH_CODE_COUNT 29 /* 257..285 */ +#define GZIP_DIST_CODE_COUNT 30 /* 0..29 */ + +/* RFC 1951 DEFLATE - dynamic block header (Sec. 3.2.7) */ +#define GZIP_HLIT_BITS 5 /* HLIT field width */ +#define GZIP_HDIST_BITS 5 /* HDIST field width */ +#define GZIP_HCLEN_BITS 4 /* HCLEN field width */ +#define GZIP_HLIT_BASE 257 /* HLIT + 257 */ +#define GZIP_HDIST_BASE 1 /* HDIST + 1 */ +#define GZIP_HCLEN_BASE 4 /* HCLEN + 4 */ +#define GZIP_CL_LEN_BITS 3 /* code-length code is 3 bits */ + +/* RFC 1951 DEFLATE - run-length repeat symbols (Sec. 3.2.7). + * sym 16: 2 extra bits, repeat previous length 3..6 times + * sym 17: 3 extra bits, repeat zero 3..10 times + * sym 18: 7 extra bits, repeat zero 11..138 times + */ +#define GZIP_REPEAT_PREV_EXTRA 2 +#define GZIP_REPEAT_PREV_BASE 3 +#define GZIP_REPEAT_Z3_EXTRA 3 +#define GZIP_REPEAT_Z3_BASE 3 +#define GZIP_REPEAT_Z7_EXTRA 7 +#define GZIP_REPEAT_Z7_BASE 11 + +/* RFC 1951 Sec. 3.2.5: length codes 257..285 base values and extra bits */ +static const uint16_t gz_len_base[29] = { + 3, 4, 5, 6, 7, 8, 9, 10, + 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, + 131, 163, 195, 227, 258 +}; +static const uint8_t gz_len_extra[29] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 5, 5, 0 +}; + +/* RFC 1951 Sec. 3.2.5: distance codes 0..29 base values and extra bits */ +static const uint16_t gz_dist_base[30] = { + 1, 2, 3, 4, 5, 7, 9, 13, + 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, + 4097, 6145, 8193, 12289, 16385, 24577 +}; +static const uint8_t gz_dist_extra[30] = { + 0, 0, 0, 0, 1, 1, 2, 2, + 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +}; + +/* RFC 1951 Sec. 3.2.7: code-length code permutation */ +static const uint8_t gz_cl_order[GZIP_CL_CODES] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +typedef struct gz_state { + /* input bit stream */ + const uint8_t *in; + uint32_t in_len; + uint32_t in_pos; + uint32_t bit_buf; + int bit_count; + + /* output buffer (doubles as sliding window) */ + uint8_t *out; + uint32_t out_max; + uint32_t out_pos; + + /* running CRC32 of decompressed bytes */ + uint32_t crc32; +} gz_state_t; + +typedef struct gz_huff { + int16_t counts[GZIP_MAX_HUFF_BITS + 1]; + int16_t symbols[GZIP_LITLEN_CODES]; +} gz_huff_t; + +/* ------------------------------------------------------------------------- */ +/* CRC32 */ +/* ------------------------------------------------------------------------- */ + +static uint32_t gz_crc32_byte(uint32_t crc, uint8_t b) +{ + int k; + crc ^= b; + for (k = 0; k < 8; k++) { + if (crc & 1U) { + crc = (crc >> 1) ^ GZIP_CRC32_POLY; + } else { + crc = crc >> 1; + } + } + return crc; +} + +/* ------------------------------------------------------------------------- */ +/* Bit stream reader (LSB-first within bytes per RFC 1951 Sec. 3.1.1) */ +/* ------------------------------------------------------------------------- */ + +static int gz_need_bits(gz_state_t *s, int n) +{ + int ret = 0; + + while ((ret == 0) && (s->bit_count < n)) { + if (s->in_pos >= s->in_len) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + else { + s->bit_buf |= ((uint32_t)s->in[s->in_pos]) << s->bit_count; + s->in_pos++; + s->bit_count += 8; + } + } + return ret; +} + +static int gz_get_bits(gz_state_t *s, int n, uint32_t *val) +{ + int ret = gz_need_bits(s, n); + if (ret == 0) { + *val = s->bit_buf & (((uint32_t)1 << n) - 1); + s->bit_buf >>= n; + s->bit_count -= n; + } + return ret; +} + +static void gz_align_byte(gz_state_t *s) +{ + int drop = s->bit_count & 7; + s->bit_buf >>= drop; + s->bit_count -= drop; +} + +/* ------------------------------------------------------------------------- */ +/* Output writer (writes byte; updates CRC32; back-ref reads from same buf) */ +/* ------------------------------------------------------------------------- */ + +static int gz_emit_byte(gz_state_t *s, uint8_t b) +{ + int ret = 0; + + if (s->out_pos >= s->out_max) { + ret = WOLFBOOT_GZIP_E_OUTPUT; + } + else { + s->out[s->out_pos] = b; + s->out_pos++; + s->crc32 = gz_crc32_byte(s->crc32, b); + } + return ret; +} + +/* ------------------------------------------------------------------------- */ +/* Canonical Huffman build / decode */ +/* ------------------------------------------------------------------------- */ + +/* Build canonical Huffman decode tables from per-symbol code lengths. + * lengths[i] is the bit length of symbol i (0 = absent). + * Returns 0 on success, WOLFBOOT_GZIP_E_HUFFMAN on malformed (over-subscribed) + * trees. Empty alphabets and single-symbol trees are accepted (a common + * DEFLATE idiom for distance trees with one or zero codes). */ +static int gz_huff_build(gz_huff_t *h, const uint8_t *lengths, int n) +{ + int ret = 0; + int sym, len, left, all_zero; + int16_t offs[GZIP_MAX_HUFF_BITS + 1]; + + for (len = 0; len <= GZIP_MAX_HUFF_BITS; len++) { + h->counts[len] = 0; + } + for (sym = 0; (sym < n) && (ret == 0); sym++) { + if (lengths[sym] > GZIP_MAX_HUFF_BITS) { + ret = WOLFBOOT_GZIP_E_HUFFMAN; + } + else { + h->counts[lengths[sym]]++; + } + } + + /* Empty alphabet (all symbols absent) is permitted. */ + all_zero = (ret == 0) && (h->counts[0] == n); + + /* Kraft inequality: sum 2^(MAX-len) * counts[len] should be <= 2^MAX. + * Detect over-subscribed (left < 0). Under-subscribed trees (left > 0) + * with one or zero codes are accepted. */ + if ((ret == 0) && !all_zero) { + left = 1; + for (len = 1; (len <= GZIP_MAX_HUFF_BITS) && (ret == 0); len++) { + left <<= 1; + if ((int)h->counts[len] > left) { + ret = WOLFBOOT_GZIP_E_HUFFMAN; + } + else { + left -= (int)h->counts[len]; + } + } + } + + if ((ret == 0) && !all_zero) { + /* Compute starting offset of each length-bucket in symbols[] */ + offs[1] = 0; + for (len = 1; len < GZIP_MAX_HUFF_BITS; len++) { + offs[len + 1] = (int16_t)(offs[len] + h->counts[len]); + } + /* Sort symbols by code length, then symbol number (canonical order) */ + for (sym = 0; sym < n; sym++) { + int sl = lengths[sym]; + if (sl != 0) { + h->symbols[offs[sl]] = (int16_t)sym; + offs[sl]++; + } + } + } + return ret; +} + +/* Decode one symbol using canonical Huffman tables. Returns the symbol on + * success (always >= 0), or negative WOLFBOOT_GZIP_E_* on error. */ +static int gz_huff_decode(gz_state_t *s, const gz_huff_t *h) +{ + int ret = WOLFBOOT_GZIP_E_HUFFMAN; /* updated to symbol or i/o error */ + int code = 0; + int first = 0; + int index = 0; + int len, count, br_ret; + uint32_t bit; + + for (len = 1; (len <= GZIP_MAX_HUFF_BITS) && + (ret == WOLFBOOT_GZIP_E_HUFFMAN); len++) { + br_ret = gz_get_bits(s, 1, &bit); + if (br_ret != 0) { + ret = br_ret; + } + else { + code = (code << 1) | (int)bit; + count = h->counts[len]; + if (code - count < first) { + ret = h->symbols[index + (code - first)]; + } + else { + index += count; + first = (first + count) << 1; + } + } + } + return ret; +} + +/* ------------------------------------------------------------------------- */ +/* Block decoders */ +/* ------------------------------------------------------------------------- */ + +/* RFC 1951 Sec. 3.2.4: stored (uncompressed) block */ +static int gz_inflate_stored(gz_state_t *s) +{ + int ret = 0; + uint32_t len = 0, nlen; + + /* Discard remaining bits in current partial byte */ + gz_align_byte(s); + + /* LEN and NLEN are little-endian 16-bit words */ + if (s->in_pos + 4 > s->in_len) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + if (ret == 0) { + len = (uint32_t)s->in[s->in_pos] | + ((uint32_t)s->in[s->in_pos + 1] << 8); + nlen = (uint32_t)s->in[s->in_pos + 2] | + ((uint32_t)s->in[s->in_pos + 3] << 8); + s->in_pos += 4; + + if ((len ^ 0xFFFFU) != nlen) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + else if (s->in_pos + len > s->in_len) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + else { + /* No buffered bits remain after align_byte; clear defensively */ + s->bit_buf = 0; + s->bit_count = 0; + } + } + + while ((ret == 0) && (len > 0)) { + ret = gz_emit_byte(s, s->in[s->in_pos]); + if (ret == 0) { + s->in_pos++; + len--; + } + } + return ret; +} + +/* Build the fixed Huffman trees defined in RFC 1951 Sec. 3.2.6 */ +static int gz_build_fixed(gz_huff_t *litlen, gz_huff_t *dist) +{ + int ret; + uint8_t lengths[GZIP_LITLEN_CODES]; + int i; + + for (i = 0; i < GZIP_FIXED_LIT_END_8BIT; i++) lengths[i] = 8; + for (i = GZIP_FIXED_LIT_END_8BIT; i < GZIP_FIXED_LIT_END_9BIT; i++) lengths[i] = 9; + for (i = GZIP_FIXED_LIT_END_9BIT; i < GZIP_FIXED_LIT_END_7BIT; i++) lengths[i] = 7; + for (i = GZIP_FIXED_LIT_END_7BIT; i < GZIP_FIXED_LIT_END; i++) lengths[i] = 8; + ret = gz_huff_build(litlen, lengths, GZIP_FIXED_LIT_END); + if (ret == 0) { + for (i = 0; i < GZIP_FIXED_DIST_COUNT; i++) lengths[i] = 5; + ret = gz_huff_build(dist, lengths, GZIP_FIXED_DIST_COUNT); + } + return ret; +} + +/* Inflate the body of a Huffman-coded block (fixed or dynamic) until the + * end-of-block symbol (256) is decoded. */ +static int gz_inflate_huffman(gz_state_t *s, + const gz_huff_t *litlen, const gz_huff_t *dist) +{ + int ret = 0; + int done = 0; + int sym, li; + uint32_t length, distance, extra, copy_pos; + + while ((ret == 0) && !done) { + sym = gz_huff_decode(s, litlen); + if (sym < 0) { + ret = sym; + } + else if (sym < GZIP_EOB_SYMBOL) { + ret = gz_emit_byte(s, (uint8_t)sym); + } + else if (sym == GZIP_EOB_SYMBOL) { + done = 1; + } + else { + /* length code 257..285 -> length 3..258 */ + li = sym - GZIP_LENGTH_CODE_BASE; + if (li >= GZIP_LENGTH_CODE_COUNT) { + ret = WOLFBOOT_GZIP_E_HUFFMAN; + } + length = 0; + if (ret == 0) { + length = gz_len_base[li]; + if (gz_len_extra[li] > 0) { + ret = gz_get_bits(s, gz_len_extra[li], &extra); + if (ret == 0) { + length += extra; + } + } + } + + distance = 0; + if (ret == 0) { + sym = gz_huff_decode(s, dist); + if (sym < 0) { + ret = sym; + } + else if (sym >= GZIP_DIST_CODE_COUNT) { + ret = WOLFBOOT_GZIP_E_HUFFMAN; + } + else { + distance = gz_dist_base[sym]; + if (gz_dist_extra[sym] > 0) { + ret = gz_get_bits(s, gz_dist_extra[sym], &extra); + if (ret == 0) { + distance += extra; + } + } + } + } + + if (ret == 0) { + if ((distance == 0) || (distance > s->out_pos)) { + ret = WOLFBOOT_GZIP_E_DISTANCE; + } + else if (s->out_pos + length > s->out_max) { + ret = WOLFBOOT_GZIP_E_OUTPUT; + } + } + + /* LZ77 copy. Output buffer doubles as the window. Copy must be + * byte-by-byte to support overlapping runs (length > distance). */ + if (ret == 0) { + copy_pos = s->out_pos - distance; + while ((ret == 0) && (length > 0)) { + ret = gz_emit_byte(s, s->out[copy_pos]); + if (ret == 0) { + copy_pos++; + length--; + } + } + } + } + } + return ret; +} + +/* RFC 1951 Sec. 3.2.7: dynamic Huffman block. + * Decodes the code-length code, expands it into the literal/length and + * distance trees, then runs gz_inflate_huffman() on the block body. */ +static int gz_inflate_dynamic(gz_state_t *s) +{ + int ret; + uint8_t cl_lens[GZIP_CL_CODES]; + uint8_t code_lens[GZIP_LITLEN_CODES + GZIP_DIST_CODES]; + gz_huff_t cl_huff; + gz_huff_t litlen_huff; + gz_huff_t dist_huff; + uint32_t hlit = 0, hdist = 0, hclen = 0, val; + int i, total, idx, sym; + uint8_t prev = 0; + + for (i = 0; i < (int)(sizeof(code_lens) / sizeof(code_lens[0])); i++) { + code_lens[i] = 0; + } + + ret = gz_get_bits(s, GZIP_HLIT_BITS, &hlit); + if (ret == 0) { + hlit += GZIP_HLIT_BASE; + ret = gz_get_bits(s, GZIP_HDIST_BITS, &hdist); + } + if (ret == 0) { + hdist += GZIP_HDIST_BASE; + ret = gz_get_bits(s, GZIP_HCLEN_BITS, &hclen); + } + if (ret == 0) { + hclen += GZIP_HCLEN_BASE; + if ((hlit > GZIP_LITLEN_CODES) || (hdist > GZIP_DIST_CODES) || + (hclen > GZIP_CL_CODES)) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + } + + /* Read code-length code lengths in the permuted order */ + if (ret == 0) { + for (i = 0; i < GZIP_CL_CODES; i++) { + cl_lens[i] = 0; + } + for (i = 0; (i < (int)hclen) && (ret == 0); i++) { + ret = gz_get_bits(s, GZIP_CL_LEN_BITS, &val); + if (ret == 0) { + cl_lens[gz_cl_order[i]] = (uint8_t)val; + } + } + } + if (ret == 0) { + ret = gz_huff_build(&cl_huff, cl_lens, GZIP_CL_CODES); + } + + /* Decode the litlen + dist code-length sequence using the CL tree */ + if (ret == 0) { + total = (int)hlit + (int)hdist; + idx = 0; + while ((ret == 0) && (idx < total)) { + sym = gz_huff_decode(s, &cl_huff); + if (sym < 0) { + ret = sym; + } + else if (sym < 16) { + code_lens[idx++] = (uint8_t)sym; + prev = (uint8_t)sym; + } + else if (sym == 16) { + /* repeat previous length 3..6 times (2 extra bits) */ + if (idx == 0) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + else { + ret = gz_get_bits(s, GZIP_REPEAT_PREV_EXTRA, &val); + if (ret == 0) { + val += GZIP_REPEAT_PREV_BASE; + if (idx + (int)val > total) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + else { + while (val--) code_lens[idx++] = prev; + } + } + } + } + else if (sym == 17) { + /* repeat zero 3..10 times (3 extra bits) */ + ret = gz_get_bits(s, GZIP_REPEAT_Z3_EXTRA, &val); + if (ret == 0) { + val += GZIP_REPEAT_Z3_BASE; + if (idx + (int)val > total) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + else { + while (val--) code_lens[idx++] = 0; + prev = 0; + } + } + } + else if (sym == 18) { + /* repeat zero 11..138 times (7 extra bits) */ + ret = gz_get_bits(s, GZIP_REPEAT_Z7_EXTRA, &val); + if (ret == 0) { + val += GZIP_REPEAT_Z7_BASE; + if (idx + (int)val > total) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + else { + while (val--) code_lens[idx++] = 0; + prev = 0; + } + } + } + else { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + } + } + + /* End-of-block symbol (256) must have a code */ + if ((ret == 0) && (code_lens[GZIP_EOB_SYMBOL] == 0)) { + ret = WOLFBOOT_GZIP_E_HUFFMAN; + } + + if (ret == 0) { + ret = gz_huff_build(&litlen_huff, code_lens, (int)hlit); + } + if (ret == 0) { + ret = gz_huff_build(&dist_huff, code_lens + hlit, (int)hdist); + } + if (ret == 0) { + ret = gz_inflate_huffman(s, &litlen_huff, &dist_huff); + } + return ret; +} + +/* ------------------------------------------------------------------------- */ +/* DEFLATE driver */ +/* ------------------------------------------------------------------------- */ + +static int gz_inflate(gz_state_t *s) +{ + int ret = 0; + uint32_t bfinal = 0, btype = 0; + gz_huff_t fixed_litlen; + gz_huff_t fixed_dist; + int fixed_built = 0; + + while ((ret == 0) && !bfinal) { + ret = gz_get_bits(s, 1, &bfinal); + if (ret == 0) { + ret = gz_get_bits(s, 2, &btype); + } + if (ret == 0) { + if (btype == 0) { + ret = gz_inflate_stored(s); + } + else if (btype == 1) { + if (!fixed_built) { + ret = gz_build_fixed(&fixed_litlen, &fixed_dist); + if (ret == 0) { + fixed_built = 1; + } + } + if (ret == 0) { + ret = gz_inflate_huffman(s, &fixed_litlen, &fixed_dist); + } + } + else if (btype == 2) { + ret = gz_inflate_dynamic(s); + } + else { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + } + } + return ret; +} + +/* ------------------------------------------------------------------------- */ +/* RFC 1952 wrapper */ +/* ------------------------------------------------------------------------- */ + +static int gz_skip_zstring(gz_state_t *s) +{ + int ret = WOLFBOOT_GZIP_E_TRUNCATED; + int done = 0; + + while ((ret != 0) && !done) { + if (s->in_pos >= s->in_len) { + done = 1; /* ret stays at TRUNCATED */ + } + else if (s->in[s->in_pos++] == 0) { + ret = 0; + done = 1; + } + } + return ret; +} + +static int gz_parse_header(gz_state_t *s) +{ + int ret = 0; + uint8_t flg = 0; + uint32_t xlen; + + if (s->in_len < GZIP_HEADER_MIN_SIZE) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + if (ret == 0) { + /* Magic 1F 8B, CM = 8 (DEFLATE) */ + if ((s->in[0] != GZIP_MAGIC_ID1) || (s->in[1] != GZIP_MAGIC_ID2) || + (s->in[2] != GZIP_CM_DEFLATE)) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + else { + flg = s->in[3]; + if (flg & GZIP_FLG_RESERVED) { + ret = WOLFBOOT_GZIP_E_FORMAT; + } + else { + /* Skip MTIME(4) + XFL(1) + OS(1) */ + s->in_pos = GZIP_HEADER_MIN_SIZE; + } + } + } + + if ((ret == 0) && (flg & GZIP_FLG_FEXTRA)) { + if (s->in_pos + 2 > s->in_len) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + else { + xlen = (uint32_t)s->in[s->in_pos] | + ((uint32_t)s->in[s->in_pos + 1] << 8); + s->in_pos += 2; + if (s->in_pos + xlen > s->in_len) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + else { + s->in_pos += xlen; + } + } + } + if ((ret == 0) && (flg & GZIP_FLG_FNAME)) { + ret = gz_skip_zstring(s); + } + if ((ret == 0) && (flg & GZIP_FLG_FCOMMENT)) { + ret = gz_skip_zstring(s); + } + if ((ret == 0) && (flg & GZIP_FLG_FHCRC)) { + if (s->in_pos + 2 > s->in_len) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + else { + s->in_pos += 2; /* header CRC; not validated */ + } + } + return ret; +} + +static int gz_parse_trailer(gz_state_t *s, uint32_t computed_crc, + uint32_t bytes_out) +{ + int ret = 0; + uint32_t got_crc, got_isize; + + /* Discard partial byte from final block, then read 8-byte trailer */ + gz_align_byte(s); + s->bit_buf = 0; + s->bit_count = 0; + + if (s->in_pos + GZIP_TRAILER_SIZE > s->in_len) { + ret = WOLFBOOT_GZIP_E_TRUNCATED; + } + if (ret == 0) { + got_crc = (uint32_t)s->in[s->in_pos] | + ((uint32_t)s->in[s->in_pos + 1] << 8) | + ((uint32_t)s->in[s->in_pos + 2] << 16) | + ((uint32_t)s->in[s->in_pos + 3] << 24); + got_isize = (uint32_t)s->in[s->in_pos + 4] | + ((uint32_t)s->in[s->in_pos + 5] << 8) | + ((uint32_t)s->in[s->in_pos + 6] << 16) | + ((uint32_t)s->in[s->in_pos + 7] << 24); + s->in_pos += GZIP_TRAILER_SIZE; + + if (got_crc != computed_crc) { + ret = WOLFBOOT_GZIP_E_CRC32; + } + else if (got_isize != bytes_out) { + ret = WOLFBOOT_GZIP_E_ISIZE; + } + } + return ret; +} + +/* ------------------------------------------------------------------------- */ +/* Public entry point */ +/* ------------------------------------------------------------------------- */ + +int wolfBoot_gunzip(const uint8_t *in, uint32_t in_len, + uint8_t *out, uint32_t out_max, + uint32_t *out_len) +{ + int ret = 0; + gz_state_t s; + + if ((in == NULL) || (out == NULL) || (out_len == NULL)) { + ret = WOLFBOOT_GZIP_E_PARAM; + } + else { + s.in = in; + s.in_len = in_len; + s.in_pos = 0; + s.bit_buf = 0; + s.bit_count = 0; + s.out = out; + s.out_max = out_max; + s.out_pos = 0; + s.crc32 = GZIP_CRC32_INIT; + + ret = gz_parse_header(&s); + if (ret == 0) { + ret = gz_inflate(&s); + } + if (ret == 0) { + /* Final CRC32 is the running register XOR'd with the final mask */ + s.crc32 ^= GZIP_CRC32_FINAL_XOR; + ret = gz_parse_trailer(&s, s.crc32, s.out_pos); + } + *out_len = s.out_pos; + } + return ret; +} + +#endif /* WOLFBOOT_GZIP */ diff --git a/src/update_disk.c b/src/update_disk.c index 2503cf9755..62631ae721 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -541,14 +541,20 @@ void RAMFUNCTION wolfBoot_start(void) /* Is this a Flattened uImage Tree (FIT) image (FDT format) */ if (wolfBoot_get_dts_size(load_address) > 0) { void* fit = (void*)load_address; - const char *kernel = NULL, *flat_dt = NULL; + const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n", fdt_version(fit), fdt_totalsize(fit)); - (void)fit_find_images(fit, &kernel, &flat_dt); + (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); @@ -560,6 +566,13 @@ void RAMFUNCTION wolfBoot_start(void) memcpy(dts_addr, dts_ptr, dts_size); } } +#ifdef WOLFBOOT_FIT_RAMDISK + if (ramdisk != NULL) { + (void)fit_load_ramdisk(fit, ramdisk, (void*)dts_addr); + } +#else + (void)ramdisk; +#endif } #endif diff --git a/src/update_ram.c b/src/update_ram.c index f8e3d0f9e4..6a2409a02d 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -368,14 +368,19 @@ void RAMFUNCTION wolfBoot_start(void) /* Is this a Flattened uImage Tree (FIT) image (FDT format) */ if (wolfBoot_get_dts_size(load_address) > 0) { void* fit = (void*)load_address; - const char *kernel = NULL, *flat_dt = NULL; + const char *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; wolfBoot_printf("Flattened uImage Tree: Version %d, Size %d\n", fdt_version(fit), fdt_totalsize(fit)); - (void)fit_find_images(fit, &kernel, &flat_dt); + (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); @@ -387,6 +392,13 @@ void RAMFUNCTION wolfBoot_start(void) memcpy(dts_addr, dts_ptr, dts_size); } } +#ifdef WOLFBOOT_FIT_RAMDISK + if (ramdisk != NULL) { + (void)fit_load_ramdisk(fit, ramdisk, (void*)dts_addr); + } +#else + (void)ramdisk; +#endif } else { /* Load DTS to RAM */ diff --git a/tools/config.mk b/tools/config.mk index 4ef65ca06b..15eefc3e01 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -93,6 +93,11 @@ ifeq ($(ARCH),) WOLFHSM_CLIENT_LOCAL_KEYS=0 endif +# Global default: 0 means "use the FIT image's `load` property verbatim". +# Defaulted globally (outside the CI ifeq block above) so FIT_RAMDISK=1 +# can be toggled on any target without forcing an explicit address. +WOLFBOOT_LOAD_RAMDISK_ADDRESS?=0 + CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO_DRIVERS \ MCUXPRESSO_CMSIS FREEDOM_E_SDK STM32CUBE CYPRESS_PDL CYPRESS_CORE_LIB CYPRESS_TARGET_LIB DEBUG VTOR \ CORTEX_M0 CORTEX_M7 CORTEX_M33 NO_ASM EXT_FLASH SPI_FLASH NO_XIP UART_FLASH ALLOW_DOWNGRADE NVM_FLASH_WRITEONCE \ @@ -109,7 +114,8 @@ CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO WOLFBOOT_PARTITION_SIZE WOLFBOOT_SECTOR_SIZE \ WOLFBOOT_PARTITION_BOOT_ADDRESS WOLFBOOT_PARTITION_UPDATE_ADDRESS \ WOLFBOOT_PARTITION_SWAP_ADDRESS WOLFBOOT_LOAD_ADDRESS \ - WOLFBOOT_LOAD_DTS_ADDRESS WOLFBOOT_DTS_BOOT_ADDRESS WOLFBOOT_DTS_UPDATE_ADDRESS \ + WOLFBOOT_LOAD_DTS_ADDRESS WOLFBOOT_LOAD_RAMDISK_ADDRESS \ + WOLFBOOT_DTS_BOOT_ADDRESS WOLFBOOT_DTS_UPDATE_ADDRESS \ WOLFBOOT_SMALL_STACK DELTA_UPDATES DELTA_BLOCK_SIZE WOLFBOOT_IMG_HASH_ONESHOT \ WOLFBOOT_HUGE_STACK FORCE_32BIT\ ENCRYPT_WITH_CHACHA ENCRYPT_WITH_AES128 ENCRYPT_WITH_AES256 ARMORED \ @@ -117,6 +123,7 @@ CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO WOLFBOOT_UNIVERSAL_KEYSTORE \ XMSS_PARAMS \ ELF BIG_ENDIAN \ + GZIP FIT_RAMDISK \ NXP_CUSTOM_DCD NXP_CUSTOM_DCD_OBJS \ FLASH_OTP_KEYSTORE \ KEYVAULT_OBJ_SIZE \ diff --git a/tools/fdt-parser/fdt-parser.c b/tools/fdt-parser/fdt-parser.c index 4abc337936..d9ac4da403 100644 --- a/tools/fdt-parser/fdt-parser.c +++ b/tools/fdt-parser/fdt-parser.c @@ -372,9 +372,9 @@ void dts_parse_fit_image(void* fit, const char* image, const char* desc) int dts_parse_fit(void* image) { - const char *conf = NULL, *kernel = NULL, *flat_dt = NULL; + const char *conf = NULL, *kernel = NULL, *flat_dt = NULL, *ramdisk = NULL; - conf = fit_find_images(image, &kernel, &flat_dt); + conf = fit_find_images(image, &kernel, &flat_dt, &ramdisk); if (conf != NULL) { printf("FIT: Found '%s' configuration\n", conf); dts_fit_image_item(image, fdt_find_node_offset(image, -1, conf), @@ -384,6 +384,9 @@ int dts_parse_fit(void* image) /* dump image information */ dts_parse_fit_image(image, kernel, "Kernel"); dts_parse_fit_image(image, flat_dt, "FDT"); + if (ramdisk != NULL) { + dts_parse_fit_image(image, ramdisk, "Ramdisk"); + } return 0; } diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 6aa9752bb1..ee0b6398e4 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -48,7 +48,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-aes256 unit-chacha20 unit-pci unit-mock-state unit-sectorflags \ unit-max-space \ unit-image unit-image-rsa unit-nvm unit-nvm-flagshome unit-enc-nvm \ - unit-enc-nvm-flagshome unit-delta unit-update-flash unit-update-flash-delta \ + unit-enc-nvm-flagshome unit-delta unit-gzip unit-update-flash unit-update-flash-delta \ unit-update-flash-hook \ unit-update-flash-self-update \ unit-update-flash-enc unit-update-ram unit-update-ram-nofixed unit-pkcs11_store unit-psa_store unit-disk \ @@ -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 @@ -321,6 +322,22 @@ unit-enc-nvm-flagshome: ../../include/target.h unit-enc-nvm.c unit-delta: ../../include/target.h unit-delta.c gcc -o $@ unit-delta.c $(CFLAGS) $(LDFLAGS) +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 0000000000..f5a3b54cda --- /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 new file mode 100644 index 0000000000..a92166abc9 --- /dev/null +++ b/tools/unit-tests/unit-gzip.c @@ -0,0 +1,582 @@ +/* unit-gzip.c + * + * unit tests for the wolfBoot native gzip inflater (src/gzip.c). + * + * Positive cases round-trip a corpus through host gzip(1) and back through + * wolfBoot_gunzip. Negative cases corrupt or truncate the gzip stream and + * verify the inflater rejects it with the appropriate error code. + * + * + * 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 "gzip.h" + +/* Pull in the implementation under test directly */ +#include "../../src/gzip.c" + +/* ------------------------------------------------------------------------- */ +/* Helpers: gzip(1) on the host produces our test fixtures */ +/* ------------------------------------------------------------------------- */ + +static uint8_t *gz_compress_buf(const uint8_t *in, size_t in_len, size_t *out_len) +{ + char in_path[64], out_path[64]; + char cmd[256]; + FILE *f = NULL; + long n; + uint8_t *buf = NULL; + + snprintf(in_path, sizeof(in_path), "/tmp/wb-gz-in-%d.bin", getpid()); + snprintf(out_path, sizeof(out_path), "/tmp/wb-gz-out-%d.gz", getpid()); + + f = fopen(in_path, "wb"); + if (f == NULL) goto cleanup; + if (in_len > 0) { + if (fwrite(in, 1, in_len, f) != in_len) { + fclose(f); + f = NULL; + goto cleanup; + } + } + fclose(f); + f = NULL; + + snprintf(cmd, sizeof(cmd), "gzip -nc %s > %s", in_path, out_path); + if (system(cmd) != 0) goto cleanup; + + f = fopen(out_path, "rb"); + if (f == NULL) goto cleanup; + if (fseek(f, 0, SEEK_END) != 0) goto cleanup; + n = ftell(f); + if (n < 0) goto cleanup; + if (fseek(f, 0, SEEK_SET) != 0) goto cleanup; + buf = (uint8_t*)malloc((size_t)n); + if (buf == NULL) goto cleanup; + if (fread(buf, 1, (size_t)n, f) != (size_t)n) { + free(buf); + buf = NULL; + goto cleanup; + } + *out_len = (size_t)n; + +cleanup: + if (f != NULL) fclose(f); + unlink(in_path); + unlink(out_path); + return buf; +} + +static void roundtrip_check(const uint8_t *input, size_t in_len) +{ + uint8_t *gz_data = NULL; + size_t gz_len = 0; + uint8_t *out; + uint32_t out_len = 0; + int rc; + + gz_data = gz_compress_buf(input, in_len, &gz_len); + ck_assert_ptr_nonnull(gz_data); + + out = (uint8_t*)malloc(in_len + 16); /* +16 to make 0-byte case allocable */ + ck_assert_ptr_nonnull(out); + + rc = wolfBoot_gunzip(gz_data, (uint32_t)gz_len, + out, (uint32_t)(in_len + 16), &out_len); + ck_assert_int_eq(rc, 0); + ck_assert_uint_eq((unsigned)out_len, (unsigned)in_len); + if (in_len > 0) { + ck_assert_int_eq(memcmp(out, input, in_len), 0); + } + + free(out); + free(gz_data); +} + +/* ------------------------------------------------------------------------- */ +/* Positive round-trip tests */ +/* ------------------------------------------------------------------------- */ + +START_TEST(test_roundtrip_empty) +{ + roundtrip_check((const uint8_t*)"", 0); +} +END_TEST + +START_TEST(test_roundtrip_short_text) +{ + const char *s = "Hello, World!\n"; + roundtrip_check((const uint8_t*)s, strlen(s)); +} +END_TEST + +START_TEST(test_roundtrip_zeros) +{ + /* Highly compressible -> exercises long back-references */ + static uint8_t buf[16 * 1024]; + memset(buf, 0, sizeof(buf)); + roundtrip_check(buf, sizeof(buf)); +} +END_TEST + +START_TEST(test_roundtrip_repeated_text) +{ + /* Exercises fixed-Huffman + back-references */ + static uint8_t buf[8 * 1024]; + const char *t = "The quick brown fox jumps over the lazy dog. "; + size_t tl = strlen(t); + size_t off = 0; + while (off + tl < sizeof(buf)) { + memcpy(buf + off, t, tl); + off += tl; + } + roundtrip_check(buf, off); +} +END_TEST + +START_TEST(test_roundtrip_pseudo_random) +{ + /* Near-incompressible -> exercises dynamic Huffman + literals */ + static uint8_t buf[128 * 1024]; + uint32_t state = 0xDEADBEEFU; + int i; + for (i = 0; i < (int)sizeof(buf); i++) { + state = state * 1103515245U + 12345U; + buf[i] = (uint8_t)(state >> 16); + } + roundtrip_check(buf, sizeof(buf)); +} +END_TEST + +START_TEST(test_roundtrip_kernel_sized) +{ + /* ~2 MB of structured-but-varied data, similar in nature to a kernel. + * Catches accumulator / long-running issues. */ + static uint8_t buf[2 * 1024 * 1024]; + int i; + for (i = 0; i < (int)sizeof(buf); i++) { + buf[i] = (uint8_t)((i * 31) ^ (i >> 4)); + } + roundtrip_check(buf, sizeof(buf)); +} +END_TEST + +/* ------------------------------------------------------------------------- */ +/* Negative tests */ +/* ------------------------------------------------------------------------- */ + +START_TEST(test_neg_bad_magic) +{ + uint8_t *gz; size_t gz_len; + uint8_t out[64]; uint32_t out_len = 0; + int rc; + const char *s = "abc"; + + gz = gz_compress_buf((const uint8_t*)s, 3, &gz_len); + ck_assert_ptr_nonnull(gz); + gz[0] ^= 0xFF; /* corrupt magic byte */ + rc = wolfBoot_gunzip(gz, (uint32_t)gz_len, out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_FORMAT); + free(gz); +} +END_TEST + +START_TEST(test_neg_bad_method) +{ + uint8_t *gz; size_t gz_len; + uint8_t out[64]; uint32_t out_len = 0; + int rc; + const char *s = "abc"; + + gz = gz_compress_buf((const uint8_t*)s, 3, &gz_len); + ck_assert_ptr_nonnull(gz); + gz[2] = 9; /* CM != 8 */ + rc = wolfBoot_gunzip(gz, (uint32_t)gz_len, out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_FORMAT); + free(gz); +} +END_TEST + +START_TEST(test_neg_reserved_flags) +{ + uint8_t *gz; size_t gz_len; + uint8_t out[64]; uint32_t out_len = 0; + int rc; + const char *s = "abc"; + + gz = gz_compress_buf((const uint8_t*)s, 3, &gz_len); + ck_assert_ptr_nonnull(gz); + gz[3] |= 0x80; /* set a reserved FLG bit */ + rc = wolfBoot_gunzip(gz, (uint32_t)gz_len, out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_FORMAT); + free(gz); +} +END_TEST + +START_TEST(test_neg_truncated_header) +{ + uint8_t buf[5] = { 0x1F, 0x8B, 0x08, 0x00, 0x00 }; /* < 10 bytes */ + uint8_t out[64]; uint32_t out_len = 0; + int rc = wolfBoot_gunzip(buf, sizeof(buf), out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_TRUNCATED); +} +END_TEST + +START_TEST(test_neg_truncated_stream) +{ + uint8_t *gz; size_t gz_len; + uint8_t out[64]; uint32_t out_len = 0; + int rc; + const char *s = "Hello, World"; + + gz = gz_compress_buf((const uint8_t*)s, strlen(s), &gz_len); + ck_assert_ptr_nonnull(gz); + /* Lop off the trailer + a couple bytes so the deflate body is incomplete */ + rc = wolfBoot_gunzip(gz, (uint32_t)(gz_len - 12), + out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_TRUNCATED); + free(gz); +} +END_TEST + +START_TEST(test_neg_bad_crc32) +{ + uint8_t *gz; size_t gz_len; + uint8_t out[64]; uint32_t out_len = 0; + int rc; + const char *s = "Hello, World!"; + + gz = gz_compress_buf((const uint8_t*)s, strlen(s), &gz_len); + ck_assert_ptr_nonnull(gz); + /* Flip a bit in the 4-byte CRC32 (8 bytes from end) */ + gz[gz_len - 8] ^= 0x01; + rc = wolfBoot_gunzip(gz, (uint32_t)gz_len, out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_CRC32); + free(gz); +} +END_TEST + +START_TEST(test_neg_bad_isize) +{ + uint8_t *gz; size_t gz_len; + uint8_t out[64]; uint32_t out_len = 0; + int rc; + const char *s = "Hello, World!"; + + gz = gz_compress_buf((const uint8_t*)s, strlen(s), &gz_len); + ck_assert_ptr_nonnull(gz); + /* Flip a bit in the 4-byte ISIZE (last 4 bytes) */ + gz[gz_len - 1] ^= 0x80; + rc = wolfBoot_gunzip(gz, (uint32_t)gz_len, out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_ISIZE); + free(gz); +} +END_TEST + +START_TEST(test_neg_output_overflow) +{ + uint8_t *gz; size_t gz_len; + uint8_t out[8]; uint32_t out_len = 0; + int rc; + /* Compressed bytes will easily exceed 8 raw output bytes when expanded */ + static uint8_t input[1024]; + memset(input, 'A', sizeof(input)); + + gz = gz_compress_buf(input, sizeof(input), &gz_len); + ck_assert_ptr_nonnull(gz); + rc = wolfBoot_gunzip(gz, (uint32_t)gz_len, out, sizeof(out), &out_len); + ck_assert_int_eq(rc, WOLFBOOT_GZIP_E_OUTPUT); + free(gz); +} +END_TEST + +START_TEST(test_neg_null_args) +{ + uint8_t buf[16]; + uint32_t out_len; + ck_assert_int_eq(wolfBoot_gunzip(NULL, 0, buf, sizeof(buf), &out_len), + WOLFBOOT_GZIP_E_PARAM); + ck_assert_int_eq(wolfBoot_gunzip(buf, 0, NULL, sizeof(buf), &out_len), + WOLFBOOT_GZIP_E_PARAM); + ck_assert_int_eq(wolfBoot_gunzip(buf, 0, buf, sizeof(buf), NULL), + WOLFBOOT_GZIP_E_PARAM); +} +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 */ +/* ------------------------------------------------------------------------- */ + +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); + tcase_add_test(tc_pos, test_roundtrip_zeros); + tcase_add_test(tc_pos, test_roundtrip_repeated_text); + tcase_add_test(tc_pos, test_roundtrip_pseudo_random); + tcase_add_test(tc_pos, test_roundtrip_kernel_sized); + + tcase_add_test(tc_neg, test_neg_bad_magic); + tcase_add_test(tc_neg, test_neg_bad_method); + tcase_add_test(tc_neg, test_neg_reserved_flags); + tcase_add_test(tc_neg, test_neg_truncated_header); + tcase_add_test(tc_neg, test_neg_truncated_stream); + tcase_add_test(tc_neg, test_neg_bad_crc32); + tcase_add_test(tc_neg, test_neg_bad_isize); + 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; +} + +int main(void) +{ + int failed; + SRunner *sr = srunner_create(gzip_suite()); + srunner_run_all(sr, CK_NORMAL); + failed = srunner_ntests_failed(sr); + srunner_free(sr); + return failed ? 1 : 0; +}