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 | { | ||
749 | set_section_perms(ro_perms, prot); | ||
750 | } | ||
751 | #endif /* CONFIG_DEBUG_RODATA */ | ||
752 | |||
707 | #else | 753 | #else |
708 | static inline void fix_kernmem_perms(void) { } | 754 | static inline void fix_kernmem_perms(void) { } |
709 | #endif /* CONFIG_ARM_KERNMEM_PERMS */ | 755 | #endif /* CONFIG_ARM_KERNMEM_PERMS */ |