fix CVE-2024-46839
Nicholas Piggin (2): workqueue: wq_watchdog_touch is always called with valid CPU workqueue: Improve scalability of workqueue watchdog touch
Wang Qing (1): workqueue/watchdog: Make unbound workqueues aware of touch_softlockup_watchdog()
kernel/watchdog.c | 5 +++-- kernel/workqueue.c | 25 ++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-)
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://gitee.com/openeuler/kernel/pulls/12030 邮件列表地址:https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/A...
FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://gitee.com/openeuler/kernel/pulls/12030 Mailing list address: https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/A...
From: Wang Qing wangqing@vivo.com
mainline inclusion from mainline-v5.12-rc7 commit 89e28ce60cb65971c73359c66d076aa20a395cd5 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAU9QV CVE: CVE-2024-46839
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
----------------------------------
There are two workqueue-specific watchdog timestamps:
+ @wq_watchdog_touched_cpu (per-CPU) updated by touch_softlockup_watchdog()
+ @wq_watchdog_touched (global) updated by touch_all_softlockup_watchdogs()
watchdog_timer_fn() checks only the global @wq_watchdog_touched for unbound workqueues. As a result, unbound workqueues are not aware of touch_softlockup_watchdog(). The watchdog might report a stall even when the unbound workqueues are blocked by a known slow code.
Solution: touch_softlockup_watchdog() must touch also the global @wq_watchdog_touched timestamp.
The global timestamp can no longer be used for bound workqueues because it is now updated from all CPUs. Instead, bound workqueues have to check only @wq_watchdog_touched_cpu and these timestamps have to be updated for all CPUs in touch_all_softlockup_watchdogs().
Beware: The change might cause the opposite problem. An unbound workqueue might get blocked on CPU A because of a real softlockup. The workqueue watchdog would miss it when the timestamp got touched on CPU B.
It is acceptable because softlockups are detected by softlockup watchdog. The workqueue watchdog is there to detect stalls where a work never finishes, for example, because of dependencies of works queued into the same workqueue.
V3: - Modify the commit message clearly according to Petr's suggestion.
Signed-off-by: Wang Qing wangqing@vivo.com Signed-off-by: Tejun Heo tj@kernel.org Signed-off-by: liwei liwei728@huawei.com --- kernel/watchdog.c | 5 +++-- kernel/workqueue.c | 17 ++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/kernel/watchdog.c b/kernel/watchdog.c index 36f458111205..466b5a6b8b3d 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -293,9 +293,10 @@ void touch_all_softlockup_watchdogs(void) * update as well, the only side effect might be a cycle delay for * the softlockup check. */ - for_each_cpu(cpu, &watchdog_allowed_mask) + for_each_cpu(cpu, &watchdog_allowed_mask) { per_cpu(watchdog_touch_ts, cpu) = SOFTLOCKUP_RESET; - wq_watchdog_touch(-1); + wq_watchdog_touch(cpu); + } }
void touch_softlockup_watchdog_sync(void) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 687c4e61d939..0f2b14eb2ab1 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5878,22 +5878,17 @@ static void wq_watchdog_timer_fn(struct timer_list *unused) kvm_check_and_clear_guest_paused();
/* get the latest of pool and touched timestamps */ + if (pool->cpu >= 0) + touched = READ_ONCE(per_cpu(wq_watchdog_touched_cpu, pool->cpu)); + else + touched = READ_ONCE(wq_watchdog_touched); pool_ts = READ_ONCE(pool->watchdog_ts); - touched = READ_ONCE(wq_watchdog_touched);
if (time_after(pool_ts, touched)) ts = pool_ts; else ts = touched;
- if (pool->cpu >= 0) { - unsigned long cpu_touched = - READ_ONCE(per_cpu(wq_watchdog_touched_cpu, - pool->cpu)); - if (time_after(cpu_touched, ts)) - ts = cpu_touched; - } - /* did we stall? */ if (time_after(now, ts + thresh)) { lockup_detected = true; @@ -5917,8 +5912,8 @@ notrace void wq_watchdog_touch(int cpu) { if (cpu >= 0) per_cpu(wq_watchdog_touched_cpu, cpu) = jiffies; - else - wq_watchdog_touched = jiffies; + + wq_watchdog_touched = jiffies; }
static void wq_watchdog_set_thresh(unsigned long thresh)
From: Nicholas Piggin npiggin@gmail.com
mainline inclusion from mainline-v6.11-rc1 commit 18e24deb1cc92f2068ce7434a94233741fbd7771 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAU9QV CVE: CVE-2024-46839
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
----------------------------------
Warn in the case it is called with cpu == -1. This does not appear to happen anywhere.
Signed-off-by: Nicholas Piggin npiggin@gmail.com Reviewed-by: Paul E. McKenney paulmck@kernel.org Signed-off-by: Tejun Heo tj@kernel.org Signed-off-by: liwei liwei728@huawei.com --- kernel/workqueue.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 0f2b14eb2ab1..4705d6e2c1ac 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5912,6 +5912,8 @@ notrace void wq_watchdog_touch(int cpu) { if (cpu >= 0) per_cpu(wq_watchdog_touched_cpu, cpu) = jiffies; + else + WARN_ONCE(1, "%s should be called with valid CPU", __func__);
wq_watchdog_touched = jiffies; }
From: Nicholas Piggin npiggin@gmail.com
mainline inclusion from mainline-v6.11-rc1 commit 98f887f820c993e05a12e8aa816c80b8661d4c87 category: bugfix bugzilla: https://gitee.com/src-openeuler/kernel/issues/IAU9QV CVE: CVE-2024-46839
Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i...
----------------------------------
On a ~2000 CPU powerpc system, hard lockups have been observed in the workqueue code when stop_machine runs (in this case due to CPU hotplug). This is due to lots of CPUs spinning in multi_cpu_stop, calling touch_nmi_watchdog() which ends up calling wq_watchdog_touch(). wq_watchdog_touch() writes to the global variable wq_watchdog_touched, and that can find itself in the same cacheline as other important workqueue data, which slows down operations to the point of lockups.
In the case of the following abridged trace, worker_pool_idr was in the hot line, causing the lockups to always appear at idr_find.
watchdog: CPU 1125 self-detected hard LOCKUP @ idr_find Call Trace: get_work_pool __queue_work call_timer_fn run_timer_softirq __do_softirq do_softirq_own_stack irq_exit timer_interrupt decrementer_common_virt * interrupt: 900 (timer) at multi_cpu_stop multi_cpu_stop cpu_stopper_thread smpboot_thread_fn kthread
Fix this by having wq_watchdog_touch() only write to the line if the last time a touch was recorded exceeds 1/4 of the watchdog threshold.
Reported-by: Srikar Dronamraju srikar@linux.vnet.ibm.com Signed-off-by: Nicholas Piggin npiggin@gmail.com Reviewed-by: Paul E. McKenney paulmck@kernel.org Signed-off-by: Tejun Heo tj@kernel.org Signed-off-by: liwei liwei728@huawei.com --- kernel/workqueue.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 4705d6e2c1ac..7224f545e6df 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5910,12 +5910,18 @@ static void wq_watchdog_timer_fn(struct timer_list *unused)
notrace void wq_watchdog_touch(int cpu) { + unsigned long thresh = READ_ONCE(wq_watchdog_thresh) * HZ; + unsigned long touch_ts = READ_ONCE(wq_watchdog_touched); + unsigned long now = jiffies; + if (cpu >= 0) - per_cpu(wq_watchdog_touched_cpu, cpu) = jiffies; + per_cpu(wq_watchdog_touched_cpu, cpu) = now; else WARN_ONCE(1, "%s should be called with valid CPU", __func__);
- wq_watchdog_touched = jiffies; + /* Don't unnecessarily store to global cacheline */ + if (time_after(now, touch_ts + thresh / 4)) + WRITE_ONCE(wq_watchdog_touched, jiffies); }
static void wq_watchdog_set_thresh(unsigned long thresh)