forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
General description: kmemcheck is a patch to the linux kernel that detects use of uninitialized memory. It does this by trapping every read and write to memory that was allocated dynamically (e.g. using kmalloc()). If a memory address is read that has not previously been written to, a message is printed to the kernel log. Thanks to Andi Kleen for the set_memory_4k() solution. Andrew Morton suggested documenting the shadow member of struct page. Signed-off-by: Vegard Nossum <[email protected]> Signed-off-by: Pekka Enberg <[email protected]> [export kmemcheck_mark_initialized] [build fix for setup_max_cpus] Signed-off-by: Ingo Molnar <[email protected]> [rebased for mainline inclusion] Signed-off-by: Vegard Nossum <[email protected]>
- Loading branch information
Showing
19 changed files
with
1,304 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#ifndef ASM_X86_KMEMCHECK_H | ||
#define ASM_X86_KMEMCHECK_H | ||
|
||
#include <linux/types.h> | ||
#include <asm/ptrace.h> | ||
|
||
#ifdef CONFIG_KMEMCHECK | ||
bool kmemcheck_active(struct pt_regs *regs); | ||
|
||
void kmemcheck_show(struct pt_regs *regs); | ||
void kmemcheck_hide(struct pt_regs *regs); | ||
|
||
bool kmemcheck_fault(struct pt_regs *regs, | ||
unsigned long address, unsigned long error_code); | ||
bool kmemcheck_trap(struct pt_regs *regs); | ||
#else | ||
static inline bool kmemcheck_active(struct pt_regs *regs) | ||
{ | ||
return false; | ||
} | ||
|
||
static inline void kmemcheck_show(struct pt_regs *regs) | ||
{ | ||
} | ||
|
||
static inline void kmemcheck_hide(struct pt_regs *regs) | ||
{ | ||
} | ||
|
||
static inline bool kmemcheck_fault(struct pt_regs *regs, | ||
unsigned long address, unsigned long error_code) | ||
{ | ||
return false; | ||
} | ||
|
||
static inline bool kmemcheck_trap(struct pt_regs *regs) | ||
{ | ||
return false; | ||
} | ||
#endif /* CONFIG_KMEMCHECK */ | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
obj-y := error.o kmemcheck.o opcode.o pte.o shadow.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
#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; | ||
} | ||
|
||
static void do_wakeup(unsigned long); | ||
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_bp(&e->trace, regs->bp); | ||
|
||
/* 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); | ||
} | ||
|
||
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_ERR "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_INFO); | ||
for (i = 0; i < SHADOW_COPY_SIZE; ++i) | ||
printk("%02x", e->memory_copy[i]); | ||
printk("\n"); | ||
|
||
printk(KERN_INFO); | ||
for (i = 0; i < SHADOW_COPY_SIZE; ++i) { | ||
if (e->shadow_copy[i] < ARRAY_SIZE(short_desc)) | ||
printk(" %c", short_desc[e->shadow_copy[i]]); | ||
else | ||
printk(" ?"); | ||
} | ||
printk("\n"); | ||
printk(KERN_INFO "%*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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#ifndef ARCH__X86__MM__KMEMCHECK__ERROR_H | ||
#define ARCH__X86__MM__KMEMCHECK__ERROR_H | ||
|
||
#include <linux/ptrace.h> | ||
|
||
#include "shadow.h" | ||
|
||
void kmemcheck_error_save(enum kmemcheck_shadow state, | ||
unsigned long address, unsigned int size, struct pt_regs *regs); | ||
|
||
void kmemcheck_error_save_bug(struct pt_regs *regs); | ||
|
||
void kmemcheck_error_recall(void); | ||
|
||
#endif |
Oops, something went wrong.