From nobody Sat Jun 13 17:09:10 2026 Received: from www262.sakura.ne.jp (www262.sakura.ne.jp [202.181.97.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7E3B7423A82 for ; Wed, 6 May 2026 11:56:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.181.97.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778068572; cv=none; b=B2+DBvrDvz/Y72lzyh1RSLcAsctZLBlSPWQ9mMbhUM2F4b5tp/VzbK+ISfTd3q/RFE/7QqNc8ZAQqYfAE7MZArW8TzFyzCSruvCl0X1qcZzTmVpLq3kUBQodPttLcc4z2X6jtG918n4oIgs/gzKc6FSkkUHKnig4CvlIyjw+jbs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778068572; c=relaxed/simple; bh=ISED7AGbaHR4bq8jbS+4VffzNW0OsQnducz4cB87Pdo=; h=Message-ID:Date:MIME-Version:Subject:From:To:Cc:References: In-Reply-To:Content-Type; b=RUAVM2fp66jIS1WCdTKNFwXldQb6yBmhFYQoxMRrud7CHwuI4r48Mwuuj3RBvnoXMzARn6W8vVFq02pPkx5OoKpnyzOX3QxdxT18jyMhLWwkh7nKVEfcZ1Yhxnz6nVmIcIT9/1nSxcq1Xq3/2b31hBfTismP7nan/pvjoAFPh+4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=I-love.SAKURA.ne.jp; spf=pass smtp.mailfrom=I-love.SAKURA.ne.jp; arc=none smtp.client-ip=202.181.97.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=I-love.SAKURA.ne.jp Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=I-love.SAKURA.ne.jp Received: from www262.sakura.ne.jp (localhost [127.0.0.1]) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTP id 646BtvdI030003; Wed, 6 May 2026 20:55:57 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Received: from [192.168.1.5] (M106072072000.v4.enabler.ne.jp [106.72.72.0]) (authenticated bits=0) by www262.sakura.ne.jp (8.15.2/8.15.2) with ESMTPSA id 646Btuif029997 (version=TLSv1.2 cipher=AES256-GCM-SHA384 bits=256 verify=NO); Wed, 6 May 2026 20:55:57 +0900 (JST) (envelope-from penguin-kernel@I-love.SAKURA.ne.jp) Message-ID: <1956540a-e458-4a44-854b-11ff07cb1072@I-love.SAKURA.ne.jp> Date: Wed, 6 May 2026 20:55:53 +0900 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: [PATCH v2] kcov: fix state corruption under CONFIG_PREEMPT_RT by eliminating per-cpu data From: Tetsuo Handa To: kasan-dev , LKML Cc: Dmitry Vyukov , Alexander Potapenko References: Content-Language: en-US In-Reply-To: Content-Transfer-Encoding: quoted-printable X-Virus-Status: clean X-Anti-Virus-Server: fsav405.rs.sakura.ne.jp Content-Type: text/plain; charset="utf-8" Problem: In CONFIG_PREEMPT_RT=3Dy kernels, KCOV experiences logical errors and WARNINGs (as reported by syzbot). The root cause is a twofold mismatch between KCOV's design and the RT preemptive model: 1. Reentrancy on the same CPU: KCOV uses per_cpu variables (kcov_percpu_data) to save/restore states and provide a temporary irq_area. In RT kernels, local_lock_irqsave() does not disable preemption. Thus, a task executing KCOV code can be preempted by a threaded Softirq on the same CPU. If the Softirq also triggers KCOV, it overwrites the per_cpu data, corrupting the preempted task's state. 2. Context Confusion: PREEMPT_RT often executes Softirqs within the task_struct context of the currently running task (e.g., a kworker or threaded IRQ handler). Since KCOV relies on in_task() to decide whether to modify current->kcov_mode, it mistakenly modifies the kcov_mode of an unrelated kworker when a Softirq "borrows" its context. Solution: This patch eliminates the use of per_cpu data structures for KCOV when CONFIG_PREEMPT_RT is enabled, moving the necessary state into task_struct. * Task-local Storage: Added kcov_saved_* fields to task_struct under CONFIG_PREEMPT_RT. This ensures that even if a task is preempted during remote coverage collection, its state remains isolated and travels with the task itself. * Eliminate irq_area Sharing: In RT kernels, kcov_remote_start() now always utilizes kcov_remote_area_get() (the remote area pool) instead of the shared per-cpu irq_area. This prevents buffer collision between preempting contexts on the same CPU. * Consistent Context Checks: Replaced unreliable in_task() checks with explicit in_hardirq() || in_nmi() guards in critical paths to ensure KCOV's state machine remains consistent regardless of whether a Softirq is threaded or "borrowing" a task context. * Conditional Lock Removal: Since all data is now task-local in the RT case, the requirement for local_lock is removed for CONFIG_PREEMPT_RT=3Dy, reducing unnecessary locking overhead. This change ensures that KCOV is fully reentrant and safe for the PREEMPT_RT execution model without sacrificing the performance of non-RT kernels. Link: https://syzkaller.appspot.com/bug?extid=3De6686317bd9fe911591a Analyzed-by: AI Mode in Google Search (no mail address) Signed-off-by: Tetsuo Handa Fixes: 5ff3b30ab57d ("kcov: collect coverage from interrupts") --- Only compile tested. I don't have environment to measure how ignoring CONFIG_KCOV_IRQ_AREA_SIZE affects reliability / performance. We might need to enforce CONFIG_KCOV_IRQ_AREA_SIZE for in_serving_softirq() case if remote_arg->area_size < CONFIG_KCOV_IRQ_AREA_SIZE in ioctl(KCOV_REMOTE_ENABLE) request. Please be sure to test this patch using CONFIG_PREEMPT_RT=3Dy and CONFIG_PREEMPT_RT=3Dn kernels in local syzkaller environment before sending upstream. include/linux/sched.h | 10 +++++ kernel/kcov.c | 87 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index 368c7b4d7cb5..2c963f4271d6 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1522,6 +1522,16 @@ struct task_struct { =20 /* Collect coverage from softirq context: */ unsigned int kcov_softirq; + +#ifdef CONFIG_PREEMPT_RT + /* Temporary storage for preempting remote coverage collection: */ + unsigned int kcov_saved_mode; + unsigned int kcov_saved_size; + void *kcov_saved_area; + struct kcov *kcov_saved_kcov; + int kcov_saved_sequence; +#endif + #endif =20 #ifdef CONFIG_MEMCG_V1 diff --git a/kernel/kcov.c b/kernel/kcov.c index 0b369e88c7c9..3178a0e03c3b 100644 --- a/kernel/kcov.c +++ b/kernel/kcov.c @@ -88,6 +88,7 @@ static DEFINE_SPINLOCK(kcov_remote_lock); static DEFINE_HASHTABLE(kcov_remote_map, 4); static struct list_head kcov_remote_areas =3D LIST_HEAD_INIT(kcov_remote_a= reas); =20 +#ifndef CONFIG_PREEMPT_RT struct kcov_percpu_data { void *irq_area; local_lock_t lock; @@ -102,6 +103,7 @@ struct kcov_percpu_data { static DEFINE_PER_CPU(struct kcov_percpu_data, kcov_percpu_data) =3D { .lock =3D INIT_LOCAL_LOCK(lock), }; +#endif =20 /* Must be called with kcov_remote_lock locked. */ static struct kcov_remote *kcov_remote_find(u64 handle) @@ -823,6 +825,44 @@ static inline bool kcov_mode_enabled(unsigned int mode) return (mode & ~KCOV_IN_CTXSW) !=3D KCOV_MODE_DISABLED; } =20 +#ifdef CONFIG_PREEMPT_RT +static inline void kcov_local_lock_irqsave(unsigned long flags) { } +static inline void kcov_local_unlock_irqrestore(unsigned long flags) { } + +static void kcov_remote_softirq_start(struct task_struct *t) +{ + unsigned int mode; + + mode =3D READ_ONCE(t->kcov_mode); + barrier(); + if (kcov_mode_enabled(mode)) { + t->kcov_saved_mode =3D mode; + t->kcov_saved_size =3D t->kcov_size; + t->kcov_saved_area =3D t->kcov_area; + t->kcov_saved_sequence =3D t->kcov_sequence; + t->kcov_saved_kcov =3D t->kcov; + kcov_stop(t); + } +} + +static void kcov_remote_softirq_stop(struct task_struct *t) +{ + if (t->kcov_saved_kcov) { + kcov_start(t, t->kcov_saved_kcov, t->kcov_saved_size, + t->kcov_saved_area, t->kcov_saved_mode, + t->kcov_saved_sequence); + t->kcov_saved_mode =3D 0; + t->kcov_saved_size =3D 0; + t->kcov_saved_area =3D NULL; + t->kcov_saved_sequence =3D 0; + t->kcov_saved_kcov =3D NULL; + } +} + +#else +#define kcov_local_lock_irqsave(flags) local_lock_irqsave(&kcov_percpu_dat= a.lock, flags) +#define kcov_local_unlock_irqrestore(flags) local_unlock_irqrestore(&kcov_= percpu_data.lock, flags) + static void kcov_remote_softirq_start(struct task_struct *t) __must_hold(&kcov_percpu_data.lock) { @@ -858,6 +898,8 @@ static void kcov_remote_softirq_stop(struct task_struct= *t) } } =20 +#endif + void kcov_remote_start(u64 handle) { struct task_struct *t =3D current; @@ -869,12 +911,13 @@ void kcov_remote_start(u64 handle) int sequence; unsigned long flags; =20 - if (WARN_ON(!kcov_check_handle(handle, true, true, true))) + /* Don't use in_task() in order to allow consistent checks in RT kernels.= */ + if (in_hardirq() || in_nmi()) return; - if (!in_task() && !in_softirq_really()) + if (WARN_ON(!kcov_check_handle(handle, true, true, true))) return; =20 - local_lock_irqsave(&kcov_percpu_data.lock, flags); + kcov_local_lock_irqsave(flags); =20 /* * Check that kcov_remote_start() is not called twice in background @@ -882,7 +925,7 @@ void kcov_remote_start(u64 handle) */ mode =3D READ_ONCE(t->kcov_mode); if (WARN_ON(in_task() && kcov_mode_enabled(mode))) { - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); return; } /* @@ -891,7 +934,7 @@ void kcov_remote_start(u64 handle) * happened while collecting coverage from a background thread. */ if (WARN_ON(in_serving_softirq() && t->kcov_softirq)) { - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); return; } =20 @@ -899,11 +942,11 @@ void kcov_remote_start(u64 handle) remote =3D kcov_remote_find(handle); if (!remote) { spin_unlock(&kcov_remote_lock); - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); return; } kcov_debug("handle =3D %llx, context: %s\n", handle, - in_task() ? "task" : "softirq"); + IS_ENABLED(CONFIG_PREEMPT_RT) || in_task() ? "task" : "softirq"); kcov =3D remote->kcov; /* Put in kcov_remote_stop(). */ kcov_get(kcov); @@ -915,6 +958,10 @@ void kcov_remote_start(u64 handle) */ mode =3D context_unsafe(kcov->mode); sequence =3D kcov->sequence; +#ifdef CONFIG_PREEMPT_RT + size =3D kcov->remote_size; + area =3D kcov_remote_area_get(size); +#else if (in_task()) { size =3D kcov->remote_size; area =3D kcov_remote_area_get(size); @@ -922,17 +969,18 @@ void kcov_remote_start(u64 handle) size =3D CONFIG_KCOV_IRQ_AREA_SIZE; area =3D this_cpu_ptr(&kcov_percpu_data)->irq_area; } +#endif spin_unlock(&kcov_remote_lock); =20 - /* Can only happen when in_task(). */ + /* Can only happen when CONFIG_PREEMPT_RT=3Dy or in_task(). */ if (!area) { - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); area =3D vmalloc(size * sizeof(unsigned long)); if (!area) { kcov_put(kcov); return; } - local_lock_irqsave(&kcov_percpu_data.lock, flags); + kcov_local_lock_irqsave(flags); } =20 /* Reset coverage size. */ @@ -944,7 +992,7 @@ void kcov_remote_start(u64 handle) } kcov_start(t, kcov, size, area, mode, sequence); =20 - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); =20 } EXPORT_SYMBOL(kcov_remote_start); @@ -1024,15 +1072,16 @@ void kcov_remote_stop(void) int sequence; unsigned long flags; =20 - if (!in_task() && !in_softirq_really()) + /* Don't use in_task() in order to allow consistent checks in RT kernels.= */ + if (in_hardirq() || in_nmi()) return; =20 - local_lock_irqsave(&kcov_percpu_data.lock, flags); + kcov_local_lock_irqsave(flags); =20 mode =3D READ_ONCE(t->kcov_mode); barrier(); if (!kcov_mode_enabled(mode)) { - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); return; } /* @@ -1040,12 +1089,12 @@ void kcov_remote_stop(void) * actually found the remote handle and started collecting coverage. */ if (in_serving_softirq() && !t->kcov_softirq) { - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); return; } /* Make sure that kcov_softirq is only set when in softirq. */ if (WARN_ON(!in_serving_softirq() && t->kcov_softirq)) { - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); return; } =20 @@ -1069,13 +1118,13 @@ void kcov_remote_stop(void) kcov_move_area(kcov->mode, kcov->area, kcov->size, area); spin_unlock(&kcov->lock); =20 - if (in_task()) { + if (IS_ENABLED(CONFIG_PREEMPT_RT) || in_task()) { spin_lock(&kcov_remote_lock); kcov_remote_area_put(area, size); spin_unlock(&kcov_remote_lock); } =20 - local_unlock_irqrestore(&kcov_percpu_data.lock, flags); + kcov_local_unlock_irqrestore(flags); =20 /* Get in kcov_remote_start(). */ kcov_put(kcov); @@ -1119,6 +1168,7 @@ static void __init selftest(void) =20 static int __init kcov_init(void) { +#ifndef CONFIG_PREEMPT_RT int cpu; =20 for_each_possible_cpu(cpu) { @@ -1128,6 +1178,7 @@ static int __init kcov_init(void) return -ENOMEM; per_cpu_ptr(&kcov_percpu_data, cpu)->irq_area =3D area; } +#endif =20 /* * The kcov debugfs file won't ever get removed and thus, --=20 2.47.3