Skip to content

Commit

Permalink
decompressors: stop decompressing upon excessive compression ratio (e…
Browse files Browse the repository at this point in the history
…nvoyproxy#733)

commit 618d2c7f79d201c508555bcbdaca5538a6e94824
Author: Pradeep Rao <pcrao@google.com>
Date:   Fri May 20 18:09:49 2022 +0000

    Fix format.

    Signed-off-by: Pradeep Rao <pcrao@google.com>

commit e57c2fc29690522ee571c27c2309612fcfa60c5a
Author: Pradeep Rao <pcrao@google.com>
Date:   Thu May 19 18:56:28 2022 +0000

    Fix release note.

    Signed-off-by: Pradeep Rao <pcrao@google.com>

commit 7a89437589334d58cc0a80d3104714c921b9d9f0
Author: Pradeep Rao <pcrao@google.com>
Date:   Mon May 16 18:16:51 2022 +0000

    Fix format.

    Signed-off-by: Pradeep Rao <pcrao@google.com>

commit a0abc80be4f180f8a26bb3ec10cd4500a31dc5d1
Author: Pradeep Rao <pcrao@google.com>
Date:   Thu May 12 17:10:56 2022 +0000

    decompressors: stop decompressing upon excessive compression ratio (envoyproxy#733)

    Signed-off-by: Pradeep Rao <pcrao@google.com>

Signed-off-by: Pradeep Rao <pcrao@google.com>
Backported-by: Luke Shumaker <lukeshu@datawire.io>
  • Loading branch information
pradeepcrao authored and LukeShu committed Jun 8, 2022
1 parent d50105e commit e989486
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 3 deletions.
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ constexpr const char* runtime_features[] = {
"envoy.reloadable_features.disable_tls_inspector_injection",
"envoy.reloadable_features.disallow_unbounded_access_logs",
"envoy.reloadable_features.early_errors_via_hcm",
"envoy.reloadable_features.enable_compression_bomb_protection",
"envoy.reloadable_features.enable_dns_cache_circuit_breakers",
"envoy.reloadable_features.fix_upgrade_response",
"envoy.reloadable_features.fix_wildcard_matching",
Expand Down
1 change: 0 additions & 1 deletion source/extensions/compression/gzip/common/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace Zlib {
/**
* Shared code between the compressor and the decompressor.
*/
// TODO(junr03): move to extensions tree once the compressor side is moved to extensions.
class Base {
public:
Base(uint64_t chunk_size, std::function<void(z_stream*)> zstream_deleter);
Expand Down
1 change: 1 addition & 0 deletions source/extensions/compression/gzip/decompressor/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ envoy_cc_library(
"//source/common/buffer:buffer_lib",
"//source/common/common:assert_lib",
"//source/common/common:minimal_logger_lib",
"//source/common/runtime:runtime_features_lib",
"//source/extensions/compression/gzip/common:zlib_base_lib",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "envoy/common/exception.h"

#include "common/common/assert.h"
#include "common/runtime/runtime_features.h"

#include "absl/container/fixed_array.h"

Expand All @@ -16,6 +17,16 @@ namespace Compression {
namespace Gzip {
namespace Decompressor {

namespace {

// How many times the output buffer is allowed to be bigger than the size of
// accumulated input. This value is used to detect compression bombs.
// TODO(rojkov): Re-design the Decompressor interface to handle compression
// bombs gracefully instead of this quick solution.
constexpr uint64_t MaxInflateRatio = 100;

} // namespace

ZlibDecompressorImpl::ZlibDecompressorImpl(Stats::Scope& scope, const std::string& stats_prefix)
: ZlibDecompressorImpl(scope, stats_prefix, 4096) {}

Expand Down Expand Up @@ -43,13 +54,26 @@ void ZlibDecompressorImpl::init(int64_t window_bits) {

void ZlibDecompressorImpl::decompress(const Buffer::Instance& input_buffer,
Buffer::Instance& output_buffer) {
uint64_t limit = MaxInflateRatio * input_buffer.length();

for (const Buffer::RawSlice& input_slice : input_buffer.getRawSlices()) {
zstream_ptr_->avail_in = input_slice.len_;
zstream_ptr_->next_in = static_cast<Bytef*>(input_slice.mem_);
while (inflateNext()) {
if (zstream_ptr_->avail_out == 0) {
updateOutput(output_buffer);
}

if (Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.enable_compression_bomb_protection") &&
(output_buffer.length() > limit)) {
stats_.zlib_data_error_.inc();
ENVOY_LOG(trace,
"excessive decompression ratio detected: output "
"size {} for input size {}",
output_buffer.length(), input_buffer.length());
return;
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions test/extensions/compression/gzip/compressor_fuzz_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ DEFINE_FUZZER(const uint8_t* buf, size_t len) {
: Envoy::Compression::Compressor::State::Flush);
decompressor.decompress(buffer, full_output);
}
RELEASE_ASSERT(full_input.toString() == full_output.toString(), "");
RELEASE_ASSERT(compressor.checksum() == decompressor.checksum(), "");
if (stats_store.counterFromString("test.zlib_data_error").value() == 0) {
RELEASE_ASSERT(full_input.toString() == full_output.toString(), "");
RELEASE_ASSERT(compressor.checksum() == decompressor.checksum(), "");
}
}

} // namespace Fuzz
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,31 @@ TEST_F(ZlibDecompressorImplTest, CallingChecksum) {
ASSERT_EQ(0, decompressor.decompression_error_);
}

// Detect excessive compression ratio by compressing a long whitespace string
// into a very small chunk of data and decompressing it again.
TEST_F(ZlibDecompressorImplTest, DetectExcessiveCompressionRatio) {
const absl::string_view ten_whitespaces = " ";
Buffer::OwnedImpl buffer;
Extensions::Compression::Gzip::Compressor::ZlibCompressorImpl compressor;
compressor.init(
Extensions::Compression::Gzip::Compressor::ZlibCompressorImpl::CompressionLevel::Standard,
Extensions::Compression::Gzip::Compressor::ZlibCompressorImpl::CompressionStrategy::Standard,
gzip_window_bits, memory_level);

for (int i = 0; i < 1000; i++) {
buffer.add(ten_whitespaces);
}

compressor.compress(buffer, Envoy::Compression::Compressor::State::Finish);

Buffer::OwnedImpl output_buffer;
Stats::IsolatedStoreImpl stats_store{};
ZlibDecompressorImpl decompressor{stats_store, "test."};
decompressor.init(gzip_window_bits);
decompressor.decompress(buffer, output_buffer);
ASSERT_EQ(stats_store.counterFromString("test.zlib_data_error").value(), 1);
}

// Exercises compression and decompression by compressing some data, decompressing it and then
// comparing compressor's input/checksum with decompressor's output/checksum.
TEST_F(ZlibDecompressorImplTest, CompressAndDecompress) {
Expand Down

0 comments on commit e989486

Please sign in to comment.