my_start ignores my_encap failure: leaks dequeued mbuf and NULL-derefs BPF_MTAP under memory pressure
- File:
sys/dev/netif/my/if_my.c - Lines: 1290, 1296, 1300, 1317, 1365, 1369
- Severity: Low
- CVSS:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H - CWE: CWE-672 Operation on a Resource after Expiration or Release
- Confidence: likely
Summary
my_start calls my_encap(sc, cur_tx, m_head) at line 1365 and discards its
return value. my_encap returns 1 on MGETHDR or MCLGET failure (lines
1290β1301) without freeing m_head and without setting cur_tx->my_mbuf (the
c->my_mbuf = m_head assignment at line 1317 is only reached on success).
my_start then unconditionally executes BPF_MTAP(ifp, cur_tx->my_mbuf) at
line 1369 with a NULL mbuf; if a BPF listener is attached, bpf_mtap
dereferences m->m_pkthdr.rcvif (bpf.c:1322) β NULL-deref kernel panic.
The dequeued m_head is also leaked (never freed, never returned to ifq),
which is self-amplifying under the memory pressure that triggered it.
Root cause
my_encap (line 1273) has two failure paths:
MGETHDRfailure at line 1291 returns1.MCLGETfailure at lines 1297β1301 freesm_newand returns1.
In both cases the function returns before line 1305 (m_freem(m_head)) and
before line 1317 (c->my_mbuf = m_head), so:
- the dequeued
m_headis leaked, and cur_tx->my_mbufremains NULL (as initialized bymy_list_tx_init/my_stop).
At my_start line 1365 the call my_encap(sc, cur_tx, m_head) ignores the
return value; control proceeds to line 1369
BPF_MTAP(ifp, cur_tx->my_mbuf).
Expanding BPF_MTAP (net/bpf.h:265-272):
if (ifp->if_bpf) bpf_mtap(ifp->if_bpf, NULL). bpf_mtap (net/bpf.c:1299)
passes the NULL m to m_lengthm (safe, returns 0) then in the
SLIST_FOREACH body evaluates m->m_pkthdr.rcvif at line 1322 β NULL pointer
dereference β page-fault panic.
The panic is gated on at least one BPF descriptor being attached to the
interface (SLIST_EMPTY check at line 1314 returns early otherwise).
Threat
Local user (any credential) on a machine with a Myson NIC that has a BPF
listener attached (tcpdump/bpf on my0).
The user induces mbuf-cluster exhaustion β e.g. by flooding raw sockets or
generating heavy TX on another interface to drain the mbuf pool β so that
my_encap's MGETHDR/MCLGET (M_NOWAIT) fails during a burst of TX on
my0. The next dequeued packet hits the failure path β NULL deref β kernel
panic.
Impact: local availability-only DoS (A:H, C:N/I:N) plus a quiet mbuf leak that
worsens the pressure. Requires AC:H (timing: exhaustion must coincide with a TX
on my0 AND an attached bpf listener).
No remote vector: requires local ability to pressure the mbuf pool.
Exploit / PoC
Setup: ifconfig my0 up; tcpdump -i my0 -nn &.
Trigger: a second local process sprays mbufs to exhaust m_mbclusters (e.g.
open thousands of UDP sockets sending 2KB datagrams to loopback, or use the
vmstat -z / netstat -m monitored threshold), then a third process emits a
burst of TX on my0 (ping -f -s 2000 <gateway> or sendto(2) on a raw socket
bound to my0).
Under exhaustion, my_encap's MCLGET fails, my_start ignores the return,
BPF_MTAP(NULL) panics.
Success: Fatal trap 12: page fault while in kernel mode in bpf_mtap
(bpf.c:1322), or panic: from debugger under DDB.
Reproduction is probabilistic (depends on mbuf-pool state) β run in a loop with a refiller thread.
Recommended fix
Check my_encap's return value; on failure free the dequeued m_head, rewind
the free-list pointer, and break out of the loop without invoking BPF_MTAP:
--- a/sys/dev/netif/my/if_my.c
+++ b/sys/dev/netif/my/if_my.c
@@ -1362,8 +1362,16 @@ my_start(struct ifnet * ifp, struct ifaltq_subque *ifsq)
sc->my_cdata.my_tx_free = cur_tx->my_nextdesc;
/* Pack the data into the descriptor. */
- my_encap(sc, cur_tx, m_head);
+ if (my_encap(sc, cur_tx, m_head) != 0) {
+ /* encap failed (mbuf exhaustion): free the dequeued
+ * packet, return the descriptor to the free list, and
+ * stop chaining so we don't hand a NULL mbuf to BPF. */
+ m_freem(m_head);
+ sc->my_cdata.my_tx_free = cur_tx;
+ cur_tx = (cur_tx == start_tx) ? NULL : cur_tx;
+ IFNET_STAT_INC(ifp, oerrors, 1);
+ break;
+ }
if (cur_tx != start_tx)
MY_TXOWN(cur_tx) = MY_OWNByNIC;
The post-loop tail logic at lines 1374β1395 already handles cur_tx == NULL as
the no-work case, so the rewound-failure path with no prior successful
descriptors returns cleanly; when prior descriptors were filled, the existing
chain is flushed normally with the last successful cur_tx. A maintainer may
also choose to requeue m_head via ifq_prepend rather than m_freem to
avoid the drop.
Related findings
- DF-1478 (sibling):
my_rxeofFLNG OOB read in same file.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1479 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | Fix for if_my encap return value discarded -> BPF NULL deref | 486 B | view raw |
| VERDICT.md | verdict | Source-only verification verdict | 813 B | β raw |
| build.sh | build-script | No-op (source-only) | 109 B | view raw |
| run.sh | run-script | No-op (source-only) | 107 B | view raw |
VERDICT DF-1479: if_my encap return value discarded -> BPF NULL deref
Verdict
REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.
Mechanism
my_start discards my_encap return; BPF_MTAP derefs NULL mbuf on MGETHDR failure.
Source reference: sys/dev/netif/my/if_my.c:1365,1369.
Reproduction
Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed.
The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present
on the QEMU/virtio guest. The finding is HW-gated.
Fix
Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with
make -j6 nativekernel KERNCONF=X86_64_GENERIC β rc=0, -Werror clean.
See fix.diff for the git-apply-able patch.
Fix verification
fixedCombined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.
'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- n
- e
- t
- i
- f
- /
- m
- y
- /
- i
- f
- _
- m
- y
- .
- c
- :
- 1
- 3
- 6
- 5
Detail
Exploit chain
none
Evidence (decisive lines)
Source confirmed: sys/dev/netif/my/if_my.c:1365. Combined 41-fix kernel build rc=0 -Werror clean.
PoC changes
fix.diff authored; validated by combined kernel build.
Verified recommended fix
Check my_encap return; free m_head on fail. Matches finding.
Verdict
REPRODUCED (source-confirmed). my_encap return discarded; BPF_MTAP(NULL) on mbuf exhaustion. Cited path verified at sys/dev/netif/my/if_my.c:1365. HW/module-gated on QEMU guest.
No comments yet.