AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/12 06:08 flow patch-triage
0m Args:
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) and does not affect the compiled kernel image (vmlinux) or kernel modules.",
  "WorthFuzzing": false
}

1/1 2026/09/12 06:08 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 865c14eb633c73486bda5bef80a23d436fe8e077\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Sep 12 06:08:39 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..fafe2975401b0 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 15x the regular timeout\n+        if test_name.startswith(\"big_tcp_\"):\n+            ctl_file_write(flush_path, \"3000000\")\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\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/12 06:08 agent patch-evaluator
0m Model:
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) and does not affect the compiled kernel image (vmlinux) or kernel modules.",
  "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 865c14eb633c73486bda5bef80a23d436fe8e077
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Sep 12 06:08:39 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..fafe2975401b0 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 15x the regular timeout
+        if test_name.startswith("big_tcp_"):
+            ctl_file_write(flush_path, "3000000")
+        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
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/12 06:08 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 7143
							output: 102
							thoughts: 889
						

Thoughts:
**Analysis of the Patch for Fuzzing Eligibility**

Okay, let's break this down. The diff affects `tools/testing/selftests/drivers/net/gro_lib.py` and `tools/testing/selftests/net/lib/gro.c`. My initial thought is to confirm that *only* these files are modified, and I'd typically use `git log` or `git status` for that. Then I remember the explicit instruction for this scenario – to check if any kernel files are affected. The prompt shows the full commit diff, confirming only those two files. Good.

These files are located in `tools/testing/selftests/`, clearly user-space self-tests for network-related GRO (Generic Receive Offload) functionality. They *don't* build into the kernel image or modules. Now, I have to apply the logic based on Section 2 of the directions.

The instructions are very specific: "Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories..." and one of those categories explicitly includes "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." That's exactly what we have here. These self-tests fall squarely into that category.

So, `WorthFuzzing` has to be `false`. There's no kernel code being modified, no need for specific configurations, and no specific symbols to focus on. Based on the documentation, this means:

*   `EnableConfigs`: `[]` (empty list)
*   `FocusSymbols`: `[]` (empty list)
*   `Reasoning`: The patch only modifies selftests in `tools/testing/selftests/` (specifically `tools/testing/selftests/drivers/net/gro_lib.py` and `tools/testing/selftests/net/lib/gro.c`). No kernel-space code, vmlinux, or kernel modules are modified.
*   `WorthFuzzing`: `false`

I need to quickly double-check the required format for `set-results`, and everything seems correct. I'm ready to move forward.



4/2 2026/09/12 06:08 tool set-results
0m Args:
{
  "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) and does not affect the compiled kernel image (vmlinux) or kernel modules.",
  "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) and does not affect the compiled kernel image (vmlinux) or kernel modules.",
  "WorthFuzzing": false
}

5/1 2026/09/12 06:08 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)