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)