Re: [PATCH 1/3] wifi: ath9k: avoid referencing uninit memory in ath9k_wmi_ctrl_rx

From: Fedor Pchelkin
Date: Mon Apr 24 2023 - 14:23:29 EST


On Wed, Mar 15, 2023 at 11:21:10PM +0300, Fedor Pchelkin wrote:
> For the reasons described in commit b383e8abed41 ("wifi: ath9k: avoid
> uninit memory read in ath9k_htc_rx_msg()"), ath9k_htc_rx_msg() should
> validate pkt_len before accessing the SKB. For example, the obtained SKB
> may have uninitialized memory in the case of
> ioctl(USB_RAW_IOCTL_EP_WRITE).
>
> Implement sanity checking inside the corresponding endpoint RX handlers:
> ath9k_wmi_ctrl_rx() and ath9k_htc_rxep(). Otherwise, uninit memory can
> be referenced.
>
> Add comments briefly describing the issue.
>
> Found by Linux Verification Center (linuxtesting.org) with Syzkaller.
>
> Fixes: fb9987d0f748 ("ath9k_htc: Support for AR9271 chipset.")
> Reported-and-tested-by: syzbot+f2cb6e0ffdb961921e4d@xxxxxxxxxxxxxxxxxxxxxxxxx
> Signed-off-by: Fedor Pchelkin <pchelkin@xxxxxxxxx>
> ---

Apologies for the delay.

I've been able to test the patches in some way on 0cf3:9271 Qualcomm
Atheros Communications AR9271 802.11n .

> drivers/net/wireless/ath/ath9k/htc_drv_txrx.c | 6 ++++++
> drivers/net/wireless/ath/ath9k/htc_hst.c | 4 ++++
> drivers/net/wireless/ath/ath9k/wmi.c | 8 ++++++++
> 3 files changed, 18 insertions(+)
>
> diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
> index 672789e3c55d..957efb26019d 100644
> --- a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
> +++ b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
> @@ -1147,6 +1147,12 @@ void ath9k_htc_rxep(void *drv_priv, struct sk_buff *skb,
> if (!data_race(priv->rx.initialized))
> goto err;
>
> + /* Validate the obtained SKB so that it is handled without error
> + * inside rx_tasklet handler.
> + */
> + if (unlikely(skb->len < sizeof(struct ieee80211_hdr)))
> + goto err;
> +
> spin_lock_irqsave(&priv->rx.rxbuflock, flags);
> list_for_each_entry(tmp_buf, &priv->rx.rxbuf, list) {
> if (!tmp_buf->in_process) {

This check above seems to be redundant: SKB is properly checked inside
ath9k_rx_prepare() in rx_tasklet handler.

> diff --git a/drivers/net/wireless/ath/ath9k/htc_hst.c b/drivers/net/wireless/ath/ath9k/htc_hst.c
> index fe62ff668f75..9d0d9d0e1aa8 100644
> --- a/drivers/net/wireless/ath/ath9k/htc_hst.c
> +++ b/drivers/net/wireless/ath/ath9k/htc_hst.c
> @@ -475,6 +475,10 @@ void ath9k_htc_rx_msg(struct htc_target *htc_handle,
> skb_pull(skb, sizeof(struct htc_frame_hdr));
>
> endpoint = &htc_handle->endpoint[epid];
> +
> + /* The endpoint RX handlers should implement their own
> + * additional SKB sanity checking
> + */
> if (endpoint->ep_callbacks.rx)
> endpoint->ep_callbacks.rx(endpoint->ep_callbacks.priv,
> skb, epid);
> diff --git a/drivers/net/wireless/ath/ath9k/wmi.c b/drivers/net/wireless/ath/ath9k/wmi.c
> index 19345b8f7bfd..2e7c361b62f5 100644
> --- a/drivers/net/wireless/ath/ath9k/wmi.c
> +++ b/drivers/net/wireless/ath/ath9k/wmi.c
> @@ -204,6 +204,10 @@ static void ath9k_wmi_rsp_callback(struct wmi *wmi, struct sk_buff *skb)
> {
> skb_pull(skb, sizeof(struct wmi_cmd_hdr));
>
> + /* Once again validate the SKB. */
> + if (unlikely(skb->len < wmi->cmd_rsp_len))
> + return;
> +
> if (wmi->cmd_rsp_buf != NULL && wmi->cmd_rsp_len != 0)
> memcpy(wmi->cmd_rsp_buf, skb->data, wmi->cmd_rsp_len);
>

The change above is very very wrong. Looking at the firmware code and
debugging driver with real device, I realized the command response is
located in the tailroom of an SKB: skb->len (SKB data length) is zero in
such packets while skb->data and skb->tail pointers are equal. So a new
skb->len and cmd_rsp_len check resulted in driver denying all WMI command
responses. My bad for proposing such a mistake.

> @@ -221,6 +225,10 @@ static void ath9k_wmi_ctrl_rx(void *priv, struct sk_buff *skb,
> if (unlikely(wmi->stopped))
> goto free_skb;
>
> + /* Validate the obtained SKB. */
> + if (unlikely(skb->len < sizeof(struct wmi_cmd_hdr)))
> + goto free_skb;
> +
> hdr = (struct wmi_cmd_hdr *) skb->data;
> cmd_id = be16_to_cpu(hdr->command_id);
>

This check above is actually good. A packet can be obtained constructed
something like this (taken from Syzkaller reproducer):

*(uint16_t*)0x20000500 = 8; // pkt_len
*(uint16_t*)0x20000502 = 0x4e00; // ATH_USB_RX_STREAM_MODE_TAG
memcpy((void*)0x20000504, "\x15\xa7\xd5\x61\xb9\xb3\xb0\x7c", 8);
syz_usb_ep_write(r[0], 0x82, 0xc, 0x20000500);

pkt_len is 8, so that it can contain an htc_frame_hdr, but when the SKB is
processed in ath9k_htc_rx_msg() and passed to the endpoint RX handler,
ath9k_wmi_ctrl_rx() specifically, the problem arises as there are no other
corresponding headers in the buffer.

wmi_cmd_hdr is pulled later so there are no problems with checking
skb->len. There are no issues arised with the patch on a real device, too.

Not sure if this can happen on an ath9k device with common firmware
(probably can't happen), but that is real with some bad USB device which
Syzkaller emulates.

I'll send the v2 versions for further discussions.

> --
> 2.34.1
>