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:
parent
f0f1b4be7e
commit
63bfcebcf5
6 changed files with 413 additions and 30 deletions
96
docs/tickets/ghidra-0002-getvttaddresses-set-hoist.md
Normal file
96
docs/tickets/ghidra-0002-getvttaddresses-set-hoist.md
Normal 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×
|
||||
Loading…
Add table
Add a link
Reference in a new issue