CVE-2026-93579: Io.netty/netty-codec-http2: netty: http/2 header field values are not validated by default (cr/lf/nul passthrough)
A flaw was found in Netty's HTTP/2 stack. This vulnerability allows a remote attacker to inject prohibited characters, such as NUL, Line Feed, and Carriage Return, into HTTP/2 header field values due to insufficient validation. When these values cross an HTTP/2 to HTTP/1.1 translation boundary, they can be exploited for request smuggling, header injection, or response splitting. This could lead to unauthorized access, data manipulation, or other security bypasses.
Other sources
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 HTTP2NAMEVALIDATOR (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.NOVALIDATION. - Only DefaultHttp2Headers(boolean validate, boolean validateValues, int) installs the real VALUEVALIDATOR (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 DEFAULTVALUEVALIDATOR → 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 MAXCHARVALUE = 255; public static byte c2b(char c) { return (byte) ((c > MAXCHARVALUE) ? '?' : 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
— Red Hat
Affected Software
Event History
Frequently Asked Questions
Which deployments are most exposed?
Deployments with an HTTP/2 to HTTP/1.1 translation boundary are the relevant exposure point. At that boundary, unvalidated NUL, LF, or CR characters in HTTP/2 header values can enable request smuggling, header injection, or response splitting.
Does exploitation require authentication or user interaction?
No. The supplied severity vector indicates network access with low attack complexity and no required privileges or user interaction.
Is the unsafe behavior enabled by default?
Yes. Netty's HTTP/2 stack does not validate header field values by default, allowing NUL, LF, and CR bytes to pass through outbound headers and decoded inbound headers.