From 1b34386981b406985cfee5a233de8a5602e9f17f Mon Sep 17 00:00:00 2001 From: Vimanyu Date: Thu, 1 Apr 2021 11:57:31 -0700 Subject: [PATCH 1/2] porting internal changes replicating cl/361925036 --- app/rest/tests/zlibwrapper_unittest.cc | 29 ++++++++++++++++++++++++++ app/rest/zlibwrapper.cc | 3 ++- app/rest/zlibwrapper.h | 3 ++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/rest/tests/zlibwrapper_unittest.cc b/app/rest/tests/zlibwrapper_unittest.cc index 785f528a3..2dee5baf5 100644 --- a/app/rest/tests/zlibwrapper_unittest.cc +++ b/app/rest/tests/zlibwrapper_unittest.cc @@ -1047,5 +1047,34 @@ TEST(ZLibWrapperStandalone, ReadPastEndOfWindow) { LOG(INFO) << "passed read-past-end-of-window test"; } +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..ae7d44cd6 100644 --- a/app/rest/zlibwrapper.cc +++ b/app/rest/zlibwrapper.cc @@ -784,7 +784,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: From c1868a7f754f9f54cc9dafdfaed2212983918183 Mon Sep 17 00:00:00 2001 From: Vimanyu Date: Thu, 1 Apr 2021 12:02:52 -0700 Subject: [PATCH 2/2] porting internal changes Replicates cl/361884241 and copied an additional missing unit test BytewiseRead. --- app/rest/tests/zlibwrapper_unittest.cc | 102 +++++++++++++++++++++++++ app/rest/zlibwrapper.cc | 4 +- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/app/rest/tests/zlibwrapper_unittest.cc b/app/rest/tests/zlibwrapper_unittest.cc index 2dee5baf5..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,6 +1048,107 @@ 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; diff --git a/app/rest/zlibwrapper.cc b/app/rest/zlibwrapper.cc index ae7d44cd6..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