| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/13 22:59 | flow | patch-triage |
3mArgs:null Results: {
"EnableConfigs": [
"AMT",
"INET",
"IP_MULTICAST",
"IPV6"
],
"FocusSymbols": [
"amt_request_handler",
"amt_update_handler",
"amt_tunnel_get_or_create",
"amt_tunnel_expire",
"amt_dev_stop",
"amt_dev_open",
"amt_dev_xmit",
"amt_rcv",
"amt_send_membership_query",
"amt_secret_work"
],
"KMSANReasoning": "The patch modifies the AMT (Automatic Multicast Tunneling) driver to defer tunnel allocation until membership update authentication, prevents tunnel table exhaustion, and resolves a use-after-free (UAF) race condition between amt_tunnel_expire(), amt_update_handler(), and amt_dev_stop().\n\nKASAN vs. KMSAN Applicability:\n- The risks and bugs addressed by this patch are concurrency races, resource exhaustion, and use-after-free errors (where concurrent updates could re-arm gc_wq on a tunnel already marked for destruction). These types of bugs (UAF, invalid pointer dereferences, concurrent list mutations) are squarely within the scope of KASAN and LOCKDEP.\n- There are no uninitialized memory reads or information leak hazards:\n - Tunnel structures are allocated using kzalloc(), guaranteeing zero-initialization of all fields including newly added members (last_seen, dead).\n - The siphash key array (amt-\u003ekey[4]) is fully initialized in amt_dev_open() using get_random_bytes() with sizeof(amt-\u003ekey).\n - Outgoing query headers (amtmq) have all fields explicitly written before transmission over the network.\n - Netlink policy updates use NLA_POLICY_EXACT_LEN for incoming attributes, which is an input validation concern rather than an uninitialized memory issue.\n - No uninitialized data or kernel structure padding is copied to user space.\n\nBecause all memory is fully initialized and the changes focus on concurrency and lifecycle management, standard KASAN and LOCKDEP fuzzing is sufficient and a dedicated KMSAN session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch refactors Automatic Multicast Tunneling (AMT) lifecycle and state management in drivers/net/amt.c. It delays tunnel allocation until Membership Update authentication, adds per-source tunnel limits, introduces rolling 4-generation secret keys, and restructures concurrency synchronization between amt_tunnel_expire and amt_dev_stop using a new tunnel 'dead' flag to eliminate use-after-free races. AMT virtual interfaces are fully reachable via rtnl netlink and network packet injection in virtualized environments, making these lifecycle and concurrency changes prime targets for syzkaller fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/10/13 22:59 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 925aeb4dbc2c869ae154145569f1fd985f86d10f\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Oct 13 22:59:37 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/amt.c b/drivers/net/amt.c\nindex b53f8ec556615..0124f1be673b3 100644\n--- a/drivers/net/amt.c\n+++ b/drivers/net/amt.c\n@@ -784,11 +784,16 @@ static void amt_send_request(struct amt_dev *amt, bool v6)\n \n static bool amt_send_membership_query(struct amt_dev *amt,\n \t\t\t\t struct sk_buff *skb,\n-\t\t\t\t struct amt_tunnel_list *tunnel,\n+\t\t\t\t __be32 daddr, __be16 dport,\n+\t\t\t\t __be32 nonce, u64 mac,\n \t\t\t\t bool v6);\n \n-static void amt_send_igmp_gq(struct amt_dev *amt,\n-\t\t\t struct amt_tunnel_list *tunnel)\n+/* The destination is passed by value rather than as a tunnel: the requesting\n+ * source is not authenticated yet and no tunnel state exists for it - see\n+ * amt_request_handler().\n+ */\n+static void amt_send_igmp_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,\n+\t\t\t __be32 nonce, u64 mac)\n {\n \tstruct sk_buff *skb;\n \n@@ -797,7 +802,8 @@ static void amt_send_igmp_gq(struct amt_dev *amt,\n \t\treturn;\n \n \tskb_pull(skb, sizeof(struct ethhdr));\n-\tif (amt_send_membership_query(amt, skb, tunnel, false)) {\n+\tif (amt_send_membership_query(amt, skb, daddr, dport, nonce, mac,\n+\t\t\t\t false)) {\n \t\tamt-\u003edev-\u003estats.tx_dropped++;\n \t\tkfree_skb(skb);\n \t}\n@@ -876,7 +882,8 @@ static struct sk_buff *amt_build_mld_gq(struct amt_dev *amt)\n \treturn skb;\n }\n \n-static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)\n+static void amt_send_mld_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,\n+\t\t\t __be32 nonce, u64 mac)\n {\n \tstruct sk_buff *skb;\n \n@@ -885,13 +892,15 @@ static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)\n \t\treturn;\n \n \tskb_pull(skb, sizeof(struct ethhdr));\n-\tif (amt_send_membership_query(amt, skb, tunnel, true)) {\n+\tif (amt_send_membership_query(amt, skb, daddr, dport, nonce, mac,\n+\t\t\t\t true)) {\n \t\tamt-\u003edev-\u003estats.tx_dropped++;\n \t\tkfree_skb(skb);\n \t}\n }\n #else\n-static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)\n+static void amt_send_mld_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,\n+\t\t\t __be32 nonce, u64 mac)\n {\n }\n #endif\n@@ -925,7 +934,14 @@ static void amt_secret_work(struct work_struct *work)\n \t\t\t\t\t secret_wq);\n \n \tspin_lock_bh(\u0026amt-\u003elock);\n-\tget_random_bytes(\u0026amt-\u003ekey, sizeof(siphash_key_t));\n+\t/* Keep three previous generations for a 180-240s MAC lifetime.\n+\t * Its 180s floor exceeds the advertised QQIC (amt-\u003eqi, 125s),\n+\t * allowing the gateway to renew its MAC with a periodic Request.\n+\t */\n+\tamt-\u003ekey[3] = amt-\u003ekey[2];\n+\tamt-\u003ekey[2] = amt-\u003ekey[1];\n+\tamt-\u003ekey[1] = amt-\u003ekey[0];\n+\tget_random_bytes(\u0026amt-\u003ekey[0], sizeof(siphash_key_t));\n \tspin_unlock_bh(\u0026amt-\u003elock);\n \tmod_delayed_work(amt_wq, \u0026amt-\u003esecret_wq,\n \t\t\t msecs_to_jiffies(AMT_SECRET_TIMEOUT));\n@@ -1115,9 +1131,16 @@ static void amt_send_multicast_data(struct amt_dev *amt,\n \t\t\t 0);\n }\n \n+/* Send a Membership Query to a source that has not been authenticated yet.\n+ *\n+ * No per-source state exists at this point and none is created: the nonce and\n+ * the keyed MAC are carried in the Query and recomputed from the Membership\n+ * Update when it comes back, so everything this needs is passed by value.\n+ */\n static bool amt_send_membership_query(struct amt_dev *amt,\n \t\t\t\t struct sk_buff *skb,\n-\t\t\t\t struct amt_tunnel_list *tunnel,\n+\t\t\t\t __be32 daddr, __be16 dport,\n+\t\t\t\t __be32 nonce, u64 mac,\n \t\t\t\t bool v6)\n {\n \tstruct amt_header_membership_query *amtmq;\n@@ -1138,13 +1161,13 @@ static bool amt_send_membership_query(struct amt_dev *amt,\n \tskb_reset_inner_headers(skb);\n \tmemset(\u0026fl4, 0, sizeof(struct flowi4));\n \tfl4.flowi4_oif = amt-\u003estream_dev-\u003eifindex;\n-\tfl4.daddr = tunnel-\u003eip4;\n+\tfl4.daddr = daddr;\n \tfl4.saddr = amt-\u003elocal_ip;\n \tfl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS);\n \tfl4.flowi4_proto = IPPROTO_UDP;\n \trt = ip_route_output_key(amt-\u003enet, \u0026fl4);\n \tif (IS_ERR(rt)) {\n-\t\tnetdev_dbg(amt-\u003edev, \"no route to %pI4\\n\", \u0026tunnel-\u003eip4);\n+\t\tnetdev_dbg(amt-\u003edev, \"no route to %pI4\\n\", \u0026daddr);\n \t\treturn true;\n \t}\n \n@@ -1154,8 +1177,8 @@ static bool amt_send_membership_query(struct amt_dev *amt,\n \tamtmq-\u003ereserved = 0;\n \tamtmq-\u003el\t= 0;\n \tamtmq-\u003eg\t= 0;\n-\tamtmq-\u003enonce\t= tunnel-\u003enonce;\n-\tamtmq-\u003eresponse_mac = tunnel-\u003emac;\n+\tamtmq-\u003enonce\t= nonce;\n+\tamtmq-\u003eresponse_mac = mac;\n \n \tif (!v6)\n \t\tskb_set_inner_protocol(skb, htons(ETH_P_IP));\n@@ -1168,11 +1191,10 @@ static bool amt_send_membership_query(struct amt_dev *amt,\n \t\t\t ip4_dst_hoplimit(\u0026rt-\u003edst),\n \t\t\t 0,\n \t\t\t amt-\u003erelay_port,\n-\t\t\t tunnel-\u003esource_port,\n+\t\t\t dport,\n \t\t\t false,\n \t\t\t false,\n \t\t\t 0);\n-\tamt_update_relay_status(tunnel, AMT_STATUS_SENT_QUERY, true);\n \treturn false;\n }\n \n@@ -1236,7 +1258,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n \t\tgroup.ip6 = ipv6_hdr(skb)-\u003edaddr;\n #endif\n \t} else {\n-\t\tdev-\u003estats.tx_errors++;\n+\t\tDEV_STATS_INC(dev, tx_errors);\n \t\tgoto free;\n \t}\n \n@@ -1249,12 +1271,21 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n \t\t/* Gateway only passes IGMP/MLD packets */\n \t\tif (!report)\n \t\t\tgoto free;\n+\t\t/* A validated report can only be forwarded after the relay's\n+\t\t * family-specific Membership Query supplies the state echoed\n+\t\t * by the Membership Update. Log this readiness failure before\n+\t\t * the shared drop path accounts it.\n+\t\t */\n \t\tif ((!v6 \u0026\u0026 !READ_ONCE(amt-\u003eready4)) ||\n-\t\t (v6 \u0026\u0026 !READ_ONCE(amt-\u003eready6)))\n+\t\t (v6 \u0026\u0026 !READ_ONCE(amt-\u003eready6))) {\n+\t\t\tnetdev_dbg(dev, \"drop %s report: waiting for Membership Query\\n\",\n+\t\t\t\t v6 ? \"MLD\" : \"IGMP\");\n \t\t\tgoto free;\n+\t\t}\n \t\tif (amt_send_membership_update(amt, skb, v6))\n \t\t\tgoto free;\n-\t\tgoto unlock;\n+\t\t/* Sent: the tunnel consumed the skb. Not a drop. */\n+\t\treturn NETDEV_TX_OK;\n \t} else if (amt-\u003emode == AMT_MODE_RELAY) {\n \t\tif (!data)\n \t\t\tgoto free;\n@@ -1283,8 +1314,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n \treturn NETDEV_TX_OK;\n free:\n \tdev_kfree_skb(skb);\n-unlock:\n-\tdev-\u003estats.tx_dropped++;\n+\tDEV_STATS_INC(dev, tx_dropped);\n \treturn NETDEV_TX_OK;\n }\n \n@@ -1328,8 +1358,30 @@ static void amt_tunnel_expire(struct work_struct *work)\n \t\t\t\t\t\t struct amt_tunnel_list,\n \t\t\t\t\t\t gc_wq);\n \tstruct amt_dev *amt = tunnel-\u003eamt;\n+\tunsigned long deadline;\n \n \tspin_lock_bh(\u0026amt-\u003elock);\n+\t/* amt_dev_stop() has taken ownership of the teardown. */\n+\tif (tunnel-\u003edead) {\n+\t\tspin_unlock_bh(\u0026amt-\u003elock);\n+\t\treturn;\n+\t}\n+\t/* A Membership Update may have refreshed the tunnel after this work\n+\t * was queued. Re-arm from last_seen rather than letting\n+\t * amt_update_handler() re-arm gc_wq: this handler is then the only\n+\t * context that frees the tunnel (and the only one amt_dev_stop() has\n+\t * to cancel), which closes the use-after-free where a concurrent\n+\t * Update re-armed gc_wq on a tunnel this function had already\n+\t * list_del_rcu()'d and kfree_rcu()'d.\n+\t */\n+\tdeadline = READ_ONCE(tunnel-\u003elast_seen) +\n+\t\t msecs_to_jiffies(amt_gmi(amt)) * 3;\n+\tif (time_before(jiffies, deadline)) {\n+\t\tmod_delayed_work(amt_wq, \u0026tunnel-\u003egc_wq, deadline - jiffies);\n+\t\tspin_unlock_bh(\u0026amt-\u003elock);\n+\t\treturn;\n+\t}\n+\ttunnel-\u003edead = true;\n \trcu_read_lock();\n \tlist_del_rcu(\u0026tunnel-\u003elist);\n \tamt-\u003enr_tunnels--;\n@@ -2461,16 +2513,107 @@ static bool amt_membership_query_handler(struct amt_dev *amt,\n \treturn false;\n }\n \n+/* The tunnel of the gateway endpoint @saddr:@sport, or NULL. */\n+static struct amt_tunnel_list *amt_tunnel_lookup(struct amt_dev *amt,\n+\t\t\t\t\t\t __be32 saddr, __be16 sport)\n+{\n+\tstruct amt_tunnel_list *tunnel;\n+\n+\tlist_for_each_entry_rcu(tunnel, \u0026amt-\u003etunnel_list, list)\n+\t\tif (tunnel-\u003eip4 == saddr \u0026\u0026 tunnel-\u003esource_port == sport)\n+\t\t\treturn tunnel;\n+\treturn NULL;\n+}\n+\n+/* Look up the tunnel for a source whose Membership Update has already been\n+ * authenticated, creating it on first contact.\n+ *\n+ * This is now the only place tunnel state is allocated, so amt-\u003emax_tunnels\n+ * bounds the number of *verified* gateways rather than the number of\n+ * unverified Requests anyone can send. A fixed per-source cap additionally\n+ * stops one verified endpoint from monopolising the table; a single gateway\n+ * needs at most one tunnel per family (RFC 7450 s4.2.2).\n+ */\n+static struct amt_tunnel_list *amt_tunnel_get_or_create(struct amt_dev *amt,\n+\t\t\t\t\t\t\t__be32 saddr,\n+\t\t\t\t\t\t\t__be16 sport)\n+{\n+\tstruct amt_tunnel_list *tunnel;\n+\tint per_src = 0;\n+\tint i;\n+\n+\ttunnel = amt_tunnel_lookup(amt, saddr, sport);\n+\tif (tunnel)\n+\t\treturn tunnel;\n+\n+\tspin_lock_bh(\u0026amt-\u003elock);\n+\n+\t/* Re-check under the lock in case a concurrent Update won the race, and\n+\t * count how many tunnels this source already holds on the same walk.\n+\t */\n+\tlist_for_each_entry(tunnel, \u0026amt-\u003etunnel_list, list) {\n+\t\tif (tunnel-\u003eip4 != saddr)\n+\t\t\tcontinue;\n+\t\tif (tunnel-\u003esource_port == sport) {\n+\t\t\tspin_unlock_bh(\u0026amt-\u003elock);\n+\t\t\treturn tunnel;\n+\t\t}\n+\t\tper_src++;\n+\t}\n+\n+\tif (amt-\u003enr_tunnels \u003e= amt-\u003emax_tunnels ||\n+\t per_src \u003e= AMT_MAX_TUNNELS_PER_SRC) {\n+\t\tspin_unlock_bh(\u0026amt-\u003elock);\n+\t\treturn NULL;\n+\t}\n+\n+\tif (!netif_running(amt-\u003edev)) {\n+\t\tspin_unlock_bh(\u0026amt-\u003elock);\n+\t\treturn NULL;\n+\t}\n+\n+\ttunnel = kzalloc(sizeof(*tunnel) +\n+\t\t\t (sizeof(struct hlist_head) * amt-\u003ehash_buckets),\n+\t\t\t GFP_ATOMIC);\n+\tif (!tunnel) {\n+\t\tspin_unlock_bh(\u0026amt-\u003elock);\n+\t\treturn NULL;\n+\t}\n+\n+\ttunnel-\u003esource_port = sport;\n+\ttunnel-\u003eip4 = saddr;\n+\ttunnel-\u003eamt = amt;\n+\tspin_lock_init(\u0026tunnel-\u003elock);\n+\tfor (i = 0; i \u003c amt-\u003ehash_buckets; i++)\n+\t\tINIT_HLIST_HEAD(\u0026tunnel-\u003egroups[i]);\n+\n+\ttunnel-\u003elast_seen = jiffies;\n+\tINIT_DELAYED_WORK(\u0026tunnel-\u003egc_wq, amt_tunnel_expire);\n+\t__amt_update_relay_status(tunnel, AMT_STATUS_RECEIVED_UPDATE, false);\n+\n+\t/* Publish only once the entry is fully initialised. */\n+\tlist_add_tail_rcu(\u0026tunnel-\u003elist, \u0026amt-\u003etunnel_list);\n+\tamt-\u003enr_tunnels++;\n+\tmod_delayed_work(amt_wq, \u0026tunnel-\u003egc_wq,\n+\t\t\t msecs_to_jiffies(amt_gmi(amt)) * 3);\n+\tspin_unlock_bh(\u0026amt-\u003elock);\n+\n+\treturn tunnel;\n+}\n+\n static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)\n {\n \tstruct amt_header_membership_update *amtmu;\n \tstruct amt_tunnel_list *tunnel;\n \tstruct ethhdr *eth;\n \tstruct iphdr *iph;\n-\tint len, hdr_size;\n+\tsiphash_key_t key[4];\n+\tint len, hdr_size, i;\n \tu64 response_mac;\n+\tbool verified = false;\n \t__be32 saddr;\n \t__be32 nonce;\n+\t__be16 sport;\n \n \tsaddr = ip_hdr(skb)-\u003esaddr;\n \n@@ -2484,30 +2627,58 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)\n \n \tnonce = amtmu-\u003enonce;\n \tresponse_mac = amtmu-\u003eresponse_mac;\n+\t/* Snapshot the tunnel endpoint port before the encap is stripped. */\n+\tsport = udp_hdr(skb)-\u003esource;\n \n-\tif (iptunnel_pull_header(skb, hdr_size, skb-\u003eprotocol, false))\n-\t\treturn true;\n+\t/* Recompute the MAC handed out in the Membership Query. Four live\n+\t * secrets cover 180-240s, exceeding the advertised QQIC (amt-\u003eqi,\n+\t * 125s), so a periodic Request can renew the MAC before it expires.\n+\t * Authenticate before decapsulation or allocating tunnel state.\n+\t */\n+\tspin_lock_bh(\u0026amt-\u003elock);\n+\tfor (i = 0; i \u003c ARRAY_SIZE(key); i++)\n+\t\tkey[i] = amt-\u003ekey[i];\n+\tspin_unlock_bh(\u0026amt-\u003elock);\n \n-\tskb_reset_network_header(skb);\n+\tfor (i = 0; i \u003c ARRAY_SIZE(key); i++) {\n+\t\tu64 mac = siphash_3u32((__force u32)saddr,\n+\t\t\t\t (__force u32)sport,\n+\t\t\t\t (__force u32)nonce,\n+\t\t\t\t \u0026key[i]) \u003e\u003e 16;\n \n-\tlist_for_each_entry_rcu(tunnel, \u0026amt-\u003etunnel_list, list) {\n-\t\tif (tunnel-\u003eip4 == saddr) {\n-\t\t\tif ((nonce == tunnel-\u003enonce \u0026\u0026\n-\t\t\t response_mac == tunnel-\u003emac)) {\n-\t\t\t\tmod_delayed_work(amt_wq, \u0026tunnel-\u003egc_wq,\n-\t\t\t\t\t\t msecs_to_jiffies(amt_gmi(amt))\n-\t\t\t\t\t\t\t\t * 3);\n-\t\t\t\tgoto report;\n-\t\t\t} else {\n-\t\t\t\tnetdev_dbg(amt-\u003edev, \"Invalid MAC\\n\");\n-\t\t\t\treturn true;\n-\t\t\t}\n+\t\tif (response_mac == mac) {\n+\t\t\tverified = true;\n+\t\t\tbreak;\n \t\t}\n \t}\n \n-\treturn true;\n+\tif (!verified) {\n+\t\tnetdev_dbg(amt-\u003edev, \"Invalid MAC\\n\");\n+\t\treturn true;\n+\t}\n+\n+\ttunnel = amt_tunnel_get_or_create(amt, saddr, sport);\n+\tif (!tunnel) {\n+\t\t/* Out of tunnel slots. Unlike the Request path this reply only\n+\t\t * ever goes to a source that proved it received our Query,\n+\t\t * so it\n+\t\t * cannot be used to reflect at a spoofed third party.\n+\t\t */\n+\t\ticmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);\n+\t\treturn true;\n+\t}\n+\n+\t/* Refresh liveness only. gc_wq is re-armed by amt_tunnel_expire()\n+\t * itself from last_seen, never from here, so this cannot re-queue the\n+\t * work of a tunnel that is being freed.\n+\t */\n+\tWRITE_ONCE(tunnel-\u003elast_seen, jiffies);\n+\n+\tif (iptunnel_pull_header(skb, hdr_size, skb-\u003eprotocol, false))\n+\t\treturn true;\n+\n+\tskb_reset_network_header(skb);\n \n-report:\n \tif (!pskb_may_pull(skb, sizeof(*iph)))\n \t\treturn true;\n \n@@ -2522,6 +2693,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)\n \t\t}\n \n \t\tspin_lock_bh(\u0026tunnel-\u003elock);\n+\t\tif (READ_ONCE(tunnel-\u003edead)) {\n+\t\t\tspin_unlock_bh(\u0026tunnel-\u003elock);\n+\t\t\treturn true;\n+\t\t}\n \t\tamt_igmp_report_handler(amt, skb, tunnel);\n \t\tspin_unlock_bh(\u0026tunnel-\u003elock);\n \n@@ -2542,6 +2717,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)\n \t\t}\n \n \t\tspin_lock_bh(\u0026tunnel-\u003elock);\n+\t\tif (READ_ONCE(tunnel-\u003edead)) {\n+\t\t\tspin_unlock_bh(\u0026tunnel-\u003elock);\n+\t\t\treturn true;\n+\t\t}\n \t\tamt_mld_report_handler(amt, skb, tunnel);\n \t\tspin_unlock_bh(\u0026tunnel-\u003elock);\n \n@@ -2684,15 +2863,29 @@ static bool amt_discovery_handler(struct amt_dev *amt, struct sk_buff *skb)\n \treturn false;\n }\n \n+/* Handle an AMT Relay Membership Request.\n+ *\n+ * The source of a Request is unauthenticated: it is a bare UDP datagram with a\n+ * trivially spoofable source address. Allocating tunnel state here let an\n+ * attacker fill the table from spoofed addresses, and let one spoofed packet\n+ * overwrite the nonce/MAC of an established tunnel and cut that gateway off.\n+ *\n+ * Nothing needs to be remembered at this point. Every input to the keyed MAC\n+ * is carried in the packet or is device state, so the MAC is generated here,\n+ * echoed back by the gateway in its Membership Update, and recomputed and\n+ * verified there - see amt_update_handler(). Tunnel state is created only once\n+ * that verification succeeds.\n+ */\n static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)\n {\n \tstruct amt_header_request *amtrh;\n \tstruct amt_tunnel_list *tunnel;\n-\tunsigned long long key;\n \tstruct udphdr *udph;\n \tstruct iphdr *iph;\n+\tsiphash_key_t key;\n+\tbool full, have = false;\n+\tint per_src = 0;\n \tu64 mac;\n-\tint i;\n \n \tif (!pskb_may_pull(skb, sizeof(*udph) + sizeof(*amtrh)))\n \t\treturn true;\n@@ -2704,59 +2897,40 @@ static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)\n \tif (amtrh-\u003ereserved1 || amtrh-\u003ereserved2 || amtrh-\u003eversion)\n \t\treturn true;\n \n-\tlist_for_each_entry_rcu(tunnel, \u0026amt-\u003etunnel_list, list)\n-\t\tif (tunnel-\u003eip4 == iph-\u003esaddr)\n-\t\t\tgoto send;\n-\n-\tspin_lock_bh(\u0026amt-\u003elock);\n-\tif (amt-\u003enr_tunnels \u003e= amt-\u003emax_tunnels) {\n-\t\tspin_unlock_bh(\u0026amt-\u003elock);\n-\t\ticmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);\n+\tif (!netif_running(amt-\u003edev) || !netif_running(amt-\u003estream_dev))\n \t\treturn true;\n-\t}\n \n-\ttunnel = kzalloc(sizeof(*tunnel) +\n-\t\t\t (sizeof(struct hlist_head) * amt-\u003ehash_buckets),\n-\t\t\t GFP_ATOMIC);\n-\tif (!tunnel) {\n-\t\tspin_unlock_bh(\u0026amt-\u003elock);\n-\t\treturn true;\n+\t/* Snapshot the secret and tunnel limits on the same locked walk. */\n+\tspin_lock_bh(\u0026amt-\u003elock);\n+\tkey = amt-\u003ekey[0];\n+\tfull = amt-\u003enr_tunnels \u003e= amt-\u003emax_tunnels;\n+\tlist_for_each_entry(tunnel, \u0026amt-\u003etunnel_list, list) {\n+\t\tif (tunnel-\u003eip4 != iph-\u003esaddr)\n+\t\t\tcontinue;\n+\t\tper_src++;\n+\t\tif (tunnel-\u003esource_port == udph-\u003esource)\n+\t\t\thave = true;\n \t}\n-\n-\ttunnel-\u003esource_port = udph-\u003esource;\n-\ttunnel-\u003eip4 = iph-\u003esaddr;\n-\n-\tmemcpy(\u0026key, \u0026tunnel-\u003ekey, sizeof(unsigned long long));\n-\ttunnel-\u003eamt = amt;\n-\tspin_lock_init(\u0026tunnel-\u003elock);\n-\tfor (i = 0; i \u003c amt-\u003ehash_buckets; i++)\n-\t\tINIT_HLIST_HEAD(\u0026tunnel-\u003egroups[i]);\n-\n-\tINIT_DELAYED_WORK(\u0026tunnel-\u003egc_wq, amt_tunnel_expire);\n-\n-\tlist_add_tail_rcu(\u0026tunnel-\u003elist, \u0026amt-\u003etunnel_list);\n-\ttunnel-\u003ekey = amt-\u003ekey;\n-\t__amt_update_relay_status(tunnel, AMT_STATUS_RECEIVED_REQUEST, true);\n-\tamt-\u003enr_tunnels++;\n-\tmod_delayed_work(amt_wq, \u0026tunnel-\u003egc_wq,\n-\t\t\t msecs_to_jiffies(amt_gmi(amt)));\n \tspin_unlock_bh(\u0026amt-\u003elock);\n \n-send:\n-\ttunnel-\u003enonce = amtrh-\u003enonce;\n-\tmac = siphash_3u32((__force u32)tunnel-\u003eip4,\n-\t\t\t (__force u32)tunnel-\u003esource_port,\n-\t\t\t (__force u32)tunnel-\u003enonce,\n-\t\t\t \u0026tunnel-\u003ekey);\n-\ttunnel-\u003emac = mac \u003e\u003e 16;\n-\n-\tif (!netif_running(amt-\u003edev) || !netif_running(amt-\u003estream_dev))\n+\t/* Withhold the Query when a new endpoint cannot get a tunnel, so the\n+\t * gateway rediscovers. Existing endpoints must still renew their MAC.\n+\t * Drop silently: an unauthenticated Request must not reflect ICMP.\n+\t */\n+\tif (!have \u0026\u0026 (full || per_src \u003e= AMT_MAX_TUNNELS_PER_SRC))\n \t\treturn true;\n \n+\tmac = siphash_3u32((__force u32)iph-\u003esaddr,\n+\t\t\t (__force u32)udph-\u003esource,\n+\t\t\t (__force u32)amtrh-\u003enonce,\n+\t\t\t \u0026key) \u003e\u003e 16;\n+\n \tif (!amtrh-\u003ep)\n-\t\tamt_send_igmp_gq(amt, tunnel);\n+\t\tamt_send_igmp_gq(amt, iph-\u003esaddr, udph-\u003esource, amtrh-\u003enonce,\n+\t\t\t\t mac);\n \telse\n-\t\tamt_send_mld_gq(amt, tunnel);\n+\t\tamt_send_mld_gq(amt, iph-\u003esaddr, udph-\u003esource, amtrh-\u003enonce,\n+\t\t\t\tmac);\n \n \treturn false;\n }\n@@ -2833,7 +3007,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)\n \t\t\t}\n \t\t\tgoto out;\n \t\tcase AMT_MSG_MULTICAST_DATA:\n-\t\t\tif (saddr != remote_ip) {\n+\t\t\tif (!remote_ip || saddr != remote_ip) {\n \t\t\t\tnetdev_dbg(amt-\u003edev, \"Invalid Relay IP\\n\");\n \t\t\t\terr = true;\n \t\t\t\tgoto drop;\n@@ -2844,7 +3018,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)\n \t\t\telse\n \t\t\t\tgoto out;\n \t\tcase AMT_MSG_MEMBERSHIP_QUERY:\n-\t\t\tif (saddr != remote_ip) {\n+\t\t\tif (!remote_ip || saddr != remote_ip) {\n \t\t\t\tnetdev_dbg(amt-\u003edev, \"Invalid Relay IP\\n\");\n \t\t\t\terr = true;\n \t\t\t\tgoto drop;\n@@ -3024,6 +3198,14 @@ static int amt_dev_open(struct net_device *dev)\n \tenable_delayed_work(\u0026amt-\u003ediscovery_wq);\n \tenable_delayed_work(\u0026amt-\u003ereq_wq);\n \n+\t/* Seed the keys before amt_socket_create() enables the encap receive\n+\t * path, so a Request or Update that arrives immediately is never\n+\t * verified against an all-zero (predictable) secret.\n+\t */\n+\tspin_lock_bh(\u0026amt-\u003elock);\n+\tget_random_bytes(\u0026amt-\u003ekey, sizeof(amt-\u003ekey));\n+\tspin_unlock_bh(\u0026amt-\u003elock);\n+\n \terr = amt_socket_create(amt);\n \tif (err) {\n \t\tdisable_delayed_work(\u0026amt-\u003ereq_wq);\n@@ -3034,7 +3216,6 @@ static int amt_dev_open(struct net_device *dev)\n \tamt-\u003ereq_cnt = 0;\n \tWRITE_ONCE(amt-\u003eremote_ip, 0);\n \tamt-\u003enonce = 0;\n-\tget_random_bytes(\u0026amt-\u003ekey, sizeof(siphash_key_t));\n \n \tamt-\u003estatus = AMT_STATUS_INIT;\n \tif (amt-\u003emode == AMT_MODE_GATEWAY) {\n@@ -3050,7 +3231,7 @@ static int amt_dev_open(struct net_device *dev)\n static int amt_dev_stop(struct net_device *dev)\n {\n \tstruct amt_dev *amt = netdev_priv(dev);\n-\tstruct amt_tunnel_list *tunnel, *tmp;\n+\tstruct amt_tunnel_list *tunnel;\n \tstruct sk_buff *skb;\n \tstruct sock *sk;\n \tint i;\n@@ -3079,9 +3260,31 @@ static int amt_dev_stop(struct net_device *dev)\n \tamt-\u003ereq_cnt = 0;\n \tWRITE_ONCE(amt-\u003eremote_ip, 0);\n \n-\tlist_for_each_entry_safe(tunnel, tmp, \u0026amt-\u003etunnel_list, list) {\n-\t\tlist_del_rcu(\u0026tunnel-\u003elist);\n-\t\tamt-\u003enr_tunnels--;\n+\t/* Mark every tunnel dead first, so a concurrent amt_tunnel_expire()\n+\t * sees -\u003edead and bails instead of freeing a tunnel in parallel; this\n+\t * path is then the sole freer.\n+\t */\n+\tspin_lock_bh(\u0026amt-\u003elock);\n+\tlist_for_each_entry(tunnel, \u0026amt-\u003etunnel_list, list)\n+\t\ttunnel-\u003edead = true;\n+\tspin_unlock_bh(\u0026amt-\u003elock);\n+\n+\t/* Unlink one tunnel at a time with list_del_rcu() rather than splicing\n+\t * the list onto a local head: a lockless reader walking tunnel_list\n+\t * must always terminate at \u0026amt-\u003etunnel_list, never at a stack head.\n+\t */\n+\tfor (;;) {\n+\t\tspin_lock_bh(\u0026amt-\u003elock);\n+\t\ttunnel = list_first_entry_or_null(\u0026amt-\u003etunnel_list,\n+\t\t\t\t\t\t struct amt_tunnel_list, list);\n+\t\tif (tunnel) {\n+\t\t\tlist_del_rcu(\u0026tunnel-\u003elist);\n+\t\t\tamt-\u003enr_tunnels--;\n+\t\t}\n+\t\tspin_unlock_bh(\u0026amt-\u003elock);\n+\t\tif (!tunnel)\n+\t\t\tbreak;\n+\n \t\tcancel_delayed_work_sync(\u0026tunnel-\u003egc_wq);\n \t\tamt_clear_groups(tunnel);\n \t\tkfree_rcu(tunnel, rcu);\n@@ -3152,9 +3355,9 @@ static const struct nla_policy amt_policy[IFLA_AMT_MAX + 1] = {\n \t[IFLA_AMT_RELAY_PORT]\t= { .type = NLA_U16 },\n \t[IFLA_AMT_GATEWAY_PORT]\t= { .type = NLA_U16 },\n \t[IFLA_AMT_LINK]\t\t= { .type = NLA_U32 },\n-\t[IFLA_AMT_LOCAL_IP]\t= { .len = sizeof_field(struct iphdr, daddr) },\n+\t[IFLA_AMT_LOCAL_IP]\t= NLA_POLICY_EXACT_LEN(sizeof(__be32)),\n \t[IFLA_AMT_REMOTE_IP]\t= { .len = sizeof_field(struct iphdr, daddr) },\n-\t[IFLA_AMT_DISCOVERY_IP]\t= { .len = sizeof_field(struct iphdr, daddr) },\n+\t[IFLA_AMT_DISCOVERY_IP]\t= NLA_POLICY_EXACT_LEN(sizeof(__be32)),\n \t[IFLA_AMT_MAX_TUNNELS]\t= { .type = NLA_U32 },\n };\n \ndiff --git a/include/net/amt.h b/include/net/amt.h\nindex 2846dde0cadce..63bb647a4780f 100644\n--- a/include/net/amt.h\n+++ b/include/net/amt.h\n@@ -240,12 +240,19 @@ struct amt_tunnel_list {\n \tu32\t\t\tnr_sources;\n \tenum amt_status\t\tstatus;\n \tstruct delayed_work\tgc_wq;\n+\t/* Last time a valid Membership Update refreshed this tunnel. gc_wq\n+\t * re-arms itself from this rather than being re-armed by the Update\n+\t * path, so the Update path cannot re-queue the work of a tunnel that\n+\t * amt_tunnel_expire() is freeing.\n+\t */\n+\tunsigned long\t\tlast_seen;\n+\t/* Set under amt-\u003elock by whichever of amt_tunnel_expire() or\n+\t * amt_dev_stop() reaches the tunnel first; the other then leaves it\n+\t * alone, so the tunnel is freed exactly once.\n+\t */\n+\tbool\t\t\tdead;\n \t__be16\t\t\tsource_port;\n \t__be32\t\t\tip4;\n-\t__be32\t\t\tnonce;\n-\tsiphash_key_t\t\tkey;\n-\tu64\t\t\tmac:48,\n-\t\t\t\treserved:16;\n \tstruct rcu_head\t\trcu;\n \tstruct hlist_head\tgroups[];\n };\n@@ -325,8 +332,10 @@ struct amt_dev {\n \tstruct work_struct\tevent_wq;\n \t/* AMT status */\n \tenum amt_status\t\tstatus;\n-\t/* Generated key */\n-\tsiphash_key_t\t\tkey;\n+\t/* Current key and three previous generations keep MACs valid for\n+\t * 180-240s, beyond the advertised QQIC (see amt_secret_work()).\n+\t */\n+\tsiphash_key_t\t\tkey[4];\n \tstruct sock\t __rcu *sk;\n \tu32\t\t\tmax_groups;\n \tu32\t\t\tmax_sources;\n@@ -383,6 +392,12 @@ struct amt_dev {\n #define AMT_SECRET_TIMEOUT\t60000\n #define IANA_AMT_UDP_PORT\t2268\n #define AMT_MAX_TUNNELS 128\n+/* Per-source tunnel cap. A single gateway needs at most one tunnel per\n+ * family; this stops one verified source endpoint from monopolising the\n+ * AMT_MAX_TUNNELS-slot table. Deployments that legitimately need more behind\n+ * one address (e.g. CGNAT) use the configurable net-next per-source bound.\n+ */\n+#define AMT_MAX_TUNNELS_PER_SRC 16\n #define AMT_MAX_REQS\t\t128\n #define AMT_GW_HLEN (sizeof(struct iphdr) + \\\n \t\t sizeof(struct udphdr) + \\\n"
} |
| 2/1 | 2026/10/13 22:59 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"AMT",
"INET",
"IP_MULTICAST",
"IPV6"
],
"FocusSymbols": [
"amt_request_handler",
"amt_update_handler",
"amt_tunnel_get_or_create",
"amt_tunnel_expire",
"amt_dev_stop",
"amt_dev_open",
"amt_dev_xmit",
"amt_rcv",
"amt_send_membership_query",
"amt_secret_work"
],
"Reasoning": "The patch refactors Automatic Multicast Tunneling (AMT) lifecycle and state management in drivers/net/amt.c. It delays tunnel allocation until Membership Update authentication, adds per-source tunnel limits, introduces rolling 4-generation secret keys, and restructures concurrency synchronization between amt_tunnel_expire and amt_dev_stop using a new tunnel 'dead' flag to eliminate use-after-free races. AMT virtual interfaces are fully reachable via rtnl netlink and network packet injection in virtualized environments, making these lifecycle and concurrency changes prime targets for syzkaller fuzzing.",
"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 925aeb4dbc2c869ae154145569f1fd985f86d10f
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Oct 13 22:59:37 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index b53f8ec556615..0124f1be673b3 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -784,11 +784,16 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
static bool amt_send_membership_query(struct amt_dev *amt,
struct sk_buff *skb,
- struct amt_tunnel_list *tunnel,
+ __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac,
bool v6);
-static void amt_send_igmp_gq(struct amt_dev *amt,
- struct amt_tunnel_list *tunnel)
+/* The destination is passed by value rather than as a tunnel: the requesting
+ * source is not authenticated yet and no tunnel state exists for it - see
+ * amt_request_handler().
+ */
+static void amt_send_igmp_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac)
{
struct sk_buff *skb;
@@ -797,7 +802,8 @@ static void amt_send_igmp_gq(struct amt_dev *amt,
return;
skb_pull(skb, sizeof(struct ethhdr));
- if (amt_send_membership_query(amt, skb, tunnel, false)) {
+ if (amt_send_membership_query(amt, skb, daddr, dport, nonce, mac,
+ false)) {
amt->dev->stats.tx_dropped++;
kfree_skb(skb);
}
@@ -876,7 +882,8 @@ static struct sk_buff *amt_build_mld_gq(struct amt_dev *amt)
return skb;
}
-static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)
+static void amt_send_mld_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac)
{
struct sk_buff *skb;
@@ -885,13 +892,15 @@ static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)
return;
skb_pull(skb, sizeof(struct ethhdr));
- if (amt_send_membership_query(amt, skb, tunnel, true)) {
+ if (amt_send_membership_query(amt, skb, daddr, dport, nonce, mac,
+ true)) {
amt->dev->stats.tx_dropped++;
kfree_skb(skb);
}
}
#else
-static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)
+static void amt_send_mld_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac)
{
}
#endif
@@ -925,7 +934,14 @@ static void amt_secret_work(struct work_struct *work)
secret_wq);
spin_lock_bh(&amt->lock);
- get_random_bytes(&amt->key, sizeof(siphash_key_t));
+ /* Keep three previous generations for a 180-240s MAC lifetime.
+ * Its 180s floor exceeds the advertised QQIC (amt->qi, 125s),
+ * allowing the gateway to renew its MAC with a periodic Request.
+ */
+ amt->key[3] = amt->key[2];
+ amt->key[2] = amt->key[1];
+ amt->key[1] = amt->key[0];
+ get_random_bytes(&amt->key[0], sizeof(siphash_key_t));
spin_unlock_bh(&amt->lock);
mod_delayed_work(amt_wq, &amt->secret_wq,
msecs_to_jiffies(AMT_SECRET_TIMEOUT));
@@ -1115,9 +1131,16 @@ static void amt_send_multicast_data(struct amt_dev *amt,
0);
}
+/* Send a Membership Query to a source that has not been authenticated yet.
+ *
+ * No per-source state exists at this point and none is created: the nonce and
+ * the keyed MAC are carried in the Query and recomputed from the Membership
+ * Update when it comes back, so everything this needs is passed by value.
+ */
static bool amt_send_membership_query(struct amt_dev *amt,
struct sk_buff *skb,
- struct amt_tunnel_list *tunnel,
+ __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac,
bool v6)
{
struct amt_header_membership_query *amtmq;
@@ -1138,13 +1161,13 @@ static bool amt_send_membership_query(struct amt_dev *amt,
skb_reset_inner_headers(skb);
memset(&fl4, 0, sizeof(struct flowi4));
fl4.flowi4_oif = amt->stream_dev->ifindex;
- fl4.daddr = tunnel->ip4;
+ fl4.daddr = daddr;
fl4.saddr = amt->local_ip;
fl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS);
fl4.flowi4_proto = IPPROTO_UDP;
rt = ip_route_output_key(amt->net, &fl4);
if (IS_ERR(rt)) {
- netdev_dbg(amt->dev, "no route to %pI4\n", &tunnel->ip4);
+ netdev_dbg(amt->dev, "no route to %pI4\n", &daddr);
return true;
}
@@ -1154,8 +1177,8 @@ static bool amt_send_membership_query(struct amt_dev *amt,
amtmq->reserved = 0;
amtmq->l = 0;
amtmq->g = 0;
- amtmq->nonce = tunnel->nonce;
- amtmq->response_mac = tunnel->mac;
+ amtmq->nonce = nonce;
+ amtmq->response_mac = mac;
if (!v6)
skb_set_inner_protocol(skb, htons(ETH_P_IP));
@@ -1168,11 +1191,10 @@ static bool amt_send_membership_query(struct amt_dev *amt,
ip4_dst_hoplimit(&rt->dst),
0,
amt->relay_port,
- tunnel->source_port,
+ dport,
false,
false,
0);
- amt_update_relay_status(tunnel, AMT_STATUS_SENT_QUERY, true);
return false;
}
@@ -1236,7 +1258,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
group.ip6 = ipv6_hdr(skb)->daddr;
#endif
} else {
- dev->stats.tx_errors++;
+ DEV_STATS_INC(dev, tx_errors);
goto free;
}
@@ -1249,12 +1271,21 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
/* Gateway only passes IGMP/MLD packets */
if (!report)
goto free;
+ /* A validated report can only be forwarded after the relay's
+ * family-specific Membership Query supplies the state echoed
+ * by the Membership Update. Log this readiness failure before
+ * the shared drop path accounts it.
+ */
if ((!v6 && !READ_ONCE(amt->ready4)) ||
- (v6 && !READ_ONCE(amt->ready6)))
+ (v6 && !READ_ONCE(amt->ready6))) {
+ netdev_dbg(dev, "drop %s report: waiting for Membership Query\n",
+ v6 ? "MLD" : "IGMP");
goto free;
+ }
if (amt_send_membership_update(amt, skb, v6))
goto free;
- goto unlock;
+ /* Sent: the tunnel consumed the skb. Not a drop. */
+ return NETDEV_TX_OK;
} else if (amt->mode == AMT_MODE_RELAY) {
if (!data)
goto free;
@@ -1283,8 +1314,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
return NETDEV_TX_OK;
free:
dev_kfree_skb(skb);
-unlock:
- dev->stats.tx_dropped++;
+ DEV_STATS_INC(dev, tx_dropped);
return NETDEV_TX_OK;
}
@@ -1328,8 +1358,30 @@ static void amt_tunnel_expire(struct work_struct *work)
struct amt_tunnel_list,
gc_wq);
struct amt_dev *amt = tunnel->amt;
+ unsigned long deadline;
spin_lock_bh(&amt->lock);
+ /* amt_dev_stop() has taken ownership of the teardown. */
+ if (tunnel->dead) {
+ spin_unlock_bh(&amt->lock);
+ return;
+ }
+ /* A Membership Update may have refreshed the tunnel after this work
+ * was queued. Re-arm from last_seen rather than letting
+ * amt_update_handler() re-arm gc_wq: this handler is then the only
+ * context that frees the tunnel (and the only one amt_dev_stop() has
+ * to cancel), which closes the use-after-free where a concurrent
+ * Update re-armed gc_wq on a tunnel this function had already
+ * list_del_rcu()'d and kfree_rcu()'d.
+ */
+ deadline = READ_ONCE(tunnel->last_seen) +
+ msecs_to_jiffies(amt_gmi(amt)) * 3;
+ if (time_before(jiffies, deadline)) {
+ mod_delayed_work(amt_wq, &tunnel->gc_wq, deadline - jiffies);
+ spin_unlock_bh(&amt->lock);
+ return;
+ }
+ tunnel->dead = true;
rcu_read_lock();
list_del_rcu(&tunnel->list);
amt->nr_tunnels--;
@@ -2461,16 +2513,107 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
return false;
}
+/* The tunnel of the gateway endpoint @saddr:@sport, or NULL. */
+static struct amt_tunnel_list *amt_tunnel_lookup(struct amt_dev *amt,
+ __be32 saddr, __be16 sport)
+{
+ struct amt_tunnel_list *tunnel;
+
+ list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list)
+ if (tunnel->ip4 == saddr && tunnel->source_port == sport)
+ return tunnel;
+ return NULL;
+}
+
+/* Look up the tunnel for a source whose Membership Update has already been
+ * authenticated, creating it on first contact.
+ *
+ * This is now the only place tunnel state is allocated, so amt->max_tunnels
+ * bounds the number of *verified* gateways rather than the number of
+ * unverified Requests anyone can send. A fixed per-source cap additionally
+ * stops one verified endpoint from monopolising the table; a single gateway
+ * needs at most one tunnel per family (RFC 7450 s4.2.2).
+ */
+static struct amt_tunnel_list *amt_tunnel_get_or_create(struct amt_dev *amt,
+ __be32 saddr,
+ __be16 sport)
+{
+ struct amt_tunnel_list *tunnel;
+ int per_src = 0;
+ int i;
+
+ tunnel = amt_tunnel_lookup(amt, saddr, sport);
+ if (tunnel)
+ return tunnel;
+
+ spin_lock_bh(&amt->lock);
+
+ /* Re-check under the lock in case a concurrent Update won the race, and
+ * count how many tunnels this source already holds on the same walk.
+ */
+ list_for_each_entry(tunnel, &amt->tunnel_list, list) {
+ if (tunnel->ip4 != saddr)
+ continue;
+ if (tunnel->source_port == sport) {
+ spin_unlock_bh(&amt->lock);
+ return tunnel;
+ }
+ per_src++;
+ }
+
+ if (amt->nr_tunnels >= amt->max_tunnels ||
+ per_src >= AMT_MAX_TUNNELS_PER_SRC) {
+ spin_unlock_bh(&amt->lock);
+ return NULL;
+ }
+
+ if (!netif_running(amt->dev)) {
+ spin_unlock_bh(&amt->lock);
+ return NULL;
+ }
+
+ tunnel = kzalloc(sizeof(*tunnel) +
+ (sizeof(struct hlist_head) * amt->hash_buckets),
+ GFP_ATOMIC);
+ if (!tunnel) {
+ spin_unlock_bh(&amt->lock);
+ return NULL;
+ }
+
+ tunnel->source_port = sport;
+ tunnel->ip4 = saddr;
+ tunnel->amt = amt;
+ spin_lock_init(&tunnel->lock);
+ for (i = 0; i < amt->hash_buckets; i++)
+ INIT_HLIST_HEAD(&tunnel->groups[i]);
+
+ tunnel->last_seen = jiffies;
+ INIT_DELAYED_WORK(&tunnel->gc_wq, amt_tunnel_expire);
+ __amt_update_relay_status(tunnel, AMT_STATUS_RECEIVED_UPDATE, false);
+
+ /* Publish only once the entry is fully initialised. */
+ list_add_tail_rcu(&tunnel->list, &amt->tunnel_list);
+ amt->nr_tunnels++;
+ mod_delayed_work(amt_wq, &tunnel->gc_wq,
+ msecs_to_jiffies(amt_gmi(amt)) * 3);
+ spin_unlock_bh(&amt->lock);
+
+ return tunnel;
+}
+
static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
{
struct amt_header_membership_update *amtmu;
struct amt_tunnel_list *tunnel;
struct ethhdr *eth;
struct iphdr *iph;
- int len, hdr_size;
+ siphash_key_t key[4];
+ int len, hdr_size, i;
u64 response_mac;
+ bool verified = false;
__be32 saddr;
__be32 nonce;
+ __be16 sport;
saddr = ip_hdr(skb)->saddr;
@@ -2484,30 +2627,58 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
nonce = amtmu->nonce;
response_mac = amtmu->response_mac;
+ /* Snapshot the tunnel endpoint port before the encap is stripped. */
+ sport = udp_hdr(skb)->source;
- if (iptunnel_pull_header(skb, hdr_size, skb->protocol, false))
- return true;
+ /* Recompute the MAC handed out in the Membership Query. Four live
+ * secrets cover 180-240s, exceeding the advertised QQIC (amt->qi,
+ * 125s), so a periodic Request can renew the MAC before it expires.
+ * Authenticate before decapsulation or allocating tunnel state.
+ */
+ spin_lock_bh(&amt->lock);
+ for (i = 0; i < ARRAY_SIZE(key); i++)
+ key[i] = amt->key[i];
+ spin_unlock_bh(&amt->lock);
- skb_reset_network_header(skb);
+ for (i = 0; i < ARRAY_SIZE(key); i++) {
+ u64 mac = siphash_3u32((__force u32)saddr,
+ (__force u32)sport,
+ (__force u32)nonce,
+ &key[i]) >> 16;
- list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) {
- if (tunnel->ip4 == saddr) {
- if ((nonce == tunnel->nonce &&
- response_mac == tunnel->mac)) {
- mod_delayed_work(amt_wq, &tunnel->gc_wq,
- msecs_to_jiffies(amt_gmi(amt))
- * 3);
- goto report;
- } else {
- netdev_dbg(amt->dev, "Invalid MAC\n");
- return true;
- }
+ if (response_mac == mac) {
+ verified = true;
+ break;
}
}
- return true;
+ if (!verified) {
+ netdev_dbg(amt->dev, "Invalid MAC\n");
+ return true;
+ }
+
+ tunnel = amt_tunnel_get_or_create(amt, saddr, sport);
+ if (!tunnel) {
+ /* Out of tunnel slots. Unlike the Request path this reply only
+ * ever goes to a source that proved it received our Query,
+ * so it
+ * cannot be used to reflect at a spoofed third party.
+ */
+ icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
+ return true;
+ }
+
+ /* Refresh liveness only. gc_wq is re-armed by amt_tunnel_expire()
+ * itself from last_seen, never from here, so this cannot re-queue the
+ * work of a tunnel that is being freed.
+ */
+ WRITE_ONCE(tunnel->last_seen, jiffies);
+
+ if (iptunnel_pull_header(skb, hdr_size, skb->protocol, false))
+ return true;
+
+ skb_reset_network_header(skb);
-report:
if (!pskb_may_pull(skb, sizeof(*iph)))
return true;
@@ -2522,6 +2693,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
}
spin_lock_bh(&tunnel->lock);
+ if (READ_ONCE(tunnel->dead)) {
+ spin_unlock_bh(&tunnel->lock);
+ return true;
+ }
amt_igmp_report_handler(amt, skb, tunnel);
spin_unlock_bh(&tunnel->lock);
@@ -2542,6 +2717,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
}
spin_lock_bh(&tunnel->lock);
+ if (READ_ONCE(tunnel->dead)) {
+ spin_unlock_bh(&tunnel->lock);
+ return true;
+ }
amt_mld_report_handler(amt, skb, tunnel);
spin_unlock_bh(&tunnel->lock);
@@ -2684,15 +2863,29 @@ static bool amt_discovery_handler(struct amt_dev *amt, struct sk_buff *skb)
return false;
}
+/* Handle an AMT Relay Membership Request.
+ *
+ * The source of a Request is unauthenticated: it is a bare UDP datagram with a
+ * trivially spoofable source address. Allocating tunnel state here let an
+ * attacker fill the table from spoofed addresses, and let one spoofed packet
+ * overwrite the nonce/MAC of an established tunnel and cut that gateway off.
+ *
+ * Nothing needs to be remembered at this point. Every input to the keyed MAC
+ * is carried in the packet or is device state, so the MAC is generated here,
+ * echoed back by the gateway in its Membership Update, and recomputed and
+ * verified there - see amt_update_handler(). Tunnel state is created only once
+ * that verification succeeds.
+ */
static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)
{
struct amt_header_request *amtrh;
struct amt_tunnel_list *tunnel;
- unsigned long long key;
struct udphdr *udph;
struct iphdr *iph;
+ siphash_key_t key;
+ bool full, have = false;
+ int per_src = 0;
u64 mac;
- int i;
if (!pskb_may_pull(skb, sizeof(*udph) + sizeof(*amtrh)))
return true;
@@ -2704,59 +2897,40 @@ static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)
if (amtrh->reserved1 || amtrh->reserved2 || amtrh->version)
return true;
- list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list)
- if (tunnel->ip4 == iph->saddr)
- goto send;
-
- spin_lock_bh(&amt->lock);
- if (amt->nr_tunnels >= amt->max_tunnels) {
- spin_unlock_bh(&amt->lock);
- icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
+ if (!netif_running(amt->dev) || !netif_running(amt->stream_dev))
return true;
- }
- tunnel = kzalloc(sizeof(*tunnel) +
- (sizeof(struct hlist_head) * amt->hash_buckets),
- GFP_ATOMIC);
- if (!tunnel) {
- spin_unlock_bh(&amt->lock);
- return true;
+ /* Snapshot the secret and tunnel limits on the same locked walk. */
+ spin_lock_bh(&amt->lock);
+ key = amt->key[0];
+ full = amt->nr_tunnels >= amt->max_tunnels;
+ list_for_each_entry(tunnel, &amt->tunnel_list, list) {
+ if (tunnel->ip4 != iph->saddr)
+ continue;
+ per_src++;
+ if (tunnel->source_port == udph->source)
+ have = true;
}
-
- tunnel->source_port = udph->source;
- tunnel->ip4 = iph->saddr;
-
- memcpy(&key, &tunnel->key, sizeof(unsigned long long));
- tunnel->amt = amt;
- spin_lock_init(&tunnel->lock);
- for (i = 0; i < amt->hash_buckets; i++)
- INIT_HLIST_HEAD(&tunnel->groups[i]);
-
- INIT_DELAYED_WORK(&tunnel->gc_wq, amt_tunnel_expire);
-
- list_add_tail_rcu(&tunnel->list, &amt->tunnel_list);
- tunnel->key = amt->key;
- __amt_update_relay_status(tunnel, AMT_STATUS_RECEIVED_REQUEST, true);
- amt->nr_tunnels++;
- mod_delayed_work(amt_wq, &tunnel->gc_wq,
- msecs_to_jiffies(amt_gmi(amt)));
spin_unlock_bh(&amt->lock);
-send:
- tunnel->nonce = amtrh->nonce;
- mac = siphash_3u32((__force u32)tunnel->ip4,
- (__force u32)tunnel->source_port,
- (__force u32)tunnel->nonce,
- &tunnel->key);
- tunnel->mac = mac >> 16;
-
- if (!netif_running(amt->dev) || !netif_running(amt->stream_dev))
+ /* Withhold the Query when a new endpoint cannot get a tunnel, so the
+ * gateway rediscovers. Existing endpoints must still renew their MAC.
+ * Drop silently: an unauthenticated Request must not reflect ICMP.
+ */
+ if (!have && (full || per_src >= AMT_MAX_TUNNELS_PER_SRC))
return true;
+ mac = siphash_3u32((__force u32)iph->saddr,
+ (__force u32)udph->source,
+ (__force u32)amtrh->nonce,
+ &key) >> 16;
+
if (!amtrh->p)
- amt_send_igmp_gq(amt, tunnel);
+ amt_send_igmp_gq(amt, iph->saddr, udph->source, amtrh->nonce,
+ mac);
else
- amt_send_mld_gq(amt, tunnel);
+ amt_send_mld_gq(amt, iph->saddr, udph->source, amtrh->nonce,
+ mac);
return false;
}
@@ -2833,7 +3007,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
}
goto out;
case AMT_MSG_MULTICAST_DATA:
- if (saddr != remote_ip) {
+ if (!remote_ip || saddr != remote_ip) {
netdev_dbg(amt->dev, "Invalid Relay IP\n");
err = true;
goto drop;
@@ -2844,7 +3018,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
else
goto out;
case AMT_MSG_MEMBERSHIP_QUERY:
- if (saddr != remote_ip) {
+ if (!remote_ip || saddr != remote_ip) {
netdev_dbg(amt->dev, "Invalid Relay IP\n");
err = true;
goto drop;
@@ -3024,6 +3198,14 @@ static int amt_dev_open(struct net_device *dev)
enable_delayed_work(&amt->discovery_wq);
enable_delayed_work(&amt->req_wq);
+ /* Seed the keys before amt_socket_create() enables the encap receive
+ * path, so a Request or Update that arrives immediately is never
+ * verified against an all-zero (predictable) secret.
+ */
+ spin_lock_bh(&amt->lock);
+ get_random_bytes(&amt->key, sizeof(amt->key));
+ spin_unlock_bh(&amt->lock);
+
err = amt_socket_create(amt);
if (err) {
disable_delayed_work(&amt->req_wq);
@@ -3034,7 +3216,6 @@ static int amt_dev_open(struct net_device *dev)
amt->req_cnt = 0;
WRITE_ONCE(amt->remote_ip, 0);
amt->nonce = 0;
- get_random_bytes(&amt->key, sizeof(siphash_key_t));
amt->status = AMT_STATUS_INIT;
if (amt->mode == AMT_MODE_GATEWAY) {
@@ -3050,7 +3231,7 @@ static int amt_dev_open(struct net_device *dev)
static int amt_dev_stop(struct net_device *dev)
{
struct amt_dev *amt = netdev_priv(dev);
- struct amt_tunnel_list *tunnel, *tmp;
+ struct amt_tunnel_list *tunnel;
struct sk_buff *skb;
struct sock *sk;
int i;
@@ -3079,9 +3260,31 @@ static int amt_dev_stop(struct net_device *dev)
amt->req_cnt = 0;
WRITE_ONCE(amt->remote_ip, 0);
- list_for_each_entry_safe(tunnel, tmp, &amt->tunnel_list, list) {
- list_del_rcu(&tunnel->list);
- amt->nr_tunnels--;
+ /* Mark every tunnel dead first, so a concurrent amt_tunnel_expire()
+ * sees ->dead and bails instead of freeing a tunnel in parallel; this
+ * path is then the sole freer.
+ */
+ spin_lock_bh(&amt->lock);
+ list_for_each_entry(tunnel, &amt->tunnel_list, list)
+ tunnel->dead = true;
+ spin_unlock_bh(&amt->lock);
+
+ /* Unlink one tunnel at a time with list_del_rcu() rather than splicing
+ * the list onto a local head: a lockless reader walking tunnel_list
+ * must always terminate at &amt->tunnel_list, never at a stack head.
+ */
+ for (;;) {
+ spin_lock_bh(&amt->lock);
+ tunnel = list_first_entry_or_null(&amt->tunnel_list,
+ struct amt_tunnel_list, list);
+ if (tunnel) {
+ list_del_rcu(&tunnel->list);
+ amt->nr_tunnels--;
+ }
+ spin_unlock_bh(&amt->lock);
+ if (!tunnel)
+ break;
+
cancel_delayed_work_sync(&tunnel->gc_wq);
amt_clear_groups(tunnel);
kfree_rcu(tunnel, rcu);
@@ -3152,9 +3355,9 @@ static const struct nla_policy amt_policy[IFLA_AMT_MAX + 1] = {
[IFLA_AMT_RELAY_PORT] = { .type = NLA_U16 },
[IFLA_AMT_GATEWAY_PORT] = { .type = NLA_U16 },
[IFLA_AMT_LINK] = { .type = NLA_U32 },
- [IFLA_AMT_LOCAL_IP] = { .len = sizeof_field(struct iphdr, daddr) },
+ [IFLA_AMT_LOCAL_IP] = NLA_POLICY_EXACT_LEN(sizeof(__be32)),
[IFLA_AMT_REMOTE_IP] = { .len = sizeof_field(struct iphdr, daddr) },
- [IFLA_AMT_DISCOVERY_IP] = { .len = sizeof_field(struct iphdr, daddr) },
+ [IFLA_AMT_DISCOVERY_IP] = NLA_POLICY_EXACT_LEN(sizeof(__be32)),
[IFLA_AMT_MAX_TUNNELS] = { .type = NLA_U32 },
};
diff --git a/include/net/amt.h b/include/net/amt.h
index 2846dde0cadce..63bb647a4780f 100644
--- a/include/net/amt.h
+++ b/include/net/amt.h
@@ -240,12 +240,19 @@ struct amt_tunnel_list {
u32 nr_sources;
enum amt_status status;
struct delayed_work gc_wq;
+ /* Last time a valid Membership Update refreshed this tunnel. gc_wq
+ * re-arms itself from this rather than being re-armed by the Update
+ * path, so the Update path cannot re-queue the work of a tunnel that
+ * amt_tunnel_expire() is freeing.
+ */
+ unsigned long last_seen;
+ /* Set under amt->lock by whichever of amt_tunnel_expire() or
+ * amt_dev_stop() reaches the tunnel first; the other then leaves it
+ * alone, so the tunnel is freed exactly once.
+ */
+ bool dead;
__be16 source_port;
__be32 ip4;
- __be32 nonce;
- siphash_key_t key;
- u64 mac:48,
- reserved:16;
struct rcu_head rcu;
struct hlist_head groups[];
};
@@ -325,8 +332,10 @@ struct amt_dev {
struct work_struct event_wq;
/* AMT status */
enum amt_status status;
- /* Generated key */
- siphash_key_t key;
+ /* Current key and three previous generations keep MACs valid for
+ * 180-240s, beyond the advertised QQIC (see amt_secret_work()).
+ */
+ siphash_key_t key[4];
struct sock __rcu *sk;
u32 max_groups;
u32 max_sources;
@@ -383,6 +392,12 @@ struct amt_dev {
#define AMT_SECRET_TIMEOUT 60000
#define IANA_AMT_UDP_PORT 2268
#define AMT_MAX_TUNNELS 128
+/* Per-source tunnel cap. A single gateway needs at most one tunnel per
+ * family; this stops one verified source endpoint from monopolising the
+ * AMT_MAX_TUNNELS-slot table. Deployments that legitimately need more behind
+ * one address (e.g. CGNAT) use the configurable net-next per-source bound.
+ */
+#define AMT_MAX_TUNNELS_PER_SRC 16
#define AMT_MAX_REQS 128
#define AMT_GW_HLEN (sizeof(struct iphdr) + \
sizeof(struct udphdr) + \
|
| 3/2 | 2026/10/13 22:59 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 11797 output: 31 thoughts: 89 Thoughts: Okay, let's break this down. **Analyzing this Kernel Patch for AMT (Automatic Multicast Tunneling)** Right, so the first thing I need to do is thoroughly understand the changes being proposed by this patch, and how they fit into the existing code. Looks like we're dealing with `drivers/net/amt.c` and `include/net/amt.h`. My immediate thought is, "Is AMT still actively used in the Linux kernel?" I know it's related to RFC 7450 – Automatic Multicast Tunneling – but I need to quickly establish its current relevance and if it's considered a core part of the networking stack. So the next step is definitely a Kconfig dive. Need to confirm that AMT is a configurable subsystem and whether or not it's enabled by default, or if it's even a compile-time option in the first place. |
| 4/2 | 2026/10/13 22:59 | tool | grepper |
0mArgs:{
"Expression": "config AMT|CONFIG_AMT",
"PathPrefixes": [
"drivers/net/"
]
}Results: {
"Output": "drivers/net/Kconfig=289=config PFCP\n--\ndrivers/net/Kconfig-301-\ndrivers/net/Kconfig:302:config AMT\ndrivers/net/Kconfig-303-\ttristate \"Automatic Multicast Tunneling (AMT)\"\n--\ndrivers/net/Makefile=17=obj-$(CONFIG_MACSEC) += macsec.o\ndrivers/net/Makefile:18:obj-$(CONFIG_AMT) += amt.o\ndrivers/net/Makefile-19-obj-$(CONFIG_MACVLAN) += macvlan.o\n"
} |
| 5/2 | 2026/10/13 22:59 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 12256 output: 34 thoughts: 22 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing Network Driver Kconfig Dependencies** Alright, let's get down to brass tacks. I need to understand the dependencies at play within the network driver configuration. My first move is to dive straight into `drivers/net/Kconfig`, specifically around line 302. That's where I expect to find the meat of the matter, the configuration options that define the relationships and prerequisites for this network interface driver. I need to quickly grasp the implications of these dependencies. Is it a core driver? Are there related module configurations? What are the potential conflicts or interactions with other hardware or software components? This initial examination should give me a good lay of the land and point me towards the relevant sub-configurations I need to review in greater detail. I'm looking for a quick scan and a mental map of the configuration space here. |
| 6/2 | 2026/10/13 22:59 | tool | read-file |
0mArgs:{
"File": "drivers/net/Kconfig",
"FirstLine": 300,
"LineCount": 20
}Results: {
"Contents": " 300:\t\t will be called pfcp.\n 301:\t\n 302:\tconfig AMT\n 303:\t\ttristate \"Automatic Multicast Tunneling (AMT)\"\n 304:\t\tdepends on INET \u0026\u0026 IP_MULTICAST\n 305:\t\tselect NET_UDP_TUNNEL\n 306:\t\thelp\n 307:\t\t This allows one to create AMT(Automatic Multicast Tunneling)\n 308:\t\t virtual interfaces that provide multicast tunneling.\n 309:\t\t There are two roles, Gateway, and Relay.\n 310:\t\t Gateway Encapsulates IGMP/MLD traffic from listeners to the Relay.\n 311:\t\t Gateway Decapsulates multicast traffic from the Relay to Listeners.\n 312:\t\t Relay Encapsulates multicast traffic from Sources to Gateway.\n 313:\t\t Relay Decapsulates IGMP/MLD traffic from Gateway.\n 314:\t\n 315:\t\t To compile this drivers as a module, choose M here: the module\n 316:\t\t will be called amt.\n 317:\t\n 318:\tconfig MACSEC\n 319:\t\ttristate \"IEEE 802.1AE MAC-level encryption (MACsec)\"\n"
} |
| 7/2 | 2026/10/13 22:59 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 12831 output: 30 thoughts: 1153 Thoughts: **AMT Driver Worthy of Fuzzing** Okay, so I'm looking at this AMT patch, and my initial thought is, "Can syzkaller handle this?" It's a virtual network device driver, a `rtnl_link_ops`, configured through netlink's `ip link add type amt` command. Good news, it looks like syzkaller already has descriptions for the rtnl messages, virtual devices, and AMT interfaces! This is a great start. We just need to confirm syzkaller's ability to create AMT devices and see if this patch makes it more relevant to fuzzing. Now, let's dive into the patch details. I need to understand what's *really* changing here. I see modifications to several functions: `amt_send_igmp_gq`, `amt_send_mld_gq`, `amt_send_membership_query` - they're taking different parameters now, shifting from a `struct amt_tunnel_list *tunnel` to direct values (`daddr`, `dport`, etc.). I spot key rotation in `amt_secret_work`, which is always interesting. `amt_dev_xmit` looks like it's getting some error handling and readiness checks, and `amt_tunnel_expire` gets a "dead" flag and re-arming based on `last_seen`. The tunnel lookup and creation is being refactored, which is a big deal. Tunnels are no longer allocated on request, which suggests the need to handle potential DoS attacks. I also spot `amt->max_tunnels` and `AMT_MAX_TUNNELS_PER_SRC` caps. The `amt_update_handler` seems to have some serious logic changes around key verification and tunnel management, while `amt_request_handler` has been rewritten. `amt_rcv` is adding checks for remote IP addresses in certain message types. The patch is initializing the key before `amt_socket_create` in `amt_dev_open`, and cleaning up tunnels more carefully in `amt_dev_stop`. There are also changes to the netlink policy and struct definitions, including the structure of the `amt_dev` and `amt_tunnel_list` data structures. The million-dollar question: Is this reachable and worth fuzzing? Let's check the criteria. This patch is definitely touching reachable executable code: Core Subsystem, Refactoring/Code Cleanups (especially the tunnel allocation refactoring), runtime assertions, and a driver accessible via netlink (rtnl) and network traffic (UDP, sockets, etc.). This is exactly what we're looking for. AMT handles packet reception, transmission, tunnel lifecycle, state management, workqueues, and locking. The concurrency fixes, the tunnel allocation, and the overall state management – these are all ripe for fuzzing! This driver has a high potential for edge cases, races, and regressions. Let's check the kernel configs. Based on Kconfig, we need `CONFIG_AMT`, which depends on `INET` and `IP_MULTICAST`, and selects `NET_UDP_TUNNEL`. We should also check for IPv6 dependencies, since AMT can work with both IPv4 and IPv6. |
| 8/2 | 2026/10/13 23:00 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_",
"PathPrefixes": [
"drivers/net/amt.c"
]
}Results: {
"Output": "drivers/net/amt.c=75=static struct igmpv3_grec igmpv3_zero_grec;\ndrivers/net/amt.c-76-\ndrivers/net/amt.c:77:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-78-#define MLD2_ALL_NODE_INIT { { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }\n--\ndrivers/net/amt.c=216=static void amt_destroy_source(struct amt_source_node *snode)\n--\ndrivers/net/amt.c-227-\t\t\t \u0026gnode-\u003egroup_addr.ip4);\ndrivers/net/amt.c:228:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-229-\t} else {\n--\ndrivers/net/amt.c=246=static void amt_del_group(struct amt_dev *amt, struct amt_group_node *gnode)\n--\ndrivers/net/amt.c-259-\t\t\t \u0026gnode-\u003egroup_addr.ip4);\ndrivers/net/amt.c:260:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-261-\telse\n--\ndrivers/net/amt.c=306=static void amt_act_src(struct amt_tunnel_list *tunnel,\n--\ndrivers/net/amt.c-347-\t\t\t action_str[act]);\ndrivers/net/amt.c:348:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-349-\telse\n--\ndrivers/net/amt.c=456=static struct amt_group_node *amt_add_group(struct amt_dev *amt,\n--\ndrivers/net/amt.c-492-\t\t\t \u0026gnode-\u003egroup_addr.ip4);\ndrivers/net/amt.c:493:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-494-\telse\n--\ndrivers/net/amt.c=795=static void amt_send_igmp_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,\n--\ndrivers/net/amt.c-811-\ndrivers/net/amt.c:812:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-813-static struct sk_buff *amt_build_mld_gq(struct amt_dev *amt)\n--\ndrivers/net/amt.c=1201=static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n--\ndrivers/net/amt.c-1206-\tunion amt_addr group = {0,};\ndrivers/net/amt.c:1207:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1208-\tstruct ipv6hdr *ip6h;\n--\ndrivers/net/amt.c-1237-\t\tgroup.ip4 = ip_hdr(skb)-\u003edaddr;\ndrivers/net/amt.c:1238:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1239-\t} else if (iph-\u003eversion == 6) {\n--\ndrivers/net/amt.c-1298-\t\t\t\t\t\tgoto found;\ndrivers/net/amt.c:1299:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1300-\t\t\t\t} else {\n--\ndrivers/net/amt.c=1394=static void amt_cleanup_srcs(struct amt_dev *amt,\n--\ndrivers/net/amt.c-1418-\t\t\t\t\t \u0026gnode-\u003egroup_addr.ip4);\ndrivers/net/amt.c:1419:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1420-\t\t\telse\n--\ndrivers/net/amt.c=1430=static void amt_add_srcs(struct amt_dev *amt, struct amt_tunnel_list *tunnel,\n--\ndrivers/net/amt.c-1435-\tstruct amt_source_node *snode;\ndrivers/net/amt.c:1436:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1437-\tstruct mld2_grec *mld_grec;\n--\ndrivers/net/amt.c-1447-\t} else {\ndrivers/net/amt.c:1448:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1449-\t\tmld_grec = grec;\n--\ndrivers/net/amt.c-1459-\t\t\tsrc.ip4 = igmp_grec-\u003egrec_src[i];\ndrivers/net/amt.c:1460:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1461-\t\telse\n--\ndrivers/net/amt.c-1479-\t\t\t\t\t \u0026gnode-\u003egroup_addr.ip4);\ndrivers/net/amt.c:1480:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1481-\t\t\telse\n--\ndrivers/net/amt.c=1510=static void amt_lookup_act_srcs(struct amt_tunnel_list *tunnel,\n--\ndrivers/net/amt.c-1520-\tstruct igmpv3_grec *igmp_grec;\ndrivers/net/amt.c:1521:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1522-\tstruct mld2_grec *mld_grec;\n--\ndrivers/net/amt.c-1532-\t} else {\ndrivers/net/amt.c:1533:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1534-\t\tmld_grec = grec;\n--\ndrivers/net/amt.c-1547-\t\t\t\tsrc.ip4 = igmp_grec-\u003egrec_src[i];\ndrivers/net/amt.c:1548:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1549-\t\t\telse\n--\ndrivers/net/amt.c-1570-\t\t\t\tsrc.ip4 = igmp_grec-\u003egrec_src[i];\ndrivers/net/amt.c:1571:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1572-\t\t\telse\n--\ndrivers/net/amt.c-1591-\t\t\t\t\t\tsrc.ip4 = igmp_grec-\u003egrec_src[j];\ndrivers/net/amt.c:1592:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1593-\t\t\t\t\telse\n--\ndrivers/net/amt.c=1604=out_sub:;\n--\ndrivers/net/amt.c-1612-\t\t\t\tsrc.ip4 = igmp_grec-\u003egrec_src[i];\ndrivers/net/amt.c:1613:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-1614-\t\t\telse\n--\ndrivers/net/amt.c=2112=static void amt_igmp_report_handler(struct amt_dev *amt, struct sk_buff *skb,\n--\ndrivers/net/amt.c-2131-\ndrivers/net/amt.c:2132:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-2133-/* RFC 3810\n--\ndrivers/net/amt.c=2334=static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb)\n--\ndrivers/net/amt.c-2373-\t\tip_eth_mc_map(iph-\u003edaddr, eth-\u003eh_dest);\ndrivers/net/amt.c:2374:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-2375-\t} else if (iph-\u003eversion == 6) {\n--\ndrivers/net/amt.c=2405=static bool amt_membership_query_handler(struct amt_dev *amt,\n--\ndrivers/net/amt.c-2467-\t\tip_eth_mc_map(iph-\u003edaddr, eth-\u003eh_dest);\ndrivers/net/amt.c:2468:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-2469-\t} else if (iph-\u003eversion == 6) {\n--\ndrivers/net/amt.c=2604=static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)\n--\ndrivers/net/amt.c-2709-\t\tip_eth_mc_map(iph-\u003edaddr, eth-\u003eh_dest);\ndrivers/net/amt.c:2710:#if IS_ENABLED(CONFIG_IPV6)\ndrivers/net/amt.c-2711-\t} else if (iph-\u003eversion == 6) {\n"
} |
| 9/2 | 2026/10/13 23:00 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17076 output: 40 thoughts: 746 Thoughts: **AMT Configuration and Symbol Identification** Okay, so I'm looking at this code and immediately notice that the IPv6 multicast handlers, specifically `amt_build_mld_gq` and `amt_send_mld_gq`, are gated by `#if IS_ENABLED(CONFIG_IPV6)`. This is a pretty standard conditional. I then see in `drivers/net/Kconfig` that `AMT` depends on both `INET` and `IP_MULTICAST`, and also selects `NET_UDP_TUNNEL`. Therefore, if I want to enable this functionality, I'll need to make sure the configs `AMT`, `INET`, `IP_MULTICAST`, and `IPV6` are all set. Now, the real challenge is figuring out which functions are *relevant* enough to use as `FocusSymbols`. Let's look at the diff. I see a list of functions that have been modified: `amt_send_igmp_gq`, `amt_send_mld_gq`, `amt_secret_work`, `amt_send_membership_query`, `amt_dev_xmit`, `amt_tunnel_expire`, `amt_tunnel_lookup`, `amt_tunnel_get_or_create`, `amt_update_handler`, `amt_request_handler`, `amt_rcv`, `amt_dev_open`, and `amt_dev_stop`. Now, the crucial question: are any of these static functions? Or are they static inline? If they are static inline, I won't be able to target them directly in my `FocusSymbols`. I went to check `drivers/net/amt.c` and found that all of these functions are indeed regular static functions. Okay, that's good! But, on an AMD64 Linux kernel, are static C functions visible as symbols if they are *not* inlined? I remember a section in the document about how to deal with static inline functions. The document states, and I quote: "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)." So, since my functions are defined in the `.c` file and are *not* inline, they will, in fact, have symbol entries in `vmlinux`, unless the compiler inlines them. I'm going to double-check `amt.c` to see if that's the case. I need to be sure that these are usable. These static functions are what I'm looking for - `amt_update_handler`, `amt_request_handler`, `amt_dev_xmit`, `amt_tunnel_expire`, etc. - they *should* be my `FocusSymbols`. |
| 10/2 | 2026/10/13 23:00 | tool | grepper |
0mArgs:{
"Expression": "amt_link_ops|amt_netdev_ops",
"PathPrefixes": [
"drivers/net/amt.c"
]
}Results: {
"Output": "drivers/net/amt.c=3314=static void amt_dev_uninit(struct net_device *dev)\n--\ndrivers/net/amt.c-3320-\ndrivers/net/amt.c:3321:static const struct net_device_ops amt_netdev_ops = {\ndrivers/net/amt.c-3322-\t.ndo_init = amt_dev_init,\n--\ndrivers/net/amt.c=3329=static void amt_link_setup(struct net_device *dev)\ndrivers/net/amt.c-3330-{\ndrivers/net/amt.c:3331:\tdev-\u003enetdev_ops = \u0026amt_netdev_ops;\ndrivers/net/amt.c-3332-\tdev-\u003eneeds_free_netdev = true;\n--\ndrivers/net/amt.c=3549=static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev)\n--\ndrivers/net/amt.c-3582-\ndrivers/net/amt.c:3583:static struct rtnl_link_ops amt_link_ops __read_mostly = {\ndrivers/net/amt.c-3584-\t.kind\t\t= \"amt\",\n--\ndrivers/net/amt.c=3648=static int __init amt_init(void)\n--\ndrivers/net/amt.c-3655-\ndrivers/net/amt.c:3656:\terr = rtnl_link_register(\u0026amt_link_ops);\ndrivers/net/amt.c-3657-\tif (err \u003c 0)\n--\ndrivers/net/amt.c-3675-rtnl_unregister:\ndrivers/net/amt.c:3676:\trtnl_link_unregister(\u0026amt_link_ops);\ndrivers/net/amt.c-3677-unregister_notifier:\n--\ndrivers/net/amt.c=3685=static void __exit amt_fini(void)\ndrivers/net/amt.c-3686-{\ndrivers/net/amt.c:3687:\trtnl_link_unregister(\u0026amt_link_ops);\ndrivers/net/amt.c-3688-\tunregister_netdevice_notifier(\u0026amt_notifier_block);\n"
} |
| 11/2 | 2026/10/13 23:00 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19098 output: 32 thoughts: 43 Thoughts: Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts: **Analyzing Packet Handling in the AMT Module** Right, so I need to get a firm grasp on the packet flow within the AMT module. My first priority is to understand how incoming packets actually reach the processing functions. Specifically, I need to trace the arrival of packets into `amt_rcv`, `amt_update_handler`, and `amt_request_handler`. I need to follow the data path. I'll need to know where these functions fit into the larger network stack. Next, I need to locate all of the places where `amt_rcv` is called, so I can understand where this first function fits in. I'm looking for the entry point, the triggering mechanism for `amt_rcv`. This will build on my understanding of how packets arrive. This is key to understanding the overall structure. I need to see the dependencies. I have to see how `amt_rcv` is wired in. |
| 12/2 | 2026/10/13 23:00 | tool | grepper |
0mArgs:{
"Expression": "amt_rcv",
"PathPrefixes": [
"drivers/net/amt.c"
]
}Results: {
"Output": "drivers/net/amt.c=2938=static void amt_gw_rcv(struct amt_dev *amt, struct sk_buff *skb)\n--\ndrivers/net/amt.c-2969-\ndrivers/net/amt.c:2970:static int amt_rcv(struct sock *sk, struct sk_buff *skb)\ndrivers/net/amt.c-2971-{\n--\ndrivers/net/amt.c=3166=static int amt_socket_create(struct amt_dev *amt)\n--\ndrivers/net/amt.c-3178-\ttunnel_cfg.encap_type = 1;\ndrivers/net/amt.c:3179:\ttunnel_cfg.encap_rcv = amt_rcv;\ndrivers/net/amt.c-3180-\ttunnel_cfg.encap_err_lookup = amt_err_lookup;\n"
} |
| 13/2 | 2026/10/13 23:00 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19632 output: 36 thoughts: 31 Thoughts: Okay, here's my interpretation of what's going on, thinking through this as an expert in the field: **Analyzing the UDP Tunnel Receive Callback** Alright, so `amt_rcv` is the receive callback function for a UDP tunnel's encapsulation process. My immediate thought is, let's trace its function calls to understand the flow of data and how it's handled within the UDP tunnel implementation. I need to figure out what happens *after* the datagram hits this specific function. What processes does `amt_rcv` trigger? Where does it pass the data? Is it further decapsulated, routed, or handed off to higher-level protocols? I'm looking for the essential logic of how packets are managed within this tunnel. Knowing the call chain will tell the whole story. |
| 14/2 | 2026/10/13 23:00 | tool | read-file |
0mArgs:{
"File": "drivers/net/amt.c",
"FirstLine": 2970,
"LineCount": 80
}Results: {
"Contents": "2970:\tstatic int amt_rcv(struct sock *sk, struct sk_buff *skb)\n2971:\t{\n2972:\t\tstruct amt_dev *amt;\n2973:\t\t__be32 remote_ip;\n2974:\t\t__be32 saddr;\n2975:\t\tint type;\n2976:\t\tbool err;\n2977:\t\n2978:\t\trcu_read_lock_bh();\n2979:\t\tamt = rcu_dereference_sk_user_data(sk);\n2980:\t\tif (!amt) {\n2981:\t\t\terr = true;\n2982:\t\t\tkfree_skb(skb);\n2983:\t\t\tgoto out;\n2984:\t\t}\n2985:\t\tremote_ip = READ_ONCE(amt-\u003eremote_ip);\n2986:\t\n2987:\t\tskb-\u003edev = amt-\u003edev;\n2988:\t\tsaddr = ip_hdr(skb)-\u003esaddr;\n2989:\t\ttype = amt_parse_type(skb);\n2990:\t\tif (type == -1) {\n2991:\t\t\terr = true;\n2992:\t\t\tgoto drop;\n2993:\t\t}\n2994:\t\n2995:\t\tif (amt-\u003emode == AMT_MODE_GATEWAY) {\n2996:\t\t\tswitch (type) {\n2997:\t\t\tcase AMT_MSG_ADVERTISEMENT:\n2998:\t\t\t\tif (saddr != amt-\u003ediscovery_ip) {\n2999:\t\t\t\t\tnetdev_dbg(amt-\u003edev, \"Invalid Relay IP\\n\");\n3000:\t\t\t\t\terr = true;\n3001:\t\t\t\t\tgoto drop;\n3002:\t\t\t\t}\n3003:\t\t\t\tif (amt_queue_event(amt, AMT_EVENT_RECEIVE, skb)) {\n3004:\t\t\t\t\tnetdev_dbg(amt-\u003edev, \"AMT Event queue full\\n\");\n3005:\t\t\t\t\terr = true;\n3006:\t\t\t\t\tgoto drop;\n3007:\t\t\t\t}\n3008:\t\t\t\tgoto out;\n3009:\t\t\tcase AMT_MSG_MULTICAST_DATA:\n3010:\t\t\t\tif (!remote_ip || saddr != remote_ip) {\n3011:\t\t\t\t\tnetdev_dbg(amt-\u003edev, \"Invalid Relay IP\\n\");\n3012:\t\t\t\t\terr = true;\n3013:\t\t\t\t\tgoto drop;\n3014:\t\t\t\t}\n3015:\t\t\t\terr = amt_multicast_data_handler(amt, skb);\n3016:\t\t\t\tif (err)\n3017:\t\t\t\t\tgoto drop;\n3018:\t\t\t\telse\n3019:\t\t\t\t\tgoto out;\n3020:\t\t\tcase AMT_MSG_MEMBERSHIP_QUERY:\n3021:\t\t\t\tif (!remote_ip || saddr != remote_ip) {\n3022:\t\t\t\t\tnetdev_dbg(amt-\u003edev, \"Invalid Relay IP\\n\");\n3023:\t\t\t\t\terr = true;\n3024:\t\t\t\t\tgoto drop;\n3025:\t\t\t\t}\n3026:\t\t\t\tif (amt_queue_event(amt, AMT_EVENT_RECEIVE, skb)) {\n3027:\t\t\t\t\tnetdev_dbg(amt-\u003edev, \"AMT Event queue full\\n\");\n3028:\t\t\t\t\terr = true;\n3029:\t\t\t\t\tgoto drop;\n3030:\t\t\t\t}\n3031:\t\t\t\tgoto out;\n3032:\t\t\tdefault:\n3033:\t\t\t\terr = true;\n3034:\t\t\t\tnetdev_dbg(amt-\u003edev, \"Invalid type of Gateway\\n\");\n3035:\t\t\t\tbreak;\n3036:\t\t\t}\n3037:\t\t} else {\n3038:\t\t\tswitch (type) {\n3039:\t\t\tcase AMT_MSG_DISCOVERY:\n3040:\t\t\t\terr = amt_discovery_handler(amt, skb);\n3041:\t\t\t\tbreak;\n3042:\t\t\tcase AMT_MSG_REQUEST:\n3043:\t\t\t\terr = amt_request_handler(amt, skb);\n3044:\t\t\t\tbreak;\n3045:\t\t\tcase AMT_MSG_MEMBERSHIP_UPDATE:\n3046:\t\t\t\terr = amt_update_handler(amt, skb);\n3047:\t\t\t\tif (err)\n3048:\t\t\t\t\tgoto drop;\n3049:\t\t\t\telse\n"
} |
| 15/2 | 2026/10/13 23:00 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20998 output: 255 thoughts: 1618 Thoughts: **AMT Fuzzing Assessment** Okay, let's break this down. From a fuzzing perspective, we're looking at a significant refactor of the Automatic Multicast Tunneling (AMT) code. My initial thoughts are centered around which functions are the key areas for investigation and which configurations are essential to enable the relevant functionality. First, the core of AMT revolves around the `amt_rcv` function, which dispatches to different handlers based on the message type in both Gateway and Relay modes. Specifically, in Relay mode, we are concerned with `amt_request_handler` (for AMT_MSG_REQUEST) and `amt_update_handler` (for AMT_MSG_MEMBERSHIP_UPDATE). These, along with `amt_tunnel_get_or_create`, are potential candidates for inlining, but let's list them explicitly for now as well as `amt_tunnel_expire`, `amt_dev_xmit`, `amt_dev_open`, `amt_dev_stop`. The goal here is to be as specific as possible. The `amt_send_membership_query` and the secret key handler `amt_secret_work` also make the list. The guidelines are clear: We need to target specific feature logic and entry points. Absolutely no ubiquitous functions. We must avoid generic allocators, deallocators, etc. These functions are specific to AMT. Therefore, for `FocusSymbols`, I will use: - `amt_request_handler`: Handles request generation, generates MAC, sends IGMP/MLD query without allocating tunnel state. - `amt_update_handler`: Handles MAC authentication, creates tunnel state via `amt_tunnel_get_or_create`, updates `last_seen`. - `amt_tunnel_get_or_create`: Responsible for tunnel lookup, per-source limits, allocation, initialization, and GC. - `amt_tunnel_expire`: Handles tunnel expiration, `tunnel->dead` checks, re-arming based on `last_seen`, and deletion. - `amt_dev_xmit`: Modified transmit handler, checking flags and accounting. - `amt_dev_stop`: Modified stop handler, marks tunnels dead, unlinks them under lock and cancels `gc_wq`. - `amt_dev_open`: Modified open handler, seeds keys prior to socket creation. - `amt_rcv`: UDP tunnel encap receive entrypoint, modified check for `remote_ip`. - `amt_send_membership_query`: Modified to take daddr, dport, nonce, mac by value instead of tunnel pointer. - `amt_secret_work`: Rotates secret keys. These functions are all extremely specific to the AMT implementation. No generic functions here. Now, for `Reasoning`, here's the why: The patch introduces substantial changes to tunnel state management, MAC validation, and teardown synchronization. This includes: deferring tunnel allocation, lifetime management and secret key rotation, handling complex synchronization and race conditions during tunnel expiration and device stop, and changes in transmit and receive paths. All these aspects warrant fuzzing to uncover possible concurrency regressions, memory management races, or parsing/validation errors. Finally, regarding `EnableConfigs`, we need the AMT driver itself, IP and Multicast support, IPv6 for MLD, and INET. We also know `NET_UDP_TUNNEL` is selected automatically via Kconfig, so it does not need to be included. Therefore, the list of configs will be: `AMT`, `INET`, `IP_MULTICAST`, `IPV6`. This should be a very clean and accurate assessment of the fuzzing needs. |
| 16/2 | 2026/10/13 23:00 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"AMT",
"INET",
"IP_MULTICAST",
"IPV6"
],
"FocusSymbols": [
"amt_request_handler",
"amt_update_handler",
"amt_tunnel_get_or_create",
"amt_tunnel_expire",
"amt_dev_stop",
"amt_dev_open",
"amt_dev_xmit",
"amt_rcv",
"amt_send_membership_query",
"amt_secret_work"
],
"Reasoning": "The patch refactors Automatic Multicast Tunneling (AMT) lifecycle and state management in drivers/net/amt.c. It delays tunnel allocation until Membership Update authentication, adds per-source tunnel limits, introduces rolling 4-generation secret keys, and restructures concurrency synchronization between amt_tunnel_expire and amt_dev_stop using a new tunnel 'dead' flag to eliminate use-after-free races. AMT virtual interfaces are fully reachable via rtnl netlink and network packet injection in virtualized environments, making these lifecycle and concurrency changes prime targets for syzkaller fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"AMT",
"INET",
"IP_MULTICAST",
"IPV6"
],
"FocusSymbols": [
"amt_request_handler",
"amt_update_handler",
"amt_tunnel_get_or_create",
"amt_tunnel_expire",
"amt_dev_stop",
"amt_dev_open",
"amt_dev_xmit",
"amt_rcv",
"amt_send_membership_query",
"amt_secret_work"
],
"Reasoning": "The patch refactors Automatic Multicast Tunneling (AMT) lifecycle and state management in drivers/net/amt.c. It delays tunnel allocation until Membership Update authentication, adds per-source tunnel limits, introduces rolling 4-generation secret keys, and restructures concurrency synchronization between amt_tunnel_expire and amt_dev_stop using a new tunnel 'dead' flag to eliminate use-after-free races. AMT virtual interfaces are fully reachable via rtnl netlink and network packet injection in virtualized environments, making these lifecycle and concurrency changes prime targets for syzkaller fuzzing.",
"WorthFuzzing": true
} |
| 17/1 | 2026/10/13 23:00 | action | If |
2mArgs:{
"WorthFuzzing": true
}Results: null |
| 18/2 | 2026/10/13 23:00 | agent | kmsan-evaluator |
2mModel:core Args: null Results: {
"KMSANReasoning": "The patch modifies the AMT (Automatic Multicast Tunneling) driver to defer tunnel allocation until membership update authentication, prevents tunnel table exhaustion, and resolves a use-after-free (UAF) race condition between amt_tunnel_expire(), amt_update_handler(), and amt_dev_stop().\n\nKASAN vs. KMSAN Applicability:\n- The risks and bugs addressed by this patch are concurrency races, resource exhaustion, and use-after-free errors (where concurrent updates could re-arm gc_wq on a tunnel already marked for destruction). These types of bugs (UAF, invalid pointer dereferences, concurrent list mutations) are squarely within the scope of KASAN and LOCKDEP.\n- There are no uninitialized memory reads or information leak hazards:\n - Tunnel structures are allocated using kzalloc(), guaranteeing zero-initialization of all fields including newly added members (last_seen, dead).\n - The siphash key array (amt-\u003ekey[4]) is fully initialized in amt_dev_open() using get_random_bytes() with sizeof(amt-\u003ekey).\n - Outgoing query headers (amtmq) have all fields explicitly written before transmission over the network.\n - Netlink policy updates use NLA_POLICY_EXACT_LEN for incoming attributes, which is an input validation concern rather than an uninitialized memory issue.\n - No uninitialized data or kernel structure padding is copied to user space.\n\nBecause all memory is fully initialized and the changes focus on concurrency and lifecycle management, standard KASAN and LOCKDEP fuzzing is sufficient and a dedicated KMSAN session is not warranted.",
"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 925aeb4dbc2c869ae154145569f1fd985f86d10f
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Oct 13 22:59:37 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index b53f8ec556615..0124f1be673b3 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -784,11 +784,16 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
static bool amt_send_membership_query(struct amt_dev *amt,
struct sk_buff *skb,
- struct amt_tunnel_list *tunnel,
+ __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac,
bool v6);
-static void amt_send_igmp_gq(struct amt_dev *amt,
- struct amt_tunnel_list *tunnel)
+/* The destination is passed by value rather than as a tunnel: the requesting
+ * source is not authenticated yet and no tunnel state exists for it - see
+ * amt_request_handler().
+ */
+static void amt_send_igmp_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac)
{
struct sk_buff *skb;
@@ -797,7 +802,8 @@ static void amt_send_igmp_gq(struct amt_dev *amt,
return;
skb_pull(skb, sizeof(struct ethhdr));
- if (amt_send_membership_query(amt, skb, tunnel, false)) {
+ if (amt_send_membership_query(amt, skb, daddr, dport, nonce, mac,
+ false)) {
amt->dev->stats.tx_dropped++;
kfree_skb(skb);
}
@@ -876,7 +882,8 @@ static struct sk_buff *amt_build_mld_gq(struct amt_dev *amt)
return skb;
}
-static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)
+static void amt_send_mld_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac)
{
struct sk_buff *skb;
@@ -885,13 +892,15 @@ static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)
return;
skb_pull(skb, sizeof(struct ethhdr));
- if (amt_send_membership_query(amt, skb, tunnel, true)) {
+ if (amt_send_membership_query(amt, skb, daddr, dport, nonce, mac,
+ true)) {
amt->dev->stats.tx_dropped++;
kfree_skb(skb);
}
}
#else
-static void amt_send_mld_gq(struct amt_dev *amt, struct amt_tunnel_list *tunnel)
+static void amt_send_mld_gq(struct amt_dev *amt, __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac)
{
}
#endif
@@ -925,7 +934,14 @@ static void amt_secret_work(struct work_struct *work)
secret_wq);
spin_lock_bh(&amt->lock);
- get_random_bytes(&amt->key, sizeof(siphash_key_t));
+ /* Keep three previous generations for a 180-240s MAC lifetime.
+ * Its 180s floor exceeds the advertised QQIC (amt->qi, 125s),
+ * allowing the gateway to renew its MAC with a periodic Request.
+ */
+ amt->key[3] = amt->key[2];
+ amt->key[2] = amt->key[1];
+ amt->key[1] = amt->key[0];
+ get_random_bytes(&amt->key[0], sizeof(siphash_key_t));
spin_unlock_bh(&amt->lock);
mod_delayed_work(amt_wq, &amt->secret_wq,
msecs_to_jiffies(AMT_SECRET_TIMEOUT));
@@ -1115,9 +1131,16 @@ static void amt_send_multicast_data(struct amt_dev *amt,
0);
}
+/* Send a Membership Query to a source that has not been authenticated yet.
+ *
+ * No per-source state exists at this point and none is created: the nonce and
+ * the keyed MAC are carried in the Query and recomputed from the Membership
+ * Update when it comes back, so everything this needs is passed by value.
+ */
static bool amt_send_membership_query(struct amt_dev *amt,
struct sk_buff *skb,
- struct amt_tunnel_list *tunnel,
+ __be32 daddr, __be16 dport,
+ __be32 nonce, u64 mac,
bool v6)
{
struct amt_header_membership_query *amtmq;
@@ -1138,13 +1161,13 @@ static bool amt_send_membership_query(struct amt_dev *amt,
skb_reset_inner_headers(skb);
memset(&fl4, 0, sizeof(struct flowi4));
fl4.flowi4_oif = amt->stream_dev->ifindex;
- fl4.daddr = tunnel->ip4;
+ fl4.daddr = daddr;
fl4.saddr = amt->local_ip;
fl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS);
fl4.flowi4_proto = IPPROTO_UDP;
rt = ip_route_output_key(amt->net, &fl4);
if (IS_ERR(rt)) {
- netdev_dbg(amt->dev, "no route to %pI4\n", &tunnel->ip4);
+ netdev_dbg(amt->dev, "no route to %pI4\n", &daddr);
return true;
}
@@ -1154,8 +1177,8 @@ static bool amt_send_membership_query(struct amt_dev *amt,
amtmq->reserved = 0;
amtmq->l = 0;
amtmq->g = 0;
- amtmq->nonce = tunnel->nonce;
- amtmq->response_mac = tunnel->mac;
+ amtmq->nonce = nonce;
+ amtmq->response_mac = mac;
if (!v6)
skb_set_inner_protocol(skb, htons(ETH_P_IP));
@@ -1168,11 +1191,10 @@ static bool amt_send_membership_query(struct amt_dev *amt,
ip4_dst_hoplimit(&rt->dst),
0,
amt->relay_port,
- tunnel->source_port,
+ dport,
false,
false,
0);
- amt_update_relay_status(tunnel, AMT_STATUS_SENT_QUERY, true);
return false;
}
@@ -1236,7 +1258,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
group.ip6 = ipv6_hdr(skb)->daddr;
#endif
} else {
- dev->stats.tx_errors++;
+ DEV_STATS_INC(dev, tx_errors);
goto free;
}
@@ -1249,12 +1271,21 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
/* Gateway only passes IGMP/MLD packets */
if (!report)
goto free;
+ /* A validated report can only be forwarded after the relay's
+ * family-specific Membership Query supplies the state echoed
+ * by the Membership Update. Log this readiness failure before
+ * the shared drop path accounts it.
+ */
if ((!v6 && !READ_ONCE(amt->ready4)) ||
- (v6 && !READ_ONCE(amt->ready6)))
+ (v6 && !READ_ONCE(amt->ready6))) {
+ netdev_dbg(dev, "drop %s report: waiting for Membership Query\n",
+ v6 ? "MLD" : "IGMP");
goto free;
+ }
if (amt_send_membership_update(amt, skb, v6))
goto free;
- goto unlock;
+ /* Sent: the tunnel consumed the skb. Not a drop. */
+ return NETDEV_TX_OK;
} else if (amt->mode == AMT_MODE_RELAY) {
if (!data)
goto free;
@@ -1283,8 +1314,7 @@ static netdev_tx_t amt_dev_xmit(struct sk_buff *skb, struct net_device *dev)
return NETDEV_TX_OK;
free:
dev_kfree_skb(skb);
-unlock:
- dev->stats.tx_dropped++;
+ DEV_STATS_INC(dev, tx_dropped);
return NETDEV_TX_OK;
}
@@ -1328,8 +1358,30 @@ static void amt_tunnel_expire(struct work_struct *work)
struct amt_tunnel_list,
gc_wq);
struct amt_dev *amt = tunnel->amt;
+ unsigned long deadline;
spin_lock_bh(&amt->lock);
+ /* amt_dev_stop() has taken ownership of the teardown. */
+ if (tunnel->dead) {
+ spin_unlock_bh(&amt->lock);
+ return;
+ }
+ /* A Membership Update may have refreshed the tunnel after this work
+ * was queued. Re-arm from last_seen rather than letting
+ * amt_update_handler() re-arm gc_wq: this handler is then the only
+ * context that frees the tunnel (and the only one amt_dev_stop() has
+ * to cancel), which closes the use-after-free where a concurrent
+ * Update re-armed gc_wq on a tunnel this function had already
+ * list_del_rcu()'d and kfree_rcu()'d.
+ */
+ deadline = READ_ONCE(tunnel->last_seen) +
+ msecs_to_jiffies(amt_gmi(amt)) * 3;
+ if (time_before(jiffies, deadline)) {
+ mod_delayed_work(amt_wq, &tunnel->gc_wq, deadline - jiffies);
+ spin_unlock_bh(&amt->lock);
+ return;
+ }
+ tunnel->dead = true;
rcu_read_lock();
list_del_rcu(&tunnel->list);
amt->nr_tunnels--;
@@ -2461,16 +2513,107 @@ static bool amt_membership_query_handler(struct amt_dev *amt,
return false;
}
+/* The tunnel of the gateway endpoint @saddr:@sport, or NULL. */
+static struct amt_tunnel_list *amt_tunnel_lookup(struct amt_dev *amt,
+ __be32 saddr, __be16 sport)
+{
+ struct amt_tunnel_list *tunnel;
+
+ list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list)
+ if (tunnel->ip4 == saddr && tunnel->source_port == sport)
+ return tunnel;
+ return NULL;
+}
+
+/* Look up the tunnel for a source whose Membership Update has already been
+ * authenticated, creating it on first contact.
+ *
+ * This is now the only place tunnel state is allocated, so amt->max_tunnels
+ * bounds the number of *verified* gateways rather than the number of
+ * unverified Requests anyone can send. A fixed per-source cap additionally
+ * stops one verified endpoint from monopolising the table; a single gateway
+ * needs at most one tunnel per family (RFC 7450 s4.2.2).
+ */
+static struct amt_tunnel_list *amt_tunnel_get_or_create(struct amt_dev *amt,
+ __be32 saddr,
+ __be16 sport)
+{
+ struct amt_tunnel_list *tunnel;
+ int per_src = 0;
+ int i;
+
+ tunnel = amt_tunnel_lookup(amt, saddr, sport);
+ if (tunnel)
+ return tunnel;
+
+ spin_lock_bh(&amt->lock);
+
+ /* Re-check under the lock in case a concurrent Update won the race, and
+ * count how many tunnels this source already holds on the same walk.
+ */
+ list_for_each_entry(tunnel, &amt->tunnel_list, list) {
+ if (tunnel->ip4 != saddr)
+ continue;
+ if (tunnel->source_port == sport) {
+ spin_unlock_bh(&amt->lock);
+ return tunnel;
+ }
+ per_src++;
+ }
+
+ if (amt->nr_tunnels >= amt->max_tunnels ||
+ per_src >= AMT_MAX_TUNNELS_PER_SRC) {
+ spin_unlock_bh(&amt->lock);
+ return NULL;
+ }
+
+ if (!netif_running(amt->dev)) {
+ spin_unlock_bh(&amt->lock);
+ return NULL;
+ }
+
+ tunnel = kzalloc(sizeof(*tunnel) +
+ (sizeof(struct hlist_head) * amt->hash_buckets),
+ GFP_ATOMIC);
+ if (!tunnel) {
+ spin_unlock_bh(&amt->lock);
+ return NULL;
+ }
+
+ tunnel->source_port = sport;
+ tunnel->ip4 = saddr;
+ tunnel->amt = amt;
+ spin_lock_init(&tunnel->lock);
+ for (i = 0; i < amt->hash_buckets; i++)
+ INIT_HLIST_HEAD(&tunnel->groups[i]);
+
+ tunnel->last_seen = jiffies;
+ INIT_DELAYED_WORK(&tunnel->gc_wq, amt_tunnel_expire);
+ __amt_update_relay_status(tunnel, AMT_STATUS_RECEIVED_UPDATE, false);
+
+ /* Publish only once the entry is fully initialised. */
+ list_add_tail_rcu(&tunnel->list, &amt->tunnel_list);
+ amt->nr_tunnels++;
+ mod_delayed_work(amt_wq, &tunnel->gc_wq,
+ msecs_to_jiffies(amt_gmi(amt)) * 3);
+ spin_unlock_bh(&amt->lock);
+
+ return tunnel;
+}
+
static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
{
struct amt_header_membership_update *amtmu;
struct amt_tunnel_list *tunnel;
struct ethhdr *eth;
struct iphdr *iph;
- int len, hdr_size;
+ siphash_key_t key[4];
+ int len, hdr_size, i;
u64 response_mac;
+ bool verified = false;
__be32 saddr;
__be32 nonce;
+ __be16 sport;
saddr = ip_hdr(skb)->saddr;
@@ -2484,30 +2627,58 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
nonce = amtmu->nonce;
response_mac = amtmu->response_mac;
+ /* Snapshot the tunnel endpoint port before the encap is stripped. */
+ sport = udp_hdr(skb)->source;
- if (iptunnel_pull_header(skb, hdr_size, skb->protocol, false))
- return true;
+ /* Recompute the MAC handed out in the Membership Query. Four live
+ * secrets cover 180-240s, exceeding the advertised QQIC (amt->qi,
+ * 125s), so a periodic Request can renew the MAC before it expires.
+ * Authenticate before decapsulation or allocating tunnel state.
+ */
+ spin_lock_bh(&amt->lock);
+ for (i = 0; i < ARRAY_SIZE(key); i++)
+ key[i] = amt->key[i];
+ spin_unlock_bh(&amt->lock);
- skb_reset_network_header(skb);
+ for (i = 0; i < ARRAY_SIZE(key); i++) {
+ u64 mac = siphash_3u32((__force u32)saddr,
+ (__force u32)sport,
+ (__force u32)nonce,
+ &key[i]) >> 16;
- list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list) {
- if (tunnel->ip4 == saddr) {
- if ((nonce == tunnel->nonce &&
- response_mac == tunnel->mac)) {
- mod_delayed_work(amt_wq, &tunnel->gc_wq,
- msecs_to_jiffies(amt_gmi(amt))
- * 3);
- goto report;
- } else {
- netdev_dbg(amt->dev, "Invalid MAC\n");
- return true;
- }
+ if (response_mac == mac) {
+ verified = true;
+ break;
}
}
- return true;
+ if (!verified) {
+ netdev_dbg(amt->dev, "Invalid MAC\n");
+ return true;
+ }
+
+ tunnel = amt_tunnel_get_or_create(amt, saddr, sport);
+ if (!tunnel) {
+ /* Out of tunnel slots. Unlike the Request path this reply only
+ * ever goes to a source that proved it received our Query,
+ * so it
+ * cannot be used to reflect at a spoofed third party.
+ */
+ icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
+ return true;
+ }
+
+ /* Refresh liveness only. gc_wq is re-armed by amt_tunnel_expire()
+ * itself from last_seen, never from here, so this cannot re-queue the
+ * work of a tunnel that is being freed.
+ */
+ WRITE_ONCE(tunnel->last_seen, jiffies);
+
+ if (iptunnel_pull_header(skb, hdr_size, skb->protocol, false))
+ return true;
+
+ skb_reset_network_header(skb);
-report:
if (!pskb_may_pull(skb, sizeof(*iph)))
return true;
@@ -2522,6 +2693,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
}
spin_lock_bh(&tunnel->lock);
+ if (READ_ONCE(tunnel->dead)) {
+ spin_unlock_bh(&tunnel->lock);
+ return true;
+ }
amt_igmp_report_handler(amt, skb, tunnel);
spin_unlock_bh(&tunnel->lock);
@@ -2542,6 +2717,10 @@ static bool amt_update_handler(struct amt_dev *amt, struct sk_buff *skb)
}
spin_lock_bh(&tunnel->lock);
+ if (READ_ONCE(tunnel->dead)) {
+ spin_unlock_bh(&tunnel->lock);
+ return true;
+ }
amt_mld_report_handler(amt, skb, tunnel);
spin_unlock_bh(&tunnel->lock);
@@ -2684,15 +2863,29 @@ static bool amt_discovery_handler(struct amt_dev *amt, struct sk_buff *skb)
return false;
}
+/* Handle an AMT Relay Membership Request.
+ *
+ * The source of a Request is unauthenticated: it is a bare UDP datagram with a
+ * trivially spoofable source address. Allocating tunnel state here let an
+ * attacker fill the table from spoofed addresses, and let one spoofed packet
+ * overwrite the nonce/MAC of an established tunnel and cut that gateway off.
+ *
+ * Nothing needs to be remembered at this point. Every input to the keyed MAC
+ * is carried in the packet or is device state, so the MAC is generated here,
+ * echoed back by the gateway in its Membership Update, and recomputed and
+ * verified there - see amt_update_handler(). Tunnel state is created only once
+ * that verification succeeds.
+ */
static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)
{
struct amt_header_request *amtrh;
struct amt_tunnel_list *tunnel;
- unsigned long long key;
struct udphdr *udph;
struct iphdr *iph;
+ siphash_key_t key;
+ bool full, have = false;
+ int per_src = 0;
u64 mac;
- int i;
if (!pskb_may_pull(skb, sizeof(*udph) + sizeof(*amtrh)))
return true;
@@ -2704,59 +2897,40 @@ static bool amt_request_handler(struct amt_dev *amt, struct sk_buff *skb)
if (amtrh->reserved1 || amtrh->reserved2 || amtrh->version)
return true;
- list_for_each_entry_rcu(tunnel, &amt->tunnel_list, list)
- if (tunnel->ip4 == iph->saddr)
- goto send;
-
- spin_lock_bh(&amt->lock);
- if (amt->nr_tunnels >= amt->max_tunnels) {
- spin_unlock_bh(&amt->lock);
- icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
+ if (!netif_running(amt->dev) || !netif_running(amt->stream_dev))
return true;
- }
- tunnel = kzalloc(sizeof(*tunnel) +
- (sizeof(struct hlist_head) * amt->hash_buckets),
- GFP_ATOMIC);
- if (!tunnel) {
- spin_unlock_bh(&amt->lock);
- return true;
+ /* Snapshot the secret and tunnel limits on the same locked walk. */
+ spin_lock_bh(&amt->lock);
+ key = amt->key[0];
+ full = amt->nr_tunnels >= amt->max_tunnels;
+ list_for_each_entry(tunnel, &amt->tunnel_list, list) {
+ if (tunnel->ip4 != iph->saddr)
+ continue;
+ per_src++;
+ if (tunnel->source_port == udph->source)
+ have = true;
}
-
- tunnel->source_port = udph->source;
- tunnel->ip4 = iph->saddr;
-
- memcpy(&key, &tunnel->key, sizeof(unsigned long long));
- tunnel->amt = amt;
- spin_lock_init(&tunnel->lock);
- for (i = 0; i < amt->hash_buckets; i++)
- INIT_HLIST_HEAD(&tunnel->groups[i]);
-
- INIT_DELAYED_WORK(&tunnel->gc_wq, amt_tunnel_expire);
-
- list_add_tail_rcu(&tunnel->list, &amt->tunnel_list);
- tunnel->key = amt->key;
- __amt_update_relay_status(tunnel, AMT_STATUS_RECEIVED_REQUEST, true);
- amt->nr_tunnels++;
- mod_delayed_work(amt_wq, &tunnel->gc_wq,
- msecs_to_jiffies(amt_gmi(amt)));
spin_unlock_bh(&amt->lock);
-send:
- tunnel->nonce = amtrh->nonce;
- mac = siphash_3u32((__force u32)tunnel->ip4,
- (__force u32)tunnel->source_port,
- (__force u32)tunnel->nonce,
- &tunnel->key);
- tunnel->mac = mac >> 16;
-
- if (!netif_running(amt->dev) || !netif_running(amt->stream_dev))
+ /* Withhold the Query when a new endpoint cannot get a tunnel, so the
+ * gateway rediscovers. Existing endpoints must still renew their MAC.
+ * Drop silently: an unauthenticated Request must not reflect ICMP.
+ */
+ if (!have && (full || per_src >= AMT_MAX_TUNNELS_PER_SRC))
return true;
+ mac = siphash_3u32((__force u32)iph->saddr,
+ (__force u32)udph->source,
+ (__force u32)amtrh->nonce,
+ &key) >> 16;
+
if (!amtrh->p)
- amt_send_igmp_gq(amt, tunnel);
+ amt_send_igmp_gq(amt, iph->saddr, udph->source, amtrh->nonce,
+ mac);
else
- amt_send_mld_gq(amt, tunnel);
+ amt_send_mld_gq(amt, iph->saddr, udph->source, amtrh->nonce,
+ mac);
return false;
}
@@ -2833,7 +3007,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
}
goto out;
case AMT_MSG_MULTICAST_DATA:
- if (saddr != remote_ip) {
+ if (!remote_ip || saddr != remote_ip) {
netdev_dbg(amt->dev, "Invalid Relay IP\n");
err = true;
goto drop;
@@ -2844,7 +3018,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
else
goto out;
case AMT_MSG_MEMBERSHIP_QUERY:
- if (saddr != remote_ip) {
+ if (!remote_ip || saddr != remote_ip) {
netdev_dbg(amt->dev, "Invalid Relay IP\n");
err = true;
goto drop;
@@ -3024,6 +3198,14 @@ static int amt_dev_open(struct net_device *dev)
enable_delayed_work(&amt->discovery_wq);
enable_delayed_work(&amt->req_wq);
+ /* Seed the keys before amt_socket_create() enables the encap receive
+ * path, so a Request or Update that arrives immediately is never
+ * verified against an all-zero (predictable) secret.
+ */
+ spin_lock_bh(&amt->lock);
+ get_random_bytes(&amt->key, sizeof(amt->key));
+ spin_unlock_bh(&amt->lock);
+
err = amt_socket_create(amt);
if (err) {
disable_delayed_work(&amt->req_wq);
@@ -3034,7 +3216,6 @@ static int amt_dev_open(struct net_device *dev)
amt->req_cnt = 0;
WRITE_ONCE(amt->remote_ip, 0);
amt->nonce = 0;
- get_random_bytes(&amt->key, sizeof(siphash_key_t));
amt->status = AMT_STATUS_INIT;
if (amt->mode == AMT_MODE_GATEWAY) {
@@ -3050,7 +3231,7 @@ static int amt_dev_open(struct net_device *dev)
static int amt_dev_stop(struct net_device *dev)
{
struct amt_dev *amt = netdev_priv(dev);
- struct amt_tunnel_list *tunnel, *tmp;
+ struct amt_tunnel_list *tunnel;
struct sk_buff *skb;
struct sock *sk;
int i;
@@ -3079,9 +3260,31 @@ static int amt_dev_stop(struct net_device *dev)
amt->req_cnt = 0;
WRITE_ONCE(amt->remote_ip, 0);
- list_for_each_entry_safe(tunnel, tmp, &amt->tunnel_list, list) {
- list_del_rcu(&tunnel->list);
- amt->nr_tunnels--;
+ /* Mark every tunnel dead first, so a concurrent amt_tunnel_expire()
+ * sees ->dead and bails instead of freeing a tunnel in parallel; this
+ * path is then the sole freer.
+ */
+ spin_lock_bh(&amt->lock);
+ list_for_each_entry(tunnel, &amt->tunnel_list, list)
+ tunnel->dead = true;
+ spin_unlock_bh(&amt->lock);
+
+ /* Unlink one tunnel at a time with list_del_rcu() rather than splicing
+ * the list onto a local head: a lockless reader walking tunnel_list
+ * must always terminate at &amt->tunnel_list, never at a stack head.
+ */
+ for (;;) {
+ spin_lock_bh(&amt->lock);
+ tunnel = list_first_entry_or_null(&amt->tunnel_list,
+ struct amt_tunnel_list, list);
+ if (tunnel) {
+ list_del_rcu(&tunnel->list);
+ amt->nr_tunnels--;
+ }
+ spin_unlock_bh(&amt->lock);
+ if (!tunnel)
+ break;
+
cancel_delayed_work_sync(&tunnel->gc_wq);
amt_clear_groups(tunnel);
kfree_rcu(tunnel, rcu);
@@ -3152,9 +3355,9 @@ static const struct nla_policy amt_policy[IFLA_AMT_MAX + 1] = {
[IFLA_AMT_RELAY_PORT] = { .type = NLA_U16 },
[IFLA_AMT_GATEWAY_PORT] = { .type = NLA_U16 },
[IFLA_AMT_LINK] = { .type = NLA_U32 },
- [IFLA_AMT_LOCAL_IP] = { .len = sizeof_field(struct iphdr, daddr) },
+ [IFLA_AMT_LOCAL_IP] = NLA_POLICY_EXACT_LEN(sizeof(__be32)),
[IFLA_AMT_REMOTE_IP] = { .len = sizeof_field(struct iphdr, daddr) },
- [IFLA_AMT_DISCOVERY_IP] = { .len = sizeof_field(struct iphdr, daddr) },
+ [IFLA_AMT_DISCOVERY_IP] = NLA_POLICY_EXACT_LEN(sizeof(__be32)),
[IFLA_AMT_MAX_TUNNELS] = { .type = NLA_U32 },
};
diff --git a/include/net/amt.h b/include/net/amt.h
index 2846dde0cadce..63bb647a4780f 100644
--- a/include/net/amt.h
+++ b/include/net/amt.h
@@ -240,12 +240,19 @@ struct amt_tunnel_list {
u32 nr_sources;
enum amt_status status;
struct delayed_work gc_wq;
+ /* Last time a valid Membership Update refreshed this tunnel. gc_wq
+ * re-arms itself from this rather than being re-armed by the Update
+ * path, so the Update path cannot re-queue the work of a tunnel that
+ * amt_tunnel_expire() is freeing.
+ */
+ unsigned long last_seen;
+ /* Set under amt->lock by whichever of amt_tunnel_expire() or
+ * amt_dev_stop() reaches the tunnel first; the other then leaves it
+ * alone, so the tunnel is freed exactly once.
+ */
+ bool dead;
__be16 source_port;
__be32 ip4;
- __be32 nonce;
- siphash_key_t key;
- u64 mac:48,
- reserved:16;
struct rcu_head rcu;
struct hlist_head groups[];
};
@@ -325,8 +332,10 @@ struct amt_dev {
struct work_struct event_wq;
/* AMT status */
enum amt_status status;
- /* Generated key */
- siphash_key_t key;
+ /* Current key and three previous generations keep MACs valid for
+ * 180-240s, beyond the advertised QQIC (see amt_secret_work()).
+ */
+ siphash_key_t key[4];
struct sock __rcu *sk;
u32 max_groups;
u32 max_sources;
@@ -383,6 +392,12 @@ struct amt_dev {
#define AMT_SECRET_TIMEOUT 60000
#define IANA_AMT_UDP_PORT 2268
#define AMT_MAX_TUNNELS 128
+/* Per-source tunnel cap. A single gateway needs at most one tunnel per
+ * family; this stops one verified source endpoint from monopolising the
+ * AMT_MAX_TUNNELS-slot table. Deployments that legitimately need more behind
+ * one address (e.g. CGNAT) use the configurable net-next per-source bound.
+ */
+#define AMT_MAX_TUNNELS_PER_SRC 16
#define AMT_MAX_REQS 128
#define AMT_GW_HLEN (sizeof(struct iphdr) + \
sizeof(struct udphdr) + \
|
| 19/3 | 2026/10/13 23:00 | llm | kmsan-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 11009 output: 36 thoughts: 1693 Thoughts: **KMSAN Analysis of AMT Patch** Okay, let's dive into this patch for the AMT (Automatic Multicast Tunneling) driver and figure out if we need a dedicated KMSAN fuzzing session. My goal is to determine if this patch introduces any uninitialized memory reads or other issues that KMSAN would be good at catching. First, I need to understand what this patch actually *does*. I see that `amt_send_membership_query` is refactored to take its arguments by value instead of a `struct amt_tunnel_list` pointer. This means the existing structure and fields from the structure are replaced with their current values passed directly into the function. This change seems straightforward. The function calls `amt_send_igmp_gq` and `amt_send_mld_gq` were updated accordingly. Next, I'm noting a crucial change in `amt_secret_work`. The `amt->key` member is changing from a single `siphash_key_t` to an array of four `siphash_key_t` values. I see that the code shifts the previous key values before obtaining a new random key using `get_random_bytes(&amt->key[0], sizeof(siphash_key_t))`. This is important. The keys will now be initialized from random bytes from `amt_dev_open`, not zero or some other default value. Looking at `amt_dev_xmit`, there's a check now for `(!v6 && !READ_ONCE(amt->ready4)) || (v6 && !READ_ONCE(amt->ready6))` with netdev_dbg logging. This seems like a reasonable check to prevent transmission on a tunnel that isn't ready. Nothing to worry about from a KMSAN perspective. The changes in `amt_tunnel_expire` focus on tunnel expiration logic using `jiffies` and a `deadline`. This doesn't seem to have direct KMSAN implications. There is logic to mark the tunnel as dead and delete it from a list, but this also seems normal. Now, let's see these new helper functions, `amt_tunnel_lookup` and `amt_tunnel_get_or_create`. These use `kzalloc` to allocate the `amt_tunnel_list` struct, and as I know `kzalloc` zeroes the memory it allocates. This is a very good sign because it prevents uninitialized memory reads in a new allocation. The fields like `source_port`, `ip4`, `amt`, `last_seen`, `gc_wq`, and `status` are initialized explicitly. `tunnel->dead` is also false because of `kzalloc`. This is looking good. In `amt_update_handler`, the patch copies the keys under a lock, calculates siphash MACs, and calls `amt_tunnel_get_or_create`. I see that there's a bit of copying here: `for (i = 0; i < ARRAY_SIZE(key); i++) key[i] = amt->key[i];` This doesn't appear problematic because `amt->key` is initialized. In `amt_request_handler`, the patch now calculates the MAC "on the fly" using `amt->key[0]` and avoids tunnel allocation. This seems more efficient and, critically, avoids potentially problematic uninitialized memory reads associated with tunnel allocation. The changes in `amt_rcv` and `amt_dev_stop` seem like straightforward bug fixes and cleanups, not related to KMSAN concerns. `amt_dev_stop` marks every tunnel dead under lock and frees them. The policy definitions in `amt_policy` are interesting, but they are just enforcing strict length requirements for the netlink attributes using `NLA_POLICY_EXACT_LEN`. This prevents potential issues related to reading garbage or bypassing input validation, which is good. Finally, in the `include/net/amt.h` file, I'm checking the struct changes. The `amt_tunnel_list` struct no longer contains the `nonce`, `key`, `mac`, and `reserved` fields; however, it *does* contain `unsigned long last_seen; bool dead;`, which are initializable. The `amt_dev` struct changes `siphash_key_t key;` to `siphash_key_t key[4];` and defined `AMT_MAX_TUNNELS_PER_SRC 16`. The `amt_dev` change is important, which is a structural change, related to the change in `amt_secret_work`, however the keys are initialized. Now, I'm focusing on `amt_dev_open`: I see the important line: `get_random_bytes(&amt->key |
| 20/3 | 2026/10/13 23:02 | tool | read-file |
0mArgs:{
"File": "drivers/net/amt.c",
"FirstLine": 1130,
"LineCount": 70
}Results: {
"Contents": "1130:\t\t\t\t false,\n1131:\t\t\t\t 0);\n1132:\t}\n1133:\t\n1134:\t/* Send a Membership Query to a source that has not been authenticated yet.\n1135:\t *\n1136:\t * No per-source state exists at this point and none is created: the nonce and\n1137:\t * the keyed MAC are carried in the Query and recomputed from the Membership\n1138:\t * Update when it comes back, so everything this needs is passed by value.\n1139:\t */\n1140:\tstatic bool amt_send_membership_query(struct amt_dev *amt,\n1141:\t\t\t\t\t struct sk_buff *skb,\n1142:\t\t\t\t\t __be32 daddr, __be16 dport,\n1143:\t\t\t\t\t __be32 nonce, u64 mac,\n1144:\t\t\t\t\t bool v6)\n1145:\t{\n1146:\t\tstruct amt_header_membership_query *amtmq;\n1147:\t\tstruct rtable *rt;\n1148:\t\tstruct flowi4 fl4;\n1149:\t\tstruct sock *sk;\n1150:\t\tint err;\n1151:\t\n1152:\t\tsk = rcu_dereference_bh(amt-\u003esk);\n1153:\t\tif (!sk)\n1154:\t\t\treturn true;\n1155:\t\n1156:\t\terr = skb_cow_head(skb, LL_RESERVED_SPACE(amt-\u003edev) + sizeof(*amtmq) +\n1157:\t\t\t\t sizeof(struct iphdr) + sizeof(struct udphdr));\n1158:\t\tif (err)\n1159:\t\t\treturn true;\n1160:\t\n1161:\t\tskb_reset_inner_headers(skb);\n1162:\t\tmemset(\u0026fl4, 0, sizeof(struct flowi4));\n1163:\t\tfl4.flowi4_oif = amt-\u003estream_dev-\u003eifindex;\n1164:\t\tfl4.daddr = daddr;\n1165:\t\tfl4.saddr = amt-\u003elocal_ip;\n1166:\t\tfl4.flowi4_dscp = inet_dsfield_to_dscp(AMT_TOS);\n1167:\t\tfl4.flowi4_proto = IPPROTO_UDP;\n1168:\t\trt = ip_route_output_key(amt-\u003enet, \u0026fl4);\n1169:\t\tif (IS_ERR(rt)) {\n1170:\t\t\tnetdev_dbg(amt-\u003edev, \"no route to %pI4\\n\", \u0026daddr);\n1171:\t\t\treturn true;\n1172:\t\t}\n1173:\t\n1174:\t\tamtmq\t\t= skb_push(skb, sizeof(*amtmq));\n1175:\t\tamtmq-\u003eversion\t= 0;\n1176:\t\tamtmq-\u003etype\t= AMT_MSG_MEMBERSHIP_QUERY;\n1177:\t\tamtmq-\u003ereserved = 0;\n1178:\t\tamtmq-\u003el\t= 0;\n1179:\t\tamtmq-\u003eg\t= 0;\n1180:\t\tamtmq-\u003enonce\t= nonce;\n1181:\t\tamtmq-\u003eresponse_mac = mac;\n1182:\t\n1183:\t\tif (!v6)\n1184:\t\t\tskb_set_inner_protocol(skb, htons(ETH_P_IP));\n1185:\t\telse\n1186:\t\t\tskb_set_inner_protocol(skb, htons(ETH_P_IPV6));\n1187:\t\tudp_tunnel_xmit_skb(rt, sk, skb,\n1188:\t\t\t\t fl4.saddr,\n1189:\t\t\t\t fl4.daddr,\n1190:\t\t\t\t AMT_TOS,\n1191:\t\t\t\t ip4_dst_hoplimit(\u0026rt-\u003edst),\n1192:\t\t\t\t 0,\n1193:\t\t\t\t amt-\u003erelay_port,\n1194:\t\t\t\t dport,\n1195:\t\t\t\t false,\n1196:\t\t\t\t false,\n1197:\t\t\t\t 0);\n1198:\t\treturn false;\n1199:\t}\n"
} |
| 21/3 | 2026/10/13 23:02 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 14888 output: 22 thoughts: 14 Thoughts: Here's a summary of my thought process, focusing on what I'd be thinking if I encountered that structure name: **Diving into `struct amt_header_membership_query`** Okay, so I see `struct amt_header_membership_query`. My mind immediately starts to work through it. The presence of `amt` likely indicates something related to Active Management Technology, likely from Intel. Knowing the common AMT structures helps a lot to be able to immediately infer its functionality. The `header` part suggests this structure likely represents information found at the beginning of a larger message or data block. `membership_query` immediately screams that this is connected to some kind of group membership query. My expert instinct is to think: "Okay, so this is about how something—likely a device—is querying information about its membership in a particular AMT context. This might involve things like what networks or groups it's part of." I'm already formulating questions: * What are the members of this struct? * What fields would be present to define the context of the query, i.e., what is the query about (e.g., what group is it asking about)? * Is it just a data structure for sending the query, or also for handling replies? * Is this used in a client or server-side scenario? * What is the specific AMT protocol in use here? * How is the membership information encoded and handled? Essentially, I'm already building a mental model of how this structure *should* function, based on my understanding of AMT and network membership principles. I want to see if the actual implementation matches this expectation. I need to dig into the member variables and understand the data to confirm my initial hypothesis. Let's get to the code. |
| 22/3 | 2026/10/13 23:02 | tool | grepper |
0mArgs:{
"Expression": "struct amt_header_membership_query"
}Results: {
"Output": "drivers/net/amt.c=1140=static bool amt_send_membership_query(struct amt_dev *amt,\n--\ndrivers/net/amt.c-1145-{\ndrivers/net/amt.c:1146:\tstruct amt_header_membership_query *amtmq;\ndrivers/net/amt.c-1147-\tstruct rtable *rt;\n--\ndrivers/net/amt.c=2405=static bool amt_membership_query_handler(struct amt_dev *amt,\n--\ndrivers/net/amt.c-2407-{\ndrivers/net/amt.c:2408:\tstruct amt_header_membership_query *amtmq;\ndrivers/net/amt.c-2409-\tstruct ethhdr *eth, *oeth;\n--\ndrivers/net/amt.c-2419-\ndrivers/net/amt.c:2420:\tamtmq = (struct amt_header_membership_query *)(udp_hdr(skb) + 1);\ndrivers/net/amt.c-2421-\tif (amtmq-\u003ereserved || amtmq-\u003eversion)\n--\ninclude/net/amt.h=136=struct amt_header_request {\n--\ninclude/net/amt.h-154-\ninclude/net/amt.h:155:struct amt_header_membership_query {\ninclude/net/amt.h-156-#if defined(__LITTLE_ENDIAN_BITFIELD)\n--\ninclude/net/amt.h=207=struct amt_headers {\n--\ninclude/net/amt.h-211-\t\tstruct amt_header_request request;\ninclude/net/amt.h:212:\t\tstruct amt_header_membership_query query;\ninclude/net/amt.h-213-\t\tstruct amt_header_membership_update update;\n--\ninclude/net/amt.h=226=struct amt_relay_headers {\n--\ninclude/net/amt.h-228-\t\tstruct amt_header_advertisement advertisement;\ninclude/net/amt.h:229:\t\tstruct amt_header_membership_query query;\ninclude/net/amt.h-230-\t\tstruct amt_header_mcast_data data;\n"
} |
| 23/3 | 2026/10/13 23:02 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 15870 output: 35 thoughts: 27 Thoughts: Here's my take on those thoughts: **Peering into the AMT Header Membership Query Structure** Alright, let's dive right in. My focus is immediately drawn to the `struct amt_header_membership_query` definition. I need to refresh my memory, or maybe even identify some subtle nuances I might have missed before. Time to go spelunking in `include/net/amt.h` and see what's actually there. Specifically, I want to see how the membership query is structured. I'm hoping to understand its fields, their layout, and how that impacts the overall AMT (Address Management Tunneling) protocol. Let's see if there's anything novel or potentially problematic. I need to ensure a solid grasp of this structure's internals for the work ahead. |
| 24/3 | 2026/10/13 23:02 | tool | read-file |
0mArgs:{
"File": "include/net/amt.h",
"FirstLine": 155,
"LineCount": 40
}Results: {
"Contents": " 155:\tstruct amt_header_membership_query {\n 156:\t#if defined(__LITTLE_ENDIAN_BITFIELD)\n 157:\t\tu64\ttype:4,\n 158:\t\t\tversion:4,\n 159:\t\t\treserved:6,\n 160:\t\t\tl:1,\n 161:\t\t\tg:1,\n 162:\t\t\tresponse_mac:48;\n 163:\t#elif defined(__BIG_ENDIAN_BITFIELD)\n 164:\t\tu64\tversion:4,\n 165:\t\t\ttype:4,\n 166:\t\t\tg:1,\n 167:\t\t\tl:1,\n 168:\t\t\treserved:6,\n 169:\t\t\tresponse_mac:48;\n 170:\t#else\n 171:\t#error \"Please fix \u003casm/byteorder.h\u003e\"\n 172:\t#endif\n 173:\t\t__be32\tnonce;\n 174:\t} __packed;\n 175:\t\n 176:\tstruct amt_header_membership_update {\n 177:\t#if defined(__LITTLE_ENDIAN_BITFIELD)\n 178:\t\tu64\ttype:4,\n 179:\t\t\tversion:4,\n 180:\t\t\treserved:8,\n 181:\t\t\tresponse_mac:48;\n 182:\t#elif defined(__BIG_ENDIAN_BITFIELD)\n 183:\t\tu64\tversion:4,\n 184:\t\t\ttype:4,\n 185:\t\t\treserved:8,\n 186:\t\t\tresponse_mac:48;\n 187:\t#else\n 188:\t#error \"Please fix \u003casm/byteorder.h\u003e\"\n 189:\t#endif\n 190:\t\t__be32\tnonce;\n 191:\t} __packed;\n 192:\t\n 193:\tstruct amt_header_mcast_data {\n 194:\t#if defined(__LITTLE_ENDIAN_BITFIELD)\n"
} |
| 25/3 | 2026/10/13 23:02 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 16649 output: 31 thoughts: 1081 Thoughts: **Analyzing AMT Key Initialization and Usage** Okay, let's break this down systematically. My initial concern was about potential uninitialized memory in the `amtmq` structure before it's pushed onto the SKB, but a quick review shows all fields (`version`, `type`, `reserved`, `l`, `g`, `response_mac`, `nonce`) are assigned, so that's a non-issue. My next step is to examine the functions using or modifying these values. `amt_request_handler` comes first: It copies the key, `key = amt->key[0]`, then calculates a MAC based on this, the source IP, UDP source port and the nonce from the `amtrh`. I need to ensure `amt->key[0]` is initialized. Further analysis shows `get_random_bytes(&amt->key, sizeof(amt->key))` initializes the key *within* `amt_dev_open`, therefore `amt_request_handler` needs to be called after `amt_dev_open`. I need to be sure that `amt_request_handler` can't be called before `amt_dev_open`, where the socket is created. That would introduce a race condition on the key, but further analysis finds it can't as `if (!netif_running(amt->dev) || !netif_running(amt->stream_dev)) return true;` prevents it. Moving on, `amt_update_handler` now comes into view. This handler makes a copy of the entire key array `amt->key` into a local `key` array, which includes all four elements and all are subsequently used in the SIPHASH calculation. This confirms that all four key elements are necessary, and used. If verified, `amt_tunnel_get_or_create` allocates and initializes a tunnel, checks the `dead` flag (read atomically), then calls report handlers. Wait! What about the `amt_secret_work` function? It shifts the key array elements: `amt->key[3] = amt->key[2];`, `amt->key[2] = amt->key[1];`, `amt->key[1] = amt->key[0];`, and generates a new key element with `get_random_bytes(&amt->key[0], sizeof(siphash_key_t));`. This looks like a key rotation mechanism. However, if this work is queued *before* `amt_dev_open`, the shift would introduce uninitialized memory. Let me see where `secret_wq` is initialized and started. |
| 26/3 | 2026/10/13 23:02 | tool | grepper |
0mArgs:{
"Expression": "secret_wq",
"PathPrefixes": [
"drivers/net/amt.c"
]
}Results: {
"Output": "drivers/net/amt.c=930=static void amt_secret_work(struct work_struct *work)\n--\ndrivers/net/amt.c-933-\t\t\t\t\t struct amt_dev,\ndrivers/net/amt.c:934:\t\t\t\t\t secret_wq);\ndrivers/net/amt.c-935-\n--\ndrivers/net/amt.c-945-\tspin_unlock_bh(\u0026amt-\u003elock);\ndrivers/net/amt.c:946:\tmod_delayed_work(amt_wq, \u0026amt-\u003esecret_wq,\ndrivers/net/amt.c-947-\t\t\t msecs_to_jiffies(AMT_SECRET_TIMEOUT));\n--\ndrivers/net/amt.c=3188=static int amt_dev_open(struct net_device *dev)\n--\ndrivers/net/amt.c-3224-\t} else if (amt-\u003emode == AMT_MODE_RELAY) {\ndrivers/net/amt.c:3225:\t\tmod_delayed_work(amt_wq, \u0026amt-\u003esecret_wq,\ndrivers/net/amt.c-3226-\t\t\t\t msecs_to_jiffies(AMT_SECRET_TIMEOUT));\n--\ndrivers/net/amt.c=3231=static int amt_dev_stop(struct net_device *dev)\n--\ndrivers/net/amt.c-3240-\tdisable_delayed_work_sync(\u0026amt-\u003ediscovery_wq);\ndrivers/net/amt.c:3241:\tcancel_delayed_work_sync(\u0026amt-\u003esecret_wq);\ndrivers/net/amt.c-3242-\n--\ndrivers/net/amt.c=3404=static int amt_newlink(struct net_device *dev,\n--\ndrivers/net/amt.c-3516-\tINIT_DELAYED_WORK(\u0026amt-\u003ereq_wq, amt_req_work);\ndrivers/net/amt.c:3517:\tINIT_DELAYED_WORK(\u0026amt-\u003esecret_wq, amt_secret_work);\ndrivers/net/amt.c-3518-\tINIT_WORK(\u0026amt-\u003eevent_wq, amt_event_work);\n"
} |
| 27/3 | 2026/10/13 23:02 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 18857 output: 33 thoughts: 1144 Thoughts: **Analyzing the AMT Patch for Potential Memory Safety Issues**
Okay, so I'm diving into this AMT patch and trying to figure out if it introduces any memory safety vulnerabilities that KMSAN would catch. The `secret_wq` is queued early in `amt_dev_open`, but importantly, `get_random_bytes` runs *before* that queueing, and the `amt_dev` itself is zeroed during allocation. That's a good starting point, meaning the keys are explicitly initialized.
Now, the patch's core purpose is clear: it addresses the unauthenticated nature of the request handling, which could lead to DoS attacks via resource exhaustion from spoofed UDP requests, and a Use-After-Free (UAF) race condition. It focuses on the fact that the requesting source isn't authenticated yet; the packet just has a UDP datagram. This is where a major source of the bug lies. It's essentially preventing the allocation of tunnel state from spoofed packets which lead to MAC overwrites of established tunnels.
Specifically, the patch:
1. Addresses an unauthenticated source.
2. Keeps multiple generations of MACs.
3. Fixes a race by re-arming the work queue (`gc_wq`) based on when a tunnel was last seen.
4. Prevents allocation of tunnel state to unauthenticated sources.
5. Fixes UAF in `amt_tunnel_expire` and `amt_dev_stop`.
6. Corrects NLA policy.
7. Adds `DEV_STATS_INC`.
I need to meticulously go through the KMSAN conditions:
1. **Kernel structures to userspace?** No, the AMT driver sends packets out and receives them. There's no `copy_to_user`, `put_user`, netlink SKB, ioctl, or BPF buffer involved. The NLA policy change is about input attribute validation.
2. **Conditional logic with uninitialized variables?** I need to check every new variable or struct field:
* `bool dead` in `struct amt_tunnel_list`: Initialized to `false` (zero) by `kzalloc` and set under `amt->lock`.
* `unsigned long last_seen` in `struct amt_tunnel_list`: Initialized to `jiffies`.
* `siphash_key_t key[4]` in `struct amt_dev`: Fully initialized with random bytes in `amt_dev_open`.
* Local variables in `amt_update_handler`: All local variables are populated or initialized.
* Local variables in `amt_request_handler`: All local variables are initialized.
3. **Complex data structures with uninitialized fields?** `amt_tunnel_get_or_create` uses `kzalloc`, which zeroes the allocated memory.
4. **Bounds checks leading to uninitialized reads?** The patch preserves existing checks with `iptunnel_pull_header` and `pskb_may_pull`. No changes seem to introduce uninitialized reads.
So, the bug here is about denial of service from a spoofed address and state corruption, and a UAF race condition, which KASAN, LOCKDEP, and KASAN will detect. Let's make sure nothing is missed. I need to review `amt_tunnel_get_or_create` and `amt_clear_groups`, and other related functions called in it or `amt_dev_stop`. Need to be thorough.
|
| 28/3 | 2026/10/13 23:02 | tool | grepper |
0mArgs:{
"Expression": "amt_clear_groups",
"PathPrefixes": [
"drivers/net/amt.c"
]
}Results: {
"Output": "drivers/net/amt.c=1321=static int amt_parse_type(struct sk_buff *skb)\n--\ndrivers/net/amt.c-1338-\ndrivers/net/amt.c:1339:static void amt_clear_groups(struct amt_tunnel_list *tunnel)\ndrivers/net/amt.c-1340-{\n--\ndrivers/net/amt.c=1355=static void amt_tunnel_expire(struct work_struct *work)\n--\ndrivers/net/amt.c-1387-\tamt-\u003enr_tunnels--;\ndrivers/net/amt.c:1388:\tamt_clear_groups(tunnel);\ndrivers/net/amt.c-1389-\trcu_read_unlock();\n--\ndrivers/net/amt.c=3231=static int amt_dev_stop(struct net_device *dev)\n--\ndrivers/net/amt.c-3288-\t\tcancel_delayed_work_sync(\u0026tunnel-\u003egc_wq);\ndrivers/net/amt.c:3289:\t\tamt_clear_groups(tunnel);\ndrivers/net/amt.c-3290-\t\tkfree_rcu(tunnel, rcu);\n"
} |
| 29/3 | 2026/10/13 23:02 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 21105 output: 367 thoughts: 420 Thoughts: **My Assessment: KMSAN Not Needed Here** Okay, so I've just reviewed these changes, and the good news is, from a memory safety perspective specifically relevant to KMSAN, things look pretty solid. I see a clean, well-structured approach. No glaring signs of uninitialized memory issues. They've explicitly addressed the use-after-free race condition and potential DoS attacks related to state hijacking or exhaustion. The deferral of tunnel allocation until after authentication is clever, and the generation key array coupled with the `dead` flag for synchronized teardown is a smart way to manage the lifecycle of these tunnels. The memory allocations are all using `kzalloc`, which is great – everything's zero-initialized by default. The key array is being completely filled with truly random bytes, covering the full `sizeof(amt->key)` size. Crucially, I see absolutely no uninitialized memory being sent to userspace or read, and everything in the modified handlers is initialized prior to usage. The netlink policy updates also enforce strict attribute lengths, which further helps with the input validation that we'd look for. So, in terms of justifying why KMSAN isn't needed here, I'd say: * The patch primarily resolves concurrency issues, specifically the use-after-free vulnerabilities. We're talking race conditions in tunnel expiration/teardown code, denial-of-service, and state exhaustion. These are concerns that are tracked and detected by KASAN. * This is not the type of thing that would fall under KMSAN or uninitialized memory exposure. * No uninitialized memory is read, copied to userspace, or exposed across network boundaries. * We're using `kzalloc` for dynamic allocations (like `amt_tunnel_get_or_create`) which ensures zero-initialization. * We're completely seeding the key array with truly random bytes. * All local variables in the updated handlers are properly initialized. * Strict netlink policies minimize the possibility of out-of-bounds or invalid parsing bugs. Thus, based on these observations, it looks like dedicated KMSAN fuzzing isn't required. We can safely mark `NeedsKMSAN` as `false` and move on. Let's call `set-results`. |
| 30/3 | 2026/10/13 23:02 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch modifies the AMT (Automatic Multicast Tunneling) driver to defer tunnel allocation until membership update authentication, prevents tunnel table exhaustion, and resolves a use-after-free (UAF) race condition between amt_tunnel_expire(), amt_update_handler(), and amt_dev_stop().\n\nKASAN vs. KMSAN Applicability:\n- The risks and bugs addressed by this patch are concurrency races, resource exhaustion, and use-after-free errors (where concurrent updates could re-arm gc_wq on a tunnel already marked for destruction). These types of bugs (UAF, invalid pointer dereferences, concurrent list mutations) are squarely within the scope of KASAN and LOCKDEP.\n- There are no uninitialized memory reads or information leak hazards:\n - Tunnel structures are allocated using kzalloc(), guaranteeing zero-initialization of all fields including newly added members (last_seen, dead).\n - The siphash key array (amt-\u003ekey[4]) is fully initialized in amt_dev_open() using get_random_bytes() with sizeof(amt-\u003ekey).\n - Outgoing query headers (amtmq) have all fields explicitly written before transmission over the network.\n - Netlink policy updates use NLA_POLICY_EXACT_LEN for incoming attributes, which is an input validation concern rather than an uninitialized memory issue.\n - No uninitialized data or kernel structure padding is copied to user space.\n\nBecause all memory is fully initialized and the changes focus on concurrency and lifecycle management, standard KASAN and LOCKDEP fuzzing is sufficient and a dedicated KMSAN session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch modifies the AMT (Automatic Multicast Tunneling) driver to defer tunnel allocation until membership update authentication, prevents tunnel table exhaustion, and resolves a use-after-free (UAF) race condition between amt_tunnel_expire(), amt_update_handler(), and amt_dev_stop().\n\nKASAN vs. KMSAN Applicability:\n- The risks and bugs addressed by this patch are concurrency races, resource exhaustion, and use-after-free errors (where concurrent updates could re-arm gc_wq on a tunnel already marked for destruction). These types of bugs (UAF, invalid pointer dereferences, concurrent list mutations) are squarely within the scope of KASAN and LOCKDEP.\n- There are no uninitialized memory reads or information leak hazards:\n - Tunnel structures are allocated using kzalloc(), guaranteeing zero-initialization of all fields including newly added members (last_seen, dead).\n - The siphash key array (amt-\u003ekey[4]) is fully initialized in amt_dev_open() using get_random_bytes() with sizeof(amt-\u003ekey).\n - Outgoing query headers (amtmq) have all fields explicitly written before transmission over the network.\n - Netlink policy updates use NLA_POLICY_EXACT_LEN for incoming attributes, which is an input validation concern rather than an uninitialized memory issue.\n - No uninitialized data or kernel structure padding is copied to user space.\n\nBecause all memory is fully initialized and the changes focus on concurrency and lifecycle management, standard KASAN and LOCKDEP fuzzing is sufficient and a dedicated KMSAN session is not warranted.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|