// SPDX-License-Identifier: 0BSD /////////////////////////////////////////////////////////////////////////////// // /// \file test_stream_flags.c /// \brief Tests Stream Header and Stream Footer coders // // Authors: Jia Tan // Lasse Collin // /////////////////////////////////////////////////////////////////////////////// #include "tests.h" // Size of the Stream Flags field // (taken from src/liblzma/common/stream_flags_common.h) #define XZ_STREAM_FLAGS_SIZE 2 #ifdef HAVE_ENCODERS // Header and footer magic bytes for .xz file format // (taken from src/liblzma/common/stream_flags_common.c) static const uint8_t xz_header_magic[6] = { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00 }; static const uint8_t xz_footer_magic[2] = { 0x59, 0x5A }; #endif #ifdef HAVE_ENCODERS static void stream_header_encode_helper(lzma_check check) { lzma_stream_flags flags = { .version = 0, .check = check, }; uint8_t header[LZMA_STREAM_HEADER_SIZE]; // Encode Stream Header assert_lzma_ret(lzma_stream_header_encode(&flags, header), LZMA_OK); // Stream Header must start with Header Magic Bytes const uint32_t magic_size = sizeof(xz_header_magic); assert_array_eq(header, xz_header_magic, magic_size); // Next must come Stream Flags const uint8_t *encoded_stream_flags = header + magic_size; // First byte is always null-byte. // Second byte must have the Check ID in the lowest four bits // and the highest four bits zero. const uint8_t expected_stream_flags[] = { 0, check }; assert_array_eq(encoded_stream_flags, expected_stream_flags, XZ_STREAM_FLAGS_SIZE); // Last part is the CRC32 of the Stream Flags const uint8_t *crc_ptr = encoded_stream_flags + XZ_STREAM_FLAGS_SIZE; const uint32_t expected_crc = lzma_crc32(expected_stream_flags, XZ_STREAM_FLAGS_SIZE, 0); assert_uint_eq(read32le(crc_ptr), expected_crc); } #endif static void test_lzma_stream_header_encode(void) { #ifndef HAVE_ENCODERS assert_skip("Encoder support disabled"); #else for (lzma_check i = 0; i < LZMA_CHECK_ID_MAX; i++) stream_header_encode_helper(i); lzma_stream_flags flags = { .version = 0, .check = LZMA_CHECK_CRC32 }; uint8_t header[LZMA_STREAM_HEADER_SIZE]; // Should fail if version > 0 flags.version = 1; assert_lzma_ret(lzma_stream_header_encode(&flags, header), LZMA_OPTIONS_ERROR); flags.version = 0; // Should fail if Check ID is invalid flags.check = INVALID_LZMA_CHECK_ID; assert_lzma_ret(lzma_stream_header_encode(&flags, header), LZMA_PROG_ERROR); flags.check = LZMA_CHECK_CRC32; // Should pass even if Backward Size is invalid // because Stream Header doesn't have that field. flags.backward_size = LZMA_VLI_MAX + 1; assert_lzma_ret(lzma_stream_header_encode(&flags, header), LZMA_OK); #endif } #if defined(HAVE_ENCODERS) static void stream_footer_encode_helper(lzma_check check) { lzma_stream_flags flags = { .version = 0, .check = check, .backward_size = LZMA_BACKWARD_SIZE_MIN, }; uint8_t footer[LZMA_STREAM_HEADER_SIZE]; // Encode Stream Footer assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_OK); // Stream Footer must start with CRC32 const uint32_t crc = read32le(footer); const uint32_t expected_crc = lzma_crc32(footer + sizeof(uint32_t), LZMA_STREAM_HEADER_SIZE - (sizeof(uint32_t) + sizeof(xz_footer_magic)), 0); assert_uint_eq(crc, expected_crc); // Next the Backward Size const uint32_t backwards_size = read32le(footer + sizeof(uint32_t)); const uint32_t expected_backwards_size = flags.backward_size / 4 - 1; assert_uint_eq(backwards_size, expected_backwards_size); // Next the Stream Flags const uint8_t *stream_flags = footer + sizeof(uint32_t) * 2; // First byte must be null assert_uint_eq(stream_flags[0], 0); // Second byte must have the Check ID in the lowest four bits // and the highest four bits zero. assert_uint_eq(stream_flags[1], check); // And ends with Footer Magic Bytes const uint8_t *expected_footer_magic = stream_flags + XZ_STREAM_FLAGS_SIZE; assert_array_eq(expected_footer_magic, xz_footer_magic, sizeof(xz_footer_magic)); } #endif static void test_lzma_stream_footer_encode(void) { #ifndef HAVE_ENCODERS assert_skip("Encoder support disabled"); #else for (lzma_check i = 0; i < LZMA_CHECK_ID_MAX; i++) stream_footer_encode_helper(i); lzma_stream_flags flags = { .version = 0, .backward_size = LZMA_BACKWARD_SIZE_MIN, .check = LZMA_CHECK_CRC32 }; uint8_t footer[LZMA_STREAM_HEADER_SIZE]; // Should fail if version > 0 flags.version = 1; assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_OPTIONS_ERROR); flags.version = 0; // Should fail if Check ID is invalid flags.check = INVALID_LZMA_CHECK_ID; assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_PROG_ERROR); // Should fail if Backward Size is invalid flags.backward_size -= 1; assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_PROG_ERROR); flags.backward_size += 2; assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_PROG_ERROR); flags.backward_size = LZMA_BACKWARD_SIZE_MAX + 4; assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_PROG_ERROR); #endif } #if defined(HAVE_ENCODERS) && defined(HAVE_DECODERS) static void stream_header_decode_helper(lzma_check check) { lzma_stream_flags flags = { .version = 0, .check = check }; uint8_t header[LZMA_STREAM_HEADER_SIZE]; assert_lzma_ret(lzma_stream_header_encode(&flags, header), LZMA_OK); lzma_stream_flags dest_flags; assert_lzma_ret(lzma_stream_header_decode(&dest_flags, header), LZMA_OK); // Version should be 0 assert_uint_eq(dest_flags.version, 0); // Backward Size should be LZMA_VLI_UNKNOWN assert_uint_eq(dest_flags.backward_size, LZMA_VLI_UNKNOWN); // Check ID must equal the argument given to this function. assert_uint_eq(dest_flags.check, check); } #endif static void test_lzma_stream_header_decode(void) { #if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS) assert_skip("Encoder or decoder support disabled"); #else for (lzma_check i = 0; i < LZMA_CHECK_ID_MAX; i++) stream_header_decode_helper(i); lzma_stream_flags flags = { .version = 0, .check = LZMA_CHECK_CRC32 }; uint8_t header[LZMA_STREAM_HEADER_SIZE]; lzma_stream_flags dest; // First encode known flags to header buffer assert_lzma_ret(lzma_stream_header_encode(&flags, header), LZMA_OK); // Should fail if magic bytes do not match header[0] ^= 1; assert_lzma_ret(lzma_stream_header_decode(&dest, header), LZMA_FORMAT_ERROR); header[0] ^= 1; // Should fail if a reserved bit is set uint8_t *stream_flags = header + sizeof(xz_header_magic); stream_flags[0] = 1; // Need to adjust CRC32 after making a change since the CRC32 // is verified before decoding the Stream Flags field. uint8_t *crc32_ptr = header + sizeof(xz_header_magic) + XZ_STREAM_FLAGS_SIZE; const uint32_t crc_orig = read32le(crc32_ptr); uint32_t new_crc32 = lzma_crc32( stream_flags, XZ_STREAM_FLAGS_SIZE, 0); write32le(crc32_ptr, new_crc32); assert_lzma_ret(lzma_stream_header_decode(&dest, header), LZMA_OPTIONS_ERROR); stream_flags[0] = 0; write32le(crc32_ptr, crc_orig); // Should fail if upper bits of check ID are set stream_flags[1] |= 0xF0; new_crc32 = lzma_crc32(stream_flags, XZ_STREAM_FLAGS_SIZE, 0); write32le(crc32_ptr, new_crc32); assert_lzma_ret(lzma_stream_header_decode(&dest, header), LZMA_OPTIONS_ERROR); stream_flags[1] = flags.check; write32le(crc32_ptr, crc_orig); // Should fail if CRC32 does not match. // First, alter a byte in the Stream Flags. stream_flags[0] = 1; assert_lzma_ret(lzma_stream_header_decode(&dest, header), LZMA_DATA_ERROR); stream_flags[0] = 0; // Next, change the CRC32. *crc32_ptr ^= 1; assert_lzma_ret(lzma_stream_header_decode(&dest, header), LZMA_DATA_ERROR); #endif } #if defined(HAVE_ENCODERS) && defined(HAVE_DECODERS) static void stream_footer_decode_helper(lzma_check check) { lzma_stream_flags flags = { .version = 0, .backward_size = LZMA_BACKWARD_SIZE_MIN, .check = check, }; uint8_t footer[LZMA_STREAM_HEADER_SIZE]; assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_OK); lzma_stream_flags dest_flags; assert_lzma_ret(lzma_stream_footer_decode(&dest_flags, footer), LZMA_OK); // Version should be 0. assert_uint_eq(dest_flags.version, 0); // Backward Size should equal the value from the flags. assert_uint_eq(dest_flags.backward_size, flags.backward_size); // Check ID must equal argument given to this function. assert_uint_eq(dest_flags.check, check); } #endif static void test_lzma_stream_footer_decode(void) { #if !defined(HAVE_ENCODERS) || !defined(HAVE_DECODERS) assert_skip("Encoder or decoder support disabled"); #else for (lzma_check i = 0; i < LZMA_CHECK_ID_MAX; i++) stream_footer_decode_helper(i); lzma_stream_flags flags = { .version = 0, .check = LZMA_CHECK_CRC32, .backward_size = LZMA_BACKWARD_SIZE_MIN }; uint8_t footer[LZMA_STREAM_HEADER_SIZE]; lzma_stream_flags dest; // First encode known flags to the footer buffer assert_lzma_ret(lzma_stream_footer_encode(&flags, footer), LZMA_OK); // Should fail if magic bytes do not match footer[LZMA_STREAM_HEADER_SIZE - 1] ^= 1; assert_lzma_ret(lzma_stream_footer_decode(&dest, footer), LZMA_FORMAT_ERROR); footer[LZMA_STREAM_HEADER_SIZE - 1] ^= 1; // Should fail if a reserved bit is set. // In the Stream Footer, the Stream Flags follow the CRC32 (4 bytes) // and the Backward Size (4 bytes) uint8_t *stream_flags = footer + sizeof(uint32_t) * 2; stream_flags[0] = 1; // Need to adjust the CRC32 so it will not fail that check instead uint8_t *crc32_ptr = footer; const uint32_t crc_orig = read32le(crc32_ptr); uint8_t *backward_size = footer + sizeof(uint32_t); uint32_t new_crc32 = lzma_crc32(backward_size, sizeof(uint32_t) + XZ_STREAM_FLAGS_SIZE, 0); write32le(crc32_ptr, new_crc32); assert_lzma_ret(lzma_stream_footer_decode(&dest, footer), LZMA_OPTIONS_ERROR); stream_flags[0] = 0; write32le(crc32_ptr, crc_orig); // Should fail if upper bits of check ID are set stream_flags[1] |= 0xF0; new_crc32 = lzma_crc32(backward_size, sizeof(uint32_t) + XZ_STREAM_FLAGS_SIZE, 0); write32le(crc32_ptr, new_crc32); assert_lzma_ret(lzma_stream_footer_decode(&dest, footer), LZMA_OPTIONS_ERROR); stream_flags[1] = flags.check; write32le(crc32_ptr, crc_orig); // Should fail if CRC32 does not match. // First, alter a byte in the Stream Flags. stream_flags[0] = 1; assert_lzma_ret(lzma_stream_footer_decode(&dest, footer), LZMA_DATA_ERROR); stream_flags[0] = 0; // Next, change the CRC32 *crc32_ptr ^= 1; assert_lzma_ret(lzma_stream_footer_decode(&dest, footer), LZMA_DATA_ERROR); #endif } static void test_lzma_stream_flags_compare(void) { lzma_stream_flags first = { .version = 0, .backward_size = LZMA_BACKWARD_SIZE_MIN, .check = LZMA_CHECK_CRC32, }; lzma_stream_flags second = first; // First test should pass assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_OK); // Altering either version should cause an error first.version = 1; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_OPTIONS_ERROR); second.version = 1; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_OPTIONS_ERROR); first.version = 0; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_OPTIONS_ERROR); second.version = 0; // Check types must be under the maximum first.check = INVALID_LZMA_CHECK_ID; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_PROG_ERROR); second.check = INVALID_LZMA_CHECK_ID; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_PROG_ERROR); first.check = LZMA_CHECK_CRC32; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_PROG_ERROR); second.check = LZMA_CHECK_CRC32; // Check types must be equal for (lzma_check i = 0; i < LZMA_CHECK_ID_MAX; i++) { first.check = i; if (i == second.check) assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_OK); else assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_DATA_ERROR); } first.check = LZMA_CHECK_CRC32; // Backward Size comparison is skipped if either are LZMA_VLI_UNKNOWN first.backward_size = LZMA_VLI_UNKNOWN; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_OK); second.backward_size = LZMA_VLI_MAX + 1; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_OK); second.backward_size = LZMA_BACKWARD_SIZE_MIN; // Backward Sizes need to be valid first.backward_size = LZMA_VLI_MAX + 4; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_PROG_ERROR); second.backward_size = LZMA_VLI_MAX + 4; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_PROG_ERROR); first.backward_size = LZMA_BACKWARD_SIZE_MIN; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_PROG_ERROR); second.backward_size = LZMA_BACKWARD_SIZE_MIN; // Backward Sizes must be equal second.backward_size = first.backward_size + 4; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_DATA_ERROR); // Should fail if Backward Sizes are > LZMA_BACKWARD_SIZE_MAX // even though they are equal first.backward_size = LZMA_BACKWARD_SIZE_MAX + 1; second.backward_size = LZMA_BACKWARD_SIZE_MAX + 1; assert_lzma_ret(lzma_stream_flags_compare(&first, &second), LZMA_PROG_ERROR); } extern int main(int argc, char **argv) { tuktest_start(argc, argv); tuktest_run(test_lzma_stream_header_encode); tuktest_run(test_lzma_stream_footer_encode); tuktest_run(test_lzma_stream_header_decode); tuktest_run(test_lzma_stream_footer_decode); tuktest_run(test_lzma_stream_flags_compare); return tuktest_end(); }