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); +}