From c4ba1716853de5178a9df785844ebb5e397a80e6 Mon Sep 17 00:00:00 2001 From: Federico Musto Date: Tue, 27 Oct 2026 12:51:51 +0100 Subject: [PATCH] net: atl1c: drop oversized RX frames instead of panicking atl1c sizes its receive buffers from the netdev MTU plus a fixed set of L2 overheads, which comes to 1522 bytes at the default MTU. A link partner that sends a longer frame - in practice a MikroTik CCR2004-1G-2XS-PCIe, whose RouterOS side defaults to l2mtu 1600 while the host stays at MTU 1500 - makes the length reported by the RX return descriptor exceed what the hardware wrote, and skb_put() then either includes bytes that were never received or trips skb_over_panic and takes the host down. Compare the descriptor length against buffer_info->length, the length the hardware was given, rather than against skb_tailroom(): the skb carries 64 bytes of slack for the DMA erratum, so tailroom is larger than the DMA buffer and a frame in between would pass the check and still be delivered with a tail the device never wrote. The same test rejects frames shorter than ETH_HLEN + ETH_FCS_LEN; before this change a reported length below ETH_FCS_LEN underflowed in skb_put(). The frame is dropped with kfree_skb_reason(SKB_DROP_REASON_PKT_TOO_BIG) so drop_monitor and the kfree_skb tracepoint see it, and counted through dev_core_stats_rx_dropped_inc(), which survives atl1c_get_stats() overwriting the hardware counters on every read. It also counts as work: atl1c_clean_rfd() has already recycled the RFD, and the refill at the end of the poll only runs when work_done is non-zero, so sustained oversized traffic would otherwise drain the ring and stop RX. Tested on a MikroTik CCR2004-1G-2XS-PCIe (4x AR8151) in a Proxmox VE host, with atl1c built from a DKMS tree carrying this change: the l2mtu-1600 traffic that panics the stock driver is dropped and counted with the patch applied, and received normally once the host MTU is raised to match. This complements commit 36c2009d90f2 ("net: atl1c: fix soft lockup on out-of-range tpd_cons read"), which fixed the TX side of the same "host dies while the card resets" problem. Fixes: 43250ddd75a3 ("atl1c: Atheros L1C Gigabit Ethernet driver") Cc: stable@vger.kernel.org Signed-off-by: Federico Musto Signed-off-by: Dave Miller --- .../net/ethernet/atheros/atl1c/atl1c_main.c | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c index e58f1d2c26bd..eddd8406867f 100644 --- a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c @@ -1934,6 +1934,39 @@ static int atl1c_clean_rx(struct napi_struct *napi, int budget) break; } atl1c_clean_rfd(rfd_ring, rrs, rfd_num); + + /* A link partner whose l2mtu is larger than ours can hand us a + * frame that does not fit the buffer the hardware was given: + * the descriptor reports the untruncated length while at most + * buffer_info->length bytes were written. skb_put() of such a + * length would pull in whatever the tail of the buffer holds + * and, past the end of the skb, trips skb_over_panic and takes + * the host down. Bound it by what the device actually wrote + * rather than by skb_tailroom(): the skb carries 64 bytes of + * slack for the DMA erratum, so the two are not the same. + * + * Frames shorter than an Ethernet header plus its FCS are + * rejected by the same test; before this, a length below + * ETH_FCS_LEN underflowed in skb_put(). + */ + if (unlikely(length < ETH_HLEN + ETH_FCS_LEN || + length > buffer_info->length)) { + dev_core_stats_rx_dropped_inc(netdev); + if (netif_msg_rx_err(adapter)) + dev_warn_ratelimited(&pdev->dev, + "dropped rx frame: %u bytes, buffer %u\n", + length, buffer_info->length); + kfree_skb_reason(skb, SKB_DROP_REASON_PKT_TOO_BIG); + /* atl1c_clean_rfd() has already recycled the RFD, so + * count the drop as work: the refill at the end of the + * poll only runs when work_done is non-zero, and + * sustained oversized traffic would otherwise drain the + * ring and stop RX for good. + */ + work_done++; + continue; + } + skb_put(skb, length - ETH_FCS_LEN); skb->protocol = eth_type_trans(skb, netdev); atl1c_rx_checksum(adapter, skb, rrs);