Commit 17581aa1 authored by Aleksandr Nogikh's avatar Aleksandr Nogikh Committed by Linus Torvalds

kcov: split ioctl handling into locked and unlocked parts

Patch series "kcov: improve mmap processing", v3.

Subsequent mmaps of the same kcov descriptor currently do not update the
virtual memory of the task and yet return 0 (success).  This is
counter-intuitive and may lead to unexpected memory access errors.

Also, this unnecessarily limits the functionality of kcov to only the
simplest usage scenarios.  Kcov instances are effectively forever attached
to their first address spaces and it becomes impossible to e.g.  reuse the
same kcov handle in forked child processes without mmapping the memory
first.  This is exactly what we tried to do in syzkaller and inadvertently
came upon this behavior.

This patch series addresses the problem described above.

This patch (of 3):

Currently all ioctls are de facto processed under a spinlock in order to
serialise them.  This, however, prohibits the use of vmalloc and other
memory management functions in the implementations of those ioctls,
unnecessary complicating any further changes to the code.

Let all ioctls first be processed inside the kcov_ioctl() function which
should execute the ones that are not compatible with spinlock and then
pass control to kcov_ioctl_locked() for all other ones.
KCOV_REMOTE_ENABLE is processed both in kcov_ioctl() and
kcov_ioctl_locked() as the steps are easily separable.

Although it is still compatible with a spinlock, move KCOV_INIT_TRACE
handling to kcov_ioctl(), so that the changes from the next commit are
easier to follow.

Link: https://lkml.kernel.org/r/20220117153634.150357-1-nogikh@google.com
Link: https://lkml.kernel.org/r/20220117153634.150357-2-nogikh@google.comSigned-off-by: default avatarAleksandr Nogikh <nogikh@google.com>
Reviewed-by: default avatarDmitry Vyukov <dvyukov@google.com>
Reviewed-by: default avatarAndrey Konovalov <andreyknvl@gmail.com>
Cc: Marco Elver <elver@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Taras Madan <tarasmadan@google.com>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent f953f140
...@@ -564,31 +564,12 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd, ...@@ -564,31 +564,12 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
unsigned long arg) unsigned long arg)
{ {
struct task_struct *t; struct task_struct *t;
unsigned long size, unused; unsigned long flags, unused;
int mode, i; int mode, i;
struct kcov_remote_arg *remote_arg; struct kcov_remote_arg *remote_arg;
struct kcov_remote *remote; struct kcov_remote *remote;
unsigned long flags;
switch (cmd) { switch (cmd) {
case KCOV_INIT_TRACE:
/*
* Enable kcov in trace mode and setup buffer size.
* Must happen before anything else.
*/
if (kcov->mode != KCOV_MODE_DISABLED)
return -EBUSY;
/*
* Size must be at least 2 to hold current position and one PC.
* Later we allocate size * sizeof(unsigned long) memory,
* that must not overflow.
*/
size = arg;
if (size < 2 || size > INT_MAX / sizeof(unsigned long))
return -EINVAL;
kcov->size = size;
kcov->mode = KCOV_MODE_INIT;
return 0;
case KCOV_ENABLE: case KCOV_ENABLE:
/* /*
* Enable coverage for the current task. * Enable coverage for the current task.
...@@ -692,9 +673,32 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) ...@@ -692,9 +673,32 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
struct kcov_remote_arg *remote_arg = NULL; struct kcov_remote_arg *remote_arg = NULL;
unsigned int remote_num_handles; unsigned int remote_num_handles;
unsigned long remote_arg_size; unsigned long remote_arg_size;
unsigned long flags; unsigned long size, flags;
if (cmd == KCOV_REMOTE_ENABLE) { kcov = filep->private_data;
switch (cmd) {
case KCOV_INIT_TRACE:
/*
* Enable kcov in trace mode and setup buffer size.
* Must happen before anything else.
*
* First check the size argument - it must be at least 2
* to hold the current position and one PC. Later we allocate
* size * sizeof(unsigned long) memory, that must not overflow.
*/
size = arg;
if (size < 2 || size > INT_MAX / sizeof(unsigned long))
return -EINVAL;
spin_lock_irqsave(&kcov->lock, flags);
if (kcov->mode != KCOV_MODE_DISABLED) {
spin_unlock_irqrestore(&kcov->lock, flags);
return -EBUSY;
}
kcov->size = size;
kcov->mode = KCOV_MODE_INIT;
spin_unlock_irqrestore(&kcov->lock, flags);
return 0;
case KCOV_REMOTE_ENABLE:
if (get_user(remote_num_handles, (unsigned __user *)(arg + if (get_user(remote_num_handles, (unsigned __user *)(arg +
offsetof(struct kcov_remote_arg, num_handles)))) offsetof(struct kcov_remote_arg, num_handles))))
return -EFAULT; return -EFAULT;
...@@ -710,16 +714,18 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) ...@@ -710,16 +714,18 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
return -EINVAL; return -EINVAL;
} }
arg = (unsigned long)remote_arg; arg = (unsigned long)remote_arg;
fallthrough;
default:
/*
* All other commands can be normally executed under a spin lock, so we
* obtain and release it here in order to simplify kcov_ioctl_locked().
*/
spin_lock_irqsave(&kcov->lock, flags);
res = kcov_ioctl_locked(kcov, cmd, arg);
spin_unlock_irqrestore(&kcov->lock, flags);
kfree(remote_arg);
return res;
} }
kcov = filep->private_data;
spin_lock_irqsave(&kcov->lock, flags);
res = kcov_ioctl_locked(kcov, cmd, arg);
spin_unlock_irqrestore(&kcov->lock, flags);
kfree(remote_arg);
return res;
} }
static const struct file_operations kcov_fops = { static const struct file_operations kcov_fops = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment