Messages in this thread Patch in this message |  | | Date | Tue, 5 May 2026 00:30:14 +0900 | | From | Tetsuo Handa <> | | Subject | [PATCH (draft)] kcov: fix potential kcov_mode corruption under CONFIG_PREEMPT_RT |
| |
Problem Description:
syzbot has reported intermittent kcov_mode corruption when running with CONFIG_PREEMPT_RT=y. Specifically, struct task_struct->kcov_mode is found to be unexpectedly modified (e.g., changed from 0 to 2) during workqueue execution, even when the work handler itself does not use KCOV [1].
Root Cause Analysis:
The issue stems from the ambiguity of in_task() under CONFIG_PREEMPT_RT. Since commit 5ff3b30ab57d ("kcov: collect coverage from interrupts"), kcov_remote_start() uses in_task() to decide whether to use a per-CPU preallocated buffer (irq_area) or a task-specific buffer (allocated via vmalloc).
In a CONFIG_PREEMPT_RT kernel, in_task() returns true even for threaded softirqs (e.g., ksoftirqd or threaded IRQ handlers), unless they are explicitly within a local_bh_disable() region at the time of the check. This leads to several critical issues:
1. Context Mismatch: A threaded interrupt handler (like usb_giveback_urb_bh) might be identified as a "task" context. If it starts a remote KCOV session, it incorrectly modifies the kworker's task_struct->kcov_mode using the logic intended for system calls.
2. State Leakage: If the result of in_task() differs between kcov_remote_start() and kcov_remote_stop() due to the subtle state changes in RT scheduling, the cleanup code (kcov_stop()) might be skipped or applied to the wrong context. This leaves kcov_mode set to a non-zero value (e.g., KCOV_MODE_TRACE_CMP) when the worker returns to process_one_work().
3. Evidence: Debug logs show the pr_err from process_one_work() carrying a [ C1] (CPU-based) prefix instead of a [Txxx] (Task-based) prefix. This confirms that at the moment of the error, in_serving_softirq() was true, causing in_task() to be false, which contradicts the state when the work started.
Proposed Fix:
Introduce a more strict context check, in_task_really(), which explicitly excludes any softirq context (including threaded ones) by checking in_serving_softirq() regardless of the CONFIG_PREEMPT_RT configuration. This ensures that threaded interrupts always use the irq_area path and do not corrupt the task_struct state of the worker threads they happen to preempt.
I wrote this patch's body, but patch description above was generated using Google AI mode. I can't tell whether above explanation is correct. Therefore, please review but don't accept yet. For now:
Link: https://syzkaller.appspot.com/bug?extid=a21650c1666eae7b2aae [1] Analyzed-by: https://share.google/aimode/5aL6wtYxiS3koWnAx (expires in 7 days) Not-yet-signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> Fixes: 5ff3b30ab57d ("kcov: collect coverage from interrupts") Reported-by: syzbot+3f51ad7ac3ae57a6fdcc@syzkaller.appspotmail.com Maybe-closes: https://syzkaller.appspot.com/bug?extid=3f51ad7ac3ae57a6fdcc --- kernel/kcov.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/kernel/kcov.c b/kernel/kcov.c index 0b369e88c7c9..c20d3c2712d7 100644 --- a/kernel/kcov.c +++ b/kernel/kcov.c @@ -171,6 +171,15 @@ static __always_inline bool in_softirq_really(void) return in_serving_softirq() && !in_hardirq() && !in_nmi(); } +static __always_inline bool in_task_really(void) +{ +#ifdef CONFIG_PREEMPT_RT + return !in_serving_softirq() && !in_hardirq() && !in_nmi(); +#else + return in_task(); +#endif +} + static notrace bool check_kcov_mode(enum kcov_mode needed_mode, struct task_struct *t) { unsigned int mode; @@ -903,7 +912,7 @@ void kcov_remote_start(u64 handle) return; } kcov_debug("handle = %llx, context: %s\n", handle, - in_task() ? "task" : "softirq"); + in_task_really() ? "task" : "softirq"); kcov = remote->kcov; /* Put in kcov_remote_stop(). */ kcov_get(kcov); @@ -915,7 +924,7 @@ void kcov_remote_start(u64 handle) */ mode = context_unsafe(kcov->mode); sequence = kcov->sequence; - if (in_task()) { + if (in_task_really()) { size = kcov->remote_size; area = kcov_remote_area_get(size); } else { @@ -924,7 +933,7 @@ void kcov_remote_start(u64 handle) } spin_unlock(&kcov_remote_lock); - /* Can only happen when in_task(). */ + /* Can only happen when in_task_really(). */ if (!area) { local_unlock_irqrestore(&kcov_percpu_data.lock, flags); area = vmalloc(size * sizeof(unsigned long)); @@ -1069,7 +1078,7 @@ void kcov_remote_stop(void) kcov_move_area(kcov->mode, kcov->area, kcov->size, area); spin_unlock(&kcov->lock); - if (in_task()) { + if (in_task_really()) { spin_lock(&kcov_remote_lock); kcov_remote_area_put(area, size); spin_unlock(&kcov_remote_lock); -- 2.47.3
|  |