From: Jamal Hadi Salim <jhs@mojatatu.com>
To: netdev@vger.kernel.org
Cc: Jamal Hadi Salim <jhs@mojatatu.com>,
Jiri Pirko <jiri@resnulli.us>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>,
stable@vger.kernel.org, vega@nebusec.ai,
Victor Nogueira <victor@mojatatu.com>
Subject: [PATCH net v2] net/sched: bound qdisc_pkt_len to prevent qdisc soft lockup
Date: Tue, 25 Aug 2026 04:14:03 -0400 [thread overview]
Message-ID: <20260825081403.133992-1-jhs@mojatatu.com> (raw)
qdisc_get_stab() accepts a user-supplied size table, and
__qdisc_calculate_pkt_len() amplifies qdisc_pkt_len() through the
overhead, the size-table data (u16), and size_log (up to
STAB_SIZE_LOG_MAX). A crafted stab can therefore set qdisc_pkt_len()
to ~1 GiB for an ordinary skb. Per-flow deficit schedulers such as
DRR and ETS replenish one quantum per loop iteration; with a tiny
quantum (1) they spin billions of times under the qdisc lock,
producing a soft lockup / RCU stall as illustrated by vega@nebusec.ai.
Cap the final qdisc_pkt_len() to QDISC_PKT_LEN_MAX so the size-table
amplification cannot drive deficit schedulers into an unbounded loop.
A legitimate size table (e.g. qfq's overhead 999999999, which is
handled by dropping) is still accepted.
Introduce cap QDISC_PKT_LEN_MAX (1 << 20) = 1 MiB which is well above
any legitimate single-skb wire length: the largest current skb->len
is GSO_MAX_SIZE (524280), and an ATM-style size table (53/48 cell tax)
amplifies that to ~578 KB, both comfortably below 1 MiB. At the same
time, 1 MiB bounds the deficit refill loop to ~1M iterations per
packet with quantum=1, which completes in a few milliseconds well
under the demonstrated softlockup threshold (~10^9 iterations).
Conditions to recreate the bug:
- CONFIG_NET_SCHED=y, CONFIG_NET_SCH_DRR=y (or CONFIG_NET_SCH_ETS=y).
- Attach a DRR (or ETS) root qdisc with a crafted TCA_STAB that
amplifies qdisc_pkt_len to ~1 GiB (e.g. size_log=15, data=[32768]).
- Add a class with a tiny quantum of 1 and send one small packet; the
deficit loop spins billions of times under the qdisc lock and trips
the softlockup detector (panic with kernel.softlockup_panic=1).
- Reachable as root or from an unprivileged user in a fresh user+net
namespace (unshare -Urn) with namespace-local CAP_NET_ADMIN.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: vega@nebusec.ai
Tested-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
v1 -> v2:
- Use a dedicated QDISC_PKT_LEN_MAX define in include/net/pkt_sched.h
instead of GSO_MAX_SIZE (Jakub Kicinski, Eric Dumazet).
__qdisc_calculate_pkt_len() deals with single-skb wire-length
accounting, not GSO segments; GSO_MAX_SIZE is semantically wrong.
QDISC_PKT_LEN_MAX = (1 << 20) = 1 MiB avoids truncating legitimate
stab accounting (BIG TCP + ATM-style 53/48 table ~ 578 KB) while
still bounding the deficit loop to ~1M iterations at quantum=1
(completes in a few ms, well under the softlockup threshold of
~10^9 iterations).
---
include/net/pkt_sched.h | 1 +
net/sched/sch_api.c | 7 +++++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h
index 18a419cd9d94..90d3e7943b19 100644
--- a/include/net/pkt_sched.h
+++ b/include/net/pkt_sched.h
@@ -12,6 +12,7 @@
#define DEFAULT_TX_QUEUE_LEN 1000
#define STAB_SIZE_LOG_MAX 30
+#define QDISC_PKT_LEN_MAX (1 << 20) /* 1 MiB */
struct qdisc_walker {
int stop;
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index 65b35528d125..90503e59e6e3 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -610,8 +610,11 @@ void __qdisc_calculate_pkt_len(struct sk_buff *skb,
pkt_len <<= stab->szopts.size_log;
out:
- if (unlikely(pkt_len < 1))
- pkt_len = 1;
+ /* A size table can inflate qdisc_pkt_len() beyond any real packet
+ * (via overhead, the data table, or size_log); cap it so deficit
+ * schedulers such as DRR/ETS terminate their refill loops.
+ */
+ pkt_len = clamp_t(int, pkt_len, 1, QDISC_PKT_LEN_MAX);
qdisc_skb_cb(skb)->pkt_len = pkt_len;
}
--
2.43.0
next reply other threads:[~2026-08-25 8:14 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-25 8:14 Jamal Hadi Salim [this message]
2026-08-27 11:01 ` [PATCH net v2] net/sched: bound qdisc_pkt_len to prevent qdisc soft lockup Paolo Abeni
2026-08-27 17:48 ` Jamal Hadi Salim
2026-08-27 19:40 ` patchwork-bot+netdevbpf
-- strict thread matches above, loose matches on Subject: below --
2026-08-19 14:32 Jamal Hadi Salim
2026-08-19 14:52 ` Eric Dumazet
2026-08-19 14:58 ` Jamal Hadi Salim
2026-08-19 15:15 ` Eric Dumazet
2026-08-19 15:30 ` Jamal Hadi Salim
2026-08-24 18:36 ` Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260825081403.133992-1-jhs@mojatatu.com \
--to=jhs@mojatatu.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=jiri@resnulli.us \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
--cc=vega@nebusec.ai \
--cc=victor@mojatatu.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox