From: Qihang <q.h.hack.winter@gmail.com>
To: netdev@vger.kernel.org
Cc: Steffen Klassert <steffen.klassert@secunet.com>,
Christian Hopps <chopps@labn.net>,
Herbert Xu <herbert@gondor.apana.org.au>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>, David Ahern <dsahern@kernel.org>,
Ido Schimmel <idosch@nvidia.com>,
linux-kernel@vger.kernel.org
Subject: [PATCH net v2 3/3] xfrm: transfer the skb->dev reference to the input path
Date: Tue, 13 Oct 2026 15:46:10 +0800 [thread overview]
Message-ID: <20261013074610.17808-4-q.h.hack.winter@gmail.com> (raw)
In-Reply-To: <20261013074610.17808-1-q.h.hack.winter@gmail.com>
vti_input() and vti6_input_proto() cache the tunnel pointer in the skb
control buffer without taking a reference. With async crypto the
receive callback runs from the crypto completion, after a possible
device teardown, and dereferences the freed tunnel device:
BUG: KASAN: slab-use-after-free in vti_rcv_cb
Taking a reference while the pointer is cached and dropping it in the
callback does not fix that: on the success path the callback drops what
may be the last reference before xfrm_input() hands the skb to
gro_cells_receive(), and taking a new reference on skb->dev at that
point is already too late, the device may have been freed in between:
BUG: KASAN: slab-use-after-free in xfrm_input
Make the reference travel with the skb instead: the receive callbacks
install the device in skb->dev and leave their reference with it, and
xfrm_input() releases it once the packet has been delivered. vti,
vti6 and xfrmi -- the only callbacks that replace skb->dev -- take the
reference when installing the device and drop it on their error paths.
Tested on arm64 with KASAN and an async AEAD: no use-after-free on
either completion path, and RTM_DELLINK on a VTI device with pending
async crypto now waits for the completion.
Fixes: df3893c176e9 ("vti: Update the ipv4 side to use it's own receive hook.")
Fixes: fa9ad96d4905 ("vti6: Update the ipv6 side to use its own receive hook.")
Cc: stable@vger.kernel.org
Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
---
net/ipv4/ip_vti.c | 6 ++++++
net/ipv6/ip6_vti.c | 7 +++++++
net/xfrm/xfrm_input.c | 13 +++++++++++++
net/xfrm/xfrm_interface_core.c | 9 ++++++++-
4 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
index 3b80929994a0..1964482064c1 100644
--- a/net/ipv4/ip_vti.c
+++ b/net/ipv4/ip_vti.c
@@ -63,6 +63,8 @@ static int vti_input(struct sk_buff *skb, int nexthdr, __be32 spi,
XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = tunnel;
+ dev_hold(tunnel->dev);
+
if (update_skb_dev)
skb->dev = tunnel->dev;
@@ -109,10 +111,14 @@ static int vti_rcv_cb(struct sk_buff *skb, int err)
dev = tunnel->dev;
+ /* Drop the reference taken in vti_input(); on success it is
+ * transferred with skb->dev and released by xfrm_input().
+ */
if (err) {
DEV_STATS_INC(dev, rx_errors);
DEV_STATS_INC(dev, rx_dropped);
+ dev_put(dev);
return 0;
}
diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c
index ab94b3a4ba9c..a47acae4756c 100644
--- a/net/ipv6/ip6_vti.c
+++ b/net/ipv6/ip6_vti.c
@@ -322,6 +322,9 @@ static int vti6_input_proto(struct sk_buff *skb, int nexthdr, __be32 spi,
rcu_read_unlock();
XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = t;
+
+ dev_hold(t->dev);
+
XFRM_SPI_SKB_CB(skb)->family = AF_INET6;
XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct ipv6hdr, daddr);
return xfrm_input(skb, nexthdr, spi, encap_type);
@@ -355,10 +358,14 @@ static int vti6_rcv_cb(struct sk_buff *skb, int err)
dev = t->dev;
+ /* Drop the reference taken in vti6_input_proto(); on success it is
+ * transferred with skb->dev and released by xfrm_input().
+ */
if (err) {
DEV_STATS_INC(dev, rx_errors);
DEV_STATS_INC(dev, rx_dropped);
+ dev_put(dev);
return 0;
}
diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index f293d5e124d4..bdd493f9d877 100644
--- a/net/xfrm/xfrm_input.c
+++ b/net/xfrm/xfrm_input.c
@@ -487,6 +487,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
const struct xfrm_state_afinfo *afinfo;
struct net *net = dev_net(skb->dev);
struct net_device *dev = skb->dev;
+ struct net_device *tdev;
int err;
__be32 seq;
__be32 seq_hi;
@@ -767,7 +768,13 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
skb_dst_drop(skb);
if (async)
dev_put(dev);
+ /* Release the reference a receive callback left with
+ * skb->dev, after the packet has been delivered.
+ */
+ tdev = skb->dev != dev ? skb->dev : NULL;
gro_cells_receive(&gro_cells, skb);
+ if (tdev)
+ dev_put(tdev);
rcu_read_unlock();
return 0;
} else {
@@ -775,6 +782,10 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
if (xo)
xfrm_gro = xo->flags & XFRM_GRO;
+ /* Release the reference a receive callback left with
+ * skb->dev, after the packet has been delivered.
+ */
+ tdev = skb->dev != dev ? skb->dev : NULL;
err = -EAFNOSUPPORT;
afinfo = xfrm_state_afinfo_get_rcu(family);
if (likely(afinfo))
@@ -788,6 +799,8 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
gro_cells_receive(&gro_cells, skb);
}
+ if (tdev)
+ dev_put(tdev);
if (async)
dev_put(dev);
rcu_read_unlock();
diff --git a/net/xfrm/xfrm_interface_core.c b/net/xfrm/xfrm_interface_core.c
index 688306bf62c5..f4859be7ed18 100644
--- a/net/xfrm/xfrm_interface_core.c
+++ b/net/xfrm/xfrm_interface_core.c
@@ -375,11 +375,18 @@ static int xfrmi_rcv_cb(struct sk_buff *skb, int err)
link = skb->dev->ifindex;
dev = xi->dev;
- skb->dev = dev;
+ if (skb->dev != dev) {
+ /* Reference travels with skb->dev; released by xfrm_input()
+ * after delivery. Guard makes a re-invocation a no-op.
+ */
+ dev_hold(dev);
+ skb->dev = dev;
+ }
if (err) {
DEV_STATS_INC(dev, rx_errors);
DEV_STATS_INC(dev, rx_dropped);
+ dev_put(dev);
return 0;
}
--
2.54.0 (Apple Git-157)
next prev parent reply other threads:[~2026-10-13 7:46 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-13 7:46 [PATCH net v2 0/3] xfrm: fix vti tunnel device use-after-free across async crypto resumption Qihang
2026-10-13 7:46 ` [PATCH net v2 1/3] xfrm: set the address family before the early invalid-state drop Qihang
2026-10-13 7:46 ` [PATCH net v2 2/3] xfrm: iptfs: run the receive callbacks before freeing the skb Qihang
2026-10-13 7:46 ` Qihang [this message]
2026-10-13 7:49 ` [PATCH net v2 0/3] xfrm: fix vti tunnel device use-after-free across async crypto resumption netdev-bot+sinfo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261013074610.17808-4-q.h.hack.winter@gmail.com \
--to=q.h.hack.winter@gmail.com \
--cc=chopps@labn.net \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@kernel.org \
--cc=herbert@gondor.apana.org.au \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=steffen.klassert@secunet.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).