Re: [BUG] net/sched: malformed UFO GSO geometry can soft-lock DRR dequeue
From: Eric Dumazet
Date: Thu Oct 22 2026 - 11:27:03 EST
Le jeu. 22 oct. 2026 à 16:57, Eric Dumazet <edumazet@xxxxxxxxxx> a écrit :
>
> Le jeu. 22 oct. 2026 à 16:48, shaos <shawnq1@xxxxxxx> a écrit :
> >
> > Hello,
> >
> > A PACKET_VNET_HDR AF_PACKET send supplies UFO GSO with gso_size=1 and hdr_len=65532 for a 524275-byte frame. qdisc_pkt_len_segs_init() computes an accounted length of 4,294,574,091 bytes. With DRR quantum 256, the dequeue path needs about 16.8 million deficit refills before it can dequeue the skb, keeping the CPU in the qdisc path long enough to trigger the soft-lockup watchdog.
> >
> > HEAD commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
> > git tree: 30fd2ef82912c26cf3a58ad0ba6afc375fa851ac
> > source version: Linux 7.3.0, local master
> > compiler: gcc 11.4.0, GNU ld 2.38
> >
> > console output:
> > https://gist.githubusercontent.com/shaos26/8a4f7f445aa23601bfe8e04f9c6a5202/raw/f4ca89881f7b8163ddea72fbc54b204385142bb8/console-output.txt
> > user-namespace positive output:
> > https://gist.githubusercontent.com/shaos26/8a4f7f445aa23601bfe8e04f9c6a5202/raw/14de8e3a06e7e73c540ebf408e73d301bae3922d/userns-positive.log
> > kernel config:
> > https://gist.githubusercontent.com/shaos26/8a4f7f445aa23601bfe8e04f9c6a5202/raw/6ea0e3da68f1e239d565c64f7906d0f9c73bf784/kernel-config.txt
> > reproducer sources:
> > https://gist.githubusercontent.com/shaos26/e2add470a38b7aab41f2b79ef021accf/raw/7153646b9233fa53aca81c9af879d48e8f95dd80/poc.c
> > https://gist.githubusercontent.com/shaos26/e2add470a38b7aab41f2b79ef021accf/raw/e8acddd986023c10a43b5106b4734a456a063deb/drop_priv.c
> >
> > Build the tested kernel with the attached configuration and boot a disposable QEMU guest. Compile the linked userspace reproducer and privilege-drop helper, then run the AF_PACKET/VNET_HDR test from a fresh user/network namespace.
> >
> > Impact and prerequisites:
> > A process with only namespace-scoped CAP_NET_ADMIN and CAP_NET_RAW can keep a CPU in the DRR dequeue path long enough to trigger a host soft-lockup. With softlockup_panic=1, as used in the test configuration, the watchdog panics the kernel. This is a local denial-of-service issue; no memory corruption or privilege escalation is demonstrated.
> >
> > Validation: Two fresh positives reproduced the soft lockup, including one run from an unprivileged UID mapped to namespace UID 0. The same-frame non-GSO control completed in milliseconds.
> >
> > Thanks,
> >
>
> Related recent work:
>
> commit 8f735d64382dcf162f4276d6699d03ad2f859c0b
> Author: Jamal Hadi Salim <jhs@xxxxxxxxxxxx>
> Date: Tue Aug 25 04:14:03 2026 -0400
>
> net/sched: bound qdisc_pkt_len to prevent qdisc soft lockup
Something like:
commit b30c29ab2b07fa54bf9f3e73984f441f4151ba87
Author: Eric Dumazet <edumazet@xxxxxxxxxx>
Date: Thu Oct 22 17:19:07 2026 +0200
net: harden qdisc_pkt_len_segs_init() against malicious GSO packets
An SKB_GSO_DODGY packet can carry untrusted gso_size and headers.
For instance, gso_size=1 and hdr_len=65532 for a 524275-byte frame
makes qdisc_pkt_len_segs_init() compute an accounted length of
about 4,294,574,091 bytes (after gso_segs silently wrapped in a u16).
With DRR quantum 256, the dequeue path then needs ~16.8 million
deficit refills before it can dequeue the skb, keeping the CPU in
the qdisc path long enough to trigger the soft-lockup watchdog.
Commit 8f735d64382d ("net/sched: bound qdisc_pkt_len to prevent
qdisc soft lockup") added QDISC_PKT_LEN_MAX to bound size table
amplification. Use the same bound for DODGY packets:
- Make gso_segs a u32 and drop packets whose segment count does
not fit GSO_MAX_SEGS, instead of silently truncating it.
- Drop packets whose estimated qdisc_pkt_len() would exceed
QDISC_PKT_LEN_MAX.
Fixes: 15e5a0307164 ("net_sched: better precise estimation on
packet length for untrusted packets")
Reported-by: shawnq1@xxxxxxx
Closes: https://lore.kernel.org/netdev/20261022144755.65025-1-shawnq1@xxxxxxx/
Signed-off-by: Eric Dumazet <edumazet@xxxxxxxxxx>
diff --git a/net/core/dev.c b/net/core/dev.c
index e76762e29360ea87ade0846e4a4f9016dca587d6..573bcc9d67b1b1e21e844f054f0b51a172713a90
100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4175,7 +4175,7 @@ static enum skb_drop_reason
qdisc_pkt_len_segs_init(struct sk_buff *skb)
{
struct skb_shared_info *shinfo = skb_shinfo(skb);
unsigned int hdr_len, tlen;
- u16 gso_segs;
+ u32 gso_segs;
qdisc_skb_cb(skb)->pkt_len = skb->len;
if (!shinfo->gso_size) {
@@ -4226,6 +4226,15 @@ static enum skb_drop_reason
qdisc_pkt_len_segs_init(struct sk_buff *skb)
if (payload <= 0)
return SKB_DROP_REASON_SKB_BAD_GSO;
gso_segs = DIV_ROUND_UP(payload, shinfo->gso_size);
+ /* shinfo->gso_segs is a 16bit field.
+ * Also make sure untrusted gso_size/hdr_len can not inflate
+ * qdisc_pkt_len() beyond QDISC_PKT_LEN_MAX, or deficit
+ * schedulers could spin in their refill loops.
+ */
+ if (unlikely(gso_segs > GSO_MAX_SEGS ||
+ skb->len + (u64)(gso_segs - 1) * hdr_len >
+ QDISC_PKT_LEN_MAX))
+ return SKB_DROP_REASON_SKB_BAD_GSO;
shinfo->gso_segs = gso_segs;
qdisc_skb_cb(skb)->pkt_segs = gso_segs;
}