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

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

commit d356edef0df1a94cc5110644bda73c8d08094792
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Jul 9 17:06:43 2026 +0100

    normalization alternative limit can be bypassed through ExactlyOne 
aggregation
---
 .../org/apache/neethi/AbstractPolicyOperator.java  | 25 +++++++---
 .../apache/neethi/PolicyNormalizationDoSTest.java  | 53 ++++++++++++++++++++++
 2 files changed, 71 insertions(+), 7 deletions(-)

diff --git a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java 
b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
index b4f920c..491ee27 100644
--- a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
+++ b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
@@ -192,9 +192,13 @@ public abstract class AbstractPolicyOperator implements 
PolicyOperator {
         ExactlyOne exactlyOne = new ExactlyOne();
         
         if (componentType == Constants.TYPE_EXACTLYONE) {            
+            long alternativesSoFar = 0;
             for (PolicyComponent comp : normalizedInnerComponets) {
                 ExactlyOne innerExactlyOne = (ExactlyOne)comp;
+                int alternativesToAdd = 
innerExactlyOne.getPolicyComponents().size();
+                checkAlternativeBudget(alternativesSoFar + alternativesToAdd);
                 
exactlyOne.addPolicyComponents(innerExactlyOne.getPolicyComponents());
+                alternativesSoFar += alternativesToAdd;
             }
             
         } else if ((componentType == Constants.TYPE_POLICY) || (componentType 
== Constants.TYPE_ALL)) {
@@ -228,6 +232,8 @@ public abstract class AbstractPolicyOperator implements 
PolicyOperator {
                 exactlyOne = (ExactlyOne) normalizedInnerComponets.get(0);
             }
         }
+
+        checkAlternativeBudget(exactlyOne.getPolicyComponents().size());
         
         return exactlyOne;
     }
@@ -243,21 +249,26 @@ public abstract class AbstractPolicyOperator implements 
PolicyOperator {
             currentAll1 = (All)pc;
 
             for (PolicyComponent pc2 : exactlyOne2.getPolicyComponents()) {
+                long nextSize = 
(long)crossProduct.getPolicyComponents().size() + 1;
+                checkAlternativeBudget(nextSize);
+
                 currentAll2 = (All)pc2;
                 crossProductAll = new All();
                 
crossProductAll.addPolicyComponents(currentAll1.getPolicyComponents());
                 
crossProductAll.addPolicyComponents(currentAll2.getPolicyComponents());
                 crossProduct.addPolicyComponent(crossProductAll);
-
-                if (crossProduct.getPolicyComponents().size() > 
MAX_ALTERNATIVES) {
-                    throw new RuntimeException(
-                        "Policy normalization exceeded the maximum number of 
alternatives ("
-                        + MAX_ALTERNATIVES + "). The policy may be crafted to 
cause "
-                        + "Algorithmic Complexity DoS via exponential 
cross-product expansion.");
-                }
             }
         }
 
         return crossProduct;
     }
+
+    private static void checkAlternativeBudget(long alternativesCount) {
+        if (alternativesCount > MAX_ALTERNATIVES) {
+            throw new RuntimeException(
+                "Policy normalization exceeded the maximum number of 
alternatives ("
+                + MAX_ALTERNATIVES + "). The policy may be crafted to cause "
+                + "Algorithmic Complexity DoS via exponential expansion.");
+        }
+    }
 }
diff --git a/src/test/java/org/apache/neethi/PolicyNormalizationDoSTest.java 
b/src/test/java/org/apache/neethi/PolicyNormalizationDoSTest.java
index 195f54e..dfbf351 100644
--- a/src/test/java/org/apache/neethi/PolicyNormalizationDoSTest.java
+++ b/src/test/java/org/apache/neethi/PolicyNormalizationDoSTest.java
@@ -77,6 +77,29 @@ public class PolicyNormalizationDoSTest extends 
PolicyTestCase {
         policy.normalize(registry, true);
     }
 
+    /**
+     * Asserts that the alternative budget is enforced globally even when the 
final
+     * count is reached through ExactlyOne aggregation, not cross-product 
generation.
+     *
+     * <pre>
+     * Policy
+     *   ExactlyOne
+     *     All -> (13 binary ExactlyOne nodes) => 8192 alternatives
+     *     All -> (13 binary ExactlyOne nodes) => 8192 alternatives
+     * </pre>
+     */
+    @Test
+    public void testExactlyOneAggregationEnforcesGlobalAlternativeLimit() {
+        Policy policy = buildExactlyOneAggregationBypassPolicy(2, 13);
+
+        try {
+            policy.normalize(registry, true);
+            fail("Expected RuntimeException due to global alternative limit");
+        } catch (RuntimeException ex) {
+            assertTrue(ex.getMessage().contains("maximum number of 
alternatives"));
+        }
+    }
+
     // -----------------------------------------------------------------------
     // Helpers
     // -----------------------------------------------------------------------
@@ -113,4 +136,34 @@ public class PolicyNormalizationDoSTest extends 
PolicyTestCase {
         return policy;
     }
 
+    private static Policy buildExactlyOneAggregationBypassPolicy(int 
branchCount, int branchDepth) {
+        Policy policy = new Policy();
+        ExactlyOne outerExactlyOne = new ExactlyOne();
+
+        for (int branch = 0; branch < branchCount; branch++) {
+            All outerBranch = new All();
+
+            for (int depth = 0; depth < branchDepth; depth++) {
+                ExactlyOne binaryChoice = new ExactlyOne();
+
+                All branchA = new All();
+                branchA.addPolicyComponent(
+                    new PrimitiveAssertion(new QName("urn:test", "branch" + 
branch + "a" + depth)));
+
+                All branchB = new All();
+                branchB.addPolicyComponent(
+                    new PrimitiveAssertion(new QName("urn:test", "branch" + 
branch + "b" + depth)));
+
+                binaryChoice.addPolicyComponent(branchA);
+                binaryChoice.addPolicyComponent(branchB);
+                outerBranch.addPolicyComponent(binaryChoice);
+            }
+
+            outerExactlyOne.addPolicyComponent(outerBranch);
+        }
+
+        policy.addPolicyComponent(outerExactlyOne);
+        return policy;
+    }
+
 }

Reply via email to