From 18d7ef726d4a58a91f39bbecba6fb5455bcc7570 Mon Sep 17 00:00:00 2001 From: Yi Tao Date: Fri, 8 Jan 2021 19:49:05 +0800 Subject: [PATCH 2538/2944] alinux: cgroup: fix dead lock in put_to_cache to #31531504 Generally, freeing a cgroup is in process context, except in a special case which call sched_free_group from rcu_process_callbacks. so it may result in dead lock if one process holds cache_header.lock while soft interrupt occurs and try to get this lock. besides, when holding this lock in soft interrupt context, it may try to allocate memory for cache node and get sleep, which will cause kernel panic. More specific, with information provided by stack trace below, the dead lock scenario is that, autogroup_destroy puts sched_free_group into a rcu callback, then cgroup_rmdir calls sched_free_group and holds lock, at this moment, time interrupt happens and when irq exits, do_softirq calls sched_free_group which registered in rcu callback and tries to acquire lock again. To fix this bug, disable interruption and check context before allocating memory. [ 8.660436] [ 8.660684] ================================ [ 8.661272] WARNING: inconsistent lock state [ 8.661859] 4.19.91+ #29 Not tainted [ 8.662343] -------------------------------- [ 8.662924] inconsistent {SOFTIRQ-ON-W} -> {IN-SOFTIRQ-W} usage. [ 8.663735] systemd/1 [HC0[0]:SC1[1]:HE1:SE0] takes: [ 8.664403] 000000004646dc33 (fair_sched_cache_header.lock){+.?.}, at: put_to_cache+0x29/0x570 [ 8.665579] {SOFTIRQ-ON-W} state was registered at: [ 8.666244] _raw_spin_lock+0x2c/0x40 [ 8.666759] get_from_cache+0x28/0x4d0 [ 8.667285] alloc_fair_sched_group+0x7f/0x810 [ 8.667906] sched_create_group+0x35/0x70 [ 8.668460] sched_autogroup_create_attach+0x60/0x2a0 [ 8.669163] ksys_setsid+0x272/0x320 [ 8.669662] __ia32_sys_setsid+0xa/0x10 [ 8.670222] do_syscall_64+0xa5/0x4a0 [ 8.670813] entry_SYSCALL_64_after_hwframe+0x49/0xbe [ 8.671501] irq event stamp: 3647592 [ 8.672056] hardirqs last enabled at (3647592): [] quarantine_put+0x8e/0x150 [ 8.673415] hardirqs last disabled at (3647591): [] quarantine_put+0x43/0x150 [ 8.674711] softirqs last enabled at (3647158): [] cgroup_idr_replace+0x3a/0x50 [ 8.676029] softirqs last disabled at (3647575): [] irq_exit+0x22a/0x250 [ 8.677245] [ 8.677245] other info that might help us debug this: [ 8.678199] Possible unsafe locking scenario: [ 8.678199] [ 8.679097] CPU0 [ 8.679467] ---- [ 8.679841] lock(fair_sched_cache_header.lock); [ 8.680515] [ 8.680899] lock(fair_sched_cache_header.lock); [ 8.681656] [ 8.681656] *** DEADLOCK *** [ 8.682501] 3 locks held by systemd/1: [ 8.683059] #0: 0000000032deb96a (&type->i_mutex_dir_key#6){++++}, at: lookup_slow+0x42/0x70 [ 8.684231] #1: 00000000de0f8d78 (kernfs_mutex){+.+.}, at: kernfs_iop_lookup+0x4c/0x1e0 [ 8.685322] #2: 00000000696ea1a1 (rcu_callback){....}, at: rcu_process_callbacks+0x521/0x1900 [ 8.686483] [ 8.686483] stack backtrace: [ 8.687083] CPU: 4 PID: 1 Comm: systemd Not tainted 4.19.91+ #29 [ 8.687897] Hardware name: Alibaba Cloud Alibaba Cloud ECS, BIOS rel-1.7.5-0-ge51488c-20140602_164612-nilsson.home.kraxel.org 04/01/2014 [ 8.689549] Call Trace: [ 8.689889] [ 8.690174] dump_stack+0xaf/0xfb [ 8.690632] mark_lock+0x441/0x1160 [ 8.691103] ? check_usage_backwards+0x320/0x320 [ 8.691735] __lock_acquire+0x652/0x1370 [ 8.692270] ? kernfs_iop_lookup+0x11b/0x1e0 [ 8.692851] ? __lookup_slow+0x215/0x3e0 [ 8.693383] ? lookup_slow+0x50/0x70 [ 8.693873] ? link_path_walk+0x38c/0xf70 [ 8.694415] lock_acquire+0x10f/0x2d0 [ 8.694917] ? put_to_cache+0x29/0x570 [ 8.695429] ? hrtimer_try_to_cancel+0x64/0x3a0 [ 8.696046] _raw_spin_lock+0x2c/0x40 [ 8.696549] ? put_to_cache+0x29/0x570 [ 8.697057] put_to_cache+0x29/0x570 [ 8.697549] free_fair_sched_group+0x1ed/0x2f0 [ 8.698147] ? init_cfs_rq+0xb0/0xb0 [ 8.698640] ? rcu_process_callbacks+0x521/0x1900 [ 8.699276] ? sched_free_group+0x30/0x30 [ 8.699826] sched_free_group+0xe/0x30 [ 8.700333] rcu_process_callbacks+0x571/0x1900 [ 8.700950] ? rcu_gp_kthread+0x1d50/0x1d50 [ 8.701519] __do_softirq+0x23b/0x889 [ 8.702021] irq_exit+0x22a/0x250 [ 8.702475] smp_apic_timer_interrupt+0xfe/0x480 [ 8.703102] apic_timer_interrupt+0xf/0x20 [ 8.703660] Signed-off-by: Yi Tao Acked-by: Shanpei Chen --- kernel/cgroup/cgroup.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 121fc50..1f63dbe 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -6415,9 +6415,9 @@ bool get_from_cache(struct cache_header *ch, void **dst, unsigned int cnt) { struct cache_node *cn; - spin_lock(&ch->lock); + spin_lock_bh(&ch->lock); if (!ch->ready_node || !ch->online) { - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); return false; } @@ -6428,12 +6428,12 @@ bool get_from_cache(struct cache_header *ch, void **dst, unsigned int cnt) /* limit may change, if too many empty_node in list, free extra */ if (ch->empty_node + ch->ready_node > ch->limit) { - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); kfree(cn); } else { list_add_tail(&cn->node, &ch->header); ch->empty_node++; - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); } return true; @@ -6444,6 +6444,9 @@ static struct cache_node *alloc_cache_node(unsigned int cnt) { struct cache_node *cn; + if (in_interrupt()) + return NULL; + cn = kmalloc(sizeof(*cn) + sizeof(void *) * cnt, GFP_KERNEL); if (!cn) return NULL; @@ -6464,34 +6467,34 @@ bool put_to_cache(struct cache_header *ch, void **src, unsigned int cnt) { struct cache_node *cn; - spin_lock(&ch->lock); + spin_lock_bh(&ch->lock); if (!ch->online || ch->ready_node >= ch->limit) { - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); return false; } /* get an empty_node */ if (!ch->empty_node) { - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); cn = alloc_cache_node(cnt); if (!cn) return false; - spin_lock(&ch->lock); + spin_lock_bh(&ch->lock); } else { cn = list_last_entry(&ch->header, struct cache_node, node); list_del(&cn->node); ch->empty_node--; } - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); ch->clean_up(src); memcpy(cn->ptr, src, sizeof(void *) * cnt); - spin_lock(&ch->lock); + spin_lock_bh(&ch->lock); list_add(&cn->node, &ch->header); ch->ready_node++; - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); return true; } @@ -6505,15 +6508,15 @@ void change_cache_limit(struct cache_header *ch, unsigned int limit) { struct cache_node *cn; - spin_lock(&ch->lock); + spin_lock_bh(&ch->lock); ch->limit = limit; if (ch->ready_node <= ch->limit) { - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); return; } /* disable cache until extra ready_node be released */ ch->online = false; - spin_unlock(&ch->lock); + spin_unlock_bh(&ch->lock); /* * cache header is offline, release extra ready_node. because cache -- 1.8.3.1