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.
4.4 KiB
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