[Git][java-team/netty][master] CVE-2026-33870
Bastien Roucariès (@rouca)
gitlab at salsa.debian.org
Mon Apr 6 15:48:26 BST 2026
Bastien Roucariès pushed to branch master at Debian Java Maintainers / netty
Commits:
8c4e14cc by Bastien Roucariès at 2026-04-06T16:46:55+02:00
CVE-2026-33870
- - - - -
3 changed files:
- debian/changelog
- + debian/patches/CVE-2026-33870.patch
- debian/patches/series
Changes:
=====================================
debian/changelog
=====================================
@@ -16,6 +16,9 @@ netty (1:4.1.48-17) unstable; urgency=medium
combined with a bypass of existing size-based mitigations using
zero-byte frames, allows an user to cause excessive CPU consumption
with minimal bandwidth, rendering the server unresponsive
+ * Fix CVE-2026-33870 (Closes: #1132229):
+ netty incorrectly parses quoted strings in HTTP/1.1 chunked transfer
+ encoding extension values, enabling request smuggling attacks.
-- Bastien Roucariès <rouca at debian.org> Mon, 06 Apr 2026 15:55:51 +0200
=====================================
debian/patches/CVE-2026-33870.patch
=====================================
@@ -0,0 +1,565 @@
+From: Chris Vest <christianvest_hansen at apple.com>
+Date: Mon, 6 Apr 2026 16:43:39 +0200
+Subject: Stricter HTTP/1.1 chunk extension parsing (#16537) Motivation: Chunk
+ extensions can include quoted string values,
+ which themselves can include linebreaks and escapes for quotes. We need to
+ parse these properly to ensure we find the correct start of the chunk data.
+
+Modification:
+- Implement full RFC 9112 HTTP/1.1 compliant parsing of chunk start
+lines.
+- Add test cases from the Funky Chunks research:
+https://w4ke.info/2025/10/29/funky-chunks-2.html
+- This inclues chunk extensions with quoted strings that have linebreaks
+in them, and quoted strings that use escape codes.
+- Remove a test case that asserted support for control characters in the
+middle of chunk start lines, including after a naked chunk length field.
+Such control characters are not permitted by the standard.
+
+ Result:
+Prevents HTTP message smuggling through carefully crafted chunk
+extensions.
+
+* Revert the ByteProcessor changes
+
+* Add a benchmark for HTTP/1.1 chunk decoding
+
+* Fix chunk initial line decoding
+
+The initial line was not correctly truncated at its line break and ended
+up including some of the chunk contents.
+
+* Failing to parse chunk size must throw NumberFormatException
+
+* Line breaks are completely disallowed within chunk extensions
+
+Change the chunk parsing back to its original code, because we know that
+line breaks are not supposed to occur within chunk extensions at all.
+This means doing the SWAR search should be suitable.
+
+Modify the byte processor and add it as a validation step of the parsed
+chunk start line.
+
+Update the tests to match.
+
+* Fix checkstyle
+
+(cherry picked from commit 3b76df1)
+
+[backport]
+- drop some test
+- add function checkChunkExtensions from recent source
+
+origin: backport, https://github.com/netty/netty/commit/60e53c99f2e80aef1025e9038e33cdf261ed9819
+---
+ .../http/HttpChunkLineValidatingByteProcessor.java | 170 +++++++++++++++++++
+ .../handler/codec/http/HttpObjectDecoder.java | 73 ++++++--
+ .../java/io/netty/handler/codec/http/HttpUtil.java | 11 ++
+ .../handler/codec/http/HttpRequestDecoderTest.java | 183 ++++++++++++++++++++-
+ 4 files changed, 426 insertions(+), 11 deletions(-)
+ create mode 100644 codec-http/src/main/java/io/netty/handler/codec/http/HttpChunkLineValidatingByteProcessor.java
+
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpChunkLineValidatingByteProcessor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpChunkLineValidatingByteProcessor.java
+new file mode 100644
+index 0000000..6839ce8
+--- /dev/null
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpChunkLineValidatingByteProcessor.java
+@@ -0,0 +1,170 @@
++/*
++ * Copyright 2026 The Netty Project
++ *
++ * The Netty Project licenses this file to you under the Apache License,
++ * version 2.0 (the "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at:
++ *
++ * https://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++ * License for the specific language governing permissions and limitations
++ * under the License.
++ */
++package io.netty.handler.codec.http;
++
++import io.netty.util.ByteProcessor;
++
++import java.util.BitSet;
++
++/**
++ * Validates the chunk start line. That is, the chunk size and chunk extensions, until the CR LF pair.
++ * See <a href="https://www.rfc-editor.org/rfc/rfc9112#name-chunked-transfer-coding">RFC 9112 section 7.1</a>.
++ *
++ * <pre>{@code
++ * chunked-body = *chunk
++ * last-chunk
++ * trailer-section
++ * CRLF
++ *
++ * chunk = chunk-size [ chunk-ext ] CRLF
++ * chunk-data CRLF
++ * chunk-size = 1*HEXDIG
++ * last-chunk = 1*("0") [ chunk-ext ] CRLF
++ *
++ * chunk-data = 1*OCTET ; a sequence of chunk-size octets
++ * chunk-ext = *( BWS ";" BWS chunk-ext-name
++ * [ BWS "=" BWS chunk-ext-val ] )
++ *
++ * chunk-ext-name = token
++ * chunk-ext-val = token / quoted-string
++ * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
++ * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
++ * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
++ * obs-text = %x80-FF
++ * OWS = *( SP / HTAB )
++ * ; optional whitespace
++ * BWS = OWS
++ * ; "bad" whitespace
++ * VCHAR = %x21-7E
++ * ; visible (printing) characters
++ * }</pre>
++ */
++final class HttpChunkLineValidatingByteProcessor implements ByteProcessor {
++ private static final int SIZE = 0;
++ private static final int CHUNK_EXT_NAME = 1;
++ private static final int CHUNK_EXT_VAL_START = 2;
++ private static final int CHUNK_EXT_VAL_QUOTED = 3;
++ private static final int CHUNK_EXT_VAL_QUOTED_ESCAPE = 4;
++ private static final int CHUNK_EXT_VAL_QUOTED_END = 5;
++ private static final int CHUNK_EXT_VAL_TOKEN = 6;
++
++ static final class Match extends BitSet {
++ private static final long serialVersionUID = 49522994383099834L;
++ private final int then;
++
++ Match(int then) {
++ super(256);
++ this.then = then;
++ }
++
++ Match chars(String chars) {
++ return chars(chars, true);
++ }
++
++ Match chars(String chars, boolean value) {
++ for (int i = 0, len = chars.length(); i < len; i++) {
++ set(chars.charAt(i), value);
++ }
++ return this;
++ }
++
++ Match range(int from, int to) {
++ return range(from, to, true);
++ }
++
++ Match range(int from, int to, boolean value) {
++ for (int i = from; i <= to; i++) {
++ set(i, value);
++ }
++ return this;
++ }
++ }
++
++ private enum State {
++ Size(
++ new Match(SIZE).chars("0123456789abcdefABCDEF \t"),
++ new Match(CHUNK_EXT_NAME).chars(";")),
++ ChunkExtName(
++ new Match(CHUNK_EXT_NAME)
++ .range(0x21, 0x7E)
++ .chars(" \t")
++ .chars("(),/:<=>?@[\\]{}", false),
++ new Match(CHUNK_EXT_VAL_START).chars("=")),
++ ChunkExtValStart(
++ new Match(CHUNK_EXT_VAL_START).chars(" \t"),
++ new Match(CHUNK_EXT_VAL_QUOTED).chars("\""),
++ new Match(CHUNK_EXT_VAL_TOKEN)
++ .range(0x21, 0x7E)
++ .chars("(),/:<=>?@[\\]{}", false)),
++ ChunkExtValQuoted(
++ new Match(CHUNK_EXT_VAL_QUOTED_ESCAPE).chars("\\"),
++ new Match(CHUNK_EXT_VAL_QUOTED_END).chars("\""),
++ new Match(CHUNK_EXT_VAL_QUOTED)
++ .chars("\t !")
++ .range(0x23, 0x5B)
++ .range(0x5D, 0x7E)
++ .range(0x80, 0xFF)),
++ ChunkExtValQuotedEscape(
++ new Match(CHUNK_EXT_VAL_QUOTED)
++ .chars("\t ")
++ .range(0x21, 0x7E)
++ .range(0x80, 0xFF)),
++ ChunkExtValQuotedEnd(
++ new Match(CHUNK_EXT_VAL_QUOTED_END).chars("\t "),
++ new Match(CHUNK_EXT_NAME).chars(";")),
++ ChunkExtValToken(
++ new Match(CHUNK_EXT_VAL_TOKEN)
++ .range(0x21, 0x7E, true)
++ .chars("(),/:<=>?@[\\]{}", false),
++ new Match(CHUNK_EXT_NAME).chars(";")),
++ ;
++
++ private final Match[] matches;
++
++ State(Match... matches) {
++ this.matches = matches;
++ }
++
++ State match(byte value) {
++ for (Match match : matches) {
++ if (match.get(value)) {
++ return STATES_BY_ORDINAL[match.then];
++ }
++ }
++ if (this == Size) {
++ throw new NumberFormatException("Invalid chunk size");
++ } else {
++ throw new InvalidChunkExtensionException("Invalid chunk extension");
++ }
++ }
++ }
++
++ private static final State[] STATES_BY_ORDINAL = State.values();
++
++ private State state = State.Size;
++
++ @Override
++ public boolean process(byte value) {
++ state = state.match(value);
++ return true;
++ }
++
++ public void finish() {
++ if (state != State.Size && state != State.ChunkExtName && state != State.ChunkExtValQuotedEnd) {
++ throw new InvalidChunkExtensionException("Invalid chunk extension");
++ }
++ }
++}
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java
+index 60d4526..e3d8df7 100644
+--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java
+@@ -349,7 +349,8 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+ if (line == null) {
+ return;
+ }
+- int chunkSize = getChunkSize(line.toString());
++ checkChunkExtensions(line);
++ int chunkSize = getChunkSize(line.array(), line.arrayOffset() + line.readerIndex(), line.readableBytes());
+ this.chunkSize = chunkSize;
+ if (chunkSize == 0) {
+ currentState = State.READ_CHUNK_FOOTER;
+@@ -570,6 +571,16 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+ return ret;
+ }
+
++ private static void checkChunkExtensions(ByteBuf line) {
++ int extensionsStart = line.bytesBefore((byte) ';');
++ if (extensionsStart == -1) {
++ return;
++ }
++ HttpChunkLineValidatingByteProcessor processor = new HttpChunkLineValidatingByteProcessor();
++ line.forEachByte(processor);
++ processor.finish();
++ }
++
+ private HttpContent invalidChunk(ByteBuf in, Exception cause) {
+ currentState = State.BAD_MESSAGE;
+
+@@ -743,17 +754,39 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+ protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
+ protected abstract HttpMessage createInvalidMessage();
+
+- private static int getChunkSize(String hex) {
+- hex = hex.trim();
+- for (int i = 0; i < hex.length(); i ++) {
+- char c = hex.charAt(i);
+- if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
+- hex = hex.substring(0, i);
+- break;
+- }
++ private static int getChunkSize(byte[] hex, int start, int length) {
++ // trim the leading bytes of white spaces, if any
++ final int skipped = skipWhiteSpaces(hex, start, length);
++ if (skipped == length) {
++ // empty case
++ throw new NumberFormatException();
+ }
++ start += skipped;
++ length -= skipped;
++ int result = 0;
++ for (int i = 0; i < length; i++) {
++ final int digit = StringUtil.decodeHexNibble(hex[start + i]);
++ if (digit == -1) {
++ // uncommon path
++ final byte b = hex[start + i];
++ if (b == ';' || isControlOrWhitespaceAsciiChar(b)) {
++ if (i == 0) {
++ // empty case
++ throw new NumberFormatException("Empty chunk size");
++ }
++ return result;
++ }
++ // non-hex char fail-fast path
++ throw new NumberFormatException("Invalid character in chunk size");
++ }
++ result *= 16;
++ result += digit;
++ if (result < 0) {
++ throw new NumberFormatException("Chunk size overflow: " + result);
+
+- return Integer.parseInt(hex, 16);
++ }
++ }
++ return result;
+ }
+
+ private static String[] splitInitialLine(AppendableCharSequence sb) {
+@@ -987,4 +1020,24 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+ return new TooLongFrameException("An HTTP line is larger than " + maxLength + " bytes.");
+ }
+ }
++
++ private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
++
++ static {
++ ISO_CONTROL_OR_WHITESPACE = new boolean[256];
++ for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
++ ISO_CONTROL_OR_WHITESPACE[128 + b] = Character.isISOControl(b) || isWhitespace(b);
++ }
++ }
++
++ private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
++ @Override
++ public boolean process(byte value) {
++ return ISO_CONTROL_OR_WHITESPACE[128 + value];
++ }
++ };
++
++ private static boolean isControlOrWhitespaceAsciiChar(byte b) {
++ return ISO_CONTROL_OR_WHITESPACE[128 + b];
++ }
+ }
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java
+index 512d841..a30a738 100644
+--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java
+@@ -665,6 +665,17 @@ public final class HttpUtil {
+ // Reject the message as invalid
+ throw new IllegalArgumentException(
+ "Content-Length value is not a number: " + firstField, e);
++
++ private static final long TOKEN_CHARS_HIGH = 0x57ffffffc7fffffeL;
++ private static final long TOKEN_CHARS_LOW = 0x3ff6cfa00000000L;
++
++ static boolean isValidTokenChar(byte octet) {
++ if (octet < 0) {
++ return false;
++ }
++ if (octet < 64) {
++ return 0 != (TOKEN_CHARS_LOW & 1L << octet);
+ }
++ return 0 != (TOKEN_CHARS_HIGH & 1L << octet - 64);
+ }
+ }
+diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java
+index e90c6af..7385885 100644
+--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java
++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestDecoderTest.java
+@@ -26,7 +26,7 @@ import org.junit.Test;
+
+ import java.util.List;
+
+-import static io.netty.handler.codec.http.HttpHeaderNames.*;
++import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
+ import static io.netty.handler.codec.http.HttpHeadersTestUtils.of;
+ import static org.hamcrest.CoreMatchers.instanceOf;
+ import static org.hamcrest.CoreMatchers.is;
+@@ -648,6 +648,187 @@ public class HttpRequestDecoderTest {
+ assertFalse(channel.finish());
+ }
+
++ @Test
++ void mustParsedChunkExtensionsWithQuotedStrings() throws Exception {
++ // See full explanation: https://w4ke.info/2025/10/29/funky-chunks-2.html
++ String requestStr = "GET /one HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n\r\n" +
++ "1;a=\" ;\t\"\r\n" + // chunk extension quote end
++ "Y\r\n" +
++ "0\r\n" +
++ "\r\n";
++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++ HttpRequest request = channel.readInbound();
++ assertFalse(request.decoderResult().isFailure()); // We parse the headers just fine.
++ assertTrue(request.headers().names().contains("Transfer-Encoding"));
++ assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
++ HttpContent content = channel.readInbound();
++ DecoderResult decoderResult = content.decoderResult();
++ assertFalse(decoderResult.isFailure()); // And we parse the chunk.
++ content.release();
++ LastHttpContent last = channel.readInbound();
++ assertEquals(0, last.content().readableBytes());
++ last.release();
++ assertFalse(channel.finish()); // And there are no other chunks parsed.
++ }
++
++ @Test
++ void mustRejectChunkExtensionsWithLineBreaksInQuotedStrings() throws Exception {
++ // See full explanation: https://w4ke.info/2025/10/29/funky-chunks-2.html
++ String requestStr = "GET /one HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n\r\n" +
++ "1;a=\"\r\n" + // chunk extension quote start
++ "X\r\n" +
++ "0\r\n\r\n" +
++ "GET /two HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n\r\n" +
++ "\"\r\n" + // chunk extension quote end
++ "Y\r\n" +
++ "0\r\n" +
++ "\r\n";
++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++ HttpRequest request = channel.readInbound();
++ assertFalse(request.decoderResult().isFailure()); // We parse the headers just fine.
++ assertTrue(request.headers().names().contains("Transfer-Encoding"));
++ assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
++ HttpContent content = channel.readInbound();
++ DecoderResult decoderResult = content.decoderResult();
++ assertTrue(decoderResult.isFailure()); // Chunk extension is not allowed to contain line breaks.
++ assertThat(decoderResult.cause()).isInstanceOf(InvalidChunkExtensionException.class);
++ content.release();
++ assertFalse(channel.finish()); // And there are no other chunks parsed.
++ }
++
++ @Test
++ void mustParseChunkExtensionsWithQuotedStringsAndEscapes() throws Exception {
++ // See full explanation: https://w4ke.info/2025/10/29/funky-chunks-2.html
++ String requestStr = "GET /one HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n\r\n" +
++ "1;a=\" \\\";\t\"\r\n" +
++ "Y\r\n" +
++ "0\r\n" +
++ "\r\n";
++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++ HttpRequest request = channel.readInbound();
++ assertFalse(request.decoderResult().isFailure()); // We parse the headers just fine.
++ assertTrue(request.headers().names().contains("Transfer-Encoding"));
++ assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
++ HttpContent content = channel.readInbound();
++ DecoderResult decoderResult = content.decoderResult();
++ assertFalse(decoderResult.isFailure()); // And we parse the chunk.
++ content.release();
++ LastHttpContent last = channel.readInbound();
++ assertEquals(0, last.content().readableBytes());
++ last.release();
++ assertFalse(channel.finish()); // And there are no other chunks parsed.
++ }
++
++ @Test
++ void mustRejectChunkExtensionsWithEscapedLineBreakInQuotedStrings() throws Exception {
++ // See full explanation: https://w4ke.info/2025/10/29/funky-chunks-2.html
++ String requestStr = "GET /one HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n\r\n" +
++ "1;a=\" \\\n;\t\"\r\n" +
++ "Y\r\n" +
++ "0\r\n" +
++ "\r\n";
++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++ HttpRequest request = channel.readInbound();
++ assertFalse(request.decoderResult().isFailure()); // We parse the headers just fine.
++ assertTrue(request.headers().names().contains("Transfer-Encoding"));
++ assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
++ HttpContent content = channel.readInbound();
++ DecoderResult decoderResult = content.decoderResult();
++ assertTrue(decoderResult.isFailure()); // Chunk extension is not allowed to contain line breaks.
++ assertThat(decoderResult.cause()).isInstanceOf(InvalidChunkExtensionException.class);
++ content.release();
++ assertFalse(channel.finish()); // And there are no other chunks parsed.
++ }
++
++ @Test
++ void mustRejectChunkExtensionsWithEscapedCarriageReturnInQuotedStrings() throws Exception {
++ // See full explanation: https://w4ke.info/2025/10/29/funky-chunks-2.html
++ String requestStr = "GET /one HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n\r\n" +
++ "1;a=\" \\\r;\t\"\r\n" +
++ "Y\r\n" +
++ "0\r\n" +
++ "\r\n";
++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++ HttpRequest request = channel.readInbound();
++ assertFalse(request.decoderResult().isFailure()); // We parse the headers just fine.
++ assertTrue(request.headers().names().contains("Transfer-Encoding"));
++ assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
++ HttpContent content = channel.readInbound();
++ DecoderResult decoderResult = content.decoderResult();
++ assertTrue(decoderResult.isFailure()); // Chunk extension is not allowed to contain carraige return.
++ assertThat(decoderResult.cause()).isInstanceOf(InvalidChunkExtensionException.class);
++ content.release();
++ assertFalse(channel.finish()); // And there are no other chunks parsed.
++ }
++
++ @Test
++ void lineLengthRestrictionMustNotApplyToChunkContents() throws Exception {
++ char[] chars = new char[10000];
++ Arrays.fill(chars, 'a');
++ String requestContent = new String(chars);
++ String requestStr = "POST /one HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n\r\n" +
++ Integer.toHexString(chars.length) + "\r\n" +
++ requestContent + "\r\n" +
++ "0\r\n\r\n";
++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++ HttpRequest request = channel.readInbound();
++ assertFalse(request.decoderResult().isFailure()); // We parse the headers just fine.
++ assertTrue(request.headers().names().contains("Transfer-Encoding"));
++ assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
++ int contentLength = 0;
++ HttpContent content;
++ do {
++ content = channel.readInbound();
++ DecoderResult decoderResult = content.decoderResult();
++ if (decoderResult.cause() != null) {
++ throw new Exception(decoderResult.cause());
++ }
++ assertFalse(decoderResult.isFailure()); // And we parse the chunk.
++ contentLength += content.content().readableBytes();
++ content.release();
++ } while (!(content instanceof LastHttpContent));
++ assertEquals(chars.length, contentLength);
++ assertFalse(channel.finish()); // And there are no other chunks parsed.
++ }
++
++ @Test
++ void mustRejectChunkSizeWithNonHexadecimalCharacters() throws Exception {
++ String requestStr = "POST /one HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Transfer-Encoding: chunked\r\n" +
++ "\r\n" +
++ "test\r\n\r\n" + // chunk extension quote start
++ "\r\n";
++ EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++ assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++ HttpRequest request = channel.readInbound();
++ assertFalse(request.decoderResult().isFailure()); // We parse the headers
++ HttpContent content = channel.readInbound();
++ assertTrue(content.decoderResult().isFailure());
++ assertThat(content.decoderResult().cause()).isInstanceOf(NumberFormatException.class);
++ assertFalse(channel.finish());
++ }
++
+ @Test
+ public void testContentLengthHeaderAndChunked() {
+ String requestStr = "POST / HTTP/1.1\r\n" +
=====================================
debian/patches/series
=====================================
@@ -34,3 +34,4 @@ CVE-2025-58056.patch
CVE-2025-67735.patch
CVE-2025-58057_post1.patch
CVE-2026-33871.patch
+CVE-2026-33870.patch
View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/8c4e14cce4f6c750cda0eb657d108ce822d4437a
--
View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/8c4e14cce4f6c750cda0eb657d108ce822d4437a
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20260406/816b1181/attachment.htm>
More information about the pkg-java-commits
mailing list