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)
PoC verification
Evidence pack
findings/poc/DF-3383 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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):
- The surviving
bpf_ifstays inbpf_iflistwithbif_ifppointing at the freedifnet(if_detach frees it) andbif_driverp == &ifp->if_bpfpointing into freed memory. - Descriptors still bound to the surviving entry are never woken
(
bpfdetachonly notifies the first entry's dlist); their laterbpfclose()βbpf_detachd()executes*bp->bif_driverp = NULL(write into freed ifnet) andifpromisc(freed_ifp, 0)(UAF through freed ifnet, incl.if_ioctldispatch) whenbd_promiscwas set. BIOCGETIF/BIOCGDLTon such a descriptor readifp->if_xnamefrom freed memory (kernel info leak into a privileged fd).- 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 = bpinto 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_testableBaseline 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']
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.
No comments yet.