Blame view

kernel/kcov.c 11.1 KB
b24413180   Greg Kroah-Hartman   License cleanup: ...
1
  // SPDX-License-Identifier: GPL-2.0
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
2
  #define pr_fmt(fmt) "kcov: " fmt
36f05ae8b   Andrey Ryabinin   kcov: don't profi...
3
  #define DISABLE_BRANCH_PROFILING
db862358a   Kefeng Wang   kcov: add more mi...
4
  #include <linux/atomic.h>
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
5
  #include <linux/compiler.h>
db862358a   Kefeng Wang   kcov: add more mi...
6
7
  #include <linux/errno.h>
  #include <linux/export.h>
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
8
9
10
  #include <linux/types.h>
  #include <linux/file.h>
  #include <linux/fs.h>
db862358a   Kefeng Wang   kcov: add more mi...
11
  #include <linux/init.h>
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
12
  #include <linux/mm.h>
db862358a   Kefeng Wang   kcov: add more mi...
13
  #include <linux/preempt.h>
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
14
  #include <linux/printk.h>
166ad0e1e   Kefeng Wang   kcov: add missing...
15
  #include <linux/sched.h>
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
16
17
18
19
20
21
  #include <linux/slab.h>
  #include <linux/spinlock.h>
  #include <linux/vmalloc.h>
  #include <linux/debugfs.h>
  #include <linux/uaccess.h>
  #include <linux/kcov.h>
4983f0ab7   Alexander Popov   kcov: make kcov w...
22
  #include <asm/setup.h>
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
23

ded97d2c2   Victor Chibotaru   kcov: support com...
24
25
  /* Number of 64-bit words written per one comparison: */
  #define KCOV_WORDS_PER_CMP 4
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
26
27
28
29
30
31
  /*
   * 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)
ded97d2c2   Victor Chibotaru   kcov: support com...
32
33
34
35
36
37
   *  - then, ioctl(KCOV_ENABLE, arg), where arg is
   *	KCOV_TRACE_PC - to trace only the PCs
   *	or
   *	KCOV_TRACE_CMP - to trace only the comparison operands
   *  - then, ioctl(KCOV_DISABLE) to disable the task.
   * Enabling/disabling ioctls can be repeated (only one task a time allowed).
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
   */
  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;
  };
c69000151   Anders Roxell   kernel/kcov.c: ma...
56
  static notrace bool check_kcov_mode(enum kcov_mode needed_mode, struct task_struct *t)
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
57
  {
0ed557aa8   Mark Rutland   sched/core / kcov...
58
  	unsigned int mode;
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
59

5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
60
61
62
63
  	/*
  	 * We are interested in code coverage as a function of a syscall inputs,
  	 * so we ignore code executed in interrupts.
  	 */
fcf4edac0   Andrey Ryabinin   kcov: remove poin...
64
  	if (!in_task())
ded97d2c2   Victor Chibotaru   kcov: support com...
65
  		return false;
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
66
  	mode = READ_ONCE(t->kcov_mode);
ded97d2c2   Victor Chibotaru   kcov: support com...
67
68
69
70
71
72
73
74
75
76
  	/*
  	 * 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();
  	return mode == needed_mode;
  }
4983f0ab7   Alexander Popov   kcov: make kcov w...
77

c69000151   Anders Roxell   kernel/kcov.c: ma...
78
  static notrace unsigned long canonicalize_ip(unsigned long ip)
ded97d2c2   Victor Chibotaru   kcov: support com...
79
  {
4983f0ab7   Alexander Popov   kcov: make kcov w...
80
  #ifdef CONFIG_RANDOMIZE_BASE
ded97d2c2   Victor Chibotaru   kcov: support com...
81
  	ip -= kaslr_offset();
4983f0ab7   Alexander Popov   kcov: make kcov w...
82
  #endif
ded97d2c2   Victor Chibotaru   kcov: support com...
83
84
  	return ip;
  }
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
85

ded97d2c2   Victor Chibotaru   kcov: support com...
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
  /*
   * Entry point from instrumented code.
   * This is called once per basic-block/edge.
   */
  void notrace __sanitizer_cov_trace_pc(void)
  {
  	struct task_struct *t;
  	unsigned long *area;
  	unsigned long ip = canonicalize_ip(_RET_IP_);
  	unsigned long pos;
  
  	t = current;
  	if (!check_kcov_mode(KCOV_MODE_TRACE_PC, t))
  		return;
  
  	area = t->kcov_area;
  	/* The first 64-bit word is the number of subsequent PCs. */
  	pos = READ_ONCE(area[0]) + 1;
  	if (likely(pos < t->kcov_size)) {
  		area[pos] = ip;
  		WRITE_ONCE(area[0], pos);
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
107
108
109
  	}
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_pc);
ded97d2c2   Victor Chibotaru   kcov: support com...
110
  #ifdef CONFIG_KCOV_ENABLE_COMPARISONS
58e57bcbc   Anders Roxell   kernel/kcov.c: ma...
111
  static void notrace write_comp_data(u64 type, u64 arg1, u64 arg2, u64 ip)
ded97d2c2   Victor Chibotaru   kcov: support com...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
  {
  	struct task_struct *t;
  	u64 *area;
  	u64 count, start_index, end_pos, max_pos;
  
  	t = current;
  	if (!check_kcov_mode(KCOV_MODE_TRACE_CMP, t))
  		return;
  
  	ip = canonicalize_ip(ip);
  
  	/*
  	 * We write all comparison arguments and types as u64.
  	 * The buffer was allocated for t->kcov_size unsigned longs.
  	 */
  	area = (u64 *)t->kcov_area;
  	max_pos = t->kcov_size * sizeof(unsigned long);
  
  	count = READ_ONCE(area[0]);
  
  	/* Every record is KCOV_WORDS_PER_CMP 64-bit words. */
  	start_index = 1 + count * KCOV_WORDS_PER_CMP;
  	end_pos = (start_index + KCOV_WORDS_PER_CMP) * sizeof(u64);
  	if (likely(end_pos <= max_pos)) {
  		area[start_index] = type;
  		area[start_index + 1] = arg1;
  		area[start_index + 2] = arg2;
  		area[start_index + 3] = ip;
  		WRITE_ONCE(area[0], count + 1);
  	}
  }
  
  void notrace __sanitizer_cov_trace_cmp1(u8 arg1, u8 arg2)
  {
  	write_comp_data(KCOV_CMP_SIZE(0), arg1, arg2, _RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_cmp1);
  
  void notrace __sanitizer_cov_trace_cmp2(u16 arg1, u16 arg2)
  {
  	write_comp_data(KCOV_CMP_SIZE(1), arg1, arg2, _RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_cmp2);
689d77f00   Dmitry Vyukov   kcov: fix compari...
155
  void notrace __sanitizer_cov_trace_cmp4(u32 arg1, u32 arg2)
ded97d2c2   Victor Chibotaru   kcov: support com...
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
  {
  	write_comp_data(KCOV_CMP_SIZE(2), arg1, arg2, _RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_cmp4);
  
  void notrace __sanitizer_cov_trace_cmp8(u64 arg1, u64 arg2)
  {
  	write_comp_data(KCOV_CMP_SIZE(3), arg1, arg2, _RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_cmp8);
  
  void notrace __sanitizer_cov_trace_const_cmp1(u8 arg1, u8 arg2)
  {
  	write_comp_data(KCOV_CMP_SIZE(0) | KCOV_CMP_CONST, arg1, arg2,
  			_RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp1);
  
  void notrace __sanitizer_cov_trace_const_cmp2(u16 arg1, u16 arg2)
  {
  	write_comp_data(KCOV_CMP_SIZE(1) | KCOV_CMP_CONST, arg1, arg2,
  			_RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp2);
689d77f00   Dmitry Vyukov   kcov: fix compari...
180
  void notrace __sanitizer_cov_trace_const_cmp4(u32 arg1, u32 arg2)
ded97d2c2   Victor Chibotaru   kcov: support com...
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
  {
  	write_comp_data(KCOV_CMP_SIZE(2) | KCOV_CMP_CONST, arg1, arg2,
  			_RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp4);
  
  void notrace __sanitizer_cov_trace_const_cmp8(u64 arg1, u64 arg2)
  {
  	write_comp_data(KCOV_CMP_SIZE(3) | KCOV_CMP_CONST, arg1, arg2,
  			_RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_const_cmp8);
  
  void notrace __sanitizer_cov_trace_switch(u64 val, u64 *cases)
  {
  	u64 i;
  	u64 count = cases[0];
  	u64 size = cases[1];
  	u64 type = KCOV_CMP_CONST;
  
  	switch (size) {
  	case 8:
  		type |= KCOV_CMP_SIZE(0);
  		break;
  	case 16:
  		type |= KCOV_CMP_SIZE(1);
  		break;
  	case 32:
  		type |= KCOV_CMP_SIZE(2);
  		break;
  	case 64:
  		type |= KCOV_CMP_SIZE(3);
  		break;
  	default:
  		return;
  	}
  	for (i = 0; i < count; i++)
  		write_comp_data(type, cases[i + 2], val, _RET_IP_);
  }
  EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
  #endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
  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)
  {
c9484b986   Mark Rutland   kcov: ensure irq ...
237
238
  	WRITE_ONCE(t->kcov_mode, KCOV_MODE_DISABLED);
  	barrier();
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
  	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;
ded97d2c2   Victor Chibotaru   kcov: support com...
259
  	kcov->mode = KCOV_MODE_INIT;
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
  	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);
ded97d2c2   Victor Chibotaru   kcov: support com...
278
  	if (kcov->mode != KCOV_MODE_INIT || vma->vm_pgoff != 0 ||
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
  	    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;
ded97d2c2   Victor Chibotaru   kcov: support com...
307
  	kcov->mode = KCOV_MODE_DISABLED;
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
308
309
310
311
312
313
314
315
316
317
318
  	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;
  }
dc55daff9   Mark Rutland   kcov: prefault th...
319
320
321
322
323
324
325
326
327
328
329
330
331
332
  /*
   * Fault in a lazily-faulted vmalloc area before it can be used by
   * __santizer_cov_trace_pc(), to avoid recursion issues if any code on the
   * vmalloc fault handling path is instrumented.
   */
  static void kcov_fault_in_area(struct kcov *kcov)
  {
  	unsigned long stride = PAGE_SIZE / sizeof(unsigned long);
  	unsigned long *area = kcov->area;
  	unsigned long offset;
  
  	for (offset = 0; offset < kcov->size; offset += stride)
  		READ_ONCE(area[offset]);
  }
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
  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;
ded97d2c2   Victor Chibotaru   kcov: support com...
356
  		kcov->mode = KCOV_MODE_INIT;
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
357
358
359
360
361
362
363
364
365
  		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.
  		 */
ded97d2c2   Victor Chibotaru   kcov: support com...
366
  		if (kcov->mode != KCOV_MODE_INIT || !kcov->area)
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
367
  			return -EINVAL;
a77660d23   Dmitry Vyukov   kcov: detect doub...
368
369
  		t = current;
  		if (kcov->t != NULL || t->kcov != NULL)
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
370
  			return -EBUSY;
ded97d2c2   Victor Chibotaru   kcov: support com...
371
372
373
374
375
376
377
378
379
380
  		if (arg == KCOV_TRACE_PC)
  			kcov->mode = KCOV_MODE_TRACE_PC;
  		else if (arg == KCOV_TRACE_CMP)
  #ifdef CONFIG_KCOV_ENABLE_COMPARISONS
  			kcov->mode = KCOV_MODE_TRACE_CMP;
  #else
  		return -ENOTSUPP;
  #endif
  		else
  			return -EINVAL;
dc55daff9   Mark Rutland   kcov: prefault th...
381
  		kcov_fault_in_area(kcov);
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
382
383
384
  		/* Cache in task struct for performance. */
  		t->kcov_size = kcov->size;
  		t->kcov_area = kcov->area;
ded97d2c2   Victor Chibotaru   kcov: support com...
385
  		/* See comment in check_kcov_mode(). */
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
  		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;
ded97d2c2   Victor Chibotaru   kcov: support com...
403
  		kcov->mode = KCOV_MODE_INIT;
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
  		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,
7483e5d42   Dmitry Vyukov   kcov: support com...
426
  	.compat_ioctl	= kcov_ioctl,
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
427
428
429
430
431
432
  	.mmap		= kcov_mmap,
  	.release        = kcov_close,
  };
  
  static int __init kcov_init(void)
  {
df4565f9e   Nicolai Stange   kernel/kcov: unpr...
433
434
435
436
437
438
  	/*
  	 * The kcov debugfs file won't ever get removed and thus,
  	 * there is no need to protect it against removal races. The
  	 * use of debugfs_create_file_unsafe() is actually safe here.
  	 */
  	if (!debugfs_create_file_unsafe("kcov", 0600, NULL, NULL, &kcov_fops)) {
5c9a8750a   Dmitry Vyukov   kernel: add kcov ...
439
440
441
442
443
444
445
446
  		pr_err("failed to create kcov in debugfs
  ");
  		return -ENOMEM;
  	}
  	return 0;
  }
  
  device_initcall(kcov_init);