This introduces CONFIG_DEBUG_RODATA, making kernel text and rodata read-only. It splits rodata from text so that rodata can also be NX.
Signed-off-by: Kees Cook <keesc...@chromium.org> --- arch/arm/include/asm/cacheflush.h | 9 ++++ arch/arm/kernel/ftrace.c | 17 +++++++ arch/arm/kernel/vmlinux.lds.S | 3 ++ arch/arm/mm/Kconfig | 11 +++++ arch/arm/mm/init.c | 97 +++++++++++++++++++++++++++++-------- 5 files changed, 117 insertions(+), 20 deletions(-) diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index 8b8b61685a34..b6fea0a1a88b 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -487,4 +487,13 @@ int set_memory_rw(unsigned long addr, int numpages); int set_memory_x(unsigned long addr, int numpages); int set_memory_nx(unsigned long addr, int numpages); +#ifdef CONFIG_DEBUG_RODATA +void mark_rodata_ro(void); +void set_kernel_text_rw(void); +void set_kernel_text_ro(void); +#else +static inline void set_kernel_text_rw(void) { } +static inline void set_kernel_text_ro(void) { } +#endif + #endif diff --git a/arch/arm/kernel/ftrace.c b/arch/arm/kernel/ftrace.c index 34e56647dcee..4ae343c1e2a3 100644 --- a/arch/arm/kernel/ftrace.c +++ b/arch/arm/kernel/ftrace.c @@ -14,6 +14,7 @@ #include <linux/ftrace.h> #include <linux/uaccess.h> +#include <linux/stop_machine.h> #include <asm/cacheflush.h> #include <asm/opcodes.h> @@ -34,6 +35,22 @@ #define OLD_NOP 0xe1a00000 /* mov r0, r0 */ +static int __ftrace_modify_code(void *data) +{ + int *command = data; + + set_kernel_text_rw(); + ftrace_modify_all_code(*command); + set_kernel_text_ro(); + + return 0; +} + +void arch_ftrace_update_code(int command) +{ + stop_machine(__ftrace_modify_code, &command, NULL); +} + static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec) { return rec->arch.old_mcount ? OLD_NOP : NOP; diff --git a/arch/arm/kernel/vmlinux.lds.S b/arch/arm/kernel/vmlinux.lds.S index 08fa667ef2f1..ec79e7268e09 100644 --- a/arch/arm/kernel/vmlinux.lds.S +++ b/arch/arm/kernel/vmlinux.lds.S @@ -120,6 +120,9 @@ SECTIONS ARM_CPU_KEEP(PROC_INFO) } +#ifdef CONFIG_DEBUG_RODATA + . = ALIGN(1<<SECTION_SHIFT); +#endif RO_DATA(PAGE_SIZE) . = ALIGN(4); diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig index 8848d7b73e66..3c7adea7e2f6 100644 --- a/arch/arm/mm/Kconfig +++ b/arch/arm/mm/Kconfig @@ -967,3 +967,14 @@ config ARM_KERNMEM_PERMS padded to section-size (1MiB) boundaries (because their permissions are different and splitting the 1M pages into 4K ones causes TLB performance problems), wasting memory. + +config DEBUG_RODATA + bool "Make kernel text and rodata read-only" + depends on ARM_KERNMEM_PERMS && KEXEC=n && KPROBES=n + default y + help + If this is set, kernel text and rodata will be made read-only. + This additionally splits rodata from kernel text so it can be made + non-executable. This creates another section-size padded region, + so it can waste more memory space while gaining a pure read-only + rodata region. diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c index 8539eb2a01ad..3baac4ad165f 100644 --- a/arch/arm/mm/init.c +++ b/arch/arm/mm/init.c @@ -632,9 +632,10 @@ struct section_perm { unsigned long end; pmdval_t mask; pmdval_t prot; + pmdval_t clear; }; -struct section_perm section_perms[] = { +struct section_perm nx_perms[] = { /* Make pages tables, etc before _stext RW (set NX). */ { .start = PAGE_OFFSET, @@ -649,12 +650,46 @@ struct section_perm section_perms[] = { .mask = ~PMD_SECT_XN, .prot = PMD_SECT_XN, }, +#ifdef CONFIG_DEBUG_RODATA + /* Make rodata NX (set RO in ro_perms below). */ + { + .start = (unsigned long)__start_rodata, + .end = (unsigned long)__init_begin, + .mask = ~PMD_SECT_XN, + .prot = PMD_SECT_XN, + }, +#endif +}; + +#ifdef CONFIG_DEBUG_RODATA +struct section_perm ro_perms[] = { + /* + * Make kernel code and rodata RX (set RO). + * This entry must be first for set_kernel_text_rw() to find it. + */ + { + .start = (unsigned long)_stext, + .end = (unsigned long)__init_begin, +#ifdef CONFIG_ARM_LPAE + .mask = ~PMD_SECT_RDONLY, + .prot = PMD_SECT_RDONLY, +#else + .mask = ~(PMD_SECT_APX | PMD_SECT_AP_WRITE), + .prot = PMD_SECT_APX | PMD_SECT_AP_WRITE, + .clear = PMD_SECT_AP_WRITE, +#endif + }, }; +#endif static inline void section_update(unsigned long addr, pmdval_t mask, pmdval_t prot) { - pmd_t *pmd = pmd_off_k(addr); + struct mm_struct *mm; + pmd_t *pmd; + + mm = current->active_mm; + pmd = pmd_offset(pud_offset(pgd_offset(mm, addr), addr), addr); #ifdef CONFIG_ARM_LPAE pmd[0] = __pmd((pmd_val(pmd[0]) & mask) | prot); @@ -681,30 +716,52 @@ static inline bool arch_has_strict_perms(void) return true; } +#define set_section_perms(perms, field) { \ + size_t i; \ + unsigned long addr; \ + \ + if (!arch_has_strict_perms()) \ + return; \ + \ + for (i = 0; i < ARRAY_SIZE(perms); i++) { \ + if (!IS_ALIGNED(perms[i].start, SECTION_SIZE) || \ + !IS_ALIGNED(perms[i].end, SECTION_SIZE)) { \ + pr_err("BUG: section %lx-%lx not aligned to %lx\n", \ + perms[i].start, perms[i].end, \ + SECTION_SIZE); \ + continue; \ + } \ + \ + for (addr = perms[i].start; \ + addr < perms[i].end; \ + addr += SECTION_SIZE) \ + section_update(addr, perms[i].mask, \ + perms[i].field); \ + } \ +} + static inline void fix_kernmem_perms(void) { - unsigned long addr; - unsigned int i; + set_section_perms(nx_perms, prot); +} - if (!arch_has_strict_perms()) - return; +#ifdef CONFIG_DEBUG_RODATA +void mark_rodata_ro(void) +{ + set_section_perms(ro_perms, prot); +} - for (i = 0; i < ARRAY_SIZE(section_perms); i++) { - if (!IS_ALIGNED(section_perms[i].start, SECTION_SIZE) || - !IS_ALIGNED(section_perms[i].end, SECTION_SIZE)) { - pr_err("BUG: section %lx-%lx not aligned to %lx\n", - section_perms[i].start, section_perms[i].end, - SECTION_SIZE); - continue; - } +void set_kernel_text_rw(void) +{ + set_section_perms(ro_perms, clear); +} - for (addr = section_perms[i].start; - addr < section_perms[i].end; - addr += SECTION_SIZE) - section_update(addr, section_perms[i].mask, - section_perms[i].prot); - } +void set_kernel_text_ro(void) +{ + set_section_perms(ro_perms, prot); } +#endif /* CONFIG_DEBUG_RODATA */ + #else static inline void fix_kernmem_perms(void) { } #endif /* CONFIG_ARM_KERNMEM_PERMS */ -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/