[Git][java-team/netty][stretch] Import Debian changes 1:4.1.7-2+deb9u4

Markus Koschany (@apo) gitlab at salsa.debian.org
Thu Jan 26 22:25:08 GMT 2023



Markus Koschany pushed to branch stretch at Debian Java Maintainers / netty


Commits:
3b76e8b6 by Markus Koschany at 2023-01-26T23:24:28+01:00
Import Debian changes 1:4.1.7-2+deb9u4

netty (1:4.1.7-2+deb9u4) stretch-security; urgency=high
..
  * Non-maintainer upload by the ELTS team.
  * Fix CVE-2021-37136, CVE-2021-37137, CVE-2021-43797 and CVE-2022-41915.
    Several out-of-memory, stack overflow or HTTP request smuggling
    vulnerabilities have been discovered in Netty which may allow attackers to
    cause a denial of service or bypass restrictions when used as a proxy.

- - - - -


6 changed files:

- debian/changelog
- + debian/patches/CVE-2021-37136.patch
- + debian/patches/CVE-2021-37137.patch
- + debian/patches/CVE-2021-43797.patch
- + debian/patches/CVE-2022-41915.patch
- debian/patches/series


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,13 @@
+netty (1:4.1.7-2+deb9u4) stretch-security; urgency=high
+
+  * Non-maintainer upload by the ELTS team.
+  * Fix CVE-2021-37136, CVE-2021-37137, CVE-2021-43797 and CVE-2022-41915.
+    Several out-of-memory, stack overflow or HTTP request smuggling
+    vulnerabilities have been discovered in Netty which may allow attackers to
+    cause a denial of service or bypass restrictions when used as a proxy.
+
+ -- Markus Koschany <apo at debian.org>  Sun, 15 Jan 2023 22:03:28 +0100
+
 netty (1:4.1.7-2+deb9u3) stretch-security; urgency=high
 
   * CVE-2021-21290: Prevent an insecure temporary file issue that could lead to
@@ -11,7 +21,7 @@ netty (1:4.1.7-2+deb9u2) stretch-security; urgency=high
   * Detect missing colon when parsing http headers with no value
     (CVE-2019-20444) (Closes: #950966)
   * Correctly handle Content-Length header that is accompanied by a second
-    Content-Length header, or by a Transfer-Encoding header, by removing the 
+    Content-Length header, or by a Transfer-Encoding header, by removing the
     extra Content-Length header. (CVE-2019-20445) (Closes: #950967)
   * Prevent denial of service resulting from unbounded memory allocation while
     decoding a ZlibEncoded byte stream, whereby an attacker could send a large


=====================================
debian/patches/CVE-2021-37136.patch
=====================================
@@ -0,0 +1,80 @@
+From: Markus Koschany <apo at debian.org>
+Date: Thu, 12 Jan 2023 22:00:10 +0100
+Subject: CVE-2021-37136
+
+Bug-Debian: https://bugs.debian.org/1014769
+Origin: https://github.com/netty/netty/commit/41d3d61a61608f2223bb364955ab2045dd5e4020
+---
+ .../handler/codec/compression/Bzip2BlockDecompressor.java |  5 +++++
+ .../netty/handler/codec/compression/Bzip2Constants.java   |  2 ++
+ .../io/netty/handler/codec/compression/Bzip2Decoder.java  | 15 ++++++++-------
+ 3 files changed, 15 insertions(+), 7 deletions(-)
+
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java
+index ae05568..5a0b497 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2BlockDecompressor.java
+@@ -228,6 +228,11 @@ final class Bzip2BlockDecompressor {
+                 bwtBlock[bwtBlockLength++] = nextByte;
+             }
+         }
++        if (bwtBlockLength > MAX_BLOCK_LENGTH) {
++            throw new DecompressionException("block length exceeds max block length: "
++                    + bwtBlockLength + " > " + MAX_BLOCK_LENGTH);
++        }
++
+         this.bwtBlockLength = bwtBlockLength;
+         initialiseInverseBWT();
+         return true;
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java
+index c0283a7..21b9a2b 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Constants.java
+@@ -49,6 +49,8 @@ final class Bzip2Constants {
+     static final int MIN_BLOCK_SIZE = 1;
+     static final int MAX_BLOCK_SIZE = 9;
+ 
++    static final int MAX_BLOCK_LENGTH = MAX_BLOCK_SIZE * BASE_BLOCK_SIZE;
++
+     /**
+      * Maximum possible Huffman alphabet size.
+      */
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java
+index cb5cdd3..8fbb612 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/Bzip2Decoder.java
+@@ -283,26 +283,27 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
+                 }
+ 
+                 final int blockLength = blockDecompressor.blockLength();
+-                final ByteBuf uncompressed = ctx.alloc().buffer(blockLength);
+-                boolean success = false;
++                ByteBuf uncompressed = ctx.alloc().buffer(blockLength);
+                 try {
+                     int uncByte;
+                     while ((uncByte = blockDecompressor.read()) >= 0) {
+                         uncompressed.writeByte(uncByte);
+                     }
+-
++                    // We did read all the data, lets reset the state and do the CRC check.
++                    currentState = State.INIT_BLOCK;
+                     int currentBlockCRC = blockDecompressor.checkCRC();
+                     streamCRC = (streamCRC << 1 | streamCRC >>> 31) ^ currentBlockCRC;
+ 
+                     out.add(uncompressed);
+-                    success = true;
++                    uncompressed = null;
+                 } finally {
+-                    if (!success) {
++                    if (uncompressed != null) {
+                         uncompressed.release();
+                     }
+                 }
+-                currentState = State.INIT_BLOCK;
+-                break;
++                // Return here so the ByteBuf that was put in the List will be forwarded to the user and so can be
++                // released as soon as possible.
++                return;
+             case EOF:
+                 in.skipBytes(in.readableBytes());
+                 return;


=====================================
debian/patches/CVE-2021-37137.patch
=====================================
@@ -0,0 +1,182 @@
+From: Markus Koschany <apo at debian.org>
+Date: Thu, 12 Jan 2023 22:03:43 +0100
+Subject: CVE-2021-37137
+
+Bug-Debian: https://bugs.debian.org/1014769
+Origin: https://github.com/netty/netty/commit/6da4956b31023ae967451e1d94ff51a746a9194f
+---
+ .../io/netty/handler/codec/compression/Snappy.java | 29 +++++++++++---
+ .../codec/compression/SnappyFrameDecoder.java      | 46 ++++++++++++++++++----
+ 2 files changed, 62 insertions(+), 13 deletions(-)
+
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java b/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java
+index ffba469..d7b6276 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/Snappy.java
+@@ -38,12 +38,11 @@ public final class Snappy {
+     private static final int COPY_2_BYTE_OFFSET = 2;
+     private static final int COPY_4_BYTE_OFFSET = 3;
+ 
+-    private State state = State.READY;
++    private State state = State.READING_PREAMBLE;
+     private byte tag;
+     private int written;
+ 
+     private enum State {
+-        READY,
+         READING_PREAMBLE,
+         READING_TAG,
+         READING_LITERAL,
+@@ -51,7 +50,7 @@ public final class Snappy {
+     }
+ 
+     public void reset() {
+-        state = State.READY;
++        state = State.READING_PREAMBLE;
+         tag = 0;
+         written = 0;
+     }
+@@ -278,8 +277,6 @@ public final class Snappy {
+     public void decode(ByteBuf in, ByteBuf out) {
+         while (in.isReadable()) {
+             switch (state) {
+-            case READY:
+-                state = State.READING_PREAMBLE;
+             case READING_PREAMBLE:
+                 int uncompressedLength = readPreamble(in);
+                 if (uncompressedLength == PREAMBLE_NOT_FULL) {
+@@ -288,7 +285,6 @@ public final class Snappy {
+                 }
+                 if (uncompressedLength == 0) {
+                     // Should never happen, but it does mean we have nothing further to do
+-                    state = State.READY;
+                     return;
+                 }
+                 out.ensureWritable(uncompressedLength);
+@@ -384,6 +380,27 @@ public final class Snappy {
+         return 0;
+     }
+ 
++    /**
++     * Get the length varint (a series of bytes, where the lower 7 bits
++     * are data and the upper bit is a flag to indicate more bytes to be
++     * read).
++     *
++     * @param in The input buffer to get the preamble from
++     * @return The calculated length based on the input buffer, or 0 if
++     *   no preamble is able to be calculated
++     */
++    int getPreamble(ByteBuf in) {
++        if (state == State.READING_PREAMBLE) {
++            int readerIndex = in.readerIndex();
++            try {
++                return readPreamble(in);
++            } finally {
++                in.readerIndex(readerIndex);
++            }
++        }
++        return 0;
++    }
++
+     /**
+      * Reads a literal from the input buffer directly to the output buffer.
+      * A "literal" is an uncompressed segment of data stored directly in the
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java
+index 3f20698..9073495 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameDecoder.java
+@@ -45,13 +45,19 @@ public class SnappyFrameDecoder extends ByteToMessageDecoder {
+     }
+ 
+     private static final int SNAPPY_IDENTIFIER_LEN = 6;
++    // See https://github.com/google/snappy/blob/1.1.9/framing_format.txt#L95
+     private static final int MAX_UNCOMPRESSED_DATA_SIZE = 65536 + 4;
++    // See https://github.com/google/snappy/blob/1.1.9/framing_format.txt#L82
++    private static final int MAX_DECOMPRESSED_DATA_SIZE = 65536;
++    // See https://github.com/google/snappy/blob/1.1.9/framing_format.txt#L82
++    private static final int MAX_COMPRESSED_CHUNK_SIZE = 16777216 - 1;
+ 
+     private final Snappy snappy = new Snappy();
+     private final boolean validateChecksums;
+ 
+     private boolean started;
+     private boolean corrupted;
++    private int numBytesToSkip;
+ 
+     /**
+      * Creates a new snappy-framed decoder with validation of checksums
+@@ -82,6 +88,16 @@ public class SnappyFrameDecoder extends ByteToMessageDecoder {
+             return;
+         }
+ 
++        if (numBytesToSkip != 0) {
++            // The last chunkType we detected was RESERVED_SKIPPABLE and we still have some bytes to skip.
++            int skipBytes = Math.min(numBytesToSkip, in.readableBytes());
++            in.skipBytes(skipBytes);
++            numBytesToSkip -= skipBytes;
++
++            // Let's return and try again.
++            return;
++        }
++
+         try {
+             int idx = in.readerIndex();
+             final int inSize = in.readableBytes();
+@@ -123,12 +139,15 @@ public class SnappyFrameDecoder extends ByteToMessageDecoder {
+                         throw new DecompressionException("Received RESERVED_SKIPPABLE tag before STREAM_IDENTIFIER");
+                     }
+ 
+-                    if (inSize < 4 + chunkLength) {
+-                        // TODO: Don't keep skippable bytes
+-                        return;
+-                    }
++                    in.skipBytes(4);
+ 
+-                    in.skipBytes(4 + chunkLength);
++                    int skipBytes = Math.min(chunkLength, in.readableBytes());
++                    in.skipBytes(skipBytes);
++                    if (skipBytes != chunkLength) {
++                        // We could skip all bytes, let's store the remaining so we can do so once we receive more
++                        // data.
++                        numBytesToSkip = chunkLength - skipBytes;
++                    }
+                     break;
+                 case RESERVED_UNSKIPPABLE:
+                     // The spec mandates that reserved unskippable chunks must immediately
+@@ -141,7 +160,8 @@ public class SnappyFrameDecoder extends ByteToMessageDecoder {
+                         throw new DecompressionException("Received UNCOMPRESSED_DATA tag before STREAM_IDENTIFIER");
+                     }
+                     if (chunkLength > MAX_UNCOMPRESSED_DATA_SIZE) {
+-                        throw new DecompressionException("Received UNCOMPRESSED_DATA larger than 65540 bytes");
++                        throw new DecompressionException("Received UNCOMPRESSED_DATA larger than " +
++                                MAX_UNCOMPRESSED_DATA_SIZE + " bytes");
+                     }
+ 
+                     if (inSize < 4 + chunkLength) {
+@@ -162,13 +182,25 @@ public class SnappyFrameDecoder extends ByteToMessageDecoder {
+                         throw new DecompressionException("Received COMPRESSED_DATA tag before STREAM_IDENTIFIER");
+                     }
+ 
++                    if (chunkLength > MAX_COMPRESSED_CHUNK_SIZE) {
++                        throw new DecompressionException("Received COMPRESSED_DATA that contains" +
++                                " chunk that exceeds " + MAX_COMPRESSED_CHUNK_SIZE + " bytes");
++                    }
++
+                     if (inSize < 4 + chunkLength) {
+                         return;
+                     }
+ 
+                     in.skipBytes(4);
+                     int checksum = in.readIntLE();
+-                    ByteBuf uncompressed = ctx.alloc().buffer();
++
++                    int uncompressedSize = snappy.getPreamble(in);
++                    if (uncompressedSize > MAX_DECOMPRESSED_DATA_SIZE) {
++                        throw new DecompressionException("Received COMPRESSED_DATA that contains" +
++                                " uncompressed data that exceeds " + MAX_DECOMPRESSED_DATA_SIZE + " bytes");
++                    }
++
++                    ByteBuf uncompressed = ctx.alloc().buffer(uncompressedSize, MAX_DECOMPRESSED_DATA_SIZE);
+                     try {
+                         if (validateChecksums) {
+                             int oldWriterIndex = in.writerIndex();


=====================================
debian/patches/CVE-2021-43797.patch
=====================================
@@ -0,0 +1,260 @@
+From: Markus Koschany <apo at debian.org>
+Date: Thu, 12 Jan 2023 22:05:23 +0100
+Subject: CVE-2021-43797
+
+Bug-Debian: https://bugs.debian.org/1001437
+Origin: https://github.com/netty/netty/commit/07aa6b5938a8b6ed7a6586e066400e2643897323
+---
+ .../handler/codec/http/DefaultHttpHeaders.java     |  8 ++
+ .../handler/codec/http/HttpRequestDecoderTest.java | 87 ++++++++++++++++++++--
+ .../codec/http/HttpResponseDecoderTest.java        | 78 +++++++++++++++++++
+ 3 files changed, 167 insertions(+), 6 deletions(-)
+
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java
+index 6204f3e..aee71cf 100644
+--- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java
+@@ -324,6 +324,10 @@ public class DefaultHttpHeaders extends HttpHeaders {
+ 
+     private static void validateHeaderNameElement(byte value) {
+         switch (value) {
++        case 0x1c:
++        case 0x1d:
++        case 0x1e:
++        case 0x1f:
+         case 0x00:
+         case '\t':
+         case '\n':
+@@ -349,6 +353,10 @@ public class DefaultHttpHeaders extends HttpHeaders {
+ 
+     private static void validateHeaderNameElement(char value) {
+         switch (value) {
++        case 0x1c:
++        case 0x1d:
++        case 0x1e:
++        case 0x1f:
+         case 0x00:
+         case '\t':
+         case '\n':
+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 aa023df..1569ecb 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
+@@ -15,6 +15,7 @@
+  */
+ package io.netty.handler.codec.http;
+ 
++import io.netty.buffer.ByteBuf;
+ import io.netty.buffer.Unpooled;
+ import io.netty.channel.embedded.EmbeddedChannel;
+ import io.netty.util.AsciiString;
+@@ -271,6 +272,75 @@ public class HttpRequestDecoderTest {
+         assertFalse(channel.finishAndReleaseAll());
+     }
+ 
++    @Test
++    public void testHeaderNameStartsWithControlChar1c() {
++        testHeaderNameStartsWithControlChar(0x1c);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar1d() {
++        testHeaderNameStartsWithControlChar(0x1d);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar1e() {
++        testHeaderNameStartsWithControlChar(0x1e);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar1f() {
++        testHeaderNameStartsWithControlChar(0x1f);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar0c() {
++        testHeaderNameStartsWithControlChar(0x0c);
++    }
++
++    private void testHeaderNameStartsWithControlChar(int controlChar) {
++        ByteBuf requestBuffer = Unpooled.buffer();
++        requestBuffer.writeCharSequence("GET /some/path HTTP/1.1\r\n" +
++                "Host: netty.io\r\n", CharsetUtil.US_ASCII);
++        requestBuffer.writeByte(controlChar);
++        requestBuffer.writeCharSequence("Transfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII);
++        testInvalidHeaders0(requestBuffer);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1c() {
++        testHeaderNameEndsWithControlChar(0x1c);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1d() {
++        testHeaderNameEndsWithControlChar(0x1d);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1e() {
++        testHeaderNameEndsWithControlChar(0x1e);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1f() {
++        testHeaderNameEndsWithControlChar(0x1f);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar0c() {
++        testHeaderNameEndsWithControlChar(0x0c);
++    }
++
++    private void testHeaderNameEndsWithControlChar(int controlChar) {
++        ByteBuf requestBuffer = Unpooled.buffer();
++        requestBuffer.writeCharSequence("GET /some/path HTTP/1.1\r\n" +
++                "Host: netty.io\r\n", CharsetUtil.US_ASCII);
++        requestBuffer.writeCharSequence("Transfer-Encoding", CharsetUtil.US_ASCII);
++        requestBuffer.writeByte(controlChar);
++        requestBuffer.writeCharSequence(": chunked\r\n\r\n", CharsetUtil.US_ASCII);
++        testInvalidHeaders0(requestBuffer);
++    }
++
+     @Test
+     public void testWhitespace() {
+         String requestStr = "GET /some/path HTTP/1.1\r\n" +
+@@ -280,9 +350,9 @@ public class HttpRequestDecoderTest {
+     }
+ 
+     @Test
+-    public void testWhitespaceBeforeTransferEncoding01() {
++    public void testWhitespaceInTransferEncoding01() {
+         String requestStr = "GET /some/path HTTP/1.1\r\n" +
+-                " Transfer-Encoding : chunked\r\n" +
++                "Transfer-Encoding : chunked\r\n" +
+                 "Content-Length: 1\r\n" +
+                 "Host: netty.io\r\n\r\n" +
+                 "a";
+@@ -290,9 +360,9 @@ public class HttpRequestDecoderTest {
+     }
+ 
+     @Test
+-    public void testWhitespaceBeforeTransferEncoding02() {
++    public void testWhitespaceInTransferEncoding02() {
+         String requestStr = "POST / HTTP/1.1" +
+-                " Transfer-Encoding : chunked\r\n" +
++                "Transfer-Encoding : chunked\r\n" +
+                 "Host: target.com" +
+                 "Content-Length: 65\r\n\r\n" +
+                 "0\r\n\r\n" +
+@@ -366,15 +436,20 @@ public class HttpRequestDecoderTest {
+         assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
+         assertFalse(request.headers().contains("Content-Length"));
+         LastHttpContent c = channel.readInbound();
++        c.release();
+         assertFalse(channel.finish());
+     }
+ 
+     private static void testInvalidHeaders0(String requestStr) {
++        testInvalidHeaders0(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII));
++    }
++
++    private static void testInvalidHeaders0(ByteBuf requestBuffer) {
+         EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
+-        assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++        assertTrue(channel.writeInbound(requestBuffer));
+         HttpRequest request = channel.readInbound();
++        assertThat(request.decoderResult().cause(), instanceOf(IllegalArgumentException.class));
+         assertTrue(request.decoderResult().isFailure());
+-        assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException);
+         assertFalse(channel.finish());
+     }
+ }
+diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java
+index d67b3ad..0800ee7 100644
+--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java
++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseDecoderTest.java
+@@ -676,4 +676,82 @@ public class HttpResponseDecoderTest {
+         assertEquals("netty.io", response.headers().get(HttpHeaderNames.HOST));
+         assertFalse(channel.finish());
+     }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar1c() {
++        testHeaderNameStartsWithControlChar(0x1c);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar1d() {
++        testHeaderNameStartsWithControlChar(0x1d);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar1e() {
++        testHeaderNameStartsWithControlChar(0x1e);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar1f() {
++        testHeaderNameStartsWithControlChar(0x1f);
++    }
++
++    @Test
++    public void testHeaderNameStartsWithControlChar0c() {
++        testHeaderNameStartsWithControlChar(0x0c);
++    }
++
++    private void testHeaderNameStartsWithControlChar(int controlChar) {
++        ByteBuf responseBuffer = Unpooled.buffer();
++        responseBuffer.writeCharSequence("HTTP/1.1 200 OK\r\n" +
++                "Host: netty.io\r\n", CharsetUtil.US_ASCII);
++        responseBuffer.writeByte(controlChar);
++        responseBuffer.writeCharSequence("Transfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII);
++        testInvalidHeaders0(responseBuffer);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1c() {
++        testHeaderNameEndsWithControlChar(0x1c);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1d() {
++        testHeaderNameEndsWithControlChar(0x1d);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1e() {
++        testHeaderNameEndsWithControlChar(0x1e);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar1f() {
++        testHeaderNameEndsWithControlChar(0x1f);
++    }
++
++    @Test
++    public void testHeaderNameEndsWithControlChar0c() {
++        testHeaderNameEndsWithControlChar(0x0c);
++    }
++
++    private void testHeaderNameEndsWithControlChar(int controlChar) {
++        ByteBuf responseBuffer = Unpooled.buffer();
++        responseBuffer.writeCharSequence("HTTP/1.1 200 OK\r\n" +
++                "Host: netty.io\r\n", CharsetUtil.US_ASCII);
++        responseBuffer.writeCharSequence("Transfer-Encoding", CharsetUtil.US_ASCII);
++        responseBuffer.writeByte(controlChar);
++        responseBuffer.writeCharSequence(": chunked\r\n\r\n", CharsetUtil.US_ASCII);
++        testInvalidHeaders0(responseBuffer);
++    }
++
++    private static void testInvalidHeaders0(ByteBuf responseBuffer) {
++        EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseDecoder());
++        assertTrue(channel.writeInbound(responseBuffer));
++        HttpResponse response = channel.readInbound();
++        assertThat(response.decoderResult().cause(), instanceOf(IllegalArgumentException.class));
++        assertTrue(response.decoderResult().isFailure());
++        assertFalse(channel.finish());
++    }
+ }


=====================================
debian/patches/CVE-2022-41915.patch
=====================================
@@ -0,0 +1,1286 @@
+From: Markus Koschany <apo at debian.org>
+Date: Thu, 12 Jan 2023 22:18:56 +0100
+Subject: CVE-2022-41915
+
+Bug-Debian: https://bugs.debian.org/1027180
+Origin: https://github.com/netty/netty/commit/fe18adff1c2b333acb135ab779a3b9ba3295a1c4
+---
+ .../handler/codec/http/DefaultHttpHeadersTest.java |   2 +
+ .../io/netty/handler/codec/DefaultHeaders.java     | 538 +++++++++++++++++----
+ .../io/netty/handler/codec/DefaultHeadersTest.java | 227 ++++++++-
+ 3 files changed, 682 insertions(+), 85 deletions(-)
+
+diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java
+index 1ac20de..3012625 100644
+--- a/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java
++++ b/codec-http/src/test/java/io/netty/handler/codec/http/DefaultHttpHeadersTest.java
+@@ -21,6 +21,7 @@ import io.netty.util.internal.StringUtil;
+ import org.junit.Test;
+ 
+ import java.util.Arrays;
++import java.util.Collections;
+ import java.util.Iterator;
+ import java.util.List;
+ 
+@@ -36,6 +37,7 @@ import static org.junit.Assert.assertTrue;
+ 
+ public class DefaultHttpHeadersTest {
+     private static final CharSequence HEADER_NAME = "testHeader";
++    private static final CharSequence ILLEGAL_VALUE = "testHeader\r\nContent-Length:45\r\n\r\n";
+ 
+     @Test(expected = IllegalArgumentException.class)
+     public void nullHeaderNameNotAllowed() {
+diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java
+index d6f7971..8d7e57d 100644
+--- a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java
++++ b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java
+@@ -5,7 +5,7 @@
+  * "License"); you may not use this file except in compliance with the License. You may obtain a
+  * copy of the License at:
+  *
+- * http://www.apache.org/licenses/LICENSE-2.0
++ * 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
+@@ -30,8 +30,8 @@ import java.util.Set;
+ import static io.netty.util.HashingStrategy.JAVA_HASHER;
+ import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
+ import static io.netty.util.internal.ObjectUtil.checkNotNull;
+-import static java.lang.Math.min;
+ import static java.lang.Math.max;
++import static java.lang.Math.min;
+ 
+ /**
+  * Default implementation of {@link Headers};
+@@ -52,6 +52,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     private final byte hashMask;
+     private final ValueConverter<V> valueConverter;
+     private final NameValidator<K> nameValidator;
++    private final ValueValidator<V> valueValidator;
+     private final HashingStrategy<K> hashingStrategy;
+     int size;
+ 
+@@ -72,6 +73,22 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+         };
+     }
+ 
++    public interface ValueValidator<V> {
++        /**
++         * Validate the given value. If the validation fails, then an implementation specific runtime exception may be
++         * thrown.
++         *
++         * @param value The value to validate.
++         */
++        void validate(V value);
++
++        ValueValidator<?> NO_VALIDATION = new ValueValidator<Object>() {
++            @Override
++            public void validate(Object value) {
++            }
++        };
++    }
++
+     @SuppressWarnings("unchecked")
+     public DefaultHeaders(ValueConverter<V> valueConverter) {
+         this(JAVA_HASHER, valueConverter);
+@@ -102,13 +119,30 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+      */
+     @SuppressWarnings("unchecked")
+     public DefaultHeaders(HashingStrategy<K> nameHashingStrategy,
+-            ValueConverter<V> valueConverter, NameValidator<K> nameValidator, int arraySizeHint) {
++                          ValueConverter<V> valueConverter, NameValidator<K> nameValidator, int arraySizeHint) {
++        this(nameHashingStrategy, valueConverter, nameValidator, arraySizeHint,
++                (ValueValidator<V>) ValueValidator.NO_VALIDATION);
++    }
++
++    /**
++     * Create a new instance.
++     * @param nameHashingStrategy Used to hash and equality compare names.
++     * @param valueConverter Used to convert values to/from native types.
++     * @param nameValidator Used to validate name elements.
++     * @param arraySizeHint A hint as to how large the hash data structure should be.
++     * The next positive power of two will be used. An upper bound may be enforced.
++     * @param valueValidator The validation strategy for entry values.
++     */
++    @SuppressWarnings("unchecked")
++    public DefaultHeaders(HashingStrategy<K> nameHashingStrategy, ValueConverter<V> valueConverter,
++                          NameValidator<K> nameValidator, int arraySizeHint, ValueValidator<V> valueValidator) {
+         this.valueConverter = checkNotNull(valueConverter, "valueConverter");
+         this.nameValidator = checkNotNull(nameValidator, "nameValidator");
+-        this.hashingStrategy = checkNotNull(nameHashingStrategy, "nameHashingStrategy");
++        hashingStrategy = checkNotNull(nameHashingStrategy, "nameHashingStrategy");
++        this.valueValidator = checkNotNull(valueValidator, "valueValidator");
+         // Enforce a bound of [2, 128] because hashMask is a byte. The max possible value of hashMask is one less
+         // than the length of this array, and we want the mask to be > 0.
+-        entries = new DefaultHeaders.HeaderEntry[findNextPositivePowerOfTwo(max(2, min(arraySizeHint, 128)))];
++        entries = new HeaderEntry[findNextPositivePowerOfTwo(max(2, min(arraySizeHint, 128)))];
+         hashMask = (byte) (entries.length - 1);
+         head = new HeaderEntry<K, V>();
+     }
+@@ -174,6 +208,15 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+         return values;
+     }
+ 
++    /**
++     * Equivalent to {@link #getAll(Object)} but no intermediate list is generated.
++     * @param name the name of the header to retrieve
++     * @return an {@link Iterator} of header values corresponding to {@code name}.
++     */
++    public Iterator<V> valueIterator(K name) {
++        return new ValueIterator(name);
++    }
++
+     @Override
+     public List<V> getAllAndRemove(K name) {
+         List<V> all = getAll(name);
+@@ -188,52 +231,52 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public boolean containsObject(K name, Object value) {
+-        return contains(name, valueConverter.convertObject(checkNotNull(value, "value")));
++        return contains(name, fromObject(name, value));
+     }
+ 
+     @Override
+     public boolean containsBoolean(K name, boolean value) {
+-        return contains(name, valueConverter.convertBoolean(value));
++        return contains(name, fromBoolean(name, value));
+     }
+ 
+     @Override
+     public boolean containsByte(K name, byte value) {
+-        return contains(name, valueConverter.convertByte(value));
++        return contains(name, fromByte(name, value));
+     }
+ 
+     @Override
+     public boolean containsChar(K name, char value) {
+-        return contains(name, valueConverter.convertChar(value));
++        return contains(name, fromChar(name, value));
+     }
+ 
+     @Override
+     public boolean containsShort(K name, short value) {
+-        return contains(name, valueConverter.convertShort(value));
++        return contains(name, fromShort(name, value));
+     }
+ 
+     @Override
+     public boolean containsInt(K name, int value) {
+-        return contains(name, valueConverter.convertInt(value));
++        return contains(name, fromInt(name, value));
+     }
+ 
+     @Override
+     public boolean containsLong(K name, long value) {
+-        return contains(name, valueConverter.convertLong(value));
++        return contains(name, fromLong(name, value));
+     }
+ 
+     @Override
+     public boolean containsFloat(K name, float value) {
+-        return contains(name, valueConverter.convertFloat(value));
++        return contains(name, fromFloat(name, value));
+     }
+ 
+     @Override
+     public boolean containsDouble(K name, double value) {
+-        return contains(name, valueConverter.convertDouble(value));
++        return contains(name, fromDouble(name, value));
+     }
+ 
+     @Override
+     public boolean containsTimeMillis(K name, long value) {
+-        return contains(name, valueConverter.convertTimeMillis(value));
++        return contains(name, fromTimeMillis(name, value));
+     }
+ 
+     @SuppressWarnings("unchecked")
+@@ -283,7 +326,8 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T add(K name, V value) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, true, name);
++        validateValue(valueValidator, name, value);
+         checkNotNull(value, "value");
+         int h = hashingStrategy.hashCode(name);
+         int i = index(h);
+@@ -293,10 +337,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T add(K name, Iterable<? extends V> values) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, true, name);
+         int h = hashingStrategy.hashCode(name);
+         int i = index(h);
+         for (V v: values) {
++            validateValue(valueValidator, name, v);
+             add0(h, i, name, v);
+         }
+         return thisT();
+@@ -304,10 +349,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T add(K name, V... values) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, true, name);
+         int h = hashingStrategy.hashCode(name);
+         int i = index(h);
+         for (V v: values) {
++            validateValue(valueValidator, name, v);
+             add0(h, i, name, v);
+         }
+         return thisT();
+@@ -315,7 +361,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T addObject(K name, Object value) {
+-        return add(name, valueConverter.convertObject(checkNotNull(value, "value")));
++        return add(name, fromObject(name, value));
+     }
+ 
+     @Override
+@@ -336,47 +382,47 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T addInt(K name, int value) {
+-        return add(name, valueConverter.convertInt(value));
++        return add(name, fromInt(name, value));
+     }
+ 
+     @Override
+     public T addLong(K name, long value) {
+-        return add(name, valueConverter.convertLong(value));
++        return add(name, fromLong(name, value));
+     }
+ 
+     @Override
+     public T addDouble(K name, double value) {
+-        return add(name, valueConverter.convertDouble(value));
++        return add(name, fromDouble(name, value));
+     }
+ 
+     @Override
+     public T addTimeMillis(K name, long value) {
+-        return add(name, valueConverter.convertTimeMillis(value));
++        return add(name, fromTimeMillis(name, value));
+     }
+ 
+     @Override
+     public T addChar(K name, char value) {
+-        return add(name, valueConverter.convertChar(value));
++        return add(name, fromChar(name, value));
+     }
+ 
+     @Override
+     public T addBoolean(K name, boolean value) {
+-        return add(name, valueConverter.convertBoolean(value));
++        return add(name, fromBoolean(name, value));
+     }
+ 
+     @Override
+     public T addFloat(K name, float value) {
+-        return add(name, valueConverter.convertFloat(value));
++        return add(name, fromFloat(name, value));
+     }
+ 
+     @Override
+     public T addByte(K name, byte value) {
+-        return add(name, valueConverter.convertByte(value));
++        return add(name, fromByte(name, value));
+     }
+ 
+     @Override
+     public T addShort(K name, short value) {
+-        return add(name, valueConverter.convertShort(value));
++        return add(name, fromShort(name, value));
+     }
+ 
+     @Override
+@@ -418,7 +464,8 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T set(K name, V value) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, false, name);
++        validateValue(valueValidator, name, value);
+         checkNotNull(value, "value");
+         int h = hashingStrategy.hashCode(name);
+         int i = index(h);
+@@ -429,7 +476,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T set(K name, Iterable<? extends V> values) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, false, name);
+         checkNotNull(values, "values");
+ 
+         int h = hashingStrategy.hashCode(name);
+@@ -440,6 +487,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+             if (v == null) {
+                 break;
+             }
++            validateValue(valueValidator, name, v);
+             add0(h, i, name, v);
+         }
+ 
+@@ -448,7 +496,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T set(K name, V... values) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, false, name);
+         checkNotNull(values, "values");
+ 
+         int h = hashingStrategy.hashCode(name);
+@@ -459,6 +507,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+             if (v == null) {
+                 break;
+             }
++            validateValue(valueValidator, name, v);
+             add0(h, i, name, v);
+         }
+ 
+@@ -467,14 +516,13 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T setObject(K name, Object value) {
+-        checkNotNull(value, "value");
+-        V convertedValue = checkNotNull(valueConverter.convertObject(value), "convertedValue");
++        V convertedValue = checkNotNull(fromObject(name, value), "convertedValue");
+         return set(name, convertedValue);
+     }
+ 
+     @Override
+     public T setObject(K name, Iterable<?> values) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, false, name);
+ 
+         int h = hashingStrategy.hashCode(name);
+         int i = index(h);
+@@ -484,7 +532,9 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+             if (v == null) {
+                 break;
+             }
+-            add0(h, i, name, valueConverter.convertObject(v));
++            V converted = fromObject(name, v);
++            validateValue(valueValidator, name, converted);
++            add0(h, i, name, converted);
+         }
+ 
+         return thisT();
+@@ -492,7 +542,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T setObject(K name, Object... values) {
+-        nameValidator.validateName(name);
++        validateName(nameValidator, false, name);
+ 
+         int h = hashingStrategy.hashCode(name);
+         int i = index(h);
+@@ -502,7 +552,9 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+             if (v == null) {
+                 break;
+             }
+-            add0(h, i, name, valueConverter.convertObject(v));
++            V converted = fromObject(name, v);
++            validateValue(valueValidator, name, converted);
++            add0(h, i, name, converted);
+         }
+ 
+         return thisT();
+@@ -510,47 +562,47 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+ 
+     @Override
+     public T setInt(K name, int value) {
+-        return set(name, valueConverter.convertInt(value));
++        return set(name, fromInt(name, value));
+     }
+ 
+     @Override
+     public T setLong(K name, long value) {
+-        return set(name, valueConverter.convertLong(value));
++        return set(name, fromLong(name, value));
+     }
+ 
+     @Override
+     public T setDouble(K name, double value) {
+-        return set(name, valueConverter.convertDouble(value));
++        return set(name, fromDouble(name, value));
+     }
+ 
+     @Override
+     public T setTimeMillis(K name, long value) {
+-        return set(name, valueConverter.convertTimeMillis(value));
++        return set(name, fromTimeMillis(name, value));
+     }
+ 
+     @Override
+     public T setFloat(K name, float value) {
+-        return set(name, valueConverter.convertFloat(value));
++        return set(name, fromFloat(name, value));
+     }
+ 
+     @Override
+     public T setChar(K name, char value) {
+-        return set(name, valueConverter.convertChar(value));
++        return set(name, fromChar(name, value));
+     }
+ 
+     @Override
+     public T setBoolean(K name, boolean value) {
+-        return set(name, valueConverter.convertBoolean(value));
++        return set(name, fromBoolean(name, value));
+     }
+ 
+     @Override
+     public T setByte(K name, byte value) {
+-        return set(name, valueConverter.convertByte(value));
++        return set(name, fromByte(name, value));
+     }
+ 
+     @Override
+     public T setShort(K name, short value) {
+-        return set(name, valueConverter.convertShort(value));
++        return set(name, fromShort(name, value));
+     }
+ 
+     @Override
+@@ -594,7 +646,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Boolean getBoolean(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToBoolean(v) : null;
++        try {
++            return v != null ? toBoolean(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -606,7 +662,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Byte getByte(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToByte(v) : null;
++        try {
++            return v != null ? toByte(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -618,7 +678,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Character getChar(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToChar(v) : null;
++        try {
++            return v != null ? toChar(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -630,7 +694,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Short getShort(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToShort(v) : null;
++        try {
++            return v != null ? toShort(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -642,7 +710,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Integer getInt(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToInt(v) : null;
++        try {
++            return v != null ? toInt(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -654,7 +726,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Long getLong(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToLong(v) : null;
++        try {
++            return v != null ? toLong(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -666,7 +742,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Float getFloat(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToFloat(v) : null;
++        try {
++            return v != null ? toFloat(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -678,7 +758,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Double getDouble(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToDouble(v) : null;
++        try {
++            return v != null ? toDouble(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -690,7 +774,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Long getTimeMillis(K name) {
+         V v = get(name);
+-        return v != null ? valueConverter.convertToTimeMillis(v) : null;
++        try {
++            return v != null ? toTimeMillis(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -702,7 +790,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Boolean getBooleanAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToBoolean(v) : null;
++        try {
++            return v != null ? toBoolean(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -714,7 +806,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Byte getByteAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToByte(v) : null;
++        try {
++            return v != null ? toByte(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -726,12 +822,9 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Character getCharAndRemove(K name) {
+         V v = getAndRemove(name);
+-        if (v == null) {
+-            return null;
+-        }
+         try {
+-            return valueConverter.convertToChar(v);
+-        } catch (Throwable ignored) {
++            return v != null ? toChar(name, v) : null;
++        } catch (RuntimeException ignore) {
+             return null;
+         }
+     }
+@@ -745,7 +838,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Short getShortAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToShort(v) : null;
++        try {
++            return v != null ? toShort(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -757,7 +854,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Integer getIntAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToInt(v) : null;
++        try {
++            return v != null ? toInt(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -769,7 +870,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Long getLongAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToLong(v) : null;
++        try {
++            return v != null ? toLong(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -781,7 +886,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Float getFloatAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToFloat(v) : null;
++        try {
++            return v != null ? toFloat(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -793,7 +902,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Double getDoubleAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToDouble(v) : null;
++        try {
++            return v != null ? toDouble(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -805,7 +918,11 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+     @Override
+     public Long getTimeMillisAndRemove(K name) {
+         V v = getAndRemove(name);
+-        return v != null ? valueConverter.convertToTimeMillis(v) : null;
++        try {
++            return v != null ? toTimeMillis(name, v) : null;
++        } catch (RuntimeException ignore) {
++            return null;
++        }
+     }
+ 
+     @Override
+@@ -878,19 +995,12 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+         return result;
+     }
+ 
+-    @Override
+-    public String toString() {
+-        StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
+-        String separator = "";
+-        for (K name : names()) {
+-            List<V> values = getAll(name);
+-            for (int i = 0; i < values.size(); ++i) {
+-                builder.append(separator);
+-                builder.append(name).append(": ").append(values.get(i));
+-                separator = ", ";
+-            }
+-        }
+-        return builder.append(']').toString();
++    protected void validateName(NameValidator<K> validator, boolean forAdd, K name) {
++        validator.validateName(name);
++    }
++
++    protected void validateValue(ValueValidator<V> validator, K name, V value) {
++        validator.validate(value);
+     }
+ 
+     protected HeaderEntry<K, V> newHeaderEntry(int h, K name, V value, HeaderEntry<K, V> next) {
+@@ -901,6 +1011,14 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+         return valueConverter;
+     }
+ 
++    protected NameValidator<K> nameValidator() {
++        return nameValidator;
++    }
++
++    protected ValueValidator<V> valueValidator() {
++        return valueValidator;
++    }
++
+     private int index(int hash) {
+         return hash & hashMask;
+     }
+@@ -948,12 +1066,199 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+         return value;
+     }
+ 
++    HeaderEntry<K, V> remove0(HeaderEntry<K, V> entry, HeaderEntry<K, V> previous) {
++        int i = index(entry.hash);
++        HeaderEntry<K, V> firstEntry = entries[i];
++        if (firstEntry == entry) {
++            entries[i] = entry.next;
++            previous = entries[i];
++        } else if (previous == null) {
++            // If we don't have any existing starting point, then start from the beginning.
++            previous = firstEntry;
++            HeaderEntry<K, V> next = firstEntry.next;
++            while (next != null && next != entry) {
++                previous = next;
++                next = next.next;
++            }
++            assert next != null: "Entry not found in its hash bucket: " + entry;
++            previous.next = entry.next;
++        } else {
++            previous.next = entry.next;
++        }
++        entry.remove();
++        --size;
++        return previous;
++    }
++
+     @SuppressWarnings("unchecked")
+     private T thisT() {
+         return (T) this;
+     }
+ 
+-    private final class HeaderIterator implements Iterator<Map.Entry<K, V>> {
++    private V fromObject(K name, Object value) {
++        try {
++            return valueConverter.convertObject(checkNotNull(value, "value"));
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert object value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromBoolean(K name, boolean value) {
++        try {
++            return valueConverter.convertBoolean(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert boolean value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromByte(K name, byte value) {
++        try {
++            return valueConverter.convertByte(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert byte value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromChar(K name, char value) {
++        try {
++            return valueConverter.convertChar(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert char value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromShort(K name, short value) {
++        try {
++            return valueConverter.convertShort(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert short value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromInt(K name, int value) {
++        try {
++            return valueConverter.convertInt(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert int value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromLong(K name, long value) {
++        try {
++            return valueConverter.convertLong(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert long value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromFloat(K name, float value) {
++        try {
++            return valueConverter.convertFloat(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert float value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromDouble(K name, double value) {
++        try {
++            return valueConverter.convertDouble(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert double value for header '" + name + '\'', e);
++        }
++    }
++
++    private V fromTimeMillis(K name, long value) {
++        try {
++            return valueConverter.convertTimeMillis(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert millsecond value for header '" + name + '\'', e);
++        }
++    }
++
++    private boolean toBoolean(K name, V value) {
++        try {
++            return valueConverter.convertToBoolean(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to boolean for header '" + name + '\'');
++        }
++    }
++
++    private byte toByte(K name, V value) {
++        try {
++            return valueConverter.convertToByte(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to byte for header '" + name + '\'');
++        }
++    }
++
++    private char toChar(K name, V value) {
++        try {
++            return valueConverter.convertToChar(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to char for header '" + name + '\'');
++        }
++    }
++
++    private short toShort(K name, V value) {
++        try {
++            return valueConverter.convertToShort(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to short for header '" + name + '\'');
++        }
++    }
++
++    private int toInt(K name, V value) {
++        try {
++            return valueConverter.convertToInt(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to int for header '" + name + '\'');
++        }
++    }
++
++    private long toLong(K name, V value) {
++        try {
++            return valueConverter.convertToLong(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to long for header '" + name + '\'');
++        }
++    }
++
++    private float toFloat(K name, V value) {
++        try {
++            return valueConverter.convertToFloat(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to float for header '" + name + '\'');
++        }
++    }
++
++    private double toDouble(K name, V value) {
++        try {
++            return valueConverter.convertToDouble(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException("Failed to convert header value to double for header '" + name + '\'');
++        }
++    }
++
++    private long toTimeMillis(K name, V value) {
++        try {
++            return valueConverter.convertToTimeMillis(value);
++        } catch (IllegalArgumentException e) {
++            throw new IllegalArgumentException(
++                    "Failed to convert header value to millsecond for header '" + name + '\'');
++        }
++    }
++
++    /**
++     * Returns a deep copy of this instance.
++     */
++    public DefaultHeaders<K, V, T> copy() {
++        DefaultHeaders<K, V, T> copy = new DefaultHeaders<K, V, T>(
++                hashingStrategy, valueConverter, nameValidator, entries.length);
++        copy.addImpl(this);
++        return copy;
++    }
++
++    private final class HeaderIterator implements Iterator<Entry<K, V>> {
+         private HeaderEntry<K, V> current = head;
+ 
+         @Override
+@@ -978,7 +1283,59 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+         }
+     }
+ 
+-    protected static class HeaderEntry<K, V> implements Map.Entry<K, V> {
++    private final class ValueIterator implements Iterator<V> {
++        private final K name;
++        private final int hash;
++        private HeaderEntry<K, V> removalPrevious;
++        private HeaderEntry<K, V> previous;
++        private HeaderEntry<K, V> next;
++
++        ValueIterator(K name) {
++            this.name = checkNotNull(name, "name");
++            hash = hashingStrategy.hashCode(name);
++            calculateNext(entries[index(hash)]);
++        }
++
++        @Override
++        public boolean hasNext() {
++            return next != null;
++        }
++
++        @Override
++        public V next() {
++            if (!hasNext()) {
++                throw new NoSuchElementException();
++            }
++            if (previous != null) {
++                removalPrevious = previous;
++            }
++            previous = next;
++            calculateNext(next.next);
++            return previous.value;
++        }
++
++        @Override
++        public void remove() {
++            if (previous == null) {
++                throw new IllegalStateException();
++            }
++            removalPrevious = remove0(previous, removalPrevious);
++            previous = null;
++        }
++
++        private void calculateNext(HeaderEntry<K, V> entry) {
++            while (entry != null) {
++                if (entry.hash == hash && hashingStrategy.equals(name, entry.key)) {
++                    next = entry;
++                    return;
++                }
++                entry = entry.next;
++            }
++            next = null;
++        }
++    }
++
++    protected static class HeaderEntry<K, V> implements Entry<K, V> {
+         protected final int hash;
+         protected final K key;
+         protected V value;
+@@ -1053,5 +1410,20 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
+         public final String toString() {
+             return key.toString() + '=' + value.toString();
+         }
++
++        @Override
++        public boolean equals(Object o) {
++            if (!(o instanceof Map.Entry)) {
++                return false;
++            }
++            Entry<?, ?> other = (Entry<?, ?>) o;
++            return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey()))  &&
++                   (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));
++        }
++
++        @Override
++        public int hashCode() {
++            return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
++        }
+     }
+ }
+diff --git a/codec/src/test/java/io/netty/handler/codec/DefaultHeadersTest.java b/codec/src/test/java/io/netty/handler/codec/DefaultHeadersTest.java
+index d6d32b4..073872b 100644
+--- a/codec/src/test/java/io/netty/handler/codec/DefaultHeadersTest.java
++++ b/codec/src/test/java/io/netty/handler/codec/DefaultHeadersTest.java
+@@ -14,8 +14,10 @@
+  */
+ package io.netty.handler.codec;
+ 
++import io.netty.util.AsciiString;
+ import org.junit.Test;
+ 
++import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
+@@ -30,6 +32,7 @@ import static org.junit.Assert.assertNotEquals;
+ import static org.junit.Assert.assertNotSame;
+ import static org.junit.Assert.assertNull;
+ import static org.junit.Assert.assertTrue;
++import static org.junit.Assert.fail;
+ 
+ /**
+  * Tests for {@link DefaultHeaders}.
+@@ -39,11 +42,15 @@ public class DefaultHeadersTest {
+     private static final class TestDefaultHeaders extends
+             DefaultHeaders<CharSequence, CharSequence, TestDefaultHeaders> {
+         public TestDefaultHeaders() {
+-            super(CharSequenceValueConverter.INSTANCE);
++            this(CharSequenceValueConverter.INSTANCE);
++        }
++
++        public TestDefaultHeaders(ValueConverter<CharSequence> converter) {
++            super(converter);
+         }
+     }
+ 
+-    private TestDefaultHeaders newInstance() {
++    private static TestDefaultHeaders newInstance() {
+         return new TestDefaultHeaders();
+     }
+ 
+@@ -102,6 +109,41 @@ public class DefaultHeadersTest {
+         assertTrue(values.containsAll(asList(of("value1"), of("value2"), of("value3"))));
+     }
+ 
++    @Test
++    public void multipleValuesPerNameIterator() {
++        TestDefaultHeaders headers = newInstance();
++        headers.add(of("name"), of("value1"));
++        headers.add(of("name"), of("value2"));
++        headers.add(of("name"), of("value3"));
++        assertEquals(3, headers.size());
++
++        List<CharSequence> values = new ArrayList<CharSequence>();
++        Iterator<CharSequence> itr = headers.valueIterator(of("name"));
++        while (itr.hasNext()) {
++            values.add(itr.next());
++        }
++        assertEquals(3, values.size());
++        assertTrue(values.containsAll(asList(of("value1"), of("value2"), of("value3"))));
++    }
++
++    @Test
++    public void multipleValuesPerNameIteratorEmpty() {
++        TestDefaultHeaders headers = newInstance();
++
++        List<CharSequence> values = new ArrayList<CharSequence>();
++        Iterator<CharSequence> itr = headers.valueIterator(of("name"));
++        while (itr.hasNext()) {
++            values.add(itr.next());
++        }
++        assertEquals(0, values.size());
++        try {
++            itr.next();
++            fail();
++        } catch (NoSuchElementException ignored) {
++            // ignored
++        }
++    }
++
+     @Test
+     public void testContains() {
+         TestDefaultHeaders headers = newInstance();
+@@ -399,6 +441,24 @@ public class DefaultHeadersTest {
+         assertFalse(headers.contains(of("name1"), of("value3")));
+     }
+ 
++    @Test
++    public void testEntryEquals() {
++        Map.Entry<CharSequence, CharSequence> same1 = newInstance().add("name", "value").iterator().next();
++        Map.Entry<CharSequence, CharSequence> same2 = newInstance().add("name", "value").iterator().next();
++        assertEquals(same1, same2);
++        assertEquals(same1.hashCode(), same2.hashCode());
++
++        Map.Entry<CharSequence, CharSequence> nameDifferent1 = newInstance().add("name1", "value").iterator().next();
++        Map.Entry<CharSequence, CharSequence> nameDifferent2 = newInstance().add("name2", "value").iterator().next();
++        assertNotEquals(nameDifferent1, nameDifferent2);
++        assertNotEquals(nameDifferent1.hashCode(), nameDifferent2.hashCode());
++
++        Map.Entry<CharSequence, CharSequence> valueDifferent1 = newInstance().add("name", "value1").iterator().next();
++        Map.Entry<CharSequence, CharSequence> valueDifferent2 = newInstance().add("name", "value2").iterator().next();
++        assertNotEquals(valueDifferent1, valueDifferent2);
++        assertNotEquals(valueDifferent1.hashCode(), valueDifferent2.hashCode());
++    }
++
+     @Test
+     public void getAllReturnsEmptyListForUnknownName() {
+         TestDefaultHeaders headers = newInstance();
+@@ -472,4 +532,167 @@ public class DefaultHeadersTest {
+         headers = newInstance();
+         assertEquals("TestDefaultHeaders[]", headers.toString());
+     }
++
++    @Test
++    public void testNotThrowWhenConvertFails() {
++        TestDefaultHeaders headers = new TestDefaultHeaders(new ValueConverter<CharSequence>() {
++            @Override
++            public CharSequence convertObject(Object value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertBoolean(boolean value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public boolean convertToBoolean(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertByte(byte value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public byte convertToByte(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertChar(char value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public char convertToChar(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertShort(short value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public short convertToShort(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertInt(int value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public int convertToInt(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertLong(long value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public long convertToLong(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertTimeMillis(long value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public long convertToTimeMillis(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertFloat(float value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public float convertToFloat(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public CharSequence convertDouble(double value) {
++                throw new IllegalArgumentException();
++            }
++
++            @Override
++            public double convertToDouble(CharSequence value) {
++                throw new IllegalArgumentException();
++            }
++        });
++        headers.set("name1", "");
++        assertNull(headers.getInt("name1"));
++        assertEquals(1, headers.getInt("name1", 1));
++
++        assertNull(headers.getBoolean(""));
++        assertFalse(headers.getBoolean("name1", false));
++
++        assertNull(headers.getByte("name1"));
++        assertEquals(1, headers.getByte("name1", (byte) 1));
++
++        assertNull(headers.getChar("name"));
++        assertEquals('n', headers.getChar("name1", 'n'));
++
++        assertNull(headers.getDouble("name"));
++        assertEquals(1, headers.getDouble("name1", 1), 0);
++
++        assertNull(headers.getFloat("name"));
++        assertEquals(Float.MAX_VALUE, headers.getFloat("name1", Float.MAX_VALUE), 0);
++
++        assertNull(headers.getLong("name"));
++        assertEquals(Long.MAX_VALUE, headers.getLong("name1", Long.MAX_VALUE));
++
++        assertNull(headers.getShort("name"));
++        assertEquals(Short.MAX_VALUE, headers.getShort("name1", Short.MAX_VALUE));
++
++        assertNull(headers.getTimeMillis("name"));
++        assertEquals(Long.MAX_VALUE, headers.getTimeMillis("name1", Long.MAX_VALUE));
++    }
++
++    @Test
++    public void testGetBooleanInvalidValue() {
++        TestDefaultHeaders headers = newInstance();
++        headers.set("name1", "invalid");
++        headers.set("name2", new AsciiString("invalid"));
++        headers.set("name3", new StringBuilder("invalid"));
++
++        assertFalse(headers.getBoolean("name1", false));
++        assertFalse(headers.getBoolean("name2", false));
++        assertFalse(headers.getBoolean("name3", false));
++    }
++
++    @Test
++    public void testGetBooleanFalseValue() {
++        TestDefaultHeaders headers = newInstance();
++        headers.set("name1", "false");
++        headers.set("name2", new AsciiString("false"));
++        headers.set("name3", new StringBuilder("false"));
++
++        assertFalse(headers.getBoolean("name1", true));
++        assertFalse(headers.getBoolean("name2", true));
++        assertFalse(headers.getBoolean("name3", true));
++    }
++
++    @Test
++    public void testGetBooleanTrueValue() {
++        TestDefaultHeaders headers = newInstance();
++        headers.set("name1", "true");
++        headers.set("name2", new AsciiString("true"));
++        headers.set("name3", new StringBuilder("true"));
++
++        assertTrue(headers.getBoolean("name1", false));
++        assertTrue(headers.getBoolean("name2", false));
++        assertTrue(headers.getBoolean("name3", false));
++    }
+ }


=====================================
debian/patches/series
=====================================
@@ -16,3 +16,7 @@
 18-CVE-2019-20445-3.patch
 19-CVE-2020-11612.patch
 20-CVE-2021-21290.patch
+CVE-2021-37136.patch
+CVE-2021-37137.patch
+CVE-2021-43797.patch
+CVE-2022-41915.patch



View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/3b76e8b6c64a35b154e3c2288cd71a5e399d1e16

-- 
View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/3b76e8b6c64a35b154e3c2288cd71a5e399d1e16
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/20230126/98ca7fa4/attachment.htm>


More information about the pkg-java-commits mailing list