netty-incubator-codec-ohttp CVE-2026-63202
HIGHSeverity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Network-delivered with no authentication barrier due to OHTTP public-key design; single crafted packet achieves permanent thread exhaustion; no confidentiality or integrity impact possible.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
Lifecycle Timeline
3Blast Radius
ecosystem impact- 1 maven packages depend on io.netty.incubator:netty-incubator-codec-bhttp (1 direct, 0 indirect)
Ecosystem-wide dependent count for version 0.0.23.Final.
DescriptionGitHub Advisory
BinaryHttpParser: Unauthenticated CPU-exhaustion DoS via infinite loop in field-section decoding
- ID: BHTTP-LOOP-001
- Severity: High
- CVSS v3.1: 7.5 -
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - CWE: CWE-835 (Loop with Unreachable Exit Condition) - secondary CWE-400 (Uncontrolled Resource Consumption)
- Affected component:
codec-bhttp→io.netty.incubator.codec.bhttp.BinaryHttpParser#readFieldSection, filecodec-bhttp/src/main/java/io/netty/incubator/codec/bhttp/BinaryHttpParser.java:619-626 - Affected version: netty-incubator-codec-ohttp HEAD
d3f2b49(release0.0.22.Final+ 3 commits). The loop has existed since the parser was introduced and is present in the latest code; all published advisory fixes are already applied. - Reachable from:
io.netty.incubator.codec.ohttp.OHttpRequestResponseContext$ContentDecoder#decodeChunk(codec-ohttp/.../OHttpRequestResponseContext.java:214), i.e. the auto-wired OHTTP server and client codecs. - Confidence: High (empirically reproduced hang + thread dump against the unmodified parser).
Summary
BinaryHttpParser decodes Binary HTTP (RFC 9292) messages. An OHTTP gateway/client built on this library feeds the decrypted OHTTP body straight into BinaryHttpParser.parse(...). The field-section decoding loop terminates only on the exact condition fieldSectionLength != 0 and relies on a Java assert to guarantee forward progress. Because (a) the loop counter can be driven negative and (b) readFieldLine(...) legitimately consumes zero bytes and returns null on a truncated/over-long field line, the loop can spin forever. Assertions are disabled in any normal production JVM, so the two assert statements meant to catch this provide no protection.
A single ~17-byte Binary HTTP message - encapsulated by an unauthenticated attacker inside a normal OHTTP request, using the gateway's public key configuration - pins one Netty event-loop thread at 100% CPU permanently. A handful of such requests exhausts the entire event-loop group and takes the OHTTP gateway (or client) fully offline.
Root cause
BinaryHttpParser.java:619-626:
HeaderType lastType = HeaderType.PSEUDO_HEADER;
while (fieldSectionLength != 0) { // 619 - "!= 0", not "> 0"
int readableBytes = in.readableBytes();
lastType = readFieldLine(in, headers, lastType, trailers);
assert lastType != null; // 622 - no-op without -ea
int read = readableBytes - in.readableBytes();
assert read > 0; // 624 - no-op without -ea
fieldSectionLength -= read; // 625
}Two cooperating defects:
- Counter can never hit zero.
fieldSectionLengthis the *declared* field-section byte length read from the wire (line 592). The loop subtracts the bytes eachreadFieldLineactually consumes. If a field line consumes more bytes than the (attacker-understated) declared length,fieldSectionLengthgoes negative and!= 0stays true forever. - Zero-progress iterations.
readFieldLine(lines 654-707) returnsnullwithout consuming any bytes when the remaining buffer cannot hold a complete field line - at lines 656, 664, 670, and 681 (thein.skipBytes(sumBytes)that advances the reader is only reached on the success path, line 705). When it returnsnull,read == 0,fieldSectionLengthis unchanged, and the loop re-enters with identical state - a tight busy spin.
The only constructs that would have stopped either case are the assert statements on lines 622 and 624, which the JVM strips unless started with -ea. Production deployments do not run with assertions enabled.
Reachability (hop-by-hop, every guard resolved)
Attacker model: OHTTP gateways publish their HPKE key configuration so that *any* client can encrypt requests to them. The attacker therefore encrypts a malicious BHTTP body under the gateway's public key - a perfectly valid OHTTP request. HPKE decapsulation succeeds; the plaintext is attacker-chosen.
OHttpServerCodec.decode→OHttpRequestResponseContext.parse→ chunk decode →ContentDecoder.decodeChunk.OHttpRequestResponseContext.java:211decrypts the chunk intodecryptedChunk; line 212 cumulates it; line 214 callsbinaryHttpParser.parse(binaryHttpCumulation, completeBodyReceived)- attacker-controlled plaintext, no application code in between.parse→READ_KNOWN_LENGTH_REQUEST_HEAD→readRequestHead(line 190).readRequestHeadreads the control data, then at lines 445-451 slices all remaining readable bytes as the field section and callsreadFieldSection(..., knownLength=true, maxFieldSectionSize).- Inside
readFieldSection:
- Guard
checkFieldSectionTooLarge(fieldSectionLength, max)(line 607): bounds only the *declared* length, which the PoC sets to1. Passes - not a barrier. - Guard
in.readableBytes() < sumBytes(line 609):sumBytesis built from the *declared* length, also tiny. Passes - not a barrier. - Guards
assert(lines 622, 624): no-ops in production. Defeated by default. - Loop entered → spins forever (defects 1 + 2).
No reachable guard bounds the *actual* consumption or forces progress. maxFieldSectionSize is irrelevant because the declared length is small and the loop is CPU-bound on a fixed, small buffer (no allocation, no memory growth to trip any size cap).
Proof of concept (executed locally, benign liveness oracle)
The real codec-bhttp sources were compiled unmodified against netty 4.1.135.Final (the version pinned in pom.xml). The harness builds a valid known-length BHTTP request whose declared field-section length (0x01) is understated relative to the actual field line, then calls parse(in, true) on a worker thread with a 6-second watchdog. No payload, no side effects - purely a timing/CPU oracle.
Malicious message (17 bytes):
00 01 67 01 68 01 61 01 70 01 01 61 01 62 01 63 01
│ └method g └scheme h └auth a └path p │ └hdr a:b──┘ └ partial line
└ framing 0 (known-length request) └ declared field-section length = 1Observed (production default, assertions OFF):
[*] malicious BHTTP bytes (17): 0001670168016101700101610162016301
[!!] HANG CONFIRMED: parse() still running after 6000 ms
[!!] worker thread CPU time: 6029 ms (≈100% of one core => busy spin)
[!!] worker stack (top frames):
at io.netty.incubator.codec.bhttp.BinaryHttpParser.readFieldSection(BinaryHttpParser.java:626)
at io.netty.incubator.codec.bhttp.BinaryHttpParser.readRequestHead(BinaryHttpParser.java:451)
at io.netty.incubator.codec.bhttp.BinaryHttpParser.parse(BinaryHttpParser.java:190)CPU time ≈ wall time ⇒ a busy spin (RUNNABLE), not a blocked wait.
Controls:
- Same input with
-ea:parse()throwsAssertionErroratreadFieldSection:624immediately - proving the assertion is the only would-be guard and is absent in production. - Well-formed request (declared length matches):
parse()returnsDefaultBinaryHttpRequestpromptly - proving the harness does not hang on valid input.
PoC sources: findings/netty-incubator-codec-ohttp/raw/Poc.java (hang + control 1) and raw/Poc2.java (negative control).
Impact
Unauthenticated, pre-business-logic remote denial of service. Each malicious request permanently consumes one Netty event-loop thread at 100% CPU. Netty event-loop groups have a small fixed thread count (default 2 × cores); a handful of requests exhausts every I/O thread, after which the gateway/client accepts no further connections and serves no traffic - a complete, persistent DoS that survives until process restart. Availability impact High; no confidentiality/integrity impact.
Adversarial re-reading (attempts to refute)
- *"
maxFieldSectionSizecaps it."* No - the declared length in the PoC is1; the cap (line 607) checks the declared value only. The spin happens on a 17-byte buffer with no allocation. Refutation fails. - *"An upstream HTTP size limit /
HttpObjectAggregatorblocks it."* No - the bug is CPU-bound, not memory-bound. The whole malicious message is tiny and well within any size limit. Refutation fails. - *"This is just CVE-2024-40642 (absent input validation)."* No - that advisory was about missing validation of method/scheme/authority/path enabling injection; that fix (the
ALLOWED_TOKEN/ALLOWED_SCHEMEvalidators, lines 76-122/461-466) is present and unrelated. This is a control-flow/termination defect in field-section length accounting. Distinct class, distinct code. - *"The hang might be a harness artifact."* No - the thread dump pinpoints
readFieldSection:626; CPU≈wall confirms a spin; the-eacontrol throws at the exact assert; the well-formed control returns. The hang is for the claimed reason. - *"
completeBodyReceivedmust be true."* Not required - the loop is insidereadFieldSection, reached viareadRequestHeadonce the control data is present, independent of that flag. The flag only affects a branch taken *after*readRequestHeadreturnsnull, which never happens here.
No concrete blocker survived. Verdict: CONFIRMED.
Remediation
- Change the loop exit condition to
while (fieldSectionLength > 0)so an overshoot (negative counter) terminates. - Treat a
null/ zero-progress return fromreadFieldLinewhilefieldSectionLength > 0as a hard framing error - throwCorruptedFrameException("truncated or over-long field line")instead of re-looping. - Reject any field line whose consumed byte count would drive
fieldSectionLengthbelow 0 (the declared length must be consumed exactly, per RFC 9292 §3.6). - Do not rely on
assertfor wire-format invariants on attacker-controlled input; assertions are disabled in production. Promote lines 622/624 to explicit exceptions.
Example:
while (fieldSectionLength > 0) {
int readableBytes = in.readableBytes();
lastType = readFieldLine(in, headers, lastType, trailers);
int read = readableBytes - in.readableBytes();
if (lastType == null || read <= 0) {
throw new CorruptedFrameException("truncated or over-long field line");
}
if (read > fieldSectionLength) {
throw new CorruptedFrameException("field line exceeds declared field-section length");
}
fieldSectionLength -= read;
}Notes
- The indeterminate-length field-section path (framing indicators 2/3) shares the same loop and the same
!= 0/ zero-progress structure; the fix above should cover both. A dedicated trace ofgetIndeterminateLength(lines 538-566) under non-defaultmaxFieldSectionSizeis recorded separately as a lead. - Default
maxFieldSectionSizefor the OHTTP codecs is8 * 1024(OHttpCodecBuilder.DEFAULT_MAX_FIELD_SECTION_SIZE), and is irrelevant to this CPU-bound spin.
Articles & Coverage 2
AnalysisAI
CPU-exhaustion denial of service in netty-incubator-codec-ohttp's BinaryHttpParser permanently pins Netty event-loop threads at 100% CPU via an infinite loop in field-section decoding, reachable by any unauthenticated remote attacker. Because OHTTP gateways publish their HPKE public key by design, an attacker can encrypt a crafted ~17-byte Binary HTTP payload as a cryptographically valid OHTTP request, which passes decryption and reaches the vulnerable parser without any authentication barrier. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | The target must be running a Java application that uses `netty-incubator-codec-ohttp` or `netty-incubator-codec-bhttp` to serve OHTTP-encapsulated requests over the network, with `OHttpServerCodec` active (the default when the library is used for its intended purpose). … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The CVSS 7.5 vector (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) accurately maps to real-world exploitation conditions: the attack is network-delivered, requires no authentication, and achieves full availability impact with a single ~17-byte payload. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | Full exploit scenario with step-by-step reproduction available after sign-in. |
| Remediation | Upgrade `io.netty.incubator:netty-incubator-codec-bhttp` and `netty-incubator-codec-ohttp` to version `0.0.23.Final`, confirmed as the fixed release per GitHub Advisory GHSA-4899-mpch-38p3 and the release tag at https://github.com/netty/netty-incubator-codec-ohttp/releases/tag/netty-incubator-codec-parent-ohttp-0.0.23.Final. … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
Within 24 hours: implement network-level rate limiting (maximum 5 connections per source IP per second) and enable CPU and thread-pool monitoring with alerting on all netty-incubator-codec-ohttp systems; isolate OHTTP services to dedicated processes with automatic restart capability enabled. …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
Oracle Java SE 7 Update 6 and earlier contains multiple sandbox bypass vulnerabilities via the ClassFinder and forName m
Remote code execution in IBM Sterling B2B Integrator, Sterling Integrator, and Tivoli Common Reporting allows unauthenti
Java Runtime Environment sandbox bypass via incorrect image channel verification in 2D component allows remote unauthent
Oracle Java SE JDK/JRE 7 and 6 Update 27 and earlier allows remote code execution with complete system compromise throug
JBoss Seam 2 in Red Hat JBoss EAP 4.3.0 fails to sanitize JBoss Expression Language inputs, allowing remote attackers to
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 update 4 and earlier, 6 up
Multiple vulnerabilities in Oracle Java 7 before Update 11 allow remote attackers to execute arbitrary code by (1) using
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 2 and earlier, 6 Up
The WLS Security component in Oracle WebLogic Server 10.3.6.0, 12.1.2.0, 12.1.3.0, and 12.2.1.0 allows remote attackers
Unspecified vulnerability in the Java Runtime Environment (JRE) component in Oracle Java SE 7 Update 7 and earlier allow
Remote unauthenticated attackers can execute arbitrary code on Adobe ColdFusion servers through Java deserialization fla
The ExceptionDelegator component in Apache Struts before 2.2.3.1 interprets parameter values as OGNL expressions during
Same weakness CWE-400 – Uncontrolled Resource Consumption
View allSame technique Denial Of Service
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-4899-mpch-38p3