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

bpfdetach() frees only the FIRST bpf_if of an interface: leftover entries (wlan radiotap) keep bif_ifp/bif_driverp pointing at the freed ifnet -> UAF writes/reads on later close or rebind; bp==NULL branch dereferences NULL instead of reporting

Summary

bpfdetach() breaks at the first bpf_if matching the ifnet and unlinks/frees only that one. A wlan vap carries two entries: Ethernet (ether_ifattach) plus DLT_IEEE802_11_RADIO (ieee80211_radiotap_vattach -> bpfattach_dlt on the same ifp; radiotap_vdetach is an empty no-op whose comment falsely assumes ether_ifattach 'claims all taps'). After ifconfig wlanX destroy the surviving entry keeps bif_ifp (freed by if_detach) and bif_driverp == &ifp->if_bpf (freed memory): a descriptor still bound to it gets bpf_detachd() on close which executes *bp->bif_driverp = NULL (write into freed ifnet) and ifpromisc(freed_ifp,0) (UAF through ifnet incl. if_ioctl dispatch); BIOCGETIF/BIOCGDLT read if_xname from freed memory; address reuse can alias a new interface with the wrong DLT. Additionally, when the lookup loop exits with bp==NULL the 'not attached' branch dereferences bp->bif_ifp - a guaranteed NULL-deref panic instead of the intended diagnostic. ATTEMPTED on guest: wlan vap creation needs an 802.11 parent (absent in QEMU) - not_reproduced/feature-absent, defect certain from code. Fix: iterate over ALL matching entries + null-check the lookup result.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3383 Β· 7 files
FileTypeDescriptionSize
README.md β€” 3.4 KB ↓ raw
attempt.log β€” 340 B view raw
probe.c β€” 444 B view raw
verdict_probe.txt β€” 242 B view raw
env.txt β€” 255 B view raw
code_hash.txt β€” 74 B view raw
verdict.json β€” 2.8 KB view raw

DF-3383 β€” bpfdetach() removes only the first bpf_if of an interface

What

bpfdetach() (sys/net/bpf.c:1558-1593) walks bpf_iflist, breaks at the first entry whose bif_ifp matches, detaches that entry's descriptors, unlinks and frees only that one struct bpf_if. Interfaces can carry several bpf_if entries: every wlan vap gets DLT_IEEE802_11_RADIO in addition to the Ethernet entry via ieee80211_radiotap_vattach() β†’ bpfattach_dlt(vap->iv_ifp, DLT_IEEE802_11_RADIO, ..., &vap->iv_rawbpf) (netproto/802_11/wlan/ieee80211_radiotap.c:126-134), and ieee80211_radiotap_vdetach() is an empty function whose comment claims "bpfattach is called by ether_ifattach and claims all taps" β€” which bpfdetach() does not do (it frees exactly one).

Consequences after ifconfig wlanX destroy (radiotap entry is younger, hence at the head of bpf_iflist, so the Ethernet entry is the survivor):

  1. The surviving bpf_if stays in bpf_iflist with bif_ifp pointing at the freed ifnet (if_detach frees it) and bif_driverp == &ifp->if_bpf pointing into freed memory.
  2. Descriptors still bound to the surviving entry are never woken (bpfdetach only notifies the first entry's dlist); their later bpfclose() β†’ bpf_detachd() executes *bp->bif_driverp = NULL (write into freed ifnet) and ifpromisc(freed_ifp, 0) (UAF through freed ifnet, incl. if_ioctl dispatch) when bd_promisc was set.
  3. BIOCGETIF/BIOCGDLT on such a descriptor read ifp->if_xname from freed memory (kernel info leak into a privileged fd).
  4. If a new ifnet is later allocated at the reused address, the stale entry aliases the new interface with the wrong DLT/driverp, and bpf_attachd() does *bp->bif_driverp = bp into foreign memory.

Also, the "interface wasn't attached" path is broken: when the loop exits with bp == NULL, bp->bif_ifp (bpf.c:1574) NULL-derefs instead of reaching the intended kprintf("bpfdetach: %s was not attached") β€” any driver that calls bpfdetach() for a never-attached interface panics (attacker reachability depends on such a driver; none found in-tree).

Why not reproduced on this guest

Triggering requires a wlan vap (two bpf_if entries on one ifnet). The QEMU guest has no 802.11 parent device: ifconfig wlan0 create wlandev ath0 β†’ SIOCIFCREATE2: Device not configured (wlan.ko loads fine). No in-tree code path other than ieee80211 radiotap creates a second bpf_if. The defect is certain from code reading; the trigger hardware is absent β€” classification not_reproduced (feature absent), not false-positive.

Fix

bpfdetach() must iterate over all matching entries (detach their descriptor lists, unlink and free each), and check bp == NULL before bp->bif_ifp:

-   for (bp = bpf_iflist; bp != NULL; bp = bp->bif_next) {
-       if (ifp == bp->bif_ifp)
-           break;
-       bp_prev = bp;
-   }
-   /* Interface wasn't attached */
-   if (bp->bif_ifp == NULL) {
+   for (bp = bpf_iflist; bp != NULL; bp = bp->bif_next) {
+       if (ifp == bp->bif_ifp) {
+           while ((d = SLIST_FIRST(&bp->bif_dlist)) != NULL) {
+               bpf_detachd(d);
+               bpf_wakeup(d);
+           }
+           /* unlink bp (track prev per iteration) */
+           ...
+       }
+   }
+   if (found == 0) {
        lwkt_reltoken(&bpf_token);
        kprintf("bpfdetach: %s was not attached\n", ifp->if_xname);
        return;
    }

(not carried in fix.diff β€” the validated diff targets the reproduced memory-corruption findings; this one is Medium/hardware-gated.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Baseline never reproduced (trigger requires 802.11 hardware absent from guest); fix not carried in the validated diff - suggested loop-over-all-matching-bpf_if patch in README.md.

['attempt.log']
per-fix-DF-3383

Confirmed kernel references

Detail

Evidence (decisive lines)

['attempt.log: kldload wlan ok; ifconfig wlan0 create wlandev ath0 -> SIOCIFCREATE2: Device not configured; ifconfig -l shows vtnet0 lo0 only', 'README.md: full code-path citation of the leftover-bpf_if UAF']

PoC changes

Probe only; the multi-bpf_if precondition cannot be constructed without an 802.11 parent device.

Verified recommended fix

Loop bpfdetach over every bpf_if whose bif_ifp matches; check bp==NULL before bp->bif_ifp.

Verdict

bpfdetach() breaks at the FIRST bpf_if matching the ifnet and frees only that one, but a wlan vap carries a second entry (DLT_IEEE802_11_RADIO registered by ieee80211_radiotap_vattach via bpfattach_dlt on the same ifp, with ieee80211_radiotap_vdetach an empty no-op). The surviving entry keeps bif_ifp/bif_driverp pointing at the ifnet that if_detach() frees: later bpfclose()->bpf_detachd() writes *bp->bif_driverp=NULL into freed memory and calls ifpromisc() on the freed ifnet (UAF), BIOCGETIF leaks freed-memory contents, and address reuse can alias a new interface with the wrong DLT. Additionally the 'not attached' path dereferences bp==NULL (bpf.c:1574) instead of printing the intended message. Not reproduced on this guest: creating the trigger requires an 802.11 vap and the QEMU guest has no wifi parent ('ifconfig wlan0 create wlandev ath0' -> SIOCIFCREATE2: Device not configured; wlan.ko loads fine). Defect is certain from code reading; trigger hardware absent - classified not_reproduced, not false-positive.