Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ARM: software-based priviledged-no-access support

Provide a software-based implementation of the priviledged no access
support found in ARMv8.1.

Userspace pages are mapped using a different domain number from the
kernel and IO mappings. If we switch the user domain to "no access"
when we enter the kernel, we can prevent the kernel from touching
userspace.

However, the kernel needs to be able to access userspace via the
various user accessor functions. With the wrapping in the previous
patch, we can temporarily enable access when the kernel needs user
access, and re-disable it afterwards.

This allows us to trap non-intended accesses to userspace, eg, caused
by an inadvertent dereference of the LIST_POISON* values, which, with
appropriate user mappings setup, can be made to succeed. This in turn
can allow use-after-free bugs to be further exploited than would
otherwise be possible.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

+125 -8
+15
arch/arm/Kconfig
··· 1694 1694 bool "Allocate 2nd-level pagetables from highmem" 1695 1695 depends on HIGHMEM 1696 1696 1697 + config CPU_SW_DOMAIN_PAN 1698 + bool "Enable use of CPU domains to implement privileged no-access" 1699 + depends on MMU && !ARM_LPAE 1700 + default y 1701 + help 1702 + Increase kernel security by ensuring that normal kernel accesses 1703 + are unable to access userspace addresses. This can help prevent 1704 + use-after-free bugs becoming an exploitable privilege escalation 1705 + by ensuring that magic values (such as LIST_POISON) will always 1706 + fault when dereferenced. 1707 + 1708 + CPUs with low-vector mappings use a best-efforts implementation. 1709 + Their lower 1MB needs to remain accessible for the vectors, but 1710 + the remainder of userspace will become appropriately inaccessible. 1711 + 1697 1712 config HW_PERF_EVENTS 1698 1713 bool "Enable hardware performance counter support for perf events" 1699 1714 depends on PERF_EVENTS
+30
arch/arm/include/asm/assembler.h
··· 446 446 .endm 447 447 448 448 .macro uaccess_disable, tmp, isb=1 449 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 450 + /* 451 + * Whenever we re-enter userspace, the domains should always be 452 + * set appropriately. 453 + */ 454 + mov \tmp, #DACR_UACCESS_DISABLE 455 + mcr p15, 0, \tmp, c3, c0, 0 @ Set domain register 456 + .if \isb 457 + instr_sync 458 + .endif 459 + #endif 449 460 .endm 450 461 451 462 .macro uaccess_enable, tmp, isb=1 463 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 464 + /* 465 + * Whenever we re-enter userspace, the domains should always be 466 + * set appropriately. 467 + */ 468 + mov \tmp, #DACR_UACCESS_ENABLE 469 + mcr p15, 0, \tmp, c3, c0, 0 470 + .if \isb 471 + instr_sync 472 + .endif 473 + #endif 452 474 .endm 453 475 454 476 .macro uaccess_save, tmp 477 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 478 + mrc p15, 0, \tmp, c3, c0, 0 479 + str \tmp, [sp, #S_FRAME_SIZE] 480 + #endif 455 481 .endm 456 482 457 483 .macro uaccess_restore 484 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 485 + ldr r0, [sp, #S_FRAME_SIZE] 486 + mcr p15, 0, r0, c3, c0, 0 487 + #endif 458 488 .endm 459 489 460 490 .macro uaccess_save_and_disable, tmp
+19 -2
arch/arm/include/asm/domain.h
··· 57 57 #define domain_mask(dom) ((3) << (2 * (dom))) 58 58 #define domain_val(dom,type) ((type) << (2 * (dom))) 59 59 60 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 61 + #define DACR_INIT \ 62 + (domain_val(DOMAIN_USER, DOMAIN_NOACCESS) | \ 63 + domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ 64 + domain_val(DOMAIN_IO, DOMAIN_CLIENT) | \ 65 + domain_val(DOMAIN_VECTORS, DOMAIN_CLIENT)) 66 + #else 60 67 #define DACR_INIT \ 61 68 (domain_val(DOMAIN_USER, DOMAIN_CLIENT) | \ 62 69 domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ 63 70 domain_val(DOMAIN_IO, DOMAIN_CLIENT) | \ 64 71 domain_val(DOMAIN_VECTORS, DOMAIN_CLIENT)) 72 + #endif 73 + 74 + #define __DACR_DEFAULT \ 75 + domain_val(DOMAIN_KERNEL, DOMAIN_CLIENT) | \ 76 + domain_val(DOMAIN_IO, DOMAIN_CLIENT) | \ 77 + domain_val(DOMAIN_VECTORS, DOMAIN_CLIENT) 78 + 79 + #define DACR_UACCESS_DISABLE \ 80 + (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_NOACCESS)) 81 + #define DACR_UACCESS_ENABLE \ 82 + (__DACR_DEFAULT | domain_val(DOMAIN_USER, DOMAIN_CLIENT)) 65 83 66 84 #ifndef __ASSEMBLY__ 67 85 ··· 94 76 return domain; 95 77 } 96 78 97 - #ifdef CONFIG_CPU_USE_DOMAINS 98 79 static inline void set_domain(unsigned val) 99 80 { 100 81 asm volatile( ··· 102 85 isb(); 103 86 } 104 87 88 + #ifdef CONFIG_CPU_USE_DOMAINS 105 89 #define modify_domain(dom,type) \ 106 90 do { \ 107 91 unsigned int domain = get_domain(); \ ··· 112 94 } while (0) 113 95 114 96 #else 115 - static inline void set_domain(unsigned val) { } 116 97 static inline void modify_domain(unsigned dom, unsigned type) { } 117 98 #endif 118 99
+14
arch/arm/include/asm/uaccess.h
··· 57 57 */ 58 58 static inline unsigned int uaccess_save_and_enable(void) 59 59 { 60 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 61 + unsigned int old_domain = get_domain(); 62 + 63 + /* Set the current domain access to permit user accesses */ 64 + set_domain((old_domain & ~domain_mask(DOMAIN_USER)) | 65 + domain_val(DOMAIN_USER, DOMAIN_CLIENT)); 66 + 67 + return old_domain; 68 + #else 60 69 return 0; 70 + #endif 61 71 } 62 72 63 73 static inline void uaccess_restore(unsigned int flags) 64 74 { 75 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 76 + /* Restore the user access mask */ 77 + set_domain(flags); 78 + #endif 65 79 } 66 80 67 81 /*
+30 -6
arch/arm/kernel/process.c
··· 129 129 buf[4] = '\0'; 130 130 131 131 #ifndef CONFIG_CPU_V7M 132 - printk("Flags: %s IRQs o%s FIQs o%s Mode %s ISA %s Segment %s\n", 133 - buf, interrupts_enabled(regs) ? "n" : "ff", 134 - fast_interrupts_enabled(regs) ? "n" : "ff", 135 - processor_modes[processor_mode(regs)], 136 - isa_modes[isa_mode(regs)], 137 - get_fs() == get_ds() ? "kernel" : "user"); 132 + { 133 + unsigned int domain = get_domain(); 134 + const char *segment; 135 + 136 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 137 + /* 138 + * Get the domain register for the parent context. In user 139 + * mode, we don't save the DACR, so lets use what it should 140 + * be. For other modes, we place it after the pt_regs struct. 141 + */ 142 + if (user_mode(regs)) 143 + domain = DACR_UACCESS_ENABLE; 144 + else 145 + domain = *(unsigned int *)(regs + 1); 146 + #endif 147 + 148 + if ((domain & domain_mask(DOMAIN_USER)) == 149 + domain_val(DOMAIN_USER, DOMAIN_NOACCESS)) 150 + segment = "none"; 151 + else if (get_fs() == get_ds()) 152 + segment = "kernel"; 153 + else 154 + segment = "user"; 155 + 156 + printk("Flags: %s IRQs o%s FIQs o%s Mode %s ISA %s Segment %s\n", 157 + buf, interrupts_enabled(regs) ? "n" : "ff", 158 + fast_interrupts_enabled(regs) ? "n" : "ff", 159 + processor_modes[processor_mode(regs)], 160 + isa_modes[isa_mode(regs)], segment); 161 + } 138 162 #else 139 163 printk("xPSR: %08lx\n", regs->ARM_cpsr); 140 164 #endif
+3
arch/arm/kernel/swp_emulate.c
··· 141 141 142 142 while (1) { 143 143 unsigned long temp; 144 + unsigned int __ua_flags; 144 145 146 + __ua_flags = uaccess_save_and_enable(); 145 147 if (type == TYPE_SWPB) 146 148 __user_swpb_asm(*data, address, res, temp); 147 149 else 148 150 __user_swp_asm(*data, address, res, temp); 151 + uaccess_restore(__ua_flags); 149 152 150 153 if (likely(res != -EAGAIN) || signal_pending(current)) 151 154 break;
+14
arch/arm/lib/csumpartialcopyuser.S
··· 17 17 18 18 .text 19 19 20 + #ifdef CONFIG_CPU_SW_DOMAIN_PAN 21 + .macro save_regs 22 + mrc p15, 0, ip, c3, c0, 0 23 + stmfd sp!, {r1, r2, r4 - r8, ip, lr} 24 + uaccess_enable ip 25 + .endm 26 + 27 + .macro load_regs 28 + ldmfd sp!, {r1, r2, r4 - r8, ip, lr} 29 + mcr p15, 0, ip, c3, c0, 0 30 + ret lr 31 + .endm 32 + #else 20 33 .macro save_regs 21 34 stmfd sp!, {r1, r2, r4 - r8, lr} 22 35 .endm ··· 37 24 .macro load_regs 38 25 ldmfd sp!, {r1, r2, r4 - r8, pc} 39 26 .endm 27 + #endif 40 28 41 29 .macro load1b, reg1 42 30 ldrusr \reg1, r0, 1