diff options
| author | Kees Cook <keescook@chromium.org> | 2014-04-03 16:29:50 -0400 |
|---|---|---|
| committer | Kees Cook <keescook@chromium.org> | 2014-10-16 17:38:54 -0400 |
| commit | 80d6b0c2eed2a504f6740cd1f5ea76dc50abfc4d (patch) | |
| tree | 32f6d8a1c5a2250cc3f303df545dfbf52da62d19 | |
| parent | 1e6b48116a95046ec51f3d40f83aff8b006674d7 (diff) | |
ARM: mm: allow text and rodata sections to be read-only
This introduces CONFIG_DEBUG_RODATA, making kernel text and rodata
read-only. Additionally, this splits rodata from text so that rodata can
also be NX, which may lead to wasted memory when aligning to SECTION_SIZE.
The read-only areas are made writable during ftrace updates and kexec.
Signed-off-by: Kees Cook <keescook@chromium.org>
Tested-by: Laura Abbott <lauraa@codeaurora.org>
Acked-by: Nicolas Pitre <nico@linaro.org>
| -rw-r--r-- | arch/arm/include/asm/cacheflush.h | 10 | ||||
| -rw-r--r-- | arch/arm/kernel/ftrace.c | 19 | ||||
| -rw-r--r-- | arch/arm/kernel/machine_kexec.c | 1 | ||||
| -rw-r--r-- | arch/arm/kernel/vmlinux.lds.S | 3 | ||||
| -rw-r--r-- | arch/arm/mm/Kconfig | 12 | ||||
| -rw-r--r-- | arch/arm/mm/init.c | 48 |
6 files changed, 92 insertions, 1 deletions
diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index 10e78d00a0bb..2d46862e7bef 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h | |||
| @@ -487,6 +487,16 @@ int set_memory_rw(unsigned long addr, int numpages); | |||
| 487 | int set_memory_x(unsigned long addr, int numpages); | 487 | int set_memory_x(unsigned long addr, int numpages); |
| 488 | int set_memory_nx(unsigned long addr, int numpages); | 488 | int set_memory_nx(unsigned long addr, int numpages); |
| 489 | 489 | ||
| 490 | #ifdef CONFIG_DEBUG_RODATA | ||
| 491 | void mark_rodata_ro(void); | ||
| 492 | void set_kernel_text_rw(void); | ||
| 493 | void set_kernel_text_ro(void); | ||
| 494 | #else | ||
| 495 | static inline void set_kernel_text_rw(void) { } | ||
| 496 | static inline void set_kernel_text_ro(void) { } | ||
| 497 | #endif | ||
| 498 | |||
| 490 | void flush_uprobe_xol_access(struct page *page, unsigned long uaddr, | 499 | void flush_uprobe_xol_access(struct page *page, unsigned long uaddr, |
| 491 | void *kaddr, unsigned long len); | 500 | void *kaddr, unsigned long len); |
| 501 | |||
| 492 | #endif | 502 | #endif |
diff --git a/arch/arm/kernel/ftrace.c b/arch/arm/kernel/ftrace.c index af9a8a927a4e..b8c75e45a950 100644 --- a/arch/arm/kernel/ftrace.c +++ b/arch/arm/kernel/ftrace.c | |||
| @@ -15,6 +15,7 @@ | |||
| 15 | #include <linux/ftrace.h> | 15 | #include <linux/ftrace.h> |
| 16 | #include <linux/uaccess.h> | 16 | #include <linux/uaccess.h> |
| 17 | #include <linux/module.h> | 17 | #include <linux/module.h> |
| 18 | #include <linux/stop_machine.h> | ||
| 18 | 19 | ||
| 19 | #include <asm/cacheflush.h> | 20 | #include <asm/cacheflush.h> |
| 20 | #include <asm/opcodes.h> | 21 | #include <asm/opcodes.h> |
| @@ -35,6 +36,22 @@ | |||
| 35 | 36 | ||
| 36 | #define OLD_NOP 0xe1a00000 /* mov r0, r0 */ | 37 | #define OLD_NOP 0xe1a00000 /* mov r0, r0 */ |
| 37 | 38 | ||
| 39 | static int __ftrace_modify_code(void *data) | ||
| 40 | { | ||
| 41 | int *command = data; | ||
| 42 | |||
| 43 | set_kernel_text_rw(); | ||
| 44 | ftrace_modify_all_code(*command); | ||
| 45 | set_kernel_text_ro(); | ||
| 46 | |||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | |||
| 50 | void arch_ftrace_update_code(int command) | ||
| 51 | { | ||
| 52 | stop_machine(__ftrace_modify_code, &command, NULL); | ||
| 53 | } | ||
| 54 | |||
| 38 | static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec) | 55 | static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec) |
| 39 | { | 56 | { |
| 40 | return rec->arch.old_mcount ? OLD_NOP : NOP; | 57 | return rec->arch.old_mcount ? OLD_NOP : NOP; |
| @@ -73,6 +90,8 @@ int ftrace_arch_code_modify_prepare(void) | |||
| 73 | int ftrace_arch_code_modify_post_process(void) | 90 | int ftrace_arch_code_modify_post_process(void) |
| 74 | { | 91 | { |
| 75 | set_all_modules_text_ro(); | 92 | set_all_modules_text_ro(); |
| 93 | /* Make sure any TLB misses during machine stop are cleared. */ | ||
| 94 | flush_tlb_all(); | ||
| 76 | return 0; | 95 | return 0; |
| 77 | } | 96 | } |
| 78 | 97 | ||
diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c index 8f75250cbe30..4423a565ef6f 100644 --- a/arch/arm/kernel/machine_kexec.c +++ b/arch/arm/kernel/machine_kexec.c | |||
| @@ -164,6 +164,7 @@ void machine_kexec(struct kimage *image) | |||
| 164 | reboot_code_buffer = page_address(image->control_code_page); | 164 | reboot_code_buffer = page_address(image->control_code_page); |
| 165 | 165 | ||
| 166 | /* Prepare parameters for reboot_code_buffer*/ | 166 | /* Prepare parameters for reboot_code_buffer*/ |
| 167 | set_kernel_text_rw(); | ||
| 167 | kexec_start_address = image->start; | 168 | kexec_start_address = image->start; |
| 168 | kexec_indirection_page = page_list; | 169 | kexec_indirection_page = page_list; |
| 169 | kexec_mach_type = machine_arch_type; | 170 | kexec_mach_type = machine_arch_type; |
diff --git a/arch/arm/kernel/vmlinux.lds.S b/arch/arm/kernel/vmlinux.lds.S index 18fd68a295ea..3afcb6c2cf06 100644 --- a/arch/arm/kernel/vmlinux.lds.S +++ b/arch/arm/kernel/vmlinux.lds.S | |||
| @@ -120,6 +120,9 @@ SECTIONS | |||
| 120 | ARM_CPU_KEEP(PROC_INFO) | 120 | ARM_CPU_KEEP(PROC_INFO) |
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | #ifdef CONFIG_DEBUG_RODATA | ||
| 124 | . = ALIGN(1<<SECTION_SHIFT); | ||
| 125 | #endif | ||
| 123 | RO_DATA(PAGE_SIZE) | 126 | RO_DATA(PAGE_SIZE) |
| 124 | 127 | ||
| 125 | . = ALIGN(4); | 128 | . = ALIGN(4); |
diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig index 7a0756df91a2..c9cd9c5bf1e1 100644 --- a/arch/arm/mm/Kconfig +++ b/arch/arm/mm/Kconfig | |||
| @@ -1017,3 +1017,15 @@ config ARM_KERNMEM_PERMS | |||
| 1017 | padded to section-size (1MiB) boundaries (because their permissions | 1017 | padded to section-size (1MiB) boundaries (because their permissions |
| 1018 | are different and splitting the 1M pages into 4K ones causes TLB | 1018 | are different and splitting the 1M pages into 4K ones causes TLB |
| 1019 | performance problems), wasting memory. | 1019 | performance problems), wasting memory. |
| 1020 | |||
| 1021 | config DEBUG_RODATA | ||
| 1022 | bool "Make kernel text and rodata read-only" | ||
| 1023 | depends on ARM_KERNMEM_PERMS | ||
| 1024 | default y | ||
| 1025 | help | ||
| 1026 | If this is set, kernel text and rodata will be made read-only. This | ||
| 1027 | is to help catch accidental or malicious attempts to change the | ||
| 1028 | kernel's executable code. Additionally splits rodata from kernel | ||
| 1029 | text so it can be made explicitly non-executable. This creates | ||
| 1030 | another section-size padded region, so it can waste more memory | ||
| 1031 | space while gaining the read-only protections. | ||
diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c index e6bfe76b2f59..dc2db779cdf4 100644 --- a/arch/arm/mm/init.c +++ b/arch/arm/mm/init.c | |||
| @@ -622,9 +622,10 @@ struct section_perm { | |||
| 622 | unsigned long end; | 622 | unsigned long end; |
| 623 | pmdval_t mask; | 623 | pmdval_t mask; |
| 624 | pmdval_t prot; | 624 | pmdval_t prot; |
| 625 | pmdval_t clear; | ||
| 625 | }; | 626 | }; |
| 626 | 627 | ||
| 627 | struct section_perm nx_perms[] = { | 628 | static struct section_perm nx_perms[] = { |
| 628 | /* Make pages tables, etc before _stext RW (set NX). */ | 629 | /* Make pages tables, etc before _stext RW (set NX). */ |
| 629 | { | 630 | { |
| 630 | .start = PAGE_OFFSET, | 631 | .start = PAGE_OFFSET, |
| @@ -639,8 +640,35 @@ struct section_perm nx_perms[] = { | |||
| 639 | .mask = ~PMD_SECT_XN, | 640 | .mask = ~PMD_SECT_XN, |
| 640 | .prot = PMD_SECT_XN, | 641 | .prot = PMD_SECT_XN, |
| 641 | }, | 642 | }, |
| 643 | #ifdef CONFIG_DEBUG_RODATA | ||
| 644 | /* Make rodata NX (set RO in ro_perms below). */ | ||
| 645 | { | ||
| 646 | .start = (unsigned long)__start_rodata, | ||
| 647 | .end = (unsigned long)__init_begin, | ||
| 648 | .mask = ~PMD_SECT_XN, | ||
| 649 | .prot = PMD_SECT_XN, | ||
| 650 | }, | ||
| 651 | #endif | ||
| 642 | }; | 652 | }; |
| 643 | 653 | ||
| 654 | #ifdef CONFIG_DEBUG_RODATA | ||
| 655 | static struct section_perm ro_perms[] = { | ||
| 656 | /* Make kernel code and rodata RX (set RO). */ | ||
| 657 | { | ||
| 658 | .start = (unsigned long)_stext, | ||
| 659 | .end = (unsigned long)__init_begin, | ||
| 660 | #ifdef CONFIG_ARM_LPAE | ||
| 661 | .mask = ~PMD_SECT_RDONLY, | ||
| 662 | .prot = PMD_SECT_RDONLY, | ||
| 663 | #else | ||
| 664 | .mask = ~(PMD_SECT_APX | PMD_SECT_AP_WRITE), | ||
| 665 | .prot = PMD_SECT_APX | PMD_SECT_AP_WRITE, | ||
| 666 | .clear = PMD_SECT_AP_WRITE, | ||
| 667 | #endif | ||
| 668 | }, | ||
| 669 | }; | ||
| 670 | #endif | ||
| 671 | |||
| 644 | /* | 672 | /* |
| 645 | * Updates section permissions only for the current mm (sections are | 673 | * Updates section permissions only for the current mm (sections are |
| 646 | * copied into each mm). During startup, this is the init_mm. Is only | 674 | * copied into each mm). During startup, this is the init_mm. Is only |
| @@ -704,6 +732,24 @@ static inline void fix_kernmem_perms(void) | |||
| 704 | { | 732 | { |
| 705 | set_section_perms(nx_perms, prot); | 733 | set_section_perms(nx_perms, prot); |
| 706 | } | 734 | } |
| 735 | |||
| 736 | #ifdef CONFIG_DEBUG_RODATA | ||
| 737 | void mark_rodata_ro(void) | ||
| 738 | { | ||
| 739 | set_section_perms(ro_perms, prot); | ||
| 740 | } | ||
| 741 | |||
| 742 | void set_kernel_text_rw(void) | ||
| 743 | { | ||
| 744 | set_section_perms(ro_perms, clear); | ||
| 745 | } | ||
| 746 | |||
| 747 | void set_kernel_text_ro(void) | ||
| 748 | { | ||
