aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Chibotaru <tchibo@google.com>2017-11-17 18:30:46 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2017-11-17 19:10:04 -0500
commitded97d2c2b2c5f1dcced0bc57133f7753b037dfc (patch)
tree2929f22e810fcd3573892b8124735a3afb6c3058
parentfcf4edac049a8bca41658970292e2dfdbc9d5f62 (diff)
kcov: support comparison operands collection
Enables kcov to collect comparison operands from instrumented code. This is done by using Clang's -fsanitize=trace-cmp instrumentation (currently not available for GCC). The comparison operands help a lot in fuzz testing. E.g. they are used in Syzkaller to cover the interiors of conditional statements with way less attempts and thus make previously unreachable code reachable. To allow separate collection of coverage and comparison operands two different work modes are implemented. Mode selection is now done via a KCOV_ENABLE ioctl call with corresponding argument value. Link: http://lkml.kernel.org/r/20171011095459.70721-1-glider@google.com Signed-off-by: Victor Chibotaru <tchibo@google.com> Signed-off-by: Alexander Potapenko <glider@google.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Andrey Konovalov <andreyknvl@google.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Alexander Popov <alex.popov@linux.com> Cc: Andrey Ryabinin <aryabinin@virtuozzo.com> Cc: Kees Cook <keescook@chromium.org> Cc: Vegard Nossum <vegard.nossum@oracle.com> Cc: Quentin Casasnovas <quentin.casasnovas@oracle.com> Cc: <syzkaller@googlegroups.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--include/linux/kcov.h12
-rw-r--r--include/uapi/linux/kcov.h24
-rw-r--r--kernel/kcov.c214
3 files changed, 211 insertions, 39 deletions
diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index f5d8ce4f4f86..3ecf6f5e3a5f 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -8,19 +8,23 @@ struct task_struct;
8 8
9#ifdef CONFIG_KCOV 9#ifdef CONFIG_KCOV
10 10
11void kcov_task_init(struct task_struct *t);
12void kcov_task_exit(struct task_struct *t);
13
14enum kcov_mode { 11enum kcov_mode {
15 /* Coverage collection is not enabled yet. */ 12 /* Coverage collection is not enabled yet. */
16 KCOV_MODE_DISABLED = 0, 13 KCOV_MODE_DISABLED = 0,
14 /* KCOV was initialized, but tracing mode hasn't been chosen yet. */
15 KCOV_MODE_INIT = 1,
17 /* 16 /*
18 * Tracing coverage collection mode. 17 * Tracing coverage collection mode.
19 * Covered PCs are collected in a per-task buffer. 18 * Covered PCs are collected in a per-task buffer.
20 */ 19 */
21 KCOV_MODE_TRACE = 1, 20 KCOV_MODE_TRACE_PC = 2,
21 /* Collecting comparison operands mode. */
22 KCOV_MODE_TRACE_CMP = 3,
22}; 23};
23 24
25void kcov_task_init(struct task_struct *t);
26void kcov_task_exit(struct task_struct *t);
27
24#else 28#else
25 29
26static inline void kcov_task_init(struct task_struct *t) {} 30static inline void kcov_task_init(struct task_struct *t) {}
diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
index 33eabbb8ada1..9529867717a8 100644
--- a/include/uapi/linux/kcov.h
+++ b/include/uapi/linux/kcov.h
@@ -8,4 +8,28 @@
8#define KCOV_ENABLE _IO('c', 100) 8#define KCOV_ENABLE _IO('c', 100)
9#define KCOV_DISABLE _IO('c', 101) 9#define KCOV_DISABLE _IO('c', 101)
10 10
11enum {
12 /*
13 * Tracing coverage collection mode.
14 * Covered PCs are collected in a per-task buffer.
15 * In new KCOV version the mode is chosen by calling
16 * ioctl(fd, KCOV_ENABLE, mode). In older versions the mode argument
17 * was supposed to be 0 in such a call. So, for reasons of backward
18 * compatibility, we have chosen the value KCOV_TRACE_PC to be 0.
19 */
20 KCOV_TRACE_PC = 0,
21 /* Collecting comparison operands mode. */
22 KCOV_TRACE_CMP = 1,
23};
24
25/*
26 * The format for the types of collected comparisons.
27 *
28 * Bit 0 shows whether one of the arguments is a compile-time constant.
29 * Bits 1 & 2 contain log2 of the argument size, up to 8 bytes.
30 */
31#define KCOV_CMP_CONST (1 << 0)
32#define KCOV_CMP_SIZE(n) ((n) << 1)
33#define KCOV_CMP_MASK KCOV_CMP_SIZE(3)
34
11#endif /* _LINUX_KCOV_IOCTLS_H */ 35#endif /* _LINUX_KCOV_IOCTLS_H */
diff --git a/kernel/kcov.c b/kernel/kcov.c
index d9f9fa9cacc6..15f33faf4013 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -22,13 +22,21 @@
22#include <linux/kcov.h> 22#include <linux/kcov.h>
23#include <asm/setup.h> 23#include <asm/setup.h>
24 24
25/* Number of 64-bit words written per one comparison: */
26#define KCOV_WORDS_PER_CMP 4
27
25/* 28/*
26 * kcov descriptor (one per opened debugfs file). 29 * kcov descriptor (one per opened debugfs file).
27 * State transitions of the descriptor: 30 * State transitions of the descriptor:
28 * - initial state after open() 31 * - initial state after open()
29 * - then there must be a single ioctl(KCOV_INIT_TRACE) call 32 * - then there must be a single ioctl(KCOV_INIT_TRACE) call
30 * - then, mmap() call (several calls are allowed but not useful) 33 * - then, mmap() call (several calls are allowed but not useful)
31 * - then, repeated enable/disable for a task (only one task a time allowed) 34 * - then, ioctl(KCOV_ENABLE, arg), where arg is
35 * KCOV_TRACE_PC - to trace only the PCs
36 * or
37 * KCOV_TRACE_CMP - to trace only the comparison operands
38 * - then, ioctl(KCOV_DISABLE) to disable the task.
39 * Enabling/disabling ioctls can be repeated (only one task a time allowed).
32 */ 40 */
33struct kcov { 41struct kcov {
34 /* 42 /*
@@ -48,51 +56,176 @@ struct kcov {
48 struct task_struct *t; 56 struct task_struct *t;
49}; 57};
50 58
51/* 59static bool check_kcov_mode(enum kcov_mode needed_mode, struct task_struct *t)
52 * Entry point from instrumented code.
53 * This is called once per basic-block/edge.
54 */
55void notrace __sanitizer_cov_trace_pc(void)
56{ 60{
57 struct task_struct *t;
58 enum kcov_mode mode; 61 enum kcov_mode mode;
59 62
60 t = current;
61 /* 63 /*
62 * We are interested in code coverage as a function of a syscall inputs, 64 * We are interested in code coverage as a function of a syscall inputs,
63 * so we ignore code executed in interrupts. 65 * so we ignore code executed in interrupts.
64 */ 66 */
65 if (!in_task()) 67 if (!in_task())
66 return; 68 return false;
67 mode = READ_ONCE(t->kcov_mode); 69 mode = READ_ONCE(t->kcov_mode);
68 if (mode == KCOV_MODE_TRACE) { 70 /*
69 unsigned long *area; 71 * There is some code that runs in interrupts but for which
70 unsigned long pos; 72 * in_interrupt() returns false (e.g. preempt_schedule_irq()).
71 unsigned long ip = _RET_IP_; 73 * READ_ONCE()/barrier() effectively provides load-acquire wrt
74 * interrupts, there are paired barrier()/WRITE_ONCE() in
75 * kcov_ioctl_locked().
76 */
77 barrier();
78 return mode == needed_mode;
79}
72 80
81static unsigned long canonicalize_ip(unsigned long ip)
82{
73#ifdef CONFIG_RANDOMIZE_BASE 83#ifdef CONFIG_RANDOMIZE_BASE
74 ip -= kaslr_offset(); 84 ip -= kaslr_offset();
75#endif 85#endif
86 return ip;
87}
76 88
77 /* 89/*
78 * There is some code that runs in interrupts but for which 90 * Entry point from instrumented code.
79 * in_interrupt() returns false (e.g. preempt_schedule_irq()). 91 * This is called once per basic-block/edge.
80 * READ_ONCE()/barrier() effectively provides load-acquire wrt 92 */
81 * interrupts, there are paired barrier()/WRITE_ONCE() in 93void notrace __sanitizer_cov_trace_pc(void)
82 * kcov_ioctl_locked(). 94{
83 */ 95 struct task_struct *t;
84 barrier(); 96 unsigned long *area;
85 area = t->kcov_area; 97 unsigned long ip = canonicalize_ip(_RET_IP_);
86 /* The first word is number of subsequent PCs. */ 98 unsigned long pos;
87 pos = READ_ONCE(area[0]) + 1; 99
88 if (likely(pos < t->kcov_size)) { 100 t = current;
89 area[pos] = ip; 101 if (!check_kcov_mode(KCOV_MODE_TRACE_PC, t))
90 WRITE_ONCE(area[0], pos); 102 return;
91 } 103
104 area = t->kcov_area;
105 /* The first 64-bit word is the number of subsequent PCs. */
106 pos = READ_ONCE(area[0]) + 1;
107 if (likely(pos < t->kcov_size)) {
108 area[pos] = ip;
109 WRITE_ONCE(area[0], pos);
92 } 110 }
93} 111}
94EXPORT_SYMBOL(__sanitizer_cov_trace_pc); 112EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
95 113
114#ifdef CONFIG_KCOV_ENABLE_COMPARISONS
115static void write_comp_data(u64 type, u64 arg1, u64 arg2, u64 ip)
116{
117 struct task_struct *t;
118 u64 *area;
119 u64 count, start_index, end_pos, max_pos;
120
121 t = current;
122 if (!check_kcov_mode(KCOV_MODE_TRACE_CMP, t))
123 return;
124
125 ip = canonicalize_ip(ip);
126
127 /*
128 * We write all comparison arguments and types as u64.
129 * The buffer was allocated for t->kcov_size unsigned longs.
130 */
131 area = (u64 *)t->kcov_area;
132 max_pos = t->kcov_size * sizeof(unsigned long);
133
134 count = READ_ONCE(area[0]);
135
136 /* Every record is KCOV_WORDS_PER_CMP 64-bit words. */
137 start_index = 1 + count * KCOV_WORDS_PER_CMP;
138 end_pos = (start_index + KCOV_WORDS_PER_CMP) * sizeof(u64);
139 if (likely(end_pos <= max_pos)) {
140 area[start_index] = type;
141 area[start_index + 1] = arg1;
142 area[start_index + 2] = arg2;
143 area[start_index + 3] = ip;
144 WRITE_ONCE(area[0], count + 1);
145 }
146}
147
148void notrace __sanitizer_cov_trace_cmp1(u8 arg1, u8 arg2)
149{
150 write_comp_data(KCOV_CMP_SIZE(0), arg1, arg2, _RET_IP_);
151}
152EXPORT_SYMBOL(__sanitizer_cov_trace_cmp1);
153
154void notrace __sanitizer_cov_trace_cmp2(u16 arg1, u16 arg2)
155{
156 write_comp_data(KCOV_CMP_SIZE(1), arg1, arg2, _RET_IP_);
157}
158EXPORT_SYMBOL(__sanitizer_cov_trace_cmp2);
159
160void notrace __sanitizer_cov_trace_cmp4(u16 arg1, u16 arg2)
161{
162 write_comp_data(KCOV_CMP_SIZE(2), arg1, arg2, _RET_IP_);
163}
164EXPORT_SYMBOL(__sanitizer_cov_trace_cmp4);
165
166void notrace __sanitizer_cov_trace_cmp8(u64 arg1, u64 arg2)
167{
168 write_comp_data(KCOV_CMP_SIZE(3), arg1, arg2, _RET_IP_);
169}
170EXPORT_SYMBOL(__sanitizer_cov_trace_cmp8);
171
172void notrace __sanitizer_cov_trace_const_cmp1(u8 arg1, u8 arg2)
173{
174 write_comp_data(KCOV_CMP_SIZE(0) | KCOV_CMP_CONST, arg1, arg2,
175 _RET_IP_);
176}
177EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp1);
178
179void notrace __sanitizer_cov_trace_const_cmp2(u16 arg1, u16 arg2)
180{
181 write_comp_data(KCOV_CMP_SIZE(1) | KCOV_CMP_CONST, arg1, arg2,
182 _RET_IP_);
183}
184EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp2);
185
186void notrace __sanitizer_cov_trace_const_cmp4(u16 arg1, u16 arg2)
187{
188 write_comp_data(KCOV_CMP_SIZE(2) | KCOV_CMP_CONST, arg1, arg2,
189 _RET_IP_);
190}
191EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp4);
192
193void notrace __sanitizer_cov_trace_const_cmp8(u64 arg1, u64 arg2)
194{
195 write_comp_data(KCOV_CMP_SIZE(3) | KCOV_CMP_CONST, arg1, arg2,
196 _RET_IP_);
197}
198EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp8);
199
200void notrace __sanitizer_cov_trace_switch(u64 val, u64 *cases)
201{
202 u64 i;
203 u64 count = cases[0];
204 u64 size = cases[1];
205 u64 type = KCOV_CMP_CONST;
206
207 switch (size) {
208 case 8:
209 type |= KCOV_CMP_SIZE(0);
210 break;
211 case 16:
212 type |= KCOV_CMP_SIZE(1);
213 break;
214 case 32:
215 type |= KCOV_CMP_SIZE(2);
216 break;
217 case 64:
218 type |= KCOV_CMP_SIZE(3);
219 break;
220 default:
221 return;
222 }
223 for (i = 0; i < count; i++)
224 write_comp_data(type, cases[i + 2], val, _RET_IP_);
225}
226EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
227#endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
228
96static void kcov_get(struct kcov *kcov) 229static void kcov_get(struct kcov *kcov)
97{ 230{
98 atomic_inc(&kcov->refcount); 231 atomic_inc(&kcov->refcount);
@@ -129,6 +262,7 @@ void kcov_task_exit(struct task_struct *t)
129 /* Just to not leave dangling references behind. */ 262 /* Just to not leave dangling references behind. */
130 kcov_task_init(t); 263 kcov_task_init(t);
131 kcov->t = NULL; 264 kcov->t = NULL;
265 kcov->mode = KCOV_MODE_INIT;
132 spin_unlock(&kcov->lock); 266 spin_unlock(&kcov->lock);
133 kcov_put(kcov); 267 kcov_put(kcov);
134} 268}
@@ -147,7 +281,7 @@ static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
147 281
148 spin_lock(&kcov->lock); 282 spin_lock(&kcov->lock);
149 size = kcov->size * sizeof(unsigned long); 283 size = kcov->size * sizeof(unsigned long);
150 if (kcov->mode == KCOV_MODE_DISABLED || vma->vm_pgoff != 0 || 284 if (kcov->mode != KCOV_MODE_INIT || vma->vm_pgoff != 0 ||
151 vma->vm_end - vma->vm_start != size) { 285 vma->vm_end - vma->vm_start != size) {
152 res = -EINVAL; 286 res = -EINVAL;
153 goto exit; 287 goto exit;
@@ -176,6 +310,7 @@ static int kcov_open(struct inode *inode, struct file *filep)
176 kcov = kzalloc(sizeof(*kcov), GFP_KERNEL); 310 kcov = kzalloc(sizeof(*kcov), GFP_KERNEL);
177 if (!kcov) 311 if (!kcov)
178 return -ENOMEM; 312 return -ENOMEM;
313 kcov->mode = KCOV_MODE_DISABLED;
179 atomic_set(&kcov->refcount, 1); 314 atomic_set(&kcov->refcount, 1);
180 spin_lock_init(&kcov->lock); 315 spin_lock_init(&kcov->lock);
181 filep->private_data = kcov; 316 filep->private_data = kcov;
@@ -211,7 +346,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
211 if (size < 2 || size > INT_MAX / sizeof(unsigned long)) 346 if (size < 2 || size > INT_MAX / sizeof(unsigned long))
212 return -EINVAL; 347 return -EINVAL;
213 kcov->size = size; 348 kcov->size = size;
214 kcov->mode = KCOV_MODE_TRACE; 349 kcov->mode = KCOV_MODE_INIT;
215 return 0; 350 return 0;
216 case KCOV_ENABLE: 351 case KCOV_ENABLE:
217 /* 352 /*
@@ -221,17 +356,25 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
221 * at task exit or voluntary by KCOV_DISABLE. After that it can 356 * at task exit or voluntary by KCOV_DISABLE. After that it can
222 * be enabled for another task. 357 * be enabled for another task.
223 */ 358 */
224 unused = arg; 359 if (kcov->mode != KCOV_MODE_INIT || !kcov->area)
225 if (unused != 0 || kcov->mode == KCOV_MODE_DISABLED ||
226 kcov->area == NULL)
227 return -EINVAL; 360 return -EINVAL;
228 if (kcov->t != NULL) 361 if (kcov->t != NULL)
229 return -EBUSY; 362 return -EBUSY;
363 if (arg == KCOV_TRACE_PC)
364 kcov->mode = KCOV_MODE_TRACE_PC;
365 else if (arg == KCOV_TRACE_CMP)
366#ifdef CONFIG_KCOV_ENABLE_COMPARISONS
367 kcov->mode = KCOV_MODE_TRACE_CMP;
368#else
369 return -ENOTSUPP;
370#endif
371 else
372 return -EINVAL;
230 t = current; 373 t = current;
231 /* Cache in task struct for performance. */ 374 /* Cache in task struct for performance. */
232 t->kcov_size = kcov->size; 375 t->kcov_size = kcov->size;
233 t->kcov_area = kcov->area; 376 t->kcov_area = kcov->area;
234 /* See comment in __sanitizer_cov_trace_pc(). */ 377 /* See comment in check_kcov_mode(). */
235 barrier(); 378 barrier();
236 WRITE_ONCE(t->kcov_mode, kcov->mode); 379 WRITE_ONCE(t->kcov_mode, kcov->mode);
237 t->kcov = kcov; 380 t->kcov = kcov;
@@ -249,6 +392,7 @@ static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
249 return -EINVAL; 392 return -EINVAL;
250 kcov_task_init(t); 393 kcov_task_init(t);
251 kcov->t = NULL; 394 kcov->t = NULL;
395 kcov->mode = KCOV_MODE_INIT;
252 kcov_put(kcov); 396 kcov_put(kcov);
253 return 0; 397 return 0;
254 default: 398 default: