| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/20 10:55 | flow | patch-triage |
3mArgs:null Results: {
"EnableConfigs": [
"NET",
"INET"
],
"FocusSymbols": [
"__skb_gso_segment",
"skb_segment",
"tcp_gso_segment",
"validate_xmit_skb",
"skb_gso_resegment_max_segs"
],
"KMSANReasoning": "The patch series adds bounded GSO resegmentation for oversized TCP skbs in the networking core transmit path (net/core/dev.c, net/core/gso.c, net/core/skbuff.c, etc.).\n\nKey considerations regarding memory initialization and sanitizers:\n1. The new field `max_segs` in `struct skb_gso_cb` occupies what was previously 2 bytes of padding within `skb-\u003ecb`. It is explicitly initialized to `min_t(unsigned int, max_segs, U16_MAX)` in `__skb_gso_segment()` and reset to 0 in newly created segment skbs in `skb_segment()`.\n2. All local variables touched or introduced (e.g., `resegment_max_segs = 0` in `validate_xmit_skb()`, stack buffer `_tcph` passed to `skb_header_pointer()`) are either initialized to 0 or populated before use.\n3. No data structures or padding bytes are exposed or copied to userspace; this logic operates purely inside the kernel's egress packet segmentation and device transmission pipeline.\n4. Any potential risks introduced by parsing TCP headers or splitting skb payloads into segments involve packet bounds and memory offsets, which are out-of-bounds or use-after-free concerns effectively monitored by standard KASAN and packet sanity checks rather than KMSAN uninitialized memory tracking.\n\nConsequently, there is no risk of uninitialized memory reads or information leaks justifying a dedicated KMSAN session.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces bounded TCP GSO re-segmentation when an skb exceeds a net device's GSO limits, modifying core network transmit and GSO segmentation paths (dev.c, skbuff.c, gso.c, tcp_offload.c). This touches reachable core networking code with packet boundary and size calculations that can trigger edge cases or assertion failures during packet transmission.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/20 10:55 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 4189b25bcaeab773bedba178dc172b222434eb58\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Sep 20 10:55:51 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/tap.c b/drivers/net/tap.c\nindex ff67d99deb39e..bc111495ebbce 100644\n--- a/drivers/net/tap.c\n+++ b/drivers/net/tap.c\n@@ -278,9 +278,10 @@ rx_handler_result_t tap_handle_frame(struct sk_buff **pskb)\n \tif (q-\u003eflags \u0026 IFF_VNET_HDR)\n \t\tfeatures |= tap-\u003etap_features;\n \tif (netif_needs_gso(skb, features)) {\n-\t\tstruct sk_buff *segs = __skb_gso_segment(skb, features, false);\n+\t\tstruct sk_buff *segs;\n \t\tstruct sk_buff *next;\n \n+\t\tsegs = __skb_gso_segment(skb, features, false, 0);\n \t\tif (IS_ERR(segs)) {\n \t\t\tdrop_reason = SKB_DROP_REASON_SKB_GSO_SEG;\n \t\t\tgoto drop;\ndiff --git a/include/linux/netdevice.h b/include/linux/netdevice.h\nindex 1f0710eef185b..427d0d5b94e49 100644\n--- a/include/linux/netdevice.h\n+++ b/include/linux/netdevice.h\n@@ -5570,10 +5570,10 @@ netif_get_gro_max_size(const struct net_device *dev, const struct sk_buff *skb)\n }\n \n static inline unsigned int\n-netif_get_gso_max_size(const struct net_device *dev, const struct sk_buff *skb)\n+netif_get_gso_max_size(const struct net_device *dev, __be16 protocol)\n {\n \t/* pairs with WRITE_ONCE() in netif_set_gso(_ipv4)_max_size() */\n-\treturn skb-\u003eprotocol == htons(ETH_P_IPV6) ?\n+\treturn protocol == htons(ETH_P_IPV6) ?\n \t READ_ONCE(dev-\u003egso_max_size) :\n \t READ_ONCE(dev-\u003egso_ipv4_max_size);\n }\ndiff --git a/include/net/gso.h b/include/net/gso.h\nindex 29975440cad51..fccb37889965f 100644\n--- a/include/net/gso.h\n+++ b/include/net/gso.h\n@@ -19,6 +19,7 @@ struct skb_gso_cb {\n \tint\tencap_level;\n \t__wsum\tcsum;\n \t__u16\tcsum_start;\n+\t__u16\tmax_segs;\t/* Max MSS segs per output skb, 0 = no limit */\n };\n #define SKB_GSO_CB_OFFSET\t32\n #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)-\u003ecb + SKB_GSO_CB_OFFSET))\n@@ -75,12 +76,13 @@ static inline __sum16 gso_make_checksum(struct sk_buff *skb, __wsum res)\n }\n \n struct sk_buff *__skb_gso_segment(struct sk_buff *skb,\n-\t\t\t\t netdev_features_t features, bool tx_path);\n+\t\t\t\t netdev_features_t features, bool tx_path,\n+\t\t\t\t unsigned int max_segs);\n \n static inline struct sk_buff *skb_gso_segment(struct sk_buff *skb,\n \t\t\t\t\t netdev_features_t features)\n {\n-\treturn __skb_gso_segment(skb, features, true);\n+\treturn __skb_gso_segment(skb, features, true, 0);\n }\n \n struct sk_buff *skb_eth_gso_segment(struct sk_buff *skb,\ndiff --git a/include/net/udp.h b/include/net/udp.h\nindex 1fee17274745f..5bc25dcf25fba 100644\n--- a/include/net/udp.h\n+++ b/include/net/udp.h\n@@ -613,7 +613,7 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,\n \t/* the GSO CB lays after the UDP one, no need to save and restore any\n \t * CB fragment\n \t */\n-\tsegs = __skb_gso_segment(skb, features, false);\n+\tsegs = __skb_gso_segment(skb, features, false, 0);\n \tif (IS_ERR_OR_NULL(segs)) {\n \t\tdrop_count = skb_shinfo(skb)-\u003egso_segs;\n \t\tgoto drop;\ndiff --git a/net/core/dev.c b/net/core/dev.c\nindex c67900354fa64..548db4d4e874c 100644\n--- a/net/core/dev.c\n+++ b/net/core/dev.c\n@@ -3834,16 +3834,24 @@ static bool skb_gso_has_extension_hdr(const struct sk_buff *skb)\n \t\t\t skb_inner_network_header_len(skb) != sizeof(struct ipv6hdr)));\n }\n \n+/*\n+ * Does @skb fit the GSO limits of @dev? The size limit depends on the L3\n+ * protocol, which validate_xmit_vlan() replaces with the VLAN ethertype when\n+ * it pushes the tag inside the skb, so look behind the tag.\n+ */\n+static bool gso_within_device_limits(const struct sk_buff *skb,\n+\t\t\t\t const struct net_device *dev)\n+{\n+\treturn skb_shinfo(skb)-\u003egso_segs \u003c= READ_ONCE(dev-\u003egso_max_segs) \u0026\u0026\n+\t skb-\u003elen \u003c netif_get_gso_max_size(dev, vlan_get_protocol(skb));\n+}\n+\n static netdev_features_t gso_features_check(const struct sk_buff *skb,\n \t\t\t\t\t struct net_device *dev,\n-\t\t\t\t\t netdev_features_t features)\n+\t\t\t\t\t netdev_features_t features,\n+\t\t\t\t\t bool check_limits)\n {\n-\tu16 gso_segs = skb_shinfo(skb)-\u003egso_segs;\n-\n-\tif (gso_segs \u003e READ_ONCE(dev-\u003egso_max_segs))\n-\t\treturn features \u0026 ~NETIF_F_GSO_MASK;\n-\n-\tif (unlikely(skb-\u003elen \u003e= netif_get_gso_max_size(dev, skb)))\n+\tif (check_limits \u0026\u0026 !gso_within_device_limits(skb, dev))\n \t\treturn features \u0026 ~NETIF_F_GSO_MASK;\n \n \tif (!skb_shinfo(skb)-\u003egso_type) {\n@@ -3892,13 +3900,15 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb,\n \treturn features;\n }\n \n-netdev_features_t netif_skb_features(struct sk_buff *skb)\n+static netdev_features_t __netif_skb_features(struct sk_buff *skb,\n+\t\t\t\t\t bool check_gso_limits)\n {\n \tstruct net_device *dev = skb-\u003edev;\n \tnetdev_features_t features = dev-\u003efeatures;\n \n \tif (skb_is_gso(skb))\n-\t\tfeatures = gso_features_check(skb, dev, features);\n+\t\tfeatures = gso_features_check(skb, dev, features,\n+\t\t\t\t\t check_gso_limits);\n \n \t/* If encapsulation offload request, verify we are testing\n \t * hardware encapsulation features instead of standard\n@@ -3921,8 +3931,79 @@ netdev_features_t netif_skb_features(struct sk_buff *skb)\n \n \treturn harmonize_features(skb, features);\n }\n+\n+netdev_features_t netif_skb_features(struct sk_buff *skb)\n+{\n+\treturn __netif_skb_features(skb, true);\n+}\n EXPORT_SYMBOL(netif_skb_features);\n \n+static bool skb_can_gso_resegment(struct sk_buff *skb,\n+\t\t\t\t netdev_features_t features)\n+{\n+\t__be16 protocol;\n+\n+\tif (!net_gso_ok(features | NETIF_F_GSO_ROBUST,\n+\t\t\tskb_shinfo(skb)-\u003egso_type))\n+\t\treturn false;\n+\n+\tif (!(features \u0026 NETIF_F_SG))\n+\t\treturn false;\n+\n+\tprotocol = skb_network_protocol(skb, NULL);\n+\tif (!protocol || !can_checksum_protocol(features, protocol))\n+\t\treturn false;\n+\n+\t/*\n+\t * The TCP frag-list path does not carry the bounded segment\n+\t * limit through skb_segment_list(). Keep bounded resegmentation\n+\t * on the regular skb path until that support is added.\n+\t */\n+\tif (skb_has_frag_list(skb))\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static unsigned int\n+skb_gso_resegment_max_segs(struct sk_buff *skb, struct net_device *dev,\n+\t\t\t netdev_features_t features)\n+{\n+\tunsigned int mss = skb_shinfo(skb)-\u003egso_size;\n+\tunsigned int hdr_len, max_segs;\n+\tunsigned int gso_max_size;\n+\tstruct tcphdr _tcph, *th;\n+\n+\tgso_max_size = netif_get_gso_max_size(dev, vlan_get_protocol(skb));\n+\n+\tif (!skb_is_gso(skb) || !skb_is_gso_tcp(skb) ||\n+\t skb-\u003eencapsulation || mss == GSO_BY_FRAGS ||\n+\t !skb_mac_header_was_set(skb) ||\n+\t !skb_transport_header_was_set(skb) ||\n+\t !skb_can_gso_resegment(skb, features))\n+\t\treturn 0;\n+\n+\tth = skb_header_pointer(skb, skb_transport_offset(skb), sizeof(_tcph),\n+\t\t\t\t\u0026_tcph);\n+\tif (!th || th-\u003edoff \u003c sizeof(*th) / 4)\n+\t\treturn 0;\n+\n+\thdr_len = skb_transport_header(skb) - skb_mac_header(skb) +\n+\t\t th-\u003edoff * 4;\n+\tif (gso_max_size \u003c= hdr_len + mss)\n+\t\treturn 0;\n+\n+\t/*\n+\t * gso_within_device_limits() accepts gso_segs == gso_max_segs but\n+\t * rejects skb-\u003elen \u003e= gso_max_size, so only the size bound needs - 1.\n+\t */\n+\tmax_segs = (gso_max_size - hdr_len - 1) / mss;\n+\tmax_segs = min_t(unsigned int, max_segs,\n+\t\t\t READ_ONCE(dev-\u003egso_max_segs));\n+\n+\treturn max_segs \u003e 1 ? max_segs : 0;\n+}\n+\n static int xmit_one(struct sk_buff *skb, struct net_device *dev,\n \t\t struct netdev_queue *txq, bool more)\n {\n@@ -4071,6 +4152,7 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,\n */\n static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)\n {\n+\tunsigned int resegment_max_segs = 0;\n \tnetdev_features_t features;\n \n \tskb = validate_xmit_unreadable_skb(skb, dev);\n@@ -4086,10 +4168,29 @@ static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device\n \tif (unlikely(!skb))\n \t\tgoto out_null;\n \n-\tif (netif_needs_gso(skb, features)) {\n+\t/*\n+\t * An oversized skb loses its GSO feature bits and is segmented\n+\t * down to MSS sized skbs below. A TCP skb can instead be split\n+\t * into GSO skbs which do fit the device, so keep the bits and\n+\t * bound the resegmentation. The features computed without the\n+\t * limit checks say whether the device offloads the GSO type at\n+\t * all.\n+\t */\n+\tif (skb_is_gso(skb) \u0026\u0026 skb_is_gso_tcp(skb) \u0026\u0026 !skb-\u003eencapsulation \u0026\u0026\n+\t !gso_within_device_limits(skb, dev)) {\n+\t\tnetdev_features_t offload = __netif_skb_features(skb, false);\n+\n+\t\tresegment_max_segs =\n+\t\t\tskb_gso_resegment_max_segs(skb, dev, offload);\n+\t\tif (resegment_max_segs)\n+\t\t\tfeatures = offload;\n+\t}\n+\n+\tif (resegment_max_segs || netif_needs_gso(skb, features)) {\n \t\tstruct sk_buff *segs;\n \n-\t\tsegs = skb_gso_segment(skb, features);\n+\t\tsegs = __skb_gso_segment(skb, features, true,\n+\t\t\t\t\t resegment_max_segs);\n \t\tif (IS_ERR(segs)) {\n \t\t\tgoto out_kfree_skb;\n \t\t} else if (segs) {\ndiff --git a/net/core/gso.c b/net/core/gso.c\nindex bcd156372f4df..157f2bfdca128 100644\n--- a/net/core/gso.c\n+++ b/net/core/gso.c\n@@ -77,6 +77,7 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)\n *\t@skb: buffer to segment\n *\t@features: features for the output path (see dev-\u003efeatures)\n *\t@tx_path: whether it is called in TX path\n+ *\t@max_segs: maximum MSS segments per output GSO skb, 0 means no limit\n *\n *\tThis function segments the given skb and returns a list of segments.\n *\n@@ -86,7 +87,8 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)\n *\tSegmentation preserves SKB_GSO_CB_OFFSET bytes of previous skb cb.\n */\n struct sk_buff *__skb_gso_segment(struct sk_buff *skb,\n-\t\t\t\t netdev_features_t features, bool tx_path)\n+\t\t\t\t netdev_features_t features, bool tx_path,\n+\t\t\t\t unsigned int max_segs)\n {\n \tstruct sk_buff *segs;\n \n@@ -117,6 +119,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb,\n \n \tSKB_GSO_CB(skb)-\u003emac_offset = skb_headroom(skb);\n \tSKB_GSO_CB(skb)-\u003eencap_level = 0;\n+\tSKB_GSO_CB(skb)-\u003emax_segs = min_t(unsigned int, max_segs, U16_MAX);\n \n \tskb_reset_mac_header(skb);\n \tskb_reset_mac_len(skb);\ndiff --git a/net/core/net_test.c b/net/core/net_test.c\nindex 9c3a590865d26..6ca0cbe3a3653 100644\n--- a/net/core/net_test.c\n+++ b/net/core/net_test.c\n@@ -4,7 +4,14 @@\n \n /* GSO */\n \n+#include \u003clinux/if_ether.h\u003e\n+#include \u003clinux/if_vlan.h\u003e\n+#include \u003clinux/ip.h\u003e\n+#include \u003clinux/ipv6.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n #include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/tcp.h\u003e\n+#include \u003cnet/gso.h\u003e\n \n static const char hdr[] = \"abcdefgh\";\n #define GSO_TEST_SIZE 1000\n@@ -34,6 +41,9 @@ enum gso_test_nr {\n \tGSO_TEST_FRAG_LIST_PURE,\n \tGSO_TEST_FRAG_LIST_NON_UNIFORM,\n \tGSO_TEST_GSO_BY_FRAGS,\n+\tGSO_TEST_BOUNDED,\n+\tGSO_TEST_BOUNDED_MULTI,\n+\tGSO_TEST_BOUNDED_ONE_MSS,\n };\n \n struct gso_test_case {\n@@ -46,10 +56,12 @@ struct gso_test_case {\n \tconst unsigned int *frags;\n \tunsigned int nr_frag_skbs;\n \tconst unsigned int *frag_skbs;\n+\tunsigned int max_segs;\n \n \t/* output as expected */\n \tunsigned int nr_segs;\n \tconst unsigned int *segs;\n+\tbool segs_are_gso;\n };\n \n static struct gso_test_case cases[] = {\n@@ -135,6 +147,54 @@ static struct gso_test_case cases[] = {\n \t\t.nr_segs = 4,\n \t\t.segs = (const unsigned int[]) { 100, 200, 300, 400 },\n \t},\n+\t{\n+\t\t.id = GSO_TEST_BOUNDED,\n+\t\t.name = \"bounded\",\n+\t\t.linear_len = GSO_TEST_SIZE,\n+\t\t.nr_frags = 3,\n+\t\t.frags = (const unsigned int[]) {\n+\t\t\tGSO_TEST_SIZE, GSO_TEST_SIZE, 3,\n+\t\t},\n+\t\t.max_segs = 2,\n+\t\t.nr_segs = 2,\n+\t\t.segs = (const unsigned int[]) {\n+\t\t\t2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,\n+\t\t},\n+\t\t.segs_are_gso = true,\n+\t},\n+\t{\n+\t\t.id = GSO_TEST_BOUNDED_MULTI,\n+\t\t.name = \"bounded_multi\",\n+\t\t.linear_len = 2 * GSO_TEST_SIZE,\n+\t\t.nr_frags = 4,\n+\t\t.frags = (const unsigned int[]) {\n+\t\t\tGSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,\n+\t\t},\n+\t\t.max_segs = 2,\n+\t\t.nr_segs = 3,\n+\t\t.segs = (const unsigned int[]) {\n+\t\t\t2 * GSO_TEST_SIZE, 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,\n+\t\t},\n+\t\t.segs_are_gso = true,\n+\t},\n+\t{\n+\t\t/*\n+\t\t * One MSS per skb is what the unbounded path produces, so a\n+\t\t * bound of a single segment must not change the output.\n+\t\t */\n+\t\t.id = GSO_TEST_BOUNDED_ONE_MSS,\n+\t\t.name = \"bounded_one_mss\",\n+\t\t.linear_len = GSO_TEST_SIZE,\n+\t\t.nr_frags = 3,\n+\t\t.frags = (const unsigned int[]) {\n+\t\t\tGSO_TEST_SIZE, GSO_TEST_SIZE, 3,\n+\t\t},\n+\t\t.max_segs = 1,\n+\t\t.nr_segs = 4,\n+\t\t.segs = (const unsigned int[]) {\n+\t\t\tGSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,\n+\t\t},\n+\t},\n };\n \n static void gso_test_case_to_desc(struct gso_test_case *t, char *desc)\n@@ -226,6 +286,7 @@ static void gso_test_func(struct kunit *test)\n \tif (tcase-\u003eid == GSO_TEST_FRAG_LIST_NON_UNIFORM)\n \t\tfeatures \u0026= ~NETIF_F_SG;\n \n+\tSKB_GSO_CB(skb)-\u003emax_segs = tcase-\u003emax_segs;\n \tsegs = skb_segment(skb, features);\n \tif (IS_ERR(segs)) {\n \t\tKUNIT_FAIL(test, \"segs error %pe\", segs);\n@@ -247,6 +308,15 @@ static void gso_test_func(struct kunit *test)\n \n \t\t/* header was copied to all segs */\n \t\tKUNIT_ASSERT_EQ(test, memcmp(skb_mac_header(cur), hdr, sizeof(hdr)), 0);\n+\t\tif (tcase-\u003esegs_are_gso) {\n+\t\t\tKUNIT_EXPECT_TRUE(test, skb_is_gso(cur));\n+\t\t\tKUNIT_EXPECT_EQ(test, skb_shinfo(cur)-\u003egso_size,\n+\t\t\t\t\tGSO_TEST_SIZE);\n+\t\t\tKUNIT_EXPECT_LE(test, skb_shinfo(cur)-\u003egso_segs,\n+\t\t\t\t\ttcase-\u003emax_segs);\n+\t\t\tKUNIT_EXPECT_FALSE(test, skb_shinfo(cur)-\u003egso_type \u0026\n+\t\t\t\t\t SKB_GSO_PARTIAL);\n+\t\t}\n \n \t\t/* last seg can be found through segs-\u003eprev pointer */\n \t\tif (!next)\n@@ -261,6 +331,177 @@ static void gso_test_func(struct kunit *test)\n \tconsume_skb(skb);\n }\n \n+#define GSO_TCP_HDR_LEN \\\n+\t(ETH_HLEN + sizeof(struct iphdr) + sizeof(struct tcphdr))\n+\n+static struct sk_buff *gso_tcp_skb_new(unsigned int payload_len)\n+{\n+\tstruct sk_buff *skb;\n+\tstruct ethhdr *eth;\n+\tstruct tcphdr *th;\n+\tstruct iphdr *iph;\n+\n+\tskb = alloc_skb(GSO_TCP_HDR_LEN + payload_len, GFP_KERNEL);\n+\tif (!skb)\n+\t\treturn NULL;\n+\tskb_put_zero(skb, GSO_TCP_HDR_LEN + payload_len);\n+\n+\tskb_reset_mac_header(skb);\n+\teth = eth_hdr(skb);\n+\teth-\u003eh_proto = htons(ETH_P_IP);\n+\tskb-\u003eprotocol = eth-\u003eh_proto;\n+\n+\tskb_set_network_header(skb, ETH_HLEN);\n+\tiph = ip_hdr(skb);\n+\tiph-\u003eversion = 4;\n+\tiph-\u003eihl = sizeof(*iph) / 4;\n+\tiph-\u003eprotocol = IPPROTO_TCP;\n+\tiph-\u003etot_len = htons(sizeof(*iph) + sizeof(*th) + payload_len);\n+\n+\tskb_set_transport_header(skb, ETH_HLEN + sizeof(*iph));\n+\tth = tcp_hdr(skb);\n+\tth-\u003edoff = sizeof(*th) / 4;\n+\n+\tskb-\u003eip_summed = CHECKSUM_PARTIAL;\n+\tskb-\u003ecsum_start = skb_transport_header(skb) - skb-\u003ehead;\n+\tskb-\u003ecsum_offset = offsetof(struct tcphdr, check);\n+\tskb_shinfo(skb)-\u003egso_type = SKB_GSO_TCPV4;\n+\tskb_shinfo(skb)-\u003egso_size = GSO_TEST_SIZE;\n+\tskb_shinfo(skb)-\u003egso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);\n+\n+\treturn skb;\n+}\n+\n+static void gso_test_tcp_bounded_segment(struct kunit *test)\n+{\n+\tnetdev_features_t features = NETIF_F_SG | NETIF_F_HW_CSUM |\n+\t\t\t\t NETIF_F_TSO;\n+\tconst unsigned int payload_len = 3 * GSO_TEST_SIZE + 3;\n+\tstruct sk_buff *skb, *segs, *cur, *next;\n+\tconst unsigned int expected[] = {\n+\t\t2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,\n+\t};\n+\tconst unsigned int max_segs = 2;\n+\tint i = 0;\n+\n+\tskb = gso_tcp_skb_new(payload_len);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\n+\tsegs = __skb_gso_segment(skb, features, true, max_segs);\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, segs);\n+\n+\tfor (cur = segs; cur; cur = next, i++) {\n+\t\tnext = cur-\u003enext;\n+\n+\t\tKUNIT_ASSERT_LT(test, i, ARRAY_SIZE(expected));\n+\t\tKUNIT_EXPECT_EQ(test, cur-\u003elen,\n+\t\t\t\tGSO_TCP_HDR_LEN + expected[i]);\n+\t\tKUNIT_EXPECT_TRUE(test, skb_is_gso(cur));\n+\t\tKUNIT_EXPECT_EQ(test, skb_shinfo(cur)-\u003egso_size,\n+\t\t\t\tGSO_TEST_SIZE);\n+\t\tKUNIT_EXPECT_LE(test, skb_shinfo(cur)-\u003egso_segs, max_segs);\n+\n+\t\tconsume_skb(cur);\n+\t}\n+\n+\tKUNIT_EXPECT_EQ(test, i, ARRAY_SIZE(expected));\n+\tconsume_skb(skb);\n+}\n+\n+#define GSO_TCP6_HDR_LEN \\\n+\t(ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))\n+\n+static struct sk_buff *gso_tcp6_skb_new(unsigned int payload_len)\n+{\n+\tstruct ipv6hdr *ip6h;\n+\tstruct sk_buff *skb;\n+\tstruct ethhdr *eth;\n+\tstruct tcphdr *th;\n+\n+\tskb = alloc_skb(GSO_TCP6_HDR_LEN + payload_len, GFP_KERNEL);\n+\tif (!skb)\n+\t\treturn NULL;\n+\tskb_put_zero(skb, GSO_TCP6_HDR_LEN + payload_len);\n+\n+\tskb_reset_mac_header(skb);\n+\teth = eth_hdr(skb);\n+\teth-\u003eh_proto = htons(ETH_P_IPV6);\n+\tskb-\u003eprotocol = eth-\u003eh_proto;\n+\n+\tskb_set_network_header(skb, ETH_HLEN);\n+\tip6h = ipv6_hdr(skb);\n+\tip6h-\u003eversion = 6;\n+\tip6h-\u003enexthdr = IPPROTO_TCP;\n+\tip6h-\u003epayload_len = htons(sizeof(*th) + payload_len);\n+\n+\tskb_set_transport_header(skb, ETH_HLEN + sizeof(*ip6h));\n+\tth = tcp_hdr(skb);\n+\tth-\u003edoff = sizeof(*th) / 4;\n+\n+\tskb-\u003eip_summed = CHECKSUM_PARTIAL;\n+\tskb-\u003ecsum_start = skb_transport_header(skb) - skb-\u003ehead;\n+\tskb-\u003ecsum_offset = offsetof(struct tcphdr, check);\n+\tskb_shinfo(skb)-\u003egso_type = SKB_GSO_TCPV6;\n+\tskb_shinfo(skb)-\u003egso_size = GSO_TEST_SIZE;\n+\tskb_shinfo(skb)-\u003egso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);\n+\n+\treturn skb;\n+}\n+\n+/* The device GSO size limit is per L3 protocol, and it has to survive the\n+ * VLAN tag which validate_xmit_vlan() can push inside the skb, because that\n+ * tag replaces skb-\u003eprotocol with the VLAN ethertype.\n+ */\n+static void gso_test_tcp_limit_l3_proto(struct kunit *test)\n+{\n+\tstatic const struct net_device_ops dummy_netdev_ops = { };\n+\tconst unsigned int payload_len = 100 * 1024;\n+\tnetdev_features_t features;\n+\tstruct net_device *dev;\n+\tstruct sk_buff *skb;\n+\n+\tdev = alloc_etherdev(0);\n+\tKUNIT_ASSERT_NOT_NULL(test, dev);\n+\tdev-\u003enetdev_ops = \u0026dummy_netdev_ops;\n+\tdev-\u003ehw_features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO6;\n+\tdev-\u003efeatures = dev-\u003ehw_features;\n+\tdev-\u003evlan_features = dev-\u003ehw_features;\n+\n+\tskb = gso_tcp6_skb_new(payload_len);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tskb-\u003edev = dev;\n+\n+\t/* The skb fits the IPv6 limit but not the IPv4 one. */\n+\tdev-\u003egso_max_size = GSO_MAX_SIZE;\n+\tdev-\u003egso_ipv4_max_size = GSO_LEGACY_MAX_SIZE;\n+\tfeatures = netif_skb_features(skb);\n+\tKUNIT_EXPECT_TRUE(test, features \u0026 NETIF_F_GSO_MASK);\n+\n+\t/* ...and the other way around. */\n+\tdev-\u003egso_max_size = GSO_LEGACY_MAX_SIZE;\n+\tdev-\u003egso_ipv4_max_size = GSO_MAX_SIZE;\n+\tfeatures = netif_skb_features(skb);\n+\tKUNIT_EXPECT_FALSE(test, features \u0026 NETIF_F_GSO_MASK);\n+\n+\t/* Pushing the tag inside must not change either answer. */\n+\tskb = vlan_insert_tag_set_proto(skb, htons(ETH_P_8021Q), 0);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tKUNIT_ASSERT_TRUE(test, skb-\u003eprotocol == htons(ETH_P_8021Q));\n+\n+\tdev-\u003egso_max_size = GSO_MAX_SIZE;\n+\tdev-\u003egso_ipv4_max_size = GSO_LEGACY_MAX_SIZE;\n+\tfeatures = netif_skb_features(skb);\n+\tKUNIT_EXPECT_TRUE(test, features \u0026 NETIF_F_GSO_MASK);\n+\n+\tdev-\u003egso_max_size = GSO_LEGACY_MAX_SIZE;\n+\tdev-\u003egso_ipv4_max_size = GSO_MAX_SIZE;\n+\tfeatures = netif_skb_features(skb);\n+\tKUNIT_EXPECT_FALSE(test, features \u0026 NETIF_F_GSO_MASK);\n+\n+\tconsume_skb(skb);\n+\tfree_netdev(dev);\n+}\n+\n /* IP tunnel flags */\n \n #include \u003cnet/ip_tunnels.h\u003e\n@@ -372,6 +613,8 @@ static void ip_tunnel_flags_test_run(struct kunit *test)\n \n static struct kunit_case net_test_cases[] = {\n \tKUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),\n+\tKUNIT_CASE(gso_test_tcp_bounded_segment),\n+\tKUNIT_CASE(gso_test_tcp_limit_l3_proto),\n \tKUNIT_CASE_PARAM(ip_tunnel_flags_test_run,\n \t\t\t ip_tunnel_flags_test_gen_params),\n \t{ },\ndiff --git a/net/core/skbuff.c b/net/core/skbuff.c\nindex dbbe10277d51d..9c0d140236bc6 100644\n--- a/net/core/skbuff.c\n+++ b/net/core/skbuff.c\n@@ -4793,6 +4793,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,\n \tstruct sk_buff *segs = NULL;\n \tstruct sk_buff *tail = NULL;\n \tstruct sk_buff *list_skb = skb_shinfo(head_skb)-\u003efrag_list;\n+\tunsigned int max_segs = SKB_GSO_CB(head_skb)-\u003emax_segs;\n \tunsigned int mss = skb_shinfo(head_skb)-\u003egso_size;\n \tbool gso_by_frags = mss == GSO_BY_FRAGS;\n \tunsigned int doffset = head_skb-\u003edata - skb_mac_header(head_skb);\n@@ -4839,7 +4840,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,\n \tcsum = !!can_checksum_protocol(features, proto);\n \n \tif (sg \u0026\u0026 csum \u0026\u0026 !gso_by_frags) {\n-\t\tif (!(features \u0026 NETIF_F_GSO_PARTIAL)) {\n+\t\tif (!max_segs \u0026\u0026 !(features \u0026 NETIF_F_GSO_PARTIAL)) {\n \t\t\tstruct sk_buff *iter;\n \t\t\tunsigned int frag_len;\n \n@@ -4874,7 +4875,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,\n \t\t * now.\n \t\t */\n \t\tDEBUG_NET_WARN_ON_ONCE(len / mss \u003e GSO_MAX_SEGS);\n-\t\tpartial_segs = min(len / mss, GSO_MAX_SEGS);\n+\t\tif (max_segs)\n+\t\t\tpartial_segs = min(len / mss, max_segs);\n+\t\telse\n+\t\t\tpartial_segs = min(len / mss, GSO_MAX_SEGS);\n \t\tif (partial_segs \u003e 1)\n \t\t\tmss *= partial_segs;\n \t\telse\n@@ -4975,6 +4979,12 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,\n \n \t\t__copy_skb_header(nskb, head_skb);\n \n+\t\t/*\n+\t\t * max_segs is a per-call limit, so output skbs must not\n+\t\t * inherit it from the input skb.\n+\t\t */\n+\t\tSKB_GSO_CB(nskb)-\u003emax_segs = 0;\n+\n \t\tskb_headers_offset_update(nskb, skb_headroom(nskb) - headroom);\n \t\tskb_reset_mac_len(nskb);\n \ndiff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c\nindex e74d99ca9face..a4076318c5352 100644\n--- a/net/ipv4/tcp_offload.c\n+++ b/net/ipv4/tcp_offload.c\n@@ -164,7 +164,8 @@ struct sk_buff *tcp_gso_segment(struct sk_buff *skb,\n \tif (unlikely(skb-\u003elen \u003c= mss))\n \t\tgoto out;\n \n-\tif (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {\n+\tif (!SKB_GSO_CB(skb)-\u003emax_segs \u0026\u0026\n+\t skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {\n \t\t/* Packet is from an untrusted source, reset gso_segs. */\n \n \t\tskb_shinfo(skb)-\u003egso_segs = DIV_ROUND_UP(skb-\u003elen, mss);\ndiff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c\nindex 2187034143255..e793aead68372 100644\n--- a/net/openvswitch/datapath.c\n+++ b/net/openvswitch/datapath.c\n@@ -375,7 +375,7 @@ static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,\n \tint err;\n \n \tBUILD_BUG_ON(sizeof(*OVS_CB(skb)) \u003e SKB_GSO_CB_OFFSET);\n-\tsegs = __skb_gso_segment(skb, NETIF_F_SG, false);\n+\tsegs = __skb_gso_segment(skb, NETIF_F_SG, false, 0);\n \tif (IS_ERR(segs))\n \t\treturn PTR_ERR(segs);\n \tif (segs == NULL)\n"
} |
| 2/1 | 2026/09/20 10:55 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": [
"NET",
"INET"
],
"FocusSymbols": [
"__skb_gso_segment",
"skb_segment",
"tcp_gso_segment",
"validate_xmit_skb",
"skb_gso_resegment_max_segs"
],
"Reasoning": "The patch introduces bounded TCP GSO re-segmentation when an skb exceeds a net device's GSO limits, modifying core network transmit and GSO segmentation paths (dev.c, skbuff.c, gso.c, tcp_offload.c). This touches reachable core networking code with packet boundary and size calculations that can trigger edge cases or assertion failures during packet transmission.",
"WorthFuzzing": true
}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 4189b25bcaeab773bedba178dc172b222434eb58
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 20 10:55:51 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/tap.c b/drivers/net/tap.c
index ff67d99deb39e..bc111495ebbce 100644
--- a/drivers/net/tap.c
+++ b/drivers/net/tap.c
@@ -278,9 +278,10 @@ rx_handler_result_t tap_handle_frame(struct sk_buff **pskb)
if (q->flags & IFF_VNET_HDR)
features |= tap->tap_features;
if (netif_needs_gso(skb, features)) {
- struct sk_buff *segs = __skb_gso_segment(skb, features, false);
+ struct sk_buff *segs;
struct sk_buff *next;
+ segs = __skb_gso_segment(skb, features, false, 0);
if (IS_ERR(segs)) {
drop_reason = SKB_DROP_REASON_SKB_GSO_SEG;
goto drop;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 1f0710eef185b..427d0d5b94e49 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -5570,10 +5570,10 @@ netif_get_gro_max_size(const struct net_device *dev, const struct sk_buff *skb)
}
static inline unsigned int
-netif_get_gso_max_size(const struct net_device *dev, const struct sk_buff *skb)
+netif_get_gso_max_size(const struct net_device *dev, __be16 protocol)
{
/* pairs with WRITE_ONCE() in netif_set_gso(_ipv4)_max_size() */
- return skb->protocol == htons(ETH_P_IPV6) ?
+ return protocol == htons(ETH_P_IPV6) ?
READ_ONCE(dev->gso_max_size) :
READ_ONCE(dev->gso_ipv4_max_size);
}
diff --git a/include/net/gso.h b/include/net/gso.h
index 29975440cad51..fccb37889965f 100644
--- a/include/net/gso.h
+++ b/include/net/gso.h
@@ -19,6 +19,7 @@ struct skb_gso_cb {
int encap_level;
__wsum csum;
__u16 csum_start;
+ __u16 max_segs; /* Max MSS segs per output skb, 0 = no limit */
};
#define SKB_GSO_CB_OFFSET 32
#define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET))
@@ -75,12 +76,13 @@ static inline __sum16 gso_make_checksum(struct sk_buff *skb, __wsum res)
}
struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
- netdev_features_t features, bool tx_path);
+ netdev_features_t features, bool tx_path,
+ unsigned int max_segs);
static inline struct sk_buff *skb_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
- return __skb_gso_segment(skb, features, true);
+ return __skb_gso_segment(skb, features, true, 0);
}
struct sk_buff *skb_eth_gso_segment(struct sk_buff *skb,
diff --git a/include/net/udp.h b/include/net/udp.h
index 1fee17274745f..5bc25dcf25fba 100644
--- a/include/net/udp.h
+++ b/include/net/udp.h
@@ -613,7 +613,7 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
/* the GSO CB lays after the UDP one, no need to save and restore any
* CB fragment
*/
- segs = __skb_gso_segment(skb, features, false);
+ segs = __skb_gso_segment(skb, features, false, 0);
if (IS_ERR_OR_NULL(segs)) {
drop_count = skb_shinfo(skb)->gso_segs;
goto drop;
diff --git a/net/core/dev.c b/net/core/dev.c
index c67900354fa64..548db4d4e874c 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3834,16 +3834,24 @@ static bool skb_gso_has_extension_hdr(const struct sk_buff *skb)
skb_inner_network_header_len(skb) != sizeof(struct ipv6hdr)));
}
+/*
+ * Does @skb fit the GSO limits of @dev? The size limit depends on the L3
+ * protocol, which validate_xmit_vlan() replaces with the VLAN ethertype when
+ * it pushes the tag inside the skb, so look behind the tag.
+ */
+static bool gso_within_device_limits(const struct sk_buff *skb,
+ const struct net_device *dev)
+{
+ return skb_shinfo(skb)->gso_segs <= READ_ONCE(dev->gso_max_segs) &&
+ skb->len < netif_get_gso_max_size(dev, vlan_get_protocol(skb));
+}
+
static netdev_features_t gso_features_check(const struct sk_buff *skb,
struct net_device *dev,
- netdev_features_t features)
+ netdev_features_t features,
+ bool check_limits)
{
- u16 gso_segs = skb_shinfo(skb)->gso_segs;
-
- if (gso_segs > READ_ONCE(dev->gso_max_segs))
- return features & ~NETIF_F_GSO_MASK;
-
- if (unlikely(skb->len >= netif_get_gso_max_size(dev, skb)))
+ if (check_limits && !gso_within_device_limits(skb, dev))
return features & ~NETIF_F_GSO_MASK;
if (!skb_shinfo(skb)->gso_type) {
@@ -3892,13 +3900,15 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb,
return features;
}
-netdev_features_t netif_skb_features(struct sk_buff *skb)
+static netdev_features_t __netif_skb_features(struct sk_buff *skb,
+ bool check_gso_limits)
{
struct net_device *dev = skb->dev;
netdev_features_t features = dev->features;
if (skb_is_gso(skb))
- features = gso_features_check(skb, dev, features);
+ features = gso_features_check(skb, dev, features,
+ check_gso_limits);
/* If encapsulation offload request, verify we are testing
* hardware encapsulation features instead of standard
@@ -3921,8 +3931,79 @@ netdev_features_t netif_skb_features(struct sk_buff *skb)
return harmonize_features(skb, features);
}
+
+netdev_features_t netif_skb_features(struct sk_buff *skb)
+{
+ return __netif_skb_features(skb, true);
+}
EXPORT_SYMBOL(netif_skb_features);
+static bool skb_can_gso_resegment(struct sk_buff *skb,
+ netdev_features_t features)
+{
+ __be16 protocol;
+
+ if (!net_gso_ok(features | NETIF_F_GSO_ROBUST,
+ skb_shinfo(skb)->gso_type))
+ return false;
+
+ if (!(features & NETIF_F_SG))
+ return false;
+
+ protocol = skb_network_protocol(skb, NULL);
+ if (!protocol || !can_checksum_protocol(features, protocol))
+ return false;
+
+ /*
+ * The TCP frag-list path does not carry the bounded segment
+ * limit through skb_segment_list(). Keep bounded resegmentation
+ * on the regular skb path until that support is added.
+ */
+ if (skb_has_frag_list(skb))
+ return false;
+
+ return true;
+}
+
+static unsigned int
+skb_gso_resegment_max_segs(struct sk_buff *skb, struct net_device *dev,
+ netdev_features_t features)
+{
+ unsigned int mss = skb_shinfo(skb)->gso_size;
+ unsigned int hdr_len, max_segs;
+ unsigned int gso_max_size;
+ struct tcphdr _tcph, *th;
+
+ gso_max_size = netif_get_gso_max_size(dev, vlan_get_protocol(skb));
+
+ if (!skb_is_gso(skb) || !skb_is_gso_tcp(skb) ||
+ skb->encapsulation || mss == GSO_BY_FRAGS ||
+ !skb_mac_header_was_set(skb) ||
+ !skb_transport_header_was_set(skb) ||
+ !skb_can_gso_resegment(skb, features))
+ return 0;
+
+ th = skb_header_pointer(skb, skb_transport_offset(skb), sizeof(_tcph),
+ &_tcph);
+ if (!th || th->doff < sizeof(*th) / 4)
+ return 0;
+
+ hdr_len = skb_transport_header(skb) - skb_mac_header(skb) +
+ th->doff * 4;
+ if (gso_max_size <= hdr_len + mss)
+ return 0;
+
+ /*
+ * gso_within_device_limits() accepts gso_segs == gso_max_segs but
+ * rejects skb->len >= gso_max_size, so only the size bound needs - 1.
+ */
+ max_segs = (gso_max_size - hdr_len - 1) / mss;
+ max_segs = min_t(unsigned int, max_segs,
+ READ_ONCE(dev->gso_max_segs));
+
+ return max_segs > 1 ? max_segs : 0;
+}
+
static int xmit_one(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
@@ -4071,6 +4152,7 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
*/
static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
{
+ unsigned int resegment_max_segs = 0;
netdev_features_t features;
skb = validate_xmit_unreadable_skb(skb, dev);
@@ -4086,10 +4168,29 @@ static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device
if (unlikely(!skb))
goto out_null;
- if (netif_needs_gso(skb, features)) {
+ /*
+ * An oversized skb loses its GSO feature bits and is segmented
+ * down to MSS sized skbs below. A TCP skb can instead be split
+ * into GSO skbs which do fit the device, so keep the bits and
+ * bound the resegmentation. The features computed without the
+ * limit checks say whether the device offloads the GSO type at
+ * all.
+ */
+ if (skb_is_gso(skb) && skb_is_gso_tcp(skb) && !skb->encapsulation &&
+ !gso_within_device_limits(skb, dev)) {
+ netdev_features_t offload = __netif_skb_features(skb, false);
+
+ resegment_max_segs =
+ skb_gso_resegment_max_segs(skb, dev, offload);
+ if (resegment_max_segs)
+ features = offload;
+ }
+
+ if (resegment_max_segs || netif_needs_gso(skb, features)) {
struct sk_buff *segs;
- segs = skb_gso_segment(skb, features);
+ segs = __skb_gso_segment(skb, features, true,
+ resegment_max_segs);
if (IS_ERR(segs)) {
goto out_kfree_skb;
} else if (segs) {
diff --git a/net/core/gso.c b/net/core/gso.c
index bcd156372f4df..157f2bfdca128 100644
--- a/net/core/gso.c
+++ b/net/core/gso.c
@@ -77,6 +77,7 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)
* @skb: buffer to segment
* @features: features for the output path (see dev->features)
* @tx_path: whether it is called in TX path
+ * @max_segs: maximum MSS segments per output GSO skb, 0 means no limit
*
* This function segments the given skb and returns a list of segments.
*
@@ -86,7 +87,8 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)
* Segmentation preserves SKB_GSO_CB_OFFSET bytes of previous skb cb.
*/
struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
- netdev_features_t features, bool tx_path)
+ netdev_features_t features, bool tx_path,
+ unsigned int max_segs)
{
struct sk_buff *segs;
@@ -117,6 +119,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
SKB_GSO_CB(skb)->mac_offset = skb_headroom(skb);
SKB_GSO_CB(skb)->encap_level = 0;
+ SKB_GSO_CB(skb)->max_segs = min_t(unsigned int, max_segs, U16_MAX);
skb_reset_mac_header(skb);
skb_reset_mac_len(skb);
diff --git a/net/core/net_test.c b/net/core/net_test.c
index 9c3a590865d26..6ca0cbe3a3653 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -4,7 +4,14 @@
/* GSO */
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/netdevice.h>
#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <net/gso.h>
static const char hdr[] = "abcdefgh";
#define GSO_TEST_SIZE 1000
@@ -34,6 +41,9 @@ enum gso_test_nr {
GSO_TEST_FRAG_LIST_PURE,
GSO_TEST_FRAG_LIST_NON_UNIFORM,
GSO_TEST_GSO_BY_FRAGS,
+ GSO_TEST_BOUNDED,
+ GSO_TEST_BOUNDED_MULTI,
+ GSO_TEST_BOUNDED_ONE_MSS,
};
struct gso_test_case {
@@ -46,10 +56,12 @@ struct gso_test_case {
const unsigned int *frags;
unsigned int nr_frag_skbs;
const unsigned int *frag_skbs;
+ unsigned int max_segs;
/* output as expected */
unsigned int nr_segs;
const unsigned int *segs;
+ bool segs_are_gso;
};
static struct gso_test_case cases[] = {
@@ -135,6 +147,54 @@ static struct gso_test_case cases[] = {
.nr_segs = 4,
.segs = (const unsigned int[]) { 100, 200, 300, 400 },
},
+ {
+ .id = GSO_TEST_BOUNDED,
+ .name = "bounded",
+ .linear_len = GSO_TEST_SIZE,
+ .nr_frags = 3,
+ .frags = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ .max_segs = 2,
+ .nr_segs = 2,
+ .segs = (const unsigned int[]) {
+ 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
+ },
+ .segs_are_gso = true,
+ },
+ {
+ .id = GSO_TEST_BOUNDED_MULTI,
+ .name = "bounded_multi",
+ .linear_len = 2 * GSO_TEST_SIZE,
+ .nr_frags = 4,
+ .frags = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ .max_segs = 2,
+ .nr_segs = 3,
+ .segs = (const unsigned int[]) {
+ 2 * GSO_TEST_SIZE, 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
+ },
+ .segs_are_gso = true,
+ },
+ {
+ /*
+ * One MSS per skb is what the unbounded path produces, so a
+ * bound of a single segment must not change the output.
+ */
+ .id = GSO_TEST_BOUNDED_ONE_MSS,
+ .name = "bounded_one_mss",
+ .linear_len = GSO_TEST_SIZE,
+ .nr_frags = 3,
+ .frags = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ .max_segs = 1,
+ .nr_segs = 4,
+ .segs = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ },
};
static void gso_test_case_to_desc(struct gso_test_case *t, char *desc)
@@ -226,6 +286,7 @@ static void gso_test_func(struct kunit *test)
if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM)
features &= ~NETIF_F_SG;
+ SKB_GSO_CB(skb)->max_segs = tcase->max_segs;
segs = skb_segment(skb, features);
if (IS_ERR(segs)) {
KUNIT_FAIL(test, "segs error %pe", segs);
@@ -247,6 +308,15 @@ static void gso_test_func(struct kunit *test)
/* header was copied to all segs */
KUNIT_ASSERT_EQ(test, memcmp(skb_mac_header(cur), hdr, sizeof(hdr)), 0);
+ if (tcase->segs_are_gso) {
+ KUNIT_EXPECT_TRUE(test, skb_is_gso(cur));
+ KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_size,
+ GSO_TEST_SIZE);
+ KUNIT_EXPECT_LE(test, skb_shinfo(cur)->gso_segs,
+ tcase->max_segs);
+ KUNIT_EXPECT_FALSE(test, skb_shinfo(cur)->gso_type &
+ SKB_GSO_PARTIAL);
+ }
/* last seg can be found through segs->prev pointer */
if (!next)
@@ -261,6 +331,177 @@ static void gso_test_func(struct kunit *test)
consume_skb(skb);
}
+#define GSO_TCP_HDR_LEN \
+ (ETH_HLEN + sizeof(struct iphdr) + sizeof(struct tcphdr))
+
+static struct sk_buff *gso_tcp_skb_new(unsigned int payload_len)
+{
+ struct sk_buff *skb;
+ struct ethhdr *eth;
+ struct tcphdr *th;
+ struct iphdr *iph;
+
+ skb = alloc_skb(GSO_TCP_HDR_LEN + payload_len, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+ skb_put_zero(skb, GSO_TCP_HDR_LEN + payload_len);
+
+ skb_reset_mac_header(skb);
+ eth = eth_hdr(skb);
+ eth->h_proto = htons(ETH_P_IP);
+ skb->protocol = eth->h_proto;
+
+ skb_set_network_header(skb, ETH_HLEN);
+ iph = ip_hdr(skb);
+ iph->version = 4;
+ iph->ihl = sizeof(*iph) / 4;
+ iph->protocol = IPPROTO_TCP;
+ iph->tot_len = htons(sizeof(*iph) + sizeof(*th) + payload_len);
+
+ skb_set_transport_header(skb, ETH_HLEN + sizeof(*iph));
+ th = tcp_hdr(skb);
+ th->doff = sizeof(*th) / 4;
+
+ skb->ip_summed = CHECKSUM_PARTIAL;
+ skb->csum_start = skb_transport_header(skb) - skb->head;
+ skb->csum_offset = offsetof(struct tcphdr, check);
+ skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
+ skb_shinfo(skb)->gso_size = GSO_TEST_SIZE;
+ skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);
+
+ return skb;
+}
+
+static void gso_test_tcp_bounded_segment(struct kunit *test)
+{
+ netdev_features_t features = NETIF_F_SG | NETIF_F_HW_CSUM |
+ NETIF_F_TSO;
+ const unsigned int payload_len = 3 * GSO_TEST_SIZE + 3;
+ struct sk_buff *skb, *segs, *cur, *next;
+ const unsigned int expected[] = {
+ 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
+ };
+ const unsigned int max_segs = 2;
+ int i = 0;
+
+ skb = gso_tcp_skb_new(payload_len);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+
+ segs = __skb_gso_segment(skb, features, true, max_segs);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, segs);
+
+ for (cur = segs; cur; cur = next, i++) {
+ next = cur->next;
+
+ KUNIT_ASSERT_LT(test, i, ARRAY_SIZE(expected));
+ KUNIT_EXPECT_EQ(test, cur->len,
+ GSO_TCP_HDR_LEN + expected[i]);
+ KUNIT_EXPECT_TRUE(test, skb_is_gso(cur));
+ KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_size,
+ GSO_TEST_SIZE);
+ KUNIT_EXPECT_LE(test, skb_shinfo(cur)->gso_segs, max_segs);
+
+ consume_skb(cur);
+ }
+
+ KUNIT_EXPECT_EQ(test, i, ARRAY_SIZE(expected));
+ consume_skb(skb);
+}
+
+#define GSO_TCP6_HDR_LEN \
+ (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
+
+static struct sk_buff *gso_tcp6_skb_new(unsigned int payload_len)
+{
+ struct ipv6hdr *ip6h;
+ struct sk_buff *skb;
+ struct ethhdr *eth;
+ struct tcphdr *th;
+
+ skb = alloc_skb(GSO_TCP6_HDR_LEN + payload_len, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+ skb_put_zero(skb, GSO_TCP6_HDR_LEN + payload_len);
+
+ skb_reset_mac_header(skb);
+ eth = eth_hdr(skb);
+ eth->h_proto = htons(ETH_P_IPV6);
+ skb->protocol = eth->h_proto;
+
+ skb_set_network_header(skb, ETH_HLEN);
+ ip6h = ipv6_hdr(skb);
+ ip6h->version = 6;
+ ip6h->nexthdr = IPPROTO_TCP;
+ ip6h->payload_len = htons(sizeof(*th) + payload_len);
+
+ skb_set_transport_header(skb, ETH_HLEN + sizeof(*ip6h));
+ th = tcp_hdr(skb);
+ th->doff = sizeof(*th) / 4;
+
+ skb->ip_summed = CHECKSUM_PARTIAL;
+ skb->csum_start = skb_transport_header(skb) - skb->head;
+ skb->csum_offset = offsetof(struct tcphdr, check);
+ skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
+ skb_shinfo(skb)->gso_size = GSO_TEST_SIZE;
+ skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);
+
+ return skb;
+}
+
+/* The device GSO size limit is per L3 protocol, and it has to survive the
+ * VLAN tag which validate_xmit_vlan() can push inside the skb, because that
+ * tag replaces skb->protocol with the VLAN ethertype.
+ */
+static void gso_test_tcp_limit_l3_proto(struct kunit *test)
+{
+ static const struct net_device_ops dummy_netdev_ops = { };
+ const unsigned int payload_len = 100 * 1024;
+ netdev_features_t features;
+ struct net_device *dev;
+ struct sk_buff *skb;
+
+ dev = alloc_etherdev(0);
+ KUNIT_ASSERT_NOT_NULL(test, dev);
+ dev->netdev_ops = &dummy_netdev_ops;
+ dev->hw_features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO6;
+ dev->features = dev->hw_features;
+ dev->vlan_features = dev->hw_features;
+
+ skb = gso_tcp6_skb_new(payload_len);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb->dev = dev;
+
+ /* The skb fits the IPv6 limit but not the IPv4 one. */
+ dev->gso_max_size = GSO_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_LEGACY_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_TRUE(test, features & NETIF_F_GSO_MASK);
+
+ /* ...and the other way around. */
+ dev->gso_max_size = GSO_LEGACY_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_FALSE(test, features & NETIF_F_GSO_MASK);
+
+ /* Pushing the tag inside must not change either answer. */
+ skb = vlan_insert_tag_set_proto(skb, htons(ETH_P_8021Q), 0);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ KUNIT_ASSERT_TRUE(test, skb->protocol == htons(ETH_P_8021Q));
+
+ dev->gso_max_size = GSO_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_LEGACY_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_TRUE(test, features & NETIF_F_GSO_MASK);
+
+ dev->gso_max_size = GSO_LEGACY_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_FALSE(test, features & NETIF_F_GSO_MASK);
+
+ consume_skb(skb);
+ free_netdev(dev);
+}
+
/* IP tunnel flags */
#include <net/ip_tunnels.h>
@@ -372,6 +613,8 @@ static void ip_tunnel_flags_test_run(struct kunit *test)
static struct kunit_case net_test_cases[] = {
KUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),
+ KUNIT_CASE(gso_test_tcp_bounded_segment),
+ KUNIT_CASE(gso_test_tcp_limit_l3_proto),
KUNIT_CASE_PARAM(ip_tunnel_flags_test_run,
ip_tunnel_flags_test_gen_params),
{ },
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index dbbe10277d51d..9c0d140236bc6 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4793,6 +4793,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
struct sk_buff *segs = NULL;
struct sk_buff *tail = NULL;
struct sk_buff *list_skb = skb_shinfo(head_skb)->frag_list;
+ unsigned int max_segs = SKB_GSO_CB(head_skb)->max_segs;
unsigned int mss = skb_shinfo(head_skb)->gso_size;
bool gso_by_frags = mss == GSO_BY_FRAGS;
unsigned int doffset = head_skb->data - skb_mac_header(head_skb);
@@ -4839,7 +4840,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
csum = !!can_checksum_protocol(features, proto);
if (sg && csum && !gso_by_frags) {
- if (!(features & NETIF_F_GSO_PARTIAL)) {
+ if (!max_segs && !(features & NETIF_F_GSO_PARTIAL)) {
struct sk_buff *iter;
unsigned int frag_len;
@@ -4874,7 +4875,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
* now.
*/
DEBUG_NET_WARN_ON_ONCE(len / mss > GSO_MAX_SEGS);
- partial_segs = min(len / mss, GSO_MAX_SEGS);
+ if (max_segs)
+ partial_segs = min(len / mss, max_segs);
+ else
+ partial_segs = min(len / mss, GSO_MAX_SEGS);
if (partial_segs > 1)
mss *= partial_segs;
else
@@ -4975,6 +4979,12 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
__copy_skb_header(nskb, head_skb);
+ /*
+ * max_segs is a per-call limit, so output skbs must not
+ * inherit it from the input skb.
+ */
+ SKB_GSO_CB(nskb)->max_segs = 0;
+
skb_headers_offset_update(nskb, skb_headroom(nskb) - headroom);
skb_reset_mac_len(nskb);
diff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c
index e74d99ca9face..a4076318c5352 100644
--- a/net/ipv4/tcp_offload.c
+++ b/net/ipv4/tcp_offload.c
@@ -164,7 +164,8 @@ struct sk_buff *tcp_gso_segment(struct sk_buff *skb,
if (unlikely(skb->len <= mss))
goto out;
- if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
+ if (!SKB_GSO_CB(skb)->max_segs &&
+ skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
/* Packet is from an untrusted source, reset gso_segs. */
skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index 2187034143255..e793aead68372 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -375,7 +375,7 @@ static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,
int err;
BUILD_BUG_ON(sizeof(*OVS_CB(skb)) > SKB_GSO_CB_OFFSET);
- segs = __skb_gso_segment(skb, NETIF_F_SG, false);
+ segs = __skb_gso_segment(skb, NETIF_F_SG, false, 0);
if (IS_ERR(segs))
return PTR_ERR(segs);
if (segs == NULL)
|
| 3/2 | 2026/09/20 10:55 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 12316 output: 30 thoughts: 1117 Thoughts: **Fuzzing Evaluation: Bounded GSO Segmentation Patch** Alright, let's break this down. My initial instinct is that this patch warrants syzkaller fuzzing, but I need to justify it rigorously. First, let's look at the patch itself. It's touching a significant chunk of the networking stack: `drivers/net/tap.c`, various `include/` files, and core networking components in `net/core/`, `net/ipv4/`, and even `net/openvswitch/`. That's already a red flag in a good way, indicating broad impact. The gist of the change appears to be introducing bounded GSO segmentation with a `max_segs` parameter. This looks like a refinement of Generic Segmentation Offload (GSO), which is a crucial area for packet transmission, especially for optimizing performance by handling segmentation in hardware or at the network driver level. Now, considering the code modifications, the changes are centered around `__skb_gso_segment`, with related updates in `validate_xmit_skb`, and `skb_segment` to handle this `max_segs` parameter. Furthermore, we see updates to `netif_get_gso_max_size`, a critical function for setting up GSO, as well as `tcp_gso_segment` and KUnit tests. This tells me we're dealing with fundamental changes in how the kernel segments packets for transmission. This directly affects the packet transmit paths, the very core of network function. This is all fully reachable via standard networking syscalls within a QEMU environment. We're talking about standard networking syscalls (sockets, tun/tap, veth, loopback, packet sockets, etc.). This makes it *highly* fuzzable, and is likely TRUE for WorthFuzzing. Now, applying the rules, this *definitely* falls under "CORE SUBSYSTEMS". Any modification to core networking logic, packet transmission, and the skbuff structure will merit fuzzing. This patch *modifies* those aspects of network function. And if there is any doubt, the presence of runtime assertions/defensive checks such as DEBUG_NET_WARN_ON_ONCE helps to raise our attention. WorthFuzzing is certainly TRUE here. Okay, let's identify the `FocusSymbols`. We need to pinpoint the kernel functions that are modified or most directly involved. I've already identified a number that are potential symbols: * `validate_xmit_skb` * `skb_gso_resegment_max_segs` (might be inlined, need to check) * `gso_within_device_limits` (static, so possibly inlined) * `skb_can_gso_resegment` (static, possibly inlined) * `__skb_gso_segment` * `skb_segment` * `tcp_gso_segment` * `netif_skb_features` * `tap_handle_frame` * `queue_gso_packets` Now, I'm going to quickly check `net/core/dev.c`, `net/core/skbuff.c`, `net/ipv4/tcp_offload.c` and make sure they're not static or entrypoints with `grepper` or by looking through the code, to determine what's actually interesting to syzkaller. Then the symbols can be refined. |
| 4/2 | 2026/09/20 10:56 | tool | grepper |
0mArgs:{
"Expression": "__skb_gso_segment",
"PathPrefix": "net/"
}Results: {
"Output": "net/core/dev.c=4153=static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)\n--\nnet/core/dev.c-4191-\nnet/core/dev.c:4192:\t\tsegs = __skb_gso_segment(skb, features, true,\nnet/core/dev.c-4193-\t\t\t\t\t resegment_max_segs);\n--\nnet/core/gso.c=66=static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)\n--\nnet/core/gso.c-75-/**\nnet/core/gso.c:76: *\t__skb_gso_segment - Perform segmentation on skb.\nnet/core/gso.c-77- *\t@skb: buffer to segment\n--\nnet/core/gso.c-88- */\nnet/core/gso.c:89:struct sk_buff *__skb_gso_segment(struct sk_buff *skb,\nnet/core/gso.c-90-\t\t\t\t netdev_features_t features, bool tx_path,\n--\nnet/core/gso.c-133-}\nnet/core/gso.c:134:EXPORT_SYMBOL(__skb_gso_segment);\nnet/core/gso.c-135-\n--\nnet/core/net_test.c=375=static void gso_test_tcp_bounded_segment(struct kunit *test)\n--\nnet/core/net_test.c-389-\nnet/core/net_test.c:390:\tsegs = __skb_gso_segment(skb, features, true, max_segs);\nnet/core/net_test.c-391-\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, segs);\n--\nnet/openvswitch/datapath.c=367=static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,\n--\nnet/openvswitch/datapath.c-377-\tBUILD_BUG_ON(sizeof(*OVS_CB(skb)) \u003e SKB_GSO_CB_OFFSET);\nnet/openvswitch/datapath.c:378:\tsegs = __skb_gso_segment(skb, NETIF_F_SG, false, 0);\nnet/openvswitch/datapath.c-379-\tif (IS_ERR(segs))\n"
} |
| 5/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 14881 output: 34 thoughts: 409 Thoughts: **Analyzing GSO Segmentation in the Kernel** Okay, let's break this down. I've got these functions bouncing around in my head: `__skb_gso_segment`, `skb_segment`, `tcp_gso_segment`, `validate_xmit_skb`, `netif_skb_features`, and `skb_gso_resegment_max_segs`. My first thought is about `skb_segment`. Is that a *ubiquitous* function, something that's called everywhere in the kernel? We need to know this, as it'll affect how we approach our analysis. Let's dig in a bit. Okay, `skb_segment` is specifically called when we're dealing with GSO segmentation. It's used when a socket buffer (`skb`) needs to be broken down into Maximum Segment Size (MSS) chunks or partial GSO chunks. The source file confirms this: it's right there in `net/core/skbuff.c`. Good, that narrows it down. Now, the question of "ubiquitous". The documentation specifically mentions we *don't* want to consider truly ubiquitous functions like memory allocators or synchronization primitives in this context, because they pollute the fuzzing effort. And, no, `skb_segment` is *not* like `kmalloc` or `mutex_lock` or a page fault handler. It's specific to packet segmentation – GSO and TSO. Good. `tcp_gso_segment` is also pretty clearly tied to TCP segmentation offload, no surprise there. `validate_xmit_skb` appears during the network transmit path, called from `__dev_queue_xmit`. Now, the focus seems to be on `skb_gso_resegment_max_segs`. This is the *new* function. It's in `net/core/dev.c`. However, is it static? Might it be inlined? I should check the callers of `validate_xmit_skb` or callers in `net/core/dev.c`. I need to figure out where that resegmentation function is used. |
| 6/2 | 2026/09/20 10:56 | tool | grepper |
0mArgs:{
"Expression": "validate_xmit_skb",
"PathPrefix": "net/core/dev.c"
}Results: {
"Output": "net/core/dev.c=4086=EXPORT_SYMBOL(skb_csum_hwoffload_help);\n--\nnet/core/dev.c-4091- */\nnet/core/dev.c:4092:static struct sk_buff *sk_validate_xmit_skb(struct sk_buff *skb,\nnet/core/dev.c-4093-\t\t\t\t\t struct net_device *dev)\n--\nnet/core/dev.c-4102-\t\tif (sk_fullsock(sk))\nnet/core/dev.c:4103:\t\t\tsk_validate = sk-\u003esk_validate_xmit_skb;\nnet/core/dev.c-4104-\t\telse if (sk_is_inet(sk) \u0026\u0026 sk-\u003esk_state == TCP_TIME_WAIT)\nnet/core/dev.c:4105:\t\t\tsk_validate = inet_twsk(sk)-\u003etw_validate_xmit_skb;\nnet/core/dev.c-4106-\t}\n--\nnet/core/dev.c=4120=static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,\n--\nnet/core/dev.c-4152- */\nnet/core/dev.c:4153:static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)\nnet/core/dev.c-4154-{\n--\nnet/core/dev.c-4166-\nnet/core/dev.c:4167:\tskb = sk_validate_xmit_skb(skb, dev);\nnet/core/dev.c-4168-\tif (unlikely(!skb))\n--\nnet/core/dev.c-4231-\nnet/core/dev.c:4232:struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *dev, bool *again)\nnet/core/dev.c-4233-{\n--\nnet/core/dev.c-4242-\nnet/core/dev.c:4243:\t\tskb = validate_xmit_skb(skb, dev, again);\nnet/core/dev.c-4244-\t\tif (IS_ERR_OR_NULL(skb))\n--\nnet/core/dev.c-4257-}\nnet/core/dev.c:4258:EXPORT_SYMBOL_GPL(validate_xmit_skb_list);\nnet/core/dev.c-4259-\n--\nnet/core/dev.c=4920=int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)\n--\nnet/core/dev.c-5013-\nnet/core/dev.c:5014:\t\tskb = validate_xmit_skb(skb, dev, \u0026again);\nnet/core/dev.c-5015-\t\tif (IS_ERR_OR_NULL(skb)) {\n--\nnet/core/dev.c=5067=int __dev_direct_xmit(struct sk_buff *skb, u16 queue_id)\n--\nnet/core/dev.c-5078-\nnet/core/dev.c:5079:\tskb = validate_xmit_skb_list(skb, dev, \u0026again);\nnet/core/dev.c-5080-\tif (skb != orig_skb)\n"
} |
| 7/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16707 output: 36 thoughts: 206 Thoughts: **Analyzing SKB Validation and Segmentation in the Kernel** Okay, let's break this down. First, `validate_xmit_skb_list` is definitely exported, that's straightforward. I see that `validate_xmit_skb` is used by both `__dev_queue_xmit` and `validate_xmit_skb_list`. Hmm, I should be careful to understand how it's being used. I need to keep in mind that `validate_xmit_skb` is static and roughly 80 lines long. That makes me wonder if the compiler might be inlining it. That could impact performance and how I reason about its execution flow. Of course, `validate_xmit_skb_list` calls `validate_xmit_skb`, so I need to account for both scenarios. Excellent, `netif_skb_features` is also exported, which is good to know. And `__skb_gso_segment` is exported. Okay, and finally, `skb_segment` is also exported! I need to dig into `skb_segment` in `net/core/skbuff.c` and see how it works. That's the key to understanding how segmentation is happening in this scenario, so I'll check that. |
| 8/2 | 2026/09/20 10:56 | tool | grepper |
0mArgs:{
"Expression": "EXPORT_SYMBOL.*skb_segment",
"PathPrefix": "net/core/skbuff.c"
}Results: {
"Output": "net/core/skbuff.c=4687=struct sk_buff *skb_segment_list(struct sk_buff *skb,\n--\nnet/core/skbuff.c-4778-}\nnet/core/skbuff.c:4779:EXPORT_SYMBOL_GPL(skb_segment_list);\nnet/core/skbuff.c-4780-\n--\nnet/core/skbuff.c=4790=struct sk_buff *skb_segment(struct sk_buff *head_skb,\n--\nnet/core/skbuff.c-5153-}\nnet/core/skbuff.c:5154:EXPORT_SYMBOL_GPL(skb_segment);\nnet/core/skbuff.c-5155-\n"
} |
| 9/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17434 output: 41 thoughts: 48 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person: **Analyzing the GSO Segmentation Mechanism** Alright, so `skb_segment` is explicitly exported under GPL, which means it's a key function for packet segmentation. Makes sense, given the name. Now, to understand how it's being used, I should dive into `tcp_gso_segment` located in `net/ipv4/tcp_offload.c`. I'm guessing this function is central to TCP Generic Segmentation Offload (GSO). I need to understand how the kernel handles segmentation for TCP packets at the IP layer. I'm going to look at the interactions between the TCP stack and GSO to see the critical areas of GSO within the TCP offload process. |
| 10/2 | 2026/09/20 10:56 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*tcp_gso_segment",
"PathPrefix": "net/ipv4/tcp_offload.c"
}Results: {
"error": "no matches"
} |
| 11/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17709 output: 37 thoughts: 29 Thoughts: Here's my take on the thought process, as if it were my own: **Diving into the `tcp_gso_segment` Implementation** Okay, so I'm curious about how `tcp_gso_segment` is implemented. I need a refresher on the details of this crucial function. My first step is to crack open the source code, specifically `net/ipv4/tcp_offload.c`. That's where I expect to find the nitty-gritty of TCP Segmentation Offload, so let's see what's what. Time to get my hands dirty and examine the code. |
| 12/2 | 2026/09/20 10:56 | tool | grepper |
0mArgs:{
"Expression": "tcp_gso_segment",
"PathPrefix": "net/ipv4/tcp_offload.c"
}Results: {
"Output": "net/ipv4/tcp_offload.c=98=static struct sk_buff *tcp4_gso_segment(struct sk_buff *skb,\n--\nnet/ipv4/tcp_offload.c-129-\nnet/ipv4/tcp_offload.c:130:\treturn tcp_gso_segment(skb, features);\nnet/ipv4/tcp_offload.c-131-}\nnet/ipv4/tcp_offload.c-132-\nnet/ipv4/tcp_offload.c:133:struct sk_buff *tcp_gso_segment(struct sk_buff *skb,\nnet/ipv4/tcp_offload.c-134-\t\t\t\tnetdev_features_t features)\n"
} |
| 13/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 18105 output: 29 thoughts: 40 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing the Scope of `tcp_gso_segment`** Alright, so `tcp_gso_segment` isn't static. That immediately piques my interest. It's defined within `net/ipv4/tcp_offload.c`, but if it's not static, it suggests it's likely meant to be used externally, potentially across different modules or even architectures. Given its location, I suspect it plays a key role in TCP offload functionality. My next immediate step is to pinpoint its declaration within the include files. I need to understand *where* it's exposed. Is it in a more general header like `tcp.h` or something more specific? The header file will give me a clear picture of its intended interface, its public API, and whether there are any dependencies I should be aware of. This will inform my assessment of its design and whether there are any inherent portability considerations. Knowing its header also gives me critical information for understanding how it is intended to be used. |
| 14/2 | 2026/09/20 10:56 | tool | grepper |
0mArgs:{
"Expression": "tcp_gso_segment\\(",
"PathPrefix": "include/"
}Results: {
"Output": "include/net/tcp.h=2440=void tcp_v4_destroy_sock(struct sock *sk);\ninclude/net/tcp.h-2441-\ninclude/net/tcp.h:2442:struct sk_buff *tcp_gso_segment(struct sk_buff *skb,\ninclude/net/tcp.h-2443-\t\t\t\tnetdev_features_t features);\n"
} |
| 15/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 18516 output: 31 thoughts: 396 Thoughts: **My Investigation into TCP GSO Segmentation** Okay, so I'm diving into the `tcp_gso_segment` function. I know it's declared in `include/net/tcp.h` and defined in `net/ipv4/tcp_offload.c`. First, I want to see how it's used elsewhere in the kernel. Let's look at `tap_handle_frame` in `drivers/net/tap.c`, and then `queue_gso_packets` in `net/openvswitch/datapath.c`. I need a holistic view to understand its role. Now, a broader examination. I'm going to shift gears and zoom in on the core network code, specifically `net/core/dev.c`. What new functions or modifications have been introduced there? I see a list: `gso_within_device_limits`, `gso_features_check`, `__netif_skb_features`, `netif_skb_features`, `skb_can_gso_resegment`, `skb_gso_resegment_max_segs`, `validate_xmit_skb`, and `validate_xmit_skb_list`, which calls `validate_xmit_skb`. Interesting. That indicates these additions are fundamental to GSO's architecture. I should follow the threads, so I'll also explore `__skb_gso_segment` in `net/core/gso.c`, and then `skb_segment` in `net/core/skbuff.c`. Of course, I need to keep the definition in `net/ipv4/tcp_offload.c` in mind, and also `tap_handle_frame` which I've already noted. Now, let's get practical. I need to figure out the build dependencies. What configuration options are absolutely necessary for this GSO code to compile? I need to check the relevant configuration flags. I'll need to check the files I looked at, namely `net/core/dev.c`, `net/core/gso.c`, and `net/core/skbuff.c`. I need to figure out if these are compiled under the umbrella of `CONFIG_NET` or if there are more precise requirements. Let's dig into `net/core/Makefile` to find the answers. That will show me the build dependencies and constraints. |
| 16/2 | 2026/09/20 10:56 | tool | read-file |
0mArgs:{
"File": "net/core/Makefile",
"FirstLine": 1,
"LineCount": 40
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\t#\n 3:\t# Makefile for the Linux networking core.\n 4:\t#\n 5:\t\n 6:\tobj-y := sock.o skbuff.o datagram.o stream.o scm.o \\\n 7:\t\t gen_stats.o gen_estimator.o net_namespace.o secure_seq.o \\\n 8:\t\t flow_dissector.o\n 9:\t\n 10:\tobj-$(CONFIG_SYSCTL) += sysctl_net_core.o\n 11:\t\n 12:\tobj-y\t\t += dev.o dev_api.o dev_addr_lists.o dst.o netevent.o \\\n 13:\t\t\t\tneighbour.o rtnetlink.o utils.o link_watch.o filter.o \\\n 14:\t\t\t\tsock_diag.o dev_ioctl.o tso.o sock_reuseport.o \\\n 15:\t\t\t\tfib_notifier.o xdp.o flow_offload.o gro.o \\\n 16:\t\t\t\tnetdev-genl.o netdev-genl-gen.o netdev_work.o gso.o\n 17:\t\n 18:\tobj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o\n 19:\t\n 20:\tobj-y += net-sysfs.o\n 21:\tobj-y += hotdata.o\n 22:\tobj-y += netdev_config.o\n 23:\tobj-y += netdev_rx_queue.o\n 24:\tobj-y += netdev_queues.o\n 25:\tobj-$(CONFIG_PAGE_POOL) += page_pool.o page_pool_user.o\n 26:\tobj-$(CONFIG_PROC_FS) += net-procfs.o\n 27:\tobj-$(CONFIG_NET_PKTGEN) += pktgen.o\n 28:\tobj-$(CONFIG_NETPOLL) += netpoll.o\n 29:\tobj-$(CONFIG_FIB_RULES) += fib_rules.o\n 30:\tobj-$(CONFIG_TRACEPOINTS) += net-traces.o\n 31:\tobj-$(CONFIG_NET_DROP_MONITOR) += drop_monitor.o\n 32:\tobj-$(CONFIG_NET_IEEE8021Q_HELPERS) += ieee8021q_helpers.o\n 33:\tobj-$(CONFIG_NET_SELFTESTS) += selftests.o\n 34:\tobj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += timestamping.o\n 35:\tobj-$(CONFIG_NET_PTP_CLASSIFY) += ptp_classifier.o\n 36:\tobj-$(CONFIG_CGROUP_NET_PRIO) += netprio_cgroup.o\n 37:\tobj-$(CONFIG_CGROUP_NET_CLASSID) += netclassid_cgroup.o\n 38:\tobj-$(CONFIG_LWTUNNEL) += lwtunnel.o\n 39:\tobj-$(CONFIG_LWTUNNEL_BPF) += lwt_bpf.o\n 40:\tobj-$(CONFIG_DST_CACHE) += dst_cache.o\n"
} |
| 17/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20287 output: 32 thoughts: 78 Thoughts: **Kernel Network Code Structure** Okay, so I'm looking at how this network code is structured within the kernel. I see the assignment: `obj-y := sock.o skbuff.o ...` and `obj-y += dev.o ... gso.o`. That tells me these object files, and likely others, are being compiled into the relevant module or target. It's pretty clear this is for the core networking functionality, probably under the umbrella of `net/core`, and that it's all part of the build process with `CONFIG_NET=y`. Makes sense. I'm curious about something though, specifically, where does `net/ipv4/tcp_offload.c` fit into all this? Is it also part of the core? Let me just quickly check the `net/ipv4/Makefile` and see how this specific component is handled. |
| 18/2 | 2026/09/20 10:56 | tool | read-file |
0mArgs:{
"File": "net/ipv4/Makefile",
"FirstLine": 1,
"LineCount": 35
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\t#\n 3:\t# Makefile for the Linux TCP/IP (INET) layer.\n 4:\t#\n 5:\t\n 6:\tobj-y := route.o inetpeer.o protocol.o \\\n 7:\t\t ip_input.o ip_fragment.o ip_forward.o ip_options.o \\\n 8:\t\t ip_output.o ip_sockglue.o inet_hashtables.o \\\n 9:\t\t inet_timewait_sock.o inet_connection_sock.o \\\n 10:\t\t tcp.o tcp_input.o tcp_output.o tcp_timer.o tcp_ipv4.o \\\n 11:\t\t tcp_minisocks.o tcp_cong.o tcp_metrics.o tcp_fastopen.o \\\n 12:\t\t tcp_recovery.o tcp_ulp.o \\\n 13:\t\t tcp_offload.o tcp_plb.o datagram.o raw.o udp.o \\\n 14:\t\t udp_offload.o arp.o icmp.o devinet.o af_inet.o igmp.o \\\n 15:\t\t fib_frontend.o fib_semantics.o fib_trie.o fib_notifier.o \\\n 16:\t\t inet_fragment.o ping.o ip_tunnel_core.o gre_offload.o \\\n 17:\t\t metrics.o netlink.o nexthop.o udp_tunnel_stub.o\n 18:\t\n 19:\tobj-$(CONFIG_NET_IP_TUNNEL) += ip_tunnel.o\n 20:\tobj-$(CONFIG_SYSCTL) += sysctl_net_ipv4.o\n 21:\tobj-$(CONFIG_PROC_FS) += proc.o\n 22:\tobj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o\n 23:\tobj-$(CONFIG_IP_MROUTE) += ipmr.o\n 24:\tobj-$(CONFIG_IP_MROUTE_COMMON) += ipmr_base.o\n 25:\tobj-$(CONFIG_NET_IPIP) += ipip.o\n 26:\tgre-y := gre_demux.o\n 27:\tfou-y := fou_core.o fou_nl.o fou_bpf.o\n 28:\tobj-$(CONFIG_NET_FOU) += fou.o\n 29:\tobj-$(CONFIG_NET_IPGRE_DEMUX) += gre.o\n 30:\tobj-$(CONFIG_NET_IPGRE) += ip_gre.o\n 31:\tudp_tunnel-y := udp_tunnel_core.o udp_tunnel_nic.o\n 32:\tobj-$(CONFIG_NET_UDP_TUNNEL) += udp_tunnel.o\n 33:\tobj-$(CONFIG_NET_IPVTI) += ip_vti.o\n 34:\tobj-$(CONFIG_SYN_COOKIES) += syncookies.o\n 35:\tobj-$(CONFIG_INET_AH) += ah4.o\n"
} |
| 19/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 21363 output: 29 thoughts: 35 Thoughts: **Investigating TCP Offload Module Inclusion** Okay, so `tcp_offload.o` is built – that's the key module. My immediate thought is, when exactly does it get included? The clue here is `obj-y` when `CONFIG_INET` is enabled. That's good, that's exactly the kind of conditional I'm looking for. This tells me I need to go straight to `net/Makefile`. I need to figure out how `tcp_offload.o` is being brought into the build system. Let's see how the dependencies are structured within that Makefile. I anticipate it's likely part of a broader network subsystem module, but I need to confirm the exact mechanisms to understand the configuration hooks fully. Time to dive into that Makefile and see how this all connects. |
| 20/2 | 2026/09/20 10:56 | tool | read-file |
0mArgs:{
"File": "net/Makefile",
"FirstLine": 1,
"LineCount": 40
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\t#\n 3:\t# Makefile for the linux networking.\n 4:\t#\n 5:\t# 2 Sep 2000, Christoph Hellwig \u003chch@infradead.org\u003e\n 6:\t# Rewritten to use lists instead of if-statements.\n 7:\t#\n 8:\t\n 9:\tobj-y\t\t\t\t:= devres.o socket.o core/\n 10:\t\n 11:\tobj-$(CONFIG_COMPAT)\t\t+= compat.o\n 12:\t\n 13:\t# LLC has to be linked before the files in net/802/\n 14:\tobj-$(CONFIG_LLC)\t\t+= llc/\n 15:\tobj-y\t\t\t\t+= ethernet/ 802/ sched/ netlink/ bpf/ ethtool/\n 16:\tobj-$(CONFIG_NETFILTER)\t\t+= netfilter/\n 17:\tobj-$(CONFIG_INET)\t\t+= ipv4/\n 18:\tobj-$(CONFIG_TLS)\t\t+= tls/\n 19:\tobj-$(CONFIG_XFRM)\t\t+= xfrm/\n 20:\tobj-$(CONFIG_UNIX)\t\t+= unix/\n 21:\tobj-$(CONFIG_INET_PSP)\t\t+= psp/\n 22:\tobj-y\t\t\t\t+= ipv6/\n 23:\tobj-$(CONFIG_PACKET)\t\t+= packet/\n 24:\tobj-$(CONFIG_NET_KEY)\t\t+= key/\n 25:\tobj-$(CONFIG_BRIDGE)\t\t+= bridge/\n 26:\tobj-$(CONFIG_NET_DEVLINK)\t+= devlink/\n 27:\tobj-y\t\t\t\t+= dsa/\n 28:\tobj-$(CONFIG_X25)\t\t+= x25/\n 29:\tobj-$(CONFIG_LAPB)\t\t+= lapb/\n 30:\tobj-$(CONFIG_CAN)\t\t+= can/\n 31:\tobj-$(CONFIG_BT)\t\t+= bluetooth/\n 32:\tobj-$(CONFIG_SUNRPC)\t\t+= sunrpc/\n 33:\tobj-$(CONFIG_AF_RXRPC)\t\t+= rxrpc/\n 34:\tobj-$(CONFIG_AF_KCM)\t\t+= kcm/\n 35:\tobj-$(CONFIG_STREAM_PARSER)\t+= strparser/\n 36:\tobj-$(CONFIG_ATM)\t\t+= atm/\n 37:\tobj-$(CONFIG_L2TP)\t\t+= l2tp/\n 38:\tobj-$(CONFIG_PHONET)\t\t+= phonet/\n 39:\tifneq ($(CONFIG_VLAN_8021Q),)\n 40:\tobj-y\t\t\t\t+= 8021q/\n"
} |
| 21/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22299 output: 28 thoughts: 48 Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience: **Kernel Configuration Dependencies for Network TAP Drivers** Right, let's break this down. My initial thought process is centered around network device configuration within the kernel. I'm immediately considering dependencies – specifically, the core network stack (`CONFIG_NET`) and the IP stack (`CONFIG_INET`). I need to be sure these are enabled before anything else. Now, my mind shifts to `tap.c`. I know that's where the TAP/MACVTAP functionality resides. So, I need to verify its configuration options: `CONFIG_TAP` or `CONFIG_MACVTAP`. To be absolutely certain, I'm heading straight to `drivers/net/Makefile`. That's where I can confirm the conditional compilation logic and the relevant kernel build dependencies. I need to ensure my TAP driver is built correctly. |
| 22/2 | 2026/09/20 10:56 | tool | grepper |
0mArgs:{
"Expression": "tap\\.o",
"PathPrefix": "drivers/net/Makefile"
}Results: {
"Output": "drivers/net/Makefile=19=obj-$(CONFIG_MACVLAN) += macvlan.o\ndrivers/net/Makefile:20:obj-$(CONFIG_MACVTAP) += macvtap.o\ndrivers/net/Makefile-21-obj-$(CONFIG_MII) += mii.o\n--\ndrivers/net/Makefile=32=obj-$(CONFIG_TUN) += tun.o\ndrivers/net/Makefile:33:obj-$(CONFIG_TAP) += tap.o\ndrivers/net/Makefile-34-obj-$(CONFIG_VETH) += veth.o\n"
} |
| 23/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22717 output: 36 thoughts: 125 Thoughts: **Analyzing the Openvswitch Kernel Patch** Okay, so we're diving into this Openvswitch patch. First things first, the relevant kernel config option is `CONFIG_OPENVSWITCH`. That's the baseline. Now, looking at the code changes, it seems the heavy lifting is happening primarily in these key areas: `net/core/dev.c`, `net/core/skbuff.c`, `net/core/gso.c`, and `net/ipv4/tcp_offload.c`. Given that, I'll be focusing on `NET` and `INET` as the primary configuration considerations. Now, let's get into the nitty-gritty. I need to understand the specifics of what this patch is altering and which functions are the most important focus symbols. To start, I'll zero in on `net/core/dev.c` and take a good look around lines 3834-4200. I expect this section to be pivotal, given the importance of device handling in any virtual switch implementation. |
| 24/2 | 2026/09/20 10:56 | tool | read-file |
0mArgs:{
"File": "net/core/dev.c",
"FirstLine": 3925,
"LineCount": 80
}Results: {
"Contents": "3925:\t\n3926:\t\tif (dev-\u003enetdev_ops-\u003endo_features_check)\n3927:\t\t\tfeatures \u0026= dev-\u003enetdev_ops-\u003endo_features_check(skb, dev,\n3928:\t\t\t\t\t\t\t\t\tfeatures);\n3929:\t\telse\n3930:\t\t\tfeatures \u0026= dflt_features_check(skb, dev, features);\n3931:\t\n3932:\t\treturn harmonize_features(skb, features);\n3933:\t}\n3934:\t\n3935:\tnetdev_features_t netif_skb_features(struct sk_buff *skb)\n3936:\t{\n3937:\t\treturn __netif_skb_features(skb, true);\n3938:\t}\n3939:\tEXPORT_SYMBOL(netif_skb_features);\n3940:\t\n3941:\tstatic bool skb_can_gso_resegment(struct sk_buff *skb,\n3942:\t\t\t\t\t netdev_features_t features)\n3943:\t{\n3944:\t\t__be16 protocol;\n3945:\t\n3946:\t\tif (!net_gso_ok(features | NETIF_F_GSO_ROBUST,\n3947:\t\t\t\tskb_shinfo(skb)-\u003egso_type))\n3948:\t\t\treturn false;\n3949:\t\n3950:\t\tif (!(features \u0026 NETIF_F_SG))\n3951:\t\t\treturn false;\n3952:\t\n3953:\t\tprotocol = skb_network_protocol(skb, NULL);\n3954:\t\tif (!protocol || !can_checksum_protocol(features, protocol))\n3955:\t\t\treturn false;\n3956:\t\n3957:\t\t/*\n3958:\t\t * The TCP frag-list path does not carry the bounded segment\n3959:\t\t * limit through skb_segment_list(). Keep bounded resegmentation\n3960:\t\t * on the regular skb path until that support is added.\n3961:\t\t */\n3962:\t\tif (skb_has_frag_list(skb))\n3963:\t\t\treturn false;\n3964:\t\n3965:\t\treturn true;\n3966:\t}\n3967:\t\n3968:\tstatic unsigned int\n3969:\tskb_gso_resegment_max_segs(struct sk_buff *skb, struct net_device *dev,\n3970:\t\t\t\t netdev_features_t features)\n3971:\t{\n3972:\t\tunsigned int mss = skb_shinfo(skb)-\u003egso_size;\n3973:\t\tunsigned int hdr_len, max_segs;\n3974:\t\tunsigned int gso_max_size;\n3975:\t\tstruct tcphdr _tcph, *th;\n3976:\t\n3977:\t\tgso_max_size = netif_get_gso_max_size(dev, vlan_get_protocol(skb));\n3978:\t\n3979:\t\tif (!skb_is_gso(skb) || !skb_is_gso_tcp(skb) ||\n3980:\t\t skb-\u003eencapsulation || mss == GSO_BY_FRAGS ||\n3981:\t\t !skb_mac_header_was_set(skb) ||\n3982:\t\t !skb_transport_header_was_set(skb) ||\n3983:\t\t !skb_can_gso_resegment(skb, features))\n3984:\t\t\treturn 0;\n3985:\t\n3986:\t\tth = skb_header_pointer(skb, skb_transport_offset(skb), sizeof(_tcph),\n3987:\t\t\t\t\t\u0026_tcph);\n3988:\t\tif (!th || th-\u003edoff \u003c sizeof(*th) / 4)\n3989:\t\t\treturn 0;\n3990:\t\n3991:\t\thdr_len = skb_transport_header(skb) - skb_mac_header(skb) +\n3992:\t\t\t th-\u003edoff * 4;\n3993:\t\tif (gso_max_size \u003c= hdr_len + mss)\n3994:\t\t\treturn 0;\n3995:\t\n3996:\t\t/*\n3997:\t\t * gso_within_device_limits() accepts gso_segs == gso_max_segs but\n3998:\t\t * rejects skb-\u003elen \u003e= gso_max_size, so only the size bound needs - 1.\n3999:\t\t */\n4000:\t\tmax_segs = (gso_max_size - hdr_len - 1) / mss;\n4001:\t\tmax_segs = min_t(unsigned int, max_segs,\n4002:\t\t\t\t READ_ONCE(dev-\u003egso_max_segs));\n4003:\t\n4004:\t\treturn max_segs \u003e 1 ? max_segs : 0;\n"
} |
| 25/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24441 output: 36 thoughts: 27 Thoughts: Here's a summary of my thinking, presented as if it were my thought process: **Diving Deeper into the Network Core** Okay, so we're talking about network device drivers, and I want to dig a little deeper. I'm going to shift my focus and start examining `net/core/dev.c`. Specifically, I think we should zero in on the lines around 4165 to 4210. I suspect there's something interesting happening in that block of code – perhaps related to [mention a specific function or mechanism, if you have one in mind based on the context of your field]. Let's see what we can find. I anticipate that this section will provide some vital insight. |
| 26/2 | 2026/09/20 10:56 | tool | read-file |
0mArgs:{
"File": "net/core/dev.c",
"FirstLine": 4165,
"LineCount": 50
}Results: {
"Contents": "4165:\t\t\tgoto out_null;\n4166:\t\n4167:\t\tskb = sk_validate_xmit_skb(skb, dev);\n4168:\t\tif (unlikely(!skb))\n4169:\t\t\tgoto out_null;\n4170:\t\n4171:\t\t/*\n4172:\t\t * An oversized skb loses its GSO feature bits and is segmented\n4173:\t\t * down to MSS sized skbs below. A TCP skb can instead be split\n4174:\t\t * into GSO skbs which do fit the device, so keep the bits and\n4175:\t\t * bound the resegmentation. The features computed without the\n4176:\t\t * limit checks say whether the device offloads the GSO type at\n4177:\t\t * all.\n4178:\t\t */\n4179:\t\tif (skb_is_gso(skb) \u0026\u0026 skb_is_gso_tcp(skb) \u0026\u0026 !skb-\u003eencapsulation \u0026\u0026\n4180:\t\t !gso_within_device_limits(skb, dev)) {\n4181:\t\t\tnetdev_features_t offload = __netif_skb_features(skb, false);\n4182:\t\n4183:\t\t\tresegment_max_segs =\n4184:\t\t\t\tskb_gso_resegment_max_segs(skb, dev, offload);\n4185:\t\t\tif (resegment_max_segs)\n4186:\t\t\t\tfeatures = offload;\n4187:\t\t}\n4188:\t\n4189:\t\tif (resegment_max_segs || netif_needs_gso(skb, features)) {\n4190:\t\t\tstruct sk_buff *segs;\n4191:\t\n4192:\t\t\tsegs = __skb_gso_segment(skb, features, true,\n4193:\t\t\t\t\t\t resegment_max_segs);\n4194:\t\t\tif (IS_ERR(segs)) {\n4195:\t\t\t\tgoto out_kfree_skb;\n4196:\t\t\t} else if (segs) {\n4197:\t\t\t\tconsume_skb(skb);\n4198:\t\t\t\tskb = segs;\n4199:\t\t\t}\n4200:\t\t} else {\n4201:\t\t\tif (skb_needs_linearize(skb, features) \u0026\u0026\n4202:\t\t\t __skb_linearize(skb))\n4203:\t\t\t\tgoto out_kfree_skb;\n4204:\t\n4205:\t\t\t/* If packet is not checksummed and device does not\n4206:\t\t\t * support checksumming for this protocol, complete\n4207:\t\t\t * checksumming here.\n4208:\t\t\t */\n4209:\t\t\tif (skb-\u003eip_summed == CHECKSUM_PARTIAL) {\n4210:\t\t\t\tif (skb-\u003eencapsulation)\n4211:\t\t\t\t\tskb_set_inner_transport_header(skb,\n4212:\t\t\t\t\t\t\t\t skb_checksum_start_offset(skb));\n4213:\t\t\t\telse\n4214:\t\t\t\t\tskb_set_transport_header(skb,\n"
} |
| 27/2 | 2026/09/20 10:56 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25495 output: 28 thoughts: 1643 Thoughts: **Focus Symbol and Configuration Analysis for Network GSO Re-segmentation** Okay, let's break this down. My goal here is to identify the critical kernel functions related to this bounded TCP GSO re-segmentation patch and ensure I've selected the right focus symbols for targeted fuzzing. I need to be precise about what I include and exclude. First, I'm checking the definition of `validate_xmit_skb` within `net/core/dev.c`. It's `static`, so the compiler *could* inline it, but it's called from both `__dev_queue_xmit` and `validate_xmit_skb_list`, meaning it's reasonably complex, it might get its own symbol or be inlined. I can include both `validate_xmit_skb` and `validate_xmit_skb_list`, or even `skb_gso_resegment_max_segs`. I'm also confirming that other relevant functions are exported, such as `__skb_gso_segment`, `skb_segment`, `tcp_gso_segment`, and `netif_skb_features`, which seem promising. Then I am reviewing rule 4: "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)." I'm zeroing in on functions that directly implement or interact with the bounded re-segmentation logic. Specifically: `__skb_gso_segment` and `skb_segment` are critical as they are the core functions with significant modifications to handle segmentation limits and the `SKB_GSO_CB` struct. `tcp_gso_segment` is also important, as it now checks `!SKB_GSO_CB(skb)->max_segs`. `validate_xmit_skb` is a core function that makes re-segmentation decisions and invokes `__skb_gso_segment`. `skb_gso_resegment_max_segs` is a new helper function to calculate the segmentation limit. `netif_skb_features` is adjusted to call `__netif_skb_features`. I need to confirm if I should list functions in other files as well, so I look for `tap_handle_frame` in `drivers/net/tap.c` and `queue_gso_packets` in `net/openvswitch/datapath.c`. These appear to have some involvement. I do note that the patch only updated the call site for `__skb_gso_segment` in `tap_handle_frame` and `queue_gso_packets`, plus `udp_rcv_segment`, so they are indeed central to this feature. Now, I'm analyzing the function's relevance. `validate_xmit_skb` and `__skb_gso_segment`, `skb_segment`, `tcp_gso_segment`, `skb_gso_resegment_max_segs` and others are all specific to GSO packet handling and segmentation which is not something that gets triggered in every part of the kernel. Therefore these will be great FocusSymbols. Finally, I'm checking the Kconfig options. I need to make sure the fuzzing configuration enables all necessary components. `NET` and `INET` are standard, but I want to ensure there aren't any hidden dependencies. I see that `vlan_get_protocol(skb)` is used. Now, I should make sure the right VLAN options are enabled, as well. |
| 28/2 | 2026/09/20 10:56 | tool | grepper |
0mArgs:{
"Expression": "vlan_get_protocol",
"PathPrefix": "include/"
}Results: {
"Output": "include/linux/if_vlan.h=604=struct vlan_type_depth {\n--\ninclude/linux/if_vlan.h-608-\ninclude/linux/if_vlan.h:609:struct vlan_type_depth __vlan_get_protocol_offset(const struct sk_buff *skb,\ninclude/linux/if_vlan.h-610-\t\t\t\t\t\t __be16 type,\n--\ninclude/linux/if_vlan.h-613-/**\ninclude/linux/if_vlan.h:614: * vlan_get_protocol_offset_inline() - get protocol EtherType.\ninclude/linux/if_vlan.h-615- * @skb: skbuff to query\n--\ninclude/linux/if_vlan.h=623=static inline\ninclude/linux/if_vlan.h:624:__be16 vlan_get_protocol_offset_inline(const struct sk_buff *skb,\ninclude/linux/if_vlan.h-625-\t\t\t\t __be16 type,\n--\ninclude/linux/if_vlan.h-631-\ninclude/linux/if_vlan.h:632:\t\tres = __vlan_get_protocol_offset(skb, type, mac_offset);\ninclude/linux/if_vlan.h-633-\n--\ninclude/linux/if_vlan.h-644-\ninclude/linux/if_vlan.h:645:static inline __be16 __vlan_get_protocol(const struct sk_buff *skb, __be16 type,\ninclude/linux/if_vlan.h-646-\t\t\t\t\t int *depth)\ninclude/linux/if_vlan.h-647-{\ninclude/linux/if_vlan.h:648:\treturn vlan_get_protocol_offset_inline(skb, type, 0, depth);\ninclude/linux/if_vlan.h-649-}\n--\ninclude/linux/if_vlan.h-651-/**\ninclude/linux/if_vlan.h:652: * vlan_get_protocol - get protocol EtherType.\ninclude/linux/if_vlan.h-653- * @skb: skbuff to query\n--\ninclude/linux/if_vlan.h-657- */\ninclude/linux/if_vlan.h:658:static inline __be16 vlan_get_protocol(const struct sk_buff *skb)\ninclude/linux/if_vlan.h-659-{\ninclude/linux/if_vlan.h:660:\treturn __vlan_get_protocol(skb, skb-\u003eprotocol, NULL);\ninclude/linux/if_vlan.h-661-}\ninclude/linux/if_vlan.h-662-\ninclude/linux/if_vlan.h:663:/* This version of __vlan_get_protocol() also pulls mac header in skb-\u003ehead */\ninclude/linux/if_vlan.h:664:static inline __be16 vlan_get_protocol_and_depth(struct sk_buff *skb,\ninclude/linux/if_vlan.h-665-\t\t\t\t\t\t __be16 type, int *depth)\n--\ninclude/linux/if_vlan.h-668-\ninclude/linux/if_vlan.h:669:\ttype = __vlan_get_protocol(skb, type, \u0026maclen);\ninclude/linux/if_vlan.h-670-\n--\ninclude/linux/if_vlan.h=683=static inline __be16 skb_protocol(const struct sk_buff *skb, bool skip_vlan)\n--\ninclude/linux/if_vlan.h-690-\ninclude/linux/if_vlan.h:691:\treturn vlan_get_protocol(skb);\ninclude/linux/if_vlan.h-692-}\n--\ninclude/net/ip_tunnels.h=494=skb_vlan_inet_prepare(struct sk_buff *skb, bool inner_proto_inherit)\n--\ninclude/net/ip_tunnels.h-503-\tif (eth_type_vlan(type))\ninclude/net/ip_tunnels.h:504:\t\ttype = __vlan_get_protocol(skb, type, \u0026maclen);\ninclude/net/ip_tunnels.h-505-\n--\ninclude/net/vxlan.h=362=static inline netdev_features_t vxlan_features_check(struct sk_buff *skb,\n--\ninclude/net/vxlan.h-369-\ninclude/net/vxlan.h:370:\tswitch (vlan_get_protocol(skb)) {\ninclude/net/vxlan.h-371-\tcase htons(ETH_P_IP):\n"
} |
| 29/2 | 2026/09/20 10:57 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 29412 output: 170 thoughts: 650 Thoughts: **My Analysis of This Patch's Fuzzing Suitability** Okay, let's break this down. First, I need to understand the scope and dependencies. `vlan_get_protocol` is a constant, so that's not a concern. `net/core/net_test.c` is a KUnit test, meaning it's unlikely to be critical for *syzkaller* testing of the main kernel code changes, though, KUnit changes still warrant investigation. I need to make sure the core parts are covered. I checked for dependencies on `tap` (CONFIG_TAP) and `openvswitch` (CONFIG_OPENVSWITCH), but those aren't required. The crucial areas are the core networking code: `net/core/dev.c`, `net/core/skbuff.c`, and `net/ipv4/tcp_offload.c`. These are fundamental and are active for any standard network interface: loopback, veth, dummy, macvlan, etc. Therefore, `CONFIG_NET` and `CONFIG_INET` are likely sufficient for coverage. Now, a sanity check: Is this patch worth fuzzing? Absolutely! It's focused on the core networking changes in packet transmission and segmentation, precisely the areas I'm interested in! This patch introduces bounded TCP GSO re-segmentation and touches key functions: `validate_xmit_skb`, `__skb_gso_segment`, `skb_segment`, and `tcp_gso_segment`. The `FocusSymbols` are spot-on. I see: * `skb_gso_resegment_max_segs` * `validate_xmit_skb` * `__skb_gso_segment` * `skb_segment` * `tcp_gso_segment` These functions are key to the functionality, it's where the most complex logic resides, and where fuzzing will have the most impact. Let's summarize. The patch implements bounded TCP GSO re-segmentation to prevent complete fallback to MSS packets when an SKB exceeds device limits. The impact is broad because it touches core networking transmit and segmentation code. The value is high because the patch involves complex arithmetic and packet-slicing logic. Malformed or edge-case packets could easily corrupt data, cause out-of-bounds access, or lead to assertions. This is precisely what fuzzing is designed to uncover. Now, let's call `set-results`. |
| 30/2 | 2026/09/20 10:57 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"NET",
"INET"
],
"FocusSymbols": [
"__skb_gso_segment",
"skb_segment",
"tcp_gso_segment",
"validate_xmit_skb",
"skb_gso_resegment_max_segs"
],
"Reasoning": "The patch introduces bounded TCP GSO re-segmentation when an skb exceeds a net device's GSO limits, modifying core network transmit and GSO segmentation paths (dev.c, skbuff.c, gso.c, tcp_offload.c). This touches reachable core networking code with packet boundary and size calculations that can trigger edge cases or assertion failures during packet transmission.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"NET",
"INET"
],
"FocusSymbols": [
"__skb_gso_segment",
"skb_segment",
"tcp_gso_segment",
"validate_xmit_skb",
"skb_gso_resegment_max_segs"
],
"Reasoning": "The patch introduces bounded TCP GSO re-segmentation when an skb exceeds a net device's GSO limits, modifying core network transmit and GSO segmentation paths (dev.c, skbuff.c, gso.c, tcp_offload.c). This touches reachable core networking code with packet boundary and size calculations that can trigger edge cases or assertion failures during packet transmission.",
"WorthFuzzing": true
} |
| 31/1 | 2026/09/20 10:57 | action | If |
2mArgs:{
"WorthFuzzing": true
}Results: null |
| 32/2 | 2026/09/20 10:57 | agent | kmsan-evaluator |
2mModel:core Args: null Results: {
"KMSANReasoning": "The patch series adds bounded GSO resegmentation for oversized TCP skbs in the networking core transmit path (net/core/dev.c, net/core/gso.c, net/core/skbuff.c, etc.).\n\nKey considerations regarding memory initialization and sanitizers:\n1. The new field `max_segs` in `struct skb_gso_cb` occupies what was previously 2 bytes of padding within `skb-\u003ecb`. It is explicitly initialized to `min_t(unsigned int, max_segs, U16_MAX)` in `__skb_gso_segment()` and reset to 0 in newly created segment skbs in `skb_segment()`.\n2. All local variables touched or introduced (e.g., `resegment_max_segs = 0` in `validate_xmit_skb()`, stack buffer `_tcph` passed to `skb_header_pointer()`) are either initialized to 0 or populated before use.\n3. No data structures or padding bytes are exposed or copied to userspace; this logic operates purely inside the kernel's egress packet segmentation and device transmission pipeline.\n4. Any potential risks introduced by parsing TCP headers or splitting skb payloads into segments involve packet bounds and memory offsets, which are out-of-bounds or use-after-free concerns effectively monitored by standard KASAN and packet sanity checks rather than KMSAN uninitialized memory tracking.\n\nConsequently, there is no risk of uninitialized memory reads or information leaks justifying a dedicated KMSAN session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 4189b25bcaeab773bedba178dc172b222434eb58
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 20 10:55:51 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/tap.c b/drivers/net/tap.c
index ff67d99deb39e..bc111495ebbce 100644
--- a/drivers/net/tap.c
+++ b/drivers/net/tap.c
@@ -278,9 +278,10 @@ rx_handler_result_t tap_handle_frame(struct sk_buff **pskb)
if (q->flags & IFF_VNET_HDR)
features |= tap->tap_features;
if (netif_needs_gso(skb, features)) {
- struct sk_buff *segs = __skb_gso_segment(skb, features, false);
+ struct sk_buff *segs;
struct sk_buff *next;
+ segs = __skb_gso_segment(skb, features, false, 0);
if (IS_ERR(segs)) {
drop_reason = SKB_DROP_REASON_SKB_GSO_SEG;
goto drop;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 1f0710eef185b..427d0d5b94e49 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -5570,10 +5570,10 @@ netif_get_gro_max_size(const struct net_device *dev, const struct sk_buff *skb)
}
static inline unsigned int
-netif_get_gso_max_size(const struct net_device *dev, const struct sk_buff *skb)
+netif_get_gso_max_size(const struct net_device *dev, __be16 protocol)
{
/* pairs with WRITE_ONCE() in netif_set_gso(_ipv4)_max_size() */
- return skb->protocol == htons(ETH_P_IPV6) ?
+ return protocol == htons(ETH_P_IPV6) ?
READ_ONCE(dev->gso_max_size) :
READ_ONCE(dev->gso_ipv4_max_size);
}
diff --git a/include/net/gso.h b/include/net/gso.h
index 29975440cad51..fccb37889965f 100644
--- a/include/net/gso.h
+++ b/include/net/gso.h
@@ -19,6 +19,7 @@ struct skb_gso_cb {
int encap_level;
__wsum csum;
__u16 csum_start;
+ __u16 max_segs; /* Max MSS segs per output skb, 0 = no limit */
};
#define SKB_GSO_CB_OFFSET 32
#define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET))
@@ -75,12 +76,13 @@ static inline __sum16 gso_make_checksum(struct sk_buff *skb, __wsum res)
}
struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
- netdev_features_t features, bool tx_path);
+ netdev_features_t features, bool tx_path,
+ unsigned int max_segs);
static inline struct sk_buff *skb_gso_segment(struct sk_buff *skb,
netdev_features_t features)
{
- return __skb_gso_segment(skb, features, true);
+ return __skb_gso_segment(skb, features, true, 0);
}
struct sk_buff *skb_eth_gso_segment(struct sk_buff *skb,
diff --git a/include/net/udp.h b/include/net/udp.h
index 1fee17274745f..5bc25dcf25fba 100644
--- a/include/net/udp.h
+++ b/include/net/udp.h
@@ -613,7 +613,7 @@ static inline struct sk_buff *udp_rcv_segment(struct sock *sk,
/* the GSO CB lays after the UDP one, no need to save and restore any
* CB fragment
*/
- segs = __skb_gso_segment(skb, features, false);
+ segs = __skb_gso_segment(skb, features, false, 0);
if (IS_ERR_OR_NULL(segs)) {
drop_count = skb_shinfo(skb)->gso_segs;
goto drop;
diff --git a/net/core/dev.c b/net/core/dev.c
index c67900354fa64..548db4d4e874c 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3834,16 +3834,24 @@ static bool skb_gso_has_extension_hdr(const struct sk_buff *skb)
skb_inner_network_header_len(skb) != sizeof(struct ipv6hdr)));
}
+/*
+ * Does @skb fit the GSO limits of @dev? The size limit depends on the L3
+ * protocol, which validate_xmit_vlan() replaces with the VLAN ethertype when
+ * it pushes the tag inside the skb, so look behind the tag.
+ */
+static bool gso_within_device_limits(const struct sk_buff *skb,
+ const struct net_device *dev)
+{
+ return skb_shinfo(skb)->gso_segs <= READ_ONCE(dev->gso_max_segs) &&
+ skb->len < netif_get_gso_max_size(dev, vlan_get_protocol(skb));
+}
+
static netdev_features_t gso_features_check(const struct sk_buff *skb,
struct net_device *dev,
- netdev_features_t features)
+ netdev_features_t features,
+ bool check_limits)
{
- u16 gso_segs = skb_shinfo(skb)->gso_segs;
-
- if (gso_segs > READ_ONCE(dev->gso_max_segs))
- return features & ~NETIF_F_GSO_MASK;
-
- if (unlikely(skb->len >= netif_get_gso_max_size(dev, skb)))
+ if (check_limits && !gso_within_device_limits(skb, dev))
return features & ~NETIF_F_GSO_MASK;
if (!skb_shinfo(skb)->gso_type) {
@@ -3892,13 +3900,15 @@ static netdev_features_t gso_features_check(const struct sk_buff *skb,
return features;
}
-netdev_features_t netif_skb_features(struct sk_buff *skb)
+static netdev_features_t __netif_skb_features(struct sk_buff *skb,
+ bool check_gso_limits)
{
struct net_device *dev = skb->dev;
netdev_features_t features = dev->features;
if (skb_is_gso(skb))
- features = gso_features_check(skb, dev, features);
+ features = gso_features_check(skb, dev, features,
+ check_gso_limits);
/* If encapsulation offload request, verify we are testing
* hardware encapsulation features instead of standard
@@ -3921,8 +3931,79 @@ netdev_features_t netif_skb_features(struct sk_buff *skb)
return harmonize_features(skb, features);
}
+
+netdev_features_t netif_skb_features(struct sk_buff *skb)
+{
+ return __netif_skb_features(skb, true);
+}
EXPORT_SYMBOL(netif_skb_features);
+static bool skb_can_gso_resegment(struct sk_buff *skb,
+ netdev_features_t features)
+{
+ __be16 protocol;
+
+ if (!net_gso_ok(features | NETIF_F_GSO_ROBUST,
+ skb_shinfo(skb)->gso_type))
+ return false;
+
+ if (!(features & NETIF_F_SG))
+ return false;
+
+ protocol = skb_network_protocol(skb, NULL);
+ if (!protocol || !can_checksum_protocol(features, protocol))
+ return false;
+
+ /*
+ * The TCP frag-list path does not carry the bounded segment
+ * limit through skb_segment_list(). Keep bounded resegmentation
+ * on the regular skb path until that support is added.
+ */
+ if (skb_has_frag_list(skb))
+ return false;
+
+ return true;
+}
+
+static unsigned int
+skb_gso_resegment_max_segs(struct sk_buff *skb, struct net_device *dev,
+ netdev_features_t features)
+{
+ unsigned int mss = skb_shinfo(skb)->gso_size;
+ unsigned int hdr_len, max_segs;
+ unsigned int gso_max_size;
+ struct tcphdr _tcph, *th;
+
+ gso_max_size = netif_get_gso_max_size(dev, vlan_get_protocol(skb));
+
+ if (!skb_is_gso(skb) || !skb_is_gso_tcp(skb) ||
+ skb->encapsulation || mss == GSO_BY_FRAGS ||
+ !skb_mac_header_was_set(skb) ||
+ !skb_transport_header_was_set(skb) ||
+ !skb_can_gso_resegment(skb, features))
+ return 0;
+
+ th = skb_header_pointer(skb, skb_transport_offset(skb), sizeof(_tcph),
+ &_tcph);
+ if (!th || th->doff < sizeof(*th) / 4)
+ return 0;
+
+ hdr_len = skb_transport_header(skb) - skb_mac_header(skb) +
+ th->doff * 4;
+ if (gso_max_size <= hdr_len + mss)
+ return 0;
+
+ /*
+ * gso_within_device_limits() accepts gso_segs == gso_max_segs but
+ * rejects skb->len >= gso_max_size, so only the size bound needs - 1.
+ */
+ max_segs = (gso_max_size - hdr_len - 1) / mss;
+ max_segs = min_t(unsigned int, max_segs,
+ READ_ONCE(dev->gso_max_segs));
+
+ return max_segs > 1 ? max_segs : 0;
+}
+
static int xmit_one(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
@@ -4071,6 +4152,7 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
*/
static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
{
+ unsigned int resegment_max_segs = 0;
netdev_features_t features;
skb = validate_xmit_unreadable_skb(skb, dev);
@@ -4086,10 +4168,29 @@ static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device
if (unlikely(!skb))
goto out_null;
- if (netif_needs_gso(skb, features)) {
+ /*
+ * An oversized skb loses its GSO feature bits and is segmented
+ * down to MSS sized skbs below. A TCP skb can instead be split
+ * into GSO skbs which do fit the device, so keep the bits and
+ * bound the resegmentation. The features computed without the
+ * limit checks say whether the device offloads the GSO type at
+ * all.
+ */
+ if (skb_is_gso(skb) && skb_is_gso_tcp(skb) && !skb->encapsulation &&
+ !gso_within_device_limits(skb, dev)) {
+ netdev_features_t offload = __netif_skb_features(skb, false);
+
+ resegment_max_segs =
+ skb_gso_resegment_max_segs(skb, dev, offload);
+ if (resegment_max_segs)
+ features = offload;
+ }
+
+ if (resegment_max_segs || netif_needs_gso(skb, features)) {
struct sk_buff *segs;
- segs = skb_gso_segment(skb, features);
+ segs = __skb_gso_segment(skb, features, true,
+ resegment_max_segs);
if (IS_ERR(segs)) {
goto out_kfree_skb;
} else if (segs) {
diff --git a/net/core/gso.c b/net/core/gso.c
index bcd156372f4df..157f2bfdca128 100644
--- a/net/core/gso.c
+++ b/net/core/gso.c
@@ -77,6 +77,7 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)
* @skb: buffer to segment
* @features: features for the output path (see dev->features)
* @tx_path: whether it is called in TX path
+ * @max_segs: maximum MSS segments per output GSO skb, 0 means no limit
*
* This function segments the given skb and returns a list of segments.
*
@@ -86,7 +87,8 @@ static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)
* Segmentation preserves SKB_GSO_CB_OFFSET bytes of previous skb cb.
*/
struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
- netdev_features_t features, bool tx_path)
+ netdev_features_t features, bool tx_path,
+ unsigned int max_segs)
{
struct sk_buff *segs;
@@ -117,6 +119,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
SKB_GSO_CB(skb)->mac_offset = skb_headroom(skb);
SKB_GSO_CB(skb)->encap_level = 0;
+ SKB_GSO_CB(skb)->max_segs = min_t(unsigned int, max_segs, U16_MAX);
skb_reset_mac_header(skb);
skb_reset_mac_len(skb);
diff --git a/net/core/net_test.c b/net/core/net_test.c
index 9c3a590865d26..6ca0cbe3a3653 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -4,7 +4,14 @@
/* GSO */
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/netdevice.h>
#include <linux/skbuff.h>
+#include <linux/tcp.h>
+#include <net/gso.h>
static const char hdr[] = "abcdefgh";
#define GSO_TEST_SIZE 1000
@@ -34,6 +41,9 @@ enum gso_test_nr {
GSO_TEST_FRAG_LIST_PURE,
GSO_TEST_FRAG_LIST_NON_UNIFORM,
GSO_TEST_GSO_BY_FRAGS,
+ GSO_TEST_BOUNDED,
+ GSO_TEST_BOUNDED_MULTI,
+ GSO_TEST_BOUNDED_ONE_MSS,
};
struct gso_test_case {
@@ -46,10 +56,12 @@ struct gso_test_case {
const unsigned int *frags;
unsigned int nr_frag_skbs;
const unsigned int *frag_skbs;
+ unsigned int max_segs;
/* output as expected */
unsigned int nr_segs;
const unsigned int *segs;
+ bool segs_are_gso;
};
static struct gso_test_case cases[] = {
@@ -135,6 +147,54 @@ static struct gso_test_case cases[] = {
.nr_segs = 4,
.segs = (const unsigned int[]) { 100, 200, 300, 400 },
},
+ {
+ .id = GSO_TEST_BOUNDED,
+ .name = "bounded",
+ .linear_len = GSO_TEST_SIZE,
+ .nr_frags = 3,
+ .frags = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ .max_segs = 2,
+ .nr_segs = 2,
+ .segs = (const unsigned int[]) {
+ 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
+ },
+ .segs_are_gso = true,
+ },
+ {
+ .id = GSO_TEST_BOUNDED_MULTI,
+ .name = "bounded_multi",
+ .linear_len = 2 * GSO_TEST_SIZE,
+ .nr_frags = 4,
+ .frags = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ .max_segs = 2,
+ .nr_segs = 3,
+ .segs = (const unsigned int[]) {
+ 2 * GSO_TEST_SIZE, 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
+ },
+ .segs_are_gso = true,
+ },
+ {
+ /*
+ * One MSS per skb is what the unbounded path produces, so a
+ * bound of a single segment must not change the output.
+ */
+ .id = GSO_TEST_BOUNDED_ONE_MSS,
+ .name = "bounded_one_mss",
+ .linear_len = GSO_TEST_SIZE,
+ .nr_frags = 3,
+ .frags = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ .max_segs = 1,
+ .nr_segs = 4,
+ .segs = (const unsigned int[]) {
+ GSO_TEST_SIZE, GSO_TEST_SIZE, GSO_TEST_SIZE, 3,
+ },
+ },
};
static void gso_test_case_to_desc(struct gso_test_case *t, char *desc)
@@ -226,6 +286,7 @@ static void gso_test_func(struct kunit *test)
if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM)
features &= ~NETIF_F_SG;
+ SKB_GSO_CB(skb)->max_segs = tcase->max_segs;
segs = skb_segment(skb, features);
if (IS_ERR(segs)) {
KUNIT_FAIL(test, "segs error %pe", segs);
@@ -247,6 +308,15 @@ static void gso_test_func(struct kunit *test)
/* header was copied to all segs */
KUNIT_ASSERT_EQ(test, memcmp(skb_mac_header(cur), hdr, sizeof(hdr)), 0);
+ if (tcase->segs_are_gso) {
+ KUNIT_EXPECT_TRUE(test, skb_is_gso(cur));
+ KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_size,
+ GSO_TEST_SIZE);
+ KUNIT_EXPECT_LE(test, skb_shinfo(cur)->gso_segs,
+ tcase->max_segs);
+ KUNIT_EXPECT_FALSE(test, skb_shinfo(cur)->gso_type &
+ SKB_GSO_PARTIAL);
+ }
/* last seg can be found through segs->prev pointer */
if (!next)
@@ -261,6 +331,177 @@ static void gso_test_func(struct kunit *test)
consume_skb(skb);
}
+#define GSO_TCP_HDR_LEN \
+ (ETH_HLEN + sizeof(struct iphdr) + sizeof(struct tcphdr))
+
+static struct sk_buff *gso_tcp_skb_new(unsigned int payload_len)
+{
+ struct sk_buff *skb;
+ struct ethhdr *eth;
+ struct tcphdr *th;
+ struct iphdr *iph;
+
+ skb = alloc_skb(GSO_TCP_HDR_LEN + payload_len, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+ skb_put_zero(skb, GSO_TCP_HDR_LEN + payload_len);
+
+ skb_reset_mac_header(skb);
+ eth = eth_hdr(skb);
+ eth->h_proto = htons(ETH_P_IP);
+ skb->protocol = eth->h_proto;
+
+ skb_set_network_header(skb, ETH_HLEN);
+ iph = ip_hdr(skb);
+ iph->version = 4;
+ iph->ihl = sizeof(*iph) / 4;
+ iph->protocol = IPPROTO_TCP;
+ iph->tot_len = htons(sizeof(*iph) + sizeof(*th) + payload_len);
+
+ skb_set_transport_header(skb, ETH_HLEN + sizeof(*iph));
+ th = tcp_hdr(skb);
+ th->doff = sizeof(*th) / 4;
+
+ skb->ip_summed = CHECKSUM_PARTIAL;
+ skb->csum_start = skb_transport_header(skb) - skb->head;
+ skb->csum_offset = offsetof(struct tcphdr, check);
+ skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
+ skb_shinfo(skb)->gso_size = GSO_TEST_SIZE;
+ skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);
+
+ return skb;
+}
+
+static void gso_test_tcp_bounded_segment(struct kunit *test)
+{
+ netdev_features_t features = NETIF_F_SG | NETIF_F_HW_CSUM |
+ NETIF_F_TSO;
+ const unsigned int payload_len = 3 * GSO_TEST_SIZE + 3;
+ struct sk_buff *skb, *segs, *cur, *next;
+ const unsigned int expected[] = {
+ 2 * GSO_TEST_SIZE, GSO_TEST_SIZE + 3,
+ };
+ const unsigned int max_segs = 2;
+ int i = 0;
+
+ skb = gso_tcp_skb_new(payload_len);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+
+ segs = __skb_gso_segment(skb, features, true, max_segs);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, segs);
+
+ for (cur = segs; cur; cur = next, i++) {
+ next = cur->next;
+
+ KUNIT_ASSERT_LT(test, i, ARRAY_SIZE(expected));
+ KUNIT_EXPECT_EQ(test, cur->len,
+ GSO_TCP_HDR_LEN + expected[i]);
+ KUNIT_EXPECT_TRUE(test, skb_is_gso(cur));
+ KUNIT_EXPECT_EQ(test, skb_shinfo(cur)->gso_size,
+ GSO_TEST_SIZE);
+ KUNIT_EXPECT_LE(test, skb_shinfo(cur)->gso_segs, max_segs);
+
+ consume_skb(cur);
+ }
+
+ KUNIT_EXPECT_EQ(test, i, ARRAY_SIZE(expected));
+ consume_skb(skb);
+}
+
+#define GSO_TCP6_HDR_LEN \
+ (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
+
+static struct sk_buff *gso_tcp6_skb_new(unsigned int payload_len)
+{
+ struct ipv6hdr *ip6h;
+ struct sk_buff *skb;
+ struct ethhdr *eth;
+ struct tcphdr *th;
+
+ skb = alloc_skb(GSO_TCP6_HDR_LEN + payload_len, GFP_KERNEL);
+ if (!skb)
+ return NULL;
+ skb_put_zero(skb, GSO_TCP6_HDR_LEN + payload_len);
+
+ skb_reset_mac_header(skb);
+ eth = eth_hdr(skb);
+ eth->h_proto = htons(ETH_P_IPV6);
+ skb->protocol = eth->h_proto;
+
+ skb_set_network_header(skb, ETH_HLEN);
+ ip6h = ipv6_hdr(skb);
+ ip6h->version = 6;
+ ip6h->nexthdr = IPPROTO_TCP;
+ ip6h->payload_len = htons(sizeof(*th) + payload_len);
+
+ skb_set_transport_header(skb, ETH_HLEN + sizeof(*ip6h));
+ th = tcp_hdr(skb);
+ th->doff = sizeof(*th) / 4;
+
+ skb->ip_summed = CHECKSUM_PARTIAL;
+ skb->csum_start = skb_transport_header(skb) - skb->head;
+ skb->csum_offset = offsetof(struct tcphdr, check);
+ skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
+ skb_shinfo(skb)->gso_size = GSO_TEST_SIZE;
+ skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(payload_len, GSO_TEST_SIZE);
+
+ return skb;
+}
+
+/* The device GSO size limit is per L3 protocol, and it has to survive the
+ * VLAN tag which validate_xmit_vlan() can push inside the skb, because that
+ * tag replaces skb->protocol with the VLAN ethertype.
+ */
+static void gso_test_tcp_limit_l3_proto(struct kunit *test)
+{
+ static const struct net_device_ops dummy_netdev_ops = { };
+ const unsigned int payload_len = 100 * 1024;
+ netdev_features_t features;
+ struct net_device *dev;
+ struct sk_buff *skb;
+
+ dev = alloc_etherdev(0);
+ KUNIT_ASSERT_NOT_NULL(test, dev);
+ dev->netdev_ops = &dummy_netdev_ops;
+ dev->hw_features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO6;
+ dev->features = dev->hw_features;
+ dev->vlan_features = dev->hw_features;
+
+ skb = gso_tcp6_skb_new(payload_len);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb->dev = dev;
+
+ /* The skb fits the IPv6 limit but not the IPv4 one. */
+ dev->gso_max_size = GSO_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_LEGACY_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_TRUE(test, features & NETIF_F_GSO_MASK);
+
+ /* ...and the other way around. */
+ dev->gso_max_size = GSO_LEGACY_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_FALSE(test, features & NETIF_F_GSO_MASK);
+
+ /* Pushing the tag inside must not change either answer. */
+ skb = vlan_insert_tag_set_proto(skb, htons(ETH_P_8021Q), 0);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ KUNIT_ASSERT_TRUE(test, skb->protocol == htons(ETH_P_8021Q));
+
+ dev->gso_max_size = GSO_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_LEGACY_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_TRUE(test, features & NETIF_F_GSO_MASK);
+
+ dev->gso_max_size = GSO_LEGACY_MAX_SIZE;
+ dev->gso_ipv4_max_size = GSO_MAX_SIZE;
+ features = netif_skb_features(skb);
+ KUNIT_EXPECT_FALSE(test, features & NETIF_F_GSO_MASK);
+
+ consume_skb(skb);
+ free_netdev(dev);
+}
+
/* IP tunnel flags */
#include <net/ip_tunnels.h>
@@ -372,6 +613,8 @@ static void ip_tunnel_flags_test_run(struct kunit *test)
static struct kunit_case net_test_cases[] = {
KUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),
+ KUNIT_CASE(gso_test_tcp_bounded_segment),
+ KUNIT_CASE(gso_test_tcp_limit_l3_proto),
KUNIT_CASE_PARAM(ip_tunnel_flags_test_run,
ip_tunnel_flags_test_gen_params),
{ },
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index dbbe10277d51d..9c0d140236bc6 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -4793,6 +4793,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
struct sk_buff *segs = NULL;
struct sk_buff *tail = NULL;
struct sk_buff *list_skb = skb_shinfo(head_skb)->frag_list;
+ unsigned int max_segs = SKB_GSO_CB(head_skb)->max_segs;
unsigned int mss = skb_shinfo(head_skb)->gso_size;
bool gso_by_frags = mss == GSO_BY_FRAGS;
unsigned int doffset = head_skb->data - skb_mac_header(head_skb);
@@ -4839,7 +4840,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
csum = !!can_checksum_protocol(features, proto);
if (sg && csum && !gso_by_frags) {
- if (!(features & NETIF_F_GSO_PARTIAL)) {
+ if (!max_segs && !(features & NETIF_F_GSO_PARTIAL)) {
struct sk_buff *iter;
unsigned int frag_len;
@@ -4874,7 +4875,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
* now.
*/
DEBUG_NET_WARN_ON_ONCE(len / mss > GSO_MAX_SEGS);
- partial_segs = min(len / mss, GSO_MAX_SEGS);
+ if (max_segs)
+ partial_segs = min(len / mss, max_segs);
+ else
+ partial_segs = min(len / mss, GSO_MAX_SEGS);
if (partial_segs > 1)
mss *= partial_segs;
else
@@ -4975,6 +4979,12 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
__copy_skb_header(nskb, head_skb);
+ /*
+ * max_segs is a per-call limit, so output skbs must not
+ * inherit it from the input skb.
+ */
+ SKB_GSO_CB(nskb)->max_segs = 0;
+
skb_headers_offset_update(nskb, skb_headroom(nskb) - headroom);
skb_reset_mac_len(nskb);
diff --git a/net/ipv4/tcp_offload.c b/net/ipv4/tcp_offload.c
index e74d99ca9face..a4076318c5352 100644
--- a/net/ipv4/tcp_offload.c
+++ b/net/ipv4/tcp_offload.c
@@ -164,7 +164,8 @@ struct sk_buff *tcp_gso_segment(struct sk_buff *skb,
if (unlikely(skb->len <= mss))
goto out;
- if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
+ if (!SKB_GSO_CB(skb)->max_segs &&
+ skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
/* Packet is from an untrusted source, reset gso_segs. */
skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index 2187034143255..e793aead68372 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -375,7 +375,7 @@ static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,
int err;
BUILD_BUG_ON(sizeof(*OVS_CB(skb)) > SKB_GSO_CB_OFFSET);
- segs = __skb_gso_segment(skb, NETIF_F_SG, false);
+ segs = __skb_gso_segment(skb, NETIF_F_SG, false, 0);
if (IS_ERR(segs))
return PTR_ERR(segs);
if (segs == NULL)
|
| 33/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 11528 output: 36 thoughts: 1580 Thoughts: **KMSAN Fuzzing Session Evaluation** Okay, so I need to decide if this patch series warrants spinning up a dedicated KMSAN fuzzing session. Let's break this down systematically. First, I need to remind myself of the core principles: Standard KASAN builds catch a lot – OOB, UAF, double frees, the usual suspects, plus LOCKDEP and UBSAN. KMSAN, on the other hand, is laser-focused on uninitialized memory reads – stack, heap, and page allocations, and kernel-to-user memory leaks. The golden rule is this: *If KASAN or other standard detectors can catch it, there's no need for KMSAN.* KMSAN is resource-intensive, so I only use it when the changes introduce or expose risks that *only* KMSAN can find. My mental checklist for "NeedsKMSAN=true" involves: 1. Kernel structures sent to userspace with potentially uninitialized fields. 2. Conditional logic or branching dependent on potentially uninitialized variables. 3. Allocation or initialization of complex data structures where uninitialized fields could be read later. 4. Bounds checks, lengths, or logic allowing access to uninitialized bytes of existing buffers. Conversely, "NeedsKMSAN=false" applies if: * The risks are primarily OOB, overflow, NULL deref, locking, or UAF issues (covered by KASAN/LOCKDEP). * All stack/heap structures introduced are fully zeroed or initialized before use. * The patch doesn't introduce any risk of uninitialized memory usage or info-leaks. Now, let's dissect these patches. It's a series of changes related to GSO (Generic Segmentation Offload), and it's quite widespread. I see modifications in `drivers/net/tap.c`, `include/linux/netdevice.h`, `include/net/gso.h`, `include/net/udp.h`, `net/core/dev.c`, `net/core/gso.c`, `net/core/net_test.c`, `net/core/skbuff.c`, `net/ipv4/tcp_offload.c`, and `net/openvswitch/datapath.c`. A significant amount of GSO code is touched. Specifically, there's the addition of `__u16 max_segs` to `struct skb_gso_cb`, and changes to `__skb_gso_segment`. I'm starting to home in on `skb_gso_cb` and how it's initialized and used. Looking at `include/net/gso.h` shows me the structure definition. I also see that `SKB_GSO_CB(skb)->max_segs` is being assigned in `__skb_gso_segment`. Hold on. `__skb_gso_segment` *explicitly* initializes `mac_offset`, `encap_level`, and `max_segs`. But what about `csum` and `csum_start`? They are **not** initialized. The fact that the addition of `max_segs` in the `struct skb_gso_cb` and subsequent changes to GSO functions, this raises a red flag. The other fields are untouched. Now, I need to determine where `__skb_gso_segment` is called, and if those uninitialized `csum` and `csum_start` fields could lead to an information leak. I need to grep or read the file to find out where `__skb_gso_segment` is called and how its results are used. This patch series is a prime candidate for a dedicated KMSAN session because it changes the data structures and control flow, which has a good chance of KMSAN exposing uninitialized read bugs. Therefore, *NeedsKMSAN=true*. |
| 34/3 | 2026/09/20 10:57 | tool | read-file |
0mArgs:{
"File": "net/core/gso.c",
"FirstLine": 100,
"LineCount": 40
}Results: {
"Contents": " 100:\t\t\tif (err \u003c 0)\n 101:\t\t\t\treturn ERR_PTR(err);\n 102:\t\t}\n 103:\t\n 104:\t\t/* Only report GSO partial support if it will enable us to\n 105:\t\t * support segmentation on this frame without needing additional\n 106:\t\t * work.\n 107:\t\t */\n 108:\t\tif (features \u0026 NETIF_F_GSO_PARTIAL) {\n 109:\t\t\tnetdev_features_t partial_features = NETIF_F_GSO_ROBUST;\n 110:\t\t\tstruct net_device *dev = skb-\u003edev;\n 111:\t\n 112:\t\t\tpartial_features |= dev-\u003efeatures \u0026 dev-\u003egso_partial_features;\n 113:\t\t\tif (!skb_gso_ok(skb, features | partial_features))\n 114:\t\t\t\tfeatures \u0026= ~NETIF_F_GSO_PARTIAL;\n 115:\t\t}\n 116:\t\n 117:\t\tBUILD_BUG_ON(SKB_GSO_CB_OFFSET +\n 118:\t\t\t sizeof(*SKB_GSO_CB(skb)) \u003e sizeof(skb-\u003ecb));\n 119:\t\n 120:\t\tSKB_GSO_CB(skb)-\u003emac_offset = skb_headroom(skb);\n 121:\t\tSKB_GSO_CB(skb)-\u003eencap_level = 0;\n 122:\t\tSKB_GSO_CB(skb)-\u003emax_segs = min_t(unsigned int, max_segs, U16_MAX);\n 123:\t\n 124:\t\tskb_reset_mac_header(skb);\n 125:\t\tskb_reset_mac_len(skb);\n 126:\t\n 127:\t\tsegs = skb_mac_gso_segment(skb, features);\n 128:\t\n 129:\t\tif (segs != skb \u0026\u0026 unlikely(skb_needs_check(skb, tx_path) \u0026\u0026 !IS_ERR(segs)))\n 130:\t\t\tskb_warn_bad_offload(skb);\n 131:\t\n 132:\t\treturn segs;\n 133:\t}\n 134:\tEXPORT_SYMBOL(__skb_gso_segment);\n 135:\t\n 136:\t/**\n 137:\t * skb_gso_transport_seglen - Return length of individual segments of a gso packet\n 138:\t *\n 139:\t * @skb: GSO skb\n"
} |
| 35/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 14679 output: 19 thoughts: 42 Thoughts: **Investigating the `skb_segment` Caller Context** Okay, hold on a second. Something feels off. My initial understanding was that `skb_segment` was exclusively called within `__skb_gso_segment`. But now I'm starting to wonder if that assumption is actually correct. Could there be other paths, other areas of the kernel that might be utilizing `skb_segment` in a way I'm not immediately thinking of? It's essential to confirm the intended usage to avoid any unexpected side effects or incorrect assumptions in any analysis I'm currently working on. Let's do a quick, thorough search for instances of `skb_segment(` throughout the kernel source code. A comprehensive examination of the call sites will either validate my initial understanding or, perhaps more importantly, illuminate other contexts in which `skb_segment` is being used. This search is a crucial sanity check before moving forward. Let's see what we find. |
| 36/3 | 2026/09/20 10:57 | tool | grepper |
0mArgs:{
"Expression": "skb_segment\\("
}Results: {
"Output": "Documentation/networking/segmentation-offloads.rst=205=Instead, the SCTP protocol layer builds an skb with the segments correctly\nDocumentation/networking/segmentation-offloads.rst:206:padded and stored as chained skbs, and skb_segment() splits based on those.\nDocumentation/networking/segmentation-offloads.rst-207-To signal this, gso_size is set to the special value GSO_BY_FRAGS.\n--\ndrivers/s390/net/qeth_core_main.c=6890=netdev_features_t qeth_features_check(struct sk_buff *skb,\n--\ndrivers/s390/net/qeth_core_main.c-6931-\tif (netif_needs_gso(skb, features)) {\ndrivers/s390/net/qeth_core_main.c:6932:\t\t/* match skb_segment(): */\ndrivers/s390/net/qeth_core_main.c-6933-\t\tunsigned int doffset = skb-\u003edata - skb_mac_header(skb);\n--\ninclude/linux/skbuff.h=4304=void skb_scrub_packet(struct sk_buff *skb, bool xnet);\ninclude/linux/skbuff.h:4305:struct sk_buff *skb_segment(struct sk_buff *skb, netdev_features_t features);\ninclude/linux/skbuff.h-4306-struct sk_buff *skb_segment_list(struct sk_buff *skb, netdev_features_t features,\n--\nlib/test_bpf.c=15300=static __init int test_skb_segment_single(const struct skb_segment_test *test)\n--\nlib/test_bpf.c-15310-\nlib/test_bpf.c:15311:\tsegs = skb_segment(skb, test-\u003efeatures);\nlib/test_bpf.c-15312-\tif (!IS_ERR(segs)) {\n--\nlib/test_bpf.c-15320-\nlib/test_bpf.c:15321:static __init int test_skb_segment(void)\nlib/test_bpf.c-15322-{\n--\nlib/test_bpf.c=15876=static int __init test_bpf_init(void)\n--\nlib/test_bpf.c-15920-\tif (!strlen(test_suite) || !strcmp(test_suite, \"test_skb_segment\"))\nlib/test_bpf.c:15921:\t\treturn test_skb_segment();\nlib/test_bpf.c-15922-\n--\nnet/core/net_test.c=207=static void gso_test_func(struct kunit *test)\n--\nnet/core/net_test.c-289-\tSKB_GSO_CB(skb)-\u003emax_segs = tcase-\u003emax_segs;\nnet/core/net_test.c:290:\tsegs = skb_segment(skb, features);\nnet/core/net_test.c-291-\tif (IS_ERR(segs)) {\n--\nnet/core/skbuff.c=4779=EXPORT_SYMBOL_GPL(skb_segment_list);\n--\nnet/core/skbuff.c-4789- */\nnet/core/skbuff.c:4790:struct sk_buff *skb_segment(struct sk_buff *head_skb,\nnet/core/skbuff.c-4791-\t\t\t netdev_features_t features)\n--\nnet/ipv4/tcp_offload.c=133=struct sk_buff *tcp_gso_segment(struct sk_buff *skb,\n--\nnet/ipv4/tcp_offload.c-181-\nnet/ipv4/tcp_offload.c:182:\tsegs = skb_segment(skb, features);\nnet/ipv4/tcp_offload.c-183-\tif (IS_ERR(segs))\n--\nnet/ipv4/udp_offload.c=481=struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,\n--\nnet/ipv4/udp_offload.c-550-\nnet/ipv4/udp_offload.c:551:\tsegs = skb_segment(gso_skb, features);\nnet/ipv4/udp_offload.c-552-\tif (IS_ERR_OR_NULL(segs)) {\n--\nnet/ipv4/udp_offload.c=633=static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,\n--\nnet/ipv4/udp_offload.c-687-\t */\nnet/ipv4/udp_offload.c:688:\tsegs = skb_segment(skb, features);\nnet/ipv4/udp_offload.c-689-out:\n--\nnet/ipv6/udp_offload.c=19=static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,\n--\nnet/ipv6/udp_offload.c-108-\t\t */\nnet/ipv6/udp_offload.c:109:\t\tsegs = skb_segment(skb, features);\nnet/ipv6/udp_offload.c-110-\t}\n--\nnet/sctp/offload.c=39=static struct sk_buff *sctp_gso_segment(struct sk_buff *skb,\n--\nnet/sctp/offload.c-71-\nnet/sctp/offload.c:72:\tsegs = skb_segment(skb, (features | NETIF_F_HW_CSUM) \u0026 ~NETIF_F_SG);\nnet/sctp/offload.c-73-\tif (IS_ERR(segs))\n"
} |
| 37/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16488 output: 38 thoughts: 91 Thoughts: **Analyzing a BPF Test Case** Okay, here's what's going on. I'm deep diving into this BPF testing code, specifically looking at `lib/test_bpf.c` around line 15311. I see `segs = skb_segment(skb, test->features);`. Wait a second, before I blindly accept this, I need to understand the implications of this `skb_segment` call within this particular test context. My immediate question is: What exactly *is* `test_skb_segment_single` doing? More specifically, does it initialize `SKB_GSO_CB(skb)`? That's the crucial detail that could make or break this section of code. I need to be sure that `skb_segment` is handling the GSO (Generic Segmentation Offload) context appropriately within this test. To answer this, I'm going to back up a few lines in `lib/test_bpf.c` and take a closer look at the surrounding code, starting from around line 15300. I need to understand the setup. |
| 38/3 | 2026/09/20 10:57 | tool | read-file |
0mArgs:{
"File": "lib/test_bpf.c",
"FirstLine": 15290,
"LineCount": 40
}Results: {
"Contents": "15290:\t\t{\n15291:\t\t\t.descr = \"gso_linear_no_head_frag\",\n15292:\t\t\t.build_skb = build_test_skb_linear_no_head_frag,\n15293:\t\t\t.features = NETIF_F_SG | NETIF_F_FRAGLIST |\n15294:\t\t\t\t NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_GSO |\n15295:\t\t\t\t NETIF_F_GRO | NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM |\n15296:\t\t\t\t NETIF_F_HW_VLAN_STAG_TX\n15297:\t\t}\n15298:\t};\n15299:\t\n15300:\tstatic __init int test_skb_segment_single(const struct skb_segment_test *test)\n15301:\t{\n15302:\t\tstruct sk_buff *skb, *segs;\n15303:\t\tint ret = -1;\n15304:\t\n15305:\t\tskb = test-\u003ebuild_skb();\n15306:\t\tif (!skb) {\n15307:\t\t\tpr_info(\"%s: failed to build_test_skb\", __func__);\n15308:\t\t\tgoto done;\n15309:\t\t}\n15310:\t\n15311:\t\tsegs = skb_segment(skb, test-\u003efeatures);\n15312:\t\tif (!IS_ERR(segs)) {\n15313:\t\t\tkfree_skb_list(segs);\n15314:\t\t\tret = 0;\n15315:\t\t}\n15316:\t\tkfree_skb(skb);\n15317:\tdone:\n15318:\t\treturn ret;\n15319:\t}\n15320:\t\n15321:\tstatic __init int test_skb_segment(void)\n15322:\t{\n15323:\t\tint i, err_cnt = 0, pass_cnt = 0;\n15324:\t\n15325:\t\tfor (i = 0; i \u003c ARRAY_SIZE(skb_segment_tests); i++) {\n15326:\t\t\tconst struct skb_segment_test *test = \u0026skb_segment_tests[i];\n15327:\t\n15328:\t\t\tcond_resched();\n15329:\t\t\tif (exclude_test(i))\n"
} |
| 39/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17546 output: 35 thoughts: 50 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Investigating SKB Construction for Testing** Alright, let's dive into this. The objective is clear: I need to understand how the test SKBs are being constructed within this `lib/test_bpf.c` context. Specifically, I should probably zero in on `build_test_skb_linear_no_head_frag` or maybe some of the other `build_test_skb` functions in that same file. They'll have the key to how these test SKBs are built, piece by piece. My goal is to break down the process, understand the specific configurations and how it's being done. I need to see how the SKB's linear data is handled, the fragmentation if it's there (or deliberately not there, in this case), and any relevant metadata that is set during construction. From this I will understand how to build my test SKBs correctly. |
| 40/3 | 2026/09/20 10:57 | tool | grepper |
0mArgs:{
"Expression": "build_test_skb_",
"PathPrefix": "lib/test_bpf.c"
}Results: {
"Output": "lib/test_bpf.c=15163=static __init struct sk_buff *build_test_skb(void)\n--\nlib/test_bpf.c-15222-\nlib/test_bpf.c:15223:static __init struct sk_buff *build_test_skb_linear_no_head_frag(void)\nlib/test_bpf.c-15224-{\n--\nlib/test_bpf.c=15283=static struct skb_segment_test skb_segment_tests[] __initconst = {\n--\nlib/test_bpf.c-15291-\t\t.descr = \"gso_linear_no_head_frag\",\nlib/test_bpf.c:15292:\t\t.build_skb = build_test_skb_linear_no_head_frag,\nlib/test_bpf.c-15293-\t\t.features = NETIF_F_SG | NETIF_F_FRAGLIST |\n"
} |
| 41/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 18109 output: 38 thoughts: 23 Thoughts: Here's my summary, as though I'm processing the code: **Diving into `build_test_skb`** Okay, time to dissect `build_test_skb` within `lib/test_bpf.c`. This is where the magic happens – the foundation for our BPF tests. My mental model here is that this function constructs the test packets, the Synthetic Kernel Buffers (SKBs), that we feed to the BPF programs for verification. I'm immediately thinking about what parameters it takes: what data does it need to generate the headers and payload we want to test with? Is it flexible enough to cover various network protocols and scenarios, or is it specific to a few cases? I'll need to look closely at its inputs and how it utilizes them. I'm especially interested in the control it gives us over packet content, length, and potentially, fragmentation. It should handle the creation and population of the struct sk_buff, including fields like `len`, `data`, `data_len`, `protocol`, and various flags. Beyond that, I hope it provides a streamlined interface for constructing different types of Ethernet, IP, TCP, and UDP headers, and if needed, other protocols based on our test requirements. The efficiency of this function matters as the tests would be slower if this function is slow to execute. So, I need to understand its design principles to determine its performance characteristics. Also, I'll pay attention to any error handling or boundary condition checks – ensuring this function is robust is critical to reliable BPF testing. Finally, I'll compare its implementation with any existing documentation or test cases to ensure I understand its usage context and how it fits into the broader BPF testing framework. |
| 42/3 | 2026/09/20 10:57 | tool | read-file |
0mArgs:{
"File": "lib/test_bpf.c",
"FirstLine": 15163,
"LineCount": 40
}Results: {
"Contents": "15163:\tstatic __init struct sk_buff *build_test_skb(void)\n15164:\t{\n15165:\t\tu32 headroom = NET_SKB_PAD + NET_IP_ALIGN + ETH_HLEN;\n15166:\t\tstruct sk_buff *skb[2];\n15167:\t\tstruct page *page[2];\n15168:\t\tint i, data_size = 8;\n15169:\t\n15170:\t\tfor (i = 0; i \u003c 2; i++) {\n15171:\t\t\tpage[i] = alloc_page(GFP_KERNEL);\n15172:\t\t\tif (!page[i]) {\n15173:\t\t\t\tif (i == 0)\n15174:\t\t\t\t\tgoto err_page0;\n15175:\t\t\t\telse\n15176:\t\t\t\t\tgoto err_page1;\n15177:\t\t\t}\n15178:\t\n15179:\t\t\t/* this will set skb[i]-\u003ehead_frag */\n15180:\t\t\tskb[i] = dev_alloc_skb(headroom + data_size);\n15181:\t\t\tif (!skb[i]) {\n15182:\t\t\t\tif (i == 0)\n15183:\t\t\t\t\tgoto err_skb0;\n15184:\t\t\t\telse\n15185:\t\t\t\t\tgoto err_skb1;\n15186:\t\t\t}\n15187:\t\n15188:\t\t\tskb_reserve(skb[i], headroom);\n15189:\t\t\tskb_put(skb[i], data_size);\n15190:\t\t\tskb[i]-\u003eprotocol = htons(ETH_P_IP);\n15191:\t\t\tskb_reset_network_header(skb[i]);\n15192:\t\t\tskb_set_mac_header(skb[i], -ETH_HLEN);\n15193:\t\n15194:\t\t\tskb_add_rx_frag(skb[i], 0, page[i], 0, 64, 64);\n15195:\t\t\t// skb_headlen(skb[i]): 8, skb[i]-\u003ehead_frag = 1\n15196:\t\t}\n15197:\t\n15198:\t\t/* setup shinfo */\n15199:\t\tskb_shinfo(skb[0])-\u003egso_size = 1448;\n15200:\t\tskb_shinfo(skb[0])-\u003egso_type = SKB_GSO_TCPV4;\n15201:\t\tskb_shinfo(skb[0])-\u003egso_type |= SKB_GSO_DODGY;\n15202:\t\tskb_shinfo(skb[0])-\u003egso_segs = 0;\n"
} |
| 43/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19288 output: 32 thoughts: 60 Thoughts: **Digging into SKB Allocation** Okay, so `dev_alloc_skb` ultimately relies on `__alloc_skb`. I need to confirm something crucial about the inner workings of `__alloc_skb`. Specifically, I need to know if the control buffer, `skb->cb`, gets zeroed out during that allocation. This is a critical point for my current analysis. To get the definitive answer, I'm going to jump straight into the source code. Let's find `__alloc_skb` definition within `net/core/skbuff.c`. Time to see what's actually happening at that level. |
| 44/3 | 2026/09/20 10:57 | tool | grepper |
0mError:no matches Args: {
"Expression": "memset\\(.*cb",
"PathPrefix": "net/core/skbuff.c"
}Results: {
"error": "no matches"
} |
| 45/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19531 output: 38 thoughts: 35 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing Memory Allocation in `__alloc_skb`** Alright, so I'm digging into `__alloc_skb` and need to quickly pinpoint where the potential bottleneck or issue lies. My initial instinct is to zero in on the memory allocation routines. Let's see, what are the primary suspects in the `__alloc_skb` process? Well, given its purpose, the code is likely calling into the kernel's memory management. Therefore, I should check out calls to `kmem_cache_alloc_node` and `kmem_cache_create` within `__alloc_skb`. These functions handle the allocation and management of kernel caches, and any performance issues there would be directly reflected in SKB allocation times. Beyond the cache management, the calls to `memset`, for zeroing the allocated memory, are also good to investigate. A heavy reliance on the CPU to handle `memset` can, naturally, also contribute to performance bottlenecks, or be optimized through other means, such as the use of an architecture-specific zeroing instruction. Time to get my hands dirty and dive into the source! |
| 46/3 | 2026/09/20 10:57 | tool | grepper |
0mArgs:{
"Expression": "struct sk_buff \\*__alloc_skb",
"PathPrefix": "net/core/skbuff.c"
}Results: {
"Output": "net/core/skbuff.c=606=static void *kmalloc_reserve(unsigned int *size, gfp_t flags, int node,\n--\nnet/core/skbuff.c-673- */\nnet/core/skbuff.c:674:struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,\nnet/core/skbuff.c-675-\t\t\t int flags, int node)\n"
} |
| 47/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19981 output: 36 thoughts: 20 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person: **Diving into the SKB Core** Okay, time to crack open `net/core/skbuff.c`. I'm going to navigate directly to line 674. I'm hoping to get a clearer picture of how they're handling… well, whatever functionality resides around that specific point. I'm expecting something core, likely related to memory management, packet manipulation, or perhaps a critical data structure interaction within the `sk_buff` itself. My intuition tells me it's probably handling some subtle nuances around buffer allocation, reference counting, or maybe even related to QoS features, given the file context. I need to refresh my understanding of the exact data structures and control flow at that location, looking for optimization opportunities or potential bottlenecks that might be masked within the abstraction. I'm also preparing myself to track calls through the relevant functions to clarify their relationships. |
| 48/3 | 2026/09/20 10:57 | tool | read-file |
0mArgs:{
"File": "net/core/skbuff.c",
"FirstLine": 674,
"LineCount": 40
}Results: {
"Contents": " 674:\tstruct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,\n 675:\t\t\t\t int flags, int node)\n 676:\t{\n 677:\t\tstruct sk_buff *skb = NULL;\n 678:\t\tstruct kmem_cache *cache;\n 679:\t\tu8 *data;\n 680:\t\n 681:\t\tif (sk_memalloc_socks() \u0026\u0026 (flags \u0026 SKB_ALLOC_RX))\n 682:\t\t\tgfp_mask |= __GFP_MEMALLOC;\n 683:\t\n 684:\t\tif (flags \u0026 SKB_ALLOC_FCLONE) {\n 685:\t\t\tcache = net_hotdata.skbuff_fclone_cache;\n 686:\t\t\tgoto fallback;\n 687:\t\t}\n 688:\t\tcache = net_hotdata.skbuff_cache;\n 689:\t\tif (unlikely(node != NUMA_NO_NODE \u0026\u0026 node != numa_mem_id()))\n 690:\t\t\tgoto fallback;\n 691:\t\n 692:\t\tif (flags \u0026 SKB_ALLOC_NAPI) {\n 693:\t\t\tskb = napi_skb_cache_get(true);\n 694:\t\t\tif (unlikely(!skb))\n 695:\t\t\t\treturn NULL;\n 696:\t\t} else if (!in_hardirq() \u0026\u0026 !irqs_disabled()) {\n 697:\t\t\tlocal_bh_disable();\n 698:\t\t\tskb = napi_skb_cache_get(false);\n 699:\t\t\tlocal_bh_enable();\n 700:\t\t}\n 701:\t\n 702:\t\tif (!skb) {\n 703:\tfallback:\n 704:\t\t\tskb = kmem_cache_alloc_node(cache, gfp_mask \u0026 ~GFP_DMA, node);\n 705:\t\t\tif (unlikely(!skb))\n 706:\t\t\t\treturn NULL;\n 707:\t\t}\n 708:\t\tskbuff_clear(skb);\n 709:\t\n 710:\t\t/* We do our best to align skb_shared_info on a separate cache\n 711:\t\t * line. It usually works because kmalloc(X \u003e SMP_CACHE_BYTES) gives\n 712:\t\t * aligned memory blocks, unless SLUB/SLAB debug is enabled.\n 713:\t\t * Both skb-\u003ehead and skb_shared_info are cache line aligned.\n"
} |
| 49/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20898 output: 33 thoughts: 31 Thoughts: Here's my analysis of line 708 and the process I'd undertake to understand it, tailored for an expert audience: **Diving into skbuff_clear(skb)** Okay, line 708: `skbuff_clear(skb);`. Right, that's a call to `skbuff_clear`, and given the context, it's pretty clear that `skb` is a pointer to a `sk_buff` structure. Now, before I jump into decompiling or tracing the code, let's think about the *purpose* of a `skbuff_clear` function. Based on naming conventions and kernel practices, it's highly likely this is intended to reset or deallocate a previously allocated `sk_buff`. The implication is that we're dealing with a network packet buffer, and this function is responsible for preparing it for reuse or complete disposal. My immediate concerns would be: * **What exactly does "clear" entail?** Is it just resetting header pointers, or are we deallocating the payload memory as well? Does it involve releasing any associated metadata? Are there any reference counts involved that need to be adjusted? * **What are the side effects?** Does this function potentially interact with other kernel subsystems or data structures that depend on this `skb`? Does it call any other functions that might trigger further actions? What kind of locking is involved, if any? * **Is this a potential source of errors?** Could there be double-frees, memory leaks, or use-after-free vulnerabilities associated with this function call, if used incorrectly? * **How is this called?** I would want to know where and why this function is being called in this location. What is the overall purpose of the code and what does the buffer represent? * **Is this buffer being used within a lock?** if so, is this function being called within that lock? The next step, if I had the source in front of me, would be to jump directly to the definition of `skbuff_clear`. I'd start with the function definition itself, tracing through any calls to helper functions, and examining how it modifies the `sk_buff` structure and any associated memory. I would look at its interactions with other kernel subsystems. I would be considering thread safety and possible race conditions. After gaining a clear understanding of that specific function and its behavior, I'd review the surrounding code to determine the buffer's lifecycle to understand why it is being "cleared" at this specific location. |
| 50/3 | 2026/09/20 10:57 | tool | grepper |
0mArgs:{
"Expression": "skbuff_clear\\(",
"PathPrefix": "net/core/skbuff.c"
}Results: {
"Output": "net/core/skbuff.c=284=static inline struct sk_buff *napi_skb_cache_get(bool alloc)\n--\nnet/core/skbuff.c-315- */\nnet/core/skbuff.c:316:static inline void skbuff_clear(struct sk_buff *skb)\nnet/core/skbuff.c-317-{\n--\nnet/core/skbuff.c=344=u32 napi_skb_cache_get_bulk(void **skbs, u32 n)\n--\nnet/core/skbuff.c-379-\t\tkasan_mempool_unpoison_object(skbs[i], skbuff_cache_size);\nnet/core/skbuff.c:380:\t\tskbuff_clear(skbs[i]);\nnet/core/skbuff.c-381-\t}\n--\nnet/core/skbuff.c=437=struct sk_buff *slab_build_skb(void *data)\n--\nnet/core/skbuff.c-446-\nnet/core/skbuff.c:447:\tskbuff_clear(skb);\nnet/core/skbuff.c-448-\tdata = __slab_build_skb(data, \u0026size);\n--\nnet/core/skbuff.c=490=struct sk_buff *__build_skb(void *data, unsigned int frag_size)\n--\nnet/core/skbuff.c-498-\nnet/core/skbuff.c:499:\tskbuff_clear(skb);\nnet/core/skbuff.c-500-\t__build_skb_around(skb, data, frag_size);\n--\nnet/core/skbuff.c=552=static struct sk_buff *__napi_build_skb(void *data, unsigned int frag_size)\n--\nnet/core/skbuff.c-559-\nnet/core/skbuff.c:560:\tskbuff_clear(skb);\nnet/core/skbuff.c-561-\t__build_skb_around(skb, data, frag_size);\n--\nnet/core/skbuff.c=674=struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,\n--\nnet/core/skbuff.c-707-\t}\nnet/core/skbuff.c:708:\tskbuff_clear(skb);\nnet/core/skbuff.c-709-\n"
} |
| 51/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22148 output: 36 thoughts: 10 Thoughts: Here's a summary of my thought process, as if I were considering `skbuff_clear`: **Analyzing `skbuff_clear`** Alright, let's dive into `skbuff_clear`. Given the name, I'm expecting this to be a function related to the `sk_buff` structure, the cornerstone of network packet handling within the kernel. It's likely designed to perform some form of cleanup or reset operation on a socket buffer. My initial hunch is that it's probably *not* a full deallocation – that'd likely be handled by a function with 'free' or 'release' in the name. Instead, I'd guess it's more about resetting specific fields within the `sk_buff` to a known state, preparing the buffer for reuse or discarding its current packet data without completely dismantling the structure. I'd expect it to zero out critical data pointers, reset length indicators (`len`, `data_len`, `tail`, `end`), and potentially clear flags related to data ownership or metadata. Maybe it handles reference counting as well. I'll need to look deeper into the implementation. I'm keen to see if it manages potential memory leaks from incomplete deallocation. What happens if the buffer has fragmented data segments (`frags`) attached? That's a key area to scrutinize. And how does it interact with any associated `net_device` or protocol-specific state? Depending on where it's called, there might be concurrency concerns, requiring appropriate locking. Essentially, this is a very important function for packet reuse, so it has to be robust. Let's see how it's implemented. |
| 52/3 | 2026/09/20 10:57 | tool | read-file |
0mArgs:{
"File": "net/core/skbuff.c",
"FirstLine": 315,
"LineCount": 30
}Results: {
"Contents": " 315:\t */\n 316:\tstatic inline void skbuff_clear(struct sk_buff *skb)\n 317:\t{\n 318:\t\t/* Replace memset(skb, 0, offsetof(struct sk_buff, tail))\n 319:\t\t * with two smaller memset(), with a barrier() between them.\n 320:\t\t * This forces the compiler to inline both calls.\n 321:\t\t */\n 322:\t\tBUILD_BUG_ON(offsetof(struct sk_buff, tail) \u003c= 128);\n 323:\t\tmemset(skb, 0, 128);\n 324:\t\tbarrier();\n 325:\t\tmemset((void *)skb + 128, 0, offsetof(struct sk_buff, tail) - 128);\n 326:\t}\n 327:\t\n 328:\t/**\n 329:\t * napi_skb_cache_get_bulk - obtain a number of zeroed skb heads from the cache\n 330:\t * @skbs: pointer to an at least @n-sized array to fill with skb pointers\n 331:\t * @n: number of entries to provide\n 332:\t *\n 333:\t * Tries to obtain @n \u0026sk_buff entries from the NAPI percpu cache and writes\n 334:\t * the pointers into the provided array @skbs. If there are less entries\n 335:\t * available, tries to replenish the cache and bulk-allocates the diff from\n 336:\t * the MM layer if needed.\n 337:\t * The heads are being zeroed with either memset() or %__GFP_ZERO, so they are\n 338:\t * ready for {,__}build_skb_around() and don't have any data buffers attached.\n 339:\t * Must be called *only* from the BH context.\n 340:\t *\n 341:\t * Return: number of successfully allocated skbs (@n if no actual allocation\n 342:\t *\t needed or kmem_cache_alloc_bulk() didn't fail).\n 343:\t */\n 344:\tu32 napi_skb_cache_get_bulk(void **skbs, u32 n)\n"
} |
| 53/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23126 output: 33 thoughts: 42 Thoughts: Okay, here's my interpretation of those thoughts, presented as my own mental process, given my expertise: **Checking `struct sk_buff`'s Layout** Alright, I'm thinking about the structure of a `struct sk_buff` again. Specifically, I'm trying to visualize the memory layout. I need to quickly recall where the `cb` member is located relative to `tail`. It's crucial for the optimization I'm considering, so a mental picture of the memory map is essential. Rather than relying on vague memory, it’s best to verify this definitively. The kernel's documentation, or rather, the source code itself is the definitive truth. Let me go directly to `include/linux/skbuff.h`. That's where the definition should be, and from there I can immediately get the answer. This is faster and more reliable than trying to reason about it from assumptions. Right, time to dive in and confirm the offset. |
| 54/3 | 2026/09/20 10:57 | tool | grepper |
0mArgs:{
"Expression": "cb\\[48\\]",
"PathPrefix": "include/linux/skbuff.h"
}Results: {
"Output": "include/linux/skbuff.h=886=struct sk_buff {\n--\ninclude/linux/skbuff.h-918-\t */\ninclude/linux/skbuff.h:919:\tchar\t\t\tcb[48] __aligned(8);\ninclude/linux/skbuff.h-920-\n"
} |
| 55/3 | 2026/09/20 10:57 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23495 output: 36 thoughts: 16 Thoughts: Here's my attempt at summarizing that thought process, tailored for an expert audience: **Diving into the sk_buff Structure** Alright, let's get down to brass tacks. I'm thinking about the implementation of `struct sk_buff`. My immediate focus is on data layout and how the different fields relate to each other. Specifically, I need to know where `tail` is defined in relation to the start of the buffer. Understanding its positioning within the `sk_buff` struct is crucial for any sort of low-level packet processing or optimization I'm working on right now. |
| 56/3 | 2026/09/20 10:58 | tool | read-file |
0mArgs:{
"File": "include/linux/skbuff.h",
"FirstLine": 915,
"LineCount": 40
}Results: {
"Contents": " 915:\t\t * layer. Please put your private variables there. If you\n 916:\t\t * want to keep them across layers you have to do a skb_clone()\n 917:\t\t * first. This is owned by whoever has the skb queued ATM.\n 918:\t\t */\n 919:\t\tchar\t\t\tcb[48] __aligned(8);\n 920:\t\n 921:\t\tunion {\n 922:\t\t\tstruct {\n 923:\t\t\t\tunsigned long\t_skb_refdst;\n 924:\t\t\t\tvoid\t\t(*destructor)(struct sk_buff *skb);\n 925:\t\t\t};\n 926:\t\t\tstruct list_head\ttcp_tsorted_anchor;\n 927:\t#ifdef CONFIG_NET_SOCK_MSG\n 928:\t\t\tunsigned long\t\t_sk_redir;\n 929:\t#endif\n 930:\t\t};\n 931:\t\n 932:\t#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)\n 933:\t\tunsigned long\t\t _nfct;\n 934:\t#endif\n 935:\t\tunsigned int\t\tlen,\n 936:\t\t\t\t\tdata_len;\n 937:\t\t__u16\t\t\tmac_len,\n 938:\t\t\t\t\thdr_len;\n 939:\t\n 940:\t\t/* Following fields are _not_ copied in __copy_skb_header()\n 941:\t\t * Note that queue_mapping is here mostly to fill a hole.\n 942:\t\t */\n 943:\t\t__u16\t\t\tqueue_mapping;\n 944:\t\n 945:\t/* if you move cloned around you also must adapt those constants */\n 946:\t#ifdef __BIG_ENDIAN_BITFIELD\n 947:\t#define CLONED_MASK\t(1 \u003c\u003c 7)\n 948:\t#else\n 949:\t#define CLONED_MASK\t1\n 950:\t#endif\n 951:\t#define CLONED_OFFSET\t\toffsetof(struct sk_buff, __cloned_offset)\n 952:\t\n 953:\t\t/* private: */\n 954:\t\t__u8\t\t\t__cloned_offset[0];\n"
} |
| 57/3 | 2026/09/20 10:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24266 output: 37 thoughts: 10 Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person: **Locating the Tail: A Quick Diagnostic Check** Alright, I'm diving in. First things first, before anything else, I need to know the state of the tail. Where *is* the tail in this specific context? Is it properly defined, or is there a problem with the tail? What parameters and values are associated with it? I'm going to quickly scan for its current status and value. Without that foundation, further analysis is going to be difficult, if not impossible. Let's start with a systematic check. I'll need to know. Where is the tail. |
| 58/3 | 2026/09/20 10:58 | tool | read-file |
0mArgs:{
"File": "include/linux/skbuff.h",
"FirstLine": 1000,
"LineCount": 40
}Results: {
"Contents": "1000:\t\t__u8\t\t\tno_fcs:1;\n1001:\t\t/* Indicates the inner headers are valid in the skbuff. */\n1002:\t\t__u8\t\t\tencapsulation:1;\n1003:\t\t__u8\t\t\tencap_hdr_csum:1;\n1004:\t\t__u8\t\t\tcsum_valid:1;\n1005:\t#ifdef CONFIG_IPV6_NDISC_NODETYPE\n1006:\t\t__u8\t\t\tndisc_nodetype:2;\n1007:\t#endif\n1008:\t\n1009:\t#if IS_ENABLED(CONFIG_IP_VS)\n1010:\t\t__u8\t\t\tipvs_property:1;\n1011:\t#endif\n1012:\t#if IS_ENABLED(CONFIG_NETFILTER_XT_TARGET_TRACE) || IS_ENABLED(CONFIG_NF_TABLES)\n1013:\t\t__u8\t\t\tnf_trace:1;\n1014:\t#endif\n1015:\t#ifdef CONFIG_NET_SWITCHDEV\n1016:\t\t__u8\t\t\toffload_fwd_mark:1;\n1017:\t\t__u8\t\t\toffload_l3_fwd_mark:1;\n1018:\t#endif\n1019:\t\t__u8\t\t\tredirected:1;\n1020:\t#ifdef CONFIG_NET_REDIRECT\n1021:\t\t__u8\t\t\tfrom_ingress:1;\n1022:\t#endif\n1023:\t#ifdef CONFIG_NETFILTER_SKIP_EGRESS\n1024:\t\t__u8\t\t\tnf_skip_egress:1;\n1025:\t#endif\n1026:\t#ifdef CONFIG_SKB_DECRYPTED\n1027:\t\t__u8\t\t\tdecrypted:1;\n1028:\t#endif\n1029:\t\t__u8\t\t\tslow_gro:1;\n1030:\t#if IS_ENABLED(CONFIG_IP_SCTP)\n1031:\t\t__u8\t\t\tcsum_not_inet:1;\n1032:\t#endif\n1033:\t\t__u8\t\t\tunreadable:1;\n1034:\t\t__u8\t\t\ttc_depth:2;\n1035:\t#if defined(CONFIG_NET_SCHED) || defined(CONFIG_NET_XGRESS)\n1036:\t\t__u16\t\t\ttc_index;\t/* traffic control index */\n1037:\t#endif\n1038:\t\n1039:\t\tu16\t\t\talloc_cpu;\n"
} |
| 59/3 | 2026/09/20 10:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25129 output: 32 thoughts: 10 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue: **Locating the Target: A `tail` Exploration** Alright, let's get down to brass tacks. I need to pinpoint the location of `tail`. Nothing particularly complex here, but it's important to be deliberate and efficient when navigating a system. My initial instinct is to assess where `tail` resides within my operational context. This is the first logical step, isn't it? Before any serious interaction or modification, I need to know precisely where I'm dealing with. Where is `tail`? |
| 60/3 | 2026/09/20 10:58 | tool | grepper |
0mError:no matches Args: {
"Expression": "tail_offset;",
"PathPrefix": "include/linux/skbuff.h"
}Results: {
"error": "no matches"
} |
| 61/3 | 2026/09/20 10:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25324 output: 33 thoughts: 18 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Hunting Down `tail` in the Kernel's SKB Structure** Right, let's dive into the guts of the networking stack. I need to get a handle on how `skb` is structured, specifically, I'm after the `tail` pointer within the `skbuff.h` header. I'm assuming it's declared and defined within `include/linux/skbuff.h`, so that's where I'll start looking. Probably need to get a clear understanding of the data structure. It's used everywhere, so better make sure to understand it. I need to ensure I'm looking at the correct version of the file, accounting for any kernel version differences that might impact the definition. Gotta make sure I'm precise with my parsing and any assumptions. Let's see what we can dig up. |
| 62/3 | 2026/09/20 10:58 | tool | grepper |
0mArgs:{
"Expression": "\\btail\\b",
"PathPrefix": "include/linux/skbuff.h"
}Results: {
"Output": "include/linux/skbuff.h=728=enum skb_tstamp_type {\n--\ninclude/linux/skbuff.h-758- * / ,----------------- + data\ninclude/linux/skbuff.h:759: * / / ,----------- + tail\ninclude/linux/skbuff.h-760- * | | | , + end\n--\ninclude/linux/skbuff.h-862- *\t@reserved_tailroom: (aka @mark) number of bytes of free space available\ninclude/linux/skbuff.h:863: *\t\tat the tail of an sk_buff\ninclude/linux/skbuff.h-864- *\t@vlan_all: vlan fields (proto \u0026 tci)\n--\ninclude/linux/skbuff.h-876- *\t@kcov_handle: KCOV remote handle for remote coverage collection\ninclude/linux/skbuff.h:877: *\t@tail: Tail pointer\ninclude/linux/skbuff.h-878- *\t@end: End pointer\n--\ninclude/linux/skbuff.h=886=struct sk_buff {\n--\ninclude/linux/skbuff.h-1091-\t/* These elements must be at the end, see alloc_skb() for details. */\ninclude/linux/skbuff.h:1092:\tsk_buff_data_t\t\ttail;\ninclude/linux/skbuff.h-1093-\tsk_buff_data_t\t\tend;\n--\ninclude/linux/skbuff.h=1462=int __skb_pad(struct sk_buff *skb, int pad, bool free_on_error);\n--\ninclude/linux/skbuff.h-1464-/**\ninclude/linux/skbuff.h:1465: *\tskb_pad\t\t\t-\tzero pad the tail of an skb\ninclude/linux/skbuff.h-1466- *\t@skb: buffer to pad\n--\ninclude/linux/skbuff.h=2223=static inline struct sk_buff *skb_peek_next(struct sk_buff *skb,\n--\ninclude/linux/skbuff.h-2233-/**\ninclude/linux/skbuff.h:2234: *\tskb_peek_tail - peek at the tail of an \u0026sk_buff_head\ninclude/linux/skbuff.h-2235- *\t@list_: list to peek at\n--\ninclude/linux/skbuff.h-2241- *\ninclude/linux/skbuff.h:2242: *\tReturns %NULL for an empty list or a pointer to the tail element.\ninclude/linux/skbuff.h-2243- *\tThe reference count is not incremented and the reference is therefore\n--\ninclude/linux/skbuff.h=2456=void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk);\n--\ninclude/linux/skbuff.h-2458-/**\ninclude/linux/skbuff.h:2459: *\t__skb_queue_tail - queue a buffer at the list tail\ninclude/linux/skbuff.h-2460- *\t@list: list to use\n--\ninclude/linux/skbuff.h=2507=struct sk_buff *skb_dequeue(struct sk_buff_head *list);\n--\ninclude/linux/skbuff.h-2509-/**\ninclude/linux/skbuff.h:2510: *\t__skb_dequeue_tail - remove from the tail of the queue\ninclude/linux/skbuff.h-2511- *\t@list: list to dequeue from\ninclude/linux/skbuff.h-2512- *\ninclude/linux/skbuff.h:2513: *\tRemove the tail of the list. This function does not take any locks\ninclude/linux/skbuff.h:2514: *\tso must be used with appropriate locks held only. The tail item is\ninclude/linux/skbuff.h-2515- *\treturned or %NULL if the list is empty.\n--\ninclude/linux/skbuff.h=2716=static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)\ninclude/linux/skbuff.h-2717-{\ninclude/linux/skbuff.h:2718:\treturn skb-\u003ehead + skb-\u003etail;\ninclude/linux/skbuff.h-2719-}\n--\ninclude/linux/skbuff.h=2721=static inline void skb_reset_tail_pointer(struct sk_buff *skb)\ninclude/linux/skbuff.h-2722-{\ninclude/linux/skbuff.h:2723:\tskb-\u003etail = skb-\u003edata - skb-\u003ehead;\ninclude/linux/skbuff.h-2724-}\n--\ninclude/linux/skbuff.h=2726=static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)\n--\ninclude/linux/skbuff.h-2728-\tskb_reset_tail_pointer(skb);\ninclude/linux/skbuff.h:2729:\tskb-\u003etail += offset;\ninclude/linux/skbuff.h-2730-}\n--\ninclude/linux/skbuff.h=2733=static inline unsigned char *skb_tail_pointer(const struct sk_buff *skb)\ninclude/linux/skbuff.h-2734-{\ninclude/linux/skbuff.h:2735:\treturn skb-\u003etail;\ninclude/linux/skbuff.h-2736-}\n--\ninclude/linux/skbuff.h=2738=static inline void skb_reset_tail_pointer(struct sk_buff *skb)\ninclude/linux/skbuff.h-2739-{\ninclude/linux/skbuff.h:2740:\tskb-\u003etail = skb-\u003edata;\ninclude/linux/skbuff.h-2741-}\n--\ninclude/linux/skbuff.h=2743=static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)\ninclude/linux/skbuff.h-2744-{\ninclude/linux/skbuff.h:2745:\tskb-\u003etail = skb-\u003edata + offset;\ninclude/linux/skbuff.h-2746-}\n--\ninclude/linux/skbuff.h=2761=static inline void skb_might_realloc(struct sk_buff *skb) {}\n--\ninclude/linux/skbuff.h-2766- */\ninclude/linux/skbuff.h:2767:void *pskb_put(struct sk_buff *skb, struct sk_buff *tail, int len);\ninclude/linux/skbuff.h-2768-void *skb_put(struct sk_buff *skb, unsigned int len);\ninclude/linux/skbuff.h=2769=static inline void *__skb_put(struct sk_buff *skb, unsigned int len)\n--\ninclude/linux/skbuff.h-2772-\tSKB_LINEAR_ASSERT(skb);\ninclude/linux/skbuff.h:2773:\tskb-\u003etail += len;\ninclude/linux/skbuff.h-2774-\tskb-\u003elen += len;\n--\ninclude/linux/skbuff.h=2902=static inline unsigned int skb_headroom(const struct sk_buff *skb)\n--\ninclude/linux/skbuff.h-2910- *\ninclude/linux/skbuff.h:2911: *\tReturn the number of bytes of free space at the tail of an sk_buff\ninclude/linux/skbuff.h-2912- */\ninclude/linux/skbuff.h=2913=static inline int skb_tailroom(const struct sk_buff *skb)\ninclude/linux/skbuff.h-2914-{\ninclude/linux/skbuff.h:2915:\treturn skb_is_nonlinear(skb) ? 0 : skb-\u003eend - skb-\u003etail;\ninclude/linux/skbuff.h-2916-}\n--\ninclude/linux/skbuff.h-2921- *\ninclude/linux/skbuff.h:2922: *\tReturn the number of bytes of free space at the tail of an sk_buff\ninclude/linux/skbuff.h-2923- *\tallocated by sk_stream_alloc()\n--\ninclude/linux/skbuff.h=2925=static inline int skb_availroom(const struct sk_buff *skb)\n--\ninclude/linux/skbuff.h-2929-\ninclude/linux/skbuff.h:2930:\treturn skb-\u003eend - skb-\u003etail - skb-\u003ereserved_tailroom;\ninclude/linux/skbuff.h-2931-}\n--\ninclude/linux/skbuff.h-2937- *\ninclude/linux/skbuff.h:2938: *\tIncrease the headroom of an empty \u0026sk_buff by reducing the tail\ninclude/linux/skbuff.h-2939- *\troom. This is only allowed for an empty buffer.\n--\ninclude/linux/skbuff.h=2941=static inline void skb_reserve(struct sk_buff *skb, int len)\n--\ninclude/linux/skbuff.h-2943-\tskb-\u003edata += len;\ninclude/linux/skbuff.h:2944:\tskb-\u003etail += len;\ninclude/linux/skbuff.h-2945-}\n--\ninclude/linux/skbuff.h=3900=static inline int __skb_cow(struct sk_buff *skb, unsigned int headroom,\n--\ninclude/linux/skbuff.h-3922- *\ninclude/linux/skbuff.h:3923: *\tThe result is skb with writable area skb-\u003ehead...skb-\u003etail\ninclude/linux/skbuff.h-3924- *\tand at least @headroom of space at head.\n"
} |
| 63/3 | 2026/09/20 10:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 28104 output: 23 thoughts: 1136 Thoughts: **Analyzing Potential GSO Vulnerability** Okay, so I'm looking at this `skb_segment` function and a potential vulnerability related to the `SKB_GSO_CB(skb)->max_segs` field within the `cb` data. The first thing that jumps out is the location of `tail` at line 1092 and `cb` at line 919. So `skbuff_clear(skb)` is called, which zeroes everything from the beginning of `struct sk_buff` all the way up to `tail`. This means that it will include and therefore zero the entire `cb[48]` field. Now, I'm thinking about how `max_segs` is handled. In `__skb_gso_segment`, it's explicitly set: `SKB_GSO_CB(skb)->max_segs = min_t(unsigned int, max_segs, U16_MAX);`. Then, in `skb_segment` itself, it's explicitly zeroed out: `SKB_GSO_CB(nskb)->max_segs = 0;`. This indicates that `max_segs` is probably intended to be a per-call limit for segmentation. Also, in `net_test.c`, they're specifically setting the value: `SKB_GSO_CB(skb)->max_segs = tcase->max_segs;`. Here's the problem: what happens if the input `head_skb` to `skb_segment` wasn't passed through `__skb_gso_segment`? That means the value of `cb` could be garbage! I need to trace this... Let's look at all the callers of `skb_segment`... 1. `tcp_gso_segment`: This calls `skb_segment` via `inet_gso_segment`, which then goes through `skb_mac_gso_segment`, and ultimately to `__skb_gso_segment`. Within `__skb_gso_segment`, we *do* set `SKB_GSO_CB(skb)->max_segs` *before* it gets to `skb_mac_gso_segment`. So it looks good. 2. `__udp_gso_segment`: Also via the same call chain. 3. `udp4_ufo_fragment`: Same here. 4. `udp6_ufo_fragment`: Same again. 5. `sctp_gso_segment`: Also the same pattern, through `inet_gso_segment` or `ipv6_gso_segment`. 6. `net_test.c`: The test cases explicitly *set* `max_segs` or call `__skb_gso_segment` before it. Okay. 7. `lib/test_bpf.c`: In this case, `test_skb_segment_single` calls `skb_segment` directly. Now, this is interesting. I need to be sure the `cb` is properly initialized here. `skbuff_clear` zeros it when the `skb` is allocated. The `test->build_skb()` function is called, but that only sets headers and frag data, no `cb` data. So, the `cb` is zeroed initially. Wait a second. What about SKBs that are *already* in the network stack, for real network traffic, not just test cases? Where do `skb_segment` or `__skb_gso_segment` get called during normal packet processing? I've got to check all callers of `__skb_gso_segment`. Time to grep the kernel source for that. |
| 64/3 | 2026/09/20 10:58 | tool | grepper |
0mArgs:{
"Expression": "__skb_gso_segment\\("
}Results: {
"Output": "drivers/net/tap.c=255=rx_handler_result_t tap_handle_frame(struct sk_buff **pskb)\n--\ndrivers/net/tap.c-283-\ndrivers/net/tap.c:284:\t\tsegs = __skb_gso_segment(skb, features, false, 0);\ndrivers/net/tap.c-285-\t\tif (IS_ERR(segs)) {\n--\ninclude/net/gso.h=66=static inline __sum16 gso_make_checksum(struct sk_buff *skb, __wsum res)\n--\ninclude/net/gso.h-77-\ninclude/net/gso.h:78:struct sk_buff *__skb_gso_segment(struct sk_buff *skb,\ninclude/net/gso.h-79-\t\t\t\t netdev_features_t features, bool tx_path,\n--\ninclude/net/gso.h=82=static inline struct sk_buff *skb_gso_segment(struct sk_buff *skb,\n--\ninclude/net/gso.h-84-{\ninclude/net/gso.h:85:\treturn __skb_gso_segment(skb, features, true, 0);\ninclude/net/gso.h-86-}\n--\ninclude/net/udp.h=580=static inline struct sk_buff *udp_rcv_segment(struct sock *sk,\n--\ninclude/net/udp.h-615-\t */\ninclude/net/udp.h:616:\tsegs = __skb_gso_segment(skb, features, false, 0);\ninclude/net/udp.h-617-\tif (IS_ERR_OR_NULL(segs)) {\n--\nnet/core/dev.c=4153=static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)\n--\nnet/core/dev.c-4191-\nnet/core/dev.c:4192:\t\tsegs = __skb_gso_segment(skb, features, true,\nnet/core/dev.c-4193-\t\t\t\t\t resegment_max_segs);\n--\nnet/core/gso.c=66=static bool skb_needs_check(const struct sk_buff *skb, bool tx_path)\n--\nnet/core/gso.c-88- */\nnet/core/gso.c:89:struct sk_buff *__skb_gso_segment(struct sk_buff *skb,\nnet/core/gso.c-90-\t\t\t\t netdev_features_t features, bool tx_path,\n--\nnet/core/net_test.c=375=static void gso_test_tcp_bounded_segment(struct kunit *test)\n--\nnet/core/net_test.c-389-\nnet/core/net_test.c:390:\tsegs = __skb_gso_segment(skb, features, true, max_segs);\nnet/core/net_test.c-391-\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, segs);\n--\nnet/openvswitch/datapath.c=367=static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,\n--\nnet/openvswitch/datapath.c-377-\tBUILD_BUG_ON(sizeof(*OVS_CB(skb)) \u003e SKB_GSO_CB_OFFSET);\nnet/openvswitch/datapath.c:378:\tsegs = __skb_gso_segment(skb, NETIF_F_SG, false, 0);\nnet/openvswitch/datapath.c-379-\tif (IS_ERR(segs))\n"
} |
| 65/3 | 2026/09/20 10:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31079 output: 35 thoughts: 1024 Thoughts: **GSO CB Size and Offset Analysis** Okay, let's break this down. My initial thought process was triggered by the behavior of `SKB_GSO_CB(skb)->max_segs`. It looks like every call to `__skb_gso_segment` explicitly sets this value, via `net/core/gso.c:122`, to `min_t(unsigned int, max_segs, U16_MAX)`. The subsequent calls to `skb_gso_segment` pass 0, and `__skb_gso_segment` gets called with the inline wrapper. I noticed that when `skb_segment` creates the new `nskb` segments, it explicitly resets `SKB_GSO_CB(nskb)->max_segs` to 0. But then, the real question hit me: what's going on with `SKB_GSO_CB_OFFSET`? I need to understand the size and layout of `struct skb_gso_cb`. Let's dig into `include/net/gso.h`. Okay, the structure itself is pretty straightforward: a union containing `mac_offset` and `next`, `encap_level`, `csum`, `csum_start`, and `max_segs`. Let's calculate the size. `mac_offset` and `next` are a union, so that's the size of the larger of the two, `struct sk_buff *`, which is 8 bytes. `encap_level` is an `int` (4 bytes), `csum` is a `__u32` (4 bytes), `csum_start` is a `__u16` (2 bytes), and `max_segs` is a `__u16` (2 bytes). That gives us a total of 8 + 4 + 4 + 2 + 2 = 20 bytes for the `skb_gso_cb` structure. But the `SKB_GSO_CB_OFFSET` is defined as 32! This is where things get interesting. So we have 32 bytes of offset and 20 bytes for the control block which is 52. Let's look at this `cb` field inside `skb`. Then I remembered the check... The size of `skb->cb` is only 48. Wait... 32 + 20 is 52 bytes, which is greater than 48. This is a critical error! There's a potential out-of-bounds write here! Then, I'm reminded of the build-time check! Let's examine line 117 in `net/core/gso.c`: `BUILD_BUG_ON(SKB_GSO_CB_OFFSET + sizeof(*SKB_GSO_CB(skb)) > sizeof(skb->cb));`. Surely the build *must* have failed, right? But it didn't! This union is where the answer lies. This indicates that `mac_offset` is *not* in a union and is a separate field. So, the size calculation is wrong. I need to go back and re-read lines 10-40 in `include/net/gso.h` very carefully. The code must have a trick up its sleeve to have passed that build check! |
| 66/3 | 2026/09/20 10:58 | tool | read-file |
0mArgs:{
"File": "include/net/gso.h",
"FirstLine": 10,
"LineCount": 35
}Results: {
"Contents": " 10:\t * For non-tunnel skb it points to skb_mac_header() and for\n 11:\t * tunnel skb it points to outer mac header.\n 12:\t * Keeps track of level of encapsulation of network headers.\n 13:\t */\n 14:\tstruct skb_gso_cb {\n 15:\t\tunion {\n 16:\t\t\tint\tmac_offset;\n 17:\t\t\tint\tdata_offset;\n 18:\t\t};\n 19:\t\tint\tencap_level;\n 20:\t\t__wsum\tcsum;\n 21:\t\t__u16\tcsum_start;\n 22:\t\t__u16\tmax_segs;\t/* Max MSS segs per output skb, 0 = no limit */\n 23:\t};\n 24:\t#define SKB_GSO_CB_OFFSET\t32\n 25:\t#define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)-\u003ecb + SKB_GSO_CB_OFFSET))\n 26:\t\n 27:\tstatic inline int skb_tnl_header_len(const struct sk_buff *inner_skb)\n 28:\t{\n 29:\t\treturn (skb_mac_header(inner_skb) - inner_skb-\u003ehead) -\n 30:\t\t\tSKB_GSO_CB(inner_skb)-\u003emac_offset;\n 31:\t}\n 32:\t\n 33:\tstatic inline int gso_pskb_expand_head(struct sk_buff *skb, int extra)\n 34:\t{\n 35:\t\tint new_headroom, headroom;\n 36:\t\tint ret;\n 37:\t\n 38:\t\theadroom = skb_headroom(skb);\n 39:\t\tret = pskb_expand_head(skb, extra, 0, GFP_ATOMIC);\n 40:\t\tif (ret)\n 41:\t\t\treturn ret;\n 42:\t\n 43:\t\tnew_headroom = skb_headroom(skb);\n 44:\t\tSKB_GSO_CB(skb)-\u003emac_offset += (new_headroom - headroom);\n"
} |
| 67/3 | 2026/09/20 10:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 33463 output: 26 thoughts: 538 Thoughts: **Analyzing a Network Driver Patch**
Alright, let's break this down. Line 15 to 18 of the code snippet immediately caught my eye:
```c
union {
int mac_offset;
int data_offset;
};
int encap_level;
__wsum csum;
__u16 csum_start;
__u16 max_segs;
```
I quickly calculated the size: `int` (4 bytes) + `int` (4 bytes) + `__wsum` (4 bytes) + `__u16` (2 bytes) + `__u16` (2 bytes) = 16 bytes. Then 32 + 16 = 48 bytes! Bingo - exactly fits in `cb[48]`! Before the patch, the `csum_start` variable had 2 bytes of padding after it, going from 14 bytes to 16. Then, `__u16 max_segs;` cleverly fills that padding. A neat and tidy optimization.
Now, my focus shifts to the patch itself. The goal is to analyze the changes in detail and look for any potential uninitialized memory reads or information leaks.
Let's start with the diff, part by part.
Part 1 examines `drivers/net/tap.c`. I see `segs = __skb_gso_segment(skb, features, false, 0);` here. It's just passing `0`. This seems straightforward, and I'll keep this in mind as I keep reviewing.
Part 2 goes into `include/linux/netdevice.h`:
```c
static inline unsigned int
netif_get_gso_max_size(const struct net_device *dev, __be16 protocol)
{
/* pairs with WRITE_ONCE() in netif_set_gso(_ipv4)_max_size() */
return protocol == htons(ETH_P_IPV6) ?
READ_ONCE(dev->gso_max_size) :
READ_ONCE(dev->gso_ipv4_max_size);
}
```
The key change here is that `const struct sk_buff *skb` is replaced with `__be16 protocol`. I need to ensure this is sound. I'll grep the entire kernel for `netif_get_gso_max_size` to see all its callers. I need to make sure none of these callers are passing an uninitialized `protocol` value or that they've all been correctly updated to accommodate this change. This is the next step to confirm there are no new vulnerabilities.
|
| 68/3 | 2026/09/20 10:58 | tool | grepper |
0mArgs:{
"Expression": "netif_get_gso_max_size\\("
}Results: {
"Output": "include/linux/netdevice.h=5572=static inline unsigned int\ninclude/linux/netdevice.h:5573:netif_get_gso_max_size(const struct net_device *dev, __be16 protocol)\ninclude/linux/netdevice.h-5574-{\n--\nnet/core/dev.c=3842=static bool gso_within_device_limits(const struct sk_buff *skb,\n--\nnet/core/dev.c-3845-\treturn skb_shinfo(skb)-\u003egso_segs \u003c= READ_ONCE(dev-\u003egso_max_segs) \u0026\u0026\nnet/core/dev.c:3846:\t skb-\u003elen \u003c netif_get_gso_max_size(dev, vlan_get_protocol(skb));\nnet/core/dev.c-3847-}\n--\nnet/core/dev.c=3969=skb_gso_resegment_max_segs(struct sk_buff *skb, struct net_device *dev,\n--\nnet/core/dev.c-3976-\nnet/core/dev.c:3977:\tgso_max_size = netif_get_gso_max_size(dev, vlan_get_protocol(skb));\nnet/core/dev.c-3978-\n"
} |
| 69/3 | 2026/09/20 10:58 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 34984 output: 24 thoughts: 3582 Thoughts: **GSO Resegmentation Analysis** Alright, let's break down these GSO (Generic Segmentation Offload) changes. The core idea is to introduce bounded resegmentation, allowing devices to further segment GSO packets, but with limits. We've got `max_segs` added to `gso.h` and updates to the `__skb_gso_segment` and `skb_gso_segment` functions, which seems reasonable for controlling the maximum number of segments created. This `max_segs` is added into `cb` inside of the skb structure. The checks in `net/core/dev.c` are interesting. `gso_within_device_limits` makes sure the `gso_segs` doesn't exceed the device's `gso_max_segs` and the packet length is within `netif_get_gso_max_size`. `gso_features_check` can disable GSO features if the device limits are exceeded. I will have to investigate why `vlan_get_protocol(skb)` is called and how this value affects the `gso_max_size`. `skb_can_gso_resegment` decides if a packet is eligible for resegmentation. It checks a bunch of conditions: GSO robustness, scatter-gather support (NETIF_F_SG), that the packet can be checksummed, and importantly, no frag_list. The logic here is pretty straightforward. `skb_gso_resegment_max_segs` calculates the maximum resegmentation allowed, considering the device's capabilities. It retrieves the VLAN protocol and uses this when determining `gso_max_size`. This function is key. It ensures the limits are applied correctly. I checked `skb_header_pointer` which is used in `skb_gso_resegment_max_segs` to ensure the TCP header is accessible. It is safe, either returning a pointer to the linear buffer or copying from packet payload. The `validate_xmit_skb` function calls `skb_gso_resegment_max_segs` when it determines the packet needs resegmentation because it's GSO TCP and exceeds device limits. The call to `__skb_gso_segment` creates the segments. Now, looking at `skb_segment` in `net/core/skbuff.c`, it's where the actual segmentation happens. This function now uses the new `max_segs` field from the SKB control block. The logic is straightforward, dividing the input packet into chunks. A critical check: `if (!max_segs && !(features & NETIF_F_GSO_PARTIAL))` protects against partial segment when there is no resegmentation allowed. The new `max_segs` is set to 0 on new `skb`s. In `net/ipv4/tcp_offload.c`, `tcp_gso_segment` doesn't apply a `max_segs` limit if the packet isn't resegmented. The `max_segs` field is also used to reset `gso_segs` and it does not use a pre-set value. Now, the important part: Does this patch introduce any potential issues with uninitialized memory reads or writes? Let's consider the scenarios: 1. **User-space exposure:** No, nothing is being copied to userspace. 2. **Uninitialized variable use:** We've traced the initialization of `max_segs`. `max_segs` gets initialized via the path to `__skb_gso_segment`. `max_segs` is guaranteed to be set, so no read of uninitialized memory. 3. **Allocation/Initialization:** The `max_segs` field added to the skb control block (inside cb) is initialized and updated through `__skb_gso_segment`. 4. **Bounds/Length Check problems:** Segmentation itself, within `skb_segment`, could be a source of problems. The new `max_segs` value is checked to limit the segmentation count in multiple places. It is still safe because bounds and length checks are present. Packet payload comes from initialized buffers so there are no uninitialized reads. `__copy_skb_header` copies from an initialized packet and clears the new `max_segs` field. The introduction of `max_segs` in the GSO path is done with care to ensure the value is present and initialized. So, based on this analysis, I don't see any immediate risks here regarding uninitialized memory reads or any other type of safety issues. |
| 70/3 | 2026/09/20 10:58 | tool | grepper |
0mArgs:{
"Expression": "__be16 vlan_get_protocol"
}Results: {
"Output": "include/linux/if_vlan.h=623=static inline\ninclude/linux/if_vlan.h:624:__be16 vlan_get_protocol_offset_inline(const struct sk_buff *skb,\ninclude/linux/if_vlan.h-625-\t\t\t\t __be16 type,\n--\ninclude/linux/if_vlan.h=645=static inline __be16 __vlan_get_protocol(const struct sk_buff *skb, __be16 type,\n--\ninclude/linux/if_vlan.h-657- */\ninclude/linux/if_vlan.h:658:static inline __be16 vlan_get_protocol(const struct sk_buff *skb)\ninclude/linux/if_vlan.h-659-{\n--\ninclude/linux/if_vlan.h-663-/* This version of __vlan_get_protocol() also pulls mac header in skb-\u003ehead */\ninclude/linux/if_vlan.h:664:static inline __be16 vlan_get_protocol_and_depth(struct sk_buff *skb,\ninclude/linux/if_vlan.h-665-\t\t\t\t\t\t __be16 type, int *depth)\n--\nnet/packet/af_packet.c=542=static u16 vlan_get_tci(const struct sk_buff *skb, struct net_device *dev)\n--\nnet/packet/af_packet.c-571-\nnet/packet/af_packet.c:572:static __be16 vlan_get_protocol_dgram(const struct sk_buff *skb)\nnet/packet/af_packet.c-573-{\n"
} |
| 71/3 | 2026/09/20 10:59 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40053 output: 38 thoughts: 26 Thoughts: Here's a summary of my immediate thought process, delivered as requested: **Diving into the VLAN Protocol Function** Okay, so I need to understand how `vlan_get_protocol` works. The logical first step is to check its definition. I'm going to jump straight into the kernel source, specifically the header file for VLAN interfaces, which I know is typically located in `include/linux/if_vlan.h`. Time to see what that function is *actually* doing. Understanding this function will likely reveal a lot about how the kernel handles VLAN tagging and de-tagging. I'm anticipating this will be a quick investigation – shouldn’t take too long to locate the declaration and start to build an understanding from there. |
| 72/3 | 2026/09/20 10:59 | tool | read-file |
0mArgs:{
"File": "include/linux/if_vlan.h",
"FirstLine": 655,
"LineCount": 15
}Results: {
"Contents": " 655:\t * Returns: the EtherType of the packet, regardless of whether it is\n 656:\t * vlan encapsulated (normal or hardware accelerated) or not.\n 657:\t */\n 658:\tstatic inline __be16 vlan_get_protocol(const struct sk_buff *skb)\n 659:\t{\n 660:\t\treturn __vlan_get_protocol(skb, skb-\u003eprotocol, NULL);\n 661:\t}\n 662:\t\n 663:\t/* This version of __vlan_get_protocol() also pulls mac header in skb-\u003ehead */\n 664:\tstatic inline __be16 vlan_get_protocol_and_depth(struct sk_buff *skb,\n 665:\t\t\t\t\t\t\t __be16 type, int *depth)\n 666:\t{\n 667:\t\tint maclen;\n 668:\t\n 669:\t\ttype = __vlan_get_protocol(skb, type, \u0026maclen);\n"
} |
| 73/3 | 2026/09/20 10:59 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40545 output: 360 thoughts: 844 Thoughts: **Analyzing the NeedsKMSAN Flag for a GSO Resegmentation Patch** Okay, so I'm taking a close look at this patch implementing bounded Generic Segmentation Offload (GSO) resegmentation. My initial impression is that the `NeedsKMSAN` flag is probably going to be `false`, but let's be methodical. First, it's reading the protocol information directly from the `skb` and header, that's pretty standard. The local variables, like `resegment_max_segs`, are properly initialized to zero, which is good practice. The same goes for how `_tcph` is handled using `skb_header_pointer` – it's either pointing to existing memory or copying data, and there's a safety net in place with the NULL check that returns 0 if it fails. No concerns there about uninitialized reads. Then I checked the test cases in `net/core/net_test.c`. They're being very explicit about initializing all the fields in the various structures. All explicitly zeroed or set, which is a good sign. No obvious uninitialized memory or information leaks there. Now, let's step back and look at the KMSAN guidelines. They're pretty specific: KMSAN is only needed for UNINITIALIZED MEMORY risks that are *only* detectable by KMSAN. I've got the criteria right here: * No kernel structures are sent to user space. This is a crucial point, and not relevant here. * No conditional logic depends on uninitialized variables. We've ensured initialization. * No complex data structure allocations that would have uninitialized fields. `max_segs` is initialized. * No bounds checks opening potential for uninitialized byte access. This patch, in essence, adds a new field (`max_segs`) to the `skb_gso_cb` struct, and it looks like the padding is used. That's fine. It initializes the new field, and resets it appropriately. Any memory safety bugs, like buffer overflows, are covered by existing KASAN/UBSAN checks. This patch's primary function is to optimize the packet segmentation itself. I'm not seeing any scenarios where uninitialized memory would be an issue. Therefore, considering all of this, there are no uninitialized memory risks. I'm comfortable setting `NeedsKMSAN = false`. It's time to prepare the tool call to `set-results`. |
| 74/3 | 2026/09/20 10:59 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series adds bounded GSO resegmentation for oversized TCP skbs in the networking core transmit path (net/core/dev.c, net/core/gso.c, net/core/skbuff.c, etc.).\n\nKey considerations regarding memory initialization and sanitizers:\n1. The new field `max_segs` in `struct skb_gso_cb` occupies what was previously 2 bytes of padding within `skb-\u003ecb`. It is explicitly initialized to `min_t(unsigned int, max_segs, U16_MAX)` in `__skb_gso_segment()` and reset to 0 in newly created segment skbs in `skb_segment()`.\n2. All local variables touched or introduced (e.g., `resegment_max_segs = 0` in `validate_xmit_skb()`, stack buffer `_tcph` passed to `skb_header_pointer()`) are either initialized to 0 or populated before use.\n3. No data structures or padding bytes are exposed or copied to userspace; this logic operates purely inside the kernel's egress packet segmentation and device transmission pipeline.\n4. Any potential risks introduced by parsing TCP headers or splitting skb payloads into segments involve packet bounds and memory offsets, which are out-of-bounds or use-after-free concerns effectively monitored by standard KASAN and packet sanity checks rather than KMSAN uninitialized memory tracking.\n\nConsequently, there is no risk of uninitialized memory reads or information leaks justifying a dedicated KMSAN session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series adds bounded GSO resegmentation for oversized TCP skbs in the networking core transmit path (net/core/dev.c, net/core/gso.c, net/core/skbuff.c, etc.).\n\nKey considerations regarding memory initialization and sanitizers:\n1. The new field `max_segs` in `struct skb_gso_cb` occupies what was previously 2 bytes of padding within `skb-\u003ecb`. It is explicitly initialized to `min_t(unsigned int, max_segs, U16_MAX)` in `__skb_gso_segment()` and reset to 0 in newly created segment skbs in `skb_segment()`.\n2. All local variables touched or introduced (e.g., `resegment_max_segs = 0` in `validate_xmit_skb()`, stack buffer `_tcph` passed to `skb_header_pointer()`) are either initialized to 0 or populated before use.\n3. No data structures or padding bytes are exposed or copied to userspace; this logic operates purely inside the kernel's egress packet segmentation and device transmission pipeline.\n4. Any potential risks introduced by parsing TCP headers or splitting skb payloads into segments involve packet bounds and memory offsets, which are out-of-bounds or use-after-free concerns effectively monitored by standard KASAN and packet sanity checks rather than KMSAN uninitialized memory tracking.\n\nConsequently, there is no risk of uninitialized memory reads or information leaks justifying a dedicated KMSAN session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|