From 3fe5360d0efc439d59f2b7a9bf2f713aa9cdb918 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Fri, 2 Oct 2026 09:13:00 -0400 Subject: 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. --- debug/risc-v-gsp-rm-vm/.gitignore | 1 + debug/risc-v-gsp-rm-vm/GREEN-EYE-ROADMAP.md | 381 +++++++++++++++++ debug/risc-v-gsp-rm-vm/Makefile | 6 +- debug/risc-v-gsp-rm-vm/README.md | 14 +- debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md | 7 + debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md | 5 + .../docs/11-GSP-RM-SERVICE-DEVELOPER-GUIDE.md | 181 +++++++++ debug/risc-v-gsp-rm-vm/docs/12-INJECTION-SDK.md | 149 +++++++ debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md | 154 +++++++ .../docs/14-ADVERSARIAL-INTEROP-GUIDE.md | 399 ++++++++++++++++++ debug/risc-v-gsp-rm-vm/docs/15-AGENTIC-GSP-IDE.md | 387 ++++++++++++++++++ debug/risc-v-gsp-rm-vm/docs/16-CUSTOM-SIMULATOR.md | 449 +++++++++++++++++++++ .../firmware/uproc_demo/gsp_ctrl.schema.json | 103 ++++- debug/risc-v-gsp-rm-vm/firmware/uproc_demo/main.c | 43 +- debug/risc-v-gsp-rm-vm/ghidra/GreenEyeControl.java | 187 +++++++++ .../risc-v-gsp-rm-vm/ghidra/MarkGreenEyeHits.java | 3 +- debug/risc-v-gsp-rm-vm/greeneye/analyze.py | 28 ++ debug/risc-v-gsp-rm-vm/greeneye/cli.py | 96 +++++ debug/risc-v-gsp-rm-vm/greeneye/gdbridge.py | 220 ++++++++++ debug/risc-v-gsp-rm-vm/greeneye/inject.py | 129 ++++++ debug/risc-v-gsp-rm-vm/greeneye/record.py | 2 +- debug/risc-v-gsp-rm-vm/greeneye/session.py | 113 ++++++ debug/risc-v-gsp-rm-vm/greeneye/target.py | 2 + debug/risc-v-gsp-rm-vm/sdk/Makefile | 32 ++ debug/risc-v-gsp-rm-vm/sdk/greeneye_service.h | 37 ++ debug/risc-v-gsp-rm-vm/sdk/injected_demo.c | 41 ++ debug/risc-v-gsp-rm-vm/tests/test_bridge.py | 81 ++++ debug/risc-v-gsp-rm-vm/tests/test_core.py | 4 +- debug/risc-v-gsp-rm-vm/tests/test_injection.py | 121 ++++++ docs/05-operations/03-risc-v-gsp-rm-vm.md | 29 +- 30 files changed, 3358 insertions(+), 46 deletions(-) create mode 100644 debug/risc-v-gsp-rm-vm/GREEN-EYE-ROADMAP.md create mode 100644 debug/risc-v-gsp-rm-vm/docs/11-GSP-RM-SERVICE-DEVELOPER-GUIDE.md create mode 100644 debug/risc-v-gsp-rm-vm/docs/12-INJECTION-SDK.md create mode 100644 debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md create mode 100644 debug/risc-v-gsp-rm-vm/docs/14-ADVERSARIAL-INTEROP-GUIDE.md create mode 100644 debug/risc-v-gsp-rm-vm/docs/15-AGENTIC-GSP-IDE.md create mode 100644 debug/risc-v-gsp-rm-vm/docs/16-CUSTOM-SIMULATOR.md create mode 100644 debug/risc-v-gsp-rm-vm/ghidra/GreenEyeControl.java create mode 100644 debug/risc-v-gsp-rm-vm/greeneye/gdbridge.py create mode 100644 debug/risc-v-gsp-rm-vm/greeneye/inject.py create mode 100644 debug/risc-v-gsp-rm-vm/sdk/Makefile create mode 100644 debug/risc-v-gsp-rm-vm/sdk/greeneye_service.h create mode 100644 debug/risc-v-gsp-rm-vm/sdk/injected_demo.c create mode 100644 debug/risc-v-gsp-rm-vm/tests/test_bridge.py create mode 100644 debug/risc-v-gsp-rm-vm/tests/test_injection.py diff --git a/debug/risc-v-gsp-rm-vm/.gitignore b/debug/risc-v-gsp-rm-vm/.gitignore index 3a97916..f8e440c 100644 --- a/debug/risc-v-gsp-rm-vm/.gitignore +++ b/debug/risc-v-gsp-rm-vm/.gitignore @@ -6,3 +6,4 @@ qemu/*.o __pycache__/ *.pyc .run/ +sdk/build/ diff --git a/debug/risc-v-gsp-rm-vm/GREEN-EYE-ROADMAP.md b/debug/risc-v-gsp-rm-vm/GREEN-EYE-ROADMAP.md new file mode 100644 index 0000000..0e1b58e --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/GREEN-EYE-ROADMAP.md @@ -0,0 +1,381 @@ +# GREEN-EYE ROADMAP — long-term program + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** +> This is the long-term plan for green-eye, the runtime analysis, injection and +> measurement environment for the NVIDIA GPU System Processor (GSP) and its +> adjacent microcontrollers. Near-term, committed work lives in +> [`docs/08-ROADMAP.md`](docs/08-ROADMAP.md); this document is the program. + +## 0. Purpose + +Open-IOV's hardest long-term problem is not writing the emulation once — it is +**surviving adversarial firmware change**. NVIDIA ships new GSP images, new +device pairings and new architectures on independent schedules, and the +firmware is deliberately opaque (stripped, signed, hardware-coupled, with +non-recoverable boot states). To keep `open-iov-vfio` accurate across that +churn, the project needs a toolchain that can: + +1. **Run** firmware off-hardware, safely and repeatably. +2. **Observe** it completely — control flow, memory, services, state machines. +3. **Change** it — inject our own services/engines and mutate inputs to learn + boundaries. +4. **Compare** — hardware vs VFIO vs simulation, old vs new firmware, with a + metric contract instead of anecdotes. +5. **Reimplement** — turn observed behaviour into reviewed code in + `open-iov-vfio` with evidence attached. + +green-eye is the vehicle for that program. It is explicitly a **development +environment**, never a shipped component. + +## 1. Starting point (H0, done) + +v0.1 (2026-10-02) provides: GDB-free QEMU RISC-V transport; hooks, +breakpoints, watchpoints; memory/struct/snapshot tooling; recorded sessions +and symbol-annotated analysis; declarative probes; plugins; a Ghidra symbol +and hit bridge; a Ghidra control bridge; a TCG full-trace plugin; a +position-independent service SDK and `greeneye inject`; and the Frida-like +`Session.call` / `run_until` primitives. Fidelity is L0/L1 (execution and +memory); the device environment (L3) is absent. + +## 2. How this roadmap is organised + +Five workstreams run through every horizon: + +* **F — Fidelity**: how much of the real GSP environment the simulator + provides (device model, boot chain, timing). +* **I — Injection**: what code we can add to the running system and how. +* **A — Adversarial navigation**: finding and understanding *unexpected* + firmware behaviour and mapping it to emulation changes. +* **C — Control & tooling**: debugging, Ghidra/agent integration, SDK, + formats, usability. +* **V — Validation**: metrics, benchmarks, golden sessions, gates that stop a + claim from landing without evidence. + +Horizons are numbered H1..H7 with explicit **entry gates** (what must be true +before starting) and **exit criteria** (what must be demonstrated). + +--- + +## H1 — Injection and control (now → weeks) + +**Goal:** make "write a service / change the firmware / prove it" a +first-class loop on the demo uproc and on re-hosted uproc code. + +### H1.1 GSP RM firmware service developer guide (deliverable today) +A written guide (docs/11) covering: RM service anatomy, service entry and +message loop, RM-core calls available to a service, allocation/control +surfaces, inter-service messaging, versioning, and the build/inject/debug +loop. On the demo this maps to the handler-table ABI; on real GSP-RM it maps +to the RM object/control model. + +### H1.2 Injection SDK v1 (deliverable today, demo-proven) +Specification (docs/12): blob format, PIC requirements, ABI, registration, +capability metadata, error model, observability hooks. Implemented in `sdk/` +and `greeneye/inject.py`, verified by direct call and firmware dispatch. + +### H1.3 GSP-RM service injection (needs H2 device model) +Once real GSP-RM runs in the simulator (H2), extend the SDK to: +* build against RM headers or against a green-eye shim for + public work; +* register a service with the simulated RM core instead of a demo handler + table; +* call RM-core entry points (allocation, control, mapping, scheduling) from + the injected code, intercepted by the simulator so calls are logged, + stubbed or forwarded; +* run injected services under the same recording/compare pipeline. +Entry gate: H2.2 (GSP-RM reaches its message loop in the simulator). +Exit: an injected service serves a control end-to-end and its session can be +diffed against hardware. + +### H1.4 Ghidra-driven execution (deliverable today, demo-proven) +`greeneye ghidra` + `GreenEyeControl.java` (docs/13): "run until here", set +breakpoints, read memory/registers, remote-call, export hits. Next +increments: +* watchpoints and structure reads from Ghidra; +* a decompiler-side hit heat-map fed by `greeneye_hits.tsv`; +* bidirectional navigation: clicking a hit opens the decompiled function. + +### H1.5 Debugger parity +Make the REPL/API cover the common GDB vocabulary for firmware work +(watchpoints, catchpoints for traps, thread/core selection, memory region +metadata) while keeping the scripted API as the primary surface. + +--- + +## H2 — Fidelity: platform device model (months) + +**Goal:** move from "executes and faults" to "boots the real GSP image to its +message loop" inside the simulator. + +### H2.1 First-touch register map (started) +The `probe` command already reports the first fault (TU104 570.144: load +access fault at firmware vaddr `0x4009014` reading `0x2000000000000A00`). +Systematically harvest first-touch addresses across versions/devices and +build the initial MMIO map from them. + +### H2.2 QEMU device model +Model, as a QEMU `-device` (or a small set): +* **falcon/SEC2 windows** (cpuctl, dmactl, fbif, imem/dmem, bootvec) with + read-back semantics adequate for boot polling; +* **CSB/TRANSCOM** mailbox windows used by the GSP application; +* **WPR2/FWSEC state** registers (so the image believes it was staged); +* **PTIMER** and basic interrupt routing; +* **DMA/IOMMU** address translation for the firmware's descriptors into + host/FB windows; +* **PMU stub** sufficient for the identity/clock provisioning the GSP app + reads early. +Entry gate: H0 + first-touch data. Exit: the image reaches `appMain` and +blocks on a host message rather than faulting. + +### H2.3 Boot-chain shims +FWSEC/booter/SEC2 are not reasonable to model fully; provide *documented +shims* that place the GSP image in the state the bootloader would have left +it (boot args in a0–a5, MPU/TCM layout, scratch state), so boot behaviour can +be studied without the security chain. Record shim assumptions in session +metadata. + +### H2.4 Multi-core and concurrency (approximate first) +GSP-RM is multi-task; adjacent controllers (SEC2/PMU) are separate cores. +Add deterministic scheduling knobs (instruction budget round-robin, timer +injection) so multi-task interleavings can be explored, replayed and +compared — without claiming real-time behaviour. + +--- + +## H3 — Non-QEMU custom simulator (months; explicitly later) + +**Why:** QEMU is an execution engine, not a workflow engine. It cannot give +us cheap snapshots, deterministic replay with time-travel, in-process +inspection, per-access callback ABI objects, or a device map expressed as +code + data we can version with the firmware profiles. A custom simulator can, +and can become the substrate for injection, fuzzing and adversarial work. + +### H3.1 Requirements +* RV64IMAC(+GC when needed) interpreter, fast enough for firmware bring-up + (target: ≥10–50 MIPS/core on one host thread; not realtime). +* Pluggable **memory map DSL**: regions (RAM/TCM/MMIO/DMA) with read/write + callbacks, access logging, and declarative register definitions. +* **Deterministic scheduler**: per-core instruction budgets, timers, event + queue; replay from an event log. +* **Snapshot/restore** of full machine state (registers, memory, device + state) for instant checkpointing and time-travel debugging. +* **Event bus** emitting the existing `events.jsonl` schema plus + `exec`/`access` events. +* **Backend ABI** identical to `Session`'s expectations, so all hooks, + watchpoints, scripts, probes, plugins, records and comparisons keep working. +* **Injection ABI**: load blobs, register services, intercept RM-core calls. +* **Python-first embedding** (C/C++ core with a Python control plane, or a + Rust core with PyO3), plus a CLI. + +### H3.2 Design sketch +``` ++-------------------- green-eye Python control plane --------------------+ +| Session API, hooks/watchpoints, recorder, plugins, Ghidra/agent bridge | ++---------------------------- Backend interface -------------------------+ + | QEMU-RSP backend (today) | native simulator backend + v v + qemu-system-riscv64 libgreenmachine (C/C++/Rust) + +-- cpu/ (interpreter, decoder) + +-- mem/ (map DSL, snapshots) + +-- dev/ (falcon, csb, pmu, dma) + +-- sched/ (cores, timers, events) + +-- trace/ (event bus, coverage) +``` +* The **map DSL** is versioned data (JSON/YAML) with per-device behaviour in + small plugin modules; new firmware/device profiles are additions, matching + the project's portability direction. +* **Snapshots** are copy-on-write memory pages + device state, giving cheap + branch exploration ("what if this reply differed?"). +* **Time-travel** is built from snapshot + deterministic event replay. + +### H3.3 Implementation plan +1. **M1**: RV64IMAC interpreter + RAM + UART; boots the demo uproc; green-eye + Session works against the native backend (parity with QEMU for the demo). +2. **M2**: memory map DSL + falcon/CSB/PTIMER devices; boots a re-hosted + uproc build; snapshots/restore. +3. **M3**: deterministic scheduler + event log/replay; multi-task tests. +4. **M4**: device-model parity with H2.2 and boot of the real GSP image under + shims. +5. **M5**: injection ABI + RM-core interception; fuzzing harness. +6. **M6**: performance and ergonomics hardening; backend selection in the + CLI (`--backend native`). + +### H3.4 Risks +Fidelity divergence from QEMU/hardware (mitigate: differential tests between +backends at H0 demos and H2 boot milestones); effort creep (gate behind H2 +success); maintenance of two backends (keep the Backend interface minimal). + +--- + +## H4 — Adversarial firmware navigation (continuous) + +**Goal:** when the firmware changes unpredictably — new handlers, opaque +state, deliberate anti-analysis, or shifted resources — navigate and +reimplement quickly. + +### H4.1 Semantic diffing +* Import each firmware's Ghidra corpus and green-eye symbol DB; diff RPC + dispatch tables, handler inventory, control sets, message layouts. +* Run the same stimulus harness against each version and use + `greeneye compare` for hit/watch/write deltas; automatically flag functions + whose call counts changed but whose static code is unchanged (behaviour + moved) vs changed code (new logic). +* Produce a "portability delta report" per version (feeds docs/07 playbooks). + +### H4.2 Capability inference +Observe what each service reads/writes (watchpoints, TCG trace) and infer +capabilities (which objects, ranges, controls it touches); compare with the +declared/emulated capability model; surface least-privilege and security +findings for `open-iov-vfio`. + +### H4.3 Mutation and fuzzing +Feed malformed construction/control traffic into re-hosted or simulated +firmware; capture error paths, asserts and panic loops with symbol +annotation; build a corpus that drives the emulation's fail-closed rules +(project security hygiene) and its error replies. + +### H4.4 Decoy / anti-analysis handling +Firmware may use timing checks, self-integrity checks, bogus code paths or +hardware-dependent branches. Techniques to build: deterministic replay to +separate data from noise; "two-run differential" with a single input bit +flipped; dead-code identification by coverage; trap-on-read of +anti-analysis registers with recorded responses; and a catalogue of observed +techniques in docs/09. + +### H4.5 Reimplementation workflow (accuracy loop) +The canonical loop for any behaviour: +1. Observe on hardware/VFIO (reference capture). +2. Observe in green-eye (simulator, replayable). +3. `compare` + metric table M1–M6 (docs/06-ACCURACY.md). +4. Implement in `open-iov-vfio` with the session directories attached as + evidence. +5. Re-run the same stimulus; the metric table must improve or stay equal. +This loop becomes a scripted harness ("consensus harness") so every change +produces comparable evidence. + +--- + +## H5 — Adjacent microcontrollers (months) + +**Goal:** treat the GSP as one node in a device-level system. + +* **Inventory**: PMU (power/voltage/clock provisioning), SEC2 and ACR/SOE + (security/boot), the booter, FWSEC/FRTS, and the DMA lines between them + (from firmware analysis and the RISC-V Story material; see + docs/09). +* **Models**: per-controller memory maps, mailbox protocols and boot state + machines as versioned data; SEC2/PMU stubs first (GSP needs them to pass + early init), full ACR/SOE later. +* **Cross-controller scenarios**: GSP↔PMU identity/clock provisioning, + GSP↔SEC2 mailbox, boot-order exercises (Init→ACR→RM for the + confidential-compute partition model). +* **Deliverable**: a device-level session that spans controllers, recorded + and comparable, so a firmware update's cross-controller interactions are + visible. + +Entry gate: H2.2 (GSP message loop). Exit: at least one cross-controller +exchange reproduced and compared with hardware evidence. + +--- + +## H6 — Verilog soft processor (far later) + +**Goal:** an FPGA/ASIC-simulation-grade soft implementation of the GSP core +environment that can run real GSP-RM images with cycle-accuracy options, for +deep timing/security/interoperability work and hardware validation of the +emulation. + +### H6.1 Scope +* RV64 core choice/build (existing open cores vs a minimal in-house core), + matching ISA/extensions used by the firmware; TCM, MPU/PMP, vectored + interrupts, timers. +* Device fabric: falcon-compatible windows, CSB/TRANSCOM, DMA engine to host + memory, WPR2/FWSEC state (with an explicit, documented security posture: + the soft model is a study environment, never a confidentiality boundary). +* Run modes: pure RTL simulation (Verilator) for determinism and coverage; + FPGA for speed; co-simulation with green-eye (JTAG/DPI/a bridge) so the + same Python hooks, sessions and comparisons apply. + +### H6.2 Integration with green-eye +* A `Backend` implementation talking to the RTL simulation clock/step and + memory API; breakpoints become simulation traps; watchpoints become + signal/page monitors. +* Sessions record cycle counts as a distinct event class (L2 timing becomes + measurable, with the accuracy caveats of docs/06 revisited). + +### H6.3 Gates +Requires: H2/H3 proven, a validated device map, and a funded reason to care +about cycle accuracy (e.g., a customer-facing timing claim). Explicitly +**not** on the critical path; do not start before the baseline goals in the +project roadmap are complete. + +--- + +## H7 — Agentic platform (continuous, gated) + +* **MCP plugin (R3)**: expose sessions as MCP tools (list/open sessions, + symbols, breakpoints, run/run_until, memory, snapshots, compare, + export_hits). Local stdio; no network by default. +* **AI harness integration**: deterministic run harnesses that an agent can + drive; sessions as the shared evidence format; guarded write actions with + an explicit capability manifest. +* **Autonomous analysis loops**: hypothesise → instrument → run → compare → + report, with human-review gates for anything that changes the product. +* **Safety**: no unattended physical-GPU cycles; no guest modifications; the + agent runs against the simulator by default. + +--- + +## Cross-cutting workstreams + +### V1 — Metrics and golden data +Keep the M1–M6 contract (docs/06) authoritative; maintain golden sessions for +the demo and re-hosted workloads; CI runs the tool tests + golden comparisons. + +### V2 — Formats and versioning +`greeneye-session/1` evolves additively with a version bump and migration +notes; symbol DBs carry provenance; injection blobs carry an ABI version and +capability metadata. + +### V3 — SDK/ABI evolution +Injection ABI v1 (demo) → v2 (RM-core interception). Breaking changes are +gated by a compatibility shim and a documented migration. + +### V4 — Security +Treat the simulator as a study environment: no confidentiality claims; keep +fuzzing and mutation behind explicit harnesses; never ship simulation shims +in the product; never place tooling in a guest. + +### V5 — Performance +Simulator performance target (H3.1); TCG trace filtering; snapshot +compression; session size budgets (region-scoped dumps). + +### V6 — Documentation and developer experience +Every horizon ships docs with copy-paste workflows, an acceptance script, and +a flag-removal (TOOL IN DEVELOPMENT) criterion; the Ghidra/agent guides are +kept current as first-class deliverables. + +## Sequencing summary + +| Horizon | Entry gate | Primary deliverable | +|---|---|---| +| H1 | H0 (done) | injection SDK + RM service guide + Ghidra control | +| H2 | H1 | QEMU device model; real GSP image reaches message loop | +| H3 | H2.2 | native simulator with snapshots/replay and injection | +| H4 | H1+H2 | adversarial diffing/capability/fuzzing harness | +| H5 | H2.2 | adjacent-controller models and cross-controller sessions | +| H6 | H3 validation | Verilog soft-core environment (far later) | +| H7 | H1 stable | MCP plugin and agentic harnesses | + +## Success criteria (program level) + +1. A new GSP version can be triaged (inventory diff + behaviour diff + + metric table) in **hours**, with recorded evidence. +2. Every new emulation behaviour lands with an A/B session pair and the M1–M6 + table. +3. Injected services prototype RM engine ideas in the simulator before any + product code is written. +4. The custom simulator reproduces the H2 boot and H4 workloads + deterministically, with snapshots and replay. +5. No claim of accuracy, portability or security depends on a single run or + on unstated fidelity assumptions. diff --git a/debug/risc-v-gsp-rm-vm/Makefile b/debug/risc-v-gsp-rm-vm/Makefile index a5103f5..fc43b37 100644 --- a/debug/risc-v-gsp-rm-vm/Makefile +++ b/debug/risc-v-gsp-rm-vm/Makefile @@ -3,11 +3,14 @@ PYTHON ?= python3 .PHONY: all demo test demo-run plugin probe-real clean info -all: demo +all: demo sdk demo: $(MAKE) -C firmware/uproc_demo +sdk: + $(MAKE) -C sdk + test: demo $(PYTHON) -m unittest discover -s tests -t . @@ -27,5 +30,6 @@ probe-real: clean: $(MAKE) -C firmware/uproc_demo clean + $(MAKE) -C sdk clean rm -rf dumps/* qemu/*.so qemu/*.o find . -name __pycache__ -type d -exec rm -rf {} + 2>/dev/null || true diff --git a/debug/risc-v-gsp-rm-vm/README.md b/debug/risc-v-gsp-rm-vm/README.md index 66f53fa..4017cd4 100644 --- a/debug/risc-v-gsp-rm-vm/README.md +++ b/debug/risc-v-gsp-rm-vm/README.md @@ -50,13 +50,14 @@ Everything else is Python stdlib. greeneye/ Python package: RSP client, session, memory, symbols, structures, recording, analysis, probes, plugins, Ghidra bridge firmware/uproc_demo/RV64 bare-metal demo "uproc" (mailbox RPC, state machine, trap) +sdk/ injected-service SDK (position-independent RV64 blobs) qemu/ launch helper + optional TCG plugin for full memory tracing -ghidra/ Ghidra scripts (export symbols, annotate hit addresses) +ghidra/ Ghidra scripts (export symbols, annotate hits, control bridge) plugins/ example plugin + authoring guide examples/ Python instrumentation scripts and declarative probe specs -tests/ unit tests + QEMU integration smoke test +tests/ unit tests + QEMU integration smoke tests docs/ full documentation set (design, scripting, plugins, accuracy, - portability playbooks, roadmap, GSP firmware notes) + portability playbooks, roadmap, service/injection guides) dumps/ recorded sessions (gitignored) ``` @@ -75,3 +76,10 @@ dumps/ recorded sessions (gitignored) | [08-ROADMAP](docs/08-ROADMAP.md) | MCP, custom simulator, device model | | [09-GSP-FIRMWARE-NOTES](docs/09-GSP-FIRMWARE-NOTES.md) | uproc/falcon/NV-RISCV facts | | [10-RELATED-WORK](docs/10-RELATED-WORK.md) | Frida/GDB/QEMU-plugin comparison | +| [11-GSP-RM-SERVICE-DEVELOPER-GUIDE](docs/11-GSP-RM-SERVICE-DEVELOPER-GUIDE.md) | writing, injecting and debugging firmware services | +| [12-INJECTION-SDK](docs/12-INJECTION-SDK.md) | injection blob/ABI specification | +| [13-GHIDRA-CONTROL](docs/13-GHIDRA-CONTROL.md) | driving green-eye from GhidraScripts | +| [14-ADVERSARIAL-INTEROP-GUIDE](docs/14-ADVERSARIAL-INTEROP-GUIDE.md) | navigating unpredictable/adversarial firmware change | +| [15-AGENTIC-GSP-IDE](docs/15-AGENTIC-GSP-IDE.md) | agentic workflows and runbooks | +| [16-CUSTOM-SIMULATOR](docs/16-CUSTOM-SIMULATOR.md) | non-QEMU simulator design (far later) | +| [GREEN-EYE-ROADMAP](GREEN-EYE-ROADMAP.md) | the long-term program (H1–H7) | diff --git a/debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md b/debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md index a9f4251..f1e1946 100644 --- a/debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md +++ b/debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md @@ -79,6 +79,13 @@ Interactively: | [08-ROADMAP](08-ROADMAP.md) | MCP, custom simulator, device model | | [09-GSP-FIRMWARE-NOTES](09-GSP-FIRMWARE-NOTES.md) | uproc/falcon/NV-RISCV facts used here | | [10-RELATED-WORK](10-RELATED-WORK.md) | Frida/GDB/QEMU-plugin comparison | +| [11-GSP-RM-SERVICE-DEVELOPER-GUIDE](11-GSP-RM-SERVICE-DEVELOPER-GUIDE.md) | writing/injecting/debugging RM services | +| [12-INJECTION-SDK](12-INJECTION-SDK.md) | injection blob/ABI specification | +| [13-GHIDRA-CONTROL](13-GHIDRA-CONTROL.md) | driving green-eye from Ghidra | +| [14-ADVERSARIAL-INTEROP-GUIDE](14-ADVERSARIAL-INTEROP-GUIDE.md) | adversarial firmware navigation | +| [15-AGENTIC-GSP-IDE](15-AGENTIC-GSP-IDE.md) | agentic workflows and runbooks | +| [16-CUSTOM-SIMULATOR](16-CUSTOM-SIMULATOR.md) | non-QEMU simulator design (later) | +| [GREEN-EYE-ROADMAP](../GREEN-EYE-ROADMAP.md) | long-term program H1–H7 | ## Where this fits in the Open-IOV workflow diff --git a/debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md b/debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md index c8b4638..10f4f5f 100644 --- a/debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md +++ b/debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md @@ -1,6 +1,11 @@ # 08 — green-eye roadmap > **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. +> This is the **near-term, committed** roadmap (rungs R0–R7 below). The +> **long-term program** — adversarial firmware navigation, adjacent +> microcontrollers, a non-QEMU custom simulator, a Verilog soft-processor +> environment and the agentic platform — lives in +> [`../GREEN-EYE-ROADMAP.md`](../GREEN-EYE-ROADMAP.md). > Items here are **not** v0.1. Do not start them ad hoc; each has a gate. ## R0. Product principles diff --git a/debug/risc-v-gsp-rm-vm/docs/11-GSP-RM-SERVICE-DEVELOPER-GUIDE.md b/debug/risc-v-gsp-rm-vm/docs/11-GSP-RM-SERVICE-DEVELOPER-GUIDE.md new file mode 100644 index 0000000..6ba8d69 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/11-GSP-RM-SERVICE-DEVELOPER-GUIDE.md @@ -0,0 +1,181 @@ +# 11 — GSP RM firmware service developer guide + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status and 06 for the +> fidelity caveats. Exact proprietary API names are not reproduced in tracked +> docs; local provenance lives in the local analysis notes. + +## 1. What this guide is for + +The GSP runs a resource manager (RM) as **services**: message handlers, state +machines and engines that own objects (devices, address spaces, channels, +memory, notifiers) and answer controls. Open-IOV emulates those services; the +long-term goal of green-eye is to let a developer **write, inject, run and +debug their own versions of these services** before (or instead of) changing +the product emulation. + +This guide teaches the loop on the demo uproc — which implements the same +shape as a GSP service — and maps each step to the future GSP-RM work in +[`GREEN-EYE-ROADMAP.md`](../GREEN-EYE-ROADMAP.md) H1.3. + +## 2. Service anatomy + +| Concept | Demo uproc (today) | GSP-RM (roadmap H1.3) | +|---|---|---| +| Entry point | exported symbol `ge_demo_service_dispatch` | service entry registered with the RM core | +| Dispatch ABI | `u64 dispatch(u64 cmd, u64 a0, u64 a1)` | same shape via the service registry shim | +| Reachability | firmware mailbox commands 16..23 select handler slot 0..7; `arg0` is the service command, `arg1`/`arg2` are parameters | RM message/control dispatch routed by the simulator's RM-core | +| State | service-local (stack/registers) or the control block | RM objects and service-private memory | +| Talking to peers | control-block fields + mailbox; debugger calls | RM-core calls and inter-service messages | +| Observability | hooks/watchpoints/sessions/TCG trace | same, plus RM-core call interception | + +A service must be **position-independent**: no globals, no libc, no absolute +addresses. Everything it calls is either its own code or an address passed in +by the injector (the future RM-core shim will pass a table of callable +entry points, which is also the interception point). + +## 3. Writing a service + +1. Copy the SDK shape from `sdk/`: + * `greeneye_service.h` — the ABI header; + * `injected_demo.c` — a working multi-command service. +2. Implement `ge_service_fn`: + ```c + __attribute__((noinline)) ge_u64 my_service_dispatch(ge_u64 cmd, ge_u64 a0, ge_u64 a1) + { + switch (cmd) { + case 1: return a0 ^ 0xbeefu; + case 2: return a0 + a1; + default: return ~cmd; + } + } + ``` +3. Keep it PIC-clean: no `static` data with pointers, no string globals, no + division (the demo build has no libc), no stack beyond a few KiB. +4. Build: + ```bash + make -C sdk # or point the Makefile at your source + make -C sdk relocs # must print "no relocations in this file" + ``` +5. Fix any relocation the linker reports before injecting; relocations mean + the blob will not run at an arbitrary load address. + +## 4. Injecting and invoking + +```bash +./greeneye.sh inject --service sdk/build/injected_demo.elf \ + --cmd 1 --arg0 0x41 --arg1 0x42 +``` + +What happens (all recorded): +1. Every `PT_LOAD` is written into RAM (default `0x81000000`; auto-sized to + fit the target). +2. The service dispatch entry is patched into handler slot 6 + (`--slot` selects 0..7). +3. A **direct debugger-side call** runs the service immediately + (`greeneye.inject.invoke_remote`): this is the unit-test path, independent + of firmware state. +4. After the firmware settles, a **firmware-dispatched call** writes + `arg0=cmd, arg1=a0, arg2=a1, mailbox=16+slot` and reads `result` + (`greeneye.inject.invoke_via_firmware`): this is the integration path. + +Programmatic form: + +```python +from greeneye.inject import load_service, invoke_remote, invoke_via_firmware +svc = load_service(s, "sdk/build/injected_demo.elf", slot=6) +print(hex(invoke_remote(s, svc, 2, 0x10, 0x20))) # 0x30 +s.run(duration=0.3) +print(hex(invoke_via_firmware(s, svc, 2, 0x10, 0x20))) # 0x30 +``` + +## 5. Talking to other services and the host + +On the demo: +* the **control block** is the shared state (`gsp_ctrl`); services coordinate + through its fields and the mailbox command range; +* the **host/debugger** is a peer via hooks, watchpoints and `Session.call`; +* a service can call another service if it is handed the other entry address + (the SDK's second export `ge_demo_service_answer` demonstrates a second + callable point; the injector resolves any exported symbol). + +On real GSP-RM (H1.3) the same roles are filled by RM-core allocation and +control calls and by messages between services. The simulator will intercept +those calls so they can be logged, stubbed, or forwarded to the real +implementation — which is exactly what makes the environment powerful for +prototyping: a service you write can be exercised against recorded RM-core +behaviour before the product emulation implements it. + +## 6. Resource surfaces an engine can use + +Category map (details are RM-version-specific and tracked privately where +provenance requires it): + +| Surface | Purpose | How green-eye helps today | +|---|---|---| +| Memory / descriptors | allocate, map, free, register host or vidmem ranges | watchpoints on allocation state; snapshot/diff; schema decode | +| Address spaces / page tables | GPU VA translation, mapping updates | hooks on map/unmap routines; memory diffs; TCG trace of accesses | +| Channels / GPFIFO / USERD | submission plumbing | hooks and remote calls; observe ring state via snapshots | +| Sync: semaphores, notifiers, interrupts | completion and signalling | watchpoints on sem values; event ordering in sessions | +| Engine scheduling / runlists | prioritisation, preemption | call-graph and hook evidence; compare runs | +| Controls / messages | configuration and queries | declarative probes; replay/compare across versions | +| Device identity / caps / clocks | what the RM reports | schema decode of the static-config block; A/B vs hardware oracle | + +Everything here is observed through the same primitives, so an engine +prototype is analysed exactly like firmware code. + +## 7. Debugging workflow + +1. **Unit-test with `call`:** breakpoints and hooks around the service entry, + `Session.call(entry, (cmd, a0, a1))`, inspect the return and watchpoints on + its data. Failures raise `RspTimeout` and leave the session recording. +2. **Integration run:** `invoke_via_firmware` with the mailbox path, while + hooks on the firmware's dispatcher and watchpoints on the control block + record the full route. +3. **Ghidra-driven:** `greeneye ghidra` + `GreenEyeControl.java` — execute + until a service address, set breakpoints from the decompiler, read + registers/memory, export hits (docs/13). +4. **Coverage:** run with the TCG plugin filtered to the service load range + (`qemu/greeneye-trace.c`, `start=`/`end=` args) to see every access. +5. **Regression:** record a session per command; `greeneye compare` detects + changed hit/watch/write profiles when the service or firmware changes. + +## 8. Rules and pitfalls + +* **PIC purity first.** If the blob needs relocation, it is not injectable + at an arbitrary address. +* **No absolute addresses from the host.** Use exported symbols and + `load_service`, never hardcoded addresses. +* **No unbounded loops.** A service that never returns blocks the call loop + until the timeout and then leaves the target stopped; the recorder shows + the last PC. +* **Watchpoint the state, hook the entry.** Watchpoints are immediate (one + instruction step) and ideal for counters; hooks give arguments/returns. +* **Never ship a service.** Injected code is a development experiment; the + product changes go through `open-iov-vfio` with the accuracy loop. +* **Record everything.** A claim about a service without a session is not + evidence. + +## 9. Worked example: add a command + +```c +/* sdk/injected_demo.c */ +case 7: + return (a0 | (a1 << 8)) & 0xFFFFFFFFull; +``` + +```bash +make -C sdk +./greeneye.sh inject --cmd 7 --arg0 0xab --arg1 0xcd +# direct call -> 0xcdab +# firmware call -> 0xcdab +./greeneye.sh analyze # event/hit report +``` + +## 10. What comes next (roadmap) + +* H1.3: services built against an RM shim, registered with a simulated RM + core, calling intercepted RM-core surfaces. +* H2: the device model that lets real GSP-RM run in the simulator at all. +* H4: adversarial navigation — diff service behaviour across firmware + versions, mutate inputs, infer capabilities. +* H5: services that interact with PMU/SEC2/ACR models. diff --git a/debug/risc-v-gsp-rm-vm/docs/12-INJECTION-SDK.md b/debug/risc-v-gsp-rm-vm/docs/12-INJECTION-SDK.md new file mode 100644 index 0000000..26f1ffb --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/12-INJECTION-SDK.md @@ -0,0 +1,149 @@ +# 12 — Injection SDK specification (v1, demo ABI) + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status and +> [`GREEN-EYE-ROADMAP.md`](../GREEN-EYE-ROADMAP.md) §H1/H3 for the program. + +## 1. Goals and non-goals + +**Goals** +* a repeatable, scriptable way to compile a piece of RV64 code, load it into a + live firmware target, register it, invoke it, and observe it with the same + hooks/sessions/analysis as firmware; +* a clear ABI so services can be written once and run under the demo uproc + today and under a simulated RM core later; +* an evidence trail: every load, registration and invocation is recorded. + +**Non-goals (v1)** +* shipping any injected code (development-only); +* hot-patching signed firmware on hardware; +* bypassing security boundaries (the simulator is a study environment). + +## 2. Blob contract + +* **Format:** ELF64 `EM_RISCV`, `RV64IMAC`, soft-float ABI, one or more + `PT_LOAD` segments. +* **Position independence:** built PIC (`-fPIC`, `-mcmodel=medany`, + `-mno-relax`, `-fno-asynchronous-unwind-tables`), no dynamic relocations. + The loader verifies by writing segments at the chosen load address; the SDK + `make relocs` target must report none. +* **Self-sufficiency:** no libc, no global data containing addresses. + Caller-provided addresses (future RM-core tables) arrive as arguments. +* **Entry symbol:** `ge_demo_service_dispatch` by default (override with + `--entry`/`entry_symbol=`). +* **Optional metadata:** a `ge_service_t` block can be exported for future + loaders (`magic 0x47455356`, version 1, name, dispatch). + +## 3. ABI v1 + +```c +typedef ge_u64 (*ge_service_fn)(ge_u64 cmd, ge_u64 a0, ge_u64 a1); +``` + +* `a0`, `a1`, `a2` registers carry `(cmd, a0, a1)` on entry. +* The return value is `a0`. +* The service may use `a3..a7`, `t0..t6`, `s0..s11` and the stack; the + injector supplies a private stack below the scratch page. +* Traps do not return: the call loop times out, records the stop, restores + registers and raises `RspTimeout`. +* No calling convention for callbacks in v1; the loader registers a **single + dispatch entry** per slot. + +## 4. Demo registration model + +* The firmware exposes eight handler slots (`gsp_ctrl.handlers[0..7]`). +* Mailbox commands **16..23** select slot `cmd-16`. +* On dispatch the firmware calls + `handlers[slot](arg0, arg1, arg2)` — i.e. `cmd = arg0`, parameters + `arg1`, `arg2`. +* Slots 0..5 are used by the built-in demo RPCs; **slot 6** is the SDK + default (`--slot`). + +### Control-block fields used (demo) + +| Field | Offset | Role | +|---|---|---| +| `mailbox` | `+0x20` | command (`16 + slot` selects an injected service) | +| `arg0` | `+0x28` | service command | +| `arg1` | `+0x30` | parameter 0 | +| `arg2` | `+0x38` | parameter 1 | +| `result` | `+0x40` | service return value | +| `handlers` | `+0x150` | eight entry slots | + +Offsets are demo-firmware-specific; use `greeneye/inject.py` constants and the +schema at `firmware/uproc_demo/gsp_ctrl.schema.json` rather than hardcoding. + +## 5. Lifecycle + +``` +compile (PIC) ──► verify relocations ──► load PT_LOADs at load_addr + │ │ + │ ├─► register entry in slot + │ └─► record "service_injected" + ▼ +invoke_remote (direct call) invoke_via_firmware (mailbox dispatch) + └──────────────► both record call_enter/call_return events +``` + +* **Load address:** default `ram_base + 0x01000000`, auto-shrunk to fit if + the image is larger; explicit with `--load-addr`. +* **Cleanup:** v1 does not unregister automatically. A service is removed by + zeroing its slot and (optionally) clearing the blob; sessions record the + injection so runs are reproducible from a known state. +* **Multiple services:** one per slot; multi-entry services are one dispatch + with subcommands. + +## 6. Observability contract + +The injector records, in the session directory: +* `service_injected` — path, name, load address, entry address, slot, size; +* `mem_write` events for every segment written; +* `call_enter` / `call_return` — address, args, return value; +* hook/watch events from user instrumentation inside the service; +* `qemu.log`-style blobs are not applicable (no probe here), but the TCG + trace can be enabled for full coverage. + +Comparison of injected-service runs uses the standard `greeneye compare` +profiles, so a change in a service's behaviour is visible like any firmware +change. + +## 7. Building (reference) + +```makefile +CC := clang +LLD ?= /usr/lib/llvm-18/bin/ld.lld +CFLAGS := --target=riscv64-unknown-elf -march=rv64imac -mabi=lp64 \ + -mcmodel=medany -mno-relax -fPIC -ffreestanding -nostdlib \ + -fno-builtin -fno-stack-protector -fno-asynchronous-unwind-tables -O2 +$(LLD) -m elf64lriscv --no-relax -e ge_demo_service_dispatch -o service.elf service.o +``` + +The tracked SDK (`sdk/`) is the executable reference. + +## 8. Versioning and compatibility + +* ABI version 1 is the demo contract described here. +* Future ABI v2 (RM-core shim) will pass a call table and a service context + instead of relying on firmware slots; blobs declare their ABI via the + optional metadata block so loaders can refuse mismatches. +* Breaking ABI changes require a compatibility shim plus a note in the + session metadata and this document. + +## 9. Security and safety + +* Injected code runs with the simulator's full privileges; the bridge and + injector are local-only tools. +* Never inject into a guest or a shipped environment; never use injection to + bypass a hardware protection. +* Fuzzing/mutation harnesses (roadmap H4.3) must run against the simulator + or re-hosted code, with sessions attached as evidence. +* Treat every injected service as untrusted code for the purposes of + watching: it may corrupt firmware state, so always record and restart clean + after crashes. + +## 10. Roadmap hooks + +* **H1.3** — RM service registration and RM-core call interception. +* **H3** — native-simulator loader with snapshots and branch exploration. +* **H4** — differential testing of injected services across firmware + versions and capability inference from their access patterns. +* **H5** — services spanning GSP and adjacent microcontrollers. diff --git a/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md b/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md new file mode 100644 index 0000000..26e534d --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md @@ -0,0 +1,154 @@ +# 13 — Ghidra control bridge + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +The bridge lets a GhidraScript drive a live green-eye session: execute the +target until a point **chosen in the decompiler**, set/clear breakpoints, read +memory and registers, make remote calls, and export hit lists back into the +Ghidra project. Nothing about the firmware has to be rebuilt or re-hosted to +answer "does execution actually reach this function?". + +``` +Ghidra (Java script) ── JSON lines over localhost ──► greeneye gdbridge ──► Session ──► QEMU + ▲ │ + └──────── greeneye_hits.tsv ── MarkGreenEyeHits.java ◄──── recorded session ┘ +``` + +## 1. Quickstart + +```bash +# Terminal 1: a live target + bridge (records by default) +./greeneye.sh ghidra --kernel firmware/uproc_demo/build/uproc_demo.elf --bridge-port 7000 +# prints: ghidra bridge on 127.0.0.1:7000 + +# Terminal 2: Ghidra headless, run until the decompiler's address +analyzeHeadless -process -noanalysis \ + -scriptPath open-iov/debug/risc-v-gsp-rm-vm/ghidra \ + -postScript GreenEyeControl.java 7000 0x800000ce +``` + +In the GUI, load `GreenEyeControl.java` from the Script Manager and supply the +port in the script arguments; with no address argument it uses the current +cursor (`currentAddress`). + +## 2. `GreenEyeControl.java` functions + +All functions exist on the script class and can be called from the script's +own `run()` or from other scripts via the bridge `send()` method. + +| Function | Effect | +|---|---| +| `ping()` | `pong` if the bridge is alive | +| `info()` | session name, pc, symbol count, recording path | +| `resolve(name)` | symbol or `name+0xNN` → runtime address | +| `setBreakpointAt(hexAddr)` / `setBreakpointHere()` | install a hook (prints on hit) | +| `clearBreakpointAt(longAddr)` | remove breakpoints at an address | +| `runUntil(hexAddr)` / `runUntilHere()` | resume and stop at the address; returns `{addr, symbol, kind}` | +| `readMemoryHere(size)` | hex bytes at the cursor | +| `readRegister(name)` / `writeRegister(name, value)` | register access | +| `callFunction(hexAddr, args...)` | Frida-like remote call; returns `a0` | +| `snapshotHere(size)` | record a memory snapshot in the session | +| `listHits()` | breakpoint hit counts from the session | +| `exportHits(path)` | write `greeneye_hits.tsv` for `MarkGreenEyeHits.java` | +| `send(cmd, argsJson)` / `resultOf(response)` | raw bridge access for new workflows | + +Example additions inside `run()`: + +```java +setBreakpointAt("0x800000ce"); +println(runUntil("0x800000ce")); // {addr:..., symbol:"rpc_sum", kind:"breakpoint"} +println(readMemoryHere(16)); +println(callFunction("0x80000048", 0x1111L)); // rpc_ping -> 0xaffe +println(exportHits("/tmp/greeneye_hits.tsv")); +``` + +## 3. Bridge protocol (specification) + +One JSON object per line; one response per request, same `id`. + +Request: `{"id": , "cmd": "", "args": {...}}` +Response: `{"id": , "ok": true, "result": ...}` or +`{"id": , "ok": false, "error": "..."}`. + +| `cmd` | args | result | +|---|---|---| +| `ping` | — | `"pong"` | +| `info` | — | `{name, pc, symbols, recording}` | +| `resolve` | `{name}` | address (int) | +| `bp_set` | `{target, kind?}` | address | +| `bp_clear` | `{addr}` | `true` | +| `bp_list` | — | `[{addr, label, hits, kind}]` | +| `watch_set` | `{target, size?, kind?}` | address | +| `run_until` | `{target, timeout?}` | `{addr, symbol, kind}` | +| `run` | `{duration?, max_stops?}` | stops | +| `step` | `{count?}` | `[pc, ...]` | +| `read_mem` | `{addr, size?}` | hex string | +| `write_mem` | `{addr, hex}` | `true` | +| `read_reg` | `{name}` | int | +| `write_reg` | `{name, value}` | `true` | +| `call` | `{target, args?, timeout?}` | return value | +| `snapshot` | `{addr, size, label?}` | label | +| `note` | `{text}` | `true` | +| `hits` | — | `[{addr, hits, symbol}]` | +| `export_hits` | `{path}` | absolute path | +| `close` | — | `true` (closes the connection and stops the bridge) | + +`target`/`addr` accept `"0x..."`, a symbol name, or `name+0xNN`. + +## 4. Workflows + +### 4.1 Execute until here +1. Start `greeneye ghidra` on the firmware image and load symbols + (`symbols export` from the Ghidra index) so hits are named. +2. Put the cursor on a function in Ghidra and run `GreenEyeControl.java`. +3. `runUntilHere()` sets a temporary breakpoint, services intervening stops + (including your hooks/watchpoints), and reports the symbol resolved at the + stop. If the function is never reached, the bridge returns a timeout error. + +### 4.2 Annotate the project with runtime evidence +1. Run a session (any CLI/scripted path) and `exportHits("/tmp/hits.tsv")`. +2. `MarkGreenEyeHits.java /tmp/hits.tsv` adds plate comments and bookmarks in + the Ghidra project. +3. Optionally export a symbol DB from Ghidra (`ExportForGreenEye.java`) and + feed it back with `symbols export --index`. + +### 4.3 Remote-call driven experiments +Use `callFunction()` to invoke firmware or injected-service functions from +the decompiler (unit-test style), then inspect memory with +`readMemoryHere()` and the recorded session. + +## 5. Address conventions + +Ghidra shows **program addresses** (e.g. libcuda file vaddr + image base). +green-eye resolves **runtime addresses**. For firmware linked at its load +address these coincide; otherwise export symbols with the right bias +(docs/05-GHIDRA-BRIDGE.md) and prefer `resolve(name)` over hardcoded numbers. + +## 6. Limitations (v1) + +* Request/response only: the script pulls state; the bridge does not push + asynchronous hit events. Poll `hits`/`bp_list` between commands. +* One command at a time per connection; multiple clients are accepted but are + not synchronised with each other. +* `watch_set` exists on the bridge but has no dedicated Java wrapper yet — + call `send("watch_set", ...)` directly. +* The bridge is unauthenticated **by design**: localhost only, never expose + it on a network interface. + +## 7. Troubleshooting + +| Symptom | Cause / fix | +|---|---| +| `Connection refused` | bridge not started, wrong port, or it exited (`close`) | +| `green-eye bridge closed the connection` | the server was stopped or the target session closed | +| timeout on `run_until` | address not executed; check symbol bias and that the target is not wedged | +| empty `hits` | the session was started without recording; use the CLI (records by default) | +| Java compile error on script load | Ghidra API drift; the script targets Ghidra 11/12 `GhidraScript` | + +## 8. Roadmap + +* Push notifications for breakpoint hits (server → Ghidra). +* Decompiler annotations from live watchpoints (value changes per field). +* A bridge client that lets multiple scripts share a session with a command + queue. +* Direct MCP exposure of the same command set (roadmap R3). 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 + `addrnamesize`, which `symbols export --index` accepts: + + ```bash + ./greeneye.sh symbols export --index --bias 0 -o symbols-new.json + ./greeneye.sh symbols export --index --bias 0 -o symbols-old.json + ``` + +2. Probe each image for first-touch structure changes (docs/07 P1.2): + + ```bash + ./greeneye.sh probe --symbols symbols-old.json --record + ./greeneye.sh probe --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 + import greeneye as ge + s = ge.launch(kernel="", record=True, name="stim") + ctrl = s.symbols.lookup("") # 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/ dumps/ --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 + import greeneye as ge + s = ge.launch(kernel="", record=True, name="caps") + base = s.symbols.lookup("") + 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 -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 --plugin + ``` + + `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 + import greeneye as ge + s = ge.launch(kernel="", record=True, name="mutate") + ctrl = s.symbols.lookup("") + 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/ dumps/ --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/ dumps/ --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("") + export_hits("dumps/", "greeneye_hits.tsv", symbols=db) + ``` + ```bash + $GHIDRA/support/analyzeHeadless -process -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("", 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/ dumps/ +--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 --bridge-port 7000 +$GHIDRA/support/analyzeHeadless -process -noanalysis \ + -scriptPath debug/risc-v-gsp-rm-vm/ghidra \ + -postScript GreenEyeControl.java 7000 0x +``` + +`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("")` 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: -> () +* firmware: -> ; symbol DB: -> + (provenance: , bias ) +* sessions: , (greeneye-session/1); stimulus: +