Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

wifi: prevent A-MSDU attacks in mesh networks

This patch is a mitigation to prevent the A-MSDU spoofing vulnerability
for mesh networks. The initial update to the IEEE 802.11 standard, in
response to the FragAttacks, missed this case (CVE-2025-27558). It can
be considered a variant of CVE-2020-24588 but for mesh networks.

This patch tries to detect if a standard MSDU was turned into an A-MSDU
by an adversary. This is done by parsing a received A-MSDU as a standard
MSDU, calculating the length of the Mesh Control header, and seeing if
the 6 bytes after this header equal the start of an rfc1042 header. If
equal, this is a strong indication of an ongoing attack attempt.

This defense was tested with mac80211_hwsim against a mesh network that
uses an empty Mesh Address Extension field, i.e., when four addresses
are used, and when using a 12-byte Mesh Address Extension field, i.e.,
when six addresses are used. Functionality of normal MSDUs and A-MSDUs
was also tested, and confirmed working, when using both an empty and
12-byte Mesh Address Extension field.

It was also tested with mac80211_hwsim that A-MSDU attacks in non-mesh
networks keep being detected and prevented.

Note that the vulnerability being patched, and the defense being
implemented, was also discussed in the following paper and in the
following IEEE 802.11 presentation:

https://papers.mathyvanhoef.com/wisec2025.pdf
https://mentor.ieee.org/802.11/dcn/25/11-25-0949-00-000m-a-msdu-mesh-spoof-protection.docx

Cc: stable@vger.kernel.org
Signed-off-by: Mathy Vanhoef <Mathy.Vanhoef@kuleuven.be>
Link: https://patch.msgid.link/20250616004635.224344-1-Mathy.Vanhoef@kuleuven.be
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

authored by

Mathy Vanhoef and committed by
Johannes Berg
737bb912 2ce6ad92

+50 -2
+50 -2
net/wireless/util.c
··· 820 820 } 821 821 EXPORT_SYMBOL(ieee80211_is_valid_amsdu); 822 822 823 + 824 + /* 825 + * Detects if an MSDU frame was maliciously converted into an A-MSDU 826 + * frame by an adversary. This is done by parsing the received frame 827 + * as if it were a regular MSDU, even though the A-MSDU flag is set. 828 + * 829 + * For non-mesh interfaces, detection involves checking whether the 830 + * payload, when interpreted as an MSDU, begins with a valid RFC1042 831 + * header. This is done by comparing the A-MSDU subheader's destination 832 + * address to the start of the RFC1042 header. 833 + * 834 + * For mesh interfaces, the MSDU includes a 6-byte Mesh Control field 835 + * and an optional variable-length Mesh Address Extension field before 836 + * the RFC1042 header. The position of the RFC1042 header must therefore 837 + * be calculated based on the mesh header length. 838 + * 839 + * Since this function intentionally parses an A-MSDU frame as an MSDU, 840 + * it only assumes that the A-MSDU subframe header is present, and 841 + * beyond this it performs its own bounds checks under the assumption 842 + * that the frame is instead parsed as a non-aggregated MSDU. 843 + */ 844 + static bool 845 + is_amsdu_aggregation_attack(struct ethhdr *eth, struct sk_buff *skb, 846 + enum nl80211_iftype iftype) 847 + { 848 + int offset; 849 + 850 + /* Non-mesh case can be directly compared */ 851 + if (iftype != NL80211_IFTYPE_MESH_POINT) 852 + return ether_addr_equal(eth->h_dest, rfc1042_header); 853 + 854 + offset = __ieee80211_get_mesh_hdrlen(eth->h_dest[0]); 855 + if (offset == 6) { 856 + /* Mesh case with empty address extension field */ 857 + return ether_addr_equal(eth->h_source, rfc1042_header); 858 + } else if (offset + ETH_ALEN <= skb->len) { 859 + /* Mesh case with non-empty address extension field */ 860 + u8 temp[ETH_ALEN]; 861 + 862 + skb_copy_bits(skb, offset, temp, ETH_ALEN); 863 + return ether_addr_equal(temp, rfc1042_header); 864 + } 865 + 866 + return false; 867 + } 868 + 823 869 void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, 824 870 const u8 *addr, enum nl80211_iftype iftype, 825 871 const unsigned int extra_headroom, ··· 907 861 /* the last MSDU has no padding */ 908 862 if (subframe_len > remaining) 909 863 goto purge; 910 - /* mitigate A-MSDU aggregation injection attacks */ 911 - if (ether_addr_equal(hdr.eth.h_dest, rfc1042_header)) 864 + /* mitigate A-MSDU aggregation injection attacks, to be 865 + * checked when processing first subframe (offset == 0). 866 + */ 867 + if (offset == 0 && is_amsdu_aggregation_attack(&hdr.eth, skb, iftype)) 912 868 goto purge; 913 869 914 870 offset += sizeof(struct ethhdr);