arm64: dump: Add checking for writable and exectuable pages
Page mappings with full RWX permissions are a security risk. x86
has an option to walk the page tables and dump any bad pages.
(See e1a58320a3 ("x86/mm: Warn on W^X mappings")). Add a similar
implementation for arm64.
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Mark Rutland <mark.rutland@arm.com>
Tested-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Laura Abbott <labbott@redhat.com>
Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
[catalin.marinas@arm.com: folded fix for KASan out of bounds from Mark Rutland]
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
			
			
This commit is contained in:
		
							parent
							
								
									cfd69e9551
								
							
						
					
					
						commit
						1404d6f13e
					
				| @ -42,6 +42,35 @@ config ARM64_RANDOMIZE_TEXT_OFFSET | ||||
| 	  of TEXT_OFFSET and platforms must not require a specific | ||||
| 	  value. | ||||
| 
 | ||||
| config DEBUG_WX | ||||
| 	bool "Warn on W+X mappings at boot" | ||||
| 	select ARM64_PTDUMP_CORE | ||||
| 	---help--- | ||||
| 	  Generate a warning if any W+X mappings are found at boot. | ||||
| 
 | ||||
| 	  This is useful for discovering cases where the kernel is leaving | ||||
| 	  W+X mappings after applying NX, as such mappings are a security risk. | ||||
| 	  This check also includes UXN, which should be set on all kernel | ||||
| 	  mappings. | ||||
| 
 | ||||
| 	  Look for a message in dmesg output like this: | ||||
| 
 | ||||
| 	    arm64/mm: Checked W+X mappings: passed, no W+X pages found. | ||||
| 
 | ||||
| 	  or like this, if the check failed: | ||||
| 
 | ||||
| 	    arm64/mm: Checked W+X mappings: FAILED, <N> W+X pages found. | ||||
| 
 | ||||
| 	  Note that even if the check fails, your kernel is possibly | ||||
| 	  still fine, as W+X mappings are not a security hole in | ||||
| 	  themselves, what they do is that they make the exploitation | ||||
| 	  of other unfixed kernel bugs easier. | ||||
| 
 | ||||
| 	  There is no runtime or memory usage effect of this option | ||||
| 	  once the kernel has booted up - it's a one time check. | ||||
| 
 | ||||
| 	  If in doubt, say "Y". | ||||
| 
 | ||||
| config DEBUG_SET_MODULE_RONX | ||||
| 	bool "Set loadable kernel module data as NX and text as RO" | ||||
| 	depends on MODULES | ||||
|  | ||||
| @ -42,5 +42,13 @@ static inline int ptdump_debugfs_register(struct ptdump_info *info, | ||||
| 	return 0; | ||||
| } | ||||
| #endif | ||||
| void ptdump_check_wx(void); | ||||
| #endif /* CONFIG_ARM64_PTDUMP_CORE */ | ||||
| 
 | ||||
| #ifdef CONFIG_DEBUG_WX | ||||
| #define debug_checkwx()	ptdump_check_wx() | ||||
| #else | ||||
| #define debug_checkwx()	do { } while (0) | ||||
| #endif | ||||
| 
 | ||||
| #endif /* __ASM_PTDUMP_H */ | ||||
|  | ||||
| @ -74,6 +74,9 @@ struct pg_state { | ||||
| 	unsigned long start_address; | ||||
| 	unsigned level; | ||||
| 	u64 current_prot; | ||||
| 	bool check_wx; | ||||
| 	unsigned long wx_pages; | ||||
| 	unsigned long uxn_pages; | ||||
| }; | ||||
| 
 | ||||
| struct prot_bits { | ||||
| @ -202,6 +205,35 @@ static void dump_prot(struct pg_state *st, const struct prot_bits *bits, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void note_prot_uxn(struct pg_state *st, unsigned long addr) | ||||
| { | ||||
| 	if (!st->check_wx) | ||||
| 		return; | ||||
| 
 | ||||
| 	if ((st->current_prot & PTE_UXN) == PTE_UXN) | ||||
| 		return; | ||||
| 
 | ||||
| 	WARN_ONCE(1, "arm64/mm: Found non-UXN mapping at address %p/%pS\n", | ||||
| 		  (void *)st->start_address, (void *)st->start_address); | ||||
| 
 | ||||
| 	st->uxn_pages += (addr - st->start_address) / PAGE_SIZE; | ||||
| } | ||||
| 
 | ||||
| static void note_prot_wx(struct pg_state *st, unsigned long addr) | ||||
| { | ||||
| 	if (!st->check_wx) | ||||
| 		return; | ||||
| 	if ((st->current_prot & PTE_RDONLY) == PTE_RDONLY) | ||||
| 		return; | ||||
| 	if ((st->current_prot & PTE_PXN) == PTE_PXN) | ||||
| 		return; | ||||
| 
 | ||||
| 	WARN_ONCE(1, "arm64/mm: Found insecure W+X mapping at address %p/%pS\n", | ||||
| 		  (void *)st->start_address, (void *)st->start_address); | ||||
| 
 | ||||
| 	st->wx_pages += (addr - st->start_address) / PAGE_SIZE; | ||||
| } | ||||
| 
 | ||||
| static void note_page(struct pg_state *st, unsigned long addr, unsigned level, | ||||
| 				u64 val) | ||||
| { | ||||
| @ -219,6 +251,8 @@ static void note_page(struct pg_state *st, unsigned long addr, unsigned level, | ||||
| 		unsigned long delta; | ||||
| 
 | ||||
| 		if (st->current_prot) { | ||||
| 			note_prot_uxn(st, addr); | ||||
| 			note_prot_wx(st, addr); | ||||
| 			pt_dump_seq_printf(st->seq, "0x%016lx-0x%016lx   ", | ||||
| 				   st->start_address, addr); | ||||
| 
 | ||||
| @ -344,6 +378,26 @@ static struct ptdump_info kernel_ptdump_info = { | ||||
| 	.base_addr	= VA_START, | ||||
| }; | ||||
| 
 | ||||
| void ptdump_check_wx(void) | ||||
| { | ||||
| 	struct pg_state st = { | ||||
| 		.seq = NULL, | ||||
| 		.marker = (struct addr_marker[]) { | ||||
| 			{ 0, NULL}, | ||||
| 			{ -1, NULL}, | ||||
| 		}, | ||||
| 		.check_wx = true, | ||||
| 	}; | ||||
| 
 | ||||
| 	walk_pgd(&st, &init_mm, 0); | ||||
| 	note_page(&st, 0, 0, 0); | ||||
| 	if (st.wx_pages || st.uxn_pages) | ||||
| 		pr_warn("Checked W+X mappings: FAILED, %lu W+X pages found, %lu non-UXN pages found\n", | ||||
| 			st.wx_pages, st.uxn_pages); | ||||
| 	else | ||||
| 		pr_info("Checked W+X mappings: passed, no W+X pages found\n"); | ||||
| } | ||||
| 
 | ||||
| static int ptdump_init(void) | ||||
| { | ||||
| 	ptdump_initialize(); | ||||
|  | ||||
| @ -40,6 +40,7 @@ | ||||
| #include <asm/tlb.h> | ||||
| #include <asm/memblock.h> | ||||
| #include <asm/mmu_context.h> | ||||
| #include <asm/ptdump.h> | ||||
| 
 | ||||
| u64 idmap_t0sz = TCR_T0SZ(VA_BITS); | ||||
| 
 | ||||
| @ -438,6 +439,8 @@ void mark_rodata_ro(void) | ||||
| 
 | ||||
| 	/* flush the TLBs after updating live kernel mappings */ | ||||
| 	flush_tlb_all(); | ||||
| 
 | ||||
| 	debug_checkwx(); | ||||
| } | ||||
| 
 | ||||
| static void __init map_kernel_segment(pgd_t *pgd, void *va_start, void *va_end, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user