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

wifi: mac80211: add strict mode disabling workarounds

Add a strict mode where we disable certain workarounds and have
additional checks such as, for now, that VHT capabilities from
association response match those from beacon/probe response. We
can extend the checks in the future.

Make it an opt-in setting by the driver so it can be set there
in some driver-specific way, for example. Also allow setting
this one hw flag through the hwflags debugfs, by writing a new
strict=0 or strict=1 value.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Reviewed-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20250205110958.5cecb0469479.I4a69617dc60ba0d6308416ffbc3102cfd08ba068@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

+79 -16
+6
include/net/mac80211.h
··· 2852 2852 * implements MLO, so operation can continue on other links when one 2853 2853 * link is switching. 2854 2854 * 2855 + * @IEEE80211_HW_STRICT: strictly enforce certain things mandated by the spec 2856 + * but otherwise ignored/worked around for interoperability. This is a 2857 + * HW flag so drivers can opt in according to their own control, e.g. in 2858 + * testing. 2859 + * 2855 2860 * @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays 2856 2861 */ 2857 2862 enum ieee80211_hw_flags { ··· 2917 2912 IEEE80211_HW_DISALLOW_PUNCTURING, 2918 2913 IEEE80211_HW_DISALLOW_PUNCTURING_5GHZ, 2919 2914 IEEE80211_HW_HANDLES_QUIET_CSA, 2915 + IEEE80211_HW_STRICT, 2920 2916 2921 2917 /* keep last, obviously */ 2922 2918 NUM_IEEE80211_HW_FLAGS
+42 -2
net/mac80211/debugfs.c
··· 492 492 FLAG(DISALLOW_PUNCTURING), 493 493 FLAG(DISALLOW_PUNCTURING_5GHZ), 494 494 FLAG(HANDLES_QUIET_CSA), 495 + FLAG(STRICT), 495 496 #undef FLAG 496 497 }; 497 498 ··· 524 523 kfree(buf); 525 524 return rv; 526 525 } 526 + 527 + static ssize_t hwflags_write(struct file *file, const char __user *user_buf, 528 + size_t count, loff_t *ppos) 529 + { 530 + struct ieee80211_local *local = file->private_data; 531 + char buf[100]; 532 + int val; 533 + 534 + if (count >= sizeof(buf)) 535 + return -EINVAL; 536 + 537 + if (copy_from_user(buf, user_buf, count)) 538 + return -EFAULT; 539 + 540 + if (count && buf[count - 1] == '\n') 541 + buf[count - 1] = '\0'; 542 + else 543 + buf[count] = '\0'; 544 + 545 + if (sscanf(buf, "strict=%d", &val) == 1) { 546 + switch (val) { 547 + case 0: 548 + ieee80211_hw_set(&local->hw, STRICT); 549 + return count; 550 + case 1: 551 + __clear_bit(IEEE80211_HW_STRICT, local->hw.flags); 552 + return count; 553 + default: 554 + return -EINVAL; 555 + } 556 + } 557 + 558 + return -EINVAL; 559 + } 560 + 561 + static const struct file_operations hwflags_ops = { 562 + .open = simple_open, 563 + .read = hwflags_read, 564 + .write = hwflags_write, 565 + }; 527 566 528 567 static ssize_t misc_read(struct file *file, char __user *user_buf, 529 568 size_t count, loff_t *ppos) ··· 615 574 return simple_read_from_buffer(user_buf, count, ppos, buf, res); 616 575 } 617 576 618 - DEBUGFS_READONLY_FILE_OPS(hwflags); 619 577 DEBUGFS_READONLY_FILE_OPS(queues); 620 578 DEBUGFS_READONLY_FILE_OPS(misc); 621 579 ··· 691 651 #ifdef CONFIG_PM 692 652 DEBUGFS_ADD_MODE(reset, 0200); 693 653 #endif 694 - DEBUGFS_ADD(hwflags); 654 + DEBUGFS_ADD_MODE(hwflags, 0600); 695 655 DEBUGFS_ADD(user_power); 696 656 DEBUGFS_ADD(power); 697 657 DEBUGFS_ADD(hw_conf);
+31 -14
net/mac80211/mlme.c
··· 168 168 bool no_vht = false; 169 169 u32 ht_cfreq; 170 170 171 + if (ieee80211_hw_check(&sdata->local->hw, STRICT)) 172 + ignore_ht_channel_mismatch = false; 173 + 171 174 *chandef = (struct cfg80211_chan_def) { 172 175 .chan = channel, 173 176 .width = NL80211_CHAN_WIDTH_20_NOHT, ··· 391 388 * zeroes, which is nonsense, and completely inconsistent with itself 392 389 * (it doesn't have 8 streams). Accept the settings in this case anyway. 393 390 */ 394 - if (!ap_min_req_set) 391 + if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set) 395 392 return true; 396 393 397 394 /* make sure the AP is consistent with itself ··· 451 448 * zeroes, which is nonsense, and completely inconsistent with itself 452 449 * (it doesn't have 8 streams). Accept the settings in this case anyway. 453 450 */ 454 - if (!ap_min_req_set) 451 + if (!ieee80211_hw_check(&sdata->local->hw, STRICT) && !ap_min_req_set) 455 452 return true; 456 453 457 454 /* Need to go over for 80MHz, 160MHz and for 80+80 */ ··· 1316 1313 * Some APs apparently get confused if our capabilities are better 1317 1314 * than theirs, so restrict what we advertise in the assoc request. 1318 1315 */ 1319 - if (!(ap_vht_cap->vht_cap_info & 1320 - cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE))) 1321 - cap &= ~(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | 1322 - IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE); 1323 - else if (!(ap_vht_cap->vht_cap_info & 1324 - cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE))) 1325 - cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; 1316 + if (!ieee80211_hw_check(&local->hw, STRICT)) { 1317 + if (!(ap_vht_cap->vht_cap_info & 1318 + cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE))) 1319 + cap &= ~(IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | 1320 + IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE); 1321 + else if (!(ap_vht_cap->vht_cap_info & 1322 + cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE))) 1323 + cap &= ~IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; 1324 + } 1326 1325 1327 1326 /* 1328 1327 * If some other vif is using the MU-MIMO capability we cannot associate ··· 1366 1361 return mu_mimo_owner; 1367 1362 } 1368 1363 1369 - static void ieee80211_assoc_add_rates(struct sk_buff *skb, 1364 + static void ieee80211_assoc_add_rates(struct ieee80211_local *local, 1365 + struct sk_buff *skb, 1370 1366 enum nl80211_chan_width width, 1371 1367 struct ieee80211_supported_band *sband, 1372 1368 struct ieee80211_mgd_assoc_data *assoc_data) 1373 1369 { 1374 1370 u32 rates; 1375 1371 1376 - if (assoc_data->supp_rates_len) { 1372 + if (assoc_data->supp_rates_len && 1373 + !ieee80211_hw_check(&local->hw, STRICT)) { 1377 1374 /* 1378 1375 * Get all rates supported by the device and the AP as 1379 1376 * some APs don't like getting a superset of their rates ··· 1591 1584 *capab |= WLAN_CAPABILITY_SPECTRUM_MGMT; 1592 1585 1593 1586 if (sband->band != NL80211_BAND_S1GHZ) 1594 - ieee80211_assoc_add_rates(skb, width, sband, assoc_data); 1587 + ieee80211_assoc_add_rates(local, skb, width, sband, assoc_data); 1595 1588 1596 1589 if (*capab & WLAN_CAPABILITY_SPECTRUM_MGMT || 1597 1590 *capab & WLAN_CAPABILITY_RADIO_MEASURE) { ··· 2058 2051 * for some reason check it and want it to be set, set the bit for all 2059 2052 * pre-EHT connections as we used to do. 2060 2053 */ 2061 - if (link->u.mgd.conn.mode < IEEE80211_CONN_MODE_EHT) 2054 + if (link->u.mgd.conn.mode < IEEE80211_CONN_MODE_EHT && 2055 + !ieee80211_hw_check(&local->hw, STRICT)) 2062 2056 capab |= WLAN_CAPABILITY_ESS; 2063 2057 2064 2058 /* add the elements for the assoc (main) link */ ··· 5037 5029 * 2G/3G/4G wifi routers, reported models include the "Onda PN51T", 5038 5030 * "Vodafone PocketWiFi 2", "ZTE MF60" and a similar T-Mobile device. 5039 5031 */ 5040 - if (!is_6ghz && 5032 + if (!ieee80211_hw_check(&local->hw, STRICT) && !is_6ghz && 5041 5033 ((assoc_data->wmm && !elems->wmm_param) || 5042 5034 (link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_HT && 5043 5035 (!elems->ht_cap_elem || !elems->ht_operation)) || ··· 5170 5162 ies->data, ies->len); 5171 5163 if (elem && elem->datalen >= sizeof(*bss_vht_cap)) 5172 5164 bss_vht_cap = (const void *)elem->data; 5165 + } 5166 + 5167 + if (ieee80211_hw_check(&local->hw, STRICT) && 5168 + (!bss_vht_cap || memcmp(bss_vht_cap, elems->vht_cap_elem, 5169 + sizeof(*bss_vht_cap)))) { 5170 + rcu_read_unlock(); 5171 + ret = false; 5172 + link_info(link, "VHT capabilities mismatch\n"); 5173 + goto out; 5173 5174 } 5174 5175 5175 5176 ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,