1
#include "source/extensions/compression/gzip/decompressor/zlib_decompressor_impl.h"
2

            
3
#include <zlib.h>
4

            
5
#include <memory>
6

            
7
#include "envoy/common/exception.h"
8

            
9
#include "source/common/common/assert.h"
10
#include "source/common/runtime/runtime_features.h"
11

            
12
#include "absl/container/fixed_array.h"
13

            
14
namespace Envoy {
15
namespace Extensions {
16
namespace Compression {
17
namespace Gzip {
18
namespace Decompressor {
19

            
20
ZlibDecompressorImpl::ZlibDecompressorImpl(Stats::Scope& scope, const std::string& stats_prefix,
21
                                           uint64_t chunk_size, uint64_t max_inflate_ratio)
22
89
    : Common::Base(chunk_size,
23
89
                   [](z_stream* z) {
24
89
                     inflateEnd(z);
25
89
                     delete z;
26
89
                   }),
27
89
      stats_(generateStats(stats_prefix, scope)), max_inflate_ratio_(max_inflate_ratio) {
28
89
  zstream_ptr_->zalloc = Z_NULL;
29
89
  zstream_ptr_->zfree = Z_NULL;
30
89
  zstream_ptr_->opaque = Z_NULL;
31
89
  zstream_ptr_->avail_out = chunk_size_;
32
89
  zstream_ptr_->next_out = chunk_char_ptr_.get();
33
89
}
34

            
35
88
void ZlibDecompressorImpl::init(int64_t window_bits) {
36
88
  ASSERT(initialized_ == false);
37
  // The inflateInit2 macro from zlib.h contains an old-style cast, so we need to suppress the
38
  // warning for this call.
39
88
#pragma GCC diagnostic push
40
88
#pragma GCC diagnostic ignored "-Wold-style-cast"
41
88
  const int result = inflateInit2(zstream_ptr_.get(), window_bits);
42
88
#pragma GCC diagnostic pop
43
88
  RELEASE_ASSERT(result >= 0, "");
44
88
  initialized_ = true;
45
88
}
46

            
47
void ZlibDecompressorImpl::decompress(const Buffer::Instance& input_buffer,
48
62
                                      Buffer::Instance& output_buffer) {
49
62
  uint64_t limit = max_inflate_ratio_ * input_buffer.length();
50

            
51
1876
  for (const Buffer::RawSlice& input_slice : input_buffer.getRawSlices()) {
52
1876
    zstream_ptr_->avail_in = input_slice.len_;
53
1876
    zstream_ptr_->next_in = static_cast<Bytef*>(input_slice.mem_);
54
16201
    while (inflateNext()) {
55
14327
      if (zstream_ptr_->avail_out == 0) {
56
12529
        updateOutput(output_buffer);
57
12529
      }
58

            
59
14327
      if (Runtime::runtimeFeatureEnabled(
60
14327
              "envoy.reloadable_features.enable_compression_bomb_protection") &&
61
14327
          (output_buffer.length() > limit)) {
62
2
        stats_.zlib_data_error_.inc();
63
2
        ENVOY_LOG(trace,
64
2
                  "excessive decompression ratio detected: output "
65
2
                  "size {} for input size {}",
66
2
                  output_buffer.length(), input_buffer.length());
67
2
        return;
68
2
      }
69
14327
    }
70
1876
  }
71

            
72
  // Flush z_stream and reset its buffer. Otherwise the stale content of the buffer
73
  // will pollute output upon the next call to decompress().
74
60
  updateOutput(output_buffer);
75
60
}
76

            
77
16201
bool ZlibDecompressorImpl::inflateNext() {
78
16201
  const int result = inflate(zstream_ptr_.get(), Z_NO_FLUSH);
79
16201
  if (result == Z_STREAM_END) {
80
    // Z_FINISH informs inflate to not maintain a sliding window if the stream completes, which
81
    // reduces inflate's memory footprint. Ref: https://www.zlib.net/manual.html.
82
49
    inflate(zstream_ptr_.get(), Z_FINISH);
83
49
    return false;
84
49
  }
85

            
86
16152
  if (result == Z_BUF_ERROR && zstream_ptr_->avail_in == 0) {
87
1804
    return false; // This means that zlib needs more input, so stop here.
88
1804
  }
89

            
90
14348
  if (result < 0) {
91
21
    decompression_error_ = result;
92
21
    ENVOY_LOG(trace,
93
21
              "zlib decompression error: {}, msg: {}. Error codes are defined in "
94
21
              "https://www.zlib.net/manual.html",
95
21
              result, zstream_ptr_->msg ? zstream_ptr_->msg : "nullptr");
96
21
    chargeErrorStats(result);
97
21
    return false;
98
21
  }
99

            
100
14327
  return true;
101
14348
}
102

            
103
27
void ZlibDecompressorImpl::chargeErrorStats(const int result) {
104
27
  switch (result) {
105
1
  case Z_ERRNO:
106
1
    stats_.zlib_errno_.inc();
107
1
    break;
108
2
  case Z_STREAM_ERROR:
109
2
    stats_.zlib_stream_error_.inc();
110
2
    break;
111
21
  case Z_DATA_ERROR:
112
21
    stats_.zlib_data_error_.inc();
113
21
    break;
114
1
  case Z_MEM_ERROR:
115
1
    stats_.zlib_mem_error_.inc();
116
1
    break;
117
1
  case Z_BUF_ERROR:
118
1
    stats_.zlib_buf_error_.inc();
119
1
    break;
120
1
  case Z_VERSION_ERROR:
121
1
    stats_.zlib_version_error_.inc();
122
1
    break;
123
27
  }
124
27
}
125

            
126
} // namespace Decompressor
127
} // namespace Gzip
128
} // namespace Compression
129
} // namespace Extensions
130
} // namespace Envoy