diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/harness/value_demos/s03_unknown_command.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/harness/value_demos/s03_unknown_command.py | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/harness/value_demos/s03_unknown_command.py b/debug/risc-v-gsp-rm-vm/harness/value_demos/s03_unknown_command.py new file mode 100644 index 0000000..162bc47 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/harness/value_demos/s03_unknown_command.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""s03 - unknown command discovery: which IDs are handled, injected, or new? + +Real scenario (docs/19-CONTROL-DISCOVERY.md, measured 2026-10-02): a swept +input surface is how new/undocumented controls are found between firmware +versions. On the demo, IDs 16..23 were *discovered* to fall through the +switch into the injected-service handler table (16..21 alias the built-in +handlers, 22..23 are NULL slots returning ``~id``) - wiring a new image can +add silently. Statically this requires reading an indirect dispatch table +per version; behaviorally it is a bounded sweep. + +Analog: 24 command IDs, zero arguments, deterministic, fresh session per ID +(the same safety strategy as the real discovery tool), classified by hooks on +the dispatcher and every handler-table entry. +""" +from __future__ import annotations + +import json +import os + +try: # package import (tests) or direct script execution (run_all) + from . import _common as C +except ImportError: + import sys as _sys + + _sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import _common as C + +HANDLERS = ("rpc_ping", "rpc_echo", "rpc_fill", "rpc_sum", "rpc_state", "rpc_fault") + + +def _classify(cmd: int, handler_hits, dispatch_seen, result, trap) -> str: + if 16 <= cmd <= 23: + return "injected-slot" + if 1 <= cmd <= 6: + return "known-handler" + if trap: + return "trap" + if result is not None and result == (~cmd) & 0xFFFFFFFFFFFFFFFF: + return "default-path" + return "no-progress" + + +def run() -> dict: + import greeneye as ge + + backend = C.choose_backend("native") + out = C.artifact_dir("s03_unknown_command") + commands = [ + 'for cmd in 1..24: ge.launch(backend="%s"); hook(rpc_dispatch + 6 handlers)' % backend, + "write mailbox cmd with zero args; run bounded; classify", + "record: outcome, handler alias, result, trap signal, rpc_count delta", + ] + scenario = C.scenario( + ids=[ + "docs/19-CONTROL-DISCOVERY.md (2026-10-02): 189-probe demo sweep in ~36 s", + "Handoff-vGPU-IO.md PART 91 (2026-10-01): new controls appear between images (0x59 era)", + "docs/14-ADVERSARIAL-INTEROP-GUIDE.md 4.1: static diff says code changed, not which input activates it", + ], + baseline=( + "static-only: read the indirect dispatcher table in Ghidra for every " + "image; on the real corpus the demo sweep took ~36 s for 189 probes " + "and still found an undocumented injected-slot class" + ), + projection=( + "the RM surface (control IDs by class, method opcodes) is gated on " + "H1.3/H2.2; the sweep/classification shape demonstrated here is " + "exactly what docs/19 section 5 designs for it" + ), + blocked=True, + device="H1.3/H2.2: RM dispatch table exposed to the simulator", + ) + C.render_banner( + "s03_unknown_command", + "Which command IDs are handled, which route to injected slots, and which are new/default?", + "unknown-control discovery between stripped firmware versions", + scenario["static_baseline"], + commands, + ) + + findings = [] + for cmd in range(1, 25): + session = ge.launch(kernel=C.DEMO_ELF, backend=backend, name=f"vd-s03-{cmd:02d}") + try: + ctrl = session.symbols.lookup("gsp_ctrl") + dispatch = [] + handler_hits = [] + signals = [] + session.hook( + "rpc_dispatch", + on_enter=lambda hit, d=dispatch: d.append( + {"cmd": hit.arg(0), "arg0": hit.arg(1), "arg1": hit.arg(2), "arg2": hit.arg(3)} + ), + ) + for name in HANDLERS: + try: + session.hook(name, on_enter=lambda hit, n=name: handler_hits.append(n)) + except Exception: + pass + session.on_event("stop", lambda hit, sig=signals: sig.append(hit.stop.signal) if hit.stop else None) + session.run(duration=0.15) + session.memory.write_u64(ctrl + C.CTRL_MAILBOX, cmd) + session.run(duration=0.15) + result = session.memory.read_u64(ctrl + C.CTRL_RESULT) + trap = 5 in [s for s in signals if s] + outcome = _classify(cmd, handler_hits, bool(dispatch), result, trap) + findings.append( + { + "cmd": cmd, + "outcome": outcome, + "trap": trap, + "signals": [s for s in signals if s], + "handler_alias": handler_hits[0] if handler_hits else None, + "dispatch_seen": bool(dispatch), + "result": result, + "result_hex": f"{result:#x}", + "expected_default": f"{(~cmd) & 0xFFFFFFFFFFFFFFFF:#x}", + } + ) + finally: + session.close() + + by_class: dict[str, list[int]] = {} + for finding in findings: + by_class.setdefault(finding["outcome"], []).append(finding["cmd"]) + injected = by_class.get("injected-slot", []) + aliases = {f["cmd"]: f["handler_alias"] for f in findings if f["cmd"] in injected} + payload = { + "success": len(injected) == 8 and len(by_class.get("known-handler", [])) == 6, + "backend": backend, + "ids_swept": len(findings), + "classes": {key: {"count": len(v), "ids": v} for key, v in sorted(by_class.items())}, + "injected_aliases": aliases, + "findings": findings, + "question": "enumerate handled / injected / default command space in one bounded sweep", + "answer": ( + f"{len(by_class.get('known-handler', []))} known handlers, " + f"{len(injected)} injected-slot IDs ({injected}), aliases {aliases}, " + f"{len(by_class.get('default-path', []))} default-path IDs; " + "the same shape (sweep + dispatcher/handler hooks) is what found the " + "silent 16..23 wiring the static map did not highlight" + ), + "success_criteria": { + "pass": "IDs 1..6 classified known, 16..23 classified injected-slot, remaining default; cmd 6 traps", + "fail": "an ID is misclassified or a fresh-session probe cannot be replayed", + }, + "real_case": scenario, + } + os.makedirs(out, exist_ok=True) + with open(os.path.join(out, "findings.jsonl"), "w", encoding="utf-8") as fh: + for finding in findings: + fh.write(json.dumps(finding, sort_keys=True) + "\n") + lines = ["# control discovery (analog)", "", f"* backend: {backend}", f"* ids swept: {len(findings)}", ""] + for key, value in sorted(by_class.items()): + lines.append(f"* {key}: {value}") + lines.append("") + C.write_text(os.path.join(out, "control-discovery.md"), "\n".join(lines) + "\n") + C.write_json(os.path.join(out, "discovery.json"), payload) + return payload + + +def main() -> int: + return 0 if C.timed_demo(run, C.artifact_dir("s03_unknown_command"))["success"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) |
