wave20: ghidra-0001 UNDF-1303 (6.5x-27x RecoveredClassHelper) + 9 clean-scan additions

Flagship: ghidra RecoveredClassHelper.addVftableReferencesToFunctionMapping
+ addFunctionsToClassMapping. Each insert does List.contains + ArrayList
copy on every add, giving O(F*R^2) per binary. Map<F, LinkedHashSet<T>>
rewrite gives O(F*R) and 27x speedup at F=2k R=500.

Reverse-engineering large C++ binaries (1000+ classes, 10k+ vtable refs)
sees seconds-to-minutes per RecoverClassesFromRTTIScript run today.

Wave 20 honor roll: cri-o, runc, youki, quickjs, hermes, ripgrep, radare2,
monero, mitmproxy. Cumulative: 115 projects.
This commit is contained in:
russell@unturf.com 2026-04-25 15:49:17 -04:00
parent 3a9c7a3e75
commit dce02d80df
No known key found for this signature in database
7 changed files with 465 additions and 0 deletions

View file

@ -0,0 +1,75 @@
# ghidra-0001: RecoveredClassHelper — O(F×R²) List.contains + ArrayList copy on every add
**Target:** NationalSecurityAgency/ghidra
**Severity:** HIGH
**CWE:** CWE-407 (Inefficient Algorithmic Complexity)
**MOAD:** MOAD-0001 (A Sedimentary Defect)
**File:** `Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassHelper.java:218-237, 256-275`
**Language:** Java
**Status:** open
## Description
`RecoveredClassHelper` builds two maps during ghidra's C++ class recovery analysis:
- `functionToVftableRefsMap: Map<Function, List<Address>>`
- `functionToClassesMap: Map<Function, List<RecoveredClass>>`
Each insert path checks for membership via `List.contains` (O(R) linear scan), then copies the existing `ArrayList` into a new `ArrayList` (O(R) defensive copy), then `Map.replace`s the entry. Per-function cost: **O(R²)** for R items added. Per-binary cost: **O(F × R²)** where F = function count, R = references-per-function.
Real-world scale: large reverse-engineered C++ binaries (malware analysis, OS kernels, AAA games) routinely have 1000+ classes and 10k+ vftable references. Class recovery analysis scripts run into seconds-to-minutes per binary today.
## Root Cause
```java
// RecoveredClassHelper.java:218 — addVftableReferencesToFunctionMapping
for (Address vtableReference : keySet) {
if (functionToVftableRefsMap.containsKey(function)) {
List<Address> referenceList = functionToVftableRefsMap.get(function);
if (!referenceList.contains(vtableReference)) { // O(R) per call
List<Address> newList = new ArrayList<>(referenceList); // O(R) copy per add
newList.add(vtableReference);
functionToVftableRefsMap.replace(function, referenceList, newList);
}
} else {
List<Address> newList = new ArrayList<>();
newList.add(vtableReference);
functionToVftableRefsMap.put(function, newList);
}
}
// RecoveredClassHelper.java:256 — addFunctionsToClassMapping (same pattern)
```
Each add does both a linear scan and a list copy. Across F functions × R references each: **O(F × R²)**.
## Fix
Replace `Map<Function, List<T>>` with `Map<Function, LinkedHashSet<T>>`. `LinkedHashSet` preserves insertion order (so callers iterating in the order references were discovered see the same order) AND gives O(1) `add` + `contains`. The defensive ArrayList copy on every add disappears entirely.
```java
private final Map<Function, LinkedHashSet<Address>> functionToVftableRefSetMap = new HashMap<>();
private final Map<Function, LinkedHashSet<RecoveredClass>> functionToClassesSetMap = new HashMap<>();
// Insert path becomes:
functionToVftableRefSetMap
.computeIfAbsent(function, k -> new LinkedHashSet<>())
.add(vtableReference);
```
Public API readers wrap the set as `List` for downstream-script compatibility:
```java
public List<Address> getVftableReferences(Function function) {
LinkedHashSet<Address> set = functionToVftableRefSetMap.get(function);
return set == null ? null : new ArrayList<>(set);
}
```
## Severity Note
Hot path on every C++ class recovery script invocation. Bench (defects/ghidra/bench/) shows 6.5× speedup at F=100 R=50, scaling to 27× at F=2000 R=500. Reverse engineering analysts running ghidra on large binaries see this latency as part of "RecoverClassesFromRTTIScript" wall-clock time.
## Complexity Gate
- F=2000 functions × R=500 refs: fixed must complete in <250ms
- k-scaling 5×: time ratio must be <17.5×