From: Weiming Shi <bestswngs@gmail.com>
To: "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>,
Jamal Hadi Salim <jhs@mojatatu.com>,
Jiri Pirko <jiri@resnulli.us>, Shuah Khan <shuah@kernel.org>
Cc: netdev@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-kernel@vger.kernel.org, Xiang Mei <xmei5@asu.edu>,
co+1fe9b56e2c61be5e@bugs.sh, Weiming Shi <bestswngs@gmail.com>
Subject: [PATCH 2/2] selftests: tc-testing: cover unset TCP transport header in TBF
Date: Mon, 28 Sep 2026 00:31:17 +0800 [thread overview]
Message-ID: <20260927163117.746432-3-bestswngs@gmail.com> (raw)
In-Reply-To: <20260927163117.746432-2-bestswngs@gmail.com>
A TCP GSO skb created through TUN can retain an unset transport
header. Send one such packet through TBF, then send another through
police and TBF. The police limit makes the vulnerable kernel stop at
one TBF drop while the fixed kernel reaches two. This checks behavior
without relying on KASAN or global logs.
Use the existing tc-testing namespace and JSON verification. The
helper creates the TUN packet, changes the ingress filter, and waits
for TBF counters.
Assisted-by: LLM
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
tools/testing/selftests/tc-testing/config | 3 +
.../tc-testing/tc-tests/qdiscs/tbf.json | 28 ++++++
.../testing/selftests/tc-testing/tdc_vnet.py | 87 +++++++++++++++++++
3 files changed, 118 insertions(+)
create mode 100644 tools/testing/selftests/tc-testing/tdc_vnet.py
diff --git a/tools/testing/selftests/tc-testing/config b/tools/testing/selftests/tc-testing/config
index 0e5618be03359..7d1a140464948 100644
--- a/tools/testing/selftests/tc-testing/config
+++ b/tools/testing/selftests/tc-testing/config
@@ -5,6 +5,9 @@
CONFIG_DUMMY=y
CONFIG_VETH=y
CONFIG_IFB=y
+CONFIG_TUN=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_DEBUG_NET=y
#
# Core Netfilter Configuration
diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/tbf.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/tbf.json
index 547a449100411..ef850a5d28ffe 100644
--- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/tbf.json
+++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/tbf.json
@@ -189,5 +189,33 @@
"teardown": [
"$TC qdisc del dev $DUMMY handle 1: root"
]
+ },
+ {
+ "id": "7f31",
+ "name": "Drop GSO packet with unset transport header",
+ "category": [
+ "qdisc",
+ "tbf"
+ ],
+ "plugins": {
+ "requires": "nsPlugin"
+ },
+ "setup": [
+ "$IP link set dev $IFB mtu 256",
+ "$TC qdisc add dev $IFB handle 1: root tbf limit 4096 burst 300 rate 1mbit"
+ ],
+ "cmdUnderTest": "python3 ./tdc_vnet.py $IP $TC $IFB",
+ "expExitCode": "0",
+ "verifyCmd": "$TC -s -j qdisc show dev $IFB root",
+ "matchJSON": [
+ {
+ "kind": "tbf",
+ "handle": "1:",
+ "drops": 2
+ }
+ ],
+ "teardown": [
+ "$TC qdisc del dev $IFB handle 1: root"
+ ]
}
]
diff --git a/tools/testing/selftests/tc-testing/tdc_vnet.py b/tools/testing/selftests/tc-testing/tdc_vnet.py
new file mode 100644
index 0000000000000..bdd663c5795ac
--- /dev/null
+++ b/tools/testing/selftests/tc-testing/tdc_vnet.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Exercise TBF and police with a TCP GSO skb lacking a transport header."""
+
+import fcntl
+import json
+import os
+import struct
+import subprocess
+import sys
+import time
+
+
+# These architectures use a different _IOW direction encoding.
+TUNSETIFF = (0x800454ca if os.uname().machine.startswith(
+ ("alpha", "hppa", "mips", "parisc", "ppc", "sparc")) else 0x400454ca)
+IFF_TUN = 0x0001
+IFF_NO_PI = 0x1000
+IFF_VNET_HDR = 0x4000
+TUN = "tuntdc0"
+DEADLINE = time.monotonic() + 18
+
+
+def run(*argv):
+ remaining = DEADLINE - time.monotonic()
+ if remaining <= 0:
+ raise RuntimeError("selftest deadline expired")
+ return subprocess.check_output(argv, stderr=subprocess.STDOUT,
+ text=True, timeout=min(3, remaining))
+
+
+def tbf_drops(tc, ifb):
+ qdiscs = json.loads(run(tc, "-s", "-j", "qdisc", "show", "dev", ifb,
+ "root"))
+ return next(qdisc["drops"] for qdisc in qdiscs
+ if qdisc["kind"] == "tbf" and qdisc["handle"] == "1:")
+
+
+def wait_for_drops(tc, ifb, expected):
+ deadline = min(DEADLINE, time.monotonic() + 5)
+ while time.monotonic() < deadline:
+ if tbf_drops(tc, ifb) >= expected:
+ return
+ time.sleep(0.05)
+ raise RuntimeError(f"TBF did not reach {expected} drops")
+
+
+def main(ip, tc, ifb):
+ tun = os.open("/dev/net/tun", os.O_RDWR | os.O_CLOEXEC | os.O_NONBLOCK)
+ try:
+ ifreq = struct.pack("16sH", TUN.encode(),
+ IFF_TUN | IFF_NO_PI | IFF_VNET_HDR)
+ fcntl.ioctl(tun, TUNSETIFF, ifreq)
+ run(ip, "link", "set", "dev", TUN, "up")
+ run(tc, "qdisc", "add", "dev", TUN, "clsact")
+ run(tc, "filter", "add", "dev", TUN, "ingress", "pref", "1",
+ "matchall", "action", "mirred", "egress", "redirect",
+ "dev", ifb)
+
+ # GSO without NEEDS_CSUM leaves transport_header unset. IPv4 IHL=0
+ # prevents the later transport-header probe from filling it in.
+ packet = struct.pack("=BBHHHH", 0, 1, 0, 8, 0, 0) + b"\x40" + bytes(999)
+ if os.write(tun, packet) != len(packet):
+ raise RuntimeError("short TUN write")
+ wait_for_drops(tc, ifb, 1)
+
+ run(tc, "filter", "delete", "dev", TUN, "ingress", "pref", "1")
+ run(tc, "filter", "add", "dev", TUN, "ingress", "pref", "1",
+ "matchall", "action", "police", "mtu", "65700",
+ "conform-exceed", "pipe/drop", "action", "mirred", "egress",
+ "redirect", "dev", ifb)
+ if os.write(tun, packet) != len(packet):
+ raise RuntimeError("short TUN write")
+ wait_for_drops(tc, ifb, 2)
+ finally:
+ os.close(tun)
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 4:
+ sys.exit(f"usage: {sys.argv[0]} IP TC IFB")
+ try:
+ main(*sys.argv[1:])
+ except (OSError, ValueError, RuntimeError, StopIteration,
+ subprocess.SubprocessError) as error:
+ sys.exit(f"tdc_vnet: {error}")
--
2.55.0
next prev parent reply other threads:[~2026-09-27 16:31 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-27 16:31 [PATCH 1/2] net: gso: validate TCP headers before segment length checks Weiming Shi
2026-09-27 16:31 ` Weiming Shi [this message]
2026-09-27 17:10 ` Eric Dumazet
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=20260927163117.746432-3-bestswngs@gmail.com \
--to=bestswngs@gmail.com \
--cc=co+1fe9b56e2c61be5e@bugs.sh \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=jhs@mojatatu.com \
--cc=jiri@resnulli.us \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shuah@kernel.org \
--cc=xmei5@asu.edu \
/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
all inboxes | Powered by JetHome®