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

NULL-pointer dereference / local kernel panic via getsockopt on RFCOMM socket with unknown option (rfcomm_socket.c:121-126)

Summary

Root cause in caller rfcomm_socket.c:121-126 (surfaced via rfcomm_getopt API contract). rfcomm_getopt (rfcomm_upper.c:562-566) returns 0 for unknown option names (not SO_RFCOMM_MTU=1/FC_INFO=2/LM=3). Caller rfcomm_ctloutput PRCO_GETOPT: m=m_get(M_WAITOK) :117 m->m_len=rfcomm_getopt(pcb,sopt_name,mtod(m)) :119 if(m->m_len==0){m_freem(m);m=NULL;error=ENOPROTOOPT;} :121-124 NO break NO goto out falls through to sopt_from_kbuf(sopt,mtod(m,void*),m->m_len) :126 m==NULL -> mtod(NULL) loads offsetof(mbuf,m_data) small address unmapped zero page -> kernel page fault -> PANIC. Trigger: any unprivileged local user socket(AF_BLUETOOTH,SOCK_STREAM,BTPROTO_RFCOMM) then getsockopt(fd,BTPROTO_RFCOMM,99,...) instant panic 100% reliable. No Bluetooth hardware needed. rfcomm_sattach no priv_check. Fix: add break after m_freem in rfcomm_socket.c:124.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0680 Β· 10 files
FileTypeDescriptionSize
trigger.c trigger-source minimal socket+getsockopt trigger 2.3 KB view raw
build.sh build-script cc -Wall -o trigger trigger.c 106 B view raw
run.sh run-script ./trigger 117 B view raw
VERDICT.md verdict full narrative: reproduced? mechanism? fix? validation? 3.5 KB ↓ raw
run.log run-log baseline decisive run with full panic signature 882 B view raw
panic.txt panic-signature Fatal trap 12 at rfcomm_ctloutput.cold.9+0x8 651 B view raw
fix_run.log run-log patched-kernel run: ENOPROTOOPT, no panic 556 B view raw
fix.diff suggested-fix git-apply-able: add break; after ENOPROTOOPT + m_freem on success 323 B view raw
fix_build.log build-log single-fix-combined kernel build (5 fixes incl. this one) 5.6 MB ↓ download
env.txt environment uname, cc version, sysctls, modules 339 B view raw
VERDICT.md verdict full narrative: reproduced? mechanism? fix? validation?
↓ download raw

DF-0680 β€” RFCOMM rfcomm_ctloutput PRCO_GETOPT missing-break NULL-deref panic

Verdict

REPRODUCED on baseline 6.5-DEVELOPMENT #0 (build Thu Jul 2 06:02:54 UTC 2026). FIXED on single-fix-combined kernel 6.5-DEVELOPMENT #1 (build Sun Jul 19 06:56:50 UTC 2026, sha256 32765f70…).

Mechanism (trigger β†’ primitive β†’ effect)

sys/netbt/rfcomm_socket.c:115-127 rfcomm_ctloutput case PRCO_GETOPT:

m = m_get(M_WAITOK, MT_DATA);                 // :117  m != NULL
crit_enter();
m->m_len = rfcomm_getopt(pcb, sopt->sopt_name, mtod(m, void *)); // :119
crit_exit();
if (m->m_len == 0) {                          // :121  unknown opt -> 0
    m_freem(m);                                // :122
    m = NULL;                                  // :123
    error = ENOPROTOOPT;                       // :124
}                                              // *** MISSING break ***
sopt_from_kbuf(sopt, mtod(m, void *), m->m_len); // :126  mtod(NULL) deref

rfcomm_getopt (sys/netbt/rfcomm_upper.c:540-566) returns 0 for any option name other than SO_RFCOMM_MTU=1 / SO_RFCOMM_FC_INFO=2 / SO_RFCOMM_LM=3. With m_len == 0, the code frees m, sets m = NULL, sets error = ENOPROTOOPT, and falls through to sopt_from_kbuf(sopt, mtod(m, void *), m->m_len). mtod(m, void *) is ((struct mbuf *)m)->m_data; with m == NULL this reads at offset offsetof(struct mbuf, m_data) == 0x18 of the NULL page β†’ kernel page fault β†’ panic.

rfcomm_sattach (sys/netbt/rfcomm_socket.c:302-329) performs no privilege check (priv_check/suser), so any unprivileged local user can create the socket. The only precondition is netbt.ko loaded (realistic on Bluetooth- enabled systems).

Trigger

socket(AF_BLUETOOTH=33, SOCK_STREAM, BTPROTO_RFCOMM=3)
getsockopt(fd, BTPROTO_RFCOMM, 99 /* unknown */, &buf, &len)
                            ^^^^^^^^^^^^^^^^^^ any value other than 1/2/3

β†’ deterministic kernel panic, 100 % reliable, no Bluetooth hardware needed.

Panic signature (baseline)

Fatal trap 12: page fault while in kernel mode
fault virtual address    = 0x18
fault code               = supervisor read data, page not present
Stopped at      rfcomm_ctloutput.cold.9+0x8:    movl    0x18,%eax
db>

0x18 is exactly offsetof(struct mbuf, m_data) β€” the NULL+offset dereference from mtod(NULL).

Realistic impact ceiling

This is a deterministic local kernel panic / DoS. The fault is a read of the NULL page (not a write), so there is no memory-corruption primitive β€” no escalation chain is possible. Once netbt.ko is loaded, any local user can panic the machine instantly and repeatedly.

Precondition: netbt.ko loaded (root action; realistic on Bluetooth-enabled systems). No kldload by the attacker is required.

Fix

sys/netbt/rfcomm_socket.c: add break; after the ENOPROTOOPT block so the function returns the error instead of falling through into the sopt_from_kbuf(mtod(NULL)) deref. Also m_freem(m) the buffer on the success path (the original code transferred bytes into sopt but never freed m, leaking it β€” a real but minor leak).

See fix.diff (git-apply-able).

Fix validation (Phase 8)

  • Baseline (with-src #0, unpatched): ./trigger β‡’ Fatal trap 12 at rfcomm_ctloutput.cold.9+0x8: movl 0x18,%eax β‡’ guest DOWN (DDB).
  • Patched (#1, all 5 fixes incl. this one): ./trigger β‡’ getsockopt returned -1 (errno=42 Protocol not available) (i.e. ENOPROTOOPT, exactly what the fixed path returns), guest STAYS UP.

Clean before/after. Fix closes the bug.

Fix verification

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Sun Jul 19 06:56:50 UTC 2026

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (live panic). rfcomm_ctloutput missing break -> mtod(NULL) fault 0x18. netbt.ko loaded. Unprivileged once loaded.