mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage
@ 2026-11-07 13:17 Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 1/6] kcov: Use unsigned int for kcov_start() mode parameter Karl Mehltretter
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-11-07 13:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Karl Mehltretter, Andrey Konovalov, Alexander Potapenko,
	Dmitry Vyukov, Marco Elver, Bradley Morgan, Anna-Maria Behnsen,
	Frederic Weisbecker, Thomas Gleixner, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Sebastian Andrzej Siewior, Clark Williams,
	linux-rt-devel, kasan-dev, linux-kernel

This series reduces the executions syzkaller spends on rechecking
spurious timer and scheduler coverage. On a Raspberry Pi 400 with
PREEMPT_RT, triage dropped from 26-34% of all executions to 10%, and
20-minute runs ended with a 26-82% larger corpus at equal execution
counts.

Documentation/dev-tools/kcov.rst says that KCOV aims at stable coverage
that is a function of syscall inputs, and that it does not collect
coverage from non-deterministic parts of the kernel such as the
scheduler. kernel/sched/ is built without KCOV instrumentation, but code
that it calls is instrumented, for example sched_clock() and the CPU
capacity helpers. Deferred hrtimer rearm runs instrumented code in task
context on interrupt return. All of that is recorded for whichever task
is in the kernel at that moment. Identical syscalls then report
different coverage, and the fuzzer re-runs programs to tell the spurious
part from real new coverage.

The code involved also runs on behalf of real syscalls, for example the
hrtimer and timekeeping ones, so building it without KCOV would lose
wanted coverage. Add a nestable KCOV_PAUSED bit and a kcov_pause guard
instead. Take the guard with kcov_pause_scope() in deferred hrtimer
rearm, __schedule(), try_to_wake_up() and wake_up_new_task(). With
KCOV=n the pause calls expand to no-ops.

Measurements from v4 on mainline 551c722f4080 (before commits
feb5c9a2447a ("softirq: Preserve interrupt context on return from
interrupt") and a9cd14bfb0c1 ("arm64: irq: exclude the softirq stack
switch from KCOV")), with syzkaller 29fd395f4ab6, every run from an
empty corpus. Each row summarizes three pairs of runs, except the last,
which summarizes five. Ranges show the minimum and maximum across pairs.
Corpus size and coverage (covered PCs) are compared at the execution
count of the kernel that executed fewer programs. The triage column is
the relative change in the share of executions spent on triage, for
example from 13.5% to 6% in the third row.

                            executions corpus    coverage  triage
  Pi 400 native, RT, 20 min 37-81k     +26..82%  +18..45%  -71..-63%
  Pi 500+ KVM, RT, 20 min   126-189k   +11..12%  +4..8%    -59..-52%
  Pi 500+ KVM, RT, 60 min   391-455k   +5..10%   -1..+1%   -58..-53%
  Pi 500+ KVM, 60 min       339-470k   +0..2%    -3..+4%   -25..-16%
  x86-64 QEMU, RT, 3 h      6-32k      +40..81%  +15..21%  -71..-59%
  x86-64 QEMU, 3 h          15-52k     +29..74%  +8..20%   -75..-68%

The gain is largest early in a run and narrows as coverage saturates.
After one hour on the Pi 500+ with PREEMPT_RT the number of covered PCs
is within -1% to +1%, the corpus is 5-10% larger and the share of
executions spent on triage is still roughly halved.

The Pi rows are real hardware. "Native" means that the board fuzzes its
own kernel, without a hypervisor. arm64 has no deferred hrtimer rearm,
so the Pi rows measure patches 4-6 only. The x86-64 rows are emulated
with QEMU TCG and -icount shift=0, at HZ=100. x86-64 fuzzing has not
been measured at real speed.

The effect also shows without a fuzzer. Of 1,000 identical fork() calls
on the Pi 400 with PREEMPT_RT (v4 on 551c722f4080), 218-233 still
produce new syzkaller signal on the base kernel and 45-53 with this
series. On the v5 base (d48723711bae) under x86-64 KVM, 10,000 pipe
wakeup round-trips produce new signal 11-12 times on the base kernel
(17 with PREEMPT_RT) and 4 times with this series (9 with PREEMPT_RT).

The spurious coverage also breaks KCOV_SELFTEST. On the v5 base
(d48723711bae) with KCOV_SELFTEST=y on x86-64 and arm64 defconfig, with
and without PREEMPT_RT, the base kernel failed the selftest in 5/5 boots
per configuration under QEMU TCG (4/5 on arm64 without PREEMPT_RT) and
10/10 under x86-64 KVM. With v5 it passed in 10/10 boots per
configuration under TCG and 20/20 under KVM.

Known limits:
 - Without PREEMPT_RT, arm64 showed no clear corpus improvement in the
   v4 runs above, which predated commits feb5c9a2447a and a9cd14bfb0c1.
 - RISC-V showed no clear improvement from this series alone, because
   its trap entry code records into task coverage on every interrupt,
   and KCOV_SELFTEST still fails there. A prototype fix looks promising.
   I will post it separately after more testing.

Changes in v5:
 - Move DEFINE_LOCK_GUARD_0(kcov_pause, ...) inside #ifdef CONFIG_KCOV
   behind a kcov_pause_scope() macro, and map
   hrtimer_rearm_deferred_kcov_paused() directly to
   __hrtimer_rearm_deferred() when CONFIG_KCOV=n, so non-KCOV builds do
   not emit guard types, constructors or cleanup variables in
   preprocessed output (Andrew Morton).
 - Update patch 5's commit message for commit feb5c9a2447a.
 - Drop the removed Assisted-by tag from the commits (commit
   1e035297ffda).
 - Rebase onto current mainline (d48723711bae).

Other v5 testing:
 - KCOV=n: kernel/sched/core.i and kernel/time/hrtimer.i contain no
   kcov_pause declarations, and in kernel/, the .text and .sched.text of
   sched/core.o, time/hrtimer.o, softirq.o, entry/common.o and fork.o
   are identical to the base.
 - Pause/resume transition tests on x86-64 and arm64 (with and without
   PREEMPT_RT): nested kcov_pause_scope() guards under KCOV_TRACE_PC and
   KCOV_TRACE_CMP, context switching while paused, and remote softirq
   coverage interrupting a paused task.
 - The affected objects build with KCOV on ARM32, arm64, RISC-V32,
   RISC-V64, s390 and LoongArch64, and on x86-64 with GCC and Clang.
   With PREEMPT_RT: x86-64, ARM32, arm64, RISC-V64 and LoongArch64.

An LLM assisted with the patches and testing.

v4:
    https://lore.kernel.org/r/20261005185803.39170-1-kmehltretter@gmail.com

Karl Mehltretter (6):
  kcov: Use unsigned int for kcov_start() mode parameter
  kcov: Add a kcov_pause guard
  hrtimer: Pause KCOV during deferred rearm
  sched/core: Pause KCOV in __schedule()
  sched/core: Pause KCOV in try_to_wake_up()
  sched/core: Pause KCOV in wake_up_new_task()

 include/linux/hrtimer_rearm.h | 23 +++++++++-
 include/linux/kcov.h          | 80 +++++++++++++++++++++++++++++++----
 kernel/kcov.c                 |  7 +--
 kernel/sched/core.c           | 10 ++++-
 4 files changed, 102 insertions(+), 18 deletions(-)

Range-diff:
1:  d11d5a628248 ! 1:  d1b5c6a641c6 kcov: Use unsigned int for kcov_start() mode parameter
    @@ Commit message
         Type the parameter unsigned int, like the field and the saved copy. No
         functional change.
     
    -    Assisted-by: LLM
         Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
         Reviewed-by: Alexander Potapenko <glider@google.com>
2:  d94ce0944f95 ! 2:  8addfc05f54c kcov: Add a kcov_pause guard
    @@ Commit message
         The shared setter no longer sets KCOV_IN_CTXSW on a disabled task,
         since its mode already fails the coverage callbacks' exact comparison.
     
    -    Provide a kcov_pause guard backed by flag helpers shared with context
    -    switch suppression. The helpers access kcov_mode with READ_ONCE() and
    -    WRITE_ONCE() and use compiler barriers to keep instrumented calls inside
    -    the suppressed region. The guard saves the previous pause state and
    -    restores it at scope exit, so sections nest. When KCOV is enabled for
    -    current, remote softirq sections save and restore the complete mode,
    -    preserving the pause state.
    +    Provide a kcov_pause guard and kcov_pause_scope() macro backed by flag
    +    helpers shared with context switch suppression. The helpers access
    +    kcov_mode with READ_ONCE() and WRITE_ONCE() and use compiler barriers
    +    to keep instrumented calls inside the suppressed region. The guard
    +    saves the previous pause state and restores it at scope exit, so
    +    sections nest. When KCOV is enabled for current, remote softirq
    +    sections save and restore the complete mode, preserving the pause
    +    state. With CONFIG_KCOV=n, kcov_pause_scope() expands to a no-op
    +    without defining the guard type or cleanup variable.
     
         With CONFIG_KCOV=y, restoring a previously clear flag still writes
         kcov_mode even when task coverage is disabled; nested guards also take
    @@ Commit message
         instrumentation because inlining does not remove the caller's own coverage
         callbacks.
     
    -    Assisted-by: LLM
         Suggested-by: Alexander Potapenko <glider@google.com>
         Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
     
      ## include/linux/kcov.h ##
    -@@
    - #ifndef _LINUX_KCOV_H
    - #define _LINUX_KCOV_H
    +@@ include/linux/kcov.h: struct task_struct;
    + 
    + #ifdef CONFIG_KCOV
      
     +#include <linux/bits.h>
     +#include <linux/cleanup.h>
    - #include <linux/sched.h>
    - #include <uapi/linux/kcov.h>
    - 
    ++
    + enum kcov_mode {
    + 	/* Coverage collection is not enabled yet. */
    + 	KCOV_MODE_DISABLED = 0,
     @@ include/linux/kcov.h: enum kcov_mode {
      	KCOV_MODE_TRACE_CMP = 3,
      };
    @@ include/linux/kcov.h: enum kcov_mode {
      
      /* See Documentation/dev-tools/kcov.rst for usage details. */
      void kcov_remote_start(u64 handle);
    -@@ include/linux/kcov.h: void __sanitizer_cov_trace_switch(kcov_u64 val, void *cases);
    +@@ include/linux/kcov.h: void __sanitizer_cov_trace_const_cmp4(u32 arg1, u32 arg2);
    + void __sanitizer_cov_trace_const_cmp8(kcov_u64 arg1, kcov_u64 arg2);
    + void __sanitizer_cov_trace_switch(kcov_u64 val, void *cases);
      
    - static inline void kcov_task_init(struct task_struct *t) {}
    - static inline void kcov_task_exit(struct task_struct *t) {}
    -+static inline unsigned int __kcov_pause(void) { return 0; }
    -+static inline void __kcov_resume(unsigned int paused) {}
    - static inline void kcov_prepare_switch(struct task_struct *t) {}
    - static inline void kcov_finish_switch(struct task_struct *t) {}
    - static inline void kcov_remote_start(u64 handle) {}
    -@@ include/linux/kcov.h: static inline void kcov_remote_start_usb_softirq(u64 id) {}
    - static inline void kcov_remote_stop_softirq(void) {}
    - 
    - #endif /* CONFIG_KCOV */
    -+
     +/*
     + * Scope-based KCOV pause:
     + *
    -+ *	guard(kcov_pause)();
    ++ *	kcov_pause_scope();
     + *
     + * pauses coverage for current until the end of the scope. Callers must be
     + * built without KCOV instrumentation.
    @@ include/linux/kcov.h: static inline void kcov_remote_start_usb_softirq(u64 id) {
     +		    _T->paused = __kcov_pause(),
     +		    __kcov_resume(_T->paused),
     +		    unsigned int paused)
    ++#define kcov_pause_scope()	guard(kcov_pause)()
     +
    + #else
    + 
    + static inline void kcov_task_init(struct task_struct *t) {}
    +@@ include/linux/kcov.h: static inline void kcov_remote_start_common(struct kcov_common_handle_id id) {}
    + static inline void kcov_remote_start_usb(u64 id) {}
    + static inline void kcov_remote_start_usb_softirq(u64 id) {}
    + static inline void kcov_remote_stop_softirq(void) {}
    ++#define kcov_pause_scope()	do { } while (0)
    + 
    + #endif /* CONFIG_KCOV */
      #endif /* _LINUX_KCOV_H */
     
      ## kernel/kcov.c ##
3:  b251939de11f ! 3:  1e93271df5d3 hrtimer: Pause KCOV during deferred rearm
    @@ Commit message
         Excluding the involved files instead would cost their coverage on real
         task-context paths, e.g. the hrtimer and timekeeping syscalls.
     
    -    Take the kcov_pause guard in an __always_inline wrapper around
    -    __hrtimer_rearm_deferred(). Use it at all call sites, including
    -    hrtick_schedule_exit(). Callers that may run with KCOV enabled for current
    -    are built without KCOV instrumentation or marked noinstr. HAVE_NOINSTR_HACK
    -    covers pre-GCC-12 x86. The other affected architectures restrict KCOV to
    -    GCC 12 or Clang through ARCH_WANTS_NO_INSTR. This avoids relying on
    -    __no_sanitize_coverage, which is empty before GCC 12. Tested with GCC 8.1
    -    and 15 on x86_64.
    +    Pause KCOV with kcov_pause_scope() in an __always_inline wrapper around
    +    __hrtimer_rearm_deferred(), and map the wrapper directly to
    +    __hrtimer_rearm_deferred() when CONFIG_KCOV=n. Use it at all call sites,
    +    including hrtick_schedule_exit(). Callers that may run with KCOV enabled
    +    for current are built without KCOV instrumentation or marked noinstr.
    +    HAVE_NOINSTR_HACK covers pre-GCC-12 x86. The other affected architectures
    +    restrict KCOV to GCC 12 or Clang through ARCH_WANTS_NO_INSTR. This avoids
    +    relying on __no_sanitize_coverage, which is empty before GCC 12. Tested
    +    with GCC 8.1 and 15 on x86_64.
     
         A later patch also pauses __schedule(); keeping hrtick_schedule_exit()
         guarded here makes the deferred-rearm fix independent of that scheduler
         change.
     
         Fixes: 15dd3a948855 ("hrtimer: Push reprogramming timers into the interrupt return path")
    -    Assisted-by: LLM
         Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
     
      ## include/linux/hrtimer_rearm.h ##
     @@
      
    - #ifdef CONFIG_HRTIMER_REARM_DEFERRED
    - #include <linux/irqflags.h>
    -+#include <linux/kcov.h>
    - #include <linux/lockdep.h>
    - #include <linux/preempt.h>
    - #include <linux/thread_info.h>
    - 
      void __hrtimer_rearm_deferred(void);
      
    ++#ifdef CONFIG_KCOV
    ++#include <linux/kcov.h>
    ++
     +/*
     + * KCOV: Pause outside __hrtimer_rearm_deferred() to suppress entry coverage.
     + * Callers with KCOV enabled for current must be built without KCOV
    @@ include/linux/hrtimer_rearm.h
     + */
     +static __always_inline void hrtimer_rearm_deferred_kcov_paused(void)
     +{
    -+	guard(kcov_pause)();
    ++	kcov_pause_scope();
     +
     +	__hrtimer_rearm_deferred();
     +}
    ++#else
    ++#define hrtimer_rearm_deferred_kcov_paused()	__hrtimer_rearm_deferred()
    ++#endif
     +
      /*
       * This is purely CPU local, so check the TIF bit first to avoid the overhead of
    @@ include/linux/hrtimer_rearm.h: hrtimer_rearm_deferred_user_irq(unsigned long *ti
     -		__hrtimer_rearm_deferred();
     +		hrtimer_rearm_deferred_kcov_paused();
      		/* Don't go into the loop if HRTIMER_REARM was the only flag */
    - 		*tif_work &= ~TIF_HRTIMER_REARM;
    + 		*tif_work &= ~_TIF_HRTIMER_REARM;
      		return !*tif_work;
     @@ include/linux/hrtimer_rearm.h: hrtimer_rearm_deferred_user_irq(unsigned long *tif_work, const unsigned long tif
      static __always_inline void hrtimer_rearm_deferred_tif(unsigned long tif_work)
    @@ include/linux/hrtimer_rearm.h: static __always_inline bool hrtimer_test_and_clea
      
      #else  /* CONFIG_HRTIMER_REARM_DEFERRED */
      static __always_inline void __hrtimer_rearm_deferred(void) { }
    -+static __always_inline void hrtimer_rearm_deferred_kcov_paused(void) { }
    ++#define hrtimer_rearm_deferred_kcov_paused()	__hrtimer_rearm_deferred()
      static __always_inline void hrtimer_rearm_deferred(void) { }
      static __always_inline void hrtimer_rearm_deferred_tif(unsigned long tif_work) { }
      static __always_inline bool
4:  8910122b4d6d ! 4:  07e20f89a5fb sched/core: Pause KCOV in __schedule()
    @@ Commit message
         KCOV_PAUSED remains set while a task is switched out. The guard in its
         resumed __schedule() frame restores the prior state.
     
    -    Assisted-by: LLM
         Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
     
      ## kernel/sched/core.c ##
     @@ kernel/sched/core.c: static void __sched notrace __schedule(int sched_mode)
      	int cpu;
      
     +	/* Callees instrumented with KCOV would leak coverage into current. */
    -+	guard(kcov_pause)();
    ++	kcov_pause_scope();
     +
      	/* Trace preemptions consistently with task switches */
      	trace_sched_entry_tp(sched_mode == SM_PREEMPT);
5:  5bb61ae630f7 ! 5:  ee6862d0659c sched/core: Pause KCOV in try_to_wake_up()
    @@ Commit message
         helpers instrumented with KCOV, such as kthread_is_per_cpu(), CPU
         capacity helpers and SCHED_HRTICK arming. They can record into current
         while in_task() is true.
     
    -    CONFIG_KCOV_SELFTEST exposes this under PREEMPT_RT. The interrupt
    -    selftest fails on x86_64 in kthread_is_per_cpu(): RT runs the timer
    -    softirq in a thread, so the wakeup runs in task context during the
    -    selftest's spin. The same helpers leak into non-RT syscall wakeups such
    -    as a pipe write waking a reader.
    +    During task-context wakeups such as a pipe write waking a reader, these
    +    helpers record coverage that depends on the target task and runqueue
    +    state into the waking task. Before commit feb5c9a2447a ("softirq:
    +    Preserve interrupt context on return from interrupt"), the IRQ-exit
    +    ktimersd wakeup under PREEMPT_RT also ran in task context and failed
    +    CONFIG_KCOV_SELFTEST in kthread_is_per_cpu().
     
    -    Pause the wakeup body with the kcov_pause guard. Take it before the
    +    Pause the wakeup body with kcov_pause_scope(). Take it before the
         preemption guard so preemption is re-enabled while KCOV remains paused.
         Wrapping only select_task_rq() would miss SCHED_HRTICK arming during
         enqueue.
     
    -    Assisted-by: LLM
         Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
     
      ## kernel/sched/core.c ##
     @@ kernel/sched/core.c: bool ttwu_state_match(struct task_struct *p, unsigned int s
       int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
       {
      +	/* Callees instrumented with KCOV would leak coverage into current. */
     -+	guard(kcov_pause)();
     ++	kcov_pause_scope();
       	guard(preempt)();
       	int cpu, success = 0;
      
6:  7c13bc0e78d5 ! 6:  95936d470e3d sched/core: Pause KCOV in wake_up_new_task()
    @@ Commit message
         The paths depend on runqueue and CPU state, so coverage varies between
         identical forks. Pause KCOV for the whole function, extending the
         scheduler exclusion to new-task wakeups.
     
    -    Assisted-by: LLM
         Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
     
      ## kernel/sched/core.c ##
     @@ kernel/sched/core.c: void wake_up_new_task(struct task_struct *p)
      	int wake_flags = WF_FORK;
      
     +	/* Callees instrumented with KCOV would leak coverage into current. */
    -+	guard(kcov_pause)();
    ++	kcov_pause_scope();
     +
      	raw_spin_lock_irqsave(&p->pi_lock, rf.flags);
      	WRITE_ONCE(p->__state, TASK_RUNNING);

base-commit: d48723711bae9b8cdc56719c448ba42fef28cbee
-- 
2.39.5

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 1/6] kcov: Use unsigned int for kcov_start() mode parameter
  2026-11-07 13:17 [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage Karl Mehltretter
@ 2026-11-07 13:17 ` Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 2/6] kcov: Add a kcov_pause guard Karl Mehltretter
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-11-07 13:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Karl Mehltretter, Andrey Konovalov, Alexander Potapenko,
	Dmitry Vyukov, Marco Elver, Bradley Morgan, Anna-Maria Behnsen,
	Frederic Weisbecker, Thomas Gleixner, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Sebastian Andrzej Siewior, Clark Williams,
	linux-rt-devel, kasan-dev, linux-kernel

task_struct::kcov_mode usually holds an enum kcov_mode value, but it can
also carry flag bits such as KCOV_IN_CTXSW, so the field is unsigned int.

kcov_remote_softirq_stop() passes the saved raw value through
kcov_start()'s enum kcov_mode parameter before kcov_start() stores it
back into the unsigned int field. The parameter type therefore does not
match the values it receives.

Type the parameter unsigned int, like the field and the saved copy. No
functional change.

Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
Reviewed-by: Alexander Potapenko <glider@google.com>
---
 kernel/kcov.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/kcov.c b/kernel/kcov.c
index 9402227bd7ba..7f106182cf1b 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -368,7 +368,7 @@ EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
 #endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
 
 static void kcov_start(struct task_struct *t, struct kcov *kcov,
-			unsigned int size, void *area, enum kcov_mode mode,
+			unsigned int size, void *area, unsigned int mode,
 			int sequence)
 {
 	kcov_debug("t = %px, size = %u, area = %px\n", t, size, area);
-- 
2.39.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 2/6] kcov: Add a kcov_pause guard
  2026-11-07 13:17 [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 1/6] kcov: Use unsigned int for kcov_start() mode parameter Karl Mehltretter
@ 2026-11-07 13:17 ` Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 3/6] hrtimer: Pause KCOV during deferred rearm Karl Mehltretter
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-11-07 13:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Karl Mehltretter, Andrey Konovalov, Alexander Potapenko,
	Dmitry Vyukov, Marco Elver, Bradley Morgan, Anna-Maria Behnsen,
	Frederic Weisbecker, Thomas Gleixner, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Sebastian Andrzej Siewior, Clark Williams,
	linux-rt-devel, kasan-dev, linux-kernel

Interrupt-return work can run after HARDIRQ_OFFSET is dropped, when
in_task() is true. KCOV then attributes instrumented callees to the
interrupted task.

Add a KCOV_PAUSED bit next to KCOV_IN_CTXSW and mask both in
kcov_mode_enabled(). The coverage callbacks need no new check because
check_kcov_mode()'s exact comparison rejects modes with KCOV_PAUSED set.

The context switch suppression keeps its own bit: kcov_prepare_switch()
runs on the previous task and kcov_finish_switch() on the one switched
in, so its lifetime is not a pause section.

The shared setter no longer sets KCOV_IN_CTXSW on a disabled task,
since its mode already fails the coverage callbacks' exact comparison.

Provide a kcov_pause guard and kcov_pause_scope() macro backed by flag
helpers shared with context switch suppression. The helpers access
kcov_mode with READ_ONCE() and WRITE_ONCE() and use compiler barriers
to keep instrumented calls inside the suppressed region. The guard
saves the previous pause state and restores it at scope exit, so
sections nest. When KCOV is enabled for current, remote softirq
sections save and restore the complete mode, preserving the pause
state. With CONFIG_KCOV=n, kcov_pause_scope() expands to a no-op
without defining the guard type or cleanup variable.

With CONFIG_KCOV=y, restoring a previously clear flag still writes
kcov_mode even when task coverage is disabled; nested guards also take
that path in this case.

The helpers are __always_inline. Guard users must be built without KCOV
instrumentation because inlining does not remove the caller's own coverage
callbacks.

Suggested-by: Alexander Potapenko <glider@google.com>
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
 include/linux/kcov.h | 80 +++++++++++++++++++++++++++++++++++++++-----
 kernel/kcov.c        |  5 ---
 2 files changed, 71 insertions(+), 14 deletions(-)

diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index 895b761b2db1..7f3c34071f85 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -9,6 +9,9 @@ struct task_struct;
 
 #ifdef CONFIG_KCOV
 
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+
 enum kcov_mode {
 	/* Coverage collection is not enabled yet. */
 	KCOV_MODE_DISABLED = 0,
@@ -23,20 +26,64 @@ enum kcov_mode {
 	KCOV_MODE_TRACE_CMP = 3,
 };
 
-#define KCOV_IN_CTXSW	(1 << 30)
+#define KCOV_IN_CTXSW	BIT(30)
+#define KCOV_PAUSED	BIT(29)
+
+static __always_inline bool kcov_mode_enabled(unsigned int mode)
+{
+	return (mode & ~(KCOV_IN_CTXSW | KCOV_PAUSED)) != KCOV_MODE_DISABLED;
+}
 
 void kcov_task_init(struct task_struct *t);
 void kcov_task_exit(struct task_struct *t);
 
-#define kcov_prepare_switch(t)			\
-do {						\
-	(t)->kcov_mode |= KCOV_IN_CTXSW;	\
-} while (0)
+static __always_inline unsigned int
+__kcov_set_flag(struct task_struct *t, unsigned int flag)
+{
+	unsigned int mode = READ_ONCE(t->kcov_mode);
+	unsigned int prev_flag = mode & flag;
+
+	if (!prev_flag && kcov_mode_enabled(mode)) {
+		WRITE_ONCE(t->kcov_mode, mode | flag);
+		barrier();
+	}
+	return prev_flag;
+}
 
-#define kcov_finish_switch(t)			\
-do {						\
-	(t)->kcov_mode &= ~KCOV_IN_CTXSW;	\
-} while (0)
+static __always_inline void
+__kcov_restore_flag(struct task_struct *t, unsigned int flag,
+		    unsigned int prev_flag)
+{
+	if (!prev_flag) {
+		barrier();
+		WRITE_ONCE(t->kcov_mode, READ_ONCE(t->kcov_mode) & ~flag);
+	}
+}
+
+static __always_inline void kcov_prepare_switch(struct task_struct *t)
+{
+	__kcov_set_flag(t, KCOV_IN_CTXSW);
+}
+
+static __always_inline void kcov_finish_switch(struct task_struct *t)
+{
+	__kcov_restore_flag(t, KCOV_IN_CTXSW, 0);
+}
+
+/*
+ * Pause coverage for current. Callers must be built without KCOV
+ * instrumentation.
+ * Pass the returned state to __kcov_resume().
+ */
+static __always_inline unsigned int __kcov_pause(void)
+{
+	return __kcov_set_flag(current, KCOV_PAUSED);
+}
+
+static __always_inline void __kcov_resume(unsigned int paused)
+{
+	__kcov_restore_flag(current, KCOV_PAUSED, paused);
+}
 
 /* See Documentation/dev-tools/kcov.rst for usage details. */
 void kcov_remote_start(u64 handle);
@@ -89,6 +136,20 @@ void __sanitizer_cov_trace_const_cmp4(u32 arg1, u32 arg2);
 void __sanitizer_cov_trace_const_cmp8(kcov_u64 arg1, kcov_u64 arg2);
 void __sanitizer_cov_trace_switch(kcov_u64 val, void *cases);
 
+/*
+ * Scope-based KCOV pause:
+ *
+ *	kcov_pause_scope();
+ *
+ * pauses coverage for current until the end of the scope. Callers must be
+ * built without KCOV instrumentation.
+ */
+DEFINE_LOCK_GUARD_0(kcov_pause,
+		    _T->paused = __kcov_pause(),
+		    __kcov_resume(_T->paused),
+		    unsigned int paused)
+#define kcov_pause_scope()	guard(kcov_pause)()
+
 #else
 
 static inline void kcov_task_init(struct task_struct *t) {}
@@ -105,6 +166,7 @@ static inline void kcov_remote_start_common(struct kcov_common_handle_id id) {}
 static inline void kcov_remote_start_usb(u64 id) {}
 static inline void kcov_remote_start_usb_softirq(u64 id) {}
 static inline void kcov_remote_stop_softirq(void) {}
+#define kcov_pause_scope()	do { } while (0)
 
 #endif /* CONFIG_KCOV */
 #endif /* _LINUX_KCOV_H */
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 7f106182cf1b..1d741a16d5ca 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -848,11 +848,6 @@ static const struct file_operations kcov_fops = {
  * collecting coverage and copies all collected coverage into the kcov area.
  */
 
-static inline bool kcov_mode_enabled(unsigned int mode)
-{
-	return (mode & ~KCOV_IN_CTXSW) != KCOV_MODE_DISABLED;
-}
-
 static void kcov_remote_softirq_start(struct task_struct *t)
 	__must_hold(&kcov_percpu_data.lock)
 {
-- 
2.39.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 3/6] hrtimer: Pause KCOV during deferred rearm
  2026-11-07 13:17 [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 1/6] kcov: Use unsigned int for kcov_start() mode parameter Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 2/6] kcov: Add a kcov_pause guard Karl Mehltretter
@ 2026-11-07 13:17 ` Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 4/6] sched/core: Pause KCOV in __schedule() Karl Mehltretter
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-11-07 13:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Karl Mehltretter, Andrey Konovalov, Alexander Potapenko,
	Dmitry Vyukov, Marco Elver, Bradley Morgan, Anna-Maria Behnsen,
	Frederic Weisbecker, Thomas Gleixner, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Sebastian Andrzej Siewior, Clark Williams,
	linux-rt-devel, kasan-dev, linux-kernel

Deferred hrtimer rearm can run after HARDIRQ_OFFSET is dropped. in_task()
is then true, so KCOV attributes the instrumented timer-reprogramming
subtree to current.

With CONFIG_KCOV_SELFTEST added to x86_64 defconfig, the interrupt
selftest fails under QEMU, detecting spurious coverage in
__hrtimer_rearm_deferred(). The same happens on s390, RISC-V and LoongArch,
which also enable HRTIMER_REARM_DEFERRED.

Excluding the involved files instead would cost their coverage on real
task-context paths, e.g. the hrtimer and timekeeping syscalls.

Pause KCOV with kcov_pause_scope() in an __always_inline wrapper around
__hrtimer_rearm_deferred(), and map the wrapper directly to
__hrtimer_rearm_deferred() when CONFIG_KCOV=n. Use it at all call sites,
including hrtick_schedule_exit(). Callers that may run with KCOV enabled
for current are built without KCOV instrumentation or marked noinstr.
HAVE_NOINSTR_HACK covers pre-GCC-12 x86. The other affected architectures
restrict KCOV to GCC 12 or Clang through ARCH_WANTS_NO_INSTR. This avoids
relying on __no_sanitize_coverage, which is empty before GCC 12. Tested
with GCC 8.1 and 15 on x86_64.

A later patch also pauses __schedule(); keeping hrtick_schedule_exit()
guarded here makes the deferred-rearm fix independent of that scheduler
change.

Fixes: 15dd3a948855 ("hrtimer: Push reprogramming timers into the interrupt return path")
Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
This patch depends on patch 2, "kcov: Add a kcov_pause guard".

 include/linux/hrtimer_rearm.h | 23 +++++++++++++++++++++--
 kernel/sched/core.c           |  2 +-
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/include/linux/hrtimer_rearm.h b/include/linux/hrtimer_rearm.h
index 12e42721c8e0..b0fd7aaff538 100644
--- a/include/linux/hrtimer_rearm.h
+++ b/include/linux/hrtimer_rearm.h
@@ -12,6 +12,24 @@
 
 void __hrtimer_rearm_deferred(void);
 
+#ifdef CONFIG_KCOV
+#include <linux/kcov.h>
+
+/*
+ * KCOV: Pause outside __hrtimer_rearm_deferred() to suppress entry coverage.
+ * Callers with KCOV enabled for current must be built without KCOV
+ * instrumentation.
+ */
+static __always_inline void hrtimer_rearm_deferred_kcov_paused(void)
+{
+	kcov_pause_scope();
+
+	__hrtimer_rearm_deferred();
+}
+#else
+#define hrtimer_rearm_deferred_kcov_paused()	__hrtimer_rearm_deferred()
+#endif
+
 /*
  * This is purely CPU local, so check the TIF bit first to avoid the overhead of
  * the atomic test_and_clear_bit() operation for the common case where the bit
@@ -43,7 +61,7 @@ hrtimer_rearm_deferred_user_irq(unsigned long *tif_work, const unsigned long tif
 	 */
 	if (unlikely((*tif_work & TIF_REARM_MASK) == _TIF_HRTIMER_REARM)) {
 		clear_thread_flag(TIF_HRTIMER_REARM);
-		__hrtimer_rearm_deferred();
+		hrtimer_rearm_deferred_kcov_paused();
 		/* Don't go into the loop if HRTIMER_REARM was the only flag */
 		*tif_work &= ~_TIF_HRTIMER_REARM;
 		return !*tif_work;
@@ -55,7 +73,7 @@ hrtimer_rearm_deferred_user_irq(unsigned long *tif_work, const unsigned long tif
 static __always_inline void hrtimer_rearm_deferred_tif(unsigned long tif_work)
 {
 	if (hrtimer_test_and_clear_rearm_deferred_tif(tif_work))
-		__hrtimer_rearm_deferred();
+		hrtimer_rearm_deferred_kcov_paused();
 }
 
 /*
@@ -78,6 +96,7 @@ static __always_inline bool hrtimer_test_and_clear_rearm_deferred(void)
 
 #else  /* CONFIG_HRTIMER_REARM_DEFERRED */
 static __always_inline void __hrtimer_rearm_deferred(void) { }
+#define hrtimer_rearm_deferred_kcov_paused()	__hrtimer_rearm_deferred()
 static __always_inline void hrtimer_rearm_deferred(void) { }
 static __always_inline void hrtimer_rearm_deferred_tif(unsigned long tif_work) { }
 static __always_inline bool
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 04b62c16fa8c..3f13170c2939 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -1020,7 +1020,7 @@ static inline void hrtick_schedule_exit(struct rq *rq)
 	}
 
 	if (rq->hrtick_sched & HRTICK_SCHED_REARM_HRTIMER)
-		__hrtimer_rearm_deferred();
+		hrtimer_rearm_deferred_kcov_paused();
 
 	rq->hrtick_sched = HRTICK_SCHED_NONE;
 }
-- 
2.39.5

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 4/6] sched/core: Pause KCOV in __schedule()
  2026-11-07 13:17 [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage Karl Mehltretter
                   ` (2 preceding siblings ...)
  2026-11-07 13:17 ` [PATCH v5 3/6] hrtimer: Pause KCOV during deferred rearm Karl Mehltretter
@ 2026-11-07 13:17 ` Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 5/6] sched/core: Pause KCOV in try_to_wake_up() Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 6/6] sched/core: Pause KCOV in wake_up_new_task() Karl Mehltretter
  5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-11-07 13:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Karl Mehltretter, Andrey Konovalov, Alexander Potapenko,
	Dmitry Vyukov, Marco Elver, Bradley Morgan, Anna-Maria Behnsen,
	Frederic Weisbecker, Thomas Gleixner, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Sebastian Andrzej Siewior, Clark Williams,
	linux-rt-devel, kasan-dev, linux-kernel

kernel/sched/ is built without KCOV instrumentation, but it calls code
that is instrumented with KCOV, such as sched_clock(), architecture
CPU-capacity helpers and profile_hits().

During preemption and schedule() calls, these callees can add
nondeterministic scheduler coverage to current.

With CONFIG_KCOV_SELFTEST added to x86_64 defconfig, the interrupt
selftest fails under QEMU, detecting spurious coverage in
arch_scale_cpu_capacity().

Annotating each callee would spread exclusions across architectures.
Pause across __schedule() instead, extending the scheduler exclusion to
its callees.

KCOV_PAUSED remains set while a task is switched out. The guard in its
resumed __schedule() frame restores the prior state.

Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
 kernel/sched/core.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 3f13170c2939..1691eb19f31a 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -7219,6 +7219,9 @@ static void __sched notrace __schedule(int sched_mode)
 	struct rq *rq;
 	int cpu;
 
+	/* Callees instrumented with KCOV would leak coverage into current. */
+	kcov_pause_scope();
+
 	/* Trace preemptions consistently with task switches */
 	trace_sched_entry_tp(sched_mode == SM_PREEMPT);
 
-- 
2.39.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 5/6] sched/core: Pause KCOV in try_to_wake_up()
  2026-11-07 13:17 [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage Karl Mehltretter
                   ` (3 preceding siblings ...)
  2026-11-07 13:17 ` [PATCH v5 4/6] sched/core: Pause KCOV in __schedule() Karl Mehltretter
@ 2026-11-07 13:17 ` Karl Mehltretter
  2026-11-07 13:17 ` [PATCH v5 6/6] sched/core: Pause KCOV in wake_up_new_task() Karl Mehltretter
  5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-11-07 13:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Karl Mehltretter, Andrey Konovalov, Alexander Potapenko,
	Dmitry Vyukov, Marco Elver, Bradley Morgan, Anna-Maria Behnsen,
	Frederic Weisbecker, Thomas Gleixner, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Sebastian Andrzej Siewior, Clark Williams,
	linux-rt-devel, kasan-dev, linux-kernel

try_to_wake_up() is built without KCOV instrumentation, but it calls
helpers instrumented with KCOV, such as kthread_is_per_cpu(), CPU
capacity helpers and SCHED_HRTICK arming. They can record into current
while in_task() is true.

During task-context wakeups such as a pipe write waking a reader, these
helpers record coverage that depends on the target task and runqueue
state into the waking task. Before commit feb5c9a2447a ("softirq:
Preserve interrupt context on return from interrupt"), the IRQ-exit
ktimersd wakeup under PREEMPT_RT also ran in task context and failed
CONFIG_KCOV_SELFTEST in kthread_is_per_cpu().

Pause the wakeup body with kcov_pause_scope(). Take it before the
preemption guard so preemption is re-enabled while KCOV remains paused.
Wrapping only select_task_rq() would miss SCHED_HRTICK arming during
enqueue.

Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
 kernel/sched/core.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 1691eb19f31a..4bd0d5736ccf 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4323,6 +4323,8 @@ bool ttwu_state_match(struct task_struct *p, unsigned int state, int *success)
  */
 int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
 {
+	/* Callees instrumented with KCOV would leak coverage into current. */
+	kcov_pause_scope();
 	guard(preempt)();
 	int cpu, success = 0;
 
-- 
2.39.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v5 6/6] sched/core: Pause KCOV in wake_up_new_task()
  2026-11-07 13:17 [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage Karl Mehltretter
                   ` (4 preceding siblings ...)
  2026-11-07 13:17 ` [PATCH v5 5/6] sched/core: Pause KCOV in try_to_wake_up() Karl Mehltretter
@ 2026-11-07 13:17 ` Karl Mehltretter
  5 siblings, 0 replies; 7+ messages in thread
From: Karl Mehltretter @ 2026-11-07 13:17 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Karl Mehltretter, Andrey Konovalov, Alexander Potapenko,
	Dmitry Vyukov, Marco Elver, Bradley Morgan, Anna-Maria Behnsen,
	Frederic Weisbecker, Thomas Gleixner, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Sebastian Andrzej Siewior, Clark Williams,
	linux-rt-devel, kasan-dev, linux-kernel

wake_up_new_task() is built without KCOV instrumentation, but CPU
selection and enqueue call helpers instrumented with KCOV. During a
KCOV-enabled fork, they can record scheduler, hrtimer and clockevent
coverage into the parent.

The paths depend on runqueue and CPU state, so coverage varies between
identical forks. Pause KCOV for the whole function, extending the
scheduler exclusion to new-task wakeups.

Signed-off-by: Karl Mehltretter <kmehltretter@gmail.com>
---
 kernel/sched/core.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 4bd0d5736ccf..a63bd78ce5d7 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -5019,6 +5019,9 @@ void wake_up_new_task(struct task_struct *p)
 	struct rq *rq;
 	int wake_flags = WF_FORK;
 
+	/* Callees instrumented with KCOV would leak coverage into current. */
+	kcov_pause_scope();
+
 	raw_spin_lock_irqsave(&p->pi_lock, rf.flags);
 	WRITE_ONCE(p->__state, TASK_RUNNING);
 	/*
-- 
2.39.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-11-07 13:17 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-07 13:17 [PATCH v5 0/6] kcov: Keep timer and scheduler noise out of task coverage Karl Mehltretter
2026-11-07 13:17 ` [PATCH v5 1/6] kcov: Use unsigned int for kcov_start() mode parameter Karl Mehltretter
2026-11-07 13:17 ` [PATCH v5 2/6] kcov: Add a kcov_pause guard Karl Mehltretter
2026-11-07 13:17 ` [PATCH v5 3/6] hrtimer: Pause KCOV during deferred rearm Karl Mehltretter
2026-11-07 13:17 ` [PATCH v5 4/6] sched/core: Pause KCOV in __schedule() Karl Mehltretter
2026-11-07 13:17 ` [PATCH v5 5/6] sched/core: Pause KCOV in try_to_wake_up() Karl Mehltretter
2026-11-07 13:17 ` [PATCH v5 6/6] sched/core: Pause KCOV in wake_up_new_task() Karl Mehltretter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®