From: Chengfeng Ye <nicoyip.dev@gmail.com>
To: Steffen Klassert <steffen.klassert@secunet.com>,
Herbert Xu <herbert@gondor.apana.org.au>,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
Chengfeng Ye <nicoyip.dev@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH ipsec v2] xfrm: fix races in device state cleanup
Date: Mon, 14 Sep 2026 21:35:40 +0800 [thread overview]
Message-ID: <20260914133540.3666907-1-nicoyip.dev@gmail.com> (raw)
The deferred-device pass in xfrm_dev_state_flush() finds states under
xfrm_state_dev_gc_lock, but drops the lock before calling
xfrm_dev_state_free() because the driver callback may sleep. The device
GC list does not hold an xfrm_state reference, so the state GC worker can
destroy the same state concurrently.
The race can proceed as follows:
CPU 0 CPU 1
find x on the device GC list
drop xfrm_state_dev_gc_lock
xfrm_state_gc_destroy(x)
xfrm_dev_state_free(x)
xfrm_state_free(x)
xfrm_dev_state_free(x)
Both paths can invoke the driver callback and drop the device reference.
CPU 0 can also access the xfrm_state after CPU 1 has freed it.
KASAN reported:
BUG: KASAN: slab-use-after-free in xfrm_dev_state_free+0x24c/0x2a0
Read of size 8 at addr ffff88810bbaa960 by task poc/102
Call Trace:
xfrm_dev_state_free+0x24c/0x2a0
xfrm_dev_state_flush+0x353/0x400
xfrm_dev_event+0x26d/0x3a0
notifier_call_chain+0xc0/0x280
__dev_notify_flags+0x169/0x250
netif_change_flags+0xe7/0x160
dev_change_flags+0x96/0x220
devinet_ioctl+0x7f4/0x1880
Freed by task 57:
kmem_cache_free+0xcb/0x3d0
xfrm_state_gc_task+0x4a8/0x650
process_one_work+0x63a/0x1070
A third xfrm_dev_state_free() caller in xfrm_state_find() can also race
with the deferred pass. Both callers can sample the same device before
either clears xso->dev, then invoke the callback and netdev_put() twice.
Since hlist_del() leaves the node poisoned rather than unhashed, they can
also unlink the device GC node twice.
Acquire a state reference while the deferred pass still holds
xfrm_state_dev_gc_lock. If the reference count is already zero, leave
destruction to the state GC worker. Also claim device cleanup under the
same lock by clearing xso->dev and unlinking the node with
hlist_del_init(). Only the caller that claims the device invokes the
callback and drops the device reference. The deferred pass releases its
temporary state reference after cleanup.
Fixes: 07b87f9eea0c ("xfrm: Fix unregister netdevice hang on hardware offload.")
Cc: stable@vger.kernel.org
Signed-off-by: Chengfeng Ye <nicoyip.dev@gmail.com>
---
Changes in v2:
- Replace the global mutex with a temporary state reference acquired
while walking the device GC list.
- Claim device cleanup under xfrm_state_dev_gc_lock so concurrent
callers cannot run the callback or netdev_put() twice.
- Use hlist_del_init() to leave the device GC node unhashed.
- Cover the xfrm_state_find() cleanup path identified during review.
v1: https://lore.kernel.org/netdev/20260730103543.2077749-1-nicoyip.dev@gmail.com/
net/xfrm/xfrm_state.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c
index 36a4f6793ede..282d09d0c247 100644
--- a/net/xfrm/xfrm_state.c
+++ b/net/xfrm/xfrm_state.c
@@ -780,17 +780,20 @@ EXPORT_SYMBOL_GPL(xfrm_dev_state_delete);
void xfrm_dev_state_free(struct xfrm_state *x)
{
struct xfrm_dev_offload *xso = &x->xso;
- struct net_device *dev = READ_ONCE(xso->dev);
+ struct net_device *dev;
+ spin_lock_bh(&xfrm_state_dev_gc_lock);
+ dev = READ_ONCE(xso->dev);
if (dev && dev->xfrmdev_ops) {
- spin_lock_bh(&xfrm_state_dev_gc_lock);
+ WRITE_ONCE(xso->dev, NULL);
if (!hlist_unhashed(&x->dev_gclist))
- hlist_del(&x->dev_gclist);
- spin_unlock_bh(&xfrm_state_dev_gc_lock);
+ hlist_del_init(&x->dev_gclist);
+ }
+ spin_unlock_bh(&xfrm_state_dev_gc_lock);
+ if (dev && dev->xfrmdev_ops) {
if (dev->xfrmdev_ops->xdo_dev_state_free)
dev->xfrmdev_ops->xdo_dev_state_free(dev, x);
- WRITE_ONCE(xso->dev, NULL);
xso->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
netdev_put(dev, &xso->dev_tracker);
}
@@ -1005,9 +1008,10 @@ int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_vali
hlist_for_each_entry_safe(x, tmp, &xfrm_state_dev_gc_list, dev_gclist) {
xso = &x->xso;
- if (xso->dev == dev) {
+ if (xso->dev == dev && xfrm_state_hold_rcu(x)) {
spin_unlock_bh(&xfrm_state_dev_gc_lock);
xfrm_dev_state_free(x);
+ xfrm_state_put(x);
spin_lock_bh(&xfrm_state_dev_gc_lock);
goto restart_gc;
}
base-commit: e6b6078ea1731b05b3b552497b3bce4bf8b014ae
--
2.43.0
next reply other threads:[~2026-09-14 13:38 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 13:35 Chengfeng Ye [this message]
2026-09-16 17:38 ` [PATCH ipsec v2] xfrm: fix races in device state cleanup netdev-bot+sashiko
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=20260914133540.3666907-1-nicoyip.dev@gmail.com \
--to=nicoyip.dev@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=herbert@gondor.apana.org.au \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
--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.