mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									1e6b48116a
								
							
						
					
					
						commit
						80d6b0c2ee
					
				
					 6 changed files with 92 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -487,6 +487,16 @@ 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
 | 
			
		||||
 | 
			
		||||
void flush_uprobe_xol_access(struct page *page, unsigned long uaddr,
 | 
			
		||||
			     void *kaddr, unsigned long len);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@
 | 
			
		|||
#include <linux/ftrace.h>
 | 
			
		||||
#include <linux/uaccess.h>
 | 
			
		||||
#include <linux/module.h>
 | 
			
		||||
#include <linux/stop_machine.h>
 | 
			
		||||
 | 
			
		||||
#include <asm/cacheflush.h>
 | 
			
		||||
#include <asm/opcodes.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +36,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;
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +90,8 @@ int ftrace_arch_code_modify_prepare(void)
 | 
			
		|||
int ftrace_arch_code_modify_post_process(void)
 | 
			
		||||
{
 | 
			
		||||
	set_all_modules_text_ro();
 | 
			
		||||
	/* Make sure any TLB misses during machine stop are cleared. */
 | 
			
		||||
	flush_tlb_all();
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -164,6 +164,7 @@ void machine_kexec(struct kimage *image)
 | 
			
		|||
	reboot_code_buffer = page_address(image->control_code_page);
 | 
			
		||||
 | 
			
		||||
	/* Prepare parameters for reboot_code_buffer*/
 | 
			
		||||
	set_kernel_text_rw();
 | 
			
		||||
	kexec_start_address = image->start;
 | 
			
		||||
	kexec_indirection_page = page_list;
 | 
			
		||||
	kexec_mach_type = machine_arch_type;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1017,3 +1017,15 @@ 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
 | 
			
		||||
	default y
 | 
			
		||||
	help
 | 
			
		||||
	  If this is set, kernel text and rodata will be made read-only. This
 | 
			
		||||
	  is to help catch accidental or malicious attempts to change the
 | 
			
		||||
	  kernel's executable code. Additionally splits rodata from kernel
 | 
			
		||||
	  text so it can be made explicitly non-executable. This creates
 | 
			
		||||
	  another section-size padded region, so it can waste more memory
 | 
			
		||||
	  space while gaining the read-only protections.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -622,9 +622,10 @@ struct section_perm {
 | 
			
		|||
	unsigned long end;
 | 
			
		||||
	pmdval_t mask;
 | 
			
		||||
	pmdval_t prot;
 | 
			
		||||
	pmdval_t clear;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct section_perm nx_perms[] = {
 | 
			
		||||
static struct section_perm nx_perms[] = {
 | 
			
		||||
	/* Make pages tables, etc before _stext RW (set NX). */
 | 
			
		||||
	{
 | 
			
		||||
		.start	= PAGE_OFFSET,
 | 
			
		||||
| 
						 | 
				
			
			@ -639,8 +640,35 @@ struct section_perm nx_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
 | 
			
		||||
static struct section_perm ro_perms[] = {
 | 
			
		||||
	/* Make kernel code and rodata RX (set RO). */
 | 
			
		||||
	{
 | 
			
		||||
		.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
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Updates section permissions only for the current mm (sections are
 | 
			
		||||
 * copied into each mm). During startup, this is the init_mm. Is only
 | 
			
		||||
| 
						 | 
				
			
			@ -704,6 +732,24 @@ static inline void fix_kernmem_perms(void)
 | 
			
		|||
{
 | 
			
		||||
	set_section_perms(nx_perms, prot);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef CONFIG_DEBUG_RODATA
 | 
			
		||||
void mark_rodata_ro(void)
 | 
			
		||||
{
 | 
			
		||||
	set_section_perms(ro_perms, prot);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void set_kernel_text_rw(void)
 | 
			
		||||
{
 | 
			
		||||
	set_section_perms(ro_perms, clear);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue