Missing arglen check in L2CA_Ping path causes kernel heap OOB read leaked to remote Bluetooth peer
| Field | Value |
|---|---|
| ID | DF-0625 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N |
| CWE | CWE-125 Out-of-bounds Read |
| File | sys/netgraph7/bluetooth/l2cap/ng_l2cap_main.c (dispatch); root cause in ng_l2cap_ulpi.c |
| Lines | 438-439 (dispatch); ulpi.c:1298-1367 (handler) |
| Area | netgraph7/bluetooth (L2CAP echo/ping) |
| Confidence | likely |
| Discovered | 2026-07-02 |
| Reported | pending |
Summary
ng_l2cap_upper_rcvmsg (main.c:439) dispatches NGM_L2CAP_L2CA_PING
to ng_l2cap_l2ca_ping_req (ulpi.c:1298), which validates
arglen >= sizeof(ng_l2cap_l2ca_ping_ip) and caps
echo_size <= NG_L2CAP_MAX_ECHO_SIZE, but never checks that arglen
actually contains sizeof(ip)+echo_size bytes. The function then passes
msg->data+sizeof(ip) and ip->echo_size to _ng_l2cap_echo_req, which
calls m_copyback to copy echo_size bytes from that pointer. If
echo_size exceeds the actual trailing data in the ng_mesg allocation,
bcopy reads adjacent kernel heap and the result is transmitted as an
L2CAP Echo Request to the remote Bluetooth peer β a kernel heap info
leak to an unauthenticated remote endpoint.
Root cause
ng_l2cap_l2ca_ping_req (ulpi.c:1298) performs two independent checks:
ulpi.c:1306:if (msg->header.arglen < sizeof(*ip))β ensures the 8-byteng_l2cap_l2ca_ping_ipheader fits.ulpi.c:1316:if (ip->echo_size > NG_L2CAP_MAX_ECHO_SIZE)β capsecho_sizeat 65531.
But it never validates arglen >= sizeof(*ip) + ip->echo_size. At
ulpi.c:1357-1358:
_ng_l2cap_echo_req(cmd->aux, cmd->ident, msg->data + sizeof(*ip), ip->echo_size);
The macro (ng_l2cap_cmds.h:338-340) calls
m_copyback((_m), sizeof(*c), (_size), (_data)) where
_data = msg->data+8 and _size = ip->echo_size. m_copyback β
_m_copyback2 (uipc_mbuf.c:2361-2386) performs
bcopy(cp, mtod(m,...)+off, mlen) reading up to min(echo_size,
M_TRAILINGSPACE(m)) β 248 bytes from the source pointer. Since msg was
allocated as sizeof(struct ng_mesg)+arglen (ng_message.h:81), and
arglen can be as small as sizeof(*ip)=8, the source pointer
msg->data+8 may point past the allocation entirely. Every byte read
beyond arglen-8 is out-of-bounds kernel heap.
The resulting mbuf is then queued via
ng_l2cap_link_cmd+ng_l2cap_lp_deliver (ulpi.c:1366-1367) and
ultimately transmitted as L2CAP signaling data on the air.
Threat model & preconditions
- Attacker position: local user who can send netgraph control messages
to the l2cap node's
l2corctlhook (typically requiring root or bluetooth-group membership, e.g. vial2pingorBTPROTO_L2CAPsocket). - Trigger: craft a PING message with
arglen=8(just theipstruct, no echo data) andecho_size=N(e.g. 200). The kernel readsNbytes of adjacent heap and sends them as L2CAP Echo Request payload to whateverbdaddrthe attacker specified inip->bdaddr. - The attacker controls the remote Bluetooth device and captures the echo payload, recovering kernel heap contents. The remote peer need not be paired or authenticated β L2CAP signaling (CID 0x0001) operates before any security-mode authentication.
- Impact: local-to-remote kernel heap info leak (
C:H, scope changeS:C). Leaked bytes may contain freedng_mesgobjects, credential structures, or other sensitive data recycled from prior kernel allocations.
Recommended fix
Add an exact-match arglen validation immediately after the echo_size
cap check, so that echo_size must match the actual trailing data in the
message:
--- a/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c
+++ b/sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c
@@ -1322,6 +1322,14 @@ ng_l2cap_l2ca_ping_req(ng_l2cap_p l2cap, struct ng_mesg *msg)
goto out;
}
+ /*
+ * Verify that the message actually contains the claimed echo data.
+ * Without this check, m_copyback in _ng_l2cap_echo_req would read
+ * past the end of msg->data into adjacent kernel heap.
+ */
+ if (msg->header.arglen != sizeof(*ip) + ip->echo_size) {
+ NG_L2CAP_ALERT(
+"%s: %s - L2CA_Ping echo_size does not match message size, "
+"arglen=%d, expected=%zd\n",
+ __func__, NG_NODE_NAME(l2cap->node),
+ msg->header.arglen, sizeof(*ip) + ip->echo_size);
+ error = EMSGSIZE;
+ goto out;
+ }
+
/* Check if we have connection to the unit */
con = ng_l2cap_con_by_addr(l2cap, &ip->bdaddr);
This mirrors the exact-match pattern already used by
ng_l2cap_l2ca_get_info_req (ulpi.c:1435:
if (msg->header.arglen != sizeof(*ip))).
References
sys/netgraph7/bluetooth/l2cap/ng_l2cap_main.c:438-439β the dispatch.sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c:1306β the incomplete arglen check.sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c:1316β the echo_size cap.sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c:1357-1358β themsg->data + sizeof(*ip)/ip->echo_sizepassed unchecked.sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c:1435β the correct exact-match pattern in the siblingget_infohandler.
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0625 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| args_overflow.c | trigger-source | documentation stub: trigger would require ng_l2cap + ng_hci + BT peer | 3.9 KB | view raw |
| build.sh | build-script | compiles the C stub | 115 B | view raw |
| run.sh | run-script | runs the stub | 47 B | view raw |
| run.log | run-log | stub output | 971 B | view raw |
| env.txt | environment | uname, kern.version | 209 B | view raw |
| fix.diff | suggested-fix | add exact-match arglen != sizeof(*ip)+echo_size check in ping handler | 1.1 KB | view raw |
| VERDICT.md | verdict | full narrative | 4.5 KB | β raw |
| ../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 |
DF-0625 β VERDICT
Verdict: NOT REPRODUCED at runtime (no BT hardware). Bug CONFIRMED in source.
Mechanism (the bug IS real in source)
ng_l2cap_l2ca_ping_req (sys/netgraph7/bluetooth/l2cap/ng_l2cap_ulpi.c:1298)
validates only:
1306: if (msg->header.arglen < sizeof(*ip)) { /* 8-byte header fits */
1316: if (ip->echo_size > NG_L2CAP_MAX_ECHO_SIZE) { /* echo_size <= 65531 */
and then, without checking that arglen actually contains
sizeof(*ip) + echo_size bytes, passes them downstream:
1357: _ng_l2cap_echo_req(cmd->aux, cmd->ident,
1358: msg->data + sizeof(*ip), ip->echo_size);
The macro (ng_l2cap_cmds.h:338-340) calls
m_copyback((_m), sizeof(*c), (_size), (_data)) with _size = ip->echo_size
and _data = msg->data + 8. The ng_mesg was allocated as
sizeof(struct ng_mesg) + arglen (ng_message.h:81), so when arglen = 8
(just the header) and echo_size = 200, the read at msg->data + 8 runs
~192 bytes past the allocation into adjacent kernel heap (bcopy in
_m_copyback2, uipc_mbuf.c:2361-2386). The resulting mbuf is then queued
via ng_l2cap_link_cmd + ng_l2cap_lp_deliver (ulpi.c:1366-1367) and
ultimately transmitted as L2CAP Echo Request payload on the air.
The sibling ng_l2cap_l2ca_get_info_req (ulpi.c:1428+) does do an
exact-match check:
1439: if (msg->header.arglen != sizeof(*ip)) {
so the fix is to mirror that pattern in the ping handler.
Why it CANNOT be triggered on this guest
The bug path at ulpi.c:1357 requires a valid BT connection (con != NULL)
at :1325 or a successful ng_l2cap_lp_con_req at :1328. The latter
fails fast without an HCI lower layer:
llpi.c:87: if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci)) {
llpi.c:92: return (ENOTCONN);
On the audit guest:
- No Bluetooth hardware (no USB BT dongle, no virtual BT adapter).
- ng_hci / ng_ubt not loaded; no HCI hook to connect to an l2cap node.
- Therefore l2cap->hci == NULL, ng_l2cap_lp_con_req returns ENOTCONN,
ng_l2cap_l2ca_ping_req takes the early goto out at :1333, and the
buggy _ng_l2cap_echo_req call at :1357 is never reached.
Additionally, the pre-installed /boot/kernel/netgraph.ko in the audit
snapshot is ABI-incompatible with a freshly-built ng_l2cap.ko (NG_ABI_VERSION
mismatch surfaced as KLD ng_l2cap.ko: depends on netgraph - not available or
version mismatch), so even loading the module to confirm the path requires
rebuilding netgraph.ko too. The bug path is still code-confirmed.
Classification: bug CONFIRMED in source, runtime trigger requires Bluetooth hardware/HCI emulation not present on this guest. Valid hard blocker: no harness can exercise the bug path without BT hardware or an HCI lower-layer emulation, neither of which is available on this kernel.
Realistic impact ceiling
If the bug WERE reachable (any deployment that actually uses DragonFly's Bluetooth L2CAP stack β typically embedded systems or routers with USB BT dongles):
- A local user with netgraph control access (root, or in a
bluetoothgroup if configured) sends a craftedNGM_L2CAP_L2CA_PINGwitharglen=8,echo_sizeup to 65531, andbdaddrpointing at a Bluetooth peer the attacker controls. - The kernel reads up to
echo_sizebytes of adjacent kernel heap and transmits them as the Echo Request payload. - The attacker captures the packet at their BT peer and recovers kernel
heap contents β freed
ng_mesgobjects, slab metadata, possibly credential structures recycled from prior allocations.
Severity Medium with C:H/S:C is appropriate IF BT hardware is present;
on this guest, no demonstrated runtime impact.
Recommended fix
fix.diff adds the missing exact-match arglen validation in
ng_l2cap_l2ca_ping_req, mirroring the pattern already used by the sibling
ng_l2cap_l2ca_get_info_req:
if (msg->header.arglen != sizeof(*ip) + ip->echo_size) {
NG_L2CAP_ALERT(...);
error = EMSGSIZE;
goto out;
}
Placed immediately after the existing echo_size cap check, so the
function returns EMSGSIZE for any PING whose claimed echo data does not
match the actual trailing bytes.
This matches the finding markdown's proposal (same check, same placement, same error code).
Caveats
The "no BT hardware" hard blocker applies to runtime demonstration. The fix is small, surgical, and mirrors a proven pattern in a sibling function β low regression risk. Recommended for merge regardless of the runtime reproducibility status.
Fix verification
not_testablen/a
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. L2CA_Ping missing arglen>=sizeof(ip)+echo_size check. No BT HW, ng_l2cap can't load.
No comments yet.