October 8, 2019·6 min read·engineeringistiosecurityenvoyhttp

Istio's header-count attack defeats a byte-size limit

CVE-2019-15226 consumes proxy CPU with many small headers that remain below the request-header size limit. The fix addresses repeated size calculation and adds a separate bound on header count.

A request can remain below its byte limit while still imposing excessive processing work. The October 8 Istio security advisory describes that distinction in CVE-2019-15226. An attacker sends thousands of small HTTP headers whose combined size remains acceptable. Envoy's work checking their total size then consumes enough CPU to cause denial of service. Both mesh gateways and sidecars are affected.

The existing limit is not absent. It is a maximum total header size, and checking that limit is part of the vulnerable work. This makes the case more instructive than a simple missing input bound. A protective calculation can itself become expensive when it repeatedly traverses a representation controlled by the request.

I consider the proxy's processing budget part of evaluating a service mesh. Uniform request handling puts common behavior in one place, but that place must remain available under hostile input. Here the relevant input property is header count as well as total bytes, and the correction addresses both accounting work and a separate count limit.

Repeated traversal changes the cost of receiving headers

The advisory says Envoy iterates over request headers to verify that their total size is below the configured maximum. A large number of small headers makes that traversal expensive without necessarily exceeding the byte bound. The attacker does not need a correspondingly large body or an application operation that consumes CPU. The proxy performs the relevant work while processing request metadata.

The Envoy 1.11.2 release history dated October 8 identifies a cached byte size in HeaderMap as part of the fix. It also adds configurable maximum header counts. These changes address distinct aspects of the problem. Caching reduces repeated calculation, while a count bound limits the number of entries the request can supply.

The tagged HeaderMap implementation makes the accounting mechanism visible. It can return a cached size or refresh the value when it is invalid. Refreshing an invalid cache computes the total through iteration. The internal calculation adds each header key's size and each value's size, then retains the result for subsequent use.

That is a change in how the same information is obtained. A caller that needs the byte size can reuse a known total rather than repeatedly summing all entries. The byte limit still has a purpose, but the accounting path no longer needs to reconstruct the total on every query when its cache is valid.

This distinction also explains why merely reducing a byte limit would not express the entire correction. A byte limit constrains aggregate content. Header count constrains the number of entries, and caching changes the cost of repeated aggregate checks. The disclosed attack uses the relationship between those quantities rather than requiring any single enormous header.

Cached accounting must follow header mutations

The implementation includes helpers to add to or subtract from the cached total when it exists. This allows operations that know their size change to maintain the aggregate directly. Other operations invalidate the cached value, causing a later refresh to recalculate it. Correctness depends on distinguishing these mutation paths.

For example, the mutable lookup function resets the cache before returning a header entry. The caller could subsequently change the entry, so the previously known total would no longer be reliable. This is a concrete example of the maintenance cost introduced by a cache. Avoiding repeated traversal is useful only when the returned size still represents the current map.

The append helper also accounts for the separator added when appending to a nonempty header value. Its reported size change includes that extra byte as well as the appended content. Such details are necessary when a computed total enforces a security limit. An approximately maintained value would weaken the meaning of the limit even if it improved performance.

The fix therefore has two obligations. It must avoid the expensive repeated computation described by the advisory, and it must preserve accurate size information as the map changes. The tagged implementation exposes the helpers and invalidation behavior used to satisfy those requirements.

The source also distinguishes asking for a cached value from requesting a refresh through refreshByteSize. The former can return an optional value, while the latter ensures a total exists by computing one if necessary. Callers must therefore use the appropriate operation for their requirement. This interface preserves the distinction between known accounting state and work needed to establish it, instead of hiding every traversal behind an apparently cheap accessor.

Header count becomes an independent limit

Envoy's release history adds a maximum header-count setting with a default of 100. It identifies common HTTP protocol options on the connection manager for downstream requests and corresponding options for upstream connections. Runtime settings can override the default request and response header-count limits. These are upstream Envoy configuration facts, not a claim that every Istio installation exposes every field through an identical high-level setting.

That distinction between upstream capability and mesh configuration matters. Istio embeds Envoy and supplies generated configuration to it. The Istio advisory directs operators to fixed Istio versions instead of instructing them to edit an unrelated Envoy executable or assume a generic upstream configuration file controls the mesh's proxies.

A count bound also has a different compatibility meaning from a size bound. A request with many tiny headers can meet one while exceeding the other. The new control expresses the number of pieces the proxy must process, which the attack deliberately increases. Applications and intermediaries generating unusually numerous headers may therefore encounter a restriction that a total-byte limit alone would not impose.

I prefer treating that as a defined protocol-processing limit rather than an arbitrary performance tuning suggestion. The release introduces it in response to a demonstrated denial-of-service path. Its purpose is to constrain work associated with a request before ordinary application handling can assume the metadata is available.

The affected component includes sidecars

The Istio advisory explicitly names both gateways and sidecars. An operator should therefore not treat this as an ingress-only update. A gateway is a visible external entry point, but sidecar proxies also parse requests on the traffic paths they handle. Updating only a front-door Deployment leaves the other affected proxy class outside the announced remediation.

The affected releases are 1.1 through 1.1.15, 1.2 through 1.2.6 and 1.3 through 1.3.1. Corresponding fixed releases are 1.1.16, 1.2.7 and 1.3.2. The advisory states that a cluster running an affected release is vulnerable. It does not add a regex-policy prerequisite like the separate August vulnerability.

The remediation instructions explicitly include all control-plane components followed by the data plane. Pilot, Mixer, Citadel and Galley are named in that sequence. The proxy implementation processing traffic must also be upgraded, so replacing only the software that distributes configuration does not complete the instruction. Control-plane and data-plane versions are separate parts of the installed mesh.

The security patch is deliberately narrow

The Istio 1.3.2 release notes say nothing else is included beyond this security fix. They also say distroless images will become available in a few days. At the date of publication, those images are a stated forthcoming artifact, not something the announcement says is already available.

That packaging detail is relevant when an installation selects a particular image variant. A fixed version number and an expected image artifact must both correspond to something actually published. The note gives operators the current availability information without requiring a claim about what will happen later.

The advisory assigns the vulnerability a score of 7.5 and identifies CPU consumption as the denial-of-service mechanism. It does not describe stealing identities or bypassing application authorization. The operational response follows the disclosed failure by replacing the implementation doing the header accounting and applying the fixed request-processing behavior throughout the mesh.

This case shows why input limits need to describe the representation being processed. Total bytes bound one quantity, while the number of headers determines another dimension of work. Cached accounting avoids reconstructing the aggregate unnecessarily, and header-count limits constrain the entry population. The corrected Istio releases carry that response to gateways and sidecars, which both need the fixed proxy on their request paths.