[Git][java-team/netty][buster] Import Debian changes 1:4.1.33-1+deb10u2

Markus Koschany gitlab at salsa.debian.org
Sun Mar 28 22:18:45 BST 2021



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


Commits:
b2c7e07b by Markus Koschany at 2021-03-28T23:16:05+02:00
Import Debian changes 1:4.1.33-1+deb10u2

netty (1:4.1.33-1+deb10u2) buster-security; urgency=high
..
  * Team upload.
  * Fix the following security vulnerabilites:
    - CVE-2019-20444:
      HttpObjectDecoder.java allows an HTTP header that lacks a colon, which
      might be interpreted as a separate header with an incorrect syntax, or
      might be interpreted as an "invalid fold."
    - CVE-2019-20445:
      HttpObjectDecoder.java allows a Content-Length header to be accompanied
      by a second Content-Length header, or by a Transfer-Encoding header.
    - CVE-2020-7238:
      Netty allows HTTP Request Smuggling because it mishandles
      Transfer-Encoding whitespace (such as a [space]Transfer-Encoding:chunked
      line) and a later Content-Length header.
    - CVE-2020-11612:
      The ZlibDecoders allow for unbounded memory allocation while decoding a
      ZlibEncoded byte stream. An attacker could send a large ZlibEncoded byte
      stream to the Netty server, forcing the server to allocate all of its
      free memory to a single decoder.
    - CVE-2021-21290:
      In Netty there is a vulnerability on Unix-like systems involving an
      insecure temp file. When netty's multipart decoders are used local
      information disclosure can occur via the local system temporary directory
      if temporary storing uploads on the disk is enabled. On unix-like
      systems, the temporary directory is shared between all user. As such,
      writing to this directory using APIs that do not explicitly set the
      file/directory permissions can lead to information disclosure.
    - CVE-2021-21295:
      In Netty there is a vulnerability that enables request smuggling. If a
      Content-Length header is present in the original HTTP/2 request, the
      field is not validated by `Http2MultiplexHandler` as it is propagated up.
      This is fine as long as the request is not proxied through as HTTP/1.1.
      If the request comes in as an HTTP/2 stream, gets converted into the
      HTTP/1.1 domain objects (`HttpRequest`, `HttpContent`, etc.) via
      `Http2StreamFrameToHttpObjectCodec `and then sent up to the child
      channel's pipeline and proxied through a remote peer as HTTP/1.1 this may
      result in request smuggling.

- - - - -


9 changed files:

- debian/changelog
- + debian/patches/CVE-2019-20444.patch
- + debian/patches/CVE-2019-20445_1.patch
- + debian/patches/CVE-2019-20445_2.patch
- + debian/patches/CVE-2019-20445_3.patch
- + debian/patches/CVE-2020-11612.patch
- + debian/patches/CVE-2021-21290.patch
- + debian/patches/CVE-2021-21295.patch
- debian/patches/series


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,44 @@
+netty (1:4.1.33-1+deb10u2) buster-security; urgency=high
+
+  * Team upload.
+  * Fix the following security vulnerabilites:
+    - CVE-2019-20444:
+      HttpObjectDecoder.java allows an HTTP header that lacks a colon, which
+      might be interpreted as a separate header with an incorrect syntax, or
+      might be interpreted as an "invalid fold."
+    - CVE-2019-20445:
+      HttpObjectDecoder.java allows a Content-Length header to be accompanied
+      by a second Content-Length header, or by a Transfer-Encoding header.
+    - CVE-2020-7238:
+      Netty allows HTTP Request Smuggling because it mishandles
+      Transfer-Encoding whitespace (such as a [space]Transfer-Encoding:chunked
+      line) and a later Content-Length header.
+    - CVE-2020-11612:
+      The ZlibDecoders allow for unbounded memory allocation while decoding a
+      ZlibEncoded byte stream. An attacker could send a large ZlibEncoded byte
+      stream to the Netty server, forcing the server to allocate all of its
+      free memory to a single decoder.
+    - CVE-2021-21290:
+      In Netty there is a vulnerability on Unix-like systems involving an
+      insecure temp file. When netty's multipart decoders are used local
+      information disclosure can occur via the local system temporary directory
+      if temporary storing uploads on the disk is enabled. On unix-like
+      systems, the temporary directory is shared between all user. As such,
+      writing to this directory using APIs that do not explicitly set the
+      file/directory permissions can lead to information disclosure.
+    - CVE-2021-21295:
+      In Netty there is a vulnerability that enables request smuggling. If a
+      Content-Length header is present in the original HTTP/2 request, the
+      field is not validated by `Http2MultiplexHandler` as it is propagated up.
+      This is fine as long as the request is not proxied through as HTTP/1.1.
+      If the request comes in as an HTTP/2 stream, gets converted into the
+      HTTP/1.1 domain objects (`HttpRequest`, `HttpContent`, etc.) via
+      `Http2StreamFrameToHttpObjectCodec `and then sent up to the child
+      channel's pipeline and proxied through a remote peer as HTTP/1.1 this may
+      result in request smuggling.
+
+ -- Markus Koschany <apo at debian.org>  Sun, 28 Mar 2021 18:46:25 +0200
+
 netty (1:4.1.33-1+deb10u1) buster-security; urgency=high
 
   * Non-maintainer upload by the Security Team.


=====================================
debian/patches/CVE-2019-20444.patch
=====================================
@@ -0,0 +1,52 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 20 Mar 2021 13:17:48 +0200
+Subject: CVE-2019-20444
+
+Bug-Debian: https://bugs.debian.org/950966
+Origin: https://github.com/netty/netty/commit/a7c18d44b46e02dadfe3da225a06e5091f5f328e
+---
+ .../io/netty/handler/codec/http/HttpObjectDecoder.java   |  5 +++++
+ .../netty/handler/codec/http/HttpRequestDecoderTest.java | 16 ++++++++++++++++
+ 2 files changed, 21 insertions(+)
+
+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 2e940d2..d3f5b79 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
+@@ -755,6 +755,11 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+             }
+         }
+ 
++        if (nameEnd == length) {
++            // There was no colon present at all.
++            throw new IllegalArgumentException("No colon found");
++        }
++
+         for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
+             if (sb.charAt(colonEnd) == ':') {
+                 colonEnd ++;
+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 2b2d0cc..414a033 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
+@@ -334,4 +334,20 @@ public class HttpRequestDecoderTest {
+         assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException);
+         assertFalse(channel.finish());
+     }
++
++    @Test
++    public void testHeaderWithNoValueAndMissingColon() {
++        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++        String requestStr = "GET /some/path HTTP/1.1\r\n" +
++                "Content-Length: 0\r\n" +
++                "Host:\r\n" +
++                "netty.io\r\n\r\n";
++
++        assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++        HttpRequest request = channel.readInbound();
++        System.err.println(request.headers().names().toString());
++        assertTrue(request.decoderResult().isFailure());
++        assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException);
++        assertFalse(channel.finish());
++    }
+ }


=====================================
debian/patches/CVE-2019-20445_1.patch
=====================================
@@ -0,0 +1,174 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 20 Mar 2021 13:48:07 +0200
+Subject: CVE-2019-20445_1
+
+This is also the fix for CVE-2020-7238.
+
+Bug-Debian: https://bugs.debian.org/950967
+Origin: https://github.com/netty/netty/commit/8494b046ec7e4f28dbd44bc699cc4c4c92251729
+---
+ .../handler/codec/http/HttpObjectDecoder.java      | 50 +++++++++++++++--
+ .../handler/codec/http/HttpRequestDecoderTest.java | 64 +++++++++++++++++++---
+ 2 files changed, 99 insertions(+), 15 deletions(-)
+
+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 d3f5b79..0a9ea14 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
+@@ -609,23 +609,61 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+         if (name != null) {
+             headers.add(name, value);
+         }
++
+         // reset name and value fields
+         name = null;
+         value = null;
+ 
+-        State nextState;
++        List<String> values = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
++        int contentLengthValuesCount = values.size();
++
++        if (contentLengthValuesCount > 0) {
++            // Guard against multiple Content-Length headers as stated in
++            // https://tools.ietf.org/html/rfc7230#section-3.3.2:
++            //
++            // If a message is received that has multiple Content-Length header
++            //   fields with field-values consisting of the same decimal value, or a
++            //   single Content-Length header field with a field value containing a
++            //   list of identical decimal values (e.g., "Content-Length: 42, 42"),
++            //   indicating that duplicate Content-Length header fields have been
++            //   generated or combined by an upstream message processor, then the
++            //   recipient MUST either reject the message as invalid or replace the
++            //   duplicated field-values with a single valid Content-Length field
++            //   containing that decimal value prior to determining the message body
++            //   length or forwarding the message.
++            if (contentLengthValuesCount > 1 && message.protocolVersion() == HttpVersion.HTTP_1_1) {
++                throw new IllegalArgumentException("Multiple Content-Length headers found");
++            }
++            contentLength = Long.parseLong(values.get(0));
++        }
+ 
+         if (isContentAlwaysEmpty(message)) {
+             HttpUtil.setTransferEncodingChunked(message, false);
+-            nextState = State.SKIP_CONTROL_CHARS;
++            return State.SKIP_CONTROL_CHARS;
+         } else if (HttpUtil.isTransferEncodingChunked(message)) {
+-            nextState = State.READ_CHUNK_SIZE;
++            // See https://tools.ietf.org/html/rfc7230#section-3.3.3
++            //
++            //       If a message is received with both a Transfer-Encoding and a
++            //       Content-Length header field, the Transfer-Encoding overrides the
++            //       Content-Length.  Such a message might indicate an attempt to
++            //       perform request smuggling (Section 9.5) or response splitting
++            //       (Section 9.4) and ought to be handled as an error.  A sender MUST
++            //       remove the received Content-Length field prior to forwarding such
++            //       a message downstream.
++            //
++            // This is also what http_parser does:
++            // https://github.com/nodejs/http-parser/blob/v2.9.2/http_parser.c#L1769
++            if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) {
++                throw new IllegalArgumentException(
++                        "Both 'Content-Length: " + contentLength + "' and 'Transfer-Encoding: chunked' found");
++            }
++
++            return State.READ_CHUNK_SIZE;
+         } else if (contentLength() >= 0) {
+-            nextState = State.READ_FIXED_LENGTH_CONTENT;
++            return State.READ_FIXED_LENGTH_CONTENT;
+         } else {
+-            nextState = State.READ_VARIABLE_LENGTH_CONTENT;
++            return State.READ_VARIABLE_LENGTH_CONTENT;
+         }
+-        return nextState;
+     }
+ 
+     private long contentLength() {
+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 414a033..717b580 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
+@@ -323,29 +323,75 @@ public class HttpRequestDecoderTest {
+ 
+     @Test
+     public void testWhitespace() {
+-        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
+         String requestStr = "GET /some/path HTTP/1.1\r\n" +
+                 "Transfer-Encoding : chunked\r\n" +
+                 "Host: netty.io\n\r\n";
+-
+-        assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
+-        HttpRequest request = channel.readInbound();
+-        assertTrue(request.decoderResult().isFailure());
+-        assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException);
+-        assertFalse(channel.finish());
++        testInvalidHeaders0(requestStr);
+     }
+ 
+     @Test
+     public void testHeaderWithNoValueAndMissingColon() {
+-        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
+         String requestStr = "GET /some/path HTTP/1.1\r\n" +
+                 "Content-Length: 0\r\n" +
+                 "Host:\r\n" +
+                 "netty.io\r\n\r\n";
++        testInvalidHeaders0(requestStr);
++    }
++
++    @Test
++    public void testMultipleContentLengthHeaders() {
++        String requestStr = "GET /some/path HTTP/1.1\r\n" +
++                "Content-Length: 1\r\n" +
++                "Content-Length: 0\r\n\r\n" +
++                "b";
++        testInvalidHeaders0(requestStr);
++    }
++
++    @Test
++    public void testMultipleContentLengthHeaders2() {
++        String requestStr = "GET /some/path HTTP/1.1\r\n" +
++                "Content-Length: 1\r\n" +
++                "Connection: close\r\n" +
++                "Content-Length: 0\r\n\r\n" +
++                "b";
++        testInvalidHeaders0(requestStr);
++    }
++
++    @Test
++    public void testContentLengthHeaderWithCommaValue() {
++        String requestStr = "GET /some/path HTTP/1.1\r\n" +
++                "Content-Length: 1,1\r\n\r\n" +
++                "b";
++        testInvalidHeaders0(requestStr);
++    }
+ 
++    @Test
++    public void testMultipleContentLengthHeadersWithFolding() {
++        String requestStr = "POST / HTTP/1.1\r\n" +
++                "Host: example.com\r\n" +
++                "Connection: close\r\n" +
++                "Content-Length: 5\r\n" +
++                "Content-Length:\r\n" +
++                "\t6\r\n\r\n" +
++                "123456";
++        testInvalidHeaders0(requestStr);
++    }
++
++    @Test
++    public void testContentLengthHeaderAndChunked() {
++        String requestStr = "POST / HTTP/1.1\r\n" +
++                "Host: example.com\r\n" +
++                "Connection: close\r\n" +
++                "Content-Length: 5\r\n" +
++                "Transfer-Encoding: chunked\r\n\r\n" +
++                "0\r\n\r\n";
++        testInvalidHeaders0(requestStr);
++    }
++
++    private static void testInvalidHeaders0(String requestStr) {
++        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
+         assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
+         HttpRequest request = channel.readInbound();
+-        System.err.println(request.headers().names().toString());
+         assertTrue(request.decoderResult().isFailure());
+         assertTrue(request.decoderResult().cause() instanceof IllegalArgumentException);
+         assertFalse(channel.finish());


=====================================
debian/patches/CVE-2019-20445_2.patch
=====================================
@@ -0,0 +1,93 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 20 Mar 2021 13:48:44 +0200
+Subject: CVE-2019-20445_2
+
+Origin: https://github.com/netty/netty/commit/629034624626b722128e0fcc6b3ec9d406cb3706
+---
+ .../handler/codec/http/HttpObjectDecoder.java      | 42 ++++++++++++++--------
+ .../handler/codec/http/HttpRequestDecoderTest.java | 10 +++++-
+ 2 files changed, 36 insertions(+), 16 deletions(-)
+
+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 0a9ea14..f81880c 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
+@@ -641,23 +641,9 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+             HttpUtil.setTransferEncodingChunked(message, false);
+             return State.SKIP_CONTROL_CHARS;
+         } else if (HttpUtil.isTransferEncodingChunked(message)) {
+-            // See https://tools.ietf.org/html/rfc7230#section-3.3.3
+-            //
+-            //       If a message is received with both a Transfer-Encoding and a
+-            //       Content-Length header field, the Transfer-Encoding overrides the
+-            //       Content-Length.  Such a message might indicate an attempt to
+-            //       perform request smuggling (Section 9.5) or response splitting
+-            //       (Section 9.4) and ought to be handled as an error.  A sender MUST
+-            //       remove the received Content-Length field prior to forwarding such
+-            //       a message downstream.
+-            //
+-            // This is also what http_parser does:
+-            // https://github.com/nodejs/http-parser/blob/v2.9.2/http_parser.c#L1769
+             if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) {
+-                throw new IllegalArgumentException(
+-                        "Both 'Content-Length: " + contentLength + "' and 'Transfer-Encoding: chunked' found");
++                handleTransferEncodingChunkedWithContentLength(message);
+             }
+-
+             return State.READ_CHUNK_SIZE;
+         } else if (contentLength() >= 0) {
+             return State.READ_FIXED_LENGTH_CONTENT;
+@@ -666,6 +652,32 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+         }
+     }
+ 
++    /**
++     * Invoked when a message with both a "Transfer-Encoding: chunked" and a "Content-Length" header field is detected.
++     * The default behavior is to <i>remove</i> the Content-Length field, but this method could be overridden
++     * to change the behavior (to, e.g., throw an exception and produce an invalid message).
++     * <p>
++     * See: https://tools.ietf.org/html/rfc7230#section-3.3.3
++     * <pre>
++     *     If a message is received with both a Transfer-Encoding and a
++     *     Content-Length header field, the Transfer-Encoding overrides the
++     *     Content-Length.  Such a message might indicate an attempt to
++     *     perform request smuggling (Section 9.5) or response splitting
++     *     (Section 9.4) and ought to be handled as an error.  A sender MUST
++     *     remove the received Content-Length field prior to forwarding such
++     *     a message downstream.
++     * </pre>
++     * Also see:
++     * https://github.com/apache/tomcat/blob/b693d7c1981fa7f51e58bc8c8e72e3fe80b7b773/
++     * java/org/apache/coyote/http11/Http11Processor.java#L747-L755
++     * https://github.com/nginx/nginx/blob/0ad4393e30c119d250415cb769e3d8bc8dce5186/
++     * src/http/ngx_http_request.c#L1946-L1953
++     */
++    protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
++        message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
++        contentLength = Long.MIN_VALUE;
++    }
++
+     private long contentLength() {
+         if (contentLength == Long.MIN_VALUE) {
+             contentLength = HttpUtil.getContentLength(message, -1L);
+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 717b580..5aa6fec 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
+@@ -385,7 +385,15 @@ public class HttpRequestDecoderTest {
+                 "Content-Length: 5\r\n" +
+                 "Transfer-Encoding: chunked\r\n\r\n" +
+                 "0\r\n\r\n";
+-        testInvalidHeaders0(requestStr);
++
++        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
++        assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
++        HttpRequest request = channel.readInbound();
++        assertFalse(request.decoderResult().isFailure());
++        assertTrue(request.headers().contains("Transfer-Encoding", "chunked", false));
++        assertFalse(request.headers().contains("Content-Length"));
++        LastHttpContent c = channel.readInbound();
++        assertFalse(channel.finish());
+     }
+ 
+     private static void testInvalidHeaders0(String requestStr) {


=====================================
debian/patches/CVE-2019-20445_3.patch
=====================================
@@ -0,0 +1,45 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 20 Mar 2021 13:49:17 +0200
+Subject: CVE-2019-20445_3
+
+Origin: https://github.com/netty/netty/commit/5f68897880467c00f29495b0aa46ed19bf7a873c
+---
+ .../handler/codec/http/HttpRequestDecoderTest.java | 25 +++++++++++++++++++++-
+ 1 file changed, 24 insertions(+), 1 deletion(-)
+
+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 5aa6fec..9a8912f 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
+@@ -325,7 +325,30 @@ public class HttpRequestDecoderTest {
+     public void testWhitespace() {
+         String requestStr = "GET /some/path HTTP/1.1\r\n" +
+                 "Transfer-Encoding : chunked\r\n" +
+-                "Host: netty.io\n\r\n";
++                "Host: netty.io\r\n\r\n";
++        testInvalidHeaders0(requestStr);
++    }
++
++    @Test
++    public void testWhitespaceBeforeTransferEncoding01() {
++        String requestStr = "GET /some/path HTTP/1.1\r\n" +
++                " Transfer-Encoding : chunked\r\n" +
++                "Content-Length: 1\r\n" +
++                "Host: netty.io\r\n\r\n" +
++                "a";
++        testInvalidHeaders0(requestStr);
++    }
++
++    @Test
++    public void testWhitespaceBeforeTransferEncoding02() {
++        String requestStr = "POST / HTTP/1.1" +
++                " Transfer-Encoding : chunked\r\n" +
++                "Host: target.com" +
++                "Content-Length: 65\r\n\r\n" +
++                "0\r\n\r\n" +
++                "GET /maliciousRequest HTTP/1.1\r\n" +
++                "Host: evilServer.com\r\n" +
++                "Foo: x";
+         testInvalidHeaders0(requestStr);
+     }
+ 


=====================================
debian/patches/CVE-2020-11612.patch
=====================================
@@ -0,0 +1,494 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sat, 20 Mar 2021 17:48:26 +0200
+Subject: CVE-2020-11612
+
+Origin: https://github.com/netty/netty/commit/1543218d3e7afcb33a90b728b14370395a3deca0
+---
+ .../handler/codec/compression/JZlibDecoder.java    | 60 +++++++++++++++++--
+ .../handler/codec/compression/JdkZlibDecoder.java  | 70 +++++++++++++++++++---
+ .../handler/codec/compression/ZlibDecoder.java     | 65 ++++++++++++++++++++
+ .../netty/handler/codec/compression/JZlibTest.java |  4 +-
+ .../handler/codec/compression/JdkZlibTest.java     |  4 +-
+ .../handler/codec/compression/ZlibCrossTest1.java  |  4 +-
+ .../handler/codec/compression/ZlibCrossTest2.java  |  4 +-
+ .../netty/handler/codec/compression/ZlibTest.java  | 57 +++++++++++++++++-
+ 8 files changed, 247 insertions(+), 21 deletions(-)
+
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java
+index 5d23bb8..ab01e56 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/JZlibDecoder.java
+@@ -18,6 +18,7 @@ package io.netty.handler.codec.compression;
+ import com.jcraft.jzlib.Inflater;
+ import com.jcraft.jzlib.JZlib;
+ import io.netty.buffer.ByteBuf;
++import io.netty.buffer.ByteBufAllocator;
+ import io.netty.channel.ChannelHandlerContext;
+ 
+ import java.util.List;
+@@ -34,7 +35,21 @@ public class JZlibDecoder extends ZlibDecoder {
+      * @throws DecompressionException if failed to initialize zlib
+      */
+     public JZlibDecoder() {
+-        this(ZlibWrapper.ZLIB);
++        this(ZlibWrapper.ZLIB, 0);
++    }
++
++    /**
++     * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB})
++     * and specified maximum buffer allocation.
++     *
++     * @param maxAllocation
++     *          Maximum size of the decompression buffer. Must be >= 0.
++     *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
++     *
++     * @throws DecompressionException if failed to initialize zlib
++     */
++    public JZlibDecoder(int maxAllocation) {
++        this(ZlibWrapper.ZLIB, maxAllocation);
+     }
+ 
+     /**
+@@ -43,6 +58,21 @@ public class JZlibDecoder extends ZlibDecoder {
+      * @throws DecompressionException if failed to initialize zlib
+      */
+     public JZlibDecoder(ZlibWrapper wrapper) {
++        this(wrapper, 0);
++    }
++
++     /**
++     * Creates a new instance with the specified wrapper and maximum buffer allocation.
++     *
++     * @param maxAllocation
++     *          Maximum size of the decompression buffer. Must be >= 0.
++     *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
++     *
++     * @throws DecompressionException if failed to initialize zlib
++     */
++    public JZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
++        super(maxAllocation);
++
+         if (wrapper == null) {
+             throw new NullPointerException("wrapper");
+         }
+@@ -53,7 +83,7 @@ public class JZlibDecoder extends ZlibDecoder {
+         }
+     }
+ 
+-    /**
++     /**
+      * Creates a new instance with the specified preset dictionary. The wrapper
+      * is always {@link ZlibWrapper#ZLIB} because it is the only format that
+      * supports the preset dictionary.
+@@ -61,6 +91,23 @@ public class JZlibDecoder extends ZlibDecoder {
+      * @throws DecompressionException if failed to initialize zlib
+      */
+     public JZlibDecoder(byte[] dictionary) {
++        this(dictionary, 0);
++    }
++
++     /**
++     * Creates a new instance with the specified preset dictionary and maximum buffer allocation.
++     * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that
++     * supports the preset dictionary.
++     *
++     * @param maxAllocation
++     *          Maximum size of the decompression buffer. Must be >= 0.
++     *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
++     *
++     * @throws DecompressionException if failed to initialize zlib
++     */
++    public JZlibDecoder(byte[] dictionary, int maxAllocation) {
++        super(maxAllocation);
++
+         if (dictionary == null) {
+             throw new NullPointerException("dictionary");
+         }
+@@ -110,11 +157,11 @@ public class JZlibDecoder extends ZlibDecoder {
+             final int oldNextInIndex = z.next_in_index;
+ 
+             // Configure output.
+-            ByteBuf decompressed = ctx.alloc().heapBuffer(inputLength << 1);
++            ByteBuf decompressed = prepareDecompressBuffer(ctx, null, inputLength << 1);
+ 
+             try {
+                 loop: for (;;) {
+-                    decompressed.ensureWritable(z.avail_in << 1);
++                    decompressed = prepareDecompressBuffer(ctx, decompressed, z.avail_in << 1);
+                     z.avail_out = decompressed.writableBytes();
+                     z.next_out = decompressed.array();
+                     z.next_out_index = decompressed.arrayOffset() + decompressed.writerIndex();
+@@ -170,4 +217,9 @@ public class JZlibDecoder extends ZlibDecoder {
+             z.next_out = null;
+         }
+     }
++
++    @Override
++    protected void decompressionBufferExhausted(ByteBuf buffer) {
++        finished = true;
++    }
+ }
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java
+index c90cc4b..6665d86 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/JdkZlibDecoder.java
+@@ -16,6 +16,7 @@
+ package io.netty.handler.codec.compression;
+ 
+ import io.netty.buffer.ByteBuf;
++import io.netty.buffer.ByteBufAllocator;
+ import io.netty.channel.ChannelHandlerContext;
+ 
+ import java.util.List;
+@@ -64,7 +65,19 @@ public class JdkZlibDecoder extends ZlibDecoder {
+      * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
+      */
+     public JdkZlibDecoder() {
+-        this(ZlibWrapper.ZLIB, null, false);
++        this(ZlibWrapper.ZLIB, null, false, 0);
++    }
++
++    /**
++     * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB})
++     * and the specified maximum buffer allocation.
++     *
++     * @param maxAllocation
++     *          Maximum size of the decompression buffer. Must be >= 0.
++     *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
++     */
++    public JdkZlibDecoder(int maxAllocation) {
++        this(ZlibWrapper.ZLIB, null, false, maxAllocation);
+     }
+ 
+     /**
+@@ -73,7 +86,20 @@ public class JdkZlibDecoder extends ZlibDecoder {
+      * supports the preset dictionary.
+      */
+     public JdkZlibDecoder(byte[] dictionary) {
+-        this(ZlibWrapper.ZLIB, dictionary, false);
++        this(ZlibWrapper.ZLIB, dictionary, false, 0);
++    }
++
++    /**
++     * Creates a new instance with the specified preset dictionary and maximum buffer allocation.
++     * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that
++     * supports the preset dictionary.
++     *
++     * @param maxAllocation
++     *          Maximum size of the decompression buffer. Must be >= 0.
++     *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
++     */
++    public JdkZlibDecoder(byte[] dictionary, int maxAllocation) {
++        this(ZlibWrapper.ZLIB, dictionary, false, maxAllocation);
+     }
+ 
+     /**
+@@ -82,18 +108,41 @@ public class JdkZlibDecoder extends ZlibDecoder {
+      * supported atm.
+      */
+     public JdkZlibDecoder(ZlibWrapper wrapper) {
+-        this(wrapper, null, false);
++        this(wrapper, null, false, 0);
++    }
++
++    /**
++     * Creates a new instance with the specified wrapper and maximum buffer allocation.
++     * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
++     * supported atm.
++     *
++     * @param maxAllocation
++     *          Maximum size of the decompression buffer. Must be >= 0.
++     *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
++     */
++    public JdkZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
++        this(wrapper, null, false, maxAllocation);
+     }
+ 
+     public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated) {
+-        this(wrapper, null, decompressConcatenated);
++        this(wrapper, null, decompressConcatenated, 0);
++    }
++
++    public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated, int maxAllocation) {
++        this(wrapper, null, decompressConcatenated, maxAllocation);
+     }
+ 
+     public JdkZlibDecoder(boolean decompressConcatenated) {
+-        this(ZlibWrapper.GZIP, null, decompressConcatenated);
++        this(ZlibWrapper.GZIP, null, decompressConcatenated, 0);
+     }
+ 
+-    private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary, boolean decompressConcatenated) {
++    public JdkZlibDecoder(boolean decompressConcatenated, int maxAllocation) {
++        this(ZlibWrapper.GZIP, null, decompressConcatenated, maxAllocation);
++    }
++
++    private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary, boolean decompressConcatenated, int maxAllocation) {
++        super(maxAllocation);
++
+         if (wrapper == null) {
+             throw new NullPointerException("wrapper");
+         }
+@@ -177,7 +226,7 @@ public class JdkZlibDecoder extends ZlibDecoder {
+             inflater.setInput(array);
+         }
+ 
+-        ByteBuf decompressed = ctx.alloc().heapBuffer(inflater.getRemaining() << 1);
++        ByteBuf decompressed = prepareDecompressBuffer(ctx, null, inflater.getRemaining() << 1);
+         try {
+             boolean readFooter = false;
+             while (!inflater.needsInput()) {
+@@ -208,7 +257,7 @@ public class JdkZlibDecoder extends ZlibDecoder {
+                     }
+                     break;
+                 } else {
+-                    decompressed.ensureWritable(inflater.getRemaining() << 1);
++                    decompressed = prepareDecompressBuffer(ctx, decompressed, inflater.getRemaining() << 1);
+                 }
+             }
+ 
+@@ -238,6 +287,11 @@ public class JdkZlibDecoder extends ZlibDecoder {
+         }
+     }
+ 
++    @Override
++    protected void decompressionBufferExhausted(ByteBuf buffer) {
++        finished = true;
++    }
++
+     @Override
+     protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
+         super.handlerRemoved0(ctx);
+diff --git a/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java b/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java
+index d01bc6b..26fd3e7 100644
+--- a/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java
++++ b/codec/src/main/java/io/netty/handler/codec/compression/ZlibDecoder.java
+@@ -16,6 +16,8 @@
+ package io.netty.handler.codec.compression;
+ 
+ import io.netty.buffer.ByteBuf;
++import io.netty.buffer.ByteBufAllocator;
++import io.netty.channel.ChannelHandlerContext;
+ import io.netty.handler.codec.ByteToMessageDecoder;
+ 
+ /**
+@@ -23,9 +25,72 @@ import io.netty.handler.codec.ByteToMessageDecoder;
+  */
+ public abstract class ZlibDecoder extends ByteToMessageDecoder {
+ 
++    /**
++     * Maximum allowed size of the decompression buffer.
++     */
++    protected final int maxAllocation;
++
++    /**
++     * Same as {@link #ZlibDecoder(int)} with maxAllocation = 0.
++     */
++    public ZlibDecoder() {
++        this(0);
++    }
++
++    /**
++     * Construct a new ZlibDecoder.
++     * @param maxAllocation
++     *          Maximum size of the decompression buffer. Must be >= 0.
++     *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
++     */
++    public ZlibDecoder(int maxAllocation) {
++        if (maxAllocation < 0) {
++            throw new IllegalArgumentException("maxAllocation must be >= 0");
++        }
++        this.maxAllocation = maxAllocation;
++    }
++
+     /**
+      * Returns {@code true} if and only if the end of the compressed stream
+      * has been reached.
+      */
+     public abstract boolean isClosed();
++
++    /**
++     * Allocate or expand the decompression buffer, without exceeding the maximum allocation.
++     * Calls {@link #decompressionBufferExhausted(ByteBuf)} if the buffer is full and cannot be expanded further.
++     */
++    protected ByteBuf prepareDecompressBuffer(ChannelHandlerContext ctx, ByteBuf buffer, int preferredSize) {
++        if (buffer == null) {
++            if (maxAllocation == 0) {
++                return ctx.alloc().heapBuffer(preferredSize);
++            }
++
++            return ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation);
++        }
++
++        // this always expands the buffer if possible, even if the expansion is less than preferredSize
++        // we throw the exception only if the buffer could not be expanded at all
++        // this means that one final attempt to deserialize will always be made with the buffer at maxAllocation
++        if (buffer.ensureWritable(preferredSize, true) == 1) {
++            // buffer must be consumed so subclasses don't add it to output
++            // we therefore duplicate it when calling decompressionBufferExhausted() to guarantee non-interference
++            // but wait until after to consume it so the subclass can tell how much output is really in the buffer
++            decompressionBufferExhausted(buffer.duplicate());
++            buffer.skipBytes(buffer.readableBytes());
++            throw new DecompressionException("Decompression buffer has reached maximum size: " + buffer.maxCapacity());
++        }
++
++        return buffer;
++    }
++
++    /**
++     * Called when the decompression buffer cannot be expanded further.
++     * Default implementation is a no-op, but subclasses can override in case they want to
++     * do something before the {@link DecompressionException} is thrown, such as log the
++     * data that was decompressed so far.
++     */
++    protected void decompressionBufferExhausted(ByteBuf buffer) {
++    }
++
+ }
+diff --git a/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java b/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java
+index 28f3919..015559e 100644
+--- a/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java
++++ b/codec/src/test/java/io/netty/handler/codec/compression/JZlibTest.java
+@@ -23,7 +23,7 @@ public class JZlibTest extends ZlibTest {
+     }
+ 
+     @Override
+-    protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
+-        return new JZlibDecoder(wrapper);
++    protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
++        return new JZlibDecoder(wrapper, maxAllocation);
+     }
+ }
+diff --git a/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java b/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java
+index 54a48a9..5ff19f1 100644
+--- a/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java
++++ b/codec/src/test/java/io/netty/handler/codec/compression/JdkZlibTest.java
+@@ -38,8 +38,8 @@ public class JdkZlibTest extends ZlibTest {
+     }
+ 
+     @Override
+-    protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
+-        return new JdkZlibDecoder(wrapper);
++    protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
++        return new JdkZlibDecoder(wrapper, maxAllocation);
+     }
+ 
+     @Test(expected = DecompressionException.class)
+diff --git a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java
+index 9e16e1a..3c31274 100644
+--- a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java
++++ b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest1.java
+@@ -23,7 +23,7 @@ public class ZlibCrossTest1 extends ZlibTest {
+     }
+ 
+     @Override
+-    protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
+-        return new JZlibDecoder(wrapper);
++    protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
++        return new JZlibDecoder(wrapper, maxAllocation);
+     }
+ }
+diff --git a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java
+index 8717019..00c6e18 100644
+--- a/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java
++++ b/codec/src/test/java/io/netty/handler/codec/compression/ZlibCrossTest2.java
+@@ -25,8 +25,8 @@ public class ZlibCrossTest2 extends ZlibTest {
+     }
+ 
+     @Override
+-    protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
+-        return new JdkZlibDecoder(wrapper);
++    protected ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation) {
++        return new JdkZlibDecoder(wrapper, maxAllocation);
+     }
+ 
+     @Test(expected = DecompressionException.class)
+diff --git a/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java b/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java
+index 7c25ec4..9d79c81 100644
+--- a/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java
++++ b/codec/src/test/java/io/netty/handler/codec/compression/ZlibTest.java
+@@ -15,7 +15,9 @@
+  */
+ package io.netty.handler.codec.compression;
+ 
++import io.netty.buffer.AbstractByteBufAllocator;
+ import io.netty.buffer.ByteBuf;
++import io.netty.buffer.ByteBufAllocator;
+ import io.netty.buffer.ByteBufInputStream;
+ import io.netty.buffer.Unpooled;
+ import io.netty.channel.embedded.EmbeddedChannel;
+@@ -88,8 +90,12 @@ public abstract class ZlibTest {
+         rand.nextBytes(BYTES_LARGE);
+     }
+ 
++    protected ZlibDecoder createDecoder(ZlibWrapper wrapper) {
++        return createDecoder(wrapper, 0);
++    }
++
+     protected abstract ZlibEncoder createEncoder(ZlibWrapper wrapper);
+-    protected abstract ZlibDecoder createDecoder(ZlibWrapper wrapper);
++    protected abstract ZlibDecoder createDecoder(ZlibWrapper wrapper, int maxAllocation);
+ 
+     @Test
+     public void testGZIP2() throws Exception {
+@@ -345,6 +351,25 @@ public abstract class ZlibTest {
+         testCompressLarge(ZlibWrapper.GZIP, ZlibWrapper.ZLIB_OR_NONE);
+     }
+ 
++    @Test
++    public void testMaxAllocation() throws Exception {
++        int maxAllocation = 1024;
++        ZlibDecoder decoder = createDecoder(ZlibWrapper.ZLIB, maxAllocation);
++        EmbeddedChannel chDecoder = new EmbeddedChannel(decoder);
++        TestByteBufAllocator alloc = new TestByteBufAllocator(chDecoder.alloc());
++        chDecoder.config().setAllocator(alloc);
++
++        try {
++            chDecoder.writeInbound(Unpooled.wrappedBuffer(deflate(BYTES_LARGE)));
++            fail("decompressed size > maxAllocation, so should have thrown exception");
++        } catch (DecompressionException e) {
++            assertTrue(e.getMessage().startsWith("Decompression buffer has reached maximum size"));
++            assertEquals(maxAllocation, alloc.getMaxAllocation());
++            assertTrue(decoder.isClosed());
++            assertFalse(chDecoder.finish());
++        }
++    }
++
+     private static byte[] gzip(byte[] bytes) throws IOException {
+         ByteArrayOutputStream out = new ByteArrayOutputStream();
+         GZIPOutputStream stream = new GZIPOutputStream(out);
+@@ -360,4 +385,34 @@ public abstract class ZlibTest {
+         stream.close();
+         return out.toByteArray();
+     }
++
++    private static final class TestByteBufAllocator extends AbstractByteBufAllocator {
++        private ByteBufAllocator wrapped;
++        private int maxAllocation;
++
++        TestByteBufAllocator(ByteBufAllocator wrapped) {
++            this.wrapped = wrapped;
++        }
++
++        public int getMaxAllocation() {
++            return maxAllocation;
++        }
++
++        @Override
++        public boolean isDirectBufferPooled() {
++            return wrapped.isDirectBufferPooled();
++        }
++
++        @Override
++        protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
++            maxAllocation = Math.max(maxAllocation, maxCapacity);
++            return wrapped.heapBuffer(initialCapacity, maxCapacity);
++        }
++
++        @Override
++        protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
++            maxAllocation = Math.max(maxAllocation, maxCapacity);
++            return wrapped.directBuffer(initialCapacity, maxCapacity);
++        }
++    }
+ }


=====================================
debian/patches/CVE-2021-21290.patch
=====================================
@@ -0,0 +1,272 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sun, 28 Mar 2021 14:56:13 +0200
+Subject: CVE-2021-21290
+
+Bug-Debian: https://bugs.debian.org/982580
+Origin: https://github.com/netty/netty/commit/c735357bf29d07856ad171c6611a2e1a0e0000ec
+---
+ .../java/io/netty/buffer/AbstractByteBufTest.java    |  4 ++--
+ .../buffer/ReadOnlyDirectByteBufferBufTest.java      |  2 +-
+ .../codec/http/multipart/AbstractDiskHttpData.java   |  5 +++--
+ .../handler/codec/http/HttpChunkedInputTest.java     |  3 ++-
+ .../io/netty/util/internal/NativeLibraryLoader.java  |  2 +-
+ .../io/netty/util/internal/PlatformDependent.java    | 20 ++++++++++++++++++++
+ .../handler/ssl/util/SelfSignedCertificate.java      |  6 ++++--
+ .../handler/stream/ChunkedWriteHandlerTest.java      |  3 ++-
+ .../transport/socket/SocketFileRegionTest.java       |  2 +-
+ .../java/io/netty/channel/epoll/EpollSpliceTest.java |  3 ++-
+ .../io/netty/channel/unix/tests/UnixTestUtils.java   |  3 ++-
+ 11 files changed, 40 insertions(+), 13 deletions(-)
+
+diff --git a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java
+index 59194ab..2679d1e 100644
+--- a/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java
++++ b/buffer/src/test/java/io/netty/buffer/AbstractByteBufTest.java
+@@ -4487,7 +4487,7 @@ public abstract class AbstractByteBufTest {
+ 
+     @Test
+     public void testReadBytesAndWriteBytesWithFileChannel() throws IOException {
+-        File file = File.createTempFile("file-channel", ".tmp");
++        File file = PlatformDependent.createTempFile("file-channel", ".tmp", null);
+         RandomAccessFile randomAccessFile = null;
+         try {
+             randomAccessFile = new RandomAccessFile(file, "rw");
+@@ -4530,7 +4530,7 @@ public abstract class AbstractByteBufTest {
+ 
+     @Test
+     public void testGetBytesAndSetBytesWithFileChannel() throws IOException {
+-        File file = File.createTempFile("file-channel", ".tmp");
++        File file = PlatformDependent.createTempFile("file-channel", ".tmp", null);
+         RandomAccessFile randomAccessFile = null;
+         try {
+             randomAccessFile = new RandomAccessFile(file, "rw");
+diff --git a/buffer/src/test/java/io/netty/buffer/ReadOnlyDirectByteBufferBufTest.java b/buffer/src/test/java/io/netty/buffer/ReadOnlyDirectByteBufferBufTest.java
+index d51ce11..6e40f08 100644
+--- a/buffer/src/test/java/io/netty/buffer/ReadOnlyDirectByteBufferBufTest.java
++++ b/buffer/src/test/java/io/netty/buffer/ReadOnlyDirectByteBufferBufTest.java
+@@ -286,7 +286,7 @@ public class ReadOnlyDirectByteBufferBufTest {
+ 
+     @Test
+     public void testWrapMemoryMapped() throws Exception {
+-        File file = File.createTempFile("netty-test", "tmp");
++        File file = PlatformDependent.createTempFile("netty-test", "tmp", null);
+         FileChannel output = null;
+         FileChannel input = null;
+         ByteBuf b1 = null;
+diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java
+index 544bc7c..c28dbae 100644
+--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java
++++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java
+@@ -20,6 +20,7 @@ import io.netty.handler.codec.http.HttpConstants;
+ import io.netty.util.internal.EmptyArrays;
+ import io.netty.util.internal.logging.InternalLogger;
+ import io.netty.util.internal.logging.InternalLoggerFactory;
++import io.netty.util.internal.PlatformDependent;
+ 
+ import java.io.File;
+ import java.io.FileInputStream;
+@@ -87,9 +88,9 @@ public abstract class AbstractDiskHttpData extends AbstractHttpData {
+         File tmpFile;
+         if (getBaseDirectory() == null) {
+             // create a temporary file
+-            tmpFile = File.createTempFile(getPrefix(), newpostfix);
++            tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, null);
+         } else {
+-            tmpFile = File.createTempFile(getPrefix(), newpostfix, new File(
++            tmpFile = PlatformDependent.createTempFile(getPrefix(), newpostfix, new File(
+                     getBaseDirectory()));
+         }
+         if (deleteOnExit()) {
+diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java
+index 002c8d0..8e75eb9 100644
+--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java
++++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpChunkedInputTest.java
+@@ -25,6 +25,7 @@ import io.netty.handler.stream.ChunkedNioFile;
+ import io.netty.handler.stream.ChunkedNioStream;
+ import io.netty.handler.stream.ChunkedStream;
+ import io.netty.handler.stream.ChunkedWriteHandler;
++import io.netty.util.internal.PlatformDependent;
+ import org.junit.Test;
+ 
+ import java.io.ByteArrayInputStream;
+@@ -46,7 +47,7 @@ public class HttpChunkedInputTest {
+ 
+         FileOutputStream out = null;
+         try {
+-            TMP = File.createTempFile("netty-chunk-", ".tmp");
++            TMP = PlatformDependent.createTempFile("netty-chunk-", ".tmp", null);
+             TMP.deleteOnExit();
+             out = new FileOutputStream(TMP);
+             out.write(BYTES);
+diff --git a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
+index 31b4a46..a47a7f5 100644
+--- a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
++++ b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
+@@ -180,7 +180,7 @@ public final class NativeLibraryLoader {
+             String prefix = libname.substring(0, index);
+             String suffix = libname.substring(index, libname.length());
+ 
+-            tmpFile = File.createTempFile(prefix, suffix, WORKDIR);
++            tmpFile = PlatformDependent.createTempFile(prefix, suffix, WORKDIR);
+             in = url.openStream();
+             out = new FileOutputStream(tmpFile);
+ 
+diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java
+index 1baeecb..fd2af44 100644
+--- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java
++++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java
+@@ -33,6 +33,7 @@ import java.lang.reflect.Field;
+ import java.lang.reflect.Method;
+ import java.nio.ByteBuffer;
+ import java.nio.ByteOrder;
++import java.nio.file.Files;
+ import java.security.AccessController;
+ import java.security.PrivilegedAction;
+ import java.util.Deque;
+@@ -56,6 +57,7 @@ import static io.netty.util.internal.PlatformDependent0.hashCodeAsciiSanitize;
+ import static io.netty.util.internal.PlatformDependent0.unalignedAccess;
+ import static java.lang.Math.max;
+ import static java.lang.Math.min;
++import java.io.IOException;
+ 
+ /**
+  * Utility that detects various properties specific to the current runtime
+@@ -1228,6 +1230,24 @@ public final class PlatformDependent {
+         return true;
+     }
+ 
++    @SuppressJava6Requirement(reason = "Guarded by version check")
++    public static File createTempFile(String prefix, String suffix, File directory) throws IOException {
++        if (javaVersion() >= 7) {
++            if (directory == null) {
++                return Files.createTempFile(prefix, suffix).toFile();
++            }
++            return Files.createTempFile(directory.toPath(), prefix, suffix).toFile();
++        }
++        if (directory == null) {
++            return File.createTempFile(prefix, suffix);
++        }
++        File file = File.createTempFile(prefix, suffix, directory);
++        // Try to adjust the perms, if this fails there is not much else we can do...
++        file.setReadable(false, false);
++        file.setReadable(true, true);
++        return file;
++    }
++
+     /**
+      * Package private for testing purposes only!
+      */
+diff --git a/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java b/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java
+index 9f010ce..34212bd 100644
+--- a/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java
++++ b/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java
+@@ -20,6 +20,7 @@ import io.netty.buffer.ByteBuf;
+ import io.netty.buffer.Unpooled;
+ import io.netty.handler.codec.base64.Base64;
+ import io.netty.util.CharsetUtil;
++import io.netty.util.internal.PlatformDependent;
+ import io.netty.util.internal.SystemPropertyUtil;
+ import io.netty.util.internal.logging.InternalLogger;
+ import io.netty.util.internal.logging.InternalLoggerFactory;
+@@ -29,6 +30,7 @@ import java.io.FileInputStream;
+ import java.io.FileOutputStream;
+ import java.io.IOException;
+ import java.io.OutputStream;
++import java.nio.file.Files;
+ import java.security.KeyPair;
+ import java.security.KeyPairGenerator;
+ import java.security.NoSuchAlgorithmException;
+@@ -238,7 +240,7 @@ public final class SelfSignedCertificate {
+             wrappedBuf.release();
+         }
+ 
+-        File keyFile = File.createTempFile("keyutil_" + fqdn + '_', ".key");
++        File keyFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".key", null);
+         keyFile.deleteOnExit();
+ 
+         OutputStream keyOut = new FileOutputStream(keyFile);
+@@ -269,7 +271,7 @@ public final class SelfSignedCertificate {
+             wrappedBuf.release();
+         }
+ 
+-        File certFile = File.createTempFile("keyutil_" + fqdn + '_', ".crt");
++        File certFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".crt", null);
+         certFile.deleteOnExit();
+ 
+         OutputStream certOut = new FileOutputStream(certFile);
+diff --git a/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java b/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java
+index 5b03048..6caf0af 100644
+--- a/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java
++++ b/handler/src/test/java/io/netty/handler/stream/ChunkedWriteHandlerTest.java
+@@ -26,6 +26,7 @@ import io.netty.channel.ChannelOutboundHandlerAdapter;
+ import io.netty.channel.embedded.EmbeddedChannel;
+ import io.netty.util.CharsetUtil;
+ import io.netty.util.ReferenceCountUtil;
++import io.netty.util.internal.PlatformDependent;
+ import org.junit.Test;
+ 
+ import java.io.ByteArrayInputStream;
+@@ -49,7 +50,7 @@ public class ChunkedWriteHandlerTest {
+ 
+         FileOutputStream out = null;
+         try {
+-            TMP = File.createTempFile("netty-chunk-", ".tmp");
++            TMP = PlatformDependent.createTempFile("netty-chunk-", ".tmp", null);
+             TMP.deleteOnExit();
+             out = new FileOutputStream(TMP);
+             out.write(BYTES);
+diff --git a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java
+index 53deb6c..d4f43f7 100644
+--- a/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java
++++ b/testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketFileRegionTest.java
+@@ -100,7 +100,7 @@ public class SocketFileRegionTest extends AbstractSocketTest {
+         cb.option(ChannelOption.AUTO_READ, autoRead);
+ 
+         final int bufferSize = 1024;
+-        final File file = File.createTempFile("netty-", ".tmp");
++        final File file = PlatformDependent.createTempFile("netty-", ".tmp", null);
+         file.deleteOnExit();
+ 
+         final FileOutputStream out = new FileOutputStream(file);
+diff --git a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java
+index c53ff1e..eae1711 100644
+--- a/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java
++++ b/transport-native-epoll/src/test/java/io/netty/channel/epoll/EpollSpliceTest.java
+@@ -29,6 +29,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
+ import io.netty.channel.unix.FileDescriptor;
+ import io.netty.testsuite.util.TestUtils;
+ import io.netty.util.NetUtil;
++import io.netty.util.internal.PlatformDependent;
+ import org.junit.Assert;
+ import org.junit.Test;
+ 
+@@ -193,7 +194,7 @@ public class EpollSpliceTest {
+     @Test
+     public void spliceToFile() throws Throwable {
+         EventLoopGroup group = new EpollEventLoopGroup(1);
+-        File file = File.createTempFile("netty-splice", null);
++        File file = PlatformDependent.createTempFile("netty-splice", null, null);
+         file.deleteOnExit();
+ 
+         SpliceHandler sh = new SpliceHandler(file);
+diff --git a/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java
+index e4ebcb4..6124ec1 100644
+--- a/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java
++++ b/transport-native-unix-common-tests/src/main/java/io/netty/channel/unix/tests/UnixTestUtils.java
+@@ -17,6 +17,7 @@ package io.netty.channel.unix.tests;
+ 
+ import io.netty.channel.unix.DomainSocketAddress;
+ import io.netty.channel.unix.Socket;
++import io.netty.util.internal.PlatformDependent;
+ 
+ import java.io.File;
+ import java.io.IOException;
+@@ -26,7 +27,7 @@ public final class UnixTestUtils {
+         try {
+             File file;
+             do {
+-                file = File.createTempFile("NETTY", "UDS");
++                file = PlatformDependent.createTempFile("NETTY", "UDS", null);
+                 if (!file.delete()) {
+                     throw new IOException("failed to delete: " + file);
+                 }


=====================================
debian/patches/CVE-2021-21295.patch
=====================================
@@ -0,0 +1,556 @@
+From: Markus Koschany <apo at debian.org>
+Date: Sun, 28 Mar 2021 18:45:48 +0200
+Subject: CVE-2021-21295
+
+Bug-Debian: https://bugs.debian.org/984948
+Origin: https://github.com/netty/netty/commit/89c241e3b1795ff257af4ad6eadc616cb2fb3dc4
+---
+ .../handler/codec/http/HttpObjectDecoder.java      |  46 ++++----
+ .../java/io/netty/handler/codec/http/HttpUtil.java |  85 ++++++++++++++
+ .../codec/http2/DefaultHttp2ConnectionDecoder.java | 100 ++++++++++++++--
+ .../http2/DefaultHttp2ConnectionDecoderTest.java   | 128 +++++++++++++++++++++
+ 4 files changed, 329 insertions(+), 30 deletions(-)
+
+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 f81880c..6a19f1e 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
+@@ -100,11 +100,13 @@ import java.util.List;
+  * implement all abstract methods properly.
+  */
+ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
++    public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
+     private static final String EMPTY_VALUE = "";
+ 
+     private final int maxChunkSize;
+     private final boolean chunkedSupported;
+     protected final boolean validateHeaders;
++    private final boolean allowDuplicateContentLengths;
+     private final HeaderParser headerParser;
+     private final LineParser lineParser;
+ 
+@@ -165,9 +167,17 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+         this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, 128);
+     }
+ 
++    protected HttpObjectDecoder(
++             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
++             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
++        this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, initialBufferSize,
++             DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS);
++    }
++
+     protected HttpObjectDecoder(
+             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
+-            boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
++            boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
++            boolean allowDuplicateContentLengths) {
+         if (maxInitialLineLength <= 0) {
+             throw new IllegalArgumentException(
+                     "maxInitialLineLength must be a positive integer: " +
+@@ -189,6 +199,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+         this.maxChunkSize = maxChunkSize;
+         this.chunkedSupported = chunkedSupported;
+         this.validateHeaders = validateHeaders;
++        this.allowDuplicateContentLengths = allowDuplicateContentLengths;
+     }
+ 
+     @Override
+@@ -614,34 +625,27 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
+         name = null;
+         value = null;
+ 
+-        List<String> values = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
+-        int contentLengthValuesCount = values.size();
++        List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
+ 
+-        if (contentLengthValuesCount > 0) {
++        if (!contentLengthFields.isEmpty()) {
++            HttpVersion version = message.protocolVersion();
++            boolean isHttp10OrEarlier = version.majorVersion() < 1 || (version.majorVersion() == 1
++                    && version.minorVersion() == 0);
+             // Guard against multiple Content-Length headers as stated in
+             // https://tools.ietf.org/html/rfc7230#section-3.3.2:
+-            //
+-            // If a message is received that has multiple Content-Length header
+-            //   fields with field-values consisting of the same decimal value, or a
+-            //   single Content-Length header field with a field value containing a
+-            //   list of identical decimal values (e.g., "Content-Length: 42, 42"),
+-            //   indicating that duplicate Content-Length header fields have been
+-            //   generated or combined by an upstream message processor, then the
+-            //   recipient MUST either reject the message as invalid or replace the
+-            //   duplicated field-values with a single valid Content-Length field
+-            //   containing that decimal value prior to determining the message body
+-            //   length or forwarding the message.
+-            if (contentLengthValuesCount > 1 && message.protocolVersion() == HttpVersion.HTTP_1_1) {
+-                throw new IllegalArgumentException("Multiple Content-Length headers found");
+-            }
+-            contentLength = Long.parseLong(values.get(0));
+-        }
++
++            contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields,
++                    isHttp10OrEarlier, allowDuplicateContentLengths);
++            if (contentLength != -1) {
++                headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
++             }
++         }
+ 
+         if (isContentAlwaysEmpty(message)) {
+             HttpUtil.setTransferEncodingChunked(message, false);
+             return State.SKIP_CONTROL_CHARS;
+         } else if (HttpUtil.isTransferEncodingChunked(message)) {
+-            if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) {
++            if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
+                 handleTransferEncodingChunkedWithContentLength(message);
+             }
+             return State.READ_CHUNK_SIZE;
+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 94af790..826976e 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
+@@ -23,9 +23,12 @@ import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.List;
+ 
++import io.netty.handler.codec.Headers;
+ import io.netty.util.AsciiString;
+ import io.netty.util.CharsetUtil;
+ import io.netty.util.NetUtil;
++import io.netty.util.internal.UnstableApi;
++import static io.netty.util.internal.StringUtil.COMMA;
+ 
+ /**
+  * Utility methods useful in the HTTP context.
+@@ -34,6 +37,7 @@ public final class HttpUtil {
+ 
+     private static final AsciiString CHARSET_EQUALS = AsciiString.of(HttpHeaderValues.CHARSET + "=");
+     private static final AsciiString SEMICOLON = AsciiString.cached(";");
++    private static final String COMMA_STRING = String.valueOf(COMMA);
+ 
+     private HttpUtil() { }
+ 
+@@ -544,4 +548,85 @@ public final class HttpUtil {
+         }
+         return hostString;
+     }
++
++    /**
++     * Validates, and optionally extracts the content length from headers. This method is not intended for
++     * general use, but is here to be shared between HTTP/1 and HTTP/2 parsing.
++     *
++     * @param contentLengthFields the content-length header fields.
++     * @param isHttp10OrEarlier {@code true} if we are handling HTTP/1.0 or earlier
++     * @param allowDuplicateContentLengths {@code true}  if multiple, identical-value content lengths should be allowed.
++     * @return the normalized content length from the headers or {@code -1} if the fields were empty.
++     * @throws IllegalArgumentException if the content-length fields are not valid
++     */
++    @UnstableApi
++    public static long normalizeAndGetContentLength(
++            List<? extends CharSequence> contentLengthFields, boolean isHttp10OrEarlier,
++            boolean allowDuplicateContentLengths) {
++        if (contentLengthFields.isEmpty()) {
++            return -1;
++        }
++
++        // Guard against multiple Content-Length headers as stated in
++        // https://tools.ietf.org/html/rfc7230#section-3.3.2:
++        //
++        // If a message is received that has multiple Content-Length header
++        //   fields with field-values consisting of the same decimal value, or a
++        //   single Content-Length header field with a field value containing a
++        //   list of identical decimal values (e.g., "Content-Length: 42, 42"),
++        //   indicating that duplicate Content-Length header fields have been
++        //   generated or combined by an upstream message processor, then the
++        //   recipient MUST either reject the message as invalid or replace the
++        //   duplicated field-values with a single valid Content-Length field
++        //   containing that decimal value prior to determining the message body
++        //   length or forwarding the message.
++        String firstField = contentLengthFields.get(0).toString();
++        boolean multipleContentLengths =
++                contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0;
++
++        if (multipleContentLengths && !isHttp10OrEarlier) {
++            if (allowDuplicateContentLengths) {
++                // Find and enforce that all Content-Length values are the same
++                String firstValue = null;
++                for (CharSequence field : contentLengthFields) {
++                    String[] tokens = field.toString().split(COMMA_STRING, -1);
++                    for (String token : tokens) {
++                        String trimmed = token.trim();
++                        if (firstValue == null) {
++                            firstValue = trimmed;
++                        } else if (!trimmed.equals(firstValue)) {
++                            throw new IllegalArgumentException(
++                                    "Multiple Content-Length values found: " + contentLengthFields);
++                        }
++                    }
++                }
++                // Replace the duplicated field-values with a single valid Content-Length field
++                firstField = firstValue;
++            } else {
++                // Reject the message as invalid
++                throw new IllegalArgumentException(
++                        "Multiple Content-Length values found: " + contentLengthFields);
++            }
++        }
++        // Ensure we not allow sign as part of the content-length:
++        // See https://github.com/squid-cache/squid/security/advisories/GHSA-qf3v-rc95-96j5
++        if (!Character.isDigit(firstField.charAt(0))) {
++            // Reject the message as invalid
++            throw new IllegalArgumentException(
++                    "Content-Length value is not a number: " + firstField);
++        }
++        try {
++            final long value = Long.parseLong(firstField);
++            if (value < 0) {
++                // Reject the message as invalid
++                throw new IllegalArgumentException(
++                        "Content-Length value must be >=0: " + value);
++            }
++            return value;
++        } catch (NumberFormatException e) {
++            // Reject the message as invalid
++            throw new IllegalArgumentException(
++                    "Content-Length value is not a number: " + firstField, e);
++        }
++    }
+ }
+diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
+index 2d78fc9..ada4feb 100644
+--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
++++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
+@@ -16,8 +16,11 @@ package io.netty.handler.codec.http2;
+ 
+ import io.netty.buffer.ByteBuf;
+ import io.netty.channel.ChannelHandlerContext;
++import io.netty.handler.codec.http.HttpHeaderNames;
+ import io.netty.handler.codec.http.HttpStatusClass;
++import io.netty.handler.codec.http.HttpUtil;
+ import io.netty.handler.codec.http2.Http2Connection.Endpoint;
++import io.netty.util.internal.SystemPropertyUtil;
+ import io.netty.util.internal.UnstableApi;
+ import io.netty.util.internal.logging.InternalLogger;
+ import io.netty.util.internal.logging.InternalLoggerFactory;
+@@ -49,6 +52,8 @@ import static java.lang.Math.min;
+  */
+ @UnstableApi
+ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
++    private static final boolean VALIDATE_CONTENT_LENGTH =
++            SystemPropertyUtil.getBoolean("io.netty.http2.validateContentLength", true);
+     private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2ConnectionDecoder.class);
+     private Http2FrameListener internalFrameListener = new PrefaceFrameListener();
+     private final Http2Connection connection;
+@@ -57,6 +62,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+     private final Http2FrameReader frameReader;
+     private Http2FrameListener listener;
+     private final Http2PromisedRequestVerifier requestVerifier;
++    private final Http2Connection.PropertyKey contentLengthKey;
+ 
+     public DefaultHttp2ConnectionDecoder(Http2Connection connection,
+                                          Http2ConnectionEncoder encoder,
+@@ -69,6 +75,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+                                          Http2FrameReader frameReader,
+                                          Http2PromisedRequestVerifier requestVerifier) {
+         this.connection = checkNotNull(connection, "connection");
++        contentLengthKey = this.connection.newKey();
+         this.frameReader = checkNotNull(frameReader, "frameReader");
+         this.encoder = checkNotNull(encoder, "encoder");
+         this.requestVerifier = checkNotNull(requestVerifier, "requestVerifier");
+@@ -167,6 +174,23 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+         listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
+     }
+ 
++    // See https://tools.ietf.org/html/rfc7540#section-8.1.2.6
++    private void verifyContentLength(Http2Stream stream, int data, boolean isEnd) throws Http2Exception {
++        if (!VALIDATE_CONTENT_LENGTH) {
++            return;
++        }
++        ContentLength contentLength = stream.getProperty(contentLengthKey);
++        if (contentLength != null) {
++            try {
++                contentLength.increaseReceivedBytes(connection.isServer(), stream.id(), data, isEnd);
++            } finally {
++                if (isEnd) {
++                    stream.removeProperty(contentLengthKey);
++                }
++            }
++        }
++    }
++
+     /**
+      * Handles all inbound frames from the network.
+      */
+@@ -176,7 +200,8 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+                               boolean endOfStream) throws Http2Exception {
+             Http2Stream stream = connection.stream(streamId);
+             Http2LocalFlowController flowController = flowController();
+-            int bytesToReturn = data.readableBytes() + padding;
++            int readable = data.readableBytes();
++            int bytesToReturn = readable + padding;
+ 
+             final boolean shouldIgnore;
+             try {
+@@ -203,7 +228,6 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+                 // All bytes have been consumed.
+                 return bytesToReturn;
+             }
+-
+             Http2Exception error = null;
+             switch (stream.state()) {
+                 case OPEN:
+@@ -231,6 +255,8 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+                     throw error;
+                 }
+ 
++                verifyContentLength(stream, readable, endOfStream);
++
+                 // Call back the application and retrieve the number of bytes that have been
+                 // immediately processed.
+                 bytesToReturn = listener.onDataRead(ctx, streamId, data, padding, endOfStream);
+@@ -311,14 +337,34 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+                             stream.state());
+             }
+ 
+-            stream.headersReceived(isInformational);
+-            encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
+-
+-            listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream);
++            if (!stream.isHeadersReceived()) {
++                // extract the content-length header
++                List<? extends CharSequence> contentLength = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
++                if (contentLength != null && !contentLength.isEmpty()) {
++                    try {
++                        long cLength = HttpUtil.normalizeAndGetContentLength(contentLength, false, true);
++                        if (cLength != -1) {
++                            headers.setLong(HttpHeaderNames.CONTENT_LENGTH, cLength);
++                            stream.setProperty(contentLengthKey, new ContentLength(cLength));
++                        }
++                    } catch (IllegalArgumentException e) {
++                        throw streamError(stream.id(), PROTOCOL_ERROR,
++                                "Multiple content-length headers received", e);
++                    }
++                }
++            }
+ 
+-            // If the headers completes this stream, close it.
+-            if (endOfStream) {
+-                lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
++            stream.headersReceived(isInformational);
++            try {
++                verifyContentLength(stream, 0, endOfStream);
++                encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
++                listener.onHeadersRead(ctx, streamId, headers, streamDependency,
++                        weight, exclusive, padding, endOfStream);
++            } finally {
++                // If the headers completes this stream, close it.
++                if (endOfStream) {
++                    lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
++                }
+             }
+         }
+ 
+@@ -675,4 +721,40 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+             onUnknownFrame0(ctx, frameType, streamId, flags, payload);
+         }
+     }
++
++    private static final class ContentLength {
++        private final long expected;
++        private long seen;
++
++        ContentLength(long expected) {
++            this.expected = expected;
++        }
++
++        void increaseReceivedBytes(boolean server, int streamId, int bytes, boolean isEnd) throws Http2Exception {
++            seen += bytes;
++            // Check for overflow
++            if (seen < 0) {
++                throw streamError(streamId, PROTOCOL_ERROR,
++                        "Received amount of data did overflow and so not match content-length header %d", expected);
++            }
++            // Check if we received more data then what was advertised via the content-length header.
++            if (seen > expected) {
++                throw streamError(streamId, PROTOCOL_ERROR,
++                        "Received amount of data %d does not match content-length header %d", seen, expected);
++            }
++
++            if (isEnd) {
++                if (seen == 0 && !server) {
++                    // This may be a response to a HEAD request, let's just allow it.
++                    return;
++                }
++
++                // Check that we really saw what was told via the content-length header.
++                if (expected > seen) {
++                    throw streamError(streamId, PROTOCOL_ERROR,
++                            "Received amount of data %d does not match content-length header %d", seen, expected);
++                }
++            }
++        }
++    }
+ }
+diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java
+index 7e87d52..d7d3cbf 100644
+--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java
++++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java
+@@ -21,17 +21,21 @@ import io.netty.channel.ChannelFuture;
+ import io.netty.channel.ChannelHandlerContext;
+ import io.netty.channel.ChannelPromise;
+ import io.netty.channel.DefaultChannelPromise;
++import io.netty.handler.codec.http.HttpHeaderNames;
+ import io.netty.handler.codec.http.HttpResponseStatus;
+ import junit.framework.AssertionFailedError;
+ import org.junit.Before;
+ import org.junit.Test;
+ import org.mockito.ArgumentCaptor;
++import org.mockito.ArgumentMatchers;
+ import org.mockito.Mock;
+ import org.mockito.MockitoAnnotations;
+ import org.mockito.invocation.InvocationOnMock;
+ import org.mockito.stubbing.Answer;
+ 
+ import java.util.Collections;
++import java.util.IdentityHashMap;
++import java.util.Map;
+ import java.util.concurrent.atomic.AtomicInteger;
+ 
+ import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+@@ -129,6 +133,21 @@ public class DefaultHttp2ConnectionDecoderTest {
+         when(stream.id()).thenReturn(STREAM_ID);
+         when(stream.state()).thenReturn(OPEN);
+         when(stream.open(anyBoolean())).thenReturn(stream);
++
++        final Map<Object, Object> properties = new IdentityHashMap<Object, Object>();
++        when(stream.getProperty(ArgumentMatchers.<Http2Connection.PropertyKey>any())).thenAnswer(new Answer<Object>() {
++            @Override
++            public Object answer(InvocationOnMock invocationOnMock) {
++                return properties.get(invocationOnMock.getArgument(0));
++            }
++        });
++        when(stream.setProperty(ArgumentMatchers.<Http2Connection.PropertyKey>any(), any())).then(new Answer<Object>() {
++            @Override
++            public Object answer(InvocationOnMock invocationOnMock) {
++                return properties.put(invocationOnMock.getArgument(0), invocationOnMock.getArgument(1));
++            }
++        });
++
+         when(pushStream.id()).thenReturn(PUSH_STREAM_ID);
+         doAnswer(new Answer<Boolean>() {
+             @Override
+@@ -743,6 +762,115 @@ public class DefaultHttp2ConnectionDecoderTest {
+         verify(listener).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER));
+     }
+ 
++    @Test(expected = Http2Exception.StreamException.class)
++    public void dataContentLengthMissmatch() throws Exception {
++        dataContentLengthInvalid(false);
++    }
++
++    @Test(expected = Http2Exception.StreamException.class)
++    public void dataContentLengthInvalid() throws Exception {
++        dataContentLengthInvalid(true);
++    }
++
++    private void dataContentLengthInvalid(boolean negative) throws Exception {
++        final ByteBuf data = dummyData();
++        int padding = 10;
++        int processedBytes = data.readableBytes() + padding;
++        mockFlowControl(processedBytes);
++        try {
++            decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers()
++                    .setLong(HttpHeaderNames.CONTENT_LENGTH, negative ? -1L : 1L), padding, false);
++            decode().onDataRead(ctx, STREAM_ID, data, padding, true);
++            verify(localFlow).receiveFlowControlledFrame(eq(stream), eq(data), eq(padding), eq(true));
++            verify(localFlow).consumeBytes(eq(stream), eq(processedBytes));
++
++            verify(listener, times(1)).onHeadersRead(eq(ctx), anyInt(),
++                    any(Http2Headers.class), eq(0), eq(DEFAULT_PRIORITY_WEIGHT), eq(false),
++                    eq(padding), eq(false));
++            // Verify that the event was absorbed and not propagated to the observer.
++            verify(listener, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean());
++        } finally {
++            data.release();
++        }
++    }
++
++    @Test(expected = Http2Exception.StreamException.class)
++    public void headersContentLengthPositiveSign() throws Exception {
++        headersContentLengthSign("+1");
++    }
++
++    @Test(expected = Http2Exception.StreamException.class)
++    public void headersContentLengthNegativeSign() throws Exception {
++        headersContentLengthSign("-1");
++    }
++
++    private void headersContentLengthSign(String length) throws Exception {
++        int padding = 10;
++        when(connection.isServer()).thenReturn(true);
++        decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers()
++                .set(HttpHeaderNames.CONTENT_LENGTH, length), padding, false);
++
++        // Verify that the event was absorbed and not propagated to the observer.
++        verify(listener, never()).onHeadersRead(eq(ctx), anyInt(),
++                any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
++    }
++
++    @Test(expected = Http2Exception.StreamException.class)
++    public void headersContentLengthMissmatch() throws Exception {
++        headersContentLength(false);
++    }
++
++    @Test(expected = Http2Exception.StreamException.class)
++    public void headersContentLengthInvalid() throws Exception {
++        headersContentLength(true);
++    }
++
++    private void headersContentLength(boolean negative) throws Exception {
++        int padding = 10;
++        when(connection.isServer()).thenReturn(true);
++        decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers()
++                .setLong(HttpHeaderNames.CONTENT_LENGTH, negative ? -1L : 1L), padding, true);
++
++        // Verify that the event was absorbed and not propagated to the observer.
++        verify(listener, never()).onHeadersRead(eq(ctx), anyInt(),
++                any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
++    }
++
++    @Test
++    public void multipleHeadersContentLengthSame() throws Exception {
++        multipleHeadersContentLength(true);
++    }
++
++    @Test(expected = Http2Exception.StreamException.class)
++    public void multipleHeadersContentLengthDifferent() throws Exception {
++        multipleHeadersContentLength(false);
++    }
++
++    private void multipleHeadersContentLength(boolean same) throws Exception {
++        int padding = 10;
++        when(connection.isServer()).thenReturn(true);
++        Http2Headers headers = new DefaultHttp2Headers();
++        if (same) {
++            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0);
++            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0);
++        } else {
++            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0);
++            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 1);
++        }
++
++        decode().onHeadersRead(ctx, STREAM_ID, headers, padding, true);
++
++        if (same) {
++            verify(listener, times(1)).onHeadersRead(eq(ctx), anyInt(),
++                    any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
++            assertEquals(1, headers.getAll(HttpHeaderNames.CONTENT_LENGTH).size());
++        } else {
++            // Verify that the event was absorbed and not propagated to the observer.
++            verify(listener, never()).onHeadersRead(eq(ctx), anyInt(),
++                    any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
++        }
++    }
++
+     private static ByteBuf dummyData() {
+         // The buffer is purposely 8 bytes so it will even work for a ping frame.
+         return wrappedBuffer("abcdefgh".getBytes(UTF_8));


=====================================
debian/patches/series
=====================================
@@ -10,3 +10,10 @@
 11-ignore-protobuf-nano.patch
 13-ignore-conscrypt.patch
 14-Correctly-handle-whitespaces-in-HTTP-header-names-as.patch
+CVE-2019-20444.patch
+CVE-2019-20445_1.patch
+CVE-2019-20445_2.patch
+CVE-2019-20445_3.patch
+CVE-2020-11612.patch
+CVE-2021-21290.patch
+CVE-2021-21295.patch



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

-- 
View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/b2c7e07bec5202fedecd15c4a5c7ea160bb1210f
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/20210328/fde30114/attachment.htm>


More information about the pkg-java-commits mailing list