Version in base suite: 4.1.33-1+deb10u1 Base version: netty_4.1.33-1+deb10u1 Target version: netty_4.1.33-1+deb10u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/n/netty/netty_4.1.33-1+deb10u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/n/netty/netty_4.1.33-1+deb10u2.dsc changelog | 47 +++ patches/CVE-2019-20444.patch | 52 +++ patches/CVE-2019-20445_1.patch | 174 ++++++++++++ patches/CVE-2019-20445_2.patch | 93 ++++++ patches/CVE-2019-20445_3.patch | 45 +++ patches/CVE-2020-11612.patch | 494 ++++++++++++++++++++++++++++++++++++ patches/CVE-2021-21290.patch | 272 ++++++++++++++++++++ patches/CVE-2021-21295.patch | 556 +++++++++++++++++++++++++++++++++++++++++ patches/CVE-2021-21409.patch | 37 ++ patches/series | 8 10 files changed, 1778 insertions(+) diff -Nru netty-4.1.33/debian/changelog netty-4.1.33/debian/changelog --- netty-4.1.33/debian/changelog 2020-01-02 22:19:52.000000000 +0000 +++ netty-4.1.33/debian/changelog 2021-04-01 21:20:46.000000000 +0000 @@ -1,3 +1,50 @@ +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. + - CVE-2021-21409: + In Netty there is a vulnerability that enables request smuggling. The + content-length header is not correctly validated if the request only uses + a single Http2HeaderFrame with the endStream set to to true. This could + lead to request smuggling if the request is proxied to a remote peer and + translated to HTTP/1.1. + + -- Markus Koschany Thu, 01 Apr 2021 23:20:46 +0200 + netty (1:4.1.33-1+deb10u1) buster-security; urgency=high * Non-maintainer upload by the Security Team. diff -Nru netty-4.1.33/debian/patches/CVE-2019-20444.patch netty-4.1.33/debian/patches/CVE-2019-20444.patch --- netty-4.1.33/debian/patches/CVE-2019-20444.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2019-20444.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,52 @@ +From: Markus Koschany +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()); ++ } + } diff -Nru netty-4.1.33/debian/patches/CVE-2019-20445_1.patch netty-4.1.33/debian/patches/CVE-2019-20445_1.patch --- netty-4.1.33/debian/patches/CVE-2019-20445_1.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2019-20445_1.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,174 @@ +From: Markus Koschany +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 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()); diff -Nru netty-4.1.33/debian/patches/CVE-2019-20445_2.patch netty-4.1.33/debian/patches/CVE-2019-20445_2.patch --- netty-4.1.33/debian/patches/CVE-2019-20445_2.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2019-20445_2.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,93 @@ +From: Markus Koschany +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 remove 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). ++ *

++ * 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.
++     * 
++ * 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) { diff -Nru netty-4.1.33/debian/patches/CVE-2019-20445_3.patch netty-4.1.33/debian/patches/CVE-2019-20445_3.patch --- netty-4.1.33/debian/patches/CVE-2019-20445_3.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2019-20445_3.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,45 @@ +From: Markus Koschany +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); + } + diff -Nru netty-4.1.33/debian/patches/CVE-2020-11612.patch netty-4.1.33/debian/patches/CVE-2020-11612.patch --- netty-4.1.33/debian/patches/CVE-2020-11612.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2020-11612.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,494 @@ +From: Markus Koschany +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); ++ } ++ } + } diff -Nru netty-4.1.33/debian/patches/CVE-2021-21290.patch netty-4.1.33/debian/patches/CVE-2021-21290.patch --- netty-4.1.33/debian/patches/CVE-2021-21290.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2021-21290.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,272 @@ +From: Markus Koschany +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); + } diff -Nru netty-4.1.33/debian/patches/CVE-2021-21295.patch netty-4.1.33/debian/patches/CVE-2021-21295.patch --- netty-4.1.33/debian/patches/CVE-2021-21295.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2021-21295.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,556 @@ +From: Markus Koschany +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 values = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); +- int contentLengthValuesCount = values.size(); ++ List 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 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 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 properties = new IdentityHashMap(); ++ when(stream.getProperty(ArgumentMatchers.any())).thenAnswer(new Answer() { ++ @Override ++ public Object answer(InvocationOnMock invocationOnMock) { ++ return properties.get(invocationOnMock.getArgument(0)); ++ } ++ }); ++ when(stream.setProperty(ArgumentMatchers.any(), any())).then(new Answer() { ++ @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() { + @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)); diff -Nru netty-4.1.33/debian/patches/CVE-2021-21409.patch netty-4.1.33/debian/patches/CVE-2021-21409.patch --- netty-4.1.33/debian/patches/CVE-2021-21409.patch 1970-01-01 00:00:00.000000000 +0000 +++ netty-4.1.33/debian/patches/CVE-2021-21409.patch 2021-04-01 21:20:46.000000000 +0000 @@ -0,0 +1,37 @@ +From: Markus Koschany +Date: Thu, 1 Apr 2021 19:16:59 +0200 +Subject: CVE-2021-21409 + +Bug-Debian: https://bugs.debian.org/986217 +Origin: https://github.com/netty/netty/commit/b0fa4d5aab4215f3c22ce6123dd8dd5f38dc0432 +--- + .../io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +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 ada4feb..92fd1bd 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 +@@ -296,10 +296,13 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { + short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { + Http2Stream stream = connection.stream(streamId); + boolean allowHalfClosedRemote = false; ++ boolean isTrailers = false; + if (stream == null && !connection.streamMayHaveExisted(streamId)) { + stream = connection.remote().createStream(streamId, endOfStream); + // Allow the state to be HALF_CLOSE_REMOTE if we're creating it in that state. + allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE; ++ } else if (stream != null) { ++ isTrailers = stream.isHeadersReceived(); + } + + if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "HEADERS")) { +@@ -337,7 +340,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder { + stream.state()); + } + +- if (!stream.isHeadersReceived()) { ++ if (!isTrailers) { + // extract the content-length header + List contentLength = headers.getAll(HttpHeaderNames.CONTENT_LENGTH); + if (contentLength != null && !contentLength.isEmpty()) { diff -Nru netty-4.1.33/debian/patches/series netty-4.1.33/debian/patches/series --- netty-4.1.33/debian/patches/series 2020-01-02 22:19:52.000000000 +0000 +++ netty-4.1.33/debian/patches/series 2021-04-01 21:20:46.000000000 +0000 @@ -10,3 +10,11 @@ 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 +CVE-2021-21409.patch