Per RFC 4301 section 4.4.2.1, the SPI is selected by the receiving end, which is interpreted as making SPI uniqueness an inbound-only requirement. Commit 94f39804d891 ("xfrm: Duplicate SPI Handling") introduced xfrm_state_lookup_spi_proto() to fix duplicate SPI allocation for inbound SAs with different destination addresses. However, it enforces global uniqueness by (spi, proto) across all states regardless of direction, which causes SPI allocation to fail for outbound SAs when the same (spi, proto) is already in use by an inbound SA. When x->dir == XFRM_DIR_IN, enforce SPI uniqueness via xfrm_state_lookup_spi_proto() scoped to inbound SAs. SAs created via PF_KEY, without direction, or with XFRM_DIR_OUT restore the pre 94f39804d891, RFC 2401 lookup by (daddr, spi, proto). Reported-by: Yan Yan Fixes: 94f39804d891 ("xfrm: Duplicate SPI Handling") Signed-off-by: Antony Antony --- net/xfrm/xfrm_state.c | 16 ++++++++++++++-- net/xfrm/xfrm_user.c | 6 +++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 1748d374abca..b1ec95141512 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -1698,15 +1698,21 @@ struct xfrm_state *xfrm_state_lookup_byspi(struct net *net, __be32 spi, } EXPORT_SYMBOL(xfrm_state_lookup_byspi); -static struct xfrm_state *xfrm_state_lookup_spi_proto(struct net *net, __be32 spi, u8 proto) +static struct xfrm_state *xfrm_state_lookup_input_spi(struct net *net, + __be32 spi, u8 proto, + u8 dir) { struct xfrm_state *x; unsigned int i; for (i = 0; i <= net->xfrm.state_hmask; i++) { hlist_for_each_entry(x, xfrm_state_deref_prot(net->xfrm.state_byspi, net) + i, byspi) { + if (x->dir != dir) + continue; + if (x->id.spi == spi && x->id.proto == proto) return x; + } } return NULL; @@ -2578,6 +2584,7 @@ int xfrm_alloc_spi(struct xfrm_state *x, u32 low, u32 high, struct xfrm_state *x0; int err = -ENOENT; u32 range = high - low + 1; + u32 mark = x->mark.v & x->mark.m; __be32 newspi = 0; spin_lock_bh(&x->lock); @@ -2599,7 +2606,12 @@ int xfrm_alloc_spi(struct xfrm_state *x, u32 low, u32 high, newspi = htonl(spi); spin_lock_bh(&net->xfrm.xfrm_state_lock); - x0 = xfrm_state_lookup_spi_proto(net, newspi, x->id.proto); + if (x->dir == XFRM_SA_DIR_IN) + x0 = xfrm_state_lookup_input_spi(net, newspi, + x->id.proto, x->dir); + else + x0 = xfrm_state_lookup(net, mark, &x->id.daddr, newspi, + x->id.proto, x->props.family); if (!x0) { x->id.spi = newspi; h = xfrm_spi_hash(net, &x->id.daddr, newspi, x->id.proto, x->props.family); diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index d56450f61669..f9db2d2c392b 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1883,13 +1883,13 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh, goto out_noput; } + if (attrs[XFRMA_SA_DIR]) + x->dir = nla_get_u8(attrs[XFRMA_SA_DIR]); + err = xfrm_alloc_spi(x, p->min, p->max, extack); if (err) goto out; - if (attrs[XFRMA_SA_DIR]) - x->dir = nla_get_u8(attrs[XFRMA_SA_DIR]); - resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq); if (IS_ERR(resp_skb)) { err = PTR_ERR(resp_skb); --- base-commit: 426c355742f02cf743b347d9d7dbdc1bfbfa31ef change-id: 20260330-alloc-spi-dir-3b6e2f4b34e9 Best regards, -- Antony Antony