This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/policy-intersection in repository https://gitbox.apache.org/repos/asf/ws-neethi.git
commit 76f9a223e67397d4ee7aea935b2715437b7bbcf3 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Fri Jul 10 09:01:05 2026 +0100 Fixing policy intersection bug --- .../org/apache/neethi/AbstractPolicyOperator.java | 8 +- .../org/apache/neethi/util/PolicyIntersector.java | 6 +- .../apache/neethi/PolicyIntersectionDoSTest.java | 94 ++++++++++++++++++++++ 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java index 491ee27..f55acbb 100644 --- a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java +++ b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java @@ -263,12 +263,16 @@ public abstract class AbstractPolicyOperator implements PolicyOperator { return crossProduct; } - private static void checkAlternativeBudget(long alternativesCount) { + public static void checkMaximumAlternativeCount(long alternativesCount, String operation) { if (alternativesCount > MAX_ALTERNATIVES) { throw new RuntimeException( - "Policy normalization exceeded the maximum number of alternatives (" + "Policy " + operation + " exceeded the maximum number of alternatives (" + MAX_ALTERNATIVES + "). The policy may be crafted to cause " + "Algorithmic Complexity DoS via exponential expansion."); } } + + private static void checkAlternativeBudget(long alternativesCount) { + checkMaximumAlternativeCount(alternativesCount, "normalization"); + } } diff --git a/src/main/java/org/apache/neethi/util/PolicyIntersector.java b/src/main/java/org/apache/neethi/util/PolicyIntersector.java index 67195ce..926aa86 100644 --- a/src/main/java/org/apache/neethi/util/PolicyIntersector.java +++ b/src/main/java/org/apache/neethi/util/PolicyIntersector.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import org.apache.neethi.AbstractPolicyOperator; import org.apache.neethi.All; import org.apache.neethi.Assertion; import org.apache.neethi.ExactlyOne; @@ -185,9 +186,6 @@ public class PolicyIntersector { public Policy intersect(Policy p1, Policy p2, boolean allowDups) { Policy compatible = new Policy(p1.getPolicyRegistry(), p1.getNamespace()); ExactlyOne eo = new ExactlyOne(compatible); - if (!compatiblePolicies(p1, p2)) { - return compatible; - } Iterator<List<Assertion>> i1 = p1.getAlternatives(); while (i1.hasNext()) { List<Assertion> alt1 = i1.next(); @@ -196,6 +194,8 @@ public class PolicyIntersector { List<Assertion> alt2 = i2.next(); All all = createCompatibleAlternatives(alt1, alt2, !allowDups); if (all != null) { + long nextSize = (long)eo.getPolicyComponents().size() + 1; + AbstractPolicyOperator.checkMaximumAlternativeCount(nextSize, "intersection"); eo.addPolicyComponent(all); } } diff --git a/src/test/java/org/apache/neethi/PolicyIntersectionDoSTest.java b/src/test/java/org/apache/neethi/PolicyIntersectionDoSTest.java new file mode 100644 index 0000000..7469496 --- /dev/null +++ b/src/test/java/org/apache/neethi/PolicyIntersectionDoSTest.java @@ -0,0 +1,94 @@ +/** + * 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 javax.xml.namespace.QName; + +import org.apache.neethi.builders.PrimitiveAssertion; +import org.junit.Test; + +public class PolicyIntersectionDoSTest extends PolicyTestCase { + + private static final int ALTERNATIVES_PER_POLICY = 101; + private static final int SAFE_ALTERNATIVES_PER_POLICY = 100; + + @Test + public void testIntersectionRejectsCartesianProductBeyondAlternativeBudget() { + Policy left = buildPolicyWithEmptyAlternatives(ALTERNATIVES_PER_POLICY); + Policy right = buildPolicyWithEmptyAlternatives(ALTERNATIVES_PER_POLICY); + + try { + left.intersect(right, true); + fail("Expected RuntimeException due to intersection alternative limit"); + } catch (RuntimeException ex) { + assertTrue(ex.getMessage().contains("intersection")); + assertTrue(ex.getMessage().contains("maximum number of alternatives")); + } + } + + @Test + public void testIntersectionAllowsCartesianProductAtAlternativeBudget() { + Policy left = buildPolicyWithEmptyAlternatives(SAFE_ALTERNATIVES_PER_POLICY); + Policy right = buildPolicyWithEmptyAlternatives(SAFE_ALTERNATIVES_PER_POLICY); + + Policy intersection = left.intersect(right, true); + + assertNotNull(intersection); + assertEquals( + SAFE_ALTERNATIVES_PER_POLICY * SAFE_ALTERNATIVES_PER_POLICY, + ((ExactlyOne)intersection.getFirstPolicyComponent()).getPolicyComponents().size()); + } + + @Test + public void testIntersectionWithNoCompatibleAlternativesReturnsEmptyPolicy() { + Policy left = buildSingleAssertionPolicy("left"); + Policy right = buildSingleAssertionPolicy("right"); + + Policy intersection = left.intersect(right, true); + + assertNotNull(intersection); + assertTrue(intersection.getFirstPolicyComponent() instanceof ExactlyOne); + assertTrue(((ExactlyOne)intersection.getFirstPolicyComponent()).getPolicyComponents().isEmpty()); + } + + private static Policy buildPolicyWithEmptyAlternatives(int alternatives) { + Policy policy = new Policy(); + ExactlyOne exactlyOne = new ExactlyOne(); + + for (int i = 0; i < alternatives; i++) { + exactlyOne.addPolicyComponent(new All()); + } + + policy.addPolicyComponent(exactlyOne); + return policy; + } + + private static Policy buildSingleAssertionPolicy(String localName) { + Policy policy = new Policy(); + ExactlyOne exactlyOne = new ExactlyOne(); + All all = new All(); + + all.addPolicyComponent(new PrimitiveAssertion(new QName("urn:test", localName))); + exactlyOne.addPolicyComponent(all); + policy.addPolicyComponent(exactlyOne); + + return policy; + } +} \ No newline at end of file
