* [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() [not found] <20260919213637.3316595-1-benquike@gmail.com> @ 2026-09-21 7:42 ` Hui Peng 2026-09-21 9:08 ` David Laight 2026-09-22 8:32 ` netdev-bot+sashiko 2026-09-21 7:42 ` [PATCH net v2 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng 2026-09-21 7:42 ` [PATCH net v2 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng 2 siblings, 2 replies; 7+ messages in thread From: Hui Peng @ 2026-09-21 7:42 UTC (permalink / raw) To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt Cc: linux-wpan, netdev, linux-kernel, Hui Peng, stable In ca8210_rx_done(), the packet length len = buf[1] + 2 is only checked 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 (sizeof(struct mac_message) = 250 bytes) on the synchronous caller's stack, overflowing the stack buffer by up to 6 bytes: BUG: KASAN: stack-out-of-bounds in ca8210_rx_done+0xc4/0xe0 Write of size 256 at addr ffff888001907660 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 ca8210_rx_done+0xc4/0xe0 ca8210_spi_exchange+0x12e/0x190 ... The buggy address belongs to stack of task init/1 and is located at offset 32 in frame: ca8210_spi_exchange+0x0/0x190 This frame has 1 object: [32, 282) 'response' Bound the synchronous response memcpy() length to min_t(size_t, len, sizeof(struct mac_message)). 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 v2: - Split the ca8210 fixes into three single-issue patches (1/3..3/3). drivers/net/ieee802154/ca8210.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c index 01af4f9..c009930 100644 --- a/drivers/net/ieee802154/ca8210.c +++ b/drivers/net/ieee802154/ca8210.c @@ -697,7 +697,8 @@ static void ca8210_rx_done(struct cas_control *cas_ctl) if (buf[0] & SPI_SYN) { if (priv->sync_command_response) { - memcpy(priv->sync_command_response, buf, len); + memcpy(priv->sync_command_response, buf, + min_t(size_t, len, sizeof(struct mac_message))); complete(&priv->sync_exchange_complete); } else { if (cascoda_api_upstream) -- 2.47.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() 2026-09-21 7:42 ` [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng @ 2026-09-21 9:08 ` David Laight 2026-09-22 8:32 ` netdev-bot+sashiko 1 sibling, 0 replies; 7+ messages in thread From: David Laight @ 2026-09-21 9:08 UTC (permalink / raw) To: Hui Peng Cc: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt, linux-wpan, netdev, linux-kernel, stable On Mon, 21 Sep 2026 07:42:03 +0000 Hui Peng <benquike@gmail.com> wrote: > In ca8210_rx_done(), the packet length len = buf[1] + 2 is only checked > 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 (sizeof(struct mac_message) = 250 bytes) on the synchronous > caller's stack, overflowing the stack buffer by up to 6 bytes: > > BUG: KASAN: stack-out-of-bounds in ca8210_rx_done+0xc4/0xe0 > Write of size 256 at addr ffff888001907660 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 > ca8210_rx_done+0xc4/0xe0 > ca8210_spi_exchange+0x12e/0x190 > ... > The buggy address belongs to stack of task init/1 > and is located at offset 32 in frame: > ca8210_spi_exchange+0x0/0x190 > This frame has 1 object: > [32, 282) 'response' > > Bound the synchronous response memcpy() length to > min_t(size_t, len, sizeof(struct mac_message)). Why min_t() ? len is unsigned so min() is fine. Shouldn't there also be some kind of indication the data was truncated? David > > 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 v2: > - Split the ca8210 fixes into three single-issue patches (1/3..3/3). > > drivers/net/ieee802154/ca8210.c | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c > index 01af4f9..c009930 100644 > --- a/drivers/net/ieee802154/ca8210.c > +++ b/drivers/net/ieee802154/ca8210.c > @@ -697,7 +697,8 @@ static void ca8210_rx_done(struct cas_control *cas_ctl) > > if (buf[0] & SPI_SYN) { > if (priv->sync_command_response) { > - memcpy(priv->sync_command_response, buf, len); > + memcpy(priv->sync_command_response, buf, > + min_t(size_t, len, sizeof(struct mac_message))); > complete(&priv->sync_exchange_complete); > } else { > if (cascoda_api_upstream) ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() 2026-09-21 7:42 ` [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng 2026-09-21 9:08 ` David Laight @ 2026-09-22 8:32 ` netdev-bot+sashiko 1 sibling, 0 replies; 7+ messages in thread From: netdev-bot+sashiko @ 2026-09-22 8:32 UTC (permalink / raw) To: benquike Cc: h.morris, alex.aring, miquel.raynal, stefan, 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 (2) · Medium: 2 · 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). - [Medium] The commit message's factual basis is wrong. - [Medium] ca8210_rx_done() still validates the device-declared frame length against the software buffer size instead of the number of bytes… Pre-existing issues: - [High] Unsynchronized stale stack pointer: ca8210_rx_done() writes device data through priv->sync_command_response, a bare pointer to the… - [High] Sleeping calls in the spi_async() completion callback. - [Low] Device-supplied length in the synchronous response consumer: hwme_get_request_sync() copies… -- ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() This patch clamps the synchronous response memcpy() in ca8210_rx_done() to min_t(size_t, len, sizeof(struct mac_message)) so that a device-declared length larger than the destination object cannot overwrite the synchronous caller's stack frame. A few questions about the change and the commit message below. The commit message states: > 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 (sizeof(struct mac_message) = 250 bytes) on the synchronous > caller's stack, overflowing the stack buffer by up to 6 bytes: [Severity: Medium] Is the 250 byte figure correct? struct mac_message in this file is: struct mac_message { u8 command_id; u8 length; union { struct mcps_data_request_pset data_req; ... u8 payload[148]; } pdata; }; All members are u8, so the alignment is 1 and the largest union member is payload[148] (data_req is 1+11+1+1+1+MAX_DATA_SIZE(114) = 129, set_req is 3+MAX_ATTRIBUTE_SIZE(122) = 125, hwme_get_cnf is 3+MAX_HWME_ATTRIBUTE_SIZE(16) = 19). That gives 2 + 148 = 150 bytes, not 250. If that is right, then with len bounded only by CA8210_SPI_BUF_SIZE the pre-patch worst case wrote 256 bytes into a 150 byte object, i.e. up to 106 bytes out of bounds rather than 6. Could the description be corrected, since stable backporters will be triaging this based on the stated magnitude? The pasted report also says: > The buggy address belongs to stack of task init/1 > and is located at offset 32 in frame: > ca8210_spi_exchange+0x0/0x190 > This frame has 1 object: > [32, 282) 'response' Can this frame come from this driver? ca8210_spi_exchange() only receives a pointer: static int ca8210_spi_exchange( const u8 *buf, size_t len, u8 *response, void *device_ref ) The struct mac_message response objects live in the sync helpers (tdme_setsfr_request_sync(), mlme_*_request_sync(), hwme_*_request_sync()) and are 150 bytes, so a 250 byte local named response in ca8210_spi_exchange() does not appear to exist. Could the actual reproducer output be included instead? > diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c > index 01af4f9cf7f2..c009930da77a 100644 > --- a/drivers/net/ieee802154/ca8210.c > +++ b/drivers/net/ieee802154/ca8210.c > @@ -697,7 +697,8 @@ static void ca8210_rx_done(struct cas_control *cas_ctl) > > if (buf[0] & SPI_SYN) { > if (priv->sync_command_response) { > - memcpy(priv->sync_command_response, buf, len); > + memcpy(priv->sync_command_response, buf, > + min_t(size_t, len, sizeof(struct mac_message))); [Severity: Medium] Would it be better to fix the length validation instead of clamping one consumer? ca8210_rx_done() still validates the device-declared length against the software buffer size: len = buf[1] + 2; if (len > CA8210_SPI_BUF_SIZE) { dev_crit(&priv->spi->dev, "Received packet len (%u) erroneously long\n", len); goto finish; } but every exchange only clocks in sizeof(struct mac_message) bytes, into a buffer pre-filled with the idle pattern, in ca8210_spi_transfer(): memset(cas_ctl->tx_in_buf, SPI_IDLE, CA8210_SPI_BUF_SIZE); ... cas_ctl->transfer.len = sizeof(struct mac_message); So for any declared len between 151 and 256, bytes 150..len-1 are the driver's own 0xFF fill and were never driven by the CA8210. The patch bounds the sync memcpy only, while the same unbounded len is still handed to the other two consumers in this function: cascoda_api_upstream(buf, len, priv->spi); ... ca8210_net_rx(priv->hw, buf, len); ca8210_net_rx() then calls ca8210_skb_rx(hw, len - 2, command + 2), so up to 106 bytes of idle fill can be delivered as 802.15.4 payload and exported through the debugfs test FIFO. This is reachable with well-formed traffic too: a maximum size MCPS_DATA_indication (msdu 127 bytes at data_ind offset 29, plus an 11 byte secspec) declares len in the 158..169 range, so its tail is replaced by 0xFF. Would bounding the existing check by sizeof(struct mac_message) and dropping the frame close this for all three consumers at once? On the sync path the truncated copy is also followed unconditionally by complete(&priv->sync_exchange_complete), so ca8210_spi_exchange() returns 0 and the caller parses a struct mac_message whose length field describes more payload than was delivered. Should a truncated confirm be reported as an error rather than success? [Severity: High] This isn't a bug introduced by this patch, but does the clamp cover the lifetime of the destination as well as its size? priv->sync_command_response is a bare pointer to the synchronous caller's on-stack struct mac_message, published without any lock in ca8210_spi_exchange(): if ((buf[0] & SPI_SYN) && response) { /* if sync wait for confirm */ reinit_completion(&priv->sync_exchange_complete); priv->sync_command_response = response; } On the -ETIME (CA8210_SYNC_TIMEOUT) and -ERESTARTSYS paths it simply clears the pointer and returns, without cancelling or flushing the in-flight SPI message: } else if (wait_remaining == 0) { dev_err(&spi->dev, "Synchronous confirm timeout\n"); status = -ETIME; } cleanup: priv->sync_command_response = NULL; return status; The write side runs from the spi_async() completion callback, ca8210_spi_transfer_complete() -> ca8210_rx_done(), and the confirm read is queued straight from the hard IRQ handler ca8210_interrupt_handler() -> ca8210_spi_transfer() -> spi_async(). Neither side takes priv->lock and there is no READ_ONCE() or barrier, so can this happen? CPU0 (spi completion) CPU1 (sync caller) if (priv->sync_command_response) /* non-NULL */ wait ... times out priv->sync_command_response = NULL; return -ETIME; /* tdme_setsfr_request_sync() etc. returns, frame reused */ memcpy(priv->sync_command_response, buf, ... ); /* writes dead stack frame */ Since the pointer is a plain load used both for the NULL test and as the memcpy destination, and memcpy() is expanded inline through fortify-string, could the compiler also reload it and observe the concurrent NULL store, giving memcpy(NULL, ...)? Would landing the confirm in a driver-owned buffer, or quiescing the completion path before clearing the pointer, be a better fix than bounding the length? > complete(&priv->sync_exchange_complete); > } else { > if (cascoda_api_upstream) [Severity: High] This is a pre-existing issue and not something this patch introduced, but while this function is being touched: can ca8210_rx_done() sleep in the context it runs in? It is called directly from ca8210_spi_transfer_complete(), which is installed as cas_ctl->msg.complete and submitted with spi_async(). The spi_async() kernel-doc in drivers/spi/spi.c states "The completion callback is invoked in a context which can't sleep", and ca8210_spi_transfer() itself allocates with GFP_ATOMIC. Further down in the same function, for buf[0] == SPI_MCPS_DATA_CONFIRM with buf[3] == IEEE802154_TRANSACTION_OVERFLOW: msleep(2000); ... mlme_reset_wpc = kmalloc_obj(*mlme_reset_wpc); msleep() and the implicit GFP_KERNEL allocation can both sleep, and the test path reaches ca8210_test_int_driver_write() which does kmemdup(..., GFP_KERNEL) as well. On controllers that finalize messages from hard IRQ or DMA completion context, would this produce a "sleeping function called from invalid context" splat, and on kthread-pump controllers stall the bus for two seconds inside the completion callback? The MLME reset is already deferred to priv->mlme_workqueue, so could the msleep() and the allocation move into ca8210_mlme_reset_worker()? [Severity: Low] This is a pre-existing issue rather than something introduced here, and the out-of-bounds part of it is already addressed by patch 2/3 of this series, which adds: 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; together with u8 lenvar = 1 in ca8210_get_ed(). What remains after the series is that struct mac_message response is not zero-initialized in hwme_get_request_sync() and the mlme_*_request_sync() helpers, unlike tdme_setsfr_request_sync() which sets response.command_id = SPI_IDLE. Since ca8210_rx_done() now copies only the bytes the device actually sent, can a short confirm leave response.command_id, status or the attribute bytes as stale stack data, so that a wrong ED level or status is returned? Would zero-initializing response in those helpers close that? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260921074205.2289211-1-benquike%40gmail.com ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v2 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() [not found] <20260919213637.3316595-1-benquike@gmail.com> 2026-09-21 7:42 ` [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng @ 2026-09-21 7:42 ` Hui Peng 2026-09-22 8:32 ` netdev-bot+sashiko 2026-09-21 7:42 ` [PATCH net v2 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng 2 siblings, 1 reply; 7+ messages in thread From: Hui Peng @ 2026-09-21 7:42 UTC (permalink / raw) To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt Cc: linux-wpan, netdev, linux-kernel, Hui Peng, stable 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 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 c009930..1de6314 100644 --- a/drivers/net/ieee802154/ca8210.c +++ b/drivers/net/ieee802154/ca8210.c @@ -1678,6 +1678,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( @@ -2028,7 +2031,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( -- 2.47.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() 2026-09-21 7:42 ` [PATCH net v2 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng @ 2026-09-22 8:32 ` netdev-bot+sashiko 0 siblings, 0 replies; 7+ messages in thread From: netdev-bot+sashiko @ 2026-09-22 8:32 UTC (permalink / raw) To: benquike Cc: h.morris, alex.aring, miquel.raynal, stefan, 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: 0 · Medium: 3 · 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). - [Medium] The commit message presents the KASAN stack-out-of-bounds splat as a live, device-triggerable overflow (Fixes: + Cc: stable,… - [Medium] The new bound check in hwme_get_request_sync() (drivers/net/ieee802154/ca8210.c:1681-1683) is one-sided: it only… - [Medium] hwme_get_request_sync() still declares `struct mac_message command, response;` with no initialiser (ca8210.c:1663) and then makes… - [Low] The patch converts `hw_attribute_length` of `hwme_get_request_sync()` into an in/out parameter — the caller must… -- ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() This adds an upper-bound check in hwme_get_request_sync() so that a HWME-GET.confirm reporting a hw_attribute_length larger than the caller's destination buffer is rejected with IEEE802154_SYSTEM_ERROR, and initialises lenvar = 1 in ca8210_get_ed() so the helper knows the capacity of the one-byte level buffer. A few questions about the change and the changelog below. > 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 > ... > [ ... ] > Fixes: ded845a781a5 ("ieee802154: Add CA8210 IEEE 802.15.4 device driver") > Cc: stable@vger.kernel.org [Severity: Medium] Is this splat reachable from anything in tree today? ca8210_get_ed() is only installed as the .ed member of ca8210_phy_ops, and the only reference to that member in the 802.15.4 stack is the presence check in ieee802154_alloc_hw(): net/mac802154/main.c:ieee802154_alloc_hw() { if (WARN_ON(!ops || !(ops->xmit_async || ops->xmit_sync) || !ops->ed || !ops->start || !ops->stop || !ops->set_channel)) return NULL; } There is no drv_* wrapper, cfg802154 op or netlink path that actually calls ops->ed, and ca8210_get_ed() is the single caller of hwme_get_request_sync(). The changelog itself notes the trace came from passing "an oversized hw_attribute_length response into ca8210_get_ed()" in QEMU. Could the changelog say that the path is currently only reachable through the never-invoked .ed callback? With a Fixes: tag, a Cc: stable and a call trace that is truncated right above ca8210_get_ed(), a stable backporter would reasonably conclude that shipping kernels have a device-reachable stack overflow here. > diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c > index c009930da77a..1de63142a45a 100644 > --- a/drivers/net/ieee802154/ca8210.c > +++ b/drivers/net/ieee802154/ca8210.c > @@ -1678,6 +1678,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( [Severity: Medium] Should this check also reject a shorter-than-expected length? The test only covers lengths larger than the caller's capacity. A confirm with status == IEEE802154_SUCCESS and hw_attribute_length == 0 still takes the success path: *hw_attribute_length becomes 0, the memcpy copies nothing, and IEEE802154_SUCCESS is returned. ca8210_get_ed() discards lenvar, so link_to_linux_err(IEEE802154_SUCCESS) returns 0: drivers/net/ieee802154/ca8210.c:ca8210_get_ed() { u8 lenvar = 1; struct ca8210_priv *priv = hw->priv; return link_to_linux_err( hwme_get_request_sync(HWME_EDVALUE, &lenvar, level, priv->spi) ); } Does that report a successful energy-detect read while *level still holds the caller's uninitialised stack byte, contrary to the ieee802154_ops::ed contract in include/net/mac802154.h that a 0 return means level was filled in? HWME attribute sizes are fixed and known to the caller, so would an exact length comparison (!= against the caller-supplied expected length) cover both directions of the mismatch in one check? [Severity: Medium] Can this check be made on never-written stack memory? hwme_get_request_sync() declares its response with no initialiser: struct mac_message command, response; The only writer of that memory is ca8210_rx_done(), which copies exactly as many bytes as the device claims: drivers/net/ieee802154/ca8210.c:ca8210_rx_done() { buf = cas_ctl->tx_in_buf; len = buf[1] + 2; ... if (buf[0] & SPI_SYN) { if (priv->sync_command_response) { memcpy(priv->sync_command_response, buf, min_t(size_t, len, sizeof(struct mac_message))); } SPI_HWME_GET_CONFIRM is HWME_GET_CONFIRM + SPI_S2M + SPI_SYN, so a frame with buf[0] = 0x73 and buf[1] = 0 passes the if (response.command_id != SPI_HWME_GET_CONFIRM) gate while leaving .status, .hw_attribute_length and .hw_attribute_value[] as stack garbage. If the garbage status happens to be 0 and the garbage length happens to be 0 or 1, does this copy up to one byte of uninitialised kernel stack into the caller's *level and return success? And for other garbage status values, does link_to_linux_err() map them to an arbitrary errno? Would zeroing response, or validating response.length against the size of struct hwme_get_confirm_pset, close this along with the out-of-bounds write the patch targets? > @@ -2028,7 +2031,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: Low] This isn't a bug, but the kernel-doc above hwme_get_request_sync() no longer matches the new calling convention: * @hw_attribute_length: Attribute length * @hw_attribute_value: Pointer to Attribute Value * @device_ref: Nondescript pointer to target device * * Return: 802.15.4 status code of HWME-GET.confirm hw_attribute_length is now an in/out parameter that the caller must pre-initialise with the capacity of hw_attribute_value, which is what makes lenvar = 1 load-bearing here, and the function can now return IEEE802154_SYSTEM_ERROR for a purely local capacity rejection rather than a device confirm status (which link_to_linux_err() turns into -EPROTO). Could the kernel-doc be updated to state both, so a future caller reading only the documented interface does not pass an uninitialised or zero length and reintroduce the overflow or the silent truncation? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260921074205.2289211-1-benquike%40gmail.com ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v2 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() [not found] <20260919213637.3316595-1-benquike@gmail.com> 2026-09-21 7:42 ` [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng 2026-09-21 7:42 ` [PATCH net v2 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng @ 2026-09-21 7:42 ` Hui Peng 2026-09-22 8:32 ` netdev-bot+sashiko 2 siblings, 1 reply; 7+ messages in thread From: Hui Peng @ 2026-09-21 7:42 UTC (permalink / raw) To: Harry Morris, Alexander Aring, Miquel Raynal, Stefan Schmidt Cc: linux-wpan, netdev, linux-kernel, Hui Peng, 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 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 1de6314..2aa7d8c 100644 --- a/drivers/net/ieee802154/ca8210.c +++ b/drivers/net/ieee802154/ca8210.c @@ -1759,19 +1759,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) { @@ -1779,9 +1775,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.47.3 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() 2026-09-21 7:42 ` [PATCH net v2 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng @ 2026-09-22 8:32 ` netdev-bot+sashiko 0 siblings, 0 replies; 7+ messages in thread From: netdev-bot+sashiko @ 2026-09-22 8:32 UTC (permalink / raw) To: benquike Cc: h.morris, alex.aring, miquel.raynal, stefan, 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: 0 · Medium: 3 · 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). - [Medium] The commit message misrepresents the defect being fixed. - [Medium] The patch silently changes `struct ieee802154_hdr hdr;` to `struct ieee802154_hdr hdr = { };` in ca8210_skb_rx()… - [Medium] The consolidated validation is placed in the callee, but the caller already parses the same MCPS_DATA_indication at a fixed offset… - [Low] The newly added validation and the code it protects read priv->promiscuous twice, with no snapshot, lock, or READ_ONCE(). -- ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() This moves all length and msdulen validation for an MCPS_DATA_indication to the top of ca8210_skb_rx(), before dev_alloc_skb(), and adds a check that the declared SPI frame length covers the payload and the optional security header. The struct ieee802154_hdr on the stack is also changed to be zero-initialised. First, a question about the commit message itself: > 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 > ... > The buggy address belongs to the object at ffff888006453dc0 > which belongs to the cache kmalloc-32 of size 32 [Severity: Medium] Can this kmalloc-32 slab-out-of-bounds actually be reached in-tree? There is only one caller of ca8210_skb_rx(), and it passes a pointer into a fixed-size array rather than a len-sized allocation: drivers/net/ieee802154/ca8210.c:ca8210_net_rx() { ... return ca8210_skb_rx(hw, len - 2, command + 2); ... } command is always cas_ctl->tx_in_buf: struct cas_control { ... u8 tx_buf[CA8210_SPI_BUF_SIZE]; u8 tx_in_buf[CA8210_SPI_BUF_SIZE]; ... }; With msdulen rejected above IEEE802154_MTU (127), the highest byte the parser can touch is data_ind[39 + 127], i.e. tx_in_buf[168], which is inside the 256-byte array, and the payload copy tops out at tx_in_buf[157]. The same was true before the patch. The test note also suggests the splat comes from calling the static function directly rather than from the driver path: > Tested in QEMU with KASAN enabled by passing a short data_ind buffer with > msdulen = 64 and len = 30 into ca8210_skb_rx(). What the driver does appear to do without the new checks is consume bytes beyond the firmware-declared frame length (leftover bytes of the same SPI exchange, or SPI_IDLE fill) and copy them into the skb handed to ieee802154_rx_irqsafe() - a stale/garbage frame rather than memory corruption. Given the Fixes: tag and Cc: stable, would it be worth rewording the message so backporters do not triage this as a remotely triggerable slab out-of-bounds read? > diff --git a/drivers/net/ieee802154/ca8210.c b/drivers/net/ieee802154/ca8210.c > index 1de63142a45a..2aa7d8c8ae00 100644 > --- a/drivers/net/ieee802154/ca8210.c > +++ b/drivers/net/ieee802154/ca8210.c > @@ -1759,19 +1759,15 @@ static int ca8210_skb_rx( > u8 *data_ind > ) > { > - struct ieee802154_hdr hdr; > + struct ieee802154_hdr hdr = { }; [Severity: Medium] Should this hunk be described in the commit message? The body only mentions consolidating the length and msdulen validation, but this line looks like a separate fix with a visible behaviour change. ca8210_skb_rx() never assigns hdr.seq, hdr.sec.frame_counter, or the fc bits frame_pending/ack_request/version/reserved, yet ieee802154_hdr_push() serialises them unconditionally: net/ieee802154/header_ops.c:ieee802154_hdr_push() { ... buf[pos++] = hdr->seq; ... memcpy(buf, fc, 2); ... } and for security-enabled frames: net/ieee802154/header_ops.c:ieee802154_hdr_push_sechdr() { ... memcpy(buf + 1, &hdr->frame_counter, 4); ... } So before this change, every non-promiscuous received frame carried a few bytes of uninitialised stack in the reconstructed MAC header delivered to the 802.15.4 stack. Zeroing closes that, but also makes the reported sequence number always 0 and clears the fc bits. Would it make sense to split this out with its own description and Fixes: tag? > 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] Is the callee the right place for this check, given that the only caller already parses the same indication at a fixed offset first? drivers/net/ieee802154/ca8210.c:ca8210_net_rx() { ... 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); ... } command[26] is data_ind[24], which is inside the region the new len >= 30 check is meant to guarantee. len itself comes straight from the device: drivers/net/ieee802154/ca8210.c:ca8210_rx_done() { ... len = buf[1] + 2; if (len > CA8210_SPI_BUF_SIZE) { ... } only the upper bound is checked, so a declared length of 2..31 still reaches the offset-26 read. Can that latch priv->last_dsn from a byte outside the declared frame, so that a later genuine frame whose DSN matches is dropped as a resend, even though the frame that set it is then rejected with -EMSGSIZE? Would moving the validation (or the last_dsn update) ahead of the DSN read close that gap? > > msdulen = data_ind[22]; /* msdu_length */ > if (msdulen > IEEE802154_MTU) { > @@ -1779,9 +1775,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))) { [Severity: Low] This check and the code it protects read priv->promiscuous twice, with no snapshot, lock or READ_ONCE(). The stronger bound (len >= 40 + msdulen) is only applied here when !priv->promiscuous, and the decision to parse the security header is taken from a second read further down: if (priv->promiscuous) goto copy_payload; /* Populate hdr */ hdr.sec.level = data_ind[29 + msdulen]; ... hdr.sec.key_id = data_ind[39 + msdulen]; priv->promiscuous is a plain bool written from ca8210_set_promiscuous_mode() in process context and from ca8210_reset_send(), while the RX path runs from ca8210_spi_transfer_complete() -> ca8210_rx_done() -> ca8210_net_rx() -> ca8210_skb_rx(). If the flag goes true to false (or is simply reloaded) between the two reads, does the security header parse then run on a frame only validated to 30 + msdulen bytes, reading up to 10 bytes past the declared frame length and pushing them into the reconstructed header? Would taking one snapshot at entry, or making the len >= 40 + msdulen requirement unconditional whenever the security-level byte is non-zero, be preferable? > + 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)); [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260921074205.2289211-1-benquike%40gmail.com ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-22 8:32 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <20260919213637.3316595-1-benquike@gmail.com>
2026-09-21 7:42 ` [PATCH net v2 1/3] ieee802154: ca8210: prevent stack buffer overflow in ca8210_rx_done() Hui Peng
2026-09-21 9:08 ` David Laight
2026-09-22 8:32 ` netdev-bot+sashiko
2026-09-21 7:42 ` [PATCH net v2 2/3] ieee802154: ca8210: prevent stack buffer overflow in hwme_get_request_sync() Hui Peng
2026-09-22 8:32 ` netdev-bot+sashiko
2026-09-21 7:42 ` [PATCH net v2 3/3] ieee802154: ca8210: validate data_ind length upfront in ca8210_skb_rx() Hui Peng
2026-09-22 8:32 ` 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®