{
  "finding_id": "DF-5037",
  "status": "reproduced",
  "reproduced": true,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "dm_pdev_insert() holds the global dm_pdev_mutex EXCLUSIVE across vn_open()/VOP_GETATTR()/dev_dioctl() (dm_pdev.c:128-182); with dm-on-dm stacking vn_open re-enters dm via dmopen()->dm_dev_lookup() and needs dm_dev_mutex, while dm_dev_remove()/dm_dev_remove_all() hold dm_dev_mutex across disable_dev()s ref-drain cv_wait and across dm_table_destroy()->target dtor->dm_pdev_decr()->dm_pdev_mutex: a textbook AB-BA inversion, root-triggerable with two ioctls (reload with a dm backing vs remove/remove_all). Reproduced on the stock guest: both workers froze in D state on the complementary locks (wchan dmdevlis vs dm_dev) for 84+ seconds of captured snapshots, all dm ioctls (dmsetup canaries) wedged behind dm_dev_mutex, unkillable, reboot required. Fixed by narrowing dm_pdev_mutex to lookup/insert (re-check after vn_open, loser closes its vnode) and by moving the ref-drain wait and device destruction out of dm_dev_mutex in dm_dev.c; the rebuilt kernel survived the identical duel (two runs, ~4.5k ioctls/s collision rate) with zero wedges and correct dm-on-dm I/O.",
  "exploit_chain": "root: dmsetup-equivalent ioctls on /dev/mapper/control. (1) create pbase + load linear table over a raw disk + RESUME it (resume is required: until dm_dev_resume_ioctl->dmsetdiskinfo initializes the disk info the disk layer rejects opens of the node with ENXIO). (2) create stk. (3) P1: hammer reload(stk) with a linear table backed by /dev/mapper/pbase \u2014 each reload holds a dm_dev busy ref on stk for the whole ioctl and inside it takes dm_dev_mutex twice via vn_close(pbase)->dmclose->dm_dev_lookup and vn_open(pbase)->dmopen->dm_dev_lookup. (4) P2: hammer remove(stk) \u2014 dm_dev_remove_ioctl unbusies then dm_dev_remove takes dm_dev_mutex and disable_dev cv_waits for ref_cnt==0 still holding dm_dev_mutex. Interleave: P2 sleeps holding dm_dev_mutex waiting for P1s busy ref; P1 sleeps in dmclose/dmopen->dm_dev_lookup waiting for dm_dev_mutex. Permanent kernel deadlock of the whole dm subsystem; any I/O to any dm device hangs; unkillable D-state threads; reboot required.",
  "evidence": "run.log (full serial console of the wedged run: 42 consecutive 2s ps snapshots with 886 D0 dmdevlis dm_abba / 887 D3 dm_dev dm_abba / two wedged dmsetup canaries); ps_detail.txt (process tree incl. the parents wedged deadlock-detector canary); run_patched.log (fix validation: counters race, clean exits, dm-on-dm dd I/O + removes OK on the patched kernel); panics_observed.txt (pre-empting known-bug manifestations on the same surface: DF-4474 leak->malloc-limit panic, DF-2443/2447-adjacent devfs sysref/devstat panics, ENXIO-until-resume gotcha); VERDICT.md (full narrative with path:line lock-order proof).",
  "kernel_refs": [
    "sys/dev/disk/dm/dm_pdev.c:128",
    "sys/dev/disk/dm/dm_pdev.c:143",
    "sys/dev/disk/dm/dm_pdev.c:168",
    "sys/dev/disk/dm/dm_pdev.c:249",
    "sys/dev/disk/dm/dm_dev.c:105",
    "sys/dev/disk/dm/dm_dev.c:66",
    "sys/dev/disk/dm/dm_dev.c:308",
    "sys/dev/disk/dm/dm_dev.c:326",
    "sys/dev/disk/dm/device-mapper.c:200",
    "sys/dev/disk/dm/dm_ioctl.c:711",
    "sys/dev/disk/dm/dm_table.c:136",
    "sys/dev/disk/dm/linear/dm_target_linear.c:67"
  ],
  "poc_changes": "Rewrote the seeded approach entirely into a raw NETBSD_DM_IOCTL proplib client: struct plistref mirror (guest lacks sys/plistref.h), prop_dictionary_sendrecv_ioctl(dict, fd, cmd, &out) argument order, version array [4,16,0]. Three decisive discoveries vs the naive PoC: (1) the guests remove-hammer trips the known DF-2443/2447 remove-path UAF (devstat_remove_entry NULL deref) unless remove/recreate is serialized with pacing; (2) kHz churn whose reloads fail ENOENT feeds the known DF-4474 prop leak into the per-malloc-type limit (RAM/10) and panics the guest in ~16-100s; (3) a dm node is unopenable (ENXIO from the disk layer) until dm_dev_resume_ioctl->dmsetdiskinfo runs, so the backing dm device MUST be create+load+resumed or every stacked pdev vn_open fails \u2014 which silently turns the deadlock PoC into (2). With those tamed, the deterministic duel (reload-vs-remove of a stacked device) froze the kernel on the first properly configured run.",
  "attempts": 8,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (fix-validation kernel: #1: Sat Sep 26 08:14:53 UTC 2026)",
  "runtime_sec": 5400,
  "guest_dirty": 0,
  "build_cmd": "in-guest: cc -O2 -Wall -I/tmp/propinc -o dm_abba dm_abba.c -lprop -lpthread (proplib headers shipped from sys/libprop); fix kernel: make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC",
  "run_cmd": "nohup /tmp/guest_duel.sh 90  (duel: ./dm_abba duel /dev/vbd0 90 with console ps-watcher + dmsetup canary loops)",
  "code_hash": "0bf447097299f8c730e5269848b7b19df3eecfc9042f75eb2cbc173799c92a38",
  "notes": "Privilege bar: /dev/mapper/control is 0640 root:operator (group read-only), so all dm ioctls require root \u2014 same bar as the other dm-family findings (DF-2450 Medium precedent), hence Medium. First-ever manifestation (before instrumentation) additionally blocked new ssh sessions (devfs/pty cascade), i.e. impact can exceed the dm subsystem. Pre-empting manifestations on the same surface corroborate known findings DF-4474 and DF-2443/DF-2447 (panics_observed.txt) and include a devfs sysref_get-on-PUTAWAY assertion panic (reference_dev via v_associate_rdev during devfs nresolve racing destroy_dev) worth tracking under the dm_dev remove-lifecycle family. The ENXIO-until-resume behavior of unresumed dm nodes is a functional quirk every raw-ioctl dm reproducer must know.",
  "recommended_fix": "Do not hold dm_pdev_mutex across vn_open/VOP_GETATTR/dev_dioctl in dm_pdev_insert (lookup/insert under lock only, re-check after open, loser closes its vnode), and do not hold dm_dev_mutex across the ref-drain cv_wait or device destruction in dm_dev_remove/dm_dev_remove_all (wait/destroy outside the lock).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "Applied fix.diff to the guests /usr/src, rebuilt (make nativekernel) and installed the kernel, rebooted, re-ran the identical duel twice (90s + 60s at ~4.5k ioctls/s collision rate): no deadlock, no freeze, workers exit cleanly, dm-on-dm create/dd-I/O/remove all functional. Baseline (unpatched) wedged permanently within one 90s run (42 frozen ps snapshots in run.log).",
  "fix_evidence": "run_patched.log; VERDICT.md Fix and validation section; fix.diff"
}