[PATCH v3 0/3] bootm: bound noload kernel decompression to the allocated buffer
Aristo Chen
aristo.chen at canonical.com
Fri Jun 5 17:42:48 CEST 2026
For a compressed kernel_noload image, bootm_load_os() allocates a
decompression buffer of ALIGN(image_len * 4, SZ_1M) and then passes
CONFIG_SYS_BOOTM_LEN (typically 128 MiB on arm64) to image_decomp() as
the output limit. The decompressors honour whatever limit they are
given, so a kernel that decompresses to more than four times its
compressed size runs past the end of the allocated buffer and silently
corrupts adjacent memory.
A 4x compression ratio is at the edge of what modern compressors
(zstd, xz) achieve on real kernels, and is trivially exceeded by
crafted, highly compressible payloads, so this is reachable both
accidentally and intentionally. The overflow can land on already-loaded
boot artefacts (FDT, ramdisk, loadables), U-Boot's own data, or
memory-mapped device registers; the existing post-decompression overlap
check in bootm_load_os() only catches overlap with the FIT itself.
Patch 1 plumbs the actual allocation size through to image_decomp() and
handle_decomp_error() via a single decomp_len variable, so
decompression stops at the buffer boundary and fails cleanly when the
image is too large. The non-noload code path is unchanged and continues
to use CONFIG_SYS_BOOTM_LEN. A clarifying note is printed when the
failure is gated by the per-image buffer, so the generic
"increase CONFIG_SYS_BOOTM_LEN" advice does not mislead.
Patch 2 raises the noload-decompression headroom from 4x to 8x. The 4x
factor is at the edge of what zstd and xz achieve on real kernels, so
well-compressed vendor kernels can fail to boot at runtime once the
bound is enforced. 8x covers them comfortably while remaining bounded.
Patch 3 adds two sandbox py-tests against the per-image buffer at the
final 8x value: one that exceeds the buffer and must be rejected, and
one that matches the buffer exactly and must succeed (guarding the
boundary).
Tested on sandbox: both new tests pass; the existing
test_fit_compressed_images_load (which covers the load-address path)
and the other tests in test/py/tests/test_fit.py continue to pass.
Changes in v3:
- Swap patch 2 (tests) and patch 3 (bump) so the multiplier is bumped
first and the tests are introduced once at the final 8x value,
rather than written at 4x and rewritten at 8x. Patch 1 stays first
as the self-contained fix for backporting.
- Patch 1: use the direct IH_TYPE_KERNEL_NOLOAD && comp != NONE
condition to gate the "noload buffer" note, instead of
'decomp_len != CONFIG_SYS_BOOTM_LEN' which relied on alignment
never coincidentally matching the global limit.
- Patch 3 (tests): drive the overflow test with ubman.wait_for() and
ubman.restart_uboot(), instead of reaching into ubman.p.expect().
- Patch 3 (tests): add @pytest.mark.buildconfigspec('gzip') to both
test methods.
Changes in v2:
- Patch 1: also print the per-image buffer size from bootm_load_os()
when the failure is gated by the noload buffer, so
handle_decomp_error()'s generic "increase CONFIG_SYS_BOOTM_LEN"
advice does not mislead.
- Patch 2: add a second sandbox test covering the buffer-limit
boundary, in addition to the existing overflow test.
- New patch 3: raise the noload-decompression headroom from 4x to 8x
so well-compressed kernels do not have to bisect to find out why
their board stopped booting.
Aristo Chen (3):
bootm: fix overflow of the noload kernel decompression buffer
bootm: increase kernel_noload decompression headroom from 4x to 8x
test/py: test kernel_noload decompression buffer overflow
boot/bootm.c | 18 +++---
test/py/tests/test_fit.py | 125 ++++++++++++++++++++++++++++++++++++++
2 files changed, 136 insertions(+), 7 deletions(-)
--
2.43.0
More information about the U-Boot
mailing list