DF-5368 / umsrace.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 | #!/usr/bin/env python3 """ums unplug race orchestrator (DF-5368 / DF-5369). Host-side: spams QEMU usb-mouse motion via the monitor socket, then device_del's the mouse while a guest-side root process holds the ums0 evdev node (/dev/input/eventN) open — which is exactly the state where ums_ev_open() started the intr xfer WITHOUT any /dev/ums0 fifo open. ums_detach() then runs usb_fifo_detach() first: with the fifo never opened, usb_fifo_close() returns at usb_dev.c:779 WITHOUT stopping the xfer, and f_sc->fp[USB_FIFO_RX] is NULLed (usb_dev.c:2032) while the intr xfer is still live. The next delivered report makes ums_intr_callback() call usb_fifo_put_bytes_max(NULL) / ..._linear(NULL) (ums.c:371 / ums.c:922) -> NULL page deref -> panic. usage: umsrace.py <cycles> [--holdfifo] (default: hold evdev node) """ import socket, subprocess, sys, threading, time SOCK = "dfbsd-qemu/boot-mon.sock" SSH = ["ssh", "-F", "dfbsd-qemu/config", "-o", "ConnectTimeout=8", "-o", "BatchMode=yes", "dfbsd"] # root: holder needs event node class Mon: def __init__(self): self.s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.s.connect(SOCK) self.s.settimeout(0.25) try: self.s.recv(65536) except Exception: pass def cmd(self, c, wait=0.6): self.s.sendall((c + "\n").encode()) time.sleep(wait) out = b"" try: out = self.s.recv(65536) except Exception: pass return out.decode(errors="replace") def spam(self, n=40): # queue a burst of relative motion events; each becomes one # interrupt-IN data delivery at the device poll interval self.s.sendall((b"mouse_move 3 3\n" * (n // 2) + b"mouse_move -3 -3\n" * (n // 2))) time.sleep(0.05) try: while True: if not self.s.recv(65536): break except Exception: pass class Spam(threading.Thread): """Continuously feed relative motion events during the detach so interrupt-IN reports keep completing while ums_detach() runs.""" def __init__(self, m): super().__init__(daemon=True) self.m = m self.run_flag = True def run(self): i = 0 while self.run_flag: self.m.s.sendall(b"mouse_move 4 4\n" if (i := i + 1) % 2 else b"mouse_move -4 -4\n") try: self.m.s.recv(65536) except Exception: pass time.sleep(0.002) # drain monitor output time.sleep(0.05) try: while True: if not self.m.s.recv(65536): break except Exception: pass def guest_ok(): return subprocess.run(SSH + ["true"], capture_output=True).returncode == 0 def guest_run(sh): return subprocess.run(SSH + ["/bin/sh"], input=sh + "\n", capture_output=True, text=True, timeout=60) def evdev_node(): r = guest_run("sysctl -a 2>/dev/null | grep -E " "'kern\\.evdev\\.input\\.[0-9]+\\.name: QEMU QEMU USB Mouse' | " "tail -1 | cut -d. -f4 | cut -d: -f1") n = r.stdout.strip() return ("/dev/input/event%s" % n) if n else None def main(): cycles = int(sys.argv[1]) if len(sys.argv) > 1 else 40 holdfifo = "--holdfifo" in sys.argv # baseline B: hold /dev/ums0 instead m = Mon() down = 0 for i in range(cycles): # (re)configure if needed: set_config 1 re-attaches ums after a # previous set_config 0 (device itself stays attached all along) st = guest_run("ls /dev/ums0 2>/dev/null") if st.returncode != 0 or not st.stdout.strip(): guest_run("usbconfig -u 0 -a 2 set_config 1") time.sleep(2.0) node = evdev_node() if not holdfifo else "/dev/ums0" if not node: print("cycle %d: no evdev node (dead?)" % i) time.sleep(2) continue # arm holder (root): holds the node open -> xfer running, no fifo guest_run("pkill -x holdopen 2>/dev/null; sleep 0.2; " "/root/holdopen %s </dev/null >/tmp/hold.err 2>&1 & " "sleep 0.5; cat /tmp/hold.err" % node) sp = Spam(m) sp.start() time.sleep(0.35) # ensure reports are flowing # software detach of the interface driver while the device is # still attached and delivering interrupt-IN reports guest_run("usbconfig -u 0 -a 2 set_config 0") time.sleep(1.5) # let the guest process the detach under load sp.run_flag = False sp.join(3) if not guest_ok(): print("GUEST DOWN at cycle %d (held %s)" % (i, node)) down += 1 break if (i + 1) % 5 == 0: print("cycle %d ok" % (i + 1)) time.sleep(0.8) print("done: down=%d" % down) if __name__ == "__main__": main() |