diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 09:13:00 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 09:13:00 -0400 |
| commit | 1e701396b1b6d204dfd8e5a8ddcd3007038029ac (patch) | |
| tree | 8dde895cf9d3e66ce912d01cb1a72ff674dc850f /debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md | |
| parent | ab48e44832db55fdedadcf44f55fa9904ca36f2f (diff) | |
| download | open-iov-1e701396b1b6d204dfd8e5a8ddcd3007038029ac.tar.gz open-iov-1e701396b1b6d204dfd8e5a8ddcd3007038029ac.zip | |
debug: green-eye wave 2 - injection SDK, Frida-like calls, Ghidra control bridge, long-term docs
Adds to debug/risc-v-gsp-rm-vm/:
- Session.call / run_until: Frida-like remote function calls and run-to-cursor,
with register restoration; verified live (rpc_ping -> 0xaffe).
- symbol+offset target resolution (gsp_ctrl+0x10) across all APIs and the REPL.
- Injection SDK (sdk/) and 'greeneye inject': position-independent RV64 service
blobs (verified zero relocations), loaded into RAM, registered in the demo
handler table, and invoked either directly or through the firmware mailbox
dispatcher. Demo ABI extended: mailbox 16..23 selects slots, arg0 carries the
service command, arg2 added (struct now 0x190; schema updated).
- Ghidra control bridge (greeneye ghidra + GreenEyeControl.java): run-until-here,
breakpoints, memory/register access, remote calls, hit export; verified
end-to-end with Ghidra headless (run_until reached rpc_sum from a GhidraScript).
- MarkGreenEyeHits.java fixed for the Ghidra 12 createBookmark API; verified
headless together with ExportForGreenEye.java.
- Analyzer renders a QEMU first-fault table for probe sessions (fault count,
epc with firmware vaddr translation, access address/size, reason).
- Tests now 35 green, including injection, Ghidra bridge, remote call, run-until
and REPL command smoke tests.
- Documentation: docs/11-16 (GSP RM service developer guide, injection SDK
specification, Ghidra control, adversarial interoperability guide, agentic
GSP IDE, custom simulator design) and GREEN-EYE-ROADMAP.md (long-term H1-H7
program); doc maps and the project RISC-V VM doc updated.
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md b/debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md new file mode 100644 index 0000000..d310f22 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md @@ -0,0 +1,399 @@ +# 14 — Firmware adversarial interoperability research guide (green-eye + Ghidra) + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +## 1. Purpose and scope + +Open-IOV's hard problem is not writing the GSP emulation once; it is keeping +`open-iov-vfio` accurate while the firmware changes underneath it. New GSP +images, device pairings and architectures arrive stripped, signed and +hardware-coupled, and some actively resist analysis. This guide is the +practical companion to `GREEN-EYE-ROADMAP.md` H4: how to move from "the new +image does something different" to a reviewed product change with evidence. + +Ghidra is the **static map**; green-eye is the **dynamic ground truth**; +recorded sessions are the **shared evidence**. Recipes use only the v0.1 +surface, plus items explicitly labelled roadmap; see also docs/06-ACCURACY.md. + +| Capability used here | State | +|---|---| +| `probe`/`record`/`analyze`/`compare`/`symbols`/`inject`/`ghidra`, hooks, watchpoints, snapshots, sessions, `export_hits` | working (v0.1) | +| Ghidra control bridge (`run_until`, breakpoints, hits) | working (v0.1) | +| Full TCG access trace (`qemu/greeneye_trace.c`) | source + optional build; not folded into sessions yet (R4) | +| Mutation harness (AFL++ class), device model, MCP | roadmap (R5, H2/R1, H7/R3) | + +## 2. The adversary model (a moving target, not a malicious party) + +This guide assumes no threat actor. The "adversary" is **unplanned firmware +change plus deliberate opacity**: new images on a hidden +schedule; stripped symbols, signed images and non-recoverable boot states; +anti-analysis behaviour (timing checks, self-consistency checks, hardware- +dependent branches) and **decoys** (code or data not on the live path); and +hardware coupling a simulator cannot yet exercise. + +1. Any single run is an anecdote. A conclusion needs at least an A/B pair. +2. A static reading is a hypothesis. It becomes evidence only when execution + reaches it in a recorded session. +3. Absence of a hit is a fact about *that* run at *that* fidelity (docs/06), + not proof of dead code. +4. Every claim carries its fidelity level (L0–L3, docs/06) and provenance. + +## 3. The joint workflow: Ghidra, green-eye, sessions + +``` + question > Ghidra (static): decompile, dispatcher tables, symbols + > green-eye (dynamic): same stimulus, hooks, watchpoints, trace + > sessions + hits TSV: greeneye compare -> triage -> evidence pack + > open-iov-vfio change (with evidence) +``` + +| Question | Ghidra answers | green-eye answers | Shared artifact | +|---|---|---|---| +| What is this code? | decompilation, call graph, xrefs | — | symbol DB (docs/05) | +| Did it execute here? | hypothesis from xrefs | `bp_hit`/`watch_hit` events | `greeneye_hits.tsv` | +| What changed between versions? | corpus/symbol diff | `compare` hit/watch/write deltas | symbol DB per version + report | +| What can this service touch? | object references | watchpoint/TCG access map | capability table | + +References: docs/05-GHIDRA-BRIDGE.md, docs/13-GHIDRA-CONTROL.md, docs/07-PORTABILITY-PLAYBOOKS.md. + +## 4. Method catalogue + +Run all commands from `open-iov/debug/risc-v-gsp-rm-vm/`; angle-bracket +placeholders are task-supplied (never hardcode private paths in docs). + +### 4.1 Semantic diff across firmware versions (H4.1) + +Goal: separate "the code changed" from "the behaviour moved" before the +emulation is touched. + +1. Build a symbol DB per version. `ghidra/ExportForGreenEye.java` emits + `addr<TAB>name<TAB>size`, which `symbols export --index` accepts: + + ```bash + ./greeneye.sh symbols export --index <new-export.tsv> --bias 0 -o symbols-new.json + ./greeneye.sh symbols export --index <old-export.tsv> --bias 0 -o symbols-old.json + ``` + +2. Probe each image for first-touch structure changes (docs/07 P1.2): + + ```bash + ./greeneye.sh probe <old.fwimage> --symbols symbols-old.json --record + ./greeneye.sh probe <new.fwimage> --symbols symbols-new.json --record + ``` + +3. Run the **same stimulus** against the re-hosted build of each version. Put + it in a script so it is identical across runs: + + ```python + # run_stimulus.py -- ./greeneye.sh run run_stimulus.py --kernel <rehosted.elf> + import greeneye as ge + s = ge.launch(kernel="<rehosted.elf>", record=True, name="stim") + ctrl = s.symbols.lookup("<control-symbol>") # demo: gsp_ctrl + s.watch(ctrl + 0x10, size=8, kind="w") + s.run(duration=0.5) # settle/self-test + s.memory.write_u64(ctrl + 0x20, 1) # mailbox PING + s.memory.write_u64(ctrl + 0x28, 0x1234) + s.run(duration=0.5) + s.note("stimulus: PING 0x1234") + s.close() + ``` + + Demo control-block offsets are demo-specific; use the schema + (`firmware/uproc_demo/gsp_ctrl.schema.json`) or SDK constants rather than + hardcoding them (docs/12 §4). + +4. Compare the sessions symbolically — breakpoint profile delta, watchpoint + delta and written-page delta: + + ```bash + ./greeneye.sh compare dumps/<old-run> dumps/<new-run> --symbols + ``` + +5. Triage with a decision table rather than intuition: + + | Static code changed? | Hit/write profile changed? | Reading | + |---|---|---| + | no | no | unchanged on this stimulus | + | no | yes | behaviour moved: inputs, ordering, environment or state | + | yes | no | changed code off this stimulus path (or dead/decoy: §4.4) | + | yes | yes | new logic on a live path — prioritise | + | n/a | symbol never hit in either run | coverage gap: widen the stimulus first | + +6. Annotate the result back into Ghidra (`greeneye_hits.tsv` + + `MarkGreenEyeHits.java`). Behaviour that moved without a code change is + usually a state/ordering fix for the emulation, not new logic. + +### 4.2 Capability inference from watchpoints and TCG traces (H4.2) + +Goal: derive what a service/engine actually reads and writes, then compare it +with the capability model the emulation assumes. + +1. Instrument the object ranges the service owns (kinds `w`, `r`, `a`): + + ```python + # capability_probe.py -- ./greeneye.sh run capability_probe.py --kernel <elf> + import greeneye as ge + s = ge.launch(kernel="<rehosted.elf>", record=True, name="caps") + base = s.symbols.lookup("<object-symbol>") + for off in (0x0, 0x10, 0x20, 0x28, 0x38, 0x40): + for kind in ("r", "w"): + s.watch(base + off, size=8, kind=kind, + callback=lambda h, k=kind: s.note("cap-" + k, pc=h.pc)) + s.run(duration=1.0) + s.close() + ``` + +2. For access coverage **between stops**, enable the TCG plugin with a PC + filter for the service's load range: + + ```bash + make -C qemu + qemu-system-riscv64 -machine virt -m 256M -display none \ + -serial file:/tmp/uart.log -bios none -kernel <rehosted.elf> -S \ + -gdb tcp:127.0.0.1:4444 \ + -plugin qemu/greeneye-trace.so,path=/tmp/ge-trace.jsonl,start=0x80000000,end=0x81000000,exec=on + ./greeneye.sh attach --port 4444 --kernel <rehosted.elf> --plugin <capability-plugin.py> + ``` + + `start=`/`end=` bound the trace, `exec=on` adds per-instruction records and + `path=` selects the JSONL output (`qemu/greeneye_trace.c`; qemu/README.md). + Folding that JSONL into sessions is roadmap R4 — today it is a separate + evidence blob. + +3. Reduce to a capability table and compare with the emulation's model: + + | Observed access | Address/range | Kind | PC/symbol | Emulated capability | Finding | + |---|---|---|---|---|---| + | write | `0x…` | w | `func+0x…` | not modelled | least-privilege candidate | + | read | `0x…` | r | `func+0x…` | denied in emulation | potential fail-closed gap | + + Least-privilege readings matter for `open-iov-vfio`, but a simulator + observation is evidence about the firmware (docs/06 §6); confirm before it + changes the product. + +### 4.3 Mutation of construction/control inputs (H4.3) + +Goal: drive error paths and asserts deliberately, capture them with symbols, +and build a corpus for the emulation's fail-closed rules. The full harness is +roadmap R5; until then, bounded mutation over existing primitives is honest. + +1. Mutate single fields deterministically and record each attempt: + + ```python + # mutate.py -- ./greeneye.sh run mutate.py --kernel <rehosted.elf> + import greeneye as ge + s = ge.launch(kernel="<rehosted.elf>", record=True, name="mutate") + ctrl = s.symbols.lookup("<control-symbol>") + for value in (0, 1, 0xFFFFFFFFFFFFFFFF, 0x8000000000000000): + s.memory.write_u64(ctrl + 0x20, value) + s.run(duration=0.2, max_stops=200) + s.note("mutation", field="mailbox", value=value) + s.close() + ``` + +2. Mutate injected-service calls on both paths (direct and mailbox): + + ```python + from greeneye.inject import load_service, invoke_remote, invoke_via_firmware + svc = load_service(s, "sdk/build/injected_demo.elf", slot=6) + invoke_remote(s, svc, cmd=0xFFFF, a0=0, a1=0) # malformed cmd + s.run(duration=0.3) + invoke_via_firmware(s, svc, cmd=0xFFFF, a0=0, a1=0) # malformed mailbox + ``` + +3. Capture error paths with hooks/watchpoints on the dispatcher, fault + handlers and assert sites (symbol DB); `./greeneye.sh demo` records a trap. +4. Compare clean vs mutated sessions, keeping the mutation value in a `note` + so the pack is self-describing: + + ```bash + ./greeneye.sh compare dumps/<clean-run> dumps/<mutated-run> --symbols + ``` + + Every corpus entry needs the firmware hash; never run mutation against + hardware (docs/12 §9). + +### 4.4 Decoy and anti-analysis handling (H4.4) + +Goal: distinguish signal from noise when the image branches on environment, +timing or integrity state, or contains bogus code paths. + +1. **Deterministic replay first.** Run the identical stimulus twice and + require the comparison to be empty: + + ```bash + ./greeneye.sh compare dumps/<run1> dumps/<run2> --symbols + ``` + + Any delta is a non-determinism candidate (scheduler, uninitialised read, time); record it. + +2. **Two-run differential.** Flip one input bit; attribute every delta to it; + unrelated movement suggests a time/integrity check: + + ```python + s.memory.write_u64(ctrl + 0x28, original ^ (1 << 0)) # one bit + ``` + +3. **Coverage-based dead-code detection.** Use the TCG trace with `exec=on` + (or a bounded `s.step(n)` pass) to build a PC set, then annotate it into + Ghidra: + + ```python + from greeneye.ghidra import export_hits + from greeneye.symbols import SymbolDB + db = SymbolDB(); db.load_json("<symbols.json>") + export_hits("dumps/<run>", "greeneye_hits.tsv", symbols=db) + ``` + ```bash + $GHIDRA/support/analyzeHeadless <proj> <name> -process <program> -noanalysis \ + -scriptPath debug/risc-v-gsp-rm-vm/ghidra \ + -postScript MarkGreenEyeHits.java /abs/path/greeneye_hits.tsv + ``` + + Zero hits across every stimulus makes a function a **decoy candidate**, not proof of dead code (docs/06). + +4. **Trap-on-register-read experiments.** Arm a read watchpoint on a register + the firmware might be probing, and record which PC reads it: + + ```python + s.watch("<reg-or-mmio-symbol>", size=8, kind="r", + callback=lambda hit: s.note("anti-analysis-read", pc=hit.pc)) + ``` + + On today's `qemu virt` an unmodelled GPU register faults rather than + returning a value (`greeneye probe` reports that first read, docs/06 §3); + the value-return experiment waits on the H2 device model — log, don't claim. +5. Maintain the catalogue of observed techniques in + docs/09-GSP-FIRMWARE-NOTES.md (roadmap H4.4), one session pair per technique. + +### 4.5 Differential execution: reference vs simulator vs injected service (H4.5) + +Goal: measure, not narrate. The contract is the M1–M6 table from docs/06: + +| # | Metric | How it is measured here | +|---|---|---| +| M1 | Inventory coverage | observed controls/RPCs vs emulated handled set | +| M2 | Reply agreement | field-by-field reply diff (canonical size, consumed fields) | +| M3 | Ordering agreement | sequence alignment of the control/event stream (LCS/edit distance) | +| M4 | State agreement | firmware state transitions vs emulation state model | +| M5 | Error-path agreement | injected faults take the same status/notifier path | +| M6 | Memory side-effects | read/write map vs emulation shadow state; snapshot hashes | + +Procedure: (1) capture the reference on hardware/VFIO (docs/07 P4); (2) capture +the simulator side with `record`/scripted `run`, hooks, watchpoints and +snapshots; (3) `./greeneye.sh compare dumps/<reference-dir> dumps/<greeneye-dir> +--symbols`, with a script or plugin for per-control M2/M3 payload events; +(4) repeat with the injected-service path to test the service boundary; +(5) escalate — divergence that would change emulation behaviour must be +reproduced physically; simulator-only divergence becomes a fidelity gap. + +### 4.6 From observation to reimplementation + +The canonical accuracy loop (roadmap H4.5): (1) observe on hardware/VFIO; +(2) observe in green-eye (replayable session); (3) `compare` + M1–M6; +(4) implement in `open-iov-vfio` with the session directories attached as +evidence; (5) re-run the same stimulus and require the metric table to improve +or stay equal. + +Practical rules: one session pair per behaviour change; the PR links the +`compare` report and session hashes plus the symbol DB diff if the static map +changed; decoy or low-fidelity observations never justify a product change. + +## 5. Ghidra integration specifics + +Start the live bridge and drive it from the decompiler: + +```bash +./greeneye.sh ghidra --kernel <rehosted.elf> --bridge-port 7000 +$GHIDRA/support/analyzeHeadless <proj> <name> -process <program> -noanalysis \ + -scriptPath debug/risc-v-gsp-rm-vm/ghidra \ + -postScript GreenEyeControl.java 7000 0x<decompiler-address> +``` + +`GreenEyeControl.java` operations (docs/13): `setBreakpointAt/Here`, +`runUntil/Here`, `readMemoryHere`, `readRegister`/`writeRegister`, +`callFunction`, `snapshotHere`, `listHits`, `exportHits`. Watchpoints exist on +the bridge (`watch_set`) without a Java wrapper: `send("watch_set", {...})`. + +Address conventions and bias pitfalls (docs/05 §1): + +* Ghidra shows **program addresses**; green-eye resolves **runtime addresses**. + For firmware linked at its load address they coincide; otherwise supply + `--bias` when building the symbol DB (`ExportForGreenEye.java` emits program + addresses). +* The classic mistake is passing a program address to `hook()` when the image + base is non-zero; prefer `s.hook("<name>")` and pass raw numbers through + `resolve`. `decompiled_index.tsv` carries both spaces; green-eye keeps the + program address in `symbol.extras["program_addr"]` for `bucket:line` mapping. +* Wrapper-generated functions are re-laid-out between versions: transfer + names, never raw wrapper addresses (docs/05 §6). +* Bridge `run_until` timeouts mean "not executed under this stimulus at this + fidelity"; they do not prove unreachability. + +## 6. Metrics, evidence pack and report template + +A finding is only a finding when the pack is complete. The evidence pack for +one adversarial-navigation task contains: + +* [ ] firmware identity (version, hash, pairing) and symbol DB(s) with provenance and hash; +* [ ] baseline/candidate session dirs (or sha256 inventory) and the exact stimulus; +* [ ] `compare` report and relevant `analyze` reports; +* [ ] M1–M6 table with the reference-capture method and explicit L2/L3 limits; +* [ ] `greeneye_hits.tsv` plus any Ghidra annotations produced; +* [ ] unexplained-delta list (empty is a claim, not a default). + +Short report template: + +```markdown +# Firmware delta: <old> -> <new> (<device>) +* firmware: <old sha256> -> <new sha256>; symbol DB: <old hash> -> <new hash> + (provenance: <export>, bias <n>) +* sessions: <baseline dir>, <candidate dir> (greeneye-session/1); stimulus: + <script/spec + mutation values> + +## Observed delta +* changed on live path: <symbols>; behaviour moved without code change: + <symbols, evidence>; coverage gaps/decoys: <symbols, why> + +## Metrics (docs/06) +| M1 | M2 | M3 | M4 | M5 | M6 | +|----|----|----|----|----|----| + +## Fidelity statement +L0/L1 in simulator; L2 not claimed; L3 absent for <registers>. +Physical reproduction required before product change: yes/no, why. + +## Product action +open-iov-vfio change (or explicit "no change"): <link/notes> +``` + +## 7. Boundaries, safety and ethics + +* **Development-only tooling.** Never shipped, never a dependency of the emulation, never in a guest (docs/00). +* **No guest modification.** Instrument the firmware under study in the + simulator; do not patch or observe a guest driver (project rule; V4). +* **No confidentiality claims.** The simulator is a study environment, never a + security or confidentiality boundary (docs/06, V4/H6). +* **Clean-room discipline.** Do not copy vendor code into product code; use + the study material to understand behaviour, then write the emulation from + observation. Tracked docs cite behaviour generically and never name source + locations. +* **Fuzzing is gated.** Mutation runs only against the simulator or re-hosted + code, behind explicit harnesses, with session evidence (docs/12 §9, R5). + Never use injection to defeat a hardware security boundary (docs/12 §1). +* **Escalate to physical.** Any conclusion that would change `open-iov-vfio` + must be confirmed against a physical capture (docs/06 §6, docs/07 P4.5). + +## 8. Roadmap hooks + +| Method | Roadmap item | What it adds | Gate | +|---|---|---|---| +| 4.1 semantic diff | H4.1 | versioned symbol corpora, automated delta report | H1+H2 | +| 4.2 capability inference | H4.2 | access map vs capability model, least-privilege findings | watchpoints (done); TCG-session fold (R4) | +| 4.3 mutation/fuzzing | H4.3, R5 | AFL++ class harness, corpus of error paths | R1/R2 | +| 4.4 decoy handling | H4.4 | technique catalogue in docs/09, deterministic replay | H2 for register-value experiments | +| 4.5 differential exec | H4.5, docs/06 | scripted consensus harness, M1–M6 automation | reference capture pipeline | +| 4.6 reimplementation | H4.5, V1 | golden sessions, CI comparison | H1 stable | + +Program-level success criterion served here: a new GSP version can be triaged +(inventory + behaviour diff + metric table) in **hours**, with evidence. |
