Add optional read-only FAT32 and ext4 support to the disk loader - #872
Add optional read-only FAT32 and ext4 support to the disk loader#872dgarske wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds an optional read-only filesystem layer to the disk boot path so slots can load a wolfBoot-signed image from a file on FAT32 or ext4 (with raw-partition fallback preserved), and expands CI/unit-test coverage for the new paths.
Changes:
- Add
DISK_FS=fat32|ext4|bothbuild option and integrate filesystem-backed slot reading intosrc/update_disk.c. - Introduce read-only filesystem layer (
disk_fs.c/.h) plus FAT32/ext4 backends. - Add extensive unit/integration tests, update QEMU scripts + workflows, and refresh documentation/config examples.
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/unit-tests/unit-update-disk-fs.c | New integration tests booting from FAT32/ext4 files (and raw fallback), including encrypted-disk variant. |
| tools/unit-tests/unit-fs-probe.c | New unit tests for backend-agnostic FS probing, cache, raw passthrough, and path splitting. |
| tools/unit-tests/unit-fat32.c | FAT32 interop tests against host-generated images (mkfs.vfat + mtools). |
| tools/unit-tests/unit-ext4.c | ext4 interop tests against host-generated images (mke2fs). |
| tools/unit-tests/Makefile | Wire new test binaries + fixture generation; conditionally include interop tests based on tool availability. |
| tools/scripts/x86_fsp/qemu/test_qemu.sh | Add -f path to test filesystem boot config; adjust keygen/TPM steps accordingly. |
| tools/scripts/x86_fsp/qemu/make_hd.sh | Add FS=1 mode to create FAT32/ext4 partitions populated with signed images for QEMU. |
| src/update_disk.c | Add slot abstraction + filesystem-backed reads; add label-based partition selection; enforce file truncation checks. |
| src/fat32.c | New read-only FAT32 backend with VFAT LFN support and bounded parsing. |
| src/ext4.c | New read-only ext4 backend (extent-mapped only) with bounded parsing and sparse-file hole handling. |
| src/disk_fs.c | New backend-independent filesystem layer: probing, dispatch, metadata cache, bounded reads, path splitting. |
| src/disk.c | Add disk_part_size() and disk_part_count() helpers used by filesystem layer. |
| options.mk | Add DISK_FS build knob to include FS objects/flags only when requested. |
| include/disk_fs.h | Public/internal API for filesystem layer and security bounds configuration. |
| include/disk.h | Export new partition helper APIs. |
| docs/compile.md | Document DISK_FS usage, supported/refused features, and security/performance notes. |
| docs/Targets.md | Update target docs to reflect optional filesystem boot path and link compile docs. |
| config/examples/zynqmp_sdcard.config | Add commented example for DISK_FS + boot file/label options. |
| config/examples/zynq7000_sdcard.config | Add commented example for DISK_FS + boot file/label options. |
| config/examples/x86_fsp_qemu_fs.config | New QEMU config enabling DISK_FS boot from file. |
| config/examples/versal_vmk180_sdcard.config | Tighten WOLFBOOT_RAMBOOT_MAX_SIZE; add DISK_FS example block. |
| config/examples/tegra234-sdcard.config | Add commented DISK_FS example block. |
| config/examples/polarfire_mpfs250.config | Add commented DISK_FS example block. |
| Makefile | Set WOLFBOOT_TARGET_BUILD=1 to scope DISK_FS checks to real target builds. |
| .gitignore | Ignore new unit-test binaries and generated filesystem fixture images. |
| .github/workflows/test-x86-fsp-qemu.yml | Add new QEMU CI job for filesystem boot path. |
| .github/workflows/test-configs.yml | Add build-matrix jobs compiling FAT32/ext4/both variants and new config coverage. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
3bfb897 to
5b877c7
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 38 out of 39 changed files in this pull request and generated 3 comments.
Suppressed comments (1)
tools/unit-tests/unit-disk.c:248
- This test fixture writes the MBR boot signature via a
uint16_t*assignment, which is host-endian. On a big-endian host this writes bytesAA 55instead of the on-disk little-endian55 AA, breaking the intent of the new endian-neutral GPT/MBR parsing tests.
Description
Adds optional read-only FAT32 and ext4 support to the disk loader, so a boot slot can name a file (
BOOT_FILE_A/BOOT_FILE_B) instead of requiring the signed image at raw offset 0 of a partition. Enabled withDISK_FS=fat32|ext4|both; off by default, and raw partitions behave exactly as before. Partitions can be selected by index or by label (GPT partition name, or the FAT32/ext4 volume label, so labels also work on MBR disks).The file is an ordinary wolfBoot-signed image -- header at offset 0 of the file, payload after it -- so signing, A/B version selection and anti-rollback are unchanged.
Also tightens
WOLFBOOT_RAMBOOT_MAX_SIZEinversal_vmk180_sdcard.configfrom0x80000000to0x70000000. That value bounds what the on-disk header may claim before the payload is authenticated; withWOLFBOOT_LOAD_ADDRESS=0x10000000the old value allowed a claimed size to reach0x90000000, past the end of the board's 2 GB DDR-low aperture.Also in this PR
Big-endian correctness for the whole disk path.
src/gpt.candsrc/disk.cread the MBR table, the GPT header and the GPT entries by casting the sector to packed structures and dereferencing them, which is only correct on a little-endian host. They now read every on-disk field byte-wise and convert at the parse boundary, sostruct guid_ptableandstruct gpt_part_infocome back in host order and downstream callers are unchanged. The GPT header CRC is now computed over the raw on-disk bytes with the CRC field zeroed, which is both what the spec says and what makes it endian-neutral. This was pre-existing and previously unreachable, since no big-endian target enables disk boot, but the filesystem parsers were already endian-neutral and it would have been the only thing blocking a big-endian disk target.A host-side test harness,
tools/fs-test. It links the real parsers against a file-backed block device, so any disk image or read-only block device can be exercised exactly as the bootloader reads it.make fixturesbuilds real volumes withmkfs.vfat+mtoolsandmke2fs -d, needing no root and no loopback mounts. It also dumps files out of an existing filesystem, which is useful for checking media by hand.A big-endian CI job.
.github/workflows/test-endian.ymlcross-builds the harness as big-endian PowerPC and reads the same fixtures underqemu-user, so any future change that reintroduces a native multi-byte read of on-disk data fails CI instead of going unnoticed.Limitations
inline_data, ext4 encryption/compression, or uninitialized extents. Each is refused with a clear error rather than misread.mke2fs -dandcppunch holes wherever the source has a run of zeros, and a signed image's header padding always does.64bit,metadata_csumandflex_bgare supported, so a volume from a plainmkfs.ext4is readable as-is.WOLFBOOT_FS_EXT4_ALLOW_DIRTYis set.Testing
ASAN=1(218 checks total across the disk/FS area, including the pre-existing raw-path suites).unit-fs-probe(35): filesystem probing, raw passthrough, metadata cache, path splitting, partition accessors.unit-fat32(18): interop against a volume from the host's realmkfs.vfat+mcopy.unit-ext4(17): interop against twomke2fsvolumes, one minimal and one with the stock64bit,metadata_csum,flex_bgfeature set.unit-fs-malicious(58): hostile input, with a negative case for every validation rule and every loop bound. Hand-built metadata, no external tools, so it never skips.unit-update-disk-fs(16): loader integration, mocking only the raw block layer so the real gpt/disk/disk_fs/fat32/ext4 stack runs underneathwolfBoot_start(). Covers slot selection across two different filesystems, the raw fallback, partition selection by volume label and by GPT partition name (including the precedence between them), rejection of a file larger than the load region, and the fail-closed defaults.unit-update-disk-fs-enc(16): the same source built with disk encryption. The mock cipher's keystream depends on the byte's position in the image, so it pins the fact that a file's byte stream lines up with the CTR counter exactly as a raw partition's does; shifting the keystream by one byte fails 7 of the 13 tests, so the check is not vacuous.make -C tools/unit-tests runpasses, 129 suites at 100%.unit-disk,unit-update-disk,unit-update-disk-oobandunit-update-disk-fitare unmodified and still pass, which is the no-regression proof for the raw path.make_hd.sh FS=1builds a GPT image whose FAT32 and ext4 partitions are produced by the realmkfs.vfatandmke2fs; the parsers mount both, match their volume labels, and read back byte-identical signed images.DISK_FS=bothbuilds clean on ZynqMP, Zynq-7000, Versal VMK180 and tegra234, plusfat32-only andext4-only variants, and in combination withENCRYPT_WITH_AES256,ENCRYPT_WITH_CHACHAandFIT_RAMDISK. New CI jobs cover all of these.versal_vmk180_sdcard.confighad no CI job at all before this branch; it now has both a raw and aDISK_FS=bothjob. Cost of the feature on that target is about 17 KB of.text(129760 -> 147320)..textfor all three objects at-Os(aarch64), well inside the bootloader partition on every target that enables it. A dedicated size/complexity pass removed duplicated node-header validation in the extent walk, merged two near-identical extent accessors, replaced six copies of the FAT cluster-range test with one helper, sharedis_pow2between the backends, and deleted dead code (fs_le64, the write-onlyinode_nofield).cppcheckclean on all new and modified sources, using the repo's ownmake cppcheckflags verbatim (-f --enable=warning --enable=portability --check-level=exhaustive --std=c89 --error-exitcode=89): exit 0, zero findings.review-security,bugs,review,defaults,cicd), the second after a size/complexity pass. Latest: 0 critical, 0 high, 0 medium, 2 low, 3 info. Four are fixed; one is declined with evidence (see below).Boot partition:line as the raw path, thenFirmware Valid., then the application banner.p2: fat32andp3: ext4in one boot, both volumes from a realmkfsBOOT_PART_A/Bwere set to bogus indices 7/8 and resolved to 0 and 2 purely from the labels "boot" and "root". FAT32 (129022 clusters) + ext4 (4K blocks, 238 groups)p0: fat32+p1: ext4(4K blocks) in one boot, ext4 winning on versionarch.mksets-DSDHCI_SDMA_DISABLEDon this target, so every filesystem metadata read is programmed I/O.p0: fat32+p1: ext4, and the ext4 file was sparse (extents(0),(16-49), blocks 1-15 a hole), so this exercised the hole-reads-as-zeros path on real hardware. Reached the app banner andApplication running successfully!gpt.c,disk.c,fat32.c,ext4.c) was run natively on the board's Linux viatools/fs-testbuilt as 32-bit MSB PowerPC. MBR parsed, FAT32 and ext4 both read back byte-identical, negative cases refused. This is what validates the endian fix on real silicon rather than under emulationBetween them these cover both partition-table formats, both label mechanisms (GPT partition name and volume-label fallback), both ext4 block sizes, both SDHCI transfer modes (SDMA and PIO), four signature algorithms and four architectures (x86, aarch64, ARMv7-A, RISC-V).
Firmware Valid.-- but the application did not start until the unrelated ELF entry-point fix in PR elf: read the ELF entry point before loading segments over it #871 was also applied, because those configs load the ELF over its own header. That is a pre-existing master-only regression, not something this branch introduces; with elf: read the ELF entry point before loading segments over it #871 applied the Versal boots straight through to its banner.IMAGE_HEADER_SIZE=1024build was rejected withImage larger than file for P:Brather than being read short. That is the intended behaviour of thefw_size + IMAGE_HEADER_SIZE > file_sizecheck, and it named the real cause instead of surfacing a confusing I/O error.test_qemu.shsigned with--ecc384while the config declaredSIGN=ECC256, so that new CI job could never have built; and ext4 refused sparse files, which every real signed image is. The unit fixtures had used/dev/urandom, which never compresses to holes, so nothing caught it -- the interop fixture is now deliberately sparse.