]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xfrm: hold dev ref until after transport_finish NF_HOOK
authorQi Tang <tpluszz77@gmail.com>
Thu, 2 Apr 2026 11:44:01 +0000 (19:44 +0800)
committerSteffen Klassert <steffen.klassert@secunet.com>
Tue, 7 Apr 2026 08:12:40 +0000 (10:12 +0200)
After async crypto completes, xfrm_input_resume() calls dev_put()
immediately on re-entry before the skb reaches transport_finish.
The skb->dev pointer is then used inside NF_HOOK and its okfn,
which can race with device teardown.

Remove the dev_put from the async resumption entry and instead
drop the reference after the NF_HOOK call in transport_finish,
using a saved device pointer since NF_HOOK may consume the skb.
This covers NF_DROP, NF_QUEUE and NF_STOLEN paths that skip
the okfn.

For non-transport exits (decaps, gro, drop) and secondary
async return points, release the reference inline when
async is set.

Suggested-by: Florian Westphal <fw@strlen.de>
Fixes: acf568ee859f ("xfrm: Reinject transport-mode packets through tasklet")
Cc: stable@vger.kernel.org
Signed-off-by: Qi Tang <tpluszz77@gmail.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
net/ipv4/xfrm4_input.c
net/ipv6/xfrm6_input.c
net/xfrm/xfrm_input.c

index f28cfd88eaf5934734105fe520e4a76c86723529..c2eac844bcdb4e25b9748eecb8bd7baada6c82b1 100644 (file)
@@ -50,6 +50,7 @@ int xfrm4_transport_finish(struct sk_buff *skb, int async)
 {
        struct xfrm_offload *xo = xfrm_offload(skb);
        struct iphdr *iph = ip_hdr(skb);
+       struct net_device *dev = skb->dev;
 
        iph->protocol = XFRM_MODE_SKB_CB(skb)->protocol;
 
@@ -73,8 +74,10 @@ int xfrm4_transport_finish(struct sk_buff *skb, int async)
        }
 
        NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
-               dev_net(skb->dev), NULL, skb, skb->dev, NULL,
+               dev_net(dev), NULL, skb, dev, NULL,
                xfrm4_rcv_encap_finish);
+       if (async)
+               dev_put(dev);
        return 0;
 }
 
index 9005fc156a20e67b65220f42cce981ae395e2293..699a001ac1662983c0c5f6d502b8841a0c431de3 100644 (file)
@@ -43,6 +43,7 @@ static int xfrm6_transport_finish2(struct net *net, struct sock *sk,
 int xfrm6_transport_finish(struct sk_buff *skb, int async)
 {
        struct xfrm_offload *xo = xfrm_offload(skb);
+       struct net_device *dev = skb->dev;
        int nhlen = -skb_network_offset(skb);
 
        skb_network_header(skb)[IP6CB(skb)->nhoff] =
@@ -68,8 +69,10 @@ int xfrm6_transport_finish(struct sk_buff *skb, int async)
        }
 
        NF_HOOK(NFPROTO_IPV6, NF_INET_PRE_ROUTING,
-               dev_net(skb->dev), NULL, skb, skb->dev, NULL,
+               dev_net(dev), NULL, skb, dev, NULL,
                xfrm6_transport_finish2);
+       if (async)
+               dev_put(dev);
        return 0;
 }
 
index dc1312ed5a095574c6d94463c8d93d52fa464091..f65291eba1f68cb1368d9f75d95705d611ebe7ec 100644 (file)
@@ -506,7 +506,6 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
                /* An encap_type of -1 indicates async resumption. */
                if (encap_type == -1) {
                        async = 1;
-                       dev_put(skb->dev);
                        seq = XFRM_SKB_CB(skb)->seq.input.low;
                        spin_lock(&x->lock);
                        goto resume;
@@ -659,8 +658,11 @@ process:
                        dev_hold(skb->dev);
 
                        nexthdr = x->type->input(x, skb);
-                       if (nexthdr == -EINPROGRESS)
+                       if (nexthdr == -EINPROGRESS) {
+                               if (async)
+                                       dev_put(skb->dev);
                                return 0;
+                       }
 
                        dev_put(skb->dev);
                        spin_lock(&x->lock);
@@ -695,9 +697,11 @@ resume:
                XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;
 
                err = xfrm_inner_mode_input(x, skb);
-               if (err == -EINPROGRESS)
+               if (err == -EINPROGRESS) {
+                       if (async)
+                               dev_put(skb->dev);
                        return 0;
-               else if (err) {
+               else if (err) {
                        XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
                        goto drop;
                }
@@ -734,6 +738,8 @@ resume_decapped:
                        sp->olen = 0;
                if (skb_valid_dst(skb))
                        skb_dst_drop(skb);
+               if (async)
+                       dev_put(skb->dev);
                gro_cells_receive(&gro_cells, skb);
                return 0;
        } else {
@@ -753,6 +759,8 @@ resume_decapped:
                                sp->olen = 0;
                        if (skb_valid_dst(skb))
                                skb_dst_drop(skb);
+                       if (async)
+                               dev_put(skb->dev);
                        gro_cells_receive(&gro_cells, skb);
                        return err;
                }
@@ -763,6 +771,8 @@ resume_decapped:
 drop_unlock:
        spin_unlock(&x->lock);
 drop:
+       if (async)
+               dev_put(skb->dev);
        xfrm_rcv_cb(skb, family, x && x->type ? x->type->proto : nexthdr, -1);
        kfree_skb(skb);
        return 0;