]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xfrm: Fix work re-schedule after cancel in xfrm_nat_keepalive_net_fini()
authorHyunwoo Kim <imv4bel@gmail.com>
Tue, 10 Mar 2026 18:16:29 +0000 (03:16 +0900)
committerSteffen Klassert <steffen.klassert@secunet.com>
Fri, 13 Mar 2026 11:36:59 +0000 (12:36 +0100)
After cancel_delayed_work_sync() is called from
xfrm_nat_keepalive_net_fini(), xfrm_state_fini() flushes remaining
states via __xfrm_state_delete(), which calls
xfrm_nat_keepalive_state_updated() to re-schedule nat_keepalive_work.

The following is a simple race scenario:

           cpu0                             cpu1

cleanup_net() [Round 1]
  ops_undo_list()
    xfrm_net_exit()
      xfrm_nat_keepalive_net_fini()
        cancel_delayed_work_sync(nat_keepalive_work);
      xfrm_state_fini()
        xfrm_state_flush()
          xfrm_state_delete(x)
            __xfrm_state_delete(x)
              xfrm_nat_keepalive_state_updated(x)
                schedule_delayed_work(nat_keepalive_work);
  rcu_barrier();
  net_complete_free();
  net_passive_dec(net);
    llist_add(&net->defer_free_list, &defer_free_list);

cleanup_net() [Round 2]
  rcu_barrier();
  net_complete_free()
    kmem_cache_free(net_cachep, net);
                                     nat_keepalive_work()
                                       // on freed net

To prevent this, cancel_delayed_work_sync() is replaced with
disable_delayed_work_sync().

Fixes: f531d13bdfe3 ("xfrm: support sending NAT keepalives in ESP in UDP states")
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
net/xfrm/xfrm_nat_keepalive.c

index ebf95d48e86c149ceabd6e059d85d39660df5b8c..1856beee0149bbcdf7abd744a71d911543795acb 100644 (file)
@@ -261,7 +261,7 @@ int __net_init xfrm_nat_keepalive_net_init(struct net *net)
 
 int xfrm_nat_keepalive_net_fini(struct net *net)
 {
-       cancel_delayed_work_sync(&net->xfrm.nat_keepalive_work);
+       disable_delayed_work_sync(&net->xfrm.nat_keepalive_work);
        return 0;
 }