From d62653749cfa2ff4f13d93570363815fa8a3373b Mon Sep 17 00:00:00 2001 From: Karthikeyan Ramasubramanian Date: Fri, 31 Oct 2025 14:06:10 -0600 Subject: [PATCH] payloads/libpayload: Support legacy LZ4 compression format Linux kernel images from upstream tree are compressed using legacy LZ4 format and not the modern LZ4 format. Hence support legacy LZ4 compression format to decompress and boot upstream Linux kernel images. Also add unit test case to verify the currently supported LZ4 compression format as well as legacy LZ4 compression format. References: * https://github.com/lz4/lz4 BUG=None TEST=make tests/liblz4/lz4-test [==========] tests_liblz4_lz4-test(tests): Running 4 test(s). [ RUN ] test_lz4 [ OK ] test_lz4 [ RUN ] test_lz4_partial_decompression [ OK ] test_lz4_partial_decompression [ RUN ] test_legacy_lz4 [ OK ] test_legacy_lz4 [ RUN ] test_legacy_lz4_partial_decompression [ OK ] test_legacy_lz4_partial_decompression [==========] tests_liblz4_lz4-test(tests): 4 test(s) run. [ PASSED ] 4 test(s). Change-Id: I7e3d407fc313e0937fd8d327840534de60d8c625 Signed-off-by: Karthikeyan Ramasubramanian Reviewed-on: https://review.coreboot.org/c/coreboot/+/89854 Tested-by: build bot (Jenkins) Reviewed-by: Julius Werner --- payloads/libpayload/include/lz4.h | 7 + payloads/libpayload/liblz4/lz4_wrapper.c | 54 +++++- payloads/libpayload/tests/liblz4/Makefile.mk | 5 + payloads/libpayload/tests/liblz4/lz4-test.c | 170 +++++++++++++++++++ 4 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 payloads/libpayload/tests/liblz4/Makefile.mk create mode 100644 payloads/libpayload/tests/liblz4/lz4-test.c diff --git a/payloads/libpayload/include/lz4.h b/payloads/libpayload/include/lz4.h index d2120a48fc..249afb3a41 100644 --- a/payloads/libpayload/include/lz4.h +++ b/payloads/libpayload/include/lz4.h @@ -47,4 +47,11 @@ size_t ulz4fn(const void *src, size_t srcn, void *dst, size_t dstn); /* Same as ulz4fn() but does not perform any bounds checks. */ size_t ulz4f(const void *src, void *dst); +/* Decompresses an Legacy LZ4 image from src to dst, ensuring that it doesn't read + * more than srcn bytes and doesn't write more than dstn. Will reliably return an + * error if buffer was too small. + * Returns amount of decompressed bytes, or 0 on error. + */ +size_t ulz4ln(const void *src, size_t srcn, void *dst, size_t dstn); + #endif /* __LZ4_H_ */ diff --git a/payloads/libpayload/liblz4/lz4_wrapper.c b/payloads/libpayload/liblz4/lz4_wrapper.c index 3d17fe6742..160d5dcb74 100644 --- a/payloads/libpayload/liblz4/lz4_wrapper.c +++ b/payloads/libpayload/liblz4/lz4_wrapper.c @@ -74,6 +74,7 @@ typedef uint64_t U64; #include "lz4.c.inc" /* #include for inlining, do not link! */ #define LZ4F_MAGICNUMBER 0x184D2204 +#define LEGACY_MAGICNUMBER 0x184C2102 struct lz4_frame_header { uint32_t magic; @@ -112,6 +113,14 @@ struct lz4_block_header { /* + uint32_t block_checksum iff has_block_checksum is set */ } __packed; +/* A wrapper function used by ulz4fn and ulz4ln to inline LZ4_decompress_generic + which is critical for performance. */ +static int lz4_wrap(const void *in, void *out, int in_size, int out_size) +{ + return LZ4_decompress_generic(in, out, in_size, out_size, endOnInputSize, + full, 0, noDict, out, NULL, 0); +} + size_t ulz4fn(const void *src, size_t srcn, void *dst, size_t dstn) { const void *in = src; @@ -163,14 +172,10 @@ size_t ulz4fn(const void *src, size_t srcn, void *dst, size_t dstn) else out += size; } else { - /* constant folding essential, do not touch params! */ - int ret = LZ4_decompress_generic(in, out, b.size, - dst + dstn - out, endOnInputSize, - full, 0, noDict, out, NULL, 0); + int ret = lz4_wrap(in, out, b.size, dst + dstn - out); if (ret < 0) break; /* decompression error */ - else - out += ret; + out += ret; } in += b.size; @@ -186,3 +191,40 @@ size_t ulz4f(const void *src, void *dst) /* LZ4 uses signed size parameters, so can't just use ((u32)-1) here. */ return ulz4fn(src, 1*GiB, dst, 1*GiB); } + +size_t ulz4ln(const void *src, size_t srcn, void *dst, size_t dstn) +{ + const void *in = src; + void *out = dst; + size_t out_size = 0; + + if (le32toh(*((const uint32_t *)in)) != LEGACY_MAGICNUMBER) + return 0; + + in += sizeof(uint32_t); + while (1) { + uint32_t block_size; + + if ((size_t)(in - src) == srcn) { + out_size = out - dst; /* decompression is complete */ + break; + } + + if ((size_t)(in - src) + sizeof(block_size) > srcn) + break; /* input overrun */ + + block_size = le32toh(*(uint32_t *)in); + in += sizeof(block_size); + + if ((size_t)(in - src) + block_size > srcn) + break; /* input overrun */ + + int ret = lz4_wrap(in, out, block_size, dst + dstn - out); + if (ret < 0) + break; /* decompression error */ + out += ret; + in += block_size; + } + + return out_size; +} diff --git a/payloads/libpayload/tests/liblz4/Makefile.mk b/payloads/libpayload/tests/liblz4/Makefile.mk new file mode 100644 index 0000000000..fa5ca8d1b3 --- /dev/null +++ b/payloads/libpayload/tests/liblz4/Makefile.mk @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +tests-y += lz4-test + +lz4-test-srcs += tests/liblz4/lz4-test.c diff --git a/payloads/libpayload/tests/liblz4/lz4-test.c b/payloads/libpayload/tests/liblz4/lz4-test.c new file mode 100644 index 0000000000..bbdc0af47d --- /dev/null +++ b/payloads/libpayload/tests/liblz4/lz4-test.c @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include + +#include "../liblz4/lz4_wrapper.c" + +/* This macro basically does nothing but suppresses linter messages */ +#define EMPTY_WRAP(...) __VA_ARGS__ + +#define TEST_RAW_DATA_SIZE sizeof((u8[]){TEST_RAW_DATA}) +/* TEST_RAW_DATA="LLLLLLLLLLLLLLLLZZZZZZZZZZZZZZZZ4444444444444444" + echo ${TEST_RAW_DATA} | hexdump -e '1/1 "0x%02x" ", "' -v */ +#define TEST_RAW_DATA EMPTY_WRAP( \ + 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, \ + 0x4c, 0x4c, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, \ + 0x5a, 0x5a, 0x5a, 0x5a, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, \ + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x0a) + +#define TEST_LZ4_DATA_SIZE sizeof((u8[]){TEST_LZ4_DATA}) +/* echo ${TEST_RAW_DATA} | lz4 -12 --favor-decSpeed -c | \ + hexdump -e '1/1 "0x%02x" ", "' -v */ +#define TEST_LZ4_DATA EMPTY_WRAP( \ + 0x04, 0x22, 0x4d, 0x18, 0x64, 0x40, 0xa7, 0x15, 0x00, 0x00, 0x00, 0x2a, 0x4c, 0x4c, \ + 0x02, 0x00, 0x2a, 0x5a, 0x5a, 0x02, 0x00, 0x26, 0x34, 0x34, 0x02, 0x00, 0x50, 0x34, \ + 0x34, 0x34, 0x34, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xea, 0x87, 0xfa) + +#define TEST_LEGACY_LZ4_DATA_SIZE sizeof((u8[]){TEST_LEGACY_LZ4_DATA}) +/* echo ${TEST_RAW_DATA} | lz4 -l -12 --favor-decSpeed -c | \ + hexdump -e '1/1 "0x%02x" ", "' -v */ +#define TEST_LEGACY_LZ4_DATA EMPTY_WRAP( \ + 0x02, 0x21, 0x4c, 0x18, 0x12, 0x00, 0x00, 0x00, 0x1b, 0x4c, 0x01, 0x00, 0x1b, 0x5a, \ + 0x01, 0x00, 0x17, 0x34, 0x01, 0x00, 0x50, 0x34, 0x34, 0x34, 0x34, 0x0a) + +#define TEST_LARGE_RAW_DATA_SIZE sizeof((u8[]){TEST_LARGE_RAW_DATA}) +/* + * LINUX_IMG_PATH=; + * hexdump -e '1/1 "0x%02x" ", "' ${LINUX_IMG_PATH} -n 320 -v + * Arm Linux kernel image header is 64 bytes long. Size of scratch buffer in one of the + * payloads is 256 bytes + Arm Linux kernel image header. Hence 320 bytes. + */ +#define TEST_LARGE_RAW_DATA EMPTY_WRAP( \ + 0x4d, 0x5a, 0x40, 0xfa, 0x19, 0x00, 0x7f, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x72, 0x02, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x41, 0x52, 0x4d, 0x64, 0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0xaa, \ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0xa0, 0x00, 0x06, 0x02, 0x0b, 0x02, 0x02, 0x14, 0x00, 0x00, 0x3b, 0x02, 0x00, 0x00, \ + 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x03, 0x02, 0x00, 0x00, 0x01, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x74, 0x65, 0x78, \ + 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, \ + 0x3b, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x60, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x3c, 0x02, 0x00, 0x0a, 0x2b, 0x00, \ + 0x00, 0x00, 0x3c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) + +#define TEST_LARGE_LZ4_DATA_SIZE sizeof((u8[]){TEST_LARGE_LZ4_DATA}) +/* head -c 320 ${LINUX_IMG_PATH} | lz4 -12 --favor-decSpeed -c | \ + hexdump -e '1/1 "0x%02x" ", "' -v */ +#define TEST_LARGE_LZ4_DATA EMPTY_WRAP( \ + 0x04, 0x22, 0x4d, 0x18, 0x64, 0x40, 0xa7, 0x9a, 0x00, 0x00, 0x00, 0xa4, 0x4d, 0x5a, \ + 0x40, 0xfa, 0x19, 0x00, 0x7f, 0x14, 0x00, 0x00, 0x02, 0x00, 0x20, 0x72, 0x02, 0x0c, \ + 0x00, 0x3e, 0x0a, 0x00, 0x00, 0x02, 0x00, 0x07, 0x14, 0x00, 0xf9, 0x00, 0x41, 0x52, \ + 0x4d, 0x64, 0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0xaa, 0x02, 0x1c, \ + 0x00, 0xf1, 0x00, 0xa0, 0x00, 0x06, 0x02, 0x0b, 0x02, 0x02, 0x14, 0x00, 0x00, 0x3b, \ + 0x02, 0x00, 0x00, 0x36, 0x14, 0x00, 0x77, 0x04, 0x04, 0x03, 0x02, 0x00, 0x00, 0x01, \ + 0x26, 0x00, 0x33, 0x01, 0x00, 0x00, 0x37, 0x00, 0x19, 0x03, 0x3e, 0x00, 0x15, 0x72, \ + 0x28, 0x00, 0x19, 0x0a, 0x31, 0x00, 0x0e, 0x0a, 0x00, 0x04, 0x1c, 0x00, 0x3f, 0x06, \ + 0x00, 0x00, 0x02, 0x00, 0x1e, 0x51, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x0a, 0x00, 0x13, \ + 0x3b, 0x70, 0x00, 0x2c, 0x3b, 0x02, 0x6f, 0x00, 0x90, 0x20, 0x00, 0x00, 0x60, 0x2e, \ + 0x64, 0x61, 0x74, 0x61, 0x0e, 0x00, 0x01, 0xc8, 0x00, 0x52, 0x3c, 0x02, 0x00, 0x0a, \ + 0x2b, 0x08, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0xf6, 0xb4, 0xb7, 0x4f) + +#define TEST_LARGE_LEGACY_LZ4_DATA_SIZE sizeof((u8[]){TEST_LARGE_LEGACY_LZ4_DATA}) +/* head -c 320 ${LINUX_IMG_PATH} | lz4 -l -12 --favor-decSpeed -c | \ + hexdump -e '1/1 "0x%02x" ", "' -v */ +#define TEST_LARGE_LEGACY_LZ4_DATA EMPTY_WRAP( \ + 0x02, 0x21, 0x4c, 0x18, 0x93, 0x00, 0x00, 0x00, 0x95, 0x4d, 0x5a, 0x40, 0xfa, 0x19, \ + 0x00, 0x7f, 0x14, 0x00, 0x01, 0x00, 0x20, 0x72, 0x02, 0x06, 0x00, 0x2f, 0x0a, 0x00, \ + 0x01, 0x00, 0x0b, 0xf9, 0x00, 0x41, 0x52, 0x4d, 0x64, 0x40, 0x00, 0x00, 0x00, 0x50, \ + 0x45, 0x00, 0x00, 0x64, 0xaa, 0x02, 0x1c, 0x00, 0xf1, 0x00, 0xa0, 0x00, 0x06, 0x02, \ + 0x0b, 0x02, 0x02, 0x14, 0x00, 0x00, 0x3b, 0x02, 0x00, 0x00, 0x36, 0x14, 0x00, 0x76, \ + 0x04, 0x04, 0x03, 0x02, 0x00, 0x00, 0x01, 0x26, 0x00, 0x00, 0x0c, 0x00, 0x03, 0x37, \ + 0x00, 0x19, 0x03, 0x3e, 0x00, 0x15, 0x72, 0x28, 0x00, 0x1a, 0x0a, 0x31, 0x00, 0x0f, \ + 0x01, 0x00, 0x06, 0x2f, 0x06, 0x00, 0x01, 0x00, 0x1f, 0x51, 0x2e, 0x74, 0x65, 0x78, \ + 0x74, 0x0a, 0x00, 0x13, 0x3b, 0x70, 0x00, 0x2c, 0x3b, 0x02, 0x6f, 0x00, 0x91, 0x20, \ + 0x00, 0x00, 0x60, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x0e, 0x00, 0x00, 0xc8, 0x00, 0x52, \ + 0x3c, 0x02, 0x00, 0x0a, 0x2b, 0x08, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00) + +static void test_lz4(void **state) +{ + const u8 test_src_data[TEST_LZ4_DATA_SIZE] = { TEST_LZ4_DATA }; + u8 test_dst_data[TEST_RAW_DATA_SIZE] = {0}; + u8 test_uncompressed_data[TEST_RAW_DATA_SIZE] = { TEST_RAW_DATA }; + size_t dst_size; + + dst_size = ulz4fn(test_src_data, sizeof(test_src_data), + test_dst_data, sizeof(test_dst_data)); + assert_int_equal(TEST_RAW_DATA_SIZE, dst_size); + assert_memory_equal(test_dst_data, test_uncompressed_data, TEST_RAW_DATA_SIZE); +} + +static void test_lz4_partial_decompression(void **state) +{ + const u8 test_src_data[TEST_LARGE_LZ4_DATA_SIZE] = { TEST_LARGE_LZ4_DATA }; + u8 test_dst_data[TEST_LARGE_RAW_DATA_SIZE / 2] = {0}; + u8 test_uncompressed_data[TEST_LARGE_RAW_DATA_SIZE] = { TEST_LARGE_RAW_DATA }; + size_t dst_size; + + ulz4fn(test_src_data, sizeof(test_src_data), + test_dst_data, sizeof(test_dst_data)); + for (dst_size = 0; dst_size < sizeof(test_dst_data); dst_size++) { + if (test_dst_data[dst_size] != test_uncompressed_data[dst_size]) + break; + } + assert_in_range(dst_size, TEST_LARGE_RAW_DATA_SIZE / 4, TEST_LARGE_RAW_DATA_SIZE); +} + +static void test_legacy_lz4(void **state) +{ + const u8 test_src_data[TEST_LEGACY_LZ4_DATA_SIZE] = { TEST_LEGACY_LZ4_DATA }; + u8 test_dst_data[TEST_RAW_DATA_SIZE] = {0}; + u8 test_uncompressed_data[TEST_RAW_DATA_SIZE] = { TEST_RAW_DATA }; + size_t dst_size; + + dst_size = ulz4ln(test_src_data, sizeof(test_src_data), + test_dst_data, sizeof(test_dst_data)); + assert_int_equal(TEST_RAW_DATA_SIZE, dst_size); + assert_memory_equal(test_dst_data, test_uncompressed_data, TEST_RAW_DATA_SIZE); +} + +static void test_legacy_lz4_partial_decompression(void **state) +{ + const u8 test_src_data[TEST_LARGE_LEGACY_LZ4_DATA_SIZE] = { TEST_LARGE_LEGACY_LZ4_DATA }; + u8 test_dst_data[TEST_LARGE_RAW_DATA_SIZE / 2] = {0}; + u8 test_uncompressed_data[TEST_LARGE_RAW_DATA_SIZE] = { TEST_LARGE_RAW_DATA }; + size_t dst_size; + + ulz4ln(test_src_data, sizeof(test_src_data), + test_dst_data, sizeof(test_dst_data)); + for (dst_size = 0; dst_size < sizeof(test_dst_data); dst_size++) { + if (test_dst_data[dst_size] != test_uncompressed_data[dst_size]) + break; + } + assert_in_range(dst_size, TEST_LARGE_RAW_DATA_SIZE / 4, TEST_LARGE_RAW_DATA_SIZE); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_lz4), + cmocka_unit_test(test_lz4_partial_decompression), + cmocka_unit_test(test_legacy_lz4), + cmocka_unit_test(test_legacy_lz4_partial_decompression), + }; + + return lp_run_group_tests(tests, NULL, NULL); +}