You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2026/07/10 09:29:37 UTC

(ws-neethi) branch coheigea/policy-builder created (now a609146)

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

coheigea pushed a change to branch coheigea/policy-builder
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git


      at a609146  Add limits for max depth, attributes, elements in a policy

This branch includes the following new commits:

     new a609146  Add limits for max depth, attributes, elements in a policy

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(ws-neethi) 01/01: Add limits for max depth, attributes, elements in a policy

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/policy-builder
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git

commit a6091462d116e6e7ff9de776d4878c9fafa4cc89
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Fri Jul 10 10:29:01 2026 +0100

    Add limits for max depth, attributes, elements in a policy
---
 src/main/java/org/apache/neethi/PolicyBuilder.java | 123 ++++++++++++++++++---
 .../org/apache/neethi/PolicyBuilderDoSTest.java    |  80 ++++++++++++++
 2 files changed, 189 insertions(+), 14 deletions(-)

diff --git a/src/main/java/org/apache/neethi/PolicyBuilder.java b/src/main/java/org/apache/neethi/PolicyBuilder.java
index 79ee02f..c5f81d0 100644
--- a/src/main/java/org/apache/neethi/PolicyBuilder.java
+++ b/src/main/java/org/apache/neethi/PolicyBuilder.java
@@ -39,15 +39,32 @@ import org.apache.neethi.builders.AssertionBuilder;
  */
 public class PolicyBuilder {
 
+    private static final String MAX_DEPTH_PROPERTY = "org.apache.neethi.parser.maxDepth";
+    private static final String MAX_ELEMENTS_PROPERTY = "org.apache.neethi.parser.maxElements";
+    private static final String MAX_ATTRIBUTES_PROPERTY = "org.apache.neethi.parser.maxAttributes";
+
+    private static final int DEFAULT_MAX_DEPTH = 256;
+    private static final int DEFAULT_MAX_ELEMENTS = 100000;
+    private static final int DEFAULT_MAX_ATTRIBUTES = 10000;
+
     protected AssertionBuilderFactory factory;
     protected PolicyRegistry defaultPolicyRegistry;
+    private final int maxDepth;
+    private final int maxElements;
+    private final int maxAttributes;
     
     public PolicyBuilder() {
         factory = new AssertionBuilderFactoryImpl(this);
+        maxDepth = readConfiguredLimit(MAX_DEPTH_PROPERTY, DEFAULT_MAX_DEPTH);
+        maxElements = readConfiguredLimit(MAX_ELEMENTS_PROPERTY, DEFAULT_MAX_ELEMENTS);
+        maxAttributes = readConfiguredLimit(MAX_ATTRIBUTES_PROPERTY, DEFAULT_MAX_ATTRIBUTES);
     }
     
     public PolicyBuilder(AssertionBuilderFactory factory) {
         this.factory = factory;
+        maxDepth = readConfiguredLimit(MAX_DEPTH_PROPERTY, DEFAULT_MAX_DEPTH);
+        maxElements = readConfiguredLimit(MAX_ELEMENTS_PROPERTY, DEFAULT_MAX_ELEMENTS);
+        maxAttributes = readConfiguredLimit(MAX_ATTRIBUTES_PROPERTY, DEFAULT_MAX_ATTRIBUTES);
     }
     
     
@@ -108,12 +125,14 @@ public class PolicyBuilder {
     }
 
     public Policy getPolicy(Element el) {
-        return getPolicyOperator(el);
+        ParseBudgetContext context = new ParseBudgetContext(maxDepth, maxElements, maxAttributes);
+        return getPolicyOperator(el, context, 1);
     }
     
     
     public Policy getPolicy(XMLStreamReader reader) {
-        return getPolicyOperator(reader);
+        ParseBudgetContext context = new ParseBudgetContext(maxDepth, maxElements, maxAttributes);
+        return getPolicyOperator(reader, context, 1);
     }
 
     /**
@@ -124,7 +143,8 @@ public class PolicyBuilder {
      * @return a Policy object of the Policy element
      */
     public Policy getPolicy(Object element) {
-        return getPolicyOperator(element);
+        ParseBudgetContext context = new ParseBudgetContext(maxDepth, maxElements, maxAttributes);
+        return getPolicyOperator(element, context, 1);
     }
 
     /**
@@ -156,6 +176,14 @@ public class PolicyBuilder {
      * @return a PolicyReference object of the PolicyReference element
      */
     public PolicyReference getPolicyReference(Object element) {
+        ParseBudgetContext context = new ParseBudgetContext(maxDepth, maxElements, maxAttributes);
+        return getPolicyReference(element, context, 1);
+    }
+
+    private PolicyReference getPolicyReference(Object element, ParseBudgetContext context, int depth) {
+        context.checkDepth(depth);
+        context.incrementElementCount();
+
         QName qn = factory.getConverterRegistry().findQName(element);
 
         if (!Constants.isPolicyRef(qn)) {
@@ -166,37 +194,48 @@ public class PolicyBuilder {
         PolicyReference reference = new PolicyReference(this);
 
         Map<QName, String> attributes = factory.getConverterRegistry().getAttributes(element);
+        context.incrementAttributeCount(attributes.size());
 
         // setting the URI value
         reference.setURI(attributes.get(new QName("URI")));
         return reference;
     }
 
-    private Policy getPolicyOperator(Object element) {
+    private Policy getPolicyOperator(Object element, ParseBudgetContext context, int depth) {
+        context.checkDepth(depth);
+        context.incrementElementCount();
+
         QName qn = factory.getConverterRegistry().findQName(element);
         
         if (Constants.isPolicyElement(qn)) {
             String ns = qn.getNamespaceURI();
-            return (Policy) processOperationElement(element, new Policy(defaultPolicyRegistry, ns));
+            return (Policy) processOperationElement(element, new Policy(defaultPolicyRegistry, ns), context, depth);
         }
         throw new IllegalArgumentException(qn + " is not a <wsp:Policy> element."); 
     }
 
-    private ExactlyOne getExactlyOneOperator(Object element) {
-        return (ExactlyOne) processOperationElement(element, new ExactlyOne());
+    private ExactlyOne getExactlyOneOperator(Object element, ParseBudgetContext context, int depth) {
+        context.checkDepth(depth);
+        context.incrementElementCount();
+        return (ExactlyOne) processOperationElement(element, new ExactlyOne(), context, depth);
     }
 
-    private All getAllOperator(Object element) {
-        return (All) processOperationElement(element, new All());
+    private All getAllOperator(Object element, ParseBudgetContext context, int depth) {
+        context.checkDepth(depth);
+        context.incrementElementCount();
+        return (All) processOperationElement(element, new All(), context, depth);
     }
 
     private PolicyOperator processOperationElement(Object operationElement,
-                                                   PolicyOperator operator) {
+                                                   PolicyOperator operator,
+                                                   ParseBudgetContext context,
+                                                   int depth) {
 
         if (Constants.TYPE_POLICY == operator.getType()) {
             Policy policyOperator = (Policy) operator;
 
             Map<QName, String> attributes = factory.getConverterRegistry().getAttributes(operationElement);
+            context.incrementAttributeCount(attributes.size());
             
             for (Map.Entry<QName, String> ent : attributes.entrySet()) {
                 policyOperator.addAttribute(ent.getKey(), ent.getValue());
@@ -216,13 +255,13 @@ public class PolicyBuilder {
                 
             } else if (Constants.isInPolicyNS(qn)) {
                 if (Constants.ELEM_POLICY.equals(qn.getLocalPart())) {
-                    operator.addPolicyComponent(getPolicyOperator(childElement));
+                    operator.addPolicyComponent(getPolicyOperator(childElement, context, depth + 1));
                 } else if (Constants.ELEM_EXACTLYONE.equals(qn.getLocalPart())) {
-                    operator.addPolicyComponent(getExactlyOneOperator(childElement));
+                    operator.addPolicyComponent(getExactlyOneOperator(childElement, context, depth + 1));
                 } else if (Constants.ELEM_ALL.equals(qn.getLocalPart())) {
-                    operator.addPolicyComponent(getAllOperator(childElement));
+                    operator.addPolicyComponent(getAllOperator(childElement, context, depth + 1));
                 } else if (Constants.ELEM_POLICY_REF.equals(qn.getLocalPart())) {
-                    operator.addPolicyComponent(getPolicyReference(childElement));
+                    operator.addPolicyComponent(getPolicyReference(childElement, context, depth + 1));
                 } else {
                     operator.addPolicyComponent(factory.build(childElement));
                 }
@@ -232,6 +271,62 @@ public class PolicyBuilder {
         }
         return operator;
     } 
+
+    private static int readConfiguredLimit(String key, int defaultValue) {
+        String value = System.getProperty(key);
+        if (value == null || value.trim().length() == 0) {
+            return defaultValue;
+        }
+        try {
+            int parsed = Integer.parseInt(value.trim());
+            return parsed > 0 ? parsed : defaultValue;
+        } catch (NumberFormatException ex) {
+            return defaultValue;
+        }
+    }
+
+    private static final class ParseBudgetContext {
+        private final int maxDepth;
+        private final int maxElements;
+        private final int maxAttributes;
+        private int elementCount;
+        private int attributeCount;
+
+        ParseBudgetContext(int maxDepth, int maxElements, int maxAttributes) {
+            this.maxDepth = maxDepth;
+            this.maxElements = maxElements;
+            this.maxAttributes = maxAttributes;
+        }
+
+        void checkDepth(int depth) {
+            if (depth > maxDepth) {
+                throw new RuntimeException(
+                    "Policy parsing exceeded the maximum policy nesting depth ("
+                    + maxDepth + ").");
+            }
+        }
+
+        void incrementElementCount() {
+            elementCount++;
+            if (elementCount > maxElements) {
+                throw new RuntimeException(
+                    "Policy parsing exceeded the maximum number of elements ("
+                    + maxElements + ").");
+            }
+        }
+
+        void incrementAttributeCount(int delta) {
+            if (delta <= 0) {
+                return;
+            }
+            attributeCount += delta;
+            if (attributeCount > maxAttributes) {
+                throw new RuntimeException(
+                    "Policy parsing exceeded the maximum number of attributes ("
+                    + maxAttributes + ").");
+            }
+        }
+    }
     
     protected void notifyUnknownPolicyElement(Object childElement) {
         //NO-Op - subclass could log or throw exception or something
diff --git a/src/test/java/org/apache/neethi/PolicyBuilderDoSTest.java b/src/test/java/org/apache/neethi/PolicyBuilderDoSTest.java
new file mode 100644
index 0000000..df26f23
--- /dev/null
+++ b/src/test/java/org/apache/neethi/PolicyBuilderDoSTest.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.neethi;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Test;
+
+/**
+ * Reproduces parser recursion DoS where deeply nested policy operators trigger
+ * StackOverflowError during PolicyBuilder.getPolicy(InputStream).
+ */
+public class PolicyBuilderDoSTest extends PolicyTestCase {
+
+    private static final int DEPTH_WITHIN_DEFAULT_BUDGET = 64;
+    private static final int DEPTH_ABOVE_DEFAULT_BUDGET = 4096;
+
+    @Test
+    public void testDeeplyNestedPolicyIsRejectedByDepthBudget() {
+        PolicyBuilder builder = new PolicyBuilder();
+        InputStream stream = xmlStream(buildNestedAllPolicyXml(DEPTH_ABOVE_DEFAULT_BUDGET));
+
+        try {
+            builder.getPolicy(stream);
+            fail("Expected RuntimeException due to policy depth budget");
+        } catch (RuntimeException ex) {
+            assertTrue(ex.getMessage().contains("maximum policy nesting depth"));
+        }
+    }
+
+    @Test
+    public void testNestedPolicyWithinDepthBudgetParses() {
+        PolicyBuilder builder = new PolicyBuilder();
+        InputStream stream = xmlStream(buildNestedAllPolicyXml(DEPTH_WITHIN_DEFAULT_BUDGET));
+        Policy policy = builder.getPolicy(stream);
+
+        assertNotNull(policy);
+    }
+
+    private static InputStream xmlStream(String xml) {
+        return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static String buildNestedAllPolicyXml(int depth) {
+        StringBuilder xml = new StringBuilder(128 + (depth * 20));
+        xml.append("<wsp:Policy xmlns:wsp=\"http://www.w3.org/ns/ws-policy\">");
+
+        for (int i = 0; i < depth; i++) {
+            xml.append("<wsp:All>");
+        }
+
+        xml.append("<wsp:All/>");
+
+        for (int i = 0; i < depth; i++) {
+            xml.append("</wsp:All>");
+        }
+
+        xml.append("</wsp:Policy>");
+        return xml.toString();
+    }
+}