File tomcat-9.0.36-CVE-2025-48988.patch of Package tomcat.40071
From ee8042ffce4cb9324dfd79efda5984f37bbb6910 Mon Sep 17 00:00:00 2001
From: Mark Thomas <markt@apache.org>
Date: Tue, 3 Jun 2025 16:05:53 +0100
Subject: [PATCH] Provide finder grained control of multi-part requests
This exposes two new configuration attributes on the Connector:
- maxPartCount
- maxPartHeaderSize
---
From 6d58522c79f85132506c69f6fbefbef51501c0a7 Mon Sep 17 00:00:00 2001
From: Mark Thomas <markt@apache.org>
Date: Thu, 5 Jun 2025 14:23:16 +0100
Subject: [PATCH] More updates towards a Commons FileUpload 1.6.0 RC/release
---
Index: apache-tomcat-9.0.36-src/MERGE.txt
===================================================================
--- apache-tomcat-9.0.36-src.orig/MERGE.txt
+++ apache-tomcat-9.0.36-src/MERGE.txt
@@ -51,7 +51,7 @@ FileUpload
Sub-tree:
src/main/java/org/apache/commons/fileupload2
The SHA1 ID for the most recent commit to be merged to Tomcat is:
-34eb241c051b02eca3b0b1b04f67b3b4e6c3a24d (2023-01-03)
+f1028401e3d59bd42aee9ab4d26995991db1aadc (2025-06-05)
Note: Tomcat's copy of fileupload also includes classes copied manually from
Commons IO.
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/Connector.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/connector/Connector.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/Connector.java
@@ -202,6 +202,10 @@ public class Connector extends Lifecycle
*/
private int maxCookieCount = 200;
+ private int maxPartCount = 10;
+
+ private int maxPartHeaderSize = 512;
+
/**
* The maximum number of parameters (GET plus POST) which will be
* automatically parsed by the container. 10000 by default. A value of less
@@ -455,6 +459,26 @@ public class Connector extends Lifecycle
}
+ public int getMaxPartCount() {
+ return maxPartCount;
+ }
+
+
+ public void setMaxPartCount(int maxPartCount) {
+ this.maxPartCount = maxPartCount;
+ }
+
+
+ public int getMaxPartHeaderSize() {
+ return maxPartHeaderSize;
+ }
+
+
+ public void setMaxPartHeaderSize(int maxPartHeaderSize) {
+ this.maxPartHeaderSize = maxPartHeaderSize;
+ }
+
+
/**
* Set the maximum number of parameters (GET plus POST) that will be
* automatically parsed by the container. A value of less than 0 means no
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/Request.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/connector/Request.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/connector/Request.java
@@ -2864,13 +2864,29 @@ public class Request implements HttpServ
upload.setFileItemFactory(factory);
upload.setFileSizeMax(mce.getMaxFileSize());
upload.setSizeMax(mce.getMaxRequestSize());
- if (maxParameterCount > -1) {
- // There is a limit. The limit for parts needs to be reduced by
- // the number of parameters we have already parsed.
- // Must be under the limit else parsing parameters would have
- // triggered an exception.
- upload.setFileCountMax(maxParameterCount - parameters.size());
+ upload.setPartHeaderSizeMax(connector.getMaxPartHeaderSize());
+ /*
+ * There are two independent limits on the number of parts.
+ *
+ * 1. The limit based on parameters. This is maxParameterCount less the number of parameters already processed.
+ *
+ * 2. The limit based on parts. This is maxPartCount.
+ *
+ * The lower of these two limits will be applied to this request.
+ *
+ * Note: Either of both limits may be set to -1 (unlimited).
+ */
+ int partLimit = maxParameterCount;
+ if (partLimit > -1) {
+ partLimit = partLimit - parameters.size();
}
+ int maxPartCount = connector.getMaxPartCount();
+ if (maxPartCount > -1) {
+ if (partLimit < 0 || partLimit > maxPartCount) {
+ partLimit = maxPartCount;
+ }
+ }
+ upload.setFileCountMax(partLimit);
parts = new ArrayList<>();
try {
Index: apache-tomcat-9.0.36-src/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
+++ apache-tomcat-9.0.36-src/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import org.apache.tomcat.util.http.fileupload.impl.FileCountLimitExceededException;
import org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl;
@@ -99,6 +100,13 @@ public abstract class FileUploadBase {
public static final String FORM_DATA = "form-data";
/**
+ * Default per part header size limit in bytes.
+ *
+ * @since FileUpload 1.6.0
+ */
+ public static final int DEFAULT_PART_HEADER_SIZE_MAX = 512;
+
+ /**
* Content-disposition value for file attachment.
*/
public static final String ATTACHMENT = "attachment";
@@ -139,6 +147,11 @@ public abstract class FileUploadBase {
private long fileCountMax = -1;
/**
+ * The maximum permitted size of the headers provided with a single part in bytes.
+ */
+ private int partHeaderSizeMax = DEFAULT_PART_HEADER_SIZE_MAX;
+
+ /**
* The content encoding to use when reading part headers.
*/
private String headerEncoding;
@@ -302,11 +315,9 @@ public abstract class FileUploadBase {
boolean successful = false;
try {
FileItemIterator iter = getItemIterator(ctx);
- FileItemFactory fac = getFileItemFactory();
+ final FileItemFactory fileItemFactory = getFileItemFactory();
+ Objects.requireNonNull(fileItemFactory, "getFileItemFactory()");
final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
- if (fac == null) {
- throw new NullPointerException("No FileItemFactory has been set.");
- }
while (iter.hasNext()) {
if (items.size() == fileCountMax) {
// The next item will exceed the limit.
@@ -315,7 +326,7 @@ public abstract class FileUploadBase {
final FileItemStream item = iter.next();
// Don't use getName() here to prevent an InvalidFileNameException.
final String fileName = ((FileItemStreamImpl) item).getName();
- FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
+ FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(),
item.isFormField(), fileName);
items.add(fileItem);
try {
@@ -577,6 +588,17 @@ public abstract class FileUploadBase {
}
/**
+ * Obtain the per part size limit for headers.
+ *
+ * @return The maximum size of the headers for a single part in bytes.
+ *
+ * @since FileUpload 1.6.0
+ */
+ public int getPartHeaderSizeMax() {
+ return partHeaderSizeMax;
+ }
+
+ /**
* Returns the progress listener.
*
* @return The progress listener, if any, or null.
@@ -586,6 +608,17 @@ public abstract class FileUploadBase {
}
/**
+ * Sets the per part size limit for headers.
+ *
+ * @param partHeaderSizeMax The maximum size of the headers in bytes.
+ *
+ * @since FileUpload 1.6.0
+ */
+ public void setPartHeaderSizeMax(final int partHeaderSizeMax) {
+ this.partHeaderSizeMax = partHeaderSizeMax;
+ }
+
+ /**
* Sets the progress listener.
*
* @param pListener The progress listener, if any. Defaults to null.
Index: apache-tomcat-9.0.36-src/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
+++ apache-tomcat-9.0.36-src/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
@@ -23,6 +23,7 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.apache.tomcat.util.http.fileupload.impl.FileUploadIOException;
+import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.util.Closeable;
import org.apache.tomcat.util.http.fileupload.util.Streams;
@@ -172,7 +173,10 @@ public class MultipartStream {
/**
* The maximum length of <code>header-part</code> that will be
* processed (10 kilobytes = 10240 bytes.).
+ *
+ * @deprecated Unused. Replaced by {@link #getPartHeaderSizeMax()}.
*/
+ @Deprecated
public static final int HEADER_PART_SIZE_MAX = 10240;
/**
@@ -268,6 +272,11 @@ public class MultipartStream {
// ----------------------------------------------------------- Constructors
/**
+ * The maximum permitted size of the headers provided with a single part in bytes.
+ */
+ private int partHeaderSizeMax = FileUploadBase.DEFAULT_PART_HEADER_SIZE_MAX;
+
+ /**
* <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
*
* <p> Note that the buffer must be at least big enough to contain the
@@ -496,9 +505,6 @@ public class MultipartStream {
* trailing <code>CRLF</code> marker. Parsing is left to the
* application.
*
- * <p><strong>TODO</strong> allow limiting maximum header size to
- * protect against abuse.
- *
* @return The <code>header-part</code> of the current encapsulation.
*
* @throws FileUploadIOException if the bytes read from the stream exceeded the size limits.
@@ -519,10 +525,11 @@ public class MultipartStream {
} catch (IOException e) {
throw new MalformedStreamException("Stream ended unexpectedly");
}
- if (++size > HEADER_PART_SIZE_MAX) {
- throw new MalformedStreamException(String.format(
- "Header section has more than %s bytes (maybe it is not properly terminated)",
- Integer.valueOf(HEADER_PART_SIZE_MAX)));
+ size++;
+ if (getPartHeaderSizeMax() != -1 && size > getPartHeaderSizeMax()) {
+ throw new FileUploadIOException(new SizeLimitExceededException(
+ String.format("Header section has more than %s bytes (maybe it is not properly terminated)", Integer.valueOf(getPartHeaderSizeMax())),
+ size, getPartHeaderSizeMax()));
}
if (b == HEADER_SEPARATOR[i]) {
i++;
@@ -573,6 +580,17 @@ public class MultipartStream {
}
/**
+ * Obtain the per part size limit for headers.
+ *
+ * @return The maximum size of the headers for a single part in bytes.
+ *
+ * @since 1.6.0
+ */
+ public int getPartHeaderSizeMax() {
+ return partHeaderSizeMax;
+ }
+
+ /**
* Creates a new {@link ItemInputStream}.
* @return A new instance of {@link ItemInputStream}.
*/
@@ -759,6 +777,17 @@ public class MultipartStream {
}
/**
+ * Sets the per part size limit for headers.
+ *
+ * @param partHeaderSizeMax The maximum size of the headers in bytes.
+ *
+ * @since 1.6.0
+ */
+ public void setPartHeaderSizeMax(final int partHeaderSizeMax) {
+ this.partHeaderSizeMax = partHeaderSizeMax;
+ }
+
+ /**
* An {@link InputStream} for reading an items contents.
*/
public class ItemInputStream extends InputStream implements Closeable {
Index: apache-tomcat-9.0.36-src/java/org/apache/tomcat/util/http/fileupload/impl/FileItemIteratorImpl.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/tomcat/util/http/fileupload/impl/FileItemIteratorImpl.java
+++ apache-tomcat-9.0.36-src/java/org/apache/tomcat/util/http/fileupload/impl/FileItemIteratorImpl.java
@@ -187,6 +187,7 @@ public class FileItemIteratorImpl implem
String.format("The boundary specified in the %s header is too long", FileUploadBase.CONTENT_TYPE), iae);
}
multiPartStream.setHeaderEncoding(charEncoding);
+ multiPartStream.setPartHeaderSizeMax(fileUploadBase.getPartHeaderSizeMax());
}
public MultipartStream getMultiPartStream() throws FileUploadException, IOException {
Index: apache-tomcat-9.0.36-src/webapps/docs/changelog.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/webapps/docs/changelog.xml
+++ apache-tomcat-9.0.36-src/webapps/docs/changelog.xml
@@ -213,6 +213,13 @@
<add>
Add support for TLS 1.3 client initiated re-keying. (markt)
</add>
+ <add>
+ Provide finer grained control of multi-part request processing via two
+ new attributes on the <code>Connector</code> element.
+ <code>maxPartCount</code> limits the total number of parts in a
+ multi-part request and <code>maxPartHeaderSize</code> limits the size of
+ the headers provided with each part. (markt)
+ </add>
</changelog>
</subsection>
<subsection name="Jasper">
@@ -321,8 +328,8 @@
<subsection name="Other">
<changelog>
<update>
- Update the internal fork of Apache Commons FileUpload to 34eb241
- (2023-01-03, 2.0-SNAPSHOT). (markt)
+ Update the internal fork of Apache Commons FileUpload to f102840
+ (2023-06-05, 1.x-SNAPSHOT). (markt)
</update>
<fix>
Make counting of active HTTP/2 streams per connection more robust.
Index: apache-tomcat-9.0.36-src/webapps/docs/config/ajp.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/webapps/docs/config/ajp.xml
+++ apache-tomcat-9.0.36-src/webapps/docs/config/ajp.xml
@@ -147,6 +147,21 @@
exceed the limit.</p>
</attribute>
+ <attribute name="maxPartCount" required="false">
+ <p>The maximum total number of parts permitted in a request where the
+ content type is <code>multipart/form-data</code>. This limit is in
+ addition to <code>maxParameterCount</code>. Requests that exceed this
+ limit will be rejected. A value of less than 0 means no limit. If not
+ specified, a default of 10 is used.</p>
+ </attribute>
+
+ <attribute name="maxPartHeaderSize" required="false">
+ <p>The maximum number of header bytes permitted per part in a request
+ where the content type is <code>multipart/form-data</code>. Requests that
+ exceed this limit will be rejected. A value of less than 0 means no limit.
+ If not specified, a default of 512 is used.</p>
+ </attribute>
+
<attribute name="maxPostSize" required="false">
<p>The maximum size in bytes of the POST which will be handled by
the container FORM URL parameter parsing. The limit can be disabled by
Index: apache-tomcat-9.0.36-src/webapps/docs/config/http.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/webapps/docs/config/http.xml
+++ apache-tomcat-9.0.36-src/webapps/docs/config/http.xml
@@ -148,6 +148,21 @@
exceed the limit.</p>
</attribute>
+ <attribute name="maxPartCount" required="false">
+ <p>The maximum total number of parts permitted in a request where the
+ content type is <code>multipart/form-data</code>. This limit is in
+ addition to <code>maxParameterCount</code>. Requests that exceed this
+ limit will be rejected. A value of less than 0 means no limit. If not
+ specified, a default of 10 is used.</p>
+ </attribute>
+
+ <attribute name="maxPartHeaderSize" required="false">
+ <p>The maximum number of header bytes permitted per part in a request
+ where the content type is <code>multipart/form-data</code>. Requests that
+ exceed this limit will be rejected. A value of less than 0 means no limit.
+ If not specified, a default of 512 is used.</p>
+ </attribute>
+
<attribute name="maxPostSize" required="false">
<p>The maximum size in bytes of the POST which will be handled by
the container FORM URL parameter parsing. The limit can be disabled by
Index: apache-tomcat-9.0.36-src/webapps/docs/config/http2.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/webapps/docs/config/http2.xml
+++ apache-tomcat-9.0.36-src/webapps/docs/config/http2.xml
@@ -296,6 +296,8 @@
<ul>
<li>maxCookieCount</li>
<li>maxParameterCount</li>
+ <li>maxPartCount</li>
+ <li>maxPartHeaderSize</li>
<li>maxPostSize</li>
<li>maxSavePostSize</li>
</ul>