linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] kcov: add AFL-style tracing
@ 2016-05-21 16:07 Vegard Nossum
  2016-05-29 16:51 ` Dmitry Vyukov
  0 siblings, 1 reply; 8+ messages in thread
From: Vegard Nossum @ 2016-05-21 16:07 UTC (permalink / raw)
  To: Dmitry Vyukov
  Cc: syzkaller, linux-kernel, Quentin Casasnovas, Michal Zalewski,
	Kees Cook, Vegard Nossum

From: Quentin Casasnovas <quentin.casasnovas@oracle.com>

AFL uses a fixed-size buffer (typically 64 KiB) where each byte is
a counter representing how many times an A -> B branch was taken.
Of course, since the buffer is fixed size, it's a little imprecise
in that e.g. two different branches could map to the same counter,
but in practice it works well.

See afl:docs/technical_details.txt for more information.

Here is a small test program that demonstrates the new capability:

	#include <sys/ioctl.h>
	#include <sys/mman.h>
	#include <sys/types.h>

	#include <errno.h>
	#include <error.h>
	#include <fcntl.h>
	#include <stdio.h>
	#include <stdlib.h>
	#include <unistd.h>

	#include <linux/types.h>

	#define KCOV_INIT_TRACE                 _IOR('c', 1, unsigned long)
	#define KCOV_INIT_AFL                   _IOR('c', 2, unsigned long)
	#define KCOV_ENABLE                     _IO('c', 100)
	#define KCOV_DISABLE                    _IO('c', 101)

	int main(int argc, char *argv[])
	{
		int fd = open("/sys/kernel/debug/kcov", O_RDWR);
		if (fd == -1)
			error(1, errno, "open()");

		unsigned long size = 1 << 10;
		if (ioctl(fd, KCOV_INIT_AFL, size) != 0)
			error(1, errno, "ioctl(KCOV_INIT_AFL)");

		void *mem = mmap(NULL, size * sizeof(long), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
		if (mem == MAP_FAILED)
			error(1, errno, "mmap()");

		/* Start kernel instrumentation */
		if (ioctl(fd, KCOV_ENABLE, 0) != 0)
			error(1, errno, "ioctl(KCOV_ENABLE)");

		printf("Hello world!\n");

		/* End kernel instrumentation*/
		if (ioctl(fd, KCOV_DISABLE, 0) != 0)
			error(1, errno, "ioctl(KCOV_DISABLE)");

		/* Hex dump of memory area */
		unsigned char *mem2 = mem;
		for (unsigned int i = 0; i < size; ++i) {
			printf("%02x ", mem2[i]);
			if (i % 32 == 31)
				printf("\n");
		}

		close(fd);
		return 0;
	}

This patch is a collaboration between Quentin Casasnovas and Vegard Nossum.

Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Michal Zalewski <lcamtuf@gmail.com>
Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
---
 include/linux/kcov.h      |  6 ++++++
 include/uapi/linux/kcov.h |  1 +
 kernel/kcov.c             | 44 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 51 insertions(+)

diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index 2883ac9..cb8eb3e 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -18,6 +18,12 @@ enum kcov_mode {
 	 * Covered PCs are collected in a per-task buffer.
 	 */
 	KCOV_MODE_TRACE = 1,
+	/*
+	 * AFL-style collection.
+	 * Covered branches are hashed and collected in a fixed size buffer
+	 * (see AFL documentation for more information).
+	 */
+	KCOV_MODE_AFL = 2,
 };
 
 #else
diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
index 574e22e..0dc1cf9 100644
--- a/include/uapi/linux/kcov.h
+++ b/include/uapi/linux/kcov.h
@@ -4,6 +4,7 @@
 #include <linux/types.h>
 
 #define KCOV_INIT_TRACE			_IOR('c', 1, unsigned long)
+#define KCOV_INIT_AFL			_IOR('c', 2, unsigned long)
 #define KCOV_ENABLE			_IO('c', 100)
 #define KCOV_DISABLE			_IO('c', 101)
 
diff --git a/kernel/kcov.c b/kernel/kcov.c
index a02f2dd..e6eb785 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -5,6 +5,7 @@
 #include <linux/types.h>
 #include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/hash.h>
 #include <linux/mm.h>
 #include <linux/printk.h>
 #include <linux/slab.h>
@@ -34,6 +35,19 @@ struct kcov {
 	enum kcov_mode		mode;
 	/* Size of arena (in long's for KCOV_MODE_TRACE). */
 	unsigned		size;
+
+	union {
+		/* For KCOV_MODE_AFL */
+		struct {
+			/* (number of bytes) - 1, where number of
+			 * bytes must be a power of 2. */
+			unsigned int		mask;
+
+			/* Previous PC (for KCOV_MODE_AFL). */
+			unsigned int		prev_location;
+		};
+	};
+
 	/* Coverage buffer shared with user space. */
 	void			*area;
 	/* Task for which we collect coverage, or NULL. */
@@ -76,6 +90,19 @@ void notrace __sanitizer_cov_trace_pc(void)
 			area[pos] = _RET_IP_;
 			WRITE_ONCE(area[0], pos);
 		}
+	} else if (mode == KCOV_MODE_AFL) {
+		struct kcov *kcov;
+		unsigned char *area;
+		unsigned long location = _RET_IP_;
+
+		/* See above */
+		barrier();
+
+		kcov = t->kcov;
+		area = kcov->area;
+
+		++area[(kcov->prev_location ^ location) & kcov->mask];
+		kcov->prev_location = hash_long(location, BITS_PER_LONG);
 	}
 }
 EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
@@ -200,6 +227,23 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
 		kcov->size = size;
 		kcov->mode = KCOV_MODE_TRACE;
 		return 0;
+	case KCOV_INIT_AFL:
+		if (kcov->mode != KCOV_MODE_DISABLED)
+			return -EBUSY;
+
+		/*
+		 * AFL mode needs a fixed-size area that is a power of 2.
+		 */
+		size = arg;
+		if (size < 2 || size > INT_MAX / sizeof(unsigned long))
+			return -EINVAL;
+		if (!is_power_of_2(size))
+			return -EINVAL;
+		kcov->size = size;
+		kcov->mask = (size * sizeof(unsigned long)) - 1;
+		kcov->prev_location = hash_long(0, BITS_PER_LONG);
+		kcov->mode = KCOV_MODE_AFL;
+		return 0;
 	case KCOV_ENABLE:
 		/*
 		 * Enable coverage for the current task.
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH] kcov: add AFL-style tracing
  2016-05-21 16:07 [PATCH] kcov: add AFL-style tracing Vegard Nossum
@ 2016-05-29 16:51 ` Dmitry Vyukov
  2016-11-16 21:27   ` [PATCH v2 0/2] " Quentin Casasnovas
  0 siblings, 1 reply; 8+ messages in thread
From: Dmitry Vyukov @ 2016-05-29 16:51 UTC (permalink / raw)
  To: Vegard Nossum
  Cc: syzkaller, LKML, Quentin Casasnovas, Michal Zalewski, Kees Cook

On Sat, May 21, 2016 at 6:07 PM, Vegard Nossum <vegard.nossum@oracle.com> wrote:
> From: Quentin Casasnovas <quentin.casasnovas@oracle.com>
>
> AFL uses a fixed-size buffer (typically 64 KiB) where each byte is
> a counter representing how many times an A -> B branch was taken.
> Of course, since the buffer is fixed size, it's a little imprecise
> in that e.g. two different branches could map to the same counter,
> but in practice it works well.


Nice!
Glad it fit so nicely within the existing code.


> See afl:docs/technical_details.txt for more information.
>
> Here is a small test program that demonstrates the new capability:
>
>         #include <sys/ioctl.h>
>         #include <sys/mman.h>
>         #include <sys/types.h>
>
>         #include <errno.h>
>         #include <error.h>
>         #include <fcntl.h>
>         #include <stdio.h>
>         #include <stdlib.h>
>         #include <unistd.h>
>
>         #include <linux/types.h>
>
>         #define KCOV_INIT_TRACE                 _IOR('c', 1, unsigned long)
>         #define KCOV_INIT_AFL                   _IOR('c', 2, unsigned long)
>         #define KCOV_ENABLE                     _IO('c', 100)
>         #define KCOV_DISABLE                    _IO('c', 101)
>
>         int main(int argc, char *argv[])
>         {
>                 int fd = open("/sys/kernel/debug/kcov", O_RDWR);
>                 if (fd == -1)
>                         error(1, errno, "open()");
>
>                 unsigned long size = 1 << 10;
>                 if (ioctl(fd, KCOV_INIT_AFL, size) != 0)
>                         error(1, errno, "ioctl(KCOV_INIT_AFL)");
>
>                 void *mem = mmap(NULL, size * sizeof(long), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
>                 if (mem == MAP_FAILED)
>                         error(1, errno, "mmap()");
>
>                 /* Start kernel instrumentation */
>                 if (ioctl(fd, KCOV_ENABLE, 0) != 0)
>                         error(1, errno, "ioctl(KCOV_ENABLE)");
>
>                 printf("Hello world!\n");
>
>                 /* End kernel instrumentation*/
>                 if (ioctl(fd, KCOV_DISABLE, 0) != 0)
>                         error(1, errno, "ioctl(KCOV_DISABLE)");
>
>                 /* Hex dump of memory area */
>                 unsigned char *mem2 = mem;
>                 for (unsigned int i = 0; i < size; ++i) {
>                         printf("%02x ", mem2[i]);
>                         if (i % 32 == 31)
>                                 printf("\n");
>                 }
>
>                 close(fd);
>                 return 0;
>         }
>
> This patch is a collaboration between Quentin Casasnovas and Vegard Nossum.
>
> Cc: Dmitry Vyukov <dvyukov@google.com>
> Cc: Michal Zalewski <lcamtuf@gmail.com>
> Cc: Kees Cook <keescook@chromium.org>
> Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
> Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
> ---
>  include/linux/kcov.h      |  6 ++++++
>  include/uapi/linux/kcov.h |  1 +
>  kernel/kcov.c             | 44 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 51 insertions(+)
>
> diff --git a/include/linux/kcov.h b/include/linux/kcov.h
> index 2883ac9..cb8eb3e 100644
> --- a/include/linux/kcov.h
> +++ b/include/linux/kcov.h
> @@ -18,6 +18,12 @@ enum kcov_mode {
>          * Covered PCs are collected in a per-task buffer.
>          */
>         KCOV_MODE_TRACE = 1,
> +       /*
> +        * AFL-style collection.
> +        * Covered branches are hashed and collected in a fixed size buffer
> +        * (see AFL documentation for more information).
> +        */
> +       KCOV_MODE_AFL = 2,
>  };
>
>  #else
> diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
> index 574e22e..0dc1cf9 100644
> --- a/include/uapi/linux/kcov.h
> +++ b/include/uapi/linux/kcov.h
> @@ -4,6 +4,7 @@
>  #include <linux/types.h>
>
>  #define KCOV_INIT_TRACE                        _IOR('c', 1, unsigned long)
> +#define KCOV_INIT_AFL                  _IOR('c', 2, unsigned long)
>  #define KCOV_ENABLE                    _IO('c', 100)
>  #define KCOV_DISABLE                   _IO('c', 101)
>
> diff --git a/kernel/kcov.c b/kernel/kcov.c
> index a02f2dd..e6eb785 100644
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -5,6 +5,7 @@
>  #include <linux/types.h>
>  #include <linux/file.h>
>  #include <linux/fs.h>
> +#include <linux/hash.h>
>  #include <linux/mm.h>
>  #include <linux/printk.h>
>  #include <linux/slab.h>
> @@ -34,6 +35,19 @@ struct kcov {
>         enum kcov_mode          mode;
>         /* Size of arena (in long's for KCOV_MODE_TRACE). */
>         unsigned                size;
> +
> +       union {
> +               /* For KCOV_MODE_AFL */
> +               struct {
> +                       /* (number of bytes) - 1, where number of
> +                        * bytes must be a power of 2. */
> +                       unsigned int            mask;
> +
> +                       /* Previous PC (for KCOV_MODE_AFL). */

"(for KCOV_MODE_AFL)" is excessive, remove. This whole struct is "For
KCOV_MODE_AFL".


> +                       unsigned int            prev_location;
> +               };
> +       };
> +
>         /* Coverage buffer shared with user space. */
>         void                    *area;
>         /* Task for which we collect coverage, or NULL. */
> @@ -76,6 +90,19 @@ void notrace __sanitizer_cov_trace_pc(void)
>                         area[pos] = _RET_IP_;
>                         WRITE_ONCE(area[0], pos);
>                 }
> +       } else if (mode == KCOV_MODE_AFL) {
> +               struct kcov *kcov;
> +               unsigned char *area;
> +               unsigned long location = _RET_IP_;
> +
> +               /* See above */
> +               barrier();

Wonder if it forces compiler to spill location onto stack.
Maybe it's better to initialize location after barrier.


> +               kcov = t->kcov;
> +               area = kcov->area;
> +
> +               ++area[(kcov->prev_location ^ location) & kcov->mask];
> +               kcov->prev_location = hash_long(location, BITS_PER_LONG);

The idea was that all data required for this function is cached in
task struct (since the function is very frequently executed). Avoids
additional indirection.
If you switch trace mode to using mask (see below), then both modes
can share that field in task struct.
And then you can add prev_location to task struct.



>         }
>  }
>  EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
> @@ -200,6 +227,23 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
>                 kcov->size = size;
>                 kcov->mode = KCOV_MODE_TRACE;
>                 return 0;
> +       case KCOV_INIT_AFL:
> +               if (kcov->mode != KCOV_MODE_DISABLED)
> +                       return -EBUSY;
> +
> +               /*
> +                * AFL mode needs a fixed-size area that is a power of 2.
> +                */
> +               size = arg;
> +               if (size < 2 || size > INT_MAX / sizeof(unsigned long))
> +                       return -EINVAL;
> +               if (!is_power_of_2(size))
> +                       return -EINVAL;
> +               kcov->size = size;
> +               kcov->mask = (size * sizeof(unsigned long)) - 1;


I assumed that KCOV_INIT_AFL accepts size in bytes, because you use
"char *area". Then spent a while to figure out how it all works and
why you multiply mask by sizeof(unsigned long).

Do you mind to do a small refactoring?
Initially I implemented both trace mode and afl mode, but then
simplified the patch for initial submission as much as possible
(assuming only trace mode).
The initial idea was that kcov->size is expressed in bytes, so that
kcov_map does not make assumption that the trace mode operates on
longs. I think it's better to restore that now that we have 2 modes.
Namely, (1) make kcov->size expressed in bytes, (2) also use mask in
__sanitizer_cov_trace_pc for trace mode:

-                 if (likely(pos < t->kcov_size)) {
+                 if (likely(pos < t->kcov_mask)) {

 Then KCOV_INIT_AFL can accept bytes and avoid these confusing
multiplications by sizeof(unsigned long).



> +               kcov->prev_location = hash_long(0, BITS_PER_LONG);
> +               kcov->mode = KCOV_MODE_AFL;
> +               return 0;
>         case KCOV_ENABLE:
>                 /*
>                  * Enable coverage for the current task.


I am not very strong about it, but I would rename
KCOV_INIT_AFL/KCOV_MODE_AFL to KCOV_INIT_TABLE/KCOV_MODE_TABLE. I
understand that it originated in AFL, but in the end it's no more than
a hash table. KCOV users do not need to know what is AFL (though, it
would be bad for them :) ).

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2 0/2] kcov: add AFL-style tracing
  2016-05-29 16:51 ` Dmitry Vyukov
@ 2016-11-16 21:27   ` Quentin Casasnovas
  2016-11-16 21:27     ` [PATCH 1/2] kcov: size of arena is now given in bytes Quentin Casasnovas
  2016-11-16 21:27     ` [PATCH 2/2] kcov: add AFL-style tracing Quentin Casasnovas
  0 siblings, 2 replies; 8+ messages in thread
From: Quentin Casasnovas @ 2016-11-16 21:27 UTC (permalink / raw)
  To: Dmitry Vyukov
  Cc: linux-kernel, Vegard Nossum, Michal Zalewski, Kees Cook, syzkaller

Hi Dmitry,

Sorry it took so long to implement the small suggestions you had, we've
been very busy with other projects (or at least that's our excuse!).

The changes to afl.git in order to use kcov were sent to Michal so
hopefully people can start fuzzing their kernel with AFL without
cherry-picking patches accross different repos soon!

Cheers,
Q

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/2] kcov: size of arena is now given in bytes.
  2016-11-16 21:27   ` [PATCH v2 0/2] " Quentin Casasnovas
@ 2016-11-16 21:27     ` Quentin Casasnovas
  2016-11-17  7:54       ` Dmitry Vyukov
  2016-11-16 21:27     ` [PATCH 2/2] kcov: add AFL-style tracing Quentin Casasnovas
  1 sibling, 1 reply; 8+ messages in thread
From: Quentin Casasnovas @ 2016-11-16 21:27 UTC (permalink / raw)
  To: Dmitry Vyukov
  Cc: linux-kernel, Vegard Nossum, Michal Zalewski, Kees Cook,
	syzkaller, Quentin Casasnovas

We'll introduce a different mode of tracing a-la AFL fixed table and Dmitry
suggests that the code would be simpler with the size expressed in bytes as
opposed unsigned longs.

We only change the kcov::size field, which will be shared between different
modes, but leave the task_struct::kcov_size field expressed in unsigned
long in order to save an unecessary bitshift/division in the hot path when
using KCOV_MODE_TRACE.

Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Michal Zalewski <lcamtuf@gmail.com>
Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
---
 kernel/kcov.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/kernel/kcov.c b/kernel/kcov.c
index 30e6d05aa5a9..c2aa93851f93 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -32,7 +32,7 @@ struct kcov {
 	/* The lock protects mode, size, area and t. */
 	spinlock_t		lock;
 	enum kcov_mode		mode;
-	/* Size of arena (in long's for KCOV_MODE_TRACE). */
+	/* Size of arena in bytes. */
 	unsigned		size;
 	/* Coverage buffer shared with user space. */
 	void			*area;
@@ -140,7 +140,7 @@ static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
 		return -ENOMEM;
 
 	spin_lock(&kcov->lock);
-	size = kcov->size * sizeof(unsigned long);
+	size = kcov->size;
 	if (kcov->mode == KCOV_MODE_DISABLED || vma->vm_pgoff != 0 ||
 	    vma->vm_end - vma->vm_start != size) {
 		res = -EINVAL;
@@ -198,13 +198,11 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
 			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->size = size * sizeof(unsigned long);
 		kcov->mode = KCOV_MODE_TRACE;
 		return 0;
 	case KCOV_ENABLE:
@@ -223,7 +221,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
 			return -EBUSY;
 		t = current;
 		/* Cache in task struct for performance. */
-		t->kcov_size = kcov->size;
+		t->kcov_size = kcov->size / sizeof(unsigned long);
 		t->kcov_area = kcov->area;
 		/* See comment in __sanitizer_cov_trace_pc(). */
 		barrier();
-- 
2.10.2

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/2] kcov: add AFL-style tracing
  2016-11-16 21:27   ` [PATCH v2 0/2] " Quentin Casasnovas
  2016-11-16 21:27     ` [PATCH 1/2] kcov: size of arena is now given in bytes Quentin Casasnovas
@ 2016-11-16 21:27     ` Quentin Casasnovas
  2016-11-17  7:52       ` Dmitry Vyukov
  1 sibling, 1 reply; 8+ messages in thread
From: Quentin Casasnovas @ 2016-11-16 21:27 UTC (permalink / raw)
  To: Dmitry Vyukov
  Cc: linux-kernel, Vegard Nossum, Michal Zalewski, Kees Cook,
	syzkaller, Quentin Casasnovas

AFL uses a fixed-size buffer (typically 64 KiB) where each byte is
a counter representing how many times an A -> B branch was taken.
Of course, since the buffer is fixed size, it's a little imprecise
in that e.g. two different branches could map to the same counter,
but in practice it works well.

See afl:docs/technical_details.txt for more information.

Here is a small test program that demonstrates the new capability:

	#include <sys/ioctl.h>
	#include <sys/mman.h>
	#include <sys/types.h>

	#include <errno.h>
	#include <error.h>
	#include <fcntl.h>
	#include <stdio.h>
	#include <stdlib.h>
	#include <unistd.h>

	#include <linux/types.h>

	#define KCOV_INIT_TRACE                 _IOR('c', 1, unsigned long)
	#define KCOV_INIT_TABLE                 _IOR('c', 2, unsigned long)
	#define KCOV_ENABLE                     _IO('c', 100)
	#define KCOV_DISABLE                    _IO('c', 101)

	int main(int argc, char *argv[])
	{
		int fd = open("/sys/kernel/debug/kcov", O_RDWR);
		if (fd == -1)
			error(1, errno, "open()");

		unsigned long size = 1 << 10;
		if (ioctl(fd, KCOV_INIT_TABLE, size) != 0)
			error(1, errno, "ioctl(KCOV_INIT_TABLE)");

		void *mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
		if (mem == MAP_FAILED)
			error(1, errno, "mmap()");

		/* Start kernel instrumentation */
		if (ioctl(fd, KCOV_ENABLE, 0) != 0)
			error(1, errno, "ioctl(KCOV_ENABLE)");

		printf("Hello world!\n");

		/* End kernel instrumentation*/
		if (ioctl(fd, KCOV_DISABLE, 0) != 0)
			error(1, errno, "ioctl(KCOV_DISABLE)");

		/* Hex dump of memory area */
		unsigned char *mem2 = mem;
		for (unsigned int i = 0; i < size / sizeof(i); ++i) {
			printf("%02x ", mem2[i]);
			if (i % 32 == 31)
				printf("\n");
		}

		close(fd);
		return 0;
	}

This patch is a collaboration between Quentin Casasnovas and Vegard Nossum.

v2: As per Dmitry's suggestions:
  - Initialize location after barrier
  - Avoid additional indirections in __sanitizer_cov_trace_pc
  - Renamed KCOV_INIT_AFL/KCOV_MODE_AFL to KCOV_INIT_TABLE/KCOV_MODE_TABLE.

Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Michal Zalewski <lcamtuf@gmail.com>
Cc: Kees Cook <keescook@chromium.org>
Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
---
 include/linux/kcov.h      |  6 ++++++
 include/linux/sched.h     | 10 ++++++++--
 include/uapi/linux/kcov.h |  1 +
 kernel/kcov.c             | 37 ++++++++++++++++++++++++++++++++++++-
 4 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index 2883ac98c280..5450b8296113 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -18,6 +18,12 @@ enum kcov_mode {
 	 * Covered PCs are collected in a per-task buffer.
 	 */
 	KCOV_MODE_TRACE = 1,
+	/*
+	 * AFL-style collection.
+	 * Covered branches are hashed and collected in a fixed size buffer
+	 * (see AFL documentation for more information).
+	 */
+	KCOV_MODE_TABLE = 2,
 };
 
 #else
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 348f51b0ec92..31f1bde64961 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1919,8 +1919,14 @@ struct task_struct {
 #ifdef CONFIG_KCOV
 	/* Coverage collection mode enabled for this task (0 if disabled). */
 	enum kcov_mode kcov_mode;
-	/* Size of the kcov_area. */
-	unsigned	kcov_size;
+	union {
+		/* Size of the kcov_area. */
+		unsigned kcov_size;
+		/* Mask to fit within kcov_area */
+		unsigned kcov_mask;
+	};
+	/* Hash of previous branch taken, to differentiate A > B from B > A */
+	unsigned long   kcov_prev_location;
 	/* Buffer for coverage collection. */
 	void		*kcov_area;
 	/* kcov desciptor wired with this task or NULL. */
diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
index 574e22ec640d..19b8ff763243 100644
--- a/include/uapi/linux/kcov.h
+++ b/include/uapi/linux/kcov.h
@@ -4,6 +4,7 @@
 #include <linux/types.h>
 
 #define KCOV_INIT_TRACE			_IOR('c', 1, unsigned long)
+#define KCOV_INIT_TABLE			_IOR('c', 2, unsigned long)
 #define KCOV_ENABLE			_IO('c', 100)
 #define KCOV_DISABLE			_IO('c', 101)
 
diff --git a/kernel/kcov.c b/kernel/kcov.c
index c2aa93851f93..8056013df16b 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -5,6 +5,7 @@
 #include <linux/types.h>
 #include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/hash.h>
 #include <linux/mm.h>
 #include <linux/printk.h>
 #include <linux/slab.h>
@@ -83,6 +84,18 @@ void notrace __sanitizer_cov_trace_pc(void)
 			area[pos] = _RET_IP_;
 			WRITE_ONCE(area[0], pos);
 		}
+	} else if (mode == KCOV_MODE_TABLE) {
+		unsigned char *area;
+		unsigned long location;
+
+		/* See above */
+		barrier();
+
+		location = _RET_IP_;
+		area = t->kcov_area;
+
+		++area[(t->kcov_prev_location ^ location) & t->kcov_mask];
+		t->kcov_prev_location = hash_long(location, BITS_PER_LONG);
 	}
 }
 EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
@@ -106,6 +119,7 @@ void kcov_task_init(struct task_struct *t)
 	t->kcov_size = 0;
 	t->kcov_area = NULL;
 	t->kcov = NULL;
+	t->kcov_prev_location = hash_long(0, BITS_PER_LONG);
 }
 
 void kcov_task_exit(struct task_struct *t)
@@ -205,6 +219,23 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
 		kcov->size = size * sizeof(unsigned long);
 		kcov->mode = KCOV_MODE_TRACE;
 		return 0;
+	case KCOV_INIT_TABLE:
+		size = arg;
+
+		if (kcov->mode != KCOV_MODE_DISABLED)
+			return -EBUSY;
+
+		/*
+		 * We infer the index in the table buffer from the return
+		 * address of the caller and need a fast way to mask the
+		 * relevant bits.
+		 */
+		if (!is_power_of_2(size))
+			return -EINVAL;
+
+		kcov->size = size;
+		kcov->mode = KCOV_MODE_TABLE;
+		return 0;
 	case KCOV_ENABLE:
 		/*
 		 * Enable coverage for the current task.
@@ -221,8 +252,12 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
 			return -EBUSY;
 		t = current;
 		/* Cache in task struct for performance. */
-		t->kcov_size = kcov->size / sizeof(unsigned long);
+		if (kcov->mode == KCOV_MODE_TRACE)
+			t->kcov_size = kcov->size / sizeof(unsigned long);
+		else if (kcov->mode == KCOV_MODE_TABLE)
+			t->kcov_mask = kcov->size - 1;
 		t->kcov_area = kcov->area;
+		t->kcov_prev_location = hash_long(0, BITS_PER_LONG);
 		/* See comment in __sanitizer_cov_trace_pc(). */
 		barrier();
 		WRITE_ONCE(t->kcov_mode, kcov->mode);
-- 
2.10.2

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] kcov: add AFL-style tracing
  2016-11-16 21:27     ` [PATCH 2/2] kcov: add AFL-style tracing Quentin Casasnovas
@ 2016-11-17  7:52       ` Dmitry Vyukov
  2017-06-21 18:16         ` Dmitry Vyukov
  0 siblings, 1 reply; 8+ messages in thread
From: Dmitry Vyukov @ 2016-11-17  7:52 UTC (permalink / raw)
  To: syzkaller
  Cc: LKML, Vegard Nossum, Michal Zalewski, Kees Cook, Quentin Casasnovas

On Wed, Nov 16, 2016 at 10:27 PM, Quentin Casasnovas
<quentin.casasnovas@oracle.com> wrote:
> AFL uses a fixed-size buffer (typically 64 KiB) where each byte is
> a counter representing how many times an A -> B branch was taken.
> Of course, since the buffer is fixed size, it's a little imprecise
> in that e.g. two different branches could map to the same counter,
> but in practice it works well.
>
> See afl:docs/technical_details.txt for more information.
>
> Here is a small test program that demonstrates the new capability:
>
>         #include <sys/ioctl.h>
>         #include <sys/mman.h>
>         #include <sys/types.h>
>
>         #include <errno.h>
>         #include <error.h>
>         #include <fcntl.h>
>         #include <stdio.h>
>         #include <stdlib.h>
>         #include <unistd.h>
>
>         #include <linux/types.h>
>
>         #define KCOV_INIT_TRACE                 _IOR('c', 1, unsigned long)
>         #define KCOV_INIT_TABLE                 _IOR('c', 2, unsigned long)
>         #define KCOV_ENABLE                     _IO('c', 100)
>         #define KCOV_DISABLE                    _IO('c', 101)
>
>         int main(int argc, char *argv[])
>         {
>                 int fd = open("/sys/kernel/debug/kcov", O_RDWR);
>                 if (fd == -1)
>                         error(1, errno, "open()");
>
>                 unsigned long size = 1 << 10;
>                 if (ioctl(fd, KCOV_INIT_TABLE, size) != 0)
>                         error(1, errno, "ioctl(KCOV_INIT_TABLE)");
>
>                 void *mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
>                 if (mem == MAP_FAILED)
>                         error(1, errno, "mmap()");
>
>                 /* Start kernel instrumentation */
>                 if (ioctl(fd, KCOV_ENABLE, 0) != 0)
>                         error(1, errno, "ioctl(KCOV_ENABLE)");
>
>                 printf("Hello world!\n");
>
>                 /* End kernel instrumentation*/
>                 if (ioctl(fd, KCOV_DISABLE, 0) != 0)
>                         error(1, errno, "ioctl(KCOV_DISABLE)");
>
>                 /* Hex dump of memory area */
>                 unsigned char *mem2 = mem;
>                 for (unsigned int i = 0; i < size / sizeof(i); ++i) {
>                         printf("%02x ", mem2[i]);
>                         if (i % 32 == 31)
>                                 printf("\n");
>                 }
>
>                 close(fd);
>                 return 0;
>         }
>
> This patch is a collaboration between Quentin Casasnovas and Vegard Nossum.
>
> v2: As per Dmitry's suggestions:
>   - Initialize location after barrier
>   - Avoid additional indirections in __sanitizer_cov_trace_pc
>   - Renamed KCOV_INIT_AFL/KCOV_MODE_AFL to KCOV_INIT_TABLE/KCOV_MODE_TABLE.
>
> Cc: Dmitry Vyukov <dvyukov@google.com>
> Cc: Michal Zalewski <lcamtuf@gmail.com>
> Cc: Kees Cook <keescook@chromium.org>
> Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
> Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
> ---
>  include/linux/kcov.h      |  6 ++++++
>  include/linux/sched.h     | 10 ++++++++--
>  include/uapi/linux/kcov.h |  1 +
>  kernel/kcov.c             | 37 ++++++++++++++++++++++++++++++++++++-
>  4 files changed, 51 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/kcov.h b/include/linux/kcov.h
> index 2883ac98c280..5450b8296113 100644
> --- a/include/linux/kcov.h
> +++ b/include/linux/kcov.h
> @@ -18,6 +18,12 @@ enum kcov_mode {
>          * Covered PCs are collected in a per-task buffer.
>          */
>         KCOV_MODE_TRACE = 1,
> +       /*
> +        * AFL-style collection.
> +        * Covered branches are hashed and collected in a fixed size buffer
> +        * (see AFL documentation for more information).
> +        */
> +       KCOV_MODE_TABLE = 2,
>  };
>
>  #else
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 348f51b0ec92..31f1bde64961 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1919,8 +1919,14 @@ struct task_struct {
>  #ifdef CONFIG_KCOV
>         /* Coverage collection mode enabled for this task (0 if disabled). */
>         enum kcov_mode kcov_mode;
> -       /* Size of the kcov_area. */
> -       unsigned        kcov_size;
> +       union {
> +               /* Size of the kcov_area. */
> +               unsigned kcov_size;
> +               /* Mask to fit within kcov_area */
> +               unsigned kcov_mask;
> +       };
> +       /* Hash of previous branch taken, to differentiate A > B from B > A */
> +       unsigned long   kcov_prev_location;
>         /* Buffer for coverage collection. */
>         void            *kcov_area;
>         /* kcov desciptor wired with this task or NULL. */
> diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
> index 574e22ec640d..19b8ff763243 100644
> --- a/include/uapi/linux/kcov.h
> +++ b/include/uapi/linux/kcov.h
> @@ -4,6 +4,7 @@
>  #include <linux/types.h>
>
>  #define KCOV_INIT_TRACE                        _IOR('c', 1, unsigned long)
> +#define KCOV_INIT_TABLE                        _IOR('c', 2, unsigned long)
>  #define KCOV_ENABLE                    _IO('c', 100)
>  #define KCOV_DISABLE                   _IO('c', 101)
>
> diff --git a/kernel/kcov.c b/kernel/kcov.c
> index c2aa93851f93..8056013df16b 100644
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -5,6 +5,7 @@
>  #include <linux/types.h>
>  #include <linux/file.h>
>  #include <linux/fs.h>
> +#include <linux/hash.h>
>  #include <linux/mm.h>
>  #include <linux/printk.h>
>  #include <linux/slab.h>
> @@ -83,6 +84,18 @@ void notrace __sanitizer_cov_trace_pc(void)
>                         area[pos] = _RET_IP_;
>                         WRITE_ONCE(area[0], pos);
>                 }
> +       } else if (mode == KCOV_MODE_TABLE) {
> +               unsigned char *area;
> +               unsigned long location;
> +
> +               /* See above */
> +               barrier();
> +
> +               location = _RET_IP_;
> +               area = t->kcov_area;
> +
> +               ++area[(t->kcov_prev_location ^ location) & t->kcov_mask];
> +               t->kcov_prev_location = hash_long(location, BITS_PER_LONG);
>         }
>  }
>  EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
> @@ -106,6 +119,7 @@ void kcov_task_init(struct task_struct *t)
>         t->kcov_size = 0;
>         t->kcov_area = NULL;
>         t->kcov = NULL;
> +       t->kcov_prev_location = hash_long(0, BITS_PER_LONG);
>  }
>
>  void kcov_task_exit(struct task_struct *t)
> @@ -205,6 +219,23 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
>                 kcov->size = size * sizeof(unsigned long);
>                 kcov->mode = KCOV_MODE_TRACE;
>                 return 0;
> +       case KCOV_INIT_TABLE:
> +               size = arg;
> +
> +               if (kcov->mode != KCOV_MODE_DISABLED)
> +                       return -EBUSY;
> +
> +               /*
> +                * We infer the index in the table buffer from the return
> +                * address of the caller and need a fast way to mask the
> +                * relevant bits.
> +                */
> +               if (!is_power_of_2(size))
> +                       return -EINVAL;
> +
> +               kcov->size = size;
> +               kcov->mode = KCOV_MODE_TABLE;
> +               return 0;
>         case KCOV_ENABLE:
>                 /*
>                  * Enable coverage for the current task.
> @@ -221,8 +252,12 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
>                         return -EBUSY;
>                 t = current;
>                 /* Cache in task struct for performance. */
> -               t->kcov_size = kcov->size / sizeof(unsigned long);
> +               if (kcov->mode == KCOV_MODE_TRACE)
> +                       t->kcov_size = kcov->size / sizeof(unsigned long);
> +               else if (kcov->mode == KCOV_MODE_TABLE)
> +                       t->kcov_mask = kcov->size - 1;
>                 t->kcov_area = kcov->area;
> +               t->kcov_prev_location = hash_long(0, BITS_PER_LONG);
>                 /* See comment in __sanitizer_cov_trace_pc(). */
>                 barrier();
>                 WRITE_ONCE(t->kcov_mode, kcov->mode);
> --


Thanks!

Reviewed-and-tested-by: Dmitry Vyukov <dvyukov@google.com>

I've tested that KCOV_MODE_TRACE works for me with these two patches applied.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/2] kcov: size of arena is now given in bytes.
  2016-11-16 21:27     ` [PATCH 1/2] kcov: size of arena is now given in bytes Quentin Casasnovas
@ 2016-11-17  7:54       ` Dmitry Vyukov
  0 siblings, 0 replies; 8+ messages in thread
From: Dmitry Vyukov @ 2016-11-17  7:54 UTC (permalink / raw)
  To: Quentin Casasnovas
  Cc: LKML, Vegard Nossum, Michal Zalewski, Kees Cook, syzkaller

On Wed, Nov 16, 2016 at 10:27 PM, Quentin Casasnovas
<quentin.casasnovas@oracle.com> wrote:
> We'll introduce a different mode of tracing a-la AFL fixed table and Dmitry
> suggests that the code would be simpler with the size expressed in bytes as
> opposed unsigned longs.
>
> We only change the kcov::size field, which will be shared between different
> modes, but leave the task_struct::kcov_size field expressed in unsigned
> long in order to save an unecessary bitshift/division in the hot path when
> using KCOV_MODE_TRACE.


Thanks!

Reviewed-and-tested-by: Dmitry Vyukov <dvyukov@google.com>

I've tested that KCOV_MODE_TRACE works for me with these two patches applied.

> but leave the task_struct::kcov_size field expressed in unsigned long

The only purpose of the cache in task struct is to make coverage fast
path fast, so this looks good to me.



> Cc: Dmitry Vyukov <dvyukov@google.com>
> Cc: Michal Zalewski <lcamtuf@gmail.com>
> Cc: Kees Cook <keescook@chromium.org>
> Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
> Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
> ---
>  kernel/kcov.c | 10 ++++------
>  1 file changed, 4 insertions(+), 6 deletions(-)
>
> diff --git a/kernel/kcov.c b/kernel/kcov.c
> index 30e6d05aa5a9..c2aa93851f93 100644
> --- a/kernel/kcov.c
> +++ b/kernel/kcov.c
> @@ -32,7 +32,7 @@ struct kcov {
>         /* The lock protects mode, size, area and t. */
>         spinlock_t              lock;
>         enum kcov_mode          mode;
> -       /* Size of arena (in long's for KCOV_MODE_TRACE). */
> +       /* Size of arena in bytes. */
>         unsigned                size;
>         /* Coverage buffer shared with user space. */
>         void                    *area;
> @@ -140,7 +140,7 @@ static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
>                 return -ENOMEM;
>
>         spin_lock(&kcov->lock);
> -       size = kcov->size * sizeof(unsigned long);
> +       size = kcov->size;
>         if (kcov->mode == KCOV_MODE_DISABLED || vma->vm_pgoff != 0 ||
>             vma->vm_end - vma->vm_start != size) {
>                 res = -EINVAL;
> @@ -198,13 +198,11 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
>                         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->size = size * sizeof(unsigned long);
>                 kcov->mode = KCOV_MODE_TRACE;
>                 return 0;
>         case KCOV_ENABLE:
> @@ -223,7 +221,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
>                         return -EBUSY;
>                 t = current;
>                 /* Cache in task struct for performance. */
> -               t->kcov_size = kcov->size;
> +               t->kcov_size = kcov->size / sizeof(unsigned long);
>                 t->kcov_area = kcov->area;
>                 /* See comment in __sanitizer_cov_trace_pc(). */
>                 barrier();
> --
> 2.10.2
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/2] kcov: add AFL-style tracing
  2016-11-17  7:52       ` Dmitry Vyukov
@ 2017-06-21 18:16         ` Dmitry Vyukov
  0 siblings, 0 replies; 8+ messages in thread
From: Dmitry Vyukov @ 2017-06-21 18:16 UTC (permalink / raw)
  To: Quentin Casasnovas
  Cc: LKML, Vegard Nossum, Michal Zalewski, Kees Cook, andreyknvl,
	Alexander Potapenko, Victor Chibotaru, syzkaller

On Thu, Nov 17, 2016 at 8:52 AM, Dmitry Vyukov <dvyukov@google.com> wrote:
> On Wed, Nov 16, 2016 at 10:27 PM, Quentin Casasnovas
> <quentin.casasnovas@oracle.com> wrote:
>> AFL uses a fixed-size buffer (typically 64 KiB) where each byte is
>> a counter representing how many times an A -> B branch was taken.
>> Of course, since the buffer is fixed size, it's a little imprecise
>> in that e.g. two different branches could map to the same counter,
>> but in practice it works well.
>>
>> See afl:docs/technical_details.txt for more information.
>>
>> Here is a small test program that demonstrates the new capability:
>>
>>         #include <sys/ioctl.h>
>>         #include <sys/mman.h>
>>         #include <sys/types.h>
>>
>>         #include <errno.h>
>>         #include <error.h>
>>         #include <fcntl.h>
>>         #include <stdio.h>
>>         #include <stdlib.h>
>>         #include <unistd.h>
>>
>>         #include <linux/types.h>
>>
>>         #define KCOV_INIT_TRACE                 _IOR('c', 1, unsigned long)
>>         #define KCOV_INIT_TABLE                 _IOR('c', 2, unsigned long)
>>         #define KCOV_ENABLE                     _IO('c', 100)
>>         #define KCOV_DISABLE                    _IO('c', 101)
>>
>>         int main(int argc, char *argv[])
>>         {
>>                 int fd = open("/sys/kernel/debug/kcov", O_RDWR);
>>                 if (fd == -1)
>>                         error(1, errno, "open()");
>>
>>                 unsigned long size = 1 << 10;
>>                 if (ioctl(fd, KCOV_INIT_TABLE, size) != 0)
>>                         error(1, errno, "ioctl(KCOV_INIT_TABLE)");
>>
>>                 void *mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
>>                 if (mem == MAP_FAILED)
>>                         error(1, errno, "mmap()");
>>
>>                 /* Start kernel instrumentation */
>>                 if (ioctl(fd, KCOV_ENABLE, 0) != 0)
>>                         error(1, errno, "ioctl(KCOV_ENABLE)");
>>
>>                 printf("Hello world!\n");
>>
>>                 /* End kernel instrumentation*/
>>                 if (ioctl(fd, KCOV_DISABLE, 0) != 0)
>>                         error(1, errno, "ioctl(KCOV_DISABLE)");
>>
>>                 /* Hex dump of memory area */
>>                 unsigned char *mem2 = mem;
>>                 for (unsigned int i = 0; i < size / sizeof(i); ++i) {
>>                         printf("%02x ", mem2[i]);
>>                         if (i % 32 == 31)
>>                                 printf("\n");
>>                 }
>>
>>                 close(fd);
>>                 return 0;
>>         }
>>
>> This patch is a collaboration between Quentin Casasnovas and Vegard Nossum.
>>
>> v2: As per Dmitry's suggestions:
>>   - Initialize location after barrier
>>   - Avoid additional indirections in __sanitizer_cov_trace_pc
>>   - Renamed KCOV_INIT_AFL/KCOV_MODE_AFL to KCOV_INIT_TABLE/KCOV_MODE_TABLE.
>>
>> Cc: Dmitry Vyukov <dvyukov@google.com>
>> Cc: Michal Zalewski <lcamtuf@gmail.com>
>> Cc: Kees Cook <keescook@chromium.org>
>> Signed-off-by: Quentin Casasnovas <quentin.casasnovas@oracle.com>
>> Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
>> ---
>>  include/linux/kcov.h      |  6 ++++++
>>  include/linux/sched.h     | 10 ++++++++--
>>  include/uapi/linux/kcov.h |  1 +
>>  kernel/kcov.c             | 37 ++++++++++++++++++++++++++++++++++++-
>>  4 files changed, 51 insertions(+), 3 deletions(-)
>>
>> diff --git a/include/linux/kcov.h b/include/linux/kcov.h
>> index 2883ac98c280..5450b8296113 100644
>> --- a/include/linux/kcov.h
>> +++ b/include/linux/kcov.h
>> @@ -18,6 +18,12 @@ enum kcov_mode {
>>          * Covered PCs are collected in a per-task buffer.
>>          */
>>         KCOV_MODE_TRACE = 1,
>> +       /*
>> +        * AFL-style collection.
>> +        * Covered branches are hashed and collected in a fixed size buffer
>> +        * (see AFL documentation for more information).
>> +        */
>> +       KCOV_MODE_TABLE = 2,
>>  };
>>
>>  #else
>> diff --git a/include/linux/sched.h b/include/linux/sched.h
>> index 348f51b0ec92..31f1bde64961 100644
>> --- a/include/linux/sched.h
>> +++ b/include/linux/sched.h
>> @@ -1919,8 +1919,14 @@ struct task_struct {
>>  #ifdef CONFIG_KCOV
>>         /* Coverage collection mode enabled for this task (0 if disabled). */
>>         enum kcov_mode kcov_mode;
>> -       /* Size of the kcov_area. */
>> -       unsigned        kcov_size;
>> +       union {
>> +               /* Size of the kcov_area. */
>> +               unsigned kcov_size;
>> +               /* Mask to fit within kcov_area */
>> +               unsigned kcov_mask;
>> +       };
>> +       /* Hash of previous branch taken, to differentiate A > B from B > A */
>> +       unsigned long   kcov_prev_location;
>>         /* Buffer for coverage collection. */
>>         void            *kcov_area;
>>         /* kcov desciptor wired with this task or NULL. */
>> diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
>> index 574e22ec640d..19b8ff763243 100644
>> --- a/include/uapi/linux/kcov.h
>> +++ b/include/uapi/linux/kcov.h
>> @@ -4,6 +4,7 @@
>>  #include <linux/types.h>
>>
>>  #define KCOV_INIT_TRACE                        _IOR('c', 1, unsigned long)
>> +#define KCOV_INIT_TABLE                        _IOR('c', 2, unsigned long)
>>  #define KCOV_ENABLE                    _IO('c', 100)
>>  #define KCOV_DISABLE                   _IO('c', 101)
>>
>> diff --git a/kernel/kcov.c b/kernel/kcov.c
>> index c2aa93851f93..8056013df16b 100644
>> --- a/kernel/kcov.c
>> +++ b/kernel/kcov.c
>> @@ -5,6 +5,7 @@
>>  #include <linux/types.h>
>>  #include <linux/file.h>
>>  #include <linux/fs.h>
>> +#include <linux/hash.h>
>>  #include <linux/mm.h>
>>  #include <linux/printk.h>
>>  #include <linux/slab.h>
>> @@ -83,6 +84,18 @@ void notrace __sanitizer_cov_trace_pc(void)
>>                         area[pos] = _RET_IP_;
>>                         WRITE_ONCE(area[0], pos);
>>                 }
>> +       } else if (mode == KCOV_MODE_TABLE) {
>> +               unsigned char *area;
>> +               unsigned long location;
>> +
>> +               /* See above */
>> +               barrier();
>> +
>> +               location = _RET_IP_;
>> +               area = t->kcov_area;
>> +
>> +               ++area[(t->kcov_prev_location ^ location) & t->kcov_mask];
>> +               t->kcov_prev_location = hash_long(location, BITS_PER_LONG);
>>         }
>>  }
>>  EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
>> @@ -106,6 +119,7 @@ void kcov_task_init(struct task_struct *t)
>>         t->kcov_size = 0;
>>         t->kcov_area = NULL;
>>         t->kcov = NULL;
>> +       t->kcov_prev_location = hash_long(0, BITS_PER_LONG);
>>  }
>>
>>  void kcov_task_exit(struct task_struct *t)
>> @@ -205,6 +219,23 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
>>                 kcov->size = size * sizeof(unsigned long);
>>                 kcov->mode = KCOV_MODE_TRACE;
>>                 return 0;
>> +       case KCOV_INIT_TABLE:
>> +               size = arg;
>> +
>> +               if (kcov->mode != KCOV_MODE_DISABLED)
>> +                       return -EBUSY;
>> +
>> +               /*
>> +                * We infer the index in the table buffer from the return
>> +                * address of the caller and need a fast way to mask the
>> +                * relevant bits.
>> +                */
>> +               if (!is_power_of_2(size))
>> +                       return -EINVAL;
>> +
>> +               kcov->size = size;
>> +               kcov->mode = KCOV_MODE_TABLE;
>> +               return 0;
>>         case KCOV_ENABLE:
>>                 /*
>>                  * Enable coverage for the current task.
>> @@ -221,8 +252,12 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
>>                         return -EBUSY;
>>                 t = current;
>>                 /* Cache in task struct for performance. */
>> -               t->kcov_size = kcov->size / sizeof(unsigned long);
>> +               if (kcov->mode == KCOV_MODE_TRACE)
>> +                       t->kcov_size = kcov->size / sizeof(unsigned long);
>> +               else if (kcov->mode == KCOV_MODE_TABLE)
>> +                       t->kcov_mask = kcov->size - 1;
>>                 t->kcov_area = kcov->area;
>> +               t->kcov_prev_location = hash_long(0, BITS_PER_LONG);
>>                 /* See comment in __sanitizer_cov_trace_pc(). */
>>                 barrier();
>>                 WRITE_ONCE(t->kcov_mode, kcov->mode);
>> --
>
>
> Thanks!
>
> Reviewed-and-tested-by: Dmitry Vyukov <dvyukov@google.com>
>
> I've tested that KCOV_MODE_TRACE works for me with these two patches applied.


Hi Quentin,

Was it merged somewhere? I still don't see this upstream.
KCOV was merged via mm tree originally. Perhaps remail this to mm/Andrew.

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2017-06-21 18:16 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-21 16:07 [PATCH] kcov: add AFL-style tracing Vegard Nossum
2016-05-29 16:51 ` Dmitry Vyukov
2016-11-16 21:27   ` [PATCH v2 0/2] " Quentin Casasnovas
2016-11-16 21:27     ` [PATCH 1/2] kcov: size of arena is now given in bytes Quentin Casasnovas
2016-11-17  7:54       ` Dmitry Vyukov
2016-11-16 21:27     ` [PATCH 2/2] kcov: add AFL-style tracing Quentin Casasnovas
2016-11-17  7:52       ` Dmitry Vyukov
2017-06-21 18:16         ` Dmitry Vyukov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).