This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/mime-boundary in repository https://gitbox.apache.org/repos/asf/cxf.git
commit a3510eb5ae6194c5d6518a97ae924341961d4220 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Tue Nov 10 15:42:15 2026 +0000 Limit MIME boundary length to bound boundary scan cost --- .../attachment/AttachmentBoundaryDeserializer.java | 1 + .../cxf/attachment/AttachmentDeserializer.java | 8 +++++ .../cxf/attachment/AttachmentDeserializerUtil.java | 16 +++++++++ .../cxf/attachment/MimeBodyPartInputStream.java | 5 +++ .../cxf/attachment/AttachmentDeserializerTest.java | 42 ++++++++++++++++++++++ 5 files changed, 72 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..ed1d49f85ef 100644 --- a/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java +++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java @@ -61,6 +61,7 @@ public class AttachmentBoundaryDeserializer { } final byte[] boundary = boundaryString.getBytes(StandardCharsets.UTF_8); + AttachmentDeserializerUtil.checkBoundaryLength(boundary); 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..457a81800b0 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,13 @@ 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 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=\"?([^\";]*)"); @@ -168,6 +175,7 @@ public class AttachmentDeserializer { throw new IOException("Couldn't determine the boundary from the message!"); } boundary = boundaryString.getBytes(StandardCharsets.UTF_8); + AttachmentDeserializerUtil.checkBoundaryLength(boundary); 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..9f9f2e2451a 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,22 @@ 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 "--" + * @throws IOException if the boundary is empty or too long + */ + static void checkBoundaryLength(byte[] boundary) throws IOException { + // +2 for the leading "--" + if (boundary == null || boundary.length <= 2 + || boundary.length > AttachmentDeserializer.DEFAULT_MAX_BOUNDARY_LENGTH + 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..cb157576557 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,11 @@ public class MimeBodyPartInputStream extends InputStream { byte[] boundaryParam, int pbsize) { super(); + if (boundaryParam == null || boundaryParam.length == 0 + || boundaryParam.length > AttachmentDeserializer.DEFAULT_MAX_BOUNDARY_LENGTH + 2) { + // +2 for the leading "--" + throw new IllegalArgumentException("Invalid MIME boundary length"); + } 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..b1eca80681f 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,48 @@ 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 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();
