sta: a device-free 802.11 station core - frames, BSS table, WPA2-PSK supplicant, CCMP - #454
Conversation
|
/review |
Code Review by Qodo
1.
|
445388c to
2c31af5
Compare
|
/review |
|
Code review by qodo was updated up to the latest commit 2c31af5 |
2c31af5 to
67068cd
Compare
|
/review |
|
Code review by qodo was updated up to the latest commit 67068cd |
67068cd to
33baa1a
Compare
|
/review |
|
Code review by qodo was updated up to the latest commit 33baa1a |
33baa1a to
534b644
Compare
josephnef
left a comment
There was a problem hiding this comment.
Reviewed at 534b644 (draft). Substance: approve; convention: one pass needed before leaving draft.
What I ran
-DDEVOURER_REQUIRE_STA_CRYPTO_TESTS=ON: builds warning-free, ctest 76/76;tests/ccmp_gen_vectors.py --checkreproduces byte for byte.- ASan+UBSan build of the five sta selftests: rc 0, zero reports.
- A random-input harness (ASan+UBSan,
-fno-sanitize-recover) overfind_ie,parse_rsn,parse_beacon/auth/assoc_resp/reason,parse_eapol_key,eapol_mic_ok,find_gtk_kde,ccmp_aad/nonce/decrypt/encrypt,CcmpReplay,BssTable::observe+select,Supplicant::on_eapolwith a permissive stub CryptoOps so the install paths run, andStationSm::on_rx/on_decrypted_msdu/tick/join/leave: 150 s, 654k iterations, no library finding.
Protocol review against 802.11-2016: MIC before any install (constant-time), replay counter moves only after MIC on msg3/group1, strictly-greater-to-install with equal-same-kind answered from cache, msg1 cache keyed on (counter, ANonce), PTK reinstalled only on differing bytes and GTK install a no-op for identical (id, bytes) without re-reading RSC (the CVE-2017-13077/78/80 classes), msg3 Secure+Install+Ack+MIC + encrypted key data + exactly one 16-byte GTK KDE, RSNE downgrade compare over the full suite sets, PTK 16/16/16, PRF-384 label/counter, PBKDF2 4096. CCMP AAD masking (subtype for data, Retry/PwrMgt/MoreData, Order for QoS, Protected set, fragment kept, TID-only QC, A4 included), nonce flags TID | mgmt<<4, 48-bit BE PN, Ext IV + Protected required, NULL-out refusal with the empty-body scratch byte, PN > 2^48-1 refused, replay window PN 0 / equal refused and shift >= 64 guarded. All consistent with the standard and mac80211. MSVC: no VLAs, no attributes, no POSIX headers. CI installs OpenSSL on every ctest job and the require flag is wired on all five.
Should-fix before leaving draft
- Convention:
PR #335appears 8 times insrc/sta/headers (Dot11.h, Ccmp.h, CryptoOps.h, Eapol.h x2, Supplicant.h x2) and there are dated narratives ("missing until 2026-09-21", "hardcoded ... until 2026-09-20", Ccmp.h:103/146/292, Eapol.h:377, StationSm.h:414) plus a dozen "an earlier version / used to" passages (BssTable.h:99/196, Ccmp.h:46/79/397, ...). Code comments are current-state only with no issue refs; state the rule and why, and let the named test cells carry provenance. Docs may keep the #335 history. StationSm.h:484liveness is beacon-only: only beacons/probe responses (lines 249, 306) refreshlast_beacon_ms_. A Connected station receiving downlink data but missing 10 beacons (the promiscuous-RX-under-video-load case where beacons are exactly what gets dropped) is declaredBeaconLostwhile traffic flows. Any frame from the BSSID addressed to us,on_decrypted_msduincluded, should count as liveness, with a cell: data flowing, no beacons, no BeaconLost.
Nits
3. Supplicant.h:393: msg3 Key Length is never compared to 16 (CCMP TK).
4. Supplicant.h:602: a GTK KDE with KeyID 0 installs; a data plane indexing by header key-id cannot tell it from the PTK slot. hostapd never sends it; refuse it.
5. Eapol.h:420: find_gtk_kde steps one byte over a lone 0x00 as padding, but 802.11 padding is 0xdd then zeros and EID 0 is SSID; find_rsn_element in the same file does not skip 0x00. Align the two (not attacker-reachable: input is KEK-unwrapped and MIC-verified).
6. Supplicant.h:350 comment says the genuine msg3 "fails its MIC against that candidate"; it fails the ANonce compare at line 407 (Malformed).
7. StationSm.h:259: leave() from Failed queues a deauth even after AuthTimeout/AssocTimeout where no association exists.
Nothing on air was measured, as the body says; that is right for this PR's scope.
534b644 to
e8ddc34
Compare
|
Thanks for the review and the fuzz run. All seven points are addressed in e8ddc34; the "Maintainer review" section of the description has the detail for each.
REQUIRE ON: ctest 76/76, the RTL8733B-only selftests 71/71, the station cells under ASan+UBSan, and |
e8ddc34 to
442c9f5
Compare
|
@josephnef marking this ready. Your draft review is addressed in e8ddc34, and on top of that 442c9f5 is a pre-ready hardening pass (the "Pre-ready pass" row in the Review rounds table). It adds three behaviour rules:
The rest is consistency: counters for malformed auth/assoc responses and ignored QoS Nulls, one shared key-data walker, and the |
PR Summary by QodoAdd a device-free 802.11 station core with WPA2-PSK and CCMP
AI Description
Diagram
High-Level Assessment
Files changed (28)
|
|
Code review by qodo was updated up to the latest commit 442c9f5 |
442c9f5 to
eb720f2
Compare
josephnef
left a comment
There was a problem hiding this comment.
Re-reviewed at eb720f2 (the squash of e8ddc34 + 442c9f5 + the qodo ready-pass fixes). All seven points from my draft review are addressed, and I read the pre-ready hardening pass and the ready-pass fixes in full.
What I checked at this head:
- Liveness:
on_rxfilters on addr2 == BSSID, so a from-DS data frame the AP relays for any SA passes the filter and refresheslast_heard_ms_; decrypted MSDUs refresh it too.test_data_keeps_the_link_aliveandtest_qos_null_is_ignored_and_alivepin both. - Cleartext EAPOL-Key after keying: group messages never from the clear; before the PTK only pairwise; after it only a retransmission of the installed message 3 (
is_installed_msg3matches ANonce + Install + Secure). That is the right exception, since hostapd installs its PTK only on message 4. join()on a live association queues the deauth to the old BSSID beforebssid_is overwritten, wipes the supplicant, and refuses an SSID that is not the configured one. The2*kMaxTries+1 <= kMaxTxQueuestatic_assert makes the "management requests are never dropped" claim structural.leave()after a timeout: no deauth after AuthTimeout or a peer deauth, one after AssocTimeout. The AssocTimeout choice is right as is: the AP holds our authentication and a deauth is what clears it.- Message 3 Key Length must be 16, GTK key id 0 refused on both routes, one
walk_key_datafor the GTK KDE and the RSN element with the 0xDD padding walked as elements (1..7 bytes pinned, lone 0x00 not skipped). on_msg1commits the candidate only on success;eapol_mic_okis tri-state and a provider failure counts ascrypto_errors, notmic_failures.- Ccmp length arithmetic refuses a wrapping sum (encrypt) and subtracts before indexing (decrypt); the SIZE_MAX cells cover both edges.
- Overlong SSID refuses the beacon; beacon interval capped at 1000 TU;
pop_tx(nullptr)refused. - Convention sweep: no PR references, dates or "used to" narration left in
src/sta, the five selftests, the vector headers or the scripts; the capture scripts now kill by recorded PID only.
Verified locally: -DDEVOURER_REQUIRE_STA_CRYPTO_TESTS=ON, ctest 76/76 (the two mt76-submodule cells skip); tests/ccmp_gen_vectors.py --check reproduces tests/ccmp_vectors.h byte for byte; the five station cells pass under DEVOURER_SANITIZE=address+undefined. Headless only, as the PR states; nothing here touches a radio.
The two declines (whole-element parse_rsn, keeping a beacon whose trailing IE is truncated) match wpa_supplicant and mac80211 respectively and are pinned by cells, so they stand.
Approving.
josephnef
left a comment
There was a problem hiding this comment.
Re-reviewed at eb720f2 (the squash of e8ddc34 + 442c9f5 + the qodo ready-pass fixes). All seven points from my draft review are addressed, and I read the pre-ready hardening pass and the ready-pass fixes in full.
What I checked at this head:
- Liveness:
on_rxfilters on addr2 == BSSID, so a from-DS data frame the AP relays for any SA passes the filter and refresheslast_heard_ms_; decrypted MSDUs refresh it too.test_data_keeps_the_link_aliveandtest_qos_null_is_ignored_and_alivepin both. - Cleartext EAPOL-Key after keying: group messages never from the clear; before the PTK only pairwise; after it only a retransmission of the installed message 3 (
is_installed_msg3matches ANonce + Install + Secure). That is the right exception, since hostapd installs its PTK only on message 4. join()on a live association queues the deauth to the old BSSID beforebssid_is overwritten, wipes the supplicant, and refuses an SSID that is not the configured one. The2*kMaxTries+1 <= kMaxTxQueuestatic_assert makes the "management requests are never dropped" claim structural.leave()after a timeout: no deauth after AuthTimeout or a peer deauth, one after AssocTimeout. The AssocTimeout choice is right as is: the AP holds our authentication and a deauth is what clears it.- Message 3 Key Length must be 16, GTK key id 0 refused on both routes, one
walk_key_datafor the GTK KDE and the RSN element with the 0xDD padding walked as elements (1..7 bytes pinned, lone 0x00 not skipped). on_msg1commits the candidate only on success;eapol_mic_okis tri-state and a provider failure counts ascrypto_errors, notmic_failures.- Ccmp length arithmetic refuses a wrapping sum (encrypt) and subtracts before indexing (decrypt); the SIZE_MAX cells cover both edges.
- Overlong SSID refuses the beacon; beacon interval capped at 1000 TU;
pop_tx(nullptr)refused. - Convention sweep: no PR references, dates or "used to" narration left in
src/sta, the five selftests, the vector headers or the scripts; the capture scripts now kill by recorded PID only.
Verified locally: -DDEVOURER_REQUIRE_STA_CRYPTO_TESTS=ON, ctest 76/76 (the two mt76-submodule cells skip); tests/ccmp_gen_vectors.py --check reproduces tests/ccmp_vectors.h byte for byte; the five station cells pass under DEVOURER_SANITIZE=address+undefined. Headless only, as the PR states; nothing here touches a radio.
The two declines (whole-element parse_rsn, keeping a beacon whose trailing IE is truncated) match wpa_supplicant and mac80211 respectively and are pinned by cells, so they stand.
Approving.
Duplicate of the approval posted 30 s earlier (pagination hid it from my check).
…supplicant, CCMP Header-only and pure under src/sta/: no IRadio, no libusb, no clock, no threads. Time is an argument, frames go in through on_rx() and out through pop_tx(), and crypto is a CryptoOps vtable the caller fills, so libdevourer gains no dependency and every line is testable under plain ctest. - Dot11.h: management/data frame builders and parsers, a bounds-checked IE walker, RSN element parsing (full suite sets), the duplicate cache, the 12-bit sequence counter, a minimal TIM. - BssTable.h: scan results and select(); offers only an infrastructure BSS (ESS set, IBSS clear) on a valid channel (1..14 or 32..253: the DS element, else the caller's RX channel), and for WPA2 only with the Privacy bit set; refuses an MFP-required BSS; a wrap-safe recency tie-break. - CryptoOps.h: AES-128-CCM, HMAC-SHA1, PBKDF2-HMAC-SHA1, RFC 3394 unwrap. - Ccmp.h: CCMP AAD/nonce/header/PN framing, and CcmpReplay - a per-TID 64-wide sliding replay window with seed(rsc) for group keys. - Eapol.h: EAPOL-Key format (descriptor v2 only), PRF, PTK derivation, constant-time MIC verify, GTK KDE, one shared key-data walker, pmk_from_psk (8..63 passphrase or exactly 64 hex), secure_wipe. - Supplicant.h: the station half of the 4-way and group-key handshakes. - StationSm.h: authenticate -> associate -> 4-way -> connected, Open or WPA2-PSK, with injectable time and a decrypted-MSDU path for group rekeys. Security and robustness properties pinned by headless cells: - keys: nothing is installed before its MIC verifies; message 1 never moves the replay counter; only a strictly greater EAPOL-Key counter installs anything, and an equal one is answered with the cached reply; no key reinstallation (CVE-2017-13077/13078/13080 class); GTK RSC seeding of the group replay window. - messages: the msg1 cache matches counter AND ANonce, so a forged msg1 that arrives first cannot poison the genuine one; a msg1 whose derivation fails in CryptoOps leaves the in-flight candidate untouched; message 3 must set Secure and carry Key Length 16; only a 16-byte (CCMP-128) GTK at key id 1-3 installs, from key data that parses to its end (0xdd+zeros padding); the GTK KDE and message 3's RSNE are found by the same walker; the RSNE downgrade check of 802.11-2016 12.7.6.4 over the full suite sets; only EAPOL-Key reaches the supplicant. - cleartext EAPOL once a PTK is installed: dropped, except the AP's retransmission of the installed handshake's message 3 (same ANonce, Install and Secure), which hostapd sends unencrypted after a lost message 4; a group message 1 or a new-ANonce message 3 in the clear never drives a rekey. - CCMP: ccmp_decrypt refuses a NULL output (which OpenSSL CCM would treat as "AAD, no tag check"), a frame without the Protected bit and a header without Ext IV; ccmp_encrypt refuses a PN past 48 bits; lengths whose sum would wrap size_t are refused before any buffer is touched (ccmp_encrypted_len returns 0 for them). - EAPOL format: length fields that overflow or disagree; build_eapol_key refuses inconsistent arguments; the MIC check is tri-state, so a failed HMAC in our own CryptoOps is a CryptoError, never a MicFailed; the 64-hex PSK spelling. - state machine: a failure, peer deauth or leave() drops the queue, the association's keys and the AID, and leave() deauthenticates only if the AP accepted an authentication; join() on a live association deauthenticates from the old AP first and refuses a BSS whose SSID is not the configured one; Connected only once message 4 has actually been queued; a dropped reply does not move the handshake deadline; AP liveness counts any frame from the AP (a beacon with its fixed body, data addressed to us, a decrypted MSDU, a QoS Null, which is otherwise ignored); the beacon-loss window is capped (interval clamped at 1000 TU); a beacon with an SSID over 32 octets and a truncated auth or association response are malformed; only an Association Response answers the Association Request; pop_tx(nullptr) is refused; a reconfigure wipes the previous PMK before deriving; the group rekey through the state machine. Code-reviewed rather than cell-pinned: the EAPOL MIC compare is constant-time (no headless cell can observe timing). Known answers: CCMP frames the Linux kernel encrypted and a real hostapd/wpa_supplicant 4-way, both captured off mac80211_hwsim and checked in (the capture scripts need root + hwsim and are for regeneration only; the captures are not in the tree); IEEE 802.11i Annex H.4.2 PSK vectors; and python-cryptography CCM vectors, a same-author transcription of the framing that pins only the cipher plumbing, with a --check mode. Not covered: any device, hardware crypto offload, PMF/802.11w, TKIP/CMAC/ SAE/EAP, AP-side per-station state, a replay window wider than 64 (HE/EHT BlockAck), and SNonce renewal on an in-association rekey (by design; the caller supplies it). A forged message 1 can still cost a handshake, as it can with wpa_supplicant; documented at Supplicant::on_msg1. DupDetector and the MSDU<->Ethernet helpers have no in-tree caller: StationSm runs no duplicate cache, so the data-plane caller keeps one. Comments in src/sta/ and the station tests state the current rule and its reason, with the named test cells as provenance: no issue references, no dates. Build: six new ctest cells (dot11_frames, bss_table always; ccmp_framing, supplicant, station_sm when OpenSSL is found; ccmp_vectors_generated when Python 3 is). DEVOURER_REQUIRE_STA_CRYPTO_TESTS=ON turns the OpenSSL-missing configure WARNING into an error, and CI sets it on every ctest job (with OpenSSL installed explicitly on Ubuntu, macOS and mingw). The standalone targets request cxx_std_20 for MSVC. docs/station-core.md is the overview; src/sta/CLAUDE.md maps the subtree for maintainers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNC8xhn1rNCi5t6uLvE6M3
eb720f2 to
5f7428f
Compare
sta: a device-free 802.11 station core - frames, BSS table, WPA2-PSK supplicant, CCMP
Size at a glance
+12,937 / −8 lines across 28 files. Under a third is library code:
src/sta/headers (the code under review)openssl_crypto_ops.h,ccmp_software.hccmp_kernel_vectors.h,eapol_kernel_vectors.h,ccmp_vectors.hdocs/station-core.md,src/sta/CLAUDE.md, rootCLAUDE.md,CMakeLists.txt, CI workflowSuggested reading order
Each header only depends on the ones before it:
Dot11.h: frames, the IE walker,parse_rsn, the duplicate cache; no dependencies.BssTable.h: scan results; uses Dot11.CryptoOps.h, thenCcmp.h: the crypto interface, then CCMP framing andthe replay window.
Eapol.h: EAPOL-Key format and the key derivations.Supplicant.h: the handshake decisions; uses Eapol.StationSm.h: the association state machine that drives it all.src/sta/CLAUDE.mdmaps which header holds which rule and which test covers it.What changed
New header-only module
src/sta/, which is pure: noIRadio, no libusb, noclock, no threads, no environment. Time is passed in as an argument. Frames
come in through
on_rx()and go out throughpop_tx(). Crypto is aCryptoOpsvtable that the caller fills, solibdevourergains no dependency.Dot11.hBssTable.hselect()offers only a BSS this station can finish a handshake withCryptoOps.hCcmp.hCcmpReplay(a per-TID 64-wide sliding window, plusseed(rsc))Eapol.hpmk_from_psk,secure_wipeSupplicant.hStationSm.hnow_ms, the decrypted-MSDU path for group rekeysTests: six ctest cells.
dot11_framesandbss_tablealways run.ccmp_framing,supplicantandstation_smneed OpenSSL, sincetests/openssl_crypto_ops.his the testCryptoOps.ccmp_vectors_generatedneeds Python 3 and is skipped without python-cryptography.
Without OpenSSL the three crypto cells are not registered at all, and
configure prints a WARNING. The new option
DEVOURER_REQUIRE_STA_CRYPTO_TESTS=ONturns that into a configure error, and CI sets it on every job that runs
ctest. In the last CI run every job I checked found OpenSSL: Ubuntu gcc/clang,
macOS gcc/clang, MSVC, mingw, sanitizers, and a sampled build-configs cell
(they all share the Ubuntu runner image).
libssl-dev, andopenssl@3withOPENSSL_ROOT_DIR.mingw-w64-x86_64-openssl.through vcpkg would add minutes per run, so it is left as is, but the
option makes a missing one fail loudly.
The standalone targets request
cxx_std_20for MSVC.The AP-side multi-station table (per-station PTK, TX PN and replay state) is
deliberately left out. It will come with a later AP-side PR, together with
its consumer.
Why
An earlier WPA2/CCMP station client, #335 by another contributor, mixed the
protocol and crypto code into a large device PR. Its review found the
EAPOL-Key MIC never verified and KRACK-class handshake defects, and the PR was
later withdrawn. This PR takes the protocol and crypto half on its own, with
no device code, so it can be reviewed and tested without hardware, with a
named test behind every security rule. Backends and harnesses that use
it can follow as separate PRs.
What is measured
Everything here is a headless assertion. Nothing was measured on air in this PR.
the replay counter.
authenticated one to install anything. An equal counter on the same
message is the AP's retransmission: it is answered with the cached reply
and installs nothing. Anything else at or below the counter is refused as
a replay.
sets.
ccmp_decryptrefuses a NULL output, which OpenSSL's CCM would otherwisetreat as AAD and skip the tag check.
ccmp_decryptrequires the Protected bit and Ext IV, andccmp_encryptrefuses a PN past 48 bits rather than reusing a nonce.
its end; its padding is 0xdd then zeros. Only a 16-byte (CCMP-128) GTK
at key id 1-3 installs. The GTK KDE and message 3's RSN element are
found by one shared key-data walker, so the two cannot disagree about
where an element starts.
the cache, so a forged message 1 that arrives first cannot poison the
genuine one. A message 1 whose derivation fails in
CryptoOpsleaves thecandidate of the handshake in flight untouched.
exception: the AP's retransmission of the installed handshake's message 3
(same ANonce, Install and Secure set). hostapd installs its PTK only
after it receives message 4, so when message 4 is lost the retransmitted
message 3 arrives unencrypted, and refusing it would cost the link. A
group message 1 or a message 3 with a new ANonce in the clear never
drives a rekey.
join()on a live association deauthenticates from the old AP beforeauthenticating to the new one, and refuses a BSS whose SSID is not the
configured one (
Failure::SsidMismatch).so at most 10,240 ms), and a beacon whose SSID is longer than 32 octets
is malformed.
malformed. A QoS Null (or any no-data subtype) counts as ignored and
still refreshes AP liveness.
pop_tx(nullptr)is refused and the framestays queued.
leave()drops the queued frames, theassociation's keys and the AID.
leave()sends a deauthentication onlyif the AP accepted an authentication.
queued. A dropped message 4 leaves it in FourWay until a retransmitted
message 3 is answered, or the handshake timeout ends it.
it on, each accepted only if it is a real channel (1–14 or 32–253).
With neither, the BSS is never offered for join, so a 5 GHz beacon
without a DS element is never sent a 2.4 GHz association request.
and WPA2 needs the Privacy bit as well as the RSN element.
its fixed body, a data frame from the BSSID addressed to this station,
or a decrypted MSDU. A link carrying downlink traffic is not declared
lost for want of beacons, and a header-only beacon does not count.
handed back to the caller on the decrypted path, and ignored in the
clear.
refused passphrase cannot leave it resident.
StationSm.eapol_mic_okis constant-time (an OR-reduction over all 16 bytes, no earlyexit). A headless cell cannot observe timing, so the only check is reading
the loop.
at all eight TIDs, and a real hostapd/wpa_supplicant 4-way (with the PTK
and GTK those tools logged). Both were captured off
mac80211_hwsimand arechecked in, so
ctestneeds no rig. The IEEE 802.11i Annex H.4.2 PSKvectors are also included.
tests/ccmp_vectors.h(python-cryptography) is a same-authortranscription of the framing. It pins only the cipher plumbing; a zero
CCM nonce Flags octet passes it. Its new
--checkproves onlythat the file matches its generator, not that the generator is right.
author with the code they test. That includes the new group-rekey cell;
there is no captured hostapd group rekey.
interop reference, not the IEEE Annex J vector. If mac80211 and this code
misread the same clause in the same way, no cell would notice.
be regenerated from it; a new capture negotiates new keys.
single in-flight candidate PTK. The genuine message 3 is then refused at
the ANonce comparison until the AP restarts the handshake. This is the same exposure
wpa_supplicant has with its TPTK. During the initial 4-way it costs an
attempt. During a PTK rekey on an established association it can cost
the link, if the AP gives up and deauthenticates. The installed PTK is
never touched. Documented at
Supplicant::on_msg1.two different vendor suites compare equal. That is stricter than
wpa_supplicant, which drops them, but it is not exact.
What it deliberately does not do
calls. A caller feeds frames in.
CryptoOps, and its CPUcost is not measured here.
frames are accepted unauthenticated.
own inline builders;
Dot11.h's golden-byte cells pin the bytes they air.msdu_to_eth/eth_to_msduhave no in-tree caller yet, and neither doesDupDetector:StationSmruns no duplicate cache, so the data-planecaller that
src/sta/CLAUDE.mdand theDupDetectorcontract describemust keep one per transmitter itself.
supplies it per association, and the library has no RNG. The PTK still
changes because the ANonce does.
BlockAck. An HE/EHT peer negotiating 256/1024 would need it widened first.
Review rounds
Every finding was checked against the code before any change. Each code fix
comes with a named selftest cell, and each was mutation-checked: the fix was
reverted and its cell confirmed to fail. The per-finding detail is in the
review threads on this PR, not repeated here.
send_auth/send_assoc, now astatic_assertThe pre-ready cleartext-EAPOL rule keeps one exception (the retransmitted installed message 3), because hostapd sends it in the clear after a lost message 4.
Declined, with reasons:
parse_rsnto consume the whole element (qodo pass 3).wpa_supplicant ignores trailing bytes after the last field it knows, and
reads an optional field only when all of it is present. That leaves room
for fields a later amendment appends. The parser already has that shape,
and it rejects a PMKID count that overruns the element, as wpa_supplicant
does.
test_parse_rsn_setspins both.finding was fixed: the msg1 cache now matches the ANonce. The library takes
its SNonce from the caller per association and has no RNG, and
wpa_supplicant also keeps one SNonce within a handshake.
parse_rsnto consume the whole element (i == len): thesame trailing-field rule as the qodo pass 3 decline.
parse_beaconkeeping a BSS whose last IE is truncated: the elementsbefore the truncation are used, as Linux's
ieee802_11_parse_elemsdoes. An AP whose RSN element is the truncated one still sets Privacy,
so it is offered neither as open nor as WPA2.
parse_rsnstill accepts a truncated RSNE, because 802.11 allowstrailing fields to be omitted.
documented.
Maintainer review
Answers to the review of this draft, point by point.
history. All
src/sta/headers, the five station selftests, the vectorfiles and the generator/extractor/capture scripts were swept.
draft / review found" passage is rewritten as the rule plus its reason,
in the present tense.
rule is named instead.
produce them were changed identically, and
tests/ccmp_vectors.hstillreproduces byte for byte.
docs/station-core.mdkeeps its provenance section; it has noreview-round narration.
AP-liveness clock: a beacon with its fixed body, a from-DS data frame from
the BSSID addressed to this station (protected or not, with its full
header present), or any MSDU handed to
on_decrypted_msdu. A header-onlybeacon still does not count.
Failure::BeaconLost,beacon_loss_ms()andbeacons_rxare kept, to avoid API churn. They are documented as "noframe from the AP within the window". The private member is renamed
last_heard_ms_.test_data_keeps_the_link_alive: three loss windows of downlinkdata (protected frames through
on_rx, then decrypted MSDUs) with nobeacons stay Connected. With nothing at all, the link reaches BeaconLost.
Each of the two refresh points was mutation-checked on its own.
refused as Malformed before its MIC is checked. Cell
test_msg3_key_length_must_be_16.Cell
test_gtk_key_id_zero_is_refused.find_gtk_kdeno longer steps over a lone 0x00, soit walks key data exactly as
find_rsn_elementdoes. The 0xdd pad is readas an empty element, pairs of zeros after it as empty ID-0 elements, and a
final odd byte ends the walk. The captured hostapd four-way still passes.
Cell
test_gtk_kde_padding: 1..7 bytes of padding after a GTK KDE areaccepted, and a lone 0x00 ahead of it is not skipped.
refused at the ANonce comparison (Malformed) before any MIC is computed;
it does not fail its MIC.
leave()after a timeout. A deauthentication goes outonly if the AP accepted an authentication. There is none after an
AuthTimeout, and none after the AP's own deauth.
authentication, and a deauth is what clears it.
test_leave_after_a_timeoutcovers AuthTimeout (nothing sent),AssocTimeout (one deauth) and a peer deauth (nothing sent).
Verification
Also checked:
--target selftests)-DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON)-DDEVOURER_REQUIRE_STA_CRYPTO_TESTS=ONDEVOURER_SANITIZE=address+undefinedpython3 tests/ccmp_gen_vectors.py --checktests/ccmp_vectors.hreproduced byte for byteThe vector capture scripts (
tests/ccmp_capture_vectors.sh,tests/eapol_capture_vectors.sh) need root andmac80211_hwsim. They are forregenerating the checked-in headers only and were not run for this PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VNC8xhn1rNCi5t6uLvE6M3