222 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * KMSAN error reporting routines.
 | |
|  *
 | |
|  * Copyright (C) 2019-2022 Google LLC
 | |
|  * Author: Alexander Potapenko <glider@google.com>
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <linux/console.h>
 | |
| #include <linux/kmsan.h>
 | |
| #include <linux/moduleparam.h>
 | |
| #include <linux/stackdepot.h>
 | |
| #include <linux/stacktrace.h>
 | |
| #include <linux/uaccess.h>
 | |
| 
 | |
| #include "kmsan.h"
 | |
| 
 | |
| static DEFINE_RAW_SPINLOCK(kmsan_report_lock);
 | |
| #define DESCR_SIZE 128
 | |
| /* Protected by kmsan_report_lock */
 | |
| static char report_local_descr[DESCR_SIZE];
 | |
| int panic_on_kmsan __read_mostly;
 | |
| EXPORT_SYMBOL_GPL(panic_on_kmsan);
 | |
| 
 | |
| #ifdef MODULE_PARAM_PREFIX
 | |
| #undef MODULE_PARAM_PREFIX
 | |
| #endif
 | |
| #define MODULE_PARAM_PREFIX "kmsan."
 | |
| module_param_named(panic, panic_on_kmsan, int, 0);
 | |
| 
 | |
| /*
 | |
|  * Skip internal KMSAN frames.
 | |
|  */
 | |
| static int get_stack_skipnr(const unsigned long stack_entries[],
 | |
| 			    int num_entries)
 | |
| {
 | |
| 	int len, skip;
 | |
| 	char buf[64];
 | |
| 
 | |
| 	for (skip = 0; skip < num_entries; ++skip) {
 | |
| 		len = scnprintf(buf, sizeof(buf), "%ps",
 | |
| 				(void *)stack_entries[skip]);
 | |
| 
 | |
| 		/* Never show __msan_* or kmsan_* functions. */
 | |
| 		if ((strnstr(buf, "__msan_", len) == buf) ||
 | |
| 		    (strnstr(buf, "kmsan_", len) == buf))
 | |
| 			continue;
 | |
| 
 | |
| 		/*
 | |
| 		 * No match for runtime functions -- @skip entries to skip to
 | |
| 		 * get to first frame of interest.
 | |
| 		 */
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return skip;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Currently the descriptions of locals generated by Clang look as follows:
 | |
|  *   ----local_name@function_name
 | |
|  * We want to print only the name of the local, as other information in that
 | |
|  * description can be confusing.
 | |
|  * The meaningful part of the description is copied to a global buffer to avoid
 | |
|  * allocating memory.
 | |
|  */
 | |
| static char *pretty_descr(char *descr)
 | |
| {
 | |
| 	int pos = 0, len = strlen(descr);
 | |
| 
 | |
| 	for (int i = 0; i < len; i++) {
 | |
| 		if (descr[i] == '@')
 | |
| 			break;
 | |
| 		if (descr[i] == '-')
 | |
| 			continue;
 | |
| 		report_local_descr[pos] = descr[i];
 | |
| 		if (pos + 1 == DESCR_SIZE)
 | |
| 			break;
 | |
| 		pos++;
 | |
| 	}
 | |
| 	report_local_descr[pos] = 0;
 | |
| 	return report_local_descr;
 | |
| }
 | |
| 
 | |
| void kmsan_print_origin(depot_stack_handle_t origin)
 | |
| {
 | |
| 	unsigned long *entries = NULL, *chained_entries = NULL;
 | |
| 	unsigned int nr_entries, chained_nr_entries, skipnr;
 | |
| 	void *pc1 = NULL, *pc2 = NULL;
 | |
| 	depot_stack_handle_t head;
 | |
| 	unsigned long magic;
 | |
| 	char *descr = NULL;
 | |
| 	unsigned int depth;
 | |
| 
 | |
| 	if (!origin)
 | |
| 		return;
 | |
| 
 | |
| 	while (true) {
 | |
| 		nr_entries = stack_depot_fetch(origin, &entries);
 | |
| 		depth = kmsan_depth_from_eb(stack_depot_get_extra_bits(origin));
 | |
| 		magic = nr_entries ? entries[0] : 0;
 | |
| 		if ((nr_entries == 4) && (magic == KMSAN_ALLOCA_MAGIC_ORIGIN)) {
 | |
| 			descr = (char *)entries[1];
 | |
| 			pc1 = (void *)entries[2];
 | |
| 			pc2 = (void *)entries[3];
 | |
| 			pr_err("Local variable %s created at:\n",
 | |
| 			       pretty_descr(descr));
 | |
| 			if (pc1)
 | |
| 				pr_err(" %pSb\n", pc1);
 | |
| 			if (pc2)
 | |
| 				pr_err(" %pSb\n", pc2);
 | |
| 			break;
 | |
| 		}
 | |
| 		if ((nr_entries == 3) && (magic == KMSAN_CHAIN_MAGIC_ORIGIN)) {
 | |
| 			/*
 | |
| 			 * Origin chains deeper than KMSAN_MAX_ORIGIN_DEPTH are
 | |
| 			 * not stored, so the output may be incomplete.
 | |
| 			 */
 | |
| 			if (depth == KMSAN_MAX_ORIGIN_DEPTH)
 | |
| 				pr_err("<Zero or more stacks not recorded to save memory>\n\n");
 | |
| 			head = entries[1];
 | |
| 			origin = entries[2];
 | |
| 			pr_err("Uninit was stored to memory at:\n");
 | |
| 			chained_nr_entries =
 | |
| 				stack_depot_fetch(head, &chained_entries);
 | |
| 			kmsan_internal_unpoison_memory(
 | |
| 				chained_entries,
 | |
| 				chained_nr_entries * sizeof(*chained_entries),
 | |
| 				/*checked*/ false);
 | |
| 			skipnr = get_stack_skipnr(chained_entries,
 | |
| 						  chained_nr_entries);
 | |
| 			stack_trace_print(chained_entries + skipnr,
 | |
| 					  chained_nr_entries - skipnr, 0);
 | |
| 			pr_err("\n");
 | |
| 			continue;
 | |
| 		}
 | |
| 		pr_err("Uninit was created at:\n");
 | |
| 		if (nr_entries) {
 | |
| 			skipnr = get_stack_skipnr(entries, nr_entries);
 | |
| 			stack_trace_print(entries + skipnr, nr_entries - skipnr,
 | |
| 					  0);
 | |
| 		} else {
 | |
| 			pr_err("(stack is not available)\n");
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void kmsan_report(depot_stack_handle_t origin, void *address, int size,
 | |
| 		  int off_first, int off_last, const void __user *user_addr,
 | |
| 		  enum kmsan_bug_reason reason)
 | |
| {
 | |
| 	unsigned long stack_entries[KMSAN_STACK_DEPTH];
 | |
| 	int num_stack_entries, skipnr;
 | |
| 	char *bug_type = NULL;
 | |
| 	unsigned long ua_flags;
 | |
| 	bool is_uaf;
 | |
| 
 | |
| 	if (!kmsan_enabled)
 | |
| 		return;
 | |
| 	if (current->kmsan_ctx.depth)
 | |
| 		return;
 | |
| 	if (!origin)
 | |
| 		return;
 | |
| 
 | |
| 	kmsan_disable_current();
 | |
| 	ua_flags = user_access_save();
 | |
| 	raw_spin_lock(&kmsan_report_lock);
 | |
| 	pr_err("=====================================================\n");
 | |
| 	is_uaf = kmsan_uaf_from_eb(stack_depot_get_extra_bits(origin));
 | |
| 	switch (reason) {
 | |
| 	case REASON_ANY:
 | |
| 		bug_type = is_uaf ? "use-after-free" : "uninit-value";
 | |
| 		break;
 | |
| 	case REASON_COPY_TO_USER:
 | |
| 		bug_type = is_uaf ? "kernel-infoleak-after-free" :
 | |
| 				    "kernel-infoleak";
 | |
| 		break;
 | |
| 	case REASON_SUBMIT_URB:
 | |
| 		bug_type = is_uaf ? "kernel-usb-infoleak-after-free" :
 | |
| 				    "kernel-usb-infoleak";
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	num_stack_entries =
 | |
| 		stack_trace_save(stack_entries, KMSAN_STACK_DEPTH, 1);
 | |
| 	skipnr = get_stack_skipnr(stack_entries, num_stack_entries);
 | |
| 
 | |
| 	pr_err("BUG: KMSAN: %s in %pSb\n", bug_type,
 | |
| 	       (void *)stack_entries[skipnr]);
 | |
| 	stack_trace_print(stack_entries + skipnr, num_stack_entries - skipnr,
 | |
| 			  0);
 | |
| 	pr_err("\n");
 | |
| 
 | |
| 	kmsan_print_origin(origin);
 | |
| 
 | |
| 	if (size) {
 | |
| 		pr_err("\n");
 | |
| 		if (off_first == off_last)
 | |
| 			pr_err("Byte %d of %d is uninitialized\n", off_first,
 | |
| 			       size);
 | |
| 		else
 | |
| 			pr_err("Bytes %d-%d of %d are uninitialized\n",
 | |
| 			       off_first, off_last, size);
 | |
| 	}
 | |
| 	if (address)
 | |
| 		pr_err("Memory access of size %d starts at %px\n", size,
 | |
| 		       address);
 | |
| 	if (user_addr && reason == REASON_COPY_TO_USER)
 | |
| 		pr_err("Data copied to user address %px\n", user_addr);
 | |
| 	pr_err("\n");
 | |
| 	dump_stack_print_info(KERN_ERR);
 | |
| 	pr_err("=====================================================\n");
 | |
| 	add_taint(TAINT_BAD_PAGE, LOCKDEP_NOW_UNRELIABLE);
 | |
| 	raw_spin_unlock(&kmsan_report_lock);
 | |
| 	if (panic_on_kmsan)
 | |
| 		panic("kmsan.panic set ...\n");
 | |
| 	user_access_restore(ua_flags);
 | |
| 	kmsan_enable_current();
 | |
| }
 |