summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/wall2/wall2_resolver5.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-25 05:21:29 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-25 05:21:29 -0400
commit45978e9202412fb9fc6088718e9720f22ece7b30 (patch)
tree1c70d4967088965bff39d5863bfef88ace5aa72f /tools/guest-diag/wall2/wall2_resolver5.py
parente5eacffa9e4e0577b8fb109610286db83bf80ff3 (diff)
downloadopen-iov-45978e9202412fb9fc6088718e9720f22ece7b30.tar.gz
open-iov-45978e9202412fb9fc6088718e9720f22ece7b30.zip
wall-2 forensics tools: GR channel-init push localization (session 73)
Tools and findings from the live wall-2 campaign (r674/r675): the vk_frame client's GR channel-init push (va=0x4480000 len=1432) cannot be translated by the sema service, so no release is applied and vkCreateDevice fails -4. Captured: the RM window-record/memdesc layout (ring memdesc PA == the service's validated ring pa; push memdesc PA injected live and read by the service without a false-translation line), the CE-only shape of the service rescue matcher, the notrace status of nvidia/modeset kprobes (QEMU gdb hbreak works), and the ring-entry encoding from channelFillGpFifo/nvWriteGpEntry (neither runs for this channel's kick). New tools: wall2_resolver{2..5}.py, wall2_livecap{1..3}.py, RAM structure scanners (scan_r673*.py, trace_push674.py), gdb675*.gdb probes, plus a README with the ranked next steps.
Diffstat (limited to 'tools/guest-diag/wall2/wall2_resolver5.py')
-rw-r--r--tools/guest-diag/wall2/wall2_resolver5.py102
1 files changed, 102 insertions, 0 deletions
diff --git a/tools/guest-diag/wall2/wall2_resolver5.py b/tools/guest-diag/wall2/wall2_resolver5.py
new file mode 100644
index 0000000..faec902
--- /dev/null
+++ b/tools/guest-diag/wall2/wall2_resolver5.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+# wall2_resolver5.py - final: continuous scan, all-candidate injection.
+#
+# Structure (r675, verified): an RM page holds the client channel's window
+# records and memdescs:
+# {0x4480000, 0x20000} push window record
+# {0x44a0000, 0x40000} ring window record
+# {0x20000, 0x03008000, PUSH_PA} push window memdesc
+# {0x40000, 0x03008000, RING_PA} ring window memdesc (PA == validated ring pa)
+# The resolver scans CONTINUOUSLY (no ring-line gate), finds pages holding the
+# pair of window records, and injects 0x4480000 -> every 0x20000 memdesc PA in
+# the same page (last write wins in the service; content check arbitrates).
+import subprocess, struct, time, sys, os
+
+RAM = "/dev/shm/vgpu-guest-ram"
+PARAM = "/sys/module/vgsp/parameters/sema_map_add"
+LOG = "/tmp/opencode/wall2_resolver5.log"
+PUSH_VA = 0x4480000
+RING_VA = 0x44a0000
+WATCH_S = int(os.environ.get("W2_WATCH", "600"))
+
+logf = open(LOG, "a", buffering=1)
+def log(*a):
+ s = " ".join(str(x) for x in a)
+ print(s, flush=True)
+ logf.write("[%s] %s\n" % (time.strftime("%H:%M:%S"), s))
+
+img = open(RAM, "rb", buffering=0)
+def gpa2off(pa):
+ if pa < 0x80000000: return pa
+ if pa < 0x180000000: return 0x80000000 + (pa - 0x100000000)
+ return None
+def off2gpa(off):
+ return off if off < 0x80000000 else 0x100000000 + (off - 0x80000000)
+def rd(pa, n):
+ off = gpa2off(pa)
+ if off is None: return None
+ img.seek(off); return img.read(n)
+
+def inject(va, pa, ln=0x1000, ap=1):
+ cmd = "echo 0x%x,0x%x,0x%x,%d > %s" % (va, pa, ln, ap, PARAM)
+ r = subprocess.run(["sudo", "sh", "-c", cmd], capture_output=True, text=True)
+ if r.returncode == 0:
+ log("INJECT va=0x%x -> pa=0x%x" % (va, pa))
+ return True
+ log("inject FAIL rc=%d %s" % (r.returncode, r.stderr.strip()))
+ return False
+
+def find_qpair(a, b):
+ img.seek(0, 2); fsz = img.tell()
+ pat = struct.pack("<QQ", a, b)
+ out = []
+ CH = 64 << 20
+ pos = 0
+ while pos < fsz:
+ img.seek(pos); d = img.read(min(CH, fsz - pos))
+ st = 0
+ while True:
+ i = d.find(pat, st)
+ if i < 0: break
+ st = i + 1
+ gpa = off2gpa(pos + i)
+ out.append(gpa & ~0xfff)
+ pos += CH
+ return out
+
+def main():
+ log("=== resolver5 start (watch=%ds) ===" % WATCH_S)
+ injected = set()
+ deadline = time.time() + WATCH_S
+ while time.time() < deadline:
+ try:
+ push_pages = set(find_qpair(PUSH_VA, 0x20000))
+ ring_pages = set(find_qpair(RING_VA, 0x40000))
+ except Exception as e:
+ log("scan error:", e); time.sleep(2); continue
+ for page in sorted(push_pages & ring_pages):
+ d = rd(page, 0x1000)
+ if not d: continue
+ # collect 0x20000 descs in this page
+ cands = []
+ for o in range(0, 0x1000 - 24, 8):
+ sz, fl = struct.unpack_from("<QQ", d, o)
+ if sz == 0x20000 and fl == 0x03008000:
+ pa = struct.unpack_from("<Q", d, o + 16)[0]
+ if 0x100000000 <= pa < 0x180000000:
+ cands.append((o, pa))
+ for (o, pa) in cands:
+ key = (page, o)
+ if key in injected: continue
+ pd = rd(pa, 0x40)
+ nz = sum(1 for b in pd if b) if pd else -1
+ log("page=0x%x desc@0x%x pa=0x%x live_nz=%d head=%s"
+ % (page, o, pa, nz, pd.hex()[:64] if pd else "?"))
+ if inject(PUSH_VA, pa):
+ injected.add(key)
+ time.sleep(1)
+ log("=== resolver5 done: %d injection(s) ===" % len(injected))
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())