Skip to content

Commit 17581aa

Browse files
a-nogikhtorvalds
authored andcommitted
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/[email protected] Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Aleksandr Nogikh <[email protected]> Reviewed-by: Dmitry Vyukov <[email protected]> Reviewed-by: Andrey Konovalov <[email protected]> Cc: Marco Elver <[email protected]> Cc: Alexander Potapenko <[email protected]> Cc: Taras Madan <[email protected]> Cc: Sebastian Andrzej Siewior <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent f953f14 commit 17581aa

File tree

1 file changed

+37
-31
lines changed

1 file changed

+37
-31
lines changed

kernel/kcov.c

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -564,31 +564,12 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
564564
unsigned long arg)
565565
{
566566
struct task_struct *t;
567-
unsigned long size, unused;
567+
unsigned long flags, unused;
568568
int mode, i;
569569
struct kcov_remote_arg *remote_arg;
570570
struct kcov_remote *remote;
571-
unsigned long flags;
572571

573572
switch (cmd) {
574-
case KCOV_INIT_TRACE:
575-
/*
576-
* Enable kcov in trace mode and setup buffer size.
577-
* Must happen before anything else.
578-
*/
579-
if (kcov->mode != KCOV_MODE_DISABLED)
580-
return -EBUSY;
581-
/*
582-
* Size must be at least 2 to hold current position and one PC.
583-
* Later we allocate size * sizeof(unsigned long) memory,
584-
* that must not overflow.
585-
*/
586-
size = arg;
587-
if (size < 2 || size > INT_MAX / sizeof(unsigned long))
588-
return -EINVAL;
589-
kcov->size = size;
590-
kcov->mode = KCOV_MODE_INIT;
591-
return 0;
592573
case KCOV_ENABLE:
593574
/*
594575
* Enable coverage for the current task.
@@ -692,9 +673,32 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
692673
struct kcov_remote_arg *remote_arg = NULL;
693674
unsigned int remote_num_handles;
694675
unsigned long remote_arg_size;
695-
unsigned long flags;
676+
unsigned long size, flags;
696677

697-
if (cmd == KCOV_REMOTE_ENABLE) {
678+
kcov = filep->private_data;
679+
switch (cmd) {
680+
case KCOV_INIT_TRACE:
681+
/*
682+
* Enable kcov in trace mode and setup buffer size.
683+
* Must happen before anything else.
684+
*
685+
* First check the size argument - it must be at least 2
686+
* to hold the current position and one PC. Later we allocate
687+
* size * sizeof(unsigned long) memory, that must not overflow.
688+
*/
689+
size = arg;
690+
if (size < 2 || size > INT_MAX / sizeof(unsigned long))
691+
return -EINVAL;
692+
spin_lock_irqsave(&kcov->lock, flags);
693+
if (kcov->mode != KCOV_MODE_DISABLED) {
694+
spin_unlock_irqrestore(&kcov->lock, flags);
695+
return -EBUSY;
696+
}
697+
kcov->size = size;
698+
kcov->mode = KCOV_MODE_INIT;
699+
spin_unlock_irqrestore(&kcov->lock, flags);
700+
return 0;
701+
case KCOV_REMOTE_ENABLE:
698702
if (get_user(remote_num_handles, (unsigned __user *)(arg +
699703
offsetof(struct kcov_remote_arg, num_handles))))
700704
return -EFAULT;
@@ -710,16 +714,18 @@ static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
710714
return -EINVAL;
711715
}
712716
arg = (unsigned long)remote_arg;
717+
fallthrough;
718+
default:
719+
/*
720+
* All other commands can be normally executed under a spin lock, so we
721+
* obtain and release it here in order to simplify kcov_ioctl_locked().
722+
*/
723+
spin_lock_irqsave(&kcov->lock, flags);
724+
res = kcov_ioctl_locked(kcov, cmd, arg);
725+
spin_unlock_irqrestore(&kcov->lock, flags);
726+
kfree(remote_arg);
727+
return res;
713728
}
714-
715-
kcov = filep->private_data;
716-
spin_lock_irqsave(&kcov->lock, flags);
717-
res = kcov_ioctl_locked(kcov, cmd, arg);
718-
spin_unlock_irqrestore(&kcov->lock, flags);
719-
720-
kfree(remote_arg);
721-
722-
return res;
723729
}
724730

725731
static const struct file_operations kcov_fops = {

0 commit comments

Comments
 (0)