428 lines
10 KiB
C
428 lines
10 KiB
C
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||
|
#ifndef _ASM_POWERPC_BOOK3S_64_KUP_H
|
||
|
#define _ASM_POWERPC_BOOK3S_64_KUP_H
|
||
|
|
||
|
#include <linux/const.h>
|
||
|
#include <asm/reg.h>
|
||
|
|
||
|
#define AMR_KUAP_BLOCK_READ UL(0x5455555555555555)
|
||
|
#define AMR_KUAP_BLOCK_WRITE UL(0xa8aaaaaaaaaaaaaa)
|
||
|
#define AMR_KUEP_BLOCKED UL(0x5455555555555555)
|
||
|
#define AMR_KUAP_BLOCKED (AMR_KUAP_BLOCK_READ | AMR_KUAP_BLOCK_WRITE)
|
||
|
|
||
|
#ifdef __ASSEMBLY__
|
||
|
|
||
|
.macro kuap_user_restore gpr1, gpr2
|
||
|
#if defined(CONFIG_PPC_PKEY)
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(67)
|
||
|
b 100f // skip_restore_amr
|
||
|
END_MMU_FTR_SECTION_NESTED_IFCLR(MMU_FTR_PKEY, 67)
|
||
|
/*
|
||
|
* AMR and IAMR are going to be different when
|
||
|
* returning to userspace.
|
||
|
*/
|
||
|
ld \gpr1, STACK_REGS_AMR(r1)
|
||
|
|
||
|
/*
|
||
|
* If kuap feature is not enabled, do the mtspr
|
||
|
* only if AMR value is different.
|
||
|
*/
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(68)
|
||
|
mfspr \gpr2, SPRN_AMR
|
||
|
cmpd \gpr1, \gpr2
|
||
|
beq 99f
|
||
|
END_MMU_FTR_SECTION_NESTED_IFCLR(MMU_FTR_BOOK3S_KUAP, 68)
|
||
|
|
||
|
isync
|
||
|
mtspr SPRN_AMR, \gpr1
|
||
|
99:
|
||
|
/*
|
||
|
* Restore IAMR only when returning to userspace
|
||
|
*/
|
||
|
ld \gpr1, STACK_REGS_IAMR(r1)
|
||
|
|
||
|
/*
|
||
|
* If kuep feature is not enabled, do the mtspr
|
||
|
* only if IAMR value is different.
|
||
|
*/
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(69)
|
||
|
mfspr \gpr2, SPRN_IAMR
|
||
|
cmpd \gpr1, \gpr2
|
||
|
beq 100f
|
||
|
END_MMU_FTR_SECTION_NESTED_IFCLR(MMU_FTR_BOOK3S_KUEP, 69)
|
||
|
|
||
|
isync
|
||
|
mtspr SPRN_IAMR, \gpr1
|
||
|
|
||
|
100: //skip_restore_amr
|
||
|
/* No isync required, see kuap_user_restore() */
|
||
|
#endif
|
||
|
.endm
|
||
|
|
||
|
.macro kuap_kernel_restore gpr1, gpr2
|
||
|
#if defined(CONFIG_PPC_PKEY)
|
||
|
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(67)
|
||
|
/*
|
||
|
* AMR is going to be mostly the same since we are
|
||
|
* returning to the kernel. Compare and do a mtspr.
|
||
|
*/
|
||
|
ld \gpr2, STACK_REGS_AMR(r1)
|
||
|
mfspr \gpr1, SPRN_AMR
|
||
|
cmpd \gpr1, \gpr2
|
||
|
beq 100f
|
||
|
isync
|
||
|
mtspr SPRN_AMR, \gpr2
|
||
|
/*
|
||
|
* No isync required, see kuap_restore_amr()
|
||
|
* No need to restore IAMR when returning to kernel space.
|
||
|
*/
|
||
|
100:
|
||
|
END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_BOOK3S_KUAP, 67)
|
||
|
#endif
|
||
|
.endm
|
||
|
|
||
|
#ifdef CONFIG_PPC_KUAP
|
||
|
.macro kuap_check_amr gpr1, gpr2
|
||
|
#ifdef CONFIG_PPC_KUAP_DEBUG
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(67)
|
||
|
mfspr \gpr1, SPRN_AMR
|
||
|
/* Prevent access to userspace using any key values */
|
||
|
LOAD_REG_IMMEDIATE(\gpr2, AMR_KUAP_BLOCKED)
|
||
|
999: tdne \gpr1, \gpr2
|
||
|
EMIT_WARN_ENTRY 999b, __FILE__, __LINE__, (BUGFLAG_WARNING | BUGFLAG_ONCE)
|
||
|
END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_BOOK3S_KUAP, 67)
|
||
|
#endif
|
||
|
.endm
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* if (pkey) {
|
||
|
*
|
||
|
* save AMR -> stack;
|
||
|
* if (kuap) {
|
||
|
* if (AMR != BLOCKED)
|
||
|
* KUAP_BLOCKED -> AMR;
|
||
|
* }
|
||
|
* if (from_user) {
|
||
|
* save IAMR -> stack;
|
||
|
* if (kuep) {
|
||
|
* KUEP_BLOCKED ->IAMR
|
||
|
* }
|
||
|
* }
|
||
|
* return;
|
||
|
* }
|
||
|
*
|
||
|
* if (kuap) {
|
||
|
* if (from_kernel) {
|
||
|
* save AMR -> stack;
|
||
|
* if (AMR != BLOCKED)
|
||
|
* KUAP_BLOCKED -> AMR;
|
||
|
* }
|
||
|
*
|
||
|
* }
|
||
|
*/
|
||
|
.macro kuap_save_amr_and_lock gpr1, gpr2, use_cr, msr_pr_cr
|
||
|
#if defined(CONFIG_PPC_PKEY)
|
||
|
|
||
|
/*
|
||
|
* if both pkey and kuap is disabled, nothing to do
|
||
|
*/
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(68)
|
||
|
b 100f // skip_save_amr
|
||
|
END_MMU_FTR_SECTION_NESTED_IFCLR(MMU_FTR_PKEY | MMU_FTR_BOOK3S_KUAP, 68)
|
||
|
|
||
|
/*
|
||
|
* if pkey is disabled and we are entering from userspace
|
||
|
* don't do anything.
|
||
|
*/
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(67)
|
||
|
.ifnb \msr_pr_cr
|
||
|
/*
|
||
|
* Without pkey we are not changing AMR outside the kernel
|
||
|
* hence skip this completely.
|
||
|
*/
|
||
|
bne \msr_pr_cr, 100f // from userspace
|
||
|
.endif
|
||
|
END_MMU_FTR_SECTION_NESTED_IFCLR(MMU_FTR_PKEY, 67)
|
||
|
|
||
|
/*
|
||
|
* pkey is enabled or pkey is disabled but entering from kernel
|
||
|
*/
|
||
|
mfspr \gpr1, SPRN_AMR
|
||
|
std \gpr1, STACK_REGS_AMR(r1)
|
||
|
|
||
|
/*
|
||
|
* update kernel AMR with AMR_KUAP_BLOCKED only
|
||
|
* if KUAP feature is enabled
|
||
|
*/
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(69)
|
||
|
LOAD_REG_IMMEDIATE(\gpr2, AMR_KUAP_BLOCKED)
|
||
|
cmpd \use_cr, \gpr1, \gpr2
|
||
|
beq \use_cr, 102f
|
||
|
/*
|
||
|
* We don't isync here because we very recently entered via an interrupt
|
||
|
*/
|
||
|
mtspr SPRN_AMR, \gpr2
|
||
|
isync
|
||
|
102:
|
||
|
END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_BOOK3S_KUAP, 69)
|
||
|
|
||
|
/*
|
||
|
* if entering from kernel we don't need save IAMR
|
||
|
*/
|
||
|
.ifnb \msr_pr_cr
|
||
|
beq \msr_pr_cr, 100f // from kernel space
|
||
|
mfspr \gpr1, SPRN_IAMR
|
||
|
std \gpr1, STACK_REGS_IAMR(r1)
|
||
|
|
||
|
/*
|
||
|
* update kernel IAMR with AMR_KUEP_BLOCKED only
|
||
|
* if KUEP feature is enabled
|
||
|
*/
|
||
|
BEGIN_MMU_FTR_SECTION_NESTED(70)
|
||
|
LOAD_REG_IMMEDIATE(\gpr2, AMR_KUEP_BLOCKED)
|
||
|
mtspr SPRN_IAMR, \gpr2
|
||
|
isync
|
||
|
END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_BOOK3S_KUEP, 70)
|
||
|
.endif
|
||
|
|
||
|
100: // skip_save_amr
|
||
|
#endif
|
||
|
.endm
|
||
|
|
||
|
#else /* !__ASSEMBLY__ */
|
||
|
|
||
|
#include <linux/jump_label.h>
|
||
|
|
||
|
DECLARE_STATIC_KEY_FALSE(uaccess_flush_key);
|
||
|
|
||
|
#ifdef CONFIG_PPC_PKEY
|
||
|
|
||
|
extern u64 __ro_after_init default_uamor;
|
||
|
extern u64 __ro_after_init default_amr;
|
||
|
extern u64 __ro_after_init default_iamr;
|
||
|
|
||
|
#include <asm/mmu.h>
|
||
|
#include <asm/ptrace.h>
|
||
|
|
||
|
/* usage of kthread_use_mm() should inherit the
|
||
|
* AMR value of the operating address space. But, the AMR value is
|
||
|
* thread-specific and we inherit the address space and not thread
|
||
|
* access restrictions. Because of this ignore AMR value when accessing
|
||
|
* userspace via kernel thread.
|
||
|
*/
|
||
|
static inline u64 current_thread_amr(void)
|
||
|
{
|
||
|
if (current->thread.regs)
|
||
|
return current->thread.regs->amr;
|
||
|
return default_amr;
|
||
|
}
|
||
|
|
||
|
static inline u64 current_thread_iamr(void)
|
||
|
{
|
||
|
if (current->thread.regs)
|
||
|
return current->thread.regs->iamr;
|
||
|
return default_iamr;
|
||
|
}
|
||
|
#endif /* CONFIG_PPC_PKEY */
|
||
|
|
||
|
#ifdef CONFIG_PPC_KUAP
|
||
|
|
||
|
static __always_inline bool kuap_is_disabled(void)
|
||
|
{
|
||
|
return !mmu_has_feature(MMU_FTR_BOOK3S_KUAP);
|
||
|
}
|
||
|
|
||
|
static inline void kuap_user_restore(struct pt_regs *regs)
|
||
|
{
|
||
|
bool restore_amr = false, restore_iamr = false;
|
||
|
unsigned long amr, iamr;
|
||
|
|
||
|
if (!mmu_has_feature(MMU_FTR_PKEY))
|
||
|
return;
|
||
|
|
||
|
if (!mmu_has_feature(MMU_FTR_BOOK3S_KUAP)) {
|
||
|
amr = mfspr(SPRN_AMR);
|
||
|
if (amr != regs->amr)
|
||
|
restore_amr = true;
|
||
|
} else {
|
||
|
restore_amr = true;
|
||
|
}
|
||
|
|
||
|
if (!mmu_has_feature(MMU_FTR_BOOK3S_KUEP)) {
|
||
|
iamr = mfspr(SPRN_IAMR);
|
||
|
if (iamr != regs->iamr)
|
||
|
restore_iamr = true;
|
||
|
} else {
|
||
|
restore_iamr = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (restore_amr || restore_iamr) {
|
||
|
isync();
|
||
|
if (restore_amr)
|
||
|
mtspr(SPRN_AMR, regs->amr);
|
||
|
if (restore_iamr)
|
||
|
mtspr(SPRN_IAMR, regs->iamr);
|
||
|
}
|
||
|
/*
|
||
|
* No isync required here because we are about to rfi
|
||
|
* back to previous context before any user accesses
|
||
|
* would be made, which is a CSI.
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
static inline void __kuap_kernel_restore(struct pt_regs *regs, unsigned long amr)
|
||
|
{
|
||
|
if (likely(regs->amr == amr))
|
||
|
return;
|
||
|
|
||
|
isync();
|
||
|
mtspr(SPRN_AMR, regs->amr);
|
||
|
/*
|
||
|
* No isync required here because we are about to rfi
|
||
|
* back to previous context before any user accesses
|
||
|
* would be made, which is a CSI.
|
||
|
*
|
||
|
* No need to restore IAMR when returning to kernel space.
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
static inline unsigned long __kuap_get_and_assert_locked(void)
|
||
|
{
|
||
|
unsigned long amr = mfspr(SPRN_AMR);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_PPC_KUAP_DEBUG)) /* kuap_check_amr() */
|
||
|
WARN_ON_ONCE(amr != AMR_KUAP_BLOCKED);
|
||
|
return amr;
|
||
|
}
|
||
|
|
||
|
/* Do nothing, book3s/64 does that in ASM */
|
||
|
static inline void __kuap_lock(void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static inline void __kuap_save_and_lock(struct pt_regs *regs)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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 unsigned long get_kuap(void)
|
||
|
{
|
||
|
/*
|
||
|
* We return AMR_KUAP_BLOCKED when we don't support KUAP because
|
||
|
* prevent_user_access_return needs to return AMR_KUAP_BLOCKED to
|
||
|
* cause restore_user_access to do a flush.
|
||
|
*
|
||
|
* This has no effect in terms of actually blocking things on hash,
|
||
|
* so it doesn't break anything.
|
||
|
*/
|
||
|
if (!mmu_has_feature(MMU_FTR_BOOK3S_KUAP))
|
||
|
return AMR_KUAP_BLOCKED;
|
||
|
|
||
|
return mfspr(SPRN_AMR);
|
||
|
}
|
||
|
|
||
|
static __always_inline void set_kuap(unsigned long value)
|
||
|
{
|
||
|
if (!mmu_has_feature(MMU_FTR_BOOK3S_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 bool __bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write)
|
||
|
{
|
||
|
/*
|
||
|
* For radix this will be a storage protection fault (DSISR_PROTFAULT).
|
||
|
* For hash this will be a key fault (DSISR_KEYFAULT)
|
||
|
*/
|
||
|
/*
|
||
|
* We do have exception table entry, but accessing the
|
||
|
* userspace results in fault. This could be because we
|
||
|
* didn't unlock the AMR or access is denied by userspace
|
||
|
* using a key value that blocks access. We are only interested
|
||
|
* in catching the use case of accessing without unlocking
|
||
|
* the AMR. Hence check for BLOCK_WRITE/READ against AMR.
|
||
|
*/
|
||
|
if (is_write) {
|
||
|
return (regs->amr & AMR_KUAP_BLOCK_WRITE) == AMR_KUAP_BLOCK_WRITE;
|
||
|
}
|
||
|
return (regs->amr & AMR_KUAP_BLOCK_READ) == AMR_KUAP_BLOCK_READ;
|
||
|
}
|
||
|
|
||
|
static __always_inline void allow_user_access(void __user *to, const void __user *from,
|
||
|
unsigned long size, unsigned long dir)
|
||
|
{
|
||
|
unsigned long thread_amr = 0;
|
||
|
|
||
|
// This is written so we can resolve to a single case at build time
|
||
|
BUILD_BUG_ON(!__builtin_constant_p(dir));
|
||
|
|
||
|
if (mmu_has_feature(MMU_FTR_PKEY))
|
||
|
thread_amr = current_thread_amr();
|
||
|
|
||
|
if (dir == KUAP_READ)
|
||
|
set_kuap(thread_amr | AMR_KUAP_BLOCK_WRITE);
|
||
|
else if (dir == KUAP_WRITE)
|
||
|
set_kuap(thread_amr | AMR_KUAP_BLOCK_READ);
|
||
|
else if (dir == KUAP_READ_WRITE)
|
||
|
set_kuap(thread_amr);
|
||
|
else
|
||
|
BUILD_BUG();
|
||
|
}
|
||
|
|
||
|
#else /* CONFIG_PPC_KUAP */
|
||
|
|
||
|
static inline unsigned long get_kuap(void)
|
||
|
{
|
||
|
return AMR_KUAP_BLOCKED;
|
||
|
}
|
||
|
|
||
|
static inline void set_kuap(unsigned long value) { }
|
||
|
|
||
|
static __always_inline void allow_user_access(void __user *to, const void __user *from,
|
||
|
unsigned long size, unsigned long dir)
|
||
|
{ }
|
||
|
|
||
|
#endif /* !CONFIG_PPC_KUAP */
|
||
|
|
||
|
static __always_inline void prevent_user_access(unsigned long dir)
|
||
|
{
|
||
|
set_kuap(AMR_KUAP_BLOCKED);
|
||
|
if (static_branch_unlikely(&uaccess_flush_key))
|
||
|
do_uaccess_flush();
|
||
|
}
|
||
|
|
||
|
static inline unsigned long prevent_user_access_return(void)
|
||
|
{
|
||
|
unsigned long flags = get_kuap();
|
||
|
|
||
|
set_kuap(AMR_KUAP_BLOCKED);
|
||
|
if (static_branch_unlikely(&uaccess_flush_key))
|
||
|
do_uaccess_flush();
|
||
|
|
||
|
return flags;
|
||
|
}
|
||
|
|
||
|
static inline void restore_user_access(unsigned long flags)
|
||
|
{
|
||
|
set_kuap(flags);
|
||
|
if (static_branch_unlikely(&uaccess_flush_key) && flags == AMR_KUAP_BLOCKED)
|
||
|
do_uaccess_flush();
|
||
|
}
|
||
|
#endif /* __ASSEMBLY__ */
|
||
|
|
||
|
#endif /* _ASM_POWERPC_BOOK3S_64_KUP_H */
|