[PATCH net v2 2/3] xfrm: iptfs: run the receive callbacks before freeing the skb

From: Qihang

Date: Tue Oct 13 2026 - 03:51:43 EST


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@xxxxxxxxxxxxxxx
Signed-off-by: Qihang <q.h.hack.winter@xxxxxxxxx>
---
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)