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

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:

  • MGETHDR failure at line 1291 returns 1.
  • MCLGET failure at lines 1297–1301 frees m_new and returns 1.

In both cases the function returns before line 1305 (m_freem(m_head)) and before line 1317 (c->my_mbuf = m_head), so:

  1. the dequeued m_head is leaked, and
  2. cur_tx->my_mbuf remains NULL (as initialized by my_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.

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.

  • DF-1478 (sibling): my_rxeof FLNG OOB read in same file.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1479 Β· 4 files
FileTypeDescriptionSize
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.md verdict Source-only verification verdict
↓ download 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

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined 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.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

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.