CVE-2019-15226 (published 2019-10-09T16:15Z)

Upon receiving each incoming request header data, Envoy will iterate over existing request headers to verify that the total size of the headers stays below a maximum limit. The implementation in versions 1.10.0 through 1.11.1 for HTTP/1.x traffic and all versions of Envoy for HTTP/2 traffic had O(n^2) performance characteristics. A remote attacker may craft a request that stays below the maximum request header size but consists of many thousands of small headers to consume CPU and result in a denial-of-service attack.

Severity: HIGH
CVSS v2 base score: 7.8 (AV:N/AC:L/Au:N/C:N/I:N/A:C)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-400
cwe_name: Uncontrolled Resource Consumption
description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
extended_description: 
            Limited resources include memory, file system storage, database connection pool entries, and CPU. If an attacker can trigger the allocation of these limited resources, but the number or size of the resources is not controlled, then the attacker could cause a denial of service that consumes all available resources. This would prevent valid users from accessing the product, and it could potentially have an impact on the surrounding environment. For example, a memory exhaustion attack against an application could slow down the application as well as its host operating system.
            There are at least three distinct scenarios which can commonly lead to resource exhaustion:
               
                  Lack of throttling for the number of allocated resources
                  Losing all references to a resource before reaching the shutdown stage
                  Not closing/returning a resource after processing
               
            Resource exhaustion problems are often result due to an incorrect implementation of the following situations:
               
                  Error conditions and other exceptional circumstances.
                  Confusion over which part of the program is responsible for releasing the resource.
               
         
         

Fix information:
repo_url: https://github.com/envoyproxy/envoy
commit_details: repo_url: https://github.com/envoyproxy/envoy
author: Asra Ali
author_date: 2019-10-07 17:58:39-04:00
msg: Track byteSize of HeaderMap internally.

Introduces a cached byte size updated internally in HeaderMap. The value
is stored as an optional, and is cleared whenever a non-const pointer or
reference to a HeaderEntry is accessed. The cached value can be set with
refreshByteSize() which performs an iteration over the HeaderMap to sum
the size of each key and value in the HeaderMap.

Signed-off-by: Asra Ali <asraa@google.com>
repository: repo_url: https://github.com/envoyproxy/envoy
repo_name: envoyproxy/envoy
description: Cloud-native high-performance edge/middle/service proxy
repo_language: C++
owner: envoyproxy
file_changes: filename: version_history.rst
change_type: ModificationType.MODIFY
diff: @@ -85,6 +85,10 @@ Version history
 * upstream: added :ref:`fail_traffic_on_panic <envoy_api_field_Cluster.CommonLbConfig.ZoneAwareLbConfig.fail_traffic_on_panic>` to allow failing all requests to a cluster during panic state.
 * zookeeper: parse responses and emit latency stats.
 
+1.11.2 (October 8, 2019)
+========================
+* http: fixed CVE-2019-15226 by adding a cached byte size in HeaderMap.
+
 1.11.1 (August 13, 2019)
 ========================
 * http: added mitigation of client initiated attacks that result in flooding of the downstream HTTP/2 connections. Those attacks can be logged at the "warning" level when the runtime feature `http.connection_manager.log_flood_exception` is enabled. The runtime setting defaults to disabled to avoid log spam when under attack.

programming_language: Markdown
filename: access_log.h
change_type: ModificationType.MODIFY
diff: @@ -78,6 +78,10 @@ public:
 
   /**
    * Log a completed request.
+   * Prior to logging, call refreshByteSize() on HeaderMaps to ensure that an accurate byte size
+   * count is logged.
+   * TODO(asraa): Remove refreshByteSize() requirement when entries in HeaderMap can no longer be
+   * modified by reference and headerMap holds an accurate internal byte size count.
    * @param request_headers supplies the incoming request headers after filtering.
    * @param response_headers supplies response headers.
    * @param response_trailers supplies response trailers.

programming_language: C++
filename: header_map.h
change_type: ModificationType.MODIFY
diff: @@ -457,9 +457,41 @@ public:
   virtual void setReferenceKey(const LowerCaseString& key, const std::string& value) PURE;
 
   /**
+   * HeaderMap contains an internal byte size count, updated as entries are added, removed, or
+   * modified through the HeaderMap interface. However, HeaderEntries can be accessed and modified
+   * by reference so that the HeaderMap can no longer accurately update the internal byte size
+   * count.
+   *
+   * Calling byteSize before a HeaderEntry is accessed will return the internal byte size count. The
+   * value is cleared when a HeaderEntry is accessed, and the value is updated and set again when
+   * refreshByteSize is called.
+   *
+   * To guarantee an accurate byte size count, call refreshByteSize.
+   *
+   * @return uint64_t the approximate size of the header map in bytes if valid.
+   */
+  virtual absl::optional<uint64_t> byteSize() const PURE;
+
+  /**
+   * This returns the sum of the byte sizes of the keys and values in the HeaderMap. This also
+   * updates and sets the byte size count.
+   *
+   * To guarantee an accurate byte size count, use this. If it is known HeaderEntries have not been
+   * manipulated since a call to refreshByteSize, it is safe to use byteSize.
+   *
+   * @return uint64_t the approximate size of the header map in bytes.
+   */
+  virtual uint64_t refreshByteSize() PURE;
+
+  /**
+   * This returns the sum of the byte sizes of the keys and values in the HeaderMap.
+   *
+   * This iterates over the HeaderMap to calculate size and should only be called directly when the
+   * user wants an explicit recalculation of the byte size.
+   *
    * @return uint64_t the approximate size of the header map in bytes.
    */
-  virtual uint64_t byteSize() const PURE;
+  virtual uint64_t byteSizeInternal() const PURE;
 
   /**
    * Get a header by key.

programming_language: C++
filename: conn_manager_impl.cc
change_type: ModificationType.MODIFY
diff: @@ -506,6 +506,18 @@ ConnectionManagerImpl::ActiveStream::~ActiveStream() {
   }
 
   connection_manager_.stats_.named_.downstream_rq_active_.dec();
+  // Refresh byte sizes of the HeaderMaps before logging.
+  // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
+  // HeaderMap holds an accurate internal byte size count.
+  if (request_headers_ != nullptr) {
+    request_headers_->refreshByteSize();
+  }
+  if (response_headers_ != nullptr) {
+    response_headers_->refreshByteSize();
+  }
+  if (response_trailers_ != nullptr) {
+    response_trailers_->refreshByteSize();
+  }
   for (const AccessLog::InstanceSharedPtr& access_log : connection_manager_.config_.accessLogs()) {
     access_log->log(request_headers_.get(), response_headers_.get(), response_trailers_.get(),
                     stream_info_);
@@ -719,14 +731,6 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers,
     }
   }
 
-  ASSERT(connection_manager_.config_.maxRequestHeadersKb() > 0);
-  if (request_headers_->byteSize() > (connection_manager_.config_.maxRequestHeadersKb() * 1024)) {
-    sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_),
-                   Code::RequestHeaderFieldsTooLarge, "", nullptr, is_head_request_, absl::nullopt,
-                   StreamInfo::ResponseCodeDetails::get().RequestHeadersTooLarge);
-    return;
-  }
-
   // Currently we only support relative paths at the application layer. We expect the codec to have
   // broken the path into pieces if applicable. NOTE: Currently the HTTP/1.1 codec only does this
   // when the allow_absolute_url flag is enabled on the HCM.

programming_language: C++
filename: header_map_impl.cc
change_type: ModificationType.MODIFY
diff: @@ -295,14 +295,17 @@ struct HeaderMapImpl::StaticLookupTable : public TrieLookupTable<EntryCb> {
   }
 };
 
-void HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data) {
+uint64_t HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data) {
   if (data.empty()) {
-    return;
+    return 0;
   }
+  uint64_t byte_size = 0;
   if (!header.empty()) {
     header.append(",", 1);
+    byte_size += 1;
   }
   header.append(data.data(), data.size());
+  return data.size() + byte_size;
 }
 
 HeaderMapImpl::HeaderMapImpl() { memset(&inline_headers_, 0, sizeof(inline_headers_)); }
@@ -319,6 +322,20 @@ HeaderMapImpl::HeaderMapImpl(
   }
 }
 
+void HeaderMapImpl::addSize(uint64_t size) {
+  // Adds size to cached_byte_size_ if it exists.
+  if (cached_byte_size_.has_value()) {
+    cached_byte_size_.value() += size;
+  }
+}
+
+void HeaderMapImpl::subtractSize(uint64_t size) {
+  if (cached_byte_size_.has_value()) {
+    ASSERT(cached_byte_size_ >= size);
+    cached_byte_size_.value() -= size;
+  }
+}
+
 void HeaderMapImpl::copyFrom(const HeaderMap& header_map) {
   header_map.iterate(
       [](const HeaderEntry& header, void* context) -> HeaderMap::Iterate {
@@ -359,10 +376,13 @@ void HeaderMapImpl::insertByKey(HeaderString&& key, HeaderString&& value) {
     if (*ref_lookup_response.entry_ == nullptr) {
       maybeCreateInline(ref_lookup_response.entry_, *ref_lookup_response.key_, std::move(value));
     } else {
-      appendToHeader((*ref_lookup_response.entry_)->value(), value.getStringView());
+      const uint64_t added_size =
+          appendToHeader((*ref_lookup_response.entry_)->value(), value.getStringView());
+      addSize(added_size);
       value.clear();
     }
   } else {
+    addSize(key.size() + value.size());
     std::list<HeaderEntryImpl>::iterator i = headers_.insert(std::move(key), std::move(value));
     i->entry_ = i;
   }
@@ -373,7 +393,8 @@ void HeaderMapImpl::addViaMove(HeaderString&& key, HeaderString&& value) {
   // the existing value.
   auto* entry = getExistingInline(key.getStringView());
   if (entry != nullptr) {
-    appendToHeader(entry->value(), value.getStringView());
+    const uint64_t added_size = appendToHeader(entry->value(), value.getStringView());
+    addSize(added_size);
     key.clear();
     value.clear();
   } else {
@@ -408,7 +429,8 @@ void HeaderMapImpl::addCopy(const LowerCaseString& key, uint64_t value) {
   if (entry != nullptr) {
     char buf[32];
     StringUtil::itoa(buf, sizeof(buf), value);
-    appendToHeader(entry->value(), buf);
+    const uint64_t added_size = appendToHeader(entry->value(), buf);
+    addSize(added_size);
     return;
   }
   HeaderString new_key;
@@ -423,7 +445,8 @@ void HeaderMapImpl::addCopy(const LowerCaseString& key, uint64_t value) {
 void HeaderMapImpl::addCopy(const LowerCaseString& key, const std::string& value) {
   auto* entry = getExistingInline(key.get());
   if (entry != nullptr) {
-    appendToHeader(entry->value(), value);
+    const uint64_t added_size = appendToHeader(entry->value(), value);
+    addSize(added_size);
     return;
   }
   HeaderString new_key;
@@ -451,13 +474,24 @@ void HeaderMapImpl::setReferenceKey(const LowerCaseString& key, const std::strin
   ASSERT(new_value.empty()); // NOLINT(bugprone-use-after-move)
 }
 
-uint64_t HeaderMapImpl::byteSize() const {
+absl::optional<uint64_t> HeaderMapImpl::byteSize() const { return cached_byte_size_; }
+
+uint64_t HeaderMapImpl::refreshByteSize() {
+  if (!cached_byte_size_.has_value()) {
+    // In this case, the cached byte size is not valid, and the byte size is computed via an
+    // iteration over the HeaderMap. The cached byte size is updated.
+    cached_byte_size_ = byteSizeInternal();
+  }
+  return cached_byte_size_.value();
+}
+
+uint64_t HeaderMapImpl::byteSizeInternal() const {
+  // Computes the total byte size by summing the byte size of the keys and values.
   uint64_t byte_size = 0;
   for (const HeaderEntryImpl& header : headers_) {
     byte_size += header.key().size();
     byte_size += header.value().size();
   }
-
   return byte_size;
 }
 
@@ -474,6 +508,7 @@ const HeaderEntry* HeaderMapImpl::get(const LowerCaseString& key) const {
 HeaderEntry* HeaderMapImpl::get(const LowerCaseString& key) {
   for (HeaderEntryImpl& header : headers_) {
     if (header.key() == key.get().c_str()) {
+      cached_byte_size_.reset();
       return &header;
     }
   }
@@ -528,6 +563,7 @@ void HeaderMapImpl::remove(const LowerCaseString& key) {
   } else {
     for (auto i = headers_.begin(); i != headers_.end();) {
       if (i->key() == key.get().c_str()) {
+        subtractSize(i->key().size() + i->value().size());
         i = headers_.erase(i);
       } else {
         ++i;
@@ -537,7 +573,7 @@ void HeaderMapImpl::remove(const LowerCaseString& key) {
 }
 
 void HeaderMapImpl::removePrefix(const LowerCaseString& prefix) {
-  headers_.remove_if([&](const HeaderEntryImpl& entry) {
+  headers_.remove_if([&prefix, this](const HeaderEntryImpl& entry) {
     bool to_remove = absl::StartsWith(entry.key().getStringView(), prefix.get());
     if (to_remove) {
       // If this header should be removed, make sure any references in the
@@ -546,8 +582,13 @@ void HeaderMapImpl::removePrefix(const LowerCaseString& prefix) {
       if (cb) {
         StaticLookupResponse ref_lookup_response = cb(*this);
         if (ref_lookup_response.entry_) {
+          const uint32_t key_value_size = (*ref_lookup_response.entry_)->key().size() +
+                                          (*ref_lookup_response.entry_)->value().size();
+          subtractSize(key_value_size);
           *ref_lookup_response.entry_ = nullptr;
         }
+      } else {
+        subtractSize(entry.key().size() + entry.value().size());
       }
     }
     return to_remove;
@@ -570,6 +611,7 @@ void HeaderMapImpl::dumpState(std::ostream& os, int indent_level) const {
 
 HeaderMapImpl::HeaderEntryImpl& HeaderMapImpl::maybeCreateInline(HeaderEntryImpl** entry,
                                                                  const LowerCaseString& key) {
+  cached_byte_size_.reset();
   if (*entry) {
     return **entry;
   }
@@ -588,6 +630,7 @@ HeaderMapImpl::HeaderEntryImpl& HeaderMapImpl::maybeCreateInline(HeaderEntryImpl
     return **entry;
   }
 
+  addSize(key.get().size() + value.size());
   std::list<HeaderEntryImpl>::iterator i = headers_.insert(key, std::move(value));
   i->entry_ = i;
   *entry = &(*i);
@@ -609,6 +652,8 @@ void HeaderMapImpl::removeInline(HeaderEntryImpl** ptr_to_entry) {
   }
 
   HeaderEntryImpl* entry = *ptr_to_entry;
+  const uint64_t size_to_subtract = entry->entry_->key().size() + entry->entry_->value().size();
+  subtractSize(size_to_subtract);
   *ptr_to_entry = nullptr;
   headers_.erase(entry->entry_);
 }

programming_language: C++
filename: header_map_impl.h
change_type: ModificationType.MODIFY
diff: @@ -16,12 +16,21 @@ namespace Http {
 
 /**
  * These are definitions of all of the inline header access functions described inside header_map.h
+ *
+ * When a non-const reference or pointer to a HeaderEntry is returned, the internal byte size count
+ * will be cleared, since HeaderMap will no longer be able to accurately update the size of that
+ * HeaderEntry.
+ * TODO(asraa): Remove functions with a non-const HeaderEntry return value.
  */
 #define DEFINE_INLINE_HEADER_FUNCS(name)                                                           \
 public:                                                                                            \
   const HeaderEntry* name() const override { return inline_headers_.name##_; }                     \
-  HeaderEntry* name() override { return inline_headers_.name##_; }                                 \
+  HeaderEntry* name() override {                                                                   \
+    cached_byte_size_.reset();                                                                     \
+    return inline_headers_.name##_;                                                                \
+  }                                                                                                \
   HeaderEntry& insert##name() override {                                                           \
+    cached_byte_size_.reset();                                                                     \
     return maybeCreateInline(&inline_headers_.name##_, Headers::get().name);                       \
   }                                                                                                \
   void remove##name() override { removeInline(&inline_headers_.name##_); }
@@ -43,7 +52,7 @@ public:
    * @param header the header to append to.
    * @param data to append to the header.
    */
-  static void appendToHeader(HeaderString& header, absl::string_view data);
+  static uint64_t appendToHeader(HeaderString& header, absl::string_view data);
 
   HeaderMapImpl();
   explicit HeaderMapImpl(
@@ -71,7 +80,9 @@ public:
   void addCopy(const LowerCaseString& key, const std::string& value) override;
   void setReference(const LowerCaseString& key, const std::string& value) override;
   void setReferenceKey(const LowerCaseString& key, const std::string& value) override;
-  uint64_t byteSize() const override;
+  absl::optional<uint64_t> byteSize() const override;
+  uint64_t refreshByteSize() override;
+  uint64_t byteSizeInternal() const override;
   const HeaderEntry* get(const LowerCaseString& key) const override;
   HeaderEntry* get(const LowerCaseString& key) override;
   void iterate(ConstIterateCb cb, void* context) const override;
@@ -195,10 +206,16 @@ protected:
   HeaderEntryImpl* getExistingInline(absl::string_view key);
 
   void removeInline(HeaderEntryImpl** entry);
+  void addSize(uint64_t size);
+  void subtractSize(uint64_t size);
 
   AllInlineHeaders inline_headers_;
   HeaderList headers_;
 
+  // When present, this holds the internal byte size of the HeaderMap. The value is removed once an
+  // inline header entry is accessed and updated when refreshByteSize() is called.
+  absl::optional<uint64_t> cached_byte_size_ = 0;
+
   ALL_INLINE_HEADERS(DEFINE_INLINE_HEADER_FUNCS)
 };
 

programming_language: C++
filename: codec_impl.cc
change_type: ModificationType.MODIFY
diff: @@ -460,8 +460,10 @@ void ConnectionImpl::onHeaderValue(const char* data, size_t length) {
   header_parsing_state_ = HeaderParsingState::Value;
   current_header_value_.append(data, length);
 
-  const uint32_t total =
-      current_header_field_.size() + current_header_value_.size() + current_header_map_->byteSize();
+  // Verify that the cached value in byte size exists.
+  ASSERT(current_header_map_->byteSize().has_value());
+  const uint32_t total = current_header_field_.size() + current_header_value_.size() +
+                         current_header_map_->byteSize().value();
   if (total > (max_request_headers_kb_ * 1024)) {
     error_code_ = Http::Code::RequestHeaderFieldsTooLarge;
     sendProtocolError();
@@ -472,6 +474,10 @@ void ConnectionImpl::onHeaderValue(const char* data, size_t length) {
 int ConnectionImpl::onHeadersCompleteBase() {
   ENVOY_CONN_LOG(trace, "headers complete", connection_);
   completeLastHeader();
+  // Validate that the completed HeaderMap's cached byte size exists and is correct.
+  // This assert iterates over the HeaderMap.
+  ASSERT(current_header_map_->byteSize().has_value() &&
+         current_header_map_->byteSize() == current_header_map_->byteSizeInternal());
   if (!(parser_.http_major == 1 && parser_.http_minor == 1)) {
     // This is not necessarily true, but it's good enough since higher layers only care if this is
     // HTTP/1.1 or not.

programming_language: C++
filename: codec_impl.cc
change_type: ModificationType.MODIFY
diff: @@ -509,6 +509,10 @@ int ConnectionImpl::onFrameReceived(const nghttp2_frame* frame) {
 
   switch (frame->hd.type) {
   case NGHTTP2_HEADERS: {
+    // Verify that the final HeaderMap's byte size is under the limit before decoding headers.
+    // This assert iterates over the HeaderMap.
+    ASSERT(stream->headers_->byteSize().has_value() &&
+           stream->headers_->byteSize().value() == stream->headers_->byteSizeInternal());
     stream->remote_end_stream_ = frame->hd.flags & NGHTTP2_FLAG_END_STREAM;
     if (!stream->cookies_.empty()) {
       HeaderString key(Headers::get().Cookie);
@@ -620,6 +624,12 @@ int ConnectionImpl::onFrameSend(const nghttp2_frame* frame) {
   case NGHTTP2_HEADERS:
   case NGHTTP2_DATA: {
     StreamImpl* stream = getStream(frame->hd.stream_id);
+    if (stream->headers_) {
+      // Verify that the final HeaderMap's byte size is under the limit before sending frames.
+      // This assert iterates over the HeaderMap.
+      ASSERT(stream->headers_->byteSize().has_value() &&
+             stream->headers_->byteSize().value() == stream->headers_->byteSizeInternal());
+    }
     stream->local_end_stream_sent_ = frame->hd.flags & NGHTTP2_FLAG_END_STREAM;
     break;
   }
@@ -808,9 +818,10 @@ int ConnectionImpl::saveHeader(const nghttp2_frame* frame, HeaderString&& name,
     stats_.headers_cb_no_stream_.inc();
     return 0;
   }
-
   stream->saveHeader(std::move(name), std::move(value));
-  if (stream->headers_->byteSize() > max_request_headers_kb_ * 1024) {
+  // Verify that the cached value in byte size exists.
+  ASSERT(stream->headers_->byteSize().has_value());
+  if (stream->headers_->byteSize().value() > max_request_headers_kb_ * 1024) {
     // This will cause the library to reset/close the stream.
     stats_.header_overflow_.inc();
     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;

programming_language: C++
filename: router.cc
change_type: ModificationType.MODIFY
diff: @@ -1398,6 +1398,15 @@ Filter::UpstreamRequest::~UpstreamRequest() {
 
   stream_info_.setUpstreamTiming(upstream_timing_);
   stream_info_.onRequestComplete();
+  // Prior to logging, refresh the byte size of the HeaderMaps.
+  // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
+  // HeaderMap holds an accurate internal byte size count.
+  if (upstream_headers_ != nullptr) {
+    upstream_headers_->refreshByteSize();
+  }
+  if (upstream_trailers_ != nullptr) {
+    upstream_trailers_->refreshByteSize();
+  }
   for (const auto& upstream_log : parent_.config_.upstream_logs_) {
     upstream_log->log(parent_.downstream_headers_, upstream_headers_.get(),
                       upstream_trailers_.get(), stream_info_);

programming_language: C++
filename: access_log_base.h
change_type: ModificationType.MODIFY
diff: @@ -27,6 +27,11 @@ public:
 
   /**
    * Log a completed request if the underlying AccessLog `filter_` allows it.
+   *
+   * Prior to logging, call refreshByteSize() on HeaderMaps to ensure that an accurate byte size
+   * count is logged.
+   * TODO(asraa): Remove refreshByteSize() requirement when entries in HeaderMap can no longer be
+   * modified by reference and HeaderMap holds an accurate internal byte size count.
    */
   void log(const Http::HeaderMap* request_headers, const Http::HeaderMap* response_headers,
            const Http::HeaderMap* response_trailers,

programming_language: C++
filename: http_grpc_access_log_impl.cc
change_type: ModificationType.MODIFY
diff: @@ -95,7 +95,7 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers,
     request_properties->set_original_path(
         std::string(request_headers.EnvoyOriginalPath()->value().getStringView()));
   }
-  request_properties->set_request_headers_bytes(request_headers.byteSize());
+  request_properties->set_request_headers_bytes(request_headers.byteSize().value());
   request_properties->set_request_body_bytes(stream_info.bytesReceived());
   if (request_headers.Method() != nullptr) {
     envoy::api::v2::core::RequestMethod method =
@@ -123,7 +123,7 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers,
   if (stream_info.responseCodeDetails()) {
     response_properties->set_response_code_details(stream_info.responseCodeDetails().value());
   }
-  response_properties->set_response_headers_bytes(response_headers.byteSize());
+  response_properties->set_response_headers_bytes(response_headers.byteSize().value());
   response_properties->set_response_body_bytes(stream_info.bytesSent());
   if (!response_headers_to_log_.empty()) {
     auto* logged_headers = response_properties->mutable_response_headers();

programming_language: C++
filename: context.cc
change_type: ModificationType.MODIFY
diff: @@ -79,7 +79,7 @@ absl::optional<CelValue> RequestWrapper::operator[](CelValue key) const {
     } else if (value == UserAgent) {
       return convertHeaderEntry(headers_.value_->UserAgent());
     } else if (value == TotalSize) {
-      return CelValue::CreateInt64(info_.bytesReceived() + headers_.value_->byteSize());
+      return CelValue::CreateInt64(info_.bytesReceived() + headers_.value_->byteSize().value());
     }
   }
   return {};

programming_language: C++
filename: rbac_filter.cc
change_type: ModificationType.MODIFY
diff: @@ -76,6 +76,10 @@ Http::FilterHeadersStatus RoleBasedAccessControlFilter::decodeHeaders(Http::Head
   if (shadow_engine != nullptr) {
     std::string shadow_resp_code =
         Filters::Common::RBAC::DynamicMetadataKeysSingleton::get().EngineResultAllowed;
+    // Refresh headers byte size before checking if allowed.
+    // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
+    // HeaderMap holds an accurate internal byte size count.
+    headers.refreshByteSize();
     if (shadow_engine->allowed(*callbacks_->connection(), headers, callbacks_->streamInfo(),
                                &effective_policy_id)) {
       ENVOY_LOG(debug, "shadow allowed");
@@ -105,6 +109,10 @@ Http::FilterHeadersStatus RoleBasedAccessControlFilter::decodeHeaders(Http::Head
   const auto engine =
       config_->engine(callbacks_->route(), Filters::Common::RBAC::EnforcementMode::Enforced);
   if (engine != nullptr) {
+    // Refresh headers byte size before checking if allowed.
+    // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
+    // HeaderMap holds an accurate internal byte size count.
+    headers.refreshByteSize();
     if (engine->allowed(*callbacks_->connection(), headers, callbacks_->streamInfo(), nullptr)) {
       ENVOY_LOG(debug, "enforced allowed");
       config_->stats().allowed_.inc();

programming_language: C++
filename: conn_manager_impl_test.cc
change_type: ModificationType.MODIFY
diff: @@ -4409,51 +4409,6 @@ TEST_F(HttpConnectionManagerImplTest, DisableKeepAliveWhenOverloaded) {
   EXPECT_EQ(1U, stats_.named_.downstream_cx_overload_disable_keepalive_.value());
 }
 
-TEST_F(HttpConnectionManagerImplTest, OverlyLongHeadersRejected) {
-  setup(false, "");
-
-  std::string response_code;
-  std::string response_body;
-  EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void {
-    StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_);
-    HeaderMapPtr headers{
-        new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}};
-    headers->addCopy(LowerCaseString("Foo"), std::string(60 * 1024, 'a'));
-
-    EXPECT_CALL(response_encoder_, encodeHeaders(_, true))
-        .WillOnce(Invoke([&response_code](const HeaderMap& headers, bool) -> void {
-          response_code = std::string(headers.Status()->value().getStringView());
-        }));
-    decoder->decodeHeaders(std::move(headers), true);
-    conn_manager_->newStream(response_encoder_);
-  }));
-
-  Buffer::OwnedImpl fake_input("1234");
-  conn_manager_->onData(fake_input, false); // kick off request
-
-  EXPECT_EQ("431", response_code);
-  EXPECT_EQ("", response_body);
-}
-
-TEST_F(HttpConnectionManagerImplTest, OverlyLongHeadersAcceptedIfConfigured) {
-  max_request_headers_kb_ = 62;
-  setup(false, "");
-
-  EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void {
-    StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_);
-    HeaderMapPtr headers{
-        new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}};
-    headers->addCopy(LowerCaseString("Foo"), std::string(60 * 1024, 'a'));
-
-    EXPECT_CALL(response_encoder_, encodeHeaders(_, _)).Times(0);
-    decoder->decodeHeaders(std::move(headers), true);
-    conn_manager_->newStream(response_encoder_);
-  }));
-
-  Buffer::OwnedImpl fake_input("1234");
-  conn_manager_->onData(fake_input, false); // kick off request
-}
-
 TEST_F(HttpConnectionManagerImplTest, TestStopAllIterationAndBufferOnDecodingPathFirstFilter) {
   setup(false, "envoy-custom-server", false);
   setUpEncoderAndDecoder(true, true);

programming_language: C++
filename: header_map_impl_speed_test.cc
change_type: ModificationType.MODIFY
diff: @@ -104,7 +104,7 @@ static void HeaderMapImplGetByteSize(benchmark::State& state) {
   addDummyHeaders(headers, state.range(0));
   uint64_t size = 0;
   for (auto _ : state) {
-    size += headers.byteSize();
+    size += headers.byteSize().value();
   }
   benchmark::DoNotOptimize(size);
 }

programming_language: C++
filename: header_map_impl_test.cc
change_type: ModificationType.MODIFY
diff: @@ -366,6 +366,7 @@ TEST(HeaderMapImplTest, InlineInsert) {
   HeaderMapImpl headers;
   EXPECT_TRUE(headers.empty());
   EXPECT_EQ(0, headers.size());
+  EXPECT_EQ(headers.byteSize().value(), 0);
   EXPECT_EQ(nullptr, headers.Host());
   headers.insertHost().value(std::string("hello"));
   EXPECT_FALSE(headers.empty());
@@ -375,6 +376,19 @@ TEST(HeaderMapImplTest, InlineInsert) {
   EXPECT_EQ("hello", headers.get(Headers::get().Host)->value().getStringView());
 }
 
+// Utility function for testing byteSize() against a manual byte count.
+uint64_t countBytesForTest(const HeaderMapImpl& headers) {
+  uint64_t byte_size = 0;
+  headers.iterate(
+      [](const Http::HeaderEntry& header, void* context) -> Http::HeaderMap::Iterate {
+        auto* byte_size = static_cast<uint64_t*>(context);
+        *byte_size += header.key().getStringView().size() + header.value().getStringView().size();
+        return Http::HeaderMap::Iterate::Continue;
+      },
+      &byte_size);
+  return byte_size;
+}
+
 TEST(HeaderMapImplTest, MoveIntoInline) {
   HeaderMapImpl headers;
   HeaderString key;
@@ -392,6 +406,7 @@ TEST(HeaderMapImplTest, MoveIntoInline) {
   headers.addViaMove(std::move(key2), std::move(value2));
   EXPECT_EQ("cache-control", headers.CacheControl()->key().getStringView());
   EXPECT_EQ("hello,there", headers.CacheControl()->value().getStringView());
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
 }
 
 TEST(HeaderMapImplTest, Remove) {
@@ -401,6 +416,7 @@ TEST(HeaderMapImplTest, Remove) {
   LowerCaseString static_key("hello");
   std::string ref_value("value");
   headers.addReference(static_key, ref_value);
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
   EXPECT_EQ("value", headers.get(static_key)->value().getStringView());
   EXPECT_EQ(HeaderString::Type::Reference, headers.get(static_key)->value().type());
   EXPECT_EQ(1UL, headers.size());
@@ -409,9 +425,11 @@ TEST(HeaderMapImplTest, Remove) {
   EXPECT_EQ(nullptr, headers.get(static_key));
   EXPECT_EQ(0UL, headers.size());
   EXPECT_TRUE(headers.empty());
+  EXPECT_EQ(headers.refreshByteSize(), 0);
 
   // Add and remove by inline.
   headers.insertContentLength().value(5);
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
   EXPECT_EQ("5", headers.ContentLength()->value().getStringView());
   EXPECT_EQ(1UL, headers.size());
   EXPECT_FALSE(headers.empty());
@@ -419,16 +437,19 @@ TEST(HeaderMapImplTest, Remove) {
   EXPECT_EQ(nullptr, headers.ContentLength());
   EXPECT_EQ(0UL, headers.size());
   EXPECT_TRUE(headers.empty());
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
 
   // Add inline and remove by name.
   headers.insertContentLength().value(5);
   EXPECT_EQ("5", headers.ContentLength()->value().getStringView());
   EXPECT_EQ(1UL, headers.size());
   EXPECT_FALSE(headers.empty());
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
   headers.remove(Headers::get().ContentLength);
   EXPECT_EQ(nullptr, headers.ContentLength());
   EXPECT_EQ(0UL, headers.size());
   EXPECT_TRUE(headers.empty());
+  EXPECT_EQ(headers.refreshByteSize(), 0);
 }
 
 TEST(HeaderMapImplTest, RemoveRegex) {
@@ -446,9 +467,11 @@ TEST(HeaderMapImplTest, RemoveRegex) {
   headers.addReference(key3, "value");
   headers.addReference(key4, "value");
   headers.addReference(key5, "value");
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
 
   // Test removing the first header, middle headers, and the end header.
   headers.removePrefix(LowerCaseString("x-prefix-"));
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
   EXPECT_EQ(nullptr, headers.get(key1));
   EXPECT_NE(nullptr, headers.get(key2));
   EXPECT_EQ(nullptr, headers.get(key3));
@@ -456,7 +479,9 @@ TEST(HeaderMapImplTest, RemoveRegex) {
   EXPECT_EQ(nullptr, headers.get(key5));
 
   // Remove all headers.
+  headers.refreshByteSize();
   headers.removePrefix(LowerCaseString(""));
+  EXPECT_EQ(headers.byteSize().value(), 0);
   EXPECT_EQ(nullptr, headers.get(key2));
   EXPECT_EQ(nullptr, headers.get(key4));
 
@@ -465,8 +490,10 @@ TEST(HeaderMapImplTest, RemoveRegex) {
   EXPECT_EQ("5", headers.ContentLength()->value().getStringView());
   EXPECT_EQ(1UL, headers.size());
   EXPECT_FALSE(headers.empty());
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
   headers.removePrefix(LowerCaseString("content"));
   EXPECT_EQ(nullptr, headers.ContentLength());
+  EXPECT_EQ(headers.refreshByteSize(), 0);
 }
 
 TEST(HeaderMapImplTest, SetRemovesAllValues) {
@@ -484,6 +511,7 @@ TEST(HeaderMapImplTest, SetRemovesAllValues) {
   headers.addReference(key2, ref_value2);
   headers.addReference(key1, ref_value3);
   headers.addReference(key1, ref_value4);
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
 
   using MockCb = testing::MockFunction<void(const std::string&, const std::string&)>;
 
@@ -531,6 +559,7 @@ TEST(HeaderMapImplTest, DoubleInlineAdd) {
     const std::string bar("bar");
     headers.addReference(Headers::get().ContentLength, foo);
     headers.addReference(Headers::get().ContentLength, bar);
+    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
     EXPECT_EQ("foo,bar", headers.ContentLength()->value().getStringView());
     EXPECT_EQ(1UL, headers.size());
   }
@@ -538,6 +567,7 @@ TEST(HeaderMapImplTest, DoubleInlineAdd) {
     HeaderMapImpl headers;
     headers.addReferenceKey(Headers::get().ContentLength, "foo");
     headers.addReferenceKey(Headers::get().ContentLength, "bar");
+    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
     EXPECT_EQ("foo,bar", headers.ContentLength()->value().getStringView());
     EXPECT_EQ(1UL, headers.size());
   }
@@ -545,6 +575,7 @@ TEST(HeaderMapImplTest, DoubleInlineAdd) {
     HeaderMapImpl headers;
     headers.addReferenceKey(Headers::get().ContentLength, 5);
     headers.addReferenceKey(Headers::get().ContentLength, 6);
+    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
     EXPECT_EQ("5,6", headers.ContentLength()->value().getStringView());
     EXPECT_EQ(1UL, headers.size());
   }
@@ -553,6 +584,7 @@ TEST(HeaderMapImplTest, DoubleInlineAdd) {
     const std::string foo("foo");
     headers.addReference(Headers::get().ContentLength, foo);
     headers.addReferenceKey(Headers::get().ContentLength, 6);
+    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
     EXPECT_EQ("foo,6", headers.ContentLength()->value().getStringView());
     EXPECT_EQ(1UL, headers.size());
   }
@@ -568,6 +600,7 @@ TEST(HeaderMapImplTest, DoubleCookieAdd) {
   headers.addReference(set_cookie, foo);
   headers.addReference(set_cookie, bar);
   EXPECT_EQ(2UL, headers.size());
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
 
   std::vector<absl::string_view> out;
   Http::HeaderUtility::getAllOfHeader(headers, "set-cookie", out);
@@ -580,6 +613,7 @@ TEST(HeaderMapImplTest, DoubleInlineSet) {
   HeaderMapImpl headers;
   headers.setReferenceKey(Headers::get().ContentType, "blah");
   headers.setReferenceKey(Headers::get().ContentType, "text/html");
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
   EXPECT_EQ("text/html", headers.ContentType()->value().getStringView());
   EXPECT_EQ(1UL, headers.size());
 }
@@ -588,6 +622,7 @@ TEST(HeaderMapImplTest, AddReferenceKey) {
   HeaderMapImpl headers;
   LowerCaseString foo("hello");
   headers.addReferenceKey(foo, "world");
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
   EXPECT_NE("world", headers.get(foo)->value().getStringView().data());
   EXPECT_EQ("world", headers.get(foo)->value().getStringView());
 }
@@ -596,10 +631,13 @@ TEST(HeaderMapImplTest, SetReferenceKey) {
   HeaderMapImpl headers;
   LowerCaseString foo("hello");
   headers.setReferenceKey(foo, "world");
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
   EXPECT_NE("world", headers.get(foo)->value().getStringView().data());
   EXPECT_EQ("world", headers.get(foo)->value().getStringView());
+  headers.refreshByteSize();
 
   headers.setReferenceKey(foo, "monde");
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
   EXPECT_NE("monde", headers.get(foo)->value().getStringView().data());
   EXPECT_EQ("monde", headers.get(foo)->value().getStringView());
 }
@@ -610,6 +648,7 @@ TEST(HeaderMapImplTest, AddCopy) {
   // Start with a string value.
   std::unique_ptr<LowerCaseString> lcKeyPtr(new LowerCaseString("hello"));
   headers.addCopy(*lcKeyPtr, "world");
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
 
   const HeaderString& value = headers.get(*lcKeyPtr)->value();
 
@@ -629,14 +668,19 @@ TEST(HeaderMapImplTest, AddCopy) {
   //
   // addReferenceKey and addCopy can both add multiple instances of a
   // given header, so we need to delete the old "hello" header.
+  // Test that removing will return 0 byte size.
+  headers.refreshByteSize();
   headers.remove(LowerCaseString("hello"));
+  EXPECT_EQ(headers.byteSize().value(), 0);
 
   // Build "hello" with string concatenation to make it unlikely that the
   // compiler is just reusing the same string constant for everything.
   lcKeyPtr = std::make_unique<LowerCaseString>(std::string("he") + "llo");
   EXPECT_STREQ("hello", lcKeyPtr->get().c_str());
 
+  headers.refreshByteSize();
   headers.addCopy(*lcKeyPtr, 42);
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
 
   const HeaderString& value3 = headers.get(*lcKeyPtr)->value();
 
@@ -662,15 +706,20 @@ TEST(HeaderMapImplTest, AddCopy) {
   headers.addCopy(cache_control, "max-age=1345");
   EXPECT_EQ("max-age=1345", headers.get(cache_control)->value().getStringView());
   EXPECT_EQ("max-age=1345", headers.CacheControl()->value().getStringView());
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
   headers.addCopy(cache_control, "public");
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
   EXPECT_EQ("max-age=1345,public", headers.get(cache_control)->value().getStringView());
   headers.addCopy(cache_control, "");
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
   EXPECT_EQ("max-age=1345,public", headers.get(cache_control)->value().getStringView());
   headers.addCopy(cache_control, 123);
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
   EXPECT_EQ("max-age=1345,public,123", headers.get(cache_control)->value().getStringView());
   headers.addCopy(cache_control, std::numeric_limits<uint64_t>::max());
   EXPECT_EQ("max-age=1345,public,123,18446744073709551615",
             headers.get(cache_control)->value().getStringView());
+  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
 }
 
 TEST(HeaderMapImplTest, Equality) {
@@ -690,6 +739,7 @@ TEST(HeaderMapImplTest, LargeCharInHeader) {
   LowerCaseString static_key("\x90hello");
   std::string ref_value("value");
   headers.addReference(static_key, ref_value);
+  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
   EXPECT_EQ("value", headers.get(static_key)->value().getStringView());
 }
 
@@ -844,6 +894,7 @@ TEST(HeaderMapImplTest, PseudoHeaderOrder) {
   {
     LowerCaseString foo("hello");
     Http::TestHeaderMapImpl headers{};
+    EXPECT_EQ(headers.refreshByteSize(), 0);
     EXPECT_EQ(0UL, headers.size());
     EXPECT_TRUE(headers.empty());
 

programming_language: C++
filename: codec_impl_test.cc
change_type: ModificationType.MODIFY
diff: @@ -1089,17 +1089,19 @@ TEST_P(Http2CodecImplTest, TestLargeRequestHeadersAtLimitAccepted) {
 
   TestHeaderMapImpl request_headers;
   HttpTestUtility::addDefaultHeaders(request_headers);
+  // Refresh byte size after adding default inline headers by reference.
+  request_headers.refreshByteSize();
   std::string key = "big";
   uint32_t head_room = 77;
   uint32_t long_string_length =
-      codec_limit_kb * 1024 - request_headers.byteSize() - key.length() - head_room;
+      codec_limit_kb * 1024 - request_headers.byteSize().value() - key.length() - head_room;
   std::string long_string = std::string(long_string_length, 'q');
   request_headers.addCopy(key, long_string);
 
   // The amount of data sent to the codec is not equivalent to the size of the
   // request headers that Envoy computes, as the codec limits based on the
   // entire http2 frame. The exact head room needed (76) was found through iteration.
-  ASSERT_EQ(request_headers.byteSize() + head_room, codec_limit_kb * 1024);
+  ASSERT_EQ(request_headers.byteSize().value() + head_room, codec_limit_kb * 1024);
 
   EXPECT_CALL(request_decoder_, decodeHeaders_(_, _));
   request_encoder_->encodeHeaders(request_headers, true);

programming_language: C++
filename: http2_integration_test.cc
change_type: ModificationType.MODIFY
diff: @@ -64,6 +64,8 @@ TEST_P(Http2IntegrationTest, Retry) { testRetry(); }
 
 TEST_P(Http2IntegrationTest, RetryAttemptCount) { testRetryAttemptCountHeader(); }
 
+TEST_P(Http2IntegrationTest, LargeRequestTrailersRejected) { testLargeRequestTrailers(66, 60); }
+
 static std::string response_metadata_filter = R"EOF(
 name: response-metadata-filter
 config: {}

programming_language: C++
filename: http_integration.cc
change_type: ModificationType.MODIFY
diff: @@ -260,7 +260,8 @@ void HttpIntegrationTest::setDownstreamProtocol(Http::CodecClient::Type downstre
 
 IntegrationStreamDecoderPtr HttpIntegrationTest::sendRequestAndWaitForResponse(
     const Http::TestHeaderMapImpl& request_headers, uint32_t request_body_size,
-    const Http::TestHeaderMapImpl& response_headers, uint32_t response_size, int upstream_index) {
+    const Http::TestHeaderMapImpl& response_headers, uint32_t response_size, int upstream_index,
+    std::chrono::milliseconds time) {
   ASSERT(codec_client_ != nullptr);
   // Send the request to Envoy.
   IntegrationStreamDecoderPtr response;
@@ -269,7 +270,7 @@ IntegrationStreamDecoderPtr HttpIntegrationTest::sendRequestAndWaitForResponse(
   } else {
     response = codec_client_->makeHeaderOnlyRequest(request_headers);
   }
-  waitForNextUpstreamRequest(upstream_index);
+  waitForNextUpstreamRequest(upstream_index, time);
   // Send response headers, and end_stream if there is no response body.
   upstream_request_->encodeHeaders(response_headers, response_size == 0);
   // Send any response data, with end_stream true.
@@ -333,7 +334,8 @@ void HttpIntegrationTest::verifyResponse(IntegrationStreamDecoderPtr response,
 }
 
 uint64_t
-HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices) {
+HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices,
+                                                std::chrono::milliseconds connection_wait_timeout) {
   uint64_t upstream_with_request;
   // If there is no upstream connection, wait for it to be established.
   if (!fake_upstream_connection_) {
@@ -341,7 +343,7 @@ HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& ups
     AssertionResult result = AssertionFailure();
     for (auto upstream_index : upstream_indices) {
       result = fake_upstreams_[upstream_index]->waitForHttpConnection(
-          *dispatcher_, fake_upstream_connection_, TestUtility::DefaultTimeout,
+          *dispatcher_, fake_upstream_connection_, connection_wait_timeout,
           max_request_headers_kb_);
       if (result) {
         upstream_with_request = upstream_index;
@@ -361,8 +363,9 @@ HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& ups
   return upstream_with_request;
 }
 
-void HttpIntegrationTest::waitForNextUpstreamRequest(uint64_t upstream_index) {
-  waitForNextUpstreamRequest(std::vector<uint64_t>({upstream_index}));
+void HttpIntegrationTest::waitForNextUpstreamRequest(
+    uint64_t upstream_index, std::chrono::milliseconds connection_wait_timeout) {
+  waitForNextUpstreamRequest(std::vector<uint64_t>({upstream_index}), connection_wait_timeout);
 }
 
 void HttpIntegrationTest::checkSimpleRequestSuccess(uint64_t expected_request_size,
@@ -916,6 +919,67 @@ void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t max_si
   }
 }
 
+void HttpIntegrationTest::testLargeRequestTrailers(uint32_t size, uint32_t max_size) {
+  // `size` parameter is the size of the trailer that will be added to the
+  // request. The actual request byte size will exceed `size` due to keys
+  // and other headers.
+
+  config_helper_.addConfigModifier(
+      [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
+          -> void { hcm.mutable_max_request_headers_kb()->set_value(max_size); });
+  max_request_headers_kb_ = max_size;
+  Http::TestHeaderMapImpl request_trailers{{"trailer", "trailer"}};
+  request_trailers.addCopy("big", std::string(size * 1024, 'a'));
+
+  initialize();
+
+  codec_client_ = makeHttpConnection(lookupPort("http"));
+  fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
+
+  auto encoder_decoder = codec_client_->startRequest(default_request_headers_);
+  request_encoder_ = &encoder_decoder.first;
+  auto response = std::move(encoder_decoder.second);
+  codec_client_->sendData(*request_encoder_, 10, false);
+  codec_client_->sendTrailers(*request_encoder_, request_trailers);
+
+  if (size >= max_size && downstream_protocol_ == Http::CodecClient::Type::HTTP2) {
+    // For HTTP/2, expect a stream reset when the size of the trailers is larger than the maximum
+    // limit.
+    response->waitForReset();
+    codec_client_->close();
+    EXPECT_FALSE(response->complete());
+
+  } else {
+    waitForNextUpstreamRequest();
+    upstream_request_->encodeHeaders(default_response_headers_, true);
+    response->waitForEndStream();
+    EXPECT_TRUE(response->complete());
+  }
+}
+
+void HttpIntegrationTest::testManyRequestHeaders(std::chrono::milliseconds time) {
+  config_helper_.addConfigModifier(
+      [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
+          -> void { hcm.mutable_max_request_headers_kb()->set_value(96); });
+  max_request_headers_kb_ = 96;
+
+  Http::TestHeaderMapImpl big_headers{
+      {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}};
+
+  for (int i = 0; i < 20000; i++) {
+    big_headers.addCopy(std::to_string(i), std::string(0, 'a'));
+  }
+  initialize();
+
+  codec_client_ = makeHttpConnection(lookupPort("http"));
+
+  auto response =
+      sendRequestAndWaitForResponse(big_headers, 0, default_response_headers_, 0, 0, time);
+
+  EXPECT_TRUE(response->complete());
+  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
+}
+
 void HttpIntegrationTest::testDownstreamResetBeforeResponseComplete() {
   initialize();
   codec_client_ = makeHttpConnection(lookupPort("http"));

programming_language: C++
filename: http_integration.h
change_type: ModificationType.MODIFY
diff: @@ -122,18 +122,21 @@ protected:
   //
   // Waits for the complete downstream response before returning.
   // Requires |codec_client_| to be initialized.
-  IntegrationStreamDecoderPtr
-  sendRequestAndWaitForResponse(const Http::TestHeaderMapImpl& request_headers,
-                                uint32_t request_body_size,
-                                const Http::TestHeaderMapImpl& response_headers,
-                                uint32_t response_body_size, int upstream_index = 0);
+  IntegrationStreamDecoderPtr sendRequestAndWaitForResponse(
+      const Http::TestHeaderMapImpl& request_headers, uint32_t request_body_size,
+      const Http::TestHeaderMapImpl& response_headers, uint32_t response_body_size,
+      int upstream_index = 0, std::chrono::milliseconds time = TestUtility::DefaultTimeout);
 
   // Wait for the end of stream on the next upstream stream on any of the provided fake upstreams.
   // Sets fake_upstream_connection_ to the connection and upstream_request_ to stream.
   // In cases where the upstream that will receive the request is not deterministic, a second
   // upstream index may be provided, in which case both upstreams will be checked for requests.
-  uint64_t waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices);
-  void waitForNextUpstreamRequest(uint64_t upstream_index = 0);
+  uint64_t waitForNextUpstreamRequest(
+      const std::vector<uint64_t>& upstream_indices,
+      std::chrono::milliseconds connection_wait_timeout = TestUtility::DefaultTimeout);
+  void waitForNextUpstreamRequest(
+      uint64_t upstream_index = 0,
+      std::chrono::milliseconds connection_wait_timeout = TestUtility::DefaultTimeout);
 
   // Close |codec_client_| and |fake_upstream_connection_| cleanly.
   void cleanupUpstreamAndDownstream();
@@ -185,7 +188,11 @@ protected:
   void testRouterUpstreamResponseBeforeRequestComplete();
 
   void testTwoRequests(bool force_network_backup = false);
+  void testLargeHeaders(Http::TestHeaderMapImpl request_headers,
+                        Http::TestHeaderMapImpl request_trailers, uint32_t size, uint32_t max_size);
   void testLargeRequestHeaders(uint32_t size, uint32_t max_size = 60);
+  void testLargeRequestTrailers(uint32_t size, uint32_t max_size = 60);
+  void testManyRequestHeaders(std::chrono::milliseconds time = TestUtility::DefaultTimeout);
 
   void testAddEncodedTrailers();
   void testRetry();

programming_language: C++
filename: protocol_integration_test.cc
change_type: ModificationType.MODIFY
diff: @@ -576,6 +576,46 @@ TEST_P(DownstreamProtocolIntegrationTest, ValidZeroLengthContent) {
   EXPECT_EQ("200", response->headers().Status()->value().getStringView());
 }
 
+// Validate that lots of tiny cookies doesn't cause a DoS (single cookie header).
+TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingConcatenated) {
+  initialize();
+
+  codec_client_ = makeHttpConnection(lookupPort("http"));
+  Http::TestHeaderMapImpl request_headers{{":method", "POST"},
+                                          {":path", "/test/long/url"},
+                                          {":scheme", "http"},
+                                          {":authority", "host"},
+                                          {"content-length", "0"}};
+  std::vector<std::string> cookie_pieces;
+  for (int i = 0; i < 7000; i++) {
+    cookie_pieces.push_back(fmt::sprintf("a%x=b", i));
+  }
+  request_headers.addCopy("cookie", absl::StrJoin(cookie_pieces, "; "));
+  auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0);
+
+  ASSERT_TRUE(response->complete());
+  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
+}
+
+// Validate that lots of tiny cookies doesn't cause a DoS (many cookie headers).
+TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingMany) {
+  initialize();
+
+  codec_client_ = makeHttpConnection(lookupPort("http"));
+  Http::TestHeaderMapImpl request_headers{{":method", "POST"},
+                                          {":path", "/test/long/url"},
+                                          {":scheme", "http"},
+                                          {":authority", "host"},
+                                          {"content-length", "0"}};
+  for (int i = 0; i < 2000; i++) {
+    request_headers.addCopy("cookie", fmt::sprintf("a%x=b", i));
+  }
+  auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0);
+
+  ASSERT_TRUE(response->complete());
+  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
+}
+
 TEST_P(DownstreamProtocolIntegrationTest, InvalidContentLength) {
   initialize();
 
@@ -850,6 +890,48 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersAccepted) {
   testLargeRequestHeaders(95, 96);
 }
 
+TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersTimeout) {
+  // Set timeout for 5 seconds, and ensure that a request with 20k+ headers can be sent.
+  testManyRequestHeaders(std::chrono::milliseconds(5000));
+}
+
+TEST_P(DownstreamProtocolIntegrationTest, LargeRequestTrailersAccepted) {
+  testLargeRequestTrailers(60, 96);
+}
+
+TEST_P(DownstreamProtocolIntegrationTest, LargeRequestTrailersRejected) {
+  testLargeRequestTrailers(66, 60);
+}
+
+TEST_P(DownstreamProtocolIntegrationTest, ManyTrailerHeaders) {
+  config_helper_.addConfigModifier(
+      [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
+          -> void { hcm.mutable_max_request_headers_kb()->set_value(96); });
+
+  Http::TestHeaderMapImpl request_trailers{};
+  for (int i = 0; i < 20000; i++) {
+    request_trailers.addCopy(std::to_string(i), "");
+  }
+
+  initialize();
+  codec_client_ = makeHttpConnection(lookupPort("http"));
+  auto encoder_decoder =
+      codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"},
+                                                          {":path", "/test/long/url"},
+                                                          {":scheme", "http"},
+                                                          {":authority", "host"}});
+  request_encoder_ = &encoder_decoder.first;
+  auto response = std::move(encoder_decoder.second);
+  codec_client_->sendTrailers(*request_encoder_, request_trailers);
+  waitForNextUpstreamRequest();
+  upstream_request_->encodeHeaders(default_response_headers_, true);
+  response->waitForEndStream();
+
+  EXPECT_TRUE(upstream_request_->complete());
+  EXPECT_TRUE(response->complete());
+  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
+}
+
 // Tests StopAllIterationAndBuffer. Verifies decode-headers-return-stop-all-filter calls decodeData
 // once after iteration is resumed.
 TEST_P(DownstreamProtocolIntegrationTest, testDecodeHeadersReturnsStopAll) {

programming_language: C++