| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/13 06:22 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/ (tools/testing/selftests/drivers/net/gro_lib.py and tools/testing/selftests/net/lib/gro.c) to add BIG TCP GRO test cases. No kernel-space code or compiled kernel artifacts (vmlinux or kernel modules) are modified.",
"WorthFuzzing": false
} |
| 1/1 | 2026/09/13 06:22 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 9d19e3b52243f8f20ee23c79bee41d98166c0970\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Sep 13 06:21:59 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/tools/testing/selftests/drivers/net/gro_lib.py b/tools/testing/selftests/drivers/net/gro_lib.py\nindex b7ac0660adc01..abecf60baa537 100644\n--- a/tools/testing/selftests/drivers/net/gro_lib.py\n+++ b/tools/testing/selftests/drivers/net/gro_lib.py\n@@ -40,13 +40,17 @@ Test cases:\n - ip_v6ext_diff: (IPv6) IPv6 ext header with different payload doesn't coalesce\n - large_max: Packets exceeding GRO_MAX_SIZE don't coalesce\n - large_rem: Large packet remainder handling\n+ - big_tcp_data_same: Same size packets coalesce past IP_MAXPACKET\n+ - big_tcp_data_lrg_sml: Smaller last packet coalesces past IP_MAXPACKET\n+ - big_tcp_tcp_seq: Packets with 16-bit truncated seqno don't coalesce\n+ - big_tcp_large_max: Packets exceeding the BIG TCP limit don't coalesce\n \"\"\"\n \n import glob\n import os\n import re\n-from lib.py import ksft_run, ksft_exit, ksft_pr\n-from lib.py import NetDrvEpEnv, KsftFailEx, KsftXfailEx\n+from lib.py import ksft_run, ksft_exit, ksft_pr, ksft_eq\n+from lib.py import NetDrvEpEnv, KsftFailEx, KsftSkipEx, KsftXfailEx\n from lib.py import NetdevFamily, EthtoolFamily\n from lib.py import bkg, cmd, ctl_file_write, defer, ethtool, ip\n from lib.py import ksft_variants, KsftNamedVariant\n@@ -55,6 +59,8 @@ from lib.py import ksft_variants, KsftNamedVariant\n # gro.c uses hardcoded DPORT=8000\n GRO_DPORT = 8000\n \n+BIG_TCP_GRO_MAX_SIZE = 128000\n+\n \n def _resolve_dmac(cfg, ipver):\n \"\"\"\n@@ -91,6 +97,34 @@ def _set_mtu_restore(dev, mtu, host):\n defer(ip, f\"link set dev {dev['ifname']} mtu {dev['mtu']}\", host=host)\n \n \n+def _set_gro_size_restore(cfg, size):\n+ \"\"\"\n+ Set the local device's GRO size limits, then confirm they stuck.\n+ \"\"\"\n+\n+ _set_mtu_restore(cfg.dev, 4096, None)\n+ _set_mtu_restore(cfg.remote_dev, 4096, cfg.remote)\n+\n+ if \"gro_max_size\" not in cfg.dev or \"gro_ipv4_max_size\" not in cfg.dev:\n+ raise KsftSkipEx(\"iproute2 does not report the GRO size limits\")\n+\n+ if (cfg.dev[\"gro_max_size\"] == size and\n+ cfg.dev[\"gro_ipv4_max_size\"] == size):\n+ return\n+\n+ old = (f\"gro_max_size {cfg.dev['gro_max_size']} \"\n+ f\"gro_ipv4_max_size {cfg.dev['gro_ipv4_max_size']}\")\n+ new = f\"gro_max_size {size} gro_ipv4_max_size {size}\"\n+\n+ ip(f\"link set dev {cfg.ifname} {new}\")\n+ defer(ip, f\"link set dev {cfg.ifname} {old}\")\n+\n+ dev = ip(\"-d link show dev \" + cfg.ifname, json=True)[0]\n+ ksft_eq(dev[\"gro_max_size\"], size, comment=\"gro_max_size not applied\")\n+ ksft_eq(dev[\"gro_ipv4_max_size\"], size,\n+ comment=\"gro_ipv4_max_size not applied\")\n+\n+\n def _set_ethtool_feat(dev, current, feats, host=None):\n s2n = {True: \"on\", False: \"off\"}\n \n@@ -239,7 +273,11 @@ def _setup(cfg, mode, test_name):\n flush_path = f\"/sys/class/net/{cfg.ifname}/gro_flush_timeout\"\n irq_path = f\"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs\"\n \n- ctl_file_write(flush_path, \"200000\")\n+ # \"big_tcp_*\" tests need a longer timeout, use 2x the regular timeout\n+ if test_name.startswith(\"big_tcp_\"):\n+ ctl_file_write(flush_path, \"400000\")\n+ else:\n+ ctl_file_write(flush_path, \"200000\")\n ctl_file_write(irq_path, \"10\")\n \n _set_ethtool_feat(cfg.ifname, cfg.feat,\n@@ -289,6 +327,9 @@ def _setup(cfg, mode, test_name):\n except KsftXfailEx:\n pass\n \n+ if test_name.startswith(\"big_tcp_\"):\n+ _set_gro_size_restore(cfg, BIG_TCP_GRO_MAX_SIZE)\n+\n \n def _gro_variants():\n \"\"\"Generator that yields all combinations of protocol and test types.\"\"\"\n@@ -304,6 +345,11 @@ def _gro_variants():\n \"large_max\", \"large_rem\",\n ]\n \n+ big_tcp_tests = [\n+ \"big_tcp_data_same\", \"big_tcp_data_lrg_sml\",\n+ \"big_tcp_tcp_seq\", \"big_tcp_large_max\",\n+ ]\n+\n # Tests specific to IPv4\n ipv4_tests = [\n \"ip_csum\",\n@@ -322,6 +368,10 @@ def _gro_variants():\n for test_name in common_tests:\n yield protocol, test_name\n \n+ if protocol in [\"ipv4\", \"ipv6\"]:\n+ for test_name in big_tcp_tests:\n+ yield protocol, test_name\n+\n if protocol in [\"ipv4\", \"ipip\"]:\n for test_name in ipv4_tests:\n yield protocol, test_name\n@@ -358,7 +408,8 @@ def run_test(cfg, mode, protocol, test_name):\n if rx_proc.ret == 42:\n raise KsftFailEx(f\"GRO over-coalesced in {protocol}/{test_name}\")\n \n- if test_name.startswith(\"large_\") and os.environ.get(\"KSFT_MACHINE_SLOW\"):\n+ if (test_name.startswith((\"large_\", \"big_tcp_\")) and\n+ os.environ.get(\"KSFT_MACHINE_SLOW\")):\n ksft_pr(f\"Ignoring {protocol}/{test_name} failure due to slow environment\")\n return\n \ndiff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c\nindex 7a333155de1ab..70b0deb3c11fa 100644\n--- a/tools/testing/selftests/net/lib/gro.c\n+++ b/tools/testing/selftests/net/lib/gro.c\n@@ -46,6 +46,12 @@\n * - large_max: exceeding max size\n * - large_rem: remainder handling\n *\n+ * big_tcp_*:\n+ * - big_tcp_data_same: equal segments coalescing past IP_MAXPACKET\n+ * - big_tcp_data_lrg_sml: a smaller final segment carrying it over\n+ * - big_tcp_tcp_seq: 16-bit truncated sequence number must not coalesce\n+ * - big_tcp_large_max: coalescing stops at the configured limit\n+ *\n * single, capacity:\n * Boring cases used to test coalescing machinery itself and stats\n * more than protocol behavior.\n@@ -110,6 +116,16 @@\n \n #define EXIT_OVER_COALESCE\t42\n \n+/* Must match BIG_TCP_GRO_MAX_SIZE in gro_lib.py. */\n+#define BIG_TCP_GRO_MAX_SIZE\t128000\n+\n+#define BIG_TCP_RECV_BUF_LEN \\\n+\t(BIG_TCP_GRO_MAX_SIZE + MAX_MSS + L2_HLEN_MAX)\n+#define BIG_TCP_MIN_MSS\t\t(ASSUMED_MTU - (MAX_HDR_LEN - ETH_HLEN))\n+#define BIG_TCP_MAX_FILL_CNT \\\n+\t((int)((BIG_TCP_GRO_MAX_SIZE - 1 - (MAX_HDR_LEN - ETH_HLEN)) / \\\n+\t BIG_TCP_MIN_MSS))\n+\n #define ipv6_optlen(p) (((p)-\u003ehdrlen+1) \u003c\u003c 3) /* calculate IPv6 extension header len */\n #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))\n \n@@ -166,6 +182,27 @@ static int num_large_pkt(void)\n \treturn max_payload() / calc_mss();\n }\n \n+/* How many maximum sized segments fit under the configured limit. */\n+static int big_tcp_large_cnt(void)\n+{\n+\treturn (BIG_TCP_GRO_MAX_SIZE - 1 - (total_hdr_len - ETH_HLEN)) /\n+\t calc_mss();\n+}\n+\n+/* How many calc_mss() sized segments are needed to satisfy the\n+ * following condition:\n+ * pkt_count * calc_mss() \u003c IP_MAXPACKET \u003c (pkt_count + 1) * calc_mss()\n+ */\n+static int big_tcp_fill_cnt(void)\n+{\n+\treturn IP_MAXPACKET / calc_mss();\n+}\n+\n+static int big_tcp_fill_len(void)\n+{\n+\treturn big_tcp_fill_cnt() * calc_mss();\n+}\n+\n static void vlog(const char *fmt, ...)\n {\n \tva_list args;\n@@ -560,6 +597,61 @@ static void send_data_pkts(int fd, struct sockaddr_ll *daddr,\n \twrite_packet(fd, buf, total_hdr_len + payload_len2, daddr);\n }\n \n+/* Send num_pkt segments of pkt_len bytes, then one of remainder len. */\n+static void send_big_tcp(int fd, struct sockaddr_ll *daddr, int pkt_len,\n+\t\t\t int num_pkt, int remainder)\n+{\n+\tstatic char pkts[BIG_TCP_MAX_FILL_CNT][MAX_HDR_LEN + MAX_MSS];\n+\tstatic char last[MAX_HDR_LEN + MAX_MSS];\n+\tconst int filled = num_pkt * pkt_len;\n+\tint i;\n+\n+\tif (num_pkt \u003e BIG_TCP_MAX_FILL_CNT)\n+\t\terror(1, 0, \"need %d packets, array holds %d\",\n+\t\t num_pkt, BIG_TCP_MAX_FILL_CNT);\n+\n+\tfor (i = 0; i \u003c num_pkt; i++)\n+\t\tcreate_packet(pkts[i], i * pkt_len, 0, pkt_len, 0);\n+\tcreate_packet(last, filled, 0, remainder, 0);\n+\n+\tfor (i = 0; i \u003c num_pkt; i++)\n+\t\twrite_packet(fd, pkts[i], total_hdr_len + pkt_len, daddr);\n+\twrite_packet(fd, last, total_hdr_len + remainder, daddr);\n+}\n+\n+/* In BIG TCP configuration, the total aggregate length can\n+ * be greater than the legacy IP_MAXPACKET. Since the aggregate\n+ * length is used in calculating the sequence numbers,\n+ * send a packet with a sequence number that differs by IP_MAXPACKET + 1,\n+ * to test against truncation bugs.\n+ */\n+static void send_big_tcp_bad_seq(int fd, struct sockaddr_ll *daddr)\n+{\n+\tstatic char pkts[BIG_TCP_MAX_FILL_CNT][MAX_HDR_LEN + MAX_MSS];\n+\tconst int num_pkt = big_tcp_fill_cnt() + 1;\n+\tstatic char last[MAX_HDR_LEN + MAX_MSS];\n+\tconst int pkt_len = calc_mss();\n+\tint bad_seq;\n+\tint filled;\n+\tint i;\n+\n+\tfilled = num_pkt * pkt_len;\n+\t/* Low 16 bits match with the correct incoming sequence number. */\n+\tbad_seq = filled - (IP_MAXPACKET + 1);\n+\n+\tif (num_pkt \u003e BIG_TCP_MAX_FILL_CNT)\n+\t\terror(1, 0, \"need %d packets, array holds %d\",\n+\t\t num_pkt, BIG_TCP_MAX_FILL_CNT);\n+\n+\tfor (i = 0; i \u003c num_pkt; i++)\n+\t\tcreate_packet(pkts[i], i * pkt_len, 0, pkt_len, 0);\n+\tcreate_packet(last, bad_seq, 0, pkt_len, 0);\n+\n+\tfor (i = 0; i \u003c num_pkt; i++)\n+\t\twrite_packet(fd, pkts[i], total_hdr_len + pkt_len, daddr);\n+\twrite_packet(fd, last, total_hdr_len + pkt_len, daddr);\n+}\n+\n /* If incoming segments make tracked segment length exceed\n * legal IP datagram length, do not coalesce\n */\n@@ -1161,7 +1253,7 @@ static void recv_error(int fd, int rcv_errno)\n static void check_recv_pkts(int fd, int *correct_payload,\n \t\t\t int correct_num_pkts)\n {\n-\tstatic char buffer[IP_MAXPACKET + L2_HLEN_MAX + 1];\n+\tstatic char buffer[BIG_TCP_RECV_BUF_LEN];\n \tint nhoff = ETH_HLEN + (pppoe ? PPPOE_SES_HLEN : 0);\n \tstruct iphdr *iph = (struct iphdr *)(buffer + nhoff);\n \tstruct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + nhoff);\n@@ -1541,6 +1633,25 @@ static void gro_sender(void)\n \t\tsend_large(txfd, \u0026daddr, remainder + 1);\n \t\twrite_packet(txfd, fin_pkt, total_hdr_len, \u0026daddr);\n \n+\t/* big tcp sub-tests */\n+\t} else if (strcmp(testname, \"big_tcp_data_same\") == 0) {\n+\t\tsend_big_tcp(txfd, \u0026daddr, calc_mss(), big_tcp_fill_cnt(),\n+\t\t\t calc_mss());\n+\t\twrite_packet(txfd, fin_pkt, total_hdr_len, \u0026daddr);\n+\t} else if (strcmp(testname, \"big_tcp_data_lrg_sml\") == 0) {\n+\t\tint remainder = calc_mss() / 2;\n+\n+\t\tsend_big_tcp(txfd, \u0026daddr, calc_mss(), big_tcp_fill_cnt(),\n+\t\t\t remainder);\n+\t\twrite_packet(txfd, fin_pkt, total_hdr_len, \u0026daddr);\n+\t} else if (strcmp(testname, \"big_tcp_tcp_seq\") == 0) {\n+\t\tsend_big_tcp_bad_seq(txfd, \u0026daddr);\n+\t\twrite_packet(txfd, fin_pkt, total_hdr_len, \u0026daddr);\n+\t} else if (strcmp(testname, \"big_tcp_large_max\") == 0) {\n+\t\tsend_big_tcp(txfd, \u0026daddr, calc_mss(), big_tcp_large_cnt(),\n+\t\t\t calc_mss());\n+\t\twrite_packet(txfd, fin_pkt, total_hdr_len, \u0026daddr);\n+\n \t/* machinery sub-tests */\n \t} else if (strcmp(testname, \"single\") == 0) {\n \t\tstatic char buf[MAX_HDR_LEN + PAYLOAD_LEN];\n@@ -1768,6 +1879,26 @@ static void gro_receiver(void)\n \t\tprintf(\"last segment sent individually: \");\n \t\tcheck_recv_pkts(rxfd, correct_payload, 3);\n \n+\t/* big tcp sub-tests */\n+\t} else if (strcmp(testname, \"big_tcp_data_same\") == 0) {\n+\t\tcorrect_payload[0] = big_tcp_fill_len() + calc_mss();\n+\t\tprintf(\"data packets of same size past IP_MAXPACKET: \");\n+\t\tcheck_recv_pkts(rxfd, correct_payload, 1);\n+\t} else if (strcmp(testname, \"big_tcp_data_lrg_sml\") == 0) {\n+\t\tcorrect_payload[0] = big_tcp_fill_len() + calc_mss() / 2;\n+\t\tprintf(\"smaller last packet past IP_MAXPACKET: \");\n+\t\tcheck_recv_pkts(rxfd, correct_payload, 1);\n+\t} else if (strcmp(testname, \"big_tcp_tcp_seq\") == 0) {\n+\t\tcorrect_payload[0] = (big_tcp_fill_cnt() + 1) * calc_mss();\n+\t\tcorrect_payload[1] = calc_mss();\n+\t\tprintf(\"aliased seq past IP_MAXPACKET doesn't coalesce: \");\n+\t\tcheck_recv_pkts(rxfd, correct_payload, 2);\n+\t} else if (strcmp(testname, \"big_tcp_large_max\") == 0) {\n+\t\tcorrect_payload[0] = big_tcp_large_cnt() * calc_mss();\n+\t\tcorrect_payload[1] = calc_mss();\n+\t\tprintf(\"shouldn't coalesce past gro_max_size: \");\n+\t\tcheck_recv_pkts(rxfd, correct_payload, 2);\n+\n \t/* machinery sub-tests */\n \t} else if (strcmp(testname, \"single\") == 0) {\n \t\tprintf(\"single data packet: \");\n"
} |
| 2/1 | 2026/09/13 06:22 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/ (tools/testing/selftests/drivers/net/gro_lib.py and tools/testing/selftests/net/lib/gro.c) to add BIG TCP GRO test cases. No kernel-space code or compiled kernel artifacts (vmlinux or kernel modules) are modified.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 9d19e3b52243f8f20ee23c79bee41d98166c0970
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 13 06:21:59 2026 +0000
syz-cluster: applied patch under review
diff --git a/tools/testing/selftests/drivers/net/gro_lib.py b/tools/testing/selftests/drivers/net/gro_lib.py
index b7ac0660adc01..abecf60baa537 100644
--- a/tools/testing/selftests/drivers/net/gro_lib.py
+++ b/tools/testing/selftests/drivers/net/gro_lib.py
@@ -40,13 +40,17 @@ Test cases:
- ip_v6ext_diff: (IPv6) IPv6 ext header with different payload doesn't coalesce
- large_max: Packets exceeding GRO_MAX_SIZE don't coalesce
- large_rem: Large packet remainder handling
+ - big_tcp_data_same: Same size packets coalesce past IP_MAXPACKET
+ - big_tcp_data_lrg_sml: Smaller last packet coalesces past IP_MAXPACKET
+ - big_tcp_tcp_seq: Packets with 16-bit truncated seqno don't coalesce
+ - big_tcp_large_max: Packets exceeding the BIG TCP limit don't coalesce
"""
import glob
import os
import re
-from lib.py import ksft_run, ksft_exit, ksft_pr
-from lib.py import NetDrvEpEnv, KsftFailEx, KsftXfailEx
+from lib.py import ksft_run, ksft_exit, ksft_pr, ksft_eq
+from lib.py import NetDrvEpEnv, KsftFailEx, KsftSkipEx, KsftXfailEx
from lib.py import NetdevFamily, EthtoolFamily
from lib.py import bkg, cmd, ctl_file_write, defer, ethtool, ip
from lib.py import ksft_variants, KsftNamedVariant
@@ -55,6 +59,8 @@ from lib.py import ksft_variants, KsftNamedVariant
# gro.c uses hardcoded DPORT=8000
GRO_DPORT = 8000
+BIG_TCP_GRO_MAX_SIZE = 128000
+
def _resolve_dmac(cfg, ipver):
"""
@@ -91,6 +97,34 @@ def _set_mtu_restore(dev, mtu, host):
defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host)
+def _set_gro_size_restore(cfg, size):
+ """
+ Set the local device's GRO size limits, then confirm they stuck.
+ """
+
+ _set_mtu_restore(cfg.dev, 4096, None)
+ _set_mtu_restore(cfg.remote_dev, 4096, cfg.remote)
+
+ if "gro_max_size" not in cfg.dev or "gro_ipv4_max_size" not in cfg.dev:
+ raise KsftSkipEx("iproute2 does not report the GRO size limits")
+
+ if (cfg.dev["gro_max_size"] == size and
+ cfg.dev["gro_ipv4_max_size"] == size):
+ return
+
+ old = (f"gro_max_size {cfg.dev['gro_max_size']} "
+ f"gro_ipv4_max_size {cfg.dev['gro_ipv4_max_size']}")
+ new = f"gro_max_size {size} gro_ipv4_max_size {size}"
+
+ ip(f"link set dev {cfg.ifname} {new}")
+ defer(ip, f"link set dev {cfg.ifname} {old}")
+
+ dev = ip("-d link show dev " + cfg.ifname, json=True)[0]
+ ksft_eq(dev["gro_max_size"], size, comment="gro_max_size not applied")
+ ksft_eq(dev["gro_ipv4_max_size"], size,
+ comment="gro_ipv4_max_size not applied")
+
+
def _set_ethtool_feat(dev, current, feats, host=None):
s2n = {True: "on", False: "off"}
@@ -239,7 +273,11 @@ def _setup(cfg, mode, test_name):
flush_path = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout"
irq_path = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs"
- ctl_file_write(flush_path, "200000")
+ # "big_tcp_*" tests need a longer timeout, use 2x the regular timeout
+ if test_name.startswith("big_tcp_"):
+ ctl_file_write(flush_path, "400000")
+ else:
+ ctl_file_write(flush_path, "200000")
ctl_file_write(irq_path, "10")
_set_ethtool_feat(cfg.ifname, cfg.feat,
@@ -289,6 +327,9 @@ def _setup(cfg, mode, test_name):
except KsftXfailEx:
pass
+ if test_name.startswith("big_tcp_"):
+ _set_gro_size_restore(cfg, BIG_TCP_GRO_MAX_SIZE)
+
def _gro_variants():
"""Generator that yields all combinations of protocol and test types."""
@@ -304,6 +345,11 @@ def _gro_variants():
"large_max", "large_rem",
]
+ big_tcp_tests = [
+ "big_tcp_data_same", "big_tcp_data_lrg_sml",
+ "big_tcp_tcp_seq", "big_tcp_large_max",
+ ]
+
# Tests specific to IPv4
ipv4_tests = [
"ip_csum",
@@ -322,6 +368,10 @@ def _gro_variants():
for test_name in common_tests:
yield protocol, test_name
+ if protocol in ["ipv4", "ipv6"]:
+ for test_name in big_tcp_tests:
+ yield protocol, test_name
+
if protocol in ["ipv4", "ipip"]:
for test_name in ipv4_tests:
yield protocol, test_name
@@ -358,7 +408,8 @@ def run_test(cfg, mode, protocol, test_name):
if rx_proc.ret == 42:
raise KsftFailEx(f"GRO over-coalesced in {protocol}/{test_name}")
- if test_name.startswith("large_") and os.environ.get("KSFT_MACHINE_SLOW"):
+ if (test_name.startswith(("large_", "big_tcp_")) and
+ os.environ.get("KSFT_MACHINE_SLOW")):
ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow environment")
return
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 7a333155de1ab..70b0deb3c11fa 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -46,6 +46,12 @@
* - large_max: exceeding max size
* - large_rem: remainder handling
*
+ * big_tcp_*:
+ * - big_tcp_data_same: equal segments coalescing past IP_MAXPACKET
+ * - big_tcp_data_lrg_sml: a smaller final segment carrying it over
+ * - big_tcp_tcp_seq: 16-bit truncated sequence number must not coalesce
+ * - big_tcp_large_max: coalescing stops at the configured limit
+ *
* single, capacity:
* Boring cases used to test coalescing machinery itself and stats
* more than protocol behavior.
@@ -110,6 +116,16 @@
#define EXIT_OVER_COALESCE 42
+/* Must match BIG_TCP_GRO_MAX_SIZE in gro_lib.py. */
+#define BIG_TCP_GRO_MAX_SIZE 128000
+
+#define BIG_TCP_RECV_BUF_LEN \
+ (BIG_TCP_GRO_MAX_SIZE + MAX_MSS + L2_HLEN_MAX)
+#define BIG_TCP_MIN_MSS (ASSUMED_MTU - (MAX_HDR_LEN - ETH_HLEN))
+#define BIG_TCP_MAX_FILL_CNT \
+ ((int)((BIG_TCP_GRO_MAX_SIZE - 1 - (MAX_HDR_LEN - ETH_HLEN)) / \
+ BIG_TCP_MIN_MSS))
+
#define ipv6_optlen(p) (((p)->hdrlen+1) << 3) /* calculate IPv6 extension header len */
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
@@ -166,6 +182,27 @@ static int num_large_pkt(void)
return max_payload() / calc_mss();
}
+/* How many maximum sized segments fit under the configured limit. */
+static int big_tcp_large_cnt(void)
+{
+ return (BIG_TCP_GRO_MAX_SIZE - 1 - (total_hdr_len - ETH_HLEN)) /
+ calc_mss();
+}
+
+/* How many calc_mss() sized segments are needed to satisfy the
+ * following condition:
+ * pkt_count * calc_mss() < IP_MAXPACKET < (pkt_count + 1) * calc_mss()
+ */
+static int big_tcp_fill_cnt(void)
+{
+ return IP_MAXPACKET / calc_mss();
+}
+
+static int big_tcp_fill_len(void)
+{
+ return big_tcp_fill_cnt() * calc_mss();
+}
+
static void vlog(const char *fmt, ...)
{
va_list args;
@@ -560,6 +597,61 @@ static void send_data_pkts(int fd, struct sockaddr_ll *daddr,
write_packet(fd, buf, total_hdr_len + payload_len2, daddr);
}
+/* Send num_pkt segments of pkt_len bytes, then one of remainder len. */
+static void send_big_tcp(int fd, struct sockaddr_ll *daddr, int pkt_len,
+ int num_pkt, int remainder)
+{
+ static char pkts[BIG_TCP_MAX_FILL_CNT][MAX_HDR_LEN + MAX_MSS];
+ static char last[MAX_HDR_LEN + MAX_MSS];
+ const int filled = num_pkt * pkt_len;
+ int i;
+
+ if (num_pkt > BIG_TCP_MAX_FILL_CNT)
+ error(1, 0, "need %d packets, array holds %d",
+ num_pkt, BIG_TCP_MAX_FILL_CNT);
+
+ for (i = 0; i < num_pkt; i++)
+ create_packet(pkts[i], i * pkt_len, 0, pkt_len, 0);
+ create_packet(last, filled, 0, remainder, 0);
+
+ for (i = 0; i < num_pkt; i++)
+ write_packet(fd, pkts[i], total_hdr_len + pkt_len, daddr);
+ write_packet(fd, last, total_hdr_len + remainder, daddr);
+}
+
+/* In BIG TCP configuration, the total aggregate length can
+ * be greater than the legacy IP_MAXPACKET. Since the aggregate
+ * length is used in calculating the sequence numbers,
+ * send a packet with a sequence number that differs by IP_MAXPACKET + 1,
+ * to test against truncation bugs.
+ */
+static void send_big_tcp_bad_seq(int fd, struct sockaddr_ll *daddr)
+{
+ static char pkts[BIG_TCP_MAX_FILL_CNT][MAX_HDR_LEN + MAX_MSS];
+ const int num_pkt = big_tcp_fill_cnt() + 1;
+ static char last[MAX_HDR_LEN + MAX_MSS];
+ const int pkt_len = calc_mss();
+ int bad_seq;
+ int filled;
+ int i;
+
+ filled = num_pkt * pkt_len;
+ /* Low 16 bits match with the correct incoming sequence number. */
+ bad_seq = filled - (IP_MAXPACKET + 1);
+
+ if (num_pkt > BIG_TCP_MAX_FILL_CNT)
+ error(1, 0, "need %d packets, array holds %d",
+ num_pkt, BIG_TCP_MAX_FILL_CNT);
+
+ for (i = 0; i < num_pkt; i++)
+ create_packet(pkts[i], i * pkt_len, 0, pkt_len, 0);
+ create_packet(last, bad_seq, 0, pkt_len, 0);
+
+ for (i = 0; i < num_pkt; i++)
+ write_packet(fd, pkts[i], total_hdr_len + pkt_len, daddr);
+ write_packet(fd, last, total_hdr_len + pkt_len, daddr);
+}
+
/* If incoming segments make tracked segment length exceed
* legal IP datagram length, do not coalesce
*/
@@ -1161,7 +1253,7 @@ static void recv_error(int fd, int rcv_errno)
static void check_recv_pkts(int fd, int *correct_payload,
int correct_num_pkts)
{
- static char buffer[IP_MAXPACKET + L2_HLEN_MAX + 1];
+ static char buffer[BIG_TCP_RECV_BUF_LEN];
int nhoff = ETH_HLEN + (pppoe ? PPPOE_SES_HLEN : 0);
struct iphdr *iph = (struct iphdr *)(buffer + nhoff);
struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + nhoff);
@@ -1541,6 +1633,25 @@ static void gro_sender(void)
send_large(txfd, &daddr, remainder + 1);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+ /* big tcp sub-tests */
+ } else if (strcmp(testname, "big_tcp_data_same") == 0) {
+ send_big_tcp(txfd, &daddr, calc_mss(), big_tcp_fill_cnt(),
+ calc_mss());
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+ } else if (strcmp(testname, "big_tcp_data_lrg_sml") == 0) {
+ int remainder = calc_mss() / 2;
+
+ send_big_tcp(txfd, &daddr, calc_mss(), big_tcp_fill_cnt(),
+ remainder);
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+ } else if (strcmp(testname, "big_tcp_tcp_seq") == 0) {
+ send_big_tcp_bad_seq(txfd, &daddr);
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+ } else if (strcmp(testname, "big_tcp_large_max") == 0) {
+ send_big_tcp(txfd, &daddr, calc_mss(), big_tcp_large_cnt(),
+ calc_mss());
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+
/* machinery sub-tests */
} else if (strcmp(testname, "single") == 0) {
static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
@@ -1768,6 +1879,26 @@ static void gro_receiver(void)
printf("last segment sent individually: ");
check_recv_pkts(rxfd, correct_payload, 3);
+ /* big tcp sub-tests */
+ } else if (strcmp(testname, "big_tcp_data_same") == 0) {
+ correct_payload[0] = big_tcp_fill_len() + calc_mss();
+ printf("data packets of same size past IP_MAXPACKET: ");
+ check_recv_pkts(rxfd, correct_payload, 1);
+ } else if (strcmp(testname, "big_tcp_data_lrg_sml") == 0) {
+ correct_payload[0] = big_tcp_fill_len() + calc_mss() / 2;
+ printf("smaller last packet past IP_MAXPACKET: ");
+ check_recv_pkts(rxfd, correct_payload, 1);
+ } else if (strcmp(testname, "big_tcp_tcp_seq") == 0) {
+ correct_payload[0] = (big_tcp_fill_cnt() + 1) * calc_mss();
+ correct_payload[1] = calc_mss();
+ printf("aliased seq past IP_MAXPACKET doesn't coalesce: ");
+ check_recv_pkts(rxfd, correct_payload, 2);
+ } else if (strcmp(testname, "big_tcp_large_max") == 0) {
+ correct_payload[0] = big_tcp_large_cnt() * calc_mss();
+ correct_payload[1] = calc_mss();
+ printf("shouldn't coalesce past gro_max_size: ");
+ check_recv_pkts(rxfd, correct_payload, 2);
+
/* machinery sub-tests */
} else if (strcmp(testname, "single") == 0) {
printf("single data packet: ");
|
| 3/2 | 2026/09/13 06:22 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 7301 output: 23 thoughts: 260 Thoughts: **Worth Fuzzing Decision Process** Okay, I need to figure out if this patch series is worth fuzzing with syzkaller. My first instinct is to dive into the diff. Let's see... it touches `tools/testing/selftests/drivers/net/gro_lib.py` and `tools/testing/selftests/net/lib/gro.c`. Hold on a second... those are both within `tools/testing/selftests/`! Right, I need to apply my rules here. I know the criteria for *not* fuzzing, and it's pretty clear-cut. Rule number two says I should return `WorthFuzzing=false` if *all* the modified code falls strictly into the categories of non-kernel and non-executable changes, particularly things like modifications to self-tests or scripts that don't affect the compiled kernel image or modules. Bingo! These files are right in that category. To be absolutely sure, I'll quickly double-check the `git diff` or `git show` to make absolutely certain that no other, less obvious, files were touched in the HEAD commit. But based on what I see here, it looks like this is a no-go for fuzzing. |
| 4/2 | 2026/09/13 06:22 | tool | grepper |
0mArgs:{
"Expression": "BIG_TCP_GRO_MAX_SIZE"
}Results: {
"Output": "tools/testing/selftests/drivers/net/gro_lib.py=60=GRO_DPORT = 8000\ntools/testing/selftests/drivers/net/gro_lib.py-61-\ntools/testing/selftests/drivers/net/gro_lib.py:62:BIG_TCP_GRO_MAX_SIZE = 128000\ntools/testing/selftests/drivers/net/gro_lib.py-63-\n--\ntools/testing/selftests/drivers/net/gro_lib.py=255=def _setup(cfg, mode, test_name):\n--\ntools/testing/selftests/drivers/net/gro_lib.py-330- if test_name.startswith(\"big_tcp_\"):\ntools/testing/selftests/drivers/net/gro_lib.py:331: _set_gro_size_restore(cfg, BIG_TCP_GRO_MAX_SIZE)\ntools/testing/selftests/drivers/net/gro_lib.py-332-\n--\ntools/testing/selftests/net/lib/gro.c-118-\ntools/testing/selftests/net/lib/gro.c:119:/* Must match BIG_TCP_GRO_MAX_SIZE in gro_lib.py. */\ntools/testing/selftests/net/lib/gro.c:120:#define BIG_TCP_GRO_MAX_SIZE\t128000\ntools/testing/selftests/net/lib/gro.c-121-\ntools/testing/selftests/net/lib/gro.c-122-#define BIG_TCP_RECV_BUF_LEN \\\ntools/testing/selftests/net/lib/gro.c:123:\t(BIG_TCP_GRO_MAX_SIZE + MAX_MSS + L2_HLEN_MAX)\ntools/testing/selftests/net/lib/gro.c-124-#define BIG_TCP_MIN_MSS\t\t(ASSUMED_MTU - (MAX_HDR_LEN - ETH_HLEN))\ntools/testing/selftests/net/lib/gro.c-125-#define BIG_TCP_MAX_FILL_CNT \\\ntools/testing/selftests/net/lib/gro.c:126:\t((int)((BIG_TCP_GRO_MAX_SIZE - 1 - (MAX_HDR_LEN - ETH_HLEN)) / \\\ntools/testing/selftests/net/lib/gro.c-127-\t BIG_TCP_MIN_MSS))\n--\ntools/testing/selftests/net/lib/gro.c=186=static int big_tcp_large_cnt(void)\ntools/testing/selftests/net/lib/gro.c-187-{\ntools/testing/selftests/net/lib/gro.c:188:\treturn (BIG_TCP_GRO_MAX_SIZE - 1 - (total_hdr_len - ETH_HLEN)) /\ntools/testing/selftests/net/lib/gro.c-189-\t calc_mss();\n"
} |
| 5/2 | 2026/09/13 06:22 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 8620 output: 113 thoughts: 328 Thoughts: **My Assessment: No Fuzzing Required** Okay, let's quickly verify. A quick `git log -n 1 --stat` check or look for any other unseen modifications. It appears the changes are confined to the `tools/testing/selftests/` directory. The diff confirms this, specifically touching `tools/testing/selftests/drivers/net/gro_lib.py` and `tools/testing/selftests/net/lib/gro.c`. Given SECTION 2's "Negative Criteria," this is straightforward. The changes are non-kernel and non-executable. They fall squarely under the category of user-space self-tests within `tools/testing/selftests/`, which by definition do *not* affect the kernel image (vmlinux) or any modules. Therefore, my assessment is: * `WorthFuzzing = False`. * `FocusSymbols = []`. * `EnableConfigs = []`. The rationale is precise. The patch *only* adds BIG TCP test cases for Generic Receive Offload (GRO) to the self-test code. It's completely isolated from any kernel code or the compilation of the kernel itself. Just to double-check my output, the `set-results` parameters are as I've derived: * `WorthFuzzing = False` (Boolean - as expected) * `FocusSymbols = []` (Empty list - correct) * `EnableConfigs = []` (Empty list - also correct) The conclusion is absolute. This aligns perfectly with the defined instructions. No fuzzing is required. |
| 6/2 | 2026/09/13 06:22 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/ (tools/testing/selftests/drivers/net/gro_lib.py and tools/testing/selftests/net/lib/gro.c) to add BIG TCP GRO test cases. No kernel-space code or compiled kernel artifacts (vmlinux or kernel modules) are modified.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch only modifies user-space selftests under tools/testing/selftests/ (tools/testing/selftests/drivers/net/gro_lib.py and tools/testing/selftests/net/lib/gro.c) to add BIG TCP GRO test cases. No kernel-space code or compiled kernel artifacts (vmlinux or kernel modules) are modified.",
"WorthFuzzing": false
} |
| 7/1 | 2026/09/13 06:22 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|