220 lines
5.8 KiB
C
220 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/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;
|
|
|
|
#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_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.allow_reporting)
|
|
return;
|
|
if (!origin)
|
|
return;
|
|
|
|
current->kmsan_ctx.allow_reporting = false;
|
|
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);
|
|
current->kmsan_ctx.allow_reporting = true;
|
|
}
|