Re: [PATCH 1/3] kcov: support comparison operands collection

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Sep 7, 2017 at 6:29 PM, Andrea Parri <parri.andrea@xxxxxxxxx> wrote:
> Hi Dmitry,
>
>
> On Aug 30, 2017 6:26 PM, "Dmitry Vyukov" <dvyukov@xxxxxxxxxx> wrote:
>
> From: Victor Chibotaru <tchibo@xxxxxxxxxx>
>
> Enables kcov to collect comparison operands from instrumented code.
> This is done by using Clang's -fsanitize=trace-cmp instrumentation
> (currently not available for GCC).
>
> The comparison operands help a lot in fuzz testing. E.g. they are
> used in Syzkaller to cover the interiors of conditional statements
> with way less attempts and thus make previously unreachable code
> reachable.
>
> To allow separate collection of coverage and comparison operands two
> different work modes are implemented. Mode selection is now done via
> a KCOV_ENABLE ioctl call with corresponding argument value.
>
> Signed-off-by: Victor Chibotaru <tchibo@xxxxxxxxxx>
> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
> Cc: Mark Rutland <mark.rutland@xxxxxxx>
> Cc: Alexander Popov <alex.popov@xxxxxxxxx>
> Cc: Andrey Ryabinin <aryabinin@xxxxxxxxxxxxx>
> Cc: Kees Cook <keescook@xxxxxxxxxxxx>
> Cc: Vegard Nossum <vegard.nossum@xxxxxxxxxx>
> Cc: Quentin Casasnovas <quentin.casasnovas@xxxxxxxxxx>
> Cc: syzkaller@xxxxxxxxxxxxxxxx
> Cc: linux-mm@xxxxxxxxx
> Cc: linux-kernel@xxxxxxxxxxxxxxx
> ---
> Clang instrumentation:
> https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-data-flow
> Syzkaller:
> https://github.com/google/syzkaller
> ---
>  include/linux/kcov.h      |  12 ++-
>  include/uapi/linux/kcov.h |  32 ++++++++
>  kernel/kcov.c             | 203
> ++++++++++++++++++++++++++++++++++++++--------
>  3 files changed, 209 insertions(+), 38 deletions(-)
>
> diff --git a/include/linux/kcov.h b/include/linux/kcov.h
> index 2883ac98c280..87e2a44f1bab 100644
> --- a/include/linux/kcov.h
> +++ b/include/linux/kcov.h
> @@ -7,19 +7,23 @@ struct task_struct;
>
>  #ifdef CONFIG_KCOV
>
> -void kcov_task_init(struct task_struct *t);
> -void kcov_task_exit(struct task_struct *t);
> -
>  enum kcov_mode {
>         /* Coverage collection is not enabled yet. */
>         KCOV_MODE_DISABLED = 0,
> +       /* KCOV was initialized, but tracing mode hasn't been chosen yet. */
> +       KCOV_MODE_INIT = 1,
>         /*
>          * Tracing coverage collection mode.
>          * Covered PCs are collected in a per-task buffer.
>          */
> -       KCOV_MODE_TRACE = 1,
> +       KCOV_MODE_TRACE_PC = 2,
> +       /* Collecting comparison operands mode. */
> +       KCOV_MODE_TRACE_CMP = 3,
>  };
>
> +void kcov_task_init(struct task_struct *t);
> +void kcov_task_exit(struct task_struct *t);
> +
>  #else
>
>  static inline void kcov_task_init(struct task_struct *t) {}
> diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
> index 574e22ec640d..a0bc3e6a5ff7 100644
> --- a/include/uapi/linux/kcov.h
> +++ b/include/uapi/linux/kcov.h
> @@ -7,4 +7,36 @@
>  #define KCOV_ENABLE                    _IO('c', 100)
>  #define KCOV_DISABLE                   _IO('c', 101)
>
> +enum {
> +       /*
> +        * Tracing coverage collection mode.
> +        * Covered PCs are collected in a per-task buffer.
> +        * In new KCOV version the mode is chosen by calling
> +        * ioctl(fd, KCOV_ENABLE, mode). In older versions the mode argument
> +        * was supposed to be 0 in such a call. So, for reasons of backward
> +        * compatibility, we have chosen the value KCOV_TRACE_PC to be 0.
> +        */
> +       KCOV_TRACE_PC = 0,
> +       /* Collecting comparison operands mode. */
> +       KCOV_TRACE_CMP = 1,
> +};
> +
> +/*
> + * Defines the format for the types of collected comparisons.
> + */
> +enum kcov_cmp_type {
> +       /*
> +        * LSB shows whether one of the arguments is a compile-time
> constant.
> +        */
> +       KCOV_CMP_CONST = 1,
> +       /*
> +        * Second and third LSBs contain the size of arguments (1/2/4/8
> bytes).
> +        */
> +       KCOV_CMP_SIZE1 = 0,
> +       KCOV_CMP_SIZE2 = 2,
> +       KCOV_CMP_SIZE4 = 4,
> +       KCOV_CMP_SIZE8 = 6,
> +       KCOV_CMP_SIZE_MASK = 6,
> +};
> +
>  #endif /* _LINUX_KCOV_IOCTLS_H */
> diff --git a/kernel/kcov.c b/kernel/kcov.c
> index cd771993f96f..2abce5dfa2df 100644
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -21,13 +21,21 @@
>  #include <linux/kcov.h>
>  #include <asm/setup.h>
>
> +/* Number of words written per one comparison. */
> +#define KCOV_WORDS_PER_CMP 3
> +
>  /*
>   * kcov descriptor (one per opened debugfs file).
>   * State transitions of the descriptor:
>   *  - initial state after open()
>   *  - then there must be a single ioctl(KCOV_INIT_TRACE) call
>   *  - then, mmap() call (several calls are allowed but not useful)
> - *  - then, repeated enable/disable for a task (only one task a time
> allowed)
> + *  - then, ioctl(KCOV_ENABLE, arg), where arg is
> + *     KCOV_TRACE_PC - to trace only the PCs
> + *     or
> + *     KCOV_TRACE_CMP - to trace only the comparison operands
> + *  - then, ioctl(KCOV_DISABLE) to disable the task.
> + * Enabling/disabling ioctls can be repeated (only one task a time
> allowed).
>   */
>  struct kcov {
>         /*
> @@ -47,6 +55,30 @@ struct kcov {
>         struct task_struct      *t;
>  };
>
> +static bool check_kcov_mode(enum kcov_mode needed_mode, struct task_struct
> *t)
> +{
> +       enum kcov_mode mode;
> +
> +       /*
> +        * We are interested in code coverage as a function of a syscall
> inputs,
> +        * so we ignore code executed in interrupts.
> +        */
> +       if (!t || !in_task())
> +               return false;
> +       mode = READ_ONCE(t->kcov_mode);
> +       /*
> +        * There is some code that runs in interrupts but for which
> +        * in_interrupt() returns false (e.g. preempt_schedule_irq()).
> +        * READ_ONCE()/barrier() effectively provides load-acquire wrt
> +        * interrupts, there are paired barrier()/WRITE_ONCE() in
> +        * kcov_ioctl_locked().
>
>
> Could you elaborate on this: what do you mean by "... provides load-acquire
> _wrt_ interrupts?" and why so?
>
>   Andrea


Without this barrier we can get the following situation.
Consider that ioctl sets t->kcov_mode but does not set t->kcov_area
yet (and/or __sanitizer_cov_trace_pc reads area ahead of mode), then
an interrupt on this task comes but in_task() returns true (observed
for preempt_schedule_irq()). Now the interrupt can decide that tracing
is enabled and write to area (which is NULL) which will cause panic
and crash.
We effectively need an acquire/release pair here, but only between the
task and an interrupt running on this task. So we don't need hardware
barriers, only compiler ordering.




> +        */
> +       barrier();
> +       if (mode != needed_mode)
> +               return false;
> +       return true;
> +}
> +
>  /*
>   * Entry point from instrumented code.
>   * This is called once per basic-block/edge.
> @@ -54,44 +86,136 @@ struct kcov {
>  void notrace __sanitizer_cov_trace_pc(void)
>  {
>         struct task_struct *t;
> -       enum kcov_mode mode;
> +       unsigned long *area;
> +       unsigned long ip = _RET_IP_;
> +       unsigned long pos;
>
>         t = current;
> -       /*
> -        * We are interested in code coverage as a function of a syscall
> inputs,
> -        * so we ignore code executed in interrupts.
> -        */
> -       if (!t || !in_task())
> +       if (!check_kcov_mode(KCOV_MODE_TRACE_PC, t))
>                 return;
> -       mode = READ_ONCE(t->kcov_mode);
> -       if (mode == KCOV_MODE_TRACE) {
> -               unsigned long *area;
> -               unsigned long pos;
> -               unsigned long ip = _RET_IP_;
>
>  #ifdef CONFIG_RANDOMIZE_BASE
> -               ip -= kaslr_offset();
> +       ip -= kaslr_offset();
>  #endif
>
> -               /*
> -                * There is some code that runs in interrupts but for which
> -                * in_interrupt() returns false (e.g.
> preempt_schedule_irq()).
> -                * READ_ONCE()/barrier() effectively provides load-acquire
> wrt
> -                * interrupts, there are paired barrier()/WRITE_ONCE() in
> -                * kcov_ioctl_locked().
> -                */
> -               barrier();
> -               area = t->kcov_area;
> -               /* The first word is number of subsequent PCs. */
> -               pos = READ_ONCE(area[0]) + 1;
> -               if (likely(pos < t->kcov_size)) {
> -                       area[pos] = ip;
> -                       WRITE_ONCE(area[0], pos);
> -               }
> +       area = t->kcov_area;
> +       /* The first word is number of subsequent PCs. */
> +       pos = READ_ONCE(area[0]) + 1;
> +       if (likely(pos < t->kcov_size)) {
> +               area[pos] = ip;
> +               WRITE_ONCE(area[0], pos);
>         }
>  }
>  EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
>
> +#ifdef CONFIG_KCOV_ENABLE_COMPARISONS
> +static void write_comp_data(u64 type, u64 arg1, u64 arg2)
> +{
> +       struct task_struct *t;
> +       u64 *area;
> +       u64 count, start_index, end_pos, max_pos;
> +
> +       t = current;
> +       if (!check_kcov_mode(KCOV_MODE_TRACE_CMP, t))
> +               return;
> +
> +       /*
> +        * We write all comparison arguments and types as u64.
> +        * The buffer was allocated for t->kcov_size unsigned longs.
> +        */
> +       area = (u64 *)t->kcov_area;
> +       max_pos = t->kcov_size * sizeof(unsigned long);
> +
> +       count = READ_ONCE(area[0]);
> +
> +       /* Every record is KCOV_WORDS_PER_CMP words. */
> +       start_index = 1 + count * KCOV_WORDS_PER_CMP;
> +       end_pos = (start_index + KCOV_WORDS_PER_CMP) * sizeof(u64);
> +       if (likely(end_pos <= max_pos)) {
> +               area[start_index] = type;
> +               area[start_index + 1] = arg1;
> +               area[start_index + 2] = arg2;
> +               WRITE_ONCE(area[0], count + 1);
> +       }
> +}
> +
> +void notrace __sanitizer_cov_trace_cmp1(u8 arg1, u8 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE1, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_cmp1);
> +
> +void notrace __sanitizer_cov_trace_cmp2(u16 arg1, u16 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE2, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_cmp2);
> +
> +void notrace __sanitizer_cov_trace_cmp4(u16 arg1, u16 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE4, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_cmp4);
> +
> +void notrace __sanitizer_cov_trace_cmp8(u64 arg1, u64 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE8, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_cmp8);
> +
> +void notrace __sanitizer_cov_trace_const_cmp1(u8 arg1, u8 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE1 | KCOV_CMP_CONST, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp1);
> +
> +void notrace __sanitizer_cov_trace_const_cmp2(u16 arg1, u16 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE2 | KCOV_CMP_CONST, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp2);
> +
> +void notrace __sanitizer_cov_trace_const_cmp4(u16 arg1, u16 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE4 | KCOV_CMP_CONST, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp4);
> +
> +void notrace __sanitizer_cov_trace_const_cmp8(u64 arg1, u64 arg2)
> +{
> +       write_comp_data(KCOV_CMP_SIZE8 | KCOV_CMP_CONST, arg1, arg2);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp8);
> +
> +void notrace __sanitizer_cov_trace_switch(u64 val, u64 *cases)
> +{
> +       u64 i;
> +       u64 count = cases[0];
> +       u64 size = cases[1];
> +       u64 type = KCOV_CMP_CONST;
> +
> +       switch (size) {
> +       case 8:
> +               type |= KCOV_CMP_SIZE1;
> +               break;
> +       case 16:
> +               type |= KCOV_CMP_SIZE2;
> +               break;
> +       case 32:
> +               type |= KCOV_CMP_SIZE4;
> +               break;
> +       case 64:
> +               type |= KCOV_CMP_SIZE8;
> +               break;
> +       default:
> +               return;
> +       }
> +       for (i = 0; i < count; i++)
> +               write_comp_data(type, cases[i + 2], val);
> +}
> +EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
> +#endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
> +
>  static void kcov_get(struct kcov *kcov)
>  {
>         atomic_inc(&kcov->refcount);
> @@ -128,6 +252,7 @@ void kcov_task_exit(struct task_struct *t)
>         /* Just to not leave dangling references behind. */
>         kcov_task_init(t);
>         kcov->t = NULL;
> +       kcov->mode = KCOV_MODE_INIT;
>         spin_unlock(&kcov->lock);
>         kcov_put(kcov);
>  }
> @@ -146,7 +271,7 @@ static int kcov_mmap(struct file *filep, struct
> vm_area_struct *vma)
>
>         spin_lock(&kcov->lock);
>         size = kcov->size * sizeof(unsigned long);
> -       if (kcov->mode == KCOV_MODE_DISABLED || vma->vm_pgoff != 0 ||
> +       if (kcov->mode != KCOV_MODE_INIT || vma->vm_pgoff != 0 ||
>             vma->vm_end - vma->vm_start != size) {
>                 res = -EINVAL;
>                 goto exit;
> @@ -175,6 +300,7 @@ static int kcov_open(struct inode *inode, struct file
> *filep)
>         kcov = kzalloc(sizeof(*kcov), GFP_KERNEL);
>         if (!kcov)
>                 return -ENOMEM;
> +       kcov->mode = KCOV_MODE_DISABLED;
>         atomic_set(&kcov->refcount, 1);
>         spin_lock_init(&kcov->lock);
>         filep->private_data = kcov;
> @@ -210,7 +336,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned
> int cmd,
>                 if (size < 2 || size > INT_MAX / sizeof(unsigned long))
>                         return -EINVAL;
>                 kcov->size = size;
> -               kcov->mode = KCOV_MODE_TRACE;
> +               kcov->mode = KCOV_MODE_INIT;
>                 return 0;
>         case KCOV_ENABLE:
>                 /*
> @@ -220,17 +346,25 @@ static int kcov_ioctl_locked(struct kcov *kcov,
> unsigned int cmd,
>                  * at task exit or voluntary by KCOV_DISABLE. After that it
> can
>                  * be enabled for another task.
>                  */
> -               unused = arg;
> -               if (unused != 0 || kcov->mode == KCOV_MODE_DISABLED ||
> -                   kcov->area == NULL)
> +               if (kcov->mode != KCOV_MODE_INIT || !kcov->area)
>                         return -EINVAL;
>                 if (kcov->t != NULL)
>                         return -EBUSY;
> +               if (arg == KCOV_TRACE_PC)
> +                       kcov->mode = KCOV_MODE_TRACE_PC;
> +               else if (arg == KCOV_TRACE_CMP)
> +#ifdef CONFIG_KCOV_ENABLE_COMPARISONS
> +                       kcov->mode = KCOV_MODE_TRACE_CMP;
> +#else
> +               return -ENOTSUPP;
> +#endif
> +               else
> +                       return -EINVAL;
>                 t = current;
>                 /* Cache in task struct for performance. */
>                 t->kcov_size = kcov->size;
>                 t->kcov_area = kcov->area;
> -               /* See comment in __sanitizer_cov_trace_pc(). */
> +               /* See comment in check_kcov_mode(). */
>                 barrier();
>                 WRITE_ONCE(t->kcov_mode, kcov->mode);
>                 t->kcov = kcov;
> @@ -248,6 +382,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned
> int cmd,
>                         return -EINVAL;
>                 kcov_task_init(t);
>                 kcov->t = NULL;
> +               kcov->mode = KCOV_MODE_INIT;
>                 kcov_put(kcov);
>                 return 0;
>         default:
> --
> 2.14.1.581.gf28d330327-goog
>
>

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@xxxxxxxxx.  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@xxxxxxxxx";> email@xxxxxxxxx </a>



[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]
  Powered by Linux