mirror of
https://github.com/torvalds/linux.git
synced 2024-11-14 08:02:07 +00:00
powerpc fixes for CVE-2020-4788
From Daniel's cover letter: IBM Power9 processors can speculatively operate on data in the L1 cache before it has been completely validated, via a way-prediction mechanism. It is not possible for an attacker to determine the contents of impermissible memory using this method, since these systems implement a combination of hardware and software security measures to prevent scenarios where protected data could be leaked. However these measures don't address the scenario where an attacker induces the operating system to speculatively execute instructions using data that the attacker controls. This can be used for example to speculatively bypass "kernel user access prevention" techniques, as discovered by Anthony Steinhauser of Google's Safeside Project. This is not an attack by itself, but there is a possibility it could be used in conjunction with side-channels or other weaknesses in the privileged code to construct an attack. This issue can be mitigated by flushing the L1 cache between privilege boundaries of concern. This patch series flushes the L1 cache on kernel entry (patch 2) and after the kernel performs any user accesses (patch 3). It also adds a self-test and performs some related cleanups. -----BEGIN PGP SIGNATURE----- iQJHBAABCAAxFiEEJFGtCPCthwEv2Y/bUevqPMjhpYAFAl+2aqETHG1wZUBlbGxl cm1hbi5pZC5hdQAKCRBR6+o8yOGlgG+hD/4njSFct2amqWfqDYR9b2OykWmnMQXn geookk5SbItQF7vh1q2SVA6r43s5ZAxgD5fezx4LgG6p3QU39+Tr0RhzUUHWMPDV UNGZK6x/N/GSYeq0bqvMHmVwS0FDjPE8nOtA8Hn2T9mUUsu9G0okpgYPLnEu6rb1 gIyS35zlLBh9obi3MfJzyln/AmCE7hdonKRtLAxvGiERJAyfAG757lrdjrwavyHy mwz+XPl5PF88jfO5cbcZT9gNHmZZPzVsOVwNcstCh2FcwuePv9dWe1pxsBxxKqP5 UXceXPcKM7VlRNmehimq7q/hfbget4RJGGKYPNXeKHOo6yfy7lJPiQV4h+5z2pSs SPP2fQQPq0aubmcO23CXFtZl4WRHQ4pax6opepnpIfC2vZ0HLXJtPrhMKcbFJNTo qPis6HWQPpIuI6l4MJfs+YO9ETxCR31Yd28qFAfPFoHlnQZTfx6NPhw8HKxTbSh2 Svr4X6Y14j3UsQgLTCArCXWAG/hlfRwxDZJ4AvR9EU0HJGDyZ45Y+LTD1N8bbsny zcYfPqWGPIanLcNPNFYIQwDZo7ff08KdmngUvf/Q9om60mP1hsPJMHf6VhPXj4fC 2TZ11fORssSlBSNtIkFkbjEG+aiWtWnz3fN3uSyT50rgGwtDHJzVzLiUWHlZKcxW X73YdxuT8fqQwg== =Yibq -----END PGP SIGNATURE----- Merge tag 'powerpc-cve-2020-4788' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux Pull powerpc fixes from Michael Ellerman: "Fixes for CVE-2020-4788. From Daniel's cover letter: IBM Power9 processors can speculatively operate on data in the L1 cache before it has been completely validated, via a way-prediction mechanism. It is not possible for an attacker to determine the contents of impermissible memory using this method, since these systems implement a combination of hardware and software security measures to prevent scenarios where protected data could be leaked. However these measures don't address the scenario where an attacker induces the operating system to speculatively execute instructions using data that the attacker controls. This can be used for example to speculatively bypass "kernel user access prevention" techniques, as discovered by Anthony Steinhauser of Google's Safeside Project. This is not an attack by itself, but there is a possibility it could be used in conjunction with side-channels or other weaknesses in the privileged code to construct an attack. This issue can be mitigated by flushing the L1 cache between privilege boundaries of concern. This patch series flushes the L1 cache on kernel entry (patch 2) and after the kernel performs any user accesses (patch 3). It also adds a self-test and performs some related cleanups" * tag 'powerpc-cve-2020-4788' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux: powerpc/64s: rename pnv|pseries_setup_rfi_flush to _setup_security_mitigations selftests/powerpc: refactor entry and rfi_flush tests selftests/powerpc: entry flush test powerpc: Only include kup-radix.h for 64-bit Book3S powerpc/64s: flush L1D after user accesses powerpc/64s: flush L1D on kernel entry selftests/powerpc: rfi_flush: disable entry flush if present
This commit is contained in:
commit
dda3f4252e
@ -2858,6 +2858,8 @@
|
||||
mds=off [X86]
|
||||
tsx_async_abort=off [X86]
|
||||
kvm.nx_huge_pages=off [X86]
|
||||
no_entry_flush [PPC]
|
||||
no_uaccess_flush [PPC]
|
||||
|
||||
Exceptions:
|
||||
This does not have any effect on
|
||||
@ -3186,6 +3188,8 @@
|
||||
|
||||
noefi Disable EFI runtime services support.
|
||||
|
||||
no_entry_flush [PPC] Don't flush the L1-D cache when entering the kernel.
|
||||
|
||||
noexec [IA-64]
|
||||
|
||||
noexec [X86]
|
||||
@ -3235,6 +3239,9 @@
|
||||
nospec_store_bypass_disable
|
||||
[HW] Disable all mitigations for the Speculative Store Bypass vulnerability
|
||||
|
||||
no_uaccess_flush
|
||||
[PPC] Don't flush the L1-D cache after accessing user data.
|
||||
|
||||
noxsave [BUGS=X86] Disables x86 extended register state save
|
||||
and restore using xsave. The kernel will fallback to
|
||||
enabling legacy floating-point and sse state.
|
||||
|
@ -27,6 +27,7 @@
|
||||
#endif
|
||||
.endm
|
||||
|
||||
#ifdef CONFIG_PPC_KUAP
|
||||
.macro kuap_check_amr gpr1, gpr2
|
||||
#ifdef CONFIG_PPC_KUAP_DEBUG
|
||||
BEGIN_MMU_FTR_SECTION_NESTED(67)
|
||||
@ -38,6 +39,7 @@
|
||||
END_MMU_FTR_SECTION_NESTED_IFSET(MMU_FTR_RADIX_KUAP, 67)
|
||||
#endif
|
||||
.endm
|
||||
#endif
|
||||
|
||||
.macro kuap_save_amr_and_lock gpr1, gpr2, use_cr, msr_pr_cr
|
||||
#ifdef CONFIG_PPC_KUAP
|
||||
@ -61,6 +63,8 @@
|
||||
|
||||
#else /* !__ASSEMBLY__ */
|
||||
|
||||
DECLARE_STATIC_KEY_FALSE(uaccess_flush_key);
|
||||
|
||||
#ifdef CONFIG_PPC_KUAP
|
||||
|
||||
#include <asm/mmu.h>
|
||||
@ -103,8 +107,16 @@ static inline void kuap_check_amr(void)
|
||||
|
||||
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 (!early_mmu_has_feature(MMU_FTR_RADIX_KUAP))
|
||||
return 0;
|
||||
return AMR_KUAP_BLOCKED;
|
||||
|
||||
return mfspr(SPRN_AMR);
|
||||
}
|
||||
@ -123,6 +135,29 @@ static inline void set_kuap(unsigned long value)
|
||||
isync();
|
||||
}
|
||||
|
||||
static inline bool
|
||||
bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write)
|
||||
{
|
||||
return WARN(mmu_has_feature(MMU_FTR_RADIX_KUAP) &&
|
||||
(regs->kuap & (is_write ? AMR_KUAP_BLOCK_WRITE : AMR_KUAP_BLOCK_READ)),
|
||||
"Bug: %s fault blocked by AMR!", is_write ? "Write" : "Read");
|
||||
}
|
||||
#else /* CONFIG_PPC_KUAP */
|
||||
static inline void kuap_restore_amr(struct pt_regs *regs, unsigned long amr) { }
|
||||
|
||||
static inline unsigned long kuap_get_and_check_amr(void)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
static inline unsigned long get_kuap(void)
|
||||
{
|
||||
return AMR_KUAP_BLOCKED;
|
||||
}
|
||||
|
||||
static inline void set_kuap(unsigned long value) { }
|
||||
#endif /* !CONFIG_PPC_KUAP */
|
||||
|
||||
static __always_inline void allow_user_access(void __user *to, const void __user *from,
|
||||
unsigned long size, unsigned long dir)
|
||||
{
|
||||
@ -142,6 +177,8 @@ static inline void prevent_user_access(void __user *to, const void __user *from,
|
||||
unsigned long size, 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)
|
||||
@ -149,6 +186,8 @@ 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;
|
||||
}
|
||||
@ -156,30 +195,9 @@ static inline unsigned long prevent_user_access_return(void)
|
||||
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();
|
||||
}
|
||||
|
||||
static inline bool
|
||||
bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write)
|
||||
{
|
||||
return WARN(mmu_has_feature(MMU_FTR_RADIX_KUAP) &&
|
||||
(regs->kuap & (is_write ? AMR_KUAP_BLOCK_WRITE : AMR_KUAP_BLOCK_READ)),
|
||||
"Bug: %s fault blocked by AMR!", is_write ? "Write" : "Read");
|
||||
}
|
||||
#else /* CONFIG_PPC_KUAP */
|
||||
static inline void kuap_restore_amr(struct pt_regs *regs, unsigned long amr)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void kuap_check_amr(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline unsigned long kuap_get_and_check_amr(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_PPC_KUAP */
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#endif /* _ASM_POWERPC_BOOK3S_64_KUP_RADIX_H */
|
||||
|
@ -57,11 +57,18 @@
|
||||
nop; \
|
||||
nop
|
||||
|
||||
#define ENTRY_FLUSH_SLOT \
|
||||
ENTRY_FLUSH_FIXUP_SECTION; \
|
||||
nop; \
|
||||
nop; \
|
||||
nop;
|
||||
|
||||
/*
|
||||
* r10 must be free to use, r13 must be paca
|
||||
*/
|
||||
#define INTERRUPT_TO_KERNEL \
|
||||
STF_ENTRY_BARRIER_SLOT
|
||||
STF_ENTRY_BARRIER_SLOT; \
|
||||
ENTRY_FLUSH_SLOT
|
||||
|
||||
/*
|
||||
* Macros for annotating the expected destination of (h)rfid
|
||||
@ -137,6 +144,9 @@
|
||||
RFSCV; \
|
||||
b rfscv_flush_fallback
|
||||
|
||||
#else /* __ASSEMBLY__ */
|
||||
/* Prototype for function defined in exceptions-64s.S */
|
||||
void do_uaccess_flush(void);
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#endif /* _ASM_POWERPC_EXCEPTION_H */
|
||||
|
@ -205,6 +205,22 @@ label##3: \
|
||||
FTR_ENTRY_OFFSET 955b-956b; \
|
||||
.popsection;
|
||||
|
||||
#define UACCESS_FLUSH_FIXUP_SECTION \
|
||||
959: \
|
||||
.pushsection __uaccess_flush_fixup,"a"; \
|
||||
.align 2; \
|
||||
960: \
|
||||
FTR_ENTRY_OFFSET 959b-960b; \
|
||||
.popsection;
|
||||
|
||||
#define ENTRY_FLUSH_FIXUP_SECTION \
|
||||
957: \
|
||||
.pushsection __entry_flush_fixup,"a"; \
|
||||
.align 2; \
|
||||
958: \
|
||||
FTR_ENTRY_OFFSET 957b-958b; \
|
||||
.popsection;
|
||||
|
||||
#define RFI_FLUSH_FIXUP_SECTION \
|
||||
951: \
|
||||
.pushsection __rfi_flush_fixup,"a"; \
|
||||
@ -237,8 +253,11 @@ label##3: \
|
||||
#include <linux/types.h>
|
||||
|
||||
extern long stf_barrier_fallback;
|
||||
extern long entry_flush_fallback;
|
||||
extern long __start___stf_entry_barrier_fixup, __stop___stf_entry_barrier_fixup;
|
||||
extern long __start___stf_exit_barrier_fixup, __stop___stf_exit_barrier_fixup;
|
||||
extern long __start___uaccess_flush_fixup, __stop___uaccess_flush_fixup;
|
||||
extern long __start___entry_flush_fixup, __stop___entry_flush_fixup;
|
||||
extern long __start___rfi_flush_fixup, __stop___rfi_flush_fixup;
|
||||
extern long __start___barrier_nospec_fixup, __stop___barrier_nospec_fixup;
|
||||
extern long __start__btb_flush_fixup, __stop__btb_flush_fixup;
|
||||
|
@ -14,7 +14,7 @@
|
||||
#define KUAP_CURRENT_WRITE 8
|
||||
#define KUAP_CURRENT (KUAP_CURRENT_READ | KUAP_CURRENT_WRITE)
|
||||
|
||||
#ifdef CONFIG_PPC64
|
||||
#ifdef CONFIG_PPC_BOOK3S_64
|
||||
#include <asm/book3s/64/kup-radix.h>
|
||||
#endif
|
||||
#ifdef CONFIG_PPC_8xx
|
||||
@ -35,6 +35,9 @@
|
||||
.macro kuap_check current, gpr
|
||||
.endm
|
||||
|
||||
.macro kuap_check_amr gpr1, gpr2
|
||||
.endm
|
||||
|
||||
#endif
|
||||
|
||||
#else /* !__ASSEMBLY__ */
|
||||
@ -53,17 +56,28 @@ static inline void setup_kuep(bool disabled) { }
|
||||
void setup_kuap(bool disabled);
|
||||
#else
|
||||
static inline void setup_kuap(bool disabled) { }
|
||||
|
||||
static inline bool
|
||||
bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void kuap_check_amr(void) { }
|
||||
|
||||
/*
|
||||
* book3s/64/kup-radix.h defines these functions for the !KUAP case to flush
|
||||
* the L1D cache after user accesses. Only include the empty stubs for other
|
||||
* platforms.
|
||||
*/
|
||||
#ifndef CONFIG_PPC_BOOK3S_64
|
||||
static inline void allow_user_access(void __user *to, const void __user *from,
|
||||
unsigned long size, unsigned long dir) { }
|
||||
static inline void prevent_user_access(void __user *to, const void __user *from,
|
||||
unsigned long size, unsigned long dir) { }
|
||||
static inline unsigned long prevent_user_access_return(void) { return 0UL; }
|
||||
static inline void restore_user_access(unsigned long flags) { }
|
||||
static inline bool
|
||||
bad_kuap_fault(struct pt_regs *regs, unsigned long address, bool is_write)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_PPC_BOOK3S_64 */
|
||||
#endif /* CONFIG_PPC_KUAP */
|
||||
|
||||
static inline void allow_read_from_user(const void __user *from, unsigned long size)
|
||||
|
@ -86,12 +86,19 @@ static inline bool security_ftr_enabled(u64 feature)
|
||||
// Software required to flush link stack on context switch
|
||||
#define SEC_FTR_FLUSH_LINK_STACK 0x0000000000001000ull
|
||||
|
||||
// The L1-D cache should be flushed when entering the kernel
|
||||
#define SEC_FTR_L1D_FLUSH_ENTRY 0x0000000000004000ull
|
||||
|
||||
// The L1-D cache should be flushed after user accesses from the kernel
|
||||
#define SEC_FTR_L1D_FLUSH_UACCESS 0x0000000000008000ull
|
||||
|
||||
// Features enabled by default
|
||||
#define SEC_FTR_DEFAULT \
|
||||
(SEC_FTR_L1D_FLUSH_HV | \
|
||||
SEC_FTR_L1D_FLUSH_PR | \
|
||||
SEC_FTR_BNDS_CHK_SPEC_BAR | \
|
||||
SEC_FTR_L1D_FLUSH_ENTRY | \
|
||||
SEC_FTR_L1D_FLUSH_UACCESS | \
|
||||
SEC_FTR_FAVOUR_SECURITY)
|
||||
|
||||
#endif /* _ASM_POWERPC_SECURITY_FEATURES_H */
|
||||
|
@ -52,12 +52,16 @@ enum l1d_flush_type {
|
||||
};
|
||||
|
||||
void setup_rfi_flush(enum l1d_flush_type, bool enable);
|
||||
void setup_entry_flush(bool enable);
|
||||
void setup_uaccess_flush(bool enable);
|
||||
void do_rfi_flush_fixups(enum l1d_flush_type types);
|
||||
#ifdef CONFIG_PPC_BARRIER_NOSPEC
|
||||
void setup_barrier_nospec(void);
|
||||
#else
|
||||
static inline void setup_barrier_nospec(void) { };
|
||||
#endif
|
||||
void do_uaccess_flush_fixups(enum l1d_flush_type types);
|
||||
void do_entry_flush_fixups(enum l1d_flush_type types);
|
||||
void do_barrier_nospec_fixups(bool enable);
|
||||
extern bool barrier_nospec_enabled;
|
||||
|
||||
|
@ -2951,15 +2951,8 @@ TRAMP_REAL_BEGIN(stf_barrier_fallback)
|
||||
.endr
|
||||
blr
|
||||
|
||||
TRAMP_REAL_BEGIN(rfi_flush_fallback)
|
||||
SET_SCRATCH0(r13);
|
||||
GET_PACA(r13);
|
||||
std r1,PACA_EXRFI+EX_R12(r13)
|
||||
ld r1,PACAKSAVE(r13)
|
||||
std r9,PACA_EXRFI+EX_R9(r13)
|
||||
std r10,PACA_EXRFI+EX_R10(r13)
|
||||
std r11,PACA_EXRFI+EX_R11(r13)
|
||||
mfctr r9
|
||||
/* Clobbers r10, r11, ctr */
|
||||
.macro L1D_DISPLACEMENT_FLUSH
|
||||
ld r10,PACA_RFI_FLUSH_FALLBACK_AREA(r13)
|
||||
ld r11,PACA_L1D_FLUSH_SIZE(r13)
|
||||
srdi r11,r11,(7 + 3) /* 128 byte lines, unrolled 8x */
|
||||
@ -2970,7 +2963,7 @@ TRAMP_REAL_BEGIN(rfi_flush_fallback)
|
||||
sync
|
||||
|
||||
/*
|
||||
* The load adresses are at staggered offsets within cachelines,
|
||||
* The load addresses are at staggered offsets within cachelines,
|
||||
* which suits some pipelines better (on others it should not
|
||||
* hurt).
|
||||
*/
|
||||
@ -2985,7 +2978,30 @@ TRAMP_REAL_BEGIN(rfi_flush_fallback)
|
||||
ld r11,(0x80 + 8)*7(r10)
|
||||
addi r10,r10,0x80*8
|
||||
bdnz 1b
|
||||
.endm
|
||||
|
||||
TRAMP_REAL_BEGIN(entry_flush_fallback)
|
||||
std r9,PACA_EXRFI+EX_R9(r13)
|
||||
std r10,PACA_EXRFI+EX_R10(r13)
|
||||
std r11,PACA_EXRFI+EX_R11(r13)
|
||||
mfctr r9
|
||||
L1D_DISPLACEMENT_FLUSH
|
||||
mtctr r9
|
||||
ld r9,PACA_EXRFI+EX_R9(r13)
|
||||
ld r10,PACA_EXRFI+EX_R10(r13)
|
||||
ld r11,PACA_EXRFI+EX_R11(r13)
|
||||
blr
|
||||
|
||||
TRAMP_REAL_BEGIN(rfi_flush_fallback)
|
||||
SET_SCRATCH0(r13);
|
||||
GET_PACA(r13);
|
||||
std r1,PACA_EXRFI+EX_R12(r13)
|
||||
ld r1,PACAKSAVE(r13)
|
||||
std r9,PACA_EXRFI+EX_R9(r13)
|
||||
std r10,PACA_EXRFI+EX_R10(r13)
|
||||
std r11,PACA_EXRFI+EX_R11(r13)
|
||||
mfctr r9
|
||||
L1D_DISPLACEMENT_FLUSH
|
||||
mtctr r9
|
||||
ld r9,PACA_EXRFI+EX_R9(r13)
|
||||
ld r10,PACA_EXRFI+EX_R10(r13)
|
||||
@ -3003,32 +3019,7 @@ TRAMP_REAL_BEGIN(hrfi_flush_fallback)
|
||||
std r10,PACA_EXRFI+EX_R10(r13)
|
||||
std r11,PACA_EXRFI+EX_R11(r13)
|
||||
mfctr r9
|
||||
ld r10,PACA_RFI_FLUSH_FALLBACK_AREA(r13)
|
||||
ld r11,PACA_L1D_FLUSH_SIZE(r13)
|
||||
srdi r11,r11,(7 + 3) /* 128 byte lines, unrolled 8x */
|
||||
mtctr r11
|
||||
DCBT_BOOK3S_STOP_ALL_STREAM_IDS(r11) /* Stop prefetch streams */
|
||||
|
||||
/* order ld/st prior to dcbt stop all streams with flushing */
|
||||
sync
|
||||
|
||||
/*
|
||||
* The load adresses are at staggered offsets within cachelines,
|
||||
* which suits some pipelines better (on others it should not
|
||||
* hurt).
|
||||
*/
|
||||
1:
|
||||
ld r11,(0x80 + 8)*0(r10)
|
||||
ld r11,(0x80 + 8)*1(r10)
|
||||
ld r11,(0x80 + 8)*2(r10)
|
||||
ld r11,(0x80 + 8)*3(r10)
|
||||
ld r11,(0x80 + 8)*4(r10)
|
||||
ld r11,(0x80 + 8)*5(r10)
|
||||
ld r11,(0x80 + 8)*6(r10)
|
||||
ld r11,(0x80 + 8)*7(r10)
|
||||
addi r10,r10,0x80*8
|
||||
bdnz 1b
|
||||
|
||||
L1D_DISPLACEMENT_FLUSH
|
||||
mtctr r9
|
||||
ld r9,PACA_EXRFI+EX_R9(r13)
|
||||
ld r10,PACA_EXRFI+EX_R10(r13)
|
||||
@ -3079,8 +3070,21 @@ TRAMP_REAL_BEGIN(rfscv_flush_fallback)
|
||||
RFSCV
|
||||
|
||||
USE_TEXT_SECTION()
|
||||
MASKED_INTERRUPT
|
||||
MASKED_INTERRUPT hsrr=1
|
||||
|
||||
_GLOBAL(do_uaccess_flush)
|
||||
UACCESS_FLUSH_FIXUP_SECTION
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
blr
|
||||
L1D_DISPLACEMENT_FLUSH
|
||||
blr
|
||||
_ASM_NOKPROBE_SYMBOL(do_uaccess_flush)
|
||||
EXPORT_SYMBOL(do_uaccess_flush)
|
||||
|
||||
|
||||
MASKED_INTERRUPT
|
||||
MASKED_INTERRUPT hsrr=1
|
||||
|
||||
#ifdef CONFIG_KVM_BOOK3S_64_HANDLER
|
||||
kvmppc_skip_interrupt:
|
||||
|
@ -945,7 +945,13 @@ early_initcall(disable_hardlockup_detector);
|
||||
static enum l1d_flush_type enabled_flush_types;
|
||||
static void *l1d_flush_fallback_area;
|
||||
static bool no_rfi_flush;
|
||||
static bool no_entry_flush;
|
||||
static bool no_uaccess_flush;
|
||||
bool rfi_flush;
|
||||
bool entry_flush;
|
||||
bool uaccess_flush;
|
||||
DEFINE_STATIC_KEY_FALSE(uaccess_flush_key);
|
||||
EXPORT_SYMBOL(uaccess_flush_key);
|
||||
|
||||
static int __init handle_no_rfi_flush(char *p)
|
||||
{
|
||||
@ -955,6 +961,22 @@ static int __init handle_no_rfi_flush(char *p)
|
||||
}
|
||||
early_param("no_rfi_flush", handle_no_rfi_flush);
|
||||
|
||||
static int __init handle_no_entry_flush(char *p)
|
||||
{
|
||||
pr_info("entry-flush: disabled on command line.");
|
||||
no_entry_flush = true;
|
||||
return 0;
|
||||
}
|
||||
early_param("no_entry_flush", handle_no_entry_flush);
|
||||
|
||||
static int __init handle_no_uaccess_flush(char *p)
|
||||
{
|
||||
pr_info("uaccess-flush: disabled on command line.");
|
||||
no_uaccess_flush = true;
|
||||
return 0;
|
||||
}
|
||||
early_param("no_uaccess_flush", handle_no_uaccess_flush);
|
||||
|
||||
/*
|
||||
* The RFI flush is not KPTI, but because users will see doco that says to use
|
||||
* nopti we hijack that option here to also disable the RFI flush.
|
||||
@ -986,6 +1008,32 @@ void rfi_flush_enable(bool enable)
|
||||
rfi_flush = enable;
|
||||
}
|
||||
|
||||
void entry_flush_enable(bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
do_entry_flush_fixups(enabled_flush_types);
|
||||
on_each_cpu(do_nothing, NULL, 1);
|
||||
} else {
|
||||
do_entry_flush_fixups(L1D_FLUSH_NONE);
|
||||
}
|
||||
|
||||
entry_flush = enable;
|
||||
}
|
||||
|
||||
void uaccess_flush_enable(bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
do_uaccess_flush_fixups(enabled_flush_types);
|
||||
static_branch_enable(&uaccess_flush_key);
|
||||
on_each_cpu(do_nothing, NULL, 1);
|
||||
} else {
|
||||
static_branch_disable(&uaccess_flush_key);
|
||||
do_uaccess_flush_fixups(L1D_FLUSH_NONE);
|
||||
}
|
||||
|
||||
uaccess_flush = enable;
|
||||
}
|
||||
|
||||
static void __ref init_fallback_flush(void)
|
||||
{
|
||||
u64 l1d_size, limit;
|
||||
@ -1044,10 +1092,28 @@ void setup_rfi_flush(enum l1d_flush_type types, bool enable)
|
||||
|
||||
enabled_flush_types = types;
|
||||
|
||||
if (!no_rfi_flush && !cpu_mitigations_off())
|
||||
if (!cpu_mitigations_off() && !no_rfi_flush)
|
||||
rfi_flush_enable(enable);
|
||||
}
|
||||
|
||||
void setup_entry_flush(bool enable)
|
||||
{
|
||||
if (cpu_mitigations_off())
|
||||
return;
|
||||
|
||||
if (!no_entry_flush)
|
||||
entry_flush_enable(enable);
|
||||
}
|
||||
|
||||
void setup_uaccess_flush(bool enable)
|
||||
{
|
||||
if (cpu_mitigations_off())
|
||||
return;
|
||||
|
||||
if (!no_uaccess_flush)
|
||||
uaccess_flush_enable(enable);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int rfi_flush_set(void *data, u64 val)
|
||||
{
|
||||
@ -1075,9 +1141,63 @@ static int rfi_flush_get(void *data, u64 *val)
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(fops_rfi_flush, rfi_flush_get, rfi_flush_set, "%llu\n");
|
||||
|
||||
static int entry_flush_set(void *data, u64 val)
|
||||
{
|
||||
bool enable;
|
||||
|
||||
if (val == 1)
|
||||
enable = true;
|
||||
else if (val == 0)
|
||||
enable = false;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/* Only do anything if we're changing state */
|
||||
if (enable != entry_flush)
|
||||
entry_flush_enable(enable);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int entry_flush_get(void *data, u64 *val)
|
||||
{
|
||||
*val = entry_flush ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(fops_entry_flush, entry_flush_get, entry_flush_set, "%llu\n");
|
||||
|
||||
static int uaccess_flush_set(void *data, u64 val)
|
||||
{
|
||||
bool enable;
|
||||
|
||||
if (val == 1)
|
||||
enable = true;
|
||||
else if (val == 0)
|
||||
enable = false;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/* Only do anything if we're changing state */
|
||||
if (enable != uaccess_flush)
|
||||
uaccess_flush_enable(enable);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uaccess_flush_get(void *data, u64 *val)
|
||||
{
|
||||
*val = uaccess_flush ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(fops_uaccess_flush, uaccess_flush_get, uaccess_flush_set, "%llu\n");
|
||||
|
||||
static __init int rfi_flush_debugfs_init(void)
|
||||
{
|
||||
debugfs_create_file("rfi_flush", 0600, powerpc_debugfs_root, NULL, &fops_rfi_flush);
|
||||
debugfs_create_file("entry_flush", 0600, powerpc_debugfs_root, NULL, &fops_entry_flush);
|
||||
debugfs_create_file("uaccess_flush", 0600, powerpc_debugfs_root, NULL, &fops_uaccess_flush);
|
||||
return 0;
|
||||
}
|
||||
device_initcall(rfi_flush_debugfs_init);
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <asm/asm-prototypes.h>
|
||||
#include <asm/book3s/64/kup-radix.h>
|
||||
#include <asm/kup.h>
|
||||
#include <asm/cputime.h>
|
||||
#include <asm/hw_irq.h>
|
||||
#include <asm/kprobes.h>
|
||||
|
@ -131,6 +131,20 @@ SECTIONS
|
||||
__stop___stf_entry_barrier_fixup = .;
|
||||
}
|
||||
|
||||
. = ALIGN(8);
|
||||
__uaccess_flush_fixup : AT(ADDR(__uaccess_flush_fixup) - LOAD_OFFSET) {
|
||||
__start___uaccess_flush_fixup = .;
|
||||
*(__uaccess_flush_fixup)
|
||||
__stop___uaccess_flush_fixup = .;
|
||||
}
|
||||
|
||||
. = ALIGN(8);
|
||||
__entry_flush_fixup : AT(ADDR(__entry_flush_fixup) - LOAD_OFFSET) {
|
||||
__start___entry_flush_fixup = .;
|
||||
*(__entry_flush_fixup)
|
||||
__stop___entry_flush_fixup = .;
|
||||
}
|
||||
|
||||
. = ALIGN(8);
|
||||
__stf_exit_barrier_fixup : AT(ADDR(__stf_exit_barrier_fixup) - LOAD_OFFSET) {
|
||||
__start___stf_exit_barrier_fixup = .;
|
||||
|
@ -234,6 +234,110 @@ void do_stf_barrier_fixups(enum stf_barrier_type types)
|
||||
do_stf_exit_barrier_fixups(types);
|
||||
}
|
||||
|
||||
void do_uaccess_flush_fixups(enum l1d_flush_type types)
|
||||
{
|
||||
unsigned int instrs[4], *dest;
|
||||
long *start, *end;
|
||||
int i;
|
||||
|
||||
start = PTRRELOC(&__start___uaccess_flush_fixup);
|
||||
end = PTRRELOC(&__stop___uaccess_flush_fixup);
|
||||
|
||||
instrs[0] = 0x60000000; /* nop */
|
||||
instrs[1] = 0x60000000; /* nop */
|
||||
instrs[2] = 0x60000000; /* nop */
|
||||
instrs[3] = 0x4e800020; /* blr */
|
||||
|
||||
i = 0;
|
||||
if (types == L1D_FLUSH_FALLBACK) {
|
||||
instrs[3] = 0x60000000; /* nop */
|
||||
/* fallthrough to fallback flush */
|
||||
}
|
||||
|
||||
if (types & L1D_FLUSH_ORI) {
|
||||
instrs[i++] = 0x63ff0000; /* ori 31,31,0 speculation barrier */
|
||||
instrs[i++] = 0x63de0000; /* ori 30,30,0 L1d flush*/
|
||||
}
|
||||
|
||||
if (types & L1D_FLUSH_MTTRIG)
|
||||
instrs[i++] = 0x7c12dba6; /* mtspr TRIG2,r0 (SPR #882) */
|
||||
|
||||
for (i = 0; start < end; start++, i++) {
|
||||
dest = (void *)start + *start;
|
||||
|
||||
pr_devel("patching dest %lx\n", (unsigned long)dest);
|
||||
|
||||
patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
|
||||
|
||||
patch_instruction((struct ppc_inst *)(dest + 1), ppc_inst(instrs[1]));
|
||||
patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
|
||||
patch_instruction((struct ppc_inst *)(dest + 3), ppc_inst(instrs[3]));
|
||||
}
|
||||
|
||||
printk(KERN_DEBUG "uaccess-flush: patched %d locations (%s flush)\n", i,
|
||||
(types == L1D_FLUSH_NONE) ? "no" :
|
||||
(types == L1D_FLUSH_FALLBACK) ? "fallback displacement" :
|
||||
(types & L1D_FLUSH_ORI) ? (types & L1D_FLUSH_MTTRIG)
|
||||
? "ori+mttrig type"
|
||||
: "ori type" :
|
||||
(types & L1D_FLUSH_MTTRIG) ? "mttrig type"
|
||||
: "unknown");
|
||||
}
|
||||
|
||||
void do_entry_flush_fixups(enum l1d_flush_type types)
|
||||
{
|
||||
unsigned int instrs[3], *dest;
|
||||
long *start, *end;
|
||||
int i;
|
||||
|
||||
start = PTRRELOC(&__start___entry_flush_fixup);
|
||||
end = PTRRELOC(&__stop___entry_flush_fixup);
|
||||
|
||||
instrs[0] = 0x60000000; /* nop */
|
||||
instrs[1] = 0x60000000; /* nop */
|
||||
instrs[2] = 0x60000000; /* nop */
|
||||
|
||||
i = 0;
|
||||
if (types == L1D_FLUSH_FALLBACK) {
|
||||
instrs[i++] = 0x7d4802a6; /* mflr r10 */
|
||||
instrs[i++] = 0x60000000; /* branch patched below */
|
||||
instrs[i++] = 0x7d4803a6; /* mtlr r10 */
|
||||
}
|
||||
|
||||
if (types & L1D_FLUSH_ORI) {
|
||||
instrs[i++] = 0x63ff0000; /* ori 31,31,0 speculation barrier */
|
||||
instrs[i++] = 0x63de0000; /* ori 30,30,0 L1d flush*/
|
||||
}
|
||||
|
||||
if (types & L1D_FLUSH_MTTRIG)
|
||||
instrs[i++] = 0x7c12dba6; /* mtspr TRIG2,r0 (SPR #882) */
|
||||
|
||||
for (i = 0; start < end; start++, i++) {
|
||||
dest = (void *)start + *start;
|
||||
|
||||
pr_devel("patching dest %lx\n", (unsigned long)dest);
|
||||
|
||||
patch_instruction((struct ppc_inst *)dest, ppc_inst(instrs[0]));
|
||||
|
||||
if (types == L1D_FLUSH_FALLBACK)
|
||||
patch_branch((struct ppc_inst *)(dest + 1), (unsigned long)&entry_flush_fallback,
|
||||
BRANCH_SET_LINK);
|
||||
else
|
||||
patch_instruction((struct ppc_inst *)(dest + 1), ppc_inst(instrs[1]));
|
||||
|
||||
patch_instruction((struct ppc_inst *)(dest + 2), ppc_inst(instrs[2]));
|
||||
}
|
||||
|
||||
printk(KERN_DEBUG "entry-flush: patched %d locations (%s flush)\n", i,
|
||||
(types == L1D_FLUSH_NONE) ? "no" :
|
||||
(types == L1D_FLUSH_FALLBACK) ? "fallback displacement" :
|
||||
(types & L1D_FLUSH_ORI) ? (types & L1D_FLUSH_MTTRIG)
|
||||
? "ori+mttrig type"
|
||||
: "ori type" :
|
||||
(types & L1D_FLUSH_MTTRIG) ? "mttrig type"
|
||||
: "unknown");
|
||||
}
|
||||
|
||||
void do_rfi_flush_fixups(enum l1d_flush_type types)
|
||||
{
|
||||
unsigned int instrs[3], *dest;
|
||||
|
@ -98,7 +98,7 @@ static void init_fw_feat_flags(struct device_node *np)
|
||||
security_ftr_clear(SEC_FTR_BNDS_CHK_SPEC_BAR);
|
||||
}
|
||||
|
||||
static void pnv_setup_rfi_flush(void)
|
||||
static void pnv_setup_security_mitigations(void)
|
||||
{
|
||||
struct device_node *np, *fw_features;
|
||||
enum l1d_flush_type type;
|
||||
@ -122,12 +122,31 @@ static void pnv_setup_rfi_flush(void)
|
||||
type = L1D_FLUSH_ORI;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are non-Power9 bare metal, we don't need to flush on kernel
|
||||
* entry or after user access: they fix a P9 specific vulnerability.
|
||||
*/
|
||||
if (!pvr_version_is(PVR_POWER9)) {
|
||||
security_ftr_clear(SEC_FTR_L1D_FLUSH_ENTRY);
|
||||
security_ftr_clear(SEC_FTR_L1D_FLUSH_UACCESS);
|
||||
}
|
||||
|
||||
enable = security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) && \
|
||||
(security_ftr_enabled(SEC_FTR_L1D_FLUSH_PR) || \
|
||||
security_ftr_enabled(SEC_FTR_L1D_FLUSH_HV));
|
||||
|
||||
setup_rfi_flush(type, enable);
|
||||
setup_count_cache_flush();
|
||||
|
||||
enable = security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) &&
|
||||
security_ftr_enabled(SEC_FTR_L1D_FLUSH_ENTRY);
|
||||
setup_entry_flush(enable);
|
||||
|
||||
enable = security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) &&
|
||||
security_ftr_enabled(SEC_FTR_L1D_FLUSH_UACCESS);
|
||||
setup_uaccess_flush(enable);
|
||||
|
||||
setup_stf_barrier();
|
||||
}
|
||||
|
||||
static void __init pnv_check_guarded_cores(void)
|
||||
@ -156,8 +175,7 @@ static void __init pnv_setup_arch(void)
|
||||
{
|
||||
set_arch_panic_timeout(10, ARCH_PANIC_TIMEOUT);
|
||||
|
||||
pnv_setup_rfi_flush();
|
||||
setup_stf_barrier();
|
||||
pnv_setup_security_mitigations();
|
||||
|
||||
/* Initialize SMP */
|
||||
pnv_smp_init();
|
||||
|
@ -349,8 +349,8 @@ void post_mobility_fixup(void)
|
||||
|
||||
cpus_read_unlock();
|
||||
|
||||
/* Possibly switch to a new RFI flush type */
|
||||
pseries_setup_rfi_flush();
|
||||
/* Possibly switch to a new L1 flush type */
|
||||
pseries_setup_security_mitigations();
|
||||
|
||||
/* Reinitialise system information for hv-24x7 */
|
||||
read_24x7_sys_info();
|
||||
|
@ -111,7 +111,7 @@ static inline unsigned long cmo_get_page_size(void)
|
||||
|
||||
int dlpar_workqueue_init(void);
|
||||
|
||||
void pseries_setup_rfi_flush(void);
|
||||
void pseries_setup_security_mitigations(void);
|
||||
void pseries_lpar_read_hblkrm_characteristics(void);
|
||||
|
||||
#endif /* _PSERIES_PSERIES_H */
|
||||
|
@ -542,7 +542,7 @@ static void init_cpu_char_feature_flags(struct h_cpu_char_result *result)
|
||||
security_ftr_clear(SEC_FTR_BNDS_CHK_SPEC_BAR);
|
||||
}
|
||||
|
||||
void pseries_setup_rfi_flush(void)
|
||||
void pseries_setup_security_mitigations(void)
|
||||
{
|
||||
struct h_cpu_char_result result;
|
||||
enum l1d_flush_type types;
|
||||
@ -579,6 +579,16 @@ void pseries_setup_rfi_flush(void)
|
||||
|
||||
setup_rfi_flush(types, enable);
|
||||
setup_count_cache_flush();
|
||||
|
||||
enable = security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) &&
|
||||
security_ftr_enabled(SEC_FTR_L1D_FLUSH_ENTRY);
|
||||
setup_entry_flush(enable);
|
||||
|
||||
enable = security_ftr_enabled(SEC_FTR_FAVOUR_SECURITY) &&
|
||||
security_ftr_enabled(SEC_FTR_L1D_FLUSH_UACCESS);
|
||||
setup_uaccess_flush(enable);
|
||||
|
||||
setup_stf_barrier();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_IOV
|
||||
@ -768,8 +778,7 @@ static void __init pSeries_setup_arch(void)
|
||||
|
||||
fwnmi_init();
|
||||
|
||||
pseries_setup_rfi_flush();
|
||||
setup_stf_barrier();
|
||||
pseries_setup_security_mitigations();
|
||||
pseries_lpar_read_hblkrm_characteristics();
|
||||
|
||||
/* By default, only probe PCI (can be overridden by rtas_pci) */
|
||||
|
@ -42,6 +42,11 @@ int perf_event_enable(int fd);
|
||||
int perf_event_disable(int fd);
|
||||
int perf_event_reset(int fd);
|
||||
|
||||
struct perf_event_read {
|
||||
__u64 nr;
|
||||
__u64 l1d_misses;
|
||||
};
|
||||
|
||||
#if !defined(__GLIBC_PREREQ) || !__GLIBC_PREREQ(2, 30)
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
@ -1,2 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
rfi_flush
|
||||
entry_flush
|
||||
|
@ -1,6 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
TEST_GEN_PROGS := rfi_flush spectre_v2
|
||||
TEST_GEN_PROGS := rfi_flush entry_flush spectre_v2
|
||||
top_srcdir = ../../../../..
|
||||
|
||||
CFLAGS += -I../../../../../usr/include
|
||||
@ -11,3 +11,5 @@ $(TEST_GEN_PROGS): ../harness.c ../utils.c
|
||||
|
||||
$(OUTPUT)/spectre_v2: CFLAGS += -m64
|
||||
$(OUTPUT)/spectre_v2: ../pmu/event.c branch_loops.S
|
||||
$(OUTPUT)/rfi_flush: flush_utils.c
|
||||
$(OUTPUT)/entry_flush: flush_utils.c
|
||||
|
139
tools/testing/selftests/powerpc/security/entry_flush.c
Normal file
139
tools/testing/selftests/powerpc/security/entry_flush.c
Normal file
@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
/*
|
||||
* Copyright 2018 IBM Corporation.
|
||||
*/
|
||||
|
||||
#define __SANE_USERSPACE_TYPES__
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "flush_utils.h"
|
||||
|
||||
int entry_flush_test(void)
|
||||
{
|
||||
char *p;
|
||||
int repetitions = 10;
|
||||
int fd, passes = 0, iter, rc = 0;
|
||||
struct perf_event_read v;
|
||||
__u64 l1d_misses_total = 0;
|
||||
unsigned long iterations = 100000, zero_size = 24 * 1024;
|
||||
unsigned long l1d_misses_expected;
|
||||
int rfi_flush_orig;
|
||||
int entry_flush, entry_flush_orig;
|
||||
|
||||
SKIP_IF(geteuid() != 0);
|
||||
|
||||
// The PMU event we use only works on Power7 or later
|
||||
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
|
||||
|
||||
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_orig) < 0) {
|
||||
perror("Unable to read powerpc/rfi_flush debugfs file");
|
||||
SKIP_IF(1);
|
||||
}
|
||||
|
||||
if (read_debugfs_file("powerpc/entry_flush", &entry_flush_orig) < 0) {
|
||||
perror("Unable to read powerpc/entry_flush debugfs file");
|
||||
SKIP_IF(1);
|
||||
}
|
||||
|
||||
if (rfi_flush_orig != 0) {
|
||||
if (write_debugfs_file("powerpc/rfi_flush", 0) < 0) {
|
||||
perror("error writing to powerpc/rfi_flush debugfs file");
|
||||
FAIL_IF(1);
|
||||
}
|
||||
}
|
||||
|
||||
entry_flush = entry_flush_orig;
|
||||
|
||||
fd = perf_event_open_counter(PERF_TYPE_RAW, /* L1d miss */ 0x400f0, -1);
|
||||
FAIL_IF(fd < 0);
|
||||
|
||||
p = (char *)memalign(zero_size, CACHELINE_SIZE);
|
||||
|
||||
FAIL_IF(perf_event_enable(fd));
|
||||
|
||||
// disable L1 prefetching
|
||||
set_dscr(1);
|
||||
|
||||
iter = repetitions;
|
||||
|
||||
/*
|
||||
* We expect to see l1d miss for each cacheline access when entry_flush
|
||||
* is set. Allow a small variation on this.
|
||||
*/
|
||||
l1d_misses_expected = iterations * (zero_size / CACHELINE_SIZE - 2);
|
||||
|
||||
again:
|
||||
FAIL_IF(perf_event_reset(fd));
|
||||
|
||||
syscall_loop(p, iterations, zero_size);
|
||||
|
||||
FAIL_IF(read(fd, &v, sizeof(v)) != sizeof(v));
|
||||
|
||||
if (entry_flush && v.l1d_misses >= l1d_misses_expected)
|
||||
passes++;
|
||||
else if (!entry_flush && v.l1d_misses < (l1d_misses_expected / 2))
|
||||
passes++;
|
||||
|
||||
l1d_misses_total += v.l1d_misses;
|
||||
|
||||
while (--iter)
|
||||
goto again;
|
||||
|
||||
if (passes < repetitions) {
|
||||
printf("FAIL (L1D misses with entry_flush=%d: %llu %c %lu) [%d/%d failures]\n",
|
||||
entry_flush, l1d_misses_total, entry_flush ? '<' : '>',
|
||||
entry_flush ? repetitions * l1d_misses_expected :
|
||||
repetitions * l1d_misses_expected / 2,
|
||||
repetitions - passes, repetitions);
|
||||
rc = 1;
|
||||
} else {
|
||||
printf("PASS (L1D misses with entry_flush=%d: %llu %c %lu) [%d/%d pass]\n",
|
||||
entry_flush, l1d_misses_total, entry_flush ? '>' : '<',
|
||||
entry_flush ? repetitions * l1d_misses_expected :
|
||||
repetitions * l1d_misses_expected / 2,
|
||||
passes, repetitions);
|
||||
}
|
||||
|
||||
if (entry_flush == entry_flush_orig) {
|
||||
entry_flush = !entry_flush_orig;
|
||||
if (write_debugfs_file("powerpc/entry_flush", entry_flush) < 0) {
|
||||
perror("error writing to powerpc/entry_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
iter = repetitions;
|
||||
l1d_misses_total = 0;
|
||||
passes = 0;
|
||||
goto again;
|
||||
}
|
||||
|
||||
perf_event_disable(fd);
|
||||
close(fd);
|
||||
|
||||
set_dscr(0);
|
||||
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/rfi_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (write_debugfs_file("powerpc/entry_flush", entry_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/entry_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return test_harness(entry_flush_test, "entry_flush_test");
|
||||
}
|
70
tools/testing/selftests/powerpc/security/flush_utils.c
Normal file
70
tools/testing/selftests/powerpc/security/flush_utils.c
Normal file
@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
/*
|
||||
* Copyright 2018 IBM Corporation.
|
||||
*/
|
||||
|
||||
#define __SANE_USERSPACE_TYPES__
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "flush_utils.h"
|
||||
|
||||
static inline __u64 load(void *addr)
|
||||
{
|
||||
__u64 tmp;
|
||||
|
||||
asm volatile("ld %0,0(%1)" : "=r"(tmp) : "b"(addr));
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void syscall_loop(char *p, unsigned long iterations,
|
||||
unsigned long zero_size)
|
||||
{
|
||||
for (unsigned long i = 0; i < iterations; i++) {
|
||||
for (unsigned long j = 0; j < zero_size; j += CACHELINE_SIZE)
|
||||
load(p + j);
|
||||
getppid();
|
||||
}
|
||||
}
|
||||
|
||||
static void sigill_handler(int signr, siginfo_t *info, void *unused)
|
||||
{
|
||||
static int warned;
|
||||
ucontext_t *ctx = (ucontext_t *)unused;
|
||||
unsigned long *pc = &UCONTEXT_NIA(ctx);
|
||||
|
||||
/* mtspr 3,RS to check for move to DSCR below */
|
||||
if ((*((unsigned int *)*pc) & 0xfc1fffff) == 0x7c0303a6) {
|
||||
if (!warned++)
|
||||
printf("WARNING: Skipping over dscr setup. Consider running 'ppc64_cpu --dscr=1' manually.\n");
|
||||
*pc += 4;
|
||||
} else {
|
||||
printf("SIGILL at %p\n", pc);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void set_dscr(unsigned long val)
|
||||
{
|
||||
static int init;
|
||||
struct sigaction sa;
|
||||
|
||||
if (!init) {
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = sigill_handler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
if (sigaction(SIGILL, &sa, NULL))
|
||||
perror("sigill_handler");
|
||||
init = 1;
|
||||
}
|
||||
|
||||
asm volatile("mtspr %1,%0" : : "r" (val), "i" (SPRN_DSCR));
|
||||
}
|
17
tools/testing/selftests/powerpc/security/flush_utils.h
Normal file
17
tools/testing/selftests/powerpc/security/flush_utils.h
Normal file
@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
|
||||
/*
|
||||
* Copyright 2018 IBM Corporation.
|
||||
*/
|
||||
|
||||
#ifndef _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H
|
||||
#define _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H
|
||||
|
||||
#define CACHELINE_SIZE 128
|
||||
|
||||
void syscall_loop(char *p, unsigned long iterations,
|
||||
unsigned long zero_size);
|
||||
|
||||
void set_dscr(unsigned long val);
|
||||
|
||||
#endif /* _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H */
|
@ -10,71 +10,12 @@
|
||||
#include <stdint.h>
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "flush_utils.h"
|
||||
|
||||
#define CACHELINE_SIZE 128
|
||||
|
||||
struct perf_event_read {
|
||||
__u64 nr;
|
||||
__u64 l1d_misses;
|
||||
};
|
||||
|
||||
static inline __u64 load(void *addr)
|
||||
{
|
||||
__u64 tmp;
|
||||
|
||||
asm volatile("ld %0,0(%1)" : "=r"(tmp) : "b"(addr));
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
static void syscall_loop(char *p, unsigned long iterations,
|
||||
unsigned long zero_size)
|
||||
{
|
||||
for (unsigned long i = 0; i < iterations; i++) {
|
||||
for (unsigned long j = 0; j < zero_size; j += CACHELINE_SIZE)
|
||||
load(p + j);
|
||||
getppid();
|
||||
}
|
||||
}
|
||||
|
||||
static void sigill_handler(int signr, siginfo_t *info, void *unused)
|
||||
{
|
||||
static int warned = 0;
|
||||
ucontext_t *ctx = (ucontext_t *)unused;
|
||||
unsigned long *pc = &UCONTEXT_NIA(ctx);
|
||||
|
||||
/* mtspr 3,RS to check for move to DSCR below */
|
||||
if ((*((unsigned int *)*pc) & 0xfc1fffff) == 0x7c0303a6) {
|
||||
if (!warned++)
|
||||
printf("WARNING: Skipping over dscr setup. Consider running 'ppc64_cpu --dscr=1' manually.\n");
|
||||
*pc += 4;
|
||||
} else {
|
||||
printf("SIGILL at %p\n", pc);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void set_dscr(unsigned long val)
|
||||
{
|
||||
static int init = 0;
|
||||
struct sigaction sa;
|
||||
|
||||
if (!init) {
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = sigill_handler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
if (sigaction(SIGILL, &sa, NULL))
|
||||
perror("sigill_handler");
|
||||
init = 1;
|
||||
}
|
||||
|
||||
asm volatile("mtspr %1,%0" : : "r" (val), "i" (SPRN_DSCR));
|
||||
}
|
||||
|
||||
int rfi_flush_test(void)
|
||||
{
|
||||
@ -85,19 +26,33 @@ int rfi_flush_test(void)
|
||||
__u64 l1d_misses_total = 0;
|
||||
unsigned long iterations = 100000, zero_size = 24 * 1024;
|
||||
unsigned long l1d_misses_expected;
|
||||
int rfi_flush_org, rfi_flush;
|
||||
int rfi_flush_orig, rfi_flush;
|
||||
int have_entry_flush, entry_flush_orig;
|
||||
|
||||
SKIP_IF(geteuid() != 0);
|
||||
|
||||
// The PMU event we use only works on Power7 or later
|
||||
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
|
||||
|
||||
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_org)) {
|
||||
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_orig) < 0) {
|
||||
perror("Unable to read powerpc/rfi_flush debugfs file");
|
||||
SKIP_IF(1);
|
||||
}
|
||||
|
||||
rfi_flush = rfi_flush_org;
|
||||
if (read_debugfs_file("powerpc/entry_flush", &entry_flush_orig) < 0) {
|
||||
have_entry_flush = 0;
|
||||
} else {
|
||||
have_entry_flush = 1;
|
||||
|
||||
if (entry_flush_orig != 0) {
|
||||
if (write_debugfs_file("powerpc/entry_flush", 0) < 0) {
|
||||
perror("error writing to powerpc/entry_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rfi_flush = rfi_flush_orig;
|
||||
|
||||
fd = perf_event_open_counter(PERF_TYPE_RAW, /* L1d miss */ 0x400f0, -1);
|
||||
FAIL_IF(fd < 0);
|
||||
@ -106,6 +61,7 @@ int rfi_flush_test(void)
|
||||
|
||||
FAIL_IF(perf_event_enable(fd));
|
||||
|
||||
// disable L1 prefetching
|
||||
set_dscr(1);
|
||||
|
||||
iter = repetitions;
|
||||
@ -147,8 +103,8 @@ again:
|
||||
repetitions * l1d_misses_expected / 2,
|
||||
passes, repetitions);
|
||||
|
||||
if (rfi_flush == rfi_flush_org) {
|
||||
rfi_flush = !rfi_flush_org;
|
||||
if (rfi_flush == rfi_flush_orig) {
|
||||
rfi_flush = !rfi_flush_orig;
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush) < 0) {
|
||||
perror("error writing to powerpc/rfi_flush debugfs file");
|
||||
return 1;
|
||||
@ -164,11 +120,19 @@ again:
|
||||
|
||||
set_dscr(0);
|
||||
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_org) < 0) {
|
||||
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/rfi_flush debugfs file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (have_entry_flush) {
|
||||
if (write_debugfs_file("powerpc/entry_flush", entry_flush_orig) < 0) {
|
||||
perror("unable to restore original value of powerpc/entry_flush "
|
||||
"debugfs file");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user