This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new 41752358c96 Limit MIME boundary length to bound boundary scan cost
(#3700)
41752358c96 is described below
commit 41752358c96ab98b1c24f4f5ac63ff0ef0320cd9
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Nov 11 15:02:17 2026 +0000
Limit MIME boundary length to bound boundary scan cost (#3700)
* Limit MIME boundary length to bound boundary scan cost
* Addressing review comments
---
.../attachment/AttachmentBoundaryDeserializer.java | 5 ++
.../cxf/attachment/AttachmentDeserializer.java | 13 +++++
.../cxf/attachment/AttachmentDeserializerUtil.java | 17 ++++++
.../cxf/attachment/MimeBodyPartInputStream.java | 4 ++
.../cxf/attachment/AttachmentDeserializerTest.java | 63 ++++++++++++++++++++++
5 files changed, 102 insertions(+)
diff --git
a/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java
b/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java
index e7bdc7d616d..b57d63ab1f7 100644
---
a/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java
+++
b/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java
@@ -39,6 +39,7 @@ public class AttachmentBoundaryDeserializer {
private final int maxHeaderLength;
private final int maxHeadersCount;
+ private final int maxBoundaryLength;
private final Message message;
public AttachmentBoundaryDeserializer(Message message) {
@@ -49,6 +50,9 @@ public class AttachmentBoundaryDeserializer {
this.maxHeadersCount = MessageUtils.getContextualInteger(message,
AttachmentDeserializer.ATTACHMENT_HEADERS_MAX_COUNT,
AttachmentDeserializer.DEFAULT_ATTACHMENT_HEADERS_MAX_COUNT);
+ this.maxBoundaryLength = MessageUtils.getContextualInteger(message,
+ AttachmentDeserializer.ATTACHMENT_MAX_BOUNDARY_LENGTH,
+ AttachmentDeserializer.DEFAULT_MAX_BOUNDARY_LENGTH);
}
public Attachment read(InputStream body) throws IOException {
@@ -61,6 +65,7 @@ public class AttachmentBoundaryDeserializer {
}
final byte[] boundary =
boundaryString.getBytes(StandardCharsets.UTF_8);
+ AttachmentDeserializerUtil.checkBoundaryLength(boundary,
maxBoundaryLength);
if (!AttachmentDeserializerUtil.readTillFirstBoundary(stream,
boundary)) {
throw new IOException("Couldn't find MIME boundary: " +
boundaryString);
}
diff --git
a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
index e182e878f17..dc55a8b1deb 100644
--- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
+++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
@@ -84,6 +84,14 @@ public class AttachmentDeserializer {
public static final int DEFAULT_MAX_HEADER_SIZE =
SystemPropertyAction.getInteger("org.apache.cxf.attachment-max-header-size",
300);
+ /**
+ * The maximum length of a MIME boundary (excluding the leading "--"). RFC
2046 limits
+ * boundaries to 70 characters. The default is 70.
+ */
+ public static final String ATTACHMENT_MAX_BOUNDARY_LENGTH =
"attachment-max-boundary-length";
+ public static final int DEFAULT_MAX_BOUNDARY_LENGTH =
+
SystemPropertyAction.getInteger("org.apache.cxf.attachment-max-boundary-length",
70);
+
public static final int THRESHOLD = 1024 * 100; //100K (byte unit)
private static final Pattern CONTENT_TYPE_BOUNDARY_PATTERN =
Pattern.compile("boundary=\"?([^\";]*)");
@@ -113,6 +121,7 @@ public class AttachmentDeserializer {
private int maxHeaderLength = DEFAULT_MAX_HEADER_SIZE;
private int maxHeadersCount = DEFAULT_ATTACHMENT_HEADERS_MAX_COUNT;
+ private int maxBoundaryLength = DEFAULT_MAX_BOUNDARY_LENGTH;
public AttachmentDeserializer(Message message) {
this(message, Collections.singletonList("multipart/related"));
@@ -128,6 +137,9 @@ public class AttachmentDeserializer {
// Get the maximum headers count
maxHeadersCount = MessageUtils.getContextualInteger(message,
ATTACHMENT_HEADERS_MAX_COUNT,
DEFAULT_ATTACHMENT_HEADERS_MAX_COUNT);
+ // Get the maximum boundary length
+ maxBoundaryLength = MessageUtils.getContextualInteger(message,
ATTACHMENT_MAX_BOUNDARY_LENGTH,
+
DEFAULT_MAX_BOUNDARY_LENGTH);
}
public void initializeAttachments() throws IOException {
@@ -168,6 +180,7 @@ public class AttachmentDeserializer {
throw new IOException("Couldn't determine the boundary from
the message!");
}
boundary = boundaryString.getBytes(StandardCharsets.UTF_8);
+ AttachmentDeserializerUtil.checkBoundaryLength(boundary,
maxBoundaryLength);
stream = new
PushbackInputStream(message.getContent(InputStream.class), PUSHBACK_AMOUNT);
if (!AttachmentDeserializerUtil.readTillFirstBoundary(stream,
boundary)) {
diff --git
a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
index c2d4d2bffed..9f30adb0eae 100644
---
a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
+++
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
@@ -36,6 +36,23 @@ final class AttachmentDeserializerUtil {
private AttachmentDeserializerUtil() {
}
+
+ /**
+ * Reject boundaries that are longer than permitted (RFC 2046 limits a
boundary to 70
+ * characters). The boundary scan cost is proportional to the boundary
length, so an
+ * overly long attacker-supplied boundary can be used to burn CPU.
+ *
+ * @param boundary the boundary, including the leading "--"
+ * @param maxBoundaryLength the maximum boundary length, excluding the
leading "--"
+ * @throws IOException if the boundary is empty or too long
+ */
+ static void checkBoundaryLength(byte[] boundary, int maxBoundaryLength)
throws IOException {
+ // +2 for the leading "--"
+ if (boundary == null || boundary.length <= 2
+ || boundary.length > maxBoundaryLength + 2) {
+ throw new IOException("Invalid MIME boundary length");
+ }
+ }
/**
* Move the read pointer to the begining of the first part read till the
end
diff --git
a/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java
b/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java
index bdabf056111..be961f9aa36 100644
--- a/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java
+++ b/core/src/main/java/org/apache/cxf/attachment/MimeBodyPartInputStream.java
@@ -38,6 +38,10 @@ public class MimeBodyPartInputStream extends InputStream {
byte[] boundaryParam,
int pbsize) {
super();
+ // The boundary length limit is enforced by the callers, which can
read it per message
+ if (boundaryParam == null || boundaryParam.length == 0) {
+ throw new IllegalArgumentException("Invalid MIME boundary");
+ }
this.inStream = inStreamParam;
this.boundary = boundaryParam;
this.pbAmount = pbsize;
diff --git
a/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java
b/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java
index 60572e06b88..ed97d5e818d 100644
---
a/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java
+++
b/core/src/test/java/org/apache/cxf/attachment/AttachmentDeserializerTest.java
@@ -716,6 +716,69 @@ public class AttachmentDeserializerTest {
() -> ad.initializeAttachments());
}
+ @Test
+ public void testBoundaryTooLong() throws Exception {
+ StringBuilder boundary = new StringBuilder();
+ IntStream.range(0, 2040).forEach(i -> boundary.append('A'));
+ boundary.append('B');
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("--").append(boundary).append("\r\n")
+ .append("Content-Type: text/xml; charset=UTF-8\r\n")
+ .append("\r\n")
+ .append("<envelope/>\r\n")
+ .append("--").append(boundary).append("--\r\n");
+
+ msg = new MessageImpl();
+ msg.setContent(InputStream.class, new
ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)));
+ msg.put(Message.CONTENT_TYPE, "multipart/related; boundary=\"" +
boundary + "\"");
+ AttachmentDeserializer ad = new AttachmentDeserializer(msg);
+
+ assertThrows("Failure expected on too long boundary",
IOException.class,
+ () -> ad.initializeAttachments());
+ }
+
+ @Test
+ public void testBoundaryMaxLengthContextualProperty() throws Exception {
+ StringBuilder boundary = new StringBuilder();
+ IntStream.range(0, 100).forEach(i -> boundary.append('A'));
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("--").append(boundary).append("\r\n")
+ .append("Content-Type: text/xml; charset=UTF-8\r\n")
+ .append("\r\n")
+ .append("<envelope/>\r\n")
+ .append("--").append(boundary).append("--\r\n");
+
+ msg = new MessageImpl();
+ msg.setContent(InputStream.class, new
ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)));
+ msg.put(Message.CONTENT_TYPE, "multipart/related; boundary=\"" +
boundary + "\"");
+ msg.put(AttachmentDeserializer.ATTACHMENT_MAX_BOUNDARY_LENGTH, "100");
+ AttachmentDeserializer ad = new AttachmentDeserializer(msg);
+ ad.initializeAttachments();
+ assertEquals("<envelope/>",
IOUtils.toString(msg.getContent(InputStream.class)));
+ }
+
+ @Test
+ public void testBoundaryMaxLength() throws Exception {
+ StringBuilder boundary = new StringBuilder();
+ IntStream.range(0, 70).forEach(i -> boundary.append('A'));
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("--").append(boundary).append("\r\n")
+ .append("Content-Type: text/xml; charset=UTF-8\r\n")
+ .append("\r\n")
+ .append("<envelope/>\r\n")
+ .append("--").append(boundary).append("--\r\n");
+
+ msg = new MessageImpl();
+ msg.setContent(InputStream.class, new
ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)));
+ msg.put(Message.CONTENT_TYPE, "multipart/related; boundary=\"" +
boundary + "\"");
+ AttachmentDeserializer ad = new AttachmentDeserializer(msg);
+ ad.initializeAttachments();
+ assertEquals("<envelope/>",
IOUtils.toString(msg.getContent(InputStream.class)));
+ }
+
@Test
public void testAttachmentHeaderSize() throws Exception {
final Random random = new Random();