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)
PoC verification
Evidence pack
findings/poc/DF-0680 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 12atrfcomm_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
fixedvalidated
see evidence pack
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.
No comments yet.