From: Xin Long lucien.xin@gmail.com
mainline inclusion from mainline-v5.13-rc4 commit 04c26faa51d1e2fe71cf13c45791f5174c37f986 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/I9BHRY CVE: CVE-2021-47163
--------------------------------
On some host, a crash could be triggered simply by repeating these commands several times:
# modprobe tipc # tipc bearer enable media udp name UDP1 localip 127.0.0.1 # rmmod tipc
[] BUG: unable to handle kernel paging request at ffffffffc096bb00 [] Workqueue: events 0xffffffffc096bb00 [] Call Trace: [] ? process_one_work+0x1a7/0x360 [] ? worker_thread+0x30/0x390 [] ? create_worker+0x1a0/0x1a0 [] ? kthread+0x116/0x130 [] ? kthread_flush_work_fn+0x10/0x10 [] ? ret_from_fork+0x35/0x40
When removing the TIPC module, the UDP tunnel sock will be delayed to release in a work queue as sock_release() can't be done in rtnl_lock(). If the work queue is schedule to run after the TIPC module is removed, kernel will crash as the work queue function cleanup_beareri() code no longer exists when trying to invoke it.
To fix it, this patch introduce a member wq_count in tipc_net to track the numbers of work queues in schedule, and wait and exit until all work queues are done in tipc_exit_net().
Fixes: d0f91938bede ("tipc: add ip/udp media type") Reported-by: Shuang Li shuali@redhat.com Signed-off-by: Xin Long lucien.xin@gmail.com Acked-by: Jon Maloy jmaloy@redhat.com Signed-off-by: David S. Miller davem@davemloft.net Conflicts: net/tipc/core.c net/tipc/core.h net/tipc/udp_media.c Signed-off-by: Ziyang Xuan william.xuanziyang@huawei.com --- v2: - Remove "Reference" in comment header. --- net/tipc/core.c | 5 +++++ net/tipc/core.h | 3 +++ net/tipc/udp_media.c | 2 ++ 3 files changed, 10 insertions(+)
diff --git a/net/tipc/core.c b/net/tipc/core.c index 49bb9fc0aa06..7b61e1650cb0 100644 --- a/net/tipc/core.c +++ b/net/tipc/core.c @@ -92,10 +92,15 @@ static int __net_init tipc_init_net(struct net *net)
static void __net_exit tipc_exit_net(struct net *net) { + struct tipc_net *tn = tipc_net(net); + tipc_net_stop(net); tipc_bcast_stop(net); tipc_nametbl_stop(net); tipc_sk_rht_destroy(net); + + while (atomic_read(&tn->wq_count)) + cond_resched(); }
static struct pernet_operations tipc_net_ops = { diff --git a/net/tipc/core.h b/net/tipc/core.h index 8020a6c360ff..3abbdbffd460 100644 --- a/net/tipc/core.h +++ b/net/tipc/core.h @@ -122,6 +122,9 @@ struct tipc_net { /* Topology subscription server */ struct tipc_topsrv *topsrv; atomic_t subscription_count; + + /* The numbers of work queues in schedule */ + atomic_t wq_count; };
static inline struct tipc_net *tipc_net(struct net *net) diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c index 1d6235479706..efe822d3bb28 100644 --- a/net/tipc/udp_media.c +++ b/net/tipc/udp_media.c @@ -771,6 +771,7 @@ static void cleanup_bearer(struct work_struct *work) kfree_rcu(rcast, rcu); }
+ atomic_dec(&tipc_net(sock_net(ub->ubsock->sk))->wq_count); if (ub->ubsock) udp_tunnel_sock_release(ub->ubsock); synchronize_net(); @@ -792,6 +793,7 @@ static void tipc_udp_disable(struct tipc_bearer *b) RCU_INIT_POINTER(ub->bearer, NULL);
/* sock_release need to be done outside of rtnl lock */ + atomic_inc(&tipc_net(sock_net(ub->ubsock->sk))->wq_count); INIT_WORK(&ub->work, cleanup_bearer); schedule_work(&ub->work); }