xfrm: hold net_device reference under RCU in bundle creation

xfrm_bundle_create() and xfrm_create_dummy_bundle() read dst->dev into
a local pointer without taking a device reference, then pass it to
xfrm_fill_dst(). A concurrent RTM_DELLINK replaces dst->dev via
dst_dev_put() and frees the old net_device, causing a use-after-free
when xfrm6_fill_dst() later dereferences the stale dev pointer.

  BUG: KASAN: slab-use-after-free in xfrm6_fill_dst+0x82c/0x860
  (net/ipv6/xfrm6_policy.c:86 netdev_hold())
  Read of size 8 at addr ffff8880142fe588 by task exploit/153
  Call Trace:
   xfrm6_fill_dst+0x82c/0x860
   xfrm_resolve_and_create_bundle+0x21d4/0x2bd0
   xfrm_lookup_with_ifid+0x485/0x1640
   ip6_dst_lookup_flow+0x19b/0x1e0
   udpv6_sendmsg+0x1443/0x2dd0

Fix this by reading dst->dev via dst_dev_rcu() and keeping the RCU
read-side critical section active until xfrm_fill_dst() has taken the
required device references.

Fixes: 25ee3286dc ("[IPSEC]: Merge common code into xfrm_bundle_create")
Fixes: a0073fe18e ("xfrm: Add a state resolution packet queue")
Suggested-by: Steffen Klassert <steffen.klassert@secunet.com>
Reported-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Link: https://lore.kernel.org/all/20260820200245.44312-1-blbllhy@gmail.com/
Cc: AutonomousCodeSecurity@microsoft.com
Assisted-by: GitHub-Copilot:claude-opus-4.6
Signed-off-by: Cen Zhang (Microsoft Security FORGE Labs) <blbllhy@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
This commit is contained in:
Cen Zhang (Microsoft Security FORGE Labs) 2026-08-26 16:17:45 -04:00 committed by Steffen Klassert
parent f89416eb3d
commit 9fa903b24b

View File

@ -2770,9 +2770,12 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
xdst0->path = dst;
err = -ENODEV;
dev = dst->dev;
if (!dev)
rcu_read_lock();
dev = dst_dev_rcu(dst);
if (!dev) {
rcu_read_unlock();
goto free_dst;
}
xfrm_init_path(xdst0, dst, nfheader_len);
xfrm_init_pmtu(bundle, nx);
@ -2780,8 +2783,10 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
for (xdst_prev = xdst0; xdst_prev != (struct xfrm_dst *)dst;
xdst_prev = (struct xfrm_dst *) xfrm_dst_child(&xdst_prev->u.dst)) {
err = xfrm_fill_dst(xdst_prev, dev, fl);
if (err)
if (err) {
rcu_read_unlock();
goto free_dst;
}
xdst_prev->u.dst.header_len = header_len;
xdst_prev->u.dst.trailer_len = trailer_len;
@ -2789,6 +2794,7 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
trailer_len -= xdst_prev->u.dst.xfrm->props.trailer_len;
}
rcu_read_unlock();
return &xdst0->u.dst;
put_states:
@ -3058,11 +3064,15 @@ static struct xfrm_dst *xfrm_create_dummy_bundle(struct net *net,
xfrm_init_path((struct xfrm_dst *)dst1, dst, 0);
err = -ENODEV;
dev = dst->dev;
if (!dev)
rcu_read_lock();
dev = dst_dev_rcu(dst);
if (!dev) {
rcu_read_unlock();
goto free_dst;
}
err = xfrm_fill_dst(xdst, dev, fl);
rcu_read_unlock();
if (err)
goto free_dst;