summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md
diff options
context:
space:
mode:
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.md399
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.