Skip to content
Snippets Groups Projects
Commit 1fe99c03 authored by Markus Koschany's avatar Markus Koschany Committed by Dylan Aïssi
Browse files

Import Debian changes 1:4.1.48-10

parent de047722
Branches debian/trixie
1 merge request!28Update from debian/trixie for apertis/v2026dev2
Pipeline #868373 passed
netty (1:4.1.48-7+deb12u1) bookworm-security; urgency=high
netty (1:4.1.48-10) unstable; urgency=high
* Team upload.
Fix CVE-2023-34462: (Closes: #1038947)
* Fix CVE-2024-29025:
Julien Viet discovered that Netty, a Java NIO client/server socket
framework, was vulnerable to allocation of resources without limits or
throttling due to the accumulation of data in the HttpPostRequestDecoder.
This would allow an attacker to cause a denial of service.
Thanks to Salvatore Bonaccorso for the report. (Closes: #1068110)
-- Markus Koschany <apo@debian.org> Sun, 12 May 2024 21:20:10 +0200
netty (1:4.1.48-9) unstable; urgency=medium
* Team upload.
* d/p/22-java-21.patch: apply upstream Java 21 compatibility patch
(Closes: #1053066).
-- Vladimir Petko <vladimir.petko@canonical.com> Tue, 28 Nov 2023 08:30:44 +1300
netty (1:4.1.48-8) unstable; urgency=medium
* Team upload.
* Fix CVE-2023-34462: (Closes: #1038947)
Guard against high memory usage when parsing ClientHello messages.
* Fix CVE-2023-44487: (Closes: #1054234)
The HTTP/2 protocol allows a denial of service (server resource
consumption) because request cancellation can reset many streams quickly.
-- Markus Koschany <apo@debian.org> Sat, 18 Nov 2023 13:40:36 +0100
-- Markus Koschany <apo@debian.org> Sun, 05 Nov 2023 21:07:13 +0100
netty (1:4.1.48-7) unstable; urgency=medium
......
This diff is collapsed.
From: Markus Koschany <apo@debian.org>
Date: Sun, 12 May 2024 21:17:23 +0200
Subject: CVE-2024-29025
Upstream-Advisory: https://github.com/netty/netty/security/advisories/GHSA-5jpm-x58v-624v
Origin: https://github.com/netty/netty/commit/0d0c6ed782d13d423586ad0c71737b2c7d02058c
---
.../multipart/HttpPostMultipartRequestDecoder.java | 41 ++++++++
.../http/multipart/HttpPostRequestDecoder.java | 69 ++++++++++++++
.../multipart/HttpPostStandardRequestDecoder.java | 44 +++++++++
.../http/multipart/HttpPostRequestDecoderTest.java | 103 +++++++++++++++++++++
4 files changed, 257 insertions(+)
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
index 4d59e4d..8a7d679 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
@@ -62,6 +62,16 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
*/
private final HttpRequest request;
+ /**
+ * The maximum number of fields allows by the form
+ */
+ private final int maxFields;
+
+ /**
+ * The maximum number of accumulated bytes when decoding a field
+ */
+ private final int maxBufferedBytes;
+
/**
* Default charset to use
*/
@@ -173,9 +183,34 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
* errors
*/
public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+ this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
+ }
+
+ /**
+ *
+ * @param factory
+ * the factory used to create InterfaceHttpData
+ * @param request
+ * the request to decode
+ * @param charset
+ * the charset to use as default
+ * @param maxFields
+ * the maximum number of fields the form can have, {@code -1} to disable
+ * @param maxBufferedBytes
+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+ * @throws NullPointerException
+ * for request or charset or factory
+ * @throws ErrorDataDecoderException
+ * if the default charset was wrong when decoding or other
+ * errors
+ */
+ public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
+ int maxFields, int maxBufferedBytes) {
this.request = checkNotNull(request, "request");
this.charset = checkNotNull(charset, "charset");
this.factory = checkNotNull(factory, "factory");
+ this.maxFields = maxFields;
+ this.maxBufferedBytes = maxBufferedBytes;
// Fill default values
setMultipart(this.request.headers().get(HttpHeaderNames.CONTENT_TYPE));
@@ -334,6 +369,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
isLastChunk = true;
}
parseBody();
+ if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
+ throw new HttpPostRequestDecoder.TooLongFormFieldException();
+ }
if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
undecodedChunk.discardReadBytes();
}
@@ -418,6 +456,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
if (data == null) {
return;
}
+ if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
+ throw new HttpPostRequestDecoder.TooManyFormFieldsException();
+ }
List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
if (datas == null) {
datas = new ArrayList<InterfaceHttpData>(1);
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java
index 0183b23..7812f3e 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java
@@ -37,6 +37,10 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
static final int DEFAULT_DISCARD_THRESHOLD = 10 * 1024 * 1024;
+ static final int DEFAULT_MAX_FIELDS = 128;
+
+ static final int DEFAULT_MAX_BUFFERED_BYTES = 1024;
+
private final InterfaceHttpPostRequestDecoder decoder;
/**
@@ -53,6 +57,25 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
}
+ /**
+ *
+ * @param request
+ * the request to decode
+ * @param maxFields
+ * the maximum number of fields the form can have, {@code -1} to disable
+ * @param maxBufferedBytes
+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+ * @throws NullPointerException
+ * for request
+ * @throws ErrorDataDecoderException
+ * if the default charset was wrong when decoding or other
+ * errors
+ */
+ public HttpPostRequestDecoder(HttpRequest request, int maxFields, int maxBufferedBytes) {
+ this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET,
+ maxFields, maxBufferedBytes);
+ }
+
/**
*
* @param factory
@@ -96,6 +119,38 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
}
}
+ /**
+ *
+ * @param factory
+ * the factory used to create InterfaceHttpData
+ * @param request
+ * the request to decode
+ * @param charset
+ * the charset to use as default
+ * @param maxFields
+ * the maximum number of fields the form can have, {@code -1} to disable
+ * @param maxBufferedBytes
+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+ * @throws NullPointerException
+ * for request or charset or factory
+ * @throws ErrorDataDecoderException
+ * if the default charset was wrong when decoding or other
+ * errors
+ */
+ public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
+ int maxFields, int maxBufferedBytes) {
+ ObjectUtil.checkNotNull(factory, "factory");
+ ObjectUtil.checkNotNull(request, "request");
+ ObjectUtil.checkNotNull(charset, "charset");
+
+ // Fill default values
+ if (isMultipart(request)) {
+ decoder = new HttpPostMultipartRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes);
+ } else {
+ decoder = new HttpPostStandardRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes);
+ }
+ }
+
/**
* states follow NOTSTARTED PREAMBLE ( (HEADERDELIMITER DISPOSITION (FIELD |
* FILEUPLOAD))* (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE (MIXEDDELIMITER
@@ -338,4 +393,18 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
super(msg, cause);
}
}
+
+ /**
+ * Exception when the maximum number of fields for a given form is reached
+ */
+ public static final class TooManyFormFieldsException extends DecoderException {
+ private static final long serialVersionUID = 1336267941020800769L;
+ }
+
+ /**
+ * Exception when a field content is too long
+ */
+ public static final class TooLongFormFieldException extends DecoderException {
+ private static final long serialVersionUID = 1336267941020800769L;
+ }
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java
index 7b94a7c..47f9315 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java
@@ -16,6 +16,7 @@
package io.netty.handler.codec.http.multipart;
import io.netty.buffer.ByteBuf;
+import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpRequest;
@@ -26,6 +27,8 @@ import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDec
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooManyFormFieldsException;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooLongFormFieldException;
import io.netty.util.internal.PlatformDependent;
import java.io.IOException;
@@ -61,6 +64,16 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
*/
private final Charset charset;
+ /**
+ * The maximum number of fields allows by the form
+ */
+ private final int maxFields;
+
+ /**
+ * The maximum number of accumulated bytes when decoding a field
+ */
+ private final int maxBufferedBytes;
+
/**
* Does the last chunk already received
*/
@@ -146,9 +159,34 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
* errors
*/
public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+ this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
+ }
+
+ /**
+ *
+ * @param factory
+ * the factory used to create InterfaceHttpData
+ * @param request
+ * the request to decode
+ * @param charset
+ * the charset to use as default
+ * @param maxFields
+ * the maximum number of fields the form can have, {@code -1} to disable
+ * @param maxBufferedBytes
+ * the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+ * @throws NullPointerException
+ * for request or charset or factory
+ * @throws ErrorDataDecoderException
+ * if the default charset was wrong when decoding or other
+ * errors
+ */
+ public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
+ int maxFields, int maxBufferedBytes) {
this.request = checkNotNull(request, "request");
this.charset = checkNotNull(charset, "charset");
this.factory = checkNotNull(factory, "factory");
+ this.maxFields = maxFields;
+ this.maxBufferedBytes = maxBufferedBytes;
try {
if (request instanceof HttpContent) {
// Offer automatically if the given request is als type of HttpContent
@@ -293,6 +331,9 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
isLastChunk = true;
}
parseBody();
+ if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
+ throw new TooLongFormFieldException();
+ }
if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
undecodedChunk.discardReadBytes();
}
@@ -373,6 +414,9 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
if (data == null) {
return;
}
+ if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
+ throw new TooManyFormFieldsException();
+ }
List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
if (datas == null) {
datas = new ArrayList<InterfaceHttpData>(1);
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
index 40771e0..bbd43be 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
@@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpContent;
@@ -803,4 +804,106 @@ public class HttpPostRequestDecoderTest {
decoder.destroy();
req.release();
}
+
+ @Test
+ public void testTooManyFormFieldsPostStandardDecoder() {
+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+
+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1);
+
+ int num = 0;
+ while (true) {
+ try {
+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar&".getBytes())));
+ } catch (DecoderException e) {
+ assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass());
+ break;
+ }
+ assertTrue(num++ < 1024);
+ }
+ assertEquals(1024, num);
+ }
+
+ @Test
+ public void testTooManyFormFieldsPostMultipartDecoder() {
+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+ req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");
+
+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1);
+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("--be38b42a9ad2713f\n".getBytes())));
+
+ int num = 0;
+ while (true) {
+ try {
+ byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" +
+ "content-length: 10\n" +
+ "content-type: text/plain; charset=UTF-8\n" +
+ "\n" +
+ "bar-stream\n" +
+ "--be38b42a9ad2713f\n").getBytes();
+ ByteBuf content = Unpooled.wrappedBuffer(bodyBytes);
+ decoder.offer(new DefaultHttpContent(content));
+ } catch (DecoderException e) {
+ assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass());
+ break;
+ }
+ assertTrue(num++ < 1024);
+ }
+ assertEquals(1024, num);
+ }
+
+ @Test
+ public void testTooLongFormFieldStandardDecoder() {
+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+
+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024);
+
+ try {
+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1])));
+ fail();
+ } catch (DecoderException e) {
+ assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass());
+ }
+ }
+
+ @Test
+ public void testFieldGreaterThanMaxBufferedBytesStandardDecoder() {
+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+
+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 6);
+
+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar".getBytes())));
+ }
+
+ @Test
+ public void testTooLongFormFieldMultipartDecoder() {
+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+ req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");
+
+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024);
+
+ try {
+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1])));
+ fail();
+ } catch (DecoderException e) {
+ assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass());
+ }
+ }
+
+ @Test
+ public void testFieldGreaterThanMaxBufferedBytesMultipartDecoder() {
+ HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+ req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");
+
+ byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" +
+ "content-length: 10\n" +
+ "content-type: text/plain; charset=UTF-8\n" +
+ "\n" +
+ "bar-stream\n" +
+ "--be38b42a9ad2713f\n").getBytes();
+
+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, bodyBytes.length - 1);
+
+ decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(bodyBytes)));
+ }
}
......@@ -23,3 +23,5 @@ CVE-2022-41881.patch
CVE-2022-41915.patch
CVE-2023-34462.patch
CVE-2023-44487.patch
22-java-21.patch
CVE-2024-29025.patch
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment