ipsec-2026-07-10

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEH7ZpcWbFyOOp6OJbrB3Eaf9PW7cFAmpQtBkACgkQrB3Eaf9P
 W7fEwQ//ZGlVHLizm9aK0bcoIGOuUBCASQOmtk19n5vYP97612LbMeaQD9jEBCd2
 c/gLK2P9hn9dnH25lvOXPwrWG+gje7lpPypeTjvzvW/andg/2DWGMvmhSv1M8a5q
 J6NUKRsCJ2jwwkpILw6ziD+xskEh9t2hxXSc1Xk6K0W5MHJlx07ieoMrL/r/HbvJ
 lDiHfg87Hlcp8K6W/aecN8NcliXEuo3KTSYCsBZM48R+Oar3VpqMC2QiLxvYZDqQ
 IEVshugVQ8FQCgSp37hDxQLqsGdAfzAcjE8SjAGPJk12IODO8MKLsSsDJWt3MX4a
 sjbIiQjEr0nDxtnwtPOBYN5ko5cpeU/A0mvAIHCTcJ36SFucii32i5hBABRV/WKh
 gKAppFmXb4TL63heupYff3jlSWRSZKvlXc4R4ybMcIHHXVslK/9DGheEAYvVxTgh
 NlV6wmj89cx/Xvzk/ODS4TlsnypIjcE+TINA5lGscRSNwtWYAeqBOxCK8fkSabka
 aau0PTkoGaLHQWp2HwjIiJFrat/ZMJfsHWUvISeaKQ5ZJFtq++rE0alkgMpnfVmp
 EOtvMdxrxYMPmeDVewH2XQseYWLFgdGzAf3i7MG1dHxEunqaoFCxSqzSnrKuFZLA
 tbkHCjm2m2/oW3A0FdqmIIKrMLIDoBYjsEG5PXVYNqOnKpx/PEw=
 =k1zg
 -----END PGP SIGNATURE-----

Merge tag 'ipsec-2026-07-10' of git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec

Steffen Klassert says:

====================
pull request (net): ipsec 2026-07-10

1) xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
   Return -EINPROGRESS from xfrm_output_one when validate_xmit_xfrm
   requeues the packet asynchronously, so the caller doesn't treat it
   as a real error and free the skb.

2) xfrm: fix stale skb->prev after async crypto steals a GSO segment
   Re-derive skb->prev from the fragment list after async crypto splits
   a GSO skb, keeping the linked-list pointers validi.

3) xfrm: nat_keepalive: avoid double free on send error
   Hold a state ref while the nat_keepalive timer is active and drop the
   timer before freeing the state, preventing a re-entered free on send
   error.

4) xfrm: fix sk_dst_cache double-free in xfrm_user_policy()
   Null the skb dst cache before freeing the policy so a later skb
   destructor doesn't double-free it.

5) xfrm: cache the offload ifindex for netlink dumps
   Cache the device ifindex at state-add time and use it for netlink
   dumps instead of dereferencing dst->dev, which may have changed by
   the time the dump runs.

6) xfrm: reject optional IPTFS templates in outbound policies
   Reject outbound policies with an optional IPTFS template,
   IPTFS must always be used if configured.

7) xfrm: clear mode callbacks after failed mode setup
   Clear the mode->init_flags and init_state callbacks on the error path
   after xfrm_init_mode fails, so a partially-initialised mode isn't
   reused in xfrm_state_construct.

8) xfrm: iptfs: propagate SKBFL_SHARED_FRAG in iptfs_skb_add_frags()
   Propagate SKBFL_SHARED_FRAG from the original skb to fragments
   allocated by iptfs_skb_add_frags, keeping shared-fragment accounting
   correct after IPTFS reassembly.

9) xfrm6: clear dst.dev on error to avoid double netdev_put in xfrm6_fill_dst()
   Clear dst->dev on the error path of xfrm6_fill_dst() so the caller
   doesn't release the netdev reference twice via dst_release.

10) xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
    Preallocate all inexact hash bins before existing entries are
    reinserted during xfrm_hash_rebuild, so reinsertion always hits an
    existing bin.

Please pull or let me know if there are problems.

ipsec-2026-07-10

* tag 'ipsec-2026-07-10' of git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec:
  xfrm: policy: preallocate inexact bins before xfrm_hash_rebuild reinsert
  xfrm6: clear dst.dev on error to avoid double netdev_put in xfrm6_fill_dst()
  xfrm: iptfs: propagate SKBFL_SHARED_FRAG in iptfs_skb_add_frags()
  xfrm: clear mode callbacks after failed mode setup
  xfrm: reject optional IPTFS templates in outbound policies
  xfrm: cache the offload ifindex for netlink dumps
  xfrm: fix sk_dst_cache double-free in xfrm_user_policy()
  xfrm: nat_keepalive: avoid double free on send error
  xfrm: fix stale skb->prev after async crypto steals a GSO segment
  xfrm: propagate -EINPROGRESS from validate_xmit_xfrm()
====================

Link: https://patch.msgid.link/20260710090349.343389-1-steffen.klassert@secunet.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-07-11 12:48:08 +02:00
commit 389704eb51
9 changed files with 73 additions and 26 deletions

View File

@ -162,6 +162,8 @@ struct xfrm_dev_offload {
*/
struct net_device *real_dev;
unsigned long offload_handle;
/* Snapshot the attached device index for dump paths. */
int ifindex;
u8 dir : 2;
u8 type : 2;
u8 flags : 2;

View File

@ -4018,6 +4018,9 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
return NULL;
}
/* Returns the skb on success, NULL if dropped, or ERR_PTR(-EINPROGRESS)
* if stolen by async xfrm crypto (delivered via xfrm_dev_resume()).
*/
static struct sk_buff *validate_xmit_skb(struct sk_buff *skb, struct net_device *dev, bool *again)
{
netdev_features_t features;
@ -4089,7 +4092,7 @@ struct sk_buff *validate_xmit_skb_list(struct sk_buff *skb, struct net_device *d
skb->prev = skb;
skb = validate_xmit_skb(skb, dev, again);
if (!skb)
if (IS_ERR_OR_NULL(skb))
continue;
if (!head)
@ -4860,8 +4863,11 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
goto recursion_alert;
skb = validate_xmit_skb(skb, dev, &again);
if (!skb)
if (IS_ERR_OR_NULL(skb)) {
if (PTR_ERR(skb) == -EINPROGRESS)
rc = NET_XMIT_SUCCESS;
goto out;
}
HARD_TX_LOCK(dev, txq, cpu);

View File

@ -88,6 +88,7 @@ static int xfrm6_fill_dst(struct xfrm_dst *xdst, struct net_device *dev,
xdst->u.rt6.rt6i_idev = in6_dev_get(dev);
if (!xdst->u.rt6.rt6i_idev) {
netdev_put(dev, &xdst->u.dst.dev_tracker);
xdst->u.dst.dev = NULL;
return -ENODEV;
}

View File

@ -182,7 +182,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
err = x->type_offload->xmit(x, skb, esp_features);
if (err) {
if (err == -EINPROGRESS)
return NULL;
return ERR_PTR(-EINPROGRESS);
XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR);
kfree_skb(skb);
@ -224,7 +224,15 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
pskb = skb2;
}
return skb;
/* skb_gso_segment() set skb->prev to the last segment, but async
* crypto may have stolen it above without updating ->prev. Repoint
* it at the last retained segment so validate_xmit_skb_list() does
* not chain onto a segment now owned by the crypto engine.
*/
if (skb)
skb->prev = pskb;
return skb ? skb : ERR_PTR(-EINPROGRESS);
}
EXPORT_SYMBOL_GPL(validate_xmit_xfrm);
@ -313,6 +321,7 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
}
xso->dev = dev;
xso->ifindex = dev->ifindex;
netdev_tracker_alloc(dev, &xso->dev_tracker, GFP_ATOMIC);
if (xuo->flags & XFRM_OFFLOAD_INBOUND)

View File

@ -480,6 +480,7 @@ static int iptfs_skb_add_frags(struct sk_buff *skb,
}
__skb_frag_ref(tofrag);
shinfo->nr_frags++;
shinfo->flags |= SKBFL_SHARED_FRAG;
/* see if we are done */
fraglen = tofrag->len;

View File

@ -55,8 +55,10 @@ static int nat_keepalive_send_ipv4(struct sk_buff *skb,
ka->encap_sport, sock_net_uid(net, NULL));
rt = ip_route_output_key(net, &fl4);
if (IS_ERR(rt))
if (IS_ERR(rt)) {
kfree_skb(skb);
return PTR_ERR(rt);
}
skb_dst_set(skb, &rt->dst);
@ -101,6 +103,7 @@ static int nat_keepalive_send_ipv6(struct sk_buff *skb,
dst = ip6_dst_lookup_flow(net, sk, &fl6, NULL);
if (IS_ERR(dst)) {
local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock);
kfree_skb(skb);
return PTR_ERR(dst);
}
@ -118,7 +121,6 @@ static void nat_keepalive_send(struct nat_keepalive *ka)
sizeof(struct ipv6hdr)) +
sizeof(struct udphdr);
const u8 nat_ka_payload = 0xFF;
int err = -EAFNOSUPPORT;
struct sk_buff *skb;
struct udphdr *uh;
@ -140,16 +142,17 @@ static void nat_keepalive_send(struct nat_keepalive *ka)
switch (ka->family) {
case AF_INET:
err = nat_keepalive_send_ipv4(skb, ka);
nat_keepalive_send_ipv4(skb, ka);
break;
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6:
err = nat_keepalive_send_ipv6(skb, ka, uh);
nat_keepalive_send_ipv6(skb, ka, uh);
break;
#endif
}
if (err)
default:
kfree_skb(skb);
break;
}
}
struct nat_keepalive_work_ctx {

View File

@ -1329,8 +1329,8 @@ static void xfrm_hash_rebuild(struct work_struct *work)
}
}
if (policy->selector.prefixlen_d < dbits ||
policy->selector.prefixlen_s < sbits)
if (policy->selector.prefixlen_d >= dbits &&
policy->selector.prefixlen_s >= sbits)
continue;
bin = xfrm_policy_inexact_alloc_bin(policy, dir);

View File

@ -1547,6 +1547,7 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr,
xso->type = XFRM_DEV_OFFLOAD_PACKET;
xso->dir = xdo->dir;
xso->dev = dev;
xso->ifindex = dev->ifindex;
xso->flags = XFRM_DEV_OFFLOAD_FLAG_ACQ;
netdev_hold(dev, &xso->dev_tracker, GFP_ATOMIC);
error = dev->xfrmdev_ops->xdo_dev_state_add(dev, x,
@ -2071,8 +2072,11 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig,
x->mode_cbs = orig->mode_cbs;
if (x->mode_cbs && x->mode_cbs->clone_state) {
if (x->mode_cbs->clone_state(x, orig))
if (x->mode_cbs->clone_state(x, orig)) {
if (!x->mode_data)
x->mode_cbs = NULL;
goto error;
}
}
x->props.reqid = m->new_reqid;
@ -3010,7 +3014,7 @@ int xfrm_user_policy(struct sock *sk, int optname, sockptr_t optval, int optlen)
if (sockptr_is_null(optval) && !optlen) {
xfrm_sk_policy_insert(sk, XFRM_POLICY_IN, NULL);
xfrm_sk_policy_insert(sk, XFRM_POLICY_OUT, NULL);
__sk_dst_reset(sk);
sk_dst_reset(sk);
return 0;
}
@ -3050,7 +3054,7 @@ int xfrm_user_policy(struct sock *sk, int optname, sockptr_t optval, int optlen)
if (err >= 0) {
xfrm_sk_policy_insert(sk, err, pol);
xfrm_pol_put(pol);
__sk_dst_reset(sk);
sk_dst_reset(sk);
err = 0;
}
@ -3291,6 +3295,8 @@ int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack)
if (x->mode_cbs->init_state)
err = x->mode_cbs->init_state(x);
module_put(x->mode_cbs->owner);
if (err && !x->mode_data)
x->mode_cbs = NULL;
}
error:
return err;

View File

@ -1201,17 +1201,26 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb)
return 0;
}
static void xso_to_xuo(const struct xfrm_dev_offload *xso,
struct xfrm_user_offload *xuo)
static void xso_to_xuo_ifindex(const struct xfrm_dev_offload *xso, int ifindex,
struct xfrm_user_offload *xuo)
{
xuo->ifindex = xso->dev->ifindex;
xuo->ifindex = ifindex;
if (xso->dir == XFRM_DEV_OFFLOAD_IN)
xuo->flags = XFRM_OFFLOAD_INBOUND;
if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
xuo->flags |= XFRM_OFFLOAD_PACKET;
}
static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
#ifdef CONFIG_XFRM_MIGRATE
static void xso_to_xuo(const struct xfrm_dev_offload *xso,
struct xfrm_user_offload *xuo)
{
xso_to_xuo_ifindex(xso, xso->dev->ifindex, xuo);
}
#endif
static int copy_user_offload_ifindex(const struct xfrm_dev_offload *xso,
int ifindex, struct sk_buff *skb)
{
struct xfrm_user_offload *xuo;
struct nlattr *attr;
@ -1222,11 +1231,22 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
xuo = nla_data(attr);
memset(xuo, 0, sizeof(*xuo));
xso_to_xuo(xso, xuo);
xso_to_xuo_ifindex(xso, ifindex, xuo);
return 0;
}
static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
{
return copy_user_offload_ifindex(xso, xso->dev->ifindex, skb);
}
static int copy_user_state_offload(const struct xfrm_dev_offload *xso,
struct sk_buff *skb)
{
return copy_user_offload_ifindex(xso, READ_ONCE(xso->ifindex), skb);
}
static bool xfrm_redact(void)
{
return IS_ENABLED(CONFIG_SECURITY) &&
@ -1433,8 +1453,8 @@ static int copy_to_user_state_extra(struct xfrm_state *x,
&x->replay);
if (ret)
goto out;
if(x->xso.dev)
ret = copy_user_offload(&x->xso, skb);
if (READ_ONCE(x->xso.dev))
ret = copy_user_state_offload(&x->xso, skb);
if (ret)
goto out;
if (x->if_id) {
@ -2104,13 +2124,12 @@ static int validate_tmpl(int nr, struct xfrm_user_tmpl *ut, u16 family,
switch (ut[i].mode) {
case XFRM_MODE_TUNNEL:
case XFRM_MODE_BEET:
case XFRM_MODE_IPTFS:
if (ut[i].optional && dir == XFRM_POLICY_OUT) {
NL_SET_ERR_MSG(extack, "Mode in optional template not allowed in outbound policy");
return -EINVAL;
}
break;
case XFRM_MODE_IPTFS:
break;
default:
if (ut[i].family != prev_family) {
NL_SET_ERR_MSG(extack, "Mode in template doesn't support a family change");
@ -4046,8 +4065,8 @@ static inline unsigned int xfrm_sa_len(struct xfrm_state *x)
l += nla_total_size(sizeof(*x->coaddr));
if (x->props.extra_flags)
l += nla_total_size(sizeof(x->props.extra_flags));
if (x->xso.dev)
l += nla_total_size(sizeof(struct xfrm_user_offload));
if (READ_ONCE(x->xso.dev))
l += nla_total_size(sizeof(struct xfrm_user_offload));
if (x->props.smark.v | x->props.smark.m) {
l += nla_total_size(sizeof(x->props.smark.v));
l += nla_total_size(sizeof(x->props.smark.m));