[PATCH net] xfrm: add missing rcu_read_lock(), skb_dst_force() and dev_hold() for xfrm_trans_reinject()

1 view
Skip to first unread message

Eric Dumazet

unread,
Aug 7, 2026, 1:15:38 PM (2 days ago) Aug 7
to David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman, net...@vger.kernel.org, eric.d...@gmail.com, Eric Dumazet, syzbot, Steffen Klassert, Liu Jian
syzbot reported a suspicious RCU usage warning in ip6_pkt_drop():

WARNING: suspicious RCU usage in ip6_pkt_drop
include/net/addrconf.h:389 suspicious rcu_dereference_check() usage!

Call Trace:
__in6_dev_get_safely include/net/addrconf.h:389 [inline]
ip6_pkt_drop+0x596/0x610 net/ipv6/route.c:4620
ip6_pkt_discard+0x1c/0x30 net/ipv6/route.c:4651
xfrm_trans_reinject+0x324/0x630 net/xfrm/xfrm_input.c:806
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486

When commit 4f4920669d21 ("xfrm: Reinject transport-mode packets through
workqueue") converted xfrm_trans_reinject from a tasklet to a workqueue,
the reinjection loop ceased running in softirq context. Workqueue workers
run in process context where local_bh_disable() does not enter an RCU
read-side critical section under CONFIG_PREEMPT_RCU.

Because finish callbacks (such as ip6_rcv_finish) expect to run under an
RCU read lock (performing route lookups, l3mdev lookups, and accessing
RCU-protected data structures), invoking them in workqueue context without
rcu_read_lock() triggers RCU lockdep warnings.

Furthermore, packets queued to the workqueue via xfrm_trans_queue_net()
may carry non-refcounted (noref) dst entries (e.g. from ip_route_input_noref).
Additionally, on netdevice unregistration, dst_dev_put() replaces dst->dev
with blackhole_netdev, so dst entries do not keep skb->dev alive while
queued in the workqueue.

Fix these issues by:
1. Calling skb_dst_force(skb) in xfrm_trans_queue_net() while still in the
caller's RCU section to ensure dst is reference-counted before queuing.
2. Holding a reference on skb->dev via dev_hold()/dev_put() across workqueue
deferral so skb->dev remains valid during finish() callback processing.
3. Acquiring rcu_read_lock() around the finish callback invocation loop in
xfrm_trans_reinject().

Fixes: 4f4920669d21 ("xfrm: Reinject transport-mode packets through workqueue")
Reported-by: syzbot <syzk...@googlegroups.com>
Signed-off-by: Eric Dumazet <edum...@google.com>
Cc: Steffen Klassert <steffen....@secunet.com>
Cc: Liu Jian <liuj...@huawei.com>
---
net/xfrm/xfrm_input.c | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index eecab337bd0a794588b192598851bd77427c8392..8f6109eada7eaaf1c70aa2da610523b42d75b50c 100644
--- a/net/xfrm/xfrm_input.c
+++ b/net/xfrm/xfrm_input.c
@@ -800,12 +800,17 @@ static void xfrm_trans_reinject(struct work_struct *work)
spin_unlock_bh(&trans->queue_lock);

local_bh_disable();
+ rcu_read_lock();
while ((skb = __skb_dequeue(&queue))) {
struct net *net = XFRM_TRANS_SKB_CB(skb)->net;
+ struct net_device *dev = skb->dev;

XFRM_TRANS_SKB_CB(skb)->finish(net, NULL, skb);
+ if (dev)
+ dev_put(dev);
put_net(net);
}
+ rcu_read_unlock();
local_bh_enable();
}

@@ -821,12 +826,18 @@ int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb,
if (skb_queue_len(&trans->queue) >= READ_ONCE(net_hotdata.max_backlog))
return -ENOBUFS;

+ if (skb_dst(skb) && !skb_dst_force(skb))
+ return -EHOSTUNREACH;
+
BUILD_BUG_ON(sizeof(struct xfrm_trans_cb) > sizeof(skb->cb));

hold_net = maybe_get_net(net);
if (!hold_net)
return -ENODEV;

+ if (skb->dev)
+ dev_hold(skb->dev);
+
XFRM_TRANS_SKB_CB(skb)->finish = finish;
XFRM_TRANS_SKB_CB(skb)->net = hold_net;
spin_lock_bh(&trans->queue_lock);
--
2.55.0.654.g21b8a5bc05-goog

Reply all
Reply to author
Forward
0 new messages