mirror of
https://github.com/torvalds/linux
synced 2024-09-06 09:51:23 +00:00
![Alexander Potapenko](/assets/img/avatar_default.png)
For each memory location KernelMemorySanitizer maintains two types of metadata: 1. The so-called shadow of that location - а byte:byte mapping describing whether or not individual bits of memory are initialized (shadow is 0) or not (shadow is 1). 2. The origins of that location - а 4-byte:4-byte mapping containing 4-byte IDs of the stack traces where uninitialized values were created. Each struct page now contains pointers to two struct pages holding KMSAN metadata (shadow and origins) for the original struct page. Utility routines in mm/kmsan/core.c and mm/kmsan/shadow.c handle the metadata creation, addressing, copying and checking. mm/kmsan/report.c performs error reporting in the cases an uninitialized value is used in a way that leads to undefined behavior. KMSAN compiler instrumentation is responsible for tracking the metadata along with the kernel memory. mm/kmsan/instrumentation.c provides the implementation for instrumentation hooks that are called from files compiled with -fsanitize=kernel-memory. To aid parameter passing (also done at instrumentation level), each task_struct now contains a struct kmsan_task_state used to track the metadata of function parameters and return values for that task. Finally, this patch provides CONFIG_KMSAN that enables KMSAN, and declares CFLAGS_KMSAN, which are applied to files compiled with KMSAN. The KMSAN_SANITIZE:=n Makefile directive can be used to completely disable KMSAN instrumentation for certain files. Similarly, KMSAN_ENABLE_CHECKS:=n disables KMSAN checks and makes newly created stack memory initialized. Users can also use functions from include/linux/kmsan-checks.h to mark certain memory regions as uninitialized or initialized (this is called "poisoning" and "unpoisoning") or check that a particular region is initialized. Link: https://lkml.kernel.org/r/20220915150417.722975-12-glider@google.com Signed-off-by: Alexander Potapenko <glider@google.com> Acked-by: Marco Elver <elver@google.com> Cc: Alexander Viro <viro@zeniv.linux.org.uk> Cc: Alexei Starovoitov <ast@kernel.org> Cc: Andrey Konovalov <andreyknvl@gmail.com> Cc: Andrey Konovalov <andreyknvl@google.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Borislav Petkov <bp@alien8.de> Cc: Christoph Hellwig <hch@lst.de> Cc: Christoph Lameter <cl@linux.com> Cc: David Rientjes <rientjes@google.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Eric Biggers <ebiggers@google.com> Cc: Eric Biggers <ebiggers@kernel.org> Cc: Eric Dumazet <edumazet@google.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Herbert Xu <herbert@gondor.apana.org.au> Cc: Ilya Leoshkevich <iii@linux.ibm.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Jens Axboe <axboe@kernel.dk> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Cc: Kees Cook <keescook@chromium.org> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Matthew Wilcox <willy@infradead.org> Cc: Michael S. Tsirkin <mst@redhat.com> Cc: Pekka Enberg <penberg@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Petr Mladek <pmladek@suse.com> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Vegard Nossum <vegard.nossum@oracle.com> Cc: Vlastimil Babka <vbabka@suse.cz> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
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;
|
|
}
|