#!/usr/bin/env python3 """Estimate capacity for copies whose destination is a GLOBAL, not a stack buffer. The capacity pass could only decide copies into stack frames (`sub sp,#F`, dst `sp+k`). That left 48 SYS_DRV sites undecided, their destinations being globals or caller pointers. For a global destination the extent can be estimated the same way GCP's `cmd02` was read by hand: dst = 0x407000, length 0x1926 constant, and 0x408926 is itself a referenced object -> the object at 0x407000 is exactly 0x1926 bytes, and the copy exactly fits So: collect every literal-pool pointer the module references, sort them, and take a destination global's capacity as the distance to the next referenced address above it. That is an ESTIMATE -- adjacent objects may be unreferenced, making it conservative (too small), which biases toward over-reporting rather than silence. HONEST STATUS: this tool has a NEGATIVE control (GCP `cmd02` must come out safe: 0x1926 into 0x1926) but NO positive control -- no known bug in this corpus copies into a global with an over-long length. Per this tree's rule, its output is therefore a TRIAGE LIST, not a verdict, and every candidate must be read. A zero result here is not evidence. Usage: ./ta_globalcap_audit.py --selftest ./ta_globalcap_audit.py --module sys_drv|asd| """ from __future__ import annotations import argparse import re import struct import ta_capacity_audit as CAP def referenced_globals(b: bytes, base: int, fns: list) -> list[int]: """Every literal-pool word that looks like an in-image pointer.""" out = set() lo, hi = base, base + len(b) for off in range(0, len(b) - 4, 4): v = struct.unpack_from(" len(b): continue ins = list(CAP.THUMB.disasm(b[o:o + sz], ea)) for n, i in enumerate(ins): if not (i.mnemonic in ("blx", "bl") and (i.op_str or "").startswith("#")): continue if int(i.op_str[1:], 16) not in hp: continue kind, k = dest_global(ins, 0, n) if kind != "literal": continue g = literal_value(b, base, ins, k) if g is None or not (base <= g < base + len(b)): continue nxt = next((x for x in globs if x > g), None) if nxt is None: continue cap = nxt - g mx, how = CAP.max_len(ins, 0, n, "r2") if mx is None: continue if mx > cap: rows.append((i.address, ea, g, cap, mx, how)) return rows def main(): ap = argparse.ArgumentParser() ap.add_argument("--selftest", action="store_true") ap.add_argument("--module", default="sys_drv") a = ap.parse_args() if a.selftest: # NEGATIVE control: GCP cmd02 copies 0x1926 into the object at 0x407000, which is # exactly 0x1926 bytes (0x408926 is the next referenced object). It must not appear. rows = analyse("gcp") bad = [r for r in rows if r[0] == 0x404C98] print("SELFTEST (global-destination capacity)") print(" NEGATIVE gcp cmd02 0x404c98 not reported: %s" % ("yes" if not bad else "NO -> %s" % (bad,))) print(" POSITIVE control: NONE AVAILABLE -- no known bug in this corpus copies") print(" over-long into a global, so this tool is a TRIAGE LIST, not a verdict.") print("\nverdict: usable for triage only (negative control holds: %s)" % ("yes" if not bad else "no")) return 0 if not bad else 1 rows = analyse(a.module) print("=== %s: %d global-destination site(s) where max length exceeds the estimated extent" % (a.module, len(rows))) for site, fn, g, cap, mx, how in rows[:20]: print(" copy @%#x (fn %#x) dst global %#x extent ~%d, max %d (%s)" % (site, fn, g, cap, mx, how)) return 0 if __name__ == "__main__": raise SystemExit(main())