diff options
| -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 |
