[PATCH net v2] net: atl1c: drop oversized RX frames instead of panicking
DORMANTno replies
From: Federico Musto <hidden>
Date: 2026-10-27 12:08:03
Also in:
lkml, stable
Subsystem:
atlx ethernet drivers, networking drivers, the rest · Maintainers:
Chris Snook, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
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: <redacted>
Signed-off-by: Federico Musto <redacted>
---
v1 -> v2, after the Sashiko review:
- bound the length by buffer_info->length instead of skb_tailroom(): the skb
is allocated with 64 bytes of slack for the DMA erratum, so tailroom is
larger than what the device wrote and a frame in between would have been
delivered with stale tail bytes
- count the dropped frame as work, so the RX ring is refilled; without it
sustained oversized traffic could drain the RFD ring and stop RX
- kfree_skb_reason() with SKB_DROP_REASON_PKT_TOO_BIG instead of
dev_kfree_skb(), which is consume_skb(), and drop the rx_length_errors
increment that atl1c_get_stats() overwrites anyway
- the commit message now mentions the runt rejection as well
v1: https://lore.kernel.org/netdev/20261023093000.73535-1-federico@rimpianto.com/ (local)
Not touched here: the RRS_RX_ERR_SUM branch frees neither the skb nor its
DMA mapping (atl1c_clean_rfd() only marks the slot free and drops the
pointer). That is pre-existing and independent of the panic, so it belongs
in a separate patch.
---
.../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 e58f1d2..eddd840 100644
--- a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c
+++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c@@ -1934,6 +1934,39 @@ rrs_checked: 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);
--
2.54.0 (Apple Git-157)