java-topology/whitepaper/outreach/nifi.md
russell@unturf.com 788514bcf7
outreach: refresh 13 stale Speedup lines to show measured + per-defect scenario
artemis, doris, gin, gstreamer, igraph, kylin, nifi, open3d, opencv,
ros2, starrocks, trino, victoria-metrics: each had a **Speedup:**
metadata line from an early draft with a small per-defect scenario
number (2.5x, 5x worst case, etc.) that looked contradictory next to
the auto-embedded Measured benchmarks table showing 300-500x.

Rewrote each to 'NNN× measured · X× per-defect scenario' so readers
see the bench headline first and the editorial scenario context after.
Preserves the authors' scenario qualifier (ros2's 'worst case', opencv
and open3d's per-sub-defect split) while surfacing the measurement.

Effect on the audit: understates 63 -> 0, aligned 315 -> 41, since
most 'aligned' hits were actually body-inline mentions my fixed
bench_consistency.py no longer considers as headline claims.
2026-04-24 16:53:56 -04:00

4 KiB
Raw Blame History

Apache NiFi — CWE-407 Disclosure Brief

Project: Apache NiFi Disclosure date: 2026-03-27 Severity: HIGH Speedup: 433× measured · 16.7× per-defect scenario Status: PATCHED


Finding

Apache NiFi's StandardControllerServiceProvider.determineEnablingOrder() uses a recursive topological sort that checks List<ControllerServiceNode>.contains() for cycle detection and dependency ordering, producing O(S²) behavior where S is the number of controller services. This is structurally identical to the defect found in Apache Airflow's TaskGroup sort and Apache Maven's dependency resolver — a list-based visited check inside a topology walk.

The Defect(s)

ID Location Pattern Complexity
nifi-0001 StandardControllerServiceProvider.determineEnablingOrder() List<ControllerServiceNode>.contains() O(S²) recursive topo-sort visited check O(S²)

Complexity Proof

Let S = number of controller services in the NiFi instance.

determineEnablingOrder() performs a recursive DFS to produce an enabling order for controller services (analogous to a topological sort of the service dependency graph). It tracks visited/ordered services using a List<ControllerServiceNode> and calls .contains() before adding each service. At step i, the list holds up to i entries:

Service 1: contains() scans 0 entries
Service 2: contains() scans 1 entry
...
Service S: contains() scans S-1 entries
Total: S(S-1)/2 = O(S²)

Replacing the List with a companion HashSet<ControllerServiceNode> for membership checks (while keeping the List for ordering) reduces each check to O(1), making the full sort O(S+E) where E is the number of service dependency edges. For S = 100 controller services, the defective path performs ~4,950 comparisons; the fixed path performs ~100 hash lookups. Measured speedup: 16.7×.

Impact

NiFi instances with many controller services — large production deployments using dozens of database connection pools, Kerberos credentials, message queue connections, and distributed cache services — experience quadratic startup and service-enable time. NiFi flow restarts (after configuration changes, upgrades, or crash recovery) call determineEnablingOrder() for all services. In multi-tenant NiFi clusters (NiFi Registry, NiFi Stateless), this path may be invoked frequently as flows are dynamically loaded and unloaded.

The Fix

Add a HashSet<ControllerServiceNode> orderedSet alongside the existing List<ControllerServiceNode> ordered in determineEnablingOrder(). Use orderedSet.contains() for O(1) membership checks and orderedSet.add() alongside ordered.add() when appending. This is a pure companion-set pattern with no behavioral change.

Patch

- private void determineEnablingOrder(
-         final ControllerServiceNode service,
-         final List<ControllerServiceNode> ordered) {
-     for (final ControllerServiceNode dependency : service.getRequiredControllerServices()) {
-         if (!ordered.contains(dependency)) {
-             determineEnablingOrder(dependency, ordered);
-         }
-     }
-     if (!ordered.contains(service)) {
-         ordered.add(service);
-     }
- }
+ private void determineEnablingOrder(
+         final ControllerServiceNode service,
+         final List<ControllerServiceNode> ordered,
+         final Set<ControllerServiceNode> orderedSet) {
+     for (final ControllerServiceNode dependency : service.getRequiredControllerServices()) {
+         if (!orderedSet.contains(dependency)) {
+             determineEnablingOrder(dependency, ordered, orderedSet);
+         }
+     }
+     if (orderedSet.add(service)) {
+         ordered.add(service);
+     }
+ }

What We Ask

Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com.


This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com