Commit 52e04b4c authored by Sumit Garg's avatar Sumit Garg Committed by Johannes Berg
Browse files

mac80211: fix race in ieee80211_register_hw()



A race condition leading to a kernel crash is observed during invocation
of ieee80211_register_hw() on a dragonboard410c device having wcn36xx
driver built as a loadable module along with a wifi manager in user-space
waiting for a wifi device (wlanX) to be active.

Sequence diagram for a particular kernel crash scenario:

    user-space  ieee80211_register_hw()  ieee80211_tasklet_handler()
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       |                    |                 |
       |<---phy0----wiphy_register()          |
       |-----iwd if_add---->|                 |
       |                    |<---IRQ----(RX packet)
       |              Kernel crash            |
       |              due to unallocated      |
       |              workqueue.              |
       |                    |                 |
       |       alloc_ordered_workqueue()      |
       |                    |                 |
       |              Misc wiphy init.        |
       |                    |                 |
       |            ieee80211_if_add()        |
       |                    |                 |

As evident from above sequence diagram, this race condition isn't specific
to a particular wifi driver but rather the initialization sequence in
ieee80211_register_hw() needs to be fixed. So re-order the initialization
sequence and the updated sequence diagram would look like:

    user-space  ieee80211_register_hw()  ieee80211_tasklet_handler()
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       |                    |                 |
       |       alloc_ordered_workqueue()      |
       |                    |                 |
       |              Misc wiphy init.        |
       |                    |                 |
       |<---phy0----wiphy_register()          |
       |-----iwd if_add---->|                 |
       |                    |<---IRQ----(RX packet)
       |                    |                 |
       |            ieee80211_if_add()        |
       |                    |                 |

Cc: stable@vger.kernel.org
Signed-off-by: default avatarSumit Garg <sumit.garg@linaro.org>
Link: https://lore.kernel.org/r/1586254255-28713-1-git-send-email-sumit.garg@linaro.org


[Johannes: fix rtnl imbalances]
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent a710d214
Loading
Loading
Loading
Loading
+13 −11
Original line number Diff line number Diff line
@@ -1069,7 +1069,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
		local->hw.wiphy->signal_type = CFG80211_SIGNAL_TYPE_UNSPEC;
		if (hw->max_signal <= 0) {
			result = -EINVAL;
			goto fail_wiphy_register;
			goto fail_workqueue;
		}
	}

@@ -1135,7 +1135,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)

	result = ieee80211_init_cipher_suites(local);
	if (result < 0)
		goto fail_wiphy_register;
		goto fail_workqueue;

	if (!local->ops->remain_on_channel)
		local->hw.wiphy->max_remain_on_channel_duration = 5000;
@@ -1161,10 +1161,6 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)

	local->hw.wiphy->max_num_csa_counters = IEEE80211_MAX_CSA_COUNTERS_NUM;

	result = wiphy_register(local->hw.wiphy);
	if (result < 0)
		goto fail_wiphy_register;

	/*
	 * We use the number of queues for feature tests (QoS, HT) internally
	 * so restrict them appropriately.
@@ -1217,9 +1213,9 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
		goto fail_flows;

	rtnl_lock();

	result = ieee80211_init_rate_ctrl_alg(local,
					      hw->rate_control_algorithm);
	rtnl_unlock();
	if (result < 0) {
		wiphy_debug(local->hw.wiphy,
			    "Failed to initialize rate control algorithm\n");
@@ -1273,6 +1269,12 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
		local->sband_allocated |= BIT(band);
	}

	result = wiphy_register(local->hw.wiphy);
	if (result < 0)
		goto fail_wiphy_register;

	rtnl_lock();

	/* add one default STA interface if supported */
	if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_STATION) &&
	    !ieee80211_hw_check(hw, NO_AUTO_VIF)) {
@@ -1312,17 +1314,17 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
#if defined(CONFIG_INET) || defined(CONFIG_IPV6)
 fail_ifa:
#endif
	wiphy_unregister(local->hw.wiphy);
 fail_wiphy_register:
	rtnl_lock();
	rate_control_deinitialize(local);
	ieee80211_remove_interfaces(local);
 fail_rate:
	rtnl_unlock();
 fail_rate:
 fail_flows:
	ieee80211_led_exit(local);
	destroy_workqueue(local->workqueue);
 fail_workqueue:
	wiphy_unregister(local->hw.wiphy);
 fail_wiphy_register:
	if (local->wiphy_ciphers_allocated)
		kfree(local->hw.wiphy->cipher_suites);
	kfree(local->int_scan_req);
@@ -1372,8 +1374,8 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)
	skb_queue_purge(&local->skb_queue_unreliable);
	skb_queue_purge(&local->skb_queue_tdls_chsw);

	destroy_workqueue(local->workqueue);
	wiphy_unregister(local->hw.wiphy);
	destroy_workqueue(local->workqueue);
	ieee80211_led_exit(local);
	kfree(local->int_scan_req);
}