Skip to contentSkip to conversation

kmemcheck: trap uses of uninitialized memory (v2)

RFC In review Vegard Nossum 4 participants 12 messages Nov 27, 2007 – Nov 29, 2007

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

RFC 1/1 kmemcheck: trap uses of uninitialized memory (v2)

General description: kmemcheck will trap every read and write to memory that was allocated dynamically (ie. with kmalloc()). If a memory address is read that has not previously been written to, a message is printed to the kernel log.

Changes since v1:

- We properly flush TLB entries that change. This used to not be the case, and so we got both spurious and missing reports of invalid memory accesses.
- There was a problem with instructions that both read from and wrote to memory in a single instruction. In particular, memcpy would use a REP MOVSB instruction, which could cause a page fault for either the source or the destination address. In this case the information from the page fault handler is not enough (as we only get the first access, the read), and we would end up either looping indefinitely (between marking the source page and the destination page present if the pages were not the same, and missing the write if the source/destination page was the same). Now we examine the instruction that caused the fault to see if it makes one or two memory accesses.
- Compiling with -Os caused gcc to turn some MOVZWs into MOVs, which would result in reads from possibly uninitialized memory. This is not incorrect per se, (since the extra bits are thrown away afterwards) so we now depend on !CC_OPTIMIZE_FOR_SIZE.
- Adjacent reads from the same EIP will not display the full stack trace.

If anybody wants to take a look at a sample log from the patched kernel, I provide this link: http://folk.uio.no/vegardno/linux/kmemcheck-20071127.txt
I do not know which (if any) of the messages are valid yet, but please have a look around anyway. If you can confirm or deny the validity of a report, that's great.

To use this patch with qemu, use either qemu-cvs or qemu-0.9.0 with the following
patch: http://lists.gnu.org/archive/html/qemu-devel/2007-03/msg00126.html

In addition, you probably want to remove the -O2 entirely from the Makefile to inhibit inlining, which makes the stack traces somewhat better.

Kind regards,
Vegard Nossum

 arch/x86/Kconfig.debug         |   10 ++
 arch/x86/kernel/Makefile_32    |    2 +
 arch/x86/kernel/kmemcheck_32.c |  290 ++++++++++++++++++++++++++++++++++++++++
 arch/x86/kernel/traps_32.c     |   16 ++-
 arch/x86/mm/fault_32.c         |   19 +++-
 include/asm-x86/kmemcheck.h    |    3 +
 include/asm-x86/kmemcheck_32.h |   33 +++++
 include/asm-x86/pgtable_32.h   |    9 +-
 include/linux/gfp.h            |    3 +-
 mm/slub.c                      |   37 +++++-
 10 files changed, 405 insertions(+), 17 deletions(-)
arch/x86/Kconfig.debug +10 -0
index 761ca7b..6a3e3ba 100644
@@ -112,4 +112,14 @@ config IOMMU_LEAK
 	  Add a simple leak tracer to the IOMMU code. This is useful when you
 	  are debugging a buggy device driver that leaks IOMMU mappings.
 
+config KMEMCHECK
+	bool "Trap use of uninitialized memory"
+	depends on X86_32 && !CC_OPTIMIZE_FOR_SIZE
+	help
+	  This option enables tracing of dynamically allocated kernel memory
+	  to see if memory is used before it has been given an initial value.
+	  Be aware that this requires half of your memory for bookkeeping and
+	  will insert extra code at *every* read and write to tracked memory
+	  thus slow down the kernel code (but user code is unaffected).
+
 endmenu
arch/x86/kernel/Makefile_32 +2 -0
index a7bc93c..91d3d9c 100644
@@ -49,6 +49,8 @@ obj-y				+= pcspeaker.o
 
 obj-$(CONFIG_SCx200)		+= scx200_32.o
 
+obj-$(CONFIG_KMEMCHECK)		+= kmemcheck_32.o
+
 # vsyscall_32.o contains the vsyscall DSO images as __initdata.
 # We must build both images before we can assemble it.
 # Note: kbuild does not track this dependency due to usage of .incbin
arch/x86/kernel/kmemcheck_32.c new file +290 -0

290 lines not on this page · raw message

arch/x86/kernel/traps_32.c +9 -7
index ef60102..5feed7e 100644
@@ -56,6 +56,7 @@
 #include <asm/arch_hooks.h>
 #include <linux/kdebug.h>
 #include <asm/stacktrace.h>
+#include <asm/kmemcheck.h>
 
 #include <linux/module.h>
 
@@ -866,8 +867,14 @@ fastcall void __kprobes do_debug(struct pt_regs * regs, long error_code)
 		 * check for kernel mode by just checking the CPL
 		 * of CS.
 		 */
-		if (!user_mode(regs))
-			goto clear_TF_reenable;
+		if (!user_mode(regs)) {
+			kmemcheck_hide(regs);
+
+			/* XXX: How do we deal with this? */
+			if (0)
+				set_tsk_thread_flag(tsk, TIF_SINGLESTEP);
+			return;
+		}
 	}
 
 	/* Ok, finally something we can handle */
@@ -883,11 +890,6 @@ clear_dr7:
 debug_vm86:
 	handle_vm86_trap((struct kernel_vm86_regs *) regs, error_code, 1);
 	return;
-
-clear_TF_reenable:
-	set_tsk_thread_flag(tsk, TIF_SINGLESTEP);
-	regs->eflags &= ~TF_MASK;
-	return;
 }
 
 /*
arch/x86/mm/fault_32.c +17 -2
index a2273d4..dcf587f 100644
@@ -30,6 +30,7 @@
 #include <asm/system.h>
 #include <asm/desc.h>
 #include <asm/segment.h>
+#include <asm/kmemcheck.h>
 
 extern void die(const char *,struct pt_regs *,long);
 
@@ -257,11 +258,13 @@ static inline pmd_t *vmalloc_sync_one(pgd_t *pgd, unsigned long address)
  *
  * This assumes no large pages in there.
  */
-static inline int vmalloc_fault(unsigned long address)
+static inline int vmalloc_fault(struct pt_regs *regs, unsigned long address,
+	unsigned long error_code)
 {
 	unsigned long pgd_paddr;
 	pmd_t *pmd_k;
 	pte_t *pte_k;
+
 	/*
 	 * Synchronize this task's top level page-table
 	 * with the 'reference' page table.
@@ -270,12 +273,23 @@ static inline int vmalloc_fault(unsigned long address)
 	 * an interrupt in the middle of a task switch..
 	 */
 	pgd_paddr = read_cr3();
+
 	pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);
 	if (!pmd_k)
 		return -1;
 	pte_k = pte_offset_kernel(pmd_k, address);
 	if (!pte_present(*pte_k))
 		return -1;
+
+	if (!pte_visible(*pte_k)) {
+		if (error_code & 2)
+			kmemcheck_access(regs, address, KMEMCHECK_WRITE);
+		else
+			kmemcheck_access(regs, address, KMEMCHECK_READ);
+
+		kmemcheck_show(regs);
+	}
+
 	return 0;
 }
 
@@ -329,7 +343,8 @@ fastcall void __kprobes do_page_fault(struct pt_regs *regs,
 	 * protection error (error_code & 9) == 0.
 	 */
 	if (unlikely(address >= TASK_SIZE)) {
-		if (!(error_code & 0x0000000d) && vmalloc_fault(address) >= 0)
+		if (!(error_code & 0x0000000d)
+			&& vmalloc_fault(regs, address, error_code) >= 0)
 			return;
 		if (notify_page_fault(regs))
 			return;
include/asm-x86/kmemcheck.h new file +3 -0
new file mode 100644
index 0000000..11de35a
@@ -0,0 +1,3 @@
+#ifdef CONFIG_X86_32
+# include "kmemcheck_32.h"
+#endif
include/asm-x86/kmemcheck_32.h new file +33 -0
new file mode 100644
index 0000000..3663664
@@ -0,0 +1,33 @@
+#ifndef ASM_X86_KMEMCHECK_32_H
+#define ASM_X86_KMEMCHECK_32_H
+
+#include <linux/percpu.h>
+#include <asm/pgtable.h>
+
+enum kmemcheck_method {
+	KMEMCHECK_READ,
+	KMEMCHECK_WRITE,
+};
+
+#ifdef CONFIG_KMEMCHECK
+DECLARE_PER_CPU(uint32_t, kmemcheck_read_addr);
+DECLARE_PER_CPU(uint32_t, kmemcheck_write_addr);
+
+void kmemcheck_show(struct pt_regs *regs);
+void kmemcheck_hide(struct pt_regs *regs);
+
+void kmemcheck_hide_pages(struct page *p, unsigned int n);
+
+void kmemcheck_access(struct pt_regs *regs,
+	unsigned long address, enum kmemcheck_method method);
+#else
+static void kmemcheck_show(struct pt_regs *regs) { }
+static void kmemcheck_hide(struct pt_regs *regs) { }
+
+static void kmemcheck_hide_pages(struct page *p, unsigned int n) { }
+
+static void kmemcheck_access(struct pt_regs *regs,
+	unsigned long address, enum kmemcheck_method method) { }
+#endif
+
+#endif
include/asm-x86/pgtable_32.h +6 -3
index ed3e70d..3a0c158 100644
@@ -103,7 +103,7 @@ void paging_init(void);
 #define _PAGE_BIT_UNUSED3	11
 #define _PAGE_BIT_NX		63
 
-#define _PAGE_PRESENT	0x001
+#define _PAGE_VISIBLE	0x001
 #define _PAGE_RW	0x002
 #define _PAGE_USER	0x004
 #define _PAGE_PWT	0x008
@@ -114,7 +114,9 @@ void paging_init(void);
 #define _PAGE_GLOBAL	0x100	/* Global TLB entry PPro+ */
 #define _PAGE_UNUSED1	0x200	/* available for programmer */
 #define _PAGE_UNUSED2	0x400
-#define _PAGE_UNUSED3	0x800
+#define _PAGE_PRESENT2	0x800
+
+#define _PAGE_PRESENT	(_PAGE_VISIBLE | _PAGE_PRESENT2)
 
 /* If _PAGE_PRESENT is clear, we use these: */
 #define _PAGE_FILE	0x040	/* nonlinear file mapping, saved PTE; unset:swap */
@@ -201,7 +203,8 @@ extern unsigned long long __PAGE_KERNEL, __PAGE_KERNEL_EXEC;
 /* The boot page tables (all created as a single array) */
 extern unsigned long pg0[];
 
-#define pte_present(x)	((x).pte_low & (_PAGE_PRESENT | _PAGE_PROTNONE))
+#define pte_present(x)	((x).pte_low & (_PAGE_PRESENT2 | _PAGE_PROTNONE))
+#define pte_visible(x)	((x).pte_low & (_PAGE_VISIBLE))
 
 /* To avoid harmful races, pmd_none(x) should check only the lower when PAE */
 #define pmd_none(x)	(!(unsigned long)pmd_val(x))
include/linux/gfp.h +2 -1
index 7e93a9a..a130067 100644
@@ -50,8 +50,9 @@ struct vm_area_struct;
 #define __GFP_THISNODE	((__force gfp_t)0x40000u)/* No fallback, no policies */
 #define __GFP_RECLAIMABLE ((__force gfp_t)0x80000u) /* Page is reclaimable */
 #define __GFP_MOVABLE	((__force gfp_t)0x100000u)  /* Page is movable */
+#define __GFP_NOTRACK	((__force gfp_t)0x200000u)  /* Don't track with kmemcheck */
 
-#define __GFP_BITS_SHIFT 21	/* Room for 21 __GFP_FOO bits */
+#define __GFP_BITS_SHIFT 22	/* Room for 22 __GFP_FOO bits */
 #define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
 
 /* This equals 0, but use constants in case they ever change */
mm/slub.c +33 -4
index 9acb413..d70c22f 100644
@@ -22,6 +22,9 @@
 #include <linux/kallsyms.h>
 #include <linux/memory.h>
 
+#include <asm/cacheflush.h>
+#include <asm/kmemcheck.h>
+
 /*
  * Lock order:
  *   1. slab_lock(page)
@@ -1039,8 +1042,9 @@ static inline unsigned long kmem_cache_flags(unsigned long objsize,
  */
 static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
 {
-	struct page * page;
+	struct page *page;
 	int pages = 1 << s->order;
+	int order = s->order;
 
 	if (s->order)
 		flags |= __GFP_COMP;
@@ -1051,14 +1055,33 @@ static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
 	if (s->flags & SLAB_RECLAIM_ACCOUNT)
 		flags |= __GFP_RECLAIMABLE;
 
+	/* Actually allocate twice as much, since we need to track the
+	 * status of each byte within the allocation. */
+	if (!(flags & __GFP_NOTRACK)) {
+		pages += pages;
+		order += 1;
+	}
+
 	if (node == -1)
-		page = alloc_pages(flags, s->order);
+		page = alloc_pages(flags, order);
 	else
-		page = alloc_pages_node(node, flags, s->order);
+		page = alloc_pages_node(node, flags, order);
 
 	if (!page)
 		return NULL;
 
+	if (!(flags & __GFP_NOTRACK)) {
+		unsigned int i;
+
+		/* Mark it as non-present for the MMU so that our accesses
+		 * to this memory will trigger a page fault and let us
+		 * analyze the memory accesses. */
+		kmemcheck_hide_pages(page, pages >> 1);
+
+		for (i = pages >> 1; i < pages; ++i)
+			clear_page(page_address(&page[i]));
+	}
+
 	mod_zone_page_state(page_zone(page),
 		(s->flags & SLAB_RECLAIM_ACCOUNT) ?
 		NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
@@ -1122,6 +1145,7 @@ out:
 static void __free_slab(struct kmem_cache *s, struct page *page)
 {
 	int pages = 1 << s->order;
+	int order = s->order;
 
 	if (unlikely(SlabDebug(page))) {
 		void *p;
@@ -1132,12 +1156,17 @@ static void __free_slab(struct kmem_cache *s, struct page *page)
 		ClearSlabDebug(page);
 	}
 
+	if (!(s->flags & __GFP_NOTRACK)) {
+		pages += pages;
+		order += 1;
+	}
+
 	mod_zone_page_state(page_zone(page),
 		(s->flags & SLAB_RECLAIM_ACCOUNT) ?
 		NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
 		- pages);
 
-	__free_pages(page, s->order);
+	__free_pages(page, order);
 }
 
 static void rcu_free_slab(struct rcu_head *h)
#1 lore raw
Quoted 4 lines · Vegard Nossum

General description: kmemcheck will trap every read and write to memory that was allocated dynamically (ie. with kmalloc()). If a memory address is read that has not previously been written to, a message is printed to the kernel log.

<snip>

Quoted 6 lines
arch/x86/kernel/kmemcheck_32.c new file +0 -0
new file mode 100644
index 0000000..9d065b9

arch/x86/kernel/kmemcheck_32.c line 8

Quoted 8 lines
arch/x86/kernel/kmemcheck_32.c new file +8 -0
@@ -0,0 +1,290 @@
+#include <linux/kernel.h>
+#include <linux/kallsyms.h>
+#include <linux/mm.h>
+#include <asm/cacheflush.h>
+#include <asm/kmemcheck.h>
+#include <asm/tlbflush.h>
+
+static int

Not 'static bool'?

Quoted 6 lines
arch/x86/kernel/kmemcheck_32.c new file +6 -0
+page_is_tracked(struct page *page)
+{
+    struct page *head;
+
+    if (!page)
+        return 0;

arch/x86/kernel/kmemcheck_32.c line 24

Quoted 10 lines
arch/x86/kernel/kmemcheck_32.c new file +10 -0
+    head = compound_head(page);
+    if (!head)
+        return 0;
+    if (!(head->flags & (1 << PG_slab)))
+        return 0;
+    if (!head->slab)
+        return 0;
+    if (head->slab->flags & __GFP_NOTRACK)
+        return 0;
+    return 1;

Why not returning 'false' and 'true'?

Quoted 60 lines
arch/x86/kernel/kmemcheck_32.c new file +60 -0
+}
+
+static void
+show_addr(uint32_t addr)
+{
+    struct page *page;
+    pte_t *pte;
+
+    if (!addr)
+        return;
+    page = virt_to_page(addr);
+    if (!page_is_tracked(page))
+        return;
+
+    pte = lookup_address(addr);
+    change_page_attr(page, 1, __pgprot(pte->pte_low | _PAGE_VISIBLE));
+    __flush_tlb_one(addr);
+}
+
+static void
+hide_addr(uint32_t addr)
+{
+    struct page *page;
+    pte_t *pte;
+
+    if (!addr)
+        return;
+    page = virt_to_page(addr);
+    if (!page_is_tracked(page))
+        return;
+    pte = lookup_address(addr);
+    change_page_attr(page, 1, __pgprot(pte->pte_low & ~_PAGE_VISIBLE));
+    __flush_tlb_one(addr);
+}
+
+DEFINE_PER_CPU(uint32_t, kmemcheck_read_addr);
+DEFINE_PER_CPU(uint32_t, kmemcheck_write_addr);
+
+void
+kmemcheck_show(struct pt_regs *regs)
+{
+    show_addr(__get_cpu_var(kmemcheck_read_addr));
+    show_addr(__get_cpu_var(kmemcheck_write_addr));
+    regs->eflags |= TF_MASK;
+}
+
+void
+kmemcheck_hide(struct pt_regs *regs)
+{
+    hide_addr(__get_cpu_var(kmemcheck_read_addr));
+    hide_addr(__get_cpu_var(kmemcheck_write_addr));
+    regs->eflags &= ~TF_MASK;
+}
+
+void
+kmemcheck_hide_pages(struct page *p, unsigned int n)
+{
+    unsigned int i;
+
+    for(i = 0; i < n; ++i) {

arch/x86/kernel/kmemcheck_32.c line 94

Quoted 10 lines
arch/x86/kernel/kmemcheck_32.c new file +10 -0
+        unsigned long address = (unsigned long) page_address(&p[i]);
+        pte_t *pte = lookup_address(address);
+
+        change_page_attr(&p[i], 1,
+            __pgprot(pte->pte_low & ~_PAGE_VISIBLE));
+        __flush_tlb_one(address);
+    }
+}
+
+static int

'static bool'?

Quoted 21 lines
arch/x86/kernel/kmemcheck_32.c new file +15 -0
+opcode_is_prefix(uint8_t b)
+{
+    return
+        /* Group 1 */
+        b == 0xf0 || b == 0xf2 || b == 0xf3
+        /* Group 2 */
+        || b == 0x2e || b == 0x36 || b == 0x3e || b == 0x26
+        || b == 0x64 || b == 0x65 || b == 0x2e || b == 0x3e
+        /* Group 3 */
+        || b == 0x66
+        /* Group 4 */
+        || b == 0x67;
+}
+
+/* This is a VERY crude opcode decoder. We only need to find the size 

of the
+ * load/store that caused our #PF and this should work for all the opcodes
+ * that we care about. Moreover, the ones who invented this instruction set
+ * should be shot. */

arch/x86/kernel/kmemcheck_32.c line 114

Quoted 2 lines
arch/x86/kernel/kmemcheck_32.c new file +2 -0
+static unsigned int
+opcode_get_size(const uint8_t *opcode)

Are we not using 'u8' in the kernel?

arch/x86/kernel/kmemcheck_32.c

Quoted 2 lines
arch/x86/kernel/kmemcheck_32.c new file +2 -0
+{
+    const uint8_t *i;

and here. Also, I find the name 'i' a bit confusing in this context, since it is not really a counter. What about 'cur', 'prefix_op' or something of the sort?

Quoted 5 lines
arch/x86/kernel/kmemcheck_32.c new file +5 -0
+
+    /* Default operand size */
+    int operand_size_override = 32;
+
+    /* prefixes */

arch/x86/kernel/kmemcheck_32.c line 131

Quoted 10 lines
arch/x86/kernel/kmemcheck_32.c new file +10 -0
+    for (i = opcode; opcode_is_prefix(*i); ++i) {
+        if (*i == 0x66)
+            operand_size_override = 16;
+    }
+
+    /* escape opcode */
+    if (*i == 0x0f) {
+        ++i;
+
+        if(*i == 0xb6)

Please do, as the previous, 'if ()'. A good checker for these kind of things is the 'scripts/checkpatch.pl'...

arch/x86/kernel/kmemcheck_32.c line 140

Quoted 9 lines
arch/x86/kernel/kmemcheck_32.c new file +9 -0
+            return operand_size_override >> 1;
+        if(*i == 0xb7)
+            return 16;
+    }
+
+    return (*i & 1) ? operand_size_override : 8;
+}
+
+static uint8_t

and here...

Quoted 1 line

+opcode_get_primary(const uint8_t *opcode)

and here..

arch/x86/kernel/kmemcheck_32.c

Quoted 2 lines
arch/x86/kernel/kmemcheck_32.c new file +2 -0
+{
+    const uint8_t *i;

and here. Also, I find the name 'i' a bit confusing in this context, since it is not really a counter. What about 'cur', 'prim_op', 'prefix_op' or something of the sort?

Quoted 23 lines
arch/x86/kernel/kmemcheck_32.c new file +23 -0
+
+    /* skip prefixes */
+    for (i = opcode; opcode_is_prefix(*i); ++i);
+    return *i;
+}
+
+static void *address_get_shadow_slab(unsigned long address,
+    struct page *head)
+{
+    if (!head->slab)
+        return NULL;
+
+    if (head->slab->flags & __GFP_NOTRACK)
+        return NULL;
+
+    return (void *) address + (PAGE_SIZE << head->slab->order);
+}
+
+static void *address_get_shadow(unsigned long address)
+{
+    struct page *page = virt_to_page(address);
+    struct page *head = compound_head(page);
+

arch/x86/kernel/kmemcheck_32.c line 176

Quoted 10 lines
arch/x86/kernel/kmemcheck_32.c new file +10 -0
+    if (!head)
+        return NULL;
+
+    if (head->flags & (1 << PG_slab))
+        return address_get_shadow_slab(address, head);
+
+    return NULL;
+}
+
+static int

'static bool'?

arch/x86/kernel/kmemcheck_32.c line 189

Quoted 13 lines
arch/x86/kernel/kmemcheck_32.c new file +13 -0
+test(void *shadow, unsigned int size)
+{
+    switch (size) {
+    case 8:
+        return *(uint8_t *) shadow == 0xff;
+    case 16:
+        return *(uint16_t *) shadow == 0xffff;
+    case 32:
+        return *(uint32_t *) shadow == 0xffffffff;
+    }
+
+    BUG();
+    return 0;

'return false;'?

arch/x86/kernel/kmemcheck_32.c

Quoted 2 lines
arch/x86/kernel/kmemcheck_32.c new file +2 -0
+}
+

<snip>

cu
Richard Knutsson

#2 lore raw

Hi,

Quoted 5 lines · Richard Knutsson
Quoted 1 line · Vegard Nossum

+static int

Not 'static bool'?

Quoted 1 line

+page_is_tracked(struct page *page)

Why not returning 'false' and 'true'?

Sorry, I am not used to using bool in C :-) I will change this if bool is preferred in kernel code.

Quoted 3 lines

arch/x86/kernel/kmemcheck_32.c line 114

Quoted 2 lines
arch/x86/kernel/kmemcheck_32.c new file +2 -0
+static unsigned int
+opcode_get_size(const uint8_t *opcode)

Are we not using 'u8' in the kernel?

Actually, I don't see any reason to use u8 when uint8_t is already standard and used in other places in the kernel.

Thanks for the other comments! I will make the necessary changes for the next version.

Quoted 2 lines

cu
Richard Knutsson

Vegard

#3 lore raw
Quoted 10 lines · Vegard Nossum

Hi,

Quoted 6 lines · Richard Knutsson
Quoted 2 lines · Vegard Nossum

+static int

Not 'static bool'?

Quoted 9 lines
Quoted 4 lines
Quoted 2 lines

+page_is_tracked(struct page *page)

Why not returning 'false' and 'true'?

Sorry, I am not used to using bool in C :-) I will change this if bool is preferred in kernel code.

Well, why not use them since we have them (C99 standard and over a year in the kernel). ;)
What is "preferred" in a group of a few thousands, is hard to say, but I believe it is the way to go. The only "resistance" to it I know, is "it is not a C idiom". A quite illogical statement, at best. However, the 0/1 vs false/true is just a preference. (I like false/true, since I also say "true AND false = false" for example... (NOT true = false, makes sense to me, NOT 1 = 0 seem strange, why can't it be 2, or -1 ;) ))

Quoted 9 lines
Quoted 5 lines

arch/x86/kernel/kmemcheck_32.c line 114

Quoted 3 lines
arch/x86/kernel/kmemcheck_32.c new file +2 -0
+static unsigned int
+opcode_get_size(const uint8_t *opcode)

Are we not using 'u8' in the kernel?

Actually, I don't see any reason to use u8 when uint8_t is already standard and used in other places in the kernel.

I believe I have heard they can be a problem in some situations. It also have the benefit of uniforming the kernel-code.

cu
Richard Knutsson

#4 lore raw
In reply to Vegard Nossum

Hi Vegard,

arch/x86/Kconfig.debug config IOMMU_LEAK · line 123

Quoted 9 lines · Vegard Nossum
arch/x86/Kconfig.debug +9 -0
+config KMEMCHECK
+       bool "Trap use of uninitialized memory"
+       depends on X86_32 && !CC_OPTIMIZE_FOR_SIZE
+       help
+         This option enables tracing of dynamically allocated kernel memory
+         to see if memory is used before it has been given an initial value.
+         Be aware that this requires half of your memory for bookkeeping and
+         will insert extra code at *every* read and write to tracked memory
+         thus slow down the kernel code (but user code is unaffected).

Is it really necessary to track every memory address? Tracking slab objects would require far less memory. You might also want to make kzalloc() and GFP_ZERO mark the memory area as initialized to avoid some page faults.

mm/slub.c static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) · line 1060

Quoted 3 lines · Vegard Nossum
mm/slub.c +3 -0
+       /* Actually allocate twice as much, since we need to track the
+        * status of each byte within the allocation. */
+       if (!(flags & __GFP_NOTRACK)) {

If you change __GFP_NOTRACK to __GFP_TRACK, you can avoid the double negation here.

Pekka

#5 lore raw

Hi,

Quoted 13 lines · Pekka Enberg

Hi Vegard,

arch/x86/Kconfig.debug config IOMMU_LEAK · line 123

Quoted 9 lines · Vegard Nossum
arch/x86/Kconfig.debug +9 -0
+config KMEMCHECK
+       bool "Trap use of uninitialized memory"
+       depends on X86_32 && !CC_OPTIMIZE_FOR_SIZE
+       help
+         This option enables tracing of dynamically allocated kernel memory
+         to see if memory is used before it has been given an initial value.
+         Be aware that this requires half of your memory for bookkeeping and
+         will insert extra code at *every* read and write to tracked memory
+         thus slow down the kernel code (but user code is unaffected).
Quoted 4 lines

Is it really necessary to track every memory address? Tracking slab objects would require far less memory. You might also want to make kzalloc() and GFP_ZERO mark the memory area as initialized to avoid some page faults.

Yes, we are in fact only tracking the memory within SLUB allocations (minus what SLUB itself needs for bookkeeping -- like the caches). Maybe the Kconfig text was unclear?

As for the kzalloc() and GFP_ZERO, I believe these will write zeros to the data in question before the memory is returned to the caller. In that case, the area will be "automatically" set to initialized since these writes are also intercepted by kmemcheck. If not, I will have to investigate some more :-)

Quoted 7 lines · Vegard Nossum

mm/slub.c static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) · line 1060

Quoted 3 lines
mm/slub.c +3 -0
+       /* Actually allocate twice as much, since we need to track the
+        * status of each byte within the allocation. */
+       if (!(flags & __GFP_NOTRACK)) {

If you change __GFP_NOTRACK to __GFP_TRACK, you can avoid the double negation here.

I deliberately chose this form. Here is my rationale: By default, when kmemcheck is enabled, we want to track as much as possible. So every "normal" allocation should be tracked. It seems easier to make an exception for the pages that should *not* be tracked (like the SLUB caches, DMA allocations), since this group of allocations is much smaller than the group of allocations that should be tracked.

I could embed __GFP_TRACK into GFP_KERNEL, but then I would have to mask this out at every non-tracked allocation, which leaves us with the exact opposite problem, just in a different place.

Thank you for looking :)

Quoted 3 lines

Pekka

Vegard

#6 lore raw

Hi,

Quoted 4 lines · Pekka Enberg
Quoted 4 lines

Is it really necessary to track every memory address? Tracking slab objects would require far less memory. You might also want to make kzalloc() and GFP_ZERO mark the memory area as initialized to avoid some page faults.

Quoted 2 lines · Vegard Nossum

Yes, we are in fact only tracking the memory within SLUB allocations (minus what SLUB itself needs for bookkeeping -- like the caches).

Yeah but you didn't answer my question: why do we track every memory address instead of slab objects? What's the benefit? Like I already said, tracking slab objects would require much less memory which makes the thing more practical. It also reduces the number of false positives (the CONFIG_OPTIMIZE_FOR_SIZE problem). And we already have slab poisoning to cover the cases we would not catch with this scheme.

Quoted 4 lines · Vegard Nossum

As for the kzalloc() and GFP_ZERO, I believe these will write zeros to the data in question before the memory is returned to the caller. In that case, the area will be "automatically" set to initialized since these writes are also intercepted by kmemcheck.

Yes, and what I proposed is as a potential optimization. Debugging aids need to be fast enough to be practical.

Pekka

#7 lore raw
Quoted 12 lines · Pekka J Enberg

Hi,

Quoted 4 lines · Pekka Enberg
Quoted 4 lines

Is it really necessary to track every memory address? Tracking slab objects would require far less memory. You might also want to make kzalloc() and GFP_ZERO mark the memory area as initialized to avoid some page faults.

Quoted 2 lines · Vegard Nossum

Yes, we are in fact only tracking the memory within SLUB allocations (minus what SLUB itself needs for bookkeeping -- like the caches).

Quoted 6 lines

Yeah but you didn't answer my question: why do we track every memory address instead of slab objects? What's the benefit? Like I already said, tracking slab objects would require much less memory which makes the thing more practical. It also reduces the number of false positives (the CONFIG_OPTIMIZE_FOR_SIZE problem). And we already have slab poisoning to cover the cases we would not catch with this scheme.

If I understand you correctly, you only want to be notified if any memory within an allocation is used before any memory within the allocation has been initialized. I think that this would be quite useless compared to tracking all the bytes of an allocation. I am not truly concerned about the memory usage; this kind of error detection is by definition slow and memory intense. I think that slab poisoning is in a way more subtle, since it does not immediately produce a warning on its own; what it does is really to invoke *other* error catching mechanisms. Slab poisoning works best with pointers since pointers will be dereferenced. What about other kinds of data? The kernel will not complain (loudly enough, anyway) if your bit-field contains some arbitrary unexpected value. Kmemcheck will immediately print a message to the log in this case.

Quoted 8 lines · Vegard Nossum
Quoted 4 lines

As for the kzalloc() and GFP_ZERO, I believe these will write zeros to the data in question before the memory is returned to the caller. In that case, the area will be "automatically" set to initialized since these writes are also intercepted by kmemcheck.

Yes, and what I proposed is as a potential optimization. Debugging aids need to be fast enough to be practical.

Yep, I didn't realize. Thanks. :-)

Quoted 3 lines

Pekka

Vegard

#9 lore raw
In reply to Vegard Nossum
Quoted 2 lines · Vegard Nossum

- We properly flush TLB entries that change. This used to not be the case, and so we

For low values of "properly" @)

arch/x86/kernel/kmemcheck_32.c line 41

Quoted 3 lines
arch/x86/kernel/kmemcheck_32.c new file +3 -0
+	pte = lookup_address(addr);
+	change_page_attr(page, 1, __pgprot(pte->pte_low | _PAGE_VISIBLE));
+	__flush_tlb_one(addr);

That's not enough, you need to flush all CPUs.

Also when you don't call global_flush_tlb() eventually because c_p_a() will leak flush objects over time.

-Andi

#8 lore raw
In reply to Vegard Nossum

Hi Vegard,

Quoted 4 lines · Vegard Nossum

If I understand you correctly, you only want to be notified if any memory within an allocation is used before any memory within the allocation has been initialized. I think that this would be quite useless compared to tracking all the bytes of an allocation.

Ok, why is it useless? I am trying to understand what kind of errors you hope to catch here.

Quoted 2 lines · Vegard Nossum

I am not truly concerned about the memory usage; this kind of error detection is by definition slow and memory intense.

There's "slow" and then there's "too slow for mainline" :-). But even if you want to track every byte, you could use a bitmap in the slab layer to cut down memory usage and get rid of "shadow pages", no?

Pekka

#10 lore raw
In reply to Andi Kleen
Quoted 6 lines · Andi Kleen
Quoted 2 lines · Vegard Nossum

- We properly flush TLB entries that change. This used to not be the case, and so we

For low values of "properly" @)

Quoted 8 lines

arch/x86/kernel/kmemcheck_32.c line 41

Quoted 3 lines
arch/x86/kernel/kmemcheck_32.c new file +3 -0
+     pte = lookup_address(addr);
+     change_page_attr(page, 1, __pgprot(pte->pte_low | _PAGE_VISIBLE));
+     __flush_tlb_one(addr);

That's not enough, you need to flush all CPUs.

Also when you don't call global_flush_tlb() eventually because c_p_a() will leak flush objects over time.

We don't need to flush all CPUs. This is my rationale: The debug exception (single-step trap) will always happen on the same CPU that the page fault occurred on. Page fault shows the page, debug exception hides the page again. Between those two operations, nothing else can happen that will use the TLB entry in question (unless you have some weird race condition, but then the code is in error anyway).

What is c_p_a() and what is a flush object?

Quoted 3 lines

-Andi

Vegard

#11 lore raw
Quoted 4 lines

We don't need to flush all CPUs. This is my rationale: The debug exception (single-step trap) will always happen on the same CPU that the page fault occurred on. Page fault shows the page, debug exception hides the page again. Between those two operations, nothing else can

You're ignoring preemptible kernels for once.

Also there is always a race. e.g. consider another CPU fetches the entry into its TLB while you have it temporarily unprotected. Then you protect it again but the other CPU still keeps the unprotected entry in its TLBs which you don't flush.

In general any changes to the direct mapping have to be done globally.

Quoted 4 lines

happen that will use the TLB entry in question (unless you have some weird race condition, but then the code is in error anyway).

What is c_p_a() and what is a flush object?

change_page_attr() is designed to queue flush requests for later which are then done by global_tlb_flush() For that it creates a list of pages. It cannot be safely used without global_flush_tlb() otherwise the flush list will just grow unbounded.

You could in theory write an equivalent optimized for your case that does not require that, but it's not even safe for your case anyways.

-Andi