* rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter
@ 2026-10-16 19:11 Bob Beckett
2026-10-16 22:15 ` Luka Gejak
` (2 more replies)
0 siblings, 3 replies; 16+ messages in thread
From: Bob Beckett @ 2026-10-16 19:11 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, kernel, matthew.leach
Hi Ping-Ke,
We are investigating a Wi-Fi failure on the Steam Deck LCD (RTL8822CE,
rtw88) and would appreciate your input; details below.
Summary
=======
On RTL8822CE (PCIe, rtw88), during heavy 5 GHz VHT downlink from one AP
model, the driver occasionally hands mac80211 a frame with these
properties:
- the RX descriptor looks normal: CRC OK, ICV OK, AES, not
software-decrypted;
- the 802.11 header is correct;
- the bytes from the CCMP header onwards are garbage.
Because rtw88 sets RX_FLAG_DECRYPTED for it, mac80211 trusts the frame
and takes its PN, a random 48-bit value, as the new last PN for that
key/TID. Every later genuine frame has a lower PN and is dropped as
RX_DROP_U_REPLAY. The station stays associated with good signal but no
data gets through until new keys are installed (reconnect). Userspace
sees this as "limited connectivity".
The AP's own PN sequence is consistent before and after the bad frame,
so the corruption happens on the receive side. The host-side DMA
bookkeeping looks consistent: the RX buffer-descriptor tag never
mismatched. The corruption starts exactly where the CCMP header begins,
which points at the device's RX decrypt/payload path rather than host
DMA. The same hardware works with this AP under Windows, so we would
like to know what the vendor driver does differently, and whether the
descriptor can tell us when a frame's payload cannot be trusted.
Setup
=====
- Steam Deck LCD: RTL8822CE (AzureWave), PCIe x1 Gen1, rtw88_8822ce
- Firmware 9.9.15 (WOW 9.9.4), H2C version 15
- Seen on SteamOS kernels 6.18.50 and 7.2.7. Both carry rtw88 backports
from rtw-next; the 7.2.7 rtw88 is close to rtw-next as of 2026-09.
- AP: Free "Freebox Revolution", 5 GHz channel 36, 80 MHz, VHT, max MPDU
11454, max A-MSDU (HT) 3839, CCMP pairwise and group, PSK/SAE
- Link at the time: signal -30 to -34 dBm, RX VHT-MCS 8-9 NSS 2 80 MHz,
during a bulk TCP download (game download) at ~20k RX descriptors/s
- wpa_supplicant + NetworkManager
Public reports of the same symptom with this AP (Steam Deck LCD +
Freebox Revolution, 5 GHz, drops when a download starts, fixed by
disabling 802.11ac on the AP, works under Windows) go back to at least
September 2024.
How it was captured
===================
The capture used bpftrace on the stock kernel, with no driver changes:
- a kprobe on ieee80211_crypto_ccmp_decrypt() compares the frame's PN
with key->u.ccmp.rx_pn[rx->security_idx] before the check, and on a
forward jump above 0x10000 dumps:
- the frame;
- the rtw88 RX descriptor plus drv_info area, which is still in the
skb in front of skb->data (rtw_pci_rx_napi() uses skb_pull());
- the previous 16 and next 24 CCMP frames;
- a kprobe on rtw_rx_query_rx_desc() classifies every descriptor;
- an fexit on rtw_pci_read32() for HISR0/HISR1;
- tracepoint:skb:kfree_skb provides drop reasons;
- mac80211 debugfs keys/*/rx_spec and replays were sampled every 2 s.
Evidence 1: the replay counter is poisoned by one frame
=======================================================
From mac80211 debugfs, for the pairwise key at TID 0:
Run rx_spec before At failure ~1 min later replays
-------- -------------- ------------ ------------ ------------
A (6.18) 0000000004b1 38edae88b295 38edae88b295 1508 -> 2759
B (7.2) 0000000093d2 cc7106634a65 cc7106634a65 748 -> 1832
- The stored PN never moves again, so no pairwise-protected frame is
accepted after the bad one: every one that reaches the CCMP check is
dropped as RX_DROP_U_REPLAY (skb:kfree_skb reason 0x10002 in
ieee80211_rx_handlers). The only data still accepted is
group-addressed traffic, which uses the unaffected group key; its PN
keeps advancing normally.
- Reconnecting installs new keys and traffic resumes, until the next bad
frame.
Evidence 2: the bad frames
==========================
Run C (7.2.7) caught 4 PN jumps in about 6 minutes:
# seq last PN PN in frame expected PN
- ---------- -------------- -------------- -----------------------
1 4025 0xb8fba 0x3dcdcabef924 0xb8fbc (4026: 0xb8fbd)
2 3174 0xcc69 0x08e1ad331968 0xcc6a (3175: 0xcc6b)
3 64 (retry) 0x15043 0x4bf378495044 0x15044 (66: 0x15046)
4 65 (retry) 0x4bf378495044 0xf0e626ce3844 0x15045
All four: QoS data, TID 0, A-MSDU, 3088 bytes. 3 and 4 came seconds
after a reconnect. "Expected PN" is derived from the neighbouring
frames (next SN in brackets).
Around jump 1, the AP's PN sequence was:
- seq 4009-4023: PN 0xb8fac-0xb8fba, consecutive;
- seq 4024: never reached the CCMP check;
- seq 4025: the bad frame;
- seq 4026, 4027, ...: PN 0xb8fbd, 0xb8fbe, ..., all rejected as
replays.
So the AP transmitted a normal PN for 4025.
The four bad frames share these properties:
- The RX descriptor is identical to the good frames around them:
w0=0x00c40c14, i.e. pkt_len 3092, drv_info_sz 4, PHYST 0, enc AES,
CRC32 0, ICV_ERR 0, SWDEC 0. w1=0x2100b004, w3=0x3f (VHT 2SS MCS9).
The low 12 bits of w2 equal the 802.11 sequence number of the frame.
- The 802.11 header is correct: FC 0x4288/0x4a88 (QoS data, FromDS,
Protected, +/-Retry), correct addresses, sequence number matching the
descriptor, QoS control 0x0080 (TID 0, A-MSDU present).
- The bytes from header offset 26 are garbage. A valid CCMP header has
byte 2 = 0x00 and byte 3 = 0x20 (ExtIV, key ID 0), as all the good
frames do:
- frame 1: 24 f9 1a 04 ...
- frame 2: 68 19 8e b7 ...
- frame 4: 44 38 f1 14 ...
- frame 3: 44 50 00 20 49 78 f3 4b. The first four bytes match the
expected PN 0x015044 exactly, then it is garbage from header offset
30.
- The "decrypted" A-MSDU body that follows is random. It should start
with a subframe header containing the station's own address.
- mac80211 receives flag=0x20102, i.e. RX_FLAG_DECRYPTED |
RX_FLAG_NO_SIGNAL_VAL | RX_FLAG_MACTIME_START (no PHY status, so no
signal value).
In other words, the descriptor and roughly the first 26-30 bytes of the
frame are right, and the rest of the buffer does not contain this
frame's data.
Evidence 3: other RX-path anomalies in the same run
===================================================
- 33 s before the first jump, during the download and with no other
activity, one descriptor claims pkt_len 11619 (above the 11454
maximum), and rtw_rx_query_rx_desc() returns -EINVAL twice in the same
second. These are garbage descriptors, which the rtw-next
RX-descriptor validation correctly rejected. This was never seen in a
21-minute ~540 Mbit/s test against a different AP.
- Not related to the bug (capture artifacts): the HISR1 RXFOVW bit (RX
FIFO overflow) and 18 "coex request time out" errors appeared only
within 1-2 s of the capture tool's debugfs snapshots. Those snapshots
read coex_info and the mac_*/bb_* register dumps, which hold
rtwdev->mutex and leave deep PS. None of the PN jumps coincide with a
snapshot.
- Firmware communication otherwise looked healthy: no H2C send failures;
663 "C2H 0x15 isn't handled" (benign); 3 TX-report purge timeouts in
~8 minutes.
Observation: heavy AP retry activity around the event
-----------------------------------------------------
Shortly after jump 1, the genuine TID 0 stream shows a single PN gap
with consecutive sequence numbers:
seq=4044 pn=0x0000000b8fcf w0=0x00c40c14 (no PHY status)
seq=4045 pn=0x0000000b9053 w0=0x04c40c14
(PHY status present: first MPDU of a new PPDU)
- Of 69 consecutive-SN pairs logged in this run, this is the only one
where the PN advances by more than 1; here it advances by 0x84.
- The PN counter is per key and the SN counter per TID, so the AP
encrypted 131 other frames for this station in between without using
new TID 0 sequence numbers.
- No other TID's PN counter on the pairwise key ever advanced (rx_spec
for TIDs 1-15 stays 0), so these were not frames on another TID that
we accepted.
- The gap falls between two A-MPDUs.
The likeliest explanation is that the AP retransmitted about 131
already-numbered MPDUs and re-encrypted each retransmission with a fresh
PN. IEEE 802.11 expects retransmissions to keep their PN, but some
implementations re-encrypt on software retry. This matches what else
happened around the onset:
- a BlockAck Request (BAR_RX_EVENT) and a BlockAck frame timeout;
- about 3,500 data frames in the second after the jump that never
reached the CCMP check and left no kfree_skb drop reason. That is
consistent with mac80211's A-MPDU reorder buffer discarding
out-of-date retransmissions via dev_kfree_skb(), i.e. consume_skb(),
which kfree_skb tracing cannot see.
This is not confirmed yet; a follow-up capture traces skb:consume_skb
per caller to account for those frames. A PN gap is legal and is not the
cause of the failure. It does suggest that the bad frames arrive while
the AP is doing heavy retry/re-aggregation toward the station.
Where the corruption appears to come from
=========================================
Host DMA bookkeeping is consistent. rtw88's rtw_pci_dma_check() compares
the RX tag in each buffer descriptor with the expected sequence and logs
"pci bus timeout, check dma status" on a mismatch (an unconditional
dev_warn). That warning appears zero times in all three runs, so every
buffer descriptor had been written when the driver consumed it. The
start of each bad buffer is also fresh: the RX descriptor's sequence
number (w2[11:0]) matches the 802.11 header's.
The corruption boundary does not line up with PCIe writes. The device
has MaxPayload 128 bytes, and on this host nearly every RX buffer goes
through SWIOTLB bounce slots, which start 2 KB-aligned because rtw88
sets no min_align_mask (see below). So DMA writes split at 128-byte
boundaries of the buffer. The good data ends 82 or 86 bytes into the
buffer, which is not such a boundary.
It lines up with the CCMP header. In frame coordinates, the good data
ends exactly at offset 26 (the start of the CCMP header), or 4 bytes
into it in one frame. That is where the part of the frame the MAC passes
through unchanged (the 802.11 header) ends, and the part handled by the
hardware decryption engine begins. Our reading is that for these frames
the payload/decrypt path produced wrong data while the descriptor still
reported CRC OK, ICV OK and hardware-decrypted. That would be a
device-internal condition, possibly related to the RX FIFO: the garbage
descriptors at Evidence 3 point the same way.
Remaining host-side possibilities, considered less likely:
- Relaxed Ordering: DevCtl: RlxdOrd+ is enabled for the device. If its
payload writes carried the RO attribute, they could in principle be
overtaken by the descriptor/tag write. It can be tested at runtime by
clearing RO with setpci.
- Index ahead of DMA: Realtek fixed this class of race in rtw89
(0bc7d1d4e63c "validate RX tag for RXQ and RPQ": "DMA and ring index
are asynchronous, so suddenly driver gets newer index ahead before
DMA"; also 94298477f81a, dda27a47c036). For pre-WiFi 7 chips the
commit says they "use different design and don't really meet this
situation". Here the buffer descriptor and the head of the buffer were
demonstrably complete.
Platform note: the kernel boots with amd_iommu=off ("PCI-DMA: Using
software bounce buffering for IO (SWIOTLB)"). rtw88 uses the default
32-bit DMA mask, because its RX buffer descriptor holds a 32-bit
address, and the machine has 16 GB, so nearly every RX buffer is
bounced. rtw_pci_rx_napi() syncs the full RTK_PCI_RX_BUF_SIZE (11478
bytes) per frame regardless of length, about 230 MB/s of memcpy at 20k
frames/s. This does not explain a boundary at the CCMP header, but it is
unnecessary overhead.
For comparison, rtlwifi's 8192EE ("new TRX flow" rings, commented "in
Windows, this == Rx_92EE_Interrupt") polls the RX buffer descriptor
before use (rtl92ee_rx_check_dma_ok()), and only passes frames up when
!crc && !hwerror (hwerror = crc | icv). rtw88 passes ICV-error frames up
as decrypted.
Ruled out
=========
- Oversized MPDUs (new_len > RTK_PCI_RX_BUF_SIZE): the bad frames are
3092 bytes, and A-MSDUs from this AP top out at about 9.7 KB.
- ASPM / deep LPS: jumps 3 and 4 happened seconds after reloading with
disable_aspm=1 disable_lps_deep=1.
- Power save (iw ... power_save off) and beamformee (support_bf=0 +
reconnect): no effect.
- Driver consuming a ring entry before its buffer descriptor was
written: no RX-tag mismatch ("pci bus timeout") in any run.
- The AP sending a bad PN: the PN sequences on both sides of each bad
frame are consecutive.
- The RX-descriptor validation added in rtw-next: it does not reject
these frames, because their descriptors are well-formed.
Questions for Realtek
=====================
1. Is there a known condition on 8822C (or 8822B/8821C) where a
hardware-decrypted MPDU or A-MSDU is delivered with a valid RX
descriptor (CRC32 0, ICV_ERR 0, SWDEC 0, enc AES) and a valid 802.11
header, but an invalid CCMP header and payload? Does the RX
descriptor or drv_info have any bit that flags it? For example
"payload invalid", "decrypt engine bypassed or aborted", or a
per-packet RX FIFO overflow.
2. What does the vendor/Windows driver check before trusting a
hardware-decrypted frame, and does it handle HISR1 RXFOVW / HISR0
RDU? rtw88 masks and ignores both.
3. Are there RX FIFO, RX DMA, security-engine or RX aggregation settings
in the Windows driver for 8822CE that differ from rtw88's and matter
for high-rate VHT A-MSDU reception (80 MHz, 2SS MCS 8-9, ~3 KB
A-MSDUs at ~20k/s)?
4. For completeness: on these chips can RXBD_IDX or the
buffer-descriptor RX tag ever be updated before the packet data DMA
has completed, as described for WiFi 7 in rtw89 0bc7d1d4e63c?
5. rtw_rx_fill_rx_status() sets RX_FLAG_DECRYPTED whenever !swdec &&
enc_type != NONE, ignoring ICV_ERR. rtw89 sets RX_FLAG_FAILED_FCS_CRC
and not DECRYPTED on icv_err; mt76 marks such frames monitor-only;
rtlwifi drops them. rtw88 does not set BIT_AICV in RCR, so such
frames are presumably dropped in hardware, and these frames had
ICV_ERR clear anyway. Would you take a patch aligning rtw88 with
rtw89 regardless?
Possible mitigation
===================
Whatever the device-side cause turns out to be, one bad frame should not
take the link down. As a defence, rtw88 could refuse to claim decryption
for an AES frame whose CCMP header is malformed. The ExtIV bit must be
set and the reserved byte must be zero for CCMP/GCMP. That lets mac80211
treat the frame as not decrypted: it then fails software decryption and
is dropped without updating the PN. This would have caught 3 of the 4
frames above; frame 3 had a plausible first half of the IV. Sketch,
untested:
/* rx.c, rtw_rx_fill_rx_status() */
if (pkt_stat->crc_err || pkt_stat->icv_err)
rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
if (pkt_stat->decrypted && !pkt_stat->icv_err &&
rtw_rx_ccmp_hdr_valid(pkt_stat, hdr))
rx_status->flag |= RX_FLAG_DECRYPTED;
static bool rtw_rx_ccmp_hdr_valid(struct rtw_rx_pkt_stat *pkt_stat,
struct ieee80211_hdr *hdr)
{
u8 *iv;
if (!ieee80211_has_protected(hdr->frame_control))
return true;
if (pkt_stat->pkt_len < ieee80211_hdrlen(hdr->frame_control) +
IEEE80211_CCMP_HDR_LEN)
return false;
iv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control);
/* CCMP/GCMP header: byte 2 reserved (0), byte 3 ExtIV must be set */
return iv[2] == 0 && (iv[3] & 0x20);
}
(This only applies when the hardware's enc_type is AES, and needs
pkt_len to be checked against the copied length.)
This does not catch a frame whose corruption begins after the ExtIV
byte, like frame 3. The real fix depends on Realtek's answer to
questions 1-3: a descriptor bit to honour, or an RX setting that avoids
the condition. A more general option would be a mac80211-side sanity
limit on the forward PN jump accepted from hardware-decrypted frames,
but that is a policy change in mac80211 rather than a driver fix.
Unrelated to the bug: syncing only the descriptor first and then
(pkt_len + offset) bytes, instead of the full 11478, would cut the
SWIOTLB copy cost per frame by about 4x for typical 3 KB frames on hosts
like this one.
Appendix: raw dumps (run C, MACs redacted)
==========================================
MAC addresses are redacted: SS = station (Steam Deck), AA = AP.
Layout of the buffer dump: 24-byte RX descriptor, 32-byte drv_info area
(PHYST=0, so this area is not a PHY status and contains stale bytes),
then the 802.11 frame.
Jump 1
------
q=0 seq=4025 last=0x0000000b8fba pn=0x3dcdcabef924 len=3088
flag=0x20102 hdrlen=26 fc=0x4288 desc_off=56 w0=0x00c40c14 w3=0x0000003f
RX desc dwords: w0=00c40c14 w1=2100b004 w2=80890fb9
w3=0000003f w4=3ffe7600 w5=0476df59
buffer (RX desc 24 + drv_info 32, then frame):
0000: 14 0c c4 00 04 b0 00 21 b9 0f 89 80 3f 00 00 00
0010: 00 76 fe 3f 59 df 76 04 96 3f a1 bd fb 6c 9f 26
0020: 5b 31 fa 29 90 c6 2c 92 f9 f4 86 44 70 47 1d 2a
0030: a2 47 2c df 83 43 0f ae 88 42 30 00 SS SS SS SS
frame[0:128] as passed to mac80211:
0000: 88 42 30 00 SS SS SS SS SS SS AA AA AA AA AA AA
0010: AA AA AA AA AA AA 90 fb 80 00 24 f9 1a 04 be ca
0020: cd 3d f1 98 26 03 ca 51 c5 d7 72 b4 5c cb d4 a9
0030: 20 09 37 ed 83 6c c8 0d 1f 45 03 ac 86 ec d1 30
0040: d2 42 b1 ac 33 33 8f b6 95 66 c1 c3 28 ea 84 a7
0050: dc a3 6d 4a 86 10 9d f6 ce a8 a3 91 5b 7d be ad
0060: d0 40 2a 35 cf b5 69 60 58 a5 6a df fd 62 b4 df
0070: 4c c3 c6 c0 17 28 4c 25 ae b2 24 1d 05 44 e9 28
Jump 2
------
q=0 seq=3174 last=0x00000000cc69 pn=0x08e1ad331968 len=3088
flag=0x20102 hdrlen=26 fc=0x4288 desc_off=56 w0=0x00c40c14 w3=0x0000003f
RX desc dwords: w0=00c40c14 w1=2100b004 w2=e0890c66
w3=0000003f w4=3ffea000 w5=0042a4f3
buffer (RX desc 24 + drv_info 32, then frame):
0000: 14 0c c4 00 04 b0 00 21 66 0c 89 e0 3f 00 00 00
0010: 00 a0 fe 3f f3 a4 42 00 e7 ee 7c c1 05 59 a8 1f
0020: 14 0c c4 00 04 b0 00 21 5d 0c 89 e0 3f 00 00 00
0030: 00 a0 fe 3f fa a3 42 00 88 42 30 00 SS SS SS SS
frame[0:128] as passed to mac80211:
0000: 88 42 30 00 SS SS SS SS SS SS AA AA AA AA AA AA
0010: AA AA AA AA AA AA 60 c6 80 00 68 19 8e b7 33 ad
0020: e1 08 62 33 52 ed 0d 6a 45 ae 7d b0 a8 cc 18 03
0030: 4a e2 fc 7e cd b3 e8 fc 99 d2 03 86 19 f1 a9 c5
0040: 5f ef ba d6 91 24 48 24 a2 e6 de bb dd 42 4f 16
0050: cc 0f bd ff d5 c4 a8 8b 8e ca f0 ed 77 ea c5 e8
0060: 95 b9 51 1a 8a 88 bb 11 08 2a 54 7c 74 08 b5 df
0070: b7 6b e1 5d 5b c6 0e fa a6 f8 7b 05 57 98 69 c8
Jump 3
------
q=0 seq=64 last=0x000000015043 pn=0x4bf378495044 len=3088
flag=0x20102 hdrlen=26 fc=0x4a88 desc_off=56 w0=0x00c40c14 w3=0x0000003f
RX desc dwords: w0=00c40c14 w1=2100b004 w2=80890040
w3=0000003f w4=3ffee400 w5=00e8b9cd
buffer (RX desc 24 + drv_info 32, then frame):
0000: 14 0c c4 00 04 b0 00 21 40 00 89 80 3f 00 00 00
0010: 00 e4 fe 3f cd b9 e8 00 c3 55 de b3 f6 ad 3a 63
0020: a3 c2 45 b0 5e f6 1c a3 9a 18 cc e1 a8 be 11 9e
0030: ec a8 48 3d d1 3f 03 be 88 4a 30 00 SS SS SS SS
frame[0:128] as passed to mac80211:
0000: 88 4a 30 00 SS SS SS SS SS SS AA AA AA AA AA AA
0010: AA AA AA AA AA AA 00 04 80 00 44 50 00 20 49 78
0020: f3 4b 75 3e db 0e a4 3d 81 52 1c 1a ff b2 f2 cd
0030: 04 dd ca 84 b9 e8 05 cc 72 7b e6 5a 7e 03 40 a6
0040: 95 6f d6 d8 94 89 ef 3f 95 76 cc 37 c2 40 6c 35
0050: a4 93 9f 13 8d fb 0f ad 15 45 47 32 86 96 b0 bc
0060: 1f 76 b5 af 83 b1 ed 87 d6 c0 79 12 56 89 57 c4
0070: de dc dd 44 27 77 a3 65 cd 67 1e 49 a1 ab d8 c1
Jump 4
------
q=0 seq=65 last=0x4bf378495044 pn=0xf0e626ce3844 len=3088
flag=0x20102 hdrlen=26 fc=0x4a88 desc_off=56 w0=0x00c40c14 w3=0x0000003f
RX desc dwords: w0=00c40c14 w1=2100b004 w2=80890041
w3=0000003f w4=3ffee400 w5=00e8b9e9
buffer (RX desc 24 + drv_info 32, then frame):
0000: 14 0c c4 00 04 b0 00 21 41 00 89 80 3f 00 00 00
0010: 00 e4 fe 3f e9 b9 e8 00 75 04 60 c1 fa f1 24 13
0020: 47 b6 bc 69 fe 61 9d c0 23 83 52 cb 32 12 4d b1
0030: e4 8c ec 59 84 ec 40 fa 88 4a 30 00 SS SS SS SS
frame[0:128] as passed to mac80211:
0000: 88 4a 30 00 SS SS SS SS SS SS AA AA AA AA AA AA
0010: AA AA AA AA AA AA 10 04 80 00 44 38 f1 14 ce 26
0020: e6 f0 d3 39 6d 3f 56 26 4b 47 7f 76 8e 53 e4 0b
0030: dc 81 57 79 85 94 0b d5 c0 f7 d9 00 84 6f 34 58
0040: 6f f7 54 33 48 55 9f c5 82 69 71 6a 1b 6e 99 2c
0050: 64 54 62 9e 73 f1 80 4e 8b 3f d4 3e 24 bc c7 a9
0060: 81 15 18 82 eb d9 06 57 ae 14 e2 cb c0 61 2a 9f
0070: ab d9 4d 92 9f d6 d6 fb ea 28 09 64 b5 6e f5 6d
Notes
=====
- The capture reads rtw88's rsvd_page debugfs file. On kernels without
rtw-next 81510c3d6f21 ("debugfs: don't vzalloc(0) when rsvd_page is
read unconfigured"), that triggers a vmalloc !size WARN. This is
unrelated to this issue.
Thanks,
Bob
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-16 19:11 rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Bob Beckett @ 2026-10-16 22:15 ` Luka Gejak 2026-10-17 8:36 ` Luka Gejak 2026-10-19 12:43 ` Matthew Leach 2026-10-21 7:30 ` Ping-Ke Shih 2026-10-21 11:32 ` Bitterblue Smith 2 siblings, 2 replies; 16+ messages in thread From: Luka Gejak @ 2026-10-16 22:15 UTC (permalink / raw) To: Bob Beckett, Ping-Ke Shih; +Cc: linux-wireless, kernel, matthew.leach Hi Bob, Thanks for the write up. I have a different read on this one. I don't think those frames were decrypted at all, and I think the descriptor is telling us less than it looks. Notes below. > the bytes from the CCMP header onwards are garbage. That data is not from this frame. Your jump 2 dump shows where it comes from. The drv_info area is the 32 bytes at offset 24, and the hardware leaves it alone when PHYST is 0, so whatever is siting there is old. At offset 32 there is a whole RX descriptor, same w0, w1, w3 and w4 as the live one, w2 sequence number 0xc5d and TSF 0x0042a3fa. The live frame is sequence 0xc66 with TSF 0x0042a4f3. So that memory is from ~9 frames and ~250us earlier. The ring has 512 buffers and one frame per buffer, so the same buffer only comes around again after about 512 frames. Data from approx. 9 frames ago can't be leftover from the last time that buffer was used, so I think the hardware wrote it for some other frame. So for the frame we got, the hardware wrote the descriptor and the 802.11 header and then stopped, and the rest of the buffer still had that other frame in it. Same for the feame 3. Its first four CCMP bytes are 44(PN0) 50(PN1) 00(the zero reserved byte) 20 (ExtIV), so that write got four bytes in the CCMP header and then stopped. > our reading is that for these frames the payload/decrypt path produced > wrong data while the descriptor still reported CRC OK, ICV OK and > hardware decrypted. I read it the other way. Nothing was decrypted into that area. The CRC, ICV and decrypt bits are the results of checks the hardware does before it hands the frame over, and the payload write is a separate step after those checks which just did not happen here. So a good descriptor doesn't mean the frame in the buffer is real. My guess for what takes the link down is that rtw_rx_query_rx_desc sets decrypted from swdec and the enc type alone, without looking at icv_err, and then rtw_rx_fill_rx_status sets RX_FLAG_DECRYPTED, with which mac80211 reads the PN out of the frame itself and saves it as the last PN for the key, and it only rejects a PN that is lower. So one frame like this pins the key to a random PN, and everything after it is dropped as a replay until new keys go in. I will send a patch that fixes this part so you can test it. It should turn a dead link into a dropped frame, but either way the test will tell. Best regards, Luka Gejak ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-16 22:15 ` Luka Gejak @ 2026-10-17 8:36 ` Luka Gejak 2026-10-19 12:43 ` Matthew Leach 1 sibling, 0 replies; 16+ messages in thread From: Luka Gejak @ 2026-10-17 8:36 UTC (permalink / raw) To: Luka Gejak, Bob Beckett, Ping-Ke Shih Cc: linux-wireless, kernel, matthew.leach On Sat Oct 17, 2026 at 12:15 AM CEST, Luka Gejak wrote: > Hi Bob, > [...] > I will send a patch that fixes this part so you can test it. It should > turn a dead link into a dropped frame, but either way the test will tell. Followup, here is the link to a patch so you can test it: https://lore.kernel.org/linux-wireless/20261017083313.56113-1-luka.gejak@linux.dev/T/#u Best regards, Luka Gejak ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-16 22:15 ` Luka Gejak 2026-10-17 8:36 ` Luka Gejak @ 2026-10-19 12:43 ` Matthew Leach 2026-10-19 16:30 ` Luka Gejak 1 sibling, 1 reply; 16+ messages in thread From: Matthew Leach @ 2026-10-19 12:43 UTC (permalink / raw) To: Luka Gejak; +Cc: Bob Beckett, Ping-Ke Shih, linux-wireless, kernel "Luka Gejak" <luka.gejak@linux.dev> writes: > Hi Bob, Hi Luka, > Thanks for the write up. I have a different read on this one. I don't > think those frames were decrypted at all, and I think the descriptor > is telling us less than it looks. Notes below. Thanks for taking the time to look at this. >> the bytes from the CCMP header onwards are garbage. > > That data is not from this frame. Your jump 2 dump shows where it > comes from. The drv_info area is the 32 bytes at offset 24, and the > hardware leaves it alone when PHYST is 0, so whatever is siting there > is old. At offset 32 there is a whole RX descriptor, same w0, w1, w3 > and w4 as the live one, w2 sequence number 0xc5d and TSF 0x0042a3fa. > The live frame is sequence 0xc66 with TSF 0x0042a4f3. So that memory > is from ~9 frames and ~250us earlier. > > The ring has 512 buffers and one frame per buffer, so the same buffer > only comes around again after about 512 frames. Data from approx. 9 > frames ago can't be leftover from the last time that buffer was used, > so I think the hardware wrote it for some other frame. So for the > frame we got, the hardware wrote the descriptor and the 802.11 header > and then stopped, and the rest of the buffer still had that other > frame in it. > > Same for the feame 3. Its first four CCMP bytes are 44(PN0) 50(PN1) > 00(the zero reserved byte) 20 (ExtIV), so that write got four bytes in > the CCMP header and then stopped. The fresh header followed by unexpected contents is consistent with an incomplete write, although I don't think we can distinguish that from consuming the buffer before all payload writes are visible. Could we compare the RX buffer contents when completion is first observed and again before returning the buffer to the hardware? If the contents change while the buffer is still host-owned, that would support premature completion signalling. >> our reading is that for these frames the payload/decrypt path produced >> wrong data while the descriptor still reported CRC OK, ICV OK and >> hardware decrypted. > > I read it the other way. Nothing was decrypted into that area. The CRC, ICV > and decrypt bits are the results of checks the hardware does before it hands > the frame over, My understanding is that BIT_AICV in RCR controls acceptance of integrity-error frames, and it is unset in our register snapshots. Hardware should therefore already be filtering those frames. That doesn't rule out the scenario you describe, where the frame passes the integrity check internally but the bytes delivered to host memory are wrong. However, all four captured bad frames have ICV_ERR clear, so checking that bit alone would not catch these examples. > and the payload write is a separate step after those checks which just > did not happen here. So a good descriptor doesn't mean the frame in > the buffer is real. > > My guess for what takes the link down is that rtw_rx_query_rx_desc sets > decrypted from swdec and the enc type alone, without looking at icv_err, and > then rtw_rx_fill_rx_status sets RX_FLAG_DECRYPTED, with which mac80211 > reads the PN out of the frame itself and saves it as the last PN for the > key, and it only rejects a PN that is lower. So one frame like this pins the > key to a random PN, and everything after it is dropped as a replay until new > keys go in. Yes, this is our take. Somehow the bogus PN gets passed to mac80211. That then poisons the kernel's own PN value, which causes all subsequent packets, which have a good PN, to be dropped as replay attacks. > I will send a patch that fixes this part so you can test it. It should > turn a dead link into a dropped frame, but either way the test will > tell. Thanks for that patch; I've taken a look. The CCMP-header validation should catch jumps 1, 2 and 4. However, jump 3 has valid reserved/ExtIV bytes (44 50 00 20) and corruption in the remaining PN bytes, so this patch would retain RX_FLAG_DECRYPTED and still poison the replay counter. This looks useful as a partial mitigation, but doesn’t yet resolve the reported failure. Regards, -- Matt ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-19 12:43 ` Matthew Leach @ 2026-10-19 16:30 ` Luka Gejak 0 siblings, 0 replies; 16+ messages in thread From: Luka Gejak @ 2026-10-19 16:30 UTC (permalink / raw) To: Matthew Leach, Luka Gejak Cc: Bob Beckett, Ping-Ke Shih, linux-wireless, kernel Hi Matthew, On Mon Oct 19, 2026 at 12:43 PM UTC, Matthew Leach wrote: > The fresh header followed by unexpected contents is consistent with an > incomplete write, although I don't think we can distinguish that from > consuming the buffer before all payload writes are visible. Could we > compare the RX buffer contents when completion is first observed and > again before returning the buffer to the hardware? If the contents > change while the buffer is still host-owned, that would support > premature completion signalling. Good idea, but Bob's report says the RX buffers go through SWIOTLB, so skb->data only gets updated when we call dma_sync_single_for_cpu(). A late write would stay in the bounce slot and you would never see it change. The second read needs another dma_sync_single_for_cpu() before comparing, so doing it only for frames that fail the CCMP header check keeps it cheap. A run with the IOMMU on would be worth trying too. The old descriptor in drv_info could also be leftover data from the device's RX FIFO, which would fit Bob's idea as well. > However, all four captured bad frames have ICV_ERR clear, so > checking that bit alone would not catch these examples. Right, the ICV part doesn't help here, it's only there to match rtw89. I'll make that clear in v2. > However, jump 3 has valid reserved/ExtIV bytes (44 50 00 20) and > corruption in the remaining PN bytes, so this patch would retain > RX_FLAG_DECRYPTED and still poison the replay counter. Agreed, it only narrows it. Checking for the LLC/SNAP header after the first A-MSDU subframe header would catch frame 3 too, but that's a guess about the payload. I'd rather see what the test shows first, since that tells us if the fix belongs in the driver or in the device. Best regards, Luka Gejak ^ permalink raw reply [flat|nested] 16+ messages in thread
* RE: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-16 19:11 rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Bob Beckett 2026-10-16 22:15 ` Luka Gejak @ 2026-10-21 7:30 ` Ping-Ke Shih 2026-10-21 19:05 ` Bob Beckett 2026-10-21 11:32 ` Bitterblue Smith 2 siblings, 1 reply; 16+ messages in thread From: Ping-Ke Shih @ 2026-10-21 7:30 UTC (permalink / raw) To: Bob Beckett Cc: linux-wireless@vger.kernel.org, kernel@collabora.com, matthew.leach@collabora.com, Timlee Bob Beckett <bob.beckett@collabora.com> wrote: > Hi Ping-Ke, > > We are investigating a Wi-Fi failure on the Steam Deck LCD (RTL8822CE, > rtw88) and would appreciate your input; details below. > > Summary > ======= > > On RTL8822CE (PCIe, rtw88), during heavy 5 GHz VHT downlink from one AP > model, Since The RTL8822CE works long time in field, is the symptom only on specific AP with heavy load? If you change to another AP or lower load (e.g. 100Mbps), does problem still present? > the driver occasionally hands mac80211 a frame with these > properties: > - the RX descriptor looks normal: CRC OK, ICV OK, AES, not > software-decrypted; Luka's patch can fix partial, so is it still normal? > - the 802.11 header is correct; > - the bytes from the CCMP header onwards are garbage. Do you have an air capture to compare with? I'd like to bisect the cause if AP TX weird packets? or receive side process it wrongly? > > The AP's own PN sequence is consistent before and after the bad frame, > so the corruption happens on the receive side. Maybe this is a hint to make a patch to defer (drop) reporting weird packet, as Luka has fixed 3 of 4 problems. > Public reports of the same symptom with this AP (Steam Deck LCD + > Freebox Revolution, 5 GHz, drops when a download starts, fixed by > disabling 802.11ac on the AP, works under Windows) go back to at least > September 2024. With disabling 802.11ac, how about the change of traffic? become not a heavy load? Can you test with 802.11ac enabled but lower load as disabling 80211.ac? > > Evidence 3: other RX-path anomalies in the same run > =================================================== > > - 33 s before the first jump, during the download and with no other > activity, one descriptor claims pkt_len 11619 (above the 11454 > maximum), and rtw_rx_query_rx_desc() returns -EINVAL twice in the same > second. These are garbage descriptors, which the rtw-next > RX-descriptor validation correctly rejected. This was never seen in a > 21-minute ~540 Mbit/s test against a different AP. If we enlarge RTK_PCI_RX_BUF_SIZE (e.g. +1000 bytes), would it help? But it is still weird to me why the pkt_len 11619 can present. For different AP, it might not send a specific size of packets, so it never happens. No idea. > > Where the corruption appears to come from > ========================================= > > Host DMA bookkeeping is consistent. rtw88's rtw_pci_dma_check() compares > the RX tag in each buffer descriptor with the expected sequence and logs > "pci bus timeout, check dma status" on a mismatch (an unconditional > dev_warn). That warning appears zero times in all three runs, Good to know this. > so every > buffer descriptor had been written when the driver consumed it. The > start of each bad buffer is also fresh: the RX descriptor's sequence > number (w2[11:0]) matches the 802.11 header's. > > The corruption boundary does not line up with PCIe writes. The device > has MaxPayload 128 bytes, and on this host nearly every RX buffer goes > through SWIOTLB bounce slots, which start 2 KB-aligned because rtw88 > sets no min_align_mask (see below). So DMA writes split at 128-byte > boundaries of the buffer. The good data ends 82 or 86 bytes into the > buffer, which is not such a boundary. What is the problem? Cache-line size? or device DMA corruption over boundary? As Luka's suggestion, it is worth to try IOMMU. > Ruled out > ========= > > - Oversized MPDUs (new_len > RTK_PCI_RX_BUF_SIZE): the bad frames are > 3092 bytes, and A-MSDUs from this AP top out at about 9.7 KB. The bad frames are never over RTK_PCI_RX_BUF_SIZE, right? > - ASPM / deep LPS: jumps 3 and 4 happened seconds after reloading with > disable_aspm=1 disable_lps_deep=1. With disable_aspm=1, you should turn off/on (cold reboot) platform to take effect. > - Power save (iw ... power_save off) and beamformee (support_bf=0 + > reconnect): no effect. > - Driver consuming a ring entry before its buffer descriptor was > written: no RX-tag mismatch ("pci bus timeout") in any run. > - The AP sending a bad PN: the PN sequences on both sides of each bad > frame are consecutive. Can you confirm this by an air capture? > - The RX-descriptor validation added in rtw-next: it does not reject > these frames, because their descriptors are well-formed. > > Questions for Realtek > ===================== > > 1. Is there a known condition on 8822C (or 8822B/8821C) where a > hardware-decrypted MPDU or A-MSDU is delivered with a valid RX > descriptor (CRC32 0, ICV_ERR 0, SWDEC 0, enc AES) and a valid 802.11 > header, but an invalid CCMP header and payload? Does the RX > descriptor or drv_info have any bit that flags it? For example > "payload invalid", "decrypt engine bypassed or aborted", or a > per-packet RX FIFO overflow. As I know, no. > 2. What does the vendor/Windows driver check before trusting a > hardware-decrypted frame, and does it handle HISR1 RXFOVW / HISR0 > RDU? rtw88 masks and ignores both. Why do you think rtw88 needs to handle them? RXFOVW and RDU are to tell driver the info, but data should be handled by hardware properly -- with some missing data, but should not receive corrupted data to driver. > 3. Are there RX FIFO, RX DMA, security-engine or RX aggregation settings > in the Windows driver for 8822CE that differ from rtw88's and matter > for high-rate VHT A-MSDU reception (80 MHz, 2SS MCS 8-9, ~3 KB > A-MSDUs at ~20k/s)? I'm not very sure they are identical (but most similar). You can check association frames by air sniffer to see their difference. > 4. For completeness: on these chips can RXBD_IDX or the > buffer-descriptor RX tag ever be updated before the packet data DMA > has completed, as described for WiFi 7 in rtw89 0bc7d1d4e63c? The RX tag in rtw88 located on memory allocated by dma_alloc_coherent(), and the RX tag must be set after packet data MAC completed. > 5. rtw_rx_fill_rx_status() sets RX_FLAG_DECRYPTED whenever !swdec && > enc_type != NONE, ignoring ICV_ERR. rtw89 sets RX_FLAG_FAILED_FCS_CRC > and not DECRYPTED on icv_err; mt76 marks such frames monitor-only; > rtlwifi drops them. rtw88 does not set BIT_AICV in RCR, so such > frames are presumably dropped in hardware, and these frames had > ICV_ERR clear anyway. Would you take a patch aligning rtw88 with > rtw89 regardless? I suppose Luka's patch did them. > Appendix: raw dumps (run C, MACs redacted) If you have an air capture by another platform, it'd be helpful. In my perspective, we need an air capture to bisect which part causes the garbage (or not properly decrypted). The packets with abnormal PN not handled by Luka's patch need additional workaround to drop it. Ping-Ke ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-21 7:30 ` Ping-Ke Shih @ 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 1/5] wifi: rtw88: do not trust a claimed decryption Bob Beckett ` (5 more replies) 0 siblings, 6 replies; 16+ messages in thread From: Bob Beckett @ 2026-10-21 19:05 UTC (permalink / raw) To: Ping-Ke Shih Cc: linux-wireless, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach, kernel From: Robert Beckett <bob.beckett@collabora.com> patches that follow are: - 1/5 is Luka's patch; - 2/5–4/5 are debug aids, not for merging, which report suspect frames, optionally drop them and poison/re-check RX buffers. We are rolling them out with the next SteamOS kernel so users can test and report. - 5/5 is a standalone fix that we might want to include in the tree. I cam upstream separately if desired. They are included for reference mostly. Details inline below On Wed, 21 Oct 2026 07:30:58 +0000, Ping-Ke Shih <pkshih@realtek.com> wrote: > Bob Beckett <bob.beckett@collabora.com> wrote: > > Hi Ping-Ke, > > > > We are investigating a Wi-Fi failure on the Steam Deck LCD (RTL8822CE, > > rtw88) and would appreciate your input; details below. > > > > Summary > > ======= > > > > On RTL8822CE (PCIe, rtw88), during heavy 5 GHz VHT downlink from one AP > > model, > > Since The RTL8822CE works long time in field, is the symptom only on > specific AP with heavy load? Yes, it only occurs with a specific AP so far. It is a Freebox Revolution Light that is only used in France by the ISP. To be clear, we do not think that the AP is doing anything wrong, we suspect that it is just exposing an issue with rtw88 as it is known not to happen on Windows with the same router. I am working on getting hold of one of those devices. So far all testing and capture has been done via an end user who is very generously helping us out. Once I've gotten one of these devices I'll be able to test much more locally and grab air captures. Until then, I am reluctant to ask end users who are not engineers to attempt significant changes to their devices for testing. > > If you change to another AP or lower load (e.g. 100Mbps), does problem > still present? I've not been able to repro on my Asus router. Others have tested on other devices without repro. So far only the Freebox is able to expose the issue. > > > the driver occasionally hands mac80211 a frame with these > > properties: > > - the RX descriptor looks normal: CRC OK, ICV OK, AES, not > > software-decrypted; > > Luka's patch can fix partial, so is it still normal? unknown yet. I'm rolling out the following patches with the next SteaomOS kernel. It includes Luka's patch and some testing and logging patches to hopefully get more details, no behavioural changes for normal use. It will take a while to get details back. I'll reply once we know more. > > > - the 802.11 header is correct; > > - the bytes from the CCMP header onwards are garbage. > > Do you have an air capture to compare with? I'd like to bisect the cause > if AP TX weird packets? or receive side process it wrongly? Not yet. I can attempt that once I have a device to test locally. (this answer applies to most other questions below requiring device access or making modifications to device/sw setup) > > > > > The AP's own PN sequence is consistent before and after the bad frame, > > so the corruption happens on the receive side. > > Maybe this is a hint to make a patch to defer (drop) reporting weird packet, > as Luka has fixed 3 of 4 problems. > > > Public reports of the same symptom with this AP (Steam Deck LCD + > > Freebox Revolution, 5 GHz, drops when a download starts, fixed by > > disabling 802.11ac on the AP, works under Windows) go back to at least > > September 2024. > > With disabling 802.11ac, how about the change of traffic? become not a heavy > load? Can you test with 802.11ac enabled but lower load as disabling 80211.ac? > > > > > > Evidence 3: other RX-path anomalies in the same run > > =================================================== > > > > - 33 s before the first jump, during the download and with no other > > activity, one descriptor claims pkt_len 11619 (above the 11454 > > maximum), and rtw_rx_query_rx_desc() returns -EINVAL twice in the same > > second. These are garbage descriptors, which the rtw-next > > RX-descriptor validation correctly rejected. This was never seen in a > > 21-minute ~540 Mbit/s test against a different AP. > > If we enlarge RTK_PCI_RX_BUF_SIZE (e.g. +1000 bytes), would it help? > But it is still weird to me why the pkt_len 11619 can present. The bad frames are 3092 bytes (pkt_len from the descriptor), against a 11478-byte buffer, so each one is well inside its own buffer, and the corruption starts 26–30 bytes in. Nothing from this AP came close to the limit: the largest A-MSDU in the captures was about 9.7 KB, and the new_len > RTK_PCI_RX_BUF_SIZE drop never triggered. Each bad buffer also starts with a valid descriptor whose sequence number matches its own header, so it isn't spill-over from an earlier frame. The 11619 descriptor is above the 11454 VHT maximum, so it can't be a real frame, it is just as garbage as the bad payloads, and the descriptor validation drops it regardless of buffer size. So I don't expect a larger buffer to help here. Separately, 11454 + 24 has no room for PHY status and shift for a maximum-size MPDU, which may be worth fixing on its own. > > For different AP, it might not send a specific size of packets, so it > never happens. No idea. > > > > > Where the corruption appears to come from > > ========================================= > > > > Host DMA bookkeeping is consistent. rtw88's rtw_pci_dma_check() compares > > the RX tag in each buffer descriptor with the expected sequence and logs > > "pci bus timeout, check dma status" on a mismatch (an unconditional > > dev_warn). That warning appears zero times in all three runs, > > Good to know this. > > > so every > > buffer descriptor had been written when the driver consumed it. The > > start of each bad buffer is also fresh: the RX descriptor's sequence > > number (w2[11:0]) matches the 802.11 header's. > > > > The corruption boundary does not line up with PCIe writes. The device > > has MaxPayload 128 bytes, and on this host nearly every RX buffer goes > > through SWIOTLB bounce slots, which start 2 KB-aligned because rtw88 > > sets no min_align_mask (see below). So DMA writes split at 128-byte > > boundaries of the buffer. The good data ends 82 or 86 bytes into the > > buffer, which is not such a boundary. > > What is the problem? Cache-line size? or device DMA corruption over boundary? no idea yet. Hopefully these next tests can at least help rule out dma still in flight. > > As Luka's suggestion, it is worth to try IOMMU. I'll try that once I have the router. > > > Ruled out > > ========= > > > > - Oversized MPDUs (new_len > RTK_PCI_RX_BUF_SIZE): the bad frames are > > 3092 bytes, and A-MSDUs from this AP top out at about 9.7 KB. > > The bad frames are never over RTK_PCI_RX_BUF_SIZE, right? correct > > > - ASPM / deep LPS: jumps 3 and 4 happened seconds after reloading with > > disable_aspm=1 disable_lps_deep=1. > > With disable_aspm=1, you should turn off/on (cold reboot) platform to > take effect. oof. yeah. I missed that. So our testing with aspm off is not reliable for previous runs. I added patch 5/5 below that should allow it to take effect while loading the module and will include it in the next SteamOS kernel. > > > - Power save (iw ... power_save off) and beamformee (support_bf=0 + > > reconnect): no effect. > > - Driver consuming a ring entry before its buffer descriptor was > > written: no RX-tag mismatch ("pci bus timeout") in any run. > > - The AP sending a bad PN: the PN sequences on both sides of each bad > > frame are consecutive. > > Can you confirm this by an air capture? Once I have the AP > > > - The RX-descriptor validation added in rtw-next: it does not reject > > these frames, because their descriptors are well-formed. > > > > Questions for Realtek > > ===================== > > > > 1. Is there a known condition on 8822C (or 8822B/8821C) where a > > hardware-decrypted MPDU or A-MSDU is delivered with a valid RX > > descriptor (CRC32 0, ICV_ERR 0, SWDEC 0, enc AES) and a valid 802.11 > > header, but an invalid CCMP header and payload? Does the RX > > descriptor or drv_info have any bit that flags it? For example > > "payload invalid", "decrypt engine bypassed or aborted", or a > > per-packet RX FIFO overflow. > > As I know, no. > > > 2. What does the vendor/Windows driver check before trusting a > > hardware-decrypted frame, and does it handle HISR1 RXFOVW / HISR0 > > RDU? rtw88 masks and ignores both. > > Why do you think rtw88 needs to handle them? > > RXFOVW and RDU are to tell driver the info, but data should be handled > by hardware properly -- with some missing data, but should not receive > corrupted data to driver. agreed, but as the windows driver apparently handles this AP fine, I am assuming that it might be more resilliant to fw erroneously indicating decrypt success (if that is the cause here). > > > 3. Are there RX FIFO, RX DMA, security-engine or RX aggregation settings > > in the Windows driver for 8822CE that differ from rtw88's and matter > > for high-rate VHT A-MSDU reception (80 MHz, 2SS MCS 8-9, ~3 KB > > A-MSDUs at ~20k/s)? > > I'm not very sure they are identical (but most similar). You can check > association frames by air sniffer to see their difference. > > > 4. For completeness: on these chips can RXBD_IDX or the > > buffer-descriptor RX tag ever be updated before the packet data DMA > > has completed, as described for WiFi 7 in rtw89 0bc7d1d4e63c? > > The RX tag in rtw88 located on memory allocated by dma_alloc_coherent(), and > the RX tag must be set after packet data MAC completed. Understood that this is the design intent. My question was whether the host can ever observe the tag before the data. dma_alloc_coherent() only makes the tag visible to the CPU without cache maintenance; the payload is in a separate streaming mapping (a SWIOTLB bounce buffer on this platform), so the order in which the host sees them depends on the order of the device's PCIe writes. Relaxed Ordering is enabled on the 8822CE here (DevCtl RlxdOrd+). Does the 8822CE set the RO attribute on RX DMA writes, in particular the buffer-descriptor/tag update? If it does, the tag write may pass the payload writes. We are hoping to test this directly with the next round of tests. the rx_debug option re-syncs a suspect buffer 100 us later, so late payload writes might show up as changed bytes, and we can repeat the test with Relaxed Ordering disabled. > > > 5. rtw_rx_fill_rx_status() sets RX_FLAG_DECRYPTED whenever !swdec && > > enc_type != NONE, ignoring ICV_ERR. rtw89 sets RX_FLAG_FAILED_FCS_CRC > > and not DECRYPTED on icv_err; mt76 marks such frames monitor-only; > > rtlwifi drops them. rtw88 does not set BIT_AICV in RCR, so such > > frames are presumably dropped in hardware, and these frames had > > ICV_ERR clear anyway. Would you take a patch aligning rtw88 with > > rtw89 regardless? > > I suppose Luka's patch did them. > > > Appendix: raw dumps (run C, MACs redacted) > > If you have an air capture by another platform, it'd be helpful. In future, hopefully. > > > In my perspective, we need an air capture to bisect which part causes > the garbage (or not properly decrypted). The packets with abnormal PN > not handled by Luka's patch need additional workaround to drop it. See patch 2 that follows. Assuming it works out, we can work on an upstream version. > > Ping-Ke Luka Gejak (1): wifi: rtw88: do not trust a claimed decryption Robert Beckett (4): wifi: rtw88: report and optionally re-verify suspect hw-decrypted frames wifi: rtw88: pci: optional RX buffer poisoning and recheck wifi: rtw88: make the PN guard threshold a test parameter wifi: rtw88: pci: clear CLKREQ/ASPM enables when disable_aspm is set drivers/net/wireless/realtek/rtw88/mac80211.c | 3 + drivers/net/wireless/realtek/rtw88/main.c | 9 ++ drivers/net/wireless/realtek/rtw88/main.h | 19 +++ drivers/net/wireless/realtek/rtw88/pci.c | 153 +++++++++++++++++- drivers/net/wireless/realtek/rtw88/pci.h | 2 + drivers/net/wireless/realtek/rtw88/rx.c | 145 ++++++++++++++++- 6 files changed, 324 insertions(+), 7 deletions(-) -- 2.55.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [RFC PATCH rtw-next 1/5] wifi: rtw88: do not trust a claimed decryption 2026-10-21 19:05 ` Bob Beckett @ 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 2/5] wifi: rtw88: report and optionally re-verify suspect hw-decrypted frames Bob Beckett ` (4 subsequent siblings) 5 siblings, 0 replies; 16+ messages in thread From: Bob Beckett @ 2026-10-21 19:05 UTC (permalink / raw) To: Ping-Ke Shih Cc: linux-wireless, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach, kernel From: Luka Gejak <luka.gejak@linux.dev> The RX descriptor says how the CRC and ICV checks on the device's own copy of the frame went, not that the bytes in our buffer are that frame. For a frame that is marked decrypted mac80211 reads the PN out of the CCMP header and stores it as the key's last PN, rejecting only a lower PN, so a single frame with a garbage CCMP header makes every genuine frame after it be dropped as a replay. Only claim decryption for a frame that passed the CRC and ICV checks, and only for an AES frame whose CCMP or GCMP header is well formed. Such a frame is left to mac80211, which drops it without moving the replay counter. Mark a frame that failed the ICV with RX_FLAG_FAILED_FCS_CRC, the way rtw89 does. Reported-by: Bob Beckett <bob.beckett@collabora.com> Closes: https://lore.kernel.org/all/20261016191100.1781056-1-bob.beckett@collabora.com/ Signed-off-by: Luka Gejak <luka.gejak@linux.dev> Link: https://lore.kernel.org/r/20261017083313.56113-1-luka.gejak@linux.dev Signed-off-by: Robert Beckett <bob.beckett@collabora.com> --- drivers/net/wireless/realtek/rtw88/rx.c | 30 +++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c index 01fd299abb7f..e292b060ac31 100644 --- a/drivers/net/wireless/realtek/rtw88/rx.c +++ b/drivers/net/wireless/realtek/rtw88/rx.c @@ -208,7 +208,7 @@ static void rtw_rx_fill_rx_status(struct rtw_dev *rtwdev, if (rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_SCAN_OFFLOAD) && test_bit(RTW_FLAG_SCANNING, rtwdev->flags)) rtw_set_rx_freq_by_pktstat(pkt_stat, rx_status); - if (pkt_stat->crc_err) + if (pkt_stat->crc_err || pkt_stat->icv_err) rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; if (pkt_stat->decrypted) rx_status->flag |= RX_FLAG_DECRYPTED; @@ -262,6 +262,24 @@ static void rtw_rx_fill_rx_status(struct rtw_dev *rtwdev, } } +static bool rtw_rx_ccmp_hdr_valid(struct rtw_rx_pkt_stat *pkt_stat, + struct ieee80211_hdr *hdr) +{ + u32 hdr_len = ieee80211_hdrlen(hdr->frame_control); + u8 *iv; + + if (!ieee80211_has_protected(hdr->frame_control)) + return true; + + if (pkt_stat->pkt_len < hdr_len + IEEE80211_CCMP_HDR_LEN + FCS_LEN) + return false; + + iv = (u8 *)hdr + hdr_len; + + /* reserved octet is zero, key ID octet has ExtIV set */ + return iv[2] == 0 && (iv[3] & BIT(5)); +} + int rtw_rx_query_rx_desc(struct rtw_dev *rtwdev, void *rx_desc8, struct rtw_rx_pkt_stat *pkt_stat, struct ieee80211_rx_status *rx_status) @@ -283,7 +301,8 @@ int rtw_rx_query_rx_desc(struct rtw_dev *rtwdev, void *rx_desc8, pkt_stat->shift = le32_get_bits(rx_desc->w0, RTW_RX_DESC_W0_SHIFT); pkt_stat->phy_status = le32_get_bits(rx_desc->w0, RTW_RX_DESC_W0_PHYST); swdec = le32_get_bits(rx_desc->w0, RTW_RX_DESC_W0_SWDEC); - pkt_stat->decrypted = !swdec && enc_type != RX_DESC_ENC_NONE; + pkt_stat->decrypted = !swdec && enc_type != RX_DESC_ENC_NONE && + !pkt_stat->crc_err && !pkt_stat->icv_err; pkt_stat->cam_id = le32_get_bits(rx_desc->w1, RTW_RX_DESC_W1_MACID); @@ -326,6 +345,13 @@ int rtw_rx_query_rx_desc(struct rtw_dev *rtwdev, void *rx_desc8, if (pkt_stat->phy_status) rtwdev->chip->ops->query_phy_status(rtwdev, phy_status, pkt_stat); + if (pkt_stat->decrypted && enc_type == RX_DESC_ENC_AES && + !rtw_rx_ccmp_hdr_valid(pkt_stat, hdr)) { + rtw_dbg(rtwdev, RTW_DBG_RX, + "AES frame without a usable CCMP header, drop the decryption flag\n"); + pkt_stat->decrypted = false; + } + rtw_rx_fill_rx_status(rtwdev, pkt_stat, hdr, rx_status); return 0; -- 2.55.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* [RFC PATCH rtw-next 2/5] wifi: rtw88: report and optionally re-verify suspect hw-decrypted frames 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 1/5] wifi: rtw88: do not trust a claimed decryption Bob Beckett @ 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 3/5] wifi: rtw88: pci: optional RX buffer poisoning and recheck Bob Beckett ` (3 subsequent siblings) 5 siblings, 0 replies; 16+ messages in thread From: Bob Beckett @ 2026-10-21 19:05 UTC (permalink / raw) To: Ping-Ke Shih Cc: linux-wireless, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach, kernel From: Robert Beckett <bob.beckett@collabora.com> Debug aid for the 8822CE 5 GHz replay-counter poisoning reported in https://lore.kernel.org/linux-wireless/20261016191100.1781056-1-bob.beckett@collabora.com/ Some 8822CE devices occasionally deliver a frame whose RX descriptor and 802.11 header are valid but whose remaining bytes, including the cleartext CCMP header, are not that frame's, while the descriptor still reports a successful hardware decryption. mac80211 then stores the garbage PN as the key's last PN and drops every later frame as a replay until the next rekey. "wifi: rtw88: do not trust a claimed decryption" catches frames whose CCMP header is malformed, but not those where only the PN bytes are garbage. Track the highest PN seen per transmitter and unicast/group key for hardware-decrypted AES data frames, reset on every key change, and treat a frame whose PN is more than 2^24 beyond it as suspect. The transmitter uses one PN counter per key for all TIDs, so a genuine frame on any TID cannot get that far ahead, however long that TID was idle, while a frame with garbage after the header carries a random 48-bit PN. A suspect PN never becomes the reference. Suspect frames, and frames rejected for a malformed CCMP header, are always reported with a dump of the RX descriptor and the start of the frame (the first 20 in full, then rate limited), so field logs show whether and how often this happens. By default nothing else changes. With rtw88_core.sw_decrypt_suspect=Y (also writable at runtime in /sys/module/rtw88_core/parameters/) a suspect frame is handed to mac80211 without RX_FLAG_DECRYPTED, so it is verified in software and dropped on MIC failure without moving the replay counter. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Robert Beckett <bob.beckett@collabora.com> --- drivers/net/wireless/realtek/rtw88/mac80211.c | 3 + drivers/net/wireless/realtek/rtw88/main.c | 4 + drivers/net/wireless/realtek/rtw88/main.h | 17 +++ drivers/net/wireless/realtek/rtw88/rx.c | 118 ++++++++++++++++++ 4 files changed, 142 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c index 766f22d31079..d5228e36fca9 100644 --- a/drivers/net/wireless/realtek/rtw88/mac80211.c +++ b/drivers/net/wireless/realtek/rtw88/mac80211.c @@ -558,6 +558,9 @@ static int rtw_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, u8 hw_key_idx; int ret = 0; + /* new keys restart the PN sequences rtw_rx_pn_guard() tracks */ + atomic_inc(&rtwdev->rx_pn_guard_gen); + switch (key->cipher) { case WLAN_CIPHER_SUITE_WEP40: hw_key_type = RTW_CAM_WEP40; diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c index cd9254370fcc..d667e7452e4f 100644 --- a/drivers/net/wireless/realtek/rtw88/main.c +++ b/drivers/net/wireless/realtek/rtw88/main.c @@ -34,14 +34,18 @@ EXPORT_SYMBOL(rtw_debug_mask); * there is a tough reason to maintain rtw_edcca_enabled by device. */ bool rtw_edcca_enabled = true; +bool rtw_sw_decrypt_suspect; module_param_named(disable_lps_deep, rtw_disable_lps_deep_mode, bool, 0644); module_param_named(support_bf, rtw_bf_support, bool, 0644); module_param_named(debug_mask, rtw_debug_mask, uint, 0644); +module_param_named(sw_decrypt_suspect, rtw_sw_decrypt_suspect, bool, 0644); MODULE_PARM_DESC(disable_lps_deep, "Set Y to disable Deep PS"); MODULE_PARM_DESC(support_bf, "Set Y to enable beamformee support"); MODULE_PARM_DESC(debug_mask, "Debugging mask"); +MODULE_PARM_DESC(sw_decrypt_suspect, + "Set Y to have mac80211 verify hardware-decrypted frames whose CCMP PN jumps implausibly (default: only report them)"); static struct ieee80211_channel rtw_channeltable_2g[] = { {.center_freq = 2412, .hw_value = 1,}, diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 9c0b746540b0..49f13034db4d 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -43,6 +43,7 @@ extern bool rtw_bf_support; extern bool rtw_disable_lps_deep_mode; extern unsigned int rtw_debug_mask; +extern bool rtw_sw_decrypt_suspect; extern bool rtw_edcca_enabled; extern const struct ieee80211_ops rtw_ops; @@ -637,6 +638,20 @@ struct rtw_tx_pkt_info { bool bt_null; }; +/* Suspect frames reported in full; later ones get rate-limited one-liners */ +#define RTW_RX_PN_GUARD_DUMPS 20 + +/* Highest CCMP PN seen per [group-addressed] key for hardware-decrypted + * data frames, see rtw_rx_pn_guard(). + */ +struct rtw_rx_pn_guard { + u64 pn[2]; + u8 ta[2][ETH_ALEN]; + bool valid[2]; + u32 gen; + u32 events; +}; + struct rtw_rx_pkt_stat { bool phy_status; bool icv_err; @@ -2069,6 +2084,8 @@ struct rtw_dev { struct rtw_fw_state fw; struct rtw_efuse efuse; struct rtw_sec_desc sec; + struct rtw_rx_pn_guard rx_pn_guard; + atomic_t rx_pn_guard_gen; struct rtw_traffic_stats stats; struct rtw_regd regd; struct rtw_bf_info bf_info; diff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c index e292b060ac31..7e6b0022ad50 100644 --- a/drivers/net/wireless/realtek/rtw88/rx.c +++ b/drivers/net/wireless/realtek/rtw88/rx.c @@ -280,6 +280,119 @@ static bool rtw_rx_ccmp_hdr_valid(struct rtw_rx_pkt_stat *pkt_stat, return iv[2] == 0 && (iv[3] & BIT(5)); } +/* A forward PN jump this large between two frames of one key/TID is not + * plausible; frames with garbage after the 802.11 header produce random + * 48-bit PNs. + */ +#define RTW_RX_PN_GUARD_JUMP BIT_ULL(24) + +static u64 rtw_rx_ccmp_pn(const u8 *iv) +{ + return (u64)iv[0] | (u64)iv[1] << 8 | (u64)iv[4] << 16 | + (u64)iv[5] << 24 | (u64)iv[6] << 32 | (u64)iv[7] << 40; +} + +static void rtw_rx_report_suspect(struct rtw_dev *rtwdev, const char *why, + struct rtw_rx_pkt_stat *pkt_stat, + void *rx_desc8, struct ieee80211_hdr *hdr, + u64 max_pn) +{ + u32 hdr_len = ieee80211_hdrlen(hdr->frame_control); + u32 n = ++rtwdev->rx_pn_guard.events; + u8 tid = ieee80211_is_data_qos(hdr->frame_control) ? + ieee80211_get_tid(hdr) : 0; + u64 pn = 0; + + if (pkt_stat->pkt_len >= hdr_len + IEEE80211_CCMP_HDR_LEN) + pn = rtw_rx_ccmp_pn((u8 *)hdr + hdr_len); + + if (n > RTW_RX_PN_GUARD_DUMPS) { + dev_warn_ratelimited(rtwdev->dev, + "suspect hw-decrypted frame #%u (%s): tid %u max pn 0x%012llx pn 0x%012llx%s\n", + n, why, tid, max_pn, pn, + pkt_stat->decrypted ? "" : ", left to mac80211"); + return; + } + + rtw_warn(rtwdev, + "suspect hw-decrypted frame #%u (%s): seq %u tid %u max pn 0x%012llx pn 0x%012llx len %u rate 0x%x%s\n", + n, why, ieee80211_get_sn(hdr), tid, max_pn, pn, + pkt_stat->pkt_len, pkt_stat->rate, + pkt_stat->decrypted ? "" : ", left to mac80211"); + print_hex_dump(KERN_WARNING, "rtw88 rx desc: ", DUMP_PREFIX_OFFSET, + 16, 1, rx_desc8, (u8 *)hdr - (u8 *)rx_desc8, false); + print_hex_dump(KERN_WARNING, "rtw88 rx frame: ", DUMP_PREFIX_OFFSET, + 16, 1, hdr, min_t(u32, pkt_stat->pkt_len, hdr_len + 128), + false); +} + +/* Some 8822CE devices occasionally deliver a frame whose descriptor and + * 802.11 header are valid but whose remaining bytes, including the + * cleartext CCMP header, are not that frame's, while the descriptor still + * claims a successful hardware decryption. mac80211 then stores the garbage + * PN as the key's last PN and drops every later frame as a replay until + * the next rekey. + * + * The transmitter numbers all frames of a key from one PN counter, shared + * by every TID (the receiver keeps per-TID replay counters only because of + * QoS reordering). So track the highest PN seen per transmitter and + * unicast/group key for hardware-decrypted data frames, and report frames + * whose PN is implausibly far beyond it. A genuine frame on any TID cannot + * be, however long that TID was idle. A suspect PN never becomes the + * reference, so later genuine frames are not flagged and later bad frames + * are still caught. With sw_decrypt_suspect set, a suspect frame is passed + * to mac80211 as not decrypted, so it is verified in software and dropped + * on MIC failure without moving the replay counter. + */ +static void rtw_rx_pn_guard(struct rtw_dev *rtwdev, + struct rtw_rx_pkt_stat *pkt_stat, + void *rx_desc8, struct ieee80211_hdr *hdr) +{ + struct rtw_rx_pn_guard *guard = &rtwdev->rx_pn_guard; + u32 gen = atomic_read(&rtwdev->rx_pn_guard_gen); + u32 hdr_len; + u64 pn, max; + u8 grp; + + if (guard->gen != gen) { + memset(guard->valid, 0, sizeof(guard->valid)); + guard->gen = gen; + } + + if (!ieee80211_is_data(hdr->frame_control) || + !ieee80211_has_protected(hdr->frame_control)) + return; + + hdr_len = ieee80211_hdrlen(hdr->frame_control); + if (pkt_stat->pkt_len < hdr_len + IEEE80211_CCMP_HDR_LEN + FCS_LEN) + return; + + grp = is_multicast_ether_addr(hdr->addr1); + pn = rtw_rx_ccmp_pn((u8 *)hdr + hdr_len); + + if (!guard->valid[grp] || + !ether_addr_equal(guard->ta[grp], hdr->addr2)) { + guard->pn[grp] = pn; + ether_addr_copy(guard->ta[grp], hdr->addr2); + guard->valid[grp] = true; + return; + } + + max = guard->pn[grp]; + if (pn <= max) + return; + + if (pn - max > RTW_RX_PN_GUARD_JUMP) { + if (READ_ONCE(rtw_sw_decrypt_suspect)) + pkt_stat->decrypted = false; + rtw_rx_report_suspect(rtwdev, "PN jump", pkt_stat, rx_desc8, + hdr, max); + return; + } + + guard->pn[grp] = pn; +} + int rtw_rx_query_rx_desc(struct rtw_dev *rtwdev, void *rx_desc8, struct rtw_rx_pkt_stat *pkt_stat, struct ieee80211_rx_status *rx_status) @@ -350,8 +463,13 @@ int rtw_rx_query_rx_desc(struct rtw_dev *rtwdev, void *rx_desc8, rtw_dbg(rtwdev, RTW_DBG_RX, "AES frame without a usable CCMP header, drop the decryption flag\n"); pkt_stat->decrypted = false; + rtw_rx_report_suspect(rtwdev, "bad CCMP header", pkt_stat, + rx_desc8, hdr, 0); } + if (pkt_stat->decrypted && enc_type == RX_DESC_ENC_AES) + rtw_rx_pn_guard(rtwdev, pkt_stat, rx_desc8, hdr); + rtw_rx_fill_rx_status(rtwdev, pkt_stat, hdr, rx_status); return 0; -- 2.55.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* [RFC PATCH rtw-next 3/5] wifi: rtw88: pci: optional RX buffer poisoning and recheck 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 1/5] wifi: rtw88: do not trust a claimed decryption Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 2/5] wifi: rtw88: report and optionally re-verify suspect hw-decrypted frames Bob Beckett @ 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 4/5] wifi: rtw88: make the PN guard threshold a test parameter Bob Beckett ` (2 subsequent siblings) 5 siblings, 0 replies; 16+ messages in thread From: Bob Beckett @ 2026-10-21 19:05 UTC (permalink / raw) To: Ping-Ke Shih Cc: linux-wireless, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach, kernel From: Robert Beckett <bob.beckett@collabora.com> Debug aid for the 8822CE 5 GHz replay-counter poisoning reported in https://lore.kernel.org/linux-wireless/20261016191100.1781056-1-bob.beckett@collabora.com/ The bad frames have a valid RX descriptor and 802.11 header but foreign bytes from the CCMP header on. On the list three explanations are open: the device stopped writing after the header, the driver consumed the buffer before the remaining writes were visible, or the device wrote the wrong data (e.g. from the wrong place in its RX FIFO). With rtw88_pci.rx_debug=Y (read when the rings are set up, so set it at module load): - RX buffers are mapped DMA_BIDIRECTIONAL and bytes [24, 256) of every buffer are filled with a position-dependent pattern before the buffer is handed to the device, also reaching a swiotlb bounce buffer; - for each frame the core reports as suspect, the poison left in the drv_info area and in the frame's own bytes after the 802.11 header is counted, the head of the buffer is synced for the CPU again after 100us, and any change is reported and dumped. Reports follow the suspect-frame budget (full for the first ones, then rate limited), except that a reread that changed is always reported in full. Poison after the header means the device never wrote those bytes; foreign non-poison bytes mean it wrote the wrong data; bytes that change on the reread mean the buffer was consumed before the device finished. Without the parameter nothing changes. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Robert Beckett <bob.beckett@collabora.com> --- drivers/net/wireless/realtek/rtw88/main.h | 1 + drivers/net/wireless/realtek/rtw88/pci.c | 131 +++++++++++++++++++++- drivers/net/wireless/realtek/rtw88/pci.h | 2 + drivers/net/wireless/realtek/rtw88/rx.c | 2 + 4 files changed, 131 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 49f13034db4d..295af5529fcc 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -659,6 +659,7 @@ struct rtw_rx_pkt_stat { bool decrypted; bool is_c2h; bool channel_invalid; + bool suspect; s32 signal_power; u16 pkt_len; diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c index 69f2840fed09..d65408084f72 100644 --- a/drivers/net/wireless/realtek/rtw88/pci.c +++ b/drivers/net/wireless/realtek/rtw88/pci.c @@ -22,6 +22,36 @@ module_param_named(disable_aspm, rtw_pci_disable_aspm, bool, 0644); MODULE_PARM_DESC(disable_msi, "Set Y to disable MSI interrupt support"); MODULE_PARM_DESC(disable_aspm, "Set Y to disable PCI ASPM support"); +static bool rtw_pci_rx_debug; +module_param_named(rx_debug, rtw_pci_rx_debug, bool, 0644); +MODULE_PARM_DESC(rx_debug, + "Set Y to poison RX buffers and recheck suspect frames (debug, takes effect at probe)"); + +/* With rx_debug, bytes [RTW_PCI_RX_POISON_START, RTW_PCI_RX_POISON_END) of + * every RX buffer are filled with a known pattern before the buffer is + * handed to the device, and frames the core reports as suspect are read + * again after a short delay. That shows whether the device wrote the bytes + * after the 802.11 header at all, and whether they were still changing when + * the driver consumed the buffer. The buffers are mapped bidirectionally so + * that the pattern also reaches a swiotlb bounce buffer. + */ +#define RTW_PCI_RX_POISON_START 24 +#define RTW_PCI_RX_POISON_END 256 +#define RTW_PCI_RX_RECHECK_US 100 + +static u8 rtw_pci_rx_poison_byte(u32 off) +{ + return 0xa5 ^ (u8)off; +} + +static void rtw_pci_rx_poison(struct sk_buff *skb) +{ + u32 i; + + for (i = RTW_PCI_RX_POISON_START; i < RTW_PCI_RX_POISON_END; i++) + skb->data[i] = rtw_pci_rx_poison_byte(i); +} + static const u32 rtw_pci_tx_queue_idx_addr[] = { [RTW_TX_QUEUE_BK] = RTK_PCI_TXBD_IDX_BKQ, [RTW_TX_QUEUE_BE] = RTK_PCI_TXBD_IDX_BEQ, @@ -128,6 +158,7 @@ static void rtw_pci_free_tx_ring(struct rtw_dev *rtwdev, static void rtw_pci_free_rx_ring_skbs(struct rtw_dev *rtwdev, struct rtw_pci_rx_ring *rx_ring) { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; struct pci_dev *pdev = to_pci_dev(rtwdev->dev); struct sk_buff *skb; int buf_sz = RTK_PCI_RX_BUF_SIZE; @@ -140,7 +171,7 @@ static void rtw_pci_free_rx_ring_skbs(struct rtw_dev *rtwdev, continue; dma = *((dma_addr_t *)skb->cb); - dma_unmap_single(&pdev->dev, dma, buf_sz, DMA_FROM_DEVICE); + dma_unmap_single(&pdev->dev, dma, buf_sz, rtwpci->rx_dma_dir); dev_kfree_skb(skb); rx_ring->buf[i] = NULL; } @@ -211,6 +242,7 @@ static int rtw_pci_reset_rx_desc(struct rtw_dev *rtwdev, struct sk_buff *skb, struct rtw_pci_rx_ring *rx_ring, u32 idx, u32 desc_sz) { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; struct pci_dev *pdev = to_pci_dev(rtwdev->dev); struct rtw_pci_rx_buffer_desc *buf_desc; int buf_sz = RTK_PCI_RX_BUF_SIZE; @@ -219,7 +251,10 @@ static int rtw_pci_reset_rx_desc(struct rtw_dev *rtwdev, struct sk_buff *skb, if (!skb) return -EINVAL; - dma = dma_map_single(&pdev->dev, skb->data, buf_sz, DMA_FROM_DEVICE); + if (rtwpci->rx_debug) + rtw_pci_rx_poison(skb); + + dma = dma_map_single(&pdev->dev, skb->data, buf_sz, rtwpci->rx_dma_dir); if (dma_mapping_error(&pdev->dev, dma)) return -EBUSY; @@ -237,11 +272,22 @@ static void rtw_pci_sync_rx_desc_device(struct rtw_dev *rtwdev, dma_addr_t dma, struct rtw_pci_rx_ring *rx_ring, u32 idx, u32 desc_sz) { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; struct device *dev = rtwdev->dev; struct rtw_pci_rx_buffer_desc *buf_desc; int buf_sz = RTK_PCI_RX_BUF_SIZE; - dma_sync_single_for_device(dev, dma, buf_sz, DMA_FROM_DEVICE); + if (rtwpci->rx_debug) { + /* Only the poisoned head needs to reach the device copy; + * the CPU did not write the rest of the buffer. + */ + rtw_pci_rx_poison(rx_ring->buf[idx]); + dma_sync_single_range_for_device(dev, dma, 0, + RTW_PCI_RX_POISON_END, + DMA_BIDIRECTIONAL); + } else { + dma_sync_single_for_device(dev, dma, buf_sz, DMA_FROM_DEVICE); + } buf_desc = (struct rtw_pci_rx_buffer_desc *)(rx_ring->r.head + idx * desc_sz); @@ -254,6 +300,7 @@ static int rtw_pci_init_rx_ring(struct rtw_dev *rtwdev, struct rtw_pci_rx_ring *rx_ring, u8 desc_size, u32 len) { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; struct pci_dev *pdev = to_pci_dev(rtwdev->dev); struct sk_buff *skb = NULL; dma_addr_t dma; @@ -302,7 +349,7 @@ static int rtw_pci_init_rx_ring(struct rtw_dev *rtwdev, if (!skb) continue; dma = *((dma_addr_t *)skb->cb); - dma_unmap_single(&pdev->dev, dma, buf_sz, DMA_FROM_DEVICE); + dma_unmap_single(&pdev->dev, dma, buf_sz, rtwpci->rx_dma_dir); dev_kfree_skb_any(skb); rx_ring->buf[i] = NULL; } @@ -324,6 +371,13 @@ static int rtw_pci_init_trx_ring(struct rtw_dev *rtwdev) u32 len; int ret; + rtwpci->rx_debug = READ_ONCE(rtw_pci_rx_debug); + rtwpci->rx_dma_dir = rtwpci->rx_debug ? DMA_BIDIRECTIONAL : + DMA_FROM_DEVICE; + if (rtwpci->rx_debug) + rtw_info(rtwdev, "rx_debug: poisoning RX buffers [%u, %u)\n", + RTW_PCI_RX_POISON_START, RTW_PCI_RX_POISON_END); + tx_desc_size = chip->tx_buf_desc_sz; for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++) { @@ -1039,6 +1093,69 @@ static int rtw_pci_get_hw_rx_ring_nr(struct rtw_dev *rtwdev, return count; } +/* Called for a frame the core reported as suspect, with the buffer synced + * for the CPU. Count how much of the poison survived in the drv_info area + * and after the 802.11 header, then read the head of the buffer again a + * little later to see whether the device was still writing to it. + */ +static void rtw_pci_rx_debug_recheck(struct rtw_dev *rtwdev, + struct rtw_pci *rtwpci, + struct sk_buff *skb, dma_addr_t dma, + u32 idx, struct rtw_rx_pkt_stat *pkt_stat) +{ + u32 drv_start = rtwdev->chip->rx_pkt_desc_sz + pkt_stat->shift; + u32 drv_end = drv_start + pkt_stat->drv_info_sz; + u32 hdr_off = (u8 *)pkt_stat->hdr - skb->data; + u32 tail_start = hdr_off + ieee80211_hdrlen(pkt_stat->hdr->frame_control); + /* only bytes that belong to the frame; shorter frames leave poison */ + u32 tail_end = min_t(u32, hdr_off + pkt_stat->pkt_len, + RTW_PCI_RX_POISON_END); + u32 tail_len = tail_end > tail_start ? tail_end - tail_start : 0; + u32 drv_len = drv_end > drv_start ? drv_end - drv_start : 0; + u32 drv_poison = 0, tail_poison = 0, changed = 0, first_changed = 0; + u8 before[RTW_PCI_RX_POISON_END]; + u32 i; + + memcpy(before, skb->data, sizeof(before)); + + for (i = max_t(u32, drv_start, RTW_PCI_RX_POISON_START); + i < min_t(u32, drv_end, RTW_PCI_RX_POISON_END); i++) + drv_poison += before[i] == rtw_pci_rx_poison_byte(i); + for (i = tail_start; i < tail_end; i++) + tail_poison += before[i] == rtw_pci_rx_poison_byte(i); + + udelay(RTW_PCI_RX_RECHECK_US); + dma_sync_single_range_for_cpu(rtwdev->dev, dma, 0, + RTW_PCI_RX_POISON_END, rtwpci->rx_dma_dir); + + for (i = 0; i < RTW_PCI_RX_POISON_END; i++) { + if (skb->data[i] == before[i]) + continue; + if (!changed) + first_changed = i; + changed++; + } + + /* a changed reread is the interesting case, always report it in full */ + if (!changed && rtwdev->rx_pn_guard.events > RTW_RX_PN_GUARD_DUMPS) { + dev_warn_ratelimited(rtwdev->dev, + "rx_debug: ring %u: drv_info %u/%u poison, after header %u/%u poison, %u bytes changed\n", + idx, drv_poison, drv_len, tail_poison, tail_len, + changed); + return; + } + + rtw_warn(rtwdev, + "rx_debug: ring %u: drv_info %u/%u poison, after header [%u,%u) %u/%u poison, %u bytes changed after %uus (first at %u)\n", + idx, drv_poison, drv_len, tail_start, tail_end, + tail_poison, tail_len, changed, RTW_PCI_RX_RECHECK_US, + first_changed); + if (changed) + print_hex_dump(KERN_WARNING, "rtw88 rx reread: ", + DUMP_PREFIX_OFFSET, 16, 1, skb->data, + RTW_PCI_RX_POISON_END, false); +} + static u32 rtw_pci_rx_napi(struct rtw_dev *rtwdev, struct rtw_pci *rtwpci, u8 hw_queue, u32 limit) { @@ -1066,13 +1183,17 @@ static u32 rtw_pci_rx_napi(struct rtw_dev *rtwdev, struct rtw_pci *rtwpci, skb = ring->buf[cur_rp]; dma = *((dma_addr_t *)skb->cb); dma_sync_single_for_cpu(rtwdev->dev, dma, RTK_PCI_RX_BUF_SIZE, - DMA_FROM_DEVICE); + rtwpci->rx_dma_dir); rx_desc = skb->data; ret = rtw_rx_query_rx_desc(rtwdev, rx_desc, &pkt_stat, &rx_status); if (ret) goto next_rp; + if (unlikely(rtwpci->rx_debug && pkt_stat.suspect)) + rtw_pci_rx_debug_recheck(rtwdev, rtwpci, skb, dma, + cur_rp, &pkt_stat); + /* offset from rx_desc to payload */ pkt_offset = pkt_desc_sz + pkt_stat.drv_info_sz + pkt_stat.shift; diff --git a/drivers/net/wireless/realtek/rtw88/pci.h b/drivers/net/wireless/realtek/rtw88/pci.h index 8ffdea11378f..4a08554512f7 100644 --- a/drivers/net/wireless/realtek/rtw88/pci.h +++ b/drivers/net/wireless/realtek/rtw88/pci.h @@ -225,6 +225,8 @@ struct rtw_pci { u16 link_ctrl; atomic_t link_usage; bool rx_no_aspm; + bool rx_debug; + enum dma_data_direction rx_dma_dir; DECLARE_BITMAP(flags, NUM_OF_RTW_PCI_FLAGS); void __iomem *mmap; diff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c index 7e6b0022ad50..d29b4bed2ad3 100644 --- a/drivers/net/wireless/realtek/rtw88/rx.c +++ b/drivers/net/wireless/realtek/rtw88/rx.c @@ -306,6 +306,8 @@ static void rtw_rx_report_suspect(struct rtw_dev *rtwdev, const char *why, if (pkt_stat->pkt_len >= hdr_len + IEEE80211_CCMP_HDR_LEN) pn = rtw_rx_ccmp_pn((u8 *)hdr + hdr_len); + pkt_stat->suspect = true; + if (n > RTW_RX_PN_GUARD_DUMPS) { dev_warn_ratelimited(rtwdev->dev, "suspect hw-decrypted frame #%u (%s): tid %u max pn 0x%012llx pn 0x%012llx%s\n", -- 2.55.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* [RFC PATCH rtw-next 4/5] wifi: rtw88: make the PN guard threshold a test parameter 2026-10-21 19:05 ` Bob Beckett ` (2 preceding siblings ...) 2026-10-21 19:05 ` [RFC PATCH rtw-next 3/5] wifi: rtw88: pci: optional RX buffer poisoning and recheck Bob Beckett @ 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 5/5] wifi: rtw88: pci: clear CLKREQ/ASPM enables when disable_aspm is set Bob Beckett 2026-10-22 5:39 ` rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Ping-Ke Shih 5 siblings, 0 replies; 16+ messages in thread From: Bob Beckett @ 2026-10-21 19:05 UTC (permalink / raw) To: Ping-Ke Shih Cc: linux-wireless, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach, kernel From: Robert Beckett <bob.beckett@collabora.com> The suspect-frame report, the rx_debug recheck and the sw_decrypt_suspect drop path only run when a hardware-decrypted frame's CCMP PN jumps forward by more than 2^24, which never happens on a healthy link. Make the threshold rtw88_core.pn_guard_jump (default 2^24, unchanged behaviour) so these paths can be exercised in testing, e.g. with pn_guard_jump=0 every frame whose PN advances is treated as suspect. That also gives a baseline of the rx_debug poison/reread results for good frames. Test only; leave it at the default in the field. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Robert Beckett <bob.beckett@collabora.com> --- drivers/net/wireless/realtek/rtw88/main.c | 5 +++++ drivers/net/wireless/realtek/rtw88/main.h | 1 + drivers/net/wireless/realtek/rtw88/rx.c | 9 ++------- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c index d667e7452e4f..0cbf65c7f2c5 100644 --- a/drivers/net/wireless/realtek/rtw88/main.c +++ b/drivers/net/wireless/realtek/rtw88/main.c @@ -35,17 +35,22 @@ EXPORT_SYMBOL(rtw_debug_mask); */ bool rtw_edcca_enabled = true; bool rtw_sw_decrypt_suspect; +/* Test only: lower it to make rtw_rx_pn_guard() treat ordinary frames as suspect. */ +unsigned long long rtw_pn_guard_jump = BIT_ULL(24); module_param_named(disable_lps_deep, rtw_disable_lps_deep_mode, bool, 0644); module_param_named(support_bf, rtw_bf_support, bool, 0644); module_param_named(debug_mask, rtw_debug_mask, uint, 0644); module_param_named(sw_decrypt_suspect, rtw_sw_decrypt_suspect, bool, 0644); +module_param_named(pn_guard_jump, rtw_pn_guard_jump, ullong, 0644); MODULE_PARM_DESC(disable_lps_deep, "Set Y to disable Deep PS"); MODULE_PARM_DESC(support_bf, "Set Y to enable beamformee support"); MODULE_PARM_DESC(debug_mask, "Debugging mask"); MODULE_PARM_DESC(sw_decrypt_suspect, "Set Y to have mac80211 verify hardware-decrypted frames whose CCMP PN jumps implausibly (default: only report them)"); +MODULE_PARM_DESC(pn_guard_jump, + "CCMP PN forward jump treated as suspect (test only, default 2^24)"); static struct ieee80211_channel rtw_channeltable_2g[] = { {.center_freq = 2412, .hw_value = 1,}, diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 295af5529fcc..43c4602eb400 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -44,6 +44,7 @@ extern bool rtw_bf_support; extern bool rtw_disable_lps_deep_mode; extern unsigned int rtw_debug_mask; extern bool rtw_sw_decrypt_suspect; +extern unsigned long long rtw_pn_guard_jump; extern bool rtw_edcca_enabled; extern const struct ieee80211_ops rtw_ops; diff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c index d29b4bed2ad3..8f00153568a1 100644 --- a/drivers/net/wireless/realtek/rtw88/rx.c +++ b/drivers/net/wireless/realtek/rtw88/rx.c @@ -280,12 +280,6 @@ static bool rtw_rx_ccmp_hdr_valid(struct rtw_rx_pkt_stat *pkt_stat, return iv[2] == 0 && (iv[3] & BIT(5)); } -/* A forward PN jump this large between two frames of one key/TID is not - * plausible; frames with garbage after the 802.11 header produce random - * 48-bit PNs. - */ -#define RTW_RX_PN_GUARD_JUMP BIT_ULL(24) - static u64 rtw_rx_ccmp_pn(const u8 *iv) { return (u64)iv[0] | (u64)iv[1] << 8 | (u64)iv[4] << 16 | @@ -384,7 +378,8 @@ static void rtw_rx_pn_guard(struct rtw_dev *rtwdev, if (pn <= max) return; - if (pn - max > RTW_RX_PN_GUARD_JUMP) { + /* rtw_pn_guard_jump is 2^24 by default; see the comment above */ + if (pn - max > READ_ONCE(rtw_pn_guard_jump)) { if (READ_ONCE(rtw_sw_decrypt_suspect)) pkt_stat->decrypted = false; rtw_rx_report_suspect(rtwdev, "PN jump", pkt_stat, rx_desc8, -- 2.55.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* [RFC PATCH rtw-next 5/5] wifi: rtw88: pci: clear CLKREQ/ASPM enables when disable_aspm is set 2026-10-21 19:05 ` Bob Beckett ` (3 preceding siblings ...) 2026-10-21 19:05 ` [RFC PATCH rtw-next 4/5] wifi: rtw88: make the PN guard threshold a test parameter Bob Beckett @ 2026-10-21 19:05 ` Bob Beckett 2026-10-21 20:50 ` Luka Gejak 2026-10-22 5:39 ` rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Ping-Ke Shih 5 siblings, 1 reply; 16+ messages in thread From: Bob Beckett @ 2026-10-21 19:05 UTC (permalink / raw) To: Ping-Ke Shih Cc: linux-wireless, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach, kernel From: Robert Beckett <bob.beckett@collabora.com> disable_aspm only makes rtw_pci_clkreq_set() and rtw_pci_aspm_set() return early, so the driver stops setting BIT_CLKREQ_SW_EN and BIT_L1_SW_EN in RTK_PCIE_LINK_CFG but never clears them. These bits are device state and survive rmmod/modprobe, as nothing resets the function in between. Reloading rtw88_pci with disable_aspm=1 after a load without it therefore leaves CLKREQ, and possibly L1, enabled in the device, and the parameter only takes effect after a platform power cycle. Clear both bits in rtw_pci_link_cfg() when disable_aspm is set, so the parameter does what it says from the moment the driver probes. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Robert Beckett <bob.beckett@collabora.com> --- drivers/net/wireless/realtek/rtw88/pci.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c index d65408084f72..a047409bc0b0 100644 --- a/drivers/net/wireless/realtek/rtw88/pci.c +++ b/drivers/net/wireless/realtek/rtw88/pci.c @@ -1501,6 +1501,21 @@ static void rtw_pci_aspm_set(struct rtw_dev *rtwdev, bool enable) rtw_dbi_write8(rtwdev, RTK_PCIE_LINK_CFG, value); } +static void rtw_pci_link_hw_disable(struct rtw_dev *rtwdev) +{ + u8 value; + int ret; + + ret = rtw_dbi_read8(rtwdev, RTK_PCIE_LINK_CFG, &value); + if (ret) { + rtw_err(rtwdev, "failed to read link config, ret=%d", ret); + return; + } + + value &= ~(BIT_CLKREQ_SW_EN | BIT_L1_SW_EN); + rtw_dbi_write8(rtwdev, RTK_PCIE_LINK_CFG, value); +} + static void rtw_pci_link_ps(struct rtw_dev *rtwdev, bool enter) { struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; @@ -1537,6 +1552,13 @@ static void rtw_pci_link_cfg(struct rtw_dev *rtwdev) if (chip->id == RTW_CHIP_TYPE_8822C) rtw_dbi_write8(rtwdev, RTK_PCIE_CLKDLY_CTRL, 0); + /* The CLKREQ/ASPM enable bits live in the device and are not reset + * by a driver reload, so a previous load may have left them set. + * Clear them explicitly when ASPM is disabled. + */ + if (rtw_pci_disable_aspm) + rtw_pci_link_hw_disable(rtwdev); + /* Though there is standard PCIE configuration space to set the * link control register, but by Realtek's design, driver should * check if host supports CLKREQ/ASPM to enable the HW module. -- 2.55.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [RFC PATCH rtw-next 5/5] wifi: rtw88: pci: clear CLKREQ/ASPM enables when disable_aspm is set 2026-10-21 19:05 ` [RFC PATCH rtw-next 5/5] wifi: rtw88: pci: clear CLKREQ/ASPM enables when disable_aspm is set Bob Beckett @ 2026-10-21 20:50 ` Luka Gejak 0 siblings, 0 replies; 16+ messages in thread From: Luka Gejak @ 2026-10-21 20:50 UTC (permalink / raw) To: Bob Beckett, Ping-Ke Shih Cc: linux-wireless, Bitterblue Smith, Timlee, matthew.leach, kernel Hi Bob, For the SteamOS test build this should do the job. Few notes for upstraming though: On Wed, 21 Oct 2026, Bob Beckett wrote: > Clear both bits in rtw_pci_link_cfg() when disable_aspm is set, so the > parameter does what it says from the moment the driver probes. rtw_pci_link_cfg() runs near the end of probe. By then rtw_chip_info_setup() has already powered the MAC on, downloaded the firmware and powered it off again with the old bits still set: rtw_pci_probe() rtw_chip_info_setup() rtw_chip_efuse_info_setup() rtw_chip_efuse_enable() rtw_mac_power_on() rtw_download_firmware() rtw_chip_efuse_disable() rtw_pci_phy_cfg() rtw_pci_link_cfg() If that power on fails, probe bails out before the bits are cleared. Should the clear go before rtw_chip_info_setup(), or should the message say it takes effect once probe reaches rtw_pci_phy_cfg()? > Assisted-by: Claude:claude-opus-5-5 816d9992d9ed simplified this tag, it now just says LLM instead of the agent and model. Or you could just drop the tag in whole as that tag requirement was dropped(will be dropped) in linux 7.4. [...] > +static void rtw_pci_link_hw_disable(struct rtw_dev *rtwdev) > +{ > + u8 value; > + int ret; > + > + ret = rtw_dbi_read8(rtwdev, RTK_PCIE_LINK_CFG, &value); disable_aspm was added in 68aa716b7dd3 for platforms where this very read fails and the device then drops off the bus, and the FA506II quirk (b0034a2e4156) reports the same thing. With this patch every disable_aspm user, and the five rtw_pci_quirks[] machines on every boot, now do the read at probe. The reports I found hit it when leaving power save or around suspend, for example https://bugs.launchpad.net/bugs/1872984 where it fails in rtw_leave_ips() with the MAC off and BIT_L1_SW_EN set. After a reload your read runs in that same state. Is it known to be safe there? If not, clearing the bits in rtw_pci_remove() on loads that set them would keep disable_aspm users off this register. It would also cover a reload without disable_aspm, which today starts with L1 still on from the last rtw_enter_ips(). > + if (ret) { > + rtw_err(rtwdev, "failed to read link config, ret=%d", ret); Missing \n, the helpers above have the same problem. > + return; > + } > + > + value &= ~(BIT_CLKREQ_SW_EN | BIT_L1_SW_EN); > + rtw_dbi_write8(rtwdev, RTK_PCIE_LINK_CFG, value); > +} This is the fourth copy of the same read, mask and write of RTK_PCIE_LINK_CFG in pci.c, a small helper taking the bits to clear and set could serve all of them. rtw_pci_link_hw_disable() also reads as if it turned the link off, a name closer to rtw_pci_clkreq_set() and rtw_pci_aspm_set() would fit better. [...] > + /* The CLKREQ/ASPM enable bits live in the device and are not reset > + * by a driver reload, so a previous load may have left them set. > + * Clear them explicitly when ASPM is disabled. > + */ > + if (rtw_pci_disable_aspm) > + rtw_pci_link_hw_disable(rtwdev); Block comments should start with /* on its own line, as netdev dropped that comment style. The last sentence only repeats the if below and can go, and naming BIT_CLKREQ_SW_EN and BIT_L1_SW_EN says more than "the CLKREQ/ASPM enable bits". Best regards, Luka Gejak ^ permalink raw reply [flat|nested] 16+ messages in thread
* RE: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-21 19:05 ` Bob Beckett ` (4 preceding siblings ...) 2026-10-21 19:05 ` [RFC PATCH rtw-next 5/5] wifi: rtw88: pci: clear CLKREQ/ASPM enables when disable_aspm is set Bob Beckett @ 2026-10-22 5:39 ` Ping-Ke Shih 2026-10-22 12:31 ` Robert Beckett 5 siblings, 1 reply; 16+ messages in thread From: Ping-Ke Shih @ 2026-10-22 5:39 UTC (permalink / raw) To: Bob Beckett Cc: linux-wireless@vger.kernel.org, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach@collabora.com, kernel@collabora.com Bob Beckett <bob.beckett@collabora.com> wrote: > On Wed, 21 Oct 2026 07:30:58 +0000, Ping-Ke Shih <pkshih@realtek.com> wrote: > > Bob Beckett <bob.beckett@collabora.com> wrote: > > > > > > The AP's own PN sequence is consistent before and after the bad frame, > > > so the corruption happens on the receive side. > > > > Maybe this is a hint to make a patch to defer (drop) reporting weird packet, > > as Luka has fixed 3 of 4 problems. I quickly checked the debug patches, which seem not detect if PN sequence is correct after the bad frame, for example seq PN 1 101 2 99999 // bad frame. should be 102. 3 103 // not detect this Or I missed something? > > > 4. For completeness: on these chips can RXBD_IDX or the > > > buffer-descriptor RX tag ever be updated before the packet data DMA > > > has completed, as described for WiFi 7 in rtw89 0bc7d1d4e63c? > > > > The RX tag in rtw88 located on memory allocated by dma_alloc_coherent(), and > > the RX tag must be set after packet data MAC completed. (fix typo: ... data DMA completed.) > > Understood that this is the design intent. My question was whether the host can > ever observe the tag before the data. dma_alloc_coherent() only makes the tag > visible to the CPU without cache maintenance; the payload is in a separate > streaming mapping (a SWIOTLB bounce buffer on this platform), so the order in > which the host sees them depends on the order of the device's PCIe writes. > Relaxed Ordering is enabled on the 8822CE here (DevCtl RlxdOrd+). > Does the 8822CE set the RO attribute on RX DMA writes, in particular the > buffer-descriptor/tag update? If it does, the tag write may pass the payload > writes. > > We are hoping to test this directly with the next round of tests. the rx_debug > option re-syncs a suspect buffer 100 us later, so late payload writes might > show up as changed bytes, and we can repeat the test with Relaxed Ordering > disabled. I'll check the behavior internally and get back to you. ^ permalink raw reply [flat|nested] 16+ messages in thread
* RE: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-22 5:39 ` rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Ping-Ke Shih @ 2026-10-22 12:31 ` Robert Beckett 0 siblings, 0 replies; 16+ messages in thread From: Robert Beckett @ 2026-10-22 12:31 UTC (permalink / raw) To: Ping-Ke Shih Cc: linux-wireless@vger.kernel.org, Luka Gejak, Bitterblue Smith, Timlee, matthew.leach@collabora.com, kernel@collabora.com From: Ping-Ke Shih <pkshih@realtek.com> To: "Bob Beckett"<bob.beckett@collabora.com> Cc: "linux-wireless@vger.kernel.org"<linux-wireless@vger.kernel.org>, "Luka Gejak"<luka.gejak@linux.dev>, "Bitterblue Smith"<rtl8821cerfe2@gmail.com>, "Timlee"<timlee@realtek.com>, "matthew.leach@collabora.com"<matthew.leach@collabora.com>, "kernel@collabora.com"<kernel@collabora.com> Date: Thu, 22 Oct 2026 06:39:55 +0100 Subject: RE: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter > Bob Beckett <bob.beckett@collabora.com> wrote: > > On Wed, 21 Oct 2026 07:30:58 +0000, Ping-Ke Shih <pkshih@realtek.com> wrote: > > > Bob Beckett <bob.beckett@collabora.com> wrote: > > > > > > > > The AP's own PN sequence is consistent before and after the bad frame, > > > > so the corruption happens on the receive side. > > > > > > Maybe this is a hint to make a patch to defer (drop) reporting weird packet, > > > as Luka has fixed 3 of 4 problems. > > I quickly checked the debug patches, which seem not detect if PN sequence is > correct after the bad frame, for example > seq PN > 1 101 > 2 99999 // bad frame. should be 102. > 3 103 // not detect this > > Or I missed something? if pn jumps, rtw_rx_pn_guard returns early before latching pn, so the next corrected pn would just be a regular increase and not get dropped. > > > > > 4. For completeness: on these chips can RXBD_IDX or the > > > > buffer-descriptor RX tag ever be updated before the packet data DMA > > > > has completed, as described for WiFi 7 in rtw89 0bc7d1d4e63c? > > > > > > The RX tag in rtw88 located on memory allocated by dma_alloc_coherent(), and > > > the RX tag must be set after packet data MAC completed. > > (fix typo: ... data DMA completed.) > > > > > Understood that this is the design intent. My question was whether the host can > > ever observe the tag before the data. dma_alloc_coherent() only makes the tag > > visible to the CPU without cache maintenance; the payload is in a separate > > streaming mapping (a SWIOTLB bounce buffer on this platform), so the order in > > which the host sees them depends on the order of the device's PCIe writes. > > Relaxed Ordering is enabled on the 8822CE here (DevCtl RlxdOrd+). > > Does the 8822CE set the RO attribute on RX DMA writes, in particular the > > buffer-descriptor/tag update? If it does, the tag write may pass the payload > > writes. > > > > We are hoping to test this directly with the next round of tests. the rx_debug > > option re-syncs a suspect buffer 100 us later, so late payload writes might > > show up as changed bytes, and we can repeat the test with Relaxed Ordering > > disabled. > > I'll check the behavior internally and get back to you. > > > ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter 2026-10-16 19:11 rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Bob Beckett 2026-10-16 22:15 ` Luka Gejak 2026-10-21 7:30 ` Ping-Ke Shih @ 2026-10-21 11:32 ` Bitterblue Smith 2 siblings, 0 replies; 16+ messages in thread From: Bitterblue Smith @ 2026-10-21 11:32 UTC (permalink / raw) To: Bob Beckett, Ping-Ke Shih; +Cc: linux-wireless, kernel, matthew.leach On 16/10/2026 22:11, Bob Beckett wrote: > Hi Ping-Ke, > > We are investigating a Wi-Fi failure on the Steam Deck LCD (RTL8822CE, > rtw88) and would appreciate your input; details below. > > Summary > ======= > > On RTL8822CE (PCIe, rtw88), during heavy 5 GHz VHT downlink from one AP > model, the driver occasionally hands mac80211 a frame with these > properties: > - the RX descriptor looks normal: CRC OK, ICV OK, AES, not > software-decrypted; > - the 802.11 header is correct; > - the bytes from the CCMP header onwards are garbage. > > Because rtw88 sets RX_FLAG_DECRYPTED for it, mac80211 trusts the frame > and takes its PN, a random 48-bit value, as the new last PN for that > key/TID. Every later genuine frame has a lower PN and is dropped as > RX_DROP_U_REPLAY. The station stays associated with good signal but no > data gets through until new keys are installed (reconnect). Userspace > sees this as "limited connectivity". > > The AP's own PN sequence is consistent before and after the bad frame, > so the corruption happens on the receive side. The host-side DMA > bookkeeping looks consistent: the RX buffer-descriptor tag never > mismatched. The corruption starts exactly where the CCMP header begins, > which points at the device's RX decrypt/payload path rather than host > DMA. The same hardware works with this AP under Windows, so we would > like to know what the vendor driver does differently, and whether the > descriptor can tell us when a frame's payload cannot be trusted. > There is an alternative driver which is probably very similar to the Windows driver: https://github.com/juanro49/rtl88x2ce-dkms If this doesn't have the same problem, you can check what it does differently compared to rtw88. > Setup > ===== > > - Steam Deck LCD: RTL8822CE (AzureWave), PCIe x1 Gen1, rtw88_8822ce > - Firmware 9.9.15 (WOW 9.9.4), H2C version 15 > - Seen on SteamOS kernels 6.18.50 and 7.2.7. Both carry rtw88 backports > from rtw-next; the 7.2.7 rtw88 is close to rtw-next as of 2026-09. > - AP: Free "Freebox Revolution", 5 GHz channel 36, 80 MHz, VHT, max MPDU > 11454, max A-MSDU (HT) 3839, CCMP pairwise and group, PSK/SAE > - Link at the time: signal -30 to -34 dBm, RX VHT-MCS 8-9 NSS 2 80 MHz, > during a bulk TCP download (game download) at ~20k RX descriptors/s > - wpa_supplicant + NetworkManager > > Public reports of the same symptom with this AP (Steam Deck LCD + > Freebox Revolution, 5 GHz, drops when a download starts, fixed by > disabling 802.11ac on the AP, works under Windows) go back to at least > September 2024. > > How it was captured > =================== > > The capture used bpftrace on the stock kernel, with no driver changes: > - a kprobe on ieee80211_crypto_ccmp_decrypt() compares the frame's PN > with key->u.ccmp.rx_pn[rx->security_idx] before the check, and on a > forward jump above 0x10000 dumps: > - the frame; > - the rtw88 RX descriptor plus drv_info area, which is still in the > skb in front of skb->data (rtw_pci_rx_napi() uses skb_pull()); > - the previous 16 and next 24 CCMP frames; > - a kprobe on rtw_rx_query_rx_desc() classifies every descriptor; > - an fexit on rtw_pci_read32() for HISR0/HISR1; > - tracepoint:skb:kfree_skb provides drop reasons; > - mac80211 debugfs keys/*/rx_spec and replays were sampled every 2 s. > > Evidence 1: the replay counter is poisoned by one frame > ======================================================= > > From mac80211 debugfs, for the pairwise key at TID 0: > > Run rx_spec before At failure ~1 min later replays > -------- -------------- ------------ ------------ ------------ > A (6.18) 0000000004b1 38edae88b295 38edae88b295 1508 -> 2759 > B (7.2) 0000000093d2 cc7106634a65 cc7106634a65 748 -> 1832 > > - The stored PN never moves again, so no pairwise-protected frame is > accepted after the bad one: every one that reaches the CCMP check is > dropped as RX_DROP_U_REPLAY (skb:kfree_skb reason 0x10002 in > ieee80211_rx_handlers). The only data still accepted is > group-addressed traffic, which uses the unaffected group key; its PN > keeps advancing normally. > - Reconnecting installs new keys and traffic resumes, until the next bad > frame. > > Evidence 2: the bad frames > ========================== > > Run C (7.2.7) caught 4 PN jumps in about 6 minutes: > > # seq last PN PN in frame expected PN > - ---------- -------------- -------------- ----------------------- > 1 4025 0xb8fba 0x3dcdcabef924 0xb8fbc (4026: 0xb8fbd) > 2 3174 0xcc69 0x08e1ad331968 0xcc6a (3175: 0xcc6b) > 3 64 (retry) 0x15043 0x4bf378495044 0x15044 (66: 0x15046) > 4 65 (retry) 0x4bf378495044 0xf0e626ce3844 0x15045 > > All four: QoS data, TID 0, A-MSDU, 3088 bytes. 3 and 4 came seconds > after a reconnect. "Expected PN" is derived from the neighbouring > frames (next SN in brackets). > > Around jump 1, the AP's PN sequence was: > - seq 4009-4023: PN 0xb8fac-0xb8fba, consecutive; > - seq 4024: never reached the CCMP check; > - seq 4025: the bad frame; > - seq 4026, 4027, ...: PN 0xb8fbd, 0xb8fbe, ..., all rejected as > replays. > > So the AP transmitted a normal PN for 4025. > > The four bad frames share these properties: > - The RX descriptor is identical to the good frames around them: > w0=0x00c40c14, i.e. pkt_len 3092, drv_info_sz 4, PHYST 0, enc AES, > CRC32 0, ICV_ERR 0, SWDEC 0. w1=0x2100b004, w3=0x3f (VHT 2SS MCS9). > The low 12 bits of w2 equal the 802.11 sequence number of the frame. > - The 802.11 header is correct: FC 0x4288/0x4a88 (QoS data, FromDS, > Protected, +/-Retry), correct addresses, sequence number matching the > descriptor, QoS control 0x0080 (TID 0, A-MSDU present). > - The bytes from header offset 26 are garbage. A valid CCMP header has > byte 2 = 0x00 and byte 3 = 0x20 (ExtIV, key ID 0), as all the good > frames do: > - frame 1: 24 f9 1a 04 ... > - frame 2: 68 19 8e b7 ... > - frame 4: 44 38 f1 14 ... > - frame 3: 44 50 00 20 49 78 f3 4b. The first four bytes match the > expected PN 0x015044 exactly, then it is garbage from header offset > 30. > - The "decrypted" A-MSDU body that follows is random. It should start > with a subframe header containing the station's own address. > - mac80211 receives flag=0x20102, i.e. RX_FLAG_DECRYPTED | > RX_FLAG_NO_SIGNAL_VAL | RX_FLAG_MACTIME_START (no PHY status, so no > signal value). > > In other words, the descriptor and roughly the first 26-30 bytes of the > frame are right, and the rest of the buffer does not contain this > frame's data. > > Evidence 3: other RX-path anomalies in the same run > =================================================== > > - 33 s before the first jump, during the download and with no other > activity, one descriptor claims pkt_len 11619 (above the 11454 > maximum), and rtw_rx_query_rx_desc() returns -EINVAL twice in the same > second. These are garbage descriptors, which the rtw-next > RX-descriptor validation correctly rejected. This was never seen in a > 21-minute ~540 Mbit/s test against a different AP. > - Not related to the bug (capture artifacts): the HISR1 RXFOVW bit (RX > FIFO overflow) and 18 "coex request time out" errors appeared only > within 1-2 s of the capture tool's debugfs snapshots. Those snapshots > read coex_info and the mac_*/bb_* register dumps, which hold > rtwdev->mutex and leave deep PS. None of the PN jumps coincide with a > snapshot. > - Firmware communication otherwise looked healthy: no H2C send failures; > 663 "C2H 0x15 isn't handled" (benign); 3 TX-report purge timeouts in > ~8 minutes. > > Observation: heavy AP retry activity around the event > ----------------------------------------------------- > > Shortly after jump 1, the genuine TID 0 stream shows a single PN gap > with consecutive sequence numbers: > > seq=4044 pn=0x0000000b8fcf w0=0x00c40c14 (no PHY status) > seq=4045 pn=0x0000000b9053 w0=0x04c40c14 > (PHY status present: first MPDU of a new PPDU) > > - Of 69 consecutive-SN pairs logged in this run, this is the only one > where the PN advances by more than 1; here it advances by 0x84. > - The PN counter is per key and the SN counter per TID, so the AP > encrypted 131 other frames for this station in between without using > new TID 0 sequence numbers. > - No other TID's PN counter on the pairwise key ever advanced (rx_spec > for TIDs 1-15 stays 0), so these were not frames on another TID that > we accepted. > - The gap falls between two A-MPDUs. > > The likeliest explanation is that the AP retransmitted about 131 > already-numbered MPDUs and re-encrypted each retransmission with a fresh > PN. IEEE 802.11 expects retransmissions to keep their PN, but some > implementations re-encrypt on software retry. This matches what else > happened around the onset: > - a BlockAck Request (BAR_RX_EVENT) and a BlockAck frame timeout; > - about 3,500 data frames in the second after the jump that never > reached the CCMP check and left no kfree_skb drop reason. That is > consistent with mac80211's A-MPDU reorder buffer discarding > out-of-date retransmissions via dev_kfree_skb(), i.e. consume_skb(), > which kfree_skb tracing cannot see. > > This is not confirmed yet; a follow-up capture traces skb:consume_skb > per caller to account for those frames. A PN gap is legal and is not the > cause of the failure. It does suggest that the bad frames arrive while > the AP is doing heavy retry/re-aggregation toward the station. > > Where the corruption appears to come from > ========================================= > > Host DMA bookkeeping is consistent. rtw88's rtw_pci_dma_check() compares > the RX tag in each buffer descriptor with the expected sequence and logs > "pci bus timeout, check dma status" on a mismatch (an unconditional > dev_warn). That warning appears zero times in all three runs, so every > buffer descriptor had been written when the driver consumed it. The > start of each bad buffer is also fresh: the RX descriptor's sequence > number (w2[11:0]) matches the 802.11 header's. > > The corruption boundary does not line up with PCIe writes. The device > has MaxPayload 128 bytes, and on this host nearly every RX buffer goes > through SWIOTLB bounce slots, which start 2 KB-aligned because rtw88 > sets no min_align_mask (see below). So DMA writes split at 128-byte > boundaries of the buffer. The good data ends 82 or 86 bytes into the > buffer, which is not such a boundary. > > It lines up with the CCMP header. In frame coordinates, the good data > ends exactly at offset 26 (the start of the CCMP header), or 4 bytes > into it in one frame. That is where the part of the frame the MAC passes > through unchanged (the 802.11 header) ends, and the part handled by the > hardware decryption engine begins. Our reading is that for these frames > the payload/decrypt path produced wrong data while the descriptor still > reported CRC OK, ICV OK and hardware-decrypted. That would be a > device-internal condition, possibly related to the RX FIFO: the garbage > descriptors at Evidence 3 point the same way. > > Remaining host-side possibilities, considered less likely: > - Relaxed Ordering: DevCtl: RlxdOrd+ is enabled for the device. If its > payload writes carried the RO attribute, they could in principle be > overtaken by the descriptor/tag write. It can be tested at runtime by > clearing RO with setpci. > - Index ahead of DMA: Realtek fixed this class of race in rtw89 > (0bc7d1d4e63c "validate RX tag for RXQ and RPQ": "DMA and ring index > are asynchronous, so suddenly driver gets newer index ahead before > DMA"; also 94298477f81a, dda27a47c036). For pre-WiFi 7 chips the > commit says they "use different design and don't really meet this > situation". Here the buffer descriptor and the head of the buffer were > demonstrably complete. > > Platform note: the kernel boots with amd_iommu=off ("PCI-DMA: Using > software bounce buffering for IO (SWIOTLB)"). rtw88 uses the default > 32-bit DMA mask, because its RX buffer descriptor holds a 32-bit > address, and the machine has 16 GB, so nearly every RX buffer is > bounced. rtw_pci_rx_napi() syncs the full RTK_PCI_RX_BUF_SIZE (11478 > bytes) per frame regardless of length, about 230 MB/s of memcpy at 20k > frames/s. This does not explain a boundary at the CCMP header, but it is > unnecessary overhead. > > For comparison, rtlwifi's 8192EE ("new TRX flow" rings, commented "in > Windows, this == Rx_92EE_Interrupt") polls the RX buffer descriptor > before use (rtl92ee_rx_check_dma_ok()), and only passes frames up when > !crc && !hwerror (hwerror = crc | icv). rtw88 passes ICV-error frames up > as decrypted. > > Ruled out > ========= > > - Oversized MPDUs (new_len > RTK_PCI_RX_BUF_SIZE): the bad frames are > 3092 bytes, and A-MSDUs from this AP top out at about 9.7 KB. > - ASPM / deep LPS: jumps 3 and 4 happened seconds after reloading with > disable_aspm=1 disable_lps_deep=1. > - Power save (iw ... power_save off) and beamformee (support_bf=0 + > reconnect): no effect. > - Driver consuming a ring entry before its buffer descriptor was > written: no RX-tag mismatch ("pci bus timeout") in any run. > - The AP sending a bad PN: the PN sequences on both sides of each bad > frame are consecutive. > - The RX-descriptor validation added in rtw-next: it does not reject > these frames, because their descriptors are well-formed. > > Questions for Realtek > ===================== > > 1. Is there a known condition on 8822C (or 8822B/8821C) where a > hardware-decrypted MPDU or A-MSDU is delivered with a valid RX > descriptor (CRC32 0, ICV_ERR 0, SWDEC 0, enc AES) and a valid 802.11 > header, but an invalid CCMP header and payload? Does the RX > descriptor or drv_info have any bit that flags it? For example > "payload invalid", "decrypt engine bypassed or aborted", or a > per-packet RX FIFO overflow. > 2. What does the vendor/Windows driver check before trusting a > hardware-decrypted frame, and does it handle HISR1 RXFOVW / HISR0 > RDU? rtw88 masks and ignores both. > 3. Are there RX FIFO, RX DMA, security-engine or RX aggregation settings > in the Windows driver for 8822CE that differ from rtw88's and matter > for high-rate VHT A-MSDU reception (80 MHz, 2SS MCS 8-9, ~3 KB > A-MSDUs at ~20k/s)? > 4. For completeness: on these chips can RXBD_IDX or the > buffer-descriptor RX tag ever be updated before the packet data DMA > has completed, as described for WiFi 7 in rtw89 0bc7d1d4e63c? > 5. rtw_rx_fill_rx_status() sets RX_FLAG_DECRYPTED whenever !swdec && > enc_type != NONE, ignoring ICV_ERR. rtw89 sets RX_FLAG_FAILED_FCS_CRC > and not DECRYPTED on icv_err; mt76 marks such frames monitor-only; > rtlwifi drops them. rtw88 does not set BIT_AICV in RCR, so such > frames are presumably dropped in hardware, and these frames had > ICV_ERR clear anyway. Would you take a patch aligning rtw88 with > rtw89 regardless? > > Possible mitigation > =================== > > Whatever the device-side cause turns out to be, one bad frame should not > take the link down. As a defence, rtw88 could refuse to claim decryption > for an AES frame whose CCMP header is malformed. The ExtIV bit must be > set and the reserved byte must be zero for CCMP/GCMP. That lets mac80211 > treat the frame as not decrypted: it then fails software decryption and > is dropped without updating the PN. This would have caught 3 of the 4 > frames above; frame 3 had a plausible first half of the IV. Sketch, > untested: > > /* rx.c, rtw_rx_fill_rx_status() */ > if (pkt_stat->crc_err || pkt_stat->icv_err) > rx_status->flag |= RX_FLAG_FAILED_FCS_CRC; > if (pkt_stat->decrypted && !pkt_stat->icv_err && > rtw_rx_ccmp_hdr_valid(pkt_stat, hdr)) > rx_status->flag |= RX_FLAG_DECRYPTED; > > static bool rtw_rx_ccmp_hdr_valid(struct rtw_rx_pkt_stat *pkt_stat, > struct ieee80211_hdr *hdr) > { > u8 *iv; > > if (!ieee80211_has_protected(hdr->frame_control)) > return true; > if (pkt_stat->pkt_len < ieee80211_hdrlen(hdr->frame_control) + > IEEE80211_CCMP_HDR_LEN) > return false; > iv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control); > /* CCMP/GCMP header: byte 2 reserved (0), byte 3 ExtIV must be set */ > return iv[2] == 0 && (iv[3] & 0x20); > } > > (This only applies when the hardware's enc_type is AES, and needs > pkt_len to be checked against the copied length.) > > This does not catch a frame whose corruption begins after the ExtIV > byte, like frame 3. The real fix depends on Realtek's answer to > questions 1-3: a descriptor bit to honour, or an RX setting that avoids > the condition. A more general option would be a mac80211-side sanity > limit on the forward PN jump accepted from hardware-decrypted frames, > but that is a policy change in mac80211 rather than a driver fix. > > Unrelated to the bug: syncing only the descriptor first and then > (pkt_len + offset) bytes, instead of the full 11478, would cut the > SWIOTLB copy cost per frame by about 4x for typical 3 KB frames on hosts > like this one. > > Appendix: raw dumps (run C, MACs redacted) > ========================================== > > MAC addresses are redacted: SS = station (Steam Deck), AA = AP. > > Layout of the buffer dump: 24-byte RX descriptor, 32-byte drv_info area > (PHYST=0, so this area is not a PHY status and contains stale bytes), > then the 802.11 frame. > > Jump 1 > ------ > > q=0 seq=4025 last=0x0000000b8fba pn=0x3dcdcabef924 len=3088 > flag=0x20102 hdrlen=26 fc=0x4288 desc_off=56 w0=0x00c40c14 w3=0x0000003f > RX desc dwords: w0=00c40c14 w1=2100b004 w2=80890fb9 > w3=0000003f w4=3ffe7600 w5=0476df59 > > buffer (RX desc 24 + drv_info 32, then frame): > 0000: 14 0c c4 00 04 b0 00 21 b9 0f 89 80 3f 00 00 00 > 0010: 00 76 fe 3f 59 df 76 04 96 3f a1 bd fb 6c 9f 26 > 0020: 5b 31 fa 29 90 c6 2c 92 f9 f4 86 44 70 47 1d 2a > 0030: a2 47 2c df 83 43 0f ae 88 42 30 00 SS SS SS SS > > frame[0:128] as passed to mac80211: > 0000: 88 42 30 00 SS SS SS SS SS SS AA AA AA AA AA AA > 0010: AA AA AA AA AA AA 90 fb 80 00 24 f9 1a 04 be ca > 0020: cd 3d f1 98 26 03 ca 51 c5 d7 72 b4 5c cb d4 a9 > 0030: 20 09 37 ed 83 6c c8 0d 1f 45 03 ac 86 ec d1 30 > 0040: d2 42 b1 ac 33 33 8f b6 95 66 c1 c3 28 ea 84 a7 > 0050: dc a3 6d 4a 86 10 9d f6 ce a8 a3 91 5b 7d be ad > 0060: d0 40 2a 35 cf b5 69 60 58 a5 6a df fd 62 b4 df > 0070: 4c c3 c6 c0 17 28 4c 25 ae b2 24 1d 05 44 e9 28 > > Jump 2 > ------ > > q=0 seq=3174 last=0x00000000cc69 pn=0x08e1ad331968 len=3088 > flag=0x20102 hdrlen=26 fc=0x4288 desc_off=56 w0=0x00c40c14 w3=0x0000003f > RX desc dwords: w0=00c40c14 w1=2100b004 w2=e0890c66 > w3=0000003f w4=3ffea000 w5=0042a4f3 > > buffer (RX desc 24 + drv_info 32, then frame): > 0000: 14 0c c4 00 04 b0 00 21 66 0c 89 e0 3f 00 00 00 > 0010: 00 a0 fe 3f f3 a4 42 00 e7 ee 7c c1 05 59 a8 1f > 0020: 14 0c c4 00 04 b0 00 21 5d 0c 89 e0 3f 00 00 00 > 0030: 00 a0 fe 3f fa a3 42 00 88 42 30 00 SS SS SS SS > > frame[0:128] as passed to mac80211: > 0000: 88 42 30 00 SS SS SS SS SS SS AA AA AA AA AA AA > 0010: AA AA AA AA AA AA 60 c6 80 00 68 19 8e b7 33 ad > 0020: e1 08 62 33 52 ed 0d 6a 45 ae 7d b0 a8 cc 18 03 > 0030: 4a e2 fc 7e cd b3 e8 fc 99 d2 03 86 19 f1 a9 c5 > 0040: 5f ef ba d6 91 24 48 24 a2 e6 de bb dd 42 4f 16 > 0050: cc 0f bd ff d5 c4 a8 8b 8e ca f0 ed 77 ea c5 e8 > 0060: 95 b9 51 1a 8a 88 bb 11 08 2a 54 7c 74 08 b5 df > 0070: b7 6b e1 5d 5b c6 0e fa a6 f8 7b 05 57 98 69 c8 > > Jump 3 > ------ > > q=0 seq=64 last=0x000000015043 pn=0x4bf378495044 len=3088 > flag=0x20102 hdrlen=26 fc=0x4a88 desc_off=56 w0=0x00c40c14 w3=0x0000003f > RX desc dwords: w0=00c40c14 w1=2100b004 w2=80890040 > w3=0000003f w4=3ffee400 w5=00e8b9cd > > buffer (RX desc 24 + drv_info 32, then frame): > 0000: 14 0c c4 00 04 b0 00 21 40 00 89 80 3f 00 00 00 > 0010: 00 e4 fe 3f cd b9 e8 00 c3 55 de b3 f6 ad 3a 63 > 0020: a3 c2 45 b0 5e f6 1c a3 9a 18 cc e1 a8 be 11 9e > 0030: ec a8 48 3d d1 3f 03 be 88 4a 30 00 SS SS SS SS > > frame[0:128] as passed to mac80211: > 0000: 88 4a 30 00 SS SS SS SS SS SS AA AA AA AA AA AA > 0010: AA AA AA AA AA AA 00 04 80 00 44 50 00 20 49 78 > 0020: f3 4b 75 3e db 0e a4 3d 81 52 1c 1a ff b2 f2 cd > 0030: 04 dd ca 84 b9 e8 05 cc 72 7b e6 5a 7e 03 40 a6 > 0040: 95 6f d6 d8 94 89 ef 3f 95 76 cc 37 c2 40 6c 35 > 0050: a4 93 9f 13 8d fb 0f ad 15 45 47 32 86 96 b0 bc > 0060: 1f 76 b5 af 83 b1 ed 87 d6 c0 79 12 56 89 57 c4 > 0070: de dc dd 44 27 77 a3 65 cd 67 1e 49 a1 ab d8 c1 > > Jump 4 > ------ > > q=0 seq=65 last=0x4bf378495044 pn=0xf0e626ce3844 len=3088 > flag=0x20102 hdrlen=26 fc=0x4a88 desc_off=56 w0=0x00c40c14 w3=0x0000003f > RX desc dwords: w0=00c40c14 w1=2100b004 w2=80890041 > w3=0000003f w4=3ffee400 w5=00e8b9e9 > > buffer (RX desc 24 + drv_info 32, then frame): > 0000: 14 0c c4 00 04 b0 00 21 41 00 89 80 3f 00 00 00 > 0010: 00 e4 fe 3f e9 b9 e8 00 75 04 60 c1 fa f1 24 13 > 0020: 47 b6 bc 69 fe 61 9d c0 23 83 52 cb 32 12 4d b1 > 0030: e4 8c ec 59 84 ec 40 fa 88 4a 30 00 SS SS SS SS > > frame[0:128] as passed to mac80211: > 0000: 88 4a 30 00 SS SS SS SS SS SS AA AA AA AA AA AA > 0010: AA AA AA AA AA AA 10 04 80 00 44 38 f1 14 ce 26 > 0020: e6 f0 d3 39 6d 3f 56 26 4b 47 7f 76 8e 53 e4 0b > 0030: dc 81 57 79 85 94 0b d5 c0 f7 d9 00 84 6f 34 58 > 0040: 6f f7 54 33 48 55 9f c5 82 69 71 6a 1b 6e 99 2c > 0050: 64 54 62 9e 73 f1 80 4e 8b 3f d4 3e 24 bc c7 a9 > 0060: 81 15 18 82 eb d9 06 57 ae 14 e2 cb c0 61 2a 9f > 0070: ab d9 4d 92 9f d6 d6 fb ea 28 09 64 b5 6e f5 6d > > Notes > ===== > > - The capture reads rtw88's rsvd_page debugfs file. On kernels without > rtw-next 81510c3d6f21 ("debugfs: don't vzalloc(0) when rsvd_page is > read unconfigured"), that triggers a vmalloc !size WARN. This is > unrelated to this issue. > > Thanks, > Bob ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-10-22 12:32 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-16 19:11 rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Bob Beckett 2026-10-16 22:15 ` Luka Gejak 2026-10-17 8:36 ` Luka Gejak 2026-10-19 12:43 ` Matthew Leach 2026-10-19 16:30 ` Luka Gejak 2026-10-21 7:30 ` Ping-Ke Shih 2026-10-21 19:05 ` Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 1/5] wifi: rtw88: do not trust a claimed decryption Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 2/5] wifi: rtw88: report and optionally re-verify suspect hw-decrypted frames Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 3/5] wifi: rtw88: pci: optional RX buffer poisoning and recheck Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 4/5] wifi: rtw88: make the PN guard threshold a test parameter Bob Beckett 2026-10-21 19:05 ` [RFC PATCH rtw-next 5/5] wifi: rtw88: pci: clear CLKREQ/ASPM enables when disable_aspm is set Bob Beckett 2026-10-21 20:50 ` Luka Gejak 2026-10-22 5:39 ` rtw88: 8822CE passes corrupted A-MSDUs as hw-decrypted, poisoning CCMP replay counter Ping-Ke Shih 2026-10-22 12:31 ` Robert Beckett 2026-10-21 11:32 ` Bitterblue Smith
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox