DF-4926 / verdict.json
{ "finding_id": "DF-4926", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED twice (identical crash site) and FIX VALIDATED dynamically. nvmm_dragonfly.c tears down the hypervisor BEFORE killing the device: nvmm_detach() checks nmachines unsynchronized (:369) and runs nvmm_fini() (:372) \u2014 lockuninit of all 128x128 machine/vcpu locks, impl->fini(), nvmm_impl=NULL \u2014 while /dev/nvmm is still dispatching ioctls; destroy_dev() only runs afterwards (:404). An unprivileged group-nvmm user storming NVMM_IOC_CAPABILITY (~1.1M/s) + machine create/destroy cycles (~20k/s) against a root kldunload panicked the stock guest twice at nvmm_ioctl+0x444: movq 0x48(%rax),%rax (rax = nvmm_impl = NULL). With fix.diff (open-count gate + destroy_dev-first ordering), the identical race produced 40/40 EBUSY, zero panics over 36M ioctls / 684k machine cycles, and a clean final unload.", "exploit_chain": "", "evidence": "panic.txt (two baseline serial-console crash excerpts, both Stopped at nvmm_ioctl+0x444: movq 0x48(%rax),%rax, guest down); run.log (racer calibration: 1.1M cap ioctls/s, 20k machine cycles/s as uid 1002); race_driver_fixed.log + run_fixed_racer.log + fix_run.log (patched module: 40/40 EBUSY, no panic, clean final unload); fix_build.log (patched module build)", "kernel_refs": [ "sys/dev/virtual/nvmm/nvmm_dragonfly.c:325", "sys/dev/virtual/nvmm/nvmm_dragonfly.c:366", "sys/dev/virtual/nvmm/nvmm_dragonfly.c:369", "sys/dev/virtual/nvmm/nvmm_dragonfly.c:372", "sys/dev/virtual/nvmm/nvmm_dragonfly.c:399", "sys/dev/virtual/nvmm/nvmm_dragonfly.c:404", "sys/dev/virtual/nvmm/nvmm.c:994", "sys/dev/virtual/nvmm/nvmm.c:1005", "sys/dev/virtual/nvmm/nvmm.c:245", "sys/kern/kern_conf.c:346" ], "poc_changes": "No executable seed (fresh racer + driver authored). Fixes made while bringing it up: (1) su -m executes -c via roots csh -> Ambiguous output redirect; replaced with a #!/bin/sh wrapper script; (2) racer opens /dev/nvmm O_RDONLY \u2014 0640 gives group read-only and DFly ioctls need no FWRITE (O_RDWR got EACCES); (3) driver tolerant of an already-loaded module.", "attempts": 3, "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", "runtime_sec": 1800, "guest_dirty": 0, "build_cmd": "cc -O2 -pthread -o nvmm_race nvmm_race.c (guest, as root; full setup: sh run.sh)", "run_cmd": "sh run.sh (creates testnv, builds racer, kldload nvmm, races 40 unload/reload cycles + final unload; baseline panics, patched returns EBUSY/clean-unload)", "code_hash": "f3a5459309fb97ed183e32d7ea488f64e44d6d7d572eddbdf662e6221c6509f8", "notes": "Requires the guest to see VMX/SVM (QEMU_EXTRA=-cpu host); backend x86-vmx attached. Unprivileged side needs only group nvmm (device 0640 root:nvmm, O_RDONLY suffices for all ioctls). UAF paths (2)/(3) \u2014 machine created between the :369 check and nvmm_fini(), and dtor/kill_machines through unmapped text after a successful unload \u2014 are code-proven but were not needed for the reproduction (path 1 panicked deterministically). Guest reset (with-src) performed after the run.", "recommended_fix": "Count open descriptors, refuse MOD_UNLOAD while any are open (or machines exist), and destroy_dev() BEFORE nvmm_fini() \u2014 see fix.diff.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 1, "fix_verdict": "FIXED dynamically: baseline (stock nvmm.ko) panicked the guest twice at nvmm_ioctl+0x444 under the race; with fix.diff applied to guest /usr/src (module rebuilt, fix_build.log) the identical workload produced 40/40 kldunload EBUSY (open-count gate), zero panics across 36M capability ioctls and 684k machine create/destroy cycles, and a clean final unload after the racers fd closed (race_driver_fixed.log, run_fixed_racer.log, fix_run.log).", "fix_evidence": "fix.diff (git-apply clean); fix_build.log (BUILD_RC=0); fix_run.log (40x EBUSY vs baseline panic, clean final unload, guest up); panic.txt (baseline crashes)" } |