Add a deterministic regression test for the gzwrite() case where a decompression input chunk is exhausted at exactly the same time as the write buffer fills up. Build a gzip file by hand from two 1 KiB stored deflate blocks and pick a chunk size that covers exactly the first block header plus its payload, so that with a 1 KiB write buffer the first input chunk runs out precisely when the write buffer is full.
Unlike the existing random data test, which only hits this corner case for rare byte patterns (about 1 percent of runs on sandbox64), this test fails 20 out of 20 runs without the preceding gunzip fix: Error: inflate() returned -5 and passed 100 out of 100 runs with it. Signed-off-by: Aristo Chen <[email protected]> --- test/cmd/unzip.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/test/cmd/unzip.c b/test/cmd/unzip.c index 623a2785884..f0664d1a8f3 100644 --- a/test/cmd/unzip.c +++ b/test/cmd/unzip.c @@ -101,11 +101,10 @@ static int dm_test_cmd_zip_unzip(struct unit_test_state *uts) } DM_TEST(dm_test_cmd_zip_unzip, UTF_CONSOLE); -static int dm_test_cmd_zip_gzwrite(struct unit_test_state *uts) +static int bind_mmc9(struct unit_test_state *uts) { struct udevice *dev; ofnode root, node; - int i, j, ret; /* Enable the mmc9 node for this test */ root = oftree_root(oftree_default()); @@ -113,6 +112,15 @@ static int dm_test_cmd_zip_gzwrite(struct unit_test_state *uts) ut_assert(ofnode_valid(node)); ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false)); + return 0; +} + +static int dm_test_cmd_zip_gzwrite(struct unit_test_state *uts) +{ + int i, j, ret; + + ut_assertok(bind_mmc9(uts)); + for (i = 0; i < ARRAY_SIZE(sizes); i++) { ret = do_test_cmd_zip_unzip(uts, sizes[i], true); if (ret) @@ -132,3 +140,94 @@ static int dm_test_cmd_zip_gzwrite(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_cmd_zip_gzwrite, UTF_CONSOLE); + +/* + * Regression test for the case where a decompression input chunk is + * exhausted at exactly the same time as the write buffer fills up, in + * which case gzwrite() used to call inflate() again with no input, + * receive Z_BUF_ERROR back and treat it as a fatal error. + * + * Craft a gzip file by hand from two stored (uncompressed) deflate + * blocks of 1 KiB each, and pick a chunk size that covers exactly the + * 5 byte header plus payload of the first stored block, so that with a + * 1 KiB write buffer the first chunk runs out precisely when the write + * buffer is full. + */ +static int dm_test_cmd_gzwrite_chunk_boundary(struct unit_test_state *uts) +{ + static const u8 gzip_hdr[10] = { + 0x1f, 0x8b, /* magic */ + 0x08, /* deflate */ + 0x00, /* no flags */ + 0x00, 0x00, 0x00, 0x00, /* mtime */ + 0x00, /* extra flags */ + 0x03, /* OS: unix */ + }; + const size_t blk = SZ_1K; + const size_t rawsize = 2 * blk; + unsigned long loadaddr = env_get_ulong("loadaddr", 16, 0); + unsigned long decaddr = loadaddr + SZ_1M; + unsigned char *gzmap = map_sysmem(loadaddr, sizeof(gzip_hdr) + + 2 * (5 + blk) + 8); + unsigned char *decmap = map_sysmem(decaddr, rawsize); + struct blk_desc *mmc_dev_desc; + u8 raw[2 * SZ_1K]; + size_t gzlen; + u8 *p = gzmap; + u32 crc; + int i; + + ut_assertok(bind_mmc9(uts)); + + for (i = 0; i < rawsize; i++) + raw[i] = (i * 251) & 0xff; + crc = crc32(0, raw, rawsize); + + memcpy(p, gzip_hdr, sizeof(gzip_hdr)); + p += sizeof(gzip_hdr); + for (i = 0; i < 2; i++) { + *p++ = (i == 1) ? 0x01 : 0x00; /* BFINAL on last block */ + *p++ = blk & 0xff; /* LEN */ + *p++ = blk >> 8; + *p++ = ~blk & 0xff; /* NLEN */ + *p++ = (~blk >> 8) & 0xff; + memcpy(p, raw + i * blk, blk); + p += blk; + } + *p++ = crc & 0xff; /* CRC32, little endian */ + *p++ = (crc >> 8) & 0xff; + *p++ = (crc >> 16) & 0xff; + *p++ = (crc >> 24) & 0xff; + *p++ = rawsize & 0xff; /* ISIZE, little endian */ + *p++ = (rawsize >> 8) & 0xff; + *p++ = (rawsize >> 16) & 0xff; + *p++ = (rawsize >> 24) & 0xff; + gzlen = p - gzmap; + + /* Input chunk: exactly one stored block header plus its payload */ + env_set_ulong("gzwrite_chunk", 5 + blk); + ut_assertok(run_commandf("gzwrite mmc 9 %lx %zx %zx", loadaddr, + gzlen, blk)); + ut_assert_skip_to_line("\t%zu bytes, crc 0x%08x", rawsize, crc); + env_set("gzwrite_chunk", NULL); + + ut_asserteq(9, blk_get_device_by_str("mmc", "9", &mmc_dev_desc)); + ut_assertok(run_commandf("mmc dev 9")); + ut_assert_nextline("switch to partitions #0, OK"); + ut_assert_nextline("mmc9 is current device"); + + ut_assertok(run_commandf("mmc read %lx 0 %zx", decaddr, + rawsize / 512)); + ut_assert_nextline("MMC read: dev # 9, block # 0, count %zu ... %zu blocks read: OK", + rawsize / 512, rawsize / 512); + + ut_asserteq_mem(raw, decmap, rawsize); + + ut_assert_console_end(); + + unmap_sysmem(gzmap); + unmap_sysmem(decmap); + + return 0; +} +DM_TEST(dm_test_cmd_gzwrite_chunk_boundary, UTF_CONSOLE); -- 2.43.0
