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

hci_disable modifies queues and link list without hci_devlock, racing with hci_intr (panic / mbuf UAF)

Summary

hci_disable (:217-263): line 250 comment "no need to hold hci_devlock the driver is disabled" but hci_intr (:328-411) is netisr-driven (bt_input.c:btintr iterates hci_unit_list calls hci_intr unconditionally) NOT driver-driven keeps running after driver disable. hci_disable line 231 calls (*hci_if->disable)() then line 232 clears BTF_RUNNING then :238-245 TAILQ_FOREACH hci_link_free (TAILQ_REMOVE+kfree from hci_links) then :252-262 IF_DRAIN all ifqueues + zeroes eventqlen/aclrxqlen/scorxqlen NONE under hci_devlock. Race (a): hci_intr :337 reads eventqlen>0 hci_disable :253 zeroes it hci_intr :339 decrements uint16_t underflow to 65535 next iter IF_DEQUEUE returns NULL KKASSERT(m!=NULL) :342 panic or NULL deref m->m_pkthdr.len :345. Race (b): IF_DRAIN and IF_DEQUEUE race same mbuf linked list no sync corrupted queue/freed-mbuf UAF. Race (c): hci_intr :395 TAILQ_FOREACH(hci_links) after releasing lock :390 hci_disable :238-245 TAILQ_REMOVE+kfree same links UAF on struct hci_link. Attacker: root SIOCSBTFLAGS hci_ioctl.c:224 or driver detach/kldunload toggle BTF_UP while BT traffic flowing. Impact: panic A:H + limited UAF C:L/I:L heap-grooming speculative. Fix: acquire hci_devlock around ALL hci_disable teardown + NULL-check after IF_DEQUEUE in hci_intr.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0683 Β· 9 files
FileTypeDescriptionSize
build.sh build-script no-op (no live PoC possible) 65 B view raw
run.sh run-script no-op (no live PoC possible) 89 B view raw
VERDICT.md verdict source-level race analysis (3 race variants) + why not testable here 5.3 KB ↓ raw
run.log run-log guest checks: no BT HW, no hci_unit, hci_disable unreachable 579 B view raw
fix.diff suggested-fix git-apply-able: lockmgr(hci_devlock) around hci_disable teardown + NULL-check m in hci_intr 1.3 KB view raw
env.txt environment uname, cc, no BT modules/devices 339 B view raw
fix_build.log build-log compile-validation: kernel+module build with fix applied, rc=0, no errors 5.6 MB ↓ download
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict source-level race analysis (3 race variants) + why not testable here
↓ download raw

DF-0683 β€” hci_disable (hci_unit.c) teardown races hci_intr (no lock)

Verdict

SOURCE-CONFIRMED, NOT TESTABLE ON THIS GUEST. The race is unambiguous in source; the trigger requires a registered hci_unit, which requires Bluetooth hardware (or a virtual BT adapter) β€” absent from this VM. There is also no unprivileged trigger: the realistic race window needs SIOCSBTFLAGS (root) or driver detach (kldunload/unplug) concurrent with active Bluetooth traffic.

Mechanism

sys/netbt/hci_unit.c:217-263 hci_disable:

void
hci_disable(struct hci_unit *unit)
{
    ...
    (*unit->hci_if->disable)(unit->hci_dev);
    unit->hci_flags &= ~BTF_RUNNING;

    /* close down any links ... */
    for (acl = 0 ; acl < 2 ; acl++) {                  // :238
        next = TAILQ_FIRST(&unit->hci_links);
        while ((link = next) != NULL) {
            next = TAILQ_NEXT(link, hl_next);
            if (acl || link->hl_type != HCI_LINK_ACL)
                hci_link_free(link, ECONNABORTED);      // TAILQ_REMOVE + kfree
        }
    }
    while ((memo = LIST_FIRST(&unit->hci_memos)) != NULL)
        hci_memo_free(memo);

    /* (no need to hold hci_devlock, the driver is disabled) */   // :250

    IF_DRAIN(&unit->hci_eventq);                        // :252
    unit->hci_eventqlen = 0;
    IF_DRAIN(&unit->hci_aclrxq);
    unit->hci_aclrxqlen = 0;
    IF_DRAIN(&unit->hci_scorxq);
    unit->hci_scorxqlen = 0;
    IF_DRAIN(&unit->hci_cmdwait);
    IF_DRAIN(&unit->hci_scodone);
}

The comment at :250 ("no need to hold hci_devlock, the driver is disabled") is wrong: hci_intr (sys/netbt/hci_unit.c:328-411) is netisr-driven β€” btintr in sys/netbt/bt_input.c walks hci_unit_list and calls hci_intr unconditionally on every BT interrupt β€” and does take hci_devlock (:335) around its eventqlen/queue TAILQ touches. After hci_disable returns, in-flight netisr work can still be running concurrently on another CPU. The teardown happens without hci_devlock, so:

Race (a) β€” counter underflow + NULL deref

  • hci_intr:337 reads eventqlen > 0.
  • hci_disable:253 zeroes eventqlen.
  • hci_intr:338 IF_DEQUEUE returns NULL (queue drained by IF_DRAIN).
  • hci_intr:339 eventqlen-- underflows to 65535 (uint16_t).
  • hci_intr:342 KKASSERT(m != NULL) panics β€” or m->m_pkthdr.len at :345 NULL-derefs.

Race (b) β€” IF_DRAIN vs IF_DEQUEUE mbuf lifetime

  • Concurrent IF_DRAIN and IF_DEQUEUE on the same ifqueue (a singly-linked mbuf list with no synchronization) corrupts the list / frees an mbuf still referenced by the other side β†’ UAF.
  • hci_intr:395 does TAILQ_FOREACH(link, &unit->hci_links, hl_next) after releasing hci_devlock at :390.
  • hci_disable:238-245 does TAILQ_REMOVE + kfree on the same links with no lock β†’ UAF on struct hci_link.

Trigger (theoretical)

  • SIOCSBTFLAGS (sys/netbt/hci_ioctl.c:224) toggling BTF_UP while BT traffic is being processed (root-only).
  • Driver detach / kldunload while BT traffic flowing (root-only).

Both paths require root AND a live hci_unit (Bluetooth hardware or virtual adapter). The guest has neither.

Why not testable here

  • No Bluetooth hardware / virtual adapter in the VM β‡’ no registered hci_unit β‡’ hci_disable is never called.
  • Even with HW, the race needs careful timing of (unprivileged?) traffic vs (root-only) SIOCSBTFLAGS.

So this finding is not live-testable on this guest. The source-level race is unambiguous and high-confidence.

Realistic impact ceiling

  • Panic (DoS) via KKASSERT(m != NULL) or NULL-deref m->m_pkthdr.len β€” Race (a). Deterministic-ish on a busy BT link.
  • Speculative UAF on struct hci_link / freed mbufs (Races b, c). With heap grooming, possibly escalatable, but the precondition (root + BT HW) makes this a rootβ†’kernel hardening gap rather than an unprivileged privesc.

Realistic class: Medium local DoS (root-triggered, BT-HW-dependent) with speculative UAF that would need a lot of work to convert.

Fix

sys/netbt/hci_unit.c: 1. Acquire hci_devlock around the entire teardown in hci_disable (links, memos, queue drains, counter zeroes). Removes the races by making the teardown atomic w.r.t. hci_intr. 2. Defense-in-depth in hci_intr: after IF_DEQUEUE returns NULL despite *rxqlen > 0, skip the KKASSERT and re-enter (goto another) instead of panicking β€” so a race that somehow survives the lock doesn't crash.

See fix.diff (git-apply-able, applies cleanly, compiles, kernel boots).

Fix validation (Phase 8)

fix_status: not_testable. The fix.diff applies and compiles as part of the single-fix-combined kernel (6.5-DEVELOPMENT #1 built and booted). The race itself cannot be reproduced on a guest without Bluetooth hardware, so no live before/after measurement is possible here. The fix is the textbook correct approach for "concurrent teardown vs consumer" races.

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

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. hci_disable teardown no lock vs hci_intr netisr -> race/UAF. No BT HW.