]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: mac80211: use two-phase skb reclamation in ieee80211_do_stop()
authorDmitry Antipov <dmantipov@yandex.ru>
Fri, 6 Sep 2024 12:31:51 +0000 (15:31 +0300)
committerJohannes Berg <johannes.berg@intel.com>
Mon, 9 Sep 2024 09:45:06 +0000 (11:45 +0200)
Since '__dev_queue_xmit()' should be called with interrupts enabled,
the following backtrace:

ieee80211_do_stop()
 ...
 spin_lock_irqsave(&local->queue_stop_reason_lock, flags)
 ...
 ieee80211_free_txskb()
  ieee80211_report_used_skb()
   ieee80211_report_ack_skb()
    cfg80211_mgmt_tx_status_ext()
     nl80211_frame_tx_status()
      genlmsg_multicast_netns()
       genlmsg_multicast_netns_filtered()
        nlmsg_multicast_filtered()
 netlink_broadcast_filtered()
  do_one_broadcast()
   netlink_broadcast_deliver()
    __netlink_sendskb()
     netlink_deliver_tap()
      __netlink_deliver_tap_skb()
       dev_queue_xmit()
        __dev_queue_xmit() ; with IRQS disabled
 ...
 spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags)

issues the warning (as reported by syzbot reproducer):

WARNING: CPU: 2 PID: 5128 at kernel/softirq.c:362 __local_bh_enable_ip+0xc3/0x120

Fix this by implementing a two-phase skb reclamation in
'ieee80211_do_stop()', where actual work is performed
outside of a section with interrupts disabled.

Fixes: 5061b0c2b906 ("mac80211: cooperate more with network namespaces")
Reported-by: syzbot+1a3986bbd3169c307819@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=1a3986bbd3169c307819
Signed-off-by: Dmitry Antipov <dmantipov@yandex.ru>
Link: https://patch.msgid.link/20240906123151.351647-1-dmantipov@yandex.ru
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/mac80211/iface.c

index e074de893ed6c1c77fe84ee22f5bb5bab551821e..6ef0990d3d296a3163f592695939fd10d61d4b8c 100644 (file)
@@ -462,6 +462,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
 {
        struct ieee80211_local *local = sdata->local;
        unsigned long flags;
+       struct sk_buff_head freeq;
        struct sk_buff *skb, *tmp;
        u32 hw_reconf_flags = 0;
        int i, flushed;
@@ -637,18 +638,32 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
                skb_queue_purge(&sdata->status_queue);
        }
 
+       /*
+        * Since ieee80211_free_txskb() may issue __dev_queue_xmit()
+        * which should be called with interrupts enabled, reclamation
+        * is done in two phases:
+        */
+       __skb_queue_head_init(&freeq);
+
+       /* unlink from local queues... */
        spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
        for (i = 0; i < IEEE80211_MAX_QUEUES; i++) {
                skb_queue_walk_safe(&local->pending[i], skb, tmp) {
                        struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
                        if (info->control.vif == &sdata->vif) {
                                __skb_unlink(skb, &local->pending[i]);
-                               ieee80211_free_txskb(&local->hw, skb);
+                               __skb_queue_tail(&freeq, skb);
                        }
                }
        }
        spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
 
+       /* ... and perform actual reclamation with interrupts enabled. */
+       skb_queue_walk_safe(&freeq, skb, tmp) {
+               __skb_unlink(skb, &freeq);
+               ieee80211_free_txskb(&local->hw, skb);
+       }
+
        if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
                ieee80211_txq_remove_vlan(local, sdata);