Skip to content

Commit f75a280

Browse files
congwangklassert
authored andcommitted
xfrm: destroy xfrm_state synchronously on net exit path
xfrm_state_put() moves struct xfrm_state to the GC list and schedules the GC work to clean it up. On net exit call path, xfrm_state_flush() is called to clean up and xfrm_flush_gc() is called to wait for the GC work to complete before exit. However, this doesn't work because one of the ->destructor(), ipcomp_destroy(), schedules the same GC work again inside the GC work. It is hard to wait for such a nested async callback. This is also why syzbot still reports the following warning: WARNING: CPU: 1 PID: 33 at net/ipv6/xfrm6_tunnel.c:351 xfrm6_tunnel_net_exit+0x2cb/0x500 net/ipv6/xfrm6_tunnel.c:351 ... ops_exit_list.isra.0+0xb0/0x160 net/core/net_namespace.c:153 cleanup_net+0x51d/0xb10 net/core/net_namespace.c:551 process_one_work+0xd0c/0x1ce0 kernel/workqueue.c:2153 worker_thread+0x143/0x14a0 kernel/workqueue.c:2296 kthread+0x357/0x430 kernel/kthread.c:246 ret_from_fork+0x3a/0x50 arch/x86/entry/entry_64.S:352 In fact, it is perfectly fine to bypass GC and destroy xfrm_state synchronously on net exit call path, because it is in process context and doesn't need a work struct to do any blocking work. This patch introduces xfrm_state_put_sync() which simply bypasses GC, and lets its callers to decide whether to use this synchronous version. On net exit path, xfrm_state_fini() and xfrm6_tunnel_net_exit() use it. And, as ipcomp_destroy() itself is blocking, it can use xfrm_state_put_sync() directly too. Also rename xfrm_state_gc_destroy() to ___xfrm_state_destroy() to reflect this change. Fixes: b48c05a ("xfrm: Fix warning in xfrm6_tunnel_net_exit.") Reported-and-tested-by: [email protected] Cc: Steffen Klassert <[email protected]> Signed-off-by: Cong Wang <[email protected]> Signed-off-by: Steffen Klassert <[email protected]>
1 parent 09db512 commit f75a280

File tree

5 files changed

+31
-17
lines changed

5 files changed

+31
-17
lines changed

include/net/xfrm.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ static inline void xfrm_pols_put(struct xfrm_policy **pols, int npols)
853853
xfrm_pol_put(pols[i]);
854854
}
855855

856-
void __xfrm_state_destroy(struct xfrm_state *);
856+
void __xfrm_state_destroy(struct xfrm_state *, bool);
857857

858858
static inline void __xfrm_state_put(struct xfrm_state *x)
859859
{
@@ -863,7 +863,13 @@ static inline void __xfrm_state_put(struct xfrm_state *x)
863863
static inline void xfrm_state_put(struct xfrm_state *x)
864864
{
865865
if (refcount_dec_and_test(&x->refcnt))
866-
__xfrm_state_destroy(x);
866+
__xfrm_state_destroy(x, false);
867+
}
868+
869+
static inline void xfrm_state_put_sync(struct xfrm_state *x)
870+
{
871+
if (refcount_dec_and_test(&x->refcnt))
872+
__xfrm_state_destroy(x, true);
867873
}
868874

869875
static inline void xfrm_state_hold(struct xfrm_state *x)
@@ -1590,7 +1596,7 @@ struct xfrmk_spdinfo {
15901596

15911597
struct xfrm_state *xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq);
15921598
int xfrm_state_delete(struct xfrm_state *x);
1593-
int xfrm_state_flush(struct net *net, u8 proto, bool task_valid);
1599+
int xfrm_state_flush(struct net *net, u8 proto, bool task_valid, bool sync);
15941600
int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_valid);
15951601
void xfrm_sad_getinfo(struct net *net, struct xfrmk_sadinfo *si);
15961602
void xfrm_spd_getinfo(struct net *net, struct xfrmk_spdinfo *si);

net/ipv6/xfrm6_tunnel.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,8 @@ static void __net_exit xfrm6_tunnel_net_exit(struct net *net)
344344
struct xfrm6_tunnel_net *xfrm6_tn = xfrm6_tunnel_pernet(net);
345345
unsigned int i;
346346

347-
xfrm_state_flush(net, IPSEC_PROTO_ANY, false);
348347
xfrm_flush_gc();
348+
xfrm_state_flush(net, IPSEC_PROTO_ANY, false, true);
349349

350350
for (i = 0; i < XFRM6_TUNNEL_SPI_BYADDR_HSIZE; i++)
351351
WARN_ON_ONCE(!hlist_empty(&xfrm6_tn->spi_byaddr[i]));

net/key/af_key.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1783,7 +1783,7 @@ static int pfkey_flush(struct sock *sk, struct sk_buff *skb, const struct sadb_m
17831783
if (proto == 0)
17841784
return -EINVAL;
17851785

1786-
err = xfrm_state_flush(net, proto, true);
1786+
err = xfrm_state_flush(net, proto, true, false);
17871787
err2 = unicast_flush_resp(sk, hdr);
17881788
if (err || err2) {
17891789
if (err == -ESRCH) /* empty table - go quietly */

net/xfrm/xfrm_state.c

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ void xfrm_state_free(struct xfrm_state *x)
432432
}
433433
EXPORT_SYMBOL(xfrm_state_free);
434434

435-
static void xfrm_state_gc_destroy(struct xfrm_state *x)
435+
static void ___xfrm_state_destroy(struct xfrm_state *x)
436436
{
437437
tasklet_hrtimer_cancel(&x->mtimer);
438438
del_timer_sync(&x->rtimer);
@@ -474,7 +474,7 @@ static void xfrm_state_gc_task(struct work_struct *work)
474474
synchronize_rcu();
475475

476476
hlist_for_each_entry_safe(x, tmp, &gc_list, gclist)
477-
xfrm_state_gc_destroy(x);
477+
___xfrm_state_destroy(x);
478478
}
479479

480480
static enum hrtimer_restart xfrm_timer_handler(struct hrtimer *me)
@@ -598,14 +598,19 @@ struct xfrm_state *xfrm_state_alloc(struct net *net)
598598
}
599599
EXPORT_SYMBOL(xfrm_state_alloc);
600600

601-
void __xfrm_state_destroy(struct xfrm_state *x)
601+
void __xfrm_state_destroy(struct xfrm_state *x, bool sync)
602602
{
603603
WARN_ON(x->km.state != XFRM_STATE_DEAD);
604604

605-
spin_lock_bh(&xfrm_state_gc_lock);
606-
hlist_add_head(&x->gclist, &xfrm_state_gc_list);
607-
spin_unlock_bh(&xfrm_state_gc_lock);
608-
schedule_work(&xfrm_state_gc_work);
605+
if (sync) {
606+
synchronize_rcu();
607+
___xfrm_state_destroy(x);
608+
} else {
609+
spin_lock_bh(&xfrm_state_gc_lock);
610+
hlist_add_head(&x->gclist, &xfrm_state_gc_list);
611+
spin_unlock_bh(&xfrm_state_gc_lock);
612+
schedule_work(&xfrm_state_gc_work);
613+
}
609614
}
610615
EXPORT_SYMBOL(__xfrm_state_destroy);
611616

@@ -708,7 +713,7 @@ xfrm_dev_state_flush_secctx_check(struct net *net, struct net_device *dev, bool
708713
}
709714
#endif
710715

711-
int xfrm_state_flush(struct net *net, u8 proto, bool task_valid)
716+
int xfrm_state_flush(struct net *net, u8 proto, bool task_valid, bool sync)
712717
{
713718
int i, err = 0, cnt = 0;
714719

@@ -730,7 +735,10 @@ int xfrm_state_flush(struct net *net, u8 proto, bool task_valid)
730735
err = xfrm_state_delete(x);
731736
xfrm_audit_state_delete(x, err ? 0 : 1,
732737
task_valid);
733-
xfrm_state_put(x);
738+
if (sync)
739+
xfrm_state_put_sync(x);
740+
else
741+
xfrm_state_put(x);
734742
if (!err)
735743
cnt++;
736744

@@ -2215,7 +2223,7 @@ void xfrm_state_delete_tunnel(struct xfrm_state *x)
22152223
if (atomic_read(&t->tunnel_users) == 2)
22162224
xfrm_state_delete(t);
22172225
atomic_dec(&t->tunnel_users);
2218-
xfrm_state_put(t);
2226+
xfrm_state_put_sync(t);
22192227
x->tunnel = NULL;
22202228
}
22212229
}
@@ -2375,8 +2383,8 @@ void xfrm_state_fini(struct net *net)
23752383
unsigned int sz;
23762384

23772385
flush_work(&net->xfrm.state_hash_work);
2378-
xfrm_state_flush(net, IPSEC_PROTO_ANY, false);
23792386
flush_work(&xfrm_state_gc_work);
2387+
xfrm_state_flush(net, IPSEC_PROTO_ANY, false, true);
23802388

23812389
WARN_ON(!list_empty(&net->xfrm.state_all));
23822390

net/xfrm/xfrm_user.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1932,7 +1932,7 @@ static int xfrm_flush_sa(struct sk_buff *skb, struct nlmsghdr *nlh,
19321932
struct xfrm_usersa_flush *p = nlmsg_data(nlh);
19331933
int err;
19341934

1935-
err = xfrm_state_flush(net, p->proto, true);
1935+
err = xfrm_state_flush(net, p->proto, true, false);
19361936
if (err) {
19371937
if (err == -ESRCH) /* empty table */
19381938
return 0;

0 commit comments

Comments
 (0)