BACKPORT: kernel: add kcov code coverage
authorDmitry Vyukov <dvyukov@google.com>
Tue, 22 Mar 2016 21:27:30 +0000 (14:27 -0700)
committerPaul Lawrence <paullawrence@google.com>
Mon, 18 Dec 2017 17:41:57 +0000 (09:41 -0800)
kcov provides code coverage collection for coverage-guided fuzzing
(randomized testing).  Coverage-guided fuzzing is a testing technique
that uses coverage feedback to determine new interesting inputs to a
system.  A notable user-space example is AFL
(http://lcamtuf.coredump.cx/afl/).  However, this technique is not
widely used for kernel testing due to missing compiler and kernel
support.

kcov does not aim to collect as much coverage as possible.  It aims to
collect more or less stable coverage that is function of syscall inputs.
To achieve this goal it does not collect coverage in soft/hard
interrupts and instrumentation of some inherently non-deterministic or
non-interesting parts of kernel is disbled (e.g.  scheduler, locking).

Currently there is a single coverage collection mode (tracing), but the
API anticipates additional collection modes.  Initially I also
implemented a second mode which exposes coverage in a fixed-size hash
table of counters (what Quentin used in his original patch).  I've
dropped the second mode for simplicity.

This patch adds the necessary support on kernel side.  The complimentary
compiler support was added in gcc revision 231296.

We've used this support to build syzkaller system call fuzzer, which has
found 90 kernel bugs in just 2 months:

  https://github.com/google/syzkaller/wiki/Found-Bugs

We've also found 30+ bugs in our internal systems with syzkaller.
Another (yet unexplored) direction where kcov coverage would greatly
help is more traditional "blob mutation".  For example, mounting a
random blob as a filesystem, or receiving a random blob over wire.

Why not gcov.  Typical fuzzing loop looks as follows: (1) reset
coverage, (2) execute a bit of code, (3) collect coverage, repeat.  A
typical coverage can be just a dozen of basic blocks (e.g.  an invalid
input).  In such context gcov becomes prohibitively expensive as
reset/collect coverage steps depend on total number of basic
blocks/edges in program (in case of kernel it is about 2M).  Cost of
kcov depends only on number of executed basic blocks/edges.  On top of
that, kernel requires per-thread coverage because there are always
background threads and unrelated processes that also produce coverage.
With inlined gcov instrumentation per-thread coverage is not possible.

kcov exposes kernel PCs and control flow to user-space which is
insecure.  But debugfs should not be mapped as user accessible.

Based on a patch by Quentin Casasnovas.

[akpm@linux-foundation.org: make task_struct.kcov_mode have type `enum kcov_mode']
[akpm@linux-foundation.org: unbreak allmodconfig]
[akpm@linux-foundation.org: follow x86 Makefile layout standards]
Signed-off-by: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Cc: syzkaller <syzkaller@googlegroups.com>
Cc: Vegard Nossum <vegard.nossum@oracle.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Tavis Ormandy <taviso@google.com>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Quentin Casasnovas <quentin.casasnovas@oracle.com>
Cc: Kostya Serebryany <kcc@google.com>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Kees Cook <keescook@google.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Sasha Levin <sasha.levin@oracle.com>
Cc: David Drysdale <drysdale@google.com>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Andrey Ryabinin <ryabinin.a.a@gmail.com>
Cc: Kirill A. Shutemov <kirill@shutemov.name>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Bug: 64145065
(cherry-picked from 5c9a8750a6409c63a0f01d51a9024861022f6593)
Change-Id: I17b5e04f6e89b241924e78ec32ead79c38b860ce
Signed-off-by: Paul Lawrence <paullawrence@google.com>
27 files changed:
Documentation/kcov.txt [new file with mode: 0644]
Makefile
arch/x86/Kconfig
arch/x86/boot/Makefile
arch/x86/boot/compressed/Makefile
arch/x86/entry/vdso/Makefile
arch/x86/kernel/apic/Makefile
arch/x86/kernel/cpu/Makefile
arch/x86/lib/Makefile
arch/x86/mm/Makefile
arch/x86/realmode/rm/Makefile
drivers/firmware/efi/libstub/Makefile
include/linux/kcov.h [new file with mode: 0644]
include/linux/sched.h
include/uapi/linux/kcov.h [new file with mode: 0644]
kernel/Makefile
kernel/exit.c
kernel/fork.c
kernel/kcov.c [new file with mode: 0644]
kernel/locking/Makefile
kernel/rcu/Makefile
kernel/sched/Makefile
lib/Kconfig.debug
lib/Makefile
mm/Makefile
mm/kasan/Makefile
scripts/Makefile.lib

diff --git a/Documentation/kcov.txt b/Documentation/kcov.txt
new file mode 100644 (file)
index 0000000..779ff4a
--- /dev/null
@@ -0,0 +1,111 @@
+kcov: code coverage for fuzzing
+===============================
+
+kcov exposes kernel code coverage information in a form suitable for coverage-
+guided fuzzing (randomized testing). Coverage data of a running kernel is
+exported via the "kcov" debugfs file. Coverage collection is enabled on a task
+basis, and thus it can capture precise coverage of a single system call.
+
+Note that kcov does not aim to collect as much coverage as possible. It aims
+to collect more or less stable coverage that is function of syscall inputs.
+To achieve this goal it does not collect coverage in soft/hard interrupts
+and instrumentation of some inherently non-deterministic parts of kernel is
+disbled (e.g. scheduler, locking).
+
+Usage:
+======
+
+Configure kernel with:
+
+        CONFIG_KCOV=y
+
+CONFIG_KCOV requires gcc built on revision 231296 or later.
+Profiling data will only become accessible once debugfs has been mounted:
+
+        mount -t debugfs none /sys/kernel/debug
+
+The following program demonstrates kcov usage from within a test program:
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define KCOV_INIT_TRACE                        _IOR('c', 1, unsigned long)
+#define KCOV_ENABLE                    _IO('c', 100)
+#define KCOV_DISABLE                   _IO('c', 101)
+#define COVER_SIZE                     (64<<10)
+
+int main(int argc, char **argv)
+{
+       int fd;
+       unsigned long *cover, n, i;
+
+       /* A single fd descriptor allows coverage collection on a single
+        * thread.
+        */
+       fd = open("/sys/kernel/debug/kcov", O_RDWR);
+       if (fd == -1)
+               perror("open"), exit(1);
+       /* Setup trace mode and trace size. */
+       if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))
+               perror("ioctl"), exit(1);
+       /* Mmap buffer shared between kernel- and user-space. */
+       cover = (unsigned long*)mmap(NULL, COVER_SIZE * sizeof(unsigned long),
+                                    PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+       if ((void*)cover == MAP_FAILED)
+               perror("mmap"), exit(1);
+       /* Enable coverage collection on the current thread. */
+       if (ioctl(fd, KCOV_ENABLE, 0))
+               perror("ioctl"), exit(1);
+       /* Reset coverage from the tail of the ioctl() call. */
+       __atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);
+       /* That's the target syscal call. */
+       read(-1, NULL, 0);
+       /* Read number of PCs collected. */
+       n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
+       for (i = 0; i < n; i++)
+               printf("0x%lx\n", cover[i + 1]);
+       /* Disable coverage collection for the current thread. After this call
+        * coverage can be enabled for a different thread.
+        */
+       if (ioctl(fd, KCOV_DISABLE, 0))
+               perror("ioctl"), exit(1);
+       /* Free resources. */
+       if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))
+               perror("munmap"), exit(1);
+       if (close(fd))
+               perror("close"), exit(1);
+       return 0;
+}
+
+After piping through addr2line output of the program looks as follows:
+
+SyS_read
+fs/read_write.c:562
+__fdget_pos
+fs/file.c:774
+__fget_light
+fs/file.c:746
+__fget_light
+fs/file.c:750
+__fget_light
+fs/file.c:760
+__fdget_pos
+fs/file.c:784
+SyS_read
+fs/read_write.c:562
+
+If a program needs to collect coverage from several threads (independently),
+it needs to open /sys/kernel/debug/kcov in each thread separately.
+
+The interface is fine-grained to allow efficient forking of test processes.
+That is, a parent process opens /sys/kernel/debug/kcov, enables trace mode,
+mmaps coverage buffer and then forks child processes in a loop. Child processes
+only need to enable coverage (disable happens automatically on thread end).
index e73705b58a3c8955ecf7f1fc8f4c7100ba52ca19..a4044ee36db462888807c10d7e34a825ea66e6b2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -369,6 +369,7 @@ LDFLAGS_MODULE  =
 CFLAGS_KERNEL  =
 AFLAGS_KERNEL  =
 CFLAGS_GCOV    = -fprofile-arcs -ftest-coverage -fno-tree-loop-im
+CFLAGS_KCOV    = -fsanitize-coverage=trace-pc
 
 
 # Use USERINCLUDE when you must reference the UAPI directories only.
@@ -416,7 +417,7 @@ export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
 export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
 
 export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
-export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN
+export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KCOV CFLAGS_KASAN
 export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
 export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
 export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
@@ -693,6 +694,14 @@ endif
 endif
 KBUILD_CFLAGS += $(stackp-flag)
 
+ifdef CONFIG_KCOV
+  ifeq ($(call cc-option, $(CFLAGS_KCOV)),)
+    $(warning Cannot use CONFIG_KCOV: \
+             -fsanitize-coverage=trace-pc is not supported by compiler)
+    CFLAGS_KCOV =
+  endif
+endif
+
 ifeq ($(cc-name),clang)
 ifneq ($(CROSS_COMPILE),)
 CLANG_TRIPLE    ?= $(CROSS_COMPILE)
index 4d3cc82560a1556dd707ef15f349b12c3574cc37..7b115f91b8e82f78c1e11a4423db83e839d6cb18 100644 (file)
@@ -27,6 +27,7 @@ config X86
        select ARCH_HAS_ELF_RANDOMIZE
        select ARCH_HAS_FAST_MULTIPLIER
        select ARCH_HAS_GCOV_PROFILE_ALL
+       select ARCH_HAS_KCOV                    if X86_64
        select ARCH_HAS_PMEM_API                if X86_64
        select ARCH_HAS_MMIO_FLUSH
        select ARCH_HAS_SG_CHAIN
index c0cc2a6be0bf5ed485421e075ddd11881e18f679..707adf8b6a9fa1b6a6f636fa04f774f46464cc20 100644 (file)
 
 KASAN_SANITIZE := n
 
+# Kernel does not boot with kcov instrumentation here.
+# One of the problems observed was insertion of __sanitizer_cov_trace_pc()
+# callback into middle of per-cpu data enabling code. Thus the callback observed
+# inconsistent state and crashed. We are interested mostly in syscall coverage,
+# so boot code is not interesting anyway.
+KCOV_INSTRUMENT                := n
+
 # If you want to preset the SVGA mode, uncomment the next line and
 # set SVGA_MODE to whatever number you want.
 # Set it to -DSVGA_MODE=NORMAL_VGA if you just want the EGA/VGA mode.
index efa6073ffa7e44b1693818f603a8fcd32841e636..6f7af9ac16e2e5f16a590b5e7e7fad4e10bda07b 100644 (file)
@@ -18,6 +18,9 @@
 
 KASAN_SANITIZE := n
 
+# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
+KCOV_INSTRUMENT                := n
+
 targets := vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 vmlinux.bin.lzma \
        vmlinux.bin.xz vmlinux.bin.lzo vmlinux.bin.lz4
 
index 7af017a8958fe9834e53d21d918c373ca7d04f0b..fddeb1f4dcd22bcbca58e86b0b617ff78ea702a0 100644 (file)
@@ -5,6 +5,9 @@
 KBUILD_CFLAGS += $(DISABLE_LTO)
 KASAN_SANITIZE := n
 
+# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
+KCOV_INSTRUMENT                := n
+
 VDSO64-$(CONFIG_X86_64)                := y
 VDSOX32-$(CONFIG_X86_X32_ABI)  := y
 VDSO32-$(CONFIG_X86_32)                := y
index 8bb12ddc5db8c8b9c7f2e0abdce44fef00f46453..8e63ebdcbd0b4cb2cfe43605ad48eaa46ca2d967 100644 (file)
@@ -2,6 +2,10 @@
 # Makefile for local APIC drivers and for the IO-APIC code
 #
 
+# Leads to non-deterministic coverage that is not a function of syscall inputs.
+# In particualr, smp_apic_timer_interrupt() is called in random places.
+KCOV_INSTRUMENT                := n
+
 obj-$(CONFIG_X86_LOCAL_APIC)   += apic.o apic_noop.o ipi.o vector.o
 obj-y                          += hw_nmi.o
 
index 58031303e30488c540d609f95d0693918c09fa64..c108683bb32c404bf77b3762b128653b3e4a8788 100644 (file)
@@ -8,6 +8,10 @@ CFLAGS_REMOVE_common.o = -pg
 CFLAGS_REMOVE_perf_event.o = -pg
 endif
 
+# If these files are instrumented, boot hangs during the first second.
+KCOV_INSTRUMENT_common.o := n
+KCOV_INSTRUMENT_perf_event.o := n
+
 # Make sure load_percpu_segment has no stackprotector
 nostackp := $(call cc-option, -fno-stack-protector)
 CFLAGS_common.o                := $(nostackp)
index db4a1c6ea7852ab7fd6bf8d864b610e2428dafb9..2b6561d97a464f072cf7d53c0f032ae4f851a486 100644 (file)
@@ -2,6 +2,9 @@
 # Makefile for x86 specific library files.
 #
 
+# Produces uninteresting flaky coverage.
+KCOV_INSTRUMENT_delay.o        := n
+
 inat_tables_script = $(srctree)/arch/x86/tools/gen-insn-attr-x86.awk
 inat_tables_maps = $(srctree)/arch/x86/lib/x86-opcode-map.txt
 quiet_cmd_inat_tables = GEN     $@
index 65c47fda26fc5643d67aed5013de8aa8257aac0a..60407270ea70c2e481b850bce927a49cca241188 100644 (file)
@@ -1,3 +1,6 @@
+# Kernel does not boot with instrumentation of tlb.c.
+KCOV_INSTRUMENT_tlb.o  := n
+
 obj-y  :=  init.o init_$(BITS).o fault.o ioremap.o extable.o pageattr.o mmap.o \
            pat.o pgtable.o physaddr.o gup.o setup_nx.o
 
index 2730d775ef9a44e4709c99f29a333f07c4691ef0..59610be05468e2575c1857545d6b118496f3292f 100644 (file)
@@ -8,6 +8,9 @@
 #
 KASAN_SANITIZE := n
 
+# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
+KCOV_INSTRUMENT                := n
+
 always := realmode.bin realmode.relocs
 
 wakeup-objs    := wakeup_asm.o wakemain.o video-mode.o
index d1f1bde85d3fd5d8e91938593b47a9c45913d9d4..f89c4f25fbd37e06b0bdf7c07a49b264def02371 100644 (file)
@@ -23,6 +23,9 @@ KBUILD_CFLAGS                 := $(cflags-y) -DDISABLE_BRANCH_PROFILING \
 GCOV_PROFILE                   := n
 KASAN_SANITIZE                 := n
 
+# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
+KCOV_INSTRUMENT                        := n
+
 lib-y                          := efi-stub-helper.o
 
 # include the stub's generic dependencies from lib/ when building for ARM/arm64
diff --git a/include/linux/kcov.h b/include/linux/kcov.h
new file mode 100644 (file)
index 0000000..2883ac9
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef _LINUX_KCOV_H
+#define _LINUX_KCOV_H
+
+#include <uapi/linux/kcov.h>
+
+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,
+       /*
+        * Tracing coverage collection mode.
+        * Covered PCs are collected in a per-task buffer.
+        */
+       KCOV_MODE_TRACE = 1,
+};
+
+#else
+
+static inline void kcov_task_init(struct task_struct *t) {}
+static inline void kcov_task_exit(struct task_struct *t) {}
+
+#endif /* CONFIG_KCOV */
+#endif /* _LINUX_KCOV_H */
index 60af9d2fa92269bacceaa8b348e1f36a28b95f88..de6b3edd835b337d7f9b91d89c3becb824f8e0a2 100644 (file)
@@ -51,6 +51,7 @@ struct sched_param {
 #include <linux/resource.h>
 #include <linux/timer.h>
 #include <linux/hrtimer.h>
+#include <linux/kcov.h>
 #include <linux/task_io_accounting.h>
 #include <linux/latencytop.h>
 #include <linux/cred.h>
@@ -1973,6 +1974,16 @@ struct task_struct {
        /* bitmask and counter of trace recursion */
        unsigned long trace_recursion;
 #endif /* CONFIG_TRACING */
+#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;
+       /* Buffer for coverage collection. */
+       void            *kcov_area;
+       /* kcov desciptor wired with this task or NULL. */
+       struct kcov     *kcov;
+#endif
 #ifdef CONFIG_MEMCG
        struct mem_cgroup *memcg_in_oom;
        gfp_t memcg_oom_gfp_mask;
diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
new file mode 100644 (file)
index 0000000..574e22e
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef _LINUX_KCOV_IOCTLS_H
+#define _LINUX_KCOV_IOCTLS_H
+
+#include <linux/types.h>
+
+#define KCOV_INIT_TRACE                        _IOR('c', 1, unsigned long)
+#define KCOV_ENABLE                    _IO('c', 100)
+#define KCOV_DISABLE                   _IO('c', 101)
+
+#endif /* _LINUX_KCOV_IOCTLS_H */
index 53abf008ecb39758e1812f7a593323d65e7fd304..2dea801370f2e4477d5c1c93d131ac3290f60a97 100644 (file)
@@ -19,6 +19,17 @@ CFLAGS_REMOVE_cgroup-debug.o = $(CC_FLAGS_FTRACE)
 CFLAGS_REMOVE_irq_work.o = $(CC_FLAGS_FTRACE)
 endif
 
+# Prevents flicker of uninteresting __do_softirq()/__local_bh_disable_ip()
+# in coverage traces.
+KCOV_INSTRUMENT_softirq.o := n
+# These are called from save_stack_trace() on slub debug path,
+# and produce insane amounts of uninteresting coverage.
+KCOV_INSTRUMENT_module.o := n
+KCOV_INSTRUMENT_extable.o := n
+# Don't self-instrument.
+KCOV_INSTRUMENT_kcov.o := n
+KASAN_SANITIZE_kcov.o := n
+
 # cond_syscall is currently not LTO compatible
 CFLAGS_sys_ni.o = $(DISABLE_LTO)
 
@@ -69,6 +80,7 @@ obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
 obj-$(CONFIG_AUDIT_WATCH) += audit_watch.o audit_fsnotify.o
 obj-$(CONFIG_AUDIT_TREE) += audit_tree.o
 obj-$(CONFIG_GCOV_KERNEL) += gcov/
+obj-$(CONFIG_KCOV) += kcov.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_KGDB) += debug/
 obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o
index 62c4bd4abd3a2a99aac0d4cf6fde311770cf0a0d..4ceeac2e39f44b88ff756f8a2e2fd9d18c476aff 100644 (file)
@@ -53,6 +53,7 @@
 #include <linux/oom.h>
 #include <linux/writeback.h>
 #include <linux/shm.h>
+#include <linux/kcov.h>
 
 #include "sched/tune.h"
 
@@ -659,6 +660,7 @@ void do_exit(long code)
        TASKS_RCU(int tasks_rcu_i);
 
        profile_task_exit(tsk);
+       kcov_task_exit(tsk);
 
        WARN_ON(blk_needs_flush_plug(tsk));
 
index 2633d5f61d3c54f5500ca5bd3478068e10b83281..7b6941926728d94f9dbabf121ea43fd1f1ed34cc 100644 (file)
@@ -75,6 +75,7 @@
 #include <linux/aio.h>
 #include <linux/compiler.h>
 #include <linux/sysctl.h>
+#include <linux/kcov.h>
 
 #include <asm/pgtable.h>
 #include <asm/pgalloc.h>
@@ -387,6 +388,8 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
 
        account_kernel_stack(stack, 1);
 
+       kcov_task_init(tsk);
+
        return tsk;
 
 free_stack:
diff --git a/kernel/kcov.c b/kernel/kcov.c
new file mode 100644 (file)
index 0000000..3efbee0
--- /dev/null
@@ -0,0 +1,273 @@
+#define pr_fmt(fmt) "kcov: " fmt
+
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/vmalloc.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/kcov.h>
+
+/*
+ * 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)
+ */
+struct kcov {
+       /*
+        * Reference counter. We keep one for:
+        *  - opened file descriptor
+        *  - task with enabled coverage (we can't unwire it from another task)
+        */
+       atomic_t                refcount;
+       /* 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). */
+       unsigned                size;
+       /* Coverage buffer shared with user space. */
+       void                    *area;
+       /* Task for which we collect coverage, or NULL. */
+       struct task_struct      *t;
+};
+
+/*
+ * Entry point from instrumented code.
+ * This is called once per basic-block/edge.
+ */
+void __sanitizer_cov_trace_pc(void)
+{
+       struct task_struct *t;
+       enum kcov_mode mode;
+
+       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_interrupt())
+               return;
+       mode = READ_ONCE(t->kcov_mode);
+       if (mode == KCOV_MODE_TRACE) {
+               unsigned long *area;
+               unsigned long pos;
+
+               /*
+                * 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] = _RET_IP_;
+                       WRITE_ONCE(area[0], pos);
+               }
+       }
+}
+EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
+
+static void kcov_get(struct kcov *kcov)
+{
+       atomic_inc(&kcov->refcount);
+}
+
+static void kcov_put(struct kcov *kcov)
+{
+       if (atomic_dec_and_test(&kcov->refcount)) {
+               vfree(kcov->area);
+               kfree(kcov);
+       }
+}
+
+void kcov_task_init(struct task_struct *t)
+{
+       t->kcov_mode = KCOV_MODE_DISABLED;
+       t->kcov_size = 0;
+       t->kcov_area = NULL;
+       t->kcov = NULL;
+}
+
+void kcov_task_exit(struct task_struct *t)
+{
+       struct kcov *kcov;
+
+       kcov = t->kcov;
+       if (kcov == NULL)
+               return;
+       spin_lock(&kcov->lock);
+       if (WARN_ON(kcov->t != t)) {
+               spin_unlock(&kcov->lock);
+               return;
+       }
+       /* Just to not leave dangling references behind. */
+       kcov_task_init(t);
+       kcov->t = NULL;
+       spin_unlock(&kcov->lock);
+       kcov_put(kcov);
+}
+
+static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+       int res = 0;
+       void *area;
+       struct kcov *kcov = vma->vm_file->private_data;
+       unsigned long size, off;
+       struct page *page;
+
+       area = vmalloc_user(vma->vm_end - vma->vm_start);
+       if (!area)
+               return -ENOMEM;
+
+       spin_lock(&kcov->lock);
+       size = kcov->size * sizeof(unsigned long);
+       if (kcov->mode == KCOV_MODE_DISABLED || vma->vm_pgoff != 0 ||
+           vma->vm_end - vma->vm_start != size) {
+               res = -EINVAL;
+               goto exit;
+       }
+       if (!kcov->area) {
+               kcov->area = area;
+               vma->vm_flags |= VM_DONTEXPAND;
+               spin_unlock(&kcov->lock);
+               for (off = 0; off < size; off += PAGE_SIZE) {
+                       page = vmalloc_to_page(kcov->area + off);
+                       if (vm_insert_page(vma, vma->vm_start + off, page))
+                               WARN_ONCE(1, "vm_insert_page() failed");
+               }
+               return 0;
+       }
+exit:
+       spin_unlock(&kcov->lock);
+       vfree(area);
+       return res;
+}
+
+static int kcov_open(struct inode *inode, struct file *filep)
+{
+       struct kcov *kcov;
+
+       kcov = kzalloc(sizeof(*kcov), GFP_KERNEL);
+       if (!kcov)
+               return -ENOMEM;
+       atomic_set(&kcov->refcount, 1);
+       spin_lock_init(&kcov->lock);
+       filep->private_data = kcov;
+       return nonseekable_open(inode, filep);
+}
+
+static int kcov_close(struct inode *inode, struct file *filep)
+{
+       kcov_put(filep->private_data);
+       return 0;
+}
+
+static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
+                            unsigned long arg)
+{
+       struct task_struct *t;
+       unsigned long size, unused;
+
+       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_TRACE;
+               return 0;
+       case KCOV_ENABLE:
+               /*
+                * Enable coverage for the current task.
+                * At this point user must have been enabled trace mode,
+                * and mmapped the file. Coverage collection is disabled only
+                * 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)
+                       return -EINVAL;
+               if (kcov->t != NULL)
+                       return -EBUSY;
+               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(). */
+               barrier();
+               WRITE_ONCE(t->kcov_mode, kcov->mode);
+               t->kcov = kcov;
+               kcov->t = t;
+               /* This is put either in kcov_task_exit() or in KCOV_DISABLE. */
+               kcov_get(kcov);
+               return 0;
+       case KCOV_DISABLE:
+               /* Disable coverage for the current task. */
+               unused = arg;
+               if (unused != 0 || current->kcov != kcov)
+                       return -EINVAL;
+               t = current;
+               if (WARN_ON(kcov->t != t))
+                       return -EINVAL;
+               kcov_task_init(t);
+               kcov->t = NULL;
+               kcov_put(kcov);
+               return 0;
+       default:
+               return -ENOTTY;
+       }
+}
+
+static long kcov_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
+{
+       struct kcov *kcov;
+       int res;
+
+       kcov = filep->private_data;
+       spin_lock(&kcov->lock);
+       res = kcov_ioctl_locked(kcov, cmd, arg);
+       spin_unlock(&kcov->lock);
+       return res;
+}
+
+static const struct file_operations kcov_fops = {
+       .open           = kcov_open,
+       .unlocked_ioctl = kcov_ioctl,
+       .mmap           = kcov_mmap,
+       .release        = kcov_close,
+};
+
+static int __init kcov_init(void)
+{
+       if (!debugfs_create_file("kcov", 0600, NULL, NULL, &kcov_fops)) {
+               pr_err("failed to create kcov in debugfs\n");
+               return -ENOMEM;
+       }
+       return 0;
+}
+
+device_initcall(kcov_init);
index 8e96f6cc2a4a8aa139a691afed04b163e81b1e93..31322a4275cd1c29b99cfc4d24cfcd8afdb9672d 100644 (file)
@@ -1,3 +1,6 @@
+# Any varying coverage in these files is non-deterministic
+# and is generally not a function of system call inputs.
+KCOV_INSTRUMENT                := n
 
 obj-y += mutex.o semaphore.o rwsem.o percpu-rwsem.o
 
index 61a16569ffbf53c2b19b9b67d908edcd225dc201..032b2c015beb6c01710f3a4c0c1eab6666d65e65 100644 (file)
@@ -1,3 +1,7 @@
+# Any varying coverage in these files is non-deterministic
+# and is generally not a function of system call inputs.
+KCOV_INSTRUMENT := n
+
 obj-y += update.o sync.o
 obj-$(CONFIG_SRCU) += srcu.o
 obj-$(CONFIG_RCU_TORTURE_TEST) += rcutorture.o
index d7ec4f7dd0d9b7fd2503f2e828e10351521ef22c..99378130a42f45e61503120b817379da8491926d 100644 (file)
@@ -2,6 +2,10 @@ ifdef CONFIG_FUNCTION_TRACER
 CFLAGS_REMOVE_clock.o = $(CC_FLAGS_FTRACE)
 endif
 
+# These files are disabled because they produce non-interesting flaky coverage
+# that is not a function of syscall inputs. E.g. involuntary context switches.
+KCOV_INSTRUMENT := n
+
 ifneq ($(CONFIG_SCHED_OMIT_FRAME_POINTER),y)
 # According to Alan Modra <alan@linuxcare.com.au>, the -fno-omit-frame-pointer is
 # needed for x86 only.  Why this used to be enabled for all architectures is beyond
index c879d72bf9f7549dd22b23111cbe069e38715e70..6899c7cd7898da9c41380d5edb41f65bb6221e30 100644 (file)
@@ -676,6 +676,27 @@ source "lib/Kconfig.kasan"
 
 endmenu # "Memory Debugging"
 
+config ARCH_HAS_KCOV
+       bool
+       help
+         KCOV does not have any arch-specific code, but currently it is enabled
+         only for x86_64. KCOV requires testing on other archs, and most likely
+         disabling of instrumentation for some early boot code.
+
+config KCOV
+       bool "Code coverage for fuzzing"
+       depends on ARCH_HAS_KCOV
+       select DEBUG_FS
+       help
+         KCOV exposes kernel code coverage information in a form suitable
+         for coverage-guided fuzzing (randomized testing).
+
+         If RANDOMIZE_BASE is enabled, PC values will not be stable across
+         different machines and across reboots. If you need stable PC values,
+         disable RANDOMIZE_BASE.
+
+         For more details, see Documentation/kcov.txt.
+
 config DEBUG_SHIRQ
        bool "Debug shared IRQ handlers"
        depends on DEBUG_KERNEL
index 4e26d0bea99a85ee0cb02c096b186b42d38cde20..b9ad86add71dc7ca8fd07a1dd9bac00ee8fcc88a 100644 (file)
@@ -7,6 +7,18 @@ ORIG_CFLAGS := $(KBUILD_CFLAGS)
 KBUILD_CFLAGS = $(subst $(CC_FLAGS_FTRACE),,$(ORIG_CFLAGS))
 endif
 
+# These files are disabled because they produce lots of non-interesting and/or
+# flaky coverage that is not a function of syscall inputs. For example,
+# rbtree can be global and individual rotations don't correlate with inputs.
+KCOV_INSTRUMENT_string.o := n
+KCOV_INSTRUMENT_rbtree.o := n
+KCOV_INSTRUMENT_list_debug.o := n
+KCOV_INSTRUMENT_debugobjects.o := n
+KCOV_INSTRUMENT_dynamic_debug.o := n
+# Kernel does not boot if we instrument this file as it uses custom calling
+# convention (see CONFIG_ARCH_HWEIGHT_CFLAGS).
+KCOV_INSTRUMENT_hweight.o := n
+
 lib-y := ctype.o string.o vsprintf.o cmdline.o \
         rbtree.o radix-tree.o dump_stack.o timerqueue.o\
         idr.o int_sqrt.o extable.o \
index 31b952edd501b27c6ae5e2e5c2dab9a52207cfff..ec91e951da2854650c56ab5f6e012dd418df8057 100644 (file)
@@ -9,6 +9,21 @@ KASAN_SANITIZE_slub.o := n
 # Since __builtin_frame_address does work as used, disable the warning.
 CFLAGS_usercopy.o += $(call cc-disable-warning, frame-address)
 
+# These files are disabled because they produce non-interesting and/or
+# flaky coverage that is not a function of syscall inputs. E.g. slab is out of
+# free pages, or a task is migrated between nodes.
+KCOV_INSTRUMENT_slab_common.o := n
+KCOV_INSTRUMENT_slob.o := n
+KCOV_INSTRUMENT_slab.o := n
+KCOV_INSTRUMENT_slub.o := n
+KCOV_INSTRUMENT_page_alloc.o := n
+KCOV_INSTRUMENT_debug-pagealloc.o := n
+KCOV_INSTRUMENT_kmemleak.o := n
+KCOV_INSTRUMENT_kmemcheck.o := n
+KCOV_INSTRUMENT_memcontrol.o := n
+KCOV_INSTRUMENT_mmzone.o := n
+KCOV_INSTRUMENT_vmstat.o := n
+
 mmu-y                  := nommu.o
 mmu-$(CONFIG_MMU)      := gup.o highmem.o memory.o mincore.o \
                           mlock.o mmap.o mprotect.o mremap.o msync.o rmap.o \
index 58dc45dd959427f46881e85b36ad20a85ca56a56..e1100433cefe28733fb1c2618a19eec9d0041f96 100644 (file)
@@ -1,4 +1,5 @@
 KASAN_SANITIZE := n
+KCOV_INSTRUMENT := n
 
 CFLAGS_REMOVE_kasan.o = -pg
 # Function splitter causes unnecessary splits in __asan_load1/__asan_store1
index c3b3c94e2446604c426671f74dee86090b7dbe51..4dfe62241909a89445c295a52dbcc2041a9c6121 100644 (file)
@@ -129,6 +129,12 @@ _c_flags += $(if $(patsubst n%,, \
                $(CFLAGS_KASAN))
 endif
 
+ifeq ($(CONFIG_KCOV),y)
+_c_flags += $(if $(patsubst n%,, \
+       $(KCOV_INSTRUMENT_$(basetarget).o)$(KCOV_INSTRUMENT)y), \
+       $(CFLAGS_KCOV))
+endif
+
 # If building the kernel in a separate objtree expand all occurrences
 # of -Idir to -I$(srctree)/dir except for absolute paths (starting with '/').