1156 lines · 31.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0#define pr_fmt(fmt) "kcov: " fmt #define DISABLE_BRANCH_PROFILING#include <linux/atomic.h>#include <linux/compiler.h>#include <linux/errno.h>#include <linux/export.h>#include <linux/types.h>#include <linux/file.h>#include <linux/fs.h>#include <linux/hashtable.h>#include <linux/init.h>#include <linux/jiffies.h>#include <linux/kmsan-checks.h>#include <linux/mm.h>#include <linux/preempt.h>#include <linux/printk.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/vmalloc.h>#include <linux/debugfs.h>#include <linux/uaccess.h>#include <linux/kcov.h>#include <linux/refcount.h>#include <linux/log2.h>#include <asm/setup.h> #define kcov_debug(fmt, ...) pr_debug("%s: " fmt, __func__, ##__VA_ARGS__) /* Number of 64-bit words written per one comparison: */#define KCOV_WORDS_PER_CMP 4 /* * 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) *  - 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). */struct kcov {	/*	 * Reference counter. We keep one for:	 *  - opened file descriptor	 *  - task with enabled coverage (we can't unwire it from another task)	 *  - each code section for remote coverage collection	 */	refcount_t		refcount;	/* The lock protects mode, size, area and t. */	spinlock_t		lock;	enum kcov_mode		mode __guarded_by(&lock);	/* Size of arena (in long's). */	unsigned int		size __guarded_by(&lock);	/* Coverage buffer shared with user space. */	void			*area __guarded_by(&lock);	/* Task for which we collect coverage, or NULL. */	struct task_struct	*t __guarded_by(&lock);	/* Collecting coverage from remote (background) threads. */	bool			remote;	/* Size of remote area (in long's). */	unsigned int		remote_size;	/*	 * Sequence is incremented each time kcov is reenabled, used by	 * kcov_remote_stop(), see the comment there.	 */	int			sequence;}; struct kcov_remote_area {	struct list_head	list;	unsigned int		size;}; struct kcov_remote {	u64			handle;	struct kcov		*kcov;	struct hlist_node	hnode;}; static DEFINE_SPINLOCK(kcov_remote_lock);static DEFINE_HASHTABLE(kcov_remote_map, 4);static struct list_head kcov_remote_areas = LIST_HEAD_INIT(kcov_remote_areas); struct kcov_percpu_data {	void			*irq_area;	local_lock_t		lock; 	unsigned int		saved_mode;	unsigned int		saved_size;	void			*saved_area;	struct kcov		*saved_kcov;	int			saved_sequence;}; static DEFINE_PER_CPU(struct kcov_percpu_data, kcov_percpu_data) = {	.lock = INIT_LOCAL_LOCK(lock),}; /* Must be called with kcov_remote_lock locked. */static struct kcov_remote *kcov_remote_find(u64 handle){	struct kcov_remote *remote; 	hash_for_each_possible(kcov_remote_map, remote, hnode, handle) {		if (remote->handle == handle)			return remote;	}	return NULL;} /* Must be called with kcov_remote_lock locked. */static struct kcov_remote *kcov_remote_add(struct kcov *kcov, u64 handle){	struct kcov_remote *remote; 	if (kcov_remote_find(handle))		return ERR_PTR(-EEXIST);	remote = kmalloc_obj(*remote, GFP_ATOMIC);	if (!remote)		return ERR_PTR(-ENOMEM);	remote->handle = handle;	remote->kcov = kcov;	hash_add(kcov_remote_map, &remote->hnode, handle);	return remote;} /* Must be called with kcov_remote_lock locked. */static struct kcov_remote_area *kcov_remote_area_get(unsigned int size){	struct kcov_remote_area *area;	struct list_head *pos; 	list_for_each(pos, &kcov_remote_areas) {		area = list_entry(pos, struct kcov_remote_area, list);		if (area->size == size) {			list_del(&area->list);			return area;		}	}	return NULL;} /* Must be called with kcov_remote_lock locked. */static void kcov_remote_area_put(struct kcov_remote_area *area,					unsigned int size){	INIT_LIST_HEAD(&area->list);	area->size = size;	list_add(&area->list, &kcov_remote_areas);	/*	 * KMSAN doesn't instrument this file, so it may not know area->list	 * is initialized. Unpoison it explicitly to avoid reports in	 * kcov_remote_area_get().	 */	kmsan_unpoison_memory(&area->list, sizeof(area->list));} /* * Unlike in_serving_softirq(), this function returns false when called during * a hardirq or an NMI that happened in the softirq context. */static __always_inline bool in_softirq_really(void){	return in_serving_softirq() && !in_hardirq() && !in_nmi();} static notrace bool check_kcov_mode(enum kcov_mode needed_mode, struct task_struct *t){	unsigned int mode; 	/*	 * We are interested in code coverage as a function of a syscall inputs,	 * so we ignore code executed in interrupts, unless we are in a remote	 * coverage collection section in a softirq.	 */	if (!in_task() && !(in_softirq_really() && t->kcov_softirq))		return false;	mode = READ_ONCE(t->kcov_mode);	/*	 * 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_start().	 */	barrier();	return mode == needed_mode;} static notrace unsigned long canonicalize_ip(unsigned long ip){#ifdef CONFIG_RANDOMIZE_BASE	ip -= kaslr_offset();#endif	return ip;} /* * 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)) {		/* Previously we write pc before updating pos. However, some		 * early interrupt code could bypass check_kcov_mode() check		 * and invoke __sanitizer_cov_trace_pc(). If such interrupt is		 * raised between writing pc and updating pos, the pc could be		 * overitten by the recursive __sanitizer_cov_trace_pc().		 * Update pos before writing pc to avoid such interleaving.		 */		WRITE_ONCE(area[0], pos);		barrier();		area[pos] = ip;	}}EXPORT_SYMBOL(__sanitizer_cov_trace_pc); #ifdef CONFIG_KCOV_ENABLE_COMPARISONSstatic void notrace write_comp_data(u64 type, u64 arg1, u64 arg2, u64 ip){	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)) {		/* See comment in __sanitizer_cov_trace_pc(). */		WRITE_ONCE(area[0], count + 1);		barrier();		area[start_index] = type;		area[start_index + 1] = arg1;		area[start_index + 2] = arg2;		area[start_index + 3] = ip;	}} 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); void notrace __sanitizer_cov_trace_cmp4(u32 arg1, u32 arg2){	write_comp_data(KCOV_CMP_SIZE(2), arg1, arg2, _RET_IP_);}EXPORT_SYMBOL(__sanitizer_cov_trace_cmp4); void notrace __sanitizer_cov_trace_cmp8(kcov_u64 arg1, kcov_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); void notrace __sanitizer_cov_trace_const_cmp4(u32 arg1, u32 arg2){	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(kcov_u64 arg1, kcov_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(kcov_u64 val, void *arg){	u64 i;	u64 *cases = arg;	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 */ static void kcov_start(struct task_struct *t, struct kcov *kcov,			unsigned int size, void *area, enum kcov_mode mode,			int sequence){	kcov_debug("t = %px, size = %u, area = %px\n", t, size, area);	t->kcov = kcov;	/* Cache in task struct for performance. */	t->kcov_size = size;	t->kcov_area = area;	t->kcov_sequence = sequence;	/* See comment in check_kcov_mode(). */	barrier();	WRITE_ONCE(t->kcov_mode, mode);} /* operates on coverage-generator-owned fields */static void kcov_stop(struct task_struct *t){	WRITE_ONCE(t->kcov_mode, KCOV_MODE_DISABLED);	barrier();	t->kcov = NULL;	t->kcov_size = 0;	t->kcov_area = NULL;} /* operates on coverage-generator-owned fields */static void kcov_task_reset(struct task_struct *t){	kcov_stop(t);	t->kcov_sequence = 0;} void kcov_task_init(struct task_struct *t){	kcov_task_reset(t);	t->kcov_remote = NULL;	t->kcov_handle = current->kcov_handle;} static void kcov_reset(struct kcov *kcov)	__must_hold(&kcov->lock){	kcov->t = NULL;	kcov->mode = KCOV_MODE_INIT;	kcov->remote = false;	kcov->remote_size = 0;	kcov->sequence++;} static void kcov_remote_reset(struct kcov *kcov)	__must_hold(&kcov->lock){	int bkt;	struct kcov_remote *remote;	struct hlist_node *tmp;	unsigned long flags; 	spin_lock_irqsave(&kcov_remote_lock, flags);	hash_for_each_safe(kcov_remote_map, bkt, tmp, remote, hnode) {		if (remote->kcov != kcov)			continue;		hash_del(&remote->hnode);		kfree(remote);	}	/* Do reset before unlock to prevent races with kcov_remote_start(). */	kcov_reset(kcov);	spin_unlock_irqrestore(&kcov_remote_lock, flags);} static void kcov_disable(struct task_struct *t, struct kcov *kcov)	__must_hold(&kcov->lock){	if (kcov->remote) {		t->kcov_handle = 0;		t->kcov_remote = NULL;		kcov_remote_reset(kcov);	} else {		kcov_task_reset(t);		kcov_reset(kcov);	}} static void kcov_get(struct kcov *kcov){	refcount_inc(&kcov->refcount);} static void kcov_put(struct kcov *kcov){	if (refcount_dec_and_test(&kcov->refcount)) {		/* Context-safety: no references left, object being destroyed. */		context_unsafe(			kcov_remote_reset(kcov);			vfree(kcov->area);		);		kfree(kcov);	}} void kcov_task_exit(struct task_struct *t){	struct kcov *kcov;	unsigned long flags; 	kcov = t->kcov;	if (kcov) {		spin_lock_irqsave(&kcov->lock, flags);		kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);		/*		 * This could be a remote task between kcov_remote_start() and		 * kcov_remote_stop().		 * In this case we should print a warning right away, since a		 * task shouldn't be exiting when it's in a kcov coverage		 * collection section.		 *		 * Otherwise, this should be a task that created a local		 * kcov instance and hasn't called KCOV_DISABLE.		 * Make sure that t->kcov->t is consistent.		 */		if (WARN_ON(kcov->remote) || WARN_ON(kcov->t != t)) {			spin_unlock_irqrestore(&kcov->lock, flags);			return;		}		/* Just to not leave dangling references behind. */		kcov_disable(t, kcov);		spin_unlock_irqrestore(&kcov->lock, flags);		kcov_put(kcov);	}	kcov = t->kcov_remote;	if (kcov) {		spin_lock_irqsave(&kcov->lock, flags);		kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);		/*		 * This is a KCOV_REMOTE_ENABLE device, and the task is the		 * user task which has requested remote coverage collection.		 * Make sure that t->kcov->t is consistent.		 */		if (WARN_ON(!kcov->remote) || WARN_ON(kcov->t != t)) {			spin_unlock_irqrestore(&kcov->lock, flags);			return;		}		/* Just to not leave dangling references behind. */		kcov_disable(t, kcov);		spin_unlock_irqrestore(&kcov->lock, flags);		kcov_put(kcov);

Lines 1–500 of 1156.