Line data Source code
1 : #include "source/extensions/compression/brotli/decompressor/brotli_decompressor_impl.h" 2 : 3 : #include <memory> 4 : 5 : #include "source/common/runtime/runtime_features.h" 6 : 7 : namespace Envoy { 8 : namespace Extensions { 9 : namespace Compression { 10 : namespace Brotli { 11 : namespace Decompressor { 12 : 13 : namespace { 14 : 15 : // How many times the output buffer is allowed to be bigger than the input 16 : // buffer. This value is used to detect compression bombs. 17 : // TODO(rojkov): Re-design the Decompressor interface to handle compression 18 : // bombs gracefully instead of this quick solution. 19 : constexpr uint32_t MaxInflateRatio = 100; 20 : 21 : } // namespace 22 : 23 : BrotliDecompressorImpl::BrotliDecompressorImpl(Stats::Scope& scope, const std::string& stats_prefix, 24 : const uint32_t chunk_size, 25 : const bool disable_ring_buffer_reallocation) 26 : : chunk_size_{chunk_size}, 27 : state_(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr), &BrotliDecoderDestroyInstance), 28 0 : stats_(generateStats(stats_prefix, scope)) { 29 0 : BROTLI_BOOL result = 30 0 : BrotliDecoderSetParameter(state_.get(), BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION, 31 0 : disable_ring_buffer_reallocation ? BROTLI_TRUE : BROTLI_FALSE); 32 0 : RELEASE_ASSERT(result == BROTLI_TRUE, ""); 33 0 : } 34 : 35 : void BrotliDecompressorImpl::decompress(const Buffer::Instance& input_buffer, 36 0 : Buffer::Instance& output_buffer) { 37 0 : Common::BrotliContext ctx(chunk_size_, MaxInflateRatio * input_buffer.length()); 38 : 39 0 : for (const Buffer::RawSlice& input_slice : input_buffer.getRawSlices()) { 40 0 : ctx.avail_in_ = input_slice.len_; 41 0 : ctx.next_in_ = static_cast<uint8_t*>(input_slice.mem_); 42 : 43 0 : while (ctx.avail_in_ > 0) { 44 0 : if (!process(ctx, output_buffer)) { 45 0 : ctx.finalizeOutput(output_buffer); 46 0 : return; 47 0 : } 48 0 : } 49 0 : } 50 : 51 : // Even though the input has been fully consumed by the decoder it still can 52 : // be unfolded into output not fitting the output chunk. Thus keep processing 53 : // until the decoder's output is fully depleted. 54 0 : bool success; 55 0 : do { 56 0 : success = process(ctx, output_buffer); 57 0 : } while (success && BrotliDecoderHasMoreOutput(state_.get())); 58 : 59 0 : ctx.finalizeOutput(output_buffer); 60 0 : } 61 : 62 0 : bool BrotliDecompressorImpl::process(Common::BrotliContext& ctx, Buffer::Instance& output_buffer) { 63 0 : BrotliDecoderResult result; 64 0 : result = BrotliDecoderDecompressStream(state_.get(), &ctx.avail_in_, &ctx.next_in_, 65 0 : &ctx.avail_out_, &ctx.next_out_, nullptr); 66 0 : if (result == BROTLI_DECODER_RESULT_ERROR) { 67 : // TODO(rojkov): currently the Brotli library doesn't specify possible errors in its API. Add 68 : // more detailed stats when they are documented. 69 0 : stats_.brotli_error_.inc(); 70 0 : return false; 71 0 : } 72 : 73 0 : if (Runtime::runtimeFeatureEnabled( 74 0 : "envoy.reloadable_features.enable_compression_bomb_protection") && 75 0 : (output_buffer.length() > ctx.max_output_size_)) { 76 0 : stats_.brotli_error_.inc(); 77 0 : return false; 78 0 : } 79 : 80 0 : ctx.updateOutput(output_buffer); 81 : 82 0 : return true; 83 0 : } 84 : 85 : } // namespace Decompressor 86 : } // namespace Brotli 87 : } // namespace Compression 88 : } // namespace Extensions 89 : } // namespace Envoy