CVE-2019-15226: Uncontrolled Resource Consumption (CWE-400)
value: 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.
CWE 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.
CVSS v3 base score: 7.5
Published: 2019-10-09T16:15Z
Repository: https://github.com/envoyproxy/envoy
Language: C++
Files changed: docs/root/intro/version_history.rst, include/envoy/access_log/access_log.h, include/envoy/http/header_map.h, source/common/http/conn_manager_impl.cc, source/common/http/header_map_impl.cc, source/common/http/header_map_impl.h, source/common/http/http1/codec_impl.cc, source/common/http/http2/codec_impl.cc, source/common/router/router.cc, source/extensions/access_loggers/common/access_log_base.h, source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc, source/extensions/filters/common/expr/context.cc, source/extensions/filters/http/rbac/rbac_filter.cc, test/common/http/conn_manager_impl_test.cc, test/common/http/header_map_impl_speed_test.cc, test/common/http/header_map_impl_test.cc, test/common/http/http2/codec_impl_test.cc, test/integration/http2_integration_test.cc, test/integration/http_integration.cc, test/integration/http_integration.h, test/integration/protocol_integration_test.cc
Commit date: 2019-10-08 12:16:56 -0400
Commit message: 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>

Diff: diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst
index e1de8d60e..97412ca55 100644
--- a/docs/root/intro/version_history.rst
+++ b/docs/root/intro/version_history.rst
@@ -83,10 +83,14 @@ Version history
 * upstream: added new :ref:`failure-percentage based outlier detection<arch_overview_outlier_detection_failure_percentage>` mode.
 * upstream: use p2c to select hosts for least-requests load balancers if all host weights are the same, even in cases where weights are not equal to 1.
 * 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.
 * http: added :ref:`inbound_empty_frames_flood <config_http_conn_man_stats_per_codec>` counter stat to the HTTP/2 codec stats, for tracking number of connections terminated for exceeding the limit on consecutive inbound frames with an empty payload and no end stream flag. The limit is configured by setting the :ref:`max_consecutive_inbound_frames_with_empty_payload config setting <envoy_api_field_core.Http2ProtocolOptions.max_consecutive_inbound_frames_with_empty_payload>`.
   Runtime feature `envoy.reloadable_features.http2_protocol_options.max_consecutive_inbound_frames_with_empty_payload` overrides :ref:`max_consecutive_inbound_frames_with_empty_payload setting <envoy_api_field_core.Http2ProtocolOptions.max_consecutive_inbound_frames_with_empty_payload>`. Large override value (i.e. 2147483647) effectively disables mitigation of inbound frames with empty payload.
diff --git a/include/envoy/access_log/access_log.h b/include/envoy/access_log/access_log.h
index 3648a6e44..394df8f26 100644
--- a/include/envoy/access_log/access_log.h
+++ b/include/envoy/access_log/access_log.h
@@ -76,10 +76,14 @@ class Instance {
 public:
   virtual ~Instance() = default;
 
   /**
    * 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.
    * @param stream_info supplies additional information about the request not
    * contained in the request headers.
diff --git a/include/envoy/http/header_map.h b/include/envoy/http/header_map.h
index def6d5974..0fa45cdfd 100644
--- a/include/envoy/http/header_map.h
+++ b/include/envoy/http/header_map.h
@@ -455,13 +455,45 @@ public:
    * @param value specifies the value of the header to set; it WILL be copied.
    */
   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.
    * @param key supplies the header key.
    * @return the header entry if it exists otherwise nullptr.
diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc
index 8c8151a22..33354eb6e 100644
--- a/source/common/http/conn_manager_impl.cc
+++ b/source/common/http/conn_manager_impl.cc
@@ -504,10 +504,22 @@ ConnectionManagerImpl::ActiveStream::~ActiveStream() {
   if (!stream_info_.hasAnyResponseFlag() && !stream_info_.responseCode()) {
     stream_info_.setResponseFlag(StreamInfo::ResponseFlag::DownstreamConnectionTermination);
   }
 
   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_);
   }
   for (const auto& log_handler : access_log_handlers_) {
@@ -717,18 +729,10 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers,
                      StreamInfo::ResponseCodeDetails::get().MissingHost);
       return;
     }
   }
 
-  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.
   // https://tools.ietf.org/html/rfc7230#section-5.3 We also need to check for the existence of
   // :path because CONNECT does not have a path, and we don't support that currently.
diff --git a/source/common/http/header_map_impl.cc b/source/common/http/header_map_impl.cc
index 7472b51e7..f7af625ee 100644
--- a/source/common/http/header_map_impl.cc
+++ b/source/common/http/header_map_impl.cc
@@ -293,18 +293,21 @@ struct HeaderMapImpl::StaticLookupTable : public TrieLookupTable<EntryCb> {
       return {&h.inline_headers_.Host_, &Headers::get().Host};
     });
   }
 };
 
-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_)); }
 
 HeaderMapImpl::HeaderMapImpl(
@@ -317,10 +320,24 @@ HeaderMapImpl::HeaderMapImpl(
     value_string.setCopy(value.second.c_str(), value.second.size());
     addViaMove(std::move(key_string), std::move(value_string));
   }
 }
 
+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 {
         // TODO(mattklein123) PERF: Avoid copying here if not necessary.
         HeaderString key_string;
@@ -357,25 +374,29 @@ void HeaderMapImpl::insertByKey(HeaderString&& key, HeaderString&& value) {
     key.clear();
     StaticLookupResponse ref_lookup_response = cb(*this);
     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;
   }
 }
 
 void HeaderMapImpl::addViaMove(HeaderString&& key, HeaderString&& value) {
   // If this is an inline header, we can't addViaMove, because we'll overwrite
   // 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 {
     insertByKey(std::move(key), std::move(value));
   }
@@ -406,11 +427,12 @@ void HeaderMapImpl::addReferenceKey(const LowerCaseString& key, const std::strin
 void HeaderMapImpl::addCopy(const LowerCaseString& key, uint64_t value) {
   auto* entry = getExistingInline(key.get());
   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;
   new_key.setCopy(key.get().c_str(), key.get().size());
   HeaderString new_value;
@@ -421,11 +443,12 @@ 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;
   new_key.setCopy(key.get().c_str(), key.get().size());
   HeaderString new_value;
@@ -449,17 +472,28 @@ void HeaderMapImpl::setReferenceKey(const LowerCaseString& key, const std::strin
   remove(key);
   insertByKey(std::move(ref_key), std::move(new_value));
   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;
 }
 
 const HeaderEntry* HeaderMapImpl::get(const LowerCaseString& key) const {
   for (const HeaderEntryImpl& header : headers_) {
@@ -472,10 +506,11 @@ 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;
     }
   }
 
   return nullptr;
@@ -526,30 +561,36 @@ void HeaderMapImpl::remove(const LowerCaseString& key) {
     StaticLookupResponse ref_lookup_response = cb(*this);
     removeInline(ref_lookup_response.entry_);
   } 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;
       }
     }
   }
 }
 
 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
       // static lookup table are cleared as well.
       EntryCb cb = ConstSingleton<StaticLookupTable>::get().find(entry.key().getStringView());
       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;
   });
 }
@@ -568,10 +609,11 @@ void HeaderMapImpl::dumpState(std::ostream& os, int indent_level) const {
       &iterate_data);
 }
 
 HeaderMapImpl::HeaderEntryImpl& HeaderMapImpl::maybeCreateInline(HeaderEntryImpl** entry,
                                                                  const LowerCaseString& key) {
+  cached_byte_size_.reset();
   if (*entry) {
     return **entry;
   }
 
   std::list<HeaderEntryImpl>::iterator i = headers_.insert(key);
@@ -586,10 +628,11 @@ HeaderMapImpl::HeaderEntryImpl& HeaderMapImpl::maybeCreateInline(HeaderEntryImpl
   if (*entry) {
     value.clear();
     return **entry;
   }
 
+  addSize(key.get().size() + value.size());
   std::list<HeaderEntryImpl>::iterator i = headers_.insert(key, std::move(value));
   i->entry_ = i;
   *entry = &(*i);
   return **entry;
 }
@@ -607,10 +650,12 @@ void HeaderMapImpl::removeInline(HeaderEntryImpl** ptr_to_entry) {
   if (!*ptr_to_entry) {
     return;
   }
 
   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_);
 }
 
 } // namespace Http
diff --git a/source/common/http/header_map_impl.h b/source/common/http/header_map_impl.h
index f7d3a6693..82624331f 100644
--- a/source/common/http/header_map_impl.h
+++ b/source/common/http/header_map_impl.h
@@ -14,16 +14,25 @@
 namespace Envoy {
 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##_); }
 
 #define DEFINE_INLINE_HEADER_STRUCT(name) HeaderEntryImpl* name##_;
@@ -41,11 +50,11 @@ public:
    * Appends data to header. If header already has a value, the string ',' is added between the
    * existing value and data.
    * @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(
       const std::initializer_list<std::pair<LowerCaseString, std::string>>& values);
   explicit HeaderMapImpl(const HeaderMap& rhs) : HeaderMapImpl() { copyFrom(rhs); }
@@ -69,11 +78,13 @@ public:
   void addReferenceKey(const LowerCaseString& key, const std::string& value) override;
   void addCopy(const LowerCaseString& key, uint64_t value) override;
   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;
   void iterateReverse(ConstIterateCb cb, void* context) const override;
   Lookup lookup(const LowerCaseString& key, const HeaderEntry** entry) const override;
@@ -193,14 +204,20 @@ protected:
   HeaderEntryImpl& maybeCreateInline(HeaderEntryImpl** entry, const LowerCaseString& key,
                                      HeaderString&& value);
   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)
 };
 
 using HeaderMapImplPtr = std::unique_ptr<HeaderMapImpl>;
 
diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc
index afadd309c..ee325e5b1 100644
--- a/source/common/http/http1/codec_impl.cc
+++ b/source/common/http/http1/codec_impl.cc
@@ -458,22 +458,28 @@ 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();
     throw CodecProtocolException("headers size exceeds limit");
   }
 }
 
 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.
     protocol_ = Protocol::Http10;
   }
diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc
index 1752eb299..7564a661a 100644
--- a/source/common/http/http2/codec_impl.cc
+++ b/source/common/http/http2/codec_impl.cc
@@ -507,10 +507,14 @@ int ConnectionImpl::onFrameReceived(const nghttp2_frame* frame) {
     return 0;
   }
 
   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);
       stream->headers_->addViaMove(std::move(key), std::move(stream->cookies_));
     }
@@ -618,10 +622,16 @@ 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;
   }
   }
 
@@ -806,13 +816,14 @@ int ConnectionImpl::saveHeader(const nghttp2_frame* frame, HeaderString&& name,
     // ignore this case here.
     // TODO(mattklein123): Figure out a test case that can hit this.
     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;
   } else {
     return 0;
diff --git a/source/common/router/router.cc b/source/common/router/router.cc
index db25e4f0a..ba9be9e13 100644
--- a/source/common/router/router.cc
+++ b/source/common/router/router.cc
@@ -1396,10 +1396,19 @@ Filter::UpstreamRequest::~UpstreamRequest() {
   }
   clearRequestEncoder();
 
   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_);
   }
 }
diff --git a/source/extensions/access_loggers/common/access_log_base.h b/source/extensions/access_loggers/common/access_log_base.h
index 9a6d6caa6..abe7f009d 100644
--- a/source/extensions/access_loggers/common/access_log_base.h
+++ b/source/extensions/access_loggers/common/access_log_base.h
@@ -25,10 +25,15 @@ class ImplBase : public AccessLog::Instance {
 public:
   ImplBase(AccessLog::FilterPtr filter) : filter_(std::move(filter)) {}
 
   /**
    * 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,
            const StreamInfo::StreamInfo& stream_info) override;
 
diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc
index e5bad26a3..def967e60 100644
--- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc
+++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc
@@ -93,11 +93,11 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers,
   }
   if (request_headers.EnvoyOriginalPath() != nullptr) {
     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 =
         envoy::api::v2::core::RequestMethod::METHOD_UNSPECIFIED;
     envoy::api::v2::core::RequestMethod_Parse(
@@ -121,11 +121,11 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers,
     response_properties->mutable_response_code()->set_value(stream_info.responseCode().value());
   }
   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();
 
     for (const auto& header : response_headers_to_log_) {
diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc
index cf2e3a1b0..faf29451b 100644
--- a/source/extensions/filters/common/expr/context.cc
+++ b/source/extensions/filters/common/expr/context.cc
@@ -77,11 +77,11 @@ absl::optional<CelValue> RequestWrapper::operator[](CelValue key) const {
     } else if (value == ID) {
       return convertHeaderEntry(headers_.value_->RequestId());
     } 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 {};
 }
 
diff --git a/source/extensions/filters/http/rbac/rbac_filter.cc b/source/extensions/filters/http/rbac/rbac_filter.cc
index 98e6db79b..c9c8ee8fc 100644
--- a/source/extensions/filters/http/rbac/rbac_filter.cc
+++ b/source/extensions/filters/http/rbac/rbac_filter.cc
@@ -74,10 +74,14 @@ Http::FilterHeadersStatus RoleBasedAccessControlFilter::decodeHeaders(Http::Head
       config_->engine(callbacks_->route(), Filters::Common::RBAC::EnforcementMode::Shadow);
 
   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");
       config_->stats().shadow_allowed_.inc();
     } else {
@@ -103,10 +107,14 @@ 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();
       return Http::FilterHeadersStatus::Continue;
     } else {
diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc
index f3746ae40..04f79d0c2 100644
--- a/test/common/http/conn_manager_impl_test.cc
+++ b/test/common/http/conn_manager_impl_test.cc
@@ -4407,55 +4407,10 @@ TEST_F(HttpConnectionManagerImplTest, DisableKeepAliveWhenOverloaded) {
   Buffer::OwnedImpl fake_input("1234");
   conn_manager_->onData(fake_input, false);
   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);
 
   // Kick off the incoming data.
diff --git a/test/common/http/header_map_impl_speed_test.cc b/test/common/http/header_map_impl_speed_test.cc
index b8b67b2c1..fa78cfc88 100644
--- a/test/common/http/header_map_impl_speed_test.cc
+++ b/test/common/http/header_map_impl_speed_test.cc
@@ -102,11 +102,11 @@ BENCHMARK(HeaderMapImplSetInline)->Arg(0)->Arg(1)->Arg(10)->Arg(50);
 static void HeaderMapImplGetByteSize(benchmark::State& state) {
   HeaderMapImpl headers;
   addDummyHeaders(headers, state.range(0));
   uint64_t size = 0;
   for (auto _ : state) {
-    size += headers.byteSize();
+    size += headers.byteSize().value();
   }
   benchmark::DoNotOptimize(size);
 }
 BENCHMARK(HeaderMapImplGetByteSize)->Arg(0)->Arg(1)->Arg(10)->Arg(50);
 
diff --git a/test/common/http/header_map_impl_test.cc b/test/common/http/header_map_impl_test.cc
index 5ec8f8ab5..771ce5dab 100644
--- a/test/common/http/header_map_impl_test.cc
+++ b/test/common/http/header_map_impl_test.cc
@@ -364,19 +364,33 @@ TEST(HeaderStringTest, All) {
 
 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());
   EXPECT_EQ(1, headers.size());
   EXPECT_EQ(":authority", headers.Host()->key().getStringView());
   EXPECT_EQ("hello", headers.Host()->value().getStringView());
   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;
   key.setCopy(Headers::get().CacheControl.get());
   HeaderString value;
@@ -390,47 +404,54 @@ TEST(HeaderMapImplTest, MoveIntoInline) {
   HeaderString value2;
   value2.setCopy("there", 5);
   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) {
   HeaderMapImpl headers;
 
   // Add random header and then remove by name.
   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());
   EXPECT_FALSE(headers.empty());
   headers.remove(static_key);
   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());
   headers.removeContentLength();
   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) {
   // These will match.
   LowerCaseString key1 = LowerCaseString("X-prefix-foo");
@@ -444,31 +465,37 @@ TEST(HeaderMapImplTest, RemoveRegex) {
   headers.addReference(key1, "value");
   headers.addReference(key2, "value");
   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));
   EXPECT_NE(nullptr, headers.get(key4));
   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));
 
   // Add inline and remove by regex
   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.removePrefix(LowerCaseString("content"));
   EXPECT_EQ(nullptr, headers.ContentLength());
+  EXPECT_EQ(headers.refreshByteSize(), 0);
 }
 
 TEST(HeaderMapImplTest, SetRemovesAllValues) {
   HeaderMapImpl headers;
 
@@ -482,10 +509,11 @@ TEST(HeaderMapImplTest, SetRemovesAllValues) {
 
   headers.addReference(key1, ref_value1);
   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&)>;
 
   {
     MockCb cb;
@@ -529,32 +557,36 @@ TEST(HeaderMapImplTest, DoubleInlineAdd) {
     HeaderMapImpl headers;
     const std::string foo("foo");
     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());
   }
   {
     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());
   }
   {
     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());
   }
   {
     HeaderMapImpl headers;
     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());
   }
 }
 
@@ -566,10 +598,11 @@ TEST(HeaderMapImplTest, DoubleCookieAdd) {
   const std::string bar("bar");
   const LowerCaseString& set_cookie = Http::Headers::get().SetCookie;
   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);
   ASSERT_EQ(out.size(), 2);
   ASSERT_EQ(out[0], "foo");
@@ -578,40 +611,46 @@ TEST(HeaderMapImplTest, DoubleCookieAdd) {
 
 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());
 }
 
 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());
 }
 
 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());
 }
 
 TEST(HeaderMapImplTest, AddCopy) {
   HeaderMapImpl headers;
 
   // 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();
 
   EXPECT_EQ("world", value.getStringView());
   EXPECT_EQ(5UL, value.size());
@@ -627,18 +666,23 @@ TEST(HeaderMapImplTest, AddCopy) {
 
   // Repeat with an int value.
   //
   // 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();
 
   EXPECT_EQ("42", value3.getStringView());
   EXPECT_EQ(2UL, value3.size());
@@ -660,19 +704,24 @@ TEST(HeaderMapImplTest, AddCopy) {
 
   LowerCaseString cache_control("cache-control");
   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) {
   TestHeaderMapImpl headers1;
   TestHeaderMapImpl headers2;
@@ -688,10 +737,11 @@ TEST(HeaderMapImplTest, Equality) {
 TEST(HeaderMapImplTest, LargeCharInHeader) {
   HeaderMapImpl headers;
   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());
 }
 
 TEST(HeaderMapImplTest, Iterate) {
   TestHeaderMapImpl headers;
@@ -842,10 +892,11 @@ TEST(HeaderMapImplTest, PseudoHeaderOrder) {
   MockCb cb;
 
   {
     LowerCaseString foo("hello");
     Http::TestHeaderMapImpl headers{};
+    EXPECT_EQ(headers.refreshByteSize(), 0);
     EXPECT_EQ(0UL, headers.size());
     EXPECT_TRUE(headers.empty());
 
     headers.addReferenceKey(foo, "world");
     EXPECT_EQ(1UL, headers.size());
diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc
index 7592ec098..98e1e74a3 100644
--- a/test/common/http/http2/codec_impl_test.cc
+++ b/test/common/http/http2/codec_impl_test.cc
@@ -1087,21 +1087,23 @@ TEST_P(Http2CodecImplTest, TestLargeRequestHeadersAtLimitAccepted) {
   max_request_headers_kb_ = codec_limit_kb;
   initialize();
 
   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);
 }
 
diff --git a/test/integration/http2_integration_test.cc b/test/integration/http2_integration_test.cc
index 1dbe5cd37..80a40f529 100644
--- a/test/integration/http2_integration_test.cc
+++ b/test/integration/http2_integration_test.cc
@@ -62,10 +62,12 @@ TEST_P(Http2IntegrationTest, RouterUpstreamResponseBeforeRequestComplete) {
 
 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: {}
 )EOF";
 
diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc
index be622d93f..306880eea 100644
--- a/test/integration/http_integration.cc
+++ b/test/integration/http_integration.cc
@@ -258,20 +258,21 @@ void HttpIntegrationTest::setDownstreamProtocol(Http::CodecClient::Type downstre
   config_helper_.setClientCodec(typeToCodecType(downstream_protocol_));
 }
 
 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;
   if (request_body_size) {
     response = codec_client_->makeRequestWithBody(request_headers, request_body_size);
   } 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.
   if (response_size) {
     upstream_request_->encodeData(response_size, true);
@@ -331,19 +332,20 @@ void HttpIntegrationTest::verifyResponse(IntegrationStreamDecoderPtr response,
 
   EXPECT_EQ(response->body(), expected_body);
 }
 
 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_) {
 
     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;
         break;
       }
@@ -359,12 +361,13 @@ HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& ups
   RELEASE_ASSERT(result, result.message());
 
   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,
                                                     uint64_t expected_response_size,
                                                     IntegrationStreamDecoder* response) {
@@ -914,10 +917,71 @@ void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t max_si
     EXPECT_TRUE(response->complete());
     EXPECT_EQ("200", response->headers().Status()->value().getStringView());
   }
 }
 
+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"));
 
   auto encoder_decoder =
diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h
index 1a2193556..be1d59944 100644
--- a/test/integration/http_integration.h
+++ b/test/integration/http_integration.h
@@ -120,22 +120,25 @@ protected:
   // Configured upstream to send |response_headers| and |response_body_size|
   // bytes of body downstream.
   //
   // 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();
 
   // Verifies the response_headers contains the expected_headers, and response body matches given
@@ -183,11 +186,15 @@ protected:
   void testRouterDownstreamDisconnectBeforeResponseComplete(
       ConnectionCreationFunction* creator = nullptr);
   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();
   void testRetryHittingBufferLimit();
   void testRetryAttemptCountHeader();
diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc
index 89332e322..2472d55df 100644
--- a/test/integration/protocol_integration_test.cc
+++ b/test/integration/protocol_integration_test.cc
@@ -574,10 +574,50 @@ TEST_P(DownstreamProtocolIntegrationTest, ValidZeroLengthContent) {
 
   ASSERT_TRUE(response->complete());
   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();
 
   codec_client_ = makeHttpConnection(lookupPort("http"));
 
@@ -848,10 +888,52 @@ TEST_P(DownstreamProtocolIntegrationTest, LargeRequestHeadersRejected) {
 
 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) {
   config_helper_.addFilter(R"EOF(
 name: call-decodedata-once-filter

Vulnerable code:   virtual uint64_t byteSize() const PURE;
  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;
  }

void HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data) {
    return;
      appendToHeader((*ref_lookup_response.entry_)->value(), value.getStringView());
    appendToHeader(entry->value(), value.getStringView());
    appendToHeader(entry->value(), buf);
    appendToHeader(entry->value(), value);
uint64_t HeaderMapImpl::byteSize() const {

  headers_.remove_if([&](const HeaderEntryImpl& entry) {
  HeaderEntry* name() override { return inline_headers_.name##_; }                                 \
  static void appendToHeader(HeaderString& header, absl::string_view data);
  uint64_t byteSize() const override;
  const uint32_t total =
      current_header_field_.size() + current_header_value_.size() + current_header_map_->byteSize();

  if (stream->headers_->byteSize() > max_request_headers_kb_ * 1024) {
  request_properties->set_request_headers_bytes(request_headers.byteSize());
  response_properties->set_response_headers_bytes(response_headers.byteSize());
      return CelValue::CreateInt64(info_.bytesReceived() + headers_.value_->byteSize());
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
}

    size += headers.byteSize();
      codec_limit_kb * 1024 - request_headers.byteSize() - key.length() - head_room;
  ASSERT_EQ(request_headers.byteSize() + head_room, codec_limit_kb * 1024);
    const Http::TestHeaderMapImpl& response_headers, uint32_t response_size, int upstream_index) {
  waitForNextUpstreamRequest(upstream_index);
HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices) {
          *dispatcher_, fake_upstream_connection_, TestUtility::DefaultTimeout,
void HttpIntegrationTest::waitForNextUpstreamRequest(uint64_t upstream_index) {
  waitForNextUpstreamRequest(std::vector<uint64_t>({upstream_index}));
  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);
  uint64_t waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices);
  void waitForNextUpstreamRequest(uint64_t upstream_index = 0);
Fixed code: 1.11.2 (October 8, 2019)
========================
* http: fixed CVE-2019-15226 by adding a cached byte size in HeaderMap.

   * 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.
   * 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.
   *
  virtual uint64_t byteSizeInternal() const PURE;
  // 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();
  }
uint64_t HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data) {
    return 0;
  uint64_t byte_size = 0;
    byte_size += 1;
  return data.size() + byte_size;
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;
  }
}

      const uint64_t added_size =
          appendToHeader((*ref_lookup_response.entry_)->value(), value.getStringView());
      addSize(added_size);
    addSize(key.size() + value.size());
    const uint64_t added_size = appendToHeader(entry->value(), value.getStringView());
    addSize(added_size);
    const uint64_t added_size = appendToHeader(entry->value(), buf);
    addSize(added_size);
    const uint64_t added_size = appendToHeader(entry->value(), value);
    addSize(added_size);
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.
      cached_byte_size_.reset();
        subtractSize(i->key().size() + i->value().size());
  headers_.remove_if([&prefix, this](const HeaderEntryImpl& entry) {
          const uint32_t key_value_size = (*ref_lookup_response.entry_)->key().size() +
                                          (*ref_lookup_response.entry_)->value().size();
          subtractSize(key_value_size);
      } else {
        subtractSize(entry.key().size() + entry.value().size());
  cached_byte_size_.reset();
  addSize(key.get().size() + value.size());
  const uint64_t size_to_subtract = entry->entry_->key().size() + entry->entry_->value().size();
  subtractSize(size_to_subtract);
 *
 * 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.
  HeaderEntry* name() override {                                                                   \
    cached_byte_size_.reset();                                                                     \
    return inline_headers_.name##_;                                                                \
  }                                                                                                \
    cached_byte_size_.reset();                                                                     \
  static uint64_t appendToHeader(HeaderString& header, absl::string_view data);
  absl::optional<uint64_t> byteSize() const override;
  uint64_t refreshByteSize() override;
  uint64_t byteSizeInternal() const override;
  void addSize(uint64_t size);
  void subtractSize(uint64_t size);
  // 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;

  // 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();
  // 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());
    // 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());
    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());
    }
  // 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) {
  // 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();
  }
   *
   * 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.
  request_properties->set_request_headers_bytes(request_headers.byteSize().value());
  response_properties->set_response_headers_bytes(response_headers.byteSize().value());
      return CelValue::CreateInt64(info_.bytesReceived() + headers_.value_->byteSize().value());
    // 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();
    // 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();
    size += headers.byteSize().value();
  EXPECT_EQ(headers.byteSize().value(), 0);
// 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;
}

  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), 0);
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), 0);
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), 0);
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), 0);
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  // Test that removing will return 0 byte size.
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), 0);
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.refreshByteSize(), 0);
  // Refresh byte size after adding default inline headers by reference.
  request_headers.refreshByteSize();
      codec_limit_kb * 1024 - request_headers.byteSize().value() - key.length() - head_room;
  ASSERT_EQ(request_headers.byteSize().value() + head_room, codec_limit_kb * 1024);
TEST_P(Http2IntegrationTest, LargeRequestTrailersRejected) { testLargeRequestTrailers(66, 60); }

    const Http::TestHeaderMapImpl& response_headers, uint32_t response_size, int upstream_index,
    std::chrono::milliseconds time) {
  waitForNextUpstreamRequest(upstream_index, time);
HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices,
                                                std::chrono::milliseconds connection_wait_timeout) {
          *dispatcher_, fake_upstream_connection_, connection_wait_timeout,
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::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());
}

  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);
  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);
  void testLargeHeaders(Http::TestHeaderMapImpl request_headers,
                        Http::TestHeaderMapImpl request_trailers, uint32_t size, uint32_t max_size);
  void testLargeRequestTrailers(uint32_t size, uint32_t max_size = 60);
  void testManyRequestHeaders(std::chrono::milliseconds time = TestUtility::DefaultTimeout);
// 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, 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());
}