Fedora Account System
Red Hat Associate
Red Hat Customer
HTTP/2 header field values are not validated by default (CR/LF/NUL passthrough) A public GitHub Security Advisory (GHSA-8whp-c7w8-2m72) describes the following issue: ## Summary Netty's HTTP/2 stack does not validate header field **values** by default. Genuine prohibited octets — NUL (0x00), LF (0x0A), CR (0x0D) — can be set in an HTTP/2 header value and are carried verbatim to the wire (outbound) and into decoded headers (inbound). This violates RFC 9113 §8.2.1 and becomes an exploitable request-smuggling / header- injection / response-splitting vector at an HTTP/2 ↔ HTTP/1.1 translation boundary. This report deliberately **reframes** an earlier report that blamed `io.netty.util.AsciiString.c2b(char)`. `c2b` is not the problem — see "Non-issue: c2b" below. The real gap is the absence of field-value validation in the HTTP/2 header layer. ## Impact - Severity: depends on deployment. Low on a pure end-to-end HTTP/2 hop (HPACK is length-prefixed, so embedded CR/LF do not split fields on the H2 wire). - Elevated to request smuggling / response splitting whenever a value crosses into HTTP/1.1 (proxy / gateway / adapter) where CR/LF/COLON are delimiters. - RFC 9113 §8.2.1: *"Failure to validate fields can be exploited for request smuggling attacks. ... A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position."* Such messages MUST be treated as malformed, and non-tunnelling intermediaries MUST NOT forward fields containing these octets. RFC 7540 §10.3 has equivalent language. ## Root cause: value validation is opt-in and off by default Header **names** are validated by default and are not affected: `DefaultHttp2Headers` `HTTP2_NAME_VALIDATOR` (active when `validate=true`, the default) runs `HttpHeaderValidationUtil.validateToken`, which rejects control chars, SP, uppercase, non-ASCII, and (for `CharSequence`) any char `> 0xFF`. Header **values** are only checked by an opt-in validator that is disabled by default: - `DefaultHttp2Headers()` and `DefaultHttp2Headers(boolean)` install `ValueValidator.NO_VALIDATION`. - Only `DefaultHttp2Headers(boolean validate, boolean validateValues, int)` installs the real `VALUE_VALIDATOR` (which calls `HttpHeaderValidationUtil.validateValidHeaderValue`, correctly rejecting `< 0x20` except HTAB, and 0x7F — i.e. CR/LF/NUL). - `DefaultHttp2HeadersDecoder` defaults `validateHeaderValues = false`. - No public builder exposes value validation. `Http2FrameCodecBuilder` / `AbstractHttp2ConnectionHandlerBuilder.validateHeaders` (default `true`) is wired **only** to the decoder's *name* validator, never to value validation and never to the encoder. The outbound encode path performs no value validation at any stage: ``` Http2FrameCodec.writeHeadersFrame -> DefaultHttp2ConnectionEncoder.writeHeaders0 (validateHeadersSentState: stream lifecycle only) -> DefaultHttp2FrameWriter.writeHeadersInternal (stream id / padding / weight only) -> DefaultHttp2HeadersEncoder / HpackEncoder.encodeHeaders (serializes as-is) ``` Because CR (0x0D), LF (0x0A), and NUL (0x00) are all ≤ 255, they survive the char→byte conversion unchanged and are emitted verbatim. ## Where it becomes exploitable (H2 → H1.1 translation) `HttpConversionUtil.translateHeaders()` copies values with raw `output.add(name, value)` and performs no CR/LF scan of its own. Whether the prohibited octets are caught depends entirely on whether the destination HTTP/1.1 `HttpHeaders` has value validation enabled: - `Http2StreamFrameToHttpObjectCodec(boolean isServer)` defaults `validateHeaders=true` → the resulting HTTP/1.1 headers use `DEFAULT_VALUE_VALIDATOR` → protected. - `InboundHttp2ToHttpAdapter` takes `validateHttpHeaders` from its builder. If set `false`, an HTTP/2 value containing CR+LF flows verbatim into the HTTP/1.1 message, enabling header injection / request smuggling / response splitting. ## Non-issue: `AsciiString.c2b()` is a clamp, not a security boundary ```java private static final char MAX_CHAR_VALUE = 255; public static byte c2b(char c) { return (byte) ((c > MAX_CHAR_VALUE) ? '?' : c); } ``` The earlier report proposed changing `c2b`. That is misdirected: 1. **C1 control passthrough (0x80–0x9F) is spec-compliant.** These octets are `obs-text` (%x80–FF), valid in field values. HPACK is length-prefixed, so they are never delimiters on the HTTP/2 wire. No action needed. 2. **The `> 255 → '?'` replacement is protective, not harmful.** It prevents the "ghost bits" truncation attack: were `c2b` to truncate raw (as the private, *unreachable* `c2b0` does), `\u010A` (266) would become `0x0A` (LF) and `\u010D` (269) would become `0x0D` (CR) on the wire — actual injection. The clamp closes that hole. The proposed "reject C1 in c2b" change adds no security and would break obs-text. 3. The silent `?` substitution of `> 255` chars is at most a correctness/interop concern (application String vs. wire bytes mismatch); it makes values safer, not more dangerous, and is not a vulnerability. Fixing `c2b` neither addresses the real gap nor is necessary. The fix belongs in the HTTP/2 header layer. ## Affected code - `io.netty.handler.codec.http2.DefaultHttp2Headers` — value validator off in the common constructors. - `io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder` — `validateHeaderValues` defaults to `false`. - `io.netty.handler.codec.http2.Http2FrameCodecBuilder` / `AbstractHttp2ConnectionHandlerBuilder` — no way to enable value validation; the `validateHeaders` flag reaches only the decoder's name check. - Outbound: `DefaultHttp2ConnectionEncoder`, `DefaultHttp2FrameWriter`, `HpackEncoder` — no value validation on encode. - Translation: `HttpConversionUtil.translateHeaders`, `InboundHttp2ToHttpAdapter` (when `validateHttpHeaders=false`). ## Suggested fix 1. Validate HTTP/2 field values against RFC 9113 §8.2.1 — reject NUL (0x00), LF (0x0A), CR (0x0D) at any position (the logic alread [truncated] Affected: - maven:io.netty:netty-codec-http2 affected >= 4.2.0.Final, <=4.2.17.Final; fixed unknown - maven:io.netty:netty-codec-http2 affected <=4.1.137.Final; fixed unknown Fixed versions: see advisory Advisory: https://github.com/netty/netty/security/advisories/GHSA-8whp-c7w8-2m72