[PATCH v2 1/8] bootm: size the noload gzip decompression buffer from ISIZE
Aristo Chen <[email protected]>
| Newsgroups | org.u-boot-project.lists.u-boot |
|---|---|
| Message-ID | <[email protected]> |
For a compressed kernel_noload image, bootm_load_os() allocates a per-image decompression buffer of ALIGN(image_len * 8, SZ_1M). The 8x multiplier is a heuristic that comfortably covers what zstd and xz achieve on real kernels, but a highly compressible payload (say, a run of zeros) can exceed it and fail decompression. gzip carries the original size in the last 4 bytes of the stream (ISIZE, modulo 2^32). Where the compressed image is a gzip stream, read ISIZE and use ALIGN(hdr_size, SZ_1M) as the buffer, capped at CONFIG_SYS_BOOTM_LEN because the value is attacker-controlled. For non-gzip streams or when ISIZE cannot be trusted, fall back to the existing 8x multiplier. The size read is done via a small static helper in bootm.c, wired up via a switch on os.comp so the same pattern can be extended to other formats without adding a new public interface. The other formats U-Boot supports (lzma, lz4, zstd) also carry a size hint and are added in follow-up patches. Suggested-by: Simon Glass <[email protected]> Signed-off-by: Aristo Chen <[email protected]> --- boot/bootm.c | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/boot/bootm.c b/boot/bootm.c index 3bce8586834..ab787979f2e 100644 --- a/boot/bootm.c +++ b/boot/bootm.c @@ -23,6 +23,7 @@ #include <asm/cache.h> #include <asm/global_data.h> #include <asm/io.h> +#include <asm/unaligned.h> #include <linux/sizes.h> #include <tpm-v2.h> #include <tpm_tcg2.h> @@ -638,6 +639,24 @@ static int handle_decomp_error(int comp_type, size_t uncomp_size, #endif #ifndef USE_HOSTCC +#if CONFIG_IS_ENABLED(GZIP) +/* + * Return the gzip stream's uncompressed size from its ISIZE trailer, or + * 0 if the buffer is not a gzip stream. Only the two magic bytes are + * checked, since a fuller validation happens inside gunzip() during + * decompression; the caller uses the return value as a size hint only. + */ +static ulong bootm_gzip_uncompressed_size(const void *src, ulong len) +{ + const u8 *b = src; + + /* Minimum gzip: 10-byte header + 2-byte deflate + 8-byte trailer */ + if (len < 20 || b[0] != 0x1f || b[1] != 0x8b) + return 0; + return get_unaligned_le32(b + len - 4); +} +#endif + static int bootm_load_os(struct bootm_headers *images, int boot_progress) { const struct image_info os = images->os; @@ -654,17 +673,36 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress) void *load_buf, *image_buf; int err; + image_buf = map_sysmem(os.image_start, image_len); + /* * 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. - * 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 + * For a gzip stream the trailing 4-byte ISIZE field holds the + * original size modulo 2^32; when it is present and within + * CONFIG_SYS_BOOTM_LEN, allocate exactly that. Otherwise fall back + * to an 8x multiplier, which 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) { phys_addr_t addr; + ulong hdr_size = 0; - decomp_len = ALIGN(image_len * 8, SZ_1M); + switch (os.comp) { +#if CONFIG_IS_ENABLED(GZIP) + case IH_COMP_GZIP: + hdr_size = bootm_gzip_uncompressed_size(image_buf, + image_len); + break; +#endif + default: + break; + } + if (hdr_size && hdr_size <= CONFIG_SYS_BOOTM_LEN) + decomp_len = ALIGN(hdr_size, SZ_1M); + else + decomp_len = ALIGN(image_len * 8, SZ_1M); decomp_limit = BOOTM_DECOMP_LIMIT_PER_IMAGE; err = lmb_alloc_mem(LMB_MEM_ALLOC_ANY, SZ_2M, &addr, decomp_len, LMB_NONE); @@ -679,7 +717,6 @@ static int bootm_load_os(struct bootm_headers *images, int boot_progress) } 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, decomp_len, &load_end); -- 2.43.0