summaryrefslogtreecommitdiff
path: root/sys/dev/kcov.c
diff options
context:
space:
mode:
authoranton <anton@cvs.openbsd.org>2018-08-19 11:42:34 +0000
committeranton <anton@cvs.openbsd.org>2018-08-19 11:42:34 +0000
commitccc7f034a58f63768110e9b4b82324eee3597706 (patch)
treec9496c52dfbf2469806099cdfbb26e5d108f4c88 /sys/dev/kcov.c
parent63e6caa26564e0dc0de5c358a1401413ea2b7e8c (diff)
Add kcov(4), a kernel code coverage tracing driver. It's used in conjunction
with the syzkaller kernel fuzzer. So far, 8 distinct panics have been found and fixed. This effort will continue. kcov is limited to architectures using Clang as their default compiler and is not enabled by default. With help from mpi@, thanks! ok kettenis@ mpi@ visa@
Diffstat (limited to 'sys/dev/kcov.c')
-rw-r--r--sys/dev/kcov.c277
1 files changed, 277 insertions, 0 deletions
diff --git a/sys/dev/kcov.c b/sys/dev/kcov.c
new file mode 100644
index 00000000000..cc8686a7927
--- /dev/null
+++ b/sys/dev/kcov.c
@@ -0,0 +1,277 @@
+/* $OpenBSD: kcov.c,v 1.1 2018/08/19 11:42:33 anton Exp $ */
+
+/*
+ * Copyright (c) 2018 Anton Lindqvist <anton@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/proc.h>
+#include <sys/kcov.h>
+#include <sys/malloc.h>
+#include <sys/stdint.h>
+#include <sys/queue.h>
+
+#include <uvm/uvm_extern.h>
+
+/* #define KCOV_DEBUG */
+#ifdef KCOV_DEBUG
+#define DPRINTF(x...) do { if (kcov_debug) printf(x); } while (0)
+#else
+#define DPRINTF(x...)
+#endif
+
+/* kcov descriptor */
+struct kd {
+ enum {
+ KCOV_MODE_DISABLED,
+ KCOV_MODE_INIT,
+ KCOV_MODE_TRACE_PC,
+ } kd_mode;
+ int kd_unit; /* device minor */
+ pid_t kd_pid; /* process being traced */
+ uintptr_t *kd_buf; /* traced coverage */
+ size_t kd_nmemb;
+ size_t kd_size;
+
+ TAILQ_ENTRY(kd) kd_entry;
+};
+
+void kcovattach(int);
+
+int kd_alloc(struct kd *, unsigned long);
+struct kd *kd_lookup(int);
+
+static inline struct kd *kd_lookup_pid(pid_t);
+static inline int inintr(void);
+
+TAILQ_HEAD(, kd) kd_list = TAILQ_HEAD_INITIALIZER(kd_list);
+
+#ifdef KCOV_DEBUG
+int kcov_debug = 1;
+#endif
+
+/*
+ * Compiling the kernel with the `-fsanitize-coverage=trace-pc' option will
+ * cause the following function to be called upon function entry and before
+ * each block instructions that maps to a single line in the original source
+ * code.
+ *
+ * If kcov is enabled for the current process, the executed address will be
+ * stored in the corresponding coverage buffer.
+ * The first element in the coverage buffer holds the index of next available
+ * element.
+ */
+void
+__sanitizer_cov_trace_pc(void)
+{
+ extern int cold;
+ struct kd *kd;
+ uint64_t idx;
+
+ /* Do not trace during boot. */
+ if (cold)
+ return;
+
+ /* Do not trace in interrupts to prevent noisy coverage. */
+ if (inintr())
+ return;
+
+ kd = kd_lookup_pid(curproc->p_p->ps_pid);
+ if (kd == NULL)
+ return;
+
+ idx = kd->kd_buf[0];
+ if (idx < kd->kd_nmemb) {
+ kd->kd_buf[idx + 1] = (uintptr_t)__builtin_return_address(0);
+ kd->kd_buf[0] = idx + 1;
+ }
+}
+
+void
+kcovattach(int count)
+{
+}
+
+int
+kcovopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+#ifdef KCOV
+ struct kd *kd;
+
+ if (kd_lookup(minor(dev)) != NULL)
+ return (EBUSY);
+
+ DPRINTF("%s: unit=%d\n", __func__, minor(dev));
+
+ kd = malloc(sizeof(*kd), M_SUBPROC, M_WAITOK | M_ZERO);
+ kd->kd_unit = minor(dev);
+ TAILQ_INSERT_TAIL(&kd_list, kd, kd_entry);
+ return (0);
+#else
+ return (ENXIO);
+#endif
+}
+
+int
+kcovclose(dev_t dev, int flag, int mode, struct proc *p)
+{
+ struct kd *kd;
+
+ kd = kd_lookup(minor(dev));
+ if (kd == NULL)
+ return (EINVAL);
+
+ DPRINTF("%s: unit=%d\n", __func__, minor(dev));
+
+ TAILQ_REMOVE(&kd_list, kd, kd_entry);
+ free(kd->kd_buf, M_SUBPROC, kd->kd_size);
+ free(kd, M_SUBPROC, sizeof(struct kd));
+ return (0);
+}
+
+int
+kcovioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct kd *kd;
+ int error = 0;
+
+ kd = kd_lookup(minor(dev));
+ if (kd == NULL)
+ return (ENXIO);
+
+ switch (cmd) {
+ case KIOSETBUFSIZE:
+ if (kd->kd_mode != KCOV_MODE_DISABLED) {
+ error = EBUSY;
+ break;
+ }
+ error = kd_alloc(kd, *((unsigned long *)data));
+ if (error == 0)
+ kd->kd_mode = KCOV_MODE_INIT;
+ break;
+ case KIOENABLE:
+ if (kd->kd_mode != KCOV_MODE_INIT) {
+ error = EBUSY;
+ break;
+ }
+ kd->kd_mode = KCOV_MODE_TRACE_PC;
+ kd->kd_pid = p->p_p->ps_pid;
+ break;
+ case KIODISABLE:
+ /* Only the enabled process may disable itself. */
+ if (kd->kd_pid != p->p_p->ps_pid ||
+ kd->kd_mode != KCOV_MODE_TRACE_PC) {
+ error = EBUSY;
+ break;
+ }
+ kd->kd_mode = KCOV_MODE_INIT;
+ kd->kd_pid = 0;
+ break;
+ default:
+ error = EINVAL;
+ DPRINTF("%s: %lu: unknown command\n", __func__, cmd);
+ }
+
+ DPRINTF("%s: unit=%d, mode=%d, pid=%d, error=%d\n",
+ __func__, kd->kd_unit, kd->kd_mode, kd->kd_pid, error);
+
+ return (error);
+}
+
+paddr_t
+kcovmmap(dev_t dev, off_t offset, int prot)
+{
+ struct kd *kd;
+ paddr_t pa;
+ vaddr_t va;
+
+ kd = kd_lookup(minor(dev));
+ if (kd == NULL)
+ return (paddr_t)(-1);
+
+ if (offset < 0 || offset >= kd->kd_nmemb * sizeof(uintptr_t))
+ return (paddr_t)(-1);
+
+ va = (vaddr_t)kd->kd_buf + offset;
+ if (pmap_extract(pmap_kernel(), va, &pa) == FALSE)
+ return (paddr_t)(-1);
+ return (pa);
+}
+
+void
+kcov_exit(struct proc *p)
+{
+ struct kd *kd;
+
+ kd = kd_lookup_pid(p->p_p->ps_pid);
+ if (kd == NULL)
+ return;
+
+ kd->kd_mode = KCOV_MODE_INIT;
+ kd->kd_pid = 0;
+}
+
+struct kd *
+kd_lookup(int unit)
+{
+ struct kd *kd;
+
+ TAILQ_FOREACH(kd, &kd_list, kd_entry) {
+ if (kd->kd_unit == unit)
+ return (kd);
+ }
+ return (NULL);
+}
+
+int
+kd_alloc(struct kd *kd, unsigned long nmemb)
+{
+ size_t size;
+
+ KASSERT(kd->kd_buf == NULL);
+
+ if (nmemb == 0 || nmemb > KCOV_BUF_MAX_NMEMB)
+ return (EINVAL);
+
+ size = roundup(nmemb * sizeof(uintptr_t), PAGE_SIZE);
+ kd->kd_buf = malloc(size, M_SUBPROC, M_WAITOK | M_ZERO);
+ /* The first element is reserved to hold the number of used elements. */
+ kd->kd_nmemb = nmemb - 1;
+ kd->kd_size = size;
+ return (0);
+}
+
+static inline struct kd *
+kd_lookup_pid(pid_t pid)
+{
+ struct kd *kd;
+
+ TAILQ_FOREACH(kd, &kd_list, kd_entry) {
+ if (kd->kd_pid == pid && kd->kd_mode == KCOV_MODE_TRACE_PC)
+ return (kd);
+ }
+ return (NULL);
+}
+
+static inline int
+inintr(void)
+{
+#if defined(__amd64__) || defined(__i386__)
+ return (curcpu()->ci_idepth > 0);
+#else
+ return (0);
+#endif
+}