[PATCH] kcov: report the first spurious PC in the interrupt selftest
From: Karl Mehltretter
Date: Sat Jul 18 2026 - 12:09:32 EST
The KCOV interrupt selftest enables KCOV_MODE_TRACE_PC without a
coverage area so that spurious coverage causes a fault. If the fault
path is instrumented, the coverage callback faults recursively.
Observed failure modes include a stack overflow on x86_64 and arm32
getting stuck in abort handling, neither of which identifies the
original coverage event.
The recursive failure is not new, but commit 9a79524d1420 ("kcov: use
WRITE_ONCE() for selftest mode stores") made the selftest effective on
configurations where the compiler had previously removed the mode
store, exposing it more broadly.
Use a two-word buffer to record the first spurious PC. Once one is
recorded, disable KCOV, report the PC, and panic. This preserves the
selftest's hard failure while avoiding the recursive fault path.
canonicalize_ip() subtracts kaslr_offset() from recorded PCs. Add it
back before printing the PC with %pS.
Fixes: 6cd0dd934b03 ("kcov: Add interrupt handling self test")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Karl Mehltretter <kmehltretter@xxxxxxxxx>
---
This patch improves the failure diagnostics.
With CONFIG_KCOV_SELFTEST, I observed failures under QEMU on arm,
arm64, riscv64, s390x, ppc64le, mips64le, loongarch, and x86_64. The
reported PCs indicate that this likely needs a global KCOV fix rather
than separate exclusions for each architecture.
For example, arm64 defconfig with KCOV and KCOV_SELFTEST reports:
[ 2.741175] kcov: running self test
[ 2.748637] kcov: spurious coverage detected during interrupt selftest: return_address+0x28/0xa8
[ 2.750623] Kernel panic - not syncing: kcov: interrupt selftest detected spurious coverage
[ 2.752147] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.2.0-rc3-...-dirty #25 PREEMPT(full)
kernel/kcov.c | 39 +++++++++++++++++++++++++++++++--------
1 file changed, 31 insertions(+), 8 deletions(-)
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 1df373fb562b..5de2d37fc407 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -1102,9 +1102,11 @@ struct kcov_common_handle_id kcov_common_handle(void)
EXPORT_SYMBOL(kcov_common_handle);
#ifdef CONFIG_KCOV_SELFTEST
+static unsigned long selftest_area[2] __initdata;
+
static void __init selftest(void)
{
- unsigned long start;
+ unsigned long start, ip;
pr_err("running self test\n");
/*
@@ -1114,15 +1116,36 @@ static void __init selftest(void)
* leaks out of that section and leads to spurious coverage.
* It's hard to call the actual interrupt handler directly,
* so we just loop here for a bit waiting for a timer interrupt.
- * We set kcov_mode to enable tracing, but don't setup the area,
- * so any attempt to trace will crash. Note: we must not call any
- * potentially traced functions in this region.
+ * We set up a two-word coverage area rather than leaving it NULL:
+ * a leak then records its PC instead of crashing on a NULL
+ * dereference, and we can report the offending PC. Note: we
+ * must not call any potentially traced functions in this region.
*/
- start = jiffies;
+ current->kcov_size = ARRAY_SIZE(selftest_area);
+ current->kcov_area = selftest_area;
+ barrier();
WRITE_ONCE(current->kcov_mode, KCOV_MODE_TRACE_PC);
- while ((jiffies - start) * MSEC_PER_SEC / HZ < 300)
- ;
- WRITE_ONCE(current->kcov_mode, 0);
+ start = jiffies;
+ while ((jiffies - start) * MSEC_PER_SEC / HZ < 300) {
+ if (READ_ONCE(selftest_area[0]))
+ break;
+ cpu_relax();
+ }
+ WRITE_ONCE(current->kcov_mode, KCOV_MODE_DISABLED);
+ barrier();
+ current->kcov_size = 0;
+ current->kcov_area = NULL;
+
+ if (selftest_area[0]) {
+ /* PCs are recorded with kaslr_offset() subtracted. */
+ ip = selftest_area[1];
+#ifdef CONFIG_RANDOMIZE_BASE
+ ip += kaslr_offset();
+#endif
+ pr_err("spurious coverage detected during interrupt selftest: %pS\n",
+ (void *)ip);
+ panic("kcov: interrupt selftest detected spurious coverage");
+ }
pr_err("done running self test\n");
}
#endif
--
2.53.0