AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/06 15:15 flow patch-triage
4m Args:
null

Results:
{
  "EnableConfigs": [
    "XFRM_USER",
    "XFRM_MIGRATE",
    "IPV6"
  ],
  "FocusSymbols": [
    "xfrm_state_lookup_exact",
    "xfrm_state_lookup_byaddr",
    "xfrm_user_state_lookup",
    "xfrm_do_migrate_state",
    "xfrm_state_update",
    "xfrm_state_migrate_install",
    "xfrm_get_ae",
    "xfrm_new_ae",
    "xfrm_add_sa_expire"
  ],
  "Reasoning": "The patch modifies the XFRM (IPsec) state lookup logic to allow exact matching of marks and masks during control plane operations (GET, DELETE, MIGRATE). This is reachable from user space via netlink messages and affects core IPsec functionality.",
  "WorthFuzzing": true
}

1/1 2026/07/06 15:15 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 68305911e5abd6e55dd802d30507109fca253d42\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Jul 6 15:15:24 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/xfrm/xfrm_migrate_state.rst b/Documentation/networking/xfrm/xfrm_migrate_state.rst\nindex 9d53cb22b00752..905e3f0c4ee06f 100644\n--- a/Documentation/networking/xfrm/xfrm_migrate_state.rst\n+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst\n@@ -27,15 +27,18 @@ SA Identification\n =================\n \n The struct is defined in ``include/uapi/linux/xfrm.h``. The SA is looked\n-up using ``xfrm_state_lookup()`` with ``id.spi``,\n-``id.daddr``, ``id.proto``, ``id.family``, and\n-``old_mark.v \u0026 old_mark.m`` as the mark key::\n+up using ``xfrm_state_lookup_exact()`` with ``id.spi``, ``id.daddr``,\n+``id.proto``, ``id.family``, and an exact match against ``old_mark.v``\n+and ``old_mark.m``. Unlike the data path, which uses a masked\n+comparison, this requires the SA's mark and mask to equal ``old_mark``\n+exactly, so a broad-mask SA is never matched when a more specific one\n+was intended. If no such SA exists, ``-ESRCH`` is returned.::\n \n     struct xfrm_user_migrate_state {\n         struct xfrm_usersa_id  id;       /* spi, daddr, proto, family */\n         xfrm_address_t         new_daddr;\n         xfrm_address_t         new_saddr;\n-        struct xfrm_mark       old_mark; /* SA lookup: key = v \u0026 m */\n+        struct xfrm_mark       old_mark; /* SA lookup key (exact v/m match) */\n         struct xfrm_selector   new_sel;  /* new selector (see Flags) */\n         __u32                  new_reqid;\n         __u32                  flags;    /* XFRM_MIGRATE_STATE_* */\n@@ -72,8 +75,8 @@ inherits the value from the existing SA (omit-to-inherit).\n      - Description\n    * - ``XFRMA_MARK``\n      - Mark on the migrated SA (``struct xfrm_mark``). Absent inherits\n-       ``old_mark``. To use no mark on the new SA, send ``XFRMA_MARK``\n-       with ``{0, 0}``.\n+       the mark of the existing SA. To use no mark on the new SA, send\n+       ``XFRMA_MARK`` with ``{0, 0}``.\n    * - ``XFRMA_ENCAP``\n      - UDP encapsulation template; only ``UDP_ENCAP_ESPINUDP`` is supported.\n        Set ``encap_type=0`` to remove encap.\n@@ -259,8 +262,9 @@ Attributes in the notification\n Error Handling\n ==============\n \n-If the target SA tuple (new daddr, SPI, proto, new family) is already\n-occupied, the operation returns ``-EEXIST`` before the migration begins.\n+If the target SA tuple (new daddr, SPI, proto, new family, mark) is\n+already occupied, the operation returns ``-EEXIST`` before the migration\n+begins.\n The old SA remains intact and the operation is safe to retry after\n resolving the conflict.\n \ndiff --git a/include/net/xfrm.h b/include/net/xfrm.h\nindex 519a0156a05cac..ebe514376254a6 100644\n--- a/include/net/xfrm.h\n+++ b/include/net/xfrm.h\n@@ -1741,11 +1741,14 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,\n \t\t\t\t\t   const xfrm_address_t *daddr,\n \t\t\t\t\t   __be32 spi, u8 proto,\n \t\t\t\t\t   unsigned short family);\n-struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,\n+struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,\n \t\t\t\t\t    const xfrm_address_t *daddr,\n \t\t\t\t\t    const xfrm_address_t *saddr,\n \t\t\t\t\t    u8 proto,\n \t\t\t\t\t    unsigned short family);\n+struct xfrm_state *xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,\n+\t\t\t\t\t   const xfrm_address_t *daddr, __be32 spi,\n+\t\t\t\t\t   u8 proto, unsigned short family);\n #ifdef CONFIG_XFRM_SUB_POLICY\n void xfrm_tmpl_sort(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n,\n \t\t    unsigned short family);\ndiff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c\nindex 89d0443b530732..2dd347fece52ca 100644\n--- a/net/ipv6/xfrm6_input.c\n+++ b/net/ipv6/xfrm6_input.c\n@@ -272,7 +272,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,\n \t\t\tbreak;\n \t\t}\n \n-\t\tx = xfrm_state_lookup_byaddr(net, skb-\u003emark, dst, src, proto, AF_INET6);\n+\t\tx = xfrm_state_lookup_byaddr(net, skb-\u003emark, 0, false, dst, src, proto, AF_INET6);\n \t\tif (!x)\n \t\t\tcontinue;\n \ndiff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c\nindex c58cd024e3c675..0b3dc7789dd790 100644\n--- a/net/xfrm/xfrm_state.c\n+++ b/net/xfrm/xfrm_state.c\n@@ -1172,11 +1172,22 @@ static struct xfrm_state *__xfrm_state_lookup_all(const struct xfrm_hash_state_p\n \treturn NULL;\n }\n \n-static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,\n-\t\t\t\t\t      u32 mark,\n-\t\t\t\t\t      const xfrm_address_t *daddr,\n-\t\t\t\t\t      __be32 spi, u8 proto,\n-\t\t\t\t\t      unsigned short family)\n+/* exact=false: data-plane wildcard match against x's mask. exact=true:\n+ * control-plane identity match, mark and mask must both match exactly.\n+ */\n+static bool xfrm_state_mark_matches(const struct xfrm_state *x, u32 mark, u32 mask, bool exact)\n+{\n+\tif (exact)\n+\t\treturn x-\u003emark.v == mark \u0026\u0026 x-\u003emark.m == mask;\n+\treturn (mark \u0026 x-\u003emark.m) == x-\u003emark.v;\n+}\n+\n+static struct xfrm_state *\n+__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,\n+\t\t    u32 mark, u32 mask, bool exact,\n+\t\t    const xfrm_address_t *daddr,\n+\t\t    __be32 spi, u8 proto,\n+\t\t    unsigned short family)\n {\n \tunsigned int h = __xfrm_spi_hash(daddr, spi, proto, family, state_ptrs-\u003ehmask);\n \tstruct xfrm_state *x;\n@@ -1188,7 +1199,7 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs\n \t\t    !xfrm_addr_equal(\u0026x-\u003eid.daddr, daddr, family))\n \t\t\tcontinue;\n \n-\t\tif ((mark \u0026 x-\u003emark.m) != x-\u003emark.v)\n+\t\tif (!xfrm_state_mark_matches(x, mark, mask, exact))\n \t\t\tcontinue;\n \t\tif (!xfrm_state_hold_rcu(x))\n \t\t\tcontinue;\n@@ -1198,6 +1209,17 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs\n \treturn NULL;\n }\n \n+static struct xfrm_state *\n+__xfrm_state_lookup_exact(const struct xfrm_hash_state_ptrs *state_ptrs,\n+\t\t\t  const struct xfrm_mark *mark,\n+\t\t\t  const xfrm_address_t *daddr,\n+\t\t\t  __be32 spi, u8 proto,\n+\t\t\t  unsigned short family)\n+{\n+\treturn __xfrm_state_lookup(state_ptrs, mark-\u003ev, mark-\u003em, true,\n+\t\t\t\t   daddr, spi, proto, family);\n+}\n+\n struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,\n \t\t\t\t\t   const xfrm_address_t *daddr,\n \t\t\t\t\t   __be32 spi, u8 proto,\n@@ -1228,7 +1250,7 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,\n \n \txfrm_hash_ptrs_get(net, \u0026state_ptrs);\n \n-\tx = __xfrm_state_lookup(\u0026state_ptrs, mark, daddr, spi, proto, family);\n+\tx = __xfrm_state_lookup(\u0026state_ptrs, mark, 0, false, daddr, spi, proto, family);\n \tif (x) {\n \t\tspin_lock(\u0026net-\u003exfrm.xfrm_state_lock);\n \t\tif (x-\u003ekm.state != XFRM_STATE_VALID) {\n@@ -1252,11 +1274,12 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,\n }\n EXPORT_SYMBOL(xfrm_input_state_lookup);\n \n-static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,\n-\t\t\t\t\t\t     u32 mark,\n-\t\t\t\t\t\t     const xfrm_address_t *daddr,\n-\t\t\t\t\t\t     const xfrm_address_t *saddr,\n-\t\t\t\t\t\t     u8 proto, unsigned short family)\n+static struct xfrm_state *\n+__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,\n+\t\t\t   u32 mark, u32 mask, bool exact,\n+\t\t\t   const xfrm_address_t *daddr,\n+\t\t\t   const xfrm_address_t *saddr,\n+\t\t\t   u8 proto, unsigned short family)\n {\n \tunsigned int h = __xfrm_src_hash(daddr, saddr, family, state_ptrs-\u003ehmask);\n \tstruct xfrm_state *x;\n@@ -1268,7 +1291,7 @@ static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_stat\n \t\t    !xfrm_addr_equal(\u0026x-\u003eprops.saddr, saddr, family))\n \t\t\tcontinue;\n \n-\t\tif ((mark \u0026 x-\u003emark.m) != x-\u003emark.v)\n+\t\tif (!xfrm_state_mark_matches(x, mark, mask, exact))\n \t\t\tcontinue;\n \t\tif (!xfrm_state_hold_rcu(x))\n \t\t\tcontinue;\n@@ -1288,10 +1311,31 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)\n \txfrm_hash_ptrs_get(net, \u0026state_ptrs);\n \n \tif (use_spi)\n-\t\treturn __xfrm_state_lookup(\u0026state_ptrs, mark, \u0026x-\u003eid.daddr,\n+\t\treturn __xfrm_state_lookup(\u0026state_ptrs, mark, 0, false, \u0026x-\u003eid.daddr,\n \t\t\t\t\t   x-\u003eid.spi, x-\u003eid.proto, family);\n \telse\n-\t\treturn __xfrm_state_lookup_byaddr(\u0026state_ptrs, mark,\n+\t\treturn __xfrm_state_lookup_byaddr(\u0026state_ptrs, mark, 0, false,\n+\t\t\t\t\t\t  \u0026x-\u003eid.daddr,\n+\t\t\t\t\t\t  \u0026x-\u003eprops.saddr,\n+\t\t\t\t\t\t  x-\u003eid.proto, family);\n+}\n+\n+/* Used by xfrm_state_update() only; xfrm_state_add()'s dup check keeps\n+ * using the wildcard __xfrm_state_locate() above.\n+ */\n+static inline struct xfrm_state *\n+__xfrm_state_locate_exact(struct xfrm_state *x, int use_spi, int family)\n+{\n+\tstruct xfrm_hash_state_ptrs state_ptrs;\n+\tstruct net *net = xs_net(x);\n+\n+\txfrm_hash_ptrs_get(net, \u0026state_ptrs);\n+\n+\tif (use_spi)\n+\t\treturn __xfrm_state_lookup_exact(\u0026state_ptrs, \u0026x-\u003emark, \u0026x-\u003eid.daddr,\n+\t\t\t\t\t\t x-\u003eid.spi, x-\u003eid.proto, family);\n+\telse\n+\t\treturn __xfrm_state_lookup_byaddr(\u0026state_ptrs, x-\u003emark.v, x-\u003emark.m, true,\n \t\t\t\t\t\t  \u0026x-\u003eid.daddr,\n \t\t\t\t\t\t  \u0026x-\u003eprops.saddr,\n \t\t\t\t\t\t  x-\u003eid.proto, family);\n@@ -2177,10 +2221,12 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,\n \t\t\t       struct netlink_ext_ack *extack)\n {\n \tif (m-\u003enew_family == m-\u003eold_family \u0026\u0026\n-\t    xfrm_addr_equal(\u0026x-\u003eid.daddr, \u0026m-\u003enew_daddr, m-\u003enew_family)) {\n+\t    xfrm_addr_equal(\u0026x-\u003eid.daddr, \u0026m-\u003enew_daddr, m-\u003enew_family) \u0026\u0026\n+\t    xc-\u003emark.v == x-\u003emark.v \u0026\u0026 xc-\u003emark.m == x-\u003emark.m) {\n \t\t/*\n-\t\t * Care is needed when the destination address of the state is\n-\t\t * to be updated as it is a part of triplet.\n+\t\t * Care is needed when the destination address or mark of the\n+\t\t * state is to be updated, as they are part of the lookup\n+\t\t * triplet.\n \t\t */\n \t\txfrm_state_insert(xc);\n \t} else {\n@@ -2229,7 +2275,7 @@ int xfrm_state_update(struct xfrm_state *x)\n \tto_put = NULL;\n \n \tspin_lock_bh(\u0026net-\u003exfrm.xfrm_state_lock);\n-\tx1 = __xfrm_state_locate(x, use_spi, x-\u003eprops.family);\n+\tx1 = __xfrm_state_locate_exact(x, use_spi, x-\u003eprops.family);\n \n \terr = -ESRCH;\n \tif (!x1)\n@@ -2374,14 +2420,14 @@ xfrm_state_lookup(struct net *net, u32 mark, const xfrm_address_t *daddr, __be32\n \trcu_read_lock();\n \txfrm_hash_ptrs_get(net, \u0026state_ptrs);\n \n-\tx = __xfrm_state_lookup(\u0026state_ptrs, mark, daddr, spi, proto, family);\n+\tx = __xfrm_state_lookup(\u0026state_ptrs, mark, 0, false, daddr, spi, proto, family);\n \trcu_read_unlock();\n \treturn x;\n }\n EXPORT_SYMBOL(xfrm_state_lookup);\n \n struct xfrm_state *\n-xfrm_state_lookup_byaddr(struct net *net, u32 mark,\n+xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,\n \t\t\t const xfrm_address_t *daddr, const xfrm_address_t *saddr,\n \t\t\t u8 proto, unsigned short family)\n {\n@@ -2392,12 +2438,29 @@ xfrm_state_lookup_byaddr(struct net *net, u32 mark,\n \n \txfrm_hash_ptrs_get(net, \u0026state_ptrs);\n \n-\tx = __xfrm_state_lookup_byaddr(\u0026state_ptrs, mark, daddr, saddr, proto, family);\n+\tx = __xfrm_state_lookup_byaddr(\u0026state_ptrs, mark, mask, exact, daddr, saddr, proto, family);\n \trcu_read_unlock();\n \treturn x;\n }\n EXPORT_SYMBOL(xfrm_state_lookup_byaddr);\n \n+struct xfrm_state *\n+xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,\n+\t\t\tconst xfrm_address_t *daddr, __be32 spi,\n+\t\t\tu8 proto, unsigned short family)\n+{\n+\tstruct xfrm_hash_state_ptrs state_ptrs;\n+\tstruct xfrm_state *x;\n+\n+\trcu_read_lock();\n+\txfrm_hash_ptrs_get(net, \u0026state_ptrs);\n+\n+\tx = __xfrm_state_lookup_exact(\u0026state_ptrs, mark, daddr, spi, proto, family);\n+\trcu_read_unlock();\n+\treturn x;\n+}\n+EXPORT_SYMBOL(xfrm_state_lookup_exact);\n+\n struct xfrm_state *\n xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid,\n \t      u32 if_id, u32 pcpu_num, u8 proto, const xfrm_address_t *daddr,\n@@ -3306,7 +3369,7 @@ int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)\n \tif (err)\n \t\treturn err;\n \n-\terr = xfrm_init_replay(x, NULL);\n+\terr = xfrm_init_replay(x, extack);\n \tif (err)\n \t\treturn err;\n \ndiff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c\nindex 6384795ee6b269..a2317e6e6802dd 100644\n--- a/net/xfrm/xfrm_user.c\n+++ b/net/xfrm/xfrm_user.c\n@@ -1089,11 +1089,12 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,\n \tstruct xfrm_state *x = NULL;\n \tstruct xfrm_mark m;\n \tint err;\n-\tu32 mark = xfrm_mark_get(attrs, \u0026m);\n+\n+\txfrm_mark_get(attrs, \u0026m);\n \n \tif (xfrm_id_proto_match(p-\u003eproto, IPSEC_PROTO_ANY)) {\n \t\terr = -ESRCH;\n-\t\tx = xfrm_state_lookup(net, mark, \u0026p-\u003edaddr, p-\u003espi, p-\u003eproto, p-\u003efamily);\n+\t\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026p-\u003edaddr, p-\u003espi, p-\u003eproto, p-\u003efamily);\n \t} else {\n \t\txfrm_address_t *saddr = NULL;\n \n@@ -1104,7 +1105,7 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,\n \t\t}\n \n \t\terr = -ESRCH;\n-\t\tx = xfrm_state_lookup_byaddr(net, mark,\n+\t\tx = xfrm_state_lookup_byaddr(net, m.v, m.m, true,\n \t\t\t\t\t     \u0026p-\u003edaddr, saddr,\n \t\t\t\t\t     p-\u003eproto, p-\u003efamily);\n \t}\n@@ -2788,14 +2789,13 @@ static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh,\n \tstruct sk_buff *r_skb;\n \tint err;\n \tstruct km_event c;\n-\tu32 mark;\n \tstruct xfrm_mark m;\n \tstruct xfrm_aevent_id *p = nlmsg_data(nlh);\n \tstruct xfrm_usersa_id *id = \u0026p-\u003esa_id;\n \n-\tmark = xfrm_mark_get(attrs, \u0026m);\n+\txfrm_mark_get(attrs, \u0026m);\n \n-\tx = xfrm_state_lookup(net, mark, \u0026id-\u003edaddr, id-\u003espi, id-\u003eproto, id-\u003efamily);\n+\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026id-\u003edaddr, id-\u003espi, id-\u003eproto, id-\u003efamily);\n \tif (x == NULL)\n \t\treturn -ESRCH;\n \n@@ -2836,7 +2836,6 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,\n \tstruct xfrm_state *x;\n \tstruct km_event c;\n \tint err = -EINVAL;\n-\tu32 mark = 0;\n \tstruct xfrm_mark m;\n \tstruct xfrm_aevent_id *p = nlmsg_data(nlh);\n \tstruct nlattr *rp = attrs[XFRMA_REPLAY_VAL];\n@@ -2856,9 +2855,10 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\treturn err;\n \t}\n \n-\tmark = xfrm_mark_get(attrs, \u0026m);\n+\txfrm_mark_get(attrs, \u0026m);\n \n-\tx = xfrm_state_lookup(net, mark, \u0026p-\u003esa_id.daddr, p-\u003esa_id.spi, p-\u003esa_id.proto, p-\u003esa_id.family);\n+\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026p-\u003esa_id.daddr, p-\u003esa_id.spi,\n+\t\t\t\t    p-\u003esa_id.proto, p-\u003esa_id.family);\n \tif (x == NULL)\n \t\treturn -ESRCH;\n \n@@ -2992,9 +2992,10 @@ static int xfrm_add_sa_expire(struct sk_buff *skb, struct nlmsghdr *nlh,\n \tstruct xfrm_user_expire *ue = nlmsg_data(nlh);\n \tstruct xfrm_usersa_info *p = \u0026ue-\u003estate;\n \tstruct xfrm_mark m;\n-\tu32 mark = xfrm_mark_get(attrs, \u0026m);\n \n-\tx = xfrm_state_lookup(net, mark, \u0026p-\u003eid.daddr, p-\u003eid.spi, p-\u003eid.proto, p-\u003efamily);\n+\txfrm_mark_get(attrs, \u0026m);\n+\n+\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026p-\u003eid.daddr, p-\u003eid.spi, p-\u003eid.proto, p-\u003efamily);\n \n \terr = -ENOENT;\n \tif (x == NULL)\n@@ -3361,9 +3362,9 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\n \n \tcopy_from_user_migrate_state(\u0026m, um);\n \n-\tx = xfrm_state_lookup(net, m.old_mark.v \u0026 m.old_mark.m,\n-\t\t\t      \u0026um-\u003eid.daddr, um-\u003eid.spi,\n-\t\t\t      um-\u003eid.proto, um-\u003eid.family);\n+\tx = xfrm_state_lookup_exact(net, \u0026m.old_mark,\n+\t\t\t\t    \u0026um-\u003eid.daddr, um-\u003eid.spi,\n+\t\t\t\t    um-\u003eid.proto, um-\u003eid.family);\n \tif (!x) {\n \t\tNL_SET_ERR_MSG(extack, \"Can not find state\");\n \t\treturn -ESRCH;\n@@ -3434,18 +3435,23 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\t\t\t\t\t       x-\u003enat_keepalive_interval);\n \n \tif (m.new_family != um-\u003eid.family ||\n-\t    !xfrm_addr_equal(\u0026m.new_daddr, \u0026um-\u003eid.daddr, um-\u003eid.family)) {\n+\t    !xfrm_addr_equal(\u0026m.new_daddr, \u0026um-\u003eid.daddr, um-\u003eid.family) ||\n+\t    (m.new_mark \u0026\u0026 (m.new_mark-\u003ev != x-\u003emark.v ||\n+\t\t\t   m.new_mark-\u003em != x-\u003emark.m))) {\n \t\tu32 new_mark_key = m.new_mark ? m.new_mark-\u003ev \u0026 m.new_mark-\u003em :\n-\t\t\t\t\t\tm.old_mark.v \u0026 m.old_mark.m;\n+\t\t\t\t\t\tx-\u003emark.v \u0026 x-\u003emark.m;\n \t\tstruct xfrm_state *x_new;\n \n \t\tx_new = xfrm_state_lookup(net, new_mark_key, \u0026m.new_daddr,\n \t\t\t\t\t  um-\u003eid.spi, um-\u003eid.proto, m.new_family);\n \t\tif (x_new) {\n \t\t\txfrm_state_put(x_new);\n-\t\t\tNL_SET_ERR_MSG(extack, \"New SA tuple already occupied\");\n-\t\t\terr = -EEXIST;\n-\t\t\tgoto out;\n+\t\t\tif (x_new != x) {\n+\t\t\t\tNL_SET_ERR_MSG(extack, \"New SA tuple already occupied\");\n+\t\t\t\terr = -EEXIST;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\t/* self-match via wide mark mask; not a collision */\n \t\t}\n \t}\n \n@@ -3467,6 +3473,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t__xfrm_state_delete(x);\n \tspin_unlock_bh(\u0026x-\u003elock);\n \n+\txfrm_state_hold(xc);\n \terr = xfrm_state_migrate_install(x, xc, \u0026m, extack);\n \tif (err \u003c 0) {\n \t\t/*\n@@ -3474,6 +3481,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\t * free under xfrm_cfg_mutex. Both SAs are gone if it does;\n \t\t * restoring x would risk SN/IV reuse.\n \t\t */\n+\t\txfrm_state_put(xc);\n \t\tgoto out;\n \t}\n \n@@ -3492,10 +3500,13 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\terr = 0;\n \t}\n \n+\txfrm_state_put(xc);\n out:\n \txfrm_state_put(x);\n \treturn err;\n out_xc:\n+\tif (m.xuo)\n+\t\txfrm_dev_state_delete(xc);\n \txc-\u003ekm.state = XFRM_STATE_DEAD;\n \txfrm_state_put(xc);\n \txfrm_state_put(x);\ndiff --git a/tools/testing/selftests/net/xfrm_state.sh b/tools/testing/selftests/net/xfrm_state.sh\nindex f6c54a6496d714..f202073726a9f4 100755\n--- a/tools/testing/selftests/net/xfrm_state.sh\n+++ b/tools/testing/selftests/net/xfrm_state.sh\n@@ -42,7 +42,11 @@ tests=\"\n \tmtu_ipv4_r2\t\t\tIPv4 MTU exceeded from ESP router r2\n \tmtu_ipv6_r2\t\t\tIPv6 MTU exceeded from ESP router r2\n \tmtu_ipv4_r3\t\t\tIPv4 MTU exceeded from router r3\n-\tmtu_ipv6_r3\t\t\tIPv6 MTU exceeded from router r3\"\n+\tmtu_ipv6_r3\t\t\tIPv6 MTU exceeded from router r3\n+\tmark_wildcard_shadow\t\tmark: wildcard SA in by-spi state get lookup\n+\tmark_wildcard_delete\t\tmark: wildcard SA in by-spi state delete\n+\tmark_wildcard_get_addr\t\tmark: wildcard SA in by-address get lookup\n+\tmark_wildcard_delete_addr\tmark: wildcard SA in by-address delete\"\n \n prefix4=\"10.1\"\n prefix6=\"fc00\"\n@@ -101,6 +105,10 @@ run_test() {\n \t\tmtu_ipv6_r2)         test_mtu_ipv6_r2 ;;\n \t\tmtu_ipv4_r3)         test_mtu_ipv4_r3 ;;\n \t\tmtu_ipv6_r3)         test_mtu_ipv6_r3 ;;\n+\t\tmark_wildcard_shadow)      test_mark_wildcard_shadow ;;\n+\t\tmark_wildcard_delete)      test_mark_wildcard_delete ;;\n+\t\tmark_wildcard_get_addr)    test_mark_wildcard_get_addr ;;\n+\t\tmark_wildcard_delete_addr) test_mark_wildcard_delete_addr ;;\n \t\tesac\n \t\tret=$?\n \n@@ -167,6 +175,8 @@ setup_namespaces() {\n \t[ -n \"${NS_S2}\" ] \u0026\u0026 ns_s2=(ip netns exec \"${NS_S2}\") \u0026\u0026 ns_active=\"${ns_active} $NS_S2\"\n \t[ -n \"${NS_R3}\" ] \u0026\u0026 ns_r3=(ip netns exec \"${NS_R3}\") \u0026\u0026 ns_active=\"${ns_active} $NS_R3\"\n \t[ -n \"${NS_B}\" ] \u0026\u0026 ns_active=\"${ns_active} $NS_B\"\n+\n+\treturn 0\n }\n \n addr_add() {\n@@ -295,6 +305,18 @@ setup_ns_set_v6x() {\n \tset_xfrm_params\n }\n \n+setup_ns_set_simple() {\n+\t# Single namespace, no veths/routes.\n+\tns_set=\"a\"\n+\timax=1\n+\tsrc=\"10.1.1.1\"\n+\tdst=\"10.1.1.2\"\n+\tsrc_net=\"10.1.0.0/24\"\n+\tdst_net=\"10.2.0.0/24\"\n+\n+\tset_xfrm_params\n+}\n+\n setup_network() {\n \t# Create veths and add addresses\n \tlocal -a ns_cmd\n@@ -403,6 +425,7 @@ setup() {\n \t\tns_set_v4x)    setup_ns_set_v4x ;;\n \t\tns_set_v6)     setup_ns_set_v6 ;;\n \t\tns_set_v6x)    setup_ns_set_v6x ;;\n+\t\tns_set_simple) setup_ns_set_simple ;;\n \t\tnamespaces)    setup_namespaces ;;\n \t\tnetwork)       setup_network ;;\n \t\txfrm)          setup_xfrm ;;\n@@ -548,6 +571,111 @@ test_mtu_ipv6_r3() {\n \treturn \"${rc}\"\n }\n \n+# SA_decoy (mark 0/0, added second) shadows SA_target (mark 1/1) on a\n+# wildcard mark lookup. No traffic sent; these only exercise the SAD.\n+\n+test_mark_wildcard_shadow() {\n+\tsetup ns_set_simple namespaces || return \"$ksft_skip\"\n+\tlocal result=0\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src}\" dst \"${dst}\" proto esp spi 0x1000 \\\n+\t\treqid 100 mode tunnel \\\n+\t\taead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \\\n+\t\tmark 1 mask 1\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src}\" dst \"${dst}\" proto esp spi 0x1000 \\\n+\t\treqid 100 mode tunnel \\\n+\t\taead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \\\n+\t\tmark 0 mask 0\n+\n+\trun_cmd_err \"${ns_a[@]}\" ip xfrm state get \\\n+\t\tdst \"${dst}\" proto esp spi 0x1000 \\\n+\t\tmark 1 mask 1\n+\n+\t# Expected: SA_target (mark 0x1/0x1). Actual (bug): SA_decoy (mark 0/0).\n+\techo \"$out\" | grep -q \"mark 0x1/0x1\" || result=1\n+\n+\treturn \"${result}\"\n+}\n+\n+test_mark_wildcard_delete() {\n+\tsetup ns_set_simple namespaces || return \"$ksft_skip\"\n+\tlocal result=0\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src}\" dst \"${dst}\" proto esp spi 0x1000 \\\n+\t\treqid 100 mode tunnel \\\n+\t\taead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \\\n+\t\tmark 1 mask 1\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src}\" dst \"${dst}\" proto esp spi 0x1000 \\\n+\t\treqid 100 mode tunnel \\\n+\t\taead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \\\n+\t\tmark 0 mask 0\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state delete \\\n+\t\tdst \"${dst}\" proto esp spi 0x1000 \\\n+\t\tmark 1 mask 1\n+\n+\trun_cmd_err \"${ns_a[@]}\" ip xfrm state show\n+\techo \"$out\" | grep -q \"mark 0x1/0x1\" \u0026\u0026 result=1\n+\n+\treturn \"${result}\"\n+}\n+\n+# by-address counterpart: proto route2/hao (IPv6 mobility) have no SPI,\n+# so xfrm_user_state_lookup() resolves them by address instead.\n+\n+test_mark_wildcard_get_addr() {\n+\tsetup ns_set_simple namespaces || return \"$ksft_skip\"\n+\tlocal result=0\n+\tlocal src6=\"fc00:9::1\"\n+\tlocal dst6=\"fc00:9::2\"\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src6}\" dst \"${dst6}\" proto route2 mode ro coa fc00:9::3 \\\n+\t\tmark 1 mask 1\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src6}\" dst \"${dst6}\" proto route2 mode ro coa fc00:9::4 \\\n+\t\tmark 0 mask 0\n+\n+\trun_cmd_err \"${ns_a[@]}\" ip xfrm state get \\\n+\t\tsrc \"${src6}\" dst \"${dst6}\" proto route2 \\\n+\t\tmark 1 mask 1\n+\n+\techo \"$out\" | grep -q \"mark 0x1/0x1\" || result=1\n+\n+\treturn \"${result}\"\n+}\n+\n+test_mark_wildcard_delete_addr() {\n+\tsetup ns_set_simple namespaces || return \"$ksft_skip\"\n+\tlocal result=0\n+\tlocal src6=\"fc00:9::1\"\n+\tlocal dst6=\"fc00:9::2\"\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src6}\" dst \"${dst6}\" proto route2 mode ro coa fc00:9::3 \\\n+\t\tmark 1 mask 1\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state add \\\n+\t\tsrc \"${src6}\" dst \"${dst6}\" proto route2 mode ro coa fc00:9::4 \\\n+\t\tmark 0 mask 0\n+\n+\trun_cmd \"${ns_a[@]}\" ip xfrm state delete \\\n+\t\tsrc \"${src6}\" dst \"${dst6}\" proto route2 \\\n+\t\tmark 1 mask 1\n+\n+\trun_cmd_err \"${ns_a[@]}\" ip xfrm state show\n+\techo \"$out\" | grep -q \"mark 0x1/0x1\" \u0026\u0026 result=1\n+\n+\treturn \"${result}\"\n+}\n+\n ################################################################################\n #\n usage() {\n"
}

2/1 2026/07/06 15:15 agent patch-evaluator
4m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "XFRM_USER",
    "XFRM_MIGRATE",
    "IPV6"
  ],
  "FocusSymbols": [
    "xfrm_state_lookup_exact",
    "xfrm_state_lookup_byaddr",
    "xfrm_user_state_lookup",
    "xfrm_do_migrate_state",
    "xfrm_state_update",
    "xfrm_state_migrate_install",
    "xfrm_get_ae",
    "xfrm_new_ae",
    "xfrm_add_sa_expire"
  ],
  "Reasoning": "The patch modifies the XFRM (IPsec) state lookup logic to allow exact matching of marks and masks during control plane operations (GET, DELETE, MIGRATE). This is reachable from user space via netlink messages and affects core IPsec functionality.",
  "WorthFuzzing": true
}

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

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

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

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

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 68305911e5abd6e55dd802d30507109fca253d42
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Jul 6 15:15:24 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/networking/xfrm/xfrm_migrate_state.rst b/Documentation/networking/xfrm/xfrm_migrate_state.rst
index 9d53cb22b00752..905e3f0c4ee06f 100644
--- a/Documentation/networking/xfrm/xfrm_migrate_state.rst
+++ b/Documentation/networking/xfrm/xfrm_migrate_state.rst
@@ -27,15 +27,18 @@ SA Identification
 =================
 
 The struct is defined in ``include/uapi/linux/xfrm.h``. The SA is looked
-up using ``xfrm_state_lookup()`` with ``id.spi``,
-``id.daddr``, ``id.proto``, ``id.family``, and
-``old_mark.v & old_mark.m`` as the mark key::
+up using ``xfrm_state_lookup_exact()`` with ``id.spi``, ``id.daddr``,
+``id.proto``, ``id.family``, and an exact match against ``old_mark.v``
+and ``old_mark.m``. Unlike the data path, which uses a masked
+comparison, this requires the SA's mark and mask to equal ``old_mark``
+exactly, so a broad-mask SA is never matched when a more specific one
+was intended. If no such SA exists, ``-ESRCH`` is returned.::
 
     struct xfrm_user_migrate_state {
         struct xfrm_usersa_id  id;       /* spi, daddr, proto, family */
         xfrm_address_t         new_daddr;
         xfrm_address_t         new_saddr;
-        struct xfrm_mark       old_mark; /* SA lookup: key = v & m */
+        struct xfrm_mark       old_mark; /* SA lookup key (exact v/m match) */
         struct xfrm_selector   new_sel;  /* new selector (see Flags) */
         __u32                  new_reqid;
         __u32                  flags;    /* XFRM_MIGRATE_STATE_* */
@@ -72,8 +75,8 @@ inherits the value from the existing SA (omit-to-inherit).
      - Description
    * - ``XFRMA_MARK``
      - Mark on the migrated SA (``struct xfrm_mark``). Absent inherits
-       ``old_mark``. To use no mark on the new SA, send ``XFRMA_MARK``
-       with ``{0, 0}``.
+       the mark of the existing SA. To use no mark on the new SA, send
+       ``XFRMA_MARK`` with ``{0, 0}``.
    * - ``XFRMA_ENCAP``
      - UDP encapsulation template; only ``UDP_ENCAP_ESPINUDP`` is supported.
        Set ``encap_type=0`` to remove encap.
@@ -259,8 +262,9 @@ Attributes in the notification
 Error Handling
 ==============
 
-If the target SA tuple (new daddr, SPI, proto, new family) is already
-occupied, the operation returns ``-EEXIST`` before the migration begins.
+If the target SA tuple (new daddr, SPI, proto, new family, mark) is
+already occupied, the operation returns ``-EEXIST`` before the migration
+begins.
 The old SA remains intact and the operation is safe to retry after
 resolving the conflict.
 
diff --git a/include/net/xfrm.h b/include/net/xfrm.h
index 519a0156a05cac..ebe514376254a6 100644
--- a/include/net/xfrm.h
+++ b/include/net/xfrm.h
@@ -1741,11 +1741,14 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
 					   const xfrm_address_t *daddr,
 					   __be32 spi, u8 proto,
 					   unsigned short family);
-struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
+struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,
 					    const xfrm_address_t *daddr,
 					    const xfrm_address_t *saddr,
 					    u8 proto,
 					    unsigned short family);
+struct xfrm_state *xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+					   const xfrm_address_t *daddr, __be32 spi,
+					   u8 proto, unsigned short family);
 #ifdef CONFIG_XFRM_SUB_POLICY
 void xfrm_tmpl_sort(struct xfrm_tmpl **dst, struct xfrm_tmpl **src, int n,
 		    unsigned short family);
diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c
index 89d0443b530732..2dd347fece52ca 100644
--- a/net/ipv6/xfrm6_input.c
+++ b/net/ipv6/xfrm6_input.c
@@ -272,7 +272,7 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
 			break;
 		}
 
-		x = xfrm_state_lookup_byaddr(net, skb->mark, dst, src, proto, AF_INET6);
+		x = xfrm_state_lookup_byaddr(net, skb->mark, 0, false, dst, src, proto, AF_INET6);
 		if (!x)
 			continue;
 
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index c58cd024e3c675..0b3dc7789dd790 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -1172,11 +1172,22 @@ static struct xfrm_state *__xfrm_state_lookup_all(const struct xfrm_hash_state_p
 	return NULL;
 }
 
-static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
-					      u32 mark,
-					      const xfrm_address_t *daddr,
-					      __be32 spi, u8 proto,
-					      unsigned short family)
+/* exact=false: data-plane wildcard match against x's mask. exact=true:
+ * control-plane identity match, mark and mask must both match exactly.
+ */
+static bool xfrm_state_mark_matches(const struct xfrm_state *x, u32 mark, u32 mask, bool exact)
+{
+	if (exact)
+		return x->mark.v == mark && x->mark.m == mask;
+	return (mark & x->mark.m) == x->mark.v;
+}
+
+static struct xfrm_state *
+__xfrm_state_lookup(const struct xfrm_hash_state_ptrs *state_ptrs,
+		    u32 mark, u32 mask, bool exact,
+		    const xfrm_address_t *daddr,
+		    __be32 spi, u8 proto,
+		    unsigned short family)
 {
 	unsigned int h = __xfrm_spi_hash(daddr, spi, proto, family, state_ptrs->hmask);
 	struct xfrm_state *x;
@@ -1188,7 +1199,7 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
 		    !xfrm_addr_equal(&x->id.daddr, daddr, family))
 			continue;
 
-		if ((mark & x->mark.m) != x->mark.v)
+		if (!xfrm_state_mark_matches(x, mark, mask, exact))
 			continue;
 		if (!xfrm_state_hold_rcu(x))
 			continue;
@@ -1198,6 +1209,17 @@ static struct xfrm_state *__xfrm_state_lookup(const struct xfrm_hash_state_ptrs
 	return NULL;
 }
 
+static struct xfrm_state *
+__xfrm_state_lookup_exact(const struct xfrm_hash_state_ptrs *state_ptrs,
+			  const struct xfrm_mark *mark,
+			  const xfrm_address_t *daddr,
+			  __be32 spi, u8 proto,
+			  unsigned short family)
+{
+	return __xfrm_state_lookup(state_ptrs, mark->v, mark->m, true,
+				   daddr, spi, proto, family);
+}
+
 struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
 					   const xfrm_address_t *daddr,
 					   __be32 spi, u8 proto,
@@ -1228,7 +1250,7 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
 
 	xfrm_hash_ptrs_get(net, &state_ptrs);
 
-	x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+	x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
 	if (x) {
 		spin_lock(&net->xfrm.xfrm_state_lock);
 		if (x->km.state != XFRM_STATE_VALID) {
@@ -1252,11 +1274,12 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
 }
 EXPORT_SYMBOL(xfrm_input_state_lookup);
 
-static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,
-						     u32 mark,
-						     const xfrm_address_t *daddr,
-						     const xfrm_address_t *saddr,
-						     u8 proto, unsigned short family)
+static struct xfrm_state *
+__xfrm_state_lookup_byaddr(const struct xfrm_hash_state_ptrs *state_ptrs,
+			   u32 mark, u32 mask, bool exact,
+			   const xfrm_address_t *daddr,
+			   const xfrm_address_t *saddr,
+			   u8 proto, unsigned short family)
 {
 	unsigned int h = __xfrm_src_hash(daddr, saddr, family, state_ptrs->hmask);
 	struct xfrm_state *x;
@@ -1268,7 +1291,7 @@ static struct xfrm_state *__xfrm_state_lookup_byaddr(const struct xfrm_hash_stat
 		    !xfrm_addr_equal(&x->props.saddr, saddr, family))
 			continue;
 
-		if ((mark & x->mark.m) != x->mark.v)
+		if (!xfrm_state_mark_matches(x, mark, mask, exact))
 			continue;
 		if (!xfrm_state_hold_rcu(x))
 			continue;
@@ -1288,10 +1311,31 @@ __xfrm_state_locate(struct xfrm_state *x, int use_spi, int family)
 	xfrm_hash_ptrs_get(net, &state_ptrs);
 
 	if (use_spi)
-		return __xfrm_state_lookup(&state_ptrs, mark, &x->id.daddr,
+		return __xfrm_state_lookup(&state_ptrs, mark, 0, false, &x->id.daddr,
 					   x->id.spi, x->id.proto, family);
 	else
-		return __xfrm_state_lookup_byaddr(&state_ptrs, mark,
+		return __xfrm_state_lookup_byaddr(&state_ptrs, mark, 0, false,
+						  &x->id.daddr,
+						  &x->props.saddr,
+						  x->id.proto, family);
+}
+
+/* Used by xfrm_state_update() only; xfrm_state_add()'s dup check keeps
+ * using the wildcard __xfrm_state_locate() above.
+ */
+static inline struct xfrm_state *
+__xfrm_state_locate_exact(struct xfrm_state *x, int use_spi, int family)
+{
+	struct xfrm_hash_state_ptrs state_ptrs;
+	struct net *net = xs_net(x);
+
+	xfrm_hash_ptrs_get(net, &state_ptrs);
+
+	if (use_spi)
+		return __xfrm_state_lookup_exact(&state_ptrs, &x->mark, &x->id.daddr,
+						 x->id.spi, x->id.proto, family);
+	else
+		return __xfrm_state_lookup_byaddr(&state_ptrs, x->mark.v, x->mark.m, true,
 						  &x->id.daddr,
 						  &x->props.saddr,
 						  x->id.proto, family);
@@ -2177,10 +2221,12 @@ int xfrm_state_migrate_install(const struct xfrm_state *x,
 			       struct netlink_ext_ack *extack)
 {
 	if (m->new_family == m->old_family &&
-	    xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) {
+	    xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family) &&
+	    xc->mark.v == x->mark.v && xc->mark.m == x->mark.m) {
 		/*
-		 * Care is needed when the destination address of the state is
-		 * to be updated as it is a part of triplet.
+		 * Care is needed when the destination address or mark of the
+		 * state is to be updated, as they are part of the lookup
+		 * triplet.
 		 */
 		xfrm_state_insert(xc);
 	} else {
@@ -2229,7 +2275,7 @@ int xfrm_state_update(struct xfrm_state *x)
 	to_put = NULL;
 
 	spin_lock_bh(&net->xfrm.xfrm_state_lock);
-	x1 = __xfrm_state_locate(x, use_spi, x->props.family);
+	x1 = __xfrm_state_locate_exact(x, use_spi, x->props.family);
 
 	err = -ESRCH;
 	if (!x1)
@@ -2374,14 +2420,14 @@ xfrm_state_lookup(struct net *net, u32 mark, const xfrm_address_t *daddr, __be32
 	rcu_read_lock();
 	xfrm_hash_ptrs_get(net, &state_ptrs);
 
-	x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family);
+	x = __xfrm_state_lookup(&state_ptrs, mark, 0, false, daddr, spi, proto, family);
 	rcu_read_unlock();
 	return x;
 }
 EXPORT_SYMBOL(xfrm_state_lookup);
 
 struct xfrm_state *
-xfrm_state_lookup_byaddr(struct net *net, u32 mark,
+xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,
 			 const xfrm_address_t *daddr, const xfrm_address_t *saddr,
 			 u8 proto, unsigned short family)
 {
@@ -2392,12 +2438,29 @@ xfrm_state_lookup_byaddr(struct net *net, u32 mark,
 
 	xfrm_hash_ptrs_get(net, &state_ptrs);
 
-	x = __xfrm_state_lookup_byaddr(&state_ptrs, mark, daddr, saddr, proto, family);
+	x = __xfrm_state_lookup_byaddr(&state_ptrs, mark, mask, exact, daddr, saddr, proto, family);
 	rcu_read_unlock();
 	return x;
 }
 EXPORT_SYMBOL(xfrm_state_lookup_byaddr);
 
+struct xfrm_state *
+xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,
+			const xfrm_address_t *daddr, __be32 spi,
+			u8 proto, unsigned short family)
+{
+	struct xfrm_hash_state_ptrs state_ptrs;
+	struct xfrm_state *x;
+
+	rcu_read_lock();
+	xfrm_hash_ptrs_get(net, &state_ptrs);
+
+	x = __xfrm_state_lookup_exact(&state_ptrs, mark, daddr, spi, proto, family);
+	rcu_read_unlock();
+	return x;
+}
+EXPORT_SYMBOL(xfrm_state_lookup_exact);
+
 struct xfrm_state *
 xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid,
 	      u32 if_id, u32 pcpu_num, u8 proto, const xfrm_address_t *daddr,
@@ -3306,7 +3369,7 @@ int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
 	if (err)
 		return err;
 
-	err = xfrm_init_replay(x, NULL);
+	err = xfrm_init_replay(x, extack);
 	if (err)
 		return err;
 
diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c
index 6384795ee6b269..a2317e6e6802dd 100644
--- a/net/xfrm/xfrm_user.c
+++ b/net/xfrm/xfrm_user.c
@@ -1089,11 +1089,12 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
 	struct xfrm_state *x = NULL;
 	struct xfrm_mark m;
 	int err;
-	u32 mark = xfrm_mark_get(attrs, &m);
+
+	xfrm_mark_get(attrs, &m);
 
 	if (xfrm_id_proto_match(p->proto, IPSEC_PROTO_ANY)) {
 		err = -ESRCH;
-		x = xfrm_state_lookup(net, mark, &p->daddr, p->spi, p->proto, p->family);
+		x = xfrm_state_lookup_exact(net, &m, &p->daddr, p->spi, p->proto, p->family);
 	} else {
 		xfrm_address_t *saddr = NULL;
 
@@ -1104,7 +1105,7 @@ static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
 		}
 
 		err = -ESRCH;
-		x = xfrm_state_lookup_byaddr(net, mark,
+		x = xfrm_state_lookup_byaddr(net, m.v, m.m, true,
 					     &p->daddr, saddr,
 					     p->proto, p->family);
 	}
@@ -2788,14 +2789,13 @@ static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
 	struct sk_buff *r_skb;
 	int err;
 	struct km_event c;
-	u32 mark;
 	struct xfrm_mark m;
 	struct xfrm_aevent_id *p = nlmsg_data(nlh);
 	struct xfrm_usersa_id *id = &p->sa_id;
 
-	mark = xfrm_mark_get(attrs, &m);
+	xfrm_mark_get(attrs, &m);
 
-	x = xfrm_state_lookup(net, mark, &id->daddr, id->spi, id->proto, id->family);
+	x = xfrm_state_lookup_exact(net, &m, &id->daddr, id->spi, id->proto, id->family);
 	if (x == NULL)
 		return -ESRCH;
 
@@ -2836,7 +2836,6 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
 	struct xfrm_state *x;
 	struct km_event c;
 	int err = -EINVAL;
-	u32 mark = 0;
 	struct xfrm_mark m;
 	struct xfrm_aevent_id *p = nlmsg_data(nlh);
 	struct nlattr *rp = attrs[XFRMA_REPLAY_VAL];
@@ -2856,9 +2855,10 @@ static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
 		return err;
 	}
 
-	mark = xfrm_mark_get(attrs, &m);
+	xfrm_mark_get(attrs, &m);
 
-	x = xfrm_state_lookup(net, mark, &p->sa_id.daddr, p->sa_id.spi, p->sa_id.proto, p->sa_id.family);
+	x = xfrm_state_lookup_exact(net, &m, &p->sa_id.daddr, p->sa_id.spi,
+				    p->sa_id.proto, p->sa_id.family);
 	if (x == NULL)
 		return -ESRCH;
 
@@ -2992,9 +2992,10 @@ static int xfrm_add_sa_expire(struct sk_buff *skb, struct nlmsghdr *nlh,
 	struct xfrm_user_expire *ue = nlmsg_data(nlh);
 	struct xfrm_usersa_info *p = &ue->state;
 	struct xfrm_mark m;
-	u32 mark = xfrm_mark_get(attrs, &m);
 
-	x = xfrm_state_lookup(net, mark, &p->id.daddr, p->id.spi, p->id.proto, p->family);
+	xfrm_mark_get(attrs, &m);
+
+	x = xfrm_state_lookup_exact(net, &m, &p->id.daddr, p->id.spi, p->id.proto, p->family);
 
 	err = -ENOENT;
 	if (x == NULL)
@@ -3361,9 +3362,9 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
 
 	copy_from_user_migrate_state(&m, um);
 
-	x = xfrm_state_lookup(net, m.old_mark.v & m.old_mark.m,
-			      &um->id.daddr, um->id.spi,
-			      um->id.proto, um->id.family);
+	x = xfrm_state_lookup_exact(net, &m.old_mark,
+				    &um->id.daddr, um->id.spi,
+				    um->id.proto, um->id.family);
 	if (!x) {
 		NL_SET_ERR_MSG(extack, "Can not find state");
 		return -ESRCH;
@@ -3434,18 +3435,23 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
 						       x->nat_keepalive_interval);
 
 	if (m.new_family != um->id.family ||
-	    !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family)) {
+	    !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family) ||
+	    (m.new_mark && (m.new_mark->v != x->mark.v ||
+			   m.new_mark->m != x->mark.m))) {
 		u32 new_mark_key = m.new_mark ? m.new_mark->v & m.new_mark->m :
-						m.old_mark.v & m.old_mark.m;
+						x->mark.v & x->mark.m;
 		struct xfrm_state *x_new;
 
 		x_new = xfrm_state_lookup(net, new_mark_key, &m.new_daddr,
 					  um->id.spi, um->id.proto, m.new_family);
 		if (x_new) {
 			xfrm_state_put(x_new);
-			NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
-			err = -EEXIST;
-			goto out;
+			if (x_new != x) {
+				NL_SET_ERR_MSG(extack, "New SA tuple already occupied");
+				err = -EEXIST;
+				goto out;
+			}
+			/* self-match via wide mark mask; not a collision */
 		}
 	}
 
@@ -3467,6 +3473,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
 	__xfrm_state_delete(x);
 	spin_unlock_bh(&x->lock);
 
+	xfrm_state_hold(xc);
 	err = xfrm_state_migrate_install(x, xc, &m, extack);
 	if (err < 0) {
 		/*
@@ -3474,6 +3481,7 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
 		 * free under xfrm_cfg_mutex. Both SAs are gone if it does;
 		 * restoring x would risk SN/IV reuse.
 		 */
+		xfrm_state_put(xc);
 		goto out;
 	}
 
@@ -3492,10 +3500,13 @@ static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,
 		err = 0;
 	}
 
+	xfrm_state_put(xc);
 out:
 	xfrm_state_put(x);
 	return err;
 out_xc:
+	if (m.xuo)
+		xfrm_dev_state_delete(xc);
 	xc->km.state = XFRM_STATE_DEAD;
 	xfrm_state_put(xc);
 	xfrm_state_put(x);
diff --git a/tools/testing/selftests/net/xfrm_state.sh b/tools/testing/selftests/net/xfrm_state.sh
index f6c54a6496d714..f202073726a9f4 100755
--- a/tools/testing/selftests/net/xfrm_state.sh
+++ b/tools/testing/selftests/net/xfrm_state.sh
@@ -42,7 +42,11 @@ tests="
 	mtu_ipv4_r2			IPv4 MTU exceeded from ESP router r2
 	mtu_ipv6_r2			IPv6 MTU exceeded from ESP router r2
 	mtu_ipv4_r3			IPv4 MTU exceeded from router r3
-	mtu_ipv6_r3			IPv6 MTU exceeded from router r3"
+	mtu_ipv6_r3			IPv6 MTU exceeded from router r3
+	mark_wildcard_shadow		mark: wildcard SA in by-spi state get lookup
+	mark_wildcard_delete		mark: wildcard SA in by-spi state delete
+	mark_wildcard_get_addr		mark: wildcard SA in by-address get lookup
+	mark_wildcard_delete_addr	mark: wildcard SA in by-address delete"
 
 prefix4="10.1"
 prefix6="fc00"
@@ -101,6 +105,10 @@ run_test() {
 		mtu_ipv6_r2)         test_mtu_ipv6_r2 ;;
 		mtu_ipv4_r3)         test_mtu_ipv4_r3 ;;
 		mtu_ipv6_r3)         test_mtu_ipv6_r3 ;;
+		mark_wildcard_shadow)      test_mark_wildcard_shadow ;;
+		mark_wildcard_delete)      test_mark_wildcard_delete ;;
+		mark_wildcard_get_addr)    test_mark_wildcard_get_addr ;;
+		mark_wildcard_delete_addr) test_mark_wildcard_delete_addr ;;
 		esac
 		ret=$?
 
@@ -167,6 +175,8 @@ setup_namespaces() {
 	[ -n "${NS_S2}" ] && ns_s2=(ip netns exec "${NS_S2}") && ns_active="${ns_active} $NS_S2"
 	[ -n "${NS_R3}" ] && ns_r3=(ip netns exec "${NS_R3}") && ns_active="${ns_active} $NS_R3"
 	[ -n "${NS_B}" ] && ns_active="${ns_active} $NS_B"
+
+	return 0
 }
 
 addr_add() {
@@ -295,6 +305,18 @@ setup_ns_set_v6x() {
 	set_xfrm_params
 }
 
+setup_ns_set_simple() {
+	# Single namespace, no veths/routes.
+	ns_set="a"
+	imax=1
+	src="10.1.1.1"
+	dst="10.1.1.2"
+	src_net="10.1.0.0/24"
+	dst_net="10.2.0.0/24"
+
+	set_xfrm_params
+}
+
 setup_network() {
 	# Create veths and add addresses
 	local -a ns_cmd
@@ -403,6 +425,7 @@ setup() {
 		ns_set_v4x)    setup_ns_set_v4x ;;
 		ns_set_v6)     setup_ns_set_v6 ;;
 		ns_set_v6x)    setup_ns_set_v6x ;;
+		ns_set_simple) setup_ns_set_simple ;;
 		namespaces)    setup_namespaces ;;
 		network)       setup_network ;;
 		xfrm)          setup_xfrm ;;
@@ -548,6 +571,111 @@ test_mtu_ipv6_r3() {
 	return "${rc}"
 }
 
+# SA_decoy (mark 0/0, added second) shadows SA_target (mark 1/1) on a
+# wildcard mark lookup. No traffic sent; these only exercise the SAD.
+
+test_mark_wildcard_shadow() {
+	setup ns_set_simple namespaces || return "$ksft_skip"
+	local result=0
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src}" dst "${dst}" proto esp spi 0x1000 \
+		reqid 100 mode tunnel \
+		aead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \
+		mark 1 mask 1
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src}" dst "${dst}" proto esp spi 0x1000 \
+		reqid 100 mode tunnel \
+		aead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \
+		mark 0 mask 0
+
+	run_cmd_err "${ns_a[@]}" ip xfrm state get \
+		dst "${dst}" proto esp spi 0x1000 \
+		mark 1 mask 1
+
+	# Expected: SA_target (mark 0x1/0x1). Actual (bug): SA_decoy (mark 0/0).
+	echo "$out" | grep -q "mark 0x1/0x1" || result=1
+
+	return "${result}"
+}
+
+test_mark_wildcard_delete() {
+	setup ns_set_simple namespaces || return "$ksft_skip"
+	local result=0
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src}" dst "${dst}" proto esp spi 0x1000 \
+		reqid 100 mode tunnel \
+		aead 'rfc4106(gcm(aes))' 0x1111111111111111111111111111111111111111 96 \
+		mark 1 mask 1
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src}" dst "${dst}" proto esp spi 0x1000 \
+		reqid 100 mode tunnel \
+		aead 'rfc4106(gcm(aes))' 0x2222222222222222222222222222222222222222 96 \
+		mark 0 mask 0
+
+	run_cmd "${ns_a[@]}" ip xfrm state delete \
+		dst "${dst}" proto esp spi 0x1000 \
+		mark 1 mask 1
+
+	run_cmd_err "${ns_a[@]}" ip xfrm state show
+	echo "$out" | grep -q "mark 0x1/0x1" && result=1
+
+	return "${result}"
+}
+
+# by-address counterpart: proto route2/hao (IPv6 mobility) have no SPI,
+# so xfrm_user_state_lookup() resolves them by address instead.
+
+test_mark_wildcard_get_addr() {
+	setup ns_set_simple namespaces || return "$ksft_skip"
+	local result=0
+	local src6="fc00:9::1"
+	local dst6="fc00:9::2"
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::3 \
+		mark 1 mask 1
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::4 \
+		mark 0 mask 0
+
+	run_cmd_err "${ns_a[@]}" ip xfrm state get \
+		src "${src6}" dst "${dst6}" proto route2 \
+		mark 1 mask 1
+
+	echo "$out" | grep -q "mark 0x1/0x1" || result=1
+
+	return "${result}"
+}
+
+test_mark_wildcard_delete_addr() {
+	setup ns_set_simple namespaces || return "$ksft_skip"
+	local result=0
+	local src6="fc00:9::1"
+	local dst6="fc00:9::2"
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::3 \
+		mark 1 mask 1
+
+	run_cmd "${ns_a[@]}" ip xfrm state add \
+		src "${src6}" dst "${dst6}" proto route2 mode ro coa fc00:9::4 \
+		mark 0 mask 0
+
+	run_cmd "${ns_a[@]}" ip xfrm state delete \
+		src "${src6}" dst "${dst6}" proto route2 \
+		mark 1 mask 1
+
+	run_cmd_err "${ns_a[@]}" ip xfrm state show
+	echo "$out" | grep -q "mark 0x1/0x1" && result=1
+
+	return "${result}"
+}
+
 ################################################################################
 #
 usage() {

3/2 2026/07/06 15:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11249
							output: 22
							thoughts: 465
						

Thoughts:
**Analyzing the XFRM State Lookup Patch**

Okay, so I'm looking at this patch that alters the way the XFRM (IPsec) state lookup works within the kernel. It seems like the core change is to make the `xfrm_state_lookup_byaddr`, `xfrm_state_lookup`, `xfrm_state_lookup_exact`, and related functions use *exact* matching of marks and masks, instead of the current wildcard approach, when a request originates from the control plane (primarily `xfrm_user.c`). This is a crucial fix, because a wildcard Security Association (SA) with a mark of 0/0 can currently overshadow a more specific SA (e.g., mark 1/1) when we're dealing with control plane actions like GET, DELETE, or MIGRATE. That's a classic race condition, and this patch should address it directly.

My immediate thoughts are, let's nail down precisely which code is modified. I see changes in `net/xfrm/xfrm_state.c`, `net/xfrm/xfrm_user.c`, `net/ipv6/xfrm6_input.c`, and `include/net/xfrm.h`.  Plus, there are these new tests in `tools/testing/selftests/net/xfrm_state.sh` which are encouraging - good to see they're building test coverage around this.

Functionally, this is all about how IPsec SAs are looked up and handled via netlink. The impact is significant for operations like state deletion, migration, and retrieval. I need to focus on those specific netlink messages like `XFRM_MSG_GETSA`, `XFRM_MSG_DELSA`, and `XFRM_MSG_MIGRATE` to see the effect of this change.

Now, for fuzzing, I need to pinpoint the critical functions. I'm going to concentrate on `xfrm_state_lookup_exact`, `xfrm_state_lookup_byaddr`, `xfrm_user_state_lookup`, `xfrm_get_ae`, `xfrm_new_ae`, `xfrm_add_sa_expire`, and `xfrm_do_migrate_state`. These are the key entry points and manipulation points of the state lookup logic. They are definitely where any issues from the patch will surface.

Finally, let's verify that the necessary kernel configuration options are set. `CONFIG_XFRM_USER` is vital for `xfrm_user.c`, and `CONFIG_XFRM_MIGRATE` is needed for `xfrm_do_migrate_state`. I'll confirm that these are enabled in the build environment to ensure this patch is fully supported.



4/2 2026/07/06 15:16 tool grepper
0m Args:
{
  "Expression": "config XFRM_MIGRATE"
}

Results:
{
  "Output": "net/xfrm/Kconfig=51=config XFRM_SUB_POLICY\n--\nnet/xfrm/Kconfig-60-\nnet/xfrm/Kconfig:61:config XFRM_MIGRATE\nnet/xfrm/Kconfig-62-\tbool \"Transformation migrate database\"\n"
}

5/2 2026/07/06 15:16 llm patch-evaluator
2m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12414
							output: 20
							thoughts: 11
						

Thoughts:
Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:

**Analyzing the XFRM_USER Perspective**

Alright, let's dive in. My immediate thought is to check out `XFRM_USER`. It's the logical starting point. I need to understand what's happening with the user-space interactions, the potential configuration aspects, and any custom implementations. I'm thinking about the control plane and data plane here, and whether the issue I'm looking at could be a result of something misconfigured at the user level. What are the relevant socket options? Are there any unexpected policy settings? I'll need to look at how the keys are being managed and if there are any obvious discrepancies between expected behavior and what I'm observing. It’s likely in the kernel-user interactions. This could be where the problem stems.



6/2 2026/07/06 15:18 tool grepper
0m Args:
{
  "Expression": "config XFRM_USER"
}

Results:
{
  "Output": "net/xfrm/Kconfig=22=if INET\nnet/xfrm/Kconfig:23:config XFRM_USER\nnet/xfrm/Kconfig-24-\ttristate \"Transformation user configuration interface\"\n--\nnet/xfrm/Kconfig-31-\nnet/xfrm/Kconfig:32:config XFRM_USER_COMPAT\nnet/xfrm/Kconfig-33-\ttristate \"Compatible ABI support\"\n"
}

7/2 2026/07/06 15:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12760
							output: 22
							thoughts: 28
						

Thoughts:
Here's my take on those thoughts, tailored for an expert audience:

**Diving into `xfrm_do_migrate_state`**

Alright, time to dig into `xfrm_do_migrate_state`. I need to refresh my memory on the intricacies of this function within `net/xfrm/xfrm_user.c`. Specifically, I want to understand how it handles state migration. This likely involves user-space interactions, possibly with netlink messages or similar mechanisms. I'll need to focus on how the function processes the data it receives, how it validates input, and how it orchestrates the transfer of security association (SA) information between different network namespaces or other contexts, because I know that's the usual implication of a "migrate" operation. I'll also be looking for any potential race conditions or security vulnerabilities that could arise during this state transfer, especially considering that the state might contain sensitive cryptographic keys or SPI values. Given my familiarity with XFRM, I'm expecting to see calls to functions like `xfrm_state_alloc`, `xfrm_state_add`, `xfrm_state_delete` and perhaps state locking mechanisms.



8/2 2026/07/06 15:18 tool grepper
0m Args:
{
  "Expression": "xfrm_do_migrate_state"
}

Results:
{
  "Output": "net/xfrm/xfrm_user.c=3304=static int xfrm_send_migrate_state(struct net *net,\n--\nnet/xfrm/xfrm_user.c-3324-\nnet/xfrm/xfrm_user.c:3325:static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\nnet/xfrm/xfrm_user.c-3326-\t\t\t\t struct nlattr **attrs, struct netlink_ext_ack *extack)\n--\nnet/xfrm/xfrm_user.c-3516-#else\nnet/xfrm/xfrm_user.c:3517:static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\nnet/xfrm/xfrm_user.c-3518-\t\t\t\t struct nlattr **attrs, struct netlink_ext_ack *extack)\n--\nnet/xfrm/xfrm_user.c=3732=static const struct xfrm_link {\n--\nnet/xfrm/xfrm_user.c-3769-\t[XFRM_MSG_GETDEFAULT  - XFRM_MSG_BASE] = { .doit = xfrm_get_default   },\nnet/xfrm/xfrm_user.c:3770:\t[XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate_state },\nnet/xfrm/xfrm_user.c-3771-};\n"
}

9/2 2026/07/06 15:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13464
							output: 22
							thoughts: 74
						

Thoughts:
Here's my take on the situation:

**XFRM Subsystem Testing Requirements**

Okay, so I'm thinking, to really nail down the `xfrm_do_migrate_state` function, we're definitely going to need the `XFRM_MIGRATE` flag activated for a proper testing environment. Makes perfect sense. And of course, given that `xfrm_user.c` is going to be integral to these tests, we're going to need `XFRM_USER` enabled. It’s pretty obvious, but let's just make sure.

Now, my next thought is, let's refresh on `xfrm_state_lookup_exact`. I'll go check it out in `net/xfrm/xfrm_state.c`. Always good to have a solid handle on how that particular function works, given its potential implications in a migration scenario. I want to make sure I understand it completely before proceeding.



10/2 2026/07/06 15:18 tool grepper
0m Args:
{
  "Expression": "xfrm_state_lookup_exact"
}

Results:
{
  "Output": "Documentation/networking/xfrm/xfrm_migrate_state.rst=29=The struct is defined in ``include/uapi/linux/xfrm.h``. The SA is looked\nDocumentation/networking/xfrm/xfrm_migrate_state.rst:30:up using ``xfrm_state_lookup_exact()`` with ``id.spi``, ``id.daddr``,\nDocumentation/networking/xfrm/xfrm_migrate_state.rst-31-``id.proto``, ``id.family``, and an exact match against ``old_mark.v``\n--\ninclude/net/xfrm.h=1744=struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark, u32 mask, bool exact,\n--\ninclude/net/xfrm.h-1748-\t\t\t\t\t    unsigned short family);\ninclude/net/xfrm.h:1749:struct xfrm_state *xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,\ninclude/net/xfrm.h-1750-\t\t\t\t\t   const xfrm_address_t *daddr, __be32 spi,\n--\nnet/xfrm/xfrm_state.c=1212=static struct xfrm_state *\nnet/xfrm/xfrm_state.c:1213:__xfrm_state_lookup_exact(const struct xfrm_hash_state_ptrs *state_ptrs,\nnet/xfrm/xfrm_state.c-1214-\t\t\t  const struct xfrm_mark *mark,\n--\nnet/xfrm/xfrm_state.c=1327=__xfrm_state_locate_exact(struct xfrm_state *x, int use_spi, int family)\n--\nnet/xfrm/xfrm_state.c-1334-\tif (use_spi)\nnet/xfrm/xfrm_state.c:1335:\t\treturn __xfrm_state_lookup_exact(\u0026state_ptrs, \u0026x-\u003emark, \u0026x-\u003eid.daddr,\nnet/xfrm/xfrm_state.c-1336-\t\t\t\t\t\t x-\u003eid.spi, x-\u003eid.proto, family);\n--\nnet/xfrm/xfrm_state.c=2447=struct xfrm_state *\nnet/xfrm/xfrm_state.c:2448:xfrm_state_lookup_exact(struct net *net, const struct xfrm_mark *mark,\nnet/xfrm/xfrm_state.c-2449-\t\t\tconst xfrm_address_t *daddr, __be32 spi,\n--\nnet/xfrm/xfrm_state.c-2457-\nnet/xfrm/xfrm_state.c:2458:\tx = __xfrm_state_lookup_exact(\u0026state_ptrs, mark, daddr, spi, proto, family);\nnet/xfrm/xfrm_state.c-2459-\trcu_read_unlock();\n--\nnet/xfrm/xfrm_state.c-2461-}\nnet/xfrm/xfrm_state.c:2462:EXPORT_SYMBOL(xfrm_state_lookup_exact);\nnet/xfrm/xfrm_state.c-2463-\n--\nnet/xfrm/xfrm_user.c=1084=static struct xfrm_state *xfrm_user_state_lookup(struct net *net,\n--\nnet/xfrm/xfrm_user.c-1096-\t\terr = -ESRCH;\nnet/xfrm/xfrm_user.c:1097:\t\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026p-\u003edaddr, p-\u003espi, p-\u003eproto, p-\u003efamily);\nnet/xfrm/xfrm_user.c-1098-\t} else {\n--\nnet/xfrm/xfrm_user.c=2784=static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh,\n--\nnet/xfrm/xfrm_user.c-2797-\nnet/xfrm/xfrm_user.c:2798:\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026id-\u003edaddr, id-\u003espi, id-\u003eproto, id-\u003efamily);\nnet/xfrm/xfrm_user.c-2799-\tif (x == NULL)\n--\nnet/xfrm/xfrm_user.c=2832=static int xfrm_new_ae(struct sk_buff *skb, struct nlmsghdr *nlh,\n--\nnet/xfrm/xfrm_user.c-2859-\nnet/xfrm/xfrm_user.c:2860:\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026p-\u003esa_id.daddr, p-\u003esa_id.spi,\nnet/xfrm/xfrm_user.c-2861-\t\t\t\t    p-\u003esa_id.proto, p-\u003esa_id.family);\n--\nnet/xfrm/xfrm_user.c=2985=static int xfrm_add_sa_expire(struct sk_buff *skb, struct nlmsghdr *nlh,\n--\nnet/xfrm/xfrm_user.c-2997-\nnet/xfrm/xfrm_user.c:2998:\tx = xfrm_state_lookup_exact(net, \u0026m, \u0026p-\u003eid.daddr, p-\u003eid.spi, p-\u003eid.proto, p-\u003efamily);\nnet/xfrm/xfrm_user.c-2999-\n--\nnet/xfrm/xfrm_user.c=3325=static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh,\n--\nnet/xfrm/xfrm_user.c-3364-\nnet/xfrm/xfrm_user.c:3365:\tx = xfrm_state_lookup_exact(net, \u0026m.old_mark,\nnet/xfrm/xfrm_user.c-3366-\t\t\t\t    \u0026um-\u003eid.daddr, um-\u003eid.spi,\n"
}

11/2 2026/07/06 15:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15312
							output: 22
							thoughts: 238
						

Thoughts:
**My Fuzzing Strategy for This IPsec SA Lookup Patch**

Okay, so I've taken a look at this patch and it's immediately clear that it's touching critical functional core logic – specifically, how IPsec Security Associations (SAs) are looked up. This is a prime target for fuzzing, given that it's reachable from userspace through netlink. My gut tells me this could be interesting.

The functions I'm focusing on are: `xfrm_state_lookup_exact`, `xfrm_state_lookup_byaddr`, `xfrm_user_state_lookup`, `xfrm_get_ae`, `xfrm_new_ae`, `xfrm_add_sa_expire`, `xfrm_do_migrate_state`, `xfrm_state_update`, and `xfrm_state_migrate_install`. These look like the key pieces involved in this lookup and management process.

Before I jump into fuzzing, I'm going to quickly check the configuration dependencies. I'll need `CONFIG_XFRM` as the base, of course. Then, `CONFIG_XFRM_USER` is essential for the userspace access, and since migration is involved, `CONFIG_XFRM_MIGRATE` is a must. Knowing what I'm dealing with, `CONFIG_INET` for `XFRM_USER` is also necessary, and I see `CONFIG_IPV6` dependency, specifically for `net/ipv6/xfrm6_input.c`. I will verify the dependencies for `XFRM_MIGRATE` to be certain I have all the boxes ticked before I start.



12/2 2026/07/06 15:18 tool grepper
0m Args:
{
  "Expression": "config XFRM_MIGRATE"
}

Results:
{
  "Output": "net/xfrm/Kconfig=51=config XFRM_SUB_POLICY\n--\nnet/xfrm/Kconfig-60-\nnet/xfrm/Kconfig:61:config XFRM_MIGRATE\nnet/xfrm/Kconfig-62-\tbool \"Transformation migrate database\"\n"
}

13/2 2026/07/06 15:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16014
							output: 34
							thoughts: 25
						

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

**XFRM_MIGRATE Dependency Investigation**

Okay, so I need to figure out the dependencies related to `XFRM_MIGRATE`. My initial instinct is to dig into the kernel configuration files, specifically `net/xfrm/Kconfig`. That's where I'd expect to find the dependencies, if any, that govern whether `XFRM_MIGRATE` is even *enabled* or *selectable* during the kernel build process. I want to quickly understand if it relies on other XFRM features, networking subsystems, or even hardware capabilities. Time to go spelunking in that Kconfig file. Let's see what secrets it holds.



14/2 2026/07/06 15:19 tool read-file
0m Args:
{
  "File": "net/xfrm/Kconfig",
  "FirstLine": 50,
  "LineCount": 20
}

Results:
{
  "Contents": "  50:\t\n  51:\tconfig XFRM_SUB_POLICY\n  52:\t\tbool \"Transformation sub policy support\"\n  53:\t\tdepends on XFRM\n  54:\t\thelp\n  55:\t\t  Support sub policy for developers. By using sub policy with main\n  56:\t\t  one, two policies can be applied to the same packet at once.\n  57:\t\t  Policy which lives shorter time in kernel should be a sub.\n  58:\t\n  59:\t\t  If unsure, say N.\n  60:\t\n  61:\tconfig XFRM_MIGRATE\n  62:\t\tbool \"Transformation migrate database\"\n  63:\t\tdepends on XFRM\n  64:\t\thelp\n  65:\t\t  A feature to update locator(s) of a given IPsec security\n  66:\t\t  association dynamically.  This feature is required, for\n  67:\t\t  instance, in a Mobile IPv6 environment with IPsec configuration\n  68:\t\t  where mobile nodes change their attachment point to the Internet.\n  69:\t\n"
}

15/2 2026/07/06 15:19 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16502
							output: 34
							thoughts: 58
						

Thoughts:
Okay, let's break this down.

**Dependency Analysis: XFRM Modules**

Alright, I need to figure out these kernel module dependencies. It looks like `XFRM_MIGRATE` absolutely relies on the base `XFRM` module. Makes sense; migration likely involves existing XFRM infrastructure. And then we have `XFRM_USER`, which seems to be sitting on top of both `INET` and `XFRM`. That signals a user-space interface, probably to interact with XFRM via sockets. Makes total sense, and this is standard. I'll need to confirm that understanding.

To get the full picture and confirm those dependencies, I should go straight to the source. The `net/xfrm/Kconfig` file should contain the definitive declaration of `XFRM_USER` and its dependencies. Let's go check that out.



16/2 2026/07/06 15:20 tool read-file
0m Args:
{
  "File": "net/xfrm/Kconfig",
  "FirstLine": 15,
  "LineCount": 20
}

Results:
{
  "Contents": "  15:\t\ttristate\n  16:\t\tselect XFRM\n  17:\t\tselect CRYPTO\n  18:\t\tselect CRYPTO_AEAD\n  19:\t\tselect CRYPTO_HASH\n  20:\t\tselect CRYPTO_SKCIPHER\n  21:\t\n  22:\tif INET\n  23:\tconfig XFRM_USER\n  24:\t\ttristate \"Transformation user configuration interface\"\n  25:\t\tselect XFRM_ALGO\n  26:\t\thelp\n  27:\t\t  Support for Transformation(XFRM) user configuration interface\n  28:\t\t  like IPsec used by native Linux tools.\n  29:\t\n  30:\t\t  If unsure, say Y.\n  31:\t\n  32:\tconfig XFRM_USER_COMPAT\n  33:\t\ttristate \"Compatible ABI support\"\n  34:\t\tdepends on XFRM_USER \u0026\u0026 COMPAT_FOR_U64_ALIGNMENT \u0026\u0026 \\\n"
}

17/2 2026/07/06 15:20 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17037
							output: 197
							thoughts: 477
						

Thoughts:
**My Analysis of XFRM Configuration and Function Dependencies**

Okay, so I'm digging into this XFRM code. It looks like the key configurations I need to keep in mind are `XFRM_USER` and `XFRM_MIGRATE`. I'm starting by looking at `net/ipv6/xfrm6_input.c`, and, as expected, it requires `IPV6`.

I'm going to document the configurations I've identified:

*   `XFRM_USER`
*   `XFRM_MIGRATE`
*   `IPV6`

Now, let's get into the functions. I need to focus on these symbols:

*   `xfrm_state_lookup_exact`
*   `xfrm_state_lookup_byaddr`
*   `xfrm_user_state_lookup`
*   `xfrm_get_ae`
*   `xfrm_new_ae`
*   `xfrm_add_sa_expire`
*   `xfrm_do_migrate_state`
*   `xfrm_state_update`
*   `xfrm_state_migrate_install`

I'm going to double-check that `xfrm_state_lookup_exact` is exported and accessible. Ah, yep, it's called from `xfrm_user.c`, so that's good. And `xfrm_state_lookup_byaddr`... yes, it's used in both `xfrm_user.c` and `xfrm6_input.c`.

Let me see if any other functions have been modified or are relevant. Okay, I see a handful of static functions, let's take a look:

*   `xfrm_state_mark_matches` (static)
*   `__xfrm_state_lookup` (static)
*   `__xfrm_state_lookup_exact` (static)
*   `__xfrm_state_lookup_byaddr` (static)
*   `__xfrm_state_locate` (static)
*   `__xfrm_state_locate_exact` (static)

I'm going to include the exported and non-static functions, as well as the important static ones, I can always come back to it.

The final list will be:

*   `xfrm_state_lookup_exact`
*   `xfrm_state_lookup_byaddr`
*   `xfrm_user_state_lookup`
*   `xfrm_do_migrate_state`
*   `xfrm_state_update`
*   `xfrm_state_migrate_install`
*   `xfrm_get_ae`
*   `xfrm_new_ae`
*   `xfrm_add_sa_expire`

Now, time to call set-results.



18/2 2026/07/06 15:20 tool set-results
0m Args:
{
  "EnableConfigs": [
    "XFRM_USER",
    "XFRM_MIGRATE",
    "IPV6"
  ],
  "FocusSymbols": [
    "xfrm_state_lookup_exact",
    "xfrm_state_lookup_byaddr",
    "xfrm_user_state_lookup",
    "xfrm_do_migrate_state",
    "xfrm_state_update",
    "xfrm_state_migrate_install",
    "xfrm_get_ae",
    "xfrm_new_ae",
    "xfrm_add_sa_expire"
  ],
  "Reasoning": "The patch modifies the XFRM (IPsec) state lookup logic to allow exact matching of marks and masks during control plane operations (GET, DELETE, MIGRATE). This is reachable from user space via netlink messages and affects core IPsec functionality.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "XFRM_USER",
    "XFRM_MIGRATE",
    "IPV6"
  ],
  "FocusSymbols": [
    "xfrm_state_lookup_exact",
    "xfrm_state_lookup_byaddr",
    "xfrm_user_state_lookup",
    "xfrm_do_migrate_state",
    "xfrm_state_update",
    "xfrm_state_migrate_install",
    "xfrm_get_ae",
    "xfrm_new_ae",
    "xfrm_add_sa_expire"
  ],
  "Reasoning": "The patch modifies the XFRM (IPsec) state lookup logic to allow exact matching of marks and masks during control plane operations (GET, DELETE, MIGRATE). This is reachable from user space via netlink messages and affects core IPsec functionality.",
  "WorthFuzzing": true
}

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