rfcomm_detach()'s callout_active()/RFCOMM_DLC_DETACH deferred-free machinery is dead code under DragonFly's callout implementation β the documented callout-vs-detach lifetime 'safety net' never engages (misleading guard over the DF-0724 race)
Summary
Every path to the callout_active() test first ran callout_stop(), which on DFly clears CALLOUT_ACTIVE unconditionally at entry and drains in-progress callbacks: RFCOMM_DLC_DETACH can never be set and the timeout's release branch is dead. Not directly exploitable (the unconditional kfree is safe precisely BECAUSE of the drain) but the guard misleadingly appears to handle the DLC timer/teardown race - the genuine race is DF-0724, and anyone relying on the DETACH flag builds on dead code. Rig proves it live (pending=1 active=1 before, both 0 after dlc_close); delete-dead-machinery fix validated. Same dead-deferral class as DF-5739/5563.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5748 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness5748.c | β | 11.8 KB | view raw | |
| l2cap_stub.c | β | 3.5 KB | view raw | |
| Makefile | β | 511 B | β download | |
| build.sh | β | 90 B | view raw | |
| run.sh | β | 167 B | view raw | |
| build.log | β | 10.3 KB | view raw | |
| fix_build.log | β | 973 B | view raw | |
| setup.log | β | 393 B | view raw | |
| run.log | β | 2.1 KB | view raw | |
| fix_run.log | β | 2.9 KB | view raw | |
| env.txt | β | 452 B | view raw | |
| fix.diff | β | 950 B | view raw | |
| VERDICT.md | β | 4.7 KB | β raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 4.5 KB | view raw |
DF-5748 β VERDICT
Status: reproduced (rig, verbatim production code) Β· Impact: none (dead code / misleading lifetime guard) Β· Confidence: certain
What was run
KLD rig rfcomm_up5748.ko: verbatim rfcomm_session.c / rfcomm_dlc.c /
rfcomm_upper.c (md5s in setup.log) + stub L2CAP; remote-peer TS 07.10
frames injected with valid FCS through the exact vector the real L2CAP layer
uses (btproto->input). Trigger sysctl hw.df5748_fire=1:
rfcomm_attach/rfcomm_rcvd/rfcomm_connecttwo DLCs (channels 3, 4 β DLCI 6, 8) β one shared session, initiator;- L2CAP
connectedcallback β SABM(0); injected UA(0) β session OPEN β productionrfcomm_dlc_connect()sends PN for both DLCs; - injected PN responses (mtu 127, CFC) β production path sends SABM(dlci);
injected UA(dlci) β production
rfcomm_dlc_open()(MSC sent, state OPEN); injected MSC responses stop the mcc timers; rfcomm_disconnect()both β state WAIT_DISCONNECT, DISC sent, 20 s ack timer armed by rfcomm_upper.c:252;- production
rfcomm_dlc_close()on A (the exact callrfcomm_detach()makes at rfcomm_upper.c:278-279); - full production
rfcomm_detach()on B; - 22 s sleep β session expiry callout fires β
rfcomm_session_free().
Observed (baseline, run.log)
[DF-5748] dlc A state=6 (6=OPEN), dlc B state=6 (6=OPEN) [DF-5748] disconnect(A) -> 0; state=7 (7=WAIT_DISCONNECT) [DF-5748] disconnect(B) -> 0; state=7 [DF-5748] A timer: pending=1 active=1 (ARMED by rfcomm_disconnect:252) [DF-5748] A after rfcomm_dlc_close(): pending=0 active=0 [DF-5748] DEAD: callout_active() == 0 after rfcomm_dlc_close(); the RFCOMM_DLC_DETACH branch at rfcomm_upper.c:293 is UNREACHABLE [DF-5748] rfcomm_detach(B) returned; B freed via immediate kfree (DETACH flag never taken) === DF-5748 harness done (no crash) ===
The decisive chain of facts, all from production calls:
rfcomm_disconnect()arms the DLC timer (pending=1 active=1) β so the callout machinery is live;rfcomm_dlc_close()β whichrfcomm_detach()always runs first for a non-CLOSED DLC (rfcomm_upper.c:278-279) β leavescallout_active() == 0, because itscallout_stop()(rfcomm_dlc.c:164) clears CALLOUT_ACTIVE unconditionally at entry (sys/kern/kern_timeout.c:869) and drains any in-progress callback (kern_timeout.c:888-921, sync STOP);- no
callout_reset(&dlc->rd_timeout, ...)site is reachable withrd_state == RFCOMM_DLC_CLOSED(all five require non-CLOSED states).
β the if (callout_active(...)) test at rfcomm_upper.c:293 can only see 0,
β RFCOMM_DLC_DETACH is never set, β the kfree() branch in
rfcomm_dlc_timeout() (rfcomm_dlc.c:203-204) is unreachable dead code.
The full production rfcomm_detach() on B completed via the
immediate-kfree path; the session self-expired 20 s later with no crash.
Why it matters
Not directly exploitable β the unconditional kfree() is safe because
callout_stop() drains. The problem is that this dead machinery is
documented as the callout-vs-detach lifetime handler ("If callout is
invoking we can't free the DLC so mark it and let the callout release it")
when it does no such thing under DragonFly's callout implementation; the
genuine race β unsynchronized double-entry into rfcomm_dlc_close() from
the softclock thread vs the socket thread β is DF-0724 and is not
mitigated by this flag. Anyone fixing DF-0724 by "relying on the DETACH
flag" would be building on dead code. NetBSD upstream uses
callout_invoking() + callout_destroy() here instead. Info severity
(dead code / misleading guard; hardening + divergence).
Fix validation (fix.diff, fix_run.log, fix_build.log)
patch -p3 < fix.diff (both hunks: unconditional documented kfree in
rfcomm_detach(); drop the dead release branch in rfcomm_dlc_timeout()),
clean rebuild β zero callout_active references remain in the module β
same trigger: identical full lifecycle, same observations, no crash
(fix_run.log). Removal validated as behavior-preserving cleanup.
fix_status: fixed (baseline_reproduced=1, patched_reproduced=0 β nothing
bad exists to reproduce; lifecycle is unchanged and clean by design of the
cleanup).
Bottom line
Provable dead code demonstrated against verbatim production code on the guest; the deferred-free "safety net" for the DLC timer never engages under DragonFly's callout API; cleanup fix applied and validated as behavior-preserving.
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
fixedfix.diff (two hunks) applied to the verbatim rig copies, clean-rebuilt with zero callout_active references remaining in the module, reloaded, same trigger: the full DLC lifecycle runs identically and cleanly - behavior-preserving dead-code removal validated (nothing bad to reproduce by design; baseline_reproduced=1 refers to the demonstrated dead guard, patched_reproduced=0 to the absence of any fault).
fix_build.log (both hunks applied, relink, callout_active grep count 0); fix_run.log (identical lifecycle, '=== DF-5748 harness done (no crash) ===')
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log: '[DF-5748] A timer: pending=1 active=1 (ARMED by rfcomm_disconnect:252)' followed by '[DF-5748] A after rfcomm_dlc_close(): pending=0 active=0' and '[DF-5748] DEAD: callout_active() == 0 after rfcomm_dlc_close(); the RFCOMM_DLC_DETACH branch at rfcomm_upper.c:293 is UNREACHABLE', then 'rfcomm_detach(B) returned; B freed via immediate kfree (DETACH flag never taken)' and '=== DF-5748 harness done (no crash) ==='; fix_run.log: identical clean lifecycle with the dead code removed; setup.log: verbatim-source md5s
PoC changes
Harness written fresh on the DF-5733 rig pattern: verbatim netbt sources + l2cap_stub.c (gained harness_last_channel()) + FCS-valid frame injectors reused; drives two DLCs (DLCI 6/8) so one demonstrates the callout state around rfcomm_dlc_close() while the other runs full production rfcomm_detach(); no production source modified for the baseline run.
Verified recommended fix
Delete the dead callout_active()/RFCOMM_DLC_DETACH machinery: unconditional documented kfree in rfcomm_detach() and drop the unreachable release branch in rfcomm_dlc_timeout() (see fix.diff); fix the real race (DF-0724) separately with proper serialization.
Verdict
Reproduced on the QEMU guest with a KLD rig compiling sys/netbt/rfcomm_session.c, rfcomm_dlc.c, rfcomm_upper.c VERBATIM against a stub L2CAP layer, driving two DLCs to RFCOMM_DLC_OPEN through the production session state machine with injected FCS-valid TS 07.10 frames. After production rfcomm_disconnect() armed the 20 s ack timer (callout_pending=1 callout_active=1), the production rfcomm_dlc_close() - the identical call rfcomm_detach() makes at rfcomm_upper.c:278-279 - left callout_active()==0: callout_stop() clears CALLOUT_ACTIVE unconditionally at entry (kern_timeout.c:869) and drains in-progress callbacks (kern_timeout.c:888-921). Since no callout_reset(&dlc->rd_timeout) site is reachable with rd_state==CLOSED, the callout_active() test at rfcomm_upper.c:293 can only ever see 0, RFCOMM_DLC_DETACH can never be set, and the kfree() branch in rfcomm_dlc_timeout() (rfcomm_dlc.c:203-204) is unreachable dead code - demonstrated live, then full production rfcomm_detach() ran the immediate-kfree path and the session expired cleanly 20 s later with no crash. Not directly exploitable (the unconditional kfree is safe precisely because callout_stop drains), but the guard is documented as the callout-vs-detach lifetime handler and does nothing - the genuine race is DF-0724, and this dead net invites fixing it the wrong way. Info severity (dead code / misleading guard / upstream divergence: NetBSD uses callout_invoking() there).
No comments yet.