VYPR
Moderate severityNVD Advisory· Published Jun 9, 2023· Updated Feb 13, 2025

Denial-of-Service in gRPC

CVE-2023-32732

Description

gRPC contains a vulnerability whereby a client can cause a termination of connection between a HTTP2 proxy and a gRPC server: a base64 encoding error for -bin suffixed headers will result in a disconnection by the gRPC server, but is typically allowed by HTTP2 proxies. We recommend upgrading beyond the commit in  https://github.com/grpc/grpc/pull/32309 https://www.google.com/url

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CVE-2023-32732 allows a client to terminate the connection between an HTTP2 proxy and a gRPC server via a base64 encoding error for -bin suffixed headers.

Root

Cause The vulnerability lies in how gRPC handles base64 encoding of binary headers (those with -bin suffix). When the server receives a malformed base64 encoded header, it disconnects the stream, but HTTP2 proxies typically allow such malformed headers, leading to a mismatch and connection termination. [1]

Exploitation

An attacker can send a specially crafted request with a base64 encoding error in a -bin suffixed header. This request passes through the HTTP2 proxy but causes the gRPC server to terminate the connection. [2]

Impact

Successful exploitation results in a denial of service (DoS) condition, disrupting communication between the proxy and the gRPC server. [3]

Mitigation

The fix is included in commit [2] and [3]. Users are advised to upgrade their gRPC libraries to patched versions. The vulnerability is also tracked in the Ruby advisory database [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
io.grpc:grpc-protobufMaven
>= 1.53.0, < 1.53.11.53.1
grpcioPyPI
>= 1.53.0, < 1.53.11.53.1
grpcRubyGems
>= 1.53.0, < 1.53.11.53.1
io.grpc:grpc-protobufMaven
>= 1.54.0, < 1.54.21.54.2
grpcioPyPI
>= 1.54.0, < 1.54.21.54.2
grpcRubyGems
>= 1.54.0, < 1.54.21.54.2

Affected products

72

Patches

2
65a2a895afaf

[chttp2] Fix some fuzzer found bugs. (#33005)

https://github.com/grpc/grpcCraig TillerMay 4, 2023via ghsa
42 files changed · +1834 526
  • src/core/ext/transport/chttp2/transport/bin_encoder.cc+12 8 modified
    @@ -149,7 +149,8 @@ static void enc_flush_some(huff_out* out) {
       }
     }
     
    -static void enc_add2(huff_out* out, uint8_t a, uint8_t b) {
    +static void enc_add2(huff_out* out, uint8_t a, uint8_t b, uint32_t* wire_size) {
    +  *wire_size += 2;
       b64_huff_sym sa = huff_alphabet[a];
       b64_huff_sym sb = huff_alphabet[b];
       out->temp = (out->temp << (sa.length + sb.length)) |
    @@ -159,15 +160,16 @@ static void enc_add2(huff_out* out, uint8_t a, uint8_t b) {
       enc_flush_some(out);
     }
     
    -static void enc_add1(huff_out* out, uint8_t a) {
    +static void enc_add1(huff_out* out, uint8_t a, uint32_t* wire_size) {
    +  *wire_size += 1;
       b64_huff_sym sa = huff_alphabet[a];
       out->temp = (out->temp << sa.length) | sa.bits;
       out->temp_length += sa.length;
       enc_flush_some(out);
     }
     
     grpc_slice grpc_chttp2_base64_encode_and_huffman_compress(
    -    const grpc_slice& input) {
    +    const grpc_slice& input, uint32_t* wire_size) {
       size_t input_length = GRPC_SLICE_LENGTH(input);
       size_t input_triplets = input_length / 3;
       size_t tail_case = input_length % 3;
    @@ -183,16 +185,17 @@ grpc_slice grpc_chttp2_base64_encode_and_huffman_compress(
       out.temp = 0;
       out.temp_length = 0;
       out.out = start_out;
    +  *wire_size = 0;
     
       // encode full triplets
       for (i = 0; i < input_triplets; i++) {
         const uint8_t low_to_high = static_cast<uint8_t>((in[0] & 0x3) << 4);
         const uint8_t high_to_low = in[1] >> 4;
    -    enc_add2(&out, in[0] >> 2, low_to_high | high_to_low);
    +    enc_add2(&out, in[0] >> 2, low_to_high | high_to_low, wire_size);
     
         const uint8_t a = static_cast<uint8_t>((in[1] & 0xf) << 2);
         const uint8_t b = (in[2] >> 6);
    -    enc_add2(&out, a | b, in[2] & 0x3f);
    +    enc_add2(&out, a | b, in[2] & 0x3f, wire_size);
         in += 3;
       }
     
    @@ -201,14 +204,15 @@ grpc_slice grpc_chttp2_base64_encode_and_huffman_compress(
         case 0:
           break;
         case 1:
    -      enc_add2(&out, in[0] >> 2, static_cast<uint8_t>((in[0] & 0x3) << 4));
    +      enc_add2(&out, in[0] >> 2, static_cast<uint8_t>((in[0] & 0x3) << 4),
    +               wire_size);
           in += 1;
           break;
         case 2: {
           const uint8_t low_to_high = static_cast<uint8_t>((in[0] & 0x3) << 4);
           const uint8_t high_to_low = in[1] >> 4;
    -      enc_add2(&out, in[0] >> 2, low_to_high | high_to_low);
    -      enc_add1(&out, static_cast<uint8_t>((in[1] & 0xf) << 2));
    +      enc_add2(&out, in[0] >> 2, low_to_high | high_to_low, wire_size);
    +      enc_add1(&out, static_cast<uint8_t>((in[1] & 0xf) << 2), wire_size);
           in += 2;
           break;
         }
    
  • src/core/ext/transport/chttp2/transport/bin_encoder.h+5 1 modified
    @@ -21,6 +21,8 @@
     
     #include <grpc/support/port_platform.h>
     
    +#include <stdint.h>
    +
     #include <grpc/slice.h>
     
     // base64 encode a slice. Returns a new slice, does not take ownership of the
    @@ -36,7 +38,9 @@ grpc_slice grpc_chttp2_huffman_compress(const grpc_slice& input);
     // grpc_slice y = grpc_chttp2_huffman_compress(x);
     // grpc_core::CSliceUnref( x);
     // return y;
    +// *wire_size is the length of the base64 encoded string prior to huffman
    +// compression (as is needed for hpack table math)
     grpc_slice grpc_chttp2_base64_encode_and_huffman_compress(
    -    const grpc_slice& input);
    +    const grpc_slice& input, uint32_t* wire_size);
     
     #endif  // GRPC_SRC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_BIN_ENCODER_H
    
  • src/core/ext/transport/chttp2/transport/hpack_encoder.cc+47 26 modified
    @@ -131,21 +131,34 @@ struct WireValue {
           : data(std::move(slice)),
             huffman_prefix(huffman_prefix),
             insert_null_before_wire_value(insert_null_before_wire_value),
    -        length(data.length() + (insert_null_before_wire_value ? 1 : 0)) {}
    +        length(data.length() + (insert_null_before_wire_value ? 1 : 0)),
    +        hpack_length(length) {}
    +  WireValue(uint8_t huffman_prefix, bool insert_null_before_wire_value,
    +            Slice slice, size_t hpack_length)
    +      : data(std::move(slice)),
    +        huffman_prefix(huffman_prefix),
    +        insert_null_before_wire_value(insert_null_before_wire_value),
    +        length(data.length() + (insert_null_before_wire_value ? 1 : 0)),
    +        hpack_length(hpack_length + (insert_null_before_wire_value ? 1 : 0)) {}
       Slice data;
       const uint8_t huffman_prefix;
       const bool insert_null_before_wire_value;
       const size_t length;
    +  const size_t hpack_length;
     };
     
    +// Construct a wire value from a slice.
    +// true_binary_enabled => use the true binary system
    +// is_bin_hdr => the header is -bin suffixed
     WireValue GetWireValue(Slice value, bool true_binary_enabled, bool is_bin_hdr) {
       if (is_bin_hdr) {
         if (true_binary_enabled) {
           return WireValue(0x00, true, std::move(value));
         } else {
    -      return WireValue(0x80, false,
    -                       Slice(grpc_chttp2_base64_encode_and_huffman_compress(
    -                           value.c_slice())));
    +      uint32_t hpack_length;
    +      Slice output(grpc_chttp2_base64_encode_and_huffman_compress(
    +          value.c_slice(), &hpack_length));
    +      return WireValue(0x80, false, std::move(output), hpack_length);
         }
       } else {
         // TODO(ctiller): opportunistically compress non-binary headers
    @@ -185,6 +198,8 @@ class BinaryStringValue {
     
       Slice data() { return std::move(wire_value_.data); }
     
    +  uint32_t hpack_length() { return wire_value_.hpack_length; }
    +
      private:
       WireValue wire_value_;
       VarintWriter<1> len_val_;
    @@ -232,14 +247,21 @@ void Encoder::EmitIndexed(uint32_t elem_index) {
       w.Write(0x80, output_.AddTiny(w.length()));
     }
     
    -void Encoder::EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
    -                                                     Slice value_slice) {
    +uint32_t Encoder::EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
    +                                                         Slice value_slice) {
    +  auto key_len = key_slice.length();
    +  auto value_len = value_slice.length();
       StringKey key(std::move(key_slice));
       key.WritePrefix(0x40, output_.AddTiny(key.prefix_length()));
       output_.Append(key.key());
       NonBinaryStringValue emit(std::move(value_slice));
       emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
    +  // Allocate an index in the hpack table for this newly emitted entry.
    +  // (we do so here because we know the length of the key and value)
    +  uint32_t index = compressor_->table_.AllocateIndex(
    +      key_len + value_len + hpack_constants::kEntryOverhead);
       output_.Append(emit.data());
    +  return index;
     }
     
     void Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice,
    @@ -252,14 +274,20 @@ void Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice,
       output_.Append(emit.data());
     }
     
    -void Encoder::EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
    -                                                  Slice value_slice) {
    +uint32_t Encoder::EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
    +                                                      Slice value_slice) {
    +  auto key_len = key_slice.length();
       StringKey key(std::move(key_slice));
       key.WritePrefix(0x40, output_.AddTiny(key.prefix_length()));
       output_.Append(key.key());
       BinaryStringValue emit(std::move(value_slice), use_true_binary_metadata_);
       emit.WritePrefix(output_.AddTiny(emit.prefix_length()));
    +  // Allocate an index in the hpack table for this newly emitted entry.
    +  // (we do so here because we know the length of the key and value)
    +  uint32_t index = compressor_->table_.AllocateIndex(
    +      key_len + emit.hpack_length() + hpack_constants::kEntryOverhead);
       output_.Append(emit.data());
    +  return index;
     }
     
     void Encoder::EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
    @@ -308,8 +336,7 @@ void SliceIndex::EmitTo(absl::string_view key, const Slice& value,
             encoder->EmitIndexed(table.DynamicIndex(it->index));
           } else {
             // Not current, emit a new literal and update the index.
    -        it->index = table.AllocateIndex(transport_length);
    -        encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
    +        it->index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
                 Slice::FromStaticString(key), value.Ref());
           }
           // Bubble this entry up if we can - ensures that the most used values end
    @@ -327,9 +354,8 @@ void SliceIndex::EmitTo(absl::string_view key, const Slice& value,
         prev = it;
       }
       // No hit, emit a new literal and add it to the index.
    -  uint32_t index = table.AllocateIndex(transport_length);
    -  encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(key),
    -                                                  value.Ref());
    +  uint32_t index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
    +      Slice::FromStaticString(key), value.Ref());
       values_.emplace_back(value.Ref(), index);
     }
     
    @@ -386,7 +412,7 @@ void Compressor<HttpStatusMetadata, HttpStatusCompressor>::EncodeWith(
       if (GPR_LIKELY(index != 0)) {
         encoder->EmitIndexed(index);
       } else {
    -    encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
    +    encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(
             Slice::FromStaticString(":status"), Slice::FromInt64(status));
       }
     }
    @@ -414,13 +440,12 @@ void Compressor<HttpMethodMetadata, HttpMethodCompressor>::EncodeWith(
     }
     
     void Encoder::EncodeAlwaysIndexed(uint32_t* index, absl::string_view key,
    -                                  Slice value, size_t transport_length) {
    +                                  Slice value, size_t) {
       if (compressor_->table_.ConvertableToDynamicIndex(*index)) {
         EmitIndexed(compressor_->table_.DynamicIndex(*index));
       } else {
    -    *index = compressor_->table_.AllocateIndex(transport_length);
    -    EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(key),
    -                                           std::move(value));
    +    *index = EmitLitHdrWithNonBinaryStringKeyIncIdx(
    +        Slice::FromStaticString(key), std::move(value));
       }
     }
     
    @@ -431,10 +456,8 @@ void Encoder::EncodeIndexedKeyWithBinaryValue(uint32_t* index,
         EmitLitHdrWithBinaryStringKeyNotIdx(
             compressor_->table_.DynamicIndex(*index), std::move(value));
       } else {
    -    *index = compressor_->table_.AllocateIndex(key.length() + value.length() +
    -                                               hpack_constants::kEntryOverhead);
    -    EmitLitHdrWithBinaryStringKeyIncIdx(Slice::FromStaticString(key),
    -                                        std::move(value));
    +    *index = EmitLitHdrWithBinaryStringKeyIncIdx(Slice::FromStaticString(key),
    +                                                 std::move(value));
       }
     }
     
    @@ -474,11 +497,9 @@ void TimeoutCompressorImpl::EncodeWith(absl::string_view key,
         previous_timeouts_.pop_back();
       }
       Slice encoded = timeout.Encode();
    -  uint32_t index = table.AllocateIndex(key.length() + encoded.length() +
    -                                       hpack_constants::kEntryOverhead);
    +  uint32_t index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
    +      Slice::FromStaticString(key), std::move(encoded));
       previous_timeouts_.push_back(PreviousTimeout{timeout, index});
    -  encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice::FromStaticString(key),
    -                                                  std::move(encoded));
     }
     
     Encoder::Encoder(HPackCompressor* compressor, bool use_true_binary_metadata,
    
  • src/core/ext/transport/chttp2/transport/hpack_encoder.h+8 8 modified
    @@ -62,9 +62,12 @@ class Encoder {
     
       void AdvertiseTableSizeChange();
       void EmitIndexed(uint32_t index);
    -  void EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
    -                                              Slice value_slice);
    -  void EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice, Slice value_slice);
    +  GRPC_MUST_USE_RESULT
    +  uint32_t EmitLitHdrWithNonBinaryStringKeyIncIdx(Slice key_slice,
    +                                                  Slice value_slice);
    +  GRPC_MUST_USE_RESULT
    +  uint32_t EmitLitHdrWithBinaryStringKeyIncIdx(Slice key_slice,
    +                                               Slice value_slice);
       void EmitLitHdrWithBinaryStringKeyNotIdx(Slice key_slice, Slice value_slice);
       void EmitLitHdrWithBinaryStringKeyNotIdx(uint32_t key_index,
                                                Slice value_slice);
    @@ -234,12 +237,9 @@ class Compressor<MetadataTrait, SmallIntegralValuesCompressor<N>> {
         }
         auto key = Slice::FromStaticString(MetadataTrait::key());
         auto encoded_value = MetadataTrait::Encode(value);
    -    size_t transport_length =
    -        key.length() + encoded_value.length() + hpack_constants::kEntryOverhead;
         if (index != nullptr) {
    -      *index = table.AllocateIndex(transport_length);
    -      encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(std::move(key),
    -                                                      std::move(encoded_value));
    +      *index = encoder->EmitLitHdrWithNonBinaryStringKeyIncIdx(
    +          std::move(key), std::move(encoded_value));
         } else {
           encoder->EmitLitHdrWithNonBinaryStringKeyNotIdx(std::move(key),
                                                           std::move(encoded_value));
    
  • src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc+2 0 modified
    @@ -23,6 +23,8 @@
     namespace grpc_core {
     
     uint32_t HPackEncoderTable::AllocateIndex(size_t element_size) {
    +  GPR_DEBUG_ASSERT(element_size >= 32);
    +
       uint32_t new_index = tail_remote_index_ + table_elems_ + 1;
       GPR_DEBUG_ASSERT(element_size <= MaxEntrySize());
     
    
  • src/core/ext/transport/chttp2/transport/hpack_encoder_table.h+2 0 modified
    @@ -50,6 +50,8 @@ class HPackEncoderTable {
       uint32_t max_size() const { return max_table_size_; }
       // Get the current table size
       uint32_t test_only_table_size() const { return table_size_; }
    +  // Get the number of entries in the table
    +  uint32_t test_only_table_elems() const { return table_elems_; }
     
       // Convert an element index into a dynamic index
       uint32_t DynamicIndex(uint32_t index) const {
    
  • src/core/ext/transport/chttp2/transport/hpack_parser.cc+451 277 modified
    @@ -38,17 +38,18 @@
     #include "absl/types/span.h"
     #include "absl/types/variant.h"
     
    -#include <grpc/status.h>
     #include <grpc/support/log.h>
     
     #include "src/core/ext/transport/chttp2/transport/decode_huff.h"
     #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
     #include "src/core/lib/debug/stats.h"
     #include "src/core/lib/debug/stats_data.h"
     #include "src/core/lib/debug/trace.h"
    +#include "src/core/lib/gprpp/crash.h"
     #include "src/core/lib/gprpp/status_helper.h"
     #include "src/core/lib/slice/slice.h"
     #include "src/core/lib/slice/slice_refcount.h"
    +#include "src/core/lib/surface/validate_metadata.h"
     #include "src/core/lib/transport/parsed_metadata.h"
     
     // IWYU pragma: no_include <type_traits>
    @@ -80,6 +81,40 @@ struct Base64InverseTable {
     };
     
     constexpr Base64InverseTable kBase64InverseTable;
    +
    +absl::Status EnsureStreamError(absl::Status error) {
    +  if (error.ok()) return error;
    +  return grpc_error_set_int(std::move(error), StatusIntProperty::kStreamId, 0);
    +}
    +
    +bool IsStreamError(const absl::Status& status) {
    +  intptr_t stream_id;
    +  return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
    +}
    +
    +class MetadataSizeLimitExceededEncoder {
    + public:
    +  explicit MetadataSizeLimitExceededEncoder(std::string& summary)
    +      : summary_(summary) {}
    +
    +  void Encode(const Slice& key, const Slice& value) {
    +    AddToSummary(key.as_string_view(), value.size());
    +  }
    +
    +  template <typename Key, typename Value>
    +  void Encode(Key, const Value& value) {
    +    AddToSummary(Key::key(), EncodedSizeOfKey(Key(), value));
    +  }
    +
    + private:
    +  void AddToSummary(absl::string_view key,
    +                    size_t value_length) GPR_ATTRIBUTE_NOINLINE {
    +    absl::StrAppend(&summary_, " ", key, ":",
    +                    hpack_constants::SizeForEntry(key.size(), value_length),
    +                    "B");
    +  }
    +  std::string& summary_;
    +};
     }  // namespace
     
     // Input tracks the current byte through the input data and provides it
    @@ -121,7 +156,8 @@ class HPackParser::Input {
       // of stream
       absl::optional<uint8_t> Next() {
         if (end_of_stream()) {
    -      return UnexpectedEOF(absl::optional<uint8_t>());
    +      UnexpectedEOF();
    +      return absl::optional<uint8_t>();
         }
         return *begin_++;
       }
    @@ -187,22 +223,30 @@ class HPackParser::Input {
       // Parse a string prefix
       absl::optional<StringPrefix> ParseStringPrefix() {
         auto cur = Next();
    -    if (!cur.has_value()) return {};
    +    if (!cur.has_value()) {
    +      GPR_DEBUG_ASSERT(eof_error());
    +      return {};
    +    }
         // Huffman if the top bit is 1
         const bool huff = (*cur & 0x80) != 0;
         // String length
         uint32_t strlen = (*cur & 0x7f);
         if (strlen == 0x7f) {
           // all ones ==> varint string length
           auto v = ParseVarint(0x7f);
    -      if (!v.has_value()) return {};
    +      if (!v.has_value()) {
    +        GPR_DEBUG_ASSERT(eof_error());
    +        return {};
    +      }
           strlen = *v;
         }
         return StringPrefix{strlen, huff};
       }
     
       // Check if we saw an EOF.. must be verified before looking at TakeError
    -  bool eof_error() const { return eof_error_; }
    +  bool eof_error() const {
    +    return eof_error_ || (!error_.ok() && !IsStreamError(error_));
    +  }
     
       // Extract the parse error, leaving the current error as NONE.
       grpc_error_handle TakeError() {
    @@ -211,34 +255,33 @@ class HPackParser::Input {
         return out;
       }
     
    -  // Set the current error - allows the rest of the code not to need to pass
    -  // around StatusOr<> which would be prohibitive here.
    -  GPR_ATTRIBUTE_NOINLINE void SetError(grpc_error_handle error) {
    -    if (!error_.ok() || eof_error_) {
    -      return;
    -    }
    -    error_ = error;
    -    begin_ = end_;
    +  bool has_error() const { return !error_.ok(); }
    +
    +  // Set the current error - tweaks the error to include a stream id so that
    +  // chttp2 does not close the connection.
    +  // Intended for errors that are specific to a stream and recoverable.
    +  // Callers should ensure that any hpack table updates happen.
    +  GPR_ATTRIBUTE_NOINLINE void SetErrorAndContinueParsing(
    +      grpc_error_handle error) {
    +    GPR_ASSERT(!error.ok());
    +    // StreamId is used as a signal to skip this stream but keep the connection
    +    // alive
    +    SetError(EnsureStreamError(std::move(error)));
       }
     
    -  // If no error is set, set it to the value produced by error_factory.
    -  // Return return_value unchanged.
    -  template <typename F, typename T>
    -  GPR_ATTRIBUTE_NOINLINE T MaybeSetErrorAndReturn(F error_factory,
    -                                                  T return_value) {
    -    if (!error_.ok() || eof_error_) return return_value;
    -    error_ = error_factory();
    +  // Set the current error, and skip past remaining bytes.
    +  // Intended for unrecoverable errors, with the expectation that they will
    +  // close the connection on return to chttp2.
    +  GPR_ATTRIBUTE_NOINLINE void SetErrorAndStopParsing(grpc_error_handle error) {
    +    GPR_ASSERT(!error.ok());
    +    SetError(std::move(error));
         begin_ = end_;
    -    return return_value;
       }
     
    -  // Set the error to an unexpected eof, and return result (code golfed as this
    -  // is a common case)
    -  template <typename T>
    -  T UnexpectedEOF(T return_value) {
    -    if (!error_.ok()) return return_value;
    +  // Set the error to an unexpected eof
    +  void UnexpectedEOF() {
    +    if (!error_.ok() && !IsStreamError(error_)) return;
         eof_error_ = true;
    -    return return_value;
       }
     
       // Update the frontier - signifies we've successfully parsed another element
    @@ -251,14 +294,24 @@ class HPackParser::Input {
       // Helper to set the error to out of range for ParseVarint
       absl::optional<uint32_t> ParseVarintOutOfRange(uint32_t value,
                                                      uint8_t last_byte) {
    -    return MaybeSetErrorAndReturn(
    -        [value, last_byte] {
    -          return GRPC_ERROR_CREATE(absl::StrFormat(
    -              "integer overflow in hpack integer decoding: have 0x%08x, "
    -              "got byte 0x%02x on byte 5",
    -              value, last_byte));
    -        },
    -        absl::optional<uint32_t>());
    +    SetErrorAndStopParsing(absl::InternalError(absl::StrFormat(
    +        "integer overflow in hpack integer decoding: have 0x%08x, "
    +        "got byte 0x%02x on byte 5",
    +        value, last_byte)));
    +    return absl::optional<uint32_t>();
    +  }
    +
    +  // If no error is set, set it to the given error (i.e. first error wins)
    +  // Do not use this directly, instead use SetErrorAndContinueParsing or
    +  // SetErrorAndStopParsing.
    +  void SetError(grpc_error_handle error) {
    +    if (!error_.ok() || eof_error_) {
    +      if (!IsStreamError(error) && IsStreamError(error_)) {
    +        error_ = std::move(error);  // connection errors dominate
    +      }
    +      return;
    +    }
    +    error_ = std::move(error);
       }
     
       // Refcount if we are backed by a slice
    @@ -279,6 +332,21 @@ class HPackParser::Input {
     // management characteristics
     class HPackParser::String {
      public:
    +  // ParseResult carries both a ParseStatus and the parsed string
    +  struct ParseResult;
    +  // Result of parsing a string
    +  enum class ParseStatus {
    +    // Parsed OK
    +    kOk,
    +    // Parse reached end of the current frame
    +    kEof,
    +    // Parse failed due to a huffman decode error
    +    kParseHuffFailed,
    +    // Parse failed due to a base64 decode error
    +    kUnbase64Failed,
    +  };
    +
    +  String() : value_(absl::Span<const uint8_t>()) {}
       String(const String&) = delete;
       String& operator=(const String&) = delete;
       String(String&& other) noexcept : value_(std::move(other.value_)) {
    @@ -308,72 +376,10 @@ class HPackParser::String {
       }
     
       // Parse a non-binary string
    -  static absl::optional<String> Parse(Input* input) {
    -    auto pfx = input->ParseStringPrefix();
    -    if (!pfx.has_value()) return {};
    -    if (pfx->huff) {
    -      // Huffman coded
    -      std::vector<uint8_t> output;
    -      auto v = ParseHuff(input, pfx->length,
    -                         [&output](uint8_t c) { output.push_back(c); });
    -      if (!v) return {};
    -      return String(std::move(output));
    -    }
    -    return ParseUncompressed(input, pfx->length);
    -  }
    +  static ParseResult Parse(Input* input);
     
       // Parse a binary string
    -  static absl::optional<String> ParseBinary(Input* input) {
    -    auto pfx = input->ParseStringPrefix();
    -    if (!pfx.has_value()) return {};
    -    if (!pfx->huff) {
    -      if (pfx->length > 0 && input->peek() == 0) {
    -        // 'true-binary'
    -        input->Advance(1);
    -        return ParseUncompressed(input, pfx->length - 1);
    -      }
    -      // Base64 encoded... pull out the string, then unbase64 it
    -      auto base64 = ParseUncompressed(input, pfx->length);
    -      if (!base64.has_value()) return {};
    -      return Unbase64(input, std::move(*base64));
    -    } else {
    -      // Huffman encoded...
    -      std::vector<uint8_t> decompressed;
    -      // State here says either we don't know if it's base64 or binary, or we do
    -      // and what is it.
    -      enum class State { kUnsure, kBinary, kBase64 };
    -      State state = State::kUnsure;
    -      auto decompressed_ok =
    -          ParseHuff(input, pfx->length, [&state, &decompressed](uint8_t c) {
    -            if (state == State::kUnsure) {
    -              // First byte... if it's zero it's binary
    -              if (c == 0) {
    -                // Save the type, and skip the zero
    -                state = State::kBinary;
    -                return;
    -              } else {
    -                // Flag base64, store this value
    -                state = State::kBase64;
    -              }
    -            }
    -            // Non-first byte, or base64 first byte
    -            decompressed.push_back(c);
    -          });
    -      if (!decompressed_ok) return {};
    -      switch (state) {
    -        case State::kUnsure:
    -          // No bytes, empty span
    -          return String(absl::Span<const uint8_t>());
    -        case State::kBinary:
    -          // Binary, we're done
    -          return String(std::move(decompressed));
    -        case State::kBase64:
    -          // Base64 - unpack it
    -          return Unbase64(input, String(std::move(decompressed)));
    -      }
    -      GPR_UNREACHABLE_CODE(abort(););
    -    }
    -  }
    +  static ParseResult ParseBinary(Input* input);
     
      private:
       void AppendBytes(const uint8_t* data, size_t length);
    @@ -385,54 +391,27 @@ class HPackParser::String {
       // Parse some huffman encoded bytes, using output(uint8_t b) to emit each
       // decoded byte.
       template <typename Out>
    -  static bool ParseHuff(Input* input, uint32_t length, Out output) {
    +  static ParseStatus ParseHuff(Input* input, uint32_t length, Out output) {
         // If there's insufficient bytes remaining, return now.
         if (input->remaining() < length) {
    -      return input->UnexpectedEOF(false);
    +      input->UnexpectedEOF();
    +      GPR_DEBUG_ASSERT(input->eof_error());
    +      return ParseStatus::kEof;
         }
         // Grab the byte range, and iterate through it.
         const uint8_t* p = input->cur_ptr();
         input->Advance(length);
    -    return HuffDecoder<Out>(output, p, p + length).Run();
    +    return HuffDecoder<Out>(output, p, p + length).Run()
    +               ? ParseStatus::kOk
    +               : ParseStatus::kParseHuffFailed;
       }
     
       // Parse some uncompressed string bytes.
    -  static absl::optional<String> ParseUncompressed(Input* input,
    -                                                  uint32_t length) {
    -    // Check there's enough bytes
    -    if (input->remaining() < length) {
    -      return input->UnexpectedEOF(absl::optional<String>());
    -    }
    -    auto* refcount = input->slice_refcount();
    -    auto* p = input->cur_ptr();
    -    input->Advance(length);
    -    if (refcount != nullptr) {
    -      return String(refcount, p, p + length);
    -    } else {
    -      return String(absl::Span<const uint8_t>(p, length));
    -    }
    -  }
    +  static ParseResult ParseUncompressed(Input* input, uint32_t length,
    +                                       uint32_t wire_size);
     
       // Turn base64 encoded bytes into not base64 encoded bytes.
    -  // Only takes input to set an error on failure.
    -  static absl::optional<String> Unbase64(Input* input, String s) {
    -    absl::optional<std::vector<uint8_t>> result;
    -    if (auto* p = absl::get_if<Slice>(&s.value_)) {
    -      result = Unbase64Loop(p->begin(), p->end());
    -    }
    -    if (auto* p = absl::get_if<absl::Span<const uint8_t>>(&s.value_)) {
    -      result = Unbase64Loop(p->begin(), p->end());
    -    }
    -    if (auto* p = absl::get_if<std::vector<uint8_t>>(&s.value_)) {
    -      result = Unbase64Loop(p->data(), p->data() + p->size());
    -    }
    -    if (!result.has_value()) {
    -      return input->MaybeSetErrorAndReturn(
    -          [] { return GRPC_ERROR_CREATE("illegal base64 encoding"); },
    -          absl::optional<String>());
    -    }
    -    return String(std::move(*result));
    -  }
    +  static ParseResult Unbase64(String s);
     
       // Main loop for Unbase64
       static absl::optional<std::vector<uint8_t>> Unbase64Loop(const uint8_t* cur,
    @@ -519,25 +498,154 @@ class HPackParser::String {
       absl::variant<Slice, absl::Span<const uint8_t>, std::vector<uint8_t>> value_;
     };
     
    +struct HPackParser::String::ParseResult {
    +  ParseResult() = delete;
    +  ParseResult(ParseStatus status, size_t wire_size, String value)
    +      : status(status), wire_size(wire_size), value(std::move(value)) {}
    +  ParseStatus status;
    +  size_t wire_size;
    +  String value;
    +};
    +
    +HPackParser::String::ParseResult HPackParser::String::ParseUncompressed(
    +    Input* input, uint32_t length, uint32_t wire_size) {
    +  // Check there's enough bytes
    +  if (input->remaining() < length) {
    +    input->UnexpectedEOF();
    +    GPR_DEBUG_ASSERT(input->eof_error());
    +    return ParseResult{ParseStatus::kEof, wire_size, String{}};
    +  }
    +  auto* refcount = input->slice_refcount();
    +  auto* p = input->cur_ptr();
    +  input->Advance(length);
    +  if (refcount != nullptr) {
    +    return ParseResult{ParseStatus::kOk, wire_size,
    +                       String(refcount, p, p + length)};
    +  } else {
    +    return ParseResult{ParseStatus::kOk, wire_size,
    +                       String(absl::Span<const uint8_t>(p, length))};
    +  }
    +}
    +
    +HPackParser::String::ParseResult HPackParser::String::Unbase64(String s) {
    +  absl::optional<std::vector<uint8_t>> result;
    +  if (auto* p = absl::get_if<Slice>(&s.value_)) {
    +    result = Unbase64Loop(p->begin(), p->end());
    +  }
    +  if (auto* p = absl::get_if<absl::Span<const uint8_t>>(&s.value_)) {
    +    result = Unbase64Loop(p->begin(), p->end());
    +  }
    +  if (auto* p = absl::get_if<std::vector<uint8_t>>(&s.value_)) {
    +    result = Unbase64Loop(p->data(), p->data() + p->size());
    +  }
    +  if (!result.has_value()) {
    +    return ParseResult{ParseStatus::kUnbase64Failed, s.string_view().length(),
    +                       String{}};
    +  }
    +  return ParseResult{ParseStatus::kOk, s.string_view().length(),
    +                     String(std::move(*result))};
    +}
    +
    +HPackParser::String::ParseResult HPackParser::String::Parse(Input* input) {
    +  auto pfx = input->ParseStringPrefix();
    +  if (!pfx.has_value()) {
    +    GPR_DEBUG_ASSERT(input->eof_error());
    +    return ParseResult{ParseStatus::kEof, 0, String{}};
    +  }
    +  if (pfx->huff) {
    +    // Huffman coded
    +    std::vector<uint8_t> output;
    +    ParseStatus sts = ParseHuff(input, pfx->length,
    +                                [&output](uint8_t c) { output.push_back(c); });
    +    size_t wire_len = output.size();
    +    return ParseResult{sts, wire_len, String(std::move(output))};
    +  }
    +  return ParseUncompressed(input, pfx->length, pfx->length);
    +}
    +
    +HPackParser::String::ParseResult HPackParser::String::ParseBinary(
    +    Input* input) {
    +  auto pfx = input->ParseStringPrefix();
    +  if (!pfx.has_value()) {
    +    GPR_DEBUG_ASSERT(input->eof_error());
    +    return ParseResult{ParseStatus::kEof, 0, String{}};
    +  }
    +  if (!pfx->huff) {
    +    if (pfx->length > 0 && input->peek() == 0) {
    +      // 'true-binary'
    +      input->Advance(1);
    +      return ParseUncompressed(input, pfx->length - 1, pfx->length);
    +    }
    +    // Base64 encoded... pull out the string, then unbase64 it
    +    auto base64 = ParseUncompressed(input, pfx->length, pfx->length);
    +    if (base64.status != ParseStatus::kOk) return base64;
    +    return Unbase64(std::move(base64.value));
    +  } else {
    +    // Huffman encoded...
    +    std::vector<uint8_t> decompressed;
    +    // State here says either we don't know if it's base64 or binary, or we do
    +    // and what is it.
    +    enum class State { kUnsure, kBinary, kBase64 };
    +    State state = State::kUnsure;
    +    auto sts =
    +        ParseHuff(input, pfx->length, [&state, &decompressed](uint8_t c) {
    +          if (state == State::kUnsure) {
    +            // First byte... if it's zero it's binary
    +            if (c == 0) {
    +              // Save the type, and skip the zero
    +              state = State::kBinary;
    +              return;
    +            } else {
    +              // Flag base64, store this value
    +              state = State::kBase64;
    +            }
    +          }
    +          // Non-first byte, or base64 first byte
    +          decompressed.push_back(c);
    +        });
    +    if (sts != ParseStatus::kOk) {
    +      return ParseResult{sts, 0, String{}};
    +    }
    +    switch (state) {
    +      case State::kUnsure:
    +        // No bytes, empty span
    +        return ParseResult{ParseStatus::kOk, 0,
    +                           String(absl::Span<const uint8_t>())};
    +      case State::kBinary:
    +        // Binary, we're done
    +        {
    +          size_t wire_len = decompressed.size();
    +          return ParseResult{ParseStatus::kOk, wire_len,
    +                             String(std::move(decompressed))};
    +        }
    +      case State::kBase64:
    +        // Base64 - unpack it
    +        return Unbase64(String(std::move(decompressed)));
    +    }
    +    GPR_UNREACHABLE_CODE(abort(););
    +  }
    +}
    +
     // Parser parses one key/value pair from a byte stream.
     class HPackParser::Parser {
      public:
       Parser(Input* input, grpc_metadata_batch* metadata_buffer, HPackTable* table,
              uint8_t* dynamic_table_updates_allowed, uint32_t* frame_length,
    -         RandomEarlyDetection* metadata_early_detection, bool is_last,
    -         LogInfo log_info)
    +         RandomEarlyDetection* metadata_early_detection, LogInfo log_info)
           : input_(input),
             metadata_buffer_(metadata_buffer),
             table_(table),
             dynamic_table_updates_allowed_(dynamic_table_updates_allowed),
             frame_length_(frame_length),
             metadata_early_detection_(metadata_early_detection),
    -        is_last_(is_last),
             log_info_(log_info) {}
     
       // Skip any priority bits, or return false on failure
       bool SkipPriority() {
    -    if (input_->remaining() < 5) return input_->UnexpectedEOF(false);
    +    if (input_->remaining() < 5) {
    +      input_->UnexpectedEOF();
    +      return false;
    +    }
         input_->Advance(5);
         return true;
       }
    @@ -610,8 +718,9 @@ class HPackParser::Parser {
           case 8:
             if (cur == 0x80) {
               // illegal value.
    -          return input_->MaybeSetErrorAndReturn(
    -              [] { return GRPC_ERROR_CREATE("Illegal hpack op code"); }, false);
    +          input_->SetErrorAndStopParsing(
    +              absl::InternalError("Illegal hpack op code"));
    +          return false;
             }
             ABSL_FALLTHROUGH_INTENDED;
           case 9:
    @@ -648,24 +757,31 @@ class HPackParser::Parser {
             type = "???";
             break;
         }
    -    gpr_log(GPR_DEBUG, "HTTP:%d:%s:%s: %s", log_info_.stream_id, type,
    -            log_info_.is_client ? "CLI" : "SVR", memento.DebugString().c_str());
    +    gpr_log(GPR_DEBUG, "HTTP:%d:%s:%s: %s%s", log_info_.stream_id, type,
    +            log_info_.is_client ? "CLI" : "SVR",
    +            memento.md.DebugString().c_str(),
    +            memento.parse_status.ok()
    +                ? ""
    +                : absl::StrCat(
    +                      " (parse error: ", memento.parse_status.ToString(), ")")
    +                      .c_str());
       }
     
    -  bool EmitHeader(const HPackTable::Memento& md) {
    +  void EmitHeader(const HPackTable::Memento& md) {
         // Pass up to the transport
    -    if (GPR_UNLIKELY(metadata_buffer_ == nullptr)) return true;
    -    *frame_length_ += md.transport_size();
    -    if (metadata_early_detection_->MustReject(*frame_length_)) {
    +    *frame_length_ += md.md.transport_size();
    +    if (!input_->has_error() &&
    +        metadata_early_detection_->MustReject(*frame_length_)) {
           // Reject any requests above hard metadata limit.
    -      return HandleMetadataSizeLimitExceeded(md, /*exceeded_hard_limit=*/true);
    -    } else if (is_last_ && metadata_early_detection_->Reject(*frame_length_)) {
    -      // Reject some random sample of requests above soft metadata limit.
    -      return HandleMetadataSizeLimitExceeded(md, /*exceeded_hard_limit=*/false);
    +      HandleMetadataHardSizeLimitExceeded(md);
    +    }
    +    if (!md.parse_status.ok()) {
    +      // Reject any requests with invalid metadata.
    +      HandleMetadataParseError(md.parse_status);
    +    }
    +    if (GPR_LIKELY(metadata_buffer_ != nullptr)) {
    +      metadata_buffer_->Set(md.md);
         }
    -
    -    metadata_buffer_->Set(md);
    -    return true;
       }
     
       bool FinishHeaderAndAddToTable(absl::optional<HPackTable::Memento> md) {
    @@ -676,73 +792,149 @@ class HPackParser::Parser {
           LogHeader(*md);
         }
         // Emit whilst we own the metadata.
    -    auto r = EmitHeader(*md);
    +    EmitHeader(*md);
         // Add to the hpack table
         grpc_error_handle err = table_->Add(std::move(*md));
         if (GPR_UNLIKELY(!err.ok())) {
    -      input_->SetError(err);
    +      input_->SetErrorAndStopParsing(std::move(err));
           return false;
         };
    -    return r;
    +    return true;
       }
     
       bool FinishHeaderOmitFromTable(absl::optional<HPackTable::Memento> md) {
         // Allow higher code to just pass in failures ... simplifies things a bit.
         if (!md.has_value()) return false;
    -    return FinishHeaderOmitFromTable(*md);
    +    FinishHeaderOmitFromTable(*md);
    +    return true;
       }
     
    -  bool FinishHeaderOmitFromTable(const HPackTable::Memento& md) {
    +  void FinishHeaderOmitFromTable(const HPackTable::Memento& md) {
         // Log if desired
         if (GRPC_TRACE_FLAG_ENABLED(grpc_trace_chttp2_hpack_parser)) {
           LogHeader(md);
         }
    -    return EmitHeader(md);
    +    EmitHeader(md);
       }
     
    +  // Helper type to build a memento from a key & value, and to consolidate some
    +  // tricky error path code.
    +  class MementoBuilder {
    +   public:
    +    explicit MementoBuilder(Input* input, absl::string_view key_string,
    +                            absl::Status status = absl::OkStatus())
    +        : input_(input), key_string_(key_string), status_(std::move(status)) {}
    +
    +    auto ErrorHandler() {
    +      return [this](absl::string_view error, const Slice&) {
    +        auto message =
    +            absl::StrCat("Error parsing '", key_string_,
    +                         "' metadata: error=", error, " key=", key_string_);
    +        gpr_log(GPR_ERROR, "%s", message.c_str());
    +        if (status_.ok()) {
    +          status_ = absl::InternalError(message);
    +        }
    +      };
    +    }
    +
    +    HPackTable::Memento Build(ParsedMetadata<grpc_metadata_batch> memento) {
    +      return HPackTable::Memento{std::move(memento), std::move(status_)};
    +    }
    +
    +    // Handle the result of parsing a value.
    +    // Returns true if parsing should continue, false if it should stop.
    +    // Stores an error on the input if necessary.
    +    bool HandleParseResult(String::ParseStatus status) {
    +      auto continuable = [this](absl::string_view error) {
    +        auto this_error = absl::InternalError(absl::StrCat(
    +            "Error parsing '", key_string_, "' metadata: error=", error));
    +        if (status_.ok()) status_ = this_error;
    +        input_->SetErrorAndContinueParsing(std::move(this_error));
    +      };
    +      switch (status) {
    +        case String::ParseStatus::kOk:
    +          return true;
    +        case String::ParseStatus::kParseHuffFailed:
    +          input_->SetErrorAndStopParsing(
    +              absl::InternalError("Huffman decoding failed"));
    +          return false;
    +        case String::ParseStatus::kUnbase64Failed:
    +          continuable("illegal base64 encoding");
    +          return true;
    +        case String::ParseStatus::kEof:
    +          GPR_DEBUG_ASSERT(input_->eof_error());
    +          return false;
    +      }
    +      GPR_UNREACHABLE_CODE(return false);
    +    }
    +
    +   private:
    +    Input* input_;
    +    absl::string_view key_string_;
    +    absl::Status status_;
    +  };
    +
       // Parse a string encoded key and a string encoded value
       absl::optional<HPackTable::Memento> ParseLiteralKey() {
         auto key = String::Parse(input_);
    -    if (!key.has_value()) return {};
    -    auto value = ParseValueString(absl::EndsWith(key->string_view(), "-bin"));
    -    if (GPR_UNLIKELY(!value.has_value())) {
    -      return {};
    +    switch (key.status) {
    +      case String::ParseStatus::kOk:
    +        break;
    +      case String::ParseStatus::kParseHuffFailed:
    +        input_->SetErrorAndStopParsing(
    +            absl::InternalError("Huffman decoding failed"));
    +        return absl::nullopt;
    +      case String::ParseStatus::kUnbase64Failed:
    +        Crash("unreachable");
    +      case String::ParseStatus::kEof:
    +        GPR_DEBUG_ASSERT(input_->eof_error());
    +        return absl::nullopt;
         }
    -    auto key_string = key->string_view();
    -    auto value_slice = value->Take();
    -    const auto transport_size = key_string.size() + value_slice.size() +
    -                                hpack_constants::kEntryOverhead;
    -    return grpc_metadata_batch::Parse(
    -        key->string_view(), std::move(value_slice), transport_size,
    -        [key_string](absl::string_view error, const Slice& value) {
    -          ReportMetadataParseError(key_string, error, value.as_string_view());
    -        });
    +    auto key_string = key.value.string_view();
    +    auto value = ParseValueString(absl::EndsWith(key_string, "-bin"));
    +    MementoBuilder builder(input_, key_string,
    +                           EnsureStreamError(ValidateKey(key_string)));
    +    if (!builder.HandleParseResult(value.status)) return absl::nullopt;
    +    auto value_slice = value.value.Take();
    +    const auto transport_size =
    +        key_string.size() + value.wire_size + hpack_constants::kEntryOverhead;
    +    return builder.Build(
    +        grpc_metadata_batch::Parse(key_string, std::move(value_slice),
    +                                   transport_size, builder.ErrorHandler()));
    +  }
    +
    +  absl::Status ValidateKey(absl::string_view key) {
    +    if (key == HttpSchemeMetadata::key() || key == HttpMethodMetadata::key() ||
    +        key == HttpAuthorityMetadata::key() || key == HttpPathMetadata::key() ||
    +        key == HttpStatusMetadata::key()) {
    +      return absl::OkStatus();
    +    }
    +    return ValidateHeaderKeyIsLegal(key);
       }
     
       // Parse an index encoded key and a string encoded value
       absl::optional<HPackTable::Memento> ParseIdxKey(uint32_t index) {
         const auto* elem = table_->Lookup(index);
         if (GPR_UNLIKELY(elem == nullptr)) {
    -      return InvalidHPackIndexError(index,
    -                                    absl::optional<HPackTable::Memento>());
    -    }
    -    auto value = ParseValueString(elem->is_binary_header());
    -    if (GPR_UNLIKELY(!value.has_value())) return {};
    -    return elem->WithNewValue(
    -        value->Take(), [=](absl::string_view error, const Slice& value) {
    -          ReportMetadataParseError(elem->key(), error, value.as_string_view());
    -        });
    -  }
    +      InvalidHPackIndexError(index);
    +      return absl::optional<HPackTable::Memento>();
    +    }
    +    MementoBuilder builder(input_, elem->md.key(), elem->parse_status);
    +    auto value = ParseValueString(elem->md.is_binary_header());
    +    if (!builder.HandleParseResult(value.status)) return absl::nullopt;
    +    return builder.Build(elem->md.WithNewValue(
    +        value.value.Take(), value.wire_size, builder.ErrorHandler()));
    +  };
     
       // Parse a varint index encoded key and a string encoded value
       absl::optional<HPackTable::Memento> ParseVarIdxKey(uint32_t offset) {
         auto index = input_->ParseVarint(offset);
    -    if (GPR_UNLIKELY(!index.has_value())) return {};
    +    if (GPR_UNLIKELY(!index.has_value())) return absl::nullopt;
         return ParseIdxKey(*index);
       }
     
       // Parse a string, figuring out if it's binary or not by the key name.
    -  absl::optional<String> ParseValueString(bool is_binary) {
    +  String::ParseResult ParseValueString(bool is_binary) {
         if (is_binary) {
           return String::ParseBinary(input_);
         } else {
    @@ -756,74 +948,54 @@ class HPackParser::Parser {
         if (!index.has_value()) return false;
         const auto* elem = table_->Lookup(*index);
         if (GPR_UNLIKELY(elem == nullptr)) {
    -      return InvalidHPackIndexError(*index, false);
    +      InvalidHPackIndexError(*index);
    +      return false;
         }
    -    return FinishHeaderOmitFromTable(*elem);
    +    FinishHeaderOmitFromTable(*elem);
    +    return true;
       }
     
       // finish parsing a max table size change
       bool FinishMaxTableSize(absl::optional<uint32_t> size) {
         if (!size.has_value()) return false;
         if (*dynamic_table_updates_allowed_ == 0) {
    -      return input_->MaybeSetErrorAndReturn(
    -          [] {
    -            return GRPC_ERROR_CREATE(
    -                "More than two max table size changes in a single frame");
    -          },
    -          false);
    +      input_->SetErrorAndStopParsing(absl::InternalError(
    +          "More than two max table size changes in a single frame"));
    +      return false;
         }
         (*dynamic_table_updates_allowed_)--;
         grpc_error_handle err = table_->SetCurrentTableSize(*size);
         if (!err.ok()) {
    -      input_->SetError(err);
    +      input_->SetErrorAndStopParsing(std::move(err));
           return false;
         }
         return true;
       }
     
       // Set an invalid hpack index error if no error has been set. Returns result
       // unmodified.
    -  template <typename R>
    -  R InvalidHPackIndexError(uint32_t index, R result) {
    -    return input_->MaybeSetErrorAndReturn(
    -        [this, index] {
    -          return grpc_error_set_int(
    -              grpc_error_set_int(
    -                  GRPC_ERROR_CREATE("Invalid HPACK index received"),
    -                  StatusIntProperty::kIndex, static_cast<intptr_t>(index)),
    -              StatusIntProperty::kSize,
    -              static_cast<intptr_t>(this->table_->num_entries()));
    -        },
    -        std::move(result));
    -  }
    -
    -  class MetadataSizeLimitExceededEncoder {
    -   public:
    -    explicit MetadataSizeLimitExceededEncoder(std::string& summary)
    -        : summary_(summary) {}
    -
    -    void Encode(const Slice& key, const Slice& value) {
    -      AddToSummary(key.as_string_view(), value.size());
    -    }
    -
    -    template <typename Key, typename Value>
    -    void Encode(Key, const Value& value) {
    -      AddToSummary(Key::key(), EncodedSizeOfKey(Key(), value));
    -    }
    +  void InvalidHPackIndexError(uint32_t index) {
    +    input_->SetErrorAndStopParsing(grpc_error_set_int(
    +        grpc_error_set_int(absl::InternalError("Invalid HPACK index received"),
    +                           StatusIntProperty::kIndex,
    +                           static_cast<intptr_t>(index)),
    +        StatusIntProperty::kSize,
    +        static_cast<intptr_t>(this->table_->num_entries())));
    +  }
     
    -   private:
    -    void AddToSummary(absl::string_view key,
    -                      size_t value_length) GPR_ATTRIBUTE_NOINLINE {
    -      absl::StrAppend(&summary_, " ", key, ":",
    -                      hpack_constants::SizeForEntry(key.size(), value_length),
    -                      "B");
    +  GPR_ATTRIBUTE_NOINLINE
    +  void HandleMetadataParseError(const absl::Status& status) {
    +    if (metadata_buffer_ != nullptr) {
    +      metadata_buffer_->Clear();
    +      metadata_buffer_ = nullptr;
         }
    -    std::string& summary_;
    -  };
    +    // StreamId is used as a signal to skip this stream but keep the connection
    +    // alive
    +    input_->SetErrorAndContinueParsing(status);
    +  }
     
       GPR_ATTRIBUTE_NOINLINE
    -  bool HandleMetadataSizeLimitExceeded(const HPackTable::Memento& md,
    -                                       bool exceeded_hard_limit) {
    +  void HandleMetadataHardSizeLimitExceeded(const HPackTable::Memento& md) {
         // Collect a summary of sizes so far for debugging
         // Do not collect contents, for fear of exposing PII.
         std::string summary;
    @@ -832,49 +1004,22 @@ class HPackParser::Parser {
           MetadataSizeLimitExceededEncoder encoder(summary);
           metadata_buffer_->Encode(&encoder);
         }
    -    summary =
    -        absl::StrCat("; adding ", md.key(), " (length ", md.transport_size(),
    -                     "B)", summary.empty() ? "" : " to ", summary);
    -    if (exceeded_hard_limit) {
    -      error_message = absl::StrCat(
    -          "received metadata size exceeds hard limit (", *frame_length_,
    -          " vs. ", metadata_early_detection_->hard_limit(), ")", summary);
    -    } else {
    -      error_message = absl::StrCat(
    -          "received metadata size exceeds soft limit (", *frame_length_,
    -          " vs. ", metadata_early_detection_->soft_limit(),
    -          "), rejecting requests with some random probability", summary);
    -    }
    -    if (metadata_buffer_ != nullptr) metadata_buffer_->Clear();
    -    // StreamId is used as a signal to skip this stream but keep the connection
    -    // alive
    -    return input_->MaybeSetErrorAndReturn(
    -        [error_message = std::move(error_message)] {
    -          return grpc_error_set_int(
    -              grpc_error_set_int(GRPC_ERROR_CREATE(error_message),
    -                                 StatusIntProperty::kRpcStatus,
    -                                 GRPC_STATUS_RESOURCE_EXHAUSTED),
    -              StatusIntProperty::kStreamId, 0);
    -        },
    -        false);
    -  }
    -
    -  static void ReportMetadataParseError(absl::string_view key,
    -                                       absl::string_view error,
    -                                       absl::string_view value) {
    -    gpr_log(
    -        GPR_ERROR, "Error parsing metadata: %s",
    -        absl::StrCat("error=", error, " key=", key, " value=", value).c_str());
    +    summary = absl::StrCat("; adding ", md.md.key(), " (length ",
    +                           md.md.transport_size(), "B)",
    +                           summary.empty() ? "" : " to ", summary);
    +    error_message = absl::StrCat(
    +        "received metadata size exceeds hard limit (", *frame_length_, " vs. ",
    +        metadata_early_detection_->hard_limit(), ")", summary);
    +    HandleMetadataParseError(absl::ResourceExhaustedError(error_message));
       }
     
       Input* const input_;
    -  grpc_metadata_batch* const metadata_buffer_;
    +  grpc_metadata_batch* metadata_buffer_;
       HPackTable* const table_;
       uint8_t* const dynamic_table_updates_allowed_;
       uint32_t* const frame_length_;
       // Random early detection of metadata size limits.
       RandomEarlyDetection* metadata_early_detection_;
    -  bool is_last_;  // Whether this is the last frame.
       const LogInfo log_info_;
     };
     
    @@ -928,26 +1073,35 @@ grpc_error_handle HPackParser::Parse(const grpc_slice& slice, bool is_last) {
     }
     
     grpc_error_handle HPackParser::ParseInput(Input input, bool is_last) {
    -  bool parsed_ok = ParseInputInner(&input, is_last);
    -  if (is_last) global_stats().IncrementHttp2MetadataSize(frame_length_);
    -  if (parsed_ok) return absl::OkStatus();
    +  ParseInputInner(&input);
    +  if (is_last) {
    +    if (metadata_early_detection_.Reject(frame_length_)) {
    +      HandleMetadataSoftSizeLimitExceeded(&input);
    +    }
    +    global_stats().IncrementHttp2MetadataSize(frame_length_);
    +  }
       if (input.eof_error()) {
         if (GPR_UNLIKELY(is_last && is_boundary())) {
    -      return GRPC_ERROR_CREATE(
    +      auto err = input.TakeError();
    +      if (!err.ok() && !IsStreamError(err)) return err;
    +      return absl::InternalError(
               "Incomplete header at the end of a header/continuation sequence");
         }
         unparsed_bytes_ = std::vector<uint8_t>(input.frontier(), input.end_ptr());
    -    return absl::OkStatus();
    +    return input.TakeError();
       }
       return input.TakeError();
     }
     
    -bool HPackParser::ParseInputInner(Input* input, bool is_last) {
    +void HPackParser::ParseInputInner(Input* input) {
       switch (priority_) {
         case Priority::None:
           break;
         case Priority::Included: {
    -      if (input->remaining() < 5) return input->UnexpectedEOF(false);
    +      if (input->remaining() < 5) {
    +        input->UnexpectedEOF();
    +        return;
    +      }
           input->Advance(5);
           input->UpdateFrontier();
           priority_ = Priority::None;
    @@ -956,15 +1110,35 @@ bool HPackParser::ParseInputInner(Input* input, bool is_last) {
       while (!input->end_of_stream()) {
         if (GPR_UNLIKELY(!Parser(input, metadata_buffer_, &table_,
                                  &dynamic_table_updates_allowed_, &frame_length_,
    -                             &metadata_early_detection_, is_last, log_info_)
    +                             &metadata_early_detection_, log_info_)
                               .Parse())) {
    -      return false;
    +      return;
         }
         input->UpdateFrontier();
       }
    -  return true;
     }
     
     void HPackParser::FinishFrame() { metadata_buffer_ = nullptr; }
     
    +void HPackParser::HandleMetadataSoftSizeLimitExceeded(Input* input) {
    +  // Collect a summary of sizes so far for debugging
    +  // Do not collect contents, for fear of exposing PII.
    +  std::string summary;
    +  std::string error_message;
    +  if (metadata_buffer_ != nullptr) {
    +    MetadataSizeLimitExceededEncoder encoder(summary);
    +    metadata_buffer_->Encode(&encoder);
    +  }
    +  error_message = absl::StrCat(
    +      "received metadata size exceeds soft limit (", frame_length_, " vs. ",
    +      metadata_early_detection_.soft_limit(),
    +      "), rejecting requests with some random probability", summary);
    +  if (metadata_buffer_ != nullptr) {
    +    metadata_buffer_->Clear();
    +    metadata_buffer_ = nullptr;
    +  }
    +  input->SetErrorAndContinueParsing(
    +      absl::ResourceExhaustedError(error_message));
    +}
    +
     }  // namespace grpc_core
    
  • src/core/ext/transport/chttp2/transport/hpack_parser.h+3 1 modified
    @@ -105,7 +105,9 @@ class HPackParser {
       class String;
     
       grpc_error_handle ParseInput(Input input, bool is_last);
    -  bool ParseInputInner(Input* input, bool is_last);
    +  void ParseInputInner(Input* input);
    +  GPR_ATTRIBUTE_NOINLINE
    +  void HandleMetadataSoftSizeLimitExceeded(Input* input);
     
       // Target metadata buffer
       grpc_metadata_batch* metadata_buffer_ = nullptr;
    
  • src/core/ext/transport/chttp2/transport/hpack_parser_table.cc+14 12 modified
    @@ -83,8 +83,8 @@ void HPackTable::MementoRingBuffer::Rebuild(uint32_t max_entries) {
     // Evict one element from the table
     void HPackTable::EvictOne() {
       auto first_entry = entries_.PopOne();
    -  GPR_ASSERT(first_entry.transport_size() <= mem_used_);
    -  mem_used_ -= first_entry.transport_size();
    +  GPR_ASSERT(first_entry.md.transport_size() <= mem_used_);
    +  mem_used_ -= first_entry.md.transport_size();
     }
     
     void HPackTable::SetMaxBytes(uint32_t max_bytes) {
    @@ -105,7 +105,7 @@ grpc_error_handle HPackTable::SetCurrentTableSize(uint32_t bytes) {
         return absl::OkStatus();
       }
       if (bytes > max_bytes_) {
    -    return GRPC_ERROR_CREATE(absl::StrFormat(
    +    return absl::InternalError(absl::StrFormat(
             "Attempt to make hpack table %d bytes when max is %d bytes", bytes,
             max_bytes_));
       }
    @@ -131,7 +131,7 @@ grpc_error_handle HPackTable::Add(Memento md) {
       }
     
       // we can't add elements bigger than the max table size
    -  if (md.transport_size() > current_table_bytes_) {
    +  if (md.md.transport_size() > current_table_bytes_) {
         // HPACK draft 10 section 4.4 states:
         // If the size of the new entry is less than or equal to the maximum
         // size, that entry is added to the table.  It is not an error to
    @@ -146,13 +146,13 @@ grpc_error_handle HPackTable::Add(Memento md) {
       }
     
       // evict entries to ensure no overflow
    -  while (md.transport_size() >
    +  while (md.md.transport_size() >
              static_cast<size_t>(current_table_bytes_) - mem_used_) {
         EvictOne();
       }
     
       // copy the finalized entry in
    -  mem_used_ += md.transport_size();
    +  mem_used_ += md.md.transport_size();
       entries_.Put(std::move(md));
       return absl::OkStatus();
     }
    @@ -229,12 +229,14 @@ const StaticTableEntry kStaticTable[hpack_constants::kLastStaticEntry] = {
     
     HPackTable::Memento MakeMemento(size_t i) {
       auto sm = kStaticTable[i];
    -  return grpc_metadata_batch::Parse(
    -      sm.key, Slice::FromStaticString(sm.value),
    -      strlen(sm.key) + strlen(sm.value) + hpack_constants::kEntryOverhead,
    -      [](absl::string_view, const Slice&) {
    -        abort();  // not expecting to see this
    -      });
    +  return HPackTable::Memento{
    +      grpc_metadata_batch::Parse(
    +          sm.key, Slice::FromStaticString(sm.value),
    +          strlen(sm.key) + strlen(sm.value) + hpack_constants::kEntryOverhead,
    +          [](absl::string_view, const Slice&) {
    +            abort();  // not expecting to see this
    +          }),
    +      absl::OkStatus()};
     }
     
     }  // namespace
    
  • src/core/ext/transport/chttp2/transport/hpack_parser_table.h+9 1 modified
    @@ -25,6 +25,8 @@
     
     #include <vector>
     
    +#include "absl/status/status.h"
    +
     #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
     #include "src/core/lib/gprpp/no_destruct.h"
     #include "src/core/lib/iomgr/error.h"
    @@ -45,7 +47,10 @@ class HPackTable {
       void SetMaxBytes(uint32_t max_bytes);
       grpc_error_handle SetCurrentTableSize(uint32_t bytes);
     
    -  using Memento = ParsedMetadata<grpc_metadata_batch>;
    +  struct Memento {
    +    ParsedMetadata<grpc_metadata_batch> md;
    +    absl::Status parse_status;
    +  };
     
       // Lookup, but don't ref.
       const Memento* Lookup(uint32_t index) const {
    @@ -68,6 +73,9 @@ class HPackTable {
       // Current entry count in the table.
       uint32_t num_entries() const { return entries_.num_entries(); }
     
    +  // Current size of the table.
    +  uint32_t test_only_table_size() const { return mem_used_; }
    +
      private:
       struct StaticMementos {
         StaticMementos();
    
  • src/core/lib/surface/validate_metadata.cc+43 42 modified
    @@ -21,46 +21,20 @@
     #include "src/core/lib/surface/validate_metadata.h"
     
     #include "absl/status/status.h"
    +#include "absl/strings/escaping.h"
    +#include "absl/strings/str_cat.h"
     #include "absl/strings/string_view.h"
     
     #include <grpc/grpc.h>
     
    -#include "src/core/lib/gpr/string.h"
     #include "src/core/lib/gprpp/bitset.h"
    -#include "src/core/lib/gprpp/memory.h"
    -#include "src/core/lib/gprpp/status_helper.h"
     #include "src/core/lib/iomgr/error.h"
    +#include "src/core/lib/slice/slice_internal.h"
     
    -static grpc_error_handle conforms_to(const grpc_slice& slice,
    -                                     const grpc_core::BitSet<256>& legal_bits,
    -                                     const char* err_desc) {
    -  const uint8_t* p = GRPC_SLICE_START_PTR(slice);
    -  const uint8_t* e = GRPC_SLICE_END_PTR(slice);
    -  for (; p != e; p++) {
    -    if (!legal_bits.is_set(*p)) {
    -      size_t len;
    -      grpc_core::UniquePtr<char> ptr(gpr_dump_return_len(
    -          reinterpret_cast<const char*> GRPC_SLICE_START_PTR(slice),
    -          GRPC_SLICE_LENGTH(slice), GPR_DUMP_HEX | GPR_DUMP_ASCII, &len));
    -      grpc_error_handle error = grpc_error_set_str(
    -          grpc_error_set_int(GRPC_ERROR_CREATE(err_desc),
    -                             grpc_core::StatusIntProperty::kOffset,
    -                             p - GRPC_SLICE_START_PTR(slice)),
    -          grpc_core::StatusStrProperty::kRawBytes,
    -          absl::string_view(ptr.get(), len));
    -      return error;
    -    }
    -  }
    -  return absl::OkStatus();
    -}
    -
    -static int error2int(grpc_error_handle error) {
    -  int r = (error.ok());
    -  return r;
    -}
    +namespace grpc_core {
     
     namespace {
    -class LegalHeaderKeyBits : public grpc_core::BitSet<256> {
    +class LegalHeaderKeyBits : public BitSet<256> {
      public:
       constexpr LegalHeaderKeyBits() {
         for (int i = 'a'; i <= 'z'; i++) set(i);
    @@ -71,19 +45,45 @@ class LegalHeaderKeyBits : public grpc_core::BitSet<256> {
       }
     };
     constexpr LegalHeaderKeyBits g_legal_header_key_bits;
    -}  // namespace
     
    -grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice) {
    -  if (GRPC_SLICE_LENGTH(slice) == 0) {
    -    return GRPC_ERROR_CREATE("Metadata keys cannot be zero length");
    +GPR_ATTRIBUTE_NOINLINE
    +absl::Status DoesNotConformTo(absl::string_view x, const char* err_desc) {
    +  return absl::InternalError(absl::StrCat(err_desc, ": ", x, " (hex ",
    +                                          absl::BytesToHexString(x), ")"));
    +}
    +
    +absl::Status ConformsTo(absl::string_view x, const BitSet<256>& legal_bits,
    +                        const char* err_desc) {
    +  for (uint8_t c : x) {
    +    if (!legal_bits.is_set(c)) {
    +      return DoesNotConformTo(x, err_desc);
    +    }
       }
    -  if (GRPC_SLICE_LENGTH(slice) > UINT32_MAX) {
    -    return GRPC_ERROR_CREATE("Metadata keys cannot be larger than UINT32_MAX");
    +  return absl::OkStatus();
    +}
    +}  // namespace
    +
    +absl::Status ValidateHeaderKeyIsLegal(absl::string_view key) {
    +  if (key.empty()) {
    +    return absl::InternalError("Metadata keys cannot be zero length");
       }
    -  if (GRPC_SLICE_START_PTR(slice)[0] == ':') {
    -    return GRPC_ERROR_CREATE("Metadata keys cannot start with :");
    +  if (key.size() > UINT32_MAX) {
    +    return absl::InternalError(
    +        "Metadata keys cannot be larger than UINT32_MAX");
       }
    -  return conforms_to(slice, g_legal_header_key_bits, "Illegal header key");
    +  return ConformsTo(key, g_legal_header_key_bits, "Illegal header key");
    +}
    +
    +}  // namespace grpc_core
    +
    +static int error2int(grpc_error_handle error) {
    +  int r = (error.ok());
    +  return r;
    +}
    +
    +grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice) {
    +  return grpc_core::ValidateHeaderKeyIsLegal(
    +      grpc_core::StringViewFromSlice(slice));
     }
     
     int grpc_header_key_is_legal(grpc_slice slice) {
    @@ -104,8 +104,9 @@ constexpr LegalHeaderNonBinValueBits g_legal_header_non_bin_value_bits;
     
     grpc_error_handle grpc_validate_header_nonbin_value_is_legal(
         const grpc_slice& slice) {
    -  return conforms_to(slice, g_legal_header_non_bin_value_bits,
    -                     "Illegal header value");
    +  return grpc_core::ConformsTo(grpc_core::StringViewFromSlice(slice),
    +                               g_legal_header_non_bin_value_bits,
    +                               "Illegal header value");
     }
     
     int grpc_header_nonbin_value_is_legal(grpc_slice slice) {
    
  • src/core/lib/surface/validate_metadata.h+9 0 modified
    @@ -25,11 +25,20 @@
     
     #include <cstring>
     
    +#include "absl/status/status.h"
    +#include "absl/strings/string_view.h"
    +
     #include <grpc/slice.h>
     #include <grpc/support/log.h>
     
     #include "src/core/lib/iomgr/error.h"
     
    +namespace grpc_core {
    +
    +absl::Status ValidateHeaderKeyIsLegal(absl::string_view key);
    +
    +}
    +
     grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice);
     grpc_error_handle grpc_validate_header_nonbin_value_is_legal(
         const grpc_slice& slice);
    
  • src/core/lib/transport/metadata_batch.h+3 2 modified
    @@ -590,8 +590,9 @@ class ParseHelper {
     
       GPR_ATTRIBUTE_NOINLINE ParsedMetadata<Container> NotFound(
           absl::string_view key) {
    -    return ParsedMetadata<Container>(Slice::FromCopiedString(key),
    -                                     std::move(value_));
    +    return ParsedMetadata<Container>(
    +        typename ParsedMetadata<Container>::FromSlicePair{},
    +        Slice::FromCopiedString(key), std::move(value_), transport_size_);
       }
     
      private:
    
  • src/core/lib/transport/parsed_metadata.h+9 5 modified
    @@ -152,9 +152,14 @@ class ParsedMetadata {
         value_.slice = value.TakeCSlice();
       }
       // Construct metadata from a string key, slice value pair.
    -  ParsedMetadata(Slice key, Slice value)
    +  // FromSlicePair() is used to adjust the overload set so that we don't
    +  // inadvertently match against any of the previous overloads.
    +  // TODO(ctiller): re-evaluate the overload functions here so and maybe
    +  // introduce some factory functions?
    +  struct FromSlicePair {};
    +  ParsedMetadata(FromSlicePair, Slice key, Slice value, uint32_t transport_size)
           : vtable_(ParsedMetadata::KeyValueVTable(key.as_string_view())),
    -        transport_size_(static_cast<uint32_t>(key.size() + value.size() + 32)) {
    +        transport_size_(transport_size) {
         value_.pointer =
             new std::pair<Slice, Slice>(std::move(key), std::move(value));
       }
    @@ -188,14 +193,13 @@ class ParsedMetadata {
       // HTTP2 defined storage size of this metadatum.
       uint32_t transport_size() const { return transport_size_; }
       // Create a new parsed metadata with the same key but a different value.
    -  ParsedMetadata WithNewValue(Slice value,
    +  ParsedMetadata WithNewValue(Slice value, uint32_t value_wire_size,
                                   MetadataParseErrorFn on_error) const {
         ParsedMetadata result;
         result.vtable_ = vtable_;
         result.value_ = value_;
         result.transport_size_ =
    -        TransportSize(static_cast<uint32_t>(key().length()),
    -                      static_cast<uint32_t>(value.length()));
    +        TransportSize(static_cast<uint32_t>(key().length()), value_wire_size);
         vtable_->with_new_value(&value, on_error, &result);
         return result;
       }
    
  • test/core/transport/chttp2/bad-base64.headers+1 0 added
    @@ -0,0 +1 @@
    +a.b.c-bin: luckily for us, it's tuesday
    
  • test/core/transport/chttp2/bad-te.headers+1 0 added
    @@ -0,0 +1 @@
    +te: garbage
    
  • test/core/transport/chttp2/bin_encoder_test.cc+3 1 modified
    @@ -70,7 +70,9 @@ static void expect_combined_equiv(const char* s, size_t len, int line) {
       grpc_slice input = grpc_slice_from_copied_buffer(s, len);
       grpc_slice base64 = grpc_chttp2_base64_encode(input);
       grpc_slice expect = grpc_chttp2_huffman_compress(base64);
    -  grpc_slice got = grpc_chttp2_base64_encode_and_huffman_compress(input);
    +  uint32_t wire_size;
    +  grpc_slice got =
    +      grpc_chttp2_base64_encode_and_huffman_compress(input, &wire_size);
       if (!grpc_slice_eq(expect, got)) {
         char* t = grpc_dump_slice(input, GPR_DUMP_HEX | GPR_DUMP_ASCII);
         char* e = grpc_dump_slice(expect, GPR_DUMP_HEX | GPR_DUMP_ASCII);
    
  • test/core/transport/chttp2/BUILD+29 0 modified
    @@ -32,6 +32,18 @@ grpc_proto_fuzzer(
         ],
     )
     
    +grpc_proto_fuzzer(
    +    name = "hpack_sync_fuzzer",
    +    srcs = ["hpack_sync_fuzzer.cc"],
    +    corpus = "hpack_sync_corpus",
    +    proto = "hpack_sync_fuzzer.proto",
    +    tags = ["no_windows"],
    +    deps = [
    +        "//:grpc",
    +        "//test/core/util:grpc_test_util",
    +    ],
    +)
    +
     grpc_proto_fuzzer(
         name = "flow_control_fuzzer",
         srcs = ["flow_control_fuzzer.cc"],
    @@ -47,6 +59,23 @@ grpc_proto_fuzzer(
         ],
     )
     
    +grpc_fuzzer(
    +    name = "hpack_parser_input_size_fuzzer",
    +    srcs = ["hpack_parser_input_size_fuzzer.cc"],
    +    corpus = "hpack_parser_input_size_corpus",
    +    external_deps = [
    +        "absl/cleanup",
    +        "absl/status:statusor",
    +        "absl/status",
    +    ],
    +    tags = ["no_windows"],
    +    deps = [
    +        "//:grpc",
    +        "//test/core/util:grpc_test_util",
    +        "//test/core/util:grpc_test_util_base",
    +    ],
    +)
    +
     grpc_fuzzer(
         name = "decode_huff_fuzzer",
         srcs = ["decode_huff_fuzzer.cc"],
    
  • test/core/transport/chttp2/hpack_encoder_test.cc+61 1 modified
    @@ -193,7 +193,7 @@ static void verify(
         bool is_eof, const char* expected,
         const std::vector<std::pair<std::string, std::string>>& header_fields) {
       const grpc_core::Slice merged(EncodeHeaderIntoBytes(is_eof, header_fields));
    -  const grpc_core::Slice expect(parse_hexstring(expected));
    +  const grpc_core::Slice expect(grpc_core::ParseHexstring(expected));
     
       EXPECT_EQ(merged, expect);
     }
    @@ -343,6 +343,66 @@ TEST(HpackEncoderTest, TestContinuationHeaders) {
       delete g_compressor;
     }
     
    +TEST(HpackEncoderTest, EncodeBinaryAsBase64) {
    +  grpc_core::MemoryAllocator memory_allocator =
    +      grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
    +                                     ->memory_quota()
    +                                     ->CreateMemoryAllocator("test"));
    +  auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
    +  grpc_metadata_batch b(arena.get());
    +  // Haiku by Bard
    +  b.Append("grpc-trace-bin",
    +           grpc_core::Slice::FromStaticString(
    +               "Base64, a tool\nTo encode binary data into "
    +               "text\nSo it can be shared."),
    +           CrashOnAppendError);
    +  grpc_transport_one_way_stats stats;
    +  stats = {};
    +  grpc_slice_buffer output;
    +  grpc_slice_buffer_init(&output);
    +  grpc_core::HPackCompressor::EncodeHeaderOptions hopt = {
    +      0xdeadbeef,  // stream_id
    +      true,        // is_eof
    +      false,       // use_true_binary_metadata
    +      150,         // max_frame_size
    +      &stats};
    +  grpc_core::HPackCompressor compressor;
    +  compressor.EncodeHeaders(hopt, b, &output);
    +  grpc_slice_buffer_destroy(&output);
    +
    +  EXPECT_EQ(compressor.test_only_table_size(), 136);
    +}
    +
    +TEST(HpackEncoderTest, EncodeBinaryAsTrueBinary) {
    +  grpc_core::MemoryAllocator memory_allocator =
    +      grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
    +                                     ->memory_quota()
    +                                     ->CreateMemoryAllocator("test"));
    +  auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
    +  grpc_metadata_batch b(arena.get());
    +  // Haiku by Bard
    +  b.Append("grpc-trace-bin",
    +           grpc_core::Slice::FromStaticString(
    +               "Base64, a tool\nTo encode binary data into "
    +               "text\nSo it can be shared."),
    +           CrashOnAppendError);
    +  grpc_transport_one_way_stats stats;
    +  stats = {};
    +  grpc_slice_buffer output;
    +  grpc_slice_buffer_init(&output);
    +  grpc_core::HPackCompressor::EncodeHeaderOptions hopt = {
    +      0xdeadbeef,  // stream_id
    +      true,        // is_eof
    +      true,        // use_true_binary_metadata
    +      150,         // max_frame_size
    +      &stats};
    +  grpc_core::HPackCompressor compressor;
    +  compressor.EncodeHeaders(hopt, b, &output);
    +  grpc_slice_buffer_destroy(&output);
    +
    +  EXPECT_EQ(compressor.test_only_table_size(), 114);
    +}
    +
     int main(int argc, char** argv) {
       grpc::testing::TestEnvironment env(&argc, argv);
       ::testing::InitGoogleTest(&argc, argv);
    
  • test/core/transport/chttp2/hpack_parser_corpus/crash-39214285ae59b7193aad114858056cca7c21a8e5+12 0 added
    @@ -0,0 +1,12 @@
    +frames {
    +  max_metadata_length: 4096
    +  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin;\t!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
    +}
    +frames {
    +  max_metadata_length: 4096
    +  parse: "*\244\020\007\360\244\017-bin\203c\035\037\000\'[\360i(bn-!?\244\037\333\360!(!\\\360\tc"
    +  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin\t;!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
    +  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin\t;!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
    +  parse: "\244\244\020\007\360\244\017-bin\213c[)(-\'bin\t;!!?\244\037\333\360!\020\007\360{(-bin\360\t!\\\t!\345\037\351\033;?G\355[((!!\\\360"
    +  absolute_max_metadata_length: 4096
    +}
    
  • test/core/transport/chttp2/hpack_parser_corpus/crash-7f5f186fa8ac3950346da51bce3d76d0437d3b20+38 0 added
    @@ -0,0 +1,38 @@
    +frames {
    +  end_of_headers: true
    +}
    +frames {
    +  end_of_headers: true
    +  end_of_stream: true
    +  stop_buffering_after_segments: 4096
    +  max_metadata_length: 16252928
    +  parse: "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
    +  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
    +  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
    +  parse: "\244\244\020\007\360\244\017-bin\213c*[)\244(\244-\017\333\360\'\360b\203cin\t!!"
    +  absolute_max_metadata_length: 16252928
    +}
    +frames {
    +  end_of_headers: true
    +  end_of_stream: true
    +  max_metadata_length: -4093
    +  parse: "\261\261\261\261\261\261\261"
    +  parse: "D\005:path"
    +  parse: "\261\261\261\261\261\261\261"
    +  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
    +  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
    +  absolute_max_metadata_length: 4096
    +}
    +frames {
    +  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
    +}
    +frames {
    +  end_of_headers: true
    +  end_of_stream: true
    +  stop_buffering_after_segments: 4096
    +  max_metadata_length: 4096
    +  parse: "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
    +  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
    +  parse: "\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272\272"
    +  parse: "\244\244\020\007\360\244\017-bin\213\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377c*[)\244(\244-\017\333\360\'\360b\203cin\t!!"
    +}
    
  • test/core/transport/chttp2/hpack_parser_input_size_corpus/empty+1 0 added
    @@ -0,0 +1 @@
    +
    
  • test/core/transport/chttp2/hpack_parser_input_size_fuzzer.cc+151 0 added
    @@ -0,0 +1,151 @@
    +// Copyright 2023 gRPC authors.
    +//
    +// Licensed under the Apache License, Version 2.0 (the "License");
    +// you may not use this file except in compliance with the License.
    +// You may obtain a copy of the License at
    +//
    +//     http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS,
    +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +// See the License for the specific language governing permissions and
    +// limitations under the License.
    +
    +// For all inputs, ensure parsing one byte at a time produces the same result as
    +// parsing the entire input at once.
    +
    +#include <stdint.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +
    +#include <memory>
    +#include <string>
    +
    +#include "absl/cleanup/cleanup.h"
    +#include "absl/status/status.h"
    +#include "absl/status/statusor.h"
    +#include "absl/strings/str_cat.h"
    +
    +#include <grpc/event_engine/memory_allocator.h>
    +#include <grpc/slice.h>
    +#include <grpc/support/alloc.h>
    +#include <grpc/support/time.h>
    +
    +#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
    +#include "src/core/lib/gprpp/ref_counted_ptr.h"
    +#include "src/core/lib/gprpp/status_helper.h"
    +#include "src/core/lib/iomgr/error.h"
    +#include "src/core/lib/iomgr/exec_ctx.h"
    +#include "src/core/lib/resource_quota/arena.h"
    +#include "src/core/lib/resource_quota/memory_quota.h"
    +#include "src/core/lib/resource_quota/resource_quota.h"
    +#include "src/core/lib/slice/slice.h"
    +#include "src/core/lib/transport/metadata_batch.h"
    +#include "test/core/util/slice_splitter.h"
    +
    +bool squelch = true;
    +bool leak_check = true;
    +
    +namespace grpc_core {
    +namespace {
    +
    +class TestEncoder {
    + public:
    +  std::string result() { return out_; }
    +
    +  void Encode(const Slice& key, const Slice& value) {
    +    out_.append(
    +        absl::StrCat(key.as_string_view(), ": ", value.as_string_view(), "\n"));
    +  }
    +
    +  template <typename T, typename V>
    +  void Encode(T, const V& v) {
    +    out_.append(absl::StrCat(T::key(), ": ", T::DisplayValue(v), "\n"));
    +  }
    +
    + private:
    +  std::string out_;
    +};
    +
    +bool IsStreamError(const absl::Status& status) {
    +  intptr_t stream_id;
    +  return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
    +}
    +
    +absl::StatusOr<std::string> TestVector(grpc_slice_split_mode mode,
    +                                       Slice input) {
    +  MemoryAllocator memory_allocator = MemoryAllocator(
    +      ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator("test"));
    +  auto arena = MakeScopedArena(1024, &memory_allocator);
    +  ExecCtx exec_ctx;
    +  grpc_slice* slices;
    +  size_t nslices;
    +  size_t i;
    +
    +  grpc_metadata_batch b(arena.get());
    +
    +  HPackParser parser;
    +  parser.BeginFrame(
    +      &b, 1024, 1024, HPackParser::Boundary::None, HPackParser::Priority::None,
    +      HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
    +
    +  grpc_split_slices(mode, const_cast<grpc_slice*>(&input.c_slice()), 1, &slices,
    +                    &nslices);
    +  auto cleanup_slices = absl::MakeCleanup([slices, nslices] {
    +    for (size_t i = 0; i < nslices; i++) {
    +      grpc_slice_unref(slices[i]);
    +    }
    +    gpr_free(slices);
    +  });
    +
    +  absl::Status found_err;
    +  for (i = 0; i < nslices; i++) {
    +    ExecCtx exec_ctx;
    +    auto err = parser.Parse(slices[i], i == nslices - 1);
    +    if (!err.ok()) {
    +      if (!IsStreamError(err)) return err;
    +      if (found_err.ok()) found_err = err;
    +    }
    +  }
    +  if (!found_err.ok()) return found_err;
    +
    +  TestEncoder encoder;
    +  b.Encode(&encoder);
    +  return encoder.result();
    +}
    +
    +std::string Stringify(absl::StatusOr<std::string> result) {
    +  if (result.ok()) {
    +    return absl::StrCat("OK\n", result.value());
    +  } else {
    +    intptr_t stream_id;
    +    bool has_stream = grpc_error_get_int(
    +        result.status(), StatusIntProperty::kStreamId, &stream_id);
    +    return absl::StrCat(
    +        has_stream ? "STREAM" : "CONNECTION", " ERROR: ",
    +        result.status().ToString(absl::StatusToStringMode::kWithNoExtraData));
    +  }
    +}
    +
    +}  // namespace
    +}  // namespace grpc_core
    +
    +extern gpr_timespec (*gpr_now_impl)(gpr_clock_type clock_type);
    +
    +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
    +  gpr_now_impl = [](gpr_clock_type clock_type) {
    +    return gpr_timespec{10, 0, clock_type};
    +  };
    +  auto slice = grpc_core::Slice::FromCopiedBuffer(data, size);
    +  auto full = grpc_core::Stringify(
    +      grpc_core::TestVector(GRPC_SLICE_SPLIT_IDENTITY, slice.Ref()));
    +  auto one_byte = grpc_core::Stringify(
    +      grpc_core::TestVector(GRPC_SLICE_SPLIT_ONE_BYTE, slice.Ref()));
    +  if (full != one_byte) {
    +    fprintf(stderr, "MISMATCHED RESULTS\nFULL SLICE: %s\nONE BYTE: %s\n",
    +            full.c_str(), one_byte.c_str());
    +    abort();
    +  }
    +  return 0;
    +}
    
  • test/core/transport/chttp2/hpack_parser_table_test.cc+7 3 modified
    @@ -37,7 +37,7 @@ void AssertIndex(const HPackTable* tbl, uint32_t idx, const char* key,
                      const char* value) {
       const auto* md = tbl->Lookup(idx);
       ASSERT_NE(md, nullptr);
    -  EXPECT_EQ(md->DebugString(), absl::StrCat(key, ": ", value));
    +  EXPECT_EQ(md->md.DebugString(), absl::StrCat(key, ": ", value));
     }
     }  // namespace
     
    @@ -119,8 +119,12 @@ TEST(HpackParserTableTest, ManyAdditions) {
         std::string value = absl::StrCat("VALUE.", i);
         auto key_slice = Slice::FromCopiedString(key);
         auto value_slice = Slice::FromCopiedString(value);
    -    auto memento =
    -        HPackTable::Memento(std::move(key_slice), std::move(value_slice));
    +    auto memento = HPackTable::Memento{
    +        ParsedMetadata<grpc_metadata_batch>(
    +            ParsedMetadata<grpc_metadata_batch>::FromSlicePair{},
    +            std::move(key_slice), std::move(value_slice),
    +            key.length() + value.length() + 32),
    +        absl::OkStatus()};
         auto add_err = tbl.Add(std::move(memento));
         ASSERT_EQ(add_err, absl::OkStatus());
         AssertIndex(&tbl, 1 + hpack_constants::kLastStaticEntry, key.c_str(),
    
  • test/core/transport/chttp2/hpack_parser_test.cc+472 71 modified
    @@ -20,60 +20,72 @@
     
     #include <stdlib.h>
     
    -#include <initializer_list>
     #include <memory>
     #include <string>
     
    +#include "absl/cleanup/cleanup.h"
     #include "absl/status/status.h"
    +#include "absl/status/statusor.h"
     #include "absl/strings/str_cat.h"
    -#include "absl/strings/str_format.h"
    +#include "absl/strings/string_view.h"
     #include "absl/types/optional.h"
    +#include "gmock/gmock.h"
     #include "gtest/gtest.h"
     
     #include <grpc/event_engine/memory_allocator.h>
     #include <grpc/grpc.h>
     #include <grpc/slice.h>
    +#include <grpc/status.h>
     #include <grpc/support/alloc.h>
     
    -#include "src/core/lib/gprpp/crash.h"
     #include "src/core/lib/gprpp/ref_counted_ptr.h"
     #include "src/core/lib/gprpp/status_helper.h"
    +#include "src/core/lib/gprpp/time.h"
     #include "src/core/lib/iomgr/exec_ctx.h"
     #include "src/core/lib/resource_quota/arena.h"
     #include "src/core/lib/resource_quota/memory_quota.h"
     #include "src/core/lib/resource_quota/resource_quota.h"
     #include "src/core/lib/slice/slice.h"
    +#include "src/core/lib/transport/error_utils.h"
     #include "test/core/util/parse_hexstring.h"
     #include "test/core/util/slice_splitter.h"
     #include "test/core/util/test_config.h"
     
    +namespace grpc_core {
    +namespace {
    +
    +const uint32_t kFailureIsConnectionError = 1;
    +const uint32_t kWithPriority = 2;
    +const uint32_t kEndOfStream = 4;
    +const uint32_t kEndOfHeaders = 8;
    +
     struct TestInput {
    -  const char* input;
    -  const char* expected_parse;
    +  absl::string_view input;
    +  absl::StatusOr<absl::string_view> expected_parse;
    +  uint32_t flags;
     };
     
     struct Test {
       absl::optional<size_t> table_size;
    +  absl::optional<size_t> max_metadata_size;
       std::vector<TestInput> inputs;
     };
     
     class ParseTest : public ::testing::TestWithParam<Test> {
      public:
    -  ParseTest() {
    -    grpc_init();
    -    parser_ = std::make_unique<grpc_core::HPackParser>();
    -  }
    +  ParseTest() { grpc_init(); }
     
       ~ParseTest() override {
         {
    -      grpc_core::ExecCtx exec_ctx;
    +      ExecCtx exec_ctx;
           parser_.reset();
         }
     
         grpc_shutdown();
       }
     
       void SetUp() override {
    +    parser_ = std::make_unique<HPackParser>();
         if (GetParam().table_size.has_value()) {
           parser_->hpack_table()->SetMaxBytes(GetParam().table_size.value());
           EXPECT_EQ(parser_->hpack_table()->SetCurrentTableSize(
    @@ -82,56 +94,89 @@ class ParseTest : public ::testing::TestWithParam<Test> {
         }
       }
     
    -  void TestVector(grpc_slice_split_mode mode, const char* hexstring,
    -                  std::string expect) {
    -    grpc_core::MemoryAllocator memory_allocator =
    -        grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
    -                                       ->memory_quota()
    -                                       ->CreateMemoryAllocator("test"));
    -    auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
    -    grpc_core::ExecCtx exec_ctx;
    -    grpc_slice input = parse_hexstring(hexstring);
    +  static bool IsStreamError(const absl::Status& status) {
    +    intptr_t stream_id;
    +    return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
    +  }
    +
    +  void TestVector(grpc_slice_split_mode mode,
    +                  absl::optional<size_t> max_metadata_size,
    +                  absl::string_view hexstring,
    +                  absl::StatusOr<absl::string_view> expect, uint32_t flags) {
    +    MemoryAllocator memory_allocator = MemoryAllocator(
    +        ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
    +            "test"));
    +    auto arena = MakeScopedArena(1024, &memory_allocator);
    +    ExecCtx exec_ctx;
    +    auto input = ParseHexstring(hexstring);
         grpc_slice* slices;
         size_t nslices;
         size_t i;
     
         grpc_metadata_batch b(arena.get());
     
         parser_->BeginFrame(
    -        &b, 4096, 4096, grpc_core::HPackParser::Boundary::None,
    -        grpc_core::HPackParser::Priority::None,
    -        grpc_core::HPackParser::LogInfo{
    -            1, grpc_core::HPackParser::LogInfo::kHeaders, false});
    +        &b, max_metadata_size.value_or(4096), max_metadata_size.value_or(4096),
    +        (flags & kEndOfStream)
    +            ? HPackParser::Boundary::EndOfStream
    +            : ((flags & kEndOfHeaders) ? HPackParser::Boundary::EndOfHeaders
    +                                       : HPackParser::Boundary::None),
    +        flags & kWithPriority ? HPackParser::Priority::Included
    +                              : HPackParser::Priority::None,
    +        HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
     
    -    grpc_split_slices(mode, &input, 1, &slices, &nslices);
    -    grpc_slice_unref(input);
    +    grpc_split_slices(mode, const_cast<grpc_slice*>(&input.c_slice()), 1,
    +                      &slices, &nslices);
    +    auto cleanup_slices = absl::MakeCleanup([slices, nslices] {
    +      for (size_t i = 0; i < nslices; i++) {
    +        grpc_slice_unref(slices[i]);
    +      }
    +      gpr_free(slices);
    +    });
     
    +    bool saw_error = false;
         for (i = 0; i < nslices; i++) {
    -      grpc_core::ExecCtx exec_ctx;
    +      ExecCtx exec_ctx;
           auto err = parser_->Parse(slices[i], i == nslices - 1);
    -      if (!err.ok()) {
    -        grpc_core::Crash(
    -            absl::StrFormat("Unexpected parse error: %s",
    -                            grpc_core::StatusToString(err).c_str()));
    +      if (!err.ok() && (flags & kFailureIsConnectionError) == 0) {
    +        EXPECT_TRUE(IsStreamError(err)) << err;
    +      }
    +      if (!saw_error && !err.ok()) {
    +        // one byte at a time mode might fail with a stream error early
    +        if (mode == GRPC_SLICE_SPLIT_ONE_BYTE &&
    +            (flags & kFailureIsConnectionError) && IsStreamError(err)) {
    +          continue;
    +        }
    +        grpc_status_code code;
    +        std::string message;
    +        grpc_error_get_status(err, Timestamp::InfFuture(), &code, &message,
    +                              nullptr, nullptr);
    +        EXPECT_EQ(code, static_cast<grpc_status_code>(expect.status().code()))
    +            << err;
    +        EXPECT_THAT(message, ::testing::HasSubstr(expect.status().message()))
    +            << err;
    +        saw_error = true;
    +        if (flags & kFailureIsConnectionError) return;
           }
         }
     
    -    for (i = 0; i < nslices; i++) {
    -      grpc_slice_unref(slices[i]);
    +    if (!saw_error) {
    +      EXPECT_TRUE(expect.ok()) << expect.status();
         }
    -    gpr_free(slices);
     
    -    TestEncoder encoder;
    -    b.Encode(&encoder);
    -    EXPECT_EQ(encoder.result(), expect);
    +    if (expect.ok()) {
    +      TestEncoder encoder;
    +      b.Encode(&encoder);
    +      EXPECT_EQ(encoder.result(), *expect) << "Input: " << hexstring;
    +    }
       }
     
      private:
       class TestEncoder {
        public:
         std::string result() { return out_; }
     
    -    void Encode(const grpc_core::Slice& key, const grpc_core::Slice& value) {
    +    void Encode(const Slice& key, const Slice& value) {
           out_.append(absl::StrCat(key.as_string_view(), ": ",
                                    value.as_string_view(), "\n"));
         }
    @@ -146,91 +191,103 @@ class ParseTest : public ::testing::TestWithParam<Test> {
         std::string out_;
       };
     
    -  std::unique_ptr<grpc_core::HPackParser> parser_;
    +  std::unique_ptr<HPackParser> parser_;
     };
     
     TEST_P(ParseTest, WholeSlices) {
       for (const auto& input : GetParam().inputs) {
    -    TestVector(GRPC_SLICE_SPLIT_MERGE_ALL, input.input, input.expected_parse);
    +    TestVector(GRPC_SLICE_SPLIT_MERGE_ALL, GetParam().max_metadata_size,
    +               input.input, input.expected_parse, input.flags);
       }
     }
     
     TEST_P(ParseTest, OneByteAtATime) {
       for (const auto& input : GetParam().inputs) {
    -    TestVector(GRPC_SLICE_SPLIT_ONE_BYTE, input.input, input.expected_parse);
    +    TestVector(GRPC_SLICE_SPLIT_ONE_BYTE, GetParam().max_metadata_size,
    +               input.input, input.expected_parse, input.flags);
       }
     }
     
     INSTANTIATE_TEST_SUITE_P(
         ParseTest, ParseTest,
         ::testing::Values(
    -        Test{
    -            {},
    -            {
    -                // D.2.1
    -                {"400a 6375 7374 6f6d 2d6b 6579 0d63 7573"
    -                 "746f 6d2d 6865 6164 6572",
    -                 "custom-key: custom-header\n"},
    -                // D.2.2
    -                {"040c 2f73 616d 706c 652f 7061 7468", ":path: /sample/path\n"},
    -                // D.2.3
    -                {"1008 7061 7373 776f 7264 0673 6563 7265"
    -                 "74",
    -                 "password: secret\n"},
    -                // D.2.4
    -                {"82", ":method: GET\n"},
    -            }},
             Test{{},
    +             {},
    +             {
    +                 // D.2.1
    +                 {"400a 6375 7374 6f6d 2d6b 6579 0d63 7573"
    +                  "746f 6d2d 6865 6164 6572",
    +                  "custom-key: custom-header\n", 0},
    +                 // D.2.2
    +                 {"040c 2f73 616d 706c 652f 7061 7468", ":path: /sample/path\n",
    +                  0},
    +                 // D.2.3
    +                 {"1008 7061 7373 776f 7264 0673 6563 7265"
    +                  "74",
    +                  "password: secret\n", 0},
    +                 // D.2.4
    +                 {"82", ":method: GET\n", 0},
    +             }},
    +        Test{{},
    +             {},
                  {
                      // D.3.1
                      {"8286 8441 0f77 7777 2e65 7861 6d70 6c65"
                       "2e63 6f6d",
                       ":path: /\n"
                       ":authority: www.example.com\n"
                       ":method: GET\n"
    -                  ":scheme: http\n"},
    +                  ":scheme: http\n",
    +                  0},
                      // D.3.2
                      {"8286 84be 5808 6e6f 2d63 6163 6865",
                       ":path: /\n"
                       ":authority: www.example.com\n"
                       ":method: GET\n"
                       ":scheme: http\n"
    -                  "cache-control: no-cache\n"},
    +                  "cache-control: no-cache\n",
    +                  0},
                      // D.3.3
                      {"8287 85bf 400a 6375 7374 6f6d 2d6b 6579"
                       "0c63 7573 746f 6d2d 7661 6c75 65",
                       ":path: /index.html\n"
                       ":authority: www.example.com\n"
                       ":method: GET\n"
                       ":scheme: https\n"
    -                  "custom-key: custom-value\n"},
    +                  "custom-key: custom-value\n",
    +                  0},
                  }},
             Test{{},
    +             {},
                  {
                      // D.4.1
                      {"8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4"
                       "ff",
                       ":path: /\n"
                       ":authority: www.example.com\n"
                       ":method: GET\n"
    -                  ":scheme: http\n"},
    +                  ":scheme: http\n",
    +                  0},
                      // D.4.2
                      {"8286 84be 5886 a8eb 1064 9cbf",
                       ":path: /\n"
                       ":authority: www.example.com\n"
                       ":method: GET\n"
                       ":scheme: http\n"
    -                  "cache-control: no-cache\n"},
    +                  "cache-control: no-cache\n",
    +                  0},
                      // D.4.3
                      {"8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925"
                       "a849 e95b b8e8 b4bf",
                       ":path: /index.html\n"
                       ":authority: www.example.com\n"
                       ":method: GET\n"
                       ":scheme: https\n"
    -                  "custom-key: custom-value\n"},
    +                  "custom-key: custom-value\n",
    +                  0},
                  }},
             Test{{256},
    +             {},
                  {
                      // D.5.1
                      {"4803 3330 3258 0770 7269 7661 7465 611d"
    @@ -241,13 +298,15 @@ INSTANTIATE_TEST_SUITE_P(
                       ":status: 302\n"
                       "cache-control: private\n"
                       "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
    -                  "location: https://www.example.com\n"},
    +                  "location: https://www.example.com\n",
    +                  0},
                      // D.5.2
                      {"4803 3330 37c1 c0bf",
                       ":status: 307\n"
                       "cache-control: private\n"
                       "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
    -                  "location: https://www.example.com\n"},
    +                  "location: https://www.example.com\n",
    +                  0},
                      // D.5.3
                      {"88c1 611d 4d6f 6e2c 2032 3120 4f63 7420"
                       "3230 3133 2032 303a 3133 3a32 3220 474d"
    @@ -262,9 +321,11 @@ INSTANTIATE_TEST_SUITE_P(
                       "location: https://www.example.com\n"
                       "content-encoding: gzip\n"
                       "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; "
    -                  "version=1\n"},
    +                  "version=1\n",
    +                  0},
                  }},
             Test{{256},
    +             {},
                  {
                      // D.6.1
                      {"4882 6402 5885 aec3 771a 4b61 96d0 7abe"
    @@ -274,13 +335,15 @@ INSTANTIATE_TEST_SUITE_P(
                       ":status: 302\n"
                       "cache-control: private\n"
                       "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
    -                  "location: https://www.example.com\n"},
    +                  "location: https://www.example.com\n",
    +                  0},
                      // D.6.2
                      {"4883 640e ffc1 c0bf",
                       ":status: 307\n"
                       "cache-control: private\n"
                       "date: Mon, 21 Oct 2013 20:13:21 GMT\n"
    -                  "location: https://www.example.com\n"},
    +                  "location: https://www.example.com\n",
    +                  0},
                      // D.6.3
                      {"88c1 6196 d07a be94 1054 d444 a820 0595"
                       "040b 8166 e084 a62d 1bff c05a 839b d9ab"
    @@ -293,18 +356,356 @@ INSTANTIATE_TEST_SUITE_P(
                       "location: https://www.example.com\n"
                       "content-encoding: gzip\n"
                       "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; "
    -                  "version=1\n"},
    +                  "version=1\n",
    +                  0},
                  }},
             Test{{},
    +             {1024},
    +             {{"3fc43fc4", absl::InternalError("Attempt to make hpack table"),
    +               kFailureIsConnectionError}}},
    +        Test{{},
    +             {},
    +             {{"3ba4a41007f0a40f2d62696e8b632a5b29a40fa4a4281007f0",
    +               absl::InternalError("Invalid HPACK index received"),
    +               kFailureIsConnectionError}}},
    +        Test{{},
    +             {},
    +             {{"2aa41007f0a40f2d62696e8163a41f1f00275bf0692862a4dbf0f00963",
    +               absl::InternalError(
    +                   "More than two max table size changes in a single frame"),
    +               kFailureIsConnectionError}}},
    +        Test{{},
    +             {},
    +             {{"2aa41007f0a40f2d62696e8363271f00275bf06928626e2d213fa40fdbf0212"
    +               "8215cf00963",
    +               absl::InternalError("illegal base64 encoding"), 0}}},
    +        Test{{},
    +             {},
    +             {{"a4a41007f0a40f2d62696e8b635b29282d2762696e3b0921213fa41fdbf0211"
    +               "007f07b282d62696ef009215c0921e51fe91b3b3f47ed5b282821215cf0",
    +               absl::InternalError(
    +                   "More than two max table size changes in a single frame"),
    +               kFailureIsConnectionError}}},
    +        Test{
    +            {},
    +            {},
    +            {{"696969696969696969696969696969696969696969696969696969696969696"
    +              "969696969696969696969696969696969696969696969696969696969696969"
    +              "6969696969696969696969696969bababababababababababababababababab"
    +              "abababababababababababababababababababababababababababababababa"
    +              "bababababababababababababababababababababababababababababababab"
    +              "abababababaa4a41007f0a40f2d62696e8bffffffffffffffffffffffffffff"
    +              "ffffffffffff632a5b29a428a42d0fdbf027f0628363696e092121",
    +              absl::InternalError("integer overflow in hpack integer decoding"),
    +              kEndOfHeaders | kFailureIsConnectionError}}},
    +        Test{{},
    +             {},
    +             {{"0e 00 00 df",
    +               absl::InternalError(
    +                   "Error parsing ':status' metadata: error=not an integer"),
    +               0}}},
    +        Test{{},
    +             {},
                  {
                      // Binary metadata: created using:
                      // tools/codegen/core/gen_header_frame.py
    -                 //    --compression inc --no_framing --hex
    +                 //    --compression inc --no_framing --output hexstr
                      //    < test/core/transport/chttp2/binary-metadata.headers
                      {"40 09 61 2e 62 2e 63 2d 62 69 6e 0c 62 32 31 6e 4d 6a 41 79 "
                       "4d 51 3d 3d",
    -                  "a.b.c-bin: omg2021\n"},
    -             }}));
    +                  "a.b.c-bin: omg2021\n", 0},
    +             }},
    +        Test{{},
    +             {},
    +             {// Binary metadata: created using:
    +              // tools/codegen/core/gen_header_frame.py
    +              //    --compression inc --no_framing --output hexstr
    +              //    < test/core/transport/chttp2/bad-base64.headers
    +              {"4009612e622e632d62696e1c6c75636b696c7920666f722075732c206974"
    +               "27732074756573646179",
    +               absl::InternalError("Error parsing 'a.b.c-bin' metadata: "
    +                                   "error=illegal base64 encoding"),
    +               0},
    +              {"be",
    +               absl::InternalError("Error parsing 'a.b.c-bin' metadata: "
    +                                   "error=illegal base64 encoding"),
    +               0}}},
    +        Test{{},
    +             {},
    +             {// created using:
    +              // tools/codegen/core/gen_header_frame.py
    +              //    --compression inc --no_framing --output hexstr
    +              //    < test/core/transport/chttp2/bad-te.headers
    +              {"400274650767617262616765",
    +               absl::InternalError("Error parsing 'te' metadata"), 0},
    +              {"be", absl::InternalError("Error parsing 'te' metadata"), 0}}},
    +        Test{{},
    +             128,
    +             {
    +                 {// Generated with: tools/codegen/core/gen_header_frame.py
    +                  // --compression inc --output hexstr --no_framing <
    +                  // test/core/transport/chttp2/large-metadata.headers
    +                  "40096164616c64726964610a6272616e64796275636b40086164616c6772"
    +                  "696d04746f6f6b4008616d6172616e74680a6272616e64796275636b4008"
    +                  "616e67656c6963610762616767696e73",
    +                  absl::ResourceExhaustedError(
    +                      "received metadata size exceeds hard limit"),
    +                  0},
    +                 // Should be able to look up the added elements individually
    +                 // (do not corrupt the hpack table test!)
    +                 {"be", "angelica: baggins\n", 0},
    +                 {"bf", "amaranth: brandybuck\n", 0},
    +                 {"c0", "adalgrim: took\n", 0},
    +                 {"c1", "adaldrida: brandybuck\n", 0},
    +                 // But not as a whole - that exceeds metadata limits for one
    +                 // request again
    +                 {"bebfc0c1",
    +                  absl::ResourceExhaustedError(
    +                      "received metadata size exceeds hard limit"),
    +                  0},
    +             }},
    +        Test{
    +            {},
    +            {},
    +            {{"be", absl::InternalError("Invalid HPACK index received"),
    +              kFailureIsConnectionError}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"80", absl::InternalError("Illegal hpack op code"),
    +              kFailureIsConnectionError}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"29", "", kFailureIsConnectionError}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"", "", kWithPriority}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"f5", absl::InternalError("Invalid HPACK index received"),
    +              kFailureIsConnectionError}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"0f", "", 0}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"7f", "", 0}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"1bffffff7c1b", "", 0}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"ffffffffff00ff",
    +              absl::InternalError("Invalid HPACK index received"),
    +              kFailureIsConnectionError}},
    +        },
    +        Test{
    +            {},
    +            {},
    +            {{"ff8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8"
    +              "d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d"
    +              "8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8d8"
    +              "d8d8d8d8d8d8d8d",
    +              absl::InternalError("integer overflow in hpack integer decoding"),
    +              kFailureIsConnectionError}}},
    +        Test{
    +            {},
    +            {9},
    +            {{"3f6672616d6573207ba2020656e645f6f665f686561646572733a2074727565a"
    +              "2020656e645f6f665f73747265616d3a2074727565a202073746f705f6275666"
    +              "66572696e675f61667465725f7365676d656e74733a2039a202070617273653a"
    +              "20225c3030305c3030305c3030305c3030305c3030305c3030305c3030305c30"
    +              "30305c3030305c3030305c3030305c3030305c3030305c3030305c3030305c30"
    +              "30305c3030305c3030305c3030305c3030305c3030305c3030305c3030305c",
    +              absl::ResourceExhaustedError(
    +                  "received metadata size exceeds hard limit"),
    +              kWithPriority}}},
    +        Test{{},
    +             {},
    +             {{"52046772706300073a737461747573033230300e7f",
    +               ":status: 200\naccept-ranges: grpc\n", 0}}},
    +        Test{{},
    +             {},
    +             {{"a4a41007f0a40f2d62696e8beda42d5b63272129a410626907",
    +               absl::InternalError("illegal base64 encoding"), 0}}},
    +        Test{
    +            // haiku segment: 149bytes*2, a:a segment: 34 bytes
    +            // So we arrange for one less than the total so we force a hpack
    +            // table overflow
    +            {149 * 2 + 34 - 1},
    +            {},
    +            {
    +                {// Generated with: tools/codegen/core/gen_header_frame.py
    +                 // --compression inc --output hexstr --no_framing <
    +                 // test/core/transport/chttp2/long-base64.headers
    +                 "4005782d62696e70516d467a5a545930494756755932396b6157356e4f67"
    +                 "704a644342305957746c6379426961573568636e6b675a47463059534268"
    +                 "626d5167625746725a584d6761585167644756346443344b56584e6c5a6e5"
    +                 "67349475a766369427a644739796157356e49475a706247567a4c673d3d",
    +                 // Haiku by Bard.
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n",
    +                 0},
    +                // Should go into the hpack table (x-bin: ... is 149 bytes long
    +                // by hpack rules)
    +                {"be",
    +                 "x-bin: Base64 encoding:\nIt takes binary data and "
    +                 "makes it text.\nUseful for storing files.\n",
    +                 0},
    +                // Add another copy
    +                {"4005782d62696e70516d467a5a545930494756755932396b6157356e4f67"
    +                 "704a644342305957746c6379426961573568636e6b675a47463059534268"
    +                 "626d5167625746725a584d6761585167644756346443344b56584e6c5a6e5"
    +                 "67349475a766369427a644739796157356e49475a706247567a4c673d3d",
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n",
    +                 0},
    +                // 149*2 == 298, so we should have two copies in the hpack table
    +                {"bebf",
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n"
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n",
    +                 0},
    +                // Add some very short headers (should push the first long thing
    +                // out)
    +                // Generated with: tools/codegen/core/gen_header_frame.py
    +                // --compression inc --output hexstr --no_framing <
    +                // test/core/transport/chttp2/short.headers
    +                {"4001610161", "a: a\n", 0},
    +                // First two entries should be what was just pushed and then one
    +                // long entry
    +                {"bebf",
    +                 "a: a\nx-bin: Base64 encoding:\nIt takes binary data and "
    +                 "makes "
    +                 "it text.\nUseful for storing files.\n",
    +                 0},
    +                // Third entry should be unprobable (it's no longer in the
    +                // table!)
    +                {"c0", absl::InternalError("Invalid HPACK index received"),
    +                 kFailureIsConnectionError},
    +            }},
    +        Test{
    +            // haiku segment: 149bytes*2, a:a segment: 34 bytes
    +            // So we arrange for one less than the total so we force a hpack
    +            // table overflow
    +            {149 * 2 + 34 - 1},
    +            {},
    +            {
    +                {// Generated with: tools/codegen/core/gen_header_frame.py
    +                 // --compression inc --output hexstr --no_framing --huff <
    +                 // test/core/transport/chttp2/long-base64.headers
    +                 "4005782d62696edbd94e1f7fbbf983262e36f313fd47c9bab54d5e592f5d0"
    +                 "73e49a09eae987c9b9c95759bf7161073dd7678e9d9347cb0d9fbf9a261fe"
    +                 "6c9a4c5c5a92f359b8fe69a3f6ae28c98bf7b90d77dc989ff43e4dd59317e"
    +                 "d71e2e3ef3cd041",
    +                 // Haiku by Bard.
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n",
    +                 0},
    +                // Should go into the hpack table (x-bin: ... is 149 bytes long
    +                // by hpack rules)
    +                {"be",
    +                 "x-bin: Base64 encoding:\nIt takes binary data and "
    +                 "makes it text.\nUseful for storing files.\n",
    +                 0},
    +                // Add another copy
    +                {"4005782d62696edbd94e1f7fbbf983262e36f313fd47c9bab54d5e592f5d0"
    +                 "73e49a09eae987c9b9c95759bf7161073dd7678e9d9347cb0d9fbf9a261fe"
    +                 "6c9a4c5c5a92f359b8fe69a3f6ae28c98bf7b90d77dc989ff43e4dd59317e"
    +                 "d71e2e3ef3cd041",
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n",
    +                 0},
    +                // 149*2 == 298, so we should have two copies in the hpack table
    +                {"bebf",
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n"
    +                 "x-bin: Base64 encoding:\nIt takes binary data and makes it "
    +                 "text.\nUseful for storing files.\n",
    +                 0},
    +                // Add some very short headers (should push the first long thing
    +                // out)
    +                // Generated with: tools/codegen/core/gen_header_frame.py
    +                // --compression inc --output hexstr --no_framing <
    +                // test/core/transport/chttp2/short.headers
    +                {"4001610161", "a: a\n", 0},
    +                // First two entries should be what was just pushed and then one
    +                // long entry
    +                {"bebf",
    +                 "a: a\nx-bin: Base64 encoding:\nIt takes binary data and "
    +                 "makes "
    +                 "it text.\nUseful for storing files.\n",
    +                 0},
    +                // Third entry should be unprobable (it's no longer in the
    +                // table!)
    +                {"c0", absl::InternalError("Invalid HPACK index received"),
    +                 kFailureIsConnectionError},
    +            }},
    +        Test{{}, {}, {{"7a", "", 0}}},
    +        Test{{},
    +             {},
    +             {{"60",
    +               absl::InternalError("Incomplete header at the end of a "
    +                                   "header/continuation sequence"),
    +               kEndOfStream | kFailureIsConnectionError}}},
    +        Test{{},
    +             {},
    +             {{"89", ":status: 204\n", 0},
    +              {"89", ":status: 204\n", 0},
    +              {"393939393939393939393939393939393939393939",
    +               absl::InternalError(
    +                   "More than two max table size changes in a single frame"),
    +               kFailureIsConnectionError}}},
    +        Test{{},
    +             {},
    +             {{"4005782d62696edbd94e1f7fbbf983267e36a313fd47c9bab54d5e592f5d",
    +               "", 0}}},
    +        Test{{}, {}, {{"72656672657368", "", 0}}},
    +        Test{{}, {}, {{"66e6645f74", "", 0}, {"66645f74", "", 0}}},
    +        Test{
    +            {},
    +            {},
    +            {{// Generated with: tools/codegen/core/gen_header_frame.py
    +              // --compression inc --output hexstr --no_framing <
    +              // test/core/transport/chttp2/MiXeD-CaSe.headers
    +              "400a4d695865442d436153651073686f756c64206e6f74207061727365",
    +              absl::InternalError("Illegal header key: MiXeD-CaSe"), 0},
    +             {// Looking up with hpack indices should work, but also return
    +              // error
    +              "be", absl::InternalError("Illegal header key: MiXeD-CaSe"), 0}}},
    +        Test{
    +            {},
    +            {},
    +            {{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    +              absl::InternalError("integer overflow in hpack integer decoding"),
    +              kFailureIsConnectionError}}},
    +        Test{{},
    +             {},
    +             {{"dadadadadadadadadadadadadadadadadadadadadadadadadadadadadadadad"
    +               "adadadadadadadadadadadadadadadadadadadadadadadadadadadadadadada"
    +               "dadadadadadadadadadadadadadadadadadadadadadadadadadadadadadadad"
    +               "adadadadadadadadadadadadadadadadadadada",
    +               absl::InternalError("Invalid HPACK index received"),
    +               kWithPriority | kFailureIsConnectionError}}}));
    +
    +}  // namespace
    +}  // namespace grpc_core
     
     int main(int argc, char** argv) {
       grpc::testing::TestEnvironment env(&argc, argv);
    
  • test/core/transport/chttp2/hpack_sync_corpus/clusterfuzz-testcase-minimized-hpack_sync_fuzzer-5224520566571008.fuzz+44 0 added
    @@ -0,0 +1,44 @@
    +headers {
    +}
    +headers {
    +}
    +headers {
    +  literal_inc_idx {
    +    key: ":scheme"
    +    value: "grpc-taOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO\"\n    value: \"`````````````````````````````````````````````````````````````````````\"\n  }\n}\nheaders {\n}\nheaders {\n  indexed: 2\n}\nheaders {\n  indexed: 2\n}\nheaders {\n  literal_inc_idx {\n    key: \"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              z                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       OOOOOOOOOOOOOOOOOOO\"\n    value: \"`````````````gs-bin"
    +  }
    +}
    +headers {
    +  literal_inc_idx {
    +    key: "user-agent"
    +    value: "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"
    +  }
    +}
    +headers {
    +  literal_not_idx {
    +    value: "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"
    +  }
    +}
    +headers {
    +  indexed: 62
    +}
    +headers {
    +  indexed: 62
    +}
    +headers {
    +  indexed: 62
    +}
    +headers {
    +  indexed: 62
    +}
    +headers {
    +  literal_not_idx {
    +    value: "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"
    +  }
    +}
    +headers {
    +  indexed: 8
    +}
    +headers {
    +  indexed: 62
    +}
    
  • test/core/transport/chttp2/hpack_sync_corpus/crash-0c85d3a3dad81ec97be1a3079ff93f17c25d9723+10 0 added
    @@ -0,0 +1,10 @@
    +headers {
    +  literal_not_idx {
    +    key: "\000\000\000\026"
    +  }
    +}
    +headers {
    +  literal_inc_idx {
    +    value: "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
    +  }
    +}
    
  • test/core/transport/chttp2/hpack_sync_corpus/crash-212b1a7ccb2034b7f21be3b413c2de51fcacef79+14 0 added
    @@ -0,0 +1,14 @@
    +headers {
    +  literal_not_idx_from_idx {
    +  }
    +}
    +headers {
    +  literal_not_idx_from_idx {
    +    index: 18688
    +  }
    +}
    +headers {
    +  literal_inc_idx {
    +    key: "\001\000\000\000\000\000\000\003"
    +  }
    +}
    
  • test/core/transport/chttp2/hpack_sync_corpus/crash-298b34c2c15ac7b7fe8174017265d7e3b3313804+12 0 added
    @@ -0,0 +1,12 @@
    +headers {
    +  literal_inc_idx {
    +    value: "\013"
    +  }
    +}
    +headers {
    +}
    +headers {
    +  literal_inc_idx {
    +    value: "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
    +  }
    +}
    
  • test/core/transport/chttp2/hpack_sync_corpus/crash-5d27241617bb39dc456910aa4fa18431f2458dc4+9 0 added
    @@ -0,0 +1,9 @@
    +headers {
    +}
    +headers {
    +}
    +headers {
    +  literal_inc_idx {
    +    key: "E"
    +  }
    +}
    
  • test/core/transport/chttp2/hpack_sync_corpus/crash-85f9f9c7c971ec3fa839df8b14b4bad15d13f4ea+24 0 added
    @@ -0,0 +1,24 @@
    +use_true_binary_metadata: true
    +headers {
    +  literal_not_idx {
    +    key: "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
    +  }
    +}
    +headers {
    +  literal_inc_idx {
    +    key: "grpc-tags-bin"
    +    value: "grpc-tags-bin"
    +  }
    +}
    +headers {
    +  literal_inc_idx {
    +    key: "grpc-tags-bin"
    +  }
    +}
    +headers {
    +  literal_inc_idx {
    +    key: "grpc-tags-bin"
    +  }
    +}
    +headers {
    +}
    
  • test/core/transport/chttp2/hpack_sync_fuzzer.cc+173 0 added
    @@ -0,0 +1,173 @@
    +// Copyright 2023 gRPC authors.
    +//
    +// Licensed under the Apache License, Version 2.0 (the "License");
    +// you may not use this file except in compliance with the License.
    +// You may obtain a copy of the License at
    +//
    +//     http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS,
    +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +// See the License for the specific language governing permissions and
    +// limitations under the License.
    +
    +#include <inttypes.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +
    +#include <algorithm>
    +#include <memory>
    +#include <string>
    +#include <tuple>
    +#include <utility>
    +#include <vector>
    +
    +#include "absl/status/status.h"
    +#include "absl/strings/escaping.h"
    +#include "absl/strings/match.h"
    +
    +#include <grpc/support/log.h>
    +
    +#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
    +#include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h"
    +#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
    +#include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
    +#include "src/core/lib/gprpp/ref_counted_ptr.h"
    +#include "src/core/lib/gprpp/status_helper.h"
    +#include "src/core/lib/iomgr/error.h"
    +#include "src/core/lib/iomgr/exec_ctx.h"
    +#include "src/core/lib/resource_quota/arena.h"
    +#include "src/core/lib/resource_quota/memory_quota.h"
    +#include "src/core/lib/resource_quota/resource_quota.h"
    +#include "src/core/lib/slice/slice.h"
    +#include "src/core/lib/slice/slice_buffer.h"
    +#include "src/core/lib/transport/metadata_batch.h"
    +#include "src/libfuzzer/libfuzzer_macro.h"
    +#include "test/core/transport/chttp2/hpack_sync_fuzzer.pb.h"
    +
    +bool squelch = true;
    +bool leak_check = true;
    +
    +static void dont_log(gpr_log_func_args* /*args*/) {}
    +
    +namespace grpc_core {
    +namespace {
    +
    +bool IsStreamError(const absl::Status& status) {
    +  intptr_t stream_id;
    +  return grpc_error_get_int(status, StatusIntProperty::kStreamId, &stream_id);
    +}
    +
    +void FuzzOneInput(const hpack_sync_fuzzer::Msg& msg) {
    +  // STAGE 1: Encode the fuzzing input into a buffer (encode_output)
    +  HPackCompressor compressor;
    +  SliceBuffer encode_output;
    +  hpack_encoder_detail::Encoder encoder(
    +      &compressor, msg.use_true_binary_metadata(), encode_output);
    +  for (const auto& header : msg.headers()) {
    +    switch (header.ty_case()) {
    +      case hpack_sync_fuzzer::Header::TY_NOT_SET:
    +        break;
    +      case hpack_sync_fuzzer::Header::kIndexed:
    +        if (header.indexed() == 0) continue;  // invalid encoding
    +        encoder.EmitIndexed(header.indexed());
    +        break;
    +      case hpack_sync_fuzzer::Header::kLiteralIncIdx:
    +        if (header.literal_inc_idx().key().length() +
    +                header.literal_inc_idx().value().length() >
    +            HPackEncoderTable::MaxEntrySize() / 2) {
    +          // Not an interesting case to fuzz
    +          continue;
    +        }
    +        if (absl::EndsWith(header.literal_inc_idx().value(), "-bin")) {
    +          std::ignore = encoder.EmitLitHdrWithBinaryStringKeyIncIdx(
    +              Slice::FromCopiedString(header.literal_inc_idx().key()),
    +              Slice::FromCopiedString(header.literal_inc_idx().value()));
    +        } else {
    +          std::ignore = encoder.EmitLitHdrWithNonBinaryStringKeyIncIdx(
    +              Slice::FromCopiedString(header.literal_inc_idx().key()),
    +              Slice::FromCopiedString(header.literal_inc_idx().value()));
    +        }
    +        break;
    +      case hpack_sync_fuzzer::Header::kLiteralNotIdx:
    +        if (absl::EndsWith(header.literal_not_idx().value(), "-bin")) {
    +          encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
    +              Slice::FromCopiedString(header.literal_not_idx().key()),
    +              Slice::FromCopiedString(header.literal_not_idx().value()));
    +        } else {
    +          encoder.EmitLitHdrWithNonBinaryStringKeyNotIdx(
    +              Slice::FromCopiedString(header.literal_not_idx().key()),
    +              Slice::FromCopiedString(header.literal_not_idx().value()));
    +        }
    +        break;
    +      case hpack_sync_fuzzer::Header::kLiteralNotIdxFromIdx:
    +        if (header.literal_not_idx_from_idx().index() == 0) continue;
    +        encoder.EmitLitHdrWithBinaryStringKeyNotIdx(
    +            header.literal_not_idx_from_idx().index(),
    +            Slice::FromCopiedString(header.literal_not_idx_from_idx().value()));
    +        break;
    +    }
    +  }
    +
    +  // STAGE 2: Decode the buffer (encode_output) into a list of headers
    +  HPackParser parser;
    +  auto memory_allocator =
    +      ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator(
    +          "test-allocator");
    +  auto arena = MakeScopedArena(1024, &memory_allocator);
    +  ExecCtx exec_ctx;
    +  grpc_metadata_batch read_metadata(arena.get());
    +  parser.BeginFrame(
    +      &read_metadata, 1024, 1024, HPackParser::Boundary::EndOfHeaders,
    +      HPackParser::Priority::None,
    +      HPackParser::LogInfo{1, HPackParser::LogInfo::kHeaders, false});
    +  std::vector<std::pair<size_t, absl::Status>> seen_errors;
    +  for (size_t i = 0; i < encode_output.Count(); i++) {
    +    auto err = parser.Parse(encode_output.c_slice_at(i),
    +                            i == (encode_output.Count() - 1));
    +    if (!err.ok()) {
    +      seen_errors.push_back(std::make_pair(i, err));
    +      // If we get a connection error (i.e. not a stream error), stop parsing,
    +      // return.
    +      if (!IsStreamError(err)) return;
    +    }
    +  }
    +
    +  // STAGE 3: If we reached here we either had a stream error or no error
    +  // parsing.
    +  // Either way, the hpack tables should be of the same size between client and
    +  // server.
    +  const auto encoder_size = encoder.hpack_table().test_only_table_size();
    +  const auto parser_size = parser.hpack_table()->test_only_table_size();
    +  const auto encoder_elems = encoder.hpack_table().test_only_table_elems();
    +  const auto parser_elems = parser.hpack_table()->num_entries();
    +  if (encoder_size != parser_size || encoder_elems != parser_elems) {
    +    fprintf(stderr, "Encoder size: %d Parser size: %d\n", encoder_size,
    +            parser_size);
    +    fprintf(stderr, "Encoder elems: %d Parser elems: %d\n", encoder_elems,
    +            parser_elems);
    +    if (!seen_errors.empty()) {
    +      fprintf(stderr, "Seen errors during parse:\n");
    +      for (const auto& error : seen_errors) {
    +        fprintf(stderr, "  [slice %" PRIdPTR "] %s\n", error.first,
    +                error.second.ToString().c_str());
    +      }
    +    }
    +    fprintf(stderr, "Encoded data:\n");
    +    for (size_t i = 0; i < encode_output.Count(); i++) {
    +      fprintf(
    +          stderr, "  [slice %" PRIdPTR "]: %s\n", i,
    +          absl::BytesToHexString(encode_output[i].as_string_view()).c_str());
    +    }
    +    abort();
    +  }
    +}
    +
    +}  // namespace
    +}  // namespace grpc_core
    +
    +DEFINE_PROTO_FUZZER(const hpack_sync_fuzzer::Msg& msg) {
    +  if (squelch) gpr_set_log_function(dont_log);
    +  grpc_core::FuzzOneInput(msg);
    +}
    
  • test/core/transport/chttp2/hpack_sync_fuzzer.proto+43 0 added
    @@ -0,0 +1,43 @@
    +// Copyright 2023 gRPC authors.
    +//
    +// Licensed under the Apache License, Version 2.0 (the "License");
    +// you may not use this file except in compliance with the License.
    +// You may obtain a copy of the License at
    +//
    +//     http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS,
    +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +// See the License for the specific language governing permissions and
    +// limitations under the License.
    +
    +syntax = "proto3";
    +
    +package hpack_sync_fuzzer;
    +
    +message Empty {}
    +
    +message StringKeyValue {
    +    string key = 1;
    +    string value = 2;
    +}
    +
    +message IndexedKeyValue {
    +    uint32 index = 1;
    +    string value = 2;
    +}
    +
    +message Header {
    +    oneof ty {
    +        uint32 indexed = 1;
    +        StringKeyValue literal_inc_idx = 2;
    +        StringKeyValue literal_not_idx = 3;
    +        IndexedKeyValue literal_not_idx_from_idx = 4;
    +    }
    +}
    +
    +message Msg {
    +  bool use_true_binary_metadata = 1;
    +  repeated Header headers = 2;
    +}
    
  • test/core/transport/chttp2/large-metadata.headers+8 0 added
    @@ -0,0 +1,8 @@
    +# 19 -> 51
    +adaldrida: brandybuck
    +# 12 -> 95
    +adalgrim: took
    +# 18 -> 145
    +amaranth: brandybuck
    +# 15 -> 192
    +angelica: baggins
    
  • test/core/transport/chttp2/long-base64.headers+1 0 added
    @@ -0,0 +1 @@
    +x-bin: QmFzZTY0IGVuY29kaW5nOgpJdCB0YWtlcyBiaW5hcnkgZGF0YSBhbmQgbWFrZXMgaXQgdGV4dC4KVXNlZnVsIGZvciBzdG9yaW5nIGZpbGVzLg==
    
  • test/core/transport/chttp2/MiXeD-CaSe.headers+1 0 added
    @@ -0,0 +1 @@
    +MiXeD-CaSe: should not parse
    
  • test/core/transport/chttp2/short.headers+1 0 added
    @@ -0,0 +1 @@
    +a: a
    
  • test/core/transport/parsed_metadata_test.cc+18 16 modified
    @@ -223,16 +223,17 @@ INSTANTIATE_TYPED_TEST_SUITE_P(My, TraitSpecializedTest, InterestingTraits);
     TEST(KeyValueTest, Simple) {
       using PM = ParsedMetadata<grpc_metadata_batch>;
       using PMPtr = std::unique_ptr<PM>;
    -  PMPtr p = std::make_unique<PM>(Slice::FromCopiedString("key"),
    -                                 Slice::FromCopiedString("value"));
    +  PMPtr p =
    +      std::make_unique<PM>(PM::FromSlicePair{}, Slice::FromCopiedString("key"),
    +                           Slice::FromCopiedString("value"), 40);
       EXPECT_EQ(p->DebugString(), "key: value");
       EXPECT_EQ(p->transport_size(), 40);
    -  PM p2 = p->WithNewValue(Slice::FromCopiedString("some_other_value"),
    -                          [](absl::string_view msg, const Slice& value) {
    -                            ASSERT_TRUE(false)
    -                                << "Should not be called: msg=" << msg
    -                                << ", value=" << value.as_string_view();
    -                          });
    +  PM p2 = p->WithNewValue(
    +      Slice::FromCopiedString("some_other_value"), strlen("some_other_value"),
    +      [](absl::string_view msg, const Slice& value) {
    +        ASSERT_TRUE(false) << "Should not be called: msg=" << msg
    +                           << ", value=" << value.as_string_view();
    +      });
       EXPECT_EQ(p->DebugString(), "key: value");
       EXPECT_EQ(p2.DebugString(), "key: some_other_value");
       EXPECT_EQ(p2.transport_size(), 51);
    @@ -247,18 +248,19 @@ TEST(KeyValueTest, Simple) {
     TEST(KeyValueTest, LongKey) {
       using PM = ParsedMetadata<grpc_metadata_batch>;
       using PMPtr = std::unique_ptr<PM>;
    -  PMPtr p = std::make_unique<PM>(Slice::FromCopiedString(std::string(60, 'a')),
    -                                 Slice::FromCopiedString("value"));
    +  PMPtr p = std::make_unique<PM>(PM::FromSlicePair{},
    +                                 Slice::FromCopiedString(std::string(60, 'a')),
    +                                 Slice::FromCopiedString("value"), 60 + 5 + 32);
       EXPECT_EQ(
           p->DebugString(),
           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: value");
       EXPECT_EQ(p->transport_size(), 97);
    -  PM p2 = p->WithNewValue(Slice::FromCopiedString("some_other_value"),
    -                          [](absl::string_view msg, const Slice& value) {
    -                            ASSERT_TRUE(false)
    -                                << "Should not be called: msg=" << msg
    -                                << ", value=" << value.as_string_view();
    -                          });
    +  PM p2 = p->WithNewValue(
    +      Slice::FromCopiedString("some_other_value"), strlen("some_other_value"),
    +      [](absl::string_view msg, const Slice& value) {
    +        ASSERT_TRUE(false) << "Should not be called: msg=" << msg
    +                           << ", value=" << value.as_string_view();
    +      });
       EXPECT_EQ(
           p->DebugString(),
           "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: value");
    
  • test/core/util/parse_hexstring.cc+12 10 modified
    @@ -21,17 +21,18 @@
     #include <stddef.h>
     #include <stdint.h>
     
    +#include <grpc/slice.h>
     #include <grpc/support/log.h>
     
    -grpc_slice parse_hexstring(const char* hexstring) {
    +namespace grpc_core {
    +Slice ParseHexstring(absl::string_view hexstring) {
       size_t nibbles = 0;
    -  const char* p = nullptr;
       uint8_t* out;
       uint8_t temp;
       grpc_slice slice;
     
    -  for (p = hexstring; *p; p++) {
    -    nibbles += (*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f');
    +  for (auto c : hexstring) {
    +    nibbles += (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
       }
     
       GPR_ASSERT((nibbles & 1) == 0);
    @@ -41,13 +42,13 @@ grpc_slice parse_hexstring(const char* hexstring) {
     
       nibbles = 0;
       temp = 0;
    -  for (p = hexstring; *p; p++) {
    -    if (*p >= '0' && *p <= '9') {
    -      temp = static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(*p - '0');
    +  for (auto c : hexstring) {
    +    if (c >= '0' && c <= '9') {
    +      temp = static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(c - '0');
           nibbles++;
    -    } else if (*p >= 'a' && *p <= 'f') {
    +    } else if (c >= 'a' && c <= 'f') {
           temp =
    -          static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(*p - 'a' + 10);
    +          static_cast<uint8_t>(temp << 4) | static_cast<uint8_t>(c - 'a' + 10);
           nibbles++;
         }
         if (nibbles == 2) {
    @@ -56,5 +57,6 @@ grpc_slice parse_hexstring(const char* hexstring) {
         }
       }
     
    -  return slice;
    +  return Slice(slice);
     }
    +}  // namespace grpc_core
    
  • test/core/util/parse_hexstring.h+6 2 modified
    @@ -19,8 +19,12 @@
     #ifndef GRPC_TEST_CORE_UTIL_PARSE_HEXSTRING_H
     #define GRPC_TEST_CORE_UTIL_PARSE_HEXSTRING_H
     
    -#include <grpc/slice.h>
    +#include "absl/strings/string_view.h"
     
    -grpc_slice parse_hexstring(const char* hexstring);
    +#include "src/core/lib/slice/slice.h"
    +
    +namespace grpc_core {
    +Slice ParseHexstring(absl::string_view hexstring);
    +}
     
     #endif  // GRPC_TEST_CORE_UTIL_PARSE_HEXSTRING_H
    
  • test/cpp/end2end/grpclb_end2end_test.cc+2 2 modified
    @@ -102,8 +102,8 @@ constexpr char kDefaultServiceConfig[] =
     using BackendService = CountedService<TestServiceImpl>;
     using BalancerService = CountedService<LoadBalancer::Service>;
     
    -const char g_kCallCredsMdKey[] = "Balancer should not ...";
    -const char g_kCallCredsMdValue[] = "... receive me";
    +const char g_kCallCredsMdKey[] = "call-creds";
    +const char g_kCallCredsMdValue[] = "should not be received by balancer";
     
     // A test user agent string sent by the client only to the grpclb loadbalancer.
     // The backend should not see this user-agent string.
    
  • tools/codegen/core/gen_header_frame.py+63 37 modified
    @@ -24,36 +24,80 @@
     import sys
     
     
    -def append_never_indexed(payload_line, n, count, key, value):
    +def append_never_indexed(payload_line, n, count, key, value, value_is_huff):
         payload_line.append(0x10)
         assert (len(key) <= 126)
         payload_line.append(len(key))
         payload_line.extend(ord(c) for c in key)
         assert (len(value) <= 126)
    -    payload_line.append(len(value))
    -    payload_line.extend(ord(c) for c in value)
    +    payload_line.append(len(value) + (0x80 if value_is_huff else 0))
    +    payload_line.extend(value)
     
     
    -def append_inc_indexed(payload_line, n, count, key, value):
    +def append_inc_indexed(payload_line, n, count, key, value, value_is_huff):
         payload_line.append(0x40)
         assert (len(key) <= 126)
         payload_line.append(len(key))
         payload_line.extend(ord(c) for c in key)
         assert (len(value) <= 126)
    -    payload_line.append(len(value))
    -    payload_line.extend(ord(c) for c in value)
    +    payload_line.append(len(value) + (0x80 if value_is_huff else 0))
    +    payload_line.extend(value)
     
     
    -def append_pre_indexed(payload_line, n, count, key, value):
    +def append_pre_indexed(payload_line, n, count, key, value, value_is_huff):
    +    assert not value_is_huff
         payload_line.append(0x80 + 61 + count - n)
     
     
    +def esc_c(line):
    +    out = "\""
    +    last_was_hex = False
    +    for c in line:
    +        if 32 <= c < 127:
    +            if c in hex_bytes and last_was_hex:
    +                out += "\"\""
    +            if c != ord('"'):
    +                out += chr(c)
    +            else:
    +                out += "\\\""
    +            last_was_hex = False
    +        else:
    +            out += "\\x%02x" % c
    +            last_was_hex = True
    +    return out + "\""
    +
    +
    +def output_c(payload_bytes):
    +    for line in payload_bytes:
    +        print((esc_c(line)))
    +
    +
    +def output_hex(payload_bytes):
    +    all_bytes = []
    +    for line in payload_bytes:
    +        all_bytes.extend(line)
    +    print(('{%s}' % ', '.join('0x%02x' % c for c in all_bytes)))
    +
    +
    +def output_hexstr(payload_bytes):
    +    all_bytes = []
    +    for line in payload_bytes:
    +        all_bytes.extend(line)
    +    print(('%s' % ''.join('%02x' % c for c in all_bytes)))
    +
    +
     _COMPRESSORS = {
         'never': append_never_indexed,
         'inc': append_inc_indexed,
         'pre': append_pre_indexed,
     }
     
    +_OUTPUTS = {
    +    'c': output_c,
    +    'hex': output_hex,
    +    'hexstr': output_hexstr,
    +}
    +
     argp = argparse.ArgumentParser('Generate header frames')
     argp.add_argument('--set_end_stream',
                       default=False,
    @@ -66,7 +110,8 @@ def append_pre_indexed(payload_line, n, count, key, value):
     argp.add_argument('--compression',
                       choices=sorted(_COMPRESSORS.keys()),
                       default='never')
    -argp.add_argument('--hex', default=False, action='store_const', const=True)
    +argp.add_argument('--huff', default=False, action='store_const', const=True)
    +argp.add_argument('--output', default='c', choices=sorted(_OUTPUTS.keys()))
     args = argp.parse_args()
     
     # parse input, fill in vals
    @@ -79,7 +124,13 @@ def append_pre_indexed(payload_line, n, count, key, value):
             continue
         key_tail, value = line[1:].split(':')
         key = (line[0] + key_tail).strip()
    -    value = value.strip()
    +    value = value.strip().encode('ascii')
    +    if args.huff:
    +        from hpack.huffman import HuffmanEncoder
    +        from hpack.huffman_constants import REQUEST_CODES
    +        from hpack.huffman_constants import REQUEST_CODES_LENGTH
    +        value = HuffmanEncoder(REQUEST_CODES,
    +                               REQUEST_CODES_LENGTH).encode(value)
         vals.append((key, value))
     
     # generate frame payload binary data
    @@ -90,7 +141,8 @@ def append_pre_indexed(payload_line, n, count, key, value):
     n = 0
     for key, value in vals:
         payload_line = []
    -    _COMPRESSORS[args.compression](payload_line, n, len(vals), key, value)
    +    _COMPRESSORS[args.compression](payload_line, n, len(vals), key, value,
    +                                   args.huff)
         n += 1
         payload_len += len(payload_line)
         payload_bytes.append(payload_line)
    @@ -117,31 +169,5 @@ def append_pre_indexed(payload_line, n, count, key, value):
     
     hex_bytes = [ord(c) for c in "abcdefABCDEF0123456789"]
     
    -
    -def esc_c(line):
    -    out = "\""
    -    last_was_hex = False
    -    for c in line:
    -        if 32 <= c < 127:
    -            if c in hex_bytes and last_was_hex:
    -                out += "\"\""
    -            if c != ord('"'):
    -                out += chr(c)
    -            else:
    -                out += "\\\""
    -            last_was_hex = False
    -        else:
    -            out += "\\x%02x" % c
    -            last_was_hex = True
    -    return out + "\""
    -
    -
     # dump bytes
    -if args.hex:
    -    all_bytes = []
    -    for line in payload_bytes:
    -        all_bytes.extend(line)
    -    print(('{%s}' % ', '.join('0x%02x' % c for c in all_bytes)))
    -else:
    -    for line in payload_bytes:
    -        print((esc_c(line)))
    +_OUTPUTS[args.output](payload_bytes)
    
29d8beee0ac2

[http2] Dont drop connections on metadata limit exceeded (#32309)

https://github.com/grpc/grpcCraig TillerFeb 7, 2023via ghsa
10 files changed · +128 418
  • build_autogenerated.yaml+0 13 modified
    @@ -9089,19 +9089,6 @@ targets:
       - test/core/surface/lame_client_test.cc
       deps:
       - grpc_test_util
    -- name: large_metadata_bad_client_test
    -  gtest: true
    -  build: test
    -  language: c++
    -  headers:
    -  - test/core/bad_client/bad_client.h
    -  - test/core/end2end/cq_verifier.h
    -  src:
    -  - test/core/bad_client/bad_client.cc
    -  - test/core/bad_client/tests/large_metadata.cc
    -  - test/core/end2end/cq_verifier.cc
    -  deps:
    -  - grpc_test_util
     - name: latch_test
       gtest: true
       build: test
    
  • CMakeLists.txt+0 40 modified
    @@ -1034,7 +1034,6 @@ if(gRPC_BUILD_TESTS)
       add_dependencies(buildtests_cxx json_token_test)
       add_dependencies(buildtests_cxx jwt_verifier_test)
       add_dependencies(buildtests_cxx lame_client_test)
    -  add_dependencies(buildtests_cxx large_metadata_bad_client_test)
       add_dependencies(buildtests_cxx latch_test)
       add_dependencies(buildtests_cxx lb_get_cpu_stats_test)
       add_dependencies(buildtests_cxx lb_load_data_store_test)
    @@ -14456,45 +14455,6 @@ target_link_libraries(lame_client_test
     )
     
     
    -endif()
    -if(gRPC_BUILD_TESTS)
    -
    -add_executable(large_metadata_bad_client_test
    -  test/core/bad_client/bad_client.cc
    -  test/core/bad_client/tests/large_metadata.cc
    -  test/core/end2end/cq_verifier.cc
    -  third_party/googletest/googletest/src/gtest-all.cc
    -  third_party/googletest/googlemock/src/gmock-all.cc
    -)
    -target_compile_features(large_metadata_bad_client_test PUBLIC cxx_std_14)
    -target_include_directories(large_metadata_bad_client_test
    -  PRIVATE
    -    ${CMAKE_CURRENT_SOURCE_DIR}
    -    ${CMAKE_CURRENT_SOURCE_DIR}/include
    -    ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
    -    ${_gRPC_RE2_INCLUDE_DIR}
    -    ${_gRPC_SSL_INCLUDE_DIR}
    -    ${_gRPC_UPB_GENERATED_DIR}
    -    ${_gRPC_UPB_GRPC_GENERATED_DIR}
    -    ${_gRPC_UPB_INCLUDE_DIR}
    -    ${_gRPC_XXHASH_INCLUDE_DIR}
    -    ${_gRPC_ZLIB_INCLUDE_DIR}
    -    third_party/googletest/googletest/include
    -    third_party/googletest/googletest
    -    third_party/googletest/googlemock/include
    -    third_party/googletest/googlemock
    -    ${_gRPC_PROTO_GENS_DIR}
    -)
    -
    -target_link_libraries(large_metadata_bad_client_test
    -  ${_gRPC_BASELIB_LIBRARIES}
    -  ${_gRPC_PROTOBUF_LIBRARIES}
    -  ${_gRPC_ZLIB_LIBRARIES}
    -  ${_gRPC_ALLTARGETS_LIBRARIES}
    -  grpc_test_util
    -)
    -
    -
     endif()
     if(gRPC_BUILD_TESTS)
     
    
  • src/core/ext/transport/chttp2/transport/hpack_parser.cc+10 4 modified
    @@ -1227,13 +1227,19 @@ class HPackParser::Parser {
             absl::StrCat("; adding ", md.key(), " (length ", md.transport_size(),
                          "B)", summary.empty() ? "" : " to ", summary);
         if (metadata_buffer_ != nullptr) metadata_buffer_->Clear();
    +    // StreamId is used as a signal to skip this stream but keep the connection
    +    // alive
         return input_->MaybeSetErrorAndReturn(
             [this, summary = std::move(summary)] {
               return grpc_error_set_int(
    -              GRPC_ERROR_CREATE(absl::StrCat(
    -                  "received initial metadata size exceeds limit (",
    -                  *frame_length_, " vs. ", metadata_size_limit_, ")", summary)),
    -              StatusIntProperty::kRpcStatus, GRPC_STATUS_RESOURCE_EXHAUSTED);
    +              grpc_error_set_int(
    +                  GRPC_ERROR_CREATE(absl::StrCat(
    +                      "received initial metadata size exceeds limit (",
    +                      *frame_length_, " vs. ", metadata_size_limit_, ")",
    +                      summary)),
    +                  StatusIntProperty::kRpcStatus,
    +                  GRPC_STATUS_RESOURCE_EXHAUSTED),
    +              StatusIntProperty::kStreamId, 0);
             },
             false);
       }
    
  • src/core/ext/transport/chttp2/transport/internal.h+0 2 modified
    @@ -567,8 +567,6 @@ struct grpc_chttp2_stream {
     
       grpc_core::Timestamp deadline = grpc_core::Timestamp::InfFuture();
     
    -  /// saw some stream level error
    -  grpc_error_handle forced_close_error;
       /// how many header frames have we received?
       uint8_t header_frames_received = 0;
       /// number of bytes received - reset at end of parse thread execution
    
  • src/core/ext/transport/chttp2/transport/parsing.cc+2 4 modified
    @@ -23,6 +23,7 @@
     
     #include <initializer_list>
     #include <string>
    +#include <utility>
     
     #include "absl/base/attributes.h"
     #include "absl/status/status.h"
    @@ -807,10 +808,7 @@ static grpc_error_handle parse_frame_slice(grpc_chttp2_transport* t,
                              &unused)) {
         grpc_chttp2_parsing_become_skip_parser(t);
         if (s) {
    -      s->forced_close_error = err;
    -      grpc_chttp2_add_rst_stream_to_next_write(t, t->incoming_stream_id,
    -                                               GRPC_HTTP2_PROTOCOL_ERROR,
    -                                               &s->stats.outgoing);
    +      grpc_chttp2_cancel_stream(t, s, std::exchange(err, absl::OkStatus()));
         }
       }
       return err;
    
  • test/core/bad_client/generate_tests.bzl+0 1 modified
    @@ -29,7 +29,6 @@ BAD_CLIENT_TESTS = {
         "headers": test_options(),
         "initial_settings_frame": test_options(),
         "head_of_line_blocking": test_options(),
    -    "large_metadata": test_options(),
         "out_of_bounds": test_options(),
         "server_registered_method": test_options(),
         "simple_request": test_options(),
    
  • test/core/bad_client/tests/large_metadata.cc+0 112 removed
    @@ -1,112 +0,0 @@
    -//
    -//
    -// Copyright 2015 gRPC authors.
    -//
    -// Licensed under the Apache License, Version 2.0 (the "License");
    -// you may not use this file except in compliance with the License.
    -// You may obtain a copy of the License at
    -//
    -//     http://www.apache.org/licenses/LICENSE-2.0
    -//
    -// Unless required by applicable law or agreed to in writing, software
    -// distributed under the License is distributed on an "AS IS" BASIS,
    -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    -// See the License for the specific language governing permissions and
    -// limitations under the License.
    -//
    -//
    -
    -#include <string.h>
    -
    -#include <algorithm>
    -#include <initializer_list>
    -#include <string>
    -#include <vector>
    -
    -#include "absl/strings/str_format.h"
    -#include "absl/strings/str_join.h"
    -
    -#include <grpc/grpc.h>
    -#include <grpc/support/log.h>
    -
    -#include "src/core/lib/surface/server.h"
    -#include "test/core/bad_client/bad_client.h"
    -#include "test/core/util/test_config.h"
    -
    -// The large-metadata headers that we're adding for this test are not
    -// actually appended to this in a single string, since the string would
    -// be longer than the C99 string literal limit.  Instead, we dynamically
    -// construct it by adding the large headers one at a time.
    -
    -// headers: generated from  large_metadata.headers in this directory
    -#define PFX_TOO_MUCH_METADATA_FROM_CLIENT_REQUEST         \
    -  "\x00\x00\x00\x04\x01\x00\x00\x00\x00"                  \
    -  "\x00"                                                  \
    -  "5{\x01\x05\x00\x00\x00\x01"                            \
    -  "\x10\x05:path\x08/foo/bar"                             \
    -  "\x10\x07:scheme\x04http"                               \
    -  "\x10\x07:method\x04POST"                               \
    -  "\x10\x0a:authority\x09localhost"                       \
    -  "\x10\x0c"                                              \
    -  "content-type\x10"                                      \
    -  "application/grpc"                                      \
    -  "\x10\x14grpc-accept-encoding\x15identity,deflate,gzip" \
    -  "\x10\x02te\x08trailers"                                \
    -  "\x10\x0auser-agent\"bad-client grpc-c/0.12.0.0 (linux)"
    -
    -// Each large-metadata header is constructed from these start and end
    -// strings, with a two-digit number in between.
    -#define PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_START_STR "\x10\x0duser-header"
    -#define PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_END_STR                   \
    -  "~aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
    -  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    -
    -// The size of each large-metadata header string.
    -#define PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_SIZE                     \
    -  ((sizeof(PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_START_STR) - 1) + 2 + \
    -   (sizeof(PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_END_STR) - 1))
    -
    -// The number of headers we're adding and the total size of the client
    -// payload.
    -#define NUM_HEADERS 46
    -#define TOO_MUCH_METADATA_FROM_CLIENT_REQUEST_SIZE           \
    -  ((sizeof(PFX_TOO_MUCH_METADATA_FROM_CLIENT_REQUEST) - 1) + \
    -   (NUM_HEADERS * PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_SIZE) + 1)
    -
    -static void verifier_fails(grpc_server* server, grpc_completion_queue* cq,
    -                           void* /*registered_method*/) {
    -  while (grpc_core::Server::FromC(server)->HasOpenConnections()) {
    -    GPR_ASSERT(grpc_completion_queue_next(
    -                   cq, grpc_timeout_milliseconds_to_deadline(20), nullptr)
    -                   .type == GRPC_QUEUE_TIMEOUT);
    -  }
    -}
    -
    -int main(int argc, char** argv) {
    -  int i;
    -  grpc_init();
    -  grpc::testing::TestEnvironment env(&argc, argv);
    -
    -  // Test sending more metadata than the server will accept.
    -  std::vector<std::string> headers;
    -  for (i = 0; i < NUM_HEADERS; ++i) {
    -    headers.push_back(absl::StrFormat(
    -        "%s%02d%s", PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_START_STR, i,
    -        PFX_TOO_MUCH_METADATA_FROM_CLIENT_HEADER_END_STR));
    -  }
    -  std::string client_headers = absl::StrJoin(headers, "");
    -  char client_payload[TOO_MUCH_METADATA_FROM_CLIENT_REQUEST_SIZE] =
    -      PFX_TOO_MUCH_METADATA_FROM_CLIENT_REQUEST;
    -  memcpy(client_payload + sizeof(PFX_TOO_MUCH_METADATA_FROM_CLIENT_REQUEST) - 1,
    -         client_headers.data(), client_headers.size());
    -  grpc_bad_client_arg args[2];
    -  args[0] = connection_preface_arg;
    -  args[1].client_validator = rst_stream_client_validator;
    -  args[1].client_payload = client_payload;
    -  args[1].client_payload_length = sizeof(client_payload) - 1;
    -
    -  grpc_run_bad_client_test(verifier_fails, args, 2, 0);
    -
    -  grpc_shutdown();
    -  return 0;
    -}
    
  • test/core/bad_client/tests/large_metadata.headers+0 106 removed
    @@ -1,106 +0,0 @@
    -# headers used in simple_request.c
    -# use tools/codegen/core/gen_header_frame.py --set_end_stream to generate
    -# the binary strings contained in the source code
    -:path: /foo/bar
    -:scheme: http
    -:method: POST
    -:authority: localhost
    -content-type: application/grpc
    -grpc-accept-encoding: identity,deflate,gzip
    -te: trailers
    -user-agent: bad-client grpc-c/0.12.0.0 (linux)
    -user-header00: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header01: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header02: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header03: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header04: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header05: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header06: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header07: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header08: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header09: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header10: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header13: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header14: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header15: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header16: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header17: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header18: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header20: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header21: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header22: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header23: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header24: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header25: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header26: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header27: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header28: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header29: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header30: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header31: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header32: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header33: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header34: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header35: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header36: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header37: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header38: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header39: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header40: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header41: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header42: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header43: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header44: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header45: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header46: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header47: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header48: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header49: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header50: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header51: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header52: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header53: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header54: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header55: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header56: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header57: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header58: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header59: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header60: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header61: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header62: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header63: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header64: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header65: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header66: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header67: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header68: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header69: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header70: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header71: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header72: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header73: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header74: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header75: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header76: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header77: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header78: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header79: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header80: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header81: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header82: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header83: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header84: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header85: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header86: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header87: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header88: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header89: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header90: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header91: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header92: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header93: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    -user-header94: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    
  • test/core/end2end/tests/large_metadata.cc+116 112 modified
    @@ -250,10 +250,6 @@ static void test_request_with_large_metadata(grpc_end2end_test_config config) {
     // Server responds with metadata larger than what the client accepts.
     static void test_request_with_bad_large_metadata_response(
         grpc_end2end_test_config config) {
    -  grpc_call* c;
    -  grpc_call* s;
    -  grpc_metadata meta;
    -  const size_t large_size = 64 * 1024;
       grpc_arg arg;
       arg.type = GRPC_ARG_INTEGER;
       arg.key = const_cast<char*>(GRPC_ARG_MAX_METADATA_SIZE);
    @@ -262,115 +258,123 @@ static void test_request_with_bad_large_metadata_response(
       grpc_end2end_test_fixture f = begin_test(
           config, "test_request_with_bad_large_metadata_response", &args, &args);
       grpc_core::CqVerifier cqv(f.cq);
    -  grpc_op ops[6];
    -  grpc_op* op;
    -  grpc_metadata_array initial_metadata_recv;
    -  grpc_metadata_array trailing_metadata_recv;
    -  grpc_metadata_array request_metadata_recv;
    -  grpc_call_details call_details;
    -  grpc_status_code status;
    -  grpc_call_error error;
    -  grpc_slice details;
    -  int was_cancelled = 2;
    -
    -  gpr_timespec deadline = five_seconds_from_now();
    -  c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS, f.cq,
    -                               grpc_slice_from_static_string("/foo"), nullptr,
    -                               deadline, nullptr);
    -  GPR_ASSERT(c);
    -
    -  meta.key = grpc_slice_from_static_string("key");
    -  meta.value = grpc_slice_malloc(large_size);
    -  memset(GRPC_SLICE_START_PTR(meta.value), 'a', large_size);
    -
    -  grpc_metadata_array_init(&initial_metadata_recv);
    -  grpc_metadata_array_init(&trailing_metadata_recv);
    -  grpc_metadata_array_init(&request_metadata_recv);
    -  grpc_call_details_init(&call_details);
    -
    -  memset(ops, 0, sizeof(ops));
    -  // Client: send request.
    -  op = ops;
    -  op->op = GRPC_OP_SEND_INITIAL_METADATA;
    -  op->data.send_initial_metadata.count = 0;
    -  op->flags = 0;
    -  op->reserved = nullptr;
    -  op++;
    -  op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
    -  op->flags = 0;
    -  op->reserved = nullptr;
    -  op++;
    -  op->op = GRPC_OP_RECV_INITIAL_METADATA;
    -  op->data.recv_initial_metadata.recv_initial_metadata = &initial_metadata_recv;
    -  op->flags = 0;
    -  op->reserved = nullptr;
    -  op++;
    -  op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
    -  op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv;
    -  op->data.recv_status_on_client.status = &status;
    -  op->data.recv_status_on_client.status_details = &details;
    -  op->flags = 0;
    -  op->reserved = nullptr;
    -  op++;
    -  error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
    -                                nullptr);
    -  GPR_ASSERT(GRPC_CALL_OK == error);
    -
    -  error =
    -      grpc_server_request_call(f.server, &s, &call_details,
    -                               &request_metadata_recv, f.cq, f.cq, tag(101));
    -  GPR_ASSERT(GRPC_CALL_OK == error);
     
    -  cqv.Expect(tag(101), true);
    -  cqv.Verify();
    -
    -  memset(ops, 0, sizeof(ops));
    -  // Server: send large initial metadata
    -  op = ops;
    -  op->op = GRPC_OP_SEND_INITIAL_METADATA;
    -  op->data.send_initial_metadata.count = 1;
    -  op->data.send_initial_metadata.metadata = &meta;
    -  op->flags = 0;
    -  op->reserved = nullptr;
    -  op++;
    -  op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
    -  op->data.recv_close_on_server.cancelled = &was_cancelled;
    -  op->flags = 0;
    -  op->reserved = nullptr;
    -  op++;
    -  op->op = GRPC_OP_SEND_STATUS_FROM_SERVER;
    -  op->data.send_status_from_server.trailing_metadata_count = 0;
    -  op->data.send_status_from_server.status = GRPC_STATUS_OK;
    -  grpc_slice status_details = grpc_slice_from_static_string("xyz");
    -  op->data.send_status_from_server.status_details = &status_details;
    -  op->flags = 0;
    -  op->reserved = nullptr;
    -  op++;
    -  error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops), tag(102),
    -                                nullptr);
    -  GPR_ASSERT(GRPC_CALL_OK == error);
    -  cqv.Expect(tag(102), true);
    -  cqv.Expect(tag(1), true);
    -  cqv.Verify();
    -
    -  GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
    -  const char* expected_error = "received initial metadata size exceeds limit";
    -  grpc_slice actual_error =
    -      grpc_slice_split_head(&details, strlen(expected_error));
    -  GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
    -  GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
    -
    -  grpc_slice_unref(actual_error);
    -  grpc_slice_unref(details);
    -  grpc_metadata_array_destroy(&initial_metadata_recv);
    -  grpc_metadata_array_destroy(&trailing_metadata_recv);
    -  grpc_metadata_array_destroy(&request_metadata_recv);
    -  grpc_call_details_destroy(&call_details);
    -
    -  grpc_call_unref(c);
    -  grpc_call_unref(s);
    -
    -  grpc_slice_unref(meta.value);
    +  for (int i = 0; i < 10; i++) {
    +    grpc_call* c;
    +    grpc_call* s;
    +    grpc_metadata meta;
    +    const size_t large_size = 64 * 1024;
    +    grpc_op ops[6];
    +    grpc_op* op;
    +    grpc_metadata_array initial_metadata_recv;
    +    grpc_metadata_array trailing_metadata_recv;
    +    grpc_metadata_array request_metadata_recv;
    +    grpc_call_details call_details;
    +    grpc_status_code status;
    +    grpc_call_error error;
    +    grpc_slice details;
    +    int was_cancelled = 2;
    +
    +    gpr_timespec deadline = five_seconds_from_now();
    +    c = grpc_channel_create_call(f.client, nullptr, GRPC_PROPAGATE_DEFAULTS,
    +                                 f.cq, grpc_slice_from_static_string("/foo"),
    +                                 nullptr, deadline, nullptr);
    +    GPR_ASSERT(c);
    +
    +    meta.key = grpc_slice_from_static_string("key");
    +    meta.value = grpc_slice_malloc(large_size);
    +    memset(GRPC_SLICE_START_PTR(meta.value), 'a', large_size);
    +
    +    grpc_metadata_array_init(&initial_metadata_recv);
    +    grpc_metadata_array_init(&trailing_metadata_recv);
    +    grpc_metadata_array_init(&request_metadata_recv);
    +    grpc_call_details_init(&call_details);
    +
    +    memset(ops, 0, sizeof(ops));
    +    // Client: send request.
    +    op = ops;
    +    op->op = GRPC_OP_SEND_INITIAL_METADATA;
    +    op->data.send_initial_metadata.count = 0;
    +    op->flags = 0;
    +    op->reserved = nullptr;
    +    op++;
    +    op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
    +    op->flags = 0;
    +    op->reserved = nullptr;
    +    op++;
    +    op->op = GRPC_OP_RECV_INITIAL_METADATA;
    +    op->data.recv_initial_metadata.recv_initial_metadata =
    +        &initial_metadata_recv;
    +    op->flags = 0;
    +    op->reserved = nullptr;
    +    op++;
    +    op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
    +    op->data.recv_status_on_client.trailing_metadata = &trailing_metadata_recv;
    +    op->data.recv_status_on_client.status = &status;
    +    op->data.recv_status_on_client.status_details = &details;
    +    op->flags = 0;
    +    op->reserved = nullptr;
    +    op++;
    +    error = grpc_call_start_batch(c, ops, static_cast<size_t>(op - ops), tag(1),
    +                                  nullptr);
    +    GPR_ASSERT(GRPC_CALL_OK == error);
    +
    +    error =
    +        grpc_server_request_call(f.server, &s, &call_details,
    +                                 &request_metadata_recv, f.cq, f.cq, tag(101));
    +    GPR_ASSERT(GRPC_CALL_OK == error);
    +
    +    cqv.Expect(tag(101), true);
    +    cqv.Verify();
    +
    +    memset(ops, 0, sizeof(ops));
    +    // Server: send large initial metadata
    +    op = ops;
    +    op->op = GRPC_OP_SEND_INITIAL_METADATA;
    +    op->data.send_initial_metadata.count = 1;
    +    op->data.send_initial_metadata.metadata = &meta;
    +    op->flags = 0;
    +    op->reserved = nullptr;
    +    op++;
    +    op->op = GRPC_OP_RECV_CLOSE_ON_SERVER;
    +    op->data.recv_close_on_server.cancelled = &was_cancelled;
    +    op->flags = 0;
    +    op->reserved = nullptr;
    +    op++;
    +    op->op = GRPC_OP_SEND_STATUS_FROM_SERVER;
    +    op->data.send_status_from_server.trailing_metadata_count = 0;
    +    op->data.send_status_from_server.status = GRPC_STATUS_OK;
    +    grpc_slice status_details = grpc_slice_from_static_string("xyz");
    +    op->data.send_status_from_server.status_details = &status_details;
    +    op->flags = 0;
    +    op->reserved = nullptr;
    +    op++;
    +    error = grpc_call_start_batch(s, ops, static_cast<size_t>(op - ops),
    +                                  tag(102), nullptr);
    +    GPR_ASSERT(GRPC_CALL_OK == error);
    +    cqv.Expect(tag(102), true);
    +    cqv.Expect(tag(1), true);
    +    cqv.Verify();
    +
    +    GPR_ASSERT(status == GRPC_STATUS_RESOURCE_EXHAUSTED);
    +    const char* expected_error = "received initial metadata size exceeds limit";
    +    grpc_slice actual_error =
    +        grpc_slice_split_head(&details, strlen(expected_error));
    +    GPR_ASSERT(0 == grpc_slice_str_cmp(actual_error, expected_error));
    +    GPR_ASSERT(0 == grpc_slice_str_cmp(call_details.method, "/foo"));
    +
    +    grpc_slice_unref(actual_error);
    +    grpc_slice_unref(details);
    +    grpc_metadata_array_destroy(&initial_metadata_recv);
    +    grpc_metadata_array_destroy(&trailing_metadata_recv);
    +    grpc_metadata_array_destroy(&request_metadata_recv);
    +    grpc_call_details_destroy(&call_details);
    +
    +    grpc_call_unref(c);
    +    grpc_call_unref(s);
    +
    +    grpc_slice_unref(meta.value);
    +  }
     
       end_test(&f);
       config.tear_down_data(&f);
    
  • tools/run_tests/generated/tests.json+0 24 modified
    @@ -4407,30 +4407,6 @@
         ],
         "uses_polling": true
       },
    -  {
    -    "args": [],
    -    "benchmark": false,
    -    "ci_platforms": [
    -      "linux",
    -      "mac",
    -      "posix",
    -      "windows"
    -    ],
    -    "cpu_cost": 1.0,
    -    "exclude_configs": [],
    -    "exclude_iomgrs": [],
    -    "flaky": false,
    -    "gtest": true,
    -    "language": "c++",
    -    "name": "large_metadata_bad_client_test",
    -    "platforms": [
    -      "linux",
    -      "mac",
    -      "posix",
    -      "windows"
    -    ],
    -    "uses_polling": true
    -  },
       {
         "args": [],
         "benchmark": false,
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.