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

ARM: uaccess: fix DACR mismatch with nested exceptions

Tomas Paukrt reports that his SAM9X60 based system (ARM926, ARMv5TJ)
fails to fix up alignment faults, eventually resulting in a kernel
oops.

The problem occurs when using CONFIG_CPU_USE_DOMAINS with commit
e6978e4bf181 ("ARM: save and reset the address limit when entering an
exception"). This is because the address limit is set back to
TASK_SIZE on exception entry, and, although it is restored on exception
exit, the domain register is not.

Hence, this sequence can occur:

interrupt
pt_regs->addr_limit = addr_limit // USER_DS
addr_limit = USER_DS
alignment exception
__probe_kernel_read()
old_fs = get_fs() // USER_DS
set_fs(KERNEL_DS)
addr_limit = KERNEL_DS
dacr.kernel = DOMAIN_MANAGER
interrupt
pt_regs->addr_limit = addr_limit // KERNEL_DS
addr_limit = USER_DS
alignment exception
__probe_kernel_read()
old_fs = get_fs() // USER_DS
set_fs(KERNEL_DS)
addr_limit = KERNEL_DS
dacr.kernel = DOMAIN_MANAGER
...
set_fs(old_fs)
addr_limit = USER_DS
dacr.kernel = DOMAIN_CLIENT
...
addr_limit = pt_regs->addr_limit // KERNEL_DS
interrupt returns

At this point, addr_limit is correctly restored to KERNEL_DS for
__probe_kernel_read() to continue execution, but dacr.kernel is not,
it has been reset by the set_fs(old_fs) to DOMAIN_CLIENT.

This would not have happened prior to the mentioned commit, because
addr_limit would remain KERNEL_DS, so get_fs() would have returned
KERNEL_DS, and so would correctly nest.

This commit fixes the problem by also saving the DACR on exception
entry if either CONFIG_CPU_SW_DOMAIN_PAN or CONFIG_CPU_USE_DOMAINS are
enabled, and resetting the DACR appropriately on exception entry to
match addr_limit and PAN settings.

Fixes: e6978e4bf181 ("ARM: save and reset the address limit when entering an exception")
Reported-by: Tomas Paukrt <tomas.paukrt@advantech.cz>
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>

+20 -5
+20 -5
arch/arm/include/asm/uaccess-asm.h
··· 67 67 #endif 68 68 .endm 69 69 70 - #ifdef CONFIG_CPU_SW_DOMAIN_PAN 70 + #if defined(CONFIG_CPU_SW_DOMAIN_PAN) || defined(CONFIG_CPU_USE_DOMAINS) 71 71 #define DACR(x...) x 72 72 #else 73 73 #define DACR(x...) 74 74 #endif 75 75 76 76 /* 77 - * Save the address limit on entry to a privileged exception and 78 - * if using PAN, save and disable usermode access. 77 + * Save the address limit on entry to a privileged exception. 78 + * 79 + * If we are using the DACR for kernel access by the user accessors 80 + * (CONFIG_CPU_USE_DOMAINS=y), always reset the DACR kernel domain 81 + * back to client mode, whether or not \disable is set. 82 + * 83 + * If we are using SW PAN, set the DACR user domain to no access 84 + * if \disable is set. 79 85 */ 80 86 .macro uaccess_entry, tsk, tmp0, tmp1, tmp2, disable 81 87 ldr \tmp1, [\tsk, #TI_ADDR_LIMIT] ··· 90 84 DACR( mrc p15, 0, \tmp0, c3, c0, 0) 91 85 DACR( str \tmp0, [sp, #SVC_DACR]) 92 86 str \tmp1, [sp, #SVC_ADDR_LIMIT] 93 - .if \disable 94 - uaccess_disable \tmp0 87 + .if \disable && IS_ENABLED(CONFIG_CPU_SW_DOMAIN_PAN) 88 + /* kernel=client, user=no access */ 89 + mov \tmp2, #DACR_UACCESS_DISABLE 90 + mcr p15, 0, \tmp2, c3, c0, 0 91 + instr_sync 92 + .elseif IS_ENABLED(CONFIG_CPU_USE_DOMAINS) 93 + /* kernel=client */ 94 + bic \tmp2, \tmp0, #domain_mask(DOMAIN_KERNEL) 95 + orr \tmp2, \tmp2, #domain_val(DOMAIN_KERNEL, DOMAIN_CLIENT) 96 + mcr p15, 0, \tmp2, c3, c0, 0 97 + instr_sync 95 98 .endif 96 99 .endm 97 100