| // 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 || kmsan_in_runtime()) | |
| return; | |
| if (current->kmsan_ctx.depth) | |
| return; | |
| if (!origin) | |
| return; | |
| kmsan_enter_runtime(); | |
| 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_leave_runtime(); | |
| } |