mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 07:03:28 -04:00
net/sched: bound qdisc_pkt_len to prevent qdisc soft lockup
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: 1da177e4c3 ("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>
Link: https://patch.msgid.link/20260825081403.133992-1-jhs@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
88c71cc0ad
commit
8f735d6438
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user