Repository navigation
[BUG] drivers/net/lan9250.c: .bss/global pktbuf overflow in lan9250_recv_buffer — RX-status packet length bounded only by DEBUGASSERT (SPI path only) #21112
Description
Description / Steps to reproduce the issue
In drivers/net/lan9250.c (LAN9250 Ethernet driver over SPI/QSPI) the received frame length taken
from the RX Status FIFO is used as a copy length into priv->pktbuf, bounded on the SPI path only
by an assert that compiles to nothing in the default build, and on the QSPI/SQI path by no capacity
check at all. priv is a static global object, so this is an out-of-bounds write into .bss
that first clobbers the structure members immediately following pktbuf (a watchdog timer and several
work structures) and then adjacent globals. The attacker is a peer on the same L2 network sending an
oversized frame, or a malicious/faulty LAN9250 PHY.
lan9250_receive() takes the received frame length from the LAN9250 RX Status FIFO register and
passes it as the copy length, with only a non-zero check:
/* drivers/net/lan9250.c:1747-1748 (lan9250_receive) */
regval = lan9250_get_reg(priv, LAN9250_RXSFR); /* RX Status FIFO register */
pktlen = (regval & RXSFF_PL_M) >> RXSFF_PL_S; /* 14-bit length field, 0..16383 */
if (pktlen) /* only a non-zero check */
lan9250_recv_buffer(priv, priv->dev.d_buf, pktlen); /* buflen = pktlen */The copy then runs into priv->pktbuf. On the SPI path the length is bounded only by a
DEBUGASSERT; on the QSPI/SQI path there is no capacity check:
/* drivers/net/lan9250.c:853 (inside #ifdef CONFIG_LAN9250_SPI) */
DEBUGASSERT(buflen <= sizeof(priv->pktbuf)); /* SPI path: default-off assert */
...
SPI_RECVBLOCK(priv->spi, buffer, buflen); /* buffer = priv->dev.d_buf = priv->pktbuf */
/* drivers/net/lan9250.c:881-899 (#else — QSPI/SQI path) */
meminfo.buflen = buflen; /* no capacity check at all */
QSPI_MEMORY(priv->qspi, &meminfo);priv->pktbuf is uint32_t pktbuf[LAN9250_PKTBUF_SIZE / 4] (:212), with
LAN9250_PKTBUF_SIZE = LAN9250_ALIGN(MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE + 4) (:142). In the
default IPv4/Ethernet config (CONFIG_NET_ETH_PKTSIZE = 590, CONFIG_NET_GUARDSIZE = 2) that is
ALIGN(596) = 596 bytes — about one max Ethernet frame. Any ordinary frame larger than ~596 bytes
(a normal 1514/1518-byte Ethernet frame already qualifies) therefore overflows it. pktlen is a
14-bit RX-status register field (max 16383); that width is the hardware status-field size, not a
demonstration that a peer can inject a 16 KB frame — the practically reachable and sufficient case is
simply "frame > ~596 bytes".
Memory class: the driver state is a static global, not a heap allocation:
/* drivers/net/lan9250.c:230 */
static struct lan9250_driver_s g_lan9250;
/* :2440 / :2512 */ priv = &g_lan9250; /* no kmm_malloc/kmm_zalloc of the driver state */pktbuf (:212) is immediately followed in the struct by txtout_timer (struct wdog_s),
txtout_work / txpoll_work / irq_work (struct work_s), and dev (:214-224). So the overflow
is a .bss/global-buffer overflow that corrupts those sibling members (watchdog + work queue
state) first, then neighbouring .bss objects — it is not a heap overflow.
Configuration dimension:
- SPI build,
CONFIG_DEBUG_ASSERTIONS = n(default): the guard is compiled out → overflow. - SPI build,
CONFIG_DEBUG_ASSERTIONS = y: the assert fires → controlled panic (DoS), no overflow. - QSPI/SQI build (
#elsepath): there is no capacity check in any configuration, so enabling
assertions does not prevent the overflow on QSPI/SQI targets.
Why this is a bug: the length is a parameter at the sink (lan9250_recv_buffer)
that originates in the caller (lan9250_receive), and the only guard anywhere on the SPI path is the
default-off DEBUGASSERT at :853 (and nothing at all on the QSPI path). The caller's if (pktlen)
check is non-zero-only, with no upper bound against MAX_NETDEV_PKTSIZE before handing the
device-reported length to the copy.
Steps:
- Build the LAN9250 driver (SPI with default
CONFIG_DEBUG_ASSERTIONSunset, or any QSPI/SQI build). - A peer on the same L2 network sends a frame larger than ~596 bytes (an ordinary full-size Ethernet
frame suffices), or a malicious/faulty LAN9250 PHY reports an oversized RX-status length. - The copy writes
pktlenbytes into the 596-byteg_lan9250.pktbuf, overflowing into the adjacent
watchdog/work members and.bss.
An AddressSanitizer build of the copy with the real buffer sizing (as a 596-byte global region)
reports a global-buffer-overflow WRITE.
Suggested fix: add an always-compiled check before lan9250_recv_buffer, and/or replace the
DEBUGASSERT, and add the equivalent check on the QSPI/SQI path:
if (pktlen > MAX_NETDEV_PKTSIZE)
{
/* drop oversized frame */
return;
}and in lan9250_recv_buffer replace the SPI DEBUGASSERT with if (buflen > sizeof(priv->pktbuf)) return;
and add the same guard before the QSPI QSPI_MEMORY transfer.
On which OS does this issue occur?
[OS: Linux]
What is the version of your OS?
ubuntu 22
NuttX Version
master
Issue Architecture
[Arch: all]
Issue Area
[Area: Drivers]
Host information
No response
Verification
- I have verified before submitting the report.
Activity
arnavsharma990 commentedon Oct 30, 2026
I have traced through the receive path myself, and agree that this is an issue. The RX length from hardware is passed into lan9250_recv_buffer() without proper runtime checks. SPI has no check (only DEBUGASSERT), and QSPI/SQI has no check.
I would recommend checking the length (including the CRC) in lan9250_rxdone_isr(). In lan9250_recv_buffer(), I would leave in the safety check.
The RX_DP_CTRL.RX_FFWD bit seems like a good option for recovery of the FIFO. This is used by Microchip in their reference implementation, and by CycloneTCP to drop packets. How exactly it works and how it handles short packets needs to be confirmed.
No board is required to do this at source level, however I would recommend doing some hardware testing to confirm the FIFO is being recovered, and packets are received. Overall this is a genuine issue to fix.