@@ -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
@@ -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;
@@ -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);
@@ -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.
@@ -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 */
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 <keescook@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(-)