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

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

commit d21f0a79a365b2c965016d7761bd9b5343439b5e
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Aug 19 09:17:20 2026 +0100

    Fixing recursion case for nested policy
---
 src/main/java/org/apache/neethi/PolicyBuilder.java | 51 ++++++++++++--
 .../neethi/PolicyBuilderNestedPolicyDoSTest.java   | 81 ++++++++++++++++++++++
 2 files changed, 127 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/neethi/PolicyBuilder.java 
b/src/main/java/org/apache/neethi/PolicyBuilder.java
index 7941c7c..e208fc9 100644
--- a/src/main/java/org/apache/neethi/PolicyBuilder.java
+++ b/src/main/java/org/apache/neethi/PolicyBuilder.java
@@ -52,6 +52,18 @@ public class PolicyBuilder {
     private final int maxDepth;
     private final int maxElements;
     private final int maxAttributes;
+
+    /**
+     * Budget of the policy parse in progress on the current thread. Assertion
+     * builders may re-enter the public getPolicy overloads for nested
+     * wsp:Policy elements (see XMLPrimitiveAssertionBuilder); such re-entrant
+     * parses must inherit the ambient budget rather than mint a fresh one,
+     * otherwise every assertion/wsp:Policy nesting layer resets the
+     * maxDepth/maxElements/maxAttributes budgets and the mutual recursion is
+     * unbounded.
+     */
+    private static final ThreadLocal<ParseBudgetContext> CURRENT_BUDGET =
+        new ThreadLocal<ParseBudgetContext>();
     
     public PolicyBuilder() {
         factory = new AssertionBuilderFactoryImpl(this);
@@ -125,14 +137,12 @@ public class PolicyBuilder {
     }
 
     public Policy getPolicy(Element el) {
-        ParseBudgetContext context = new ParseBudgetContext(maxDepth, 
maxElements, maxAttributes);
-        return getPolicyOperator(el, context, 1);
+        return parseWithBudget(el);
     }
     
     
     public Policy getPolicy(XMLStreamReader reader) {
-        ParseBudgetContext context = new ParseBudgetContext(maxDepth, 
maxElements, maxAttributes);
-        return getPolicyOperator(reader, context, 1);
+        return parseWithBudget(reader);
     }
 
     /**
@@ -143,8 +153,24 @@ public class PolicyBuilder {
      * @return a Policy object of the Policy element
      */
     public Policy getPolicy(Object element) {
+        return parseWithBudget(element);
+    }
+
+    private Policy parseWithBudget(Object element) {
+        ParseBudgetContext ambient = CURRENT_BUDGET.get();
+        if (ambient != null) {
+            // Re-entrant parse: an assertion builder called back into the
+            // engine for a nested wsp:Policy element. Continue the enclosing
+            // parse's budget and depth instead of starting a fresh one.
+            return getPolicyOperator(element, ambient, 
ambient.getReentryDepth());
+        }
         ParseBudgetContext context = new ParseBudgetContext(maxDepth, 
maxElements, maxAttributes);
-        return getPolicyOperator(element, context, 1);
+        CURRENT_BUDGET.set(context);
+        try {
+            return getPolicyOperator(element, context, 1);
+        } finally {
+            CURRENT_BUDGET.remove();
+        }
     }
 
     /**
@@ -263,9 +289,13 @@ public class PolicyBuilder {
                 } else if 
(Constants.ELEM_POLICY_REF.equals(qn.getLocalPart())) {
                     
operator.addPolicyComponent(getPolicyReference(childElement, context, depth + 
1));
                 } else {
+                    // a nested wsp:Policy inside this assertion re-enters
+                    // getPolicy below the assertion element itself
+                    context.recordReentryDepth(depth + 2);
                     operator.addPolicyComponent(factory.build(childElement));
                 }
             } else {
+                context.recordReentryDepth(depth + 2);
                 operator.addPolicyComponent(factory.build(childElement));
             }
         }
@@ -291,6 +321,9 @@ public class PolicyBuilder {
         private final int maxAttributes;
         private int elementCount;
         private int attributeCount;
+        // depth at which a re-entrant getPolicy call (from an assertion
+        // builder) resumes; recorded just before each factory.build call
+        private int reentryDepth = 2;
 
         ParseBudgetContext(int maxDepth, int maxElements, int maxAttributes) {
             this.maxDepth = maxDepth;
@@ -298,6 +331,14 @@ public class PolicyBuilder {
             this.maxAttributes = maxAttributes;
         }
 
+        void recordReentryDepth(int depth) {
+            reentryDepth = depth;
+        }
+
+        int getReentryDepth() {
+            return reentryDepth;
+        }
+
         void checkDepth(int depth) {
             if (depth > maxDepth) {
                 throw new RuntimeException(
diff --git 
a/src/test/java/org/apache/neethi/PolicyBuilderNestedPolicyDoSTest.java 
b/src/test/java/org/apache/neethi/PolicyBuilderNestedPolicyDoSTest.java
new file mode 100644
index 0000000..9fe09e3
--- /dev/null
+++ b/src/test/java/org/apache/neethi/PolicyBuilderNestedPolicyDoSTest.java
@@ -0,0 +1,81 @@
+/**
+ * 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.nio.charset.StandardCharsets;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Element;
+
+import org.junit.Test;
+
+/**
+ * Reproduces unbounded recursion when a primitive assertion contains only a
+ * nested policy and the assertion builder re-enters the public policy parser.
+ */
+public class PolicyBuilderNestedPolicyDoSTest extends PolicyTestCase {
+
+    private static final int SANDWICH_LAYERS = 100000;
+
+    @Test
+    public void testNestedPolicyAssertionsAreRejectedByDepthBudget() {
+        PolicyBuilder builder = new PolicyBuilder();
+
+        try {
+            
builder.getPolicy(parseElement(buildPolicyAssertionSandwichXml(SANDWICH_LAYERS)));
+            fail("Expected RuntimeException due to policy depth budget");
+        } catch (RuntimeException ex) {
+            assertTrue(ex.getMessage().contains("maximum policy nesting 
depth"));
+        }
+    }
+
+    private static Element parseElement(String xml) {
+        try {
+            DocumentBuilderFactory factory = 
DocumentBuilderFactory.newInstance();
+            factory.setNamespaceAware(true);
+            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+            
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, 
true);
+            return factory.newDocumentBuilder().parse(
+                new 
ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))).getDocumentElement();
+        } catch (Exception ex) {
+            throw new RuntimeException("Could not parse test policy.", ex);
+        }
+    }
+
+    private static String buildPolicyAssertionSandwichXml(int layers) {
+        StringBuilder xml = new StringBuilder(128 + (layers * 40));
+        xml.append("<wsp:Policy xmlns:wsp=\"http://www.w3.org/ns/ws-policy\"; ")
+            .append("xmlns:p=\"urn:neethi-test\">");
+
+        for (int i = 0; i < layers; i++) {
+            xml.append("<p:Assertion><wsp:Policy>");
+        }
+        xml.append("<p:Assertion/>");
+        for (int i = 0; i < layers; i++) {
+            xml.append("</wsp:Policy></p:Assertion>");
+        }
+
+        xml.append("</wsp:Policy>");
+        return xml.toString();
+    }
+}
\ No newline at end of file

Reply via email to