DF-4284 / verdict.json
{ "finding_id": "DF-4284", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "midi_unload() iterates midi_devs with TAILQ_FOREACH whose body calls midi_destroy(), which TAILQ_REMOVEs and kfrees the current entry; the loop advance reads TAILQ_NEXT(m) from freed memory (UAF read) and, with slab reuse between free and advance, operates on a groom-controlled pointer (lockmgr(&m->lock)/m->busy). Demonstrated by a verbatim-TAILQ harness on the guest: reuse mode reads forged tqe_next 0x414141414140 from the freed slot and faults at exactly 0x414141414150; fixed semantics (stash next before destroy) traverse all devices cleanly. Root-gated (kldunload) and hardware-gated (needs >=1 registered midi device, i.e. snd_emu10kx) so no live kernel trigger on this guest.", "exploit_chain": "root: kldunload sound with >=1 midi device -> midi_unload TAILQ_FOREACH -> midi_destroy(m) kfrees m -> loop advance reads m->link.tqe_next from freed M_MIDI slot (groomable via same-zone kmalloc churn) -> next iteration lockmgr(&m->lock) on attacker-influenced pointer -> panic/arbitrary kernel-memory access during privileged unload", "evidence": "run.log: [groom] freed slot reoccupied; forged link.tqe_next = 0x414141414140 then [reuse] signal 11 at 0x414141414150 (exit 99); [fixed] clean: all devices destroyed, no UAF (exit 0)", "kernel_refs": [ "sys/dev/sound/midi/midi.c:1469-1477", "sys/dev/sound/midi/midi.c:1418-1438", "sys/dev/sound/midi/midi.c:1429", "sys/dev/sound/midi/midi.c:1436" ], "poc_changes": "harness written from scratch; iteration 1 missed TAILQ_INIT define (compile fix); iteration 2 used a non-canonical forged pointer (SIGBUS before handler) -> switched to canonical 0x414141414140 and added SIGBUS handling + fflush", "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": 60, "guest_dirty": 1, "build_cmd": "sh build.sh (cc -O0 -g -Wall -o midi_unload_model midi_unload_model.c)", "run_cmd": "sh run.sh", "code_hash": "9686798dfe084ea8d3660013b9d6c4c6bb865098df8ecbfa406160481228cd79", "notes": "Model-level reproduction of the UAF read semantics (verbatim TAILQ macros). Adjacent latent defect documented, not fixed by minimal diff: dead exit1 path (midi.c:1488-1489) would re-release m->lock after midi_destroy freed it (unreachable today because midi_destroy always returns 0).", "recommended_fix": "stash TAILQ_NEXT(m, link) before midi_destroy(m, 1) in the midi_unload loop (fix.diff)", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Validated at harness level: with the stash-next loop shape (fix.diff) all devices are visited and destroyed cleanly (exit 0) while the baseline faults through the freed-slot pointer (exit 99). Kernel-level patched build not exercisable on this guest (no midi devices can register).", "fix_evidence": "run.log: [fixed] fixed midi_unload(): stash next before destroy + [fixed] clean: all devices destroyed, no UAF exit=0 vs [reuse] signal 11 at 0x414141414150 exit=99" } |