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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.