Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 890274c2 authored by Michael Ellerman's avatar Michael Ellerman
Browse files

powerpc/64s: Implement KUAP for Radix MMU



Kernel Userspace Access Prevention utilises a feature of the Radix MMU
which disallows read and write access to userspace addresses. By
utilising this, the kernel is prevented from accessing user data from
outside of trusted paths that perform proper safety checks, such as
copy_{to/from}_user() and friends.

Userspace access is disabled from early boot and is only enabled when
performing an operation like copy_{to/from}_user(). The register that
controls this (AMR) does not prevent userspace from accessing itself,
so there is no need to save and restore when entering and exiting
userspace.

When entering the kernel from the kernel we save AMR and if it is not
blocking user access (because eg. we faulted doing a user access) we
reblock user access for the duration of the exception (ie. the page
fault) and then restore the AMR when returning back to the kernel.

This feature can be tested by using the lkdtm driver (CONFIG_LKDTM=y)
and performing the following:

  # (echo ACCESS_USERSPACE) > [debugfs]/provoke-crash/DIRECT

If enabled, this should send SIGSEGV to the thread.

We also add paranoid checking of AMR in switch and syscall return
under CONFIG_PPC_KUAP_DEBUG.

Co-authored-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
Signed-off-by: default avatarRussell Currey <ruscur@russell.cc>
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
parent ef296729
Loading
Loading
Loading
Loading
+102 −0
Original line number Original line Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_POWERPC_BOOK3S_64_KUP_RADIX_H
#define _ASM_POWERPC_BOOK3S_64_KUP_RADIX_H

#include <linux/const.h>

#define AMR_KUAP_BLOCK_READ	UL(0x4000000000000000)
#define AMR_KUAP_BLOCK_WRITE	UL(0x8000000000000000)
#define AMR_KUAP_BLOCKED	(AMR_KUAP_BLOCK_READ | AMR_KUAP_BLOCK_WRITE)
#define AMR_KUAP_SHIFT		62

#ifdef __ASSEMBLY__

.macro kuap_restore_amr	gpr
#ifdef CONFIG_PPC_KUAP
	BEGIN_MMU_FTR_SECTION_NESTED(67)
	ld	\gpr, STACK_REGS_KUAP(r1)
	mtspr	SPRN_AMR, \gpr
	END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_RADIX_KUAP, 67)
#endif
.endm

.macro kuap_check_amr gpr1, gpr2
#ifdef CONFIG_PPC_KUAP_DEBUG
	BEGIN_MMU_FTR_SECTION_NESTED(67)
	mfspr	\gpr1, SPRN_AMR
	li	\gpr2, (AMR_KUAP_BLOCKED >> AMR_KUAP_SHIFT)
	sldi	\gpr2, \gpr2, AMR_KUAP_SHIFT
999:	tdne	\gpr1, \gpr2
	EMIT_BUG_ENTRY 999b, __FILE__, __LINE__, (BUGFLAG_WARNING | BUGFLAG_ONCE)
	END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_RADIX_KUAP, 67)
#endif
.endm

.macro kuap_save_amr_and_lock gpr1, gpr2, use_cr, msr_pr_cr
#ifdef CONFIG_PPC_KUAP
	BEGIN_MMU_FTR_SECTION_NESTED(67)
	.ifnb \msr_pr_cr
	bne	\msr_pr_cr, 99f
	.endif
	mfspr	\gpr1, SPRN_AMR
	std	\gpr1, STACK_REGS_KUAP(r1)
	li	\gpr2, (AMR_KUAP_BLOCKED >> AMR_KUAP_SHIFT)
	sldi	\gpr2, \gpr2, AMR_KUAP_SHIFT
	cmpd	\use_cr, \gpr1, \gpr2
	beq	\use_cr, 99f
	// We don't isync here because we very recently entered via rfid
	mtspr	SPRN_AMR, \gpr2
	isync
99:
	END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_RADIX_KUAP, 67)
#endif
.endm

#else /* !__ASSEMBLY__ */

#ifdef CONFIG_PPC_KUAP

#include <asm/reg.h>

/*
 * We support individually allowing read or write, but we don't support nesting
 * because that would require an expensive read/modify write of the AMR.
 */

static inline void set_kuap(unsigned long value)
{
	if (!mmu_has_feature(MMU_FTR_RADIX_KUAP))
		return;

	/*
	 * ISA v3.0B says we need a CSI (Context Synchronising Instruction) both
	 * before and after the move to AMR. See table 6 on page 1134.
	 */
	isync();
	mtspr(SPRN_AMR, value);
	isync();
}

static inline void allow_user_access(void __user *to, const void __user *from,
				     unsigned long size)
{
	// This is written so we can resolve to a single case at build time
	if (__builtin_constant_p(to) && to == NULL)
		set_kuap(AMR_KUAP_BLOCK_WRITE);
	else if (__builtin_constant_p(from) && from == NULL)
		set_kuap(AMR_KUAP_BLOCK_READ);
	else
		set_kuap(0);
}

static inline void prevent_user_access(void __user *to, const void __user *from,
				       unsigned long size)
{
	set_kuap(AMR_KUAP_BLOCKED);
}

#endif /* CONFIG_PPC_KUAP */

#endif /* __ASSEMBLY__ */

#endif /* _ASM_POWERPC_BOOK3S_64_KUP_RADIX_H */
+2 −0
Original line number Original line Diff line number Diff line
@@ -497,6 +497,7 @@ END_FTR_SECTION_NESTED(ftr,ftr,943)
	RESTORE_CTR(r1, area);						   \
	RESTORE_CTR(r1, area);						   \
	b	bad_stack;						   \
	b	bad_stack;						   \
3:	EXCEPTION_PROLOG_COMMON_1();					   \
3:	EXCEPTION_PROLOG_COMMON_1();					   \
	kuap_save_amr_and_lock r9, r10, cr1, cr0;			   \
	beq	4f;			/* if from kernel mode		*/ \
	beq	4f;			/* if from kernel mode		*/ \
	ACCOUNT_CPU_USER_ENTRY(r13, r9, r10);				   \
	ACCOUNT_CPU_USER_ENTRY(r13, r9, r10);				   \
	SAVE_PPR(area, r9);						   \
	SAVE_PPR(area, r9);						   \
@@ -691,6 +692,7 @@ END_FTR_SECTION_IFSET(CPU_FTR_CTRL)
 */
 */
#define EXCEPTION_COMMON_NORET_STACK(area, trap, label, hdlr, additions) \
#define EXCEPTION_COMMON_NORET_STACK(area, trap, label, hdlr, additions) \
	EXCEPTION_PROLOG_COMMON_1();				\
	EXCEPTION_PROLOG_COMMON_1();				\
	kuap_save_amr_and_lock r9, r10, cr1;			\
	EXCEPTION_PROLOG_COMMON_2(area);			\
	EXCEPTION_PROLOG_COMMON_2(area);			\
	EXCEPTION_PROLOG_COMMON_3(trap);			\
	EXCEPTION_PROLOG_COMMON_3(trap);			\
	/* Volatile regs are potentially clobbered here */	\
	/* Volatile regs are potentially clobbered here */	\
+3 −0
Original line number Original line Diff line number Diff line
@@ -100,6 +100,9 @@ label##5: \
#define END_MMU_FTR_SECTION(msk, val)		\
#define END_MMU_FTR_SECTION(msk, val)		\
	END_MMU_FTR_SECTION_NESTED(msk, val, 97)
	END_MMU_FTR_SECTION_NESTED(msk, val, 97)


#define END_MMU_FTR_SECTION_NESTED_IFSET(msk, label)	\
	END_MMU_FTR_SECTION_NESTED((msk), (msk), label)

#define END_MMU_FTR_SECTION_IFSET(msk)	END_MMU_FTR_SECTION((msk), (msk))
#define END_MMU_FTR_SECTION_IFSET(msk)	END_MMU_FTR_SECTION((msk), (msk))
#define END_MMU_FTR_SECTION_IFCLR(msk)	END_MMU_FTR_SECTION((msk), 0)
#define END_MMU_FTR_SECTION_IFCLR(msk)	END_MMU_FTR_SECTION((msk), 0)


+4 −0
Original line number Original line Diff line number Diff line
@@ -2,6 +2,10 @@
#ifndef _ASM_POWERPC_KUP_H_
#ifndef _ASM_POWERPC_KUP_H_
#define _ASM_POWERPC_KUP_H_
#define _ASM_POWERPC_KUP_H_


#ifdef CONFIG_PPC64
#include <asm/book3s/64/kup-radix.h>
#endif

#ifndef __ASSEMBLY__
#ifndef __ASSEMBLY__


#include <asm/pgtable.h>
#include <asm/pgtable.h>
+9 −1
Original line number Original line Diff line number Diff line
@@ -107,6 +107,11 @@
 */
 */
#define MMU_FTR_1T_SEGMENT		ASM_CONST(0x40000000)
#define MMU_FTR_1T_SEGMENT		ASM_CONST(0x40000000)


/*
 * Supports KUAP (key 0 controlling userspace addresses) on radix
 */
#define MMU_FTR_RADIX_KUAP		ASM_CONST(0x80000000)

/* MMU feature bit sets for various CPUs */
/* MMU feature bit sets for various CPUs */
#define MMU_FTRS_DEFAULT_HPTE_ARCH_V2	\
#define MMU_FTRS_DEFAULT_HPTE_ARCH_V2	\
	MMU_FTR_HPTE_TABLE | MMU_FTR_PPCAS_ARCH_V2
	MMU_FTR_HPTE_TABLE | MMU_FTR_PPCAS_ARCH_V2
@@ -164,7 +169,10 @@ enum {
#endif
#endif
#ifdef CONFIG_PPC_RADIX_MMU
#ifdef CONFIG_PPC_RADIX_MMU
		MMU_FTR_TYPE_RADIX |
		MMU_FTR_TYPE_RADIX |
#endif
#ifdef CONFIG_PPC_KUAP
		MMU_FTR_RADIX_KUAP |
#endif /* CONFIG_PPC_KUAP */
#endif /* CONFIG_PPC_RADIX_MMU */
		0,
		0,
};
};


Loading