Netdev List
 help / color / mirror / Atom feed
* [PATCH ipsec] xfrm: do not take x->lock under xfrm_state_lock in the NAT keepalive walk
@ 2026-07-29  3:28 Yuejie Shi
  0 siblings, 0 replies; only message in thread
From: Yuejie Shi @ 2026-07-29  3:28 UTC (permalink / raw)
  To: Steffen Klassert, Herbert Xu, David S . Miller
  Cc: Eyal Birger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, netdev, linux-kernel, stable

nat_keepalive_work() walks the SA list with xfrm_state_walk(), which
holds net->xfrm.xfrm_state_lock across the callback, and
nat_keepalive_work_single() then takes x->lock underneath it.

The rest of xfrm uses the opposite order.  xfrm_timer_handler() takes
x->lock and calls __xfrm_state_delete(), which takes xfrm_state_lock,
and xfrm_state_flush() deliberately drops xfrm_state_lock before calling
xfrm_state_delete() for that very reason.  The NAT keepalive walk is the
only place that inverts it.

lockdep reports it as soon as both paths have run, which needs nothing
more than a few outbound ESP-in-UDP SAs carrying
XFRMA_NAT_KEEPALIVE_INTERVAL and a hard lifetime -- no traffic and no
peer are required:

  WARNING: possible circular locking dependency detected
  swapper/3/0 is trying to acquire lock:
   (&net->xfrm.xfrm_state_lock){+.-.}, at: __xfrm_state_delete+0x60/0x324
  but task is already holding lock:
   (&x->lock){+.-.}, at: xfrm_timer_handler+0x94/0x50c

  -> #1 (&x->lock){+.-.}:
         _raw_spin_lock+0x48/0x60
         nat_keepalive_work_single+0x120/0xb60
         xfrm_state_walk+0x284/0x468
         nat_keepalive_work+0xc4/0x15c
         process_one_work+0x450/0xc78

  -> #0 (&net->xfrm.xfrm_state_lock){+.-.}:
         _raw_spin_lock+0x48/0x60
         __xfrm_state_delete+0x60/0x324
         xfrm_timer_handler+0x35c/0x50c
         __hrtimer_run_queues+0x1c4/0x5b8

   Possible unsafe locking scenario:
         CPU0                    CPU1
         ----                    ----
    lock(&x->lock);
                                 lock(&net->xfrm.xfrm_state_lock);
                                 lock(&x->lock);
    lock(&net->xfrm.xfrm_state_lock);

xfrm_user_rcv_msg() gates every operation on netlink_net_capable(skb,
CAP_NET_ADMIN), which is CAP_NET_ADMIN over the netns' user namespace,
so an unprivileged user who unshares a user and network namespace can
set this up.  When the two orders do collide, both CPUs spin forever --
one of them inside hrtimer softirq context, which also stops softirq
processing on that CPU.

x->lock is needed here because the data path updates x->lastused under
it.  x->nat_keepalive_expiration is only ever touched by this worker,
which is a per-netns delayed_work and so never runs concurrently with
itself.

Restructuring the walk so the state list lock can be dropped is not
worth it for a once-per-second housekeeping pass.  Use spin_trylock()
instead: it cannot block, so the inversion is gone, and a state whose
lock happens to be held by the data path at that instant is simply
revisited on the next run.  Deferring one keepalive by a second is
harmless -- the configured intervals are in seconds, and next_run is
recomputed from scratch on every pass.

Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states")
Cc: stable@vger.kernel.org
Signed-off-by: Yuejie Shi <syjcnss@gmail.com>
---
 net/xfrm/xfrm_nat_keepalive.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c
index eb1b6f67739e..7143c7a11c18 100644
--- a/net/xfrm/xfrm_nat_keepalive.c
+++ b/net/xfrm/xfrm_nat_keepalive.c
@@ -173,7 +173,19 @@ static int nat_keepalive_work_single(struct xfrm_state *x, int count, void *ptr)
 	if (!interval)
 		return 0;
 
-	spin_lock(&x->lock);
+	/* This runs from xfrm_state_walk() with net->xfrm.xfrm_state_lock
+	 * held, while the rest of xfrm takes x->lock first and
+	 * xfrm_state_lock second (see xfrm_timer_handler() ->
+	 * __xfrm_state_delete(), and xfrm_state_flush(), which drops
+	 * xfrm_state_lock before deleting a state).  Blocking on x->lock
+	 * here would invert that order and deadlock.  A keepalive is
+	 * housekeeping, so if the state is busy just look at it again on
+	 * the next run.
+	 */
+	if (!spin_trylock(&x->lock)) {
+		next_run = ctx->now + 1;
+		goto out;
+	}
 
 	delta = (int)(ctx->now - x->lastused);
 	if (delta < interval) {
@@ -192,6 +204,7 @@ static int nat_keepalive_work_single(struct xfrm_state *x, int count, void *ptr)
 	if (send_keepalive)
 		nat_keepalive_send(&ka);
 
+out:
 	if (!ctx->next_run || next_run < ctx->next_run)
 		ctx->next_run = next_run;
 	return 0;
--
2.51.0

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-07-29  3:29 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29  3:28 [PATCH ipsec] xfrm: do not take x->lock under xfrm_state_lock in the NAT keepalive walk Yuejie Shi

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