Skip to content

FIT: gzip-compressed kernel + ramdisk (initramfs) support#763

Merged
danielinux merged 7 commits into
wolfSSL:masterfrom
dgarske:fit_compressed
May 5, 2026
Merged

FIT: gzip-compressed kernel + ramdisk (initramfs) support#763
danielinux merged 7 commits into
wolfSSL:masterfrom
dgarske:fit_compressed

Conversation

@dgarske
Copy link
Copy Markdown
Member

@dgarske dgarske commented Apr 29, 2026

Summary

Adds an in-tree DEFLATE/gzip inflater and wires it into the FIT image path so
wolfBoot can boot compressed Linux kernels and ramdisks directly out of a FIT
container, with per-subimage hash verification on top of the outer wolfBoot
signature. Also adds initramfs (ramdisk) extraction and DTB /chosen fixup so
the kernel can find the loaded ramdisk.

Default-on (GZIP=1) for the FIT-using example targets: ZynqMP (eMMC + SD),
Versal VMK180 (eMMC + SD), PolarFire MPFS250 (SD + QSPI). Builds on the
existing master configs are byte-identical to before this branch.

What's in here

gzip inflater (src/gzip.c, include/gzip.h)

  • Clean-room RFC 1951 (DEFLATE) + RFC 1952 (gzip wrapper) implementation.
  • Single pass, no heap allocations — output buffer doubles as the LZ77 window.
  • CRC32 + ISIZE trailer verified; gated by WOLFBOOT_GZIP.
  • wolfBoot C style: declarations at top of function, single return per
    function, /* … */ comments, every callee return code checked.

FIT integration (src/fdt.c, include/fdt.h)

  • New fit_load_image_ex(out_max); fit_load_image() kept as a wrapper so
    existing callers don't change.
  • When WOLFBOOT_GZIP is on and a subimage carries compression = "gzip",
    the inflater writes straight to the FIT-declared load address.
  • After load (compressed or not), fit_verify_hash checks the hash-1
    subnode against the loaded bytes. SHA-256 / SHA-384 return codes from
    wc_InitSha*, wc_Sha*Update, wc_Sha*Final are now propagated — a
    misbehaving crypto backend can't silently degrade to a no-op.

Ramdisk / initramfs support

  • fit_find_images() gains a ramdisk out-arg.
  • New fdt_fixup_initrd() writes /chosen/linux,initrd-start and
    linux,initrd-end as 64-bit big-endian cells.
  • src/update_disk.c and src/update_ram.c load the FIT ramdisk node
    (under WOLFBOOT_FIT_RAMDISK) and patch the DTB.
  • Compressed ramdisks reuse the same fit_load_image_ex() decompress path.
  • RAMDISK=1 build switch + WOLFBOOT_LOAD_RAMDISK_ADDRESS plumbed
    through tools/config.mkMakefile sed → include/target.h.in.
    WOLFBOOT_LOAD_RAMDISK_ADDRESS=0 (the default) keeps the ramdisk
    wherever fit_load_image returned it (FIT load addr or in-FIT pointer).
  • hal/zynq.c and hal/versal.c fdt_totalsize headroom bumped
    512 → 768 bytes to fit the new linux,initrd-{start,end} entries.

Example configs

  • New config/examples/zynqmp_sdcard_ramdisk.config
    (load address 0x40000000).
  • GZIP=1 flipped on by default in: zynqmp.config,
    zynqmp_sdcard.config, polarfire_mpfs250.config,
    polarfire_mpfs250_qspi.config, versal_vmk180.config,
    versal_vmk180_sdcard.config. Pass GZIP=0 to opt out and keep the
    pre-branch host-side gzip workflow.

Unit tests (tools/unit-tests/unit-gzip.c)

  • 6 round-trip corpora (empty, short text, all-zeros, structured text,
    pseudo-random, ~2 MB kernel-sized) — host gzip(1)wolfBoot_gunzip.
  • 9 negative cases: bad magic, bad CM, reserved FLG bits, truncated
    header, truncated DEFLATE body, CRC32 mismatch, ISIZE mismatch, output
    overflow, NULL parameters.
  • All 15 cases pass under libcheck.

Docs (docs/Targets.md)

  • Versal PetaLinux walkthrough now documents both GZIP=1 (default,
    compression="gzip" + Image.gz) and GZIP=0 (plain Image) flows.
  • New ZynqMP "Booting PetaLinux" subsection covering the same mechanism
    plus the RAMDISK=1 / WOLFBOOT_LOAD_RAMDISK_ADDRESS setup and a
    sample ITS layout for kernel + DTB + initramfs.
  • PolarFire MPFS250 SD-card section now describes Option A (default,
    compression="gzip", mkimage builds the FIT directly) and Option B
    (host-side gzip -cdvk, compression="none").

Notes for reviewers

  • The gzip inflater is the only new crypto-adjacent code path; defense in
    depth is provided by the existing wolfBoot signature on the outer FIT
    plus the per-subimage hash-1 verify. CRC32 in the gzip trailer is a
    data-integrity check, not a security boundary.
  • No public API signatures changed. fit_load_image keeps its old
    prototype; the new behavior is reached via fit_load_image_ex.
  • WOLFBOOT_LOAD_RAMDISK_ADDRESS=0 is the safe default — when unset the
    ramdisk stays wherever the FIT/loader put it.

@dgarske dgarske self-assigned this Apr 29, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds native gzip/DEFLATE decompression to wolfBoot’s FIT image loading path, plus optional FIT ramdisk extraction with DTB /chosen initrd fixups, along with build/config toggles, documentation updates, and unit tests.

Changes:

  • Introduces an in-tree gzip inflater (src/gzip.c, include/gzip.h) with host-side unit tests.
  • Extends FIT parsing/loading to support compression = "gzip", per-subimage hash-1 verification, and optional ramdisk discovery + DTB initrd fixups.
  • Wires new GZIP/RAMDISK build switches and load-address configuration through the build system and example configs/docs.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
tools/unit-tests/unit-gzip.c Adds host unit tests for the gzip inflater (round-trip + negative cases).
tools/unit-tests/Makefile Adds unit-gzip target to the unit test suite list and build rules.
tools/fdt-parser/fdt-parser.c Updates FIT parsing tool to display an optional ramdisk image.
tools/config.mk Plumbs WOLFBOOT_LOAD_RAMDISK_ADDRESS, GZIP, and RAMDISK through config variables.
src/update_ram.c Loads FIT ramdisk (optional) and applies DTB initrd fixups in RAM-boot path.
src/update_disk.c Loads FIT ramdisk (optional) and applies DTB initrd fixups in disk-boot path.
src/gzip.c Implements RFC1951/RFC1952 decompression behind WOLFBOOT_GZIP.
src/fdt.c Extends FIT loader for gzip decompression, hash verification, ramdisk discovery, and initrd fixups.
options.mk Adds build switches GZIP and RAMDISK and wires in gzip object/CFLAGS.
include/target.h.in Adds WOLFBOOT_LOAD_RAMDISK_ADDRESS template define.
include/gzip.h Declares gzip API and constants/error codes for wolfBoot gunzip.
include/fdt.h Updates FIT APIs and adds fdt_fixup_initrd() declaration.
hal/zynq.c Increases DTB totalsize headroom for new /chosen properties.
hal/versal.c Increases DTB totalsize headroom for new /chosen properties.
docs/Targets.md Documents gzip FIT flow + ramdisk/initramfs flow for multiple targets.
config/examples/zynqmp_sdcard.config Enables GZIP=1 by default and documents optional RAMDISK settings.
config/examples/zynqmp.config Enables GZIP=1 by default.
config/examples/versal_vmk180_sdcard.config Enables GZIP=1 by default.
config/examples/versal_vmk180.config Enables GZIP=1 by default.
config/examples/polarfire_mpfs250_qspi.config Enables GZIP=1 by default.
config/examples/polarfire_mpfs250.config Enables GZIP=1 by default.
Makefile Ensures WOLFBOOT_LOAD_RAMDISK_ADDRESS is substituted into include/target.h.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tools/unit-tests/unit-gzip.c Outdated
Comment thread tools/unit-tests/unit-gzip.c Outdated
Comment thread tools/unit-tests/unit-gzip.c Outdated
Comment thread tools/unit-tests/unit-gzip.c
Comment thread tools/unit-tests/unit-gzip.c Outdated
Comment thread src/fdt.c
Comment thread src/fdt.c Outdated
Comment thread src/fdt.c Outdated
Comment thread docs/Targets.md
Comment thread docs/Targets.md Outdated
dgarske added 3 commits April 29, 2026 11:29
New src/gzip.c implements DEFLATE (RFC 1951) plus the gzip wrapper
(RFC 1952) from the RFC text only. Single-pass inflate, no allocations:
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, ~10x smaller code than fast lookup tables
which matters in the bootloader. CRC32 + ISIZE verified against the
gzip trailer. Gated by WOLFBOOT_GZIP.

include/gzip.h carries the public entry point plus the RFC-canonical
constants (magic bytes, CM=DEFLATE, fixed Huffman boundaries, EOB
symbol, dynamic block field widths, run-length repeat metadata, CRC32
init/final-XOR, header/trailer sizes, alphabet sizes) so future
maintainers can cross-reference the RFC sections by name instead of
chasing literal numbers.

Tests in tools/unit-tests/unit-gzip.c round-trip 6 corpora through host
gzip(1) and back through wolfBoot_gunzip (empty, short text, all-zeros,
structured text, pseudo-random, ~2 MB kernel-sized). 9 negative cases
cover bad magic, bad CM, reserved FLG bits, truncated header,
truncated DEFLATE body, CRC32 mismatch, ISIZE mismatch, output overflow,
and NULL parameters. All 15 pass under libcheck.
Wires the new wolfBoot_gunzip inflater into the FIT image-loading path
and adds initramfs (ramdisk) extraction with DTB /chosen fixup so a
single signed FIT can carry kernel, DTB, and rootfs.

GZIP path
---------
* fit_load_image_ex(out_max) added; fit_load_image kept as a wrapper.
* When a subimage carries compression="gzip", inflate straight to the
  FIT-declared load address, then verify the FIT hash-1 subnode
  (sha256 / sha384 if available) for defense in depth on top of the
  outer wolfBoot signature. The compression property is now read
  unconditionally so a build without WOLFBOOT_GZIP can warn and fail
  closed instead of silently memcpy-ing compressed bytes as if they
  were raw.
* fit_verify_hash propagates wc_InitSha256 / wc_Sha256Update /
  wc_Sha256Final return codes (and the SHA-384 equivalents) - any
  non-zero return is treated as a verification failure so a misbehaving
  backend cannot silently degrade to a no-op.
* GZIP=1 is the new default in the FIT-using example configs (zynqmp,
  zynqmp_sdcard, polarfire_mpfs250, polarfire_mpfs250_qspi,
  versal_vmk180, versal_vmk180_sdcard); set GZIP=0 to opt out.

Ramdisk path
------------
* fit_find_images() gains a ramdisk out-arg and fdt_fixup_initrd()
  writes /chosen/linux,initrd-{start,end} as 64-bit big-endian cells.
* update_disk.c and update_ram.c load the FIT ramdisk node (under
  WOLFBOOT_FIT_RAMDISK) and patch the loaded DTB. Compressed (gzip)
  ramdisks reuse the same fit_load_image_ex() decompress path.
* RAMDISK=1 build switch defines WOLFBOOT_FIT_RAMDISK;
  WOLFBOOT_LOAD_RAMDISK_ADDRESS is plumbed through tools/config.mk ->
  Makefile sed -> include/target.h.in. Defaults to 0; when 0 the
  ramdisk stays at whatever fit_load_image returned.
* hal/zynq.c and hal/versal.c bump fdt_totalsize headroom from 512 to
  768 bytes to fit the new linux,initrd-{start,end} entries.
* config/examples/zynqmp_sdcard.config gains a commented-out opt-in
  block (RAMDISK=1, WOLFBOOT_LOAD_RAMDISK_ADDRESS=0x40000000, alt
  LINUX_BOOTARGS) so a single config file covers both rootfs-on-disk
  and FIT-bundled-initramfs flows.

Builds against the existing master configs are byte-identical when
GZIP=0 and RAMDISK is unset.
…arFire

PolarFire MPFS250, Versal VMK180, and ZynqMP "Booting PetaLinux"
walkthroughs now describe both options for handing PetaLinux off through
the FIT image:

  * Option A (default GZIP=1): set compression="gzip" in the .its,
    point data at Image.gz / linux.bin.gz, and let mkimage build the
    FIT directly. wolfBoot decompresses straight to the kernel load
    address at boot and verifies hash-1.
  * Option B (GZIP=0): keep the existing host-side gzip -cdvk /
    gunzip step and compression="none" in the .its.

ZynqMP also gains a "FIT ramdisk (initramfs)" subsection covering
RAMDISK=1, WOLFBOOT_LOAD_RAMDISK_ADDRESS, the commented-out opt-in
block in zynqmp_sdcard.config, gzip ramdisk support, and a sample ITS
layout with kernel + DTB + ramdisk subimages.
Copy link
Copy Markdown

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #763

Scan targets checked: wolfboot-bugs, wolfboot-src

Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

@dgarske dgarske force-pushed the fit_compressed branch 3 times, most recently from f34b980 to 623e43c Compare April 30, 2026 17:32
@dgarske dgarske assigned danielinux and wolfSSL-Bot and unassigned dgarske May 1, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/fdt.c Outdated
Comment thread tools/config.mk
Copy link
Copy Markdown

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #763

Scan targets checked: wolfboot-bugs, wolfboot-src

No new issues found in the changed files. ✅

AlexLanzano
AlexLanzano previously approved these changes May 1, 2026
Copy link
Copy Markdown
Member

@AlexLanzano AlexLanzano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall. Just some minor naming considerations.

Comment thread docs/Targets.md Outdated
Comment thread docs/Targets.md Outdated
Comment thread hal/versal.c Outdated
Comment thread hal/zynq.c Outdated
Copy link
Copy Markdown
Member

@danielinux danielinux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the FIT image is verified when it's still compressed (which is good, because we don't want to parse unverified FITs), there's perhaps a potential TOCTOU (verify/uncompress/load/stage) because there's no verification of the uncompressed image afterwards.

I would suggest considering to add one extra check (at least for integrity/expected sha) after decompression on the final payload.

rizlik
rizlik previously approved these changes May 4, 2026
Copy link
Copy Markdown
Contributor

@rizlik rizlik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very neat gzip implementation!
I just left two NITs, but feel free to ignore them.

On the TOCTOU attack:

Both this and current implementation allow an attacker who can modify the FIT content after the verification but before parsing/loading to bypass any authentication check.
It would be useful to defend against this scenario, for example when loading from external flash where an attacker can control the SPI lines but cannot modify internal RAM.
To prevent this wolfBoot should copy the FIT image in RAM first, then verify, parse and load only using the copy.
This trades-off with RAM usage, that now need to store a full extra copy of the FIT.
I do think it would be a nice opt-in feature, but outside of this PR.

Comment thread include/gzip.h Outdated
Comment thread src/update_disk.c
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

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

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

tools/unit-tests/unit-fit-gzip.c (new), tools/unit-tests/Makefile
  - New libcheck binary covering the FIT loader's compression
    branches: gzip success, gzip stream corruption, unknown
    compression, compression="none" baseline, and the no-load
    fail-closed path. Built twice from the same source - once with
    WOLFBOOT_GZIP for the success / runtime-failure paths, and once
    without it so the compile-time fail-closed branch is also tested.
@danielinux danielinux merged commit 9ca1d43 into wolfSSL:master May 5, 2026
371 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants