diff mbox series

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

Message ID 20230315202112.163012-2-pchelkin@ispras.ru (mailing list archive)
State Awaiting Upstream
Delegated to: Netdev Maintainers
Headers show
Series wifi: ath9k: deal with uninit memory | expand

Checks

Context Check Description
netdev/tree_selection success Not a local patch

Commit Message

Fedor Pchelkin March 15, 2023, 8:21 p.m. UTC
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@syzkaller.appspotmail.com
Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
---
 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(+)

Comments

Kalle Valo March 17, 2023, 5:26 a.m. UTC | #1
Fedor Pchelkin <pchelkin@ispras.ru> writes:

> 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.

It would be also nice to know how you have tested these. Syzkaller is no
substitute for testing on a real hardware.
Fedor Pchelkin March 18, 2023, 8:25 p.m. UTC | #2
On Fri, Mar 17, 2023 at 07:26:11AM +0200, Kalle Valo wrote:
> 
> It would be also nice to know how you have tested these. Syzkaller is no
> substitute for testing on a real hardware.
> 

Unfortunately, currently I can't test this on real hardware so probably we
should postpone the patch discussion for some time. Roughly in a week or
two I'll be able to do some testing and try to reproduce the problem
there.

For sure this should be tested on real hardware as some issues may arise.
I sent the patch based on the commit b383e8abed41 ("wifi: ath9k: avoid
uninit memory read in ath9k_htc_rx_msg()") where it is explained
thoroughly what can lead to such behaviour. At the moment I don't see
anything in the code which can prevent that invalid scenario to happen for
endpoint callbacks path.

Actually, sanity checks for SKB length have been added everywhere inside
ath9k_htc_rx_msg() except where the endpoint callbacks are called. As for
the repro, the SKB inside ath9k_hif_usb_rx_stream() is allocated with
pkt_len=8 so it passes the 'htc_frame_hdr' check and processing in
ath9k_htc_rx_msg() but it obviously cannot be handled correctly in the
endpoint callbacks then.
Fedor Pchelkin April 24, 2023, 6:23 p.m. UTC | #3
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@syzkaller.appspotmail.com
> Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
> ---

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
>
diff mbox series

Patch

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) {
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);
 
@@ -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);