[PATCH net-next v2 08/11] net: ethernet: cortina: Validate RX fragment lengths
From: Linus Walleij <linusw@kernel.org>
Date: 2026-09-28 08:51:02
Subsystem:
arm/cortina systems gemini arm architecture, networking drivers, the rest · Maintainers:
Hans Ulli Kroll, Linus Walleij, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
RX descriptor lengths are used to adjust the page offset and populate skb fragments without checking that the resulting range remains inside the posted free queue fragment. A zero-length descriptor is logged but is still appended. The backing page is larger than the default 2 KiB DMA fragment, so checking only the page boundary would allow a malformed descriptor for the first half of a page to consume data from the sibling fragment. That fragment may still be owned by the device. Reject a short initial fragment before applying NET_IP_ALIGN, reject frame length underflow and ranges extending beyond either the DMA fragment or the page, and drop zero-length fragments instead of adding them to the skb. Count these drops as receive and length errors. Assisted-by: LLM Signed-off-by: Linus Walleij <linusw@kernel.org> --- drivers/net/ethernet/cortina/gemini.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index d677d7431ab2..258bb44d5570 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c@@ -1593,6 +1593,7 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget, struct gemini_ethernet_port *port = netdev_priv(netdev); unsigned short m = (1 << port->rxq_order) - 1; struct gemini_ethernet *geth = port->geth; + unsigned int freeq_frag_len = 1 << geth->freeq_frag_order; void __iomem *ptr_reg = port->rxq_rwptr; unsigned int frag_nr = port->rx_frag_nr; struct sk_buff *skb = port->rx_skb;
@@ -1667,6 +1668,9 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget, if (!skb) goto err_drop; + if (frag_len < NET_IP_ALIGN) + goto err_length; + page_offs += NET_IP_ALIGN; frag_len -= NET_IP_ALIGN; frag_nr = 0;
@@ -1675,15 +1679,26 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget, goto err_drop; } - if (word3.bits32 & EOF_BIT) + if (word3.bits32 & EOF_BIT) { + if (frame_len < skb->len) + goto err_length; frag_len = frame_len - skb->len; + } /* append page frag to skb */ if (frag_nr == MAX_SKB_FRAGS) goto err_drop; + if (frag_len > freeq_frag_len - + (page_offs & (freeq_frag_len - 1)) || + frag_len > PAGE_SIZE - page_offs) + goto err_length; - if (frag_len == 0 && net_ratelimit()) - netdev_err(netdev, "Received fragment with len = 0\n"); + if (!frag_len) { + if (net_ratelimit()) + netdev_err(netdev, + "Received fragment with len = 0\n"); + goto err_length; + } skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len); skb->len += frag_len;
@@ -1698,6 +1713,11 @@ static unsigned int gmac_rx(struct net_device *netdev, unsigned int budget, } goto next_desc; +err_length: + if (!dropping) { + port->stats.rx_errors++; + port->stats.rx_length_errors++; + } err_drop: if (skb) { napi_free_frags(&port->napi);
--
2.55.0