mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses
@ 2026-09-30  7:19 Hui Peng
  2026-09-30  7:19 ` [PATCH net v3 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Hui Peng @ 2026-09-30  7:19 UTC (permalink / raw)
  To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt
  Cc: David Laight, linux-wpan, netdev, linux-kernel, stable, Hui Peng

This series fixes three out-of-bounds access bugs in the Cascoda CA8210
IEEE 802.15.4 driver:

1. Reject received SPI packets with len > sizeof(struct mac_message) in
   ca8210_rx_done() instead of checking len > CA8210_SPI_BUF_SIZE (256),
   preventing a stack buffer overflow when copying a synchronous response
   into priv->sync_command_response (a struct mac_message on the caller's
   stack) and matching the actual SPI transfer length
   (cas_ctl->transfer.len = sizeof(struct mac_message)).
2. Initialize lenvar = 1 in ca8210_get_ed() and validate
   hw_attribute_length against *hw_attribute_length in
   hwme_get_request_sync() before memcpy() to prevent overflowing the
   caller's stack buffer.
3. Validate the received SPI frame length len upfront at the start of
   ca8210_skb_rx() before reading data_ind or allocating the skb.

Changes in v3:
- Patch 1/3: Check len > sizeof(struct mac_message) in ca8210_rx_done()
  where dev_crit() logs "Received packet len (%u) erroneously long" and
  drops the packet, instead of silently truncating the memcpy() with
  min_t(), addressing David Laight's feedback.

Changes in v2:
- Split the ca8210 fixes into three single-issue patches (1/3..3/3) and
  addressed Miquel Raynal's review comments on patches 2/3 and 3/3.

Hui Peng (3):
  ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done()
  ieee802154: ca8210: prevent stack buffer overflow in
    hwme_get_request_sync()
  ieee802154: ca8210: validate data_ind length upfront in
    ca8210_skb_rx()

 drivers/net/ieee802154/ca8210.c | 39 +++++++++++++++++++++++----------
 1 file changed, 27 insertions(+), 12 deletions(-)

-- 
2.49.0

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net v3 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done()
  2026-09-30  7:19 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
@ 2026-09-30  7:19 ` Hui Peng
  2026-10-04  7:39   ` netdev-bot+sashiko
  2026-09-30  7:19 ` [PATCH net v3 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng
  2026-09-30  7:19 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
  2 siblings, 1 reply; 11+ messages in thread
From: Hui Peng @ 2026-09-30  7:19 UTC (permalink / raw)
  To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt
  Cc: David Laight, linux-wpan, netdev, linux-kernel, stable, Hui Peng

In ca8210_spi_transfer(), each SPI transfer reads
sizeof(struct mac_message) bytes into cas_ctl->tx_in_buf:

  cas_ctl->transfer.len = sizeof(struct mac_message);

However, ca8210_rx_done() only checks the received packet length
len = buf[1] + 2 against CA8210_SPI_BUF_SIZE (256). When buf[0] & SPI_SYN
is set and priv->sync_command_response is non-NULL, memcpy() copies up to
256 bytes into priv->sync_command_response, which points to a
struct mac_message object on the synchronous caller's stack, overflowing
the stack buffer:

  BUG: KASAN: stack-out-of-bounds in ca8210_rx_done+0x117/0x6c0
  Write of size 256 at addr ffff8881009e7c40 by task swapper/0/1
  Call Trace:
   <TASK>
   dump_stack_lvl+0x70/0xa0
   print_report+0x153/0x4c6
   kasan_report+0xf1/0x120
   kasan_check_range+0x125/0x200
   __asan_memcpy+0x3c/0x60
   ca8210_rx_done+0x117/0x6c0
  ...
  This frame has 1 object:
   [32, 182) 'response'

Check len > sizeof(struct mac_message) instead of
len > CA8210_SPI_BUF_SIZE in ca8210_rx_done() so that any packet exceeding
sizeof(struct mac_message) is logged as erroneously long via dev_crit()
and dropped before copying into priv->sync_command_response or passing it
to ca8210_net_rx().

Tested in QEMU with KASAN enabled by passing a 256-byte SPI_SYN response
into ca8210_rx_done().

Fixes: ded845a781a5 ("ieee802154: Add CA8210 IEEE 802.15.4 device driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- Check len > sizeof(struct mac_message) at the top of ca8210_rx_done()
  where dev_crit() logs the error and drops the packet instead of silently
  truncating memcpy() with min_t(), addressing David Laight's feedback.

Changes in v2:
- Split the ca8210 fixes into three single-issue patches (1/3..3/3).

 drivers/net/ieee802154/ca8210.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
index 01af4f9..a990a0f 100644
--- a/drivers/net/ieee802154/ca8210.c
+++ b/drivers/net/ieee802154/ca8210.c
@@ -686,7 +686,7 @@ static void ca8210_rx_done(struct cas_control *cas_ctl)
 
 	buf = cas_ctl->tx_in_buf;
 	len = buf[1] + 2;
-	if (len > CA8210_SPI_BUF_SIZE) {
+	if (len > sizeof(struct mac_message)) {
 		dev_crit(
 			&priv->spi->dev,
 			"Received packet len (%u) erroneously long\n",
-- 
2.49.0

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net v3 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync()
  2026-09-30  7:19 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
  2026-09-30  7:19 ` [PATCH net v3 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng
@ 2026-09-30  7:19 ` Hui Peng
  2026-10-04  7:39   ` netdev-bot+sashiko
  2026-09-30  7:19 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
  2 siblings, 1 reply; 11+ messages in thread
From: Hui Peng @ 2026-09-30  7:19 UTC (permalink / raw)
  To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt
  Cc: David Laight, linux-wpan, netdev, linux-kernel, stable, Hui Peng

In ca8210_get_ed(), an uninitialized u8 lenvar and a pointer to a 1-byte
stack buffer (u8 *level) are passed to hwme_get_request_sync(), which
unconditionally copies response.pdata.hwme_get_cnf.hw_attribute_length
bytes into hw_attribute_value without checking the caller's destination
buffer capacity, overflowing level on the stack when hw_attribute_length
exceeds 1:

  BUG: KASAN: stack-out-of-bounds in hwme_get_request_sync.constprop.0.isra.0+0xf3/0x170
  Write of size 16 at addr ffff888001907780 by task init/1
  Call Trace:
   <TASK>
   dump_stack_lvl+0x70/0xa0
   print_report+0x153/0x4c6
   kasan_report+0xf1/0x120
   kasan_check_range+0x125/0x200
   __asan_memcpy+0x3c/0x60
   hwme_get_request_sync.constprop.0.isra.0+0xf3/0x170
   ca8210_get_ed+0x9c/0xf0
  ...
  The buggy address belongs to stack of task init/1
   and is located at offset 48 in frame:
   ca8210_get_ed+0x0/0xf0
  This frame has 2 objects:
   [48, 49) 'level'
   [64, 65) 'lenvar'

Initialize lenvar = 1 in ca8210_get_ed() and return
IEEE802154_SYSTEM_ERROR in hwme_get_request_sync() if
response.pdata.hwme_get_cnf.hw_attribute_length exceeds
*hw_attribute_length.

Tested in QEMU with KASAN enabled by passing an oversized
hw_attribute_length response into ca8210_get_ed().

Fixes: ded845a781a5 ("ieee802154: Add CA8210 IEEE 802.15.4 device driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- No changes.

Changes in v2:
- Split out as patch 2/3.
- Replaced the temporary stack buffer in ca8210_get_ed() with lenvar = 1
  and an upper-bound check against *hw_attribute_length in
  hwme_get_request_sync() as requested by Miquel Raynal.

 drivers/net/ieee802154/ca8210.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
index a990a0f..8aa7ffe 100644
--- a/drivers/net/ieee802154/ca8210.c
+++ b/drivers/net/ieee802154/ca8210.c
@@ -1677,6 +1677,9 @@ static u8 hwme_get_request_sync(
 		return IEEE802154_SYSTEM_ERROR;
 
 	if (response.pdata.hwme_get_cnf.status == IEEE802154_SUCCESS) {
+		if (response.pdata.hwme_get_cnf.hw_attribute_length >
+		    *hw_attribute_length)
+			return IEEE802154_SYSTEM_ERROR;
 		*hw_attribute_length =
 			response.pdata.hwme_get_cnf.hw_attribute_length;
 		memcpy(
@@ -2027,7 +2030,7 @@ static int ca8210_xmit_async(struct ieee802154_hw *hw, struct sk_buff *skb)
  */
 static int ca8210_get_ed(struct ieee802154_hw *hw, u8 *level)
 {
-	u8 lenvar;
+	u8 lenvar = 1;
 	struct ca8210_priv *priv = hw->priv;
 
 	return link_to_linux_err(

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()
  2026-09-30  7:19 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
  2026-09-30  7:19 ` [PATCH net v3 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng
  2026-09-30  7:19 ` [PATCH net v3 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng
@ 2026-09-30  7:19 ` Hui Peng
  2026-10-04  7:39   ` netdev-bot+sashiko
  2 siblings, 1 reply; 11+ messages in thread
From: Hui Peng @ 2026-09-30  7:19 UTC (permalink / raw)
  To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt
  Cc: David Laight, linux-wpan, netdev, linux-kernel, stable, Hui Peng

In ca8210_skb_rx(), data_ind is read at offsets 22 (msdulen), 23
(mpdulinkquality), 29 + msdulen (hdr.sec.level), 30..39 + msdulen
(security header), and 29 .. 29 + msdulen (payload) without verifying
that the received SPI frame length len covers those offsets, causing an
out-of-bounds read when msdulen exceeds len - 30:

  BUG: KASAN: slab-out-of-bounds in ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
  Read of size 64 at addr ffff888006453ddd by task init/1
  Call Trace:
   <TASK>
   dump_stack_lvl+0x70/0xa0
   print_report+0x153/0x4c6
   kasan_report+0xf1/0x120
   kasan_check_range+0x125/0x200
   __asan_memcpy+0x23/0x60
   ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
   ca8210_net_rx+0x96/0xc0
  ...
  The buggy address belongs to the object at ffff888006453dc0
   which belongs to the cache kmalloc-32 of size 32
  The buggy address is located 29 bytes inside of
   allocated 32-byte region [ffff888006453dc0, ffff888006453de0)

Consolidate all length and msdulen validations into a single upfront check
at the beginning of ca8210_skb_rx() before allocating the skb.

Tested in QEMU with KASAN enabled by passing a short data_ind buffer with
msdulen = 64 and len = 30 into ca8210_skb_rx().

Fixes: ded845a781a5 ("ieee802154: Add CA8210 IEEE 802.15.4 device driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- No changes.

Changes in v2:
- Split out as patch 3/3.
- Consolidated all length checks in ca8210_skb_rx() into a single place
  at the beginning of the function before dev_alloc_skb() and dropped the
  unrelated hdr.seq assignment as requested by Miquel Raynal.

 drivers/net/ieee802154/ca8210.c | 32 ++++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
index 8aa7ffe..ab245ad 100644
--- a/drivers/net/ieee802154/ca8210.c
+++ b/drivers/net/ieee802154/ca8210.c
@@ -1758,19 +1758,15 @@ static int ca8210_skb_rx(
 	u8                    *data_ind
 )
 {
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = { };
 	int msdulen;
 	int hlen;
-	u8 mpdulinkquality = data_ind[23];
+	u8 mpdulinkquality;
 	struct sk_buff *skb;
 	struct ca8210_priv *priv = hw->priv;
 
-	/* Allocate mtu size buffer for every rx packet */
-	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
-	if (!skb)
-		return -ENOMEM;
-
-	skb_reserve(skb, sizeof(hdr));
+	if (len < 30)
+		return -EMSGSIZE;
 
 	msdulen = data_ind[22]; /* msdu_length */
 	if (msdulen > IEEE802154_MTU) {
@@ -1778,9 +1774,25 @@ static int ca8210_skb_rx(
 			&priv->spi->dev,
 			"received erroneously large msdu length!\n"
 		);
-		kfree_skb(skb);
 		return -EMSGSIZE;
 	}
+
+	if (len < 30 + msdulen ||
+	    (!priv->promiscuous && data_ind[29 + msdulen] > 0 &&
+	     len < 29 + msdulen + sizeof(struct secspec))) {
+		dev_err(&priv->spi->dev,
+			"received truncated data indication!\n");
+		return -EMSGSIZE;
+	}
+
+	mpdulinkquality = data_ind[23];
+
+	/* Allocate mtu size buffer for every rx packet */
+	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
+	if (!skb)
+		return -ENOMEM;
+
+	skb_reserve(skb, sizeof(hdr));
 	dev_dbg(&priv->spi->dev, "skb buffer length = %d\n", msdulen);
 
 	if (priv->promiscuous)

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net v3 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done()
  2026-09-30  7:19 ` [PATCH net v3 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng
@ 2026-10-04  7:39   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04  7:39 UTC (permalink / raw)
  To: benquike
  Cc: h.morris, alex.aring, miquel.raynal, stefan, david.laight.linux,
	linux-wpan, netdev, linux-kernel, stable, kuba

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 1 (2) · Medium: 0 · Low: 0 (1)

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] ieee802154: ca8210: dropped Rx frames and log flood DoS due to
  length limit

Pre-existing issues:
- [High] priv->sync_command_response is a plain pointer to a synchronous
  caller's on-stack struct mac_message, and nothing synchronizes access…
- [High] ca8210_rx_done() calls msleep(2000) (line 718) and then
  kmalloc_obj(*mlme_reset_wpc) (line 723, default GFP_KERNEL) when it…
- [Low] Baseline decision: not reported (preexisting, low impact, outside
  the bug class the commit claims to fix).

--

ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done()

This patch changes the length check in ca8210_rx_done() so that it compares
against sizeof(struct mac_message) instead of CA8210_SPI_BUF_SIZE. A
response that is too long is now logged and dropped. It is no longer copied
into the synchronous caller's on-stack response buffer or passed to
ca8210_net_rx().

> diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
> index 01af4f9cf7f29..a990a0f347ad8 100644
> --- a/drivers/net/ieee802154/ca8210.c
> +++ b/drivers/net/ieee802154/ca8210.c
> @@ -686,7 +686,7 @@ static void ca8210_rx_done(struct cas_control *cas_ctl)
>  
>  	buf = cas_ctl->tx_in_buf;
>  	len = buf[1] + 2;
> -	if (len > CA8210_SPI_BUF_SIZE) {
> +	if (len > sizeof(struct mac_message)) {
>  		dev_crit(
>  			&priv->spi->dev,
>  			"Received packet len (%u) erroneously long\n",

[Severity: High]
Does this new limit also drop legitimate MCPS-DATA.indication frames?

The check sits at the top of ca8210_rx_done(), so it applies to every
message from the device. It is not limited to the synchronous responses
that get copied into priv->sync_command_response.

ca8210_net_rx() passes SPI_MCPS_DATA_INDICATION payloads to
ca8210_skb_rx() as buf + 2. ca8210_skb_rx() then reads the security
spec that follows the MSDU:

	hdr.sec.level = data_ind[29 + msdulen];
	...
	if (hdr.sec.level > 0) {
		hdr.sec.key_id_mode = data_ind[30 + msdulen];
		memcpy(&hdr.sec.extended_src, &data_ind[31 + msdulen], 8);
		hdr.sec.key_id = data_ind[39 + msdulen];
	}

That makes a full indication 42 + msdulen bytes. With a 127-byte MSDU
(IEEE802154_MTU), the device reports len = 169. That is larger than
sizeof(struct mac_message), which is 150 according to the KASAN report
in the changelog. If I read this right, any received frame with more
than 108 bytes of payload is now discarded.

Each dropped frame also triggers a dev_crit() with no rate limiting.
Anyone in radio range can send long frames, so a remote peer could
flood the kernel log this way. Should this at least use
dev_crit_ratelimited() or dev_err_ratelimited()?

ca8210_spi_transfer() only clocks in sizeof(struct mac_message) bytes.
So before this patch, the tail of a long indication may already have
come from stale tx_in_buf contents.

Would it be better to keep the generic limit here, and check against
sizeof(struct mac_message) only before the memcpy() into
priv->sync_command_response? Or should the transfer length be sized to
cover the largest MCPS-DATA.indication? Either way, dropping long data
frames on stable kernels looks like a regression worth avoiding.

[Severity: High]
This is a pre-existing issue, but since this patch is about the memcpy()
into priv->sync_command_response, can the pointer itself go stale?

Later in ca8210_rx_done(), the pointer is checked and then written through.
No lock or barrier protects it:

drivers/net/ieee802154/ca8210.c:ca8210_rx_done() {
    ...
	if (buf[0] & SPI_SYN) {
		if (priv->sync_command_response) {
			memcpy(priv->sync_command_response, buf, len);
			complete(&priv->sync_exchange_complete);
    ...
}

ca8210_spi_exchange() stores a pointer to the caller's stack buffer. It
then waits with an interruptible timeout. On timeout or signal, it clears
the pointer and returns:

drivers/net/ieee802154/ca8210.c:ca8210_spi_exchange() {
    ...
		priv->sync_command_response = response;
    ...
	wait_remaining = wait_for_completion_interruptible_timeout(
		&priv->sync_exchange_complete,
		msecs_to_jiffies(CA8210_SYNC_TIMEOUT)
	);
    ...
cleanup:
	priv->sync_command_response = NULL;
	return status;
}

Suppose the device response arrives just as the waiter times out or gets a
signal. Could this interleaving happen?

CPU1 (SPI completion)
ca8210_spi_transfer_complete()
  ca8210_rx_done()
    if (priv->sync_command_response)   <- sees the caller's buffer

CPU2 (a sync caller such as mlme_reset_request_sync())
ca8210_spi_exchange()
  wait_for_completion_interruptible_timeout() returns 0 or -ERESTARTSYS
  priv->sync_command_response = NULL;
  returns, and the on-stack response buffer goes out of scope

CPU1
    memcpy(priv->sync_command_response, buf, len);

That would write up to sizeof(struct mac_message) bytes supplied by the
device into a stack frame that the caller no longer owns.

Also, ca8210_spi_exchange() callers are not serialized by any mutex. Can
two concurrent synchronous callers overwrite each other's
sync_command_response and sync_exchange_complete? If so, one caller's
reply could land in the other caller's buffer.

[Severity: High]
This isn't a bug introduced by this patch, but can ca8210_rx_done() sleep
in a context where sleeping is not allowed?

The only caller of ca8210_rx_done() is ca8210_spi_transfer_complete().
ca8210_spi_transfer() installs that function as the spi_async() completion
callback:

	cas_ctl->msg.complete = ca8210_spi_transfer_complete;
	...
	status = spi_async(spi, &cas_ctl->msg);

The spi_async() kernel-doc in drivers/spi/spi.c says:

 * The completion callback is invoked in a context which can't sleep.

When ca8210_rx_done() receives SPI_MCPS_DATA_CONFIRM with
IEEE802154_TRANSACTION_OVERFLOW, it does this:

		if (buf[3] == IEEE802154_TRANSACTION_OVERFLOW) {
			...
			msleep(2000);
			...
			mlme_reset_wpc = kmalloc_obj(*mlme_reset_wpc);

Some controllers finalize messages from IRQ or tasklet context. Would this
trigger "scheduling while atomic" there?

Other controllers finalize from the message pump kthread. There, it looks
like this would stall the SPI bus for two seconds.

[Severity: Low]
This is a pre-existing issue, but ca8210_spi_transfer_complete() dumps the
received buffer using the device's length byte without checking it. That
happens before ca8210_rx_done() reaches the new check:

	if (duplex_rx) {
		dev_dbg(&priv->spi->dev, "READ CMD DURING TX\n");
		for (i = 0; i < cas_ctl->tx_in_buf[1] + 2; i++)
			dev_dbg(
				&priv->spi->dev,
				"%#03x\n",
				cas_ctl->tx_in_buf[i]
			);
		ca8210_rx_done(cas_ctl);
	}

If tx_in_buf[1] == 0xff, does i reach 256 and read tx_in_buf[256]? That is
one byte past the end of the CA8210_SPI_BUF_SIZE array.

This only happens when the dev_dbg() call site is enabled. The loop also
prints bytes beyond the sizeof(struct mac_message) bytes that the transfer
actually received.

Should this loop be limited to sizeof(struct mac_message) as well?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930071914.421586-1-benquike%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net v3 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync()
  2026-09-30  7:19 ` [PATCH net v3 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng
@ 2026-10-04  7:39   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04  7:39 UTC (permalink / raw)
  To: benquike
  Cc: h.morris, alex.aring, miquel.raynal, stefan, david.laight.linux,
	linux-wpan, netdev, linux-kernel, stable, kuba

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The commit message overstates how reachable the bug is, and the
  stable backport is not justified.
- [Low] The patch changes the contract of hwme_get_request_sync(), but
  its kernel-doc was not updated.

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the sync
  response pointer can be written to after its stack buffer is gone…
- [Medium] This problem wasn't introduced by this patch, but
  hwme_get_request_sync() uses a stack 'struct mac_message response'…

--

ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync()

This commit adds an upper-bound check to hwme_get_request_sync(). If a
HWME-GET confirm reports an hw_attribute_length larger than the
caller-supplied *hw_attribute_length, the function now returns
IEEE802154_SYSTEM_ERROR instead of copying past the end of the destination
buffer. It also sets lenvar to 1 in ca8210_get_ed(), so the size of the
1-byte level buffer is passed in.

> In ca8210_get_ed(), an uninitialized u8 lenvar and a pointer to a 1-byte
> stack buffer (u8 *level) are passed to hwme_get_request_sync(), which
> unconditionally copies response.pdata.hwme_get_cnf.hw_attribute_length
> bytes into hw_attribute_value without checking the caller's destination
> buffer capacity, overflowing level on the stack when hw_attribute_length
> exceeds 1:

[ ... ]

> Tested in QEMU with KASAN enabled by passing an oversized
> hw_attribute_length response into ca8210_get_ed().
>
> Fixes: ded845a781a5 ("ieee802154: Add CA8210 IEEE 802.15.4 device driver")
> Cc: stable@vger.kernel.org

[Severity: Low]
Can any in-tree code path reach this overflow?

ca8210_get_ed() is the only caller of hwme_get_request_sync(). The only
reference to ca8210_get_ed() is the ops table entry:

    .ed = ca8210_get_ed,

In net/mac802154, the only use of ops->ed seems to be the NULL check in
ieee802154_alloc_hw():

    if (WARN_ON(!ops || !(ops->xmit_async || ops->xmit_sync) || !ops->ed ||

Energy detection scans are also rejected in two places.
nl802154_trigger_scan() accepts only NL802154_SCAN_ACTIVE and
NL802154_SCAN_PASSIVE, and mac802154_trigger_scan_locked() has:

    if (request->type != NL802154_SCAN_PASSIVE &&
        request->type != NL802154_SCAN_ACTIVE)
        return -EOPNOTSUPP;

The splat shows init/1 calling ca8210_get_ed() directly. Together with the
testing note quoted above, this looks like it came from a test harness
rather than a path that exists in the tree.

The code change itself looks correct. Could the commit message call this
hardening of a callback that has no in-tree caller today? And could the
Cc: stable tag either be dropped or be given a justification?

> diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
> index a990a0f347ad8..8aa7ffeb22d78 100644
> --- a/drivers/net/ieee802154/ca8210.c
> +++ b/drivers/net/ieee802154/ca8210.c
> @@ -1677,6 +1677,9 @@ static u8 hwme_get_request_sync(
>  		return IEEE802154_SYSTEM_ERROR;
>  
>  	if (response.pdata.hwme_get_cnf.status == IEEE802154_SUCCESS) {
> +		if (response.pdata.hwme_get_cnf.hw_attribute_length >
> +		    *hw_attribute_length)
> +			return IEEE802154_SYSTEM_ERROR;

[Severity: Low]
With this check, *hw_attribute_length is now read as the capacity of the
hw_attribute_value buffer, which makes it an in/out parameter. Should the
kernel-doc for hwme_get_request_sync() be updated as well? It still says:

 * @hw_attribute_length: Attribute length
 * @hw_attribute_value:  Pointer to Attribute Value

It doesn't say that callers must set the buffer size before the call. It
also doesn't mention that a successful confirm with a length that is too
large now returns IEEE802154_SYSTEM_ERROR.

A future caller that follows the kernel-doc could make the same
uninitialized lenvar mistake this patch fixes.

>  		*hw_attribute_length =
>  			response.pdata.hwme_get_cnf.hw_attribute_length;
>  		memcpy(

[Severity: Medium]
This is a pre-existing issue, but can a short HWME_GET_CONFIRM make this
function return success with stale data?

response is an uninitialized struct mac_message on the stack in
hwme_get_request_sync(). ca8210_rx_done() copies into it only the length
the device advertises:

    len = buf[1] + 2;
    ...
    memcpy(priv->sync_command_response, buf, len);

If the confirm is truncated, hw_attribute_length and hw_attribute_value[0]
still hold whatever was left on the stack.

A length of 0 passes the new check. Nothing is copied, and SUCCESS is
returned. ca8210_get_ed() then returns 0 without ever writing *level,
because it doesn't check lenvar after the call.

A length of 1 in a truncated frame would copy one uninitialized stack byte
into *level and still report success.

Would it make sense to:

- check that response.length covers the confirm fields and the claimed
  value,
- require lenvar == 1 in ca8210_get_ed(), and
- zero-initialize response?

As noted above, mac802154 doesn't currently call ops->ed.

> @@ -2027,7 +2030,7 @@ static int ca8210_xmit_async(struct ieee802154_hw *hw, struct sk_buff *skb)
>   */
>  static int ca8210_get_ed(struct ieee802154_hw *hw, u8 *level)
>  {
> -	u8 lenvar;
> +	u8 lenvar = 1;
>  	struct ca8210_priv *priv = hw->priv;
>  
>  	return link_to_linux_err(

[Severity: High]
This is a pre-existing issue, but can ca8210_rx_done() write into the
response buffer in hwme_get_request_sync() after that function has
returned?

ca8210_spi_exchange() publishes the caller's stack buffer:

    priv->sync_command_response = response;

ca8210_rx_done(), which runs from the SPI completion path, then does:

    if (priv->sync_command_response) {
        memcpy(priv->sync_command_response, buf, len);
        complete(&priv->sync_exchange_complete);

It holds no lock and doesn't use READ_ONCE().

The wait on sync_exchange_complete can be interrupted by a signal
(-ERESTARTSYS) or can time out after CA8210_SYNC_TIMEOUT. In either case,
ca8210_spi_exchange() does:

cleanup:
    priv->sync_command_response = NULL;
    return status;

Nothing waits for an rx_done() that is already running:

CPU1                                  CPU2
ca8210_spi_exchange()                 ca8210_rx_done()
                                        if (priv->sync_command_response)
  wait interrupted or timed out
  priv->sync_command_response = NULL
  return
hwme_get_request_sync() returns
                                        memcpy(priv->sync_command_response,
                                               buf, len);

In that case the memcpy() writes up to sizeof(struct mac_message) bytes
into a stack frame that has already been unwound. If the compiler reloads
the pointer after it has been cleared, the result is a write through a NULL
pointer instead.

The same pattern applies to every *_request_sync() helper, for example
ca8210_set_channel()->mlme_set_request_sync(). Nothing in the driver locks
sync_command_response, and priv->lock is only used in the TX path.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930071914.421586-1-benquike%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()
  2026-09-30  7:19 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
@ 2026-10-04  7:39   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-10-04  7:39 UTC (permalink / raw)
  To: benquike
  Cc: h.morris, alex.aring, miquel.raynal, stefan, david.laight.linux,
	linux-wpan, netdev, linux-kernel, stable, kuba

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 2 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The commit message says the patch fixes a slab-out-of-bounds
  read, quotes a KASAN splat from a kmalloc-32 object and adds Cc:…
- [Medium] The new truncation check in ca8210_skb_rx() reads
  priv->promiscuous without a lock at line 1781 to decide whether to…

--

ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()

This patch moves the length checks in ca8210_skb_rx() ahead of the skb
allocation. It adds checks that len covers the fixed 30-byte header, the
msdu payload, and the security header when one is present. It also
zero-initializes hdr.

> In ca8210_skb_rx(), data_ind is read at offsets 22 (msdulen), 23
> (mpdulinkquality), 29 + msdulen (hdr.sec.level), 30..39 + msdulen
> (security header), and 29 .. 29 + msdulen (payload) without verifying
> that the received SPI frame length len covers those offsets, causing an
> out-of-bounds read when msdulen exceeds len - 30:
>
>   BUG: KASAN: slab-out-of-bounds in ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
>   Read of size 64 at addr ffff888006453ddd by task init/1

[ ... ]

>   The buggy address belongs to the object at ffff888006453dc0
>    which belongs to the cache kmalloc-32 of size 32

[ ... ]

> Tested in QEMU with KASAN enabled by passing a short data_ind buffer with
> msdulen = 64 and len = 30 into ca8210_skb_rx().

[Severity: Medium]
Can this slab-out-of-bounds read happen on a real driver path? As far as
I can tell, ca8210_skb_rx() has only one caller chain,
ca8210_spi_transfer_complete()->ca8210_rx_done()->ca8210_net_rx(), and
data_ind always points into the fixed-size tx_in_buf:

drivers/net/ieee802154/ca8210.c:
#define CA8210_SPI_BUF_SIZE 256

struct cas_control {
    ...
	u8 tx_in_buf[CA8210_SPI_BUF_SIZE];
};

ca8210_rx_done() {
    ...
	buf = cas_ctl->tx_in_buf;
    ...
	ca8210_net_rx(priv->hw, buf, len);
}

ca8210_net_rx() {
    ...
		return ca8210_skb_rx(hw, len - 2, command + 2);
}

The existing check already caps msdulen at IEEE802154_MTU (127). That
makes the largest read data_ind[39 + 127], which is tx_in_buf[168]. The
payload copy of data_ind[29..155] also stays well inside the 256-byte
array, both before and after this patch.

The kmalloc-32 object in the splat seems to come from the test harness,
which passed a short buffer directly into ca8210_skb_rx(). The trace
still shows ca8210_net_rx() as the caller, though, so it reads like a
live heap over-read.

The real effect looks like a data-integrity problem, not a memory-safety
one. Bytes past the length the device declared get delivered as payload
and security header fields. Those bytes are the rest of the MISO transfer
or the SPI_IDLE fill from ca8210_spi_transfer(), and this can only happen
with a buggy or malicious SPI peripheral.

Could the commit message describe it that way instead of presenting a
KASAN slab-out-of-bounds report? The Cc: stable tag was added on the
basis of that report.

> diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
> index 8aa7ffeb22d78..ab245add9ff5c 100644
> --- a/drivers/net/ieee802154/ca8210.c
> +++ b/drivers/net/ieee802154/ca8210.c

[ ... ]

> @@ -1778,9 +1774,25 @@ static int ca8210_skb_rx(
>  			&priv->spi->dev,
>  			"received erroneously large msdu length!\n"
>  		);
> -		kfree_skb(skb);
>  		return -EMSGSIZE;
>  	}
> +
> +	if (len < 30 + msdulen ||
> +	    (!priv->promiscuous && data_ind[29 + msdulen] > 0 &&
> +	     len < 29 + msdulen + sizeof(struct secspec))) {
> +		dev_err(&priv->spi->dev,
> +			"received truncated data indication!\n");
> +		return -EMSGSIZE;
> +	}
> +
> +	mpdulinkquality = data_ind[23];
> +
> +	/* Allocate mtu size buffer for every rx packet */
> +	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	skb_reserve(skb, sizeof(hdr));
>  	dev_dbg(&priv->spi->dev, "skb buffer length = %d\n", msdulen);
>
>  	if (priv->promiscuous)

[Severity: Medium]
Can the two reads of priv->promiscuous in ca8210_skb_rx() disagree? The
new truncation check reads the flag once, and the later "if
(priv->promiscuous) goto copy_payload;" reads it again. The
dev_alloc_skb() call in between forces a reload.

priv->promiscuous is a plain bool with no locking. It is written from
process context by ca8210_set_promiscuous_mode(), which mac802154 calls
through drv_set_promiscuous_mode():

ca8210_set_promiscuous_mode() {
    ...
	} else {
		priv->promiscuous = on;
	}
}

ca8210_reset_send() also sets it to false. The RX path runs from SPI
completion context, and ca8210_net_rx() drops priv->lock before it calls
ca8210_skb_rx(). So one possible interleaving is:

RX: ca8210_rx_done()->ca8210_net_rx()->ca8210_skb_rx()
    new check sees priv->promiscuous == true
    secspec length check skipped, len == 30 + msdulen

                        ca8210_set_promiscuous_mode(hw, false)
                            priv->promiscuous = on;   /* false */

RX: dev_alloc_skb()
    if (priv->promiscuous)         /* now false, no goto */
    hdr.sec.level = data_ind[29 + msdulen];     /* > 0 */
    hdr.sec.key_id_mode = data_ind[30 + msdulen];
    memcpy(&hdr.sec.extended_src, &data_ind[31 + msdulen], 8);
    hdr.sec.key_id = data_ind[39 + msdulen];

These reads go up to 10 bytes past the validated len. This is the case
the new check is meant to reject. The bytes are still inside the
256-byte tx_in_buf, so the result is garbage security header fields
rather than a memory-safety fault.

Would it make sense to read the flag once into a local, for example
"bool promisc = READ_ONCE(priv->promiscuous);", and use it in both
places?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260930071914.421586-1-benquike%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()
  2026-09-24  6:22 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
@ 2026-09-28  6:42   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28  6:42 UTC (permalink / raw)
  To: benquike
  Cc: h.morris, alex.aring, miquel.raynal, stefan, david.laight.linux,
	linux-wpan, netdev, linux-kernel, stable, kuba

Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 2 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] ieee802154: ca8210: TOCTOU race on priv->promiscuous allows OOB
  read
- [Medium] The commit message overstates the impact.
- [Medium] The new upfront length check in ca8210_skb_rx() does not cover
  the first read of the MCPS-DATA.indication fields.
- [Low] The patch changes `struct ieee802154_hdr hdr;` to `struct
  ieee802154_hdr hdr = { };` in ca8210_skb_rx()…

--

ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()

This patch moves the length checks in ca8210_skb_rx() to the start of the
function, ahead of the skb allocation. It adds a minimum length check, a
check that len covers the msdu, and a check that len covers the security
header. It also zero-initialises the on-stack struct ieee802154_hdr.

>     In ca8210_skb_rx(), data_ind is read at offsets 22 (msdulen), 23
>     (mpdulinkquality), 29 + msdulen (hdr.sec.level), 30..39 + msdulen
>     (security header), and 29 .. 29 + msdulen (payload) without verifying
>     that the received SPI frame length len covers those offsets, causing an
>     out-of-bounds read when msdulen exceeds len - 30:
>
>       BUG: KASAN: slab-out-of-bounds in ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
>       Read of size 64 at addr ffff888006453ddd by task init/1

[ ... ]

>       The buggy address belongs to the object at ffff888006453dc0
>        which belongs to the cache kmalloc-32 of size 32

[Severity: Medium]
Can the real driver produce this slab-out-of-bounds read? The only caller
of ca8210_skb_rx() is ca8210_net_rx(), which gets its buffer from
ca8210_rx_done():

drivers/net/ieee802154/ca8210.c:ca8210_rx_done() {
    ...
	buf = cas_ctl->tx_in_buf;
	len = buf[1] + 2;
    ...
	ca8210_net_rx(priv->hw, buf, len);
    ...
}

ca8210_net_rx() then does:

	return ca8210_skb_rx(hw, len - 2, command + 2);

tx_in_buf is a fixed u8 tx_in_buf[CA8210_SPI_BUF_SIZE] (256 bytes) that
sits inside struct cas_control. It is memset to SPI_IDLE before each
transfer.

msdulen is already capped at IEEE802154_MTU (127). So the highest byte
ca8210_skb_rx() can touch is tx_in_buf[2 + 39 + 127], which is inside the
object.

The kmalloc-32 splat seems to come from the test harness passing a 32-byte
buffer directly into ca8210_skb_rx(). The underlying problem looks like
missing validation of a truncated MCPS-DATA.indication, not a read outside
the allocation.

With Cc: stable attached, could the commit message describe the real
impact more precisely, so stable and CVE triage are not misled?

> diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
> index 8aa7ffeb22d7..ab245add9ff5 100644
> --- a/drivers/net/ieee802154/ca8210.c
> +++ b/drivers/net/ieee802154/ca8210.c
> @@ -1758,19 +1758,15 @@ static int ca8210_skb_rx(
>  	u8                    *data_ind
>  )
>  {
> -	struct ieee802154_hdr hdr;
> +	struct ieee802154_hdr hdr = { };

[Severity: Low]
This isn't a bug, but this change in behaviour isn't mentioned in the
commit message.

ieee802154_hdr_push() writes hdr->seq and the full 2-byte frame control
into the skb:

net/ieee802154/header_ops.c:ieee802154_hdr_push() {
    ...
	buf[pos++] = hdr->seq;
    ...
	memcpy(buf, fc, 2);

	memcpy(skb_push(skb, pos), buf, pos);
    ...
}

ca8210_skb_rx() never sets hdr.seq, fc.ack_request, fc.frame_pending, the
reserved fc bits or sec.frame_counter. Before this patch, uninitialised
stack bytes ended up in the reconstructed MAC header of every
non-promiscuous received frame. After this patch those fields are always
zero, so seq is 0 for every frame.

The v2 changelog says the hdr.seq assignment was dropped as unrelated.
Could this initialisation either get its own patch or be described in
this commit message? Otherwise a backporter might drop it as unrelated
and bring the stack data leak back.

>  	int msdulen;
>  	int hlen;
> -	u8 mpdulinkquality = data_ind[23];
> +	u8 mpdulinkquality;
>  	struct sk_buff *skb;
>  	struct ca8210_priv *priv = hw->priv;
>  
> -	/* Allocate mtu size buffer for every rx packet */
> -	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
> -	if (!skb)
> -		return -ENOMEM;
> -
> -	skb_reserve(skb, sizeof(hdr));
> +	if (len < 30)
> +		return -EMSGSIZE;

[Severity: Medium]
This check runs only after the caller has already read and stored a
data_ind field. Is the upfront validation complete?

ca8210_rx_done() sets len = buf[1] + 2 and checks only an upper bound.
ca8210_net_rx() then reads the DSN (data_ind[24]) before any length check:

drivers/net/ieee802154/ca8210.c:ca8210_net_rx() {
    ...
	if (command[0] == SPI_MCPS_DATA_INDICATION) {
		spin_lock_irqsave(&priv->lock, flags);
		if (command[26] == priv->last_dsn) {
			...
			return 0;
		}
		priv->last_dsn = command[26];
		spin_unlock_irqrestore(&priv->lock, flags);
		return ca8210_skb_rx(hw, len - 2, command + 2);
    ...
}

Suppose the device sends a truncated indication with buf[1] < 25. Then
command[26] is stale data or SPI_IDLE filler. It gets stored in
priv->last_dsn, and ca8210_skb_rx() then rejects the frame with -EMSGSIZE.

Wouldn't the next valid frame whose DSN matches that byte be silently
dropped as a resend? Should the minimum length check move ahead of the
command[26] access in ca8210_net_rx()?

>  
>  	msdulen = data_ind[22]; /* msdu_length */
>  	if (msdulen > IEEE802154_MTU) {

[ ... ]

> @@ -1778,9 +1774,25 @@ static int ca8210_skb_rx(
>  			&priv->spi->dev,
>  			"received erroneously large msdu length!\n"
>  		);
> -		kfree_skb(skb);
>  		return -EMSGSIZE;
>  	}
> +
> +	if (len < 30 + msdulen ||
> +	    (!priv->promiscuous && data_ind[29 + msdulen] > 0 &&
> +	     len < 29 + msdulen + sizeof(struct secspec))) {
> +		dev_err(&priv->spi->dev,
> +			"received truncated data indication!\n");
> +		return -EMSGSIZE;
> +	}
> +
> +	mpdulinkquality = data_ind[23];
> +
> +	/* Allocate mtu size buffer for every rx packet */
> +	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	skb_reserve(skb, sizeof(hdr));
>  	dev_dbg(&priv->spi->dev, "skb buffer length = %d\n", msdulen);
>  
>  	if (priv->promiscuous)
>

[Severity: High]
Can priv->promiscuous change between the new truncation check and the
later promiscuous test that jumps to copy_payload?

With this patch ca8210_skb_rx() reads priv->promiscuous twice. The
first read decides whether the secspec length condition is applied. The
second read decides whether the security header is parsed:

	hdr.sec.level = data_ind[29 + msdulen];
	if (hdr.sec.level > 0) {
		hdr.sec.key_id_mode = data_ind[30 + msdulen];
		memcpy(&hdr.sec.extended_src, &data_ind[31 + msdulen], 8);
		hdr.sec.key_id = data_ind[39 + msdulen];
	}

ca8210_set_promiscuous_mode() writes priv->promiscuous from the
mac802154 .set_promiscuous_mode callback without taking priv->lock.
ca8210_skb_rx() does not hold priv->lock either, because
ca8210_net_rx() drops it before the call. The two reads are also
separated by dev_alloc_skb() and dev_dbg(), so the compiler has to
reload the field.

Suppose promiscuous mode is switched off between the two reads, and
the frame has len == 30 + msdulen and a non-zero security level. The
frame passes the new check with the secspec condition skipped. It then
falls through into the security header parsing, which reads
data_ind[30 + msdulen] .. data_ind[39 + msdulen]. That is up to 10
bytes past len, and those stale bytes end up in the MAC header pushed
onto the skb. This is exactly the access the new check is meant to
prevent.

Would it be better to sample the flag once, for example with a local
bool promiscuous = READ_ONCE(priv->promiscuous) at the top of the
function? That local could then be used for both the length check and
the goto to copy_payload.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260924062244.2444225-1-benquike%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()
  2026-09-22  9:30 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
@ 2026-09-24  6:42   ` netdev-bot+sashiko
  0 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sashiko @ 2026-09-24  6:42 UTC (permalink / raw)
  To: benquike
  Cc: h.morris, alex.aring, miquel.raynal, stefan, david.laight.linux,
	linux-wpan, netdev, linux-kernel, stable, kuba

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 3 · Low: 0 (2)

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] The commit message mischaracterises the defect: it pastes a
  KASAN "slab-out-of-bounds ... kmalloc-32 of size 32" report and…
- [Medium] The patch silently changes 'struct ieee802154_hdr hdr;' to
  'struct ieee802154_hdr hdr = { };' (ca8210.c:1761) without mentioning…
- [Medium] The new consolidated validation in ca8210_skb_rx() gates the
  security-header length check on priv->promiscuous…

Pre-existing issues:
- [Low] ca8210_net_rx() reads command[26] (== data_ind[24], the DSN of an
  MCPS_DATA_INDICATION) and latches it into priv->last_dsn…
- [Low] Concern (reported for completeness, assessed as not actionable):
  hdr is now explicitly zero-initialized and no path assigns hdr.seq,…

--

ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()

This moves all length and msdulen validation in ca8210_skb_rx() into a
single check at the top of the function, before dev_alloc_skb(), and adds
a new check that the reported SPI frame length covers the payload and the
optional security header.  The hdr declaration is also changed to be
zero-initialized.

A few questions below.

> In ca8210_skb_rx(), data_ind is read at offsets 22 (msdulen), 23
> (mpdulinkquality), 29 + msdulen (hdr.sec.level), 30..39 + msdulen
> (security header), and 29 .. 29 + msdulen (payload) without verifying
> that the received SPI frame length len covers those offsets, causing an
> out-of-bounds read when msdulen exceeds len - 30:
>
>   BUG: KASAN: slab-out-of-bounds in ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
>   Read of size 64 at addr ffff888006453ddd by task init/1
> ...
>   The buggy address belongs to the object at ffff888006453dc0
>    which belongs to the cache kmalloc-32 of size 32

[Severity: Medium]
Can this slab-out-of-bounds actually be reached from the driver's receive
path?

ca8210_skb_rx() has one caller chain:

  ca8210_spi_transfer_complete() -> ca8210_rx_done() -> ca8210_net_rx()
    -> ca8210_skb_rx(hw, len - 2, command + 2)

and the buffer is always cas_ctl->tx_in_buf, a fixed array embedded in a
kzalloc'd struct cas_control:

drivers/net/ieee802154/ca8210.c:
    struct cas_control {
        ...
        u8 tx_in_buf[CA8210_SPI_BUF_SIZE];
    ...
    };

with CA8210_SPI_BUF_SIZE == 256, not a kmalloc-32 allocation.  Since the
pre-existing check caps msdulen at IEEE802154_MTU (127), the furthest byte
the unpatched code can touch is data_ind[39 + 127] == tx_in_buf[168], and
skb_put_data() copies tx_in_buf[31..157] — all inside the 256-byte array.

The changelog's own note:

> Tested in QEMU with KASAN enabled by passing a short data_ind buffer with
> msdulen = 64 and len = 30 into ca8210_skb_rx().

suggests the splat came from a synthetic direct call on a 32-byte buffer
rather than from the SPI receive path.  On the real path the observable
effect of the missing checks looks like stale SPI buffer bytes (idle fill
0xFF, or residue from a previous exchange) being copied into the skb and
into hdr.sec, which is a data-integrity issue rather than memory
corruption.

Could the commit message describe the actual in-tree consequence, and
could the Fixes:/Cc: stable rationale be restated accordingly so stable
triage isn't misled by the pasted KASAN report?

> diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
> index 8aa7ffeb22d78..ab245add9ff5c 100644
> --- a/drivers/net/ieee802154/ca8210.c
> +++ b/drivers/net/ieee802154/ca8210.c
> @@ -1758,19 +1758,15 @@ static int ca8210_skb_rx(
>  	u8                    *data_ind
>  )
>  {
> -	struct ieee802154_hdr hdr;
> +	struct ieee802154_hdr hdr = { };

[Severity: Medium]
Is this zero-initialization a separate fix that deserves a mention in the
changelog?

ieee802154_hdr_push() unconditionally emits the sequence octet and both
frame control bytes:

net/ieee802154/header_ops.c:ieee802154_hdr_push() {
	...
	buf[pos++] = hdr->seq;
	...
	memcpy(buf, fc, 2);
	memcpy(skb_push(skb, pos), buf, pos);
}

while ca8210_skb_rx() only assigns hdr.fc.type, hdr.fc.security_enabled,
hdr.fc.intra_pan and the two address-mode fields, and never assigns
hdr.seq, hdr.fc.version (outside the security path), hdr.fc.ack_request,
hdr.fc.frame_pending or the reserved bits.  Before this hunk those bytes
were uninitialized stack data pushed into every reconstructed header handed
to mac802154 via ieee802154_rx_irqsafe(); after it they are deterministic
zeros.

The only trace of this is below the --- line ("dropped the unrelated
hdr.seq assignment"), which does not end up in the git log.  Should the
changelog describe this behaviour change, or should the hunk be split into
its own patch?

[Severity: Low]
This isn't a bug introduced by this patch, but with hdr now explicitly
zeroed and no path assigning hdr.seq, every non-promiscuous frame is
reported upstream with sequence number 0, even though the driver does read
the real DSN from the same message in ca8210_net_rx():

	if (command[26] == priv->last_dsn) {

Previously the emitted octet was stack garbage, so this is unchanged in
kind, and the v2 changelog records that wiring up hdr.seq was dropped at
the maintainer's request.  Noting it only so the intent is on record.

>  	int msdulen;
>  	int hlen;
> -	u8 mpdulinkquality = data_ind[23];
> +	u8 mpdulinkquality;
>  	struct sk_buff *skb;
>  	struct ca8210_priv *priv = hw->priv;
>  
> -	/* Allocate mtu size buffer for every rx packet */
> -	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
> -	if (!skb)
> -		return -ENOMEM;
> -
> -	skb_reserve(skb, sizeof(hdr));
> +	if (len < 30)
> +		return -EMSGSIZE;

[Severity: Low]
This is a pre-existing issue, but the caller parses the same message with
no minimum length at all, so the new "upfront" check is not the first
parse of the frame:

drivers/net/ieee802154/ca8210.c:ca8210_rx_done() {
	buf = cas_ctl->tx_in_buf;
	len = buf[1] + 2;
	if (len > sizeof(struct mac_message)) {
	...
}

drivers/net/ieee802154/ca8210.c:ca8210_net_rx() {
	...
	if (command[26] == priv->last_dsn) {
	...
	priv->last_dsn = command[26];
	spin_unlock_irqrestore(&priv->lock, flags);
	return ca8210_skb_rx(hw, len - 2, command + 2);
}

len is only bounded from above, so a data indication declaring buf[1] < 25
still has command[26] (== data_ind[24]) read and latched into
priv->last_dsn before ca8210_skb_rx() gets to reject it.  The read stays
inside tx_in_buf, but the byte is idle fill or residue, so last_dsn can be
poisoned and a later legitimate frame carrying that DSN is dropped as a
resend.

Would it make sense to also give ca8210_net_rx() a minimum length check
for SPI_MCPS_DATA_INDICATION, so both parsers of this message format agree
on its minimum size?

>  
>  	msdulen = data_ind[22]; /* msdu_length */
>  	if (msdulen > IEEE802154_MTU) {
> @@ -1778,9 +1774,25 @@ static int ca8210_skb_rx(
>  			&priv->spi->dev,
>  			"received erroneously large msdu length!\n"
>  		);
> -		kfree_skb(skb);
>  		return -EMSGSIZE;
>  	}
> +
> +	if (len < 30 + msdulen ||
> +	    (!priv->promiscuous && data_ind[29 + msdulen] > 0 &&
> +	     len < 29 + msdulen + sizeof(struct secspec))) {
> +		dev_err(&priv->spi->dev,
> +			"received truncated data indication!\n");
> +		return -EMSGSIZE;
> +	}

[Severity: Medium]
Can the two reads of priv->promiscuous disagree here?

The security-header length check is gated on !priv->promiscuous above, but
the decision to actually parse the security header is a second, independent
read a few lines later:

	if (priv->promiscuous)
		goto copy_payload;

	/* Populate hdr */
	hdr.sec.level = data_ind[29 + msdulen];
	...
		hdr.sec.key_id_mode = data_ind[30 + msdulen];
		memcpy(&hdr.sec.extended_src, &data_ind[31 + msdulen], 8);
		hdr.sec.key_id = data_ind[39 + msdulen];

priv->promiscuous is a plain bool written without any lock, READ_ONCE() or
barrier:

drivers/net/ieee802154/ca8210.c:ca8210_set_promiscuous_mode() {
	...
		priv->promiscuous = on;
}

drivers/net/ieee802154/ca8210.c:ca8210_reset_send() {
	...
	priv->promiscuous = false;
}

and ca8210_net_rx() drops priv->lock before calling ca8210_skb_rx():

	spin_unlock_irqrestore(&priv->lock, flags);
	return ca8210_skb_rx(hw, len - 2, command + 2);

So if the flag reads true at the check and false at the use — for example
a monitor-mode toggle from ca8210_set_promiscuous_mode() landing between
the two — the "len < 29 + msdulen + sizeof(struct secspec)" branch is
never evaluated, yet data_ind[29 + msdulen] through data_ind[39 + msdulen]
are dereferenced.  That reads up to 11 bytes past the end of the frame the
length check validated (stale tx_in_buf contents), producing a fabricated
hdr.sec or an ieee802154_hdr_push() failure and a dropped frame, which is
the exact byte class this patch sets out to stop touching.

Would taking one snapshot, e.g.

	bool promisc = READ_ONCE(priv->promiscuous);

and using it for both the check and the parse decision, or validating the
security-header length unconditionally, close that window?

> +
> +	mpdulinkquality = data_ind[23];
> +
> +	/* Allocate mtu size buffer for every rx packet */
> +	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
> +	if (!skb)
> +		return -ENOMEM;
> +
> +	skb_reserve(skb, sizeof(hdr));
>  	dev_dbg(&priv->spi->dev, "skb buffer length = %d\n", msdulen);
>  
>  	if (priv->promiscuous)

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260922093126.141969-1-benquike%40gmail.com

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()
  2026-09-24  6:22 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
@ 2026-09-24  6:22 ` Hui Peng
  2026-09-28  6:42   ` netdev-bot+sashiko
  0 siblings, 1 reply; 11+ messages in thread
From: Hui Peng @ 2026-09-24  6:22 UTC (permalink / raw)
  To: h.morris, alex.aring, miquel.raynal, stefan
  Cc: david.laight.linux, linux-wpan, netdev, linux-kernel, stable, Hui Peng

In ca8210_skb_rx(), data_ind is read at offsets 22 (msdulen), 23
(mpdulinkquality), 29 + msdulen (hdr.sec.level), 30..39 + msdulen
(security header), and 29 .. 29 + msdulen (payload) without verifying
that the received SPI frame length len covers those offsets, causing an
out-of-bounds read when msdulen exceeds len - 30:

  BUG: KASAN: slab-out-of-bounds in ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
  Read of size 64 at addr ffff888006453ddd by task init/1
  Call Trace:
   <TASK>
   dump_stack_lvl+0x70/0xa0
   print_report+0x153/0x4c6
   kasan_report+0xf1/0x120
   kasan_check_range+0x125/0x200
   __asan_memcpy+0x23/0x60
   ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
   ca8210_net_rx+0x96/0xc0
  ...
  The buggy address belongs to the object at ffff888006453dc0
   which belongs to the cache kmalloc-32 of size 32
  The buggy address is located 29 bytes inside of
   allocated 32-byte region [ffff888006453dc0, ffff888006453de0)

Consolidate all length and msdulen validations into a single upfront check
at the beginning of ca8210_skb_rx() before allocating the skb.

Tested in QEMU with KASAN enabled by passing a short data_ind buffer with
msdulen = 64 and len = 30 into ca8210_skb_rx().

Fixes: ded845a781a5 ("ieee802154: Add CA8210 IEEE 802.15.4 device driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- No changes.

Changes in v2:
- Split out as patch 3/3.
- Consolidated all length checks in ca8210_skb_rx() into a single place
  at the beginning of the function before dev_alloc_skb() and dropped the
  unrelated hdr.seq assignment as requested by Miquel Raynal.

 drivers/net/ieee802154/ca8210.c | 32 ++++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
index 8aa7ffe..ab245ad 100644
--- a/drivers/net/ieee802154/ca8210.c
+++ b/drivers/net/ieee802154/ca8210.c
@@ -1758,19 +1758,15 @@ static int ca8210_skb_rx(
 	u8                    *data_ind
 )
 {
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = { };
 	int msdulen;
 	int hlen;
-	u8 mpdulinkquality = data_ind[23];
+	u8 mpdulinkquality;
 	struct sk_buff *skb;
 	struct ca8210_priv *priv = hw->priv;
 
-	/* Allocate mtu size buffer for every rx packet */
-	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
-	if (!skb)
-		return -ENOMEM;
-
-	skb_reserve(skb, sizeof(hdr));
+	if (len < 30)
+		return -EMSGSIZE;
 
 	msdulen = data_ind[22]; /* msdu_length */
 	if (msdulen > IEEE802154_MTU) {
@@ -1778,9 +1774,25 @@ static int ca8210_skb_rx(
 			&priv->spi->dev,
 			"received erroneously large msdu length!\n"
 		);
-		kfree_skb(skb);
 		return -EMSGSIZE;
 	}
+
+	if (len < 30 + msdulen ||
+	    (!priv->promiscuous && data_ind[29 + msdulen] > 0 &&
+	     len < 29 + msdulen + sizeof(struct secspec))) {
+		dev_err(&priv->spi->dev,
+			"received truncated data indication!\n");
+		return -EMSGSIZE;
+	}
+
+	mpdulinkquality = data_ind[23];
+
+	/* Allocate mtu size buffer for every rx packet */
+	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
+	if (!skb)
+		return -ENOMEM;
+
+	skb_reserve(skb, sizeof(hdr));
 	dev_dbg(&priv->spi->dev, "skb buffer length = %d\n", msdulen);
 
 	if (priv->promiscuous)

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx()
  2026-09-22  9:30 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
@ 2026-09-22  9:30 ` Hui Peng
  2026-09-24  6:42   ` netdev-bot+sashiko
  0 siblings, 1 reply; 11+ messages in thread
From: Hui Peng @ 2026-09-22  9:30 UTC (permalink / raw)
  To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt
  Cc: Hui Peng, David Laight, linux-wpan, netdev, linux-kernel, stable

In ca8210_skb_rx(), data_ind is read at offsets 22 (msdulen), 23
(mpdulinkquality), 29 + msdulen (hdr.sec.level), 30..39 + msdulen
(security header), and 29 .. 29 + msdulen (payload) without verifying
that the received SPI frame length len covers those offsets, causing an
out-of-bounds read when msdulen exceeds len - 30:

  BUG: KASAN: slab-out-of-bounds in ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
  Read of size 64 at addr ffff888006453ddd by task init/1
  Call Trace:
   <TASK>
   dump_stack_lvl+0x70/0xa0
   print_report+0x153/0x4c6
   kasan_report+0xf1/0x120
   kasan_check_range+0x125/0x200
   __asan_memcpy+0x23/0x60
   ca8210_skb_rx.constprop.0.isra.0+0x137/0x160
   ca8210_net_rx+0x96/0xc0
  ...
  The buggy address belongs to the object at ffff888006453dc0
   which belongs to the cache kmalloc-32 of size 32
  The buggy address is located 29 bytes inside of
   allocated 32-byte region [ffff888006453dc0, ffff888006453de0)

Consolidate all length and msdulen validations into a single upfront check
at the beginning of ca8210_skb_rx() before allocating the skb.

Tested in QEMU with KASAN enabled by passing a short data_ind buffer with
msdulen = 64 and len = 30 into ca8210_skb_rx().

Fixes: ded845a781a5 ("ieee802154: Add CA8210 IEEE 802.15.4 device driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
Changes in v3:
- No changes.

Changes in v2:
- Split out as patch 3/3.
- Consolidated all length checks in ca8210_skb_rx() into a single place
  at the beginning of the function before dev_alloc_skb() and dropped the
  unrelated hdr.seq assignment as requested by Miquel Raynal.

 drivers/net/ieee802154/ca8210.c | 32 ++++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c
index 8aa7ffe..ab245ad 100644
--- a/drivers/net/ieee802154/ca8210.c
+++ b/drivers/net/ieee802154/ca8210.c
@@ -1758,19 +1758,15 @@ static int ca8210_skb_rx(
 	u8                    *data_ind
 )
 {
-	struct ieee802154_hdr hdr;
+	struct ieee802154_hdr hdr = { };
 	int msdulen;
 	int hlen;
-	u8 mpdulinkquality = data_ind[23];
+	u8 mpdulinkquality;
 	struct sk_buff *skb;
 	struct ca8210_priv *priv = hw->priv;
 
-	/* Allocate mtu size buffer for every rx packet */
-	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
-	if (!skb)
-		return -ENOMEM;
-
-	skb_reserve(skb, sizeof(hdr));
+	if (len < 30)
+		return -EMSGSIZE;
 
 	msdulen = data_ind[22]; /* msdu_length */
 	if (msdulen > IEEE802154_MTU) {
@@ -1778,9 +1774,25 @@ static int ca8210_skb_rx(
 			&priv->spi->dev,
 			"received erroneously large msdu length!\n"
 		);
-		kfree_skb(skb);
 		return -EMSGSIZE;
 	}
+
+	if (len < 30 + msdulen ||
+	    (!priv->promiscuous && data_ind[29 + msdulen] > 0 &&
+	     len < 29 + msdulen + sizeof(struct secspec))) {
+		dev_err(&priv->spi->dev,
+			"received truncated data indication!\n");
+		return -EMSGSIZE;
+	}
+
+	mpdulinkquality = data_ind[23];
+
+	/* Allocate mtu size buffer for every rx packet */
+	skb = dev_alloc_skb(IEEE802154_MTU + sizeof(hdr));
+	if (!skb)
+		return -ENOMEM;
+
+	skb_reserve(skb, sizeof(hdr));
 	dev_dbg(&priv->spi->dev, "skb buffer length = %d\n", msdulen);
 
 	if (priv->promiscuous)
-- 
2.49.0

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-10-04  7:39 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30  7:19 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
2026-09-30  7:19 ` [PATCH net v3 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng
2026-10-04  7:39   ` netdev-bot+sashiko
2026-09-30  7:19 ` [PATCH net v3 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng
2026-10-04  7:39   ` netdev-bot+sashiko
2026-09-30  7:19 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
2026-10-04  7:39   ` netdev-bot+sashiko
  -- strict thread matches above, loose matches on Subject: below --
2026-09-24  6:22 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
2026-09-24  6:22 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
2026-09-28  6:42   ` netdev-bot+sashiko
2026-09-22  9:30 [PATCH net v3 0/3] ieee802154: ca8210: fix stack and slab out-of-bounds accesses Hui Peng
2026-09-22  9:30 ` [PATCH net v3 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
2026-09-24  6:42   ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®