ghidra-0002 UNDF-1304: 28x-768x getVttAddresses set hoist (companion to ghidra-0001)

Three coupled defects in RTTIGccClassRecoverer:
1. isPossibleVttStart REBUILDS vtableAndVftableAddrs on every call (O(V) waste)
2. getVttAddresses calls it inside outer while loop (multiplies the rebuild)
3. addPointerToList uses List<Address>.contains for membership (O(V) per check)

Fix: hoist Set<Address> once, pass to isPossibleVttStart, eliminate per-call rebuild.

ghidra-0001 covers RecoveredClassHelper (MSVC + gcc) — the foundation pattern.
ghidra-0002 covers gcc-specific VTT recovery — extends coverage to Linux C++ binaries.

Together: ghidra C++ class recovery drops from seconds-to-minutes to milliseconds.
This commit is contained in:
russell@unturf.com 2026-04-25 17:49:23 -04:00
parent f0f1b4be7e
commit 63bfcebcf5
No known key found for this signature in database
6 changed files with 413 additions and 30 deletions

View file

@ -0,0 +1,96 @@
# ghidra-0002: RTTIGccClassRecoverer.getVttAddresses — O(A²·V + A·V) coupled defects
**Target:** NationalSecurityAgency/ghidra
**Severity:** HIGH
**CWE:** CWE-407 (Inefficient Algorithmic Complexity)
**MOAD:** MOAD-0001 (A Sedimentary Defect)
**File:** `Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RTTIGccClassRecoverer.java:880-925, 944-960, 988-1005`
**Language:** Java
**Status:** open
## Description
Three coupled patterns in ghidra's gcc-compiled C++ class recovery:
1. **`isPossibleVttStart(address, vtables, knownVtts)` rebuilds `vtableAndVftableAddrs` on every invocation** — calls `getListOfVtableAndVftableTops(vtables)` from scratch each time, walking all vtables to extract their addresses. O(V) wasted work per call.
2. **`getVttAddresses` calls `isPossibleVttStart` once per address-to-check inside an outer `while (keepChecking)` retry loop** — multiplies the rebuild cost across iterations.
3. **`addPointerToList` uses `List.contains` on `List<Address>` for membership** — O(V) and O(T) per check during the per-VTT pointer walk.
Total per analysis: O(outer_iters × A × V) just for the rebuilds, plus O(A × V) for the linear-scan contains. A gcc-compiled C++ binary with 1000 classes (2000 vtable+vftable addresses) and 500 candidate addresses produces ~30M ops per RecoverClassesFromRTTIScript invocation.
## Root Cause
```java
// getVttAddresses(): outer loop with per-call rebuild
while (keepChecking) {
for (Address possibleVttStart : addressesToCheck) {
if (isPossibleVttStart(possibleVttStart, vtables, vttStarts)) { // rebuilds list
vttStarts.add(possibleVttStart);
}
}
...
}
// isPossibleVttStart(): O(V) rebuild every call
private boolean isPossibleVttStart(Address address, List<Vtable> vtables, List<Address> knownVtts) {
List<Address> vtableAndVftableAddrs = getListOfVtableAndVftableTops(vtables); // <- O(V) every call
...
if (referencedAddress != null && (vtableAndVftableAddrs.contains(referencedAddress) ||
knownVtts.contains(referencedAddress))) { // O(V)+O(T) per check
return true;
}
}
// addPointerToList(): rebuilt-each-time + List.contains
List<Address> vtableAndVftableAddrs = getListOfVtableAndVftableTops(vtables); // built once but List
List<Address> vttStarts = getVttAddresses(vtts);
for (Vtt vtt : vtts) {
while (referencedAddress != null &&
(vtableAndVftableAddrs.contains(referencedAddress) || // O(V) per check
vttStarts.contains(referencedAddress))) { // O(T) per check
...
}
}
```
## Fix
1. Build `Set<Address> vtableAndVftableSet` once in `getVttAddresses` (and again in `addPointerToList`) before any inner loop.
2. Change `isPossibleVttStart` to accept the prebuilt sets as parameters (no per-call rebuild).
3. Maintain `Set<Address> vttStartSet` alongside the existing `List` so `vttStarts.add(addr)` updates both for downstream `isPossibleVttStart` calls.
```java
Set<Address> vtableAndVftableSet =
new HashSet<>(getListOfVtableAndVftableTops(vtables));
Set<Address> vttStartSet = new HashSet<>();
while (keepChecking) {
for (Address possibleVttStart : addressesToCheck) {
if (isPossibleVttStart(possibleVttStart, vtableAndVftableSet, vttStartSet)) {
vttStarts.add(possibleVttStart);
vttStartSet.add(possibleVttStart);
}
}
...
}
private boolean isPossibleVttStart(Address address, Set<Address> vtableAndVftableSet,
Set<Address> knownVttSet) throws CancelledException {
if (isSelfReferencing(address)) return true;
Address referencedAddress = getReferencedAddress(address);
return referencedAddress != null && (vtableAndVftableSet.contains(referencedAddress) ||
knownVttSet.contains(referencedAddress));
}
```
## Severity Note
Hot path on every gcc-compiled C++ binary's class recovery analysis. Bench (defects/ghidra/bench/) shows 28× speedup at C=200 A=100 and 768× at C=5000 A=2500. Reverse-engineering Chromium-class binaries (1000+ classes) sees seconds-to-minutes per RecoverClassesFromRTTIScript run today; this patch drops it to milliseconds.
Companion to ghidra-0001 (UNDF-2026-000001303) which fixes the parallel pattern in `RecoveredClassHelper`. Together, the two patches cover the major hot paths in ghidra's C++ class recovery infrastructure.
## Complexity Gate
- C=2000 classes × A=1000 candidate-addresses: fixed must complete in <1ms
- k-scaling 5×: time ratio must be <17.5×