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

Markus Koschany (@apo) gitlab at salsa.debian.org
Mon Jul 1 16:55:46 BST 2024



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


Commits:
f98b874b by Markus Koschany at 2024-07-01T17:55:21+02:00
Import Debian changes 1:4.1.7-2+deb9u5

netty (1:4.1.7-2+deb9u5) stretch-security; urgency=high
.
  * Non-maintainer upload by the ELTS team.
  * 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.

- - - - -


3 changed files:

- debian/changelog
- + debian/patches/CVE-2024-29025.patch
- debian/patches/series


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,14 @@
+netty (1:4.1.7-2+deb9u5) stretch-security; urgency=high
+
+  * Non-maintainer upload by the ELTS team.
+  * 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.
+
+ -- Markus Koschany <apo at debian.org>  Mon, 24 Jun 2024 15:02:35 +0200
+
 netty (1:4.1.7-2+deb9u4) stretch-security; urgency=high
 
   * Non-maintainer upload by the ELTS team.


=====================================
debian/patches/CVE-2024-29025.patch
=====================================
@@ -0,0 +1,428 @@
+From: Markus Koschany <apo at debian.org>
+Date: Fri, 21 Jun 2024 11:59:35 +0200
+Subject: CVE-2024-29025
+
+Bug-Debian: https://bugs.debian.org/1068110
+Origin: https://github.com/netty/netty/commit/0d0c6ed782d13d423586ad0c71737b2c7d02058c
+---
+ .../multipart/HttpPostMultipartRequestDecoder.java |  45 ++++++++-
+ .../http/multipart/HttpPostRequestDecoder.java     |  70 ++++++++++++++
+ .../multipart/HttpPostStandardRequestDecoder.java  |  44 +++++++++
+ .../http/multipart/HttpPostRequestDecoderTest.java | 103 +++++++++++++++++++++
+ 4 files changed, 261 insertions(+), 1 deletion(-)
+
+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 3896364..9bebb14 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
+@@ -61,6 +61,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
+      */
+@@ -157,7 +167,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
+         this(factory, request, HttpConstants.DEFAULT_CHARSET);
+     }
+ 
+-    /**
++     /**
+      *
+      * @param factory
+      *            the factory used to create InterfaceHttpData
+@@ -171,7 +181,31 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
+      *             if the default charset was wrong when decoding or other
+      *             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) {
+         if (factory == null) {
+             throw new NullPointerException("factory");
+         }
+@@ -184,6 +218,9 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
+         this.request = request;
+         this.charset = charset;
+         this.factory = factory;
++        this.maxFields = maxFields;
++        this.maxBufferedBytes = maxBufferedBytes;
++
+         // Fill default values
+ 
+         setMultipart(this.request.headers().get(HttpHeaderNames.CONTENT_TYPE));
+@@ -345,6 +382,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();
+         }
+@@ -429,6 +469,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 a58541e..1b616f2 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
+@@ -25,6 +25,7 @@ import io.netty.util.internal.StringUtil;
+ 
+ import java.nio.charset.Charset;
+ import java.util.List;
++import io.netty.util.internal.ObjectUtil;
+ 
+ /**
+  * This decoder will decode Body and can handle POST BODY.
+@@ -36,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;
+ 
+     /**
+@@ -52,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
+@@ -100,6 +124,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
+@@ -343,4 +399,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 085609a..2784dd5 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;
+@@ -27,6 +28,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 java.io.IOException;
+ import java.nio.charset.Charset;
+@@ -60,6 +63,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
+      */
+@@ -145,6 +158,29 @@ 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) {
+         if (factory == null) {
+             throw new NullPointerException("factory");
+         }
+@@ -157,6 +193,8 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
+         this.request = request;
+         this.charset = charset;
+         this.factory = factory;
++        this.maxFields = maxFields;
++        this.maxBufferedBytes = maxBufferedBytes;
+         if (request instanceof HttpContent) {
+             // Offer automatically if the given request is als type of HttpContent
+             // See #1089
+@@ -299,6 +337,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();
+         }
+@@ -379,6 +420,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 1334107..bdc5fd7 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;
+@@ -491,4 +492,106 @@ public class HttpPostRequestDecoderTest {
+             content.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)));
++    }
+ }


=====================================
debian/patches/series
=====================================
@@ -20,3 +20,4 @@ CVE-2021-37136.patch
 CVE-2021-37137.patch
 CVE-2021-43797.patch
 CVE-2022-41915.patch
+CVE-2024-29025.patch



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

-- 
This project does not include diff previews in email notifications.
View it on GitLab: https://salsa.debian.org/java-team/netty/-/commit/f98b874b4f7cf2977e46a970a7aa251fe2f0751b
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20240701/deb3540c/attachment.htm>


More information about the pkg-java-commits mailing list