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

ieee80211_recv_bar parses BAR control/sequence fields with no frame-length validation β€” OOB read of up to 10 bytes on short ctl frames

Summary

ieee80211_recv_bar casts the mbuf to struct ieee80211_frame_bar and reads wh->i_ctl (bytes 16-17), wh->i_seq (18-19) and i_fc[1] with no length check of its own. The only guarantee on every ctl dispatch path is m_pkthdr.len >= sizeof(struct ieee80211_frame_min) = 10 bytes. Contrast the ACTION path, which is fully per-subtype length-verified before dispatch - the equivalent BAR check does not exist anywhere. A 10-19 byte BAR (driver-fed or injected) reads up to 10 bytes past valid mbuf data; tid is masked to 4 bits and rxseq to 12 bits, so no corruption/leak - worst case a boundary page-fault panic. Hardening-class but a real absent bound, stronger than DF-0324 because no caller validates either. Not executed (Low). Fix: reject m_pkthdr.len < sizeof(struct ieee80211_frame_bar) at entry.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3313 Β· 2 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
verdict.json β€” 1.8 KB view raw

DF-3313 β€” ieee80211_recv_bar reads BAR control/sequence fields with no frame-length validation (OOB read on short ctl frames)

File: sys/netproto/802_11/wlan/ieee80211_ht.c (pass 2 audit)

What

ieee80211_recv_bar() (sys/netproto/802_11/wlan/ieee80211_ht.c:977-1050) casts the mbuf to struct ieee80211_frame_bar and reads wh->i_ctl (bytes 16-17) and wh->i_seq (bytes 18-19) β€” plus wh->i_fc[1] (:1046) β€” with no length check of its own.

The only length guarantee on the ctl-frame path is the generic m->m_pkthdr.len >= sizeof(struct ieee80211_frame_min) (10 bytes) at the top of each vap input routine (ieee80211_sta.c:571, ieee80211_hostap.c, ieee80211_adhoc.c, ieee80211_wds.c, ieee80211_mesh.c) before vap->iv_recv_ctl(ni, m, subtype) dispatches subtype BAR straight to ieee80211_recv_bar (ieee80211_sta.c:926-930, ieee80211_hostap.c:2248-2254, ieee80211_adhoc.c:949-956). Contrast the ACTION-frame path, which is fully length-verified per subtype by ieee80211_parse_action() (ieee80211_input.c:754-803) before ic->ic_recv_action dispatch β€” the equivalent BAR check simply does not exist anywhere.

A BAR ctl frame of 10-19 bytes (perfectly deliverable by drivers that pass truncated ctl frames up, and by injection/SDR setups) makes the kernel read up to 10 bytes past the valid mbuf data: tid = MS(le16toh(wh->i_ctl), IEEE80211_BAR_TID) (:996) and rxseq = le16toh(wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT (:1009).

Impact

OOB read only. tid is masked to 4 bits (0-15 β†’ ni_rx_ampdu[tid] stays in bounds), rxseq to 12 bits; neither value is reflected back to the sender, so no info leak and no memory corruption. Worst case is a page-fault panic if the mbuf data ends at a mapping boundary (rx clusters normally leave the read inside the cluster). Severity: Low.

--- a/sys/netproto/802_11/wlan/ieee80211_ht.c
+++ b/sys/netproto/802_11/wlan/ieee80211_ht.c
@@ -993,6 +993,10 @@ ieee80211_recv_bar(struct ieee80211_node *ni, struct mbuf *m0)
    if (!ieee80211_recv_bar_ena) {
@@      return;
    }
    wh = mtod(m0, struct ieee80211_frame_bar *);
+   if (m0->m_pkthdr.len < sizeof(*wh)) {
+       vap->iv_stats.is_rx_tooshort++;
+       return;
+   }
    /* XXX check basic BAR */

(An m_pullup variant is the belt-and-braces form; the length check is the security-relevant part.)

PoC status

Not executed (Low severity β€” OOB read with masked sinks, no leak/corruption; Phase V not warranted under the conditional-verify rule). The missing check is provable by inspection: no length validation exists on any BAR dispatch path (grep-clean), and ieee80211_recv_bar reads through offset 19 unconditionally.

Fix verification

not_testable
per-fix-DF-3313

Confirmed kernel references

Detail

Evidence (decisive lines)

['README.md: dispatch-path analysis with path:line for every ctl entry (sta.c:926, hostap.c:2248, adhoc.c:949) vs the action-path contrast (ieee80211_input.c:754-803)']

Verified recommended fix

Reject BAR frames shorter than sizeof(struct ieee80211_frame_bar) at the top of ieee80211_recv_bar (see README.md diff).

Verdict

ieee80211_recv_bar (ieee80211_ht.c:977-1050) reads wh->i_ctl/wh->i_seq through byte offset 19 with no own length check; every vap ctl dispatch path guarantees only sizeof(ieee80211_frame_min)=10 bytes before calling it (contrast: ACTION frames are length-verified per subtype in ieee80211_parse_action before dispatch). A short BAR delivered by a driver/injection is an OOB read of up to 10 bytes past valid mbuf data. Sinks are bit-masked (TID 4 bits, seq 12 bits) so there is no corruption/leak; worst case a boundary page-fault panic. Not executed: Low severity, Phase V conditional rule not met. The absent check is certain from source; manifestation depends on the radio driver delivering short ctl frames.