| From: Johannes Berg <johannes.berg@intel.com> |
| Date: Fri, 30 Sep 2022 23:44:23 +0200 |
| Subject: [PATCH] wifi: cfg80211: fix BSS refcounting bugs |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| commit 0b7808818cb9df6680f98996b8e9a439fa7bcc2f upstream. |
| |
| There are multiple refcounting bugs related to multi-BSSID: |
| - In bss_ref_get(), if the BSS has a hidden_beacon_bss, then |
| the bss pointer is overwritten before checking for the |
| transmitted BSS, which is clearly wrong. Fix this by using |
| the bss_from_pub() macro. |
| |
| - In cfg80211_bss_update() we copy the transmitted_bss pointer |
| from tmp into new, but then if we release new, we'll unref |
| it erroneously. We already set the pointer and ref it, but |
| need to NULL it since it was copied from the tmp data. |
| |
| - In cfg80211_inform_single_bss_data(), if adding to the non- |
| transmitted list fails, we unlink the BSS and yet still we |
| return it, but this results in returning an entry without |
| a reference. We shouldn't return it anyway if it was broken |
| enough to not get added there. |
| |
| This fixes CVE-2022-42720. |
| |
| Reported-by: Sönke Huster <shuster@seemoo.tu-darmstadt.de> |
| Tested-by: Sönke Huster <shuster@seemoo.tu-darmstadt.de> |
| Fixes: a3584f56de1c ("cfg80211: Properly track transmitting and non-transmitting BSS") |
| Signed-off-by: Johannes Berg <johannes.berg@intel.com> |
| --- |
| |
| --- a/net/wireless/scan.c |
| +++ b/net/wireless/scan.c |
| @@ -143,18 +143,12 @@ static inline void bss_ref_get(struct cf |
| lockdep_assert_held(&rdev->bss_lock); |
| |
| bss->refcount++; |
| - if (bss->pub.hidden_beacon_bss) { |
| - bss = container_of(bss->pub.hidden_beacon_bss, |
| - struct cfg80211_internal_bss, |
| - pub); |
| - bss->refcount++; |
| - } |
| - if (bss->pub.transmitted_bss) { |
| - bss = container_of(bss->pub.transmitted_bss, |
| - struct cfg80211_internal_bss, |
| - pub); |
| - bss->refcount++; |
| - } |
| + |
| + if (bss->pub.hidden_beacon_bss) |
| + bss_from_pub(bss->pub.hidden_beacon_bss)->refcount++; |
| + |
| + if (bss->pub.transmitted_bss) |
| + bss_from_pub(bss->pub.transmitted_bss)->refcount++; |
| } |
| |
| static inline void bss_ref_put(struct cfg80211_registered_device *rdev, |
| @@ -1743,6 +1737,8 @@ cfg80211_bss_update(struct cfg80211_regi |
| new->refcount = 1; |
| INIT_LIST_HEAD(&new->hidden_list); |
| INIT_LIST_HEAD(&new->pub.nontrans_list); |
| + /* we'll set this later if it was non-NULL */ |
| + new->pub.transmitted_bss = NULL; |
| |
| if (rcu_access_pointer(tmp->pub.proberesp_ies)) { |
| hidden = rb_find_bss(rdev, tmp, BSS_CMP_HIDE_ZLEN); |
| @@ -1983,10 +1979,15 @@ cfg80211_inform_single_bss_data(struct w |
| spin_lock_bh(&rdev->bss_lock); |
| if (cfg80211_add_nontrans_list(non_tx_data->tx_bss, |
| &res->pub)) { |
| - if (__cfg80211_unlink_bss(rdev, res)) |
| + if (__cfg80211_unlink_bss(rdev, res)) { |
| rdev->bss_generation++; |
| + res = NULL; |
| + } |
| } |
| spin_unlock_bh(&rdev->bss_lock); |
| + |
| + if (!res) |
| + return NULL; |
| } |
| |
| trace_cfg80211_return_bss(&res->pub); |