ignore wide channel configs that do not appear in the 802.11ac spec
authorstsp <stsp@openbsd.org>
Sat, 21 Oct 2023 06:47:23 +0000 (06:47 +0000)
committerstsp <stsp@openbsd.org>
Sat, 21 Oct 2023 06:47:23 +0000 (06:47 +0000)
Wide channel configurations not listed in operating class tables of
the 802.11ac spec can trigger regulatory assertion failures in iwm(4)
firmware, and potentially other device firmware.
Ignore non-standard channels configs such that we downgrade to 40MHz
or even 20MHz to make such APs usable, albeit at lower speed.

Found by dlg@ with a mikrotik AP advertising channel configs that do
not appear as such in the spec:
  80 MHz: |104|108|112|116|
  40 MHz: |primary: 112|secondary above: 116|
Either of these triggered iwm0: 0x000014FD | ADVANCED_SYSASSERT

Fix tested by myself on iwx(4) AX200 and dlg@ on iwm(4) 7260.
Johannes Berg helped with deciphering the error code, thanks!

sys/net80211/ieee80211_node.c

index c1a06bc..48fe04c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_node.c,v 1.196 2023/04/11 00:45:09 jsg Exp $        */
+/*     $OpenBSD: ieee80211_node.c,v 1.197 2023/10/21 06:47:23 stsp Exp $       */
 /*     $NetBSD: ieee80211_node.c,v 1.14 2004/05/09 09:18:47 dyoung Exp $       */
 
 /*-
@@ -2392,6 +2392,83 @@ ieee80211_clear_htcaps(struct ieee80211_node *ni)
 }
 #endif
 
+int
+ieee80211_40mhz_valid_secondary_above(uint8_t primary_chan)
+{
+       static const uint8_t valid_secondary_chan[] = {
+               2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+               40, 48, 56, 64, 104, 112, 120, 128, 136, 144, 153, 161
+       };
+       uint8_t secondary_chan;
+       int i;
+
+       if (primary_chan >= 1 && primary_chan <= 13)
+               secondary_chan = primary_chan + 1;
+       else if (primary_chan >= 36 && primary_chan <= 157)
+               secondary_chan = primary_chan + 4;
+       else
+               return 0;
+
+       for (i = 0; i < nitems(valid_secondary_chan); i++) {
+               if (secondary_chan == valid_secondary_chan[i])
+                       return 1;
+       }
+
+       return 0;
+}
+
+int
+ieee80211_40mhz_valid_secondary_below(uint8_t primary_chan)
+{
+       static const uint8_t valid_secondary_chan[] = {
+               1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+               36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157
+       };
+       int8_t secondary_chan;
+       int i;
+
+       if (primary_chan >= 2 && primary_chan <= 14)
+               secondary_chan = primary_chan - 1;
+       else if (primary_chan >= 40 && primary_chan <= 161)
+               secondary_chan = primary_chan - 4;
+       else
+               return 0;
+
+       for (i = 0; i < nitems(valid_secondary_chan); i++) {
+               if (secondary_chan == valid_secondary_chan[i])
+                       return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * Only accept 40 MHz channel configurations that conform to
+ * regulatory operating classes as defined by the 802.11ac spec.
+ * Passing other configurations down to firmware can result in
+ * regulatory assertions being trigged, such as fatal firmware
+ * error 14FD in iwm(4).
+ *
+ * See 802.11ac 2013, page 380, Tables E-1 to E-5.
+ */
+int
+ieee80211_40mhz_center_freq_valid(uint8_t primary_chan, uint8_t htop0)
+{
+       uint8_t sco;
+
+       sco = ((htop0 & IEEE80211_HTOP0_SCO_MASK) >> IEEE80211_HTOP0_SCO_SHIFT);
+       switch (sco) {
+       case IEEE80211_HTOP0_SCO_SCN:
+               return 1;
+       case IEEE80211_HTOP0_SCO_SCA:
+               return ieee80211_40mhz_valid_secondary_above(primary_chan);
+       case IEEE80211_HTOP0_SCO_SCB:
+               return ieee80211_40mhz_valid_secondary_below(primary_chan);
+       }
+
+       return 0;
+}
+
 /*
  * Install received HT op information in the node's state block.
  */
@@ -2402,9 +2479,10 @@ ieee80211_setup_htop(struct ieee80211_node *ni, const uint8_t *data,
        if (len != 22)
                return 0;
 
-       ni->ni_primary_chan = data[0]; /* XXX corresponds to ni_chan */
-
+       ni->ni_primary_chan = data[0]; /* corresponds to ni_chan */
        ni->ni_htop0 = data[1];
+       if (!ieee80211_40mhz_center_freq_valid(data[0], data[1]))
+               ni->ni_htop0 &= ~IEEE80211_HTOP0_SCO_MASK;
        ni->ni_htop1 = (data[2] | (data[3] << 8));
        ni->ni_htop2 = (data[3] | (data[4] << 8));
 
@@ -2441,6 +2519,31 @@ ieee80211_setup_vhtcaps(struct ieee80211_node *ni, const uint8_t *data,
        ni->ni_flags |= IEEE80211_NODE_VHTCAP;
 }
 
+/*
+ * Only accept 80 MHz channel configurations that conform to
+ * regulatory operating classes as defined by the 802.11ac spec.
+ * Passing other configurations down to firmware can result in
+ * regulatory assertions being trigged, such as fatal firmware
+ * error 14FD in iwm(4).
+ *
+ * See 802.11ac 2013, page 380, Tables E-1 to E-5.
+ */
+int
+ieee80211_80mhz_center_freq_valid(const uint8_t chanidx)
+{
+       static const uint8_t valid_center_chanidx[] = {
+               42, 50, 58, 106, 112, 114, 138, 155
+       };
+       int i;
+
+       for (i = 0; i < nitems(valid_center_chanidx); i++) {
+               if (chanidx == valid_center_chanidx[i])
+                       return 1;
+       }
+
+       return 0;
+}
+
 /*
  * Install received VHT op information in the node's state block.
  */
@@ -2448,6 +2551,8 @@ int
 ieee80211_setup_vhtop(struct ieee80211_node *ni, const uint8_t *data,
     uint8_t len, int isprobe)
 {
+       uint8_t sco;
+       int have_40mhz;
 
        if (len != 5)
                return 0;
@@ -2458,9 +2563,26 @@ ieee80211_setup_vhtop(struct ieee80211_node *ni, const uint8_t *data,
            data[0] != IEEE80211_VHTOP0_CHAN_WIDTH_8080)
                return 0;
 
-       ni->ni_vht_chan_width = data[0];
-       ni->ni_vht_chan_center_freq_idx0 = data[1];
-       ni->ni_vht_chan_center_freq_idx1 = data[2];
+       sco = ((ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK) >>
+           IEEE80211_HTOP0_SCO_SHIFT);
+       have_40mhz = (sco == IEEE80211_HTOP0_SCO_SCA ||
+           sco == IEEE80211_HTOP0_SCO_SCB);
+
+       if (have_40mhz && ieee80211_80mhz_center_freq_valid(data[1])) {
+               ni->ni_vht_chan_width = data[0];
+               ni->ni_vht_chan_center_freq_idx0 = data[1];
+
+               /* Only used in non-consecutive 80-80 160MHz configs. */
+               if (data[2] && ieee80211_80mhz_center_freq_valid(data[2]))
+                       ni->ni_vht_chan_center_freq_idx1 = data[2];
+               else
+                       ni->ni_vht_chan_center_freq_idx1 = 0;
+       } else {
+               ni->ni_vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_HT;
+               ni->ni_vht_chan_center_freq_idx0 = 0;
+               ni->ni_vht_chan_center_freq_idx1 = 0;
+       }
+
        ni->ni_vht_basic_mcs = (data[3] | data[4] << 8);
        return 1;
 }