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.
Require the final frame length to cover an Ethernet frame and fit in the
current descriptor so a malformed chain cannot pass a short skb to GRO.
Account these descriptor inconsistencies as drops rather than wire length
errors.
The issue was found by inspecting the descriptor arithmetic and validated
against the Gemini receive descriptor definitions. The series was tested on
a D-Link DIR-685 with media playback and ksmbd transfers under ping load.
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/ethernet/cortina/gemini.c | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 943c31c02674..d8bafcdf4d5f 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c
@@ -1737,6 +1737,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;
@@ -1811,6 +1812,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_drop;
+
page_offs += NET_IP_ALIGN;
frag_len -= NET_IP_ALIGN;
frag_nr = 0;
@@ -1819,15 +1823,28 @@ 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 < ETH_ZLEN ||
+ frame_len < skb->len ||
+ frame_len - skb->len > frag_len)
+ goto err_drop;
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_drop;
- 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_drop;
+ }
skb_fill_page_desc(skb, frag_nr, page, page_offs, frag_len);
skb->len += frag_len;
--
2.56.0