java-topology/defects/nifi/patch/nifi-0001-parameter-context-cycle-stack-hashset.md
russell@unturf.com 068ebbd29f cpp-systems: tor CLEAN.md updated to note existing patches tor-0001/0002/0003
Scanned bitcoin/dragonfly/tor/transmission/nmap/ceph/allegro5 for additional
CWE-407 defects. All repos found CLEAN beyond previously recorded patches.
Updated tor/CLEAN.md to correctly reference existing tor-0001 through tor-0003.
2026-03-29 19:54:59 -04:00

76 lines
2.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000472
# UNDF: (pending)
# nifi-0001: StandardParameterContext.verifyNoCycles — O(D²) Stack.contains on DFS path
## CWE-407 — Algorithmic Complexity
| Field | Value |
|-------|-------|
| ID | nifi-0001 |
| Severity | MEDIUM |
| Ecosystem | nifi |
| Package | nifi-framework-components |
| File | `nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/parameter/StandardParameterContext.java` |
| Lines | 532556 |
| Complexity | O(D²) |
| Hot path | Called on every ParameterContext inheritance update |
## Defect
`verifyNoCycles` uses a `Stack<String>` (which extends `Vector<String>`) to track the DFS
path and calls `traversedIds.contains(id)` to detect back-edges. `Stack.contains()` is an
O(D) linear scan, called once per node at each recursion level, giving O(D²) total where D is
the depth of the ParameterContext inheritance chain.
```java
private void verifyNoCycles(final Stack<String> traversedIds,
final List<ParameterContext> parameterContexts) {
for (final ParameterContext parameterContext : parameterContexts) {
final String id = parameterContext.getIdentifier();
if (traversedIds.contains(id)) { // O(D) linear scan
throw new IllegalStateException(...);
}
traversedIds.push(id);
verifyNoCycles(traversedIds, parameterContext.getInheritedParameterContexts());
traversedIds.pop();
}
}
```
## Fix
Replace `Stack<String>` with a `HashSet<String>` for the visited-in-current-path set. Since
`Stack` is used as a DFS path tracker, we need O(1) membership checks. Use a `Set<String>` for
the cycle check and a separate `Deque<String>` only if ordering is needed (it is not here).
```java
private void verifyNoCycles(final List<ParameterContext> parameterContexts) {
final Set<String> traversedIds = new HashSet<>();
traversedIds.add(id);
verifyNoCycles(traversedIds, parameterContexts);
}
private void verifyNoCycles(final Set<String> traversedIds,
final List<ParameterContext> parameterContexts) {
for (final ParameterContext parameterContext : parameterContexts) {
final String id = parameterContext.getIdentifier();
if (traversedIds.contains(id)) { // O(1) hash lookup
throw new IllegalStateException(
String.format("Circular references in Parameter Contexts not allowed. "
+ "[%s] was detected in a cycle.", parameterContext.getName()));
}
traversedIds.add(id);
verifyNoCycles(traversedIds, parameterContext.getInheritedParameterContexts());
traversedIds.remove(id);
}
}
```
## Speedup
| D (chain depth) | Before (ops) | After (ops) | Speedup |
|-----------------|-------------|-------------|---------|
| 10 | 100 | 10 | 10× |
| 50 | 2,500 | 50 | 50× |
| 100 | 10,000 | 100 | 100× |
| 500 | 250,000 | 500 | 500× |