DF-2533 / verdict.json
{ "finding_id": "DF-2533", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Both defensive paths of the real xa_close (verbatim sha256-gated xdisk.c compiled into the guest kernel) are defective exactly as claimed. Path si_drv1==NULL (xdisk.c:769-770) returns ENXIO still holding the exclusive xdisk_lk: observed lockstatus=2 after return and a probe thread running the real xa_open stuck forever on the leaked lock, wedging every later xdisk/dmsg operation (permanent subsystem DoS). Path sc->terminating (xdisk.c:771-773) releases sc->lk, a lock that was never acquired, while still holding xdisk_lk: observed the unconditional kern_lock.c:768 panic (lockmgr: LK_RELEASE: no lock held) with backtrace lockmgr_release <- xa_close, on the stock INVARIANTS kernel and on any kernel since the check is not ifdef-gated. Production reachability of the NULL path is real (D_TRACKCLOSE per-close dispatch in devfs plus terminate_check freeing under xdisk_lk); the terminating branch is currently dead-defensive but is a booby trap. Escalation ceiling honestly quantified: panic / permanent subsystem wedge, no memory-corruption primitive, no uid=0 chain. Fix mirrors the xa_open pattern; A/B validated on a rebuilt kernel: lock released, no panic, probe completes.", "exploit_chain": "Unprivileged holder of a root-created /dev/xa%d node close(2)s concurrently with span-loss teardown: a first closer (or the xa_open BLK_OPEN error unwind) drives opencnt to 0 so xa_terminate_check frees the softc and NULLs si_drv1 under xdisk_lk; D_TRACKCLOSE makes devfs dispatch d_close for every close, so the raced second close enters xa_close, reads si_drv1==NULL and returns holding xdisk_lk exclusively and permanently: every subsequent xdisk_open/xdisk_close/xdisk_ioctl/xa_open/xa_close/xaio_rcvdmsg blocks forever (xaopen/xawait threads pile up; root cannot even attach or detach anymore). The terminating variant instead panics the node instantly (lockmgr LK_RELEASE of a never-held lock). Impact ceiling: local DoS / kernel panic.", "evidence": "run.log (lockstatus=2 LEAKED + probe STUCK FOREVER lines), panic.txt (panic: lockmgr: LK_RELEASE: no lock held; xa_close+0x183 backtrace; sc->lk.lk_count=0x0 printed pre-call), run.patched.log (lockstatus=0, probe finished=1, no panic), fix_build.log, sha256_gate.log, env.txt", "kernel_refs": [ "sys/dev/disk/xdisk/xdisk.c:767", "sys/dev/disk/xdisk/xdisk.c:769", "sys/dev/disk/xdisk/xdisk.c:772", "sys/dev/disk/xdisk/xdisk.c:703", "sys/kern/kern_lock.c:768", "sys/vfs/devfs/devfs_vnops.c:1170" ], "poc_changes": "Authored the harness fresh (no seed): includes verbatim xdisk.c with pre-include #define interposition of make_dev/destroy_dev/dev_ops_remove_all/disk_destroy/devstat_remove_entry; fabricated kmalloc cdev + xa_softc; sysctl triggers call the real xa_close/xa_open; lockstatus probe + real-xa_open watchdog thread prove the leak; kernel-compiled via conf/files because runtime kldload GPFs on this guest. Two harness-only fixes during bring-up: dropped static on interposed stubs (-Werror prototype mismatch) and nothing else; the DF-2534 half of the harness needed periodic tsleep (see DF-2534 verdict).", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #2: Mon Oct 19 20:55:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 5400, "guest_dirty": 0, "build_cmd": "cd findings/poc/DF-2533 && ./build.sh (sha256 gate 58b0ed1672b5fb63f7d484a7fae6ba04484372c962e13d9c573b69790cfa55c9; scp harness; conf/files entry dev/disk/xdisk/df253x_harness.c standard; make -j6 nativekernel KERNCONF=X86_64_GENERIC; make installkernel; reboot)", "run_cmd": "dfbsd-qemu/vm.sh run_root sysctl -w debug.df2533_close_null=1 ; dfbsd-qemu/vm.sh run_root sysctl -w debug.df2533_close_term=1 (panics; capture via vm.sh log); patched build: same triggers, all clean", "code_hash": "dd2289882cb7b55b3f57fdd81c548be2ef1ad7add4aa04ed6e811419aaaee03c", "notes": "The wrong-lock panic is not INVARIANTS-gated (kern_lock.c:768 unconditional), so production kernels panic too. The lock-leak path was reproduced 3 times across boots with identical results. Combined fix validation kernel (both DF-2533+DF-2534 diffs) sha256 15d189799d499d96ee6bbb5796ef3b3c5e316fce06d15507a47776119ae43385 reproduced bit-exact on the host from the two fix.diff files.", "recommended_fix": "In xa_close, release xdisk_lk (not sc->lk) on both raced-destruction early-return paths, mirroring xa_open:703-715." } |