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

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit 94c935d1406bc0875daebd36994595efa2f48ae0
Author: Robert Lazarski <[email protected]>
AuthorDate: Fri Sep 4 11:23:55 2026 -1000

    Bound the MTOM, SwA and SOAP bodies too
    
    The ceilings added for the form builders left the other stream-reading 
builders
    unbounded, which bounds nothing: the caller picks the builder by choosing 
the
    Content-Type, so sending multipart/related avoided them entirely. Bound the
    whole multipart body in MIMEBuilder, before the parts are buffered to heap 
or
    disk, and a plain SOAP or POX body in SOAPBuilder. Its MIME path takes its 
root
    part from the now-bounded multipart body and needs no ceiling of its own. 
Item
    10 of the threat model scoped itself to the form builders, which is what let
    this stand; it now says every stream-reading builder.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 SECURITY.md                                        | 18 ++++++++----
 .../src/org/apache/axis2/builder/MIMEBuilder.java  | 12 ++++++--
 .../apache/axis2/builder/RequestSizeLimits.java    | 20 ++++++++++++++
 .../src/org/apache/axis2/builder/SOAPBuilder.java  | 10 ++++++-
 .../axis2/builder/RequestSizeLimitsTest.java       | 32 ++++++++++++++++++++++
 src/site/markdown/release-notes/2.0.2.md           | 10 +++++--
 6 files changed, 90 insertions(+), 12 deletions(-)

diff --git a/SECURITY.md b/SECURITY.md
index 76475cfb4f..1feeae5d1b 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -312,12 +312,18 @@ migration from `commons-fileupload` 1.x to 
`commons-fileupload2` in
    does not currently support. Operators in cloud environments should pair
    these settings with network egress controls.
 
-10. **Request body ceilings (2.0.2):** The `multipart/form-data` and
-    `application/x-www-form-urlencoded` builders read the transport stream
-    directly, so a servlet container's post-size limit never sees the body.
-    `multipartMaxRequestSize` and `multipartMaxFileSize` (100 MB) and
-    `formUrlEncodedMaxRequestSize` (2 MB) bound them; `-1` restores the
-    previous unbounded behaviour, and either may be set per service.
+10. **Request body ceilings (2.0.2):** The message builders read the transport
+    stream directly, so a servlet container's post-size limit never sees the
+    body. `multipartMaxRequestSize` and `multipartMaxFileSize` (100 MB),
+    `formUrlEncodedMaxRequestSize` (2 MB), `mtomMaxRequestSize` (100 MB, the
+    whole `multipart/related` body, so MTOM and SwA) and `soapMaxRequestSize`
+    (100 MB, a plain SOAP or POX body) bound them; `-1` restores the previous
+    unbounded behaviour, and any may be set per service.
+
+    Every builder that reads the stream has to be bounded, not just the ones
+    whose limits were reported: the caller picks which builder runs by choosing
+    the Content-Type, so a ceiling on the form builders alone is avoided by
+    sending `multipart/related` instead.
     Multipart temp files are now deleted rather than accumulating: form-field
     parts as soon as their text is read, file parts once the item backing the
     `DataHandler` is unreachable.
diff --git a/modules/kernel/src/org/apache/axis2/builder/MIMEBuilder.java 
b/modules/kernel/src/org/apache/axis2/builder/MIMEBuilder.java
index 454bda3cc0..0e1f61e811 100644
--- a/modules/kernel/src/org/apache/axis2/builder/MIMEBuilder.java
+++ b/modules/kernel/src/org/apache/axis2/builder/MIMEBuilder.java
@@ -36,8 +36,16 @@ public class MIMEBuilder implements Builder {
     public OMElement processDocument(InputStream inputStream, String 
contentType,
                                      MessageContext msgContext)
             throws AxisFault {
-        Attachments attachments =
-                BuilderUtil.createAttachmentsMap(msgContext, inputStream, 
contentType);
+        // Bounded before the parts are read: the attachment map buffers parts 
in
+        // heap by default, or on disk with cacheAttachments, and neither was
+        // bounded. The caller chooses this builder by sending
+        // multipart/related, so leaving it unbounded left the form-builder
+        // ceilings trivially avoidable.
+        long maxRequestSize = RequestSizeLimits.resolve(msgContext,
+                RequestSizeLimits.MTOM_MAX_REQUEST_SIZE,
+                RequestSizeLimits.DEFAULT_MTOM_MAX_REQUEST_SIZE);
+        Attachments attachments = BuilderUtil.createAttachmentsMap(msgContext,
+                BoundedInputStream.wrap(inputStream, maxRequestSize), 
contentType);
 
         ContentType ct;
         try {
diff --git a/modules/kernel/src/org/apache/axis2/builder/RequestSizeLimits.java 
b/modules/kernel/src/org/apache/axis2/builder/RequestSizeLimits.java
index 28b48682a3..ce9461b37d 100644
--- a/modules/kernel/src/org/apache/axis2/builder/RequestSizeLimits.java
+++ b/modules/kernel/src/org/apache/axis2/builder/RequestSizeLimits.java
@@ -62,6 +62,26 @@ public final class RequestSizeLimits {
     public static final long DEFAULT_FORM_URLENCODED_MAX_REQUEST_SIZE = 2L * 
1024 * 1024;
 
     /** Sentinel for "no ceiling", matching the commons-fileupload2 
convention. */
+    /**
+     * Ceiling on a {@code multipart/related} body: MTOM and SwA.
+     * <p>
+     * The same reasoning as the form builders, which is why this belongs 
here: the
+     * MIME builder reads the transport stream directly, so a servlet 
container's
+     * post-size limit never sees the body. It also means the caller picks 
which
+     * builder runs by choosing the Content-Type, so bounding only the form 
builders
+     * bounds nothing -- an attacker simply sends {@code multipart/related}.
+     */
+    public static final String MTOM_MAX_REQUEST_SIZE = "mtomMaxRequestSize";
+
+    /** Ceiling on a plain SOAP or POX body. */
+    public static final String SOAP_MAX_REQUEST_SIZE = "soapMaxRequestSize";
+
+    /** Default {@link #MTOM_MAX_REQUEST_SIZE}: 100 MB. */
+    public static final long DEFAULT_MTOM_MAX_REQUEST_SIZE = 100L * 1024 * 
1024;
+
+    /** Default {@link #SOAP_MAX_REQUEST_SIZE}: 100 MB. */
+    public static final long DEFAULT_SOAP_MAX_REQUEST_SIZE = 100L * 1024 * 
1024;
+
     public static final long UNLIMITED = -1L;
 
     private RequestSizeLimits() {
diff --git a/modules/kernel/src/org/apache/axis2/builder/SOAPBuilder.java 
b/modules/kernel/src/org/apache/axis2/builder/SOAPBuilder.java
index 7c4167f992..c16c43032b 100644
--- a/modules/kernel/src/org/apache/axis2/builder/SOAPBuilder.java
+++ b/modules/kernel/src/org/apache/axis2/builder/SOAPBuilder.java
@@ -41,7 +41,15 @@ public class SOAPBuilder implements MIMEAwareBuilder {
             
             // createSOAPModelBuilder takes care of configuring the underlying 
parser to
             // avoid the security issue described in CVE-2010-1632
-            OMXMLParserWrapper builder = 
OMXMLBuilderFactory.createSOAPModelBuilder(inputStream,
+            // Bounded for the same reason as the other builders: this reads 
the
+            // transport stream directly. processMIMEMessage below takes its 
root
+            // part from an already-bounded multipart body, so it needs no 
ceiling
+            // of its own.
+            long maxRequestSize = RequestSizeLimits.resolve(messageContext,
+                    RequestSizeLimits.SOAP_MAX_REQUEST_SIZE,
+                    RequestSizeLimits.DEFAULT_SOAP_MAX_REQUEST_SIZE);
+            OMXMLParserWrapper builder = 
OMXMLBuilderFactory.createSOAPModelBuilder(
+                    BoundedInputStream.wrap(inputStream, maxRequestSize),
                     charSetEncoding);
             messageContext.setProperty(Constants.BUILDER, builder);
             SOAPEnvelope envelope = (SOAPEnvelope) 
builder.getDocumentElement();
diff --git 
a/modules/kernel/test/org/apache/axis2/builder/RequestSizeLimitsTest.java 
b/modules/kernel/test/org/apache/axis2/builder/RequestSizeLimitsTest.java
index 20be388192..6ce056919a 100644
--- a/modules/kernel/test/org/apache/axis2/builder/RequestSizeLimitsTest.java
+++ b/modules/kernel/test/org/apache/axis2/builder/RequestSizeLimitsTest.java
@@ -127,4 +127,36 @@ public class RequestSizeLimitsTest extends TestCase {
         }
         return total;
     }
+
+    /**
+     * The ceilings the form builders had were avoidable: the caller picks the
+     * builder by choosing the Content-Type, so multipart/related and a plain 
SOAP
+     * body needed their own.
+     */
+    public void testMtomAndSoapCeilingsHaveDefaults() throws Exception {
+        assertEquals(RequestSizeLimits.DEFAULT_MTOM_MAX_REQUEST_SIZE,
+                RequestSizeLimits.resolve(messageContext,
+                        RequestSizeLimits.MTOM_MAX_REQUEST_SIZE,
+                        RequestSizeLimits.DEFAULT_MTOM_MAX_REQUEST_SIZE));
+        assertEquals(RequestSizeLimits.DEFAULT_SOAP_MAX_REQUEST_SIZE,
+                RequestSizeLimits.resolve(messageContext,
+                        RequestSizeLimits.SOAP_MAX_REQUEST_SIZE,
+                        RequestSizeLimits.DEFAULT_SOAP_MAX_REQUEST_SIZE));
+    }
+
+    public void testMtomCeilingIsConfigurable() throws Exception {
+        axisConfiguration.addParameter(
+                new Parameter(RequestSizeLimits.MTOM_MAX_REQUEST_SIZE, 
"8192"));
+        assertEquals(8192L, RequestSizeLimits.resolve(messageContext,
+                RequestSizeLimits.MTOM_MAX_REQUEST_SIZE,
+                RequestSizeLimits.DEFAULT_MTOM_MAX_REQUEST_SIZE));
+    }
+
+    public void testSoapCeilingCanBeOptedOut() throws Exception {
+        axisConfiguration.addParameter(
+                new Parameter(RequestSizeLimits.SOAP_MAX_REQUEST_SIZE, "-1"));
+        assertEquals(RequestSizeLimits.UNLIMITED, 
RequestSizeLimits.resolve(messageContext,
+                RequestSizeLimits.SOAP_MAX_REQUEST_SIZE,
+                RequestSizeLimits.DEFAULT_SOAP_MAX_REQUEST_SIZE));
+    }
 }
diff --git a/src/site/markdown/release-notes/2.0.2.md 
b/src/site/markdown/release-notes/2.0.2.md
index 03703de9a0..84f2e6751a 100644
--- a/src/site/markdown/release-notes/2.0.2.md
+++ b/src/site/markdown/release-notes/2.0.2.md
@@ -69,9 +69,13 @@ in `SECURITY.md`.
 - **Request bodies are bounded.** The `multipart/form-data` and
   `application/x-www-form-urlencoded` builders read the transport stream 
directly,
   so a servlet container's post-size limit never saw the body.
-  `multipartMaxRequestSize` and `multipartMaxFileSize` (100 MB) and
-  `formUrlEncodedMaxRequestSize` (2 MB) now bound them; `-1` restores the 
previous
-  unbounded behaviour, and either may be set per service. The ceilings are 
enforced
+  `multipartMaxRequestSize` and `multipartMaxFileSize` (100 MB),
+  `formUrlEncodedMaxRequestSize` (2 MB), `mtomMaxRequestSize` (100 MB, 
covering the
+  whole `multipart/related` body and so MTOM and SwA) and `soapMaxRequestSize`
+  (100 MB, a plain SOAP or POX body) now bound them; `-1` restores the previous
+  unbounded behaviour, and any may be set per service. All the stream-reading
+  builders are covered, not only the form ones: the caller picks the builder by
+  choosing the Content-Type, so bounding some of them bounds none. The 
ceilings are enforced
   against bytes actually read, so a chunked body is bounded on the same terms 
as a
   declared one. Multipart temporary files are deleted rather than accumulating.
 

Reply via email to