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/08/19 08:28:40 UTC
(ws-neethi) 01/01: Put limit on reference expansions
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch coheigea/reference-expansions
in repository https://gitbox.apache.org/repos/asf/ws-neethi.git
commit 32f408ce2251e22e554441aab486c1df93811382
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Wed Aug 19 09:28:11 2026 +0100
Put limit on reference expansions
---
.../org/apache/neethi/AbstractPolicyOperator.java | 56 ++++++++++++++++++----
1 file changed, 47 insertions(+), 9 deletions(-)
diff --git a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
index f55acbb..e4e4555 100644
--- a/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
+++ b/src/main/java/org/apache/neethi/AbstractPolicyOperator.java
@@ -42,6 +42,18 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
* fast, predictable RuntimeException.
*/
private static final int MAX_ALTERNATIVES = 10_000;
+
+ /**
+ * Maximum number of PolicyReference expansions a single normalization
+ * pass may perform. The alternative-count cap alone cannot see reference
+ * re-expansion: a reference DAG in which every level holds two sibling
+ * references to the next level normalizes to a single alternative at each
+ * step while the expansion work doubles per level (2^d work from O(d)
+ * parsed elements, because the on-path cycle token is removed once each
+ * expansion completes). Budgeting expansions converts that unbounded
+ * work into a fast, predictable RuntimeException.
+ */
+ private static final int MAX_REFERENCE_EXPANSIONS = 100_000;
public AbstractPolicyOperator() {
@@ -95,7 +107,7 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
}
- result.addPolicyComponent(normalizeOperator(policy, policy, reg, deep, new HashSet<String>()));
+ result.addPolicyComponent(normalizeOperator(policy, policy, reg, deep, new NormalizeBudget()));
return result;
}
@@ -103,7 +115,7 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
PolicyOperator operator,
PolicyRegistry reg,
boolean deep,
- Set<String> resolving) {
+ NormalizeBudget budget) {
short type = operator.getType();
@@ -160,26 +172,24 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
String resolvedId = ((Policy) policyComponent).getId();
String resolvingToken = resolvedId != null && resolvedId.length() > 0
? "id:" + resolvedId : "uri:" + uri;
- if (!resolving.add(resolvingToken)) {
- throw new RuntimeException("Circular PolicyReference detected: " + resolvingToken);
- }
+ budget.enterReference(resolvingToken);
try {
All all = new All();
all.addPolicyComponents(((Policy) policyComponent).getPolicyComponents());
- childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, resolving));
+ childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, budget));
} finally {
- resolving.remove(resolvingToken);
+ budget.exitReference(resolvingToken);
}
} else if (policyComponent.getType() == Constants.TYPE_POLICY) {
All all = new All();
all.addPolicyComponents(((Policy) policyComponent).getPolicyComponents());
- childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, resolving));
+ childComponentsList.add(AbstractPolicyOperator.normalizeOperator(policy, all, reg, deep, budget));
} else {
childComponentsList.add(AbstractPolicyOperator
.normalizeOperator(policy,
- (PolicyOperator)policyComponent, reg, deep, resolving));
+ (PolicyOperator)policyComponent, reg, deep, budget));
}
}
@@ -263,6 +273,34 @@ public abstract class AbstractPolicyOperator implements PolicyOperator {
return crossProduct;
}
+ /**
+ * Per-normalization-pass accounting: the on-path cycle set plus the
+ * budget that bounds total reference-expansion work.
+ */
+ private static final class NormalizeBudget {
+ private final Set<String> resolving = new HashSet<String>();
+ private long referenceExpansions;
+
+ void enterReference(String token) {
+ if (!resolving.add(token)) {
+ throw new RuntimeException("Circular PolicyReference detected: " + token);
+ }
+ referenceExpansions++;
+ if (referenceExpansions > MAX_REFERENCE_EXPANSIONS) {
+ resolving.remove(token);
+ throw new RuntimeException(
+ "Policy normalization exceeded the maximum number of PolicyReference"
+ + " expansions (" + MAX_REFERENCE_EXPANSIONS + "). The policy may be"
+ + " crafted to cause Algorithmic Complexity DoS via reference"
+ + " re-expansion.");
+ }
+ }
+
+ void exitReference(String token) {
+ resolving.remove(token);
+ }
+ }
+
public static void checkMaximumAlternativeCount(long alternativesCount, String operation) {
if (alternativesCount > MAX_ALTERNATIVES) {
throw new RuntimeException(