β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4285

midi_init() error paths: lockuninit() on held locks (KKASSERT panic) and m->synth leaked on every destruction path

Summary

err2 is reached with m->lock and m->qlock held and immediately lockuninits both - kern_lock KKASSERTs the lock is unheld -> attach-time panic on MPU-driver init failure. Additionally m->synth is never freed: err1 kfrees only m, and midi_destroy also leaks it on every successful teardown. Release-before-uninit + free-synth fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4285 Β· 5 files
FileTypeDescriptionSize
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
fix.diff β€” 572 B view raw
manifest.json β€” 756 B view raw
verdict.json β€” 2.3 KB view raw

DF-4285 β€” midi_init() error paths: lockuninit() on held locks (KKASSERT panic) + m->synth leak

What

Two defects on the error paths of midi_init() [sys/dev/sound/midi/midi.c:273-385]:

  1. lockuninit on held locks β†’ panic. m->lock and m->qlock are acquired at midi.c:325-326 before the failure points that jump to err2 (the MIDIQ_BUF sanity check at 343-345 and, realistically, MPU_INIT(m, cookie) failure at 354-355). err2 [371-373] then calls lockuninit(&m->qlock); lockuninit(&m->lock) while both locks are still held. DragonFly's lockuninit() KKASSERTs the lock is not held [sys/kern/kern_lock.c:1325-1336: KKASSERT((count & (LKC_EXREQ|LKC_UPREQ)) == 0 && ((count & LKC_SHARED) || (count & LKC_SMASK) == 0))] β†’ kernel panic on an MPU-driver init failure at attach time (e.g. flaky emu10kx UART).

  2. m->synth leak. m->synth is allocated at midi.c:311, but every destruction/error path frees only m: err2 β†’ err1 kfree(m, M_MIDI) [380], and β€” beyond the init path β€” midi_destroy() [1432-1436] also never frees m->synth. Every failed init and every successful midi_uninit/midi_destroy leaks sizeof(struct synth_midi) + kobj bookkeeping.

Trigger

Driver-internal error path: requires a midi-registering driver whose MPU_INIT fails at attach (in-tree: snd_emu10kx) for (1); any midi device destroy for (2). Not triggerable on the audit guest (no such hardware). Source-certain; hardware-gated β†’ filed Low.

Fix

fix.diff β€” release both locks before lockuninit on err2; free m->synth on err1 (covers err2 fallthrough as well).

No runtime PoC exists for this finding (error path is hardware-gated); pack documents the source-level proof.

VERDICT.md
↓ download raw

VERDICT β€” DF-4285

Status: inconclusive (source-certain defect; error path unreachable on this guest) Impact: attach-time kernel panic (KKASSERT in lockuninit) + small kernel heap leak Confidence: certain (defect), speculative (real-world trigger)

Proof chain

  • midi.c:325-326: lockmgr(&m->lock, LK_EXCLUSIVE); lockmgr(&m->qlock, LK_EXCLUSIVE);
  • midi.c:343-345 and 354-355: goto err2 reached with both locks held (the MIDIQ_BUF check 343-345 is effectively unreachable with M_WAITOK allocations, but MPU_INIT(m, cookie) failure at 354 is a normal hardware-driver error return).
  • midi.c:371-373 (err2): lockuninit(&m->qlock); lockuninit(&m->lock);
  • sys/kern/kern_lock.c:1325-1336: lockuninit() contains KKASSERT((count & (LKC_EXREQ | LKC_UPREQ)) == 0 && ((count & LKC_SHARED) || (count & LKC_SMASK) == 0)) β€” a held (exclusive) lock has LKC_SMASK owner bits set β†’ assertion fails β†’ panic.
  • Leak: midi.c:311 m->synth = kmalloc(...); freed nowhere β€” err1 kfree(m, M_MIDI) [380] and midi_destroy() [1432-1436] free only m (grep confirms no kfree(m->synth) anywhere in the tree).

Why not runtime-verified

The only in-tree midi_init caller chain is snd_emu10kx β†’ mpu401_init β†’ midi_init (emu10kx-midi.c:201, mpu401.c:185). The audit guest has no EMU10Kx PCI device (QEMU has no model for it), so no MPU_INIT can ever run, let alone fail, there. Faking it would require modifying the read-only sys/ tree / out-of-tree module, out of audit scope. The defect is certain from source; only its real-world firing is hardware-dependent.

Fix

fix.diff: release both locks before lockuninit on err2, and kfree(m->synth, M_MIDI) on err1 (err2 falls through into err1, covering both). No kernel build validation for the same hardware reason.

Bottom line

Genuine error-path defect pair (panic + leak) in midi_init, certain at source level, gated on midi hardware attach failure β€” Low severity, defense-in-depth value.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Not testable: the failing MPU_INIT attach path cannot be staged on this guest (no emu10kx hardware). fix.diff is a minimal, self-evident error-path correction.

source-level: fix.diff vs midi.c:371-380
↓ fix.diffper-fix-DF-4285

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md proof chain: midi.c:325-326 (locks acquired) vs midi.c:354-355,371-373 (err2 lockuninit with locks held) vs kern_lock.c:1325-1336 (KKASSERT); grep shows no kfree(m->synth) anywhere in sys/

PoC changes

no seed; no runnable PoC possible (hardware-gated error path)

Verified recommended fix

release both locks before lockuninit on err2 and kfree(m->synth) on err1 (fix.diff)

Verdict

midi_init() error paths are defective with certainty at source level: err2 (reached with m->lock and m->qlock held after MPU_INIT failure) calls lockuninit() on both locks, which KKASSERTs they are unheld (kern_lock.c:1330-1335) -> panic; and m->synth (allocated at midi.c:311) is never freed on err1/err2 nor in midi_destroy() -> leak. Not runtime-verifiable on this guest: the error path requires an MPU driver attach failure, and the only in-tree midi_init caller is snd_emu401/emu10kx hardware which QEMU cannot emulate.