diff options
author | Arjan van de Ven <arjan@infradead.org> | 2008-01-30 07:34:08 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-01-30 07:34:08 -0500 |
commit | edeed30589f5defe63ce6aaae56f2b7c855e4520 (patch) | |
tree | a49736ff74dcbd3feba3c8318b204fb2165f892d | |
parent | adafdf6a4e45f2d1051e10aebe13025e89dbdf6d (diff) |
x86: add testcases for RODATA and NX protections/attributes
Latest update; I now have 4 NX tests, but 2 fail so they're #if 0'd.
I also cleaned up the NX test code quite a bit, and got rid of the ugly
exception table sorting stuff.
From: Arjan van de Ven <arjan@linux.intel.com>
This patch adds testcases for the CONFIG_DEBUG_RODATA configuration option
as well as the NX CPU feature/mappings. Both testcases can move to tests/
once that patch gets merged into mainline.
(I'm half considering moving the rodata test into mm/init.c but I'll
wait with that until init.c is unified)
As part of this I had to fix a not-quite-right alignment in the vmlinux.lds.h
for the RODATA sections, which lead to 1 page less being marked read only.
Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
-rw-r--r-- | arch/x86/kernel/test_nx.c | 176 | ||||
-rw-r--r-- | arch/x86/kernel/test_rodata.c | 86 | ||||
-rw-r--r-- | arch/x86/mm/init_32.c | 3 | ||||
-rw-r--r-- | arch/x86/mm/init_64.c | 3 | ||||
-rw-r--r-- | include/asm-generic/vmlinux.lds.h | 1 | ||||
-rw-r--r-- | include/asm-x86/cacheflush.h | 7 |
6 files changed, 276 insertions, 0 deletions
diff --git a/arch/x86/kernel/test_nx.c b/arch/x86/kernel/test_nx.c new file mode 100644 index 000000000000..6d7ef11e7975 --- /dev/null +++ b/arch/x86/kernel/test_nx.c | |||
@@ -0,0 +1,176 @@ | |||
1 | /* | ||
2 | * test_nx.c: functional test for NX functionality | ||
3 | * | ||
4 | * (C) Copyright 2008 Intel Corporation | ||
5 | * Author: Arjan van de Ven <arjan@linux.intel.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * as published by the Free Software Foundation; version 2 | ||
10 | * of the License. | ||
11 | */ | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/sort.h> | ||
14 | #include <asm/uaccess.h> | ||
15 | |||
16 | extern int rodata_test_data; | ||
17 | |||
18 | /* | ||
19 | * This file checks 4 things: | ||
20 | * 1) Check if the stack is not executable | ||
21 | * 2) Check if kmalloc memory is not executable | ||
22 | * 3) Check if the .rodata section is not executable | ||
23 | * 4) Check if the .data section of a module is not executable | ||
24 | * | ||
25 | * To do this, the test code tries to execute memory in stack/kmalloc/etc, | ||
26 | * and then checks if the expected trap happens. | ||
27 | * | ||
28 | * Sadly, this implies having a dynamic exception handling table entry. | ||
29 | * ... which can be done (and will make Rusty cry)... but it can only | ||
30 | * be done in a stand-alone module with only 1 entry total. | ||
31 | * (otherwise we'd have to sort and that's just too messy) | ||
32 | */ | ||
33 | |||
34 | |||
35 | |||
36 | /* | ||
37 | * We want to set up an exception handling point on our stack, | ||
38 | * which means a variable value. This function is rather dirty | ||
39 | * and walks the exception table of the module, looking for a magic | ||
40 | * marker and replaces it with a specific function. | ||
41 | */ | ||
42 | static void fudze_exception_table(void *marker, void *new) | ||
43 | { | ||
44 | struct module *mod = THIS_MODULE; | ||
45 | struct exception_table_entry *extable; | ||
46 | |||
47 | /* | ||
48 | * Note: This module has only 1 exception table entry, | ||
49 | * so searching and sorting is not needed. If that changes, | ||
50 | * this would be the place to search and re-sort the exception | ||
51 | * table. | ||
52 | */ | ||
53 | if (mod->num_exentries > 1) { | ||
54 | printk(KERN_ERR "test_nx: too many exception table entries!\n"); | ||
55 | printk(KERN_ERR "test_nx: test results are not reliable.\n"); | ||
56 | return; | ||
57 | } | ||
58 | extable = (struct exception_table_entry *)mod->extable; | ||
59 | extable[0].insn = (unsigned long)new; | ||
60 | } | ||
61 | |||
62 | |||
63 | /* | ||
64 | * exception tables get their symbols translated so we need | ||
65 | * to use a fake function to put in there, which we can then | ||
66 | * replace at runtime. | ||
67 | */ | ||
68 | void foo_label(void); | ||
69 | |||
70 | /* | ||
71 | * returns 0 for not-executable, negative for executable | ||
72 | * | ||
73 | * Note: we cannot allow this function to be inlined, because | ||
74 | * that would give us more than 1 exception table entry. | ||
75 | * This in turn would break the assumptions above. | ||
76 | */ | ||
77 | static noinline int test_address(void *address) | ||
78 | { | ||
79 | unsigned long result; | ||
80 | |||
81 | /* Set up an exception table entry for our address */ | ||
82 | fudze_exception_table(&foo_label, address); | ||
83 | result = 1; | ||
84 | asm volatile( | ||
85 | "foo_label:\n" | ||
86 | "0: call *%[fake_code]\n" | ||
87 | "1:\n" | ||
88 | ".section .fixup,\"ax\"\n" | ||
89 | "2: mov %[zero], %[rslt]\n" | ||
90 | " ret\n" | ||
91 | ".previous\n" | ||
92 | ".section __ex_table,\"a\"\n" | ||
93 | " .align 8\n" | ||
94 | " .quad 0b\n" | ||
95 | " .quad 2b\n" | ||
96 | ".previous\n" | ||
97 | : [rslt] "=r" (result) | ||
98 | : [fake_code] "r" (address), [zero] "r" (0UL), "0" (result) | ||
99 | ); | ||
100 | /* change the exception table back for the next round */ | ||
101 | fudze_exception_table(address, &foo_label); | ||
102 | |||
103 | if (result) | ||
104 | return -ENODEV; | ||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | static unsigned char test_data = 0xC3; /* 0xC3 is the opcode for "ret" */ | ||
109 | |||
110 | static int test_NX(void) | ||
111 | { | ||
112 | int ret = 0; | ||
113 | /* 0xC3 is the opcode for "ret" */ | ||
114 | char stackcode[] = {0xC3, 0x90, 0 }; | ||
115 | char *heap; | ||
116 | |||
117 | test_data = 0xC3; | ||
118 | |||
119 | printk(KERN_INFO "Testing NX protection\n"); | ||
120 | |||
121 | /* Test 1: check if the stack is not executable */ | ||
122 | if (test_address(&stackcode)) { | ||
123 | printk(KERN_ERR "test_nx: stack was executable\n"); | ||
124 | ret = -ENODEV; | ||
125 | } | ||
126 | |||
127 | |||
128 | /* Test 2: Check if the heap is executable */ | ||
129 | heap = kmalloc(64, GFP_KERNEL); | ||
130 | if (!heap) | ||
131 | return -ENOMEM; | ||
132 | heap[0] = 0xC3; /* opcode for "ret" */ | ||
133 | |||
134 | if (test_address(heap)) { | ||
135 | printk(KERN_ERR "test_nx: heap was executable\n"); | ||
136 | ret = -ENODEV; | ||
137 | } | ||
138 | kfree(heap); | ||
139 | |||
140 | /* | ||
141 | * The following 2 tests currently fail, this needs to get fixed | ||
142 | * Until then, don't run them to avoid too many people getting scared | ||
143 | * by the error message | ||
144 | */ | ||
145 | #if 0 | ||
146 | |||
147 | #ifdef CONFIG_DEBUG_RODATA | ||
148 | /* Test 3: Check if the .rodata section is executable */ | ||
149 | if (rodata_test_data != 0xC3) { | ||
150 | printk(KERN_ERR "test_nx: .rodata marker has invalid value\n"); | ||
151 | ret = -ENODEV; | ||
152 | } else if (test_address(&rodata_test_data)) { | ||
153 | printk(KERN_ERR "test_nx: .rodata section is executable\n"); | ||
154 | ret = -ENODEV; | ||
155 | } | ||
156 | #endif | ||
157 | |||
158 | /* Test 4: Check if the .data section of a module is executable */ | ||
159 | if (test_address(&test_data)) { | ||
160 | printk(KERN_ERR "test_nx: .data section is executable\n"); | ||
161 | ret = -ENODEV; | ||
162 | } | ||
163 | |||
164 | #endif | ||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | static void test_exit(void) | ||
169 | { | ||
170 | } | ||
171 | |||
172 | module_init(test_NX); | ||
173 | module_exit(test_exit); | ||
174 | MODULE_LICENSE("GPL"); | ||
175 | MODULE_DESCRIPTION("Testcase for the NX infrastructure"); | ||
176 | MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>"); | ||
diff --git a/arch/x86/kernel/test_rodata.c b/arch/x86/kernel/test_rodata.c new file mode 100644 index 000000000000..4c163772000e --- /dev/null +++ b/arch/x86/kernel/test_rodata.c | |||
@@ -0,0 +1,86 @@ | |||
1 | /* | ||
2 | * test_rodata.c: functional test for mark_rodata_ro function | ||
3 | * | ||
4 | * (C) Copyright 2008 Intel Corporation | ||
5 | * Author: Arjan van de Ven <arjan@linux.intel.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * as published by the Free Software Foundation; version 2 | ||
10 | * of the License. | ||
11 | */ | ||
12 | #include <linux/module.h> | ||
13 | #include <asm/sections.h> | ||
14 | extern int rodata_test_data; | ||
15 | |||
16 | int rodata_test(void) | ||
17 | { | ||
18 | unsigned long result; | ||
19 | unsigned long start, end; | ||
20 | |||
21 | /* test 1: read the value */ | ||
22 | /* If this test fails, some previous testrun has clobbered the state */ | ||
23 | if (!rodata_test_data) { | ||
24 | printk(KERN_ERR "rodata_test: test 1 fails (start data)\n"); | ||
25 | return -ENODEV; | ||
26 | } | ||
27 | |||
28 | /* test 2: write to the variable; this should fault */ | ||
29 | /* | ||
30 | * If this test fails, we managed to overwrite the data | ||
31 | * | ||
32 | * This is written in assembly to be able to catch the | ||
33 | * exception that is supposed to happen in the correct | ||
34 | * case | ||
35 | */ | ||
36 | |||
37 | result = 1; | ||
38 | asm volatile( | ||
39 | "0: mov %[zero],(%[rodata_test])\n" | ||
40 | " mov %[zero], %[rslt]\n" | ||
41 | "1:\n" | ||
42 | ".section .fixup,\"ax\"\n" | ||
43 | "2: jmp 1b\n" | ||
44 | ".previous\n" | ||
45 | ".section __ex_table,\"a\"\n" | ||
46 | " .align 16\n" | ||
47 | #ifdef CONFIG_X86_32 | ||
48 | " .long 0b,2b\n" | ||
49 | #else | ||
50 | " .quad 0b,2b\n" | ||
51 | #endif | ||
52 | ".previous" | ||
53 | : [rslt] "=r" (result) | ||
54 | : [rodata_test] "r" (&rodata_test_data), [zero] "r" (0UL) | ||
55 | ); | ||
56 | |||
57 | |||
58 | if (!result) { | ||
59 | printk(KERN_ERR "rodata_test: test data was not read only\n"); | ||
60 | return -ENODEV; | ||
61 | } | ||
62 | |||
63 | /* test 3: check the value hasn't changed */ | ||
64 | /* If this test fails, we managed to overwrite the data */ | ||
65 | if (!rodata_test_data) { | ||
66 | printk(KERN_ERR "rodata_test: Test 3 failes (end data)\n"); | ||
67 | return -ENODEV; | ||
68 | } | ||
69 | /* test 4: check if the rodata section is 4Kb aligned */ | ||
70 | start = (unsigned long)__start_rodata; | ||
71 | end = (unsigned long)__end_rodata; | ||
72 | if (start & (PAGE_SIZE - 1)) { | ||
73 | printk(KERN_ERR "rodata_test: .rodata is not 4k aligned\n"); | ||
74 | return -ENODEV; | ||
75 | } | ||
76 | if (end & (PAGE_SIZE - 1)) { | ||
77 | printk(KERN_ERR "rodata_test: .rodata end is not 4k aligned\n"); | ||
78 | return -ENODEV; | ||
79 | } | ||
80 | |||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | MODULE_LICENSE("GPL"); | ||
85 | MODULE_DESCRIPTION("Testcase for the DEBUG_RODATA infrastructure"); | ||
86 | MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>"); | ||
diff --git a/arch/x86/mm/init_32.c b/arch/x86/mm/init_32.c index 4d1156545194..a72737c05747 100644 --- a/arch/x86/mm/init_32.c +++ b/arch/x86/mm/init_32.c | |||
@@ -736,6 +736,8 @@ static int noinline do_test_wp_bit(void) | |||
736 | } | 736 | } |
737 | 737 | ||
738 | #ifdef CONFIG_DEBUG_RODATA | 738 | #ifdef CONFIG_DEBUG_RODATA |
739 | const int rodata_test_data = 0xC3; | ||
740 | EXPORT_SYMBOL_GPL(rodata_test_data); | ||
739 | 741 | ||
740 | void mark_rodata_ro(void) | 742 | void mark_rodata_ro(void) |
741 | { | 743 | { |
@@ -765,6 +767,7 @@ void mark_rodata_ro(void) | |||
765 | set_pages_ro(virt_to_page(start), size >> PAGE_SHIFT); | 767 | set_pages_ro(virt_to_page(start), size >> PAGE_SHIFT); |
766 | printk("Write protecting the kernel read-only data: %luk\n", | 768 | printk("Write protecting the kernel read-only data: %luk\n", |
767 | size >> 10); | 769 | size >> 10); |
770 | rodata_test(); | ||
768 | 771 | ||
769 | #ifdef CONFIG_CPA_DEBUG | 772 | #ifdef CONFIG_CPA_DEBUG |
770 | printk("Testing CPA: undo %lx-%lx\n", start, start + size); | 773 | printk("Testing CPA: undo %lx-%lx\n", start, start + size); |
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c index f97ace7a55e5..50d29f5da02b 100644 --- a/arch/x86/mm/init_64.c +++ b/arch/x86/mm/init_64.c | |||
@@ -573,6 +573,7 @@ void free_init_pages(char *what, unsigned long begin, unsigned long end) | |||
573 | set_memory_rw(begin, (end - begin)/PAGE_SIZE); | 573 | set_memory_rw(begin, (end - begin)/PAGE_SIZE); |
574 | set_memory_np(begin, (end - begin)/PAGE_SIZE); | 574 | set_memory_np(begin, (end - begin)/PAGE_SIZE); |
575 | set_memory_nx(begin, (end - begin)/PAGE_SIZE); | 575 | set_memory_nx(begin, (end - begin)/PAGE_SIZE); |
576 | rodata_test(); | ||
576 | } | 577 | } |
577 | #endif | 578 | #endif |
578 | } | 579 | } |
@@ -585,6 +586,8 @@ void free_initmem(void) | |||
585 | } | 586 | } |
586 | 587 | ||
587 | #ifdef CONFIG_DEBUG_RODATA | 588 | #ifdef CONFIG_DEBUG_RODATA |
589 | const int rodata_test_data = 0xC3; | ||
590 | EXPORT_SYMBOL_GPL(rodata_test_data); | ||
588 | 591 | ||
589 | void mark_rodata_ro(void) | 592 | void mark_rodata_ro(void) |
590 | { | 593 | { |
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 76df771be585..f784d2f34149 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h | |||
@@ -184,6 +184,7 @@ | |||
184 | VMLINUX_SYMBOL(__start___param) = .; \ | 184 | VMLINUX_SYMBOL(__start___param) = .; \ |
185 | *(__param) \ | 185 | *(__param) \ |
186 | VMLINUX_SYMBOL(__stop___param) = .; \ | 186 | VMLINUX_SYMBOL(__stop___param) = .; \ |
187 | . = ALIGN((align)); \ | ||
187 | VMLINUX_SYMBOL(__end_rodata) = .; \ | 188 | VMLINUX_SYMBOL(__end_rodata) = .; \ |
188 | } \ | 189 | } \ |
189 | . = ALIGN((align)); | 190 | . = ALIGN((align)); |
diff --git a/include/asm-x86/cacheflush.h b/include/asm-x86/cacheflush.h index 157da0206ccc..3e74aff90809 100644 --- a/include/asm-x86/cacheflush.h +++ b/include/asm-x86/cacheflush.h | |||
@@ -47,5 +47,12 @@ void clflush_cache_range(void *addr, int size); | |||
47 | #ifdef CONFIG_DEBUG_RODATA | 47 | #ifdef CONFIG_DEBUG_RODATA |
48 | void mark_rodata_ro(void); | 48 | void mark_rodata_ro(void); |
49 | #endif | 49 | #endif |
50 | #ifdef CONFIG_DEBUG_RODATA_TEST | ||
51 | void rodata_test(void); | ||
52 | #else | ||
53 | static inline void rodata_test(void) | ||
54 | { | ||
55 | } | ||
56 | #endif | ||
50 | 57 | ||
51 | #endif | 58 | #endif |