1054 lines · 27.7 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/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;	/* Size of arena (in long's). */	unsigned int		size;	/* Coverage buffer shared with user space. */	void			*area;	/* Task for which we collect coverage, or NULL. */	struct task_struct	*t;	/* 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; 	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); /* 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(sizeof(*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);} 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_serving_softirq() && 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)) {		area[pos] = ip;		WRITE_ONCE(area[0], pos);	}}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)) {		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); 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(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); 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(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 */ 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);} 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;} static void kcov_task_reset(struct task_struct *t){	kcov_stop(t);	t->kcov_sequence = 0;	t->kcov_handle = 0;} void kcov_task_init(struct task_struct *t){	kcov_task_reset(t);	t->kcov_handle = current->kcov_handle;} static void kcov_reset(struct kcov *kcov){	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){	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){	kcov_task_reset(t);	if (kcov->remote)		kcov_remote_reset(kcov);	else		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)) {		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 == NULL)		return; 	spin_lock_irqsave(&kcov->lock, flags);	kcov_debug("t = %px, kcov->t = %px\n", t, kcov->t);	/*	 * For KCOV_ENABLE devices we want to make sure that t->kcov->t == t,	 * which comes down to:	 *        WARN_ON(!kcov->remote && kcov->t != t);	 *	 * For KCOV_REMOTE_ENABLE devices, the exiting task is either:	 *	 * 1. 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. Here t points to the task that is collecting remote	 *    coverage, and t->kcov->t points to the thread that created the	 *    kcov device. Which means that to detect this case we need to	 *    check that t != t->kcov->t, and this gives us the following:	 *        WARN_ON(kcov->remote && kcov->t != t);	 *	 * 2. The task that created kcov exiting without calling KCOV_DISABLE,	 *    and then again we make sure that t->kcov->t == t:	 *        WARN_ON(kcov->remote && kcov->t != t);	 *	 * By combining all three checks into one we get:	 */	if (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);} 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;	unsigned long flags; 	area = vmalloc_user(vma->vm_end - vma->vm_start);	if (!area)		return -ENOMEM; 	spin_lock_irqsave(&kcov->lock, flags);	size = kcov->size * sizeof(unsigned long);	if (kcov->mode != KCOV_MODE_INIT || vma->vm_pgoff != 0 ||	    vma->vm_end - vma->vm_start != size) {		res = -EINVAL;		goto exit;	}	if (!kcov->area) {		kcov->area = area;		vma->vm_flags |= VM_DONTEXPAND;		spin_unlock_irqrestore(&kcov->lock, flags);		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_irqrestore(&kcov->lock, flags);	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;	kcov->mode = KCOV_MODE_DISABLED;

Lines 1–500 of 1054.