Skip to content
Snippets Groups Projects
Select Git revision
  • 444fc5cde64330661bf59944c43844e7d4c2ccd8
  • vme-testing default
  • ci-test
  • master
  • remoteproc
  • am625-sk-ov5640
  • pcal6534-upstreaming
  • lps22df-upstreaming
  • msc-upstreaming
  • imx8mp
  • iio/noa1305
  • vme-next
  • vme-next-4.14-rc4
  • v4.14-rc4
  • v4.14-rc3
  • v4.14-rc2
  • v4.14-rc1
  • v4.13
  • vme-next-4.13-rc7
  • v4.13-rc7
  • v4.13-rc6
  • v4.13-rc5
  • v4.13-rc4
  • v4.13-rc3
  • v4.13-rc2
  • v4.13-rc1
  • v4.12
  • v4.12-rc7
  • v4.12-rc6
  • v4.12-rc5
  • v4.12-rc4
  • v4.12-rc3
32 results

uaccess-asm.h

Blame
    • Russell King's avatar
      71f8af11
      ARM: uaccess: fix DACR mismatch with nested exceptions · 71f8af11
      Russell King authored
      
      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
      e6978e4b ("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: e6978e4b ("ARM: save and reset the address limit when entering an exception")
      Reported-by: default avatarTomas Paukrt <tomas.paukrt@advantech.cz>
      Signed-off-by: default avatarRussell King <rmk+kernel@armlinux.org.uk>
      71f8af11
      History
      ARM: uaccess: fix DACR mismatch with nested exceptions
      Russell King authored
      
      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
      e6978e4b ("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: e6978e4b ("ARM: save and reset the address limit when entering an exception")
      Reported-by: default avatarTomas Paukrt <tomas.paukrt@advantech.cz>
      Signed-off-by: default avatarRussell King <rmk+kernel@armlinux.org.uk>
    uaccess-asm.h 2.83 KiB
    /* SPDX-License-Identifier: GPL-2.0-only */
    
    #ifndef __ASM_UACCESS_ASM_H__
    #define __ASM_UACCESS_ASM_H__
    
    #include <asm/asm-offsets.h>
    #include <asm/domain.h>
    #include <asm/memory.h>
    #include <asm/thread_info.h>
    
    	.macro	csdb
    #ifdef CONFIG_THUMB2_KERNEL
    	.inst.w	0xf3af8014
    #else
    	.inst	0xe320f014
    #endif
    	.endm
    
    	.macro check_uaccess, addr:req, size:req, limit:req, tmp:req, bad:req
    #ifndef CONFIG_CPU_USE_DOMAINS
    	adds	\tmp, \addr, #\size - 1
    	sbcscc	\tmp, \tmp, \limit
    	bcs	\bad
    #ifdef CONFIG_CPU_SPECTRE
    	movcs	\addr, #0
    	csdb
    #endif
    #endif
    	.endm
    
    	.macro uaccess_mask_range_ptr, addr:req, size:req, limit:req, tmp:req
    #ifdef CONFIG_CPU_SPECTRE
    	sub	\tmp, \limit, #1
    	subs	\tmp, \tmp, \addr	@ tmp = limit - 1 - addr
    	addhs	\tmp, \tmp, #1		@ if (tmp >= 0) {
    	subshs	\tmp, \tmp, \size	@ tmp = limit - (addr + size) }
    	movlo	\addr, #0		@ if (tmp < 0) addr = NULL
    	csdb
    #endif
    	.endm
    
    	.macro	uaccess_disable, tmp, isb=1
    #ifdef CONFIG_CPU_SW_DOMAIN_PAN
    	/*
    	 * Whenever we re-enter userspace, the domains should always be
    	 * set appropriately.
    	 */
    	mov	\tmp, #DACR_UACCESS_DISABLE
    	mcr	p15, 0, \tmp, c3, c0, 0		@ Set domain register
    	.if	\isb
    	instr_sync
    	.endif
    #endif
    	.endm
    
    	.macro	uaccess_enable, tmp, isb=1
    #ifdef CONFIG_CPU_SW_DOMAIN_PAN
    	/*
    	 * Whenever we re-enter userspace, the domains should always be
    	 * set appropriately.
    	 */
    	mov	\tmp, #DACR_UACCESS_ENABLE
    	mcr	p15, 0, \tmp, c3, c0, 0
    	.if	\isb
    	instr_sync
    	.endif
    #endif
    	.endm
    
    #if defined(CONFIG_CPU_SW_DOMAIN_PAN) || defined(CONFIG_CPU_USE_DOMAINS)
    #define DACR(x...)	x
    #else
    #define DACR(x...)
    #endif
    
    	/*
    	 * Save the address limit on entry to a privileged exception.
    	 *
    	 * If we are using the DACR for kernel access by the user accessors
    	 * (CONFIG_CPU_USE_DOMAINS=y), always reset the DACR kernel domain
    	 * back to client mode, whether or not \disable is set.
    	 *
    	 * If we are using SW PAN, set the DACR user domain to no access
    	 * if \disable is set.
    	 */
    	.macro	uaccess_entry, tsk, tmp0, tmp1, tmp2, disable
    	ldr	\tmp1, [\tsk, #TI_ADDR_LIMIT]
    	mov	\tmp2, #TASK_SIZE
    	str	\tmp2, [\tsk, #TI_ADDR_LIMIT]
     DACR(	mrc	p15, 0, \tmp0, c3, c0, 0)
     DACR(	str	\tmp0, [sp, #SVC_DACR])
    	str	\tmp1, [sp, #SVC_ADDR_LIMIT]
    	.if \disable && IS_ENABLED(CONFIG_CPU_SW_DOMAIN_PAN)
    	/* kernel=client, user=no access */
    	mov	\tmp2, #DACR_UACCESS_DISABLE
    	mcr	p15, 0, \tmp2, c3, c0, 0
    	instr_sync
    	.elseif IS_ENABLED(CONFIG_CPU_USE_DOMAINS)
    	/* kernel=client */
    	bic	\tmp2, \tmp0, #domain_mask(DOMAIN_KERNEL)
    	orr	\tmp2, \tmp2, #domain_val(DOMAIN_KERNEL, DOMAIN_CLIENT)
    	mcr	p15, 0, \tmp2, c3, c0, 0
    	instr_sync
    	.endif
    	.endm
    
    	/* Restore the user access state previously saved by uaccess_entry */
    	.macro	uaccess_exit, tsk, tmp0, tmp1
    	ldr	\tmp1, [sp, #SVC_ADDR_LIMIT]
     DACR(	ldr	\tmp0, [sp, #SVC_DACR])
    	str	\tmp1, [\tsk, #TI_ADDR_LIMIT]
     DACR(	mcr	p15, 0, \tmp0, c3, c0, 0)
    	.endm
    
    #undef DACR
    
    #endif /* __ASM_UACCESS_ASM_H__ */