mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +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_x(unsigned long addr, int numpages);
 | 
				
			||||||
int set_memory_nx(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 flush_uprobe_xol_access(struct page *page, unsigned long uaddr,
 | 
				
			||||||
			     void *kaddr, unsigned long len);
 | 
								     void *kaddr, unsigned long len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@
 | 
				
			||||||
#include <linux/ftrace.h>
 | 
					#include <linux/ftrace.h>
 | 
				
			||||||
#include <linux/uaccess.h>
 | 
					#include <linux/uaccess.h>
 | 
				
			||||||
#include <linux/module.h>
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/stop_machine.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <asm/cacheflush.h>
 | 
					#include <asm/cacheflush.h>
 | 
				
			||||||
#include <asm/opcodes.h>
 | 
					#include <asm/opcodes.h>
 | 
				
			||||||
| 
						 | 
					@ -35,6 +36,22 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define	OLD_NOP		0xe1a00000	/* mov r0, r0 */
 | 
					#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)
 | 
					static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return rec->arch.old_mcount ? OLD_NOP : NOP;
 | 
						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)
 | 
					int ftrace_arch_code_modify_post_process(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	set_all_modules_text_ro();
 | 
						set_all_modules_text_ro();
 | 
				
			||||||
 | 
						/* Make sure any TLB misses during machine stop are cleared. */
 | 
				
			||||||
 | 
						flush_tlb_all();
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -164,6 +164,7 @@ void machine_kexec(struct kimage *image)
 | 
				
			||||||
	reboot_code_buffer = page_address(image->control_code_page);
 | 
						reboot_code_buffer = page_address(image->control_code_page);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Prepare parameters for reboot_code_buffer*/
 | 
						/* Prepare parameters for reboot_code_buffer*/
 | 
				
			||||||
 | 
						set_kernel_text_rw();
 | 
				
			||||||
	kexec_start_address = image->start;
 | 
						kexec_start_address = image->start;
 | 
				
			||||||
	kexec_indirection_page = page_list;
 | 
						kexec_indirection_page = page_list;
 | 
				
			||||||
	kexec_mach_type = machine_arch_type;
 | 
						kexec_mach_type = machine_arch_type;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -120,6 +120,9 @@ SECTIONS
 | 
				
			||||||
			ARM_CPU_KEEP(PROC_INFO)
 | 
								ARM_CPU_KEEP(PROC_INFO)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_DEBUG_RODATA
 | 
				
			||||||
 | 
						. = ALIGN(1<<SECTION_SHIFT);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
	RO_DATA(PAGE_SIZE)
 | 
						RO_DATA(PAGE_SIZE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	. = ALIGN(4);
 | 
						. = ALIGN(4);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1017,3 +1017,15 @@ config ARM_KERNMEM_PERMS
 | 
				
			||||||
	  padded to section-size (1MiB) boundaries (because their permissions
 | 
						  padded to section-size (1MiB) boundaries (because their permissions
 | 
				
			||||||
	  are different and splitting the 1M pages into 4K ones causes TLB
 | 
						  are different and splitting the 1M pages into 4K ones causes TLB
 | 
				
			||||||
	  performance problems), wasting memory.
 | 
						  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;
 | 
						unsigned long end;
 | 
				
			||||||
	pmdval_t mask;
 | 
						pmdval_t mask;
 | 
				
			||||||
	pmdval_t prot;
 | 
						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). */
 | 
						/* Make pages tables, etc before _stext RW (set NX). */
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		.start	= PAGE_OFFSET,
 | 
							.start	= PAGE_OFFSET,
 | 
				
			||||||
| 
						 | 
					@ -639,8 +640,35 @@ struct section_perm nx_perms[] = {
 | 
				
			||||||
		.mask	= ~PMD_SECT_XN,
 | 
							.mask	= ~PMD_SECT_XN,
 | 
				
			||||||
		.prot	= 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
 | 
					 * Updates section permissions only for the current mm (sections are
 | 
				
			||||||
 * copied into each mm). During startup, this is the init_mm. Is only
 | 
					 * 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);
 | 
						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
 | 
					#else
 | 
				
			||||||
static inline void fix_kernmem_perms(void) { }
 | 
					static inline void fix_kernmem_perms(void) { }
 | 
				
			||||||
#endif /* CONFIG_ARM_KERNMEM_PERMS */
 | 
					#endif /* CONFIG_ARM_KERNMEM_PERMS */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue