Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 9 additions & 43 deletions issues/there-is-no-wifi.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,48 +230,11 @@ Depends on: nothing; W6 waits on it.
`build/ssh/tests/fuzz.rs`; and a third-party capture corpus from outside the
references, beside the crate, with provenance, terms and `NOTICE` rows,
pinned by hash.
- **Owed: `lib/ieee80211`'s readings checked against the text.** W1 was
written without IEEE 802.11-2020, 802.11ax-2021 or the WFA WMM
Specification v1.2 at hand; each reading below rests on the writer's
knowledge, and on the captures where `lib/ieee80211/tests/corpus.rs` reads
them. Owner: the orchestrator.
- **Exit:** every reading below checked against the text; each one the text
contradicts fixed, and each leniency the text rules out turned into a
refusal by name.
- **Frame structure:** the protocol version, type 3 as Extension, the
reserved and contention-free subtypes; non-QoS Order as strictly
ordered; the four-address layout; Fragment Number and More Fragments;
`ManagementWithDs`; `GroupTransmitter` for a management SA and a data TA.
- **Control:** the BAR and BA Type values and TID_INFO; a BlockAckReq's
Fragment Number being 0; the HE bitmap sizes by Fragment Number bits 2
and 1, bit 0 a fragment level, bit 3 refused; the bandwidth-signaling TA.
- **Management bodies:** a beacon interval of 0 refused; the AID's two high
bits and its range of 1 to 2007.
- **Elements:** the Element ID Extension octet; each typed element allowed
once, WMM's included; the SSID's 32 octets; 1 to 8 Supported Rates and
Extended Supported Rates of length 0 refused; the selectors 127, 126, 123
and 122; the TIM's offset as N1/2, its bound of 251 octets and its minimum
of one bitmap octet; Country's string, triplets, operating triplet from
201 and channel bound of 200; MFPR as bit 6, MFPC as bit 7 and MFPR
without MFPC refused; the RSNXE's Field Length and SAE-H2E as bit 5; the
HT and VHT field splits and reserved values; Extended Capabilities' bit
numbering; HE Capabilities' 6, 11 and 4 octet fields, the 160 and 80+80
MCS sets under PHY Capabilities B3 and B4, PPE Thresholds Present as
B55, and the PPE length of 7 + 6 × (NSTS + 1) × RU count bits; HE
Operation's bits 14, 15 and 17 and its five-octet 6 GHz field; WMM's
layouts, version 1, AIFSN of at least 2 and each ACI once, and its exact
length.
- **Data and the window:** the A-MSDU subframe header and its padding; the
bridge-tunnel types 0x80F3 and 0x8137; CCMP's Ext IV bit; the §10.25.6
rules with the half-space boundary of 2048; the HE buffer of 256.
- **Each refusal's class**, `Malformed` or `Unsupported`.
- **The leniencies**, each to become a refusal if the text rules it out:
octets past the RSNE's and the RSNXE's last field ignored; octets past
the fields of HT and VHT Capabilities and Operation and of HE
Capabilities and Operation ignored (fewer refused); a Country element
accepted without its pad octet, and with one where none is needed. The
strict side, padding after an A-MSDU's last subframe refused, is checked
too.
- **Landed** in #924 and #930; the exit is met, the readings checked
against the text (#924, issuecomment-6173453493).
- **Owed:** the bridge-tunnel OUI 00-00-F8 and its EtherTypes 0x80F3 and
0x8137 checked against ISO/IEC TR 11802-5:1997. Owner: the orchestrator.
Exit: checked, and fixed in `lib/ieee80211/src/data.rs` if contradicted.

**W2, the handshakes, in `wlan`**, sans-IO: the 4-way and group-key
handshakes, SAE by hash-to-element, PMF's keys, and the Transition Disable
Expand Down Expand Up @@ -324,7 +287,8 @@ W2.
networks, an access point at the T14's bench serving a WPA2-Personal network
and a WPA3-Personal one by hash-to-element, with PMF and HE, an instrument
the orchestrator owns. Exit: a T14 row reads the firmware up, its MAC
address, and the test network seen. The network's name arrives per run
address, and the test network seen, its beacon's Country element read: a
missing pad (`CountryPad`) refuses the whole beacon. The network's name arrives per run
through a test job holding `wifi`, so no shipping path reads the ESP.

**W6, the first version: a full-rate join.** Depends on: W5 and its test
Expand All @@ -335,6 +299,8 @@ networks, P4, P5, P6, P8.
- each channel's limit as the tighter of the platform's table and the
AP's country element;
- a country element that would raise a limit;
- under operating classes, a country element with an invalid first
channel or a reserved class read up to it, as §10.22.3 has;
- a platform with no table, refused by name.
- **Exit, T14 rows:**
- WPA2 and WPA3 joins, each with a lease, DNS and TCP;
Expand Down
53 changes: 33 additions & 20 deletions lib/ieee80211/src/control.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
//! BlockAckReq (802.11-2020 §9.3.1.7) and BlockAck (§9.3.1.8) of the Compressed variant, the one an
//! HT, VHT or HE station's immediate block-ack agreement uses; their bodies follow Frame Control
//! and Duration.
//! BlockAckReq (§9.3.1.7) and BlockAck (§9.3.1.8) of the Compressed variant, the one an HT, VHT
//! or HE station's immediate block-ack agreement uses; their bodies follow Frame Control and
//! Duration.
//!
//! An HE BlockAck's bitmap is 32, 64, 128 or 256 bits long, as its Fragment Number subfield says
//! (802.11ax-2021 §9.3.1.8.2). Acknowledging fragments, the subfield's bit 0, is refused, because a
//! fragment is (`Refused::Fragmented`).
//! A BlockAck's bitmap is 64 or 256 bits long, as its Fragment Number subfield says (Table 9-38).
//! Acknowledging fragments, the subfield's B0, is refused, because a fragment is
//! (`Refused::Fragmented`).

use crate::read::Reader;
use crate::{Address, Refused, Seq};

/// The BAR and BA Type value of the Compressed variant (Table 9-24, Table 9-28).
/// The BAR and BA Type value of the Compressed variant.
const COMPRESSED: u16 = 2;
/// The BAR Type values Table 9-36 defines.
const BAR_TYPES: [u16; 5] = [1, COMPRESSED, 3, 6, 10];
/// The BA Type values Table 9-37 defines.
const BA_TYPES: [u16; 7] = [1, COMPRESSED, 6, 7, 8, 10, 11];

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BlockAckReq {
Expand Down Expand Up @@ -45,15 +49,23 @@ impl BlockAck {
}

/// The receiver and transmitter, the control field's TID, and the Starting Sequence Control's
/// Fragment Number and sequence number.
fn compressed(r: &mut Reader<'_>) -> Result<(Address, Address, u8, u8, Seq), Refused> {
/// Fragment Number and sequence number, of a frame whose defined Type values are `types`.
fn compressed(
r: &mut Reader<'_>,
types: &[u16],
) -> Result<(Address, Address, u8, u8, Seq), Refused> {
let ra = r.address()?;
// A VHT transmitter may set the TA's group bit as a bandwidth signaling TA (§9.3.1.1).
// A VHT or HE transmitter may set the TA's group bit as a bandwidth signaling TA (§9.3.1.7.1,
// §10.6.12).
let Address([first, rest @ ..]) = r.address()?;
let [b1, b2, b3, b4, b5] = rest;
let ta = Address([first & !1, b1, b2, b3, b4, b5]);
let control = r.u16()?;
if (control >> 1) & 0x0F != COMPRESSED {
let kind = (control >> 1) & 0x0F;
if !types.contains(&kind) {
return Err(Refused::ReservedBlockAckType);
}
if kind != COMPRESSED {
return Err(Refused::BlockAckVariant);
}
let [_, high] = control.to_le_bytes();
Expand All @@ -65,9 +77,9 @@ fn compressed(r: &mut Reader<'_>) -> Result<(Address, Address, u8, u8, Seq), Ref
impl BlockAckReq {
pub(crate) fn parse(after_duration: &[u8]) -> Result<Self, Refused> {
let mut r = Reader::new(after_duration);
let (ra, ta, tid, fragment, ssn) = compressed(&mut r)?;
let (ra, ta, tid, fragment, ssn) = compressed(&mut r, &BAR_TYPES)?;
if fragment != 0 {
return Err(Refused::BitmapSize);
return Err(Refused::BlockAckFragmentNumber);
}
if !r.is_empty() {
return Err(Refused::ControlLength);
Expand All @@ -79,15 +91,16 @@ impl BlockAckReq {
impl BlockAck {
pub(crate) fn parse(after_duration: &[u8]) -> Result<Self, Refused> {
let mut r = Reader::new(after_duration);
let (ra, ta, tid, fragment, ssn) = compressed(&mut r)?;
// Bits 2 and 1 of the Fragment Number give the bitmap's length, bit 0 a fragment level.
let bits: u16 = match fragment {
let (ra, ta, tid, fragment, ssn) = compressed(&mut r, &BA_TYPES)?;
// B2–B1 of 0 is a 64-bit bitmap and 2 a 256-bit one; 1, 3 and B3 set are reserved.
let bits: u16 = match fragment & 0b1110 {
0b0000 => 64,
0b0010 => 256,
0b0100 => 128,
0b0110 => 32,
_ => return Err(Refused::BitmapSize),
0b0100 => 256,
_ => return Err(Refused::BlockAckFragmentNumber),
};
if fragment & 1 != 0 {
return Err(Refused::FragmentLevel);
}
let mut bitmap = [0u64; 4];
let mut octets = r.take(usize::from(bits / 8))?.iter();
for word in &mut bitmap {
Expand Down
18 changes: 11 additions & 7 deletions lib/ieee80211/src/data.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Data frame bodies: the CCMP header (§12.5.3.2), A-MSDU (§9.3.2.2), and translation between an
//! MSDU and an Ethernet II frame by IEEE 802.1H: an RFC 1042 LLC/SNAP header, or the bridge-tunnel
//! one for the two EtherTypes 802.1H sends that way.
//! Data frame bodies: the CCMP header (§12.5.2.2), A-MSDU (§9.3.2.2), and translation between an
//! MSDU and an Ethernet II frame as §5.1.4 recommends, by ISO/IEC TR 11802-5:1997 (formerly IEEE
//! Std 802.1H): an RFC 1042 LLC/SNAP header, or the bridge-tunnel one for the two EtherTypes its
//! selective translation table sends that way.

use crate::frame::Data;
use crate::read::Reader;
Expand Down Expand Up @@ -50,7 +51,8 @@ pub fn msdus<'a>(frame: &Data<'_>, body: &'a [u8]) -> Result<Msdus<'a>, Refused>
}
// The A-MSDU Present bit is outside CCMP's protection unless both peers negotiated SPP
// A-MSDU, so a first subframe whose destination is an LLC/SNAP header is a frame an attacker
// flipped the bit on (Vanhoef, "Fragment and Forge", USENIX Security 2021, §3.2).
// flipped the bit on, which §10.11 forbids (Vanhoef, "Fragment and Forge", USENIX Security
// 2021, §3.2).
if body.starts_with(&SNAP) {
return Err(Refused::SnapHeaderAsAddress);
}
Expand Down Expand Up @@ -108,9 +110,11 @@ pub struct Ethernet<'a> {

const SNAP: [u8; 6] = [0xAA, 0xAA, 0x03, 0x00, 0x00, 0x00];
const BRIDGE_TUNNEL: [u8; 6] = [0xAA, 0xAA, 0x03, 0x00, 0x00, 0xF8];
/// The EtherTypes 802.1H carries under the bridge-tunnel OUI: AppleTalk AARP and IPX.
/// The EtherTypes the selective translation table carries under the bridge-tunnel OUI: AppleTalk
/// AARP and IPX.
const TUNNELED: [u16; 2] = [0x80F3, 0x8137];
/// The least EtherType: below it the field is an IEEE 802.3 length (IEEE 802.3 §3.2.6).
/// The least type: IEEE Std 802.3-2022 §3.2.6 reads a Length/Type value of at most 1500 as a
/// length and one of at least 1536 (0x0600) as a type.
const MIN_ETHERTYPE: u16 = 0x0600;

pub const LLC_SNAP_LEN: usize = 8;
Expand Down Expand Up @@ -148,7 +152,7 @@ pub fn llc_snap(ethertype: u16) -> Result<[u8; LLC_SNAP_LEN], Refused> {
}

/// The header of a QoS Data frame a station sends its access point: To DS, Address 1 the BSSID,
/// Address 2 the source and Address 3 the destination (Table 9-30).
/// Address 2 the source and Address 3 the destination (Table 9-60).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ToDs {
pub bssid: Address,
Expand Down
15 changes: 7 additions & 8 deletions lib/ieee80211/src/element/country.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! The Country element (§9.4.2.8): a country string, then triplets, then a pad octet of 0 where
//! one makes the length even.
//! The Country element (§9.4.2.7): a country string, then triplets, then a pad octet of 0 exactly
//! where one makes the length even.
//!
//! Channel numbers are not checked: their meaning depends on the operating class (Annex E), and
//! §10.22.3 has a station ignore the rest of the element at an invalid one, not refuse the frame.

use crate::Refused;

Expand All @@ -20,7 +23,6 @@ pub enum Triplet {

/// A first octet of at least this opens an Operating triplet.
const OPERATING_EXTENSION: u8 = 201;
const LAST_CHANNEL: u16 = 200;

impl<'a> Country<'a> {
pub(super) fn parse(body: &'a [u8]) -> Result<Self, Refused> {
Expand All @@ -41,11 +43,8 @@ impl<'a> Country<'a> {
if triplets.is_empty() {
return Err(Refused::ElementLength);
}
for &[first, channels, _] in triplets.as_chunks::<3>().0 {
let past_last = u16::from(first).saturating_add(u16::from(channels)) > LAST_CHANNEL + 1;
if first < OPERATING_EXTENSION && (first == 0 || channels == 0 || past_last) {
return Err(Refused::CountryChannels);
}
if !body.len().is_multiple_of(2) {
return Err(Refused::CountryPad);
}
Ok(Self { string: [a, b, environment], triplets })
}
Expand Down
10 changes: 5 additions & 5 deletions lib/ieee80211/src/element/he.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! HE Capabilities (802.11ax-2021 §9.4.2.248) and HE Operation (§9.4.2.249), Element ID Extension
//! 35 and 36. Their lengths follow from bits inside them; a shorter element is refused, and octets
//! past those fields are ignored, so a field a later amendment appends costs no beacon.
//! HE Capabilities (§9.4.2.247) and HE Operation (§9.4.2.248), Element ID Extension 35 and 36.
//! Their lengths follow from bits inside them; a shorter element is refused, and octets past those
//! fields are ignored, as §10.28.8 requires of an extensible element.

use crate::read::Reader;
use crate::Refused;
Expand Down Expand Up @@ -35,7 +35,7 @@ impl<'a> HeCapabilities<'a> {
}

/// The PPE Thresholds field: NSTS and an RU index bitmask, then a pair of 3-bit thresholds per
/// stream and RU size, padded to whole octets (§9.4.2.248.5).
/// stream and RU size, padded to whole octets (§9.4.2.247.5).
fn ppe<'a>(r: &mut Reader<'a>) -> Result<&'a [u8], Refused> {
let &first = r.rest().first().ok_or(Refused::PpeThresholds)?;
let streams = u32::from(first & 0b111).saturating_add(1);
Expand All @@ -62,7 +62,7 @@ pub struct HeOperation {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SixGhz {
pub primary_channel: u8,
/// Channel Width in bits 0 and 1, Duplicate Beacon bit 2, Regulatory Info bits 3 to 5.
/// Channel Width in bits 0 and 1, Duplicate Beacon bit 2, Regulatory Info bits 3 to 6.
pub control: u8,
pub center_segment0: u8,
pub center_segment1: u8,
Expand Down
12 changes: 6 additions & 6 deletions lib/ieee80211/src/element/ht.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! HT Capabilities (§9.4.2.55), HT Operation (§9.4.2.56), VHT Capabilities (§9.4.2.157) and VHT
//! Operation (§9.4.2.158): fixed-length elements, refused when shorter or where a field holds a
//! value the standard reserves. Octets past the fixed fields are ignored, so a field a later
//! amendment appends costs no beacon.
//! HT Capabilities (§9.4.2.54), HT Operation (§9.4.2.55), VHT Capabilities (§9.4.2.156) and VHT
//! Operation (§9.4.2.157): fixed-length elements, refused when shorter or where a field holds a
//! value the standard reserves. Octets past the fixed fields are ignored, as §10.28.8 requires of
//! an extensible element.

use crate::read::Reader;
use crate::Refused;
Expand Down Expand Up @@ -36,7 +36,7 @@ impl HtCapabilities {
}
}

/// Secondary Channel Offset (§9.4.2.56).
/// Secondary Channel Offset (§9.4.2.55).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SecondaryChannel {
None,
Expand Down Expand Up @@ -98,7 +98,7 @@ impl VhtCapabilities {
}
}

/// The VHT Operation Information's Channel Width (Table 9-274).
/// The VHT Operation Information's Channel Width (Table 9-316).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VhtChannelWidth {
TwentyOrForty,
Expand Down
9 changes: 4 additions & 5 deletions lib/ieee80211/src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//! by, each checked against its definition and allowed once.
//!
//! A vendor-specific element other than WMM's, and any other element this module does not type, is
//! skipped, as §10.28.9 has a receiver ignore what it does not know; one it types that breaks its
//! definition refuses the whole list.
//! skipped, as §10.28.6 and §10.28.7 have a receiver ignore what it does not know; one it types
//! that breaks its definition refuses the whole list.

mod country;
mod he;
Expand Down Expand Up @@ -101,11 +101,10 @@ pub struct Rate {
}

impl Rate {
/// BSS membership selectors (Table 9-78).
/// BSS membership selectors (Table 9-131).
pub const HT_PHY: u8 = 127;
pub const VHT_PHY: u8 = 126;
pub const SAE_H2E_ONLY: u8 = 123;
/// 802.11ax-2021 Table 9-78.
pub const HE_PHY: u8 = 122;
}

Expand Down Expand Up @@ -142,7 +141,7 @@ pub struct Elements<'a> {
pub wmm: Option<Wmm>,
}

/// The Extended Capabilities element: bit n of its field, absent past its length, is 0 (§9.4.2.26).
/// The Extended Capabilities element: bit n of its field, absent past its length, is 0 (§9.4.2.25).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ExtendedCapabilities<'a>(pub &'a [u8]);

Expand Down
18 changes: 11 additions & 7 deletions lib/ieee80211/src/element/rsn.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
//! The RSN element (§9.4.2.24) and the RSN Extension element (§9.4.2.241).
//! The RSN element (§9.4.2.23) and the RSN Extension element (§9.4.2.240).
//!
//! An RSNE's fields after its version are each optional, but only from the end: one present
//! needs every one before it. What follows the last field is ignored, as §10.28.9 has a receiver
//! do with an extensible element.
//! needs every one before it. Table 9-130 does not mark the RSNE extensible, so an octet past its
//! last field is refused. The RSNXE is extensible: octets past its field are ignored, as §10.28.8
//! requires.

use crate::read::Reader;
use crate::Refused;

/// A cipher or AKM suite selector: an OUI or CID and a type (§9.4.2.24.2, §9.4.2.24.3).
/// A cipher or AKM suite selector: an OUI or CID and a type (§9.4.2.23.2, §9.4.2.23.3).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Suite(pub [u8; 4]);

Expand All @@ -16,7 +17,7 @@ impl Suite {
Self([0x00, 0x0F, 0xAC, kind])
}

// Cipher suites, Table 9-149.
// Cipher suites, Table 9-188.
pub const TKIP: Self = Self::ieee(2);
pub const CCMP_128: Self = Self::ieee(4);
pub const BIP_CMAC_128: Self = Self::ieee(6);
Expand All @@ -28,7 +29,7 @@ impl Suite {
pub const BIP_GMAC_256: Self = Self::ieee(12);
pub const BIP_CMAC_256: Self = Self::ieee(13);

// AKM suites, Table 9-151.
// AKM suites, Table 9-190.
pub const AKM_8021X: Self = Self::ieee(1);
pub const AKM_PSK: Self = Self::ieee(2);
pub const AKM_FT_PSK: Self = Self::ieee(4);
Expand Down Expand Up @@ -111,6 +112,9 @@ impl<'a> Rsn<'a> {
return Ok(rsn);
}
rsn.group_management = Some(Suite(r.array()?));
if !r.is_empty() {
return Err(Refused::ElementLength);
}
Ok(rsn)
}

Expand Down Expand Up @@ -147,7 +151,7 @@ impl<'a> Rsnx<'a> {
self.0.get(n / 8).is_some_and(|octet| octet >> (n % 8) & 1 != 0)
}

/// SAE Hash-to-Element, bit 5 (Table 9-321).
/// SAE Hash-to-Element, bit 5 (Table 9-373).
pub fn sae_h2e(&self) -> bool {
self.bit(5)
}
Expand Down
4 changes: 2 additions & 2 deletions lib/ieee80211/src/element/wmm.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The WMM Information and Parameter elements (Wi-Fi Alliance, WMM Specification v1.2, §2.2.1 and
//! §2.2.2): vendor-specific elements under OUI 00-50-F2, OUI type 2, version 1.
//! The WMM Information and Parameter elements (Wi-Fi Alliance, WMM Specification v1.2.0, §2.2.1
//! and §2.2.2): vendor-specific elements under OUI 00-50-F2, OUI type 2, version 1.

use crate::read::Reader;
use crate::Refused;
Expand Down
Loading