| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/interrupt.h> |
| #include <linux/kdebug.h> |
| #include <linux/kmemcheck.h> |
| #include <linux/kernel.h> |
| #include <linux/types.h> |
| #include <linux/ptrace.h> |
| #include <linux/stacktrace.h> |
| #include <linux/string.h> |
| |
| #include "error.h" |
| #include "shadow.h" |
| |
| enum kmemcheck_error_type { |
| KMEMCHECK_ERROR_INVALID_ACCESS, |
| KMEMCHECK_ERROR_BUG, |
| }; |
| |
| #define SHADOW_COPY_SIZE (1 << CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT) |
| |
| struct kmemcheck_error { |
| enum kmemcheck_error_type type; |
| |
| union { |
| /* KMEMCHECK_ERROR_INVALID_ACCESS */ |
| struct { |
| /* Kind of access that caused the error */ |
| enum kmemcheck_shadow state; |
| /* Address and size of the erroneous read */ |
| unsigned long address; |
| unsigned int size; |
| }; |
| }; |
| |
| struct pt_regs regs; |
| struct stack_trace trace; |
| unsigned long trace_entries[32]; |
| |
| /* We compress it to a char. */ |
| unsigned char shadow_copy[SHADOW_COPY_SIZE]; |
| unsigned char memory_copy[SHADOW_COPY_SIZE]; |
| }; |
| |
| /* |
| * Create a ring queue of errors to output. We can't call printk() directly |
| * from the kmemcheck traps, since this may call the console drivers and |
| * result in a recursive fault. |
| */ |
| static struct kmemcheck_error error_fifo[CONFIG_KMEMCHECK_QUEUE_SIZE]; |
| static unsigned int error_count; |
| static unsigned int error_rd; |
| static unsigned int error_wr; |
| static unsigned int error_missed_count; |
| |
| static struct kmemcheck_error *error_next_wr(void) |
| { |
| struct kmemcheck_error *e; |
| |
| if (error_count == ARRAY_SIZE(error_fifo)) { |
| ++error_missed_count; |
| return NULL; |
| } |
| |
| e = &error_fifo[error_wr]; |
| if (++error_wr == ARRAY_SIZE(error_fifo)) |
| error_wr = 0; |
| ++error_count; |
| return e; |
| } |
| |
| static struct kmemcheck_error *error_next_rd(void) |
| { |
| struct kmemcheck_error *e; |
| |
| if (error_count == 0) |
| return NULL; |
| |
| e = &error_fifo[error_rd]; |
| if (++error_rd == ARRAY_SIZE(error_fifo)) |
| error_rd = 0; |
| --error_count; |
| return e; |
| } |
| |
| void kmemcheck_error_recall(void) |
| { |
| static const char *desc[] = { |
| [KMEMCHECK_SHADOW_UNALLOCATED] = "unallocated", |
| [KMEMCHECK_SHADOW_UNINITIALIZED] = "uninitialized", |
| [KMEMCHECK_SHADOW_INITIALIZED] = "initialized", |
| [KMEMCHECK_SHADOW_FREED] = "freed", |
| }; |
| |
| static const char short_desc[] = { |
| [KMEMCHECK_SHADOW_UNALLOCATED] = 'a', |
| [KMEMCHECK_SHADOW_UNINITIALIZED] = 'u', |
| [KMEMCHECK_SHADOW_INITIALIZED] = 'i', |
| [KMEMCHECK_SHADOW_FREED] = 'f', |
| }; |
| |
| struct kmemcheck_error *e; |
| unsigned int i; |
| |
| e = error_next_rd(); |
| if (!e) |
| return; |
| |
| switch (e->type) { |
| case KMEMCHECK_ERROR_INVALID_ACCESS: |
| printk(KERN_WARNING "WARNING: kmemcheck: Caught %d-bit read from %s memory (%p)\n", |
| 8 * e->size, e->state < ARRAY_SIZE(desc) ? |
| desc[e->state] : "(invalid shadow state)", |
| (void *) e->address); |
| |
| printk(KERN_WARNING); |
| for (i = 0; i < SHADOW_COPY_SIZE; ++i) |
| printk(KERN_CONT "%02x", e->memory_copy[i]); |
| printk(KERN_CONT "\n"); |
| |
| printk(KERN_WARNING); |
| for (i = 0; i < SHADOW_COPY_SIZE; ++i) { |
| if (e->shadow_copy[i] < ARRAY_SIZE(short_desc)) |
| printk(KERN_CONT " %c", short_desc[e->shadow_copy[i]]); |
| else |
| printk(KERN_CONT " ?"); |
| } |
| printk(KERN_CONT "\n"); |
| printk(KERN_WARNING "%*c\n", 2 + 2 |
| * (int) (e->address & (SHADOW_COPY_SIZE - 1)), '^'); |
| break; |
| case KMEMCHECK_ERROR_BUG: |
| printk(KERN_EMERG "ERROR: kmemcheck: Fatal error\n"); |
| break; |
| } |
| |
| __show_regs(&e->regs, 1); |
| print_stack_trace(&e->trace, 0); |
| } |
| |
| static void do_wakeup(unsigned long data) |
| { |
| while (error_count > 0) |
| kmemcheck_error_recall(); |
| |
| if (error_missed_count > 0) { |
| printk(KERN_WARNING "kmemcheck: Lost %d error reports because " |
| "the queue was too small\n", error_missed_count); |
| error_missed_count = 0; |
| } |
| } |
| |
| static DECLARE_TASKLET(kmemcheck_tasklet, &do_wakeup, 0); |
| |
| /* |
| * Save the context of an error report. |
| */ |
| void kmemcheck_error_save(enum kmemcheck_shadow state, |
| unsigned long address, unsigned int size, struct pt_regs *regs) |
| { |
| static unsigned long prev_ip; |
| |
| struct kmemcheck_error *e; |
| void *shadow_copy; |
| void *memory_copy; |
| |
| /* Don't report several adjacent errors from the same EIP. */ |
| if (regs->ip == prev_ip) |
| return; |
| prev_ip = regs->ip; |
| |
| e = error_next_wr(); |
| if (!e) |
| return; |
| |
| e->type = KMEMCHECK_ERROR_INVALID_ACCESS; |
| |
| e->state = state; |
| e->address = address; |
| e->size = size; |
| |
| /* Save regs */ |
| memcpy(&e->regs, regs, sizeof(*regs)); |
| |
| /* Save stack trace */ |
| e->trace.nr_entries = 0; |
| e->trace.entries = e->trace_entries; |
| e->trace.max_entries = ARRAY_SIZE(e->trace_entries); |
| e->trace.skip = 0; |
| save_stack_trace_regs(regs, &e->trace); |
| |
| /* Round address down to nearest 16 bytes */ |
| shadow_copy = kmemcheck_shadow_lookup(address |
| & ~(SHADOW_COPY_SIZE - 1)); |
| BUG_ON(!shadow_copy); |
| |
| memcpy(e->shadow_copy, shadow_copy, SHADOW_COPY_SIZE); |
| |
| kmemcheck_show_addr(address); |
| memory_copy = (void *) (address & ~(SHADOW_COPY_SIZE - 1)); |
| memcpy(e->memory_copy, memory_copy, SHADOW_COPY_SIZE); |
| kmemcheck_hide_addr(address); |
| |
| tasklet_hi_schedule_first(&kmemcheck_tasklet); |
| } |
| |
| /* |
| * Save the context of a kmemcheck bug. |
| */ |
| void kmemcheck_error_save_bug(struct pt_regs *regs) |
| { |
| struct kmemcheck_error *e; |
| |
| e = error_next_wr(); |
| if (!e) |
| return; |
| |
| e->type = KMEMCHECK_ERROR_BUG; |
| |
| memcpy(&e->regs, regs, sizeof(*regs)); |
| |
| e->trace.nr_entries = 0; |
| e->trace.entries = e->trace_entries; |
| e->trace.max_entries = ARRAY_SIZE(e->trace_entries); |
| e->trace.skip = 1; |
| save_stack_trace(&e->trace); |
| |
| tasklet_hi_schedule_first(&kmemcheck_tasklet); |
| } |