Skip to content

Commit

Permalink
kmemcheck: add the kmemcheck core
Browse files Browse the repository at this point in the history
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
vegard committed Jun 13, 2009
1 parent e594c8d commit dfec072
Show file tree
Hide file tree
Showing 19 changed files with 1,304 additions and 2 deletions.
5 changes: 5 additions & 0 deletions arch/x86/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ ifdef CONFIG_CC_STACKPROTECTOR
endif
endif

# Don't unroll struct assignments with kmemcheck enabled
ifeq ($(CONFIG_KMEMCHECK),y)
KBUILD_CFLAGS += $(call cc-option,-fno-builtin-memcpy)
endif

# Stackpointer is addressed different for 32 bit and 64 bit x86
sp-$(CONFIG_X86_32) := esp
sp-$(CONFIG_X86_64) := rsp
Expand Down
42 changes: 42 additions & 0 deletions arch/x86/include/asm/kmemcheck.h
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
9 changes: 9 additions & 0 deletions arch/x86/include/asm/pgtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,15 @@ static inline int pte_present(pte_t a)
return pte_flags(a) & (_PAGE_PRESENT | _PAGE_PROTNONE);
}

static inline int pte_hidden(pte_t x)
{
#ifdef CONFIG_KMEMCHECK
return pte_flags(x) & _PAGE_HIDDEN;
#else
return 0;
#endif
}

static inline int pmd_present(pmd_t pmd)
{
return pmd_flags(pmd) & _PAGE_PRESENT;
Expand Down
4 changes: 2 additions & 2 deletions arch/x86/include/asm/pgtable_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#define _PAGE_BIT_GLOBAL 8 /* Global TLB entry PPro+ */
#define _PAGE_BIT_UNUSED1 9 /* available for programmer */
#define _PAGE_BIT_IOMAP 10 /* flag used to indicate IO mapping */
#define _PAGE_BIT_UNUSED3 11
#define _PAGE_BIT_HIDDEN 11 /* hidden by kmemcheck */
#define _PAGE_BIT_PAT_LARGE 12 /* On 2MB or 1GB pages */
#define _PAGE_BIT_SPECIAL _PAGE_BIT_UNUSED1
#define _PAGE_BIT_CPA_TEST _PAGE_BIT_UNUSED1
Expand All @@ -41,7 +41,7 @@
#define _PAGE_GLOBAL (_AT(pteval_t, 1) << _PAGE_BIT_GLOBAL)
#define _PAGE_UNUSED1 (_AT(pteval_t, 1) << _PAGE_BIT_UNUSED1)
#define _PAGE_IOMAP (_AT(pteval_t, 1) << _PAGE_BIT_IOMAP)
#define _PAGE_UNUSED3 (_AT(pteval_t, 1) << _PAGE_BIT_UNUSED3)
#define _PAGE_HIDDEN (_AT(pteval_t, 1) << _PAGE_BIT_HIDDEN)
#define _PAGE_PAT (_AT(pteval_t, 1) << _PAGE_BIT_PAT)
#define _PAGE_PAT_LARGE (_AT(pteval_t, 1) << _PAGE_BIT_PAT_LARGE)
#define _PAGE_SPECIAL (_AT(pteval_t, 1) << _PAGE_BIT_SPECIAL)
Expand Down
2 changes: 2 additions & 0 deletions arch/x86/mm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ obj-$(CONFIG_X86_PTDUMP) += dump_pagetables.o

obj-$(CONFIG_HIGHMEM) += highmem_32.o

obj-$(CONFIG_KMEMCHECK) += kmemcheck/

obj-$(CONFIG_MMIOTRACE) += mmiotrace.o
mmiotrace-y := kmmio.o pf_in.o mmio-mod.o
obj-$(CONFIG_MMIOTRACE_TEST) += testmmiotrace.o
Expand Down
1 change: 1 addition & 0 deletions arch/x86/mm/kmemcheck/Makefile
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
229 changes: 229 additions & 0 deletions arch/x86/mm/kmemcheck/error.c
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;
}
}
15 changes: 15 additions & 0 deletions arch/x86/mm/kmemcheck/error.h
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
Loading

0 comments on commit dfec072

Please sign in to comment.