CVE-2026-63202: Netty incubator codec bhttp Denial of Service Vulnerability
HIGHCVE-2026-63202 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting Netty incubator codec bhttp libraries in the Maven ecosystem. The issue involves unauthenticated cpu-exhaustion dos via infinite loop in field-section decoding. Remediation is available by upgrading affected packages to version 0.0.23.Final.
CVE-2026-63202 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).
How to Fix CVE-2026-63202
To remediate this vulnerability, upgrade all affected Netty incubator codec bhttp dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
<dependency>
<groupId>io.netty.incubator</groupId>
<artifactId>netty-incubator-codec-bhttp</artifactId>
<version>0.0.23.Final</version>
</dependency>
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
mvn dependency:tree -Dincludes=io.netty.incubator
How to Check if You Are Affected
To determine whether your Java application is affected by CVE-2026-63202, inspect your build dependencies using the Maven Dependency Plugin:
mvn dependency:tree -Dincludes=io.netty.incubator
Analyze the terminal output to locate any version of Netty incubator codec bhttp in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.0.23.Final), your application is affected.
Note: The mvn dependency:tree -Dincludes=io.netty.incubator command inspects the resolved dependency graph declared in your project's POM files; it does not perform automated security scanning or inspect runtime dynamic classpaths. Always verify runtime container classpaths and shaded JARs.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In Netty incubator codec bhttp, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-63202 Affected Netty incubator codec bhttp Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
io.netty.incubator:netty-incubator-codec-bhttp
|
Maven |
< 0.0.23.Final
|
0.0.23.Final
|
References
Frequently Asked Questions
What is CVE-2026-63202?
Which Netty incubator codec bhttp versions are affected?
What version fixes CVE-2026-63202?
Is netty-incubator-codec-bhttp affected?
What is the CVSS score of CVE-2026-63202?
What CWE is associated with CVE-2026-63202?
How can I check Maven dependencies for CVE-2026-63202?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-4899-mpch-38p3",
"modified" : "2026-08-20T18:43:38Z",
"published" : "2026-08-20T18:43:38Z",
"aliases" : [ "CVE-2026-63202" ],
"summary" : "netty-incubator-codec-ohttp BinaryHttpParser: Unauthenticated CPU-exhaustion DoS via infinite loop in field-section decoding",
"details" : "# BinaryHttpParser: Unauthenticated CPU-exhaustion DoS via infinite loop in field-section decoding\n\n- **ID:** BHTTP-LOOP-001\n- **Severity:** High\n- **CVSS v3.1:** 7.5 — `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`\n- **CWE:** CWE-835 (Loop with Unreachable Exit Condition) — secondary CWE-400 (Uncontrolled Resource Consumption)\n- **Affected component:** `codec-bhttp` → `io.netty.incubator.codec.bhttp.BinaryHttpParser#readFieldSection`, file `codec-bhttp/src/main/java/io/netty/incubator/codec/bhttp/BinaryHttpParser.java:619-626`\n- **Affected version:** netty-incubator-codec-ohttp HEAD `d3f2b49` (release `0.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.\n- **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.\n- **Confidence:** High (empirically reproduced hang + thread dump against the unmodified parser).\n\n## Summary\n\n`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.\n\nA 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.\n\n## Root cause\n\n`BinaryHttpParser.java:619-626`:\n\n```java\nHeaderType lastType = HeaderType.PSEUDO_HEADER;\nwhile (fieldSectionLength != 0) { // 619 — \"!= 0\", not \"> 0\"\n int readableBytes = in.readableBytes();\n lastType = readFieldLine(in, headers, lastType, trailers);\n assert lastType != null; // 622 — no-op without -ea\n int read = readableBytes - in.readableBytes();\n assert read > 0; // 624 — no-op without -ea\n fieldSectionLength -= read; // 625\n}\n```\n\nTwo cooperating defects:\n\n1. **Counter can never hit zero.** `fieldSectionLength` is the *declared* field-section byte length read from the wire (line 592). The loop subtracts the bytes each `readFieldLine` actually consumes. If a field line consumes more bytes than the (attacker-understated) declared length, `fieldSectionLength` goes negative and `!= 0` stays true forever.\n\n2. **Zero-progress iterations.** `readFieldLine` (lines 654-707) returns `null` **without consuming any bytes** when the remaining buffer cannot hold a complete field line — at lines 656, 664, 670, and 681 (the `in.skipBytes(sumBytes)` that advances the reader is only reached on the success path, line 705). When it returns `null`, `read == 0`, `fieldSectionLength` is unchanged, and the loop re-enters with identical state — a tight busy spin.\n\nThe 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.\n\n## Reachability (hop-by-hop, every guard resolved)\n\nAttacker 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.\n\n1. `OHttpServerCodec.decode` → `OHttpRequestResponseContext.parse` → chunk decode → `ContentDecoder.decodeChunk`.\n2. `OHttpRequestResponseContext.java:211` decrypts the chunk into `decryptedChunk`; line 212 cumulates it; **line 214** calls `binaryHttpParser.parse(binaryHttpCumulation, completeBodyReceived)` — attacker-controlled plaintext, no application code in between.\n3. `parse` → `READ_KNOWN_LENGTH_REQUEST_HEAD` → `readRequestHead` (line 190).\n4. `readRequestHead` reads the control data, then at lines 445-451 slices **all** remaining readable bytes as the field section and calls `readFieldSection(..., knownLength=true, maxFieldSectionSize)`.\n5. Inside `readFieldSection`:\n - **Guard `checkFieldSectionTooLarge(fieldSectionLength, max)` (line 607):** bounds only the *declared* length, which the PoC sets to `1`. **Passes — not a barrier.**\n - **Guard `in.readableBytes() < sumBytes` (line 609):** `sumBytes` is built from the *declared* length, also tiny. **Passes — not a barrier.**\n - **Guards `assert` (lines 622, 624):** no-ops in production. **Defeated by default.**\n - Loop entered → spins forever (defects 1 + 2).\n\nNo 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).\n\n## Proof of concept (executed locally, benign liveness oracle)\n\nThe 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.\n\nMalicious message (17 bytes):\n```\n00 01 67 01 68 01 61 01 70 01 01 61 01 62 01 63 01\n│ └method g └scheme h └auth a └path p │ └hdr a:b──┘ └ partial line\n└ framing 0 (known-length request) └ declared field-section length = 1\n```\n\nObserved (production default, assertions OFF):\n```\n[*] malicious BHTTP bytes (17): 0001670168016101700101610162016301\n[!!] HANG CONFIRMED: parse() still running after 6000 ms\n[!!] worker thread CPU time: 6029 ms (≈100% of one core => busy spin)\n[!!] worker stack (top frames):\n at io.netty.incubator.codec.bhttp.BinaryHttpParser.readFieldSection(BinaryHttpParser.java:626)\n at io.netty.incubator.codec.bhttp.BinaryHttpParser.readRequestHead(BinaryHttpParser.java:451)\n at io.netty.incubator.codec.bhttp.BinaryHttpParser.parse(BinaryHttpParser.java:190)\n```\nCPU time ≈ wall time ⇒ a busy spin (RUNNABLE), not a blocked wait.\n\nControls:\n- **Same input with `-ea`:** `parse()` throws `AssertionError` at `readFieldSection:624` immediately — proving the assertion is the only would-be guard and is absent in production.\n- **Well-formed request (declared length matches):** `parse()` returns `DefaultBinaryHttpRequest` promptly — proving the harness does not hang on valid input.\n\nPoC sources: `findings/netty-incubator-codec-ohttp/raw/Poc.java` (hang + control 1) and `raw/Poc2.java` (negative control).\n\n## Impact\n\nUnauthenticated, 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.\n\n## Adversarial re-reading (attempts to refute)\n\n- *\"`maxFieldSectionSize` caps it.\"* No — the declared length in the PoC is `1`; the cap (line 607) checks the declared value only. The spin happens on a 17-byte buffer with no allocation. Refutation fails.\n- *\"An upstream HTTP size limit / `HttpObjectAggregator` blocks it.\"* No — the bug is CPU-bound, not memory-bound. The whole malicious message is tiny and well within any size limit. Refutation fails.\n- *\"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_SCHEME` validators, 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.\n- *\"The hang might be a harness artifact.\"* No — the thread dump pinpoints `readFieldSection:626`; CPU≈wall confirms a spin; the `-ea` control throws at the exact assert; the well-formed control returns. The hang is for the claimed reason.\n- *\"`completeBodyReceived` must be true.\"* Not required — the loop is inside `readFieldSection`, reached via `readRequestHead` once the control data is present, independent of that flag. The flag only affects a branch taken *after* `readRequestHead` returns `null`, which never happens here.\n\nNo concrete blocker survived. Verdict: **CONFIRMED**.\n\n## Remediation\n\n1. Change the loop exit condition to `while (fieldSectionLength > 0)` so an overshoot (negative counter) terminates.\n2. Treat a `null` / zero-progress return from `readFieldLine` while `fieldSectionLength > 0` as a hard framing error — throw `CorruptedFrameException(\"truncated or over-long field line\")` instead of re-looping.\n3. Reject any field line whose consumed byte count would drive `fieldSectionLength` below 0 (the declared length must be consumed exactly, per RFC 9292 §3.6).\n4. Do not rely on `assert` for wire-format invariants on attacker-controlled input; assertions are disabled in production. Promote lines 622/624 to explicit exceptions.\n\nExample:\n```java\nwhile (fieldSectionLength > 0) {\n int readableBytes = in.readableBytes();\n lastType = readFieldLine(in, headers, lastType, trailers);\n int read = readableBytes - in.readableBytes();\n if (lastType == null || read <= 0) {\n throw new CorruptedFrameException(\"truncated or over-long field line\");\n }\n if (read > fieldSectionLength) {\n throw new CorruptedFrameException(\"field line exceeds declared field-section length\");\n }\n fieldSectionLength -= read;\n}\n```\n\n## Notes\n\n- 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 of `getIndeterminateLength` (lines 538-566) under non-default `maxFieldSectionSize` is recorded separately as a lead.\n- Default `maxFieldSectionSize` for the OHTTP codecs is `8 * 1024` (`OHttpCodecBuilder.DEFAULT_MAX_FIELD_SECTION_SIZE`), and is irrelevant to this CPU-bound spin.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Maven",
"name" : "io.netty.incubator:netty-incubator-codec-bhttp"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.0.23.Final"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.0.22.Final"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/netty/netty-incubator-codec-ohttp/security/advisories/GHSA-4899-mpch-38p3"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/netty/netty-incubator-codec-ohttp"
}, {
"type" : "WEB",
"url" : "https://github.com/netty/netty-incubator-codec-ohttp/releases/tag/netty-incubator-codec-parent-ohttp-0.0.23.Final"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-400", "CWE-835" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-08-20T18:43:38Z",
"nvd_published_at" : null
}
}