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/

Reply via email to