[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