This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 3.6.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/3.6.x-fixes by this push:
     new c7704e83e58 Limit MIME boundary length to bound boundary scan cost 
(#3700)
c7704e83e58 is described below

commit c7704e83e584ffb58c6df834e72aa168c00fca65
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
    
    (cherry picked from commit 41752358c96ab98b1c24f4f5ac63ff0ef0320cd9)
---
 .../cxf/attachment/AttachmentDeserializer.java     | 13 +++++
 .../cxf/attachment/AttachmentDeserializerUtil.java | 17 ++++++
 .../cxf/attachment/MimeBodyPartInputStream.java    |  4 ++
 .../cxf/attachment/AttachmentDeserializerTest.java | 63 ++++++++++++++++++++++
 4 files changed, 97 insertions(+)

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 5429d675862..57de178a4da 100644
--- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
+++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
@@ -85,6 +85,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=\"?([^\";]*)");
@@ -114,6 +122,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"));
@@ -129,6 +138,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 {
@@ -169,6 +181,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 (!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 755ae5353de..463381aa6c8 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();

Reply via email to