kcov: allow simultaneous KCOV_ENABLE/KCOV_REMOTE_ENABLE

Allow the same userspace thread to simultaneously collect normal coverage
in syscall context (KCOV_ENABLE) and remote coverage of asynchronous work
created by the thread (KCOV_REMOTE_ENABLE).  With this, remote KCOV
coverage becomes useful for generic fuzzing and not just fuzzing of
specific data injection interfaces.

This requires that the task_struct::kcov_* fields are separated into ones
that are used by the task that generates coverage, and ones that are used
by the task that requested remote coverage.  To split this up:

 - Split task_struct::kcov into kcov and kcov_remote. kcov_task_exit() now
   has to clean up both separately.
 - Only use task_struct::kcov_mode on the task that generates coverage.
 - Only reset task_struct::kcov_handle on the task that requested remote
   coverage.

After this change, fields used by the task that generates coverage are:

 - kcov_mode
 - kcov_size
 - kcov_area
 - kcov
 - kcov_sequence
 - kcov_softirq

Fields used by the task that requested remote coverage are:

 - kcov_remote
 - kcov_handle

[jannh@google.com: remove unused constant KCOV_MODE_REMOTE, per Dmitry]
  Link: https://lore.kernel.org/20260515-kcov-simultaneous-remote-v2-1-56fde1cfa509@google.com
[jannh@google.com: update documentation on remote coverage collection]
  Link: https://lore.kernel.org/20260519-kcov-docs-v1-1-5bb22f4cb20c@google.com
[jannh@google.com: move and reword sentence on simultaneous normal/remote collection
  Link: https://lore.kernel.org/20260520-kcov-docs-v2-1-819f78778763@google.com
Link: https://lore.kernel.org/20260505-kcov-simultaneous-remote-v1-1-a670ba7cefd2@google.com
Signed-off-by: Jann Horn <jannh@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Andrey Konovalov <andreyknvl@gmail.com>
Cc: Marco Elver <elver@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Jann Horn 2026-05-05 11:00:46 +02:00 committed by Andrew Morton
parent 442082297e
commit 1d9c949369
4 changed files with 61 additions and 44 deletions

View File

@ -237,6 +237,9 @@ Both ``kcov_remote_start`` and ``kcov_remote_stop`` annotations and the
collection sections. The way a handle is used depends on the context where the
matching code section executes.
A thread can use two separate KCOV instances to collect remote coverage and
normal coverage at the same time.
KCOV supports collecting remote coverage from the following contexts:
1. Global kernel background tasks. These are the tasks that are spawned during
@ -262,6 +265,9 @@ gets saved to the ``kcov_handle`` field in the current ``task_struct`` and
needs to be passed to the newly spawned local tasks via custom kernel code
modifications. Those tasks should in turn use the passed handle in their
``kcov_remote_start`` and ``kcov_remote_stop`` annotations.
In the kernel, common handles are wrapped in a ``kcov_common_handle_id``, which
consumes no space in builds without ``CONFIG_KCOV``; subsystems that integrate
with this mechanism should not need to use any ``#ifdef CONFIG_KCOV`` or such.
KCOV follows a predefined format for both global and common handles. Each
handle is a ``u64`` integer. Currently, only the one top and the lower 4 bytes

View File

@ -21,8 +21,6 @@ enum kcov_mode {
KCOV_MODE_TRACE_PC = 2,
/* Collecting comparison operands mode. */
KCOV_MODE_TRACE_CMP = 3,
/* The process owns a KCOV remote reference. */
KCOV_MODE_REMOTE = 4,
};
#define KCOV_IN_CTXSW (1 << 30)

View File

@ -1517,6 +1517,9 @@ struct task_struct {
/* KCOV descriptor wired with this task or NULL: */
struct kcov *kcov;
/* KCOV descriptor for remote coverage collection from other tasks: */
struct kcov *kcov_remote;
/* KCOV common handle for remote coverage collection: */
u64 kcov_handle;

View File

@ -368,6 +368,7 @@ static void kcov_start(struct task_struct *t, struct kcov *kcov,
WRITE_ONCE(t->kcov_mode, mode);
}
/* operates on coverage-generator-owned fields */
static void kcov_stop(struct task_struct *t)
{
WRITE_ONCE(t->kcov_mode, KCOV_MODE_DISABLED);
@ -377,16 +378,17 @@ static void kcov_stop(struct task_struct *t)
t->kcov_area = NULL;
}
/* operates on coverage-generator-owned fields */
static void kcov_task_reset(struct task_struct *t)
{
kcov_stop(t);
t->kcov_sequence = 0;
t->kcov_handle = 0;
}
void kcov_task_init(struct task_struct *t)
{
kcov_task_reset(t);
t->kcov_remote = NULL;
t->kcov_handle = current->kcov_handle;
}
@ -423,11 +425,14 @@ static void kcov_remote_reset(struct kcov *kcov)
static void kcov_disable(struct task_struct *t, struct kcov *kcov)
__must_hold(&kcov->lock)
{
kcov_task_reset(t);
if (kcov->remote)
if (kcov->remote) {
t->kcov_handle = 0;
t->kcov_remote = NULL;
kcov_remote_reset(kcov);
else
} else {
kcov_task_reset(t);
kcov_reset(kcov);
}
}
static void kcov_get(struct kcov *kcov)
@ -453,41 +458,47 @@ void kcov_task_exit(struct task_struct *t)
unsigned long flags;
kcov = t->kcov;
if (kcov == NULL)
return;
spin_lock_irqsave(&kcov->lock, flags);
kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);
/*
* For KCOV_ENABLE devices we want to make sure that t->kcov->t == t,
* which comes down to:
* WARN_ON(!kcov->remote && kcov->t != t);
*
* For KCOV_REMOTE_ENABLE devices, the exiting task is either:
*
* 1. A remote task between kcov_remote_start() and kcov_remote_stop().
* In this case we should print a warning right away, since a task
* shouldn't be exiting when it's in a kcov coverage collection
* section. Here t points to the task that is collecting remote
* coverage, and t->kcov->t points to the thread that created the
* kcov device. Which means that to detect this case we need to
* check that t != t->kcov->t, and this gives us the following:
* WARN_ON(kcov->remote && kcov->t != t);
*
* 2. The task that created kcov exiting without calling KCOV_DISABLE,
* and then again we make sure that t->kcov->t == t:
* WARN_ON(kcov->remote && kcov->t != t);
*
* By combining all three checks into one we get:
*/
if (WARN_ON(kcov->t != t)) {
if (kcov) {
spin_lock_irqsave(&kcov->lock, flags);
kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);
/*
* This could be a remote task between kcov_remote_start() and
* kcov_remote_stop().
* In this case we should print a warning right away, since a
* task shouldn't be exiting when it's in a kcov coverage
* collection section.
*
* Otherwise, this should be a task that created a local
* kcov instance and hasn't called KCOV_DISABLE.
* Make sure that t->kcov->t is consistent.
*/
if (WARN_ON(kcov->remote) || WARN_ON(kcov->t != t)) {
spin_unlock_irqrestore(&kcov->lock, flags);
return;
}
/* Just to not leave dangling references behind. */
kcov_disable(t, kcov);
spin_unlock_irqrestore(&kcov->lock, flags);
return;
kcov_put(kcov);
}
kcov = t->kcov_remote;
if (kcov) {
spin_lock_irqsave(&kcov->lock, flags);
kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);
/*
* This is a KCOV_REMOTE_ENABLE device, and the task is the
* user task which has requested remote coverage collection.
* Make sure that t->kcov->t is consistent.
*/
if (WARN_ON(!kcov->remote) || WARN_ON(kcov->t != t)) {
spin_unlock_irqrestore(&kcov->lock, flags);
return;
}
/* Just to not leave dangling references behind. */
kcov_disable(t, kcov);
spin_unlock_irqrestore(&kcov->lock, flags);
kcov_put(kcov);
}
/* Just to not leave dangling references behind. */
kcov_disable(t, kcov);
spin_unlock_irqrestore(&kcov->lock, flags);
kcov_put(kcov);
}
static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
@ -629,9 +640,9 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
case KCOV_DISABLE:
/* Disable coverage for the current task. */
unused = arg;
if (unused != 0 || current->kcov != kcov)
return -EINVAL;
t = current;
if (unused != 0 || (kcov != t->kcov && kcov != t->kcov_remote))
return -EINVAL;
if (WARN_ON(kcov->t != t))
return -EINVAL;
kcov_disable(t, kcov);
@ -641,7 +652,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
if (kcov->mode != KCOV_MODE_INIT || !kcov->area)
return -EINVAL;
t = current;
if (kcov->t != NULL || t->kcov != NULL)
if (kcov->t != NULL || t->kcov_remote != NULL)
return -EBUSY;
remote_arg = (struct kcov_remote_arg *)arg;
mode = kcov_get_mode(remote_arg->trace_mode);
@ -651,8 +662,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
LONG_MAX / sizeof(unsigned long))
return -EINVAL;
kcov->mode = mode;
t->kcov = kcov;
t->kcov_mode = KCOV_MODE_REMOTE;
t->kcov_remote = kcov;
kcov->t = t;
kcov->remote = true;
kcov->remote_size = remote_arg->area_size;