Skip to content

Commit 80d6b0c

Browse files
committed
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>
1 parent 1e6b481 commit 80d6b0c

File tree

6 files changed

+92
-1
lines changed

6 files changed

+92
-1
lines changed

arch/arm/include/asm/cacheflush.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,16 @@ int set_memory_rw(unsigned long addr, int numpages);
487487
int set_memory_x(unsigned long addr, int numpages);
488488
int set_memory_nx(unsigned long addr, int numpages);
489489

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+
490499
void flush_uprobe_xol_access(struct page *page, unsigned long uaddr,
491500
void *kaddr, unsigned long len);
501+
492502
#endif

arch/arm/kernel/ftrace.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/ftrace.h>
1616
#include <linux/uaccess.h>
1717
#include <linux/module.h>
18+
#include <linux/stop_machine.h>
1819

1920
#include <asm/cacheflush.h>
2021
#include <asm/opcodes.h>
@@ -35,6 +36,22 @@
3536

3637
#define OLD_NOP 0xe1a00000 /* mov r0, r0 */
3738

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+
3855
static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec)
3956
{
4057
return rec->arch.old_mcount ? OLD_NOP : NOP;
@@ -73,6 +90,8 @@ int ftrace_arch_code_modify_prepare(void)
7390
int ftrace_arch_code_modify_post_process(void)
7491
{
7592
set_all_modules_text_ro();
93+
/* Make sure any TLB misses during machine stop are cleared. */
94+
flush_tlb_all();
7695
return 0;
7796
}
7897

arch/arm/kernel/machine_kexec.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ void machine_kexec(struct kimage *image)
164164
reboot_code_buffer = page_address(image->control_code_page);
165165

166166
/* Prepare parameters for reboot_code_buffer*/
167+
set_kernel_text_rw();
167168
kexec_start_address = image->start;
168169
kexec_indirection_page = page_list;
169170
kexec_mach_type = machine_arch_type;

arch/arm/kernel/vmlinux.lds.S

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ SECTIONS
120120
ARM_CPU_KEEP(PROC_INFO)
121121
}
122122

123+
#ifdef CONFIG_DEBUG_RODATA
124+
. = ALIGN(1<<SECTION_SHIFT);
125+
#endif
123126
RO_DATA(PAGE_SIZE)
124127

125128
. = ALIGN(4);

arch/arm/mm/Kconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,3 +1017,15 @@ config ARM_KERNMEM_PERMS
10171017
padded to section-size (1MiB) boundaries (because their permissions
10181018
are different and splitting the 1M pages into 4K ones causes TLB
10191019
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.

arch/arm/mm/init.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,9 +622,10 @@ struct section_perm {
622622
unsigned long end;
623623
pmdval_t mask;
624624
pmdval_t prot;
625+
pmdval_t clear;
625626
};
626627

627-
struct section_perm nx_perms[] = {
628+
static struct section_perm nx_perms[] = {
628629
/* Make pages tables, etc before _stext RW (set NX). */
629630
{
630631
.start = PAGE_OFFSET,
@@ -639,8 +640,35 @@ struct section_perm nx_perms[] = {
639640
.mask = ~PMD_SECT_XN,
640641
.prot = PMD_SECT_XN,
641642
},
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
642652
};
643653

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+
644672
/*
645673
* Updates section permissions only for the current mm (sections are
646674
* copied into each mm). During startup, this is the init_mm. Is only
@@ -704,6 +732,24 @@ static inline void fix_kernmem_perms(void)
704732
{
705733
set_section_perms(nx_perms, prot);
706734
}
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+
707753
#else
708754
static inline void fix_kernmem_perms(void) { }
709755
#endif /* CONFIG_ARM_KERNMEM_PERMS */

0 commit comments

Comments
 (0)