java-topology/whitepaper/outreach/opencv.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.4 KiB
Raw Blame History

OpenCV — CWE-407 Disclosure Brief

Project: OpenCV Disclosure date: 2026-03-27 Severity: HIGH Speedup: 368× measured · 8.5× (opencv-0001), 3.5× (opencv-0002) per-defect scenario Status: PATCHED


Finding

Two independent defects in OpenCV use std::find on unsorted vectors for repeated set-membership checks in performance-critical graph traversal and pattern matching code. The first (opencv-0001) occurs in the TimVX backend's conflict map update, where recursive DFS multiplies a linear scan into O(C²×G) behavior. The second (opencv-0002) occurs in G-API pattern matching, where node classification calls std::find on endpoint-node vectors inside a match loop.

The Defect(s)

ID Location Pattern Complexity
opencv-0001 modules/dnn/src/op_timvx.cpp:30,869 tvUpdateConfictMap+isConflict std::find on graphConflictMap vector (G) inside recursive DFS (depth C) O(C²×G)
opencv-0002 modules/gapi/src/compiler/passes/pattern_matching.cpp:296,306 std::find on patternEndOpNodes/patternStartOpNodes (E,S) inside loop over M matches O(M×(E+S))

Complexity Proof

opencv-0001: Let C = number of conflicting layer pairs in the DFS recursion depth, G = size of graphConflictMap vector.

tvUpdateConfictMap is called recursively during TimVX subgraph construction. At each recursion level, isConflict() calls std::find on the graphConflictMap vector to check whether a layer pair is already recorded:

DFS depth C levels × O(G) find per level = O(C×G) per DFS path
With C paths of depth C: O(C²×G)

Replacing graphConflictMap with unordered_set<int> (using a combined hash of the two layer IDs) gives O(1) lookup: total O(C²). Measured speedup: 8.5×.

opencv-0002: Let M = number of candidate matches, E = size of patternEndOpNodes, S = size of patternStartOpNodes.

Pattern node classification calls std::find twice per match — once against patternEndOpNodes and once against patternStartOpNodes — to determine whether a node is a boundary node:

M matches × (O(E) + O(S)) = O(M×(E+S))

With unordered_set for both endpoint sets, each check is O(1): total O(M). Measured speedup: 3.5×.

Impact

opencv-0001 affects DNN inference deployments using the TimVX hardware accelerator backend (common on Arm NPU devices like the VeriSilicon VIP series). Larger networks with more conflicting layers trigger exponentially more conflict-map scans during model compilation.

opencv-0002 affects G-API graph compilation, which is invoked when constructing processing pipelines using the G-API framework. Pipelines with many pattern nodes and match candidates experience quadratic compilation latency.

The Fix

opencv-0001: Replace graphConflictMap (a std::vector<std::pair<int,int>>) with an std::unordered_set<int> using a bijective hash (e.g., a * MAX_LAYERS + b). Replace isConflict()'s std::find with .count().

opencv-0002: Convert patternEndOpNodes and patternStartOpNodes from std::vector to std::unordered_set before the match loop. Replace std::find(...) != end() with .count().

Patch

// opencv-0001: op_timvx.cpp
- std::vector<std::pair<int,int>> graphConflictMap;
+ std::unordered_set<int> graphConflictMap;  // key = a*MAX_LAYERS+b

- bool isConflict(int a, int b) {
-     return std::find(graphConflictMap.begin(), graphConflictMap.end(),
-                      std::make_pair(a,b)) != graphConflictMap.end();
- }
+ bool isConflict(int a, int b) {
+     return graphConflictMap.count(a * MAX_LAYERS + b) > 0;
+ }

// opencv-0002: pattern_matching.cpp
+ std::unordered_set<NodeHandle> endSet(patternEndOpNodes.begin(),
+                                        patternEndOpNodes.end());
+ std::unordered_set<NodeHandle> startSet(patternStartOpNodes.begin(),
+                                          patternStartOpNodes.begin());
  for (auto& match : matches) {
-     if (std::find(patternEndOpNodes.begin(), patternEndOpNodes.end(), node)
-         != patternEndOpNodes.end()) { ... }
+     if (endSet.count(node)) { ... }
  }

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