From nobody Thu Oct 2 07:48:58 2025 Received: from mail.ispras.ru (mail.ispras.ru [83.149.199.84]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0590027702A; Fri, 19 Sep 2025 21:09:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=83.149.199.84 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758316175; cv=none; b=ZMY/zB8fC/p+NiFI9o1sAPoXwyJb+eJANX5eqfLCDgdTOHXAXbQmz8rZC1usbuAglRG7v7y0W9+HOhNtrNBXLwtP869gQjEhMu44Yvjpkgv+PKDMxkRj4zZAM0/RPdgzYTMLz5a5ZeI64vYxfvl9tU/C+zUQNM6OIqzgEgfwCiU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758316175; c=relaxed/simple; bh=AzWR9yINVsv3/jho544foII4PxWX83Ya9OPk097+Zbc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MbgGE2MTT/cPZryGhLtWPn16x7SDME5cK29tHuxC5J//dzmjaeCdFMMW3GVdtlr09G3Ob/arCQF4z+zWmwOLo+3wEiQsXPTu2A2tZuKmdyHXOeQZW27+QFoiMT9gA4KEA85j8jle78OCoOIjLN6P9ExQCfkef1oDPdM3fFufDBw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=ispras.ru; spf=pass smtp.mailfrom=ispras.ru; dkim=pass (1024-bit key) header.d=ispras.ru header.i=@ispras.ru header.b=tKfCwHlP; arc=none smtp.client-ip=83.149.199.84 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=ispras.ru Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ispras.ru Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ispras.ru header.i=@ispras.ru header.b="tKfCwHlP" Received: from debian.intra.ispras.ru (unknown [10.10.165.9]) by mail.ispras.ru (Postfix) with ESMTPSA id E3C394076723; Fri, 19 Sep 2025 21:09:22 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 mail.ispras.ru E3C394076723 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ispras.ru; s=default; t=1758316163; bh=+4qxICeTyd+XYx4CHFefq+jkZF3Hp0q0bI8/zHksiuM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tKfCwHlPSRiua6MQSmB168M4VVwkNYPrrNlGFei+Wb8761LMLDXoHrzbeayHyo3+/ pf0/a4Q8qQa3Q7x4FCnSF3dxxuN+qQxLfPqaTLNHBxBZ+exbp8pQJV+WJ2kDwXewBB FwJs3BaUwp2h7Hy+cfaUBZXy/2HIe0camhl1WYkU= From: Fedor Pchelkin To: Ping-Ke Shih , Zong-Zhe Yang Cc: Fedor Pchelkin , Bitterblue Smith , Po-Hao Huang , linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, lvc-project@linuxtesting.org, stable@vger.kernel.org Subject: [PATCH rtw-next v5 1/4] wifi: rtw89: fix use-after-free in rtw89_core_tx_kick_off_and_wait() Date: Sat, 20 Sep 2025 00:08:47 +0300 Message-ID: <20250919210852.823912-2-pchelkin@ispras.ru> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20250919210852.823912-1-pchelkin@ispras.ru> References: <20250919210852.823912-1-pchelkin@ispras.ru> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" There is a bug observed when rtw89_core_tx_kick_off_and_wait() tries to access already freed skb_data: BUG: KFENCE: use-after-free write in rtw89_core_tx_kick_off_and_wait drive= rs/net/wireless/realtek/rtw89/core.c:1110 CPU: 6 UID: 0 PID: 41377 Comm: kworker/u64:24 Not tainted 6.17.0-rc1+ #1 = PREEMPT(lazy) Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS edk2-20250523-= 14.fc42 05/23/2025 Workqueue: events_unbound cfg80211_wiphy_work [cfg80211] Use-after-free write at 0x0000000020309d9d (in kfence-#251): rtw89_core_tx_kick_off_and_wait drivers/net/wireless/realtek/rtw89/core.c:= 1110 rtw89_core_scan_complete drivers/net/wireless/realtek/rtw89/core.c:5338 rtw89_hw_scan_complete_cb drivers/net/wireless/realtek/rtw89/fw.c:7979 rtw89_chanctx_proceed_cb drivers/net/wireless/realtek/rtw89/chan.c:3165 rtw89_chanctx_proceed drivers/net/wireless/realtek/rtw89/chan.h:141 rtw89_hw_scan_complete drivers/net/wireless/realtek/rtw89/fw.c:8012 rtw89_mac_c2h_scanofld_rsp drivers/net/wireless/realtek/rtw89/mac.c:5059 rtw89_fw_c2h_work drivers/net/wireless/realtek/rtw89/fw.c:6758 process_one_work kernel/workqueue.c:3241 worker_thread kernel/workqueue.c:3400 kthread kernel/kthread.c:463 ret_from_fork arch/x86/kernel/process.c:154 ret_from_fork_asm arch/x86/entry/entry_64.S:258 kfence-#251: 0x0000000056e2393d-0x000000009943cb62, size=3D232, cache=3Dsk= buff_head_cache allocated by task 41377 on cpu 6 at 77869.159548s (0.009551s ago): __alloc_skb net/core/skbuff.c:659 __netdev_alloc_skb net/core/skbuff.c:734 ieee80211_nullfunc_get net/mac80211/tx.c:5844 rtw89_core_send_nullfunc drivers/net/wireless/realtek/rtw89/core.c:3431 rtw89_core_scan_complete drivers/net/wireless/realtek/rtw89/core.c:5338 rtw89_hw_scan_complete_cb drivers/net/wireless/realtek/rtw89/fw.c:7979 rtw89_chanctx_proceed_cb drivers/net/wireless/realtek/rtw89/chan.c:3165 rtw89_chanctx_proceed drivers/net/wireless/realtek/rtw89/chan.c:3194 rtw89_hw_scan_complete drivers/net/wireless/realtek/rtw89/fw.c:8012 rtw89_mac_c2h_scanofld_rsp drivers/net/wireless/realtek/rtw89/mac.c:5059 rtw89_fw_c2h_work drivers/net/wireless/realtek/rtw89/fw.c:6758 process_one_work kernel/workqueue.c:3241 worker_thread kernel/workqueue.c:3400 kthread kernel/kthread.c:463 ret_from_fork arch/x86/kernel/process.c:154 ret_from_fork_asm arch/x86/entry/entry_64.S:258 freed by task 1045 on cpu 9 at 77869.168393s (0.001557s ago): ieee80211_tx_status_skb net/mac80211/status.c:1117 rtw89_pci_release_txwd_skb drivers/net/wireless/realtek/rtw89/pci.c:564 rtw89_pci_release_tx_skbs.isra.0 drivers/net/wireless/realtek/rtw89/pci.c:= 651 rtw89_pci_release_tx drivers/net/wireless/realtek/rtw89/pci.c:676 rtw89_pci_napi_poll drivers/net/wireless/realtek/rtw89/pci.c:4238 __napi_poll net/core/dev.c:7495 net_rx_action net/core/dev.c:7557 net/core/dev.c:7684 handle_softirqs kernel/softirq.c:580 do_softirq.part.0 kernel/softirq.c:480 __local_bh_enable_ip kernel/softirq.c:407 rtw89_pci_interrupt_threadfn drivers/net/wireless/realtek/rtw89/pci.c:927 irq_thread_fn kernel/irq/manage.c:1133 irq_thread kernel/irq/manage.c:1257 kthread kernel/kthread.c:463 ret_from_fork arch/x86/kernel/process.c:154 ret_from_fork_asm arch/x86/entry/entry_64.S:258 It is a consequence of a race between the waiting and the signaling side of the completion: Waiting thread Completing thread rtw89_core_tx_kick_off_and_wait() rcu_assign_pointer(skb_data->wait, wait) /* start waiting */ wait_for_completion_timeout() rtw89_pci_tx_status() rtw89_core_tx_wait_comple= te() rcu_read_lock() /* signals completion a= nd * proceeds further */ complete(&wait->complet= ion) rcu_read_unlock() ... /* frees skb_data */ ieee80211_tx_status_ni() /* returns (exit status doesn't matter) */ wait_for_completion_timeout() ... /* accesses the already freed skb_data */ rcu_assign_pointer(skb_data->wait, NULL) The completing side might proceed and free the underlying skb even before the waiting side is fully awoken and run to execution. Actually the race happens regardless of wait_for_completion_timeout() exit status, e.g. the waiting side may hit a timeout and the concurrent completing side is still able to free the skb. Skbs which are sent by rtw89_core_tx_kick_off_and_wait() are owned by the driver. They don't come from core ieee80211 stack so no need to pass them to ieee80211_tx_status_ni() on completing side. Introduce a work function which will act as a garbage collector for rtw89_tx_wait_info objects and the associated skbs. Thus no potentially heavy locks are required on the completing side. Found by Linux Verification Center (linuxtesting.org). Fixes: 1ae5ca615285 ("wifi: rtw89: add function to wait for completion of T= X skbs") Cc: stable@vger.kernel.org Suggested-by: Zong-Zhe Yang Signed-off-by: Fedor Pchelkin Acked-by: Ping-Ke Shih --- v5: - use completion_done() and delayed work (Ping-Ke) v4: - fill wait's fields before publishing (Zong-Zhe) - leave dev_kfree_skb_any + kfree_rcu for tx_wait release (Zong-Zhe) v3: - decrease waiting timeout in rtw89_tx_wait_work() (Zong-Zhe) - clear tx_waits list from rtw89_hci_reset(), too (Zong-Zhe) - keep RCU updating for skb_data->wait (Zong-Zhe) - keep the old order of calls in rtw89_pci_tx_status() (Zong-Zhe) - drop wait->finished as complete_all() would make the completion be done permanently v2: - use a work function to manage release of tx_waits and associated skbs= (Zong-Zhe) drivers/net/wireless/realtek/rtw89/core.c | 30 +++++++++++++++---- drivers/net/wireless/realtek/rtw89/core.h | 35 +++++++++++++++++++++-- drivers/net/wireless/realtek/rtw89/pci.c | 3 +- drivers/net/wireless/realtek/rtw89/ser.c | 2 ++ 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wirele= ss/realtek/rtw89/core.c index fe059c4a6b55..ec467ae0e9e6 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -1135,6 +1135,14 @@ rtw89_core_tx_update_desc_info(struct rtw89_dev *rtw= dev, } } =20 +static void rtw89_tx_wait_work(struct wiphy *wiphy, struct wiphy_work *wor= k) +{ + struct rtw89_dev *rtwdev =3D container_of(work, struct rtw89_dev, + tx_wait_work.work); + + rtw89_tx_wait_list_clear(rtwdev); +} + void rtw89_core_tx_kick_off(struct rtw89_dev *rtwdev, u8 qsel) { u8 ch_dma; @@ -1152,6 +1160,8 @@ int rtw89_core_tx_kick_off_and_wait(struct rtw89_dev = *rtwdev, struct sk_buff *sk unsigned long time_left; int ret =3D 0; =20 + lockdep_assert_wiphy(rtwdev->hw->wiphy); + wait =3D kzalloc(sizeof(*wait), GFP_KERNEL); if (!wait) { rtw89_core_tx_kick_off(rtwdev, qsel); @@ -1159,18 +1169,23 @@ int rtw89_core_tx_kick_off_and_wait(struct rtw89_de= v *rtwdev, struct sk_buff *sk } =20 init_completion(&wait->completion); + wait->skb =3D skb; rcu_assign_pointer(skb_data->wait, wait); =20 rtw89_core_tx_kick_off(rtwdev, qsel); time_left =3D wait_for_completion_timeout(&wait->completion, msecs_to_jiffies(timeout)); - if (time_left =3D=3D 0) - ret =3D -ETIMEDOUT; - else if (!wait->tx_done) - ret =3D -EAGAIN; =20 - rcu_assign_pointer(skb_data->wait, NULL); - kfree_rcu(wait, rcu_head); + if (time_left =3D=3D 0) { + ret =3D -ETIMEDOUT; + list_add_tail(&wait->list, &rtwdev->tx_waits); + wiphy_delayed_work_queue(rtwdev->hw->wiphy, &rtwdev->tx_wait_work, + RTW89_TX_WAIT_WORK_TIMEOUT); + } else { + if (!wait->tx_done) + ret =3D -EAGAIN; + rtw89_tx_wait_release(wait); + } =20 return ret; } @@ -5548,6 +5563,7 @@ void rtw89_core_stop(struct rtw89_dev *rtwdev) wiphy_work_cancel(wiphy, &btc->dhcp_notify_work); wiphy_work_cancel(wiphy, &btc->icmp_notify_work); cancel_delayed_work_sync(&rtwdev->txq_reinvoke_work); + wiphy_delayed_work_cancel(wiphy, &rtwdev->tx_wait_work); wiphy_delayed_work_cancel(wiphy, &rtwdev->track_work); wiphy_delayed_work_cancel(wiphy, &rtwdev->track_ps_work); wiphy_delayed_work_cancel(wiphy, &rtwdev->chanctx_work); @@ -5773,6 +5789,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev) INIT_LIST_HEAD(&rtwdev->scan_info.pkt_list[band]); } INIT_LIST_HEAD(&rtwdev->scan_info.chan_list); + INIT_LIST_HEAD(&rtwdev->tx_waits); INIT_WORK(&rtwdev->ba_work, rtw89_core_ba_work); INIT_WORK(&rtwdev->txq_work, rtw89_core_txq_work); INIT_DELAYED_WORK(&rtwdev->txq_reinvoke_work, rtw89_core_txq_reinvoke_wor= k); @@ -5784,6 +5801,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev) wiphy_delayed_work_init(&rtwdev->coex_rfk_chk_work, rtw89_coex_rfk_chk_wo= rk); wiphy_delayed_work_init(&rtwdev->cfo_track_work, rtw89_phy_cfo_track_work= ); wiphy_delayed_work_init(&rtwdev->mcc_prepare_done_work, rtw89_mcc_prepare= _done_work); + wiphy_delayed_work_init(&rtwdev->tx_wait_work, rtw89_tx_wait_work); INIT_DELAYED_WORK(&rtwdev->forbid_ba_work, rtw89_forbid_ba_work); wiphy_delayed_work_init(&rtwdev->antdiv_work, rtw89_phy_antdiv_work); rtwdev->txq_wq =3D alloc_workqueue("rtw89_tx_wq", WQ_UNBOUND | WQ_HIGHPRI= , 0); diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wirele= ss/realtek/rtw89/core.h index 3d4ad2ffb75c..d15fa70eb4dc 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -3507,9 +3507,12 @@ struct rtw89_phy_rate_pattern { bool enable; }; =20 +#define RTW89_TX_WAIT_WORK_TIMEOUT msecs_to_jiffies(500) struct rtw89_tx_wait_info { struct rcu_head rcu_head; + struct list_head list; struct completion completion; + struct sk_buff *skb; bool tx_done; }; =20 @@ -6000,6 +6003,9 @@ struct rtw89_dev { /* used to protect rpwm */ spinlock_t rpwm_lock; =20 + struct list_head tx_waits; + struct wiphy_delayed_work tx_wait_work; + struct rtw89_cam_info cam_info; =20 struct sk_buff_head c2h_queue; @@ -6256,6 +6262,26 @@ rtw89_assoc_link_rcu_dereference(struct rtw89_dev *r= twdev, u8 macid) list_first_entry_or_null(&p->dlink_pool, typeof(*p->links_inst), dlink_sc= hd); \ }) =20 +static inline void rtw89_tx_wait_release(struct rtw89_tx_wait_info *wait) +{ + dev_kfree_skb_any(wait->skb); + kfree_rcu(wait, rcu_head); +} + +static inline void rtw89_tx_wait_list_clear(struct rtw89_dev *rtwdev) +{ + struct rtw89_tx_wait_info *wait, *tmp; + + lockdep_assert_wiphy(rtwdev->hw->wiphy); + + list_for_each_entry_safe(wait, tmp, &rtwdev->tx_waits, list) { + if (!completion_done(&wait->completion)) + continue; + list_del(&wait->list); + rtw89_tx_wait_release(wait); + } +} + static inline int rtw89_hci_tx_write(struct rtw89_dev *rtwdev, struct rtw89_core_tx_request *tx_req) { @@ -6265,6 +6291,7 @@ static inline int rtw89_hci_tx_write(struct rtw89_dev= *rtwdev, static inline void rtw89_hci_reset(struct rtw89_dev *rtwdev) { rtwdev->hci.ops->reset(rtwdev); + rtw89_tx_wait_list_clear(rtwdev); } =20 static inline int rtw89_hci_start(struct rtw89_dev *rtwdev) @@ -7345,11 +7372,12 @@ static inline struct sk_buff *rtw89_alloc_skb_for_r= x(struct rtw89_dev *rtwdev, return dev_alloc_skb(length); } =20 -static inline void rtw89_core_tx_wait_complete(struct rtw89_dev *rtwdev, +static inline bool rtw89_core_tx_wait_complete(struct rtw89_dev *rtwdev, struct rtw89_tx_skb_data *skb_data, bool tx_done) { struct rtw89_tx_wait_info *wait; + bool ret =3D false; =20 rcu_read_lock(); =20 @@ -7357,11 +7385,14 @@ static inline void rtw89_core_tx_wait_complete(stru= ct rtw89_dev *rtwdev, if (!wait) goto out; =20 + ret =3D true; wait->tx_done =3D tx_done; - complete(&wait->completion); + /* Don't access skb anymore after completion */ + complete_all(&wait->completion); =20 out: rcu_read_unlock(); + return ret; } =20 static inline bool rtw89_is_mlo_1_1(struct rtw89_dev *rtwdev) diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireles= s/realtek/rtw89/pci.c index c8286eb84276..8dd91d867ea6 100644 --- a/drivers/net/wireless/realtek/rtw89/pci.c +++ b/drivers/net/wireless/realtek/rtw89/pci.c @@ -464,7 +464,8 @@ static void rtw89_pci_tx_status(struct rtw89_dev *rtwde= v, struct rtw89_tx_skb_data *skb_data =3D RTW89_TX_SKB_CB(skb); struct ieee80211_tx_info *info; =20 - rtw89_core_tx_wait_complete(rtwdev, skb_data, tx_status =3D=3D RTW89_TX_D= ONE); + if (rtw89_core_tx_wait_complete(rtwdev, skb_data, tx_status =3D=3D RTW89_= TX_DONE)) + return; =20 info =3D IEEE80211_SKB_CB(skb); ieee80211_tx_info_clear_status(info); diff --git a/drivers/net/wireless/realtek/rtw89/ser.c b/drivers/net/wireles= s/realtek/rtw89/ser.c index bb39fdbcba0d..fe7beff8c424 100644 --- a/drivers/net/wireless/realtek/rtw89/ser.c +++ b/drivers/net/wireless/realtek/rtw89/ser.c @@ -502,7 +502,9 @@ static void ser_reset_trx_st_hdl(struct rtw89_ser *ser,= u8 evt) } =20 drv_stop_rx(ser); + wiphy_lock(wiphy); drv_trx_reset(ser); + wiphy_unlock(wiphy); =20 /* wait m3 */ hal_send_m2_event(ser); --=20 2.51.0