* [PATCH net v2 0/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations
2026-09-02 21:29 [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Jamal Hadi Salim
@ 2026-09-02 21:29 ` Jamal Hadi Salim
2026-09-02 21:29 ` [PATCH net v2 2/3] net: reject oversized tx_queue_len at netlink parse time Jamal Hadi Salim
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Jamal Hadi Salim @ 2026-09-02 21:29 UTC (permalink / raw)
To: netdev
Cc: Jamal Hadi Salim, stable, Jiri Pirko, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Vega, Victor Nogueira
An unprivileged user (via unshare -Urn) can set a huge tx_queue_len
and exhaust global memory through ring allocations sized from it
(pfifo_fast skb_arrays, tun/tap ptr_rings).
The reproducer from vega@nebusec.ai set the following params for
illustration: txqlen of 500000 -> ~32 GiB/ring attempts, 1.6 GB tun,
~960 MB tap. Gets worse when you consider qdiscs like mq.
What we fix: every path an unprivileged user can use to install
an oversized tx_queue_len is rejected with -ERANGE before any ring is
allocated; per-ring memory is bounded at 256 KiB.
This is for you sashikos: What we deliberately _do not fix_
bound the NUMBER of rings. With the cap in place the worst case moves
from "one knob" to the aggregate of ring x queues x devices, example:
ip link add v0 numtxqueues 4096 txqueuelen 32767 type veth
tc qdisc add dev v0 root mq
-> 4096 * 3 * 32767 * 8 = ~3.0 GiB (one command)
50 tun devices x 256 queues x 32767 x 8 = ~3.1 GiB
Unfortunately tx_queue_len is a bit ambigious in meaning:
In some cases it means a ring size (which is pre-allocated, ex:
tun, tap, and pfifo_fast); a cap of 4096 seems reasonable here.
but in other cases it is used to indicate a queue limit ex:
the qdisc consumers that allocate nothing (pfifo/bfifo/gred/plug/sfb,
htb direct_qlen, qfq, teql). 32767 is a legitimate high-BDP queue
length, so we are going to keep that value.
Getting back to you sashikos, after this is merged and shows up
in net-next we will send followup patches as follows:
this series is not misread as "closes the OOM class"):
a) Per-site ring limits at six identified locations
- pfifo_fast init/resize,
- tun attach/resize,
- tap minor/resize)
if you can spot more in your review we will take care of those as well.
b) memcg accounting (GFP_KERNEL_ACCOUNT) for those ring
allocations: contains a memcg-limited container's ring memory.
Not GFP_KERNEL_ACCOUNT has no effect on the unshare attacker
but will protect against containers (memory.max in its cgroup)
Patches:
--------
1/3 net: cap tx_queue_len at S16_MAX in netif_change_tx_queue_len()
(netlink set, sysfs, SIOCSIFTXQLEN choke point)
2/3 net: reject oversized tx_queue_len at netlink parse time
(IFLA_TXQLEN policy: closes the create path + veth peer nest)
3/3 selftests: tdc regression tests (netlink, sysfs, create paths)
Changes:
--------
v1 -> v2:
- new patch 2/3: close the device-creation path (Jakub Kicinski
flagged that rtnl_create_link() bypasses the cap)
- rationale reworded: the 32767 ceiling citing NLA u32 range
policy can express (s16 bounds), replacing the invalid virtio
ring-depth claim
- rebuilt tdc coverage (sysfs path now tested; nondeterministic
resize-rollback case dropped)
Sashiko v1 review links:
https://sashiko.dev/#/patchset/20260828121902.66837-1-jhs@mojatatu.com
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260828121902.66837-1-jhs@mojatatu.com
v1: https://lore.kernel.org/netdev/20260828121902.66837-1-jhs@mojatatu.com/
Jamal Hadi Salim (3):
net: cap tx_queue_len at S16_MAX to prevent oversized ring
allocations
net: reject oversized tx_queue_len at netlink parse time
selftests: tc-testing: add tx_queue_len cap regression tests
.../tc-testing/tc-tests/qdiscs/pfifo_fast.json | 209 +++++++++++++++++-
net/core/dev.c | 2 +-
net/core/rtnetlink.c | 9 +-
3 files changed, 211 insertions(+), 2 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations
@ 2026-09-02 21:29 Jamal Hadi Salim
2026-09-02 21:29 ` [PATCH net v2 0/3] " Jamal Hadi Salim
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Jamal Hadi Salim @ 2026-09-02 21:29 UTC (permalink / raw)
To: netdev
Cc: Jamal Hadi Salim, stable, Jiri Pirko, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Vega, Victor Nogueira
Several subsystems allocate ring buffers sized by dev->tx_queue_len
with no upper bound. An unprivileged user (via unshare -Urn) can set a
huge tx_queue_len and exhaust global memory with ring allocations:
- pfifo_fast: pfifo_fast_init() and pfifo_fast_change_tx_queue_len()
allocate 3 skb_array rings of tx_queue_len entries each.
- tun: tun_queue_resize() and the queue-attach path resize ptr_rings
to tx_queue_len on the NETDEV_CHANGE_TX_QUEUE_LEN notifier.
- tap (macvtap/ipvtap): tap_queue_resize() and tap_init() resize/init
ptr_rings to tx_queue_len on the same notifier.
netif_change_tx_queue_len() is the single entry point for IFLA_TXQLEN,
sysfs, and the SIOCSIFTXQLEN ioctl. Cap new_len at S16_MAX (32767)
there so the oversized value is rejected at set time. This takes
effect whether the device is up or down, before dev->tx_queue_len is
written, before any notifier fires, and before any ring is allocated.
The "> S16_MAX" check also subsumes the previous unsigned-long
truncation test, and a negative ifr_qlen from the ioctl lands far
above the cap after conversion, so both old failure modes are covered
by the one comparison.
tx_queue_len is ambigious: both a per-ring sizing multiplier and a
default queue-length/limit knob for consumers that allocate
nothing at set time (pfifo/bfifo/gred/plug/sfb limits, htb
direct_qlen, qfq max_classes, teql). 32767 is chosen as the largest
value NLA_POLICY_FULL_RANGE can express for the u32 IFLA_TXQLEN
policy in patch 2/3 while staying a legitimate queue length on
high-BDP paths; the ring-memory trade-off of a shared knob is
disclosed below.
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).
- pfifo_fast: create veth pairs, set tx_queue_len to 500000, attach
mq+pfifo_fast. ~28 iterations OOMs a 2GB guest.
- tun: create 50 tun devices with IFF_MULTI_QUEUE, set tx_queue_len to
500000, open 8 queues each. ~1.6GB of ptr_ring allocations OOMs a
512MB guest.
- tap: same as tun with IFF_TAP. ~960MB OOMs a 512MB guest.
- On the fixed kernel the oversized tx_queue_len is rejected with
-ERANGE at set time (all four paths: RTM_SETLINK, RTM_NEWLINK
create, sysfs, ioctl - the latter two via this check, the former
two via this check and the 2/3 parse policy respectively).
Fixes: 6a643ddb5624 ("net: introduce helper dev_change_tx_queue_len()")
Reported-by: Vega <vega@nebusec.ai>
Closes: https://lore.kernel.org/netdev/20260828121902.66837-1-jhs@mojatatu.com/
Tested-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
net/core/dev.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index 38336858c168..1d3fc0a268a5 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -9982,7 +9982,7 @@ int netif_change_tx_queue_len(struct net_device *dev, unsigned long new_len)
unsigned int orig_len = dev->tx_queue_len;
int res;
- if (new_len != (unsigned int)new_len)
+ if (new_len > S16_MAX)
return -ERANGE;
if (new_len != orig_len) {
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net v2 2/3] net: reject oversized tx_queue_len at netlink parse time
2026-09-02 21:29 [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Jamal Hadi Salim
2026-09-02 21:29 ` [PATCH net v2 0/3] " Jamal Hadi Salim
@ 2026-09-02 21:29 ` Jamal Hadi Salim
2026-09-05 1:10 ` netdev-bot+sashiko
2026-09-02 21:29 ` [PATCH net v2 3/3] selftests: tc-testing: add tx_queue_len cap regression tests Jamal Hadi Salim
` (2 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Jamal Hadi Salim @ 2026-09-02 21:29 UTC (permalink / raw)
To: netdev
Cc: Jamal Hadi Salim, stable, Jiri Pirko, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Cong Wang, Shuah Khan, Vega, Victor Nogueira
rtnl_create_link() assigns IFLA_TXQLEN directly to dev->tx_queue_len
without going through netif_change_tx_queue_len(), so a device created
with "ip link add ... txqueuelen 500000" bypasses the S16_MAX cap and
still triggers the oversized ring allocations in pfifo_fast, tun and
tap. The veth peer nest (rtnl_nla_parse_ifinfomsg()) and the
RTM_NEWLINK-on-existing-device path reach the same sinks.
Enforce the cap in ifla_policy instead: IFLA_TXQLEN becomes
NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range) with
txqlen_range = { .min = 0, .max = S16_MAX }. All netlink consumers
parse against this policy - rtnl_setlink(), rtnl_newlink() (create
and change), and the veth peer nest - so every netlink path is capped
at parse time and rejects the attribute with -ERANGE plus a proper
"integer out of range" extack message before any device state is
modified (the RTM_SETLINK half-application wart is gone with it).
Document the bound in the rt-link.yaml netlink spec.
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):
ip link add v0 txqueuelen 500000 type veth peer name v1
-> on the fixed kernel this is rejected with -ERANGE ("integer out
of range" extack) instead of installing an oversized tx_queue_len
that later inflates pfifo_fast/tun/tap ring allocations.
- ip link set v0 txqueuelen 500000 is likewise rejected at parse time.
Fixes: 38f7b870d4a6 ("[RTNETLINK]: Link creation API")
Reported-by: Vega <vega@nebusec.ai>
Tested-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
Documentation/netlink/specs/rt-link.yaml | 2 ++
net/core/rtnetlink.c | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml
index b80c2ac3ac31..99f6fba456cc 100644
--- a/Documentation/netlink/specs/rt-link.yaml
+++ b/Documentation/netlink/specs/rt-link.yaml
@@ -898,6 +898,8 @@ attribute-sets:
-
name: txqlen
type: u32
+ checks:
+ max: 32767
-
name: map
type: binary
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 81c5a6104dea..9ea4ff9c1e29 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -2285,7 +2285,12 @@ int rtnl_unicast(struct sk_buff *skb, struct net *net, u32 pid)
rcu_read_unlock();
nla_put_failure:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
+static const struct netlink_range_validation txqlen_range = {
+ .min = 0,
+ .max = S16_MAX,
+};
+
static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
@@ -2297,7 +2302,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
[IFLA_LINK] = { .type = NLA_U32 },
[IFLA_MASTER] = { .type = NLA_U32 },
[IFLA_CARRIER] = { .type = NLA_U8 },
- [IFLA_TXQLEN] = { .type = NLA_U32 },
+ [IFLA_TXQLEN] = NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range),
[IFLA_WEIGHT] = { .type = NLA_U32 },
[IFLA_OPERSTATE] = { .type = NLA_U8 },
[IFLA_LINKMODE] = { .type = NLA_U8 },
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net v2 3/3] selftests: tc-testing: add tx_queue_len cap regression tests
2026-09-02 21:29 [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Jamal Hadi Salim
2026-09-02 21:29 ` [PATCH net v2 0/3] " Jamal Hadi Salim
2026-09-02 21:29 ` [PATCH net v2 2/3] net: reject oversized tx_queue_len at netlink parse time Jamal Hadi Salim
@ 2026-09-02 21:29 ` Jamal Hadi Salim
2026-09-05 1:10 ` netdev-bot+sashiko
2026-09-04 23:40 ` [PATCH net v2 0/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations patchwork-bot+netdevbpf
2026-09-05 1:10 ` [PATCH net v2 1/3] " netdev-bot+sashiko
4 siblings, 1 reply; 8+ messages in thread
From: Jamal Hadi Salim @ 2026-09-02 21:29 UTC (permalink / raw)
To: netdev
Cc: Jamal Hadi Salim, stable, Jiri Pirko, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Donald Hunter, Shuah Khan, Vega, Victor Nogueira
Add nine test cases for the S16_MAX tx_queue_len cap to the
pfifo_fast suite. Netlink cases exercise the ifla_policy bound
(2/3); the two new sysfs cases exercise the netif_change_tx_queue_len()
choke point that 1/3 owns (SIOCSIFTXQLEN shares it; the ioctl is not
portably reachable from tdc):
- dbe3: set txqueuelen 32767 (S16_MAX) - accepted, pins the exact
boundary value.
- b50e: set txqueuelen 32768 - rejected with -ERANGE.
- 40f8: write 32768 to /sys/class/net/*/tx_queue_len - rejected
(covers patch 1/3 directly; netlink cannot reach this path).
- 4b6e: write 32767 via sysfs - accepted, boundary positive control
for the patch-1 path.
- b90d: create a dummy with txqueuelen 32767 - accepted.
- 57ab: create a dummy with txqueuelen 32768 - rejected at netlink
parse time.
- e777: create a dummy with txqueuelen 500000 - rejected (the v1
bypass path flagged by review).
- 31ac: create a veth with an oversized txqueuelen on the peer nest -
rejected (the peer nest is parsed against ifla_policy too).
- b567: create a veth with txqueuelen on both ends within the cap -
accepted (positive control for the peer nest).
The three negative-creation verifies assert device absence
("ip -o link show" must not contain the device), not merely absence
of a qlen pattern - the device does not exist when creation fails, so
the exit code carries the signal and the verify adds content.
The v1 04b5 "resize rollback" case is dropped: with the cap checked
first, netif_change_tx_queue_len() returns -ERANGE before the write,
the notifier or any qdisc resize, so the case exercised no resize and
no rollback. It was also nondeterministic: pre-patch, the resize
issues three ~11 MB kvmallocs for qlen 500000 which normally succeed,
so the case passed on an unfixed kernel only under memory pressure -
its outcome depended on the test host's free memory.
Test commands run inside the netns, but nsPlugin creates the veth
peer in the root namespace, so the teardown deletes the in-ns end
only; deleting the peer via the pair is implicit.
Note: iproute2 treats "txqueuelen" appearing after "type X" as a
link-type attribute and silently drops it, so the creation cases
place it before "type" to actually reach the kernel.
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
Changes since v1:
- new patch: rebuilt tdc coverage for the v2 series (drop
nondeterministic 04b5; boundary pair + create-path + peer-nest)
Internal review (i1) changes, 2026-09-02:
- add sysfs coverage (40f8/4b6e): the seven v2 cases all drive
netlink and exercise only 2/3; patch 1/3 owns sysfs+ioctl
- strengthen the three negative-creation verifies (57ab/e777/31ac):
assert device absence instead of pattern absence on a nonexistent
device (vacuous before)
- fix the Cc: header (recipients were folded into To:)
- drop the no-op "$IP link del dev $DEV0" teardown (peer lives in
the root namespace; deleting $DEV1 removes the pair)
- subject prefix: selftests/tc-testing (conventional for the dir)
---
.../selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json | 204 ++++++++++++
1 file changed, 204 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..a6e25e76ecb1 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,209 @@
"teardown": [
"$TC qdisc del dev $DUMMY handle 1: root"
]
+ },
+ {
+ "id": "dbe3",
+ "name": "Set tx_queue_len to S16_MAX boundary (32767 accepted)",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [],
+ "cmdUnderTest": "$IP link set dev $DUMMY txqueuelen 32767",
+ "expExitCode": "0",
+ "verifyCmd": "$IP link show dev $DUMMY",
+ "matchPattern": "qlen 32767$",
+ "matchCount": "1",
+ "teardown": []
+ },
+ {
+ "id": "b50e",
+ "name": "Reject tx_queue_len above S16_MAX at set time (32768)",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [],
+ "cmdUnderTest": "$IP link set dev $DUMMY txqueuelen 32768",
+ "expExitCode": "2",
+ "verifyCmd": "$IP link show dev $DUMMY",
+ "matchPattern": "qlen 1000$",
+ "matchCount": "1",
+ "teardown": []
+ },
+ {
+ "id": "40f8",
+ "name": "Reject tx_queue_len above S16_MAX via sysfs (32768)",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [],
+ "cmdUnderTest": "sh -c 'echo 32768 > /sys/class/net/$DUMMY/tx_queue_len'",
+ "expExitCode": "1",
+ "verifyCmd": "$IP link show dev $DUMMY",
+ "matchPattern": "qlen 1000$",
+ "matchCount": "1",
+ "teardown": []
+ },
+ {
+ "id": "4b6e",
+ "name": "Set tx_queue_len to S16_MAX via sysfs (32767 accepted)",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [],
+ "cmdUnderTest": "sh -c 'echo 32767 > /sys/class/net/$DUMMY/tx_queue_len'",
+ "expExitCode": "0",
+ "verifyCmd": "$IP link show dev $DUMMY",
+ "matchPattern": "qlen 32767$",
+ "matchCount": "1",
+ "teardown": []
+ },
+ {
+ "id": "b90d",
+ "name": "Create device with tx_queue_len at S16_MAX boundary (32767 accepted)",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ [
+ "$IP link del dev $DUMMY",
+ 0,
+ 1
+ ]
+ ],
+ "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 32767 type dummy",
+ "expExitCode": "0",
+ "verifyCmd": "$IP link show dev $DUMMY",
+ "matchPattern": "qlen 32767$",
+ "matchCount": "1",
+ "teardown": [
+ [
+ "$IP link del dev $DUMMY",
+ 0,
+ 1
+ ]
+ ]
+ },
+ {
+ "id": "57ab",
+ "name": "Reject creating device with tx_queue_len above S16_MAX (32768)",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ [
+ "$IP link del dev $DUMMY",
+ 0,
+ 1
+ ]
+ ],
+ "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 32768 type dummy",
+ "expExitCode": "2",
+ "verifyCmd": "$IP -o link show",
+ "matchPattern": "^[0-9]+: $DUMMY",
+ "matchCount": "0",
+ "teardown": []
+ },
+ {
+ "id": "e777",
+ "name": "Reject creating device with oversized tx_queue_len (500000)",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ [
+ "$IP link del dev $DUMMY",
+ 0,
+ 1
+ ]
+ ],
+ "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 500000 type dummy",
+ "expExitCode": "2",
+ "verifyCmd": "$IP -o link show",
+ "matchPattern": "^[0-9]+: $DUMMY",
+ "matchCount": "0",
+ "teardown": []
+ },
+ {
+ "id": "31ac",
+ "name": "Reject veth peer nest tx_queue_len above S16_MAX at create",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ [
+ "$IP link del dev $DEV1",
+ 0,
+ 1
+ ]
+ ],
+ "cmdUnderTest": "$IP link add dev $DEV1 type veth peer name $DEV0 txqueuelen 500000",
+ "expExitCode": "2",
+ "verifyCmd": "$IP -o link show",
+ "matchPattern": "^[0-9]+: $DEV1",
+ "matchCount": "0",
+ "teardown": []
+ },
+ {
+ "id": "b567",
+ "name": "Accept veth peer nest tx_queue_len within S16_MAX",
+ "category": [
+ "qdisc",
+ "pfifo_fast"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ [
+ "$IP link del dev $DEV1",
+ 0,
+ 1
+ ]
+ ],
+ "cmdUnderTest": "$IP link add dev $DEV1 txqueuelen 100 type veth peer name $DEV0 txqueuelen 200",
+ "expExitCode": "0",
+ "verifyCmd": "$IP link show",
+ "matchPattern": "qlen (100|200)$",
+ "matchCount": "2",
+ "teardown": [
+ [
+ "$IP link del dev $DEV0",
+ 0,
+ 1
+ ]
+ ]
}
]
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 0/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations
2026-09-02 21:29 [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Jamal Hadi Salim
` (2 preceding siblings ...)
2026-09-02 21:29 ` [PATCH net v2 3/3] selftests: tc-testing: add tx_queue_len cap regression tests Jamal Hadi Salim
@ 2026-09-04 23:40 ` patchwork-bot+netdevbpf
2026-09-05 1:10 ` [PATCH net v2 1/3] " netdev-bot+sashiko
4 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-04 23:40 UTC (permalink / raw)
To: Jamal Hadi Salim
Cc: netdev, stable, jiri, davem, edumazet, kuba, pabeni, horms,
donald.hunter, vega, victor
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Wed, 2 Sep 2026 17:29:07 -0400 you wrote:
> An unprivileged user (via unshare -Urn) can set a huge tx_queue_len
> and exhaust global memory through ring allocations sized from it
> (pfifo_fast skb_arrays, tun/tap ptr_rings).
> The reproducer from vega@nebusec.ai set the following params for
> illustration: txqlen of 500000 -> ~32 GiB/ring attempts, 1.6 GB tun,
> ~960 MB tap. Gets worse when you consider qdiscs like mq.
>
> [...]
Here is the summary with links:
- [net,v2,1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations
https://git.kernel.org/netdev/net/c/66ab4c59b74d
- [net,v2,2/3] net: reject oversized tx_queue_len at netlink parse time
https://git.kernel.org/netdev/net/c/1aa9e143bf51
- [net,v2,3/3] selftests: tc-testing: add tx_queue_len cap regression tests
https://git.kernel.org/netdev/net/c/0a7252d7f854
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations
2026-09-02 21:29 [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Jamal Hadi Salim
` (3 preceding siblings ...)
2026-09-04 23:40 ` [PATCH net v2 0/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations patchwork-bot+netdevbpf
@ 2026-09-05 1:10 ` netdev-bot+sashiko
4 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-09-05 1:10 UTC (permalink / raw)
To: jhs
Cc: netdev, stable, jiri, davem, edumazet, kuba, pabeni, horms,
donald.hunter, xiyou.wangcong, shuah, vega, victor
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 2
- [Medium] Unconditional uAPI narrowing of tx_queue_len for every
netdevice, justified in the commit message by a claim that is…
- [Low] RTM_NEWLINK device-creation bypass of the new S16_MAX cap.
- [Low] The new 32767 upper bound is not recorded in the sysfs ABI
documentation.
--
net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations
This replaces the unsigned-long truncation test in
netif_change_tx_queue_len() with a hard upper bound of S16_MAX, so that
values above 32767 are rejected with -ERANGE at set time instead of later
driving skb_array/ptr_ring allocations in pfifo_fast, tun and tap.
Closes: https://lore.kernel.org/netdev/20260828121902.66837-1-jhs@mojatatu.com/
> diff --git a/net/core/dev.c b/net/core/dev.c
> index 290e0f099e6b..ecfbd72d5d1a 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -9982,7 +9982,7 @@ int netif_change_tx_queue_len(struct net_device *dev, unsigned long new_len)
> unsigned int orig_len = dev->tx_queue_len;
> int res;
>
> - if (new_len != (unsigned int)new_len)
> + if (new_len > S16_MAX)
> return -ERANGE;
[Severity: Medium]
The commit message justifies the constant with:
"32767 is chosen as the largest value NLA_POLICY_FULL_RANGE can express
for the u32 IFLA_TXQLEN policy in patch 2/3"
Is that accurate? NLA_POLICY_FULL_RANGE() takes a pointer to:
include/net/netlink.h:
struct netlink_range_validation {
u64 min, max;
};
so a u32 bound such as U16_MAX or U32_MAX is expressible there. The s16
limitation applies to NLA_POLICY_RANGE()/MIN()/MAX(), which embed the
bounds in the policy entry, not to the FULL_RANGE variant.
Since the new check in netif_change_tx_queue_len() applies to all callers
(sysfs tx_queue_len_store(), SIOCSIFTXQLEN, RTM_SETLINK via do_setlink()),
previously accepted configurations such as
ip link set dev X txqueuelen 65535
echo 100000 > /sys/class/net/X/tx_queue_len
now return -ERANGE, including for consumers that allocate nothing at set
time and only use the value as a packet-count limit:
net/sched/sch_fifo.c:fifo_init() {
...
limit = qdisc_dev(sch)->tx_queue_len;
...
}
along with gred/plug/sfb limits, htb direct_qlen, qfq max_classes and
teql. Could the reasoning for narrowing the shared knob be restated
without the FULL_RANGE constraint, or could the bound instead be applied
in the allocating consumers (pfifo_fast_init(), tun/tap ring sizing)?
The same paragraph also says:
"a negative ifr_qlen from the ioctl lands far above the cap after
conversion, so both old failure modes are covered by the one
comparison."
The ioctl path already rejects negatives before the helper is reached:
net/core/dev_ioctl.c:dev_ifsioc() {
...
case SIOCSIFTXQLEN:
if (ifr->ifr_qlen < 0)
return -EINVAL;
return dev_change_tx_queue_len(dev, ifr->ifr_qlen);
...
}
so is the claim about that failure mode being covered here still needed?
[Severity: Low]
The commit message says netif_change_tx_queue_len() "is the single entry
point for IFLA_TXQLEN, sysfs, and the SIOCSIFTXQLEN ioctl", and that on the
fixed kernel RTM_NEWLINK create is also rejected. With only this patch
applied, does the device creation path still bypass the cap?
net/core/rtnetlink.c:rtnl_create_link() {
...
if (tb[IFLA_TXQLEN])
dev->tx_queue_len = nla_get_u32(tb[IFLA_TXQLEN]);
...
}
and at this commit the policy imposes no range:
net/core/rtnetlink.c:
[IFLA_TXQLEN] = { .type = NLA_U32 },
so ip link add dev X txqueuelen 500000 type dummy stores the oversized
value directly, which pfifo_fast_init() and tap_init()/tun queue attach
then use for ring sizing.
This is closed later in the same series: the follow-up commit "net: reject
oversized tx_queue_len at netlink parse time" changes the policy entry to
NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range) with .max = S16_MAX, so all
netlink parse paths reject the attribute before rtnl_create_link() runs.
Mentioning that dependency here would make a partial backport of patch 1
alone less likely to leave the create path uncapped.
[Severity: Low]
Should the sysfs ABI documentation be updated for the new maximum?
Documentation/ABI/testing/sysfs-class-net still describes the attribute
without any upper bound:
What: /sys/class/net/<iface>/tx_queue_len
...
Indicates the interface transmit queue len in number
of packets, as an integer value.
while writes above 32767 through tx_queue_len_store() ->
dev_change_tx_queue_len() -> netif_change_tx_queue_len() now fail with
-ERANGE. The kernel-doc for the exported wrapper dev_change_tx_queue_len()
in net/core/dev_api.c likewise only states "Return: 0 on success, -errno on
failure" and does not mention the 32767 limit.
The netlink half of this is handled inside the series: the follow-up commit
"net: reject oversized tx_queue_len at netlink parse time" adds
checks: max: 32767 to Documentation/netlink/specs/rt-link.yaml, so only the
sysfs ABI text and the wrapper kernel-doc remain unamended.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-2899.v2.20260901233641%40mojatatu.com
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 2/3] net: reject oversized tx_queue_len at netlink parse time
2026-09-02 21:29 ` [PATCH net v2 2/3] net: reject oversized tx_queue_len at netlink parse time Jamal Hadi Salim
@ 2026-09-05 1:10 ` netdev-bot+sashiko
0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-09-05 1:10 UTC (permalink / raw)
To: jhs
Cc: netdev, stable, jiri, davem, edumazet, kuba, pabeni, horms,
donald.hunter, xiyou.wangcong, shuah, vega, victor
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 0
- [Medium] The changelog of "net: reject oversized tx_queue_len at
netlink parse time" misstates which paths and which sinks were…
--
net: reject oversized tx_queue_len at netlink parse time
This moves the tx_queue_len bound into ifla_policy so IFLA_TXQLEN is
validated against a netlink_range_validation of 0..S16_MAX at parse time,
covering the rtnl_create_link() path that assigns dev->tx_queue_len
directly. The rt-link.yaml spec gets a matching max check.
No Link: tags in the commit header.
One question about the changelog, quoting the parts in question:
> rtnl_create_link() assigns IFLA_TXQLEN directly to dev->tx_queue_len
> without going through netif_change_tx_queue_len(), so a device created
> with "ip link add ... txqueuelen 500000" bypasses the S16_MAX cap and
> still triggers the oversized ring allocations in pfifo_fast, tun and
> tap. The veth peer nest (rtnl_nla_parse_ifinfomsg()) and the
> RTM_NEWLINK-on-existing-device path reach the same sinks.
[Severity: Medium]
Is the tun sink actually reachable through the rtnl_create_link() bypass
described here? drivers/net/tun.c refuses every rtnetlink creation
attempt:
drivers/net/tun.c:tun_validate() {
NL_SET_ERR_MSG(extack,
"tun/tap creation via rtnetlink is not supported.");
return -EOPNOTSUPP;
}
so rtnl_create_link() never runs for a tun device, and the remaining
writers of an existing tun's tx_queue_len (do_setlink(), sysfs,
SIOCSIFTXQLEN) all funnel through netif_change_tx_queue_len(), which
already returns before touching state:
net/core/dev.c:netif_change_tx_queue_len() {
if (new_len > S16_MAX)
return -ERANGE;
if (new_len != orig_len) {
WRITE_ONCE(dev->tx_queue_len, new_len);
...
}
The tap half of the claim does hold, since macvtap/ipvtap are created via
rtnetlink and rtnl_create_link() can overwrite the TUN_READQ_SIZE default
that tap_open() later uses to size the ptr_ring. Should the tun mention
be dropped?
Similarly, does the RTM_NEWLINK-on-existing-device path reach the same
sinks? __rtnl_newlink() dispatches an existing device away from
rtnl_create_link():
net/core/rtnetlink.c:__rtnl_newlink() {
if (dev)
return rtnl_changelink(skb, nlh, ops, dev, tgt_net, tbs, data, extack);
...
}
and rtnl_changelink() -> do_setlink() handles the attribute as:
net/core/rtnetlink.c:do_setlink() {
if (tb[IFLA_TXQLEN]) {
unsigned int value = nla_get_u32(tb[IFLA_TXQLEN]);
err = netif_change_tx_queue_len(dev, value);
...
}
which is the already-capped helper above. That leaves the raw assignment
in rtnl_create_link():
net/core/rtnetlink.c:rtnl_create_link() {
if (tb[IFLA_TXQLEN])
dev->tx_queue_len = nla_get_u32(tb[IFLA_TXQLEN]);
...
}
as the only genuine bypass, reached from rtnl_newlink_create() and the
veth/vxcan/netkit peer nests. Could the changelog be narrowed to that?
> at parse time and rejects the attribute with -ERANGE plus a proper
> "integer out of range" extack message before any device state is
> modified (the RTM_SETLINK half-application wart is gone with it).
Is the parenthetical too broad? do_setlink() still applies attributes
one at a time and can leave earlier attributes committed when a later one
fails, so this only removes the half-application for an out-of-range
IFLA_TXQLEN rather than making RTM_SETLINK atomic.
> diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
> index 81c5a6104dea..be9d1625bac3 100644
> --- a/net/core/rtnetlink.c
> +++ b/net/core/rtnetlink.c
> @@ -2287,6 +2287,11 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb,
> return -EMSGSIZE;
> }
>
> +static const struct netlink_range_validation txqlen_range = {
> + .min = 0,
> + .max = S16_MAX,
> +};
> +
[ ... ]
> - [IFLA_TXQLEN] = { .type = NLA_U32 },
> + [IFLA_TXQLEN] = NLA_POLICY_FULL_RANGE(NLA_U32, &txqlen_range),
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-2899.v2.20260901233641%40mojatatu.com
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 3/3] selftests: tc-testing: add tx_queue_len cap regression tests
2026-09-02 21:29 ` [PATCH net v2 3/3] selftests: tc-testing: add tx_queue_len cap regression tests Jamal Hadi Salim
@ 2026-09-05 1:10 ` netdev-bot+sashiko
0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-09-05 1:10 UTC (permalink / raw)
To: jhs
Cc: netdev, stable, jiri, davem, edumazet, kuba, pabeni, horms,
donald.hunter, xiyou.wangcong, shuah, vega, victor
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 1
- [Medium] The three new negative-creation cases (57ab, e777, 31ac) in
tools/testing/selftests/tc-testing/tc-tests/qdiscs/pfifo_fast.json…
- [Low] The commit message's veth topology/teardown explanation does not
match test b567 (pfifo_fast.json:283-312).
--
selftests: tc-testing: add tx_queue_len cap regression tests
This adds nine tdc cases to the pfifo_fast suite covering the S16_MAX
tx_queue_len cap: netlink set/create boundary pairs, two sysfs writes, and
two veth peer-nest cases. A couple of questions on the new verify stages
and on the namespace description in the changelog.
> 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..a6e25e76ecb1 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,209 @@
> "teardown": [
> "$TC qdisc del dev $DUMMY handle 1: root"
> ]
> + },
[ ... dbe3, b50e, 40f8, 4b6e, b90d snipped ... ]
> + {
> + "id": "57ab",
> + "name": "Reject creating device with tx_queue_len above S16_MAX (32768)",
[ ... ]
> + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 32768 type dummy",
> + "expExitCode": "2",
> + "verifyCmd": "$IP -o link show",
> + "matchPattern": "^[0-9]+: $DUMMY",
> + "matchCount": "0",
> + "teardown": []
> + },
[Severity: Medium]
Can this matchPattern ever match anything? tdc expands NAMES keywords only
in commands: exec_cmd() runs replace_keywords() on the command string, and
nsPlugin's SubPlugin._replace_keywords() is likewise applied to commands
only. The verify stage compiles the pattern verbatim:
tools/testing/selftests/tc-testing/tdc.py:run_one_test() {
...
match_pattern = re.compile(
str(tidx["matchPattern"]), re.DOTALL | re.MULTILINE)
match_index = re.findall(match_pattern, procout)
if len(match_index) != int(tidx["matchCount"]):
...
}
So "$DUMMY" is not substituted, and in a Python regex the "$" is a
zero-width end-of-line assertion, meaning "^[0-9]+: $DUMMY" requires the
literal text "DUMMY" to appear after an end-of-line position. findall()
then always returns [], and matchCount "0" is satisfied unconditionally -
including on a kernel that happily created the oversized-qlen device.
The same pattern appears in e777 ("^[0-9]+: $DUMMY") and in 31ac
("^[0-9]+: $DEV1"), so all three negative-creation cases are checked by
expExitCode alone.
That seems to conflict with this part of the changelog:
The three negative-creation verifies assert device absence
("ip -o link show" must not contain the device), not merely absence
of a qlen pattern - the device does not exist when creation fails, so
the exit code carries the signal and the verify adds content.
A grep for a "$VAR" inside "matchPattern" across
tools/testing/selftests/tc-testing/tc-tests hits only these three new
lines, so it does not look like an existing idiom either.
Would moving the absence check into verifyCmd (where substitution does
happen, e.g. "$IP -o link show dev $DUMMY"), or using a literal device name
in the pattern, work better here? Alternatively tdc.py could be extended
to expand NAMES in matchPattern.
> + {
> + "id": "e777",
> + "name": "Reject creating device with oversized tx_queue_len (500000)",
[ ... ]
> + "cmdUnderTest": "$IP link add dev $DUMMY txqueuelen 500000 type dummy",
> + "expExitCode": "2",
> + "verifyCmd": "$IP -o link show",
> + "matchPattern": "^[0-9]+: $DUMMY",
> + "matchCount": "0",
> + "teardown": []
> + },
> + {
> + "id": "31ac",
> + "name": "Reject veth peer nest tx_queue_len above S16_MAX at create",
[ ... ]
> + "cmdUnderTest": "$IP link add dev $DEV1 type veth peer name $DEV0 txqueuelen 500000",
> + "expExitCode": "2",
> + "verifyCmd": "$IP -o link show",
> + "matchPattern": "^[0-9]+: $DEV1",
> + "matchCount": "0",
> + "teardown": []
> + },
> + {
> + "id": "b567",
> + "name": "Accept veth peer nest tx_queue_len within S16_MAX",
[ ... ]
> + "setup": [
> + [
> + "$IP link del dev $DEV1",
> + 0,
> + 1
> + ]
> + ],
> + "cmdUnderTest": "$IP link add dev $DEV1 txqueuelen 100 type veth peer name $DEV0 txqueuelen 200",
> + "expExitCode": "0",
> + "verifyCmd": "$IP link show",
> + "matchPattern": "qlen (100|200)$",
> + "matchCount": "2",
> + "teardown": [
> + [
> + "$IP link del dev $DEV0",
> + 0,
> + 1
> + ]
> + ]
> }
> ]
[Severity: Low]
Is the namespace description in the changelog accurate for b567? The
message says:
Test commands run inside the netns, but nsPlugin creates the veth
peer in the root namespace, so the teardown deletes the in-ns end
only; deleting the peer via the pair is implicit.
and the changelog notes:
drop the no-op "$IP link del dev $DEV0" teardown (peer lives in
the root namespace; deleting $DEV1 removes the pair)
The plugin-created pair does span namespaces:
tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py:_nl_ns_create() {
...
ip.link('add', ifname=dev1, kind='veth',
peer={'ifname': dev0, 'net_ns_fd':'/proc/1/ns/net'})
...
}
But b567's setup deletes $DEV1 first, which removes that original pair
including the root-namespace $DEV0, and its cmdUnderTest recreates both
ends. That command runs inside the test netns:
tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py:adjust_command() {
...
if stage == 'setup' or stage == 'execute' or stage == 'verify' or stage == 'teardown':
cmdlist.insert(0, self.args.NAMES['NS'])
cmdlist.insert(0, 'exec')
cmdlist.insert(0, 'netns')
cmdlist.insert(0, self.args.NAMES['IP'])
...
}
so both new ends live in the test namespace, and b567's teardown is
"$IP link del dev $DEV0" - the very command the changelog says was dropped
as a no-op, deleting the in-ns peer rather than $DEV1. Functionally this
still tears the pair down; could the message be adjusted to match what the
test actually sets up?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/QDISC-2899.v2.20260901233641%40mojatatu.com
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-05 1:10 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02 21:29 [PATCH net v2 1/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations Jamal Hadi Salim
2026-09-02 21:29 ` [PATCH net v2 0/3] " Jamal Hadi Salim
2026-09-02 21:29 ` [PATCH net v2 2/3] net: reject oversized tx_queue_len at netlink parse time Jamal Hadi Salim
2026-09-05 1:10 ` netdev-bot+sashiko
2026-09-02 21:29 ` [PATCH net v2 3/3] selftests: tc-testing: add tx_queue_len cap regression tests Jamal Hadi Salim
2026-09-05 1:10 ` netdev-bot+sashiko
2026-09-04 23:40 ` [PATCH net v2 0/3] net: cap tx_queue_len at S16_MAX to prevent oversized ring allocations patchwork-bot+netdevbpf
2026-09-05 1:10 ` [PATCH net v2 1/3] " netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox