1 // SPDX-License-Identifier: 0BSD 2 3 /////////////////////////////////////////////////////////////////////////////// 4 // 5 /// \file test_lzip_decoder.c 6 /// \brief Tests decoding lzip data 7 // 8 // Author: Jia Tan 9 // 10 /////////////////////////////////////////////////////////////////////////////// 11 12 #include "tests.h" 13 14 #ifdef HAVE_LZIP_DECODER 15 16 // Memlimit large enough to pass all of the test files 17 #define MEMLIMIT (1U << 20) 18 #define DECODE_CHUNK_SIZE 1024 19 20 21 // The uncompressed data in the test files are short US-ASCII strings. 22 // The tests check if the decompressed output is what it is expected to be. 23 // Storing the strings here as text would break the tests on EBCDIC systems 24 // and storing the strings as an array of hex values is inconvenient, so 25 // store the CRC32 values of the expected data instead. 26 // 27 // CRC32 value of "Hello\nWorld\n" 28 static const uint32_t hello_world_crc = 0x15A2A343; 29 30 // CRC32 value of "Trailing garbage\n" 31 static const uint32_t trailing_garbage_crc = 0x87081A60; 32 33 34 // Helper function to decode a good file with no flags and plenty high memlimit 35 static void 36 basic_lzip_decode(const char *src, const uint32_t expected_crc) 37 { 38 size_t file_size; 39 uint8_t *data = tuktest_file_from_srcdir(src, &file_size); 40 uint32_t checksum = 0; 41 42 lzma_stream strm = LZMA_STREAM_INIT; 43 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 0), LZMA_OK); 44 45 uint8_t output_buffer[DECODE_CHUNK_SIZE]; 46 47 strm.next_in = data; 48 strm.next_out = output_buffer; 49 strm.avail_out = sizeof(output_buffer); 50 51 // Feed 1 byte at a time to the decoder to look for any bugs 52 // when switching between decoding sequences 53 lzma_ret ret = LZMA_OK; 54 while (ret == LZMA_OK) { 55 strm.avail_in = 1; 56 ret = lzma_code(&strm, LZMA_RUN); 57 if (strm.avail_out == 0) { 58 checksum = lzma_crc32(output_buffer, 59 (size_t)(strm.next_out - output_buffer), 60 checksum); 61 strm.next_out = output_buffer; 62 strm.avail_out = sizeof(output_buffer); 63 } 64 } 65 66 assert_lzma_ret(ret, LZMA_STREAM_END); 67 assert_uint_eq(strm.total_in, file_size); 68 69 checksum = lzma_crc32(output_buffer, 70 (size_t)(strm.next_out - output_buffer), 71 checksum); 72 assert_uint_eq(checksum, expected_crc); 73 74 lzma_end(&strm); 75 } 76 77 78 static void 79 test_options(void) 80 { 81 // Test NULL stream 82 assert_lzma_ret(lzma_lzip_decoder(NULL, MEMLIMIT, 0), 83 LZMA_PROG_ERROR); 84 85 // Test invalid flags 86 lzma_stream strm = LZMA_STREAM_INIT; 87 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, UINT32_MAX), 88 LZMA_OPTIONS_ERROR); 89 // Memlimit tests are done elsewhere 90 } 91 92 93 static void 94 test_v0_decode(void) 95 { 96 // This tests if liblzma can decode lzip version 0 files. 97 // lzip 1.17 and older can decompress this, but lzip 1.18 98 // and newer can no longer decode these files. 99 basic_lzip_decode("files/good-1-v0.lz", hello_world_crc); 100 } 101 102 103 static void 104 test_v1_decode(void) 105 { 106 // This tests decoding a basic lzip v1 file 107 basic_lzip_decode("files/good-1-v1.lz", hello_world_crc); 108 } 109 110 111 // Helper function to decode a good file with trailing bytes after 112 // the lzip stream 113 static void 114 trailing_helper(const char *src, const uint32_t expected_data_checksum, 115 const uint32_t expected_trailing_checksum) 116 { 117 size_t file_size; 118 uint32_t checksum = 0; 119 uint8_t *data = tuktest_file_from_srcdir(src, &file_size); 120 lzma_stream strm = LZMA_STREAM_INIT; 121 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 122 LZMA_CONCATENATED), LZMA_OK); 123 124 uint8_t output_buffer[DECODE_CHUNK_SIZE]; 125 126 strm.next_in = data; 127 strm.avail_in = file_size; 128 strm.next_out = output_buffer; 129 strm.avail_out = sizeof(output_buffer); 130 131 lzma_ret ret = LZMA_OK; 132 while (ret == LZMA_OK) { 133 ret = lzma_code(&strm, LZMA_RUN); 134 if (strm.avail_out == 0) { 135 checksum = lzma_crc32(output_buffer, 136 (size_t)(strm.next_out - output_buffer), 137 checksum); 138 strm.next_out = output_buffer; 139 strm.avail_out = sizeof(output_buffer); 140 } 141 } 142 143 assert_lzma_ret(ret, LZMA_STREAM_END); 144 assert_uint(strm.total_in, <, file_size); 145 146 checksum = lzma_crc32(output_buffer, 147 (size_t)(strm.next_out - output_buffer), 148 checksum); 149 150 assert_uint_eq(checksum, expected_data_checksum); 151 152 // Trailing data should be readable from strm.next_in 153 checksum = lzma_crc32(strm.next_in, strm.avail_in, 0); 154 assert_uint_eq(checksum, expected_trailing_checksum); 155 156 lzma_end(&strm); 157 } 158 159 160 // Helper function to decode a bad file and compare to returned error to 161 // what the caller expects 162 static void 163 decode_expect_error(const char *src, lzma_ret expected_error) 164 { 165 lzma_stream strm = LZMA_STREAM_INIT; 166 size_t file_size; 167 uint8_t *data = tuktest_file_from_srcdir(src, &file_size); 168 169 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 170 LZMA_CONCATENATED), LZMA_OK); 171 172 uint8_t output_buffer[DECODE_CHUNK_SIZE]; 173 174 strm.avail_in = file_size; 175 strm.next_in = data; 176 177 lzma_ret ret = LZMA_OK; 178 179 do { 180 // Discard output since we are only looking for errors 181 strm.next_out = output_buffer; 182 strm.avail_out = sizeof(output_buffer); 183 if (strm.avail_in == 0) 184 ret = lzma_code(&strm, LZMA_FINISH); 185 else 186 ret = lzma_code(&strm, LZMA_RUN); 187 } while (ret == LZMA_OK); 188 189 assert_lzma_ret(ret, expected_error); 190 lzma_end(&strm); 191 } 192 193 194 static void 195 test_v0_trailing(void) 196 { 197 trailing_helper("files/good-1-v0-trailing-1.lz", hello_world_crc, 198 trailing_garbage_crc); 199 } 200 201 202 static void 203 test_v1_trailing(void) 204 { 205 trailing_helper("files/good-1-v1-trailing-1.lz", hello_world_crc, 206 trailing_garbage_crc); 207 208 // The second files/good-1-v1-trailing-2.lz will have the same 209 // expected output and trailing output as 210 // files/good-1-v1-trailing-1.lz, but this tests if the prefix 211 // to the trailing data contains lzip magic bytes. 212 // When this happens, the expected behavior is to silently ignore 213 // the magic byte prefix and consume it from the input file. 214 trailing_helper("files/good-1-v1-trailing-2.lz", hello_world_crc, 215 trailing_garbage_crc); 216 217 // Expect LZMA_BUF error if a file ends with the lzip magic bytes 218 // but does not contain any data after 219 decode_expect_error("files/bad-1-v1-trailing-magic.lz", 220 LZMA_BUF_ERROR); 221 } 222 223 224 static void 225 test_concatenated(void) 226 { 227 // First test a file with one v0 member and one v1 member 228 // The first member should contain "Hello\n" and 229 // the second member should contain "World!\n" 230 lzma_stream strm = LZMA_STREAM_INIT; 231 size_t file_size; 232 uint8_t *v0_v1 = tuktest_file_from_srcdir("files/good-2-v0-v1.lz", 233 &file_size); 234 235 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 236 LZMA_CONCATENATED), LZMA_OK); 237 238 uint8_t output_buffer[DECODE_CHUNK_SIZE]; 239 240 strm.next_in = v0_v1; 241 strm.avail_in = file_size; 242 strm.next_out = output_buffer; 243 strm.avail_out = sizeof(output_buffer); 244 245 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END); 246 247 assert_uint_eq(strm.total_in, file_size); 248 249 uint32_t checksum = lzma_crc32(output_buffer, strm.total_out, 0); 250 assert_uint_eq(checksum, hello_world_crc); 251 252 // The second file contains one v1 member and one v2 member 253 uint8_t *v1_v0 = tuktest_file_from_srcdir("files/good-2-v1-v0.lz", 254 &file_size); 255 256 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 257 LZMA_CONCATENATED), LZMA_OK); 258 259 strm.next_in = v1_v0; 260 strm.avail_in = file_size; 261 strm.next_out = output_buffer; 262 strm.avail_out = sizeof(output_buffer); 263 264 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END); 265 266 assert_uint_eq(strm.total_in, file_size); 267 checksum = lzma_crc32(output_buffer, strm.total_out, 0); 268 assert_uint_eq(checksum, hello_world_crc); 269 270 // The third file contains 2 v1 members 271 uint8_t *v1_v1 = tuktest_file_from_srcdir("files/good-2-v1-v1.lz", 272 &file_size); 273 274 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 275 LZMA_CONCATENATED), LZMA_OK); 276 277 strm.next_in = v1_v1; 278 strm.avail_in = file_size; 279 strm.next_out = output_buffer; 280 strm.avail_out = sizeof(output_buffer); 281 282 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END); 283 284 assert_uint_eq(strm.total_in, file_size); 285 checksum = lzma_crc32(output_buffer, strm.total_out, 0); 286 assert_uint_eq(checksum, hello_world_crc); 287 288 lzma_end(&strm); 289 } 290 291 292 static void 293 test_crc(void) 294 { 295 // Test invalid checksum 296 lzma_stream strm = LZMA_STREAM_INIT; 297 size_t file_size; 298 uint8_t *data = tuktest_file_from_srcdir("files/bad-1-v1-crc32.lz", 299 &file_size); 300 301 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 302 LZMA_CONCATENATED), LZMA_OK); 303 304 uint8_t output_buffer[DECODE_CHUNK_SIZE]; 305 306 strm.next_in = data; 307 strm.avail_in = file_size; 308 strm.next_out = output_buffer; 309 strm.avail_out = sizeof(output_buffer); 310 311 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_DATA_ERROR); 312 313 // Test ignoring the checksum value - should decode successfully 314 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 315 LZMA_CONCATENATED | LZMA_IGNORE_CHECK), LZMA_OK); 316 317 strm.next_in = data; 318 strm.avail_in = file_size; 319 strm.next_out = output_buffer; 320 strm.avail_out = sizeof(output_buffer); 321 322 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END); 323 assert_uint_eq(strm.total_in, file_size); 324 325 // Test tell check 326 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 327 LZMA_CONCATENATED | LZMA_TELL_ANY_CHECK), LZMA_OK); 328 329 strm.next_in = data; 330 strm.avail_in = file_size; 331 strm.next_out = output_buffer; 332 strm.avail_out = sizeof(output_buffer); 333 334 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_GET_CHECK); 335 assert_uint_eq(lzma_get_check(&strm), LZMA_CHECK_CRC32); 336 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_DATA_ERROR); 337 lzma_end(&strm); 338 } 339 340 341 static void 342 test_invalid_magic_bytes(void) 343 { 344 uint8_t lzip_id_string[] = { 0x4C, 0x5A, 0x49, 0x50 }; 345 lzma_stream strm = LZMA_STREAM_INIT; 346 347 for (uint32_t i = 0; i < ARRAY_SIZE(lzip_id_string); i++) { 348 // Corrupt magic bytes 349 lzip_id_string[i] ^= 1; 350 uint8_t output_buffer[DECODE_CHUNK_SIZE]; 351 352 assert_lzma_ret(lzma_lzip_decoder(&strm, MEMLIMIT, 0), 353 LZMA_OK); 354 355 strm.next_in = lzip_id_string; 356 strm.avail_in = sizeof(lzip_id_string); 357 strm.next_out = output_buffer; 358 strm.avail_out = sizeof(output_buffer); 359 360 assert_lzma_ret(lzma_code(&strm, LZMA_RUN), 361 LZMA_FORMAT_ERROR); 362 363 // Reset magic bytes 364 lzip_id_string[i] ^= 1; 365 } 366 367 lzma_end(&strm); 368 } 369 370 371 static void 372 test_invalid_version(void) 373 { 374 // The file contains a version number that is not 0 or 1, 375 // so it should cause an error 376 decode_expect_error("files/unsupported-1-v234.lz", 377 LZMA_OPTIONS_ERROR); 378 } 379 380 381 static void 382 test_invalid_dictionary_size(void) 383 { 384 // The first file has a too small dictionary size field. 385 decode_expect_error("files/bad-1-v1-dict-1.lz", LZMA_DATA_ERROR); 386 387 // The second file has a too large dictionary size field. 388 decode_expect_error("files/bad-1-v1-dict-2.lz", LZMA_DATA_ERROR); 389 } 390 391 392 static void 393 test_invalid_uncomp_size(void) 394 { 395 // Test invalid v0 lzip file uncomp size 396 decode_expect_error("files/bad-1-v0-uncomp-size.lz", 397 LZMA_DATA_ERROR); 398 399 // Test invalid v1 lzip file uncomp size 400 decode_expect_error("files/bad-1-v1-uncomp-size.lz", 401 LZMA_DATA_ERROR); 402 } 403 404 405 static void 406 test_invalid_member_size(void) 407 { 408 decode_expect_error("files/bad-1-v1-member-size.lz", 409 LZMA_DATA_ERROR); 410 } 411 412 413 static void 414 test_invalid_memlimit(void) 415 { 416 // A very low memlimit should prevent decoding. 417 // It should be possible to update the memlimit after the error. 418 size_t file_size; 419 uint8_t *data = tuktest_file_from_srcdir("files/good-1-v1.lz", 420 &file_size); 421 422 uint8_t output_buffer[DECODE_CHUNK_SIZE]; 423 424 lzma_stream strm = LZMA_STREAM_INIT; 425 426 assert_lzma_ret(lzma_lzip_decoder(&strm, 1, 0), LZMA_OK); 427 428 strm.next_in = data; 429 strm.avail_in = file_size; 430 strm.next_out = output_buffer; 431 strm.avail_out = sizeof(output_buffer); 432 433 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_MEMLIMIT_ERROR); 434 435 // Up the memlimit so that decoding can continue. 436 // First only increase by a small amount and expect an error. 437 assert_lzma_ret(lzma_memlimit_set(&strm, 100), LZMA_MEMLIMIT_ERROR); 438 assert_lzma_ret(lzma_memlimit_set(&strm, MEMLIMIT), LZMA_OK); 439 440 // Finish decoding 441 assert_lzma_ret(lzma_code(&strm, LZMA_FINISH), LZMA_STREAM_END); 442 443 assert_uint_eq(strm.total_in, file_size); 444 uint32_t checksum = lzma_crc32(output_buffer, strm.total_out, 0); 445 assert_uint_eq(checksum, hello_world_crc); 446 447 lzma_end(&strm); 448 } 449 #endif 450 451 452 extern int 453 main(int argc, char **argv) 454 { 455 tuktest_start(argc, argv); 456 457 #ifndef HAVE_LZIP_DECODER 458 tuktest_early_skip("lzip decoder disabled"); 459 #else 460 tuktest_run(test_options); 461 tuktest_run(test_v0_decode); 462 tuktest_run(test_v1_decode); 463 tuktest_run(test_v0_trailing); 464 tuktest_run(test_v1_trailing); 465 tuktest_run(test_concatenated); 466 tuktest_run(test_crc); 467 tuktest_run(test_invalid_magic_bytes); 468 tuktest_run(test_invalid_version); 469 tuktest_run(test_invalid_dictionary_size); 470 tuktest_run(test_invalid_uncomp_size); 471 tuktest_run(test_invalid_member_size); 472 tuktest_run(test_invalid_memlimit); 473 return tuktest_end(); 474 #endif 475 } 476