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..6a6dd05b15 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -1257,7 +1257,45 @@ 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 and verifies the FIT `hash-1` SHA-256 against the +decompressed bytes (defense-in-depth on top of the outer wolfBoot 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 +3428,112 @@ 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 - though if the FIT does include a `hash-1` subnode under the +ramdisk image, wolfBoot will verify it after decompression. + +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 +3688,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 +3696,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 +3705,51 @@ 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 +and verifies the FIT `hash-1` SHA-256 against the decompressed bytes. + +##### 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..3590a4bc85 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,15 @@ 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); + +/* 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 __cplusplus } diff --git a/include/gzip.h b/include/gzip.h new file mode 100644 index 0000000000..af27655fc6 --- /dev/null +++ b/include/gzip.h @@ -0,0 +1,116 @@ +/* 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 */ +#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 */ +#define GZIP_CRC32_INIT 0xFFFFFFFFU +#define GZIP_CRC32_FINAL_XOR 0xFFFFFFFFU +#define GZIP_CRC32_POLY 0xEDB88320U /* IEEE 802.3 reflected */ + +/* 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 + +/* 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 + +/* 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..5e2ee3932c 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -30,6 +30,26 @@ #include "string.h" #include +#ifdef WOLFBOOT_GZIP +#include "gzip.h" +#ifdef WOLFBOOT_HASH_SHA256 +#include +#endif +#ifdef WOLFBOOT_HASH_SHA384 +#include +#endif +#endif /* WOLFBOOT_GZIP */ + +/* Default upper bound on a single FIT subimage's decompressed size. + * The outer wolfBoot signature already authenticates the FIT, but a + * concrete cap defends against a malformed-but-signed FIT scribbling + * across unrelated memory. Override per target via: + * 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 +807,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 +827,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 +850,205 @@ 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_GZIP +/* Verify FIT per-subimage hash-1 subnode against the loaded/decompressed + * image bytes. This is defense-in-depth: the outer wolfBoot signature + * already authenticates the entire FIT blob, but recomputing the per-image + * hash catches inflater bugs and corrupt streams that still parse. + * + * Returns 0 on success or when no usable hash node is present. + * Returns negative on hash mismatch. + * + * If hash-1.algo names an algorithm that is not compiled into this build, + * a warning is printed and 0 is returned (best-effort verification). */ +static int fit_verify_hash(const void *fdt, int img_off, + const uint8_t *data, uint32_t data_len) +{ + int ret = 0; + int done = 0; + int hash_off, len = 0; + const char *algo = NULL; + const uint8_t *value = NULL; +#if defined(WOLFBOOT_HASH_SHA256) || defined(WOLFBOOT_HASH_SHA384) + int did_init = 0; +#endif +#ifdef WOLFBOOT_HASH_SHA256 + wc_Sha256 sha256_ctx; + uint8_t sha256_digest[WC_SHA256_DIGEST_SIZE]; +#endif +#ifdef WOLFBOOT_HASH_SHA384 + wc_Sha384 sha384_ctx; + uint8_t sha384_digest[WC_SHA384_DIGEST_SIZE]; +#endif + + hash_off = fdt_subnode_offset_namelen(fdt, img_off, "hash-1", 6); + if (hash_off < 0) { + done = 1; /* no hash-1 subnode; nothing to verify */ + } + + if (!done) { + algo = (const char*)fdt_getprop(fdt, hash_off, "algo", &len); + if (algo == NULL || len <= 0) { + wolfBoot_printf("FIT hash-1: missing algo\n"); + done = 1; + } + } + + if (!done) { + value = (const uint8_t*)fdt_getprop(fdt, hash_off, "value", &len); + if (value == NULL) { + /* mkimage emits the hash node but populates 'value' only after + * signing; an empty 'value' on an unsigned tree is benign. */ + done = 1; + } + } + +#ifdef WOLFBOOT_HASH_SHA256 + if (!done && strcmp(algo, "sha256") == 0) { + if (len != WC_SHA256_DIGEST_SIZE) { + wolfBoot_printf("FIT hash-1: bad sha256 value len %d\n", len); + ret = -1; + } + if (ret == 0) { + ret = wc_InitSha256(&sha256_ctx); + if (ret == 0) { + did_init = 1; + } + } + if (ret == 0) { + ret = wc_Sha256Update(&sha256_ctx, data, (word32)data_len); + } + if (ret == 0) { + ret = wc_Sha256Final(&sha256_ctx, sha256_digest); + } + if (did_init) { + wc_Sha256Free(&sha256_ctx); + did_init = 0; + } + if (ret != 0) { + wolfBoot_printf("FIT hash-1 (sha256): wc_Sha256 failed rc=%d\n", + ret); + ret = -1; + } + else if (memcmp(sha256_digest, value, WC_SHA256_DIGEST_SIZE) != 0) { + wolfBoot_printf("FIT hash-1 (sha256): MISMATCH\n"); + ret = -1; + } + else { + wolfBoot_printf("FIT hash-1 (sha256): OK\n"); + } + done = 1; + } +#endif + +#ifdef WOLFBOOT_HASH_SHA384 + if (!done && strcmp(algo, "sha384") == 0) { + if (len != WC_SHA384_DIGEST_SIZE) { + wolfBoot_printf("FIT hash-1: bad sha384 value len %d\n", len); + ret = -1; + } + if (ret == 0) { + ret = wc_InitSha384(&sha384_ctx); + if (ret == 0) { + did_init = 1; + } + } + if (ret == 0) { + ret = wc_Sha384Update(&sha384_ctx, data, (word32)data_len); + } + if (ret == 0) { + ret = wc_Sha384Final(&sha384_ctx, sha384_digest); + } + if (did_init) { + wc_Sha384Free(&sha384_ctx); + did_init = 0; + } + if (ret != 0) { + wolfBoot_printf("FIT hash-1 (sha384): wc_Sha384 failed rc=%d\n", + ret); + ret = -1; + } + else if (memcmp(sha384_digest, value, WC_SHA384_DIGEST_SIZE) != 0) { + wolfBoot_printf("FIT hash-1 (sha384): MISMATCH\n"); + ret = -1; + } + else { + wolfBoot_printf("FIT hash-1 (sha384): OK\n"); + } + done = 1; + } +#endif + + if ((ret == 0) && !done) { + wolfBoot_printf("FIT hash-1: algo '%s' not built in, skipping\n", + algo); + } + return ret; +} +#endif /* WOLFBOOT_GZIP */ + +void* fit_load_image_ex(void* fdt, const char* image, int* lenp, + uint32_t out_max) { 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 +1056,88 @@ 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); + 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); + } + +#ifdef WOLFBOOT_GZIP + /* Defense-in-depth: verify FIT hash-1 against loaded + * bytes */ + if (fit_verify_hash(fdt, off, (const uint8_t*)load, + (uint32_t)len) != 0) { + wolfBoot_printf("FIT hash verification failed for " + "%s\n", image); + return NULL; + } +#endif - /* load should always have entry, but if not use load address */ - data = (entry != NULL) ? entry : load; + /* 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 +1151,9 @@ void* fit_load_image(void* fdt, const char* image, int* lenp) } +void* fit_load_image(void* fdt, const char* image, int* lenp) +{ + return fit_load_image_ex(fdt, image, lenp, WOLFBOOT_FIT_MAX_DECOMP); +} + #endif /* (MMU || WOLFBOOT_FDT) && !BUILD_LOADER_STAGE1 */ diff --git a/src/gzip.c b/src/gzip.c new file mode 100644 index 0000000000..83d0d60529 --- /dev/null +++ b/src/gzip.c @@ -0,0 +1,750 @@ +/* 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 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..01f7e1a5e5 100644 --- a/src/update_disk.c +++ b/src/update_disk.c @@ -541,12 +541,12 @@ 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); } @@ -560,6 +560,45 @@ void RAMFUNCTION wolfBoot_start(void) memcpy(dts_addr, dts_ptr, dts_size); } } +#ifdef WOLFBOOT_FIT_RAMDISK + if (ramdisk != NULL) { + int rd_size = 0; + uint8_t *rd_ptr = (uint8_t*)fit_load_image(fit, ramdisk, &rd_size); + if (rd_ptr != NULL && rd_size > 0) { + uint8_t *rd_dst; + /* If WOLFBOOT_LOAD_RAMDISK_ADDRESS is set (nonzero), use it + * as the canonical destination (overrides the FIT's `load` + * property). Otherwise honor whatever fit_load_image + * returned (FIT-specified load addr or in-FIT pointer). */ + if (WOLFBOOT_LOAD_RAMDISK_ADDRESS != 0) { + rd_dst = (uint8_t*)WOLFBOOT_LOAD_RAMDISK_ADDRESS; + if (rd_ptr != rd_dst) { + wolfBoot_printf("Loading ramdisk: %p -> %p (%d bytes)\n", + rd_ptr, rd_dst, rd_size); + memcpy(rd_dst, rd_ptr, rd_size); + } + else { + wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", + rd_dst, rd_size); + } + } + else { + rd_dst = rd_ptr; + wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", + rd_dst, rd_size); + } + if (dts_addr != NULL) { + (void)fdt_fixup_initrd((void*)dts_addr, + (uint64_t)(uintptr_t)rd_dst, (uint64_t)rd_size); + } + } + else { + wolfBoot_printf("FIT: ramdisk node present but load failed\n"); + } + } +#else + (void)ramdisk; +#endif } #endif diff --git a/src/update_ram.c b/src/update_ram.c index f8e3d0f9e4..0d3061b4e3 100644 --- a/src/update_ram.c +++ b/src/update_ram.c @@ -368,12 +368,12 @@ 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); } @@ -387,6 +387,45 @@ void RAMFUNCTION wolfBoot_start(void) memcpy(dts_addr, dts_ptr, dts_size); } } +#ifdef WOLFBOOT_FIT_RAMDISK + if (ramdisk != NULL) { + int rd_size = 0; + uint8_t *rd_ptr = (uint8_t*)fit_load_image(fit, ramdisk, &rd_size); + if (rd_ptr != NULL && rd_size > 0) { + uint8_t *rd_dst; + /* If WOLFBOOT_LOAD_RAMDISK_ADDRESS is set (nonzero), use it + * as the canonical destination (overrides the FIT's `load` + * property). Otherwise honor whatever fit_load_image + * returned (FIT-specified load addr or in-FIT pointer). */ + if (WOLFBOOT_LOAD_RAMDISK_ADDRESS != 0) { + rd_dst = (uint8_t*)WOLFBOOT_LOAD_RAMDISK_ADDRESS; + if (rd_ptr != rd_dst) { + wolfBoot_printf("Loading ramdisk: %p -> %p (%d bytes)\n", + rd_ptr, rd_dst, rd_size); + memcpy(rd_dst, rd_ptr, rd_size); + } + else { + wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", + rd_dst, rd_size); + } + } + else { + rd_dst = rd_ptr; + wolfBoot_printf("Loaded ramdisk: %p (%d bytes)\n", + rd_dst, rd_size); + } + if (dts_addr != NULL) { + (void)fdt_fixup_initrd((void*)dts_addr, + (uint64_t)(uintptr_t)rd_dst, (uint64_t)rd_size); + } + } + else { + wolfBoot_printf("FIT: ramdisk node present but load failed\n"); + } + } +#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..ac65d1bc91 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 \ @@ -321,6 +321,9 @@ 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) + 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-gzip.c b/tools/unit-tests/unit-gzip.c new file mode 100644 index 0000000000..c15862c2e8 --- /dev/null +++ b/tools/unit-tests/unit-gzip.c @@ -0,0 +1,372 @@ +/* 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 + +/* ------------------------------------------------------------------------- */ +/* Test runner */ +/* ------------------------------------------------------------------------- */ + +static Suite *gzip_suite(void) +{ + Suite *s = suite_create("gzip"); + TCase *tc_pos = tcase_create("roundtrip"); + TCase *tc_neg = tcase_create("negative"); + + /* 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_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); + + suite_add_tcase(s, tc_pos); + suite_add_tcase(s, tc_neg); + 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; +}