diff --git a/app/rest/tests/zlibwrapper_unittest.cc b/app/rest/tests/zlibwrapper_unittest.cc index 785f528a3..1bfe20766 100644 --- a/app/rest/tests/zlibwrapper_unittest.cc +++ b/app/rest/tests/zlibwrapper_unittest.cc @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "gtest/gtest.h" @@ -1047,5 +1048,135 @@ TEST(ZLibWrapperStandalone, ReadPastEndOfWindow) { LOG(INFO) << "passed read-past-end-of-window test"; } +TEST(ZLibWrapperStandalone, BytewiseRead) { + std::string text = + "v nedrah tundry vydra v getrah tyrit v vedrah yadra kedra"; + size_t text_len = text.size(); + size_t archive_len = ZLib::MinCompressbufSize(text_len); + std::string archive(archive_len, '\0'); + size_t decompressed_len = text_len + 1; + std::string decompressed(decompressed_len, '\0'); + size_t decompressed_offset = 0; + + ZLib compressor; + compressor.SetGzipHeaderMode(); + int rc = compressor.Compress((Bytef*)archive.data(), &archive_len, + (Bytef*)text.data(), text_len); + ASSERT_EQ(rc, Z_OK); + + ZLib zlib; + zlib.SetGzipHeaderMode(); + for (size_t i = 0; i < archive_len; ++i) { + size_t source_len = 1; + size_t dest_len = decompressed_len - decompressed_offset; + rc = zlib.UncompressAtMost( + (Bytef*)decompressed.data() + decompressed_offset, &dest_len, + (Bytef*)archive.data() + i, &source_len); + ASSERT_EQ(rc, Z_OK); + ASSERT_EQ(source_len, 0); + decompressed_offset += dest_len; + } + + ASSERT_TRUE(zlib.IsGzipFooterValid()); + ASSERT_EQ(decompressed_offset, text_len); + + std::string truncated_output(decompressed.data(), text_len); + ASSERT_EQ(truncated_output, text); + + // if we haven't segfaulted by now, we pass + LOG(INFO) << "passed bytewise-read test"; +} + +TEST(ZLibWrapperStandaloneTest, TruncatedData) { + const int kBufferLen = 64; + std::string uncompressed = "Hello, World!"; + std::string compressed( + "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xf3\x48\xcd\xc9\xc9" + "\xd7\x51\x08\xcf\x2f\xca\x49\x51\x04\x00\xd0\xc3\x4a\xec\x0d" + "\x00\x00\x00", + 33); + + // Verify that "compressed" contains valid gzip data. + { + ZLib zlib; + zlib.SetGzipHeaderMode(); + char uncompbuf[kBufferLen]; + bzero(uncompbuf, kBufferLen); + uLongf uncomplen = kBufferLen; + int err = zlib.Uncompress( + reinterpret_cast(uncompbuf), &uncomplen, + reinterpret_cast(compressed.c_str()), compressed.size()); + ASSERT_EQ(err, Z_OK); + ASSERT_EQ(uncompressed, std::string_view(uncompbuf, uncomplen)); + } + + // Test truncated data with ZLib::Uncompress(). + for (int len = compressed.size() - 1; len > 0; len--) { + SCOPED_TRACE(absl::StrCat("Decompressing first ", len, " out of ", + compressed.size(), " bytes")); + ZLib zlib; + zlib.SetGzipHeaderMode(); + char uncompbuf[kBufferLen]; + bzero(uncompbuf, kBufferLen); + uLongf uncomplen = kBufferLen; + int err = zlib.Uncompress( + reinterpret_cast(uncompbuf), &uncomplen, + reinterpret_cast(compressed.c_str()), len); + ASSERT_NE(err, Z_OK); + } + + // Test truncated data with ZLib::UncompressAtMost() and + // ZLib::UncompressDone(). + for (int len = compressed.size() - 1; len > 0; len--) { + SCOPED_TRACE(absl::StrCat("Decompressing first ", len, " out of ", + compressed.size(), " bytes")); + ZLib zlib; + zlib.SetGzipHeaderMode(); + char uncompbuf[kBufferLen]; + bzero(uncompbuf, kBufferLen); + uLongf uncomplen = kBufferLen; + uLongf complen = len; + int err = zlib.UncompressAtMost( + reinterpret_cast(uncompbuf), &uncomplen, + reinterpret_cast(compressed.c_str()), &complen); + ASSERT_EQ(err, Z_OK); + ASSERT_EQ(complen, 0); + if (uncomplen > 0) { + EXPECT_THAT(uncompressed, + testing::StartsWith(absl::string_view(uncompbuf, uncomplen))); + } + ASSERT_FALSE(zlib.UncompressChunkDone()); + } +} + +TEST(ZLibWrapperStandalone, GzipUncompressedLength) { + ZLib zlib; + + // "Hello, World!", compressed. + std::string hello_world( + "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xf3\x48\xcd\xc9\xc9" + "\xd7\x51\x08\xcf\x2f\xca\x49\x51\x04\x00\xd0\xc3\x4a\xec\x0d" + "\x00\x00\x00", + 33); + EXPECT_EQ(13, zlib.GzipUncompressedLength( + reinterpret_cast(hello_world.c_str()), + hello_world.size())); + + // Empty string, "", compressed. + std::string empty( + "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x03\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00", + 20); + EXPECT_EQ(0, + zlib.GzipUncompressedLength( + reinterpret_cast(empty.c_str()), empty.size())); + + std::string bad_data("\x01\x01\x01\x01", 4); + for (int len = 0; len <= bad_data.size(); len++) { + EXPECT_EQ(0, zlib.GzipUncompressedLength( + reinterpret_cast(bad_data.c_str()), len)); + } +} + } // namespace } // namespace firebase diff --git a/app/rest/zlibwrapper.cc b/app/rest/zlibwrapper.cc index aef6e31a6..428054413 100644 --- a/app/rest/zlibwrapper.cc +++ b/app/rest/zlibwrapper.cc @@ -703,7 +703,9 @@ int ZLib::UncompressChunk(Bytef* dest, uLongf* destLen, const Bytef* source, // mode, we also check the gzip footer to make sure we pass the gzip // consistency checks. We RETURN true iff both types of checks pass. bool ZLib::UncompressChunkDone() { - assert(!first_chunk_ && uncomp_init_); + if (first_chunk_ || !uncomp_init_) { + return false; + } // Make sure we're at the end-of-compressed-data point. This means // if we call inflate with Z_FINISH we won't consume any input or // write any output @@ -784,7 +786,8 @@ int ZLib::Uncompress(Bytef* dest, uLongf* destLen, const Bytef* source, // read uncompress length from gzip footer uLongf ZLib::GzipUncompressedLength(const Bytef* source, uLong len) { - assert(len > 4); + if (len <= 4) return 0; // malformed data. + return (static_cast(source[len - 1]) << 24) + (static_cast(source[len - 2]) << 16) + (static_cast(source[len - 3]) << 8) + diff --git a/app/rest/zlibwrapper.h b/app/rest/zlibwrapper.h index a0d204229..f96c65f90 100644 --- a/app/rest/zlibwrapper.h +++ b/app/rest/zlibwrapper.h @@ -126,7 +126,8 @@ class ZLib { int Uncompress(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen); - // Get the uncompressed size from the gzip header. + // Get the uncompressed size from the gzip header. Returns 0 if source is too + // short (len < 5). uLongf GzipUncompressedLength(const Bytef* source, uLong len); // Special helper function to help uncompress gzipped documents: