Skip to content

Commit 5c9a875

Browse files
dvyukovtorvalds
authored andcommitted
kernel: add kcov code coverage
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. [[email protected]: make task_struct.kcov_mode have type `enum kcov_mode'] [[email protected]: unbreak allmodconfig] [[email protected]: follow x86 Makefile layout standards] Signed-off-by: Dmitry Vyukov <[email protected]> Reviewed-by: Kees Cook <[email protected]> Cc: syzkaller <[email protected]> Cc: Vegard Nossum <[email protected]> Cc: Catalin Marinas <[email protected]> Cc: Tavis Ormandy <[email protected]> Cc: Will Deacon <[email protected]> Cc: Quentin Casasnovas <[email protected]> Cc: Kostya Serebryany <[email protected]> Cc: Eric Dumazet <[email protected]> Cc: Alexander Potapenko <[email protected]> Cc: Kees Cook <[email protected]> Cc: Bjorn Helgaas <[email protected]> Cc: Sasha Levin <[email protected]> Cc: David Drysdale <[email protected]> Cc: Ard Biesheuvel <[email protected]> Cc: Andrey Ryabinin <[email protected]> Cc: Kirill A. Shutemov <[email protected]> Cc: Jiri Slaby <[email protected]> Cc: Ingo Molnar <[email protected]> Cc: Thomas Gleixner <[email protected]> Cc: "H. Peter Anvin" <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent ade356b commit 5c9a875

File tree

28 files changed

+567
-1
lines changed

28 files changed

+567
-1
lines changed

Documentation/kcov.txt

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
kcov: code coverage for fuzzing
2+
===============================
3+
4+
kcov exposes kernel code coverage information in a form suitable for coverage-
5+
guided fuzzing (randomized testing). Coverage data of a running kernel is
6+
exported via the "kcov" debugfs file. Coverage collection is enabled on a task
7+
basis, and thus it can capture precise coverage of a single system call.
8+
9+
Note that kcov does not aim to collect as much coverage as possible. It aims
10+
to collect more or less stable coverage that is function of syscall inputs.
11+
To achieve this goal it does not collect coverage in soft/hard interrupts
12+
and instrumentation of some inherently non-deterministic parts of kernel is
13+
disbled (e.g. scheduler, locking).
14+
15+
Usage:
16+
======
17+
18+
Configure kernel with:
19+
20+
CONFIG_KCOV=y
21+
22+
CONFIG_KCOV requires gcc built on revision 231296 or later.
23+
Profiling data will only become accessible once debugfs has been mounted:
24+
25+
mount -t debugfs none /sys/kernel/debug
26+
27+
The following program demonstrates kcov usage from within a test program:
28+
29+
#include <stdio.h>
30+
#include <stddef.h>
31+
#include <stdint.h>
32+
#include <stdlib.h>
33+
#include <sys/types.h>
34+
#include <sys/stat.h>
35+
#include <sys/ioctl.h>
36+
#include <sys/mman.h>
37+
#include <unistd.h>
38+
#include <fcntl.h>
39+
40+
#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long)
41+
#define KCOV_ENABLE _IO('c', 100)
42+
#define KCOV_DISABLE _IO('c', 101)
43+
#define COVER_SIZE (64<<10)
44+
45+
int main(int argc, char **argv)
46+
{
47+
int fd;
48+
unsigned long *cover, n, i;
49+
50+
/* A single fd descriptor allows coverage collection on a single
51+
* thread.
52+
*/
53+
fd = open("/sys/kernel/debug/kcov", O_RDWR);
54+
if (fd == -1)
55+
perror("open"), exit(1);
56+
/* Setup trace mode and trace size. */
57+
if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))
58+
perror("ioctl"), exit(1);
59+
/* Mmap buffer shared between kernel- and user-space. */
60+
cover = (unsigned long*)mmap(NULL, COVER_SIZE * sizeof(unsigned long),
61+
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
62+
if ((void*)cover == MAP_FAILED)
63+
perror("mmap"), exit(1);
64+
/* Enable coverage collection on the current thread. */
65+
if (ioctl(fd, KCOV_ENABLE, 0))
66+
perror("ioctl"), exit(1);
67+
/* Reset coverage from the tail of the ioctl() call. */
68+
__atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);
69+
/* That's the target syscal call. */
70+
read(-1, NULL, 0);
71+
/* Read number of PCs collected. */
72+
n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
73+
for (i = 0; i < n; i++)
74+
printf("0x%lx\n", cover[i + 1]);
75+
/* Disable coverage collection for the current thread. After this call
76+
* coverage can be enabled for a different thread.
77+
*/
78+
if (ioctl(fd, KCOV_DISABLE, 0))
79+
perror("ioctl"), exit(1);
80+
/* Free resources. */
81+
if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))
82+
perror("munmap"), exit(1);
83+
if (close(fd))
84+
perror("close"), exit(1);
85+
return 0;
86+
}
87+
88+
After piping through addr2line output of the program looks as follows:
89+
90+
SyS_read
91+
fs/read_write.c:562
92+
__fdget_pos
93+
fs/file.c:774
94+
__fget_light
95+
fs/file.c:746
96+
__fget_light
97+
fs/file.c:750
98+
__fget_light
99+
fs/file.c:760
100+
__fdget_pos
101+
fs/file.c:784
102+
SyS_read
103+
fs/read_write.c:562
104+
105+
If a program needs to collect coverage from several threads (independently),
106+
it needs to open /sys/kernel/debug/kcov in each thread separately.
107+
108+
The interface is fine-grained to allow efficient forking of test processes.
109+
That is, a parent process opens /sys/kernel/debug/kcov, enables trace mode,
110+
mmaps coverage buffer and then forks child processes in a loop. Child processes
111+
only need to enable coverage (disable happens automatically on thread end).

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ LDFLAGS_MODULE =
365365
CFLAGS_KERNEL =
366366
AFLAGS_KERNEL =
367367
CFLAGS_GCOV = -fprofile-arcs -ftest-coverage
368+
CFLAGS_KCOV = -fsanitize-coverage=trace-pc
368369

369370

370371
# Use USERINCLUDE when you must reference the UAPI directories only.
@@ -411,7 +412,7 @@ export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
411412
export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
412413

413414
export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
414-
export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN CFLAGS_UBSAN
415+
export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KCOV CFLAGS_KASAN CFLAGS_UBSAN
415416
export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
416417
export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
417418
export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
@@ -673,6 +674,14 @@ endif
673674
endif
674675
KBUILD_CFLAGS += $(stackp-flag)
675676

677+
ifdef CONFIG_KCOV
678+
ifeq ($(call cc-option, $(CFLAGS_KCOV)),)
679+
$(warning Cannot use CONFIG_KCOV: \
680+
-fsanitize-coverage=trace-pc is not supported by compiler)
681+
CFLAGS_KCOV =
682+
endif
683+
endif
684+
676685
ifeq ($(cc-name),clang)
677686
KBUILD_CPPFLAGS += $(call cc-option,-Qunused-arguments,)
678687
KBUILD_CPPFLAGS += $(call cc-option,-Wno-unknown-warning-option,)

arch/x86/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ config X86
2828
select ARCH_HAS_ELF_RANDOMIZE
2929
select ARCH_HAS_FAST_MULTIPLIER
3030
select ARCH_HAS_GCOV_PROFILE_ALL
31+
select ARCH_HAS_KCOV if X86_64
3132
select ARCH_HAS_PMEM_API if X86_64
3233
select ARCH_HAS_MMIO_FLUSH
3334
select ARCH_HAS_SG_CHAIN

arch/x86/boot/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
KASAN_SANITIZE := n
1313
OBJECT_FILES_NON_STANDARD := y
1414

15+
# Kernel does not boot with kcov instrumentation here.
16+
# One of the problems observed was insertion of __sanitizer_cov_trace_pc()
17+
# callback into middle of per-cpu data enabling code. Thus the callback observed
18+
# inconsistent state and crashed. We are interested mostly in syscall coverage,
19+
# so boot code is not interesting anyway.
20+
KCOV_INSTRUMENT := n
21+
1522
# If you want to preset the SVGA mode, uncomment the next line and
1623
# set SVGA_MODE to whatever number you want.
1724
# Set it to -DSVGA_MODE=NORMAL_VGA if you just want the EGA/VGA mode.

arch/x86/boot/compressed/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
KASAN_SANITIZE := n
2020
OBJECT_FILES_NON_STANDARD := y
2121

22+
# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
23+
KCOV_INSTRUMENT := n
24+
2225
targets := vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 vmlinux.bin.lzma \
2326
vmlinux.bin.xz vmlinux.bin.lzo vmlinux.bin.lz4
2427

arch/x86/entry/vdso/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ KASAN_SANITIZE := n
77
UBSAN_SANITIZE := n
88
OBJECT_FILES_NON_STANDARD := y
99

10+
# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
11+
KCOV_INSTRUMENT := n
12+
1013
VDSO64-$(CONFIG_X86_64) := y
1114
VDSOX32-$(CONFIG_X86_X32_ABI) := y
1215
VDSO32-$(CONFIG_X86_32) := y

arch/x86/kernel/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ OBJECT_FILES_NON_STANDARD_relocate_kernel_$(BITS).o := y
2525
OBJECT_FILES_NON_STANDARD_mcount_$(BITS).o := y
2626
OBJECT_FILES_NON_STANDARD_test_nx.o := y
2727

28+
# If instrumentation of this dir is enabled, boot hangs during first second.
29+
# Probably could be more selective here, but note that files related to irqs,
30+
# boot, dumpstack/stacktrace, etc are either non-interesting or can lead to
31+
# non-deterministic coverage.
32+
KCOV_INSTRUMENT := n
33+
2834
CFLAGS_irq.o := -I$(src)/../include/asm/trace
2935

3036
obj-y := process_$(BITS).o signal.o

arch/x86/kernel/apic/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
# Makefile for local APIC drivers and for the IO-APIC code
33
#
44

5+
# Leads to non-deterministic coverage that is not a function of syscall inputs.
6+
# In particualr, smp_apic_timer_interrupt() is called in random places.
7+
KCOV_INSTRUMENT := n
8+
59
obj-$(CONFIG_X86_LOCAL_APIC) += apic.o apic_noop.o ipi.o vector.o
610
obj-y += hw_nmi.o
711

arch/x86/kernel/cpu/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ CFLAGS_REMOVE_common.o = -pg
88
CFLAGS_REMOVE_perf_event.o = -pg
99
endif
1010

11+
# If these files are instrumented, boot hangs during the first second.
12+
KCOV_INSTRUMENT_common.o := n
13+
KCOV_INSTRUMENT_perf_event.o := n
14+
1115
# Make sure load_percpu_segment has no stackprotector
1216
nostackp := $(call cc-option, -fno-stack-protector)
1317
CFLAGS_common.o := $(nostackp)

arch/x86/lib/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# Makefile for x86 specific library files.
33
#
44

5+
# Produces uninteresting flaky coverage.
6+
KCOV_INSTRUMENT_delay.o := n
7+
58
inat_tables_script = $(srctree)/arch/x86/tools/gen-insn-attr-x86.awk
69
inat_tables_maps = $(srctree)/arch/x86/lib/x86-opcode-map.txt
710
quiet_cmd_inat_tables = GEN $@

arch/x86/mm/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Kernel does not boot with instrumentation of tlb.c.
2+
KCOV_INSTRUMENT_tlb.o := n
3+
14
obj-y := init.o init_$(BITS).o fault.o ioremap.o extable.o pageattr.o mmap.o \
25
pat.o pgtable.o physaddr.o gup.o setup_nx.o
36

arch/x86/realmode/rm/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
KASAN_SANITIZE := n
1010
OBJECT_FILES_NON_STANDARD := y
1111

12+
# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
13+
KCOV_INSTRUMENT := n
14+
1215
always := realmode.bin realmode.relocs
1316

1417
wakeup-objs := wakeup_asm.o wakemain.o video-mode.o

drivers/firmware/efi/libstub/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ KASAN_SANITIZE := n
2525
UBSAN_SANITIZE := n
2626
OBJECT_FILES_NON_STANDARD := y
2727

28+
# Prevents link failures: __sanitizer_cov_trace_pc() is not linked in.
29+
KCOV_INSTRUMENT := n
30+
2831
lib-y := efi-stub-helper.o
2932

3033
# include the stub's generic dependencies from lib/ when building for ARM/arm64

include/linux/kcov.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#ifndef _LINUX_KCOV_H
2+
#define _LINUX_KCOV_H
3+
4+
#include <uapi/linux/kcov.h>
5+
6+
struct task_struct;
7+
8+
#ifdef CONFIG_KCOV
9+
10+
void kcov_task_init(struct task_struct *t);
11+
void kcov_task_exit(struct task_struct *t);
12+
13+
enum kcov_mode {
14+
/* Coverage collection is not enabled yet. */
15+
KCOV_MODE_DISABLED = 0,
16+
/*
17+
* Tracing coverage collection mode.
18+
* Covered PCs are collected in a per-task buffer.
19+
*/
20+
KCOV_MODE_TRACE = 1,
21+
};
22+
23+
#else
24+
25+
static inline void kcov_task_init(struct task_struct *t) {}
26+
static inline void kcov_task_exit(struct task_struct *t) {}
27+
28+
#endif /* CONFIG_KCOV */
29+
#endif /* _LINUX_KCOV_H */

include/linux/sched.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ struct sched_param {
5151
#include <linux/resource.h>
5252
#include <linux/timer.h>
5353
#include <linux/hrtimer.h>
54+
#include <linux/kcov.h>
5455
#include <linux/task_io_accounting.h>
5556
#include <linux/latencytop.h>
5657
#include <linux/cred.h>
@@ -1818,6 +1819,16 @@ struct task_struct {
18181819
/* bitmask and counter of trace recursion */
18191820
unsigned long trace_recursion;
18201821
#endif /* CONFIG_TRACING */
1822+
#ifdef CONFIG_KCOV
1823+
/* Coverage collection mode enabled for this task (0 if disabled). */
1824+
enum kcov_mode kcov_mode;
1825+
/* Size of the kcov_area. */
1826+
unsigned kcov_size;
1827+
/* Buffer for coverage collection. */
1828+
void *kcov_area;
1829+
/* kcov desciptor wired with this task or NULL. */
1830+
struct kcov *kcov;
1831+
#endif
18211832
#ifdef CONFIG_MEMCG
18221833
struct mem_cgroup *memcg_in_oom;
18231834
gfp_t memcg_oom_gfp_mask;

include/uapi/linux/kcov.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef _LINUX_KCOV_IOCTLS_H
2+
#define _LINUX_KCOV_IOCTLS_H
3+
4+
#include <linux/types.h>
5+
6+
#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long)
7+
#define KCOV_ENABLE _IO('c', 100)
8+
#define KCOV_DISABLE _IO('c', 101)
9+
10+
#endif /* _LINUX_KCOV_IOCTLS_H */

kernel/Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ ifdef CONFIG_FUNCTION_TRACER
1818
CFLAGS_REMOVE_irq_work.o = $(CC_FLAGS_FTRACE)
1919
endif
2020

21+
# Prevents flicker of uninteresting __do_softirq()/__local_bh_disable_ip()
22+
# in coverage traces.
23+
KCOV_INSTRUMENT_softirq.o := n
24+
# These are called from save_stack_trace() on slub debug path,
25+
# and produce insane amounts of uninteresting coverage.
26+
KCOV_INSTRUMENT_module.o := n
27+
KCOV_INSTRUMENT_extable.o := n
28+
# Don't self-instrument.
29+
KCOV_INSTRUMENT_kcov.o := n
30+
KASAN_SANITIZE_kcov.o := n
31+
2132
# cond_syscall is currently not LTO compatible
2233
CFLAGS_sys_ni.o = $(DISABLE_LTO)
2334

@@ -68,6 +79,7 @@ obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
6879
obj-$(CONFIG_AUDIT_WATCH) += audit_watch.o audit_fsnotify.o
6980
obj-$(CONFIG_AUDIT_TREE) += audit_tree.o
7081
obj-$(CONFIG_GCOV_KERNEL) += gcov/
82+
obj-$(CONFIG_KCOV) += kcov.o
7183
obj-$(CONFIG_KPROBES) += kprobes.o
7284
obj-$(CONFIG_KGDB) += debug/
7385
obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o

kernel/exit.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include <linux/oom.h>
5454
#include <linux/writeback.h>
5555
#include <linux/shm.h>
56+
#include <linux/kcov.h>
5657

5758
#include <asm/uaccess.h>
5859
#include <asm/unistd.h>
@@ -655,6 +656,7 @@ void do_exit(long code)
655656
TASKS_RCU(int tasks_rcu_i);
656657

657658
profile_task_exit(tsk);
659+
kcov_task_exit(tsk);
658660

659661
WARN_ON(blk_needs_flush_plug(tsk));
660662

kernel/fork.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
#include <linux/aio.h>
7676
#include <linux/compiler.h>
7777
#include <linux/sysctl.h>
78+
#include <linux/kcov.h>
7879

7980
#include <asm/pgtable.h>
8081
#include <asm/pgalloc.h>
@@ -392,6 +393,8 @@ static struct task_struct *dup_task_struct(struct task_struct *orig)
392393

393394
account_kernel_stack(ti, 1);
394395

396+
kcov_task_init(tsk);
397+
395398
return tsk;
396399

397400
free_ti:

0 commit comments

Comments
 (0)