summaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-06-17 09:54:27 -0600
committerTom Rini <[email protected]>2026-06-17 09:54:27 -0600
commita57f2a31a7cd875dd96291394fb7c8c574b70a65 (patch)
tree96bcb846f6a7ed1359a404981250966b4c50d1af /boot
parente79de74103d9d411aa6b4e63582c5d7075c6a7a8 (diff)
parenta7ea33e3a35860326aeb5792f337bd9082d40ecf (diff)
Merge patch series "bootm: bound noload kernel decompression to the allocated buffer"
Aristo Chen <[email protected]> says: 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. Link: https://lore.kernel.org/r/[email protected]
Diffstat (limited to 'boot')
-rw-r--r--boot/bootm.c18
1 files changed, 11 insertions, 7 deletions
diff --git a/boot/bootm.c b/boot/bootm.c
index ec74873b503..4c260a5f5ce 100644
--- a/boot/bootm.c
+++ b/boot/bootm.c
@@ -617,6 +617,7 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress)
ulong blob_end = os.end;
ulong image_start = os.image_start;
ulong image_len = os.image_len;
+ ulong decomp_len = CONFIG_SYS_BOOTM_LEN;
ulong flush_start = ALIGN_DOWN(load, ARCH_DMA_MINALIGN);
bool no_overlap;
void *load_buf, *image_buf;
@@ -625,16 +626,16 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress)
/*
* For a "noload" compressed kernel we need to allocate a buffer large
* enough to decompress in to and use that as the load address now.
- * Assume that the kernel compression is at most a factor of 4 since
- * zstd almost achieves that.
+ * Allow up to 8x compression: this comfortably covers what zstd and xz
+ * achieve on real kernels, with headroom for well-compressed payloads.
* Use an alignment of 2MB since this might help arm64
*/
if (os.type == IH_TYPE_KERNEL_NOLOAD && os.comp != IH_COMP_NONE) {
- ulong req_size = ALIGN(image_len * 4, SZ_1M);
phys_addr_t addr;
+ decomp_len = ALIGN(image_len * 8, SZ_1M);
err = lmb_alloc_mem(LMB_MEM_ALLOC_ANY, SZ_2M, &addr,
- req_size, LMB_NONE);
+ decomp_len, LMB_NONE);
if (err)
return 1;
@@ -642,17 +643,20 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress)
images->os.load = (ulong)addr;
images->ep = (ulong)addr;
debug("Allocated %lx bytes at %lx for kernel (size %lx) decompression\n",
- req_size, load, image_len);
+ decomp_len, load, image_len);
}
load_buf = map_sysmem(load, 0);
image_buf = map_sysmem(os.image_start, image_len);
err = image_decomp(os.comp, load, os.image_start, os.type,
load_buf, image_buf, image_len,
- CONFIG_SYS_BOOTM_LEN, &load_end);
+ decomp_len, &load_end);
if (err) {
err = handle_decomp_error(os.comp, load_end - load,
- CONFIG_SYS_BOOTM_LEN, err);
+ decomp_len, err);
+ if (os.type == IH_TYPE_KERNEL_NOLOAD && os.comp != IH_COMP_NONE)
+ printf("Note: noload decompression buffer is %#lx bytes (not CONFIG_SYS_BOOTM_LEN)\n",
+ decomp_len);
bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
return err;
}