Netdev List
 help / color / mirror / Atom feed
* [PATCH net v2 0/3] xfrm: fix vti tunnel device use-after-free across async crypto resumption
@ 2026-10-13  7:46 Qihang
  2026-10-13  7:46 ` [PATCH net v2 1/3] xfrm: set the address family before the early invalid-state drop Qihang
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Qihang @ 2026-10-13  7:46 UTC (permalink / raw)
  To: netdev
  Cc: Steffen Klassert, Christian Hopps, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Ido Schimmel, linux-kernel

This replaces the v1 vti/vti6 series.

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.

The reference cannot simply be held in the input path and dropped in
the callback: the callback is not reached on every path (IPTFS frees
the outer skb itself, and the early drop for a non-valid state runs
with family still AF_UNSPEC), and on the success path the callback may
drop the last reference before xfrm_input() is done with skb->dev.

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.

Patch 3 covers vti, vti6 and xfrmi, the only receive callbacks that
replace skb->dev (esp, ah and ipcomp do not).

For Chris: patch 2 covers the outer-skb free paths in
__input_process_payload(), iptfs_input_ordered() and the reorder-window
free list.  The deferred fragment (consume_skb(defer)), the reassembly
drop timer / __iptfs_reassem_done() and the state teardown still free
the skb directly; I left those alone for now.

Changes since v1:
- split into three patches: the first two fix the paths where the
  receive callback is not dispatched at all, the third is the device
  reference handling
- the reference is taken before delivery and released through a saved
  pointer afterwards, instead of holding skb->dev after the callback
- v1: https://lore.kernel.org/netdev/20260930090813.73901-1-q.h.hack.winter@gmail.com/

Qihang (3):
  xfrm: set the address family before the early invalid-state drop
  xfrm: iptfs: run the receive callbacks before freeing the skb
  xfrm: transfer the skb->dev reference to the input path

 net/ipv4/ip_vti.c              |  6 ++++++
 net/ipv6/ip6_vti.c             |  7 +++++++
 net/xfrm/xfrm_inout.h          |  2 ++
 net/xfrm/xfrm_input.c          | 35 ++++++++++++++++++++++++++++++++--
 net/xfrm/xfrm_interface_core.c |  9 ++++++++-
 net/xfrm/xfrm_iptfs.c          |  6 +++---
 6 files changed, 59 insertions(+), 6 deletions(-)

-- 
2.54.0 (Apple Git-157)


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net v2 1/3] xfrm: set the address family before the early invalid-state drop
  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 ` Qihang
  2026-10-13  7:46 ` [PATCH net v2 2/3] xfrm: iptfs: run the receive callbacks before freeing the skb Qihang
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Qihang @ 2026-10-13  7:46 UTC (permalink / raw)
  To: netdev
  Cc: Steffen Klassert, Christian Hopps, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Ido Schimmel, linux-kernel

The early drop in xfrm_input() for a state that is no longer valid runs
while family is still AF_UNSPEC, so xfrm_rcv_cb() cannot find the
afinfo and the receive callback is never invoked for such a packet.
Set the family from the state before the check so that the drop path
dispatches the callback like every other drop does.

Fixes: 5f24f41e8ea6 ("xfrm: Remove inner/outer modes from input path")
Cc: stable@vger.kernel.org
Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
---
 net/xfrm/xfrm_input.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index 5ed87d51392a..ddf8fd75fd20 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)
 				      encap_type == UDP_ENCAP_ESPINUDP))) {
 		x = xfrm_input_state(skb);
 		xfrm_proto = x->type ? x->type->proto : nexthdr;
+		family = x->props.family;
 
 		if (unlikely(x->km.state != XFRM_STATE_VALID)) {
 			if (x->km.state == XFRM_STATE_ACQ)
@@ -500,8 +501,6 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 			goto drop;
 		}
 
-		family = x->props.family;
-
 		/* An encap_type of -2 indicates reconstructed inner packet */
 		if (encap_type == -2)
 			goto resume_decapped;
-- 
2.54.0 (Apple Git-157)


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH net v2 2/3] xfrm: iptfs: run the receive callbacks before freeing the skb
  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 ` Qihang
  2026-10-13  7:46 ` [PATCH net v2 3/3] xfrm: transfer the skb->dev reference to the input path Qihang
  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
  3 siblings, 0 replies; 5+ messages in thread
From: Qihang @ 2026-10-13  7:46 UTC (permalink / raw)
  To: netdev
  Cc: Steffen Klassert, Christian Hopps, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Ido Schimmel, linux-kernel

IPTFS consumes the outer skb itself and frees it directly on several
paths, so the receive callbacks registered for the protocol never run
for those packets and whatever they cached in the skb control buffer is
never cleaned up.  Add xfrm_input_drop(), which dispatches the receive
callback with err = -1 before freeing the skb, and use it for the
outer-skb free paths in __input_process_payload(),
iptfs_input_ordered() and the reorder-window free list of iptfs_input().

Fixes: 4b3faf610cc6 ("xfrm: iptfs: add new iptfs xfrm mode impl")
Cc: stable@vger.kernel.org
Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
---
 net/xfrm/xfrm_inout.h |  2 ++
 net/xfrm/xfrm_input.c | 19 +++++++++++++++++++
 net/xfrm/xfrm_iptfs.c |  6 +++---
 3 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/net/xfrm/xfrm_inout.h b/net/xfrm/xfrm_inout.h
index efc5e6b2e87b..be1f5e569ae7 100644
--- a/net/xfrm/xfrm_inout.h
+++ b/net/xfrm/xfrm_inout.h
@@ -6,6 +6,8 @@
 #ifndef XFRM_INOUT_H
 #define XFRM_INOUT_H 1
 
+void xfrm_input_drop(struct sk_buff *skb);
+
 static inline void xfrm4_extract_header(struct sk_buff *skb)
 {
 	const struct iphdr *iph = ip_hdr(skb);
diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index ddf8fd75fd20..f293d5e124d4 100644
--- a/net/xfrm/xfrm_input.c
+++ b/net/xfrm/xfrm_input.c
@@ -120,6 +120,25 @@ static int xfrm_rcv_cb(struct sk_buff *skb, unsigned int family, u8 protocol,
 	return ret;
 }
 
+/* Finish an skb that is freed outside of xfrm_input()'s own drop path, e.g.
+ * from IPTFS.  The receive callbacks must still run before the skb goes away.
+ */
+void xfrm_input_drop(struct sk_buff *skb)
+{
+	struct sec_path *sp = skb_sec_path(skb);
+	struct xfrm_state *x;
+
+	if (!sp || !sp->len) {
+		kfree_skb(skb);
+		return;
+	}
+
+	x = sp->xvec[sp->len - 1];
+	xfrm_rcv_cb(skb, x->props.family, x->type ? x->type->proto : 0, -1);
+	kfree_skb(skb);
+}
+EXPORT_SYMBOL(xfrm_input_drop);
+
 struct sec_path *secpath_set(struct sk_buff *skb)
 {
 	struct sec_path *sp, *tmp = skb_ext_find(skb, SKB_EXT_SEC_PATH);
diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c
index 6920940a35b4..4b3d04e531a5 100644
--- a/net/xfrm/xfrm_iptfs.c
+++ b/net/xfrm/xfrm_iptfs.c
@@ -1211,7 +1211,7 @@ static bool __input_process_payload(struct xfrm_state *x, u32 data,
 		 * either be save in ra_newskb, trimmed and sent on as an skb or
 		 * placed in defer to be freed.
 		 */
-		kfree_skb(skb);
+		xfrm_input_drop(skb);
 	}
 	return true;
 }
@@ -1298,7 +1298,7 @@ static void iptfs_input_ordered(struct xfrm_state *x, struct sk_buff *skb)
 	if (!consumed) {
 		skb = skbseq.root_skb;
 		skb_abort_seq_read(&skbseq);
-		kfree_skb(skb);
+		xfrm_input_drop(skb);
 	}
 }
 
@@ -1751,7 +1751,7 @@ static int iptfs_input(struct xfrm_state *x, struct sk_buff *skb)
 
 	list_for_each_entry_safe(skb, next, &freelist, list) {
 		skb_list_del_init(skb);
-		kfree_skb(skb);
+		xfrm_input_drop(skb);
 	}
 done:
 	/* We always have dealt with the input SKB, either we are re-using it,
-- 
2.54.0 (Apple Git-157)


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH net v2 3/3] xfrm: transfer the skb->dev reference to the input path
  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
  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
  3 siblings, 0 replies; 5+ messages in thread
From: Qihang @ 2026-10-13  7:46 UTC (permalink / raw)
  To: netdev
  Cc: Steffen Klassert, Christian Hopps, Herbert Xu, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	David Ahern, Ido Schimmel, linux-kernel

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)


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH net v2 0/3] xfrm: fix vti tunnel device use-after-free across async crypto resumption
  2026-10-13  7:46 [PATCH net v2 0/3] xfrm: fix vti tunnel device use-after-free across async crypto resumption Qihang
                   ` (2 preceding siblings ...)
  2026-10-13  7:46 ` [PATCH net v2 3/3] xfrm: transfer the skb->dev reference to the input path Qihang
@ 2026-10-13  7:49 ` netdev-bot+sinfo
  3 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sinfo @ 2026-10-13  7:49 UTC (permalink / raw)
  To: Qihang
  Cc: netdev, Steffen Klassert, Christian Hopps, Herbert Xu,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, David Ahern, Ido Schimmel, linux-kernel

Hi!

This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:

 - How the issue was discovered, e.g. hit in production, hit during
   development, syzbot report, manual code inspection, LLM or static
   analysis tool scan.

Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.

The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-13  7:49 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH net v2 3/3] xfrm: transfer the skb->dev reference to the input path Qihang
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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox