mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
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:
parent
442082297e
commit
1d9c949369
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user