pfifo_fast_init() and pfifo_fast_change_tx_queue_len() allocate skb ring arrays sized by dev->tx_queue_len with GFP_KERNEL and no upper bound. An unprivileged user (via unshare -Urn) can set a huge tx_queue_len and attach many pfifo_fast qdiscs to exhaust global memory, causing a system-wide OOM. Cap the ring size to 65535 (default tx_queue_len is 1000) and use GFP_KERNEL_ACCOUNT so the allocations are charged to the allocating process's memory cgroup. A pr_warn_ratelimited is emitted when the ring size is capped so the user is not silently clamped. Conditions to recreate the bug: - CONFIG_NET_SCHED=y, CONFIG_VETH=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y. - Unprivileged user in a fresh user+net namespace (unshare -Urn). - Create a veth pair, set tx_queue_len to a huge value (e.g. 500000) while the devices are down. - Attach mq at root, then replace each child queue with pfifo_fast: tc qdisc replace dev veth0 root handle 1: mq tc qdisc replace dev veth0 parent 1:1 pfifo_fast tc qdisc replace dev veth0 parent 1:2 pfifo_fast ... - Repeat across many veth pairs. Each pfifo_fast allocates 3 skb_array rings of tx_queue_len entries (~12MB per qdisc at QLEN=500000). - On the unfixed kernel this exhausts global memory in ~28 iterations on a 2GB guest -> OOM panic. On the fixed kernel the ring is capped to 65535 entries (~1.5MB per qdisc) and charged to the memcg. Fixes: c5ad119fb6c0 ("net: sched: pfifo_fast use skb_array") Reported-by: vega@nebusec.ai Tested-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim --- net/sched/sch_generic.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index ef2b4bf51564..e98fd236afa0 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -910,11 +910,17 @@ static int pfifo_fast_init(struct Qdisc *qdisc, struct nlattr *opt, if (!qlen) return -EINVAL; + if (qlen > 65535) { + pr_warn_ratelimited("pfifo_fast: capping ring size %u to 65535 for dev %s\n", + qlen, qdisc_dev(qdisc)->name); + qlen = 65535; + } + for (prio = 0; prio < PFIFO_FAST_BANDS; prio++) { struct skb_array *q = band2list(priv, prio); int err; - err = skb_array_init(q, qlen, GFP_KERNEL); + err = skb_array_init(q, qlen, GFP_KERNEL_ACCOUNT); if (err) return -ENOMEM; } @@ -957,8 +963,14 @@ static int pfifo_fast_change_tx_queue_len(struct Qdisc *sch, bands[prio] = q; } + if (new_len > 65535) { + pr_warn_ratelimited("pfifo_fast: capping ring size %u to 65535 for dev %s\n", + new_len, qdisc_dev(sch)->name); + new_len = 65535; + } + return skb_array_resize_multiple_bh(bands, PFIFO_FAST_BANDS, new_len, - GFP_KERNEL); + GFP_KERNEL_ACCOUNT); } struct Qdisc_ops pfifo_fast_ops __read_mostly = { -- 2.43.0 Add a tdc test case (0249) that sets a huge tx_queue_len (100000000) on a dummy device and attaches pfifo_fast. On the fixed kernel the ring is internally capped to 65535 entries, so the qdisc is created successfully (exit 0). On the unfixed kernel the 100M-entry ring allocation (~2.4GB) fails, causing the qdisc add to fail (exit 2). Fixes: c5ad119fb6c0 ("net: sched: pfifo_fast use skb_array") Signed-off-by: Jamal Hadi Salim --- .../tc-tests/qdiscs/pfifo_fast.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json index 30da27fe8806..2cbe9738d699 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json @@ -105,5 +105,28 @@ "teardown": [ "$TC qdisc del dev $DUMMY handle 1: root" ] + }, + { + "id": "0249", + "name": "Create pfifo_fast with huge tx_queue_len (ring cap regression)", + "category": [ + "qdisc", + "pfifo_fast" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$IP link set dev $DUMMY txqueuelen 100000000" + ], + "cmdUnderTest": "$TC qdisc add dev $DUMMY handle 1: root pfifo_fast", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc pfifo_fast 1: root", + "matchCount": "1", + "teardown": [ + "$IP link set dev $DUMMY txqueuelen 1000", + "$TC qdisc del dev $DUMMY handle 1: root" + ] } ] -- 2.43.0