diff options
author | Stephen Smalley <sds@tycho.nsa.gov> | 2015-10-05 12:55:20 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2015-10-06 05:11:48 -0400 |
commit | e1a58320a38dfa72be48a0f1a3a92273663ba6db (patch) | |
tree | 1ff867a83c601546e0910c009c96f4e1c1cd43b6 | |
parent | 38a413cbc2b2834683b21823d964bc2d2f0abb82 (diff) |
x86/mm: Warn on W^X mappings
Warn on any residual W+X mappings after setting NX
if DEBUG_WX is enabled. Introduce a separate
X86_PTDUMP_CORE config that enables the code for
dumping the page tables without enabling the debugfs
interface, so that DEBUG_WX can be enabled without
exposing the debugfs interface. Switch EFI_PGT_DUMP
to using X86_PTDUMP_CORE so that it also does not require
enabling the debugfs interface.
On success it prints this to the kernel log:
x86/mm: Checked W+X mappings: passed, no W+X pages found.
On failure it prints a warning and a count of the failed pages:
------------[ cut here ]------------
WARNING: CPU: 1 PID: 1 at arch/x86/mm/dump_pagetables.c:226 note_page+0x610/0x7b0()
x86/mm: Found insecure W+X mapping at address ffffffff81755000/__stop___ex_table+0xfa8/0xabfa8
[...]
Call Trace:
[<ffffffff81380a5f>] dump_stack+0x44/0x55
[<ffffffff8109d3f2>] warn_slowpath_common+0x82/0xc0
[<ffffffff8109d48c>] warn_slowpath_fmt+0x5c/0x80
[<ffffffff8106cfc9>] ? note_page+0x5c9/0x7b0
[<ffffffff8106d010>] note_page+0x610/0x7b0
[<ffffffff8106d409>] ptdump_walk_pgd_level_core+0x259/0x3c0
[<ffffffff8106d5a7>] ptdump_walk_pgd_level_checkwx+0x17/0x20
[<ffffffff81063905>] mark_rodata_ro+0xf5/0x100
[<ffffffff817415a0>] ? rest_init+0x80/0x80
[<ffffffff817415bd>] kernel_init+0x1d/0xe0
[<ffffffff8174cd1f>] ret_from_fork+0x3f/0x70
[<ffffffff817415a0>] ? rest_init+0x80/0x80
---[ end trace a1f23a1e42a2ac76 ]---
x86/mm: Checked W+X mappings: FAILED, 171 W+X pages found.
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
Acked-by: Kees Cook <keescook@chromium.org>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Arjan van de Ven <arjan@linux.intel.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-kernel@vger.kernel.org
Link: http://lkml.kernel.org/r/1444064120-11450-1-git-send-email-sds@tycho.nsa.gov
[ Improved the Kconfig help text and made the new option default-y
if CONFIG_DEBUG_RODATA=y, because it already found buggy mappings,
so we really want people to have this on by default. ]
Signed-off-by: Ingo Molnar <mingo@kernel.org>
-rw-r--r-- | arch/x86/Kconfig.debug | 36 | ||||
-rw-r--r-- | arch/x86/include/asm/pgtable.h | 7 | ||||
-rw-r--r-- | arch/x86/mm/Makefile | 2 | ||||
-rw-r--r-- | arch/x86/mm/dump_pagetables.c | 42 | ||||
-rw-r--r-- | arch/x86/mm/init_32.c | 2 | ||||
-rw-r--r-- | arch/x86/mm/init_64.c | 2 |
6 files changed, 88 insertions, 3 deletions
diff --git a/arch/x86/Kconfig.debug b/arch/x86/Kconfig.debug index d8c0d3266173..3e0baf726eef 100644 --- a/arch/x86/Kconfig.debug +++ b/arch/x86/Kconfig.debug | |||
@@ -65,10 +65,14 @@ config EARLY_PRINTK_EFI | |||
65 | This is useful for kernel debugging when your machine crashes very | 65 | This is useful for kernel debugging when your machine crashes very |
66 | early before the console code is initialized. | 66 | early before the console code is initialized. |
67 | 67 | ||
68 | config X86_PTDUMP_CORE | ||
69 | def_bool n | ||
70 | |||
68 | config X86_PTDUMP | 71 | config X86_PTDUMP |
69 | bool "Export kernel pagetable layout to userspace via debugfs" | 72 | bool "Export kernel pagetable layout to userspace via debugfs" |
70 | depends on DEBUG_KERNEL | 73 | depends on DEBUG_KERNEL |
71 | select DEBUG_FS | 74 | select DEBUG_FS |
75 | select X86_PTDUMP_CORE | ||
72 | ---help--- | 76 | ---help--- |
73 | Say Y here if you want to show the kernel pagetable layout in a | 77 | Say Y here if you want to show the kernel pagetable layout in a |
74 | debugfs file. This information is only useful for kernel developers | 78 | debugfs file. This information is only useful for kernel developers |
@@ -79,7 +83,8 @@ config X86_PTDUMP | |||
79 | 83 | ||
80 | config EFI_PGT_DUMP | 84 | config EFI_PGT_DUMP |
81 | bool "Dump the EFI pagetable" | 85 | bool "Dump the EFI pagetable" |
82 | depends on EFI && X86_PTDUMP | 86 | depends on EFI |
87 | select X86_PTDUMP_CORE | ||
83 | ---help--- | 88 | ---help--- |
84 | Enable this if you want to dump the EFI page table before | 89 | Enable this if you want to dump the EFI page table before |
85 | enabling virtual mode. This can be used to debug miscellaneous | 90 | enabling virtual mode. This can be used to debug miscellaneous |
@@ -105,6 +110,35 @@ config DEBUG_RODATA_TEST | |||
105 | feature as well as for the change_page_attr() infrastructure. | 110 | feature as well as for the change_page_attr() infrastructure. |
106 | If in doubt, say "N" | 111 | If in doubt, say "N" |
107 | 112 | ||
113 | config DEBUG_WX | ||
114 | bool "Warn on W+X mappings at boot" | ||
115 | depends on DEBUG_RODATA | ||
116 | default y | ||
117 | select X86_PTDUMP_CORE | ||
118 | ---help--- | ||
119 | Generate a warning if any W+X mappings are found at boot. | ||
120 | |||
121 | This is useful for discovering cases where the kernel is leaving | ||
122 | W+X mappings after applying NX, as such mappings are a security risk. | ||
123 | |||
124 | Look for a message in dmesg output like this: | ||
125 | |||
126 | x86/mm: Checked W+X mappings: passed, no W+X pages found. | ||
127 | |||
128 | or like this, if the check failed: | ||
129 | |||
130 | x86/mm: Checked W+X mappings: FAILED, <N> W+X pages found. | ||
131 | |||
132 | Note that even if the check fails, your kernel is possibly | ||
133 | still fine, as W+X mappings are not a security hole in | ||
134 | themselves, what they do is that they make the exploitation | ||
135 | of other unfixed kernel bugs easier. | ||
136 | |||
137 | There is no runtime or memory usage effect of this option | ||
138 | once the kernel has booted up - it's a one time check. | ||
139 | |||
140 | If in doubt, say "Y". | ||
141 | |||
108 | config DEBUG_SET_MODULE_RONX | 142 | config DEBUG_SET_MODULE_RONX |
109 | bool "Set loadable kernel module data as NX and text as RO" | 143 | bool "Set loadable kernel module data as NX and text as RO" |
110 | depends on MODULES | 144 | depends on MODULES |
diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h index 59fc3414c68b..c0b41f111a9a 100644 --- a/arch/x86/include/asm/pgtable.h +++ b/arch/x86/include/asm/pgtable.h | |||
@@ -19,6 +19,13 @@ | |||
19 | #include <asm/x86_init.h> | 19 | #include <asm/x86_init.h> |
20 | 20 | ||
21 | void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd); | 21 | void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd); |
22 | void ptdump_walk_pgd_level_checkwx(void); | ||
23 | |||
24 | #ifdef CONFIG_DEBUG_WX | ||
25 | #define debug_checkwx() ptdump_walk_pgd_level_checkwx() | ||
26 | #else | ||
27 | #define debug_checkwx() do { } while (0) | ||
28 | #endif | ||
22 | 29 | ||
23 | /* | 30 | /* |
24 | * ZERO_PAGE is a global shared page that is always zero: used | 31 | * ZERO_PAGE is a global shared page that is always zero: used |
diff --git a/arch/x86/mm/Makefile b/arch/x86/mm/Makefile index a482d105172b..65c47fda26fc 100644 --- a/arch/x86/mm/Makefile +++ b/arch/x86/mm/Makefile | |||
@@ -14,7 +14,7 @@ obj-$(CONFIG_SMP) += tlb.o | |||
14 | obj-$(CONFIG_X86_32) += pgtable_32.o iomap_32.o | 14 | obj-$(CONFIG_X86_32) += pgtable_32.o iomap_32.o |
15 | 15 | ||
16 | obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o | 16 | obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o |
17 | obj-$(CONFIG_X86_PTDUMP) += dump_pagetables.o | 17 | obj-$(CONFIG_X86_PTDUMP_CORE) += dump_pagetables.o |
18 | 18 | ||
19 | obj-$(CONFIG_HIGHMEM) += highmem_32.o | 19 | obj-$(CONFIG_HIGHMEM) += highmem_32.o |
20 | 20 | ||
diff --git a/arch/x86/mm/dump_pagetables.c b/arch/x86/mm/dump_pagetables.c index 71ab2d741024..1bf417e9cc13 100644 --- a/arch/x86/mm/dump_pagetables.c +++ b/arch/x86/mm/dump_pagetables.c | |||
@@ -32,6 +32,8 @@ struct pg_state { | |||
32 | const struct addr_marker *marker; | 32 | const struct addr_marker *marker; |
33 | unsigned long lines; | 33 | unsigned long lines; |
34 | bool to_dmesg; | 34 | bool to_dmesg; |
35 | bool check_wx; | ||
36 | unsigned long wx_pages; | ||
35 | }; | 37 | }; |
36 | 38 | ||
37 | struct addr_marker { | 39 | struct addr_marker { |
@@ -214,6 +216,16 @@ static void note_page(struct seq_file *m, struct pg_state *st, | |||
214 | const char *unit = units; | 216 | const char *unit = units; |
215 | unsigned long delta; | 217 | unsigned long delta; |
216 | int width = sizeof(unsigned long) * 2; | 218 | int width = sizeof(unsigned long) * 2; |
219 | pgprotval_t pr = pgprot_val(st->current_prot); | ||
220 | |||
221 | if (st->check_wx && (pr & _PAGE_RW) && !(pr & _PAGE_NX)) { | ||
222 | WARN_ONCE(1, | ||
223 | "x86/mm: Found insecure W+X mapping at address %p/%pS\n", | ||
224 | (void *)st->start_address, | ||
225 | (void *)st->start_address); | ||
226 | st->wx_pages += (st->current_address - | ||
227 | st->start_address) / PAGE_SIZE; | ||
228 | } | ||
217 | 229 | ||
218 | /* | 230 | /* |
219 | * Now print the actual finished series | 231 | * Now print the actual finished series |
@@ -346,7 +358,8 @@ static void walk_pud_level(struct seq_file *m, struct pg_state *st, pgd_t addr, | |||
346 | #define pgd_none(a) pud_none(__pud(pgd_val(a))) | 358 | #define pgd_none(a) pud_none(__pud(pgd_val(a))) |
347 | #endif | 359 | #endif |
348 | 360 | ||
349 | void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd) | 361 | static void ptdump_walk_pgd_level_core(struct seq_file *m, pgd_t *pgd, |
362 | bool checkwx) | ||
350 | { | 363 | { |
351 | #ifdef CONFIG_X86_64 | 364 | #ifdef CONFIG_X86_64 |
352 | pgd_t *start = (pgd_t *) &init_level4_pgt; | 365 | pgd_t *start = (pgd_t *) &init_level4_pgt; |
@@ -362,6 +375,10 @@ void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd) | |||
362 | st.to_dmesg = true; | 375 | st.to_dmesg = true; |
363 | } | 376 | } |
364 | 377 | ||
378 | st.check_wx = checkwx; | ||
379 | if (checkwx) | ||
380 | st.wx_pages = 0; | ||
381 | |||
365 | for (i = 0; i < PTRS_PER_PGD; i++) { | 382 | for (i = 0; i < PTRS_PER_PGD; i++) { |
366 | st.current_address = normalize_addr(i * PGD_LEVEL_MULT); | 383 | st.current_address = normalize_addr(i * PGD_LEVEL_MULT); |
367 | if (!pgd_none(*start)) { | 384 | if (!pgd_none(*start)) { |
@@ -381,8 +398,26 @@ void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd) | |||
381 | /* Flush out the last page */ | 398 | /* Flush out the last page */ |
382 | st.current_address = normalize_addr(PTRS_PER_PGD*PGD_LEVEL_MULT); | 399 | st.current_address = normalize_addr(PTRS_PER_PGD*PGD_LEVEL_MULT); |
383 | note_page(m, &st, __pgprot(0), 0); | 400 | note_page(m, &st, __pgprot(0), 0); |
401 | if (!checkwx) | ||
402 | return; | ||
403 | if (st.wx_pages) | ||
404 | pr_info("x86/mm: Checked W+X mappings: FAILED, %lu W+X pages found.\n", | ||
405 | st.wx_pages); | ||
406 | else | ||
407 | pr_info("x86/mm: Checked W+X mappings: passed, no W+X pages found.\n"); | ||
408 | } | ||
409 | |||
410 | void ptdump_walk_pgd_level(struct seq_file *m, pgd_t *pgd) | ||
411 | { | ||
412 | ptdump_walk_pgd_level_core(m, pgd, false); | ||
384 | } | 413 | } |
385 | 414 | ||
415 | void ptdump_walk_pgd_level_checkwx(void) | ||
416 | { | ||
417 | ptdump_walk_pgd_level_core(NULL, NULL, true); | ||
418 | } | ||
419 | |||
420 | #ifdef CONFIG_X86_PTDUMP | ||
386 | static int ptdump_show(struct seq_file *m, void *v) | 421 | static int ptdump_show(struct seq_file *m, void *v) |
387 | { | 422 | { |
388 | ptdump_walk_pgd_level(m, NULL); | 423 | ptdump_walk_pgd_level(m, NULL); |
@@ -400,10 +435,13 @@ static const struct file_operations ptdump_fops = { | |||
400 | .llseek = seq_lseek, | 435 | .llseek = seq_lseek, |
401 | .release = single_release, | 436 | .release = single_release, |
402 | }; | 437 | }; |
438 | #endif | ||
403 | 439 | ||
404 | static int pt_dump_init(void) | 440 | static int pt_dump_init(void) |
405 | { | 441 | { |
442 | #ifdef CONFIG_X86_PTDUMP | ||
406 | struct dentry *pe; | 443 | struct dentry *pe; |
444 | #endif | ||
407 | 445 | ||
408 | #ifdef CONFIG_X86_32 | 446 | #ifdef CONFIG_X86_32 |
409 | /* Not a compile-time constant on x86-32 */ | 447 | /* Not a compile-time constant on x86-32 */ |
@@ -415,10 +453,12 @@ static int pt_dump_init(void) | |||
415 | address_markers[FIXADDR_START_NR].start_address = FIXADDR_START; | 453 | address_markers[FIXADDR_START_NR].start_address = FIXADDR_START; |
416 | #endif | 454 | #endif |
417 | 455 | ||
456 | #ifdef CONFIG_X86_PTDUMP | ||
418 | pe = debugfs_create_file("kernel_page_tables", 0600, NULL, NULL, | 457 | pe = debugfs_create_file("kernel_page_tables", 0600, NULL, NULL, |
419 | &ptdump_fops); | 458 | &ptdump_fops); |
420 | if (!pe) | 459 | if (!pe) |
421 | return -ENOMEM; | 460 | return -ENOMEM; |
461 | #endif | ||
422 | 462 | ||
423 | return 0; | 463 | return 0; |
424 | } | 464 | } |
diff --git a/arch/x86/mm/init_32.c b/arch/x86/mm/init_32.c index 7562f42914b4..cb4ef3de61f9 100644 --- a/arch/x86/mm/init_32.c +++ b/arch/x86/mm/init_32.c | |||
@@ -957,6 +957,8 @@ void mark_rodata_ro(void) | |||
957 | set_pages_ro(virt_to_page(start), size >> PAGE_SHIFT); | 957 | set_pages_ro(virt_to_page(start), size >> PAGE_SHIFT); |
958 | #endif | 958 | #endif |
959 | mark_nxdata_nx(); | 959 | mark_nxdata_nx(); |
960 | if (__supported_pte_mask & _PAGE_NX) | ||
961 | debug_checkwx(); | ||
960 | } | 962 | } |
961 | #endif | 963 | #endif |
962 | 964 | ||
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c index 30564e2752d3..f8b157366700 100644 --- a/arch/x86/mm/init_64.c +++ b/arch/x86/mm/init_64.c | |||
@@ -1150,6 +1150,8 @@ void mark_rodata_ro(void) | |||
1150 | free_init_pages("unused kernel", | 1150 | free_init_pages("unused kernel", |
1151 | (unsigned long) __va(__pa_symbol(rodata_end)), | 1151 | (unsigned long) __va(__pa_symbol(rodata_end)), |
1152 | (unsigned long) __va(__pa_symbol(_sdata))); | 1152 | (unsigned long) __va(__pa_symbol(_sdata))); |
1153 | |||
1154 | debug_checkwx(); | ||
1153 | } | 1155 | } |
1154 | 1156 | ||
1155 | #endif | 1157 | #endif |