[PATCH ath-next 1/2] wifi: ath11k: guard copy engine access against a cleared srng
Julius Bairaktaris <[email protected]> Wed, 5 Aug 2026 21:21:28 +0200
| Newsgroups | org.kernel.vger.linux-wireless,org.infradead.lists.ath11k,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
ath11k_core_reconfigure_on_crash() zeroes the ring list with
ath11k_hal_srng_clear() and only rebuilds the copy engine rings later,
inside ath11k_core_qmi_firmware_ready() -> ath11k_ce_init_pipes().
ath11k_ce_send() is gated on ATH11K_FLAG_CRASH_FLUSH, but that flag is
cleared before ath11k_core_qmi_firmware_ready() is called - it has to be,
since the firmware bring-up itself sends WMI commands through the copy
engine. For the whole of that bring-up the gate is open while the ring is
still zeroed, so any WMI command issued in that window dereferences the
NULL u.src_ring.tp_addr in ath11k_hal_srng_access_begin():
Unable to handle kernel read from unreadable memory at virtual address 0
CPU: 0 UID: 101 PID: 1846 Comm: hostapd
pc : ath11k_hal_srng_access_begin+0xc/0x60 [ath11k]
lr : ath11k_ce_send+0x114/0x350 [ath11k]
Call trace:
ath11k_hal_srng_access_begin+0xc/0x60 [ath11k] (P)
ath11k_htc_send+0x188/0x3c8 [ath11k]
ath11k_wmi_cmd_send+0xc8/0x2f4 [ath11k]
ath11k_wmi_send_peer_create_cmd+0x78/0xe0 [ath11k]
ath11k_peer_create+0x138/0x500 [ath11k]
The caller matters: this is a command issued fresh during the recovery,
not a task that was already blocked in ath11k_wmi_cmd_send(). A blocked
one sleeps through the whole window and never re-enters ath11k_ce_send(),
which is why hammering beacon updates does not reproduce it and a station
associating during recovery does.
ath11k_ce_completed_send_next() has no gate at all, and
ath11k_core_reconfigure_on_crash() calls ath11k_ce_cleanup_pipes() before
anything else, so a crash arriving after a previous recovery already ran
ath11k_hal_srng_clear() reaps rings whose tp_addr is NULL. This is a
different entry into the same dereference from the one fixed by
commit e8d85672dd7e ("wifi: ath11k: fix NULL pointer dereference in
ath11k_hal_srng_access_begin"), which stops a successful bring-up from
being repeated; it does not close this window.
Test u.src_ring.tp_addr rather than srng->initialized in both places:
ath11k_hal_srng_setup() publishes ->initialized before ->tp_addr with no
barrier between them, so an ->initialized check can be true while the
pointer that is about to be dereferenced is still NULL.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.12-01460-QCAHKSWPL_SILICONZ-1
Fixes: 32be3ca4cf78 ("wifi: ath11k: HAL SRNG: don't deinitialize and re-initialize again")
Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <[email protected]>
---
drivers/net/wireless/ath/ath11k/ce.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/drivers/net/wireless/ath/ath11k/ce.c b/drivers/net/wireless/ath/ath11k/ce.c
--- a/drivers/net/wireless/ath/ath11k/ce.c
+++ b/drivers/net/wireless/ath/ath11k/ce.c
@@ -472,6 +472,17 @@ static struct sk_buff *ath11k_ce_completed_send_next(struct ath11k_ce_pipe *pipe
spin_lock_bh(&srng->lock);
+ /*
+ * ath11k_ce_cleanup_pipes() can reach this on a ring that
+ * ath11k_hal_srng_clear() has zeroed and that recovery has not
+ * rebuilt. Bail out as for an empty ring rather than dereference
+ * the NULL tp_addr in ath11k_hal_srng_access_begin().
+ */
+ if (unlikely(!srng->u.src_ring.tp_addr)) {
+ skb = ERR_PTR(-EIO);
+ goto err_unlock;
+ }
+
ath11k_hal_srng_access_begin(ab, srng);
desc = ath11k_hal_srng_src_reap_next(ab, srng);
@@ -750,6 +761,19 @@ int ath11k_ce_send(struct ath11k_base *ab, struct sk_buff *skb, u8 pipe_id,
srng = &ab->hal.srng_list[pipe->src_ring->hal_ring_id];
+ /*
+ * ATH11K_FLAG_CRASH_FLUSH is cleared before the copy engine rings
+ * are rebuilt, so the check above lets a send through while the
+ * ring is still zeroed. Test the pointer that would be
+ * dereferenced: ath11k_hal_srng_setup() publishes ->initialized
+ * before ->tp_addr without a barrier, so ->initialized would still
+ * race the rebuild.
+ */
+ if (unlikely(!srng->u.src_ring.tp_addr)) {
+ spin_unlock_bh(&ab->ce.ce_lock);
+ return -ESHUTDOWN;
+ }
+
spin_lock_bh(&srng->lock);
ath11k_hal_srng_access_begin(ab, srng);