From c2118d72cbda89a6a39ef1bc7c837c1599d4fe9b Mon Sep 17 00:00:00 2001
From: Vegard Nossum <vegard.nossum@oracle.com>
Date: Fri, 21 May 2021 11:26:03 +0200
Subject: [PATCH] kcov: add dereference tracing mode

So far just a sketch, proper changelog TBD.

Example user program (can be run as init):

	#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>

	#if 1 // init
	#include <sys/mount.h>
	#include <errno.h>
	#include <error.h>
	#endif

	#define KCOV_INIT_TRACE			_IOR('c', 1, unsigned long)
	#define KCOV_ENABLE			_IO('c', 100)
	#define KCOV_DISABLE			_IO('c', 101)
	#define COVER_SIZE			512 // 1 page

	#define KCOV_TRACE_PC  0
	#define KCOV_TRACE_CMP 1
	#define KCOV_TRACE_DEREF 2

	int main(int argc, char **argv)
	{
	    uint64_t type, arg1, arg2, is_const, size;
	    unsigned long n, i;

	#if 1 // init
	    mkdir("/sys", 0755);
	    if (mount("nodev", "/sys", "sysfs", 0, "") == -1)
		error(EXIT_FAILURE, errno, "mount(/sys)");

	    if (mount("nodev", "/sys/kernel/debug", "debugfs", 0, "") == -1)
		error(EXIT_FAILURE, errno, "mount(/sys/kernel/debug)");
	#endif

	    int fd = open("/sys/kernel/debug/kcov", O_RDWR);
	    if (fd == -1)
		perror("open"), exit(1);

	    if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))
		perror("ioctl"), exit(1);

	    uint64_t *cover = (uint64_t *) mmap(NULL, COVER_SIZE * sizeof(unsigned long),
		PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	    if ((void*)cover == MAP_FAILED)
		perror("mmap"), exit(1);

	    if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_DEREF))
		perror("ioctl"), exit(1);

	    // just do something...
	    char buf[1024];
	    read(open("/sys/kernel/slab/kmalloc-32/order", O_RDONLY), buf, sizeof(buf));

	    if (ioctl(fd, KCOV_DISABLE, 0))
		perror("ioctl"), exit(1);

	    for (i = 0; i < COVER_SIZE; i++)
		printf("%08lx", cover[i]);
	    printf("\n");

	    /* Free resources. */
	    if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))
		perror("munmap"), exit(1);

	    if (close(fd))
		perror("close"), exit(1);

	    return 0;
	}

Signed-off-by: Vegard Nossum <vegard.nossum@oracle.com>
---
 include/linux/kcov.h                      |   2 +
 include/uapi/linux/kcov.h                 |   2 +
 kernel/kcov.c                             |  41 +++-
 lib/Kconfig.debug                         |   7 +
 scripts/Makefile.gcc-plugins              |   2 +
 scripts/Makefile.kcov                     |   1 +
 scripts/gcc-plugins/Kconfig               |   6 +
 scripts/gcc-plugins/sancov_deref_plugin.c | 221 ++++++++++++++++++++++
 8 files changed, 277 insertions(+), 5 deletions(-)
 create mode 100644 scripts/gcc-plugins/sancov_deref_plugin.c

diff --git a/include/linux/kcov.h b/include/linux/kcov.h
index 55dc338f6bcdd..3d1f359437a38 100644
--- a/include/linux/kcov.h
+++ b/include/linux/kcov.h
@@ -21,6 +21,8 @@ enum kcov_mode {
 	KCOV_MODE_TRACE_PC = 2,
 	/* Collecting comparison operands mode. */
 	KCOV_MODE_TRACE_CMP = 3,
+	/* Collecting dereferences mode. */
+	KCOV_MODE_TRACE_DEREF = 4,
 };
 
 #define KCOV_IN_CTXSW	(1 << 30)
diff --git a/include/uapi/linux/kcov.h b/include/uapi/linux/kcov.h
index 1d0350e44ae34..25c4f2a58a867 100644
--- a/include/uapi/linux/kcov.h
+++ b/include/uapi/linux/kcov.h
@@ -35,6 +35,8 @@ enum {
 	KCOV_TRACE_PC = 0,
 	/* Collecting comparison operands mode. */
 	KCOV_TRACE_CMP = 1,
+	/* Collecting dereferences mode. */
+	KCOV_TRACE_DEREF = 2,
 };
 
 /*
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 80bfe71bbe13e..121e31a6f64cf 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -40,6 +40,8 @@
  *	KCOV_TRACE_PC - to trace only the PCs
  *	or
  *	KCOV_TRACE_CMP - to trace only the comparison operands
+ *	or
+ *	KCOV_TRACE_DEREF - to trace struct dereferences
  *  - then, ioctl(KCOV_DISABLE) to disable the task.
  * Enabling/disabling ioctls can be repeated (only one task a time allowed).
  */
@@ -322,6 +324,31 @@ void notrace __sanitizer_cov_trace_switch(u64 val, u64 *cases)
 EXPORT_SYMBOL(__sanitizer_cov_trace_switch);
 #endif /* ifdef CONFIG_KCOV_ENABLE_COMPARISONS */
 
+#ifdef CONFIG_KCOV_ENABLE_DEREFERENCES
+void notrace __sanitizer_cov_trace_deref(u32 hash)
+{
+	struct task_struct *t;
+	u64 *area;
+	unsigned long mask;
+
+	t = current;
+	if (!check_kcov_mode(KCOV_MODE_TRACE_DEREF, t))
+		return;
+
+	// TODO: precompute this
+	area = t->kcov_area;
+	mask = rounddown_pow_of_two(t->kcov_size) - 1;
+
+	// TODO: racy without atomic/{READ,WRITE}_ONCE?
+	area[BIT_WORD(hash) & mask] |= BIT_MASK(hash);
+}
+EXPORT_SYMBOL(__sanitizer_cov_trace_deref);
+#endif /* ifdef KCOV_ENABLE_DEREFERENCES */
+
+void notrace __sanitizer_cov_trace_(u64 val, u64 *cases)
+{
+}
+
 static void kcov_start(struct task_struct *t, struct kcov *kcov,
 			unsigned int size, void *area, enum kcov_mode mode,
 			int sequence)
@@ -516,11 +543,9 @@ static int kcov_get_mode(unsigned long arg)
 	if (arg == KCOV_TRACE_PC)
 		return KCOV_MODE_TRACE_PC;
 	else if (arg == KCOV_TRACE_CMP)
-#ifdef CONFIG_KCOV_ENABLE_COMPARISONS
-		return KCOV_MODE_TRACE_CMP;
-#else
-		return -ENOTSUPP;
-#endif
+		return IS_ENABLED(CONFIG_KCOV_ENABLE_COMPARISONS) ? KCOV_MODE_TRACE_CMP : -ENOTSUPP;
+	else if (arg == KCOV_TRACE_DEREF)
+		return IS_ENABLED(CONFIG_KCOV_ENABLE_DEREFERENCES) ? KCOV_MODE_TRACE_DEREF : -ENOTSUPP;
 	else
 		return -EINVAL;
 }
@@ -922,6 +947,9 @@ static void kcov_move_area(enum kcov_mode mode, void *dst_area,
 		BUILD_BUG_ON(!is_power_of_2(KCOV_WORDS_PER_CMP));
 		entry_size_log = __ilog2_u64(sizeof(u64) * KCOV_WORDS_PER_CMP);
 		break;
+	case KCOV_MODE_TRACE_DEREF:
+		// TODO
+		break;
 	default:
 		WARN_ON(1);
 		return;
@@ -946,6 +974,9 @@ static void kcov_move_area(enum kcov_mode mode, void *dst_area,
 	case KCOV_MODE_TRACE_CMP:
 		WRITE_ONCE(*(u64 *)dst_area, dst_len + entries_moved);
 		break;
+	case KCOV_MODE_TRACE_DEREF:
+		// TODO
+		break;
 	default:
 		break;
 	}
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 678c13967580e..2cc3af7544367 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2007,6 +2007,13 @@ config KCOV_ENABLE_COMPARISONS
 	  These operands can be used by fuzzing engines to improve the quality
 	  of fuzzing coverage.
 
+config KCOV_ENABLE_DEREFERENCES
+	bool "Enable dereference collection by KCOV"
+	depends on KCOV
+	select GCC_PLUGIN_SANCOV_DEREF
+	help
+	  Blaargh.
+
 config KCOV_INSTRUMENT_ALL
 	bool "Instrument all code by default"
 	depends on KCOV
diff --git a/scripts/Makefile.gcc-plugins b/scripts/Makefile.gcc-plugins
index 952e46876329a..93d8aafed01c1 100644
--- a/scripts/Makefile.gcc-plugins
+++ b/scripts/Makefile.gcc-plugins
@@ -11,6 +11,7 @@ endif
 export DISABLE_LATENT_ENTROPY_PLUGIN
 
 gcc-plugin-$(CONFIG_GCC_PLUGIN_SANCOV)		+= sancov_plugin.so
+gcc-plugin-$(CONFIG_GCC_PLUGIN_SANCOV_DEREF)	+= sancov_deref_plugin.so
 
 gcc-plugin-$(CONFIG_GCC_PLUGIN_STRUCTLEAK)	+= structleak_plugin.so
 gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_STRUCTLEAK_VERBOSE)	\
@@ -51,6 +52,7 @@ export DISABLE_ARM_SSP_PER_TASK_PLUGIN
 GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y))
 # The sancov_plugin.so is included via CFLAGS_KCOV, so it is removed here.
 GCC_PLUGINS_CFLAGS := $(filter-out %/sancov_plugin.so, $(GCC_PLUGINS_CFLAGS))
+GCC_PLUGINS_CFLAGS := $(filter-out %/sancov_deref_plugin.so, $(GCC_PLUGINS_CFLAGS))
 export GCC_PLUGINS_CFLAGS
 
 # Add the flags to the build!
diff --git a/scripts/Makefile.kcov b/scripts/Makefile.kcov
index 67e8cfe3474b7..1aeda23124dca 100644
--- a/scripts/Makefile.kcov
+++ b/scripts/Makefile.kcov
@@ -2,5 +2,6 @@
 kcov-flags-$(CONFIG_CC_HAS_SANCOV_TRACE_PC)	+= -fsanitize-coverage=trace-pc
 kcov-flags-$(CONFIG_KCOV_ENABLE_COMPARISONS)	+= -fsanitize-coverage=trace-cmp
 kcov-flags-$(CONFIG_GCC_PLUGIN_SANCOV)		+= -fplugin=$(objtree)/scripts/gcc-plugins/sancov_plugin.so
+kcov-flags-$(CONFIG_GCC_PLUGIN_SANCOV_DEREF)	+= -fplugin=$(objtree)/scripts/gcc-plugins/sancov_deref_plugin.so
 
 export CFLAGS_KCOV := $(kcov-flags-y)
diff --git a/scripts/gcc-plugins/Kconfig b/scripts/gcc-plugins/Kconfig
index ab9eb4cbe33a6..eec1a1fa1f42d 100644
--- a/scripts/gcc-plugins/Kconfig
+++ b/scripts/gcc-plugins/Kconfig
@@ -43,6 +43,12 @@ config GCC_PLUGIN_SANCOV
 	  gcc-4.5 on). It is based on the commit "Add fuzzing coverage support"
 	  by Dmitry Vyukov <dvyukov@google.com>.
 
+config GCC_PLUGIN_SANCOV_DEREF
+	bool
+	help
+	  This plugin inserts a __sanitizer_cov_trace_deref() call whenever a
+	  struct is dereferenced.
+
 config GCC_PLUGIN_LATENT_ENTROPY
 	bool "Generate some entropy during boot and runtime"
 	help
diff --git a/scripts/gcc-plugins/sancov_deref_plugin.c b/scripts/gcc-plugins/sancov_deref_plugin.c
new file mode 100644
index 0000000000000..d613fe9d008fd
--- /dev/null
+++ b/scripts/gcc-plugins/sancov_deref_plugin.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates.
+ * Author: Vegard Nossum <vegard.nossum@oracle.com>
+ *
+ * Based on:
+ *
+ * sancov_plugin
+ * Copyright 2011-2016 by Emese Revfy <re.emese@gmail.com>
+ * Licensed under the GPL v2, or (at your option) v3
+ *
+ * randomize_layout_plugin
+ * Copyright 2014-2016 by Open Source Security, Inc., Brad Spengler <spender@grsecurity.net>
+ *                   and PaX Team <pageexec@freemail.hu>
+ * Licensed under the GPL v2
+ */
+
+#include "gcc-common.h"
+
+#define ORIG_TYPE_NAME(node) \
+	(TYPE_NAME(TYPE_MAIN_VARIANT(node)) != NULL_TREE ? ((const unsigned char *)IDENTIFIER_POINTER(TYPE_NAME(TYPE_MAIN_VARIANT(node)))) : (const unsigned char *)"anonymous")
+
+__visible int plugin_is_GPL_compatible;
+
+tree sancov_deref_fndecl;
+
+static struct plugin_info sancov_deref_plugin_info = {
+	.version	= "20210519",
+	.help		= "sancov_deref plugin\n",
+};
+
+/*
+ * Hashing helpers adapted from old Linux dcache.h and include/linux/hash.h
+ */
+
+static inline unsigned long
+partial_name_hash(unsigned long c, unsigned long prevhash)
+{
+	return (prevhash + (c << 4) + (c >> 4)) * 11;
+}
+
+static inline unsigned long
+name_hash(const char *name, unsigned long prevhash)
+{
+	unsigned long hash = prevhash;
+	unsigned int len = strlen(name);
+	while (len--)
+		hash = partial_name_hash(*name++, hash);
+	return hash;
+}
+
+#define GOLDEN_RATIO_64 0x61C8864680B583EBull
+
+static inline unsigned long
+end_name_hash(unsigned long prevhash)
+{
+	return (prevhash * GOLDEN_RATIO_64) >> 32;
+}
+
+static unsigned int sancov_deref_execute(void)
+{
+	basic_block bb;
+
+	if (!strcmp(DECL_NAME_POINTER(current_function_decl), DECL_NAME_POINTER(sancov_deref_fndecl)))
+		return 0;
+
+	FOR_EACH_BB_FN(bb, cfun) {
+		gimple_stmt_iterator gsi;
+
+		for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
+			gimple stmt;
+			const_tree rhs1;
+			const_tree rhs1_arg0;
+			const_tree rhs1_arg0_type;
+			const_tree rhs1_arg0_type_name;
+			const_tree rhs1_arg1;
+			const char *struct_name;
+			const char *member_name;
+			gcall *gcall;
+
+			stmt = gsi_stmt(gsi);
+			if (gimple_code(stmt) != GIMPLE_ASSIGN)
+				continue;
+
+			rhs1 = gimple_assign_rhs1(stmt);
+			if (TREE_CODE(rhs1) != COMPONENT_REF)
+				continue;
+
+			rhs1_arg0 = TREE_OPERAND(rhs1, 0);
+			if (TREE_CODE(rhs1_arg0) != MEM_REF)
+				continue;
+
+			rhs1_arg0_type = TREE_TYPE(rhs1_arg0);
+			if (TREE_CODE(rhs1_arg0_type) != RECORD_TYPE)
+				continue;
+
+			rhs1_arg1 = TREE_OPERAND(rhs1, 1);
+			if (TREE_CODE(rhs1_arg1) != FIELD_DECL)
+				continue;
+
+			rhs1_arg0_type_name = TYPE_NAME(rhs1_arg0_type);
+			if (rhs1_arg0_type_name == NULL_TREE) {
+				// why?
+				//debug_tree(rhs1_arg0_type_name);
+				continue;
+			}
+
+			if (TREE_CODE(rhs1_arg0_type_name) == TYPE_DECL) {
+				// even with this check, we sometimes crash in DECL_ORIGINAL_TYPE() below. why??
+				//debug_tree(rhs1_arg0_type_name);
+				continue;
+
+				rhs1_arg0_type = DECL_ORIGINAL_TYPE(rhs1_arg0_type_name);
+				rhs1_arg0_type_name = TYPE_NAME(rhs1_arg0_type);
+			}
+
+			if (TREE_CODE(rhs1_arg0_type_name) != IDENTIFIER_NODE) {
+				// why?
+				//debug_tree(rhs1_arg0_type_name);
+				continue;
+			}
+
+			struct_name = TYPE_NAME_POINTER(rhs1_arg0_type);
+			member_name = DECL_NAME_POINTER(rhs1_arg1);
+
+			// TODO: skip tracing some incredibly common dereferences
+			// that do nothing in terms of coverage, like list_head::next
+
+			// random seed
+			unsigned long hash = 0x177e3471c15cd026UL;
+			hash = name_hash(struct_name, hash);
+			hash = name_hash("::", hash);
+			hash = name_hash(member_name, hash);
+			hash = end_name_hash(hash);
+
+			//fprintf(stderr, "@@@ %lx %s::%s\n", hash, struct_name, member_name);
+
+			gcall = as_a_gcall(gimple_build_call(sancov_deref_fndecl, 1, build_int_cstu(uint32_type_node, hash)));
+			gimple_set_location(gcall, gimple_location(stmt));
+			gsi_insert_before(&gsi, gcall, GSI_SAME_STMT);
+		}
+
+	}
+
+	return 0;
+}
+
+#define PASS_NAME sancov_deref
+
+#define NO_GATE
+#define TODO_FLAGS_FINISH TODO_dump_func | TODO_verify_stmts | TODO_update_ssa_no_phi | TODO_verify_flow
+
+#include "gcc-generate-gimple-pass.h"
+
+static void sancov_deref_start_unit(void __unused *gcc_data, void __unused *user_data)
+{
+	tree leaf_attr, nothrow_attr;
+	tree BT_FN_VOID = build_function_type_list(void_type_node, NULL_TREE);
+
+	sancov_deref_fndecl = build_fn_decl("__sanitizer_cov_trace_deref", BT_FN_VOID);
+
+	DECL_ASSEMBLER_NAME(sancov_deref_fndecl);
+	TREE_PUBLIC(sancov_deref_fndecl) = 1;
+	DECL_EXTERNAL(sancov_deref_fndecl) = 1;
+	DECL_ARTIFICIAL(sancov_deref_fndecl) = 1;
+	DECL_PRESERVE_P(sancov_deref_fndecl) = 1;
+	DECL_UNINLINABLE(sancov_deref_fndecl) = 1;
+	TREE_USED(sancov_deref_fndecl) = 1;
+
+	nothrow_attr = tree_cons(get_identifier("nothrow"), NULL, NULL);
+	decl_attributes(&sancov_deref_fndecl, nothrow_attr, 0);
+	gcc_assert(TREE_NOTHROW(sancov_deref_fndecl));
+	leaf_attr = tree_cons(get_identifier("leaf"), NULL, NULL);
+	decl_attributes(&sancov_deref_fndecl, leaf_attr, 0);
+}
+
+__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version)
+{
+	int i;
+	const char * const plugin_name = plugin_info->base_name;
+	const int argc = plugin_info->argc;
+	const struct plugin_argument * const argv = plugin_info->argv;
+	bool enable = true;
+
+	static const struct ggc_root_tab gt_ggc_r_gt_sancov_deref[] = {
+		{
+			.base = &sancov_deref_fndecl,
+			.nelt = 1,
+			.stride = sizeof(sancov_deref_fndecl),
+			.cb = &gt_ggc_mx_tree_node,
+			.pchw = &gt_pch_nx_tree_node
+		},
+		LAST_GGC_ROOT_TAB
+	};
+
+	/* BBs can be split afterwards?? */
+	PASS_INFO(sancov_deref, "asan", 0, PASS_POS_INSERT_BEFORE);
+
+	if (!plugin_default_version_check(version, &gcc_version)) {
+		error(G_("incompatible gcc/plugin versions"));
+		return 1;
+	}
+
+	for (i = 0; i < argc; ++i) {
+		if (!strcmp(argv[i].key, "no-sancov-deref")) {
+			enable = false;
+			continue;
+		}
+		error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key);
+	}
+
+	register_callback(plugin_name, PLUGIN_INFO, NULL, &sancov_deref_plugin_info);
+
+	if (!enable)
+		return 0;
+
+	register_callback(plugin_name, PLUGIN_START_UNIT, &sancov_deref_start_unit, NULL);
+	register_callback(plugin_name, PLUGIN_REGISTER_GGC_ROOTS, NULL, (void *)&gt_ggc_r_gt_sancov_deref);
+	register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &sancov_deref_pass_info);
+
+	return 0;
+}
-- 
2.23.0.718.g5ad94255a8

