AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/08 00:51 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "hsr_create_tagged_frame",
    "prp_create_tagged_frame"
  ],
  "KMSANReasoning": "The patch fixes a logical bug where shared `skb` data is mutated by one egress path, corrupting the data for other clones (e.g., incorrect LAN ID or MAC address). The fix uses `skb_cow()` to ensure the `skb` data buffer is private before any modifications are made. This does not involve uninitialized memory, info-leaks, or complex structure allocations that might leave fields uninitialized. The bug is a shared mutable state issue, and the fix correctly privatizes the buffer. Standard testing and KASAN are sufficient for memory safety here; KMSAN is not applicable as there is no risk of uninitialized memory reads.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch fixes a bug in the HSR driver where per-egress writes could corrupt the original skb or other clones of it. It introduces `hsr_clone_private()` to ensure skb data is private before mutation. The changes affect core packet forwarding logic in the HSR driver, which is reachable and worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/08/08 00:51 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 1c860b44546d44296ed91bcc5d8779589d5e0c80\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Aug 8 00:51:56 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c\nindex 0774981a65c16..efcbf3cf26f9b 100644\n--- a/net/hsr/hsr_forward.c\n+++ b/net/hsr/hsr_forward.c\n@@ -325,8 +325,29 @@ static struct sk_buff *hsr_fill_tag(struct sk_buff *skb,\n \treturn skb;\n }\n \n-/* If the original frame was an HSR tagged frame, just clone it to be sent\n- * unchanged. Otherwise, create a private frame especially tagged for 'port'.\n+/* Clone an skb and make the clone's data private, so that per-egress\n+ * writes cannot corrupt the original skb or other clones of it.\n+ * Returns NULL on allocation failure.\n+ */\n+static struct sk_buff *hsr_clone_private(struct sk_buff *skb)\n+{\n+\tstruct sk_buff *clone;\n+\n+\tclone = skb_clone(skb, GFP_ATOMIC);\n+\tif (!clone)\n+\t\treturn NULL;\n+\tif (skb_cow(clone, 0)) {\n+\t\tkfree_skb(clone);\n+\t\treturn NULL;\n+\t}\n+\n+\treturn clone;\n+}\n+\n+/* If the original frame was an HSR tagged frame, return a private clone\n+ * of it with the path id updated for 'port'. Otherwise, return a private\n+ * clone for hardware tag insertion, or create a private frame especially\n+ * tagged for 'port'.\n  */\n struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,\n \t\t\t\t\tstruct hsr_port *port)\n@@ -336,14 +357,18 @@ struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,\n \tint movelen;\n \n \tif (frame-\u003eskb_hsr) {\n-\t\tstruct hsr_ethhdr *hsr_ethhdr =\n-\t\t\t(struct hsr_ethhdr *)skb_mac_header(frame-\u003eskb_hsr);\n+\t\tstruct hsr_ethhdr *hsr_ethhdr;\n+\n+\t\tskb = hsr_clone_private(frame-\u003eskb_hsr);\n+\t\tif (!skb)\n+\t\t\treturn NULL;\n \n \t\t/* set the lane id properly */\n+\t\thsr_ethhdr = (struct hsr_ethhdr *)skb_mac_header(skb);\n \t\thsr_set_path_id(frame, hsr_ethhdr, port);\n-\t\treturn skb_clone(frame-\u003eskb_hsr, GFP_ATOMIC);\n+\t\treturn skb;\n \t} else if (port-\u003edev-\u003efeatures \u0026 NETIF_F_HW_HSR_TAG_INS) {\n-\t\treturn skb_clone(frame-\u003eskb_std, GFP_ATOMIC);\n+\t\treturn hsr_clone_private(frame-\u003eskb_std);\n \t}\n \n \t/* Create the new skb with enough headroom to fit the HSR tag */\n@@ -377,17 +402,23 @@ struct sk_buff *prp_create_tagged_frame(struct hsr_frame_info *frame,\n \tstruct sk_buff *skb;\n \n \tif (frame-\u003eskb_prp) {\n-\t\tstruct prp_rct *trailer = skb_get_PRP_rct(frame-\u003eskb_prp);\n+\t\tstruct prp_rct *trailer;\n+\n+\t\tskb = hsr_clone_private(frame-\u003eskb_prp);\n+\t\tif (!skb)\n+\t\t\treturn NULL;\n \n+\t\ttrailer = skb_get_PRP_rct(skb);\n \t\tif (trailer) {\n \t\t\tprp_set_lan_id(trailer, port);\n \t\t} else {\n \t\t\tWARN_ONCE(!trailer, \"errored PRP skb\");\n+\t\t\tkfree_skb(skb);\n \t\t\treturn NULL;\n \t\t}\n-\t\treturn skb_clone(frame-\u003eskb_prp, GFP_ATOMIC);\n+\t\treturn skb;\n \t} else if (port-\u003edev-\u003efeatures \u0026 NETIF_F_HW_HSR_TAG_INS) {\n-\t\treturn skb_clone(frame-\u003eskb_std, GFP_ATOMIC);\n+\t\treturn hsr_clone_private(frame-\u003eskb_std);\n \t}\n \n \tskb = skb_copy_expand(frame-\u003eskb_std, skb_headroom(frame-\u003eskb_std),\n@@ -420,6 +451,22 @@ static void hsr_deliver_master(struct sk_buff *skb, struct net_device *dev,\n static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,\n \t\t    struct hsr_frame_info *frame)\n {\n+\t/* An interlink-bound skb from get_untagged_frame() can still alias\n+\t * another live consumer: for master-originated frames the clone\n+\t * shares the original TX skb (which taps or the TX path may still\n+\t * hold); for ring frames the master also consumes them when they\n+\t * are destined to the local node without being exclusive to it.\n+\t * Privatize before any address mutation.\n+\t */\n+\tif (port-\u003etype == HSR_PT_INTERLINK \u0026\u0026\n+\t    (frame-\u003eport_rcv-\u003etype == HSR_PT_MASTER ||\n+\t     (frame-\u003eis_local_dest \u0026\u0026 !frame-\u003eis_local_exclusive)) \u0026\u0026\n+\t    skb_cow(skb, 0)) {\n+\t\tframe-\u003eport_rcv-\u003edev-\u003estats.rx_dropped++;\n+\t\tkfree_skb(skb);\n+\t\treturn NET_XMIT_DROP;\n+\t}\n+\n \tif (frame-\u003eport_rcv-\u003etype == HSR_PT_MASTER) {\n \t\thsr_addr_subst_dest(frame-\u003enode_src, skb, port);\n \ndiff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile\nindex 31fb9326cf533..87fe34951b8b0 100644\n--- a/tools/testing/selftests/net/hsr/Makefile\n+++ b/tools/testing/selftests/net/hsr/Makefile\n@@ -5,6 +5,7 @@ top_srcdir = ../../../../..\n TEST_PROGS := \\\n \thsr_ping.sh \\\n \thsr_redbox.sh \\\n+\thsr_shared_mutation.sh \\\n \tlink_faults.sh \\\n \tprp_ping.sh \\\n # end of TEST_PROGS\ndiff --git a/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh b/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh\nnew file mode 100755\nindex 0000000000000..0b8b8791190d1\n--- /dev/null\n+++ b/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh\n@@ -0,0 +1,242 @@\n+#!/bin/bash\n+# SPDX-License-Identifier: GPL-2.0\n+#\n+# Verify that per-egress mutations of shared skb data are private:\n+#\n+# F2 (path/LAN ID): on an affected kernel the second slave's LAN-ID write\n+# lands in the first slave's still-queued clone; with a netem delay on\n+# slave A, injected frames leave A carrying B's LAN ID.\n+#\n+# F1 (RedBox source MAC): on an affected kernel an HSR-tagged multicast\n+# frame received on a RedBox slave is cloned for master and interlink,\n+# and the interlink's RedBox-MAC rewrite lands in the master clone's\n+# buffer, so the local stack receives the RedBox MAC instead of the\n+# originating node's MAC.\n+\n+ipv6=false\n+\n+source ./hsr_common.sh\n+\n+DUR=5\n+\n+require()\n+{\n+\tcommand -v \"$1\" \u003e/dev/null 2\u003e\u00261 \u0026\u0026 return 0\n+\techo \"SKIP: $1 not available\"\n+\texit $ksft_skip\n+}\n+\n+require ip\n+require tc\n+require python3\n+\n+trap cleanup_all_ns EXIT\n+\n+# ------------------------------------------------------- F2: LAN-ID isolation\n+# PRP DANP (proto 1), AF_PACKET pre-tagged injection, netem on slave A.\n+run_f2()\n+{\n+\tsetup_ns ns 2\u003e/dev/null || return $ksft_skip\n+\tnsx() { ip netns exec \"$ns\" \"$@\"; }\n+\n+\t# Probe sch_netem inside the disposable namespace only.\n+\tif ! nsx tc qdisc add dev lo root netem delay 1ms 2\u003e/dev/null; then\n+\t\techo \"SKIP: sch_netem not available\"\n+\t\treturn $ksft_skip\n+\tfi\n+\tnsx tc qdisc del dev lo root 2\u003e/dev/null\n+\n+\t# Capability probes end here; setup or runtime failure below is FAIL.\n+\tnsx ip link add vA type veth peer name vAp ||\n+\t\t{ echo \"FAIL: veth A\"; return 1; }\n+\tnsx ip link add vB type veth peer name vBp ||\n+\t\t{ echo \"FAIL: veth B\"; return 1; }\n+\tfor i in vA vB vAp vBp; do\n+\t\tnsx ip link set \"$i\" up || { echo \"FAIL: $i up\"; return 1; }\n+\tdone\n+\tif ! nsx ip link add name prp0 type hsr slave1 vA slave2 vB \\\n+\t\tsupervision 45 proto 1 2\u003e/dev/null; then\n+\t\techo \"SKIP: HSR/PRP not supported by this kernel\"\n+\t\treturn $ksft_skip\n+\tfi\n+\tnsx ip link set prp0 up || { echo \"FAIL: prp0 up\"; return 1; }\n+\tnsx tc qdisc add dev vA root netem delay 200ms ||\n+\t\t{ echo \"FAIL: netem\"; return 1; }\n+\n+\tnsx python3 /dev/stdin \"$DUR\" \u003c\u003c'PYF2'\n+import socket, struct, select, sys, time\n+\n+dur = int(sys.argv[1])\n+def lanid(pkt):\n+    if len(pkt) \u003c 20 or pkt[-2:] != b\"\\x88\\xfb\":\n+        return None\n+    return (pkt[-4] \u003e\u003e 4) \u0026 0xF\n+\n+SRC = bytes.fromhex(open(\"/sys/class/net/prp0/address\").read().replace(\":\", \"\"))\n+DST = bytes.fromhex(\"02aabbccdd01\")\n+PAY = bytes(range(46))\n+rct0 = struct.pack(\"\u003eH\", 0) + struct.pack(\"\u003eH\", 52 \u0026 0x0FFF) + b\"\\x88\\xfb\"\n+frame = DST + SRC + b\"\\x08\\x00\" + PAY + rct0\n+\n+tx = socket.socket(socket.AF_PACKET, socket.SOCK_RAW); tx.bind((\"prp0\", 0))\n+sA = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,\n+        socket.ntohs(0x0003))\n+sA.bind((\"vAp\", 0))\n+sB = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,\n+        socket.ntohs(0x0003))\n+sB.bind((\"vBp\", 0))\n+sA.setblocking(False); sB.setblocking(False)\n+\n+a, b = [], []\n+\n+def match(pkt):\n+    return (pkt[:6] == DST and pkt[6:12] == SRC\n+            and pkt[12:14] == b\"\\x08\\x00\" and pkt[14:14 + len(PAY)] == PAY)\n+\n+def drain(timeout):\n+    wait = timeout\n+    while True:\n+        r, _, _ = select.select([sA, sB], [], [], wait)\n+        if not r:\n+            return\n+        for s in r:\n+            pkt = s.recv(65535)\n+            if not match(pkt):\n+                continue\n+            lid = lanid(pkt)\n+            if lid is not None:\n+                (a if s is sA else b).append(lid)\n+        wait = 0\n+\n+# Drain while sending so the burst cannot overflow the capture sockets'\n+# receive buffers; the netem-delayed A-side frames arrive afterwards and\n+# are collected below.\n+for _ in range(200):\n+    tx.send(frame)\n+    drain(0)\n+    time.sleep(0.001)\n+\n+end = time.time() + dur\n+while time.time() \u003c end:\n+    drain(0.3)\n+\n+print(\"A-side count=%d lan ids=%s\" % (len(a), sorted(set(a))))\n+print(\"B-side count=%d lan ids=%s\" % (len(b), sorted(set(b))))\n+if len(a) \u003c 150 or len(b) \u003c 150:\n+    print(\"FAIL: too few injected frames captured (A=%d B=%d, sent 200)\"\n+          % (len(a), len(b)))\n+    sys.exit(1)\n+bad_a = [x for x in a if (x \u0026 1) != 0]\n+bad_b = [x for x in b if (x \u0026 1) != 1]\n+if bad_a or bad_b:\n+    print(\"FAIL: shared-mutation corruption - A: %d/%d wrong-lan,\"\n+          \" B: %d/%d wrong-lan\"\n+          % (len(bad_a), len(a), len(bad_b), len(b)))\n+    sys.exit(1)\n+print(\"PASS: per-egress LAN IDs isolated (A all bit0=0, B all bit0=1)\")\n+sys.exit(0)\n+PYF2\n+}\n+\n+# --------------------------------------------- F1: RedBox source-MAC privacy\n+# HSR RedBox (proto 0), tagged multicast from a slave: master must keep\n+# the node MAC, interlink must carry the RedBox MAC.\n+run_f1()\n+{\n+\tsetup_ns ns 2\u003e/dev/null || return $ksft_skip\n+\tnsx() { ip netns exec \"$ns\" \"$@\"; }\n+\n+\tnsx ip link add vA type veth peer name vAp ||\n+\t\t{ echo \"FAIL: veth A\"; return 1; }\n+\tnsx ip link add vB type veth peer name vBp ||\n+\t\t{ echo \"FAIL: veth B\"; return 1; }\n+\tnsx ip link add vI type veth peer name vIp ||\n+\t\t{ echo \"FAIL: veth I\"; return 1; }\n+\tfor i in vA vB vI vAp vBp vIp; do\n+\t\tnsx ip link set \"$i\" up || { echo \"FAIL: $i up\"; return 1; }\n+\tdone\n+\tif ! nsx ip link add name hsr0 type hsr slave1 vA slave2 vB \\\n+\t\tinterlink vI supervision 45 proto 0 2\u003e/dev/null; then\n+\t\techo \"SKIP: HSR RedBox not supported by this kernel\"\n+\t\treturn $ksft_skip\n+\tfi\n+\tnsx ip link set hsr0 up || { echo \"FAIL: hsr0 up\"; return 1; }\n+\n+\tnsx python3 /dev/stdin \u003c\u003c'PYF1'\n+import socket, select, sys, time\n+\n+NODE  = bytes.fromhex(\"021122334455\")\n+MCAST = bytes.fromhex(\"01005e000001\")\n+RB    = bytes.fromhex(open(\"/sys/class/net/vI/address\").read().replace(\":\", \"\"))\n+PAY   = bytes(range(46))\n+\n+def frame(seq):\n+    # LSDU size = payload + HSR tag (HSR_HLEN), as hsr_fill_tag() computes it\n+    tag = (((1 \u003c\u003c 12) | (len(PAY) + 6)).to_bytes(2, \"big\")\n+           + seq.to_bytes(2, \"big\") + b\"\\x08\\x00\")\n+    return MCAST + NODE + b\"\\x89\\x2f\" + tag + PAY\n+\n+tx = socket.socket(socket.AF_PACKET, socket.SOCK_RAW); tx.bind((\"vAp\", 0))\n+sm = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,\n+        socket.ntohs(0x0003))\n+sm.bind((\"hsr0\", 0))\n+si = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,\n+        socket.ntohs(0x0003))\n+si.bind((\"vIp\", 0))\n+sm.setblocking(False); si.setblocking(False)\n+\n+for i in range(3):\n+    tx.send(frame(i + 1)); time.sleep(0.05)\n+\n+m_src = i_src = None\n+end = time.time() + 4\n+while time.time() \u003c end and (m_src is None or i_src is None):\n+    r, _, _ = select.select([sm, si], [], [], 0.3)\n+    for s in r:\n+        pkt = s.recv(65535)\n+        # exact flow: dst, post-strip EtherType, exact payload, min length;\n+        # h_source is the asserted value and must NOT be filtered on\n+        if (len(pkt) \u003c 60 or pkt[:6] != MCAST or pkt[12:14] != b\"\\x08\\x00\"\n+                or pkt[14:14 + len(PAY)] != PAY):\n+            continue\n+        if s is sm and m_src is None:\n+            m_src = pkt[6:12]\n+        elif s is si and i_src is None:\n+            i_src = pkt[6:12]\n+\n+print(\"master h_source    =\", m_src.hex() if m_src else None)\n+print(\"node MAC           =\", NODE.hex())\n+print(\"interlink h_source =\", i_src.hex() if i_src else None)\n+print(\"redbox MAC         =\", RB.hex())\n+if i_src != RB:\n+    print(\"FAIL: interlink did not carry the RedBox MAC\")\n+    sys.exit(1)\n+if m_src != NODE:\n+    print(\"FAIL: master received %s instead of the node MAC \"\n+          \"(shared-mutation corruption)\"\n+          % (m_src.hex() if m_src else \"nothing\"))\n+    sys.exit(1)\n+print(\"PASS: master kept node MAC, interlink kept RedBox MAC\")\n+sys.exit(0)\n+PYF1\n+}\n+\n+rc=0\n+\n+run_f2\n+ret=$?\n+rc=$(ksft_status_merge \"$rc\" \"$ret\")\n+\n+run_f1\n+ret=$?\n+rc=$(ksft_status_merge \"$rc\" \"$ret\")\n+\n+if [ \"$rc\" -eq 0 ]; then\n+\techo \"hsr_shared_mutation: per-egress mutation isolation (F1+F2) [ OK ]\"\n+elif [ \"$rc\" -eq \"$ksft_skip\" ]; then\n+\techo \"hsr_shared_mutation: subtests skipped (capabilities missing)\"\n+else\n+\techo \"hsr_shared_mutation: per-egress mutation isolation [ FAIL ]\" \\\n+\t\t\"rc=$rc\" 1\u003e\u00262\n+fi\n+exit \"$rc\"\n"
}

2/1 2026/08/08 00:51 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "hsr_create_tagged_frame",
    "prp_create_tagged_frame"
  ],
  "Reasoning": "The patch fixes a bug in the HSR driver where per-egress writes could corrupt the original skb or other clones of it. It introduces `hsr_clone_private()` to ensure skb data is private before mutation. The changes affect core packet forwarding logic in the HSR driver, which is reachable and worth fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a '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:
For your convenience, here is the diff of the changes:
commit 1c860b44546d44296ed91bcc5d8779589d5e0c80
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Aug 8 00:51:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a65c16..efcbf3cf26f9b 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -325,8 +325,29 @@ static struct sk_buff *hsr_fill_tag(struct sk_buff *skb,
 	return skb;
 }
 
-/* If the original frame was an HSR tagged frame, just clone it to be sent
- * unchanged. Otherwise, create a private frame especially tagged for 'port'.
+/* Clone an skb and make the clone's data private, so that per-egress
+ * writes cannot corrupt the original skb or other clones of it.
+ * Returns NULL on allocation failure.
+ */
+static struct sk_buff *hsr_clone_private(struct sk_buff *skb)
+{
+	struct sk_buff *clone;
+
+	clone = skb_clone(skb, GFP_ATOMIC);
+	if (!clone)
+		return NULL;
+	if (skb_cow(clone, 0)) {
+		kfree_skb(clone);
+		return NULL;
+	}
+
+	return clone;
+}
+
+/* If the original frame was an HSR tagged frame, return a private clone
+ * of it with the path id updated for 'port'. Otherwise, return a private
+ * clone for hardware tag insertion, or create a private frame especially
+ * tagged for 'port'.
  */
 struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,
 					struct hsr_port *port)
@@ -336,14 +357,18 @@ struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,
 	int movelen;
 
 	if (frame->skb_hsr) {
-		struct hsr_ethhdr *hsr_ethhdr =
-			(struct hsr_ethhdr *)skb_mac_header(frame->skb_hsr);
+		struct hsr_ethhdr *hsr_ethhdr;
+
+		skb = hsr_clone_private(frame->skb_hsr);
+		if (!skb)
+			return NULL;
 
 		/* set the lane id properly */
+		hsr_ethhdr = (struct hsr_ethhdr *)skb_mac_header(skb);
 		hsr_set_path_id(frame, hsr_ethhdr, port);
-		return skb_clone(frame->skb_hsr, GFP_ATOMIC);
+		return skb;
 	} else if (port->dev->features & NETIF_F_HW_HSR_TAG_INS) {
-		return skb_clone(frame->skb_std, GFP_ATOMIC);
+		return hsr_clone_private(frame->skb_std);
 	}
 
 	/* Create the new skb with enough headroom to fit the HSR tag */
@@ -377,17 +402,23 @@ struct sk_buff *prp_create_tagged_frame(struct hsr_frame_info *frame,
 	struct sk_buff *skb;
 
 	if (frame->skb_prp) {
-		struct prp_rct *trailer = skb_get_PRP_rct(frame->skb_prp);
+		struct prp_rct *trailer;
+
+		skb = hsr_clone_private(frame->skb_prp);
+		if (!skb)
+			return NULL;
 
+		trailer = skb_get_PRP_rct(skb);
 		if (trailer) {
 			prp_set_lan_id(trailer, port);
 		} else {
 			WARN_ONCE(!trailer, "errored PRP skb");
+			kfree_skb(skb);
 			return NULL;
 		}
-		return skb_clone(frame->skb_prp, GFP_ATOMIC);
+		return skb;
 	} else if (port->dev->features & NETIF_F_HW_HSR_TAG_INS) {
-		return skb_clone(frame->skb_std, GFP_ATOMIC);
+		return hsr_clone_private(frame->skb_std);
 	}
 
 	skb = skb_copy_expand(frame->skb_std, skb_headroom(frame->skb_std),
@@ -420,6 +451,22 @@ static void hsr_deliver_master(struct sk_buff *skb, struct net_device *dev,
 static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,
 		    struct hsr_frame_info *frame)
 {
+	/* An interlink-bound skb from get_untagged_frame() can still alias
+	 * another live consumer: for master-originated frames the clone
+	 * shares the original TX skb (which taps or the TX path may still
+	 * hold); for ring frames the master also consumes them when they
+	 * are destined to the local node without being exclusive to it.
+	 * Privatize before any address mutation.
+	 */
+	if (port->type == HSR_PT_INTERLINK &&
+	    (frame->port_rcv->type == HSR_PT_MASTER ||
+	     (frame->is_local_dest && !frame->is_local_exclusive)) &&
+	    skb_cow(skb, 0)) {
+		frame->port_rcv->dev->stats.rx_dropped++;
+		kfree_skb(skb);
+		return NET_XMIT_DROP;
+	}
+
 	if (frame->port_rcv->type == HSR_PT_MASTER) {
 		hsr_addr_subst_dest(frame->node_src, skb, port);
 
diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile
index 31fb9326cf533..87fe34951b8b0 100644
--- a/tools/testing/selftests/net/hsr/Makefile
+++ b/tools/testing/selftests/net/hsr/Makefile
@@ -5,6 +5,7 @@ top_srcdir = ../../../../..
 TEST_PROGS := \
 	hsr_ping.sh \
 	hsr_redbox.sh \
+	hsr_shared_mutation.sh \
 	link_faults.sh \
 	prp_ping.sh \
 # end of TEST_PROGS
diff --git a/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh b/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh
new file mode 100755
index 0000000000000..0b8b8791190d1
--- /dev/null
+++ b/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh
@@ -0,0 +1,242 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Verify that per-egress mutations of shared skb data are private:
+#
+# F2 (path/LAN ID): on an affected kernel the second slave's LAN-ID write
+# lands in the first slave's still-queued clone; with a netem delay on
+# slave A, injected frames leave A carrying B's LAN ID.
+#
+# F1 (RedBox source MAC): on an affected kernel an HSR-tagged multicast
+# frame received on a RedBox slave is cloned for master and interlink,
+# and the interlink's RedBox-MAC rewrite lands in the master clone's
+# buffer, so the local stack receives the RedBox MAC instead of the
+# originating node's MAC.
+
+ipv6=false
+
+source ./hsr_common.sh
+
+DUR=5
+
+require()
+{
+	command -v "$1" >/dev/null 2>&1 && return 0
+	echo "SKIP: $1 not available"
+	exit $ksft_skip
+}
+
+require ip
+require tc
+require python3
+
+trap cleanup_all_ns EXIT
+
+# ------------------------------------------------------- F2: LAN-ID isolation
+# PRP DANP (proto 1), AF_PACKET pre-tagged injection, netem on slave A.
+run_f2()
+{
+	setup_ns ns 2>/dev/null || return $ksft_skip
+	nsx() { ip netns exec "$ns" "$@"; }
+
+	# Probe sch_netem inside the disposable namespace only.
+	if ! nsx tc qdisc add dev lo root netem delay 1ms 2>/dev/null; then
+		echo "SKIP: sch_netem not available"
+		return $ksft_skip
+	fi
+	nsx tc qdisc del dev lo root 2>/dev/null
+
+	# Capability probes end here; setup or runtime failure below is FAIL.
+	nsx ip link add vA type veth peer name vAp ||
+		{ echo "FAIL: veth A"; return 1; }
+	nsx ip link add vB type veth peer name vBp ||
+		{ echo "FAIL: veth B"; return 1; }
+	for i in vA vB vAp vBp; do
+		nsx ip link set "$i" up || { echo "FAIL: $i up"; return 1; }
+	done
+	if ! nsx ip link add name prp0 type hsr slave1 vA slave2 vB \
+		supervision 45 proto 1 2>/dev/null; then
+		echo "SKIP: HSR/PRP not supported by this kernel"
+		return $ksft_skip
+	fi
+	nsx ip link set prp0 up || { echo "FAIL: prp0 up"; return 1; }
+	nsx tc qdisc add dev vA root netem delay 200ms ||
+		{ echo "FAIL: netem"; return 1; }
+
+	nsx python3 /dev/stdin "$DUR" <<'PYF2'
+import socket, struct, select, sys, time
+
+dur = int(sys.argv[1])
+def lanid(pkt):
+    if len(pkt) < 20 or pkt[-2:] != b"\x88\xfb":
+        return None
+    return (pkt[-4] >> 4) & 0xF
+
+SRC = bytes.fromhex(open("/sys/class/net/prp0/address").read().replace(":", ""))
+DST = bytes.fromhex("02aabbccdd01")
+PAY = bytes(range(46))
+rct0 = struct.pack(">H", 0) + struct.pack(">H", 52 & 0x0FFF) + b"\x88\xfb"
+frame = DST + SRC + b"\x08\x00" + PAY + rct0
+
+tx = socket.socket(socket.AF_PACKET, socket.SOCK_RAW); tx.bind(("prp0", 0))
+sA = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+sA.bind(("vAp", 0))
+sB = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+sB.bind(("vBp", 0))
+sA.setblocking(False); sB.setblocking(False)
+
+a, b = [], []
+
+def match(pkt):
+    return (pkt[:6] == DST and pkt[6:12] == SRC
+            and pkt[12:14] == b"\x08\x00" and pkt[14:14 + len(PAY)] == PAY)
+
+def drain(timeout):
+    wait = timeout
+    while True:
+        r, _, _ = select.select([sA, sB], [], [], wait)
+        if not r:
+            return
+        for s in r:
+            pkt = s.recv(65535)
+            if not match(pkt):
+                continue
+            lid = lanid(pkt)
+            if lid is not None:
+                (a if s is sA else b).append(lid)
+        wait = 0
+
+# Drain while sending so the burst cannot overflow the capture sockets'
+# receive buffers; the netem-delayed A-side frames arrive afterwards and
+# are collected below.
+for _ in range(200):
+    tx.send(frame)
+    drain(0)
+    time.sleep(0.001)
+
+end = time.time() + dur
+while time.time() < end:
+    drain(0.3)
+
+print("A-side count=%d lan ids=%s" % (len(a), sorted(set(a))))
+print("B-side count=%d lan ids=%s" % (len(b), sorted(set(b))))
+if len(a) < 150 or len(b) < 150:
+    print("FAIL: too few injected frames captured (A=%d B=%d, sent 200)"
+          % (len(a), len(b)))
+    sys.exit(1)
+bad_a = [x for x in a if (x & 1) != 0]
+bad_b = [x for x in b if (x & 1) != 1]
+if bad_a or bad_b:
+    print("FAIL: shared-mutation corruption - A: %d/%d wrong-lan,"
+          " B: %d/%d wrong-lan"
+          % (len(bad_a), len(a), len(bad_b), len(b)))
+    sys.exit(1)
+print("PASS: per-egress LAN IDs isolated (A all bit0=0, B all bit0=1)")
+sys.exit(0)
+PYF2
+}
+
+# --------------------------------------------- F1: RedBox source-MAC privacy
+# HSR RedBox (proto 0), tagged multicast from a slave: master must keep
+# the node MAC, interlink must carry the RedBox MAC.
+run_f1()
+{
+	setup_ns ns 2>/dev/null || return $ksft_skip
+	nsx() { ip netns exec "$ns" "$@"; }
+
+	nsx ip link add vA type veth peer name vAp ||
+		{ echo "FAIL: veth A"; return 1; }
+	nsx ip link add vB type veth peer name vBp ||
+		{ echo "FAIL: veth B"; return 1; }
+	nsx ip link add vI type veth peer name vIp ||
+		{ echo "FAIL: veth I"; return 1; }
+	for i in vA vB vI vAp vBp vIp; do
+		nsx ip link set "$i" up || { echo "FAIL: $i up"; return 1; }
+	done
+	if ! nsx ip link add name hsr0 type hsr slave1 vA slave2 vB \
+		interlink vI supervision 45 proto 0 2>/dev/null; then
+		echo "SKIP: HSR RedBox not supported by this kernel"
+		return $ksft_skip
+	fi
+	nsx ip link set hsr0 up || { echo "FAIL: hsr0 up"; return 1; }
+
+	nsx python3 /dev/stdin <<'PYF1'
+import socket, select, sys, time
+
+NODE  = bytes.fromhex("021122334455")
+MCAST = bytes.fromhex("01005e000001")
+RB    = bytes.fromhex(open("/sys/class/net/vI/address").read().replace(":", ""))
+PAY   = bytes(range(46))
+
+def frame(seq):
+    # LSDU size = payload + HSR tag (HSR_HLEN), as hsr_fill_tag() computes it
+    tag = (((1 << 12) | (len(PAY) + 6)).to_bytes(2, "big")
+           + seq.to_bytes(2, "big") + b"\x08\x00")
+    return MCAST + NODE + b"\x89\x2f" + tag + PAY
+
+tx = socket.socket(socket.AF_PACKET, socket.SOCK_RAW); tx.bind(("vAp", 0))
+sm = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+sm.bind(("hsr0", 0))
+si = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+si.bind(("vIp", 0))
+sm.setblocking(False); si.setblocking(False)
+
+for i in range(3):
+    tx.send(frame(i + 1)); time.sleep(0.05)
+
+m_src = i_src = None
+end = time.time() + 4
+while time.time() < end and (m_src is None or i_src is None):
+    r, _, _ = select.select([sm, si], [], [], 0.3)
+    for s in r:
+        pkt = s.recv(65535)
+        # exact flow: dst, post-strip EtherType, exact payload, min length;
+        # h_source is the asserted value and must NOT be filtered on
+        if (len(pkt) < 60 or pkt[:6] != MCAST or pkt[12:14] != b"\x08\x00"
+                or pkt[14:14 + len(PAY)] != PAY):
+            continue
+        if s is sm and m_src is None:
+            m_src = pkt[6:12]
+        elif s is si and i_src is None:
+            i_src = pkt[6:12]
+
+print("master h_source    =", m_src.hex() if m_src else None)
+print("node MAC           =", NODE.hex())
+print("interlink h_source =", i_src.hex() if i_src else None)
+print("redbox MAC         =", RB.hex())
+if i_src != RB:
+    print("FAIL: interlink did not carry the RedBox MAC")
+    sys.exit(1)
+if m_src != NODE:
+    print("FAIL: master received %s instead of the node MAC "
+          "(shared-mutation corruption)"
+          % (m_src.hex() if m_src else "nothing"))
+    sys.exit(1)
+print("PASS: master kept node MAC, interlink kept RedBox MAC")
+sys.exit(0)
+PYF1
+}
+
+rc=0
+
+run_f2
+ret=$?
+rc=$(ksft_status_merge "$rc" "$ret")
+
+run_f1
+ret=$?
+rc=$(ksft_status_merge "$rc" "$ret")
+
+if [ "$rc" -eq 0 ]; then
+	echo "hsr_shared_mutation: per-egress mutation isolation (F1+F2) [ OK ]"
+elif [ "$rc" -eq "$ksft_skip" ]; then
+	echo "hsr_shared_mutation: subtests skipped (capabilities missing)"
+else
+	echo "hsr_shared_mutation: per-egress mutation isolation [ FAIL ]" \
+		"rc=$rc" 1>&2
+fi
+exit "$rc"

3/2 2026/08/08 00:51 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 6803
							output: 20
							thoughts: 185
						

Thoughts:
**Analyzing the HSR Driver Patch**

Okay, so I'm looking at this patch for the HSR driver, specifically in `net/hsr/hsr_forward.c`. It's introducing a critical change; it's using a new function, `hsr_clone_private()`, which does a copy-on-write (`skb_cow()`) of the SKB, effectively making the data private.  The implications here are significant. It's replacing the standard `skb_clone()` calls in `hsr_create_tagged_frame()` and `prp_create_tagged_frame()` with this copy-on-write version.

The rationale is clear – and crucial. This is about preventing data corruption. With `skb_clone()`, any modifications to the cloned SKB would affect the original and any other clones, particularly during per-egress writes, and that's a recipe for disaster in a high-availability system. This update in `hsr_xmit()` is particularly interesting. The patch is ensuring the SKB is privatized *before* any address modification for interlink-bound SKBs. That means the copy-on-write happens upfront to guard against exactly this type of concurrent modification issue.

Essentially, this patch is addressing a race condition, a classic problem in network drivers where multiple parts of the system are trying to write to the same buffer. This is a solid approach to fixing that. Now, I need to dive into the code itself, analyze the function implementations, and also make sure the Kconfig makes sense, confirming the correct configuration options are set.  Let's see the details.



4/2 2026/08/08 00:52 tool grepper
0m Args:
{
  "Expression": "hsr_clone_private"
}

Results:
{
  "Output": "net/hsr/hsr_forward.c=288=static struct sk_buff *hsr_fill_tag(struct sk_buff *skb,\n--\nnet/hsr/hsr_forward.c-331- */\nnet/hsr/hsr_forward.c:332:static struct sk_buff *hsr_clone_private(struct sk_buff *skb)\nnet/hsr/hsr_forward.c-333-{\n--\nnet/hsr/hsr_forward.c=352=struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,\n--\nnet/hsr/hsr_forward.c-361-\nnet/hsr/hsr_forward.c:362:\t\tskb = hsr_clone_private(frame-\u003eskb_hsr);\nnet/hsr/hsr_forward.c-363-\t\tif (!skb)\n--\nnet/hsr/hsr_forward.c-370-\t} else if (port-\u003edev-\u003efeatures \u0026 NETIF_F_HW_HSR_TAG_INS) {\nnet/hsr/hsr_forward.c:371:\t\treturn hsr_clone_private(frame-\u003eskb_std);\nnet/hsr/hsr_forward.c-372-\t}\n--\nnet/hsr/hsr_forward.c=399=struct sk_buff *prp_create_tagged_frame(struct hsr_frame_info *frame,\n--\nnet/hsr/hsr_forward.c-406-\nnet/hsr/hsr_forward.c:407:\t\tskb = hsr_clone_private(frame-\u003eskb_prp);\nnet/hsr/hsr_forward.c-408-\t\tif (!skb)\n--\nnet/hsr/hsr_forward.c-420-\t} else if (port-\u003edev-\u003efeatures \u0026 NETIF_F_HW_HSR_TAG_INS) {\nnet/hsr/hsr_forward.c:421:\t\treturn hsr_clone_private(frame-\u003eskb_std);\nnet/hsr/hsr_forward.c-422-\t}\n"
}

5/2 2026/08/08 00:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 7917
							output: 17
							thoughts: 29
						

Thoughts:
Here's my take on the changes and where I should focus my attention:

**Navigating the HSR Implementation**

Okay, so the modifications are definitely within `net/hsr/hsr_forward.c`. That's the core forwarding logic for High-availability Seamless Redundancy (HSR), so it's the right place to begin. Before diving deep into the code itself, I should probably check the Kconfig configuration options for HSR. I need to understand how this feature is enabled, potentially what dependencies it has, and how I can control it in the build. Knowing the Kconfig options will quickly illuminate the bigger picture. This will help frame the changes within the overall system design.



6/2 2026/08/08 00:52 tool grepper
0m Args:
{
  "Expression": "config HSR"
}

Results:
{
  "Output": "net/hsr/Kconfig-5-\nnet/hsr/Kconfig:6:config HSR\nnet/hsr/Kconfig-7-\ttristate \"High-availability Seamless Redundancy (HSR \u0026 PRP)\"\n"
}

7/2 2026/08/08 00:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 8177
							output: 22
							thoughts: 71
						

Thoughts:
Here's my analysis of the situation:

**Fuzzing Focus: Initial Assessment**

Okay, so the current configuration is `HSR`. That's the baseline. My immediate thought is, let's nail down where we're going to direct our fuzzing efforts. The system's telling me about these modified functions: `hsr_create_tagged_frame`, `prp_create_tagged_frame`, and `hsr_xmit`. Right, those are our targets. Now, before I dive deep into crafting test cases, I need to understand the scope and potential for this. Are these global, or are they static within a specific module? The static/non-static nature of these functions will influence how I approach vulnerability discovery and exploit design.



8/2 2026/08/08 00:52 tool grepper
0m Args:
{
  "Expression": "hsr_create_tagged_frame"
}

Results:
{
  "Output": "net/hsr/hsr_device.c=621=static const struct hsr_proto_ops hsr_ops = {\nnet/hsr/hsr_device.c-622-\t.send_sv_frame = send_hsr_supervision_frame,\nnet/hsr/hsr_device.c:623:\t.create_tagged_frame = hsr_create_tagged_frame,\nnet/hsr/hsr_device.c-624-\t.get_untagged_frame = hsr_get_untagged_frame,\n--\nnet/hsr/hsr_forward.c=332=static struct sk_buff *hsr_clone_private(struct sk_buff *skb)\n--\nnet/hsr/hsr_forward.c-351- */\nnet/hsr/hsr_forward.c:352:struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,\nnet/hsr/hsr_forward.c-353-\t\t\t\t\tstruct hsr_port *port)\n--\nnet/hsr/hsr_forward.h=17=struct sk_buff *prp_create_tagged_frame(struct hsr_frame_info *frame,\nnet/hsr/hsr_forward.h-18-\t\t\t\t\tstruct hsr_port *port);\nnet/hsr/hsr_forward.h:19:struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,\nnet/hsr/hsr_forward.h-20-\t\t\t\t\tstruct hsr_port *port);\n"
}

9/2 2026/08/08 00:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 8842
							output: 19
							thoughts: 42
						

Thoughts:
Okay, here's my interpretation of those code snippets, summarized in the first person, as you requested:

**Assessing Function Scope**

Alright, let me quickly analyze these function declarations. It's immediately apparent that `hsr_create_tagged_frame` and `prp_create_tagged_frame` are declared as non-static. This likely indicates that they are intended to be accessible outside of the current compilation unit, probably within a larger module or project. They are designed to be callable from elsewhere, likely as part of some public API or interface. However, the use case of `hsr_xmit` is slightly different. The `static` keyword suggests it's scoped to the current file or translation unit. So, `hsr_xmit` is clearly an internal function, probably an implementation detail, not meant for external use. It's likely involved in handling some data transmission process, but the implementation is kept hidden, potentially to allow for future internal changes without breaking external dependencies. Understanding the static/non-static distinction is crucial for proper module design and code maintainability, especially for an experienced developer like myself.



10/2 2026/08/08 00:52 tool grepper
0m Args:
{
  "Expression": "hsr_xmit"
}

Results:
{
  "Output": "net/hsr/hsr_forward.c=430=static void hsr_deliver_master(struct sk_buff *skb, struct net_device *dev,\n--\nnet/hsr/hsr_forward.c-450-\nnet/hsr/hsr_forward.c:451:static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,\nnet/hsr/hsr_forward.c-452-\t\t    struct hsr_frame_info *frame)\n--\nnet/hsr/hsr_forward.c=566=static void hsr_forward_do(struct hsr_frame_info *frame)\n--\nnet/hsr/hsr_forward.c-627-\t\t} else {\nnet/hsr/hsr_forward.c:628:\t\t\tif (!hsr_xmit(skb, port, frame))\nnet/hsr/hsr_forward.c-629-\t\t\t\tif (port-\u003etype == HSR_PT_SLAVE_A ||\n"
}

11/2 2026/08/08 00:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 9411
							output: 21
							thoughts: 43
						

Thoughts:
Okay, here's my interpretation of that code structure, presented as if it were my thought process:

**Diving into the HSR Transmission Logic**

Alright, let's break this down. From what I'm seeing, `hsr_xmit` is a static function. That means it's internal to this compilation unit, not meant for external use. It's only called from within another function within the same file, which is `hsr_forward_do`. Okay, that gives me a clear idea of its purpose: it's likely responsible for the actual *transmission* of a High-availability Seamless Redundancy (HSR) frame, likely the lower-level sending function.

Now, `hsr_forward_do` itself is also static. So it's similar; also hidden away from external calls. It seems like it's the next level up, the function that *does* the HSR forwarding, and I'm guessing some sort of pre-processing before it hands it off to the actual transmission function. And the key is that it's called from `hsr_forward_skb`, so this means that an incoming SKB is going to get handled here.
Okay, so the workflow appears to be: an incoming packet (`skb`) goes to `hsr_forward_skb`, which then triggers `hsr_forward_do`, which preps the frame, and finally calls `hsr_xmit` to send it out. That setup indicates a deliberate encapsulation of the HSR forwarding process, allowing for potential internal optimizations or changes to the forwarding logic without affecting the external interface. It's a clean way to manage the transmission path.

So, in short, I'm inferring this flow: `hsr_forward_skb` -> `hsr_forward_do` -> `hsr_xmit`. Each piece is neatly tucked away, likely for good code structure.



12/2 2026/08/08 00:52 tool grepper
0m Args:
{
  "Expression": "hsr_forward_skb"
}

Results:
{
  "Output": "net/hsr/hsr_device.c=224=static netdev_tx_t hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n--\nnet/hsr/hsr_device.c-235-\t\tspin_lock_bh(\u0026hsr-\u003eseqnr_lock);\nnet/hsr/hsr_device.c:236:\t\thsr_forward_skb(skb, master);\nnet/hsr/hsr_device.c-237-\t\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n--\nnet/hsr/hsr_device.c=293=static void send_hsr_supervision_frame(struct hsr_port *port,\n--\nnet/hsr/hsr_device.c-363-\nnet/hsr/hsr_device.c:364:\thsr_forward_skb(skb, port);\nnet/hsr/hsr_device.c-365-\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n--\nnet/hsr/hsr_device.c=369=static void send_prp_supervision_frame(struct hsr_port *master,\n--\nnet/hsr/hsr_device.c-404-\nnet/hsr/hsr_device.c:405:\thsr_forward_skb(skb, master);\nnet/hsr/hsr_device.c-406-\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\n--\nnet/hsr/hsr_forward.c=723=static int fill_frame_info(struct hsr_frame_info *frame,\n--\nnet/hsr/hsr_forward.c-780-/* Must be called holding rcu read lock (because of the port parameter) */\nnet/hsr/hsr_forward.c:781:void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)\nnet/hsr/hsr_forward.c-782-{\n--\nnet/hsr/hsr_forward.h-15-\nnet/hsr/hsr_forward.h:16:void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port);\nnet/hsr/hsr_forward.h-17-struct sk_buff *prp_create_tagged_frame(struct hsr_frame_info *frame,\n--\nnet/hsr/hsr_slave.c=24=static rx_handler_result_t hsr_handle_frame(struct sk_buff **pskb)\n--\nnet/hsr/hsr_slave.c-80-\t\tspin_lock_bh(\u0026hsr-\u003eseqnr_lock);\nnet/hsr/hsr_slave.c:81:\t\thsr_forward_skb(skb, port);\nnet/hsr/hsr_slave.c-82-\t\tspin_unlock_bh(\u0026hsr-\u003eseqnr_lock);\nnet/hsr/hsr_slave.c-83-\t} else {\nnet/hsr/hsr_slave.c:84:\t\thsr_forward_skb(skb, port);\nnet/hsr/hsr_slave.c-85-\t}\n"
}

13/2 2026/08/08 00:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 10718
							output: 17
							thoughts: 65
						

Thoughts:
Here's my summary, as if I were thinking it:

**Focusing In on the HSR Code**

Okay, so I'm digging into this HSR implementation. The `hsr_forward_skb` function is non-static, which is interesting. That means it's likely called from somewhere outside the immediate HSR module itself, making it a good jumping-off point for analysis. It, along with `hsr_create_tagged_frame` and `prp_create_tagged_frame`, seem like key symbols to concentrate on. They're probably core to the packet processing and tagging. My next step will be to figure out if there are any Kconfig dependencies for HSR. Understanding the build configuration will help determine if these functions are always available or conditional on some other feature being enabled. This gives me context.



14/2 2026/08/08 00:52 tool grepper
0m Args:
{
  "Expression": "config HSR"
}

Results:
{
  "Output": "net/hsr/Kconfig-5-\nnet/hsr/Kconfig:6:config HSR\nnet/hsr/Kconfig-7-\ttristate \"High-availability Seamless Redundancy (HSR \u0026 PRP)\"\n"
}

15/2 2026/08/08 00:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11044
							output: 33
							thoughts: 12
						

Thoughts:
Here's my thought process, summarized as you requested:

**Assessing the Landscape**

Okay, hold on a second. Before I dive too deep, I need to take a step back and get a clear picture of the dependencies involved with HSR. I can't just jump in; I need to understand what other components, systems, or data sources HSR relies on. What are the inputs? What other systems does it interface with? Identifying these dependencies is crucial for any meaningful analysis or understanding. It will dictate the scope of my investigation and inform the direction of my research. Understanding the connections is fundamental to avoid missing any piece of the puzzle.



16/2 2026/08/08 00:52 tool read-file
0m Args:
{
  "File": "net/hsr/Kconfig",
  "FirstLine": 1,
  "LineCount": 20
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t#\n   3:\t# IEC 62439-3 High-availability Seamless Redundancy\n   4:\t#\n   5:\t\n   6:\tconfig HSR\n   7:\t\ttristate \"High-availability Seamless Redundancy (HSR \u0026 PRP)\"\n   8:\t\thelp\n   9:\t\t  This enables IEC 62439 defined High-availability Seamless\n  10:\t\t  Redundancy (HSR) and Parallel Redundancy Protocol (PRP).\n  11:\t\n  12:\t\t  If you say Y here, then your Linux box will be able to act as a\n  13:\t\t  DANH (\"Doubly attached node implementing HSR\") or DANP (\"Doubly\n  14:\t\t  attached node implementing PRP\"). For this to work, your Linux box\n  15:\t\t  needs (at least) two physical Ethernet interfaces.\n  16:\t\n  17:\t\t  For DANH, it must be connected as a node in a ring network together\n  18:\t\t  with other HSR capable nodes. All Ethernet frames sent over the HSR\n  19:\t\t  device will be sent in both directions on the ring (over both slave\n  20:\t\t  ports), giving a redundant, instant fail-over network. Each HSR node\n"
}

17/2 2026/08/08 00:52 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11561
							output: 135
							thoughts: 355
						

Thoughts:
**My Analysis of the HSR Patch**

Okay, so I've got this patch for the HSR driver. The first thing I note is that it doesn't seem to introduce any new dependencies, which is good. I need to be efficient here.

Right, let's see what else we're dealing with. The actual code change is in `net/hsr/hsr_forward.c`. I also see a test script, `tools/testing/selftests/net/hsr/hsr_shared_mutation.sh`, which seems to be using `ip link` to set up HSR and PRP interfaces. Interesting. The config used in the test is just `HSR`, which simplifies things a bit.

I had a quick look at `NET_F_HW_HSR_TAG_INS`, but it looks like just a netdev feature flag and doesn't appear to be tied to a specific config. So, the only relevant config here is indeed `HSR`.

Now, the important part: the reasoning. This patch fixes a bug. The driver could corrupt SKBs or their clones during per-egress writes, and that's not good, of course. It introduces `hsr_clone_private()` to ensure skb data is private before modification. That's a good approach to address the shared data issues. The changes are within the core packet forwarding logic, so the impact is potentially significant. This is definitely reachable code and worth fuzzing.

I've taken a quick glance at the symbols in question: `hsr_forward_skb`, `hsr_create_tagged_frame`, and `prp_create_tagged_frame`. Just making sure, let's quickly check whether `hsr_clone_private` is static and it is. And the other two are non-static.

Okay, I'm satisfied. I'll use set-results to return the results.



18/2 2026/08/08 00:52 tool set-results
0m Args:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "hsr_create_tagged_frame",
    "prp_create_tagged_frame"
  ],
  "Reasoning": "The patch fixes a bug in the HSR driver where per-egress writes could corrupt the original skb or other clones of it. It introduces `hsr_clone_private()` to ensure skb data is private before mutation. The changes affect core packet forwarding logic in the HSR driver, which is reachable and worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "HSR"
  ],
  "FocusSymbols": [
    "hsr_forward_skb",
    "hsr_create_tagged_frame",
    "prp_create_tagged_frame"
  ],
  "Reasoning": "The patch fixes a bug in the HSR driver where per-egress writes could corrupt the original skb or other clones of it. It introduces `hsr_clone_private()` to ensure skb data is private before mutation. The changes affect core packet forwarding logic in the HSR driver, which is reachable and worth fuzzing.",
  "WorthFuzzing": true
}

19/1 2026/08/08 00:52 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

20/2 2026/08/08 00:52 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch fixes a logical bug where shared `skb` data is mutated by one egress path, corrupting the data for other clones (e.g., incorrect LAN ID or MAC address). The fix uses `skb_cow()` to ensure the `skb` data buffer is private before any modifications are made. This does not involve uninitialized memory, info-leaks, or complex structure allocations that might leave fields uninitialized. The bug is a shared mutable state issue, and the fix correctly privatizes the buffer. Standard testing and KASAN are sufficient for memory safety here; KMSAN is not applicable as there is no risk of uninitialized memory reads.",
  "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:
For your convenience, here is the diff of the changes:
commit 1c860b44546d44296ed91bcc5d8779589d5e0c80
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Aug 8 00:51:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a65c16..efcbf3cf26f9b 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -325,8 +325,29 @@ static struct sk_buff *hsr_fill_tag(struct sk_buff *skb,
 	return skb;
 }
 
-/* If the original frame was an HSR tagged frame, just clone it to be sent
- * unchanged. Otherwise, create a private frame especially tagged for 'port'.
+/* Clone an skb and make the clone's data private, so that per-egress
+ * writes cannot corrupt the original skb or other clones of it.
+ * Returns NULL on allocation failure.
+ */
+static struct sk_buff *hsr_clone_private(struct sk_buff *skb)
+{
+	struct sk_buff *clone;
+
+	clone = skb_clone(skb, GFP_ATOMIC);
+	if (!clone)
+		return NULL;
+	if (skb_cow(clone, 0)) {
+		kfree_skb(clone);
+		return NULL;
+	}
+
+	return clone;
+}
+
+/* If the original frame was an HSR tagged frame, return a private clone
+ * of it with the path id updated for 'port'. Otherwise, return a private
+ * clone for hardware tag insertion, or create a private frame especially
+ * tagged for 'port'.
  */
 struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,
 					struct hsr_port *port)
@@ -336,14 +357,18 @@ struct sk_buff *hsr_create_tagged_frame(struct hsr_frame_info *frame,
 	int movelen;
 
 	if (frame->skb_hsr) {
-		struct hsr_ethhdr *hsr_ethhdr =
-			(struct hsr_ethhdr *)skb_mac_header(frame->skb_hsr);
+		struct hsr_ethhdr *hsr_ethhdr;
+
+		skb = hsr_clone_private(frame->skb_hsr);
+		if (!skb)
+			return NULL;
 
 		/* set the lane id properly */
+		hsr_ethhdr = (struct hsr_ethhdr *)skb_mac_header(skb);
 		hsr_set_path_id(frame, hsr_ethhdr, port);
-		return skb_clone(frame->skb_hsr, GFP_ATOMIC);
+		return skb;
 	} else if (port->dev->features & NETIF_F_HW_HSR_TAG_INS) {
-		return skb_clone(frame->skb_std, GFP_ATOMIC);
+		return hsr_clone_private(frame->skb_std);
 	}
 
 	/* Create the new skb with enough headroom to fit the HSR tag */
@@ -377,17 +402,23 @@ struct sk_buff *prp_create_tagged_frame(struct hsr_frame_info *frame,
 	struct sk_buff *skb;
 
 	if (frame->skb_prp) {
-		struct prp_rct *trailer = skb_get_PRP_rct(frame->skb_prp);
+		struct prp_rct *trailer;
+
+		skb = hsr_clone_private(frame->skb_prp);
+		if (!skb)
+			return NULL;
 
+		trailer = skb_get_PRP_rct(skb);
 		if (trailer) {
 			prp_set_lan_id(trailer, port);
 		} else {
 			WARN_ONCE(!trailer, "errored PRP skb");
+			kfree_skb(skb);
 			return NULL;
 		}
-		return skb_clone(frame->skb_prp, GFP_ATOMIC);
+		return skb;
 	} else if (port->dev->features & NETIF_F_HW_HSR_TAG_INS) {
-		return skb_clone(frame->skb_std, GFP_ATOMIC);
+		return hsr_clone_private(frame->skb_std);
 	}
 
 	skb = skb_copy_expand(frame->skb_std, skb_headroom(frame->skb_std),
@@ -420,6 +451,22 @@ static void hsr_deliver_master(struct sk_buff *skb, struct net_device *dev,
 static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,
 		    struct hsr_frame_info *frame)
 {
+	/* An interlink-bound skb from get_untagged_frame() can still alias
+	 * another live consumer: for master-originated frames the clone
+	 * shares the original TX skb (which taps or the TX path may still
+	 * hold); for ring frames the master also consumes them when they
+	 * are destined to the local node without being exclusive to it.
+	 * Privatize before any address mutation.
+	 */
+	if (port->type == HSR_PT_INTERLINK &&
+	    (frame->port_rcv->type == HSR_PT_MASTER ||
+	     (frame->is_local_dest && !frame->is_local_exclusive)) &&
+	    skb_cow(skb, 0)) {
+		frame->port_rcv->dev->stats.rx_dropped++;
+		kfree_skb(skb);
+		return NET_XMIT_DROP;
+	}
+
 	if (frame->port_rcv->type == HSR_PT_MASTER) {
 		hsr_addr_subst_dest(frame->node_src, skb, port);
 
diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile
index 31fb9326cf533..87fe34951b8b0 100644
--- a/tools/testing/selftests/net/hsr/Makefile
+++ b/tools/testing/selftests/net/hsr/Makefile
@@ -5,6 +5,7 @@ top_srcdir = ../../../../..
 TEST_PROGS := \
 	hsr_ping.sh \
 	hsr_redbox.sh \
+	hsr_shared_mutation.sh \
 	link_faults.sh \
 	prp_ping.sh \
 # end of TEST_PROGS
diff --git a/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh b/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh
new file mode 100755
index 0000000000000..0b8b8791190d1
--- /dev/null
+++ b/tools/testing/selftests/net/hsr/hsr_shared_mutation.sh
@@ -0,0 +1,242 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Verify that per-egress mutations of shared skb data are private:
+#
+# F2 (path/LAN ID): on an affected kernel the second slave's LAN-ID write
+# lands in the first slave's still-queued clone; with a netem delay on
+# slave A, injected frames leave A carrying B's LAN ID.
+#
+# F1 (RedBox source MAC): on an affected kernel an HSR-tagged multicast
+# frame received on a RedBox slave is cloned for master and interlink,
+# and the interlink's RedBox-MAC rewrite lands in the master clone's
+# buffer, so the local stack receives the RedBox MAC instead of the
+# originating node's MAC.
+
+ipv6=false
+
+source ./hsr_common.sh
+
+DUR=5
+
+require()
+{
+	command -v "$1" >/dev/null 2>&1 && return 0
+	echo "SKIP: $1 not available"
+	exit $ksft_skip
+}
+
+require ip
+require tc
+require python3
+
+trap cleanup_all_ns EXIT
+
+# ------------------------------------------------------- F2: LAN-ID isolation
+# PRP DANP (proto 1), AF_PACKET pre-tagged injection, netem on slave A.
+run_f2()
+{
+	setup_ns ns 2>/dev/null || return $ksft_skip
+	nsx() { ip netns exec "$ns" "$@"; }
+
+	# Probe sch_netem inside the disposable namespace only.
+	if ! nsx tc qdisc add dev lo root netem delay 1ms 2>/dev/null; then
+		echo "SKIP: sch_netem not available"
+		return $ksft_skip
+	fi
+	nsx tc qdisc del dev lo root 2>/dev/null
+
+	# Capability probes end here; setup or runtime failure below is FAIL.
+	nsx ip link add vA type veth peer name vAp ||
+		{ echo "FAIL: veth A"; return 1; }
+	nsx ip link add vB type veth peer name vBp ||
+		{ echo "FAIL: veth B"; return 1; }
+	for i in vA vB vAp vBp; do
+		nsx ip link set "$i" up || { echo "FAIL: $i up"; return 1; }
+	done
+	if ! nsx ip link add name prp0 type hsr slave1 vA slave2 vB \
+		supervision 45 proto 1 2>/dev/null; then
+		echo "SKIP: HSR/PRP not supported by this kernel"
+		return $ksft_skip
+	fi
+	nsx ip link set prp0 up || { echo "FAIL: prp0 up"; return 1; }
+	nsx tc qdisc add dev vA root netem delay 200ms ||
+		{ echo "FAIL: netem"; return 1; }
+
+	nsx python3 /dev/stdin "$DUR" <<'PYF2'
+import socket, struct, select, sys, time
+
+dur = int(sys.argv[1])
+def lanid(pkt):
+    if len(pkt) < 20 or pkt[-2:] != b"\x88\xfb":
+        return None
+    return (pkt[-4] >> 4) & 0xF
+
+SRC = bytes.fromhex(open("/sys/class/net/prp0/address").read().replace(":", ""))
+DST = bytes.fromhex("02aabbccdd01")
+PAY = bytes(range(46))
+rct0 = struct.pack(">H", 0) + struct.pack(">H", 52 & 0x0FFF) + b"\x88\xfb"
+frame = DST + SRC + b"\x08\x00" + PAY + rct0
+
+tx = socket.socket(socket.AF_PACKET, socket.SOCK_RAW); tx.bind(("prp0", 0))
+sA = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+sA.bind(("vAp", 0))
+sB = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+sB.bind(("vBp", 0))
+sA.setblocking(False); sB.setblocking(False)
+
+a, b = [], []
+
+def match(pkt):
+    return (pkt[:6] == DST and pkt[6:12] == SRC
+            and pkt[12:14] == b"\x08\x00" and pkt[14:14 + len(PAY)] == PAY)
+
+def drain(timeout):
+    wait = timeout
+    while True:
+        r, _, _ = select.select([sA, sB], [], [], wait)
+        if not r:
+            return
+        for s in r:
+            pkt = s.recv(65535)
+            if not match(pkt):
+                continue
+            lid = lanid(pkt)
+            if lid is not None:
+                (a if s is sA else b).append(lid)
+        wait = 0
+
+# Drain while sending so the burst cannot overflow the capture sockets'
+# receive buffers; the netem-delayed A-side frames arrive afterwards and
+# are collected below.
+for _ in range(200):
+    tx.send(frame)
+    drain(0)
+    time.sleep(0.001)
+
+end = time.time() + dur
+while time.time() < end:
+    drain(0.3)
+
+print("A-side count=%d lan ids=%s" % (len(a), sorted(set(a))))
+print("B-side count=%d lan ids=%s" % (len(b), sorted(set(b))))
+if len(a) < 150 or len(b) < 150:
+    print("FAIL: too few injected frames captured (A=%d B=%d, sent 200)"
+          % (len(a), len(b)))
+    sys.exit(1)
+bad_a = [x for x in a if (x & 1) != 0]
+bad_b = [x for x in b if (x & 1) != 1]
+if bad_a or bad_b:
+    print("FAIL: shared-mutation corruption - A: %d/%d wrong-lan,"
+          " B: %d/%d wrong-lan"
+          % (len(bad_a), len(a), len(bad_b), len(b)))
+    sys.exit(1)
+print("PASS: per-egress LAN IDs isolated (A all bit0=0, B all bit0=1)")
+sys.exit(0)
+PYF2
+}
+
+# --------------------------------------------- F1: RedBox source-MAC privacy
+# HSR RedBox (proto 0), tagged multicast from a slave: master must keep
+# the node MAC, interlink must carry the RedBox MAC.
+run_f1()
+{
+	setup_ns ns 2>/dev/null || return $ksft_skip
+	nsx() { ip netns exec "$ns" "$@"; }
+
+	nsx ip link add vA type veth peer name vAp ||
+		{ echo "FAIL: veth A"; return 1; }
+	nsx ip link add vB type veth peer name vBp ||
+		{ echo "FAIL: veth B"; return 1; }
+	nsx ip link add vI type veth peer name vIp ||
+		{ echo "FAIL: veth I"; return 1; }
+	for i in vA vB vI vAp vBp vIp; do
+		nsx ip link set "$i" up || { echo "FAIL: $i up"; return 1; }
+	done
+	if ! nsx ip link add name hsr0 type hsr slave1 vA slave2 vB \
+		interlink vI supervision 45 proto 0 2>/dev/null; then
+		echo "SKIP: HSR RedBox not supported by this kernel"
+		return $ksft_skip
+	fi
+	nsx ip link set hsr0 up || { echo "FAIL: hsr0 up"; return 1; }
+
+	nsx python3 /dev/stdin <<'PYF1'
+import socket, select, sys, time
+
+NODE  = bytes.fromhex("021122334455")
+MCAST = bytes.fromhex("01005e000001")
+RB    = bytes.fromhex(open("/sys/class/net/vI/address").read().replace(":", ""))
+PAY   = bytes(range(46))
+
+def frame(seq):
+    # LSDU size = payload + HSR tag (HSR_HLEN), as hsr_fill_tag() computes it
+    tag = (((1 << 12) | (len(PAY) + 6)).to_bytes(2, "big")
+           + seq.to_bytes(2, "big") + b"\x08\x00")
+    return MCAST + NODE + b"\x89\x2f" + tag + PAY
+
+tx = socket.socket(socket.AF_PACKET, socket.SOCK_RAW); tx.bind(("vAp", 0))
+sm = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+sm.bind(("hsr0", 0))
+si = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,
+        socket.ntohs(0x0003))
+si.bind(("vIp", 0))
+sm.setblocking(False); si.setblocking(False)
+
+for i in range(3):
+    tx.send(frame(i + 1)); time.sleep(0.05)
+
+m_src = i_src = None
+end = time.time() + 4
+while time.time() < end and (m_src is None or i_src is None):
+    r, _, _ = select.select([sm, si], [], [], 0.3)
+    for s in r:
+        pkt = s.recv(65535)
+        # exact flow: dst, post-strip EtherType, exact payload, min length;
+        # h_source is the asserted value and must NOT be filtered on
+        if (len(pkt) < 60 or pkt[:6] != MCAST or pkt[12:14] != b"\x08\x00"
+                or pkt[14:14 + len(PAY)] != PAY):
+            continue
+        if s is sm and m_src is None:
+            m_src = pkt[6:12]
+        elif s is si and i_src is None:
+            i_src = pkt[6:12]
+
+print("master h_source    =", m_src.hex() if m_src else None)
+print("node MAC           =", NODE.hex())
+print("interlink h_source =", i_src.hex() if i_src else None)
+print("redbox MAC         =", RB.hex())
+if i_src != RB:
+    print("FAIL: interlink did not carry the RedBox MAC")
+    sys.exit(1)
+if m_src != NODE:
+    print("FAIL: master received %s instead of the node MAC "
+          "(shared-mutation corruption)"
+          % (m_src.hex() if m_src else "nothing"))
+    sys.exit(1)
+print("PASS: master kept node MAC, interlink kept RedBox MAC")
+sys.exit(0)
+PYF1
+}
+
+rc=0
+
+run_f2
+ret=$?
+rc=$(ksft_status_merge "$rc" "$ret")
+
+run_f1
+ret=$?
+rc=$(ksft_status_merge "$rc" "$ret")
+
+if [ "$rc" -eq 0 ]; then
+	echo "hsr_shared_mutation: per-egress mutation isolation (F1+F2) [ OK ]"
+elif [ "$rc" -eq "$ksft_skip" ]; then
+	echo "hsr_shared_mutation: subtests skipped (capabilities missing)"
+else
+	echo "hsr_shared_mutation: per-egress mutation isolation [ FAIL ]" \
+		"rc=$rc" 1>&2
+fi
+exit "$rc"

21/3 2026/08/08 00:52 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 6800
							output: 162
							thoughts: 1027
						

Thoughts:
**Analyzing the HSR Patch for Uninitialized Memory**

Okay, so I've been looking over this patch that modifies `net/hsr/hsr_forward.c`. My initial impression is that it's designed to fix data corruption stemming from shared `skb` data in the HSR module, specifically when handling multiple egress paths. The core of the fix revolves around privatizing the `skb` data before modification to avoid cross-talk between different clones, which makes sense.

The primary changes involve the introduction of `hsr_clone_private()`, which combines `skb_clone()` and `skb_cow()`. This new function replaces the raw `skb_clone()` calls in `hsr_create_tagged_frame()` and `prp_create_tagged_frame()`. Additionally, a check has been added in `hsr_xmit()` to use `skb_cow()` for interlink-bound SKBs.

My primary concern, given the context, is to determine if this patch exposes or introduces any uninitialized memory risks. Essentially, we're trying to prevent data corruption. The shared data issue is the problem. When multiple egress paths mutate skb data, they change shared data, which, if not done correctly, can cause issues with other clones. The fix uses `skb_cow()` to ensure that the skb has private data.

My analysis focuses on this point. `skb_cow(skb, 0)` is critical here. It ensures that the SKB either already has a private head (and data), or will reallocate them. Importantly, this reallocation *copies* existing data, it doesn't leave any part of the buffer uninitialized. We're dealing with a logical bug – data corruption due to incorrect sharing – not an uninitialized memory read or potential information leak. The "corrupted" data is valid packet data. It's just *incorrect* for a specific clone. There's no scenario where uninitialized memory is exposed to user space or used in control flow.

I've double-checked the internal workings of `skb_cow()`. It ultimately calls `__skb_cow()`, which then uses `pskb_expand_head` if a reallocation is needed. While the newly allocated headroom/tailroom in `pskb_expand_head` *is* uninitialized, it is *outside* the `skb->data` to `skb->tail` range. Thus, it's not read unless explicitly pushed/put, which is standard skb behavior.

The patch's changes can be summarized as:

1.  `hsr_clone_private()`: Clones and privatizes an skb.
2.  `hsr_create_tagged_frame()` and `prp_create_tagged_frame()`: Replace clone with private clone.
3.  `hsr_xmit()`:  Privatizes for interlink ports before mutation.

None of these changes *introduce* uninitialized memory vulnerabilities. They are all about properly privatizing the skb data to prevent unintended data sharing and modification, a classic shared-mutable-state bug. The patch doesn't involve any complex structure allocations that might leave fields uninitialized. Standard testing and KASAN should be sufficient for memory safety here. KMSAN is not applicable as there is no risk of uninitialized memory reads. Therefore, the "NeedsKMSAN" flag should be `false`. The reasoning is that the patch fixes a logical bug related to shared data corruption with `skb_cow()` and this does not introduce any risks related to uninitialized memory. I'm confident about this. I'll make the appropriate call and mark it as such.



22/3 2026/08/08 00:52 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch fixes a logical bug where shared `skb` data is mutated by one egress path, corrupting the data for other clones (e.g., incorrect LAN ID or MAC address). The fix uses `skb_cow()` to ensure the `skb` data buffer is private before any modifications are made. This does not involve uninitialized memory, info-leaks, or complex structure allocations that might leave fields uninitialized. The bug is a shared mutable state issue, and the fix correctly privatizes the buffer. Standard testing and KASAN are sufficient for memory safety here; KMSAN is not applicable as there is no risk of uninitialized memory reads.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch fixes a logical bug where shared `skb` data is mutated by one egress path, corrupting the data for other clones (e.g., incorrect LAN ID or MAC address). The fix uses `skb_cow()` to ensure the `skb` data buffer is private before any modifications are made. This does not involve uninitialized memory, info-leaks, or complex structure allocations that might leave fields uninitialized. The bug is a shared mutable state issue, and the fix correctly privatizes the buffer. Standard testing and KASAN are sufficient for memory safety here; KMSAN is not applicable as there is no risk of uninitialized memory reads.",
  "NeedsKMSAN": false
}

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