Skip to content

[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

Open

Description

@K-ANOY

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 (#else path): 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:

  1. Build the LAN9250 driver (SPI with default CONFIG_DEBUG_ASSERTIONS unset, or any QSPI/SQI build).
  2. 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.
  3. The copy writes pktlen bytes into the 596-byte g_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

  1. added
    Arch: allIssues that apply to all architectures
    OS: LinuxIssues related to Linux (building system, etc)
    on Oct 29, 2026
  2. arnavsharma990 commented on Oct 30, 2026

    @arnavsharma990
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Arch: allIssues that apply to all architecturesArea: DriversDrivers issuesOS: LinuxIssues related to Linux (building system, etc)Type: BugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions