mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
* Mitigate Indirect Target Selection (ITS) issue
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEV76QKkVc4xCGURexaDWVMHDJkrAFAmgebIwACgkQaDWVMHDJ krCGSA/+I+W/uqiz58Z2Zu4RrXMYFfKJxacF7My9wnOyRxaJduS3qrz1E5wHqBId f6M8wDx9nS24UxDkBbi84NdtlG1zj8nV8djtszGKVeqHG2DcQMMOXBKZSjOmTo2b GIZ3a3xEqXaFfnGQxXSZrvtHIwCmv10H2oyGHu0vBp/SJuWXNg72oivOGhbm0uWs 0/bdIK8+1sW7OAmhhKdvMVpmzL8TQJnkUHSkQilPB2Tsf9wWDfeY7kDkK5YwQpk2 ZK+hrmwCFXQZELY65F2+y/cFim/F38HiqVdvIkV1wFSVqVVE9hEKJ4BDZl1fXZKB p4qpDFgxO27E/eMo9IZfxRH4TdSoK6YLWo9FGWHKBPnciJfAeO9EP/AwAIhEQRdx YZlN9sGS6ja7O1Eh423BBw6cFj6ta0ck2T1PoYk32FXc6sgqCphsfvBD3+tJxz8/ xoZ3BzoErdPqSXbH5cSI972kQW0JLESiMTZa827qnJtT672t6uBcsnnmR0ZbJH1f TJCC9qgwpBiEkiGW3gwv00SC7CkXo3o0FJw0pa3MkKHGd7csxBtGBHI1b6Jj+oB0 yWf1HxSqwrq2Yek8R7lWd4jIxyWfKriEMTu7xCMUUFlprKmR2RufsADvqclNyedQ sGBCc4eu1cpZp2no/IFm+IvkuzUHnkS/WNL1LbZ9YI8h8unjZHE= =UVgZ -----END PGP SIGNATURE----- Merge tag 'its-for-linus-20250509' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip Pull x86 ITS mitigation from Dave Hansen: "Mitigate Indirect Target Selection (ITS) issue. I'd describe this one as a good old CPU bug where the behavior is _obviously_ wrong, but since it just results in bad predictions it wasn't wrong enough to notice. Well, the researchers noticed and also realized that thus bug undermined a bunch of existing indirect branch mitigations. Thus the unusually wide impact on this one. Details: ITS is a bug in some Intel CPUs that affects indirect branches including RETs in the first half of a cacheline. Due to ITS such branches may get wrongly predicted to a target of (direct or indirect) branch that is located in the second half of a cacheline. Researchers at VUSec found this behavior and reported to Intel. Affected processors: - Cascade Lake, Cooper Lake, Whiskey Lake V, Coffee Lake R, Comet Lake, Ice Lake, Tiger Lake and Rocket Lake. Scope of impact: - Guest/host isolation: When eIBRS is used for guest/host isolation, the indirect branches in the VMM may still be predicted with targets corresponding to direct branches in the guest. - Intra-mode using cBPF: cBPF can be used to poison the branch history to exploit ITS. Realigning the indirect branches and RETs mitigates this attack vector. - User/kernel: With eIBRS enabled user/kernel isolation is *not* impacted by ITS. - Indirect Branch Prediction Barrier (IBPB): Due to this bug indirect branches may be predicted with targets corresponding to direct branches which were executed prior to IBPB. This will be fixed in the microcode. Mitigation: As indirect branches in the first half of cacheline are affected, the mitigation is to replace those indirect branches with a call to thunk that is aligned to the second half of the cacheline. RETs that take prediction from RSB are not affected, but they may be affected by RSB-underflow condition. So, RETs in the first half of cacheline are also patched to a return thunk that executes the RET aligned to second half of cacheline" * tag 'its-for-linus-20250509' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: selftest/x86/bugs: Add selftests for ITS x86/its: FineIBT-paranoid vs ITS x86/its: Use dynamic thunks for indirect branches x86/ibt: Keep IBT disabled during alternative patching mm/execmem: Unify early execmem_cache behaviour x86/its: Align RETs in BHB clear sequence to avoid thunking x86/its: Add support for RSB stuffing mitigation x86/its: Add "vmexit" option to skip mitigation on some CPUs x86/its: Enable Indirect Target Selection mitigation x86/its: Add support for ITS-safe return thunk x86/its: Add support for ITS-safe indirect thunk x86/its: Enumerate Indirect Target Selection (ITS) bug Documentation: x86/bugs/its: Add ITS documentation
This commit is contained in:
commit
6f5bf947ba
35 changed files with 1581 additions and 49 deletions
|
@ -511,6 +511,7 @@ Description: information about CPUs heterogeneity.
|
|||
|
||||
What: /sys/devices/system/cpu/vulnerabilities
|
||||
/sys/devices/system/cpu/vulnerabilities/gather_data_sampling
|
||||
/sys/devices/system/cpu/vulnerabilities/indirect_target_selection
|
||||
/sys/devices/system/cpu/vulnerabilities/itlb_multihit
|
||||
/sys/devices/system/cpu/vulnerabilities/l1tf
|
||||
/sys/devices/system/cpu/vulnerabilities/mds
|
||||
|
|
|
@ -23,3 +23,4 @@ are configurable at compile, boot or run time.
|
|||
gather_data_sampling
|
||||
reg-file-data-sampling
|
||||
rsb
|
||||
indirect-target-selection
|
||||
|
|
168
Documentation/admin-guide/hw-vuln/indirect-target-selection.rst
Normal file
168
Documentation/admin-guide/hw-vuln/indirect-target-selection.rst
Normal file
|
@ -0,0 +1,168 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Indirect Target Selection (ITS)
|
||||
===============================
|
||||
|
||||
ITS is a vulnerability in some Intel CPUs that support Enhanced IBRS and were
|
||||
released before Alder Lake. ITS may allow an attacker to control the prediction
|
||||
of indirect branches and RETs located in the lower half of a cacheline.
|
||||
|
||||
ITS is assigned CVE-2024-28956 with a CVSS score of 4.7 (Medium).
|
||||
|
||||
Scope of Impact
|
||||
---------------
|
||||
- **eIBRS Guest/Host Isolation**: Indirect branches in KVM/kernel may still be
|
||||
predicted with unintended target corresponding to a branch in the guest.
|
||||
|
||||
- **Intra-Mode BTI**: In-kernel training such as through cBPF or other native
|
||||
gadgets.
|
||||
|
||||
- **Indirect Branch Prediction Barrier (IBPB)**: After an IBPB, indirect
|
||||
branches may still be predicted with targets corresponding to direct branches
|
||||
executed prior to the IBPB. This is fixed by the IPU 2025.1 microcode, which
|
||||
should be available via distro updates. Alternatively microcode can be
|
||||
obtained from Intel's github repository [#f1]_.
|
||||
|
||||
Affected CPUs
|
||||
-------------
|
||||
Below is the list of ITS affected CPUs [#f2]_ [#f3]_:
|
||||
|
||||
======================== ============ ==================== ===============
|
||||
Common name Family_Model eIBRS Intra-mode BTI
|
||||
Guest/Host Isolation
|
||||
======================== ============ ==================== ===============
|
||||
SKYLAKE_X (step >= 6) 06_55H Affected Affected
|
||||
ICELAKE_X 06_6AH Not affected Affected
|
||||
ICELAKE_D 06_6CH Not affected Affected
|
||||
ICELAKE_L 06_7EH Not affected Affected
|
||||
TIGERLAKE_L 06_8CH Not affected Affected
|
||||
TIGERLAKE 06_8DH Not affected Affected
|
||||
KABYLAKE_L (step >= 12) 06_8EH Affected Affected
|
||||
KABYLAKE (step >= 13) 06_9EH Affected Affected
|
||||
COMETLAKE 06_A5H Affected Affected
|
||||
COMETLAKE_L 06_A6H Affected Affected
|
||||
ROCKETLAKE 06_A7H Not affected Affected
|
||||
======================== ============ ==================== ===============
|
||||
|
||||
- All affected CPUs enumerate Enhanced IBRS feature.
|
||||
- IBPB isolation is affected on all ITS affected CPUs, and need a microcode
|
||||
update for mitigation.
|
||||
- None of the affected CPUs enumerate BHI_CTRL which was introduced in Golden
|
||||
Cove (Alder Lake and Sapphire Rapids). This can help guests to determine the
|
||||
host's affected status.
|
||||
- Intel Atom CPUs are not affected by ITS.
|
||||
|
||||
Mitigation
|
||||
----------
|
||||
As only the indirect branches and RETs that have their last byte of instruction
|
||||
in the lower half of the cacheline are vulnerable to ITS, the basic idea behind
|
||||
the mitigation is to not allow indirect branches in the lower half.
|
||||
|
||||
This is achieved by relying on existing retpoline support in the kernel, and in
|
||||
compilers. ITS-vulnerable retpoline sites are runtime patched to point to newly
|
||||
added ITS-safe thunks. These safe thunks consists of indirect branch in the
|
||||
second half of the cacheline. Not all retpoline sites are patched to thunks, if
|
||||
a retpoline site is evaluated to be ITS-safe, it is replaced with an inline
|
||||
indirect branch.
|
||||
|
||||
Dynamic thunks
|
||||
~~~~~~~~~~~~~~
|
||||
From a dynamically allocated pool of safe-thunks, each vulnerable site is
|
||||
replaced with a new thunk, such that they get a unique address. This could
|
||||
improve the branch prediction accuracy. Also, it is a defense-in-depth measure
|
||||
against aliasing.
|
||||
|
||||
Note, for simplicity, indirect branches in eBPF programs are always replaced
|
||||
with a jump to a static thunk in __x86_indirect_its_thunk_array. If required,
|
||||
in future this can be changed to use dynamic thunks.
|
||||
|
||||
All vulnerable RETs are replaced with a static thunk, they do not use dynamic
|
||||
thunks. This is because RETs get their prediction from RSB mostly that does not
|
||||
depend on source address. RETs that underflow RSB may benefit from dynamic
|
||||
thunks. But, RETs significantly outnumber indirect branches, and any benefit
|
||||
from a unique source address could be outweighed by the increased icache
|
||||
footprint and iTLB pressure.
|
||||
|
||||
Retpoline
|
||||
~~~~~~~~~
|
||||
Retpoline sequence also mitigates ITS-unsafe indirect branches. For this
|
||||
reason, when retpoline is enabled, ITS mitigation only relocates the RETs to
|
||||
safe thunks. Unless user requested the RSB-stuffing mitigation.
|
||||
|
||||
RSB Stuffing
|
||||
~~~~~~~~~~~~
|
||||
RSB-stuffing via Call Depth Tracking is a mitigation for Retbleed RSB-underflow
|
||||
attacks. And it also mitigates RETs that are vulnerable to ITS.
|
||||
|
||||
Mitigation in guests
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
All guests deploy ITS mitigation by default, irrespective of eIBRS enumeration
|
||||
and Family/Model of the guest. This is because eIBRS feature could be hidden
|
||||
from a guest. One exception to this is when a guest enumerates BHI_DIS_S, which
|
||||
indicates that the guest is running on an unaffected host.
|
||||
|
||||
To prevent guests from unnecessarily deploying the mitigation on unaffected
|
||||
platforms, Intel has defined ITS_NO bit(62) in MSR IA32_ARCH_CAPABILITIES. When
|
||||
a guest sees this bit set, it should not enumerate the ITS bug. Note, this bit
|
||||
is not set by any hardware, but is **intended for VMMs to synthesize** it for
|
||||
guests as per the host's affected status.
|
||||
|
||||
Mitigation options
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
The ITS mitigation can be controlled using the "indirect_target_selection"
|
||||
kernel parameter. The available options are:
|
||||
|
||||
======== ===================================================================
|
||||
on (default) Deploy the "Aligned branch/return thunks" mitigation.
|
||||
If spectre_v2 mitigation enables retpoline, aligned-thunks are only
|
||||
deployed for the affected RET instructions. Retpoline mitigates
|
||||
indirect branches.
|
||||
|
||||
off Disable ITS mitigation.
|
||||
|
||||
vmexit Equivalent to "=on" if the CPU is affected by guest/host isolation
|
||||
part of ITS. Otherwise, mitigation is not deployed. This option is
|
||||
useful when host userspace is not in the threat model, and only
|
||||
attacks from guest to host are considered.
|
||||
|
||||
stuff Deploy RSB-fill mitigation when retpoline is also deployed.
|
||||
Otherwise, deploy the default mitigation. When retpoline mitigation
|
||||
is enabled, RSB-stuffing via Call-Depth-Tracking also mitigates
|
||||
ITS.
|
||||
|
||||
force Force the ITS bug and deploy the default mitigation.
|
||||
======== ===================================================================
|
||||
|
||||
Sysfs reporting
|
||||
---------------
|
||||
|
||||
The sysfs file showing ITS mitigation status is:
|
||||
|
||||
/sys/devices/system/cpu/vulnerabilities/indirect_target_selection
|
||||
|
||||
Note, microcode mitigation status is not reported in this file.
|
||||
|
||||
The possible values in this file are:
|
||||
|
||||
.. list-table::
|
||||
|
||||
* - Not affected
|
||||
- The processor is not vulnerable.
|
||||
* - Vulnerable
|
||||
- System is vulnerable and no mitigation has been applied.
|
||||
* - Vulnerable, KVM: Not affected
|
||||
- System is vulnerable to intra-mode BTI, but not affected by eIBRS
|
||||
guest/host isolation.
|
||||
* - Mitigation: Aligned branch/return thunks
|
||||
- The mitigation is enabled, affected indirect branches and RETs are
|
||||
relocated to safe thunks.
|
||||
* - Mitigation: Retpolines, Stuffing RSB
|
||||
- The mitigation is enabled using retpoline and RSB stuffing.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [#f1] Microcode repository - https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files
|
||||
|
||||
.. [#f2] Affected Processors list - https://www.intel.com/content/www/us/en/developer/topic-technology/software-security-guidance/processors-affected-consolidated-product-cpu-model.html
|
||||
|
||||
.. [#f3] Affected Processors list (machine readable) - https://github.com/intel/Intel-affected-processor-list
|
|
@ -2202,6 +2202,23 @@
|
|||
different crypto accelerators. This option can be used
|
||||
to achieve best performance for particular HW.
|
||||
|
||||
indirect_target_selection= [X86,Intel] Mitigation control for Indirect
|
||||
Target Selection(ITS) bug in Intel CPUs. Updated
|
||||
microcode is also required for a fix in IBPB.
|
||||
|
||||
on: Enable mitigation (default).
|
||||
off: Disable mitigation.
|
||||
force: Force the ITS bug and deploy default
|
||||
mitigation.
|
||||
vmexit: Only deploy mitigation if CPU is affected by
|
||||
guest/host isolation part of ITS.
|
||||
stuff: Deploy RSB-fill mitigation when retpoline is
|
||||
also deployed. Otherwise, deploy the default
|
||||
mitigation.
|
||||
|
||||
For details see:
|
||||
Documentation/admin-guide/hw-vuln/indirect-target-selection.rst
|
||||
|
||||
init= [KNL]
|
||||
Format: <full_path>
|
||||
Run specified binary instead of /sbin/init as init
|
||||
|
@ -3693,6 +3710,7 @@
|
|||
expose users to several CPU vulnerabilities.
|
||||
Equivalent to: if nokaslr then kpti=0 [ARM64]
|
||||
gather_data_sampling=off [X86]
|
||||
indirect_target_selection=off [X86]
|
||||
kvm.nx_huge_pages=off [X86]
|
||||
l1tf=off [X86]
|
||||
mds=off [X86]
|
||||
|
|
|
@ -2711,6 +2711,18 @@ config MITIGATION_SSB
|
|||
of speculative execution in a similar way to the Meltdown and Spectre
|
||||
security vulnerabilities.
|
||||
|
||||
config MITIGATION_ITS
|
||||
bool "Enable Indirect Target Selection mitigation"
|
||||
depends on CPU_SUP_INTEL && X86_64
|
||||
depends on MITIGATION_RETPOLINE && MITIGATION_RETHUNK
|
||||
select EXECMEM
|
||||
default y
|
||||
help
|
||||
Enable Indirect Target Selection (ITS) mitigation. ITS is a bug in
|
||||
BPU on some Intel CPUs that may allow Spectre V2 style attacks. If
|
||||
disabled, mitigation cannot be enabled via cmdline.
|
||||
See <file:Documentation/admin-guide/hw-vuln/indirect-target-selection.rst>
|
||||
|
||||
endif
|
||||
|
||||
config ARCH_HAS_ADD_PAGES
|
||||
|
|
|
@ -1525,7 +1525,9 @@ SYM_CODE_END(rewind_stack_and_make_dead)
|
|||
* ORC to unwind properly.
|
||||
*
|
||||
* The alignment is for performance and not for safety, and may be safely
|
||||
* refactored in the future if needed.
|
||||
* refactored in the future if needed. The .skips are for safety, to ensure
|
||||
* that all RETs are in the second half of a cacheline to mitigate Indirect
|
||||
* Target Selection, rather than taking the slowpath via its_return_thunk.
|
||||
*/
|
||||
SYM_FUNC_START(clear_bhb_loop)
|
||||
ANNOTATE_NOENDBR
|
||||
|
@ -1536,10 +1538,22 @@ SYM_FUNC_START(clear_bhb_loop)
|
|||
call 1f
|
||||
jmp 5f
|
||||
.align 64, 0xcc
|
||||
/*
|
||||
* Shift instructions so that the RET is in the upper half of the
|
||||
* cacheline and don't take the slowpath to its_return_thunk.
|
||||
*/
|
||||
.skip 32 - (.Lret1 - 1f), 0xcc
|
||||
ANNOTATE_INTRA_FUNCTION_CALL
|
||||
1: call 2f
|
||||
RET
|
||||
.Lret1: RET
|
||||
.align 64, 0xcc
|
||||
/*
|
||||
* As above shift instructions for RET at .Lret2 as well.
|
||||
*
|
||||
* This should be ideally be: .skip 32 - (.Lret2 - 2f), 0xcc
|
||||
* but some Clang versions (e.g. 18) don't like this.
|
||||
*/
|
||||
.skip 32 - 18, 0xcc
|
||||
2: movl $5, %eax
|
||||
3: jmp 4f
|
||||
nop
|
||||
|
@ -1547,7 +1561,7 @@ SYM_FUNC_START(clear_bhb_loop)
|
|||
jnz 3b
|
||||
sub $1, %ecx
|
||||
jnz 1b
|
||||
RET
|
||||
.Lret2: RET
|
||||
5: lfence
|
||||
pop %rbp
|
||||
RET
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <linux/stringify.h>
|
||||
#include <linux/objtool.h>
|
||||
#include <asm/asm.h>
|
||||
#include <asm/bug.h>
|
||||
|
||||
#define ALT_FLAGS_SHIFT 16
|
||||
|
||||
|
@ -124,6 +125,37 @@ static __always_inline int x86_call_depth_emit_accounting(u8 **pprog,
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
extern void its_init_mod(struct module *mod);
|
||||
extern void its_fini_mod(struct module *mod);
|
||||
extern void its_free_mod(struct module *mod);
|
||||
extern u8 *its_static_thunk(int reg);
|
||||
#else /* CONFIG_MITIGATION_ITS */
|
||||
static inline void its_init_mod(struct module *mod) { }
|
||||
static inline void its_fini_mod(struct module *mod) { }
|
||||
static inline void its_free_mod(struct module *mod) { }
|
||||
static inline u8 *its_static_thunk(int reg)
|
||||
{
|
||||
WARN_ONCE(1, "ITS not compiled in");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_MITIGATION_RETHUNK) && defined(CONFIG_OBJTOOL)
|
||||
extern bool cpu_wants_rethunk(void);
|
||||
extern bool cpu_wants_rethunk_at(void *addr);
|
||||
#else
|
||||
static __always_inline bool cpu_wants_rethunk(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static __always_inline bool cpu_wants_rethunk_at(void *addr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
extern void alternatives_smp_module_add(struct module *mod, char *name,
|
||||
void *locks, void *locks_end,
|
||||
|
|
|
@ -481,6 +481,7 @@
|
|||
#define X86_FEATURE_AMD_HETEROGENEOUS_CORES (21*32 + 6) /* Heterogeneous Core Topology */
|
||||
#define X86_FEATURE_AMD_WORKLOAD_CLASS (21*32 + 7) /* Workload Classification */
|
||||
#define X86_FEATURE_PREFER_YMM (21*32 + 8) /* Avoid ZMM registers due to downclocking */
|
||||
#define X86_FEATURE_INDIRECT_THUNK_ITS (21*32 + 9) /* Use thunk for indirect branches in lower half of cacheline */
|
||||
|
||||
/*
|
||||
* BUG word(s)
|
||||
|
@ -533,4 +534,6 @@
|
|||
#define X86_BUG_BHI X86_BUG(1*32 + 3) /* "bhi" CPU is affected by Branch History Injection */
|
||||
#define X86_BUG_IBPB_NO_RET X86_BUG(1*32 + 4) /* "ibpb_no_ret" IBPB omits return target predictions */
|
||||
#define X86_BUG_SPECTRE_V2_USER X86_BUG(1*32 + 5) /* "spectre_v2_user" CPU is affected by Spectre variant 2 attack between user processes */
|
||||
#define X86_BUG_ITS X86_BUG(1*32 + 6) /* "its" CPU is affected by Indirect Target Selection */
|
||||
#define X86_BUG_ITS_NATIVE_ONLY X86_BUG(1*32 + 7) /* "its_native_only" CPU is affected by ITS, VMX is not affected */
|
||||
#endif /* _ASM_X86_CPUFEATURES_H */
|
||||
|
|
|
@ -211,6 +211,14 @@
|
|||
* VERW clears CPU Register
|
||||
* File.
|
||||
*/
|
||||
#define ARCH_CAP_ITS_NO BIT_ULL(62) /*
|
||||
* Not susceptible to
|
||||
* Indirect Target Selection.
|
||||
* This bit is not set by
|
||||
* HW, but is synthesized by
|
||||
* VMMs for guests to know
|
||||
* their affected status.
|
||||
*/
|
||||
|
||||
#define MSR_IA32_FLUSH_CMD 0x0000010b
|
||||
#define L1D_FLUSH BIT(0) /*
|
||||
|
|
|
@ -336,10 +336,14 @@
|
|||
|
||||
#else /* __ASSEMBLER__ */
|
||||
|
||||
#define ITS_THUNK_SIZE 64
|
||||
|
||||
typedef u8 retpoline_thunk_t[RETPOLINE_THUNK_SIZE];
|
||||
typedef u8 its_thunk_t[ITS_THUNK_SIZE];
|
||||
extern retpoline_thunk_t __x86_indirect_thunk_array[];
|
||||
extern retpoline_thunk_t __x86_indirect_call_thunk_array[];
|
||||
extern retpoline_thunk_t __x86_indirect_jump_thunk_array[];
|
||||
extern its_thunk_t __x86_indirect_its_thunk_array[];
|
||||
|
||||
#ifdef CONFIG_MITIGATION_RETHUNK
|
||||
extern void __x86_return_thunk(void);
|
||||
|
@ -363,6 +367,12 @@ static inline void srso_return_thunk(void) {}
|
|||
static inline void srso_alias_return_thunk(void) {}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
extern void its_return_thunk(void);
|
||||
#else
|
||||
static inline void its_return_thunk(void) {}
|
||||
#endif
|
||||
|
||||
extern void retbleed_return_thunk(void);
|
||||
extern void srso_return_thunk(void);
|
||||
extern void srso_alias_return_thunk(void);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <linux/mmu_context.h>
|
||||
#include <linux/bsearch.h>
|
||||
#include <linux/sync_core.h>
|
||||
#include <linux/execmem.h>
|
||||
#include <asm/text-patching.h>
|
||||
#include <asm/alternative.h>
|
||||
#include <asm/sections.h>
|
||||
|
@ -31,6 +32,8 @@
|
|||
#include <asm/paravirt.h>
|
||||
#include <asm/asm-prototypes.h>
|
||||
#include <asm/cfi.h>
|
||||
#include <asm/ibt.h>
|
||||
#include <asm/set_memory.h>
|
||||
|
||||
int __read_mostly alternatives_patched;
|
||||
|
||||
|
@ -124,6 +127,165 @@ const unsigned char * const x86_nops[ASM_NOP_MAX+1] =
|
|||
#endif
|
||||
};
|
||||
|
||||
#ifdef CONFIG_FINEIBT
|
||||
static bool cfi_paranoid __ro_after_init;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
|
||||
static struct module *its_mod;
|
||||
static void *its_page;
|
||||
static unsigned int its_offset;
|
||||
|
||||
/* Initialize a thunk with the "jmp *reg; int3" instructions. */
|
||||
static void *its_init_thunk(void *thunk, int reg)
|
||||
{
|
||||
u8 *bytes = thunk;
|
||||
int offset = 0;
|
||||
int i = 0;
|
||||
|
||||
#ifdef CONFIG_FINEIBT
|
||||
if (cfi_paranoid) {
|
||||
/*
|
||||
* When ITS uses indirect branch thunk the fineibt_paranoid
|
||||
* caller sequence doesn't fit in the caller site. So put the
|
||||
* remaining part of the sequence (<ea> + JNE) into the ITS
|
||||
* thunk.
|
||||
*/
|
||||
bytes[i++] = 0xea; /* invalid instruction */
|
||||
bytes[i++] = 0x75; /* JNE */
|
||||
bytes[i++] = 0xfd;
|
||||
|
||||
offset = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (reg >= 8) {
|
||||
bytes[i++] = 0x41; /* REX.B prefix */
|
||||
reg -= 8;
|
||||
}
|
||||
bytes[i++] = 0xff;
|
||||
bytes[i++] = 0xe0 + reg; /* jmp *reg */
|
||||
bytes[i++] = 0xcc;
|
||||
|
||||
return thunk + offset;
|
||||
}
|
||||
|
||||
void its_init_mod(struct module *mod)
|
||||
{
|
||||
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
|
||||
return;
|
||||
|
||||
mutex_lock(&text_mutex);
|
||||
its_mod = mod;
|
||||
its_page = NULL;
|
||||
}
|
||||
|
||||
void its_fini_mod(struct module *mod)
|
||||
{
|
||||
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
|
||||
return;
|
||||
|
||||
WARN_ON_ONCE(its_mod != mod);
|
||||
|
||||
its_mod = NULL;
|
||||
its_page = NULL;
|
||||
mutex_unlock(&text_mutex);
|
||||
|
||||
for (int i = 0; i < mod->its_num_pages; i++) {
|
||||
void *page = mod->its_page_array[i];
|
||||
execmem_restore_rox(page, PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void its_free_mod(struct module *mod)
|
||||
{
|
||||
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < mod->its_num_pages; i++) {
|
||||
void *page = mod->its_page_array[i];
|
||||
execmem_free(page);
|
||||
}
|
||||
kfree(mod->its_page_array);
|
||||
}
|
||||
|
||||
static void *its_alloc(void)
|
||||
{
|
||||
void *page __free(execmem) = execmem_alloc(EXECMEM_MODULE_TEXT, PAGE_SIZE);
|
||||
|
||||
if (!page)
|
||||
return NULL;
|
||||
|
||||
if (its_mod) {
|
||||
void *tmp = krealloc(its_mod->its_page_array,
|
||||
(its_mod->its_num_pages+1) * sizeof(void *),
|
||||
GFP_KERNEL);
|
||||
if (!tmp)
|
||||
return NULL;
|
||||
|
||||
its_mod->its_page_array = tmp;
|
||||
its_mod->its_page_array[its_mod->its_num_pages++] = page;
|
||||
|
||||
execmem_make_temp_rw(page, PAGE_SIZE);
|
||||
}
|
||||
|
||||
return no_free_ptr(page);
|
||||
}
|
||||
|
||||
static void *its_allocate_thunk(int reg)
|
||||
{
|
||||
int size = 3 + (reg / 8);
|
||||
void *thunk;
|
||||
|
||||
#ifdef CONFIG_FINEIBT
|
||||
/*
|
||||
* The ITS thunk contains an indirect jump and an int3 instruction so
|
||||
* its size is 3 or 4 bytes depending on the register used. If CFI
|
||||
* paranoid is used then 3 extra bytes are added in the ITS thunk to
|
||||
* complete the fineibt_paranoid caller sequence.
|
||||
*/
|
||||
if (cfi_paranoid)
|
||||
size += 3;
|
||||
#endif
|
||||
|
||||
if (!its_page || (its_offset + size - 1) >= PAGE_SIZE) {
|
||||
its_page = its_alloc();
|
||||
if (!its_page) {
|
||||
pr_err("ITS page allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
memset(its_page, INT3_INSN_OPCODE, PAGE_SIZE);
|
||||
its_offset = 32;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the indirect branch instruction will be in the lower half
|
||||
* of a cacheline, then update the offset to reach the upper half.
|
||||
*/
|
||||
if ((its_offset + size - 1) % 64 < 32)
|
||||
its_offset = ((its_offset - 1) | 0x3F) + 33;
|
||||
|
||||
thunk = its_page + its_offset;
|
||||
its_offset += size;
|
||||
|
||||
return its_init_thunk(thunk, reg);
|
||||
}
|
||||
|
||||
u8 *its_static_thunk(int reg)
|
||||
{
|
||||
u8 *thunk = __x86_indirect_its_thunk_array[reg];
|
||||
|
||||
#ifdef CONFIG_FINEIBT
|
||||
/* Paranoid thunk starts 2 bytes before */
|
||||
if (cfi_paranoid)
|
||||
return thunk - 2;
|
||||
#endif
|
||||
return thunk;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Nomenclature for variable names to simplify and clarify this code and ease
|
||||
* any potential staring at it:
|
||||
|
@ -581,7 +743,8 @@ static int emit_indirect(int op, int reg, u8 *bytes)
|
|||
return i;
|
||||
}
|
||||
|
||||
static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8 *bytes)
|
||||
static int __emit_trampoline(void *addr, struct insn *insn, u8 *bytes,
|
||||
void *call_dest, void *jmp_dest)
|
||||
{
|
||||
u8 op = insn->opcode.bytes[0];
|
||||
int i = 0;
|
||||
|
@ -602,7 +765,7 @@ static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8
|
|||
switch (op) {
|
||||
case CALL_INSN_OPCODE:
|
||||
__text_gen_insn(bytes+i, op, addr+i,
|
||||
__x86_indirect_call_thunk_array[reg],
|
||||
call_dest,
|
||||
CALL_INSN_SIZE);
|
||||
i += CALL_INSN_SIZE;
|
||||
break;
|
||||
|
@ -610,7 +773,7 @@ static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8
|
|||
case JMP32_INSN_OPCODE:
|
||||
clang_jcc:
|
||||
__text_gen_insn(bytes+i, op, addr+i,
|
||||
__x86_indirect_jump_thunk_array[reg],
|
||||
jmp_dest,
|
||||
JMP32_INSN_SIZE);
|
||||
i += JMP32_INSN_SIZE;
|
||||
break;
|
||||
|
@ -625,6 +788,48 @@ clang_jcc:
|
|||
return i;
|
||||
}
|
||||
|
||||
static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8 *bytes)
|
||||
{
|
||||
return __emit_trampoline(addr, insn, bytes,
|
||||
__x86_indirect_call_thunk_array[reg],
|
||||
__x86_indirect_jump_thunk_array[reg]);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
static int emit_its_trampoline(void *addr, struct insn *insn, int reg, u8 *bytes)
|
||||
{
|
||||
u8 *thunk = __x86_indirect_its_thunk_array[reg];
|
||||
u8 *tmp = its_allocate_thunk(reg);
|
||||
|
||||
if (tmp)
|
||||
thunk = tmp;
|
||||
|
||||
return __emit_trampoline(addr, insn, bytes, thunk, thunk);
|
||||
}
|
||||
|
||||
/* Check if an indirect branch is at ITS-unsafe address */
|
||||
static bool cpu_wants_indirect_its_thunk_at(unsigned long addr, int reg)
|
||||
{
|
||||
if (!cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS))
|
||||
return false;
|
||||
|
||||
/* Indirect branch opcode is 2 or 3 bytes depending on reg */
|
||||
addr += 1 + reg / 8;
|
||||
|
||||
/* Lower-half of the cacheline? */
|
||||
return !(addr & 0x20);
|
||||
}
|
||||
#else /* CONFIG_MITIGATION_ITS */
|
||||
|
||||
#ifdef CONFIG_FINEIBT
|
||||
static bool cpu_wants_indirect_its_thunk_at(unsigned long addr, int reg)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_MITIGATION_ITS */
|
||||
|
||||
/*
|
||||
* Rewrite the compiler generated retpoline thunk calls.
|
||||
*
|
||||
|
@ -699,6 +904,15 @@ static int patch_retpoline(void *addr, struct insn *insn, u8 *bytes)
|
|||
bytes[i++] = 0xe8; /* LFENCE */
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
/*
|
||||
* Check if the address of last byte of emitted-indirect is in
|
||||
* lower-half of the cacheline. Such branches need ITS mitigation.
|
||||
*/
|
||||
if (cpu_wants_indirect_its_thunk_at((unsigned long)addr + i, reg))
|
||||
return emit_its_trampoline(addr, insn, reg, bytes);
|
||||
#endif
|
||||
|
||||
ret = emit_indirect(op, reg, bytes + i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
@ -732,6 +946,7 @@ void __init_or_module noinline apply_retpolines(s32 *start, s32 *end)
|
|||
int len, ret;
|
||||
u8 bytes[16];
|
||||
u8 op1, op2;
|
||||
u8 *dest;
|
||||
|
||||
ret = insn_decode_kernel(&insn, addr);
|
||||
if (WARN_ON_ONCE(ret < 0))
|
||||
|
@ -748,6 +963,12 @@ void __init_or_module noinline apply_retpolines(s32 *start, s32 *end)
|
|||
|
||||
case CALL_INSN_OPCODE:
|
||||
case JMP32_INSN_OPCODE:
|
||||
/* Check for cfi_paranoid + ITS */
|
||||
dest = addr + insn.length + insn.immediate.value;
|
||||
if (dest[-1] == 0xea && (dest[0] & 0xf0) == 0x70) {
|
||||
WARN_ON_ONCE(cfi_mode != CFI_FINEIBT);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0f: /* escape */
|
||||
|
@ -775,6 +996,21 @@ void __init_or_module noinline apply_retpolines(s32 *start, s32 *end)
|
|||
|
||||
#ifdef CONFIG_MITIGATION_RETHUNK
|
||||
|
||||
bool cpu_wants_rethunk(void)
|
||||
{
|
||||
return cpu_feature_enabled(X86_FEATURE_RETHUNK);
|
||||
}
|
||||
|
||||
bool cpu_wants_rethunk_at(void *addr)
|
||||
{
|
||||
if (!cpu_feature_enabled(X86_FEATURE_RETHUNK))
|
||||
return false;
|
||||
if (x86_return_thunk != its_return_thunk)
|
||||
return true;
|
||||
|
||||
return !((unsigned long)addr & 0x20);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrite the compiler generated return thunk tail-calls.
|
||||
*
|
||||
|
@ -791,7 +1027,7 @@ static int patch_return(void *addr, struct insn *insn, u8 *bytes)
|
|||
int i = 0;
|
||||
|
||||
/* Patch the custom return thunks... */
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETHUNK)) {
|
||||
if (cpu_wants_rethunk_at(addr)) {
|
||||
i = JMP32_INSN_SIZE;
|
||||
__text_gen_insn(bytes, JMP32_INSN_OPCODE, addr, x86_return_thunk, i);
|
||||
} else {
|
||||
|
@ -808,7 +1044,7 @@ void __init_or_module noinline apply_returns(s32 *start, s32 *end)
|
|||
{
|
||||
s32 *s;
|
||||
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETHUNK))
|
||||
if (cpu_wants_rethunk())
|
||||
static_call_force_reinit();
|
||||
|
||||
for (s = start; s < end; s++) {
|
||||
|
@ -1022,8 +1258,6 @@ int cfi_get_func_arity(void *func)
|
|||
static bool cfi_rand __ro_after_init = true;
|
||||
static u32 cfi_seed __ro_after_init;
|
||||
|
||||
static bool cfi_paranoid __ro_after_init = false;
|
||||
|
||||
/*
|
||||
* Re-hash the CFI hash with a boot-time seed while making sure the result is
|
||||
* not a valid ENDBR instruction.
|
||||
|
@ -1436,6 +1670,19 @@ static int cfi_rand_callers(s32 *start, s32 *end)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int emit_paranoid_trampoline(void *addr, struct insn *insn, int reg, u8 *bytes)
|
||||
{
|
||||
u8 *thunk = (void *)__x86_indirect_its_thunk_array[reg] - 2;
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
u8 *tmp = its_allocate_thunk(reg);
|
||||
if (tmp)
|
||||
thunk = tmp;
|
||||
#endif
|
||||
|
||||
return __emit_trampoline(addr, insn, bytes, thunk, thunk);
|
||||
}
|
||||
|
||||
static int cfi_rewrite_callers(s32 *start, s32 *end)
|
||||
{
|
||||
s32 *s;
|
||||
|
@ -1477,9 +1724,14 @@ static int cfi_rewrite_callers(s32 *start, s32 *end)
|
|||
memcpy(bytes, fineibt_paranoid_start, fineibt_paranoid_size);
|
||||
memcpy(bytes + fineibt_caller_hash, &hash, 4);
|
||||
|
||||
ret = emit_indirect(op, 11, bytes + fineibt_paranoid_ind);
|
||||
if (WARN_ON_ONCE(ret != 3))
|
||||
continue;
|
||||
if (cpu_wants_indirect_its_thunk_at((unsigned long)addr + fineibt_paranoid_ind, 11)) {
|
||||
emit_paranoid_trampoline(addr + fineibt_caller_size,
|
||||
&insn, 11, bytes + fineibt_caller_size);
|
||||
} else {
|
||||
ret = emit_indirect(op, 11, bytes + fineibt_paranoid_ind);
|
||||
if (WARN_ON_ONCE(ret != 3))
|
||||
continue;
|
||||
}
|
||||
|
||||
text_poke_early(addr, bytes, fineibt_paranoid_size);
|
||||
}
|
||||
|
@ -1706,29 +1958,66 @@ Efault:
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool is_paranoid_thunk(unsigned long addr)
|
||||
{
|
||||
u32 thunk;
|
||||
|
||||
__get_kernel_nofault(&thunk, (u32 *)addr, u32, Efault);
|
||||
return (thunk & 0x00FFFFFF) == 0xfd75ea;
|
||||
|
||||
Efault:
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* regs->ip points to a LOCK Jcc.d8 instruction from the fineibt_paranoid_start[]
|
||||
* sequence.
|
||||
* sequence, or to an invalid instruction (0xea) + Jcc.d8 for cfi_paranoid + ITS
|
||||
* thunk.
|
||||
*/
|
||||
static bool decode_fineibt_paranoid(struct pt_regs *regs, unsigned long *target, u32 *type)
|
||||
{
|
||||
unsigned long addr = regs->ip - fineibt_paranoid_ud;
|
||||
u32 hash;
|
||||
|
||||
if (!cfi_paranoid || !is_cfi_trap(addr + fineibt_caller_size - LEN_UD2))
|
||||
if (!cfi_paranoid)
|
||||
return false;
|
||||
|
||||
__get_kernel_nofault(&hash, addr + fineibt_caller_hash, u32, Efault);
|
||||
*target = regs->r11 + fineibt_preamble_size;
|
||||
*type = regs->r10;
|
||||
if (is_cfi_trap(addr + fineibt_caller_size - LEN_UD2)) {
|
||||
*target = regs->r11 + fineibt_preamble_size;
|
||||
*type = regs->r10;
|
||||
|
||||
/*
|
||||
* Since the trapping instruction is the exact, but LOCK prefixed,
|
||||
* Jcc.d8 that got us here, the normal fixup will work.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since the trapping instruction is the exact, but LOCK prefixed,
|
||||
* Jcc.d8 that got us here, the normal fixup will work.
|
||||
* The cfi_paranoid + ITS thunk combination results in:
|
||||
*
|
||||
* 0: 41 ba 78 56 34 12 mov $0x12345678, %r10d
|
||||
* 6: 45 3b 53 f7 cmp -0x9(%r11), %r10d
|
||||
* a: 4d 8d 5b f0 lea -0x10(%r11), %r11
|
||||
* e: 2e e8 XX XX XX XX cs call __x86_indirect_paranoid_thunk_r11
|
||||
*
|
||||
* Where the paranoid_thunk looks like:
|
||||
*
|
||||
* 1d: <ea> (bad)
|
||||
* __x86_indirect_paranoid_thunk_r11:
|
||||
* 1e: 75 fd jne 1d
|
||||
* __x86_indirect_its_thunk_r11:
|
||||
* 20: 41 ff eb jmp *%r11
|
||||
* 23: cc int3
|
||||
*
|
||||
*/
|
||||
return true;
|
||||
if (is_paranoid_thunk(regs->ip)) {
|
||||
*target = regs->r11 + fineibt_preamble_size;
|
||||
*type = regs->r10;
|
||||
|
||||
regs->ip = *target;
|
||||
return true;
|
||||
}
|
||||
|
||||
Efault:
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2031,6 +2320,8 @@ static noinline void __init alt_reloc_selftest(void)
|
|||
|
||||
void __init alternative_instructions(void)
|
||||
{
|
||||
u64 ibt;
|
||||
|
||||
int3_selftest();
|
||||
|
||||
/*
|
||||
|
@ -2057,6 +2348,9 @@ void __init alternative_instructions(void)
|
|||
*/
|
||||
paravirt_set_cap();
|
||||
|
||||
/* Keep CET-IBT disabled until caller/callee are patched */
|
||||
ibt = ibt_save(/*disable*/ true);
|
||||
|
||||
__apply_fineibt(__retpoline_sites, __retpoline_sites_end,
|
||||
__cfi_sites, __cfi_sites_end, true);
|
||||
|
||||
|
@ -2080,6 +2374,8 @@ void __init alternative_instructions(void)
|
|||
*/
|
||||
apply_seal_endbr(__ibt_endbr_seal, __ibt_endbr_seal_end);
|
||||
|
||||
ibt_restore(ibt);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
/* Patch to UP if other cpus not imminent. */
|
||||
if (!noreplace_smp && (num_present_cpus() == 1 || setup_max_cpus <= 1)) {
|
||||
|
|
|
@ -49,6 +49,7 @@ static void __init srbds_select_mitigation(void);
|
|||
static void __init l1d_flush_select_mitigation(void);
|
||||
static void __init srso_select_mitigation(void);
|
||||
static void __init gds_select_mitigation(void);
|
||||
static void __init its_select_mitigation(void);
|
||||
|
||||
/* The base value of the SPEC_CTRL MSR without task-specific bits set */
|
||||
u64 x86_spec_ctrl_base;
|
||||
|
@ -66,6 +67,14 @@ static DEFINE_MUTEX(spec_ctrl_mutex);
|
|||
|
||||
void (*x86_return_thunk)(void) __ro_after_init = __x86_return_thunk;
|
||||
|
||||
static void __init set_return_thunk(void *thunk)
|
||||
{
|
||||
if (x86_return_thunk != __x86_return_thunk)
|
||||
pr_warn("x86/bugs: return thunk changed\n");
|
||||
|
||||
x86_return_thunk = thunk;
|
||||
}
|
||||
|
||||
/* Update SPEC_CTRL MSR and its cached copy unconditionally */
|
||||
static void update_spec_ctrl(u64 val)
|
||||
{
|
||||
|
@ -178,6 +187,7 @@ void __init cpu_select_mitigations(void)
|
|||
*/
|
||||
srso_select_mitigation();
|
||||
gds_select_mitigation();
|
||||
its_select_mitigation();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1118,7 +1128,7 @@ do_cmd_auto:
|
|||
setup_force_cpu_cap(X86_FEATURE_RETHUNK);
|
||||
setup_force_cpu_cap(X86_FEATURE_UNRET);
|
||||
|
||||
x86_return_thunk = retbleed_return_thunk;
|
||||
set_return_thunk(retbleed_return_thunk);
|
||||
|
||||
if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD &&
|
||||
boot_cpu_data.x86_vendor != X86_VENDOR_HYGON)
|
||||
|
@ -1153,7 +1163,7 @@ do_cmd_auto:
|
|||
setup_force_cpu_cap(X86_FEATURE_RETHUNK);
|
||||
setup_force_cpu_cap(X86_FEATURE_CALL_DEPTH);
|
||||
|
||||
x86_return_thunk = call_depth_return_thunk;
|
||||
set_return_thunk(call_depth_return_thunk);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -1187,6 +1197,145 @@ do_cmd_auto:
|
|||
pr_info("%s\n", retbleed_strings[retbleed_mitigation]);
|
||||
}
|
||||
|
||||
#undef pr_fmt
|
||||
#define pr_fmt(fmt) "ITS: " fmt
|
||||
|
||||
enum its_mitigation_cmd {
|
||||
ITS_CMD_OFF,
|
||||
ITS_CMD_ON,
|
||||
ITS_CMD_VMEXIT,
|
||||
ITS_CMD_RSB_STUFF,
|
||||
};
|
||||
|
||||
enum its_mitigation {
|
||||
ITS_MITIGATION_OFF,
|
||||
ITS_MITIGATION_VMEXIT_ONLY,
|
||||
ITS_MITIGATION_ALIGNED_THUNKS,
|
||||
ITS_MITIGATION_RETPOLINE_STUFF,
|
||||
};
|
||||
|
||||
static const char * const its_strings[] = {
|
||||
[ITS_MITIGATION_OFF] = "Vulnerable",
|
||||
[ITS_MITIGATION_VMEXIT_ONLY] = "Mitigation: Vulnerable, KVM: Not affected",
|
||||
[ITS_MITIGATION_ALIGNED_THUNKS] = "Mitigation: Aligned branch/return thunks",
|
||||
[ITS_MITIGATION_RETPOLINE_STUFF] = "Mitigation: Retpolines, Stuffing RSB",
|
||||
};
|
||||
|
||||
static enum its_mitigation its_mitigation __ro_after_init = ITS_MITIGATION_ALIGNED_THUNKS;
|
||||
|
||||
static enum its_mitigation_cmd its_cmd __ro_after_init =
|
||||
IS_ENABLED(CONFIG_MITIGATION_ITS) ? ITS_CMD_ON : ITS_CMD_OFF;
|
||||
|
||||
static int __init its_parse_cmdline(char *str)
|
||||
{
|
||||
if (!str)
|
||||
return -EINVAL;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_MITIGATION_ITS)) {
|
||||
pr_err("Mitigation disabled at compile time, ignoring option (%s)", str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(str, "off")) {
|
||||
its_cmd = ITS_CMD_OFF;
|
||||
} else if (!strcmp(str, "on")) {
|
||||
its_cmd = ITS_CMD_ON;
|
||||
} else if (!strcmp(str, "force")) {
|
||||
its_cmd = ITS_CMD_ON;
|
||||
setup_force_cpu_bug(X86_BUG_ITS);
|
||||
} else if (!strcmp(str, "vmexit")) {
|
||||
its_cmd = ITS_CMD_VMEXIT;
|
||||
} else if (!strcmp(str, "stuff")) {
|
||||
its_cmd = ITS_CMD_RSB_STUFF;
|
||||
} else {
|
||||
pr_err("Ignoring unknown indirect_target_selection option (%s).", str);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_param("indirect_target_selection", its_parse_cmdline);
|
||||
|
||||
static void __init its_select_mitigation(void)
|
||||
{
|
||||
enum its_mitigation_cmd cmd = its_cmd;
|
||||
|
||||
if (!boot_cpu_has_bug(X86_BUG_ITS) || cpu_mitigations_off()) {
|
||||
its_mitigation = ITS_MITIGATION_OFF;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Retpoline+CDT mitigates ITS, bail out */
|
||||
if (boot_cpu_has(X86_FEATURE_RETPOLINE) &&
|
||||
boot_cpu_has(X86_FEATURE_CALL_DEPTH)) {
|
||||
its_mitigation = ITS_MITIGATION_RETPOLINE_STUFF;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Exit early to avoid irrelevant warnings */
|
||||
if (cmd == ITS_CMD_OFF) {
|
||||
its_mitigation = ITS_MITIGATION_OFF;
|
||||
goto out;
|
||||
}
|
||||
if (spectre_v2_enabled == SPECTRE_V2_NONE) {
|
||||
pr_err("WARNING: Spectre-v2 mitigation is off, disabling ITS\n");
|
||||
its_mitigation = ITS_MITIGATION_OFF;
|
||||
goto out;
|
||||
}
|
||||
if (!IS_ENABLED(CONFIG_MITIGATION_RETPOLINE) ||
|
||||
!IS_ENABLED(CONFIG_MITIGATION_RETHUNK)) {
|
||||
pr_err("WARNING: ITS mitigation depends on retpoline and rethunk support\n");
|
||||
its_mitigation = ITS_MITIGATION_OFF;
|
||||
goto out;
|
||||
}
|
||||
if (IS_ENABLED(CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B)) {
|
||||
pr_err("WARNING: ITS mitigation is not compatible with CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B\n");
|
||||
its_mitigation = ITS_MITIGATION_OFF;
|
||||
goto out;
|
||||
}
|
||||
if (boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE)) {
|
||||
pr_err("WARNING: ITS mitigation is not compatible with lfence mitigation\n");
|
||||
its_mitigation = ITS_MITIGATION_OFF;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cmd == ITS_CMD_RSB_STUFF &&
|
||||
(!boot_cpu_has(X86_FEATURE_RETPOLINE) || !IS_ENABLED(CONFIG_MITIGATION_CALL_DEPTH_TRACKING))) {
|
||||
pr_err("RSB stuff mitigation not supported, using default\n");
|
||||
cmd = ITS_CMD_ON;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case ITS_CMD_OFF:
|
||||
its_mitigation = ITS_MITIGATION_OFF;
|
||||
break;
|
||||
case ITS_CMD_VMEXIT:
|
||||
if (boot_cpu_has_bug(X86_BUG_ITS_NATIVE_ONLY)) {
|
||||
its_mitigation = ITS_MITIGATION_VMEXIT_ONLY;
|
||||
goto out;
|
||||
}
|
||||
fallthrough;
|
||||
case ITS_CMD_ON:
|
||||
its_mitigation = ITS_MITIGATION_ALIGNED_THUNKS;
|
||||
if (!boot_cpu_has(X86_FEATURE_RETPOLINE))
|
||||
setup_force_cpu_cap(X86_FEATURE_INDIRECT_THUNK_ITS);
|
||||
setup_force_cpu_cap(X86_FEATURE_RETHUNK);
|
||||
set_return_thunk(its_return_thunk);
|
||||
break;
|
||||
case ITS_CMD_RSB_STUFF:
|
||||
its_mitigation = ITS_MITIGATION_RETPOLINE_STUFF;
|
||||
setup_force_cpu_cap(X86_FEATURE_RETHUNK);
|
||||
setup_force_cpu_cap(X86_FEATURE_CALL_DEPTH);
|
||||
set_return_thunk(call_depth_return_thunk);
|
||||
if (retbleed_mitigation == RETBLEED_MITIGATION_NONE) {
|
||||
retbleed_mitigation = RETBLEED_MITIGATION_STUFF;
|
||||
pr_info("Retbleed mitigation updated to stuffing\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
out:
|
||||
pr_info("%s\n", its_strings[its_mitigation]);
|
||||
}
|
||||
|
||||
#undef pr_fmt
|
||||
#define pr_fmt(fmt) "Spectre V2 : " fmt
|
||||
|
||||
|
@ -2607,10 +2756,10 @@ static void __init srso_select_mitigation(void)
|
|||
|
||||
if (boot_cpu_data.x86 == 0x19) {
|
||||
setup_force_cpu_cap(X86_FEATURE_SRSO_ALIAS);
|
||||
x86_return_thunk = srso_alias_return_thunk;
|
||||
set_return_thunk(srso_alias_return_thunk);
|
||||
} else {
|
||||
setup_force_cpu_cap(X86_FEATURE_SRSO);
|
||||
x86_return_thunk = srso_return_thunk;
|
||||
set_return_thunk(srso_return_thunk);
|
||||
}
|
||||
if (has_microcode)
|
||||
srso_mitigation = SRSO_MITIGATION_SAFE_RET;
|
||||
|
@ -2800,6 +2949,11 @@ static ssize_t rfds_show_state(char *buf)
|
|||
return sysfs_emit(buf, "%s\n", rfds_strings[rfds_mitigation]);
|
||||
}
|
||||
|
||||
static ssize_t its_show_state(char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "%s\n", its_strings[its_mitigation]);
|
||||
}
|
||||
|
||||
static char *stibp_state(void)
|
||||
{
|
||||
if (spectre_v2_in_eibrs_mode(spectre_v2_enabled) &&
|
||||
|
@ -2982,6 +3136,9 @@ static ssize_t cpu_show_common(struct device *dev, struct device_attribute *attr
|
|||
case X86_BUG_RFDS:
|
||||
return rfds_show_state(buf);
|
||||
|
||||
case X86_BUG_ITS:
|
||||
return its_show_state(buf);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -3061,6 +3218,11 @@ ssize_t cpu_show_reg_file_data_sampling(struct device *dev, struct device_attrib
|
|||
{
|
||||
return cpu_show_common(dev, attr, buf, X86_BUG_RFDS);
|
||||
}
|
||||
|
||||
ssize_t cpu_show_indirect_target_selection(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return cpu_show_common(dev, attr, buf, X86_BUG_ITS);
|
||||
}
|
||||
#endif
|
||||
|
||||
void __warn_thunk(void)
|
||||
|
|
|
@ -1227,6 +1227,10 @@ static const __initconst struct x86_cpu_id cpu_vuln_whitelist[] = {
|
|||
#define GDS BIT(6)
|
||||
/* CPU is affected by Register File Data Sampling */
|
||||
#define RFDS BIT(7)
|
||||
/* CPU is affected by Indirect Target Selection */
|
||||
#define ITS BIT(8)
|
||||
/* CPU is affected by Indirect Target Selection, but guest-host isolation is not affected */
|
||||
#define ITS_NATIVE_ONLY BIT(9)
|
||||
|
||||
static const struct x86_cpu_id cpu_vuln_blacklist[] __initconst = {
|
||||
VULNBL_INTEL_STEPS(INTEL_IVYBRIDGE, X86_STEP_MAX, SRBDS),
|
||||
|
@ -1238,22 +1242,25 @@ static const struct x86_cpu_id cpu_vuln_blacklist[] __initconst = {
|
|||
VULNBL_INTEL_STEPS(INTEL_BROADWELL_G, X86_STEP_MAX, SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_BROADWELL_X, X86_STEP_MAX, MMIO),
|
||||
VULNBL_INTEL_STEPS(INTEL_BROADWELL, X86_STEP_MAX, SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_SKYLAKE_X, X86_STEP_MAX, MMIO | RETBLEED | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_SKYLAKE_X, 0x5, MMIO | RETBLEED | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_SKYLAKE_X, X86_STEP_MAX, MMIO | RETBLEED | GDS | ITS),
|
||||
VULNBL_INTEL_STEPS(INTEL_SKYLAKE_L, X86_STEP_MAX, MMIO | RETBLEED | GDS | SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_SKYLAKE, X86_STEP_MAX, MMIO | RETBLEED | GDS | SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_KABYLAKE_L, X86_STEP_MAX, MMIO | RETBLEED | GDS | SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_KABYLAKE, X86_STEP_MAX, MMIO | RETBLEED | GDS | SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_KABYLAKE_L, 0xb, MMIO | RETBLEED | GDS | SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_KABYLAKE_L, X86_STEP_MAX, MMIO | RETBLEED | GDS | SRBDS | ITS),
|
||||
VULNBL_INTEL_STEPS(INTEL_KABYLAKE, 0xc, MMIO | RETBLEED | GDS | SRBDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_KABYLAKE, X86_STEP_MAX, MMIO | RETBLEED | GDS | SRBDS | ITS),
|
||||
VULNBL_INTEL_STEPS(INTEL_CANNONLAKE_L, X86_STEP_MAX, RETBLEED),
|
||||
VULNBL_INTEL_STEPS(INTEL_ICELAKE_L, X86_STEP_MAX, MMIO | MMIO_SBDS | RETBLEED | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_ICELAKE_D, X86_STEP_MAX, MMIO | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_ICELAKE_X, X86_STEP_MAX, MMIO | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_COMETLAKE, X86_STEP_MAX, MMIO | MMIO_SBDS | RETBLEED | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_COMETLAKE_L, 0x0, MMIO | RETBLEED),
|
||||
VULNBL_INTEL_STEPS(INTEL_COMETLAKE_L, X86_STEP_MAX, MMIO | MMIO_SBDS | RETBLEED | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_TIGERLAKE_L, X86_STEP_MAX, GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_TIGERLAKE, X86_STEP_MAX, GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_ICELAKE_L, X86_STEP_MAX, MMIO | MMIO_SBDS | RETBLEED | GDS | ITS | ITS_NATIVE_ONLY),
|
||||
VULNBL_INTEL_STEPS(INTEL_ICELAKE_D, X86_STEP_MAX, MMIO | GDS | ITS | ITS_NATIVE_ONLY),
|
||||
VULNBL_INTEL_STEPS(INTEL_ICELAKE_X, X86_STEP_MAX, MMIO | GDS | ITS | ITS_NATIVE_ONLY),
|
||||
VULNBL_INTEL_STEPS(INTEL_COMETLAKE, X86_STEP_MAX, MMIO | MMIO_SBDS | RETBLEED | GDS | ITS),
|
||||
VULNBL_INTEL_STEPS(INTEL_COMETLAKE_L, 0x0, MMIO | RETBLEED | ITS),
|
||||
VULNBL_INTEL_STEPS(INTEL_COMETLAKE_L, X86_STEP_MAX, MMIO | MMIO_SBDS | RETBLEED | GDS | ITS),
|
||||
VULNBL_INTEL_STEPS(INTEL_TIGERLAKE_L, X86_STEP_MAX, GDS | ITS | ITS_NATIVE_ONLY),
|
||||
VULNBL_INTEL_STEPS(INTEL_TIGERLAKE, X86_STEP_MAX, GDS | ITS | ITS_NATIVE_ONLY),
|
||||
VULNBL_INTEL_STEPS(INTEL_LAKEFIELD, X86_STEP_MAX, MMIO | MMIO_SBDS | RETBLEED),
|
||||
VULNBL_INTEL_STEPS(INTEL_ROCKETLAKE, X86_STEP_MAX, MMIO | RETBLEED | GDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_ROCKETLAKE, X86_STEP_MAX, MMIO | RETBLEED | GDS | ITS | ITS_NATIVE_ONLY),
|
||||
VULNBL_INTEL_TYPE(INTEL_ALDERLAKE, ATOM, RFDS),
|
||||
VULNBL_INTEL_STEPS(INTEL_ALDERLAKE_L, X86_STEP_MAX, RFDS),
|
||||
VULNBL_INTEL_TYPE(INTEL_RAPTORLAKE, ATOM, RFDS),
|
||||
|
@ -1318,6 +1325,32 @@ static bool __init vulnerable_to_rfds(u64 x86_arch_cap_msr)
|
|||
return cpu_matches(cpu_vuln_blacklist, RFDS);
|
||||
}
|
||||
|
||||
static bool __init vulnerable_to_its(u64 x86_arch_cap_msr)
|
||||
{
|
||||
/* The "immunity" bit trumps everything else: */
|
||||
if (x86_arch_cap_msr & ARCH_CAP_ITS_NO)
|
||||
return false;
|
||||
if (boot_cpu_data.x86_vendor != X86_VENDOR_INTEL)
|
||||
return false;
|
||||
|
||||
/* None of the affected CPUs have BHI_CTRL */
|
||||
if (boot_cpu_has(X86_FEATURE_BHI_CTRL))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If a VMM did not expose ITS_NO, assume that a guest could
|
||||
* be running on a vulnerable hardware or may migrate to such
|
||||
* hardware.
|
||||
*/
|
||||
if (boot_cpu_has(X86_FEATURE_HYPERVISOR))
|
||||
return true;
|
||||
|
||||
if (cpu_matches(cpu_vuln_blacklist, ITS))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void __init cpu_set_bug_bits(struct cpuinfo_x86 *c)
|
||||
{
|
||||
u64 x86_arch_cap_msr = x86_read_arch_cap_msr();
|
||||
|
@ -1452,6 +1485,12 @@ static void __init cpu_set_bug_bits(struct cpuinfo_x86 *c)
|
|||
if (cpu_has(c, X86_FEATURE_AMD_IBPB) && !cpu_has(c, X86_FEATURE_AMD_IBPB_RET))
|
||||
setup_force_cpu_bug(X86_BUG_IBPB_NO_RET);
|
||||
|
||||
if (vulnerable_to_its(x86_arch_cap_msr)) {
|
||||
setup_force_cpu_bug(X86_BUG_ITS);
|
||||
if (cpu_matches(cpu_vuln_blacklist, ITS_NATIVE_ONLY))
|
||||
setup_force_cpu_bug(X86_BUG_ITS_NATIVE_ONLY);
|
||||
}
|
||||
|
||||
if (cpu_matches(cpu_vuln_whitelist, NO_MELTDOWN))
|
||||
return;
|
||||
|
||||
|
|
|
@ -354,7 +354,7 @@ create_trampoline(struct ftrace_ops *ops, unsigned int *tramp_size)
|
|||
goto fail;
|
||||
|
||||
ip = trampoline + size;
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETHUNK))
|
||||
if (cpu_wants_rethunk_at(ip))
|
||||
__text_gen_insn(ip, JMP32_INSN_OPCODE, ip, x86_return_thunk, JMP32_INSN_SIZE);
|
||||
else
|
||||
memcpy(ip, retq, sizeof(retq));
|
||||
|
|
|
@ -266,6 +266,8 @@ int module_finalize(const Elf_Ehdr *hdr,
|
|||
ibt_endbr = s;
|
||||
}
|
||||
|
||||
its_init_mod(me);
|
||||
|
||||
if (retpolines || cfi) {
|
||||
void *rseg = NULL, *cseg = NULL;
|
||||
unsigned int rsize = 0, csize = 0;
|
||||
|
@ -286,6 +288,9 @@ int module_finalize(const Elf_Ehdr *hdr,
|
|||
void *rseg = (void *)retpolines->sh_addr;
|
||||
apply_retpolines(rseg, rseg + retpolines->sh_size);
|
||||
}
|
||||
|
||||
its_fini_mod(me);
|
||||
|
||||
if (returns) {
|
||||
void *rseg = (void *)returns->sh_addr;
|
||||
apply_returns(rseg, rseg + returns->sh_size);
|
||||
|
@ -326,4 +331,5 @@ int module_finalize(const Elf_Ehdr *hdr,
|
|||
void module_arch_cleanup(struct module *mod)
|
||||
{
|
||||
alternatives_smp_module_del(mod);
|
||||
its_free_mod(mod);
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ static void __ref __static_call_transform(void *insn, enum insn_type type,
|
|||
break;
|
||||
|
||||
case RET:
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETHUNK))
|
||||
if (cpu_wants_rethunk_at(insn))
|
||||
code = text_gen_insn(JMP32_INSN_OPCODE, insn, x86_return_thunk);
|
||||
else
|
||||
code = &retinsn;
|
||||
|
@ -90,7 +90,7 @@ static void __ref __static_call_transform(void *insn, enum insn_type type,
|
|||
case JCC:
|
||||
if (!func) {
|
||||
func = __static_call_return;
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETHUNK))
|
||||
if (cpu_wants_rethunk())
|
||||
func = x86_return_thunk;
|
||||
}
|
||||
|
||||
|
|
|
@ -505,6 +505,16 @@ PROVIDE(__ref_stack_chk_guard = __stack_chk_guard);
|
|||
"SRSO function pair won't alias");
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_MITIGATION_ITS) && !defined(CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B)
|
||||
. = ASSERT(__x86_indirect_its_thunk_rax & 0x20, "__x86_indirect_thunk_rax not in second half of cacheline");
|
||||
. = ASSERT(((__x86_indirect_its_thunk_rcx - __x86_indirect_its_thunk_rax) % 64) == 0, "Indirect thunks are not cacheline apart");
|
||||
. = ASSERT(__x86_indirect_its_thunk_array == __x86_indirect_its_thunk_rax, "Gap in ITS thunk array");
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_MITIGATION_ITS) && !defined(CONFIG_DEBUG_FORCE_FUNCTION_ALIGN_64B)
|
||||
. = ASSERT(its_return_thunk & 0x20, "its_return_thunk not in second half of cacheline");
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_X86_64 */
|
||||
|
||||
/*
|
||||
|
|
|
@ -1584,7 +1584,7 @@ EXPORT_SYMBOL_GPL(kvm_emulate_rdpmc);
|
|||
ARCH_CAP_PSCHANGE_MC_NO | ARCH_CAP_TSX_CTRL_MSR | ARCH_CAP_TAA_NO | \
|
||||
ARCH_CAP_SBDR_SSDP_NO | ARCH_CAP_FBSDP_NO | ARCH_CAP_PSDP_NO | \
|
||||
ARCH_CAP_FB_CLEAR | ARCH_CAP_RRSBA | ARCH_CAP_PBRSB_NO | ARCH_CAP_GDS_NO | \
|
||||
ARCH_CAP_RFDS_NO | ARCH_CAP_RFDS_CLEAR | ARCH_CAP_BHI_NO)
|
||||
ARCH_CAP_RFDS_NO | ARCH_CAP_RFDS_CLEAR | ARCH_CAP_BHI_NO | ARCH_CAP_ITS_NO)
|
||||
|
||||
static u64 kvm_get_arch_capabilities(void)
|
||||
{
|
||||
|
@ -1618,6 +1618,8 @@ static u64 kvm_get_arch_capabilities(void)
|
|||
data |= ARCH_CAP_MDS_NO;
|
||||
if (!boot_cpu_has_bug(X86_BUG_RFDS))
|
||||
data |= ARCH_CAP_RFDS_NO;
|
||||
if (!boot_cpu_has_bug(X86_BUG_ITS))
|
||||
data |= ARCH_CAP_ITS_NO;
|
||||
|
||||
if (!boot_cpu_has(X86_FEATURE_RTM)) {
|
||||
/*
|
||||
|
|
|
@ -367,6 +367,54 @@ SYM_FUNC_END(call_depth_return_thunk)
|
|||
|
||||
#endif /* CONFIG_MITIGATION_CALL_DEPTH_TRACKING */
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
|
||||
.macro ITS_THUNK reg
|
||||
|
||||
/*
|
||||
* If CFI paranoid is used then the ITS thunk starts with opcodes (0xea; jne 1b)
|
||||
* that complete the fineibt_paranoid caller sequence.
|
||||
*/
|
||||
1: .byte 0xea
|
||||
SYM_INNER_LABEL(__x86_indirect_paranoid_thunk_\reg, SYM_L_GLOBAL)
|
||||
UNWIND_HINT_UNDEFINED
|
||||
ANNOTATE_NOENDBR
|
||||
jne 1b
|
||||
SYM_INNER_LABEL(__x86_indirect_its_thunk_\reg, SYM_L_GLOBAL)
|
||||
UNWIND_HINT_UNDEFINED
|
||||
ANNOTATE_NOENDBR
|
||||
ANNOTATE_RETPOLINE_SAFE
|
||||
jmp *%\reg
|
||||
int3
|
||||
.align 32, 0xcc /* fill to the end of the line */
|
||||
.skip 32 - (__x86_indirect_its_thunk_\reg - 1b), 0xcc /* skip to the next upper half */
|
||||
.endm
|
||||
|
||||
/* ITS mitigation requires thunks be aligned to upper half of cacheline */
|
||||
.align 64, 0xcc
|
||||
.skip 29, 0xcc
|
||||
|
||||
#define GEN(reg) ITS_THUNK reg
|
||||
#include <asm/GEN-for-each-reg.h>
|
||||
#undef GEN
|
||||
|
||||
.align 64, 0xcc
|
||||
SYM_FUNC_ALIAS(__x86_indirect_its_thunk_array, __x86_indirect_its_thunk_rax)
|
||||
SYM_CODE_END(__x86_indirect_its_thunk_array)
|
||||
|
||||
.align 64, 0xcc
|
||||
.skip 32, 0xcc
|
||||
SYM_CODE_START(its_return_thunk)
|
||||
UNWIND_HINT_FUNC
|
||||
ANNOTATE_NOENDBR
|
||||
ANNOTATE_UNRET_SAFE
|
||||
ret
|
||||
int3
|
||||
SYM_CODE_END(its_return_thunk)
|
||||
EXPORT_SYMBOL(its_return_thunk)
|
||||
|
||||
#endif /* CONFIG_MITIGATION_ITS */
|
||||
|
||||
/*
|
||||
* This function name is magical and is used by -mfunction-return=thunk-extern
|
||||
* for the compiler to generate JMPs to it.
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/initrd.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/execmem.h>
|
||||
|
||||
#include <asm/asm.h>
|
||||
#include <asm/bios_ebda.h>
|
||||
|
@ -755,6 +756,8 @@ void mark_rodata_ro(void)
|
|||
pr_info("Write protecting kernel text and read-only data: %luk\n",
|
||||
size >> 10);
|
||||
|
||||
execmem_cache_make_ro();
|
||||
|
||||
kernel_set_to_readonly = 1;
|
||||
|
||||
#ifdef CONFIG_CPA_DEBUG
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <linux/gfp.h>
|
||||
#include <linux/kcore.h>
|
||||
#include <linux/bootmem_info.h>
|
||||
#include <linux/execmem.h>
|
||||
|
||||
#include <asm/processor.h>
|
||||
#include <asm/bios_ebda.h>
|
||||
|
@ -1391,6 +1392,8 @@ void mark_rodata_ro(void)
|
|||
(end - start) >> 10);
|
||||
set_memory_ro(start, (end - start) >> PAGE_SHIFT);
|
||||
|
||||
execmem_cache_make_ro();
|
||||
|
||||
kernel_set_to_readonly = 1;
|
||||
|
||||
/*
|
||||
|
|
|
@ -663,7 +663,10 @@ static void emit_indirect_jump(u8 **pprog, int reg, u8 *ip)
|
|||
{
|
||||
u8 *prog = *pprog;
|
||||
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETPOLINE_LFENCE)) {
|
||||
if (cpu_feature_enabled(X86_FEATURE_INDIRECT_THUNK_ITS)) {
|
||||
OPTIMIZER_HIDE_VAR(reg);
|
||||
emit_jump(&prog, its_static_thunk(reg), ip);
|
||||
} else if (cpu_feature_enabled(X86_FEATURE_RETPOLINE_LFENCE)) {
|
||||
EMIT_LFENCE();
|
||||
EMIT2(0xFF, 0xE0 + reg);
|
||||
} else if (cpu_feature_enabled(X86_FEATURE_RETPOLINE)) {
|
||||
|
@ -685,7 +688,7 @@ static void emit_return(u8 **pprog, u8 *ip)
|
|||
{
|
||||
u8 *prog = *pprog;
|
||||
|
||||
if (cpu_feature_enabled(X86_FEATURE_RETHUNK)) {
|
||||
if (cpu_wants_rethunk()) {
|
||||
emit_jump(&prog, x86_return_thunk, ip);
|
||||
} else {
|
||||
EMIT1(0xC3); /* ret */
|
||||
|
|
|
@ -600,6 +600,7 @@ CPU_SHOW_VULN_FALLBACK(spec_rstack_overflow);
|
|||
CPU_SHOW_VULN_FALLBACK(gds);
|
||||
CPU_SHOW_VULN_FALLBACK(reg_file_data_sampling);
|
||||
CPU_SHOW_VULN_FALLBACK(ghostwrite);
|
||||
CPU_SHOW_VULN_FALLBACK(indirect_target_selection);
|
||||
|
||||
static DEVICE_ATTR(meltdown, 0444, cpu_show_meltdown, NULL);
|
||||
static DEVICE_ATTR(spectre_v1, 0444, cpu_show_spectre_v1, NULL);
|
||||
|
@ -616,6 +617,7 @@ static DEVICE_ATTR(spec_rstack_overflow, 0444, cpu_show_spec_rstack_overflow, NU
|
|||
static DEVICE_ATTR(gather_data_sampling, 0444, cpu_show_gds, NULL);
|
||||
static DEVICE_ATTR(reg_file_data_sampling, 0444, cpu_show_reg_file_data_sampling, NULL);
|
||||
static DEVICE_ATTR(ghostwrite, 0444, cpu_show_ghostwrite, NULL);
|
||||
static DEVICE_ATTR(indirect_target_selection, 0444, cpu_show_indirect_target_selection, NULL);
|
||||
|
||||
static struct attribute *cpu_root_vulnerabilities_attrs[] = {
|
||||
&dev_attr_meltdown.attr,
|
||||
|
@ -633,6 +635,7 @@ static struct attribute *cpu_root_vulnerabilities_attrs[] = {
|
|||
&dev_attr_gather_data_sampling.attr,
|
||||
&dev_attr_reg_file_data_sampling.attr,
|
||||
&dev_attr_ghostwrite.attr,
|
||||
&dev_attr_indirect_target_selection.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
|
@ -78,6 +78,8 @@ extern ssize_t cpu_show_gds(struct device *dev,
|
|||
extern ssize_t cpu_show_reg_file_data_sampling(struct device *dev,
|
||||
struct device_attribute *attr, char *buf);
|
||||
extern ssize_t cpu_show_ghostwrite(struct device *dev, struct device_attribute *attr, char *buf);
|
||||
extern ssize_t cpu_show_indirect_target_selection(struct device *dev,
|
||||
struct device_attribute *attr, char *buf);
|
||||
|
||||
extern __printf(4, 5)
|
||||
struct device *cpu_device_create(struct device *parent, void *drvdata,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <linux/types.h>
|
||||
#include <linux/moduleloader.h>
|
||||
#include <linux/cleanup.h>
|
||||
|
||||
#if (defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)) && \
|
||||
!defined(CONFIG_KASAN_VMALLOC)
|
||||
|
@ -53,7 +54,7 @@ enum execmem_range_flags {
|
|||
EXECMEM_ROX_CACHE = (1 << 1),
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ARCH_HAS_EXECMEM_ROX
|
||||
#if defined(CONFIG_ARCH_HAS_EXECMEM_ROX) && defined(CONFIG_EXECMEM)
|
||||
/**
|
||||
* execmem_fill_trapping_insns - set memory to contain instructions that
|
||||
* will trap
|
||||
|
@ -93,9 +94,15 @@ int execmem_make_temp_rw(void *ptr, size_t size);
|
|||
* Return: 0 on success or negative error code on failure.
|
||||
*/
|
||||
int execmem_restore_rox(void *ptr, size_t size);
|
||||
|
||||
/*
|
||||
* Called from mark_readonly(), where the system transitions to ROX.
|
||||
*/
|
||||
void execmem_cache_make_ro(void);
|
||||
#else
|
||||
static inline int execmem_make_temp_rw(void *ptr, size_t size) { return 0; }
|
||||
static inline int execmem_restore_rox(void *ptr, size_t size) { return 0; }
|
||||
static inline void execmem_cache_make_ro(void) { }
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@ -170,6 +177,8 @@ void *execmem_alloc(enum execmem_type type, size_t size);
|
|||
*/
|
||||
void execmem_free(void *ptr);
|
||||
|
||||
DEFINE_FREE(execmem, void *, if (_T) execmem_free(_T));
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
/**
|
||||
* execmem_vmap - create virtual mapping for EXECMEM_MODULE_DATA memory
|
||||
|
|
|
@ -586,6 +586,11 @@ struct module {
|
|||
atomic_t refcnt;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MITIGATION_ITS
|
||||
int its_num_pages;
|
||||
void **its_page_array;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CONSTRUCTORS
|
||||
/* Constructor functions. */
|
||||
ctor_fn_t *ctors;
|
||||
|
|
40
mm/execmem.c
40
mm/execmem.c
|
@ -254,6 +254,34 @@ out_unlock:
|
|||
return ptr;
|
||||
}
|
||||
|
||||
static bool execmem_cache_rox = false;
|
||||
|
||||
void execmem_cache_make_ro(void)
|
||||
{
|
||||
struct maple_tree *free_areas = &execmem_cache.free_areas;
|
||||
struct maple_tree *busy_areas = &execmem_cache.busy_areas;
|
||||
MA_STATE(mas_free, free_areas, 0, ULONG_MAX);
|
||||
MA_STATE(mas_busy, busy_areas, 0, ULONG_MAX);
|
||||
struct mutex *mutex = &execmem_cache.mutex;
|
||||
void *area;
|
||||
|
||||
execmem_cache_rox = true;
|
||||
|
||||
mutex_lock(mutex);
|
||||
|
||||
mas_for_each(&mas_free, area, ULONG_MAX) {
|
||||
unsigned long pages = mas_range_len(&mas_free) >> PAGE_SHIFT;
|
||||
set_memory_ro(mas_free.index, pages);
|
||||
}
|
||||
|
||||
mas_for_each(&mas_busy, area, ULONG_MAX) {
|
||||
unsigned long pages = mas_range_len(&mas_busy) >> PAGE_SHIFT;
|
||||
set_memory_ro(mas_busy.index, pages);
|
||||
}
|
||||
|
||||
mutex_unlock(mutex);
|
||||
}
|
||||
|
||||
static int execmem_cache_populate(struct execmem_range *range, size_t size)
|
||||
{
|
||||
unsigned long vm_flags = VM_ALLOW_HUGE_VMAP;
|
||||
|
@ -274,9 +302,15 @@ static int execmem_cache_populate(struct execmem_range *range, size_t size)
|
|||
/* fill memory with instructions that will trap */
|
||||
execmem_fill_trapping_insns(p, alloc_size, /* writable = */ true);
|
||||
|
||||
err = set_memory_rox((unsigned long)p, vm->nr_pages);
|
||||
if (err)
|
||||
goto err_free_mem;
|
||||
if (execmem_cache_rox) {
|
||||
err = set_memory_rox((unsigned long)p, vm->nr_pages);
|
||||
if (err)
|
||||
goto err_free_mem;
|
||||
} else {
|
||||
err = set_memory_x((unsigned long)p, vm->nr_pages);
|
||||
if (err)
|
||||
goto err_free_mem;
|
||||
}
|
||||
|
||||
err = execmem_cache_add(p, alloc_size);
|
||||
if (err)
|
||||
|
|
|
@ -189,6 +189,15 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
|
|||
op2 = ins.opcode.bytes[1];
|
||||
op3 = ins.opcode.bytes[2];
|
||||
|
||||
/*
|
||||
* XXX hack, decoder is buggered and thinks 0xea is 7 bytes long.
|
||||
*/
|
||||
if (op1 == 0xea) {
|
||||
insn->len = 1;
|
||||
insn->type = INSN_BUG;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ins.rex_prefix.nbytes) {
|
||||
rex = ins.rex_prefix.bytes[0];
|
||||
rex_w = X86_REX_W(rex) >> 3;
|
||||
|
|
|
@ -121,6 +121,7 @@ TARGETS += user_events
|
|||
TARGETS += vDSO
|
||||
TARGETS += mm
|
||||
TARGETS += x86
|
||||
TARGETS += x86/bugs
|
||||
TARGETS += zram
|
||||
#Please keep the TARGETS list alphabetically sorted
|
||||
# Run "make quicktest=1 run_tests" or
|
||||
|
|
3
tools/testing/selftests/x86/bugs/Makefile
Normal file
3
tools/testing/selftests/x86/bugs/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
|||
TEST_PROGS := its_sysfs.py its_permutations.py its_indirect_alignment.py its_ret_alignment.py
|
||||
TEST_FILES := common.py
|
||||
include ../../lib.mk
|
164
tools/testing/selftests/x86/bugs/common.py
Executable file
164
tools/testing/selftests/x86/bugs/common.py
Executable file
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Intel Corporation
|
||||
#
|
||||
# This contains kselftest framework adapted common functions for testing
|
||||
# mitigation for x86 bugs.
|
||||
|
||||
import os, sys, re, shutil
|
||||
|
||||
sys.path.insert(0, '../../kselftest')
|
||||
import ksft
|
||||
|
||||
def read_file(path):
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
with open(path, 'r') as file:
|
||||
return file.read().strip()
|
||||
|
||||
def cpuinfo_has(arg):
|
||||
cpuinfo = read_file('/proc/cpuinfo')
|
||||
if arg in cpuinfo:
|
||||
return True
|
||||
return False
|
||||
|
||||
def cmdline_has(arg):
|
||||
cmdline = read_file('/proc/cmdline')
|
||||
if arg in cmdline:
|
||||
return True
|
||||
return False
|
||||
|
||||
def cmdline_has_either(args):
|
||||
cmdline = read_file('/proc/cmdline')
|
||||
for arg in args:
|
||||
if arg in cmdline:
|
||||
return True
|
||||
return False
|
||||
|
||||
def cmdline_has_none(args):
|
||||
return not cmdline_has_either(args)
|
||||
|
||||
def cmdline_has_all(args):
|
||||
cmdline = read_file('/proc/cmdline')
|
||||
for arg in args:
|
||||
if arg not in cmdline:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_sysfs(bug):
|
||||
return read_file("/sys/devices/system/cpu/vulnerabilities/" + bug)
|
||||
|
||||
def sysfs_has(bug, mitigation):
|
||||
status = get_sysfs(bug)
|
||||
if mitigation in status:
|
||||
return True
|
||||
return False
|
||||
|
||||
def sysfs_has_either(bugs, mitigations):
|
||||
for bug in bugs:
|
||||
for mitigation in mitigations:
|
||||
if sysfs_has(bug, mitigation):
|
||||
return True
|
||||
return False
|
||||
|
||||
def sysfs_has_none(bugs, mitigations):
|
||||
return not sysfs_has_either(bugs, mitigations)
|
||||
|
||||
def sysfs_has_all(bugs, mitigations):
|
||||
for bug in bugs:
|
||||
for mitigation in mitigations:
|
||||
if not sysfs_has(bug, mitigation):
|
||||
return False
|
||||
return True
|
||||
|
||||
def bug_check_pass(bug, found):
|
||||
ksft.print_msg(f"\nFound: {found}")
|
||||
# ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
|
||||
ksft.test_result_pass(f'{bug}: {found}')
|
||||
|
||||
def bug_check_fail(bug, found, expected):
|
||||
ksft.print_msg(f'\nFound:\t {found}')
|
||||
ksft.print_msg(f'Expected:\t {expected}')
|
||||
ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
|
||||
ksft.test_result_fail(f'{bug}: {found}')
|
||||
|
||||
def bug_status_unknown(bug, found):
|
||||
ksft.print_msg(f'\nUnknown status: {found}')
|
||||
ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
|
||||
ksft.test_result_fail(f'{bug}: {found}')
|
||||
|
||||
def basic_checks_sufficient(bug, mitigation):
|
||||
if not mitigation:
|
||||
bug_status_unknown(bug, "None")
|
||||
return True
|
||||
elif mitigation == "Not affected":
|
||||
ksft.test_result_pass(bug)
|
||||
return True
|
||||
elif mitigation == "Vulnerable":
|
||||
if cmdline_has_either([f'{bug}=off', 'mitigations=off']):
|
||||
bug_check_pass(bug, mitigation)
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_section_info(vmlinux, section_name):
|
||||
from elftools.elf.elffile import ELFFile
|
||||
with open(vmlinux, 'rb') as f:
|
||||
elffile = ELFFile(f)
|
||||
section = elffile.get_section_by_name(section_name)
|
||||
if section is None:
|
||||
ksft.print_msg("Available sections in vmlinux:")
|
||||
for sec in elffile.iter_sections():
|
||||
ksft.print_msg(sec.name)
|
||||
raise ValueError(f"Section {section_name} not found in {vmlinux}")
|
||||
return section['sh_addr'], section['sh_offset'], section['sh_size']
|
||||
|
||||
def get_patch_sites(vmlinux, offset, size):
|
||||
import struct
|
||||
output = []
|
||||
with open(vmlinux, 'rb') as f:
|
||||
f.seek(offset)
|
||||
i = 0
|
||||
while i < size:
|
||||
data = f.read(4) # s32
|
||||
if not data:
|
||||
break
|
||||
sym_offset = struct.unpack('<i', data)[0] + i
|
||||
i += 4
|
||||
output.append(sym_offset)
|
||||
return output
|
||||
|
||||
def get_instruction_from_vmlinux(elffile, section, virtual_address, target_address):
|
||||
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
|
||||
section_start = section['sh_addr']
|
||||
section_end = section_start + section['sh_size']
|
||||
|
||||
if not (section_start <= target_address < section_end):
|
||||
return None
|
||||
|
||||
offset = target_address - section_start
|
||||
code = section.data()[offset:offset + 16]
|
||||
|
||||
cap = init_capstone()
|
||||
for instruction in cap.disasm(code, target_address):
|
||||
if instruction.address == target_address:
|
||||
return instruction
|
||||
return None
|
||||
|
||||
def init_capstone():
|
||||
from capstone import Cs, CS_ARCH_X86, CS_MODE_64, CS_OPT_SYNTAX_ATT
|
||||
cap = Cs(CS_ARCH_X86, CS_MODE_64)
|
||||
cap.syntax = CS_OPT_SYNTAX_ATT
|
||||
return cap
|
||||
|
||||
def get_runtime_kernel():
|
||||
import drgn
|
||||
return drgn.program_from_kernel()
|
||||
|
||||
def check_dependencies_or_skip(modules, script_name="unknown test"):
|
||||
for mod in modules:
|
||||
try:
|
||||
__import__(mod)
|
||||
except ImportError:
|
||||
ksft.test_result_skip(f"Skipping {script_name}: missing module '{mod}'")
|
||||
ksft.finished()
|
150
tools/testing/selftests/x86/bugs/its_indirect_alignment.py
Executable file
150
tools/testing/selftests/x86/bugs/its_indirect_alignment.py
Executable file
|
@ -0,0 +1,150 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Intel Corporation
|
||||
#
|
||||
# Test for indirect target selection (ITS) mitigation.
|
||||
#
|
||||
# Test if indirect CALL/JMP are correctly patched by evaluating
|
||||
# the vmlinux .retpoline_sites in /proc/kcore.
|
||||
|
||||
# Install dependencies
|
||||
# add-apt-repository ppa:michel-slm/kernel-utils
|
||||
# apt update
|
||||
# apt install -y python3-drgn python3-pyelftools python3-capstone
|
||||
#
|
||||
# Best to copy the vmlinux at a standard location:
|
||||
# mkdir -p /usr/lib/debug/lib/modules/$(uname -r)
|
||||
# cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
|
||||
#
|
||||
# Usage: ./its_indirect_alignment.py [vmlinux]
|
||||
|
||||
import os, sys, argparse
|
||||
from pathlib import Path
|
||||
|
||||
this_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, this_dir + '/../../kselftest')
|
||||
import ksft
|
||||
import common as c
|
||||
|
||||
bug = "indirect_target_selection"
|
||||
|
||||
mitigation = c.get_sysfs(bug)
|
||||
if not mitigation or "Aligned branch/return thunks" not in mitigation:
|
||||
ksft.test_result_skip("Skipping its_indirect_alignment.py: Aligned branch/return thunks not enabled")
|
||||
ksft.finished()
|
||||
|
||||
if c.sysfs_has("spectre_v2", "Retpolines"):
|
||||
ksft.test_result_skip("Skipping its_indirect_alignment.py: Retpolines deployed")
|
||||
ksft.finished()
|
||||
|
||||
c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_indirect_alignment.py")
|
||||
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from drgn.helpers.common.memory import identify_address
|
||||
|
||||
cap = c.init_capstone()
|
||||
|
||||
if len(os.sys.argv) > 1:
|
||||
arg_vmlinux = os.sys.argv[1]
|
||||
if not os.path.exists(arg_vmlinux):
|
||||
ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at argument path: {arg_vmlinux}")
|
||||
ksft.exit_fail()
|
||||
os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True)
|
||||
os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux')
|
||||
|
||||
vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
|
||||
if not os.path.exists(vmlinux):
|
||||
ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at {vmlinux}")
|
||||
ksft.exit_fail()
|
||||
|
||||
ksft.print_msg(f"Using vmlinux: {vmlinux}")
|
||||
|
||||
retpolines_start_vmlinux, retpolines_sec_offset, size = c.get_section_info(vmlinux, '.retpoline_sites')
|
||||
ksft.print_msg(f"vmlinux: Section .retpoline_sites (0x{retpolines_start_vmlinux:x}) found at 0x{retpolines_sec_offset:x} with size 0x{size:x}")
|
||||
|
||||
sites_offset = c.get_patch_sites(vmlinux, retpolines_sec_offset, size)
|
||||
total_retpoline_tests = len(sites_offset)
|
||||
ksft.print_msg(f"Found {total_retpoline_tests} retpoline sites")
|
||||
|
||||
prog = c.get_runtime_kernel()
|
||||
retpolines_start_kcore = prog.symbol('__retpoline_sites').address
|
||||
ksft.print_msg(f'kcore: __retpoline_sites: 0x{retpolines_start_kcore:x}')
|
||||
|
||||
x86_indirect_its_thunk_r15 = prog.symbol('__x86_indirect_its_thunk_r15').address
|
||||
ksft.print_msg(f'kcore: __x86_indirect_its_thunk_r15: 0x{x86_indirect_its_thunk_r15:x}')
|
||||
|
||||
tests_passed = 0
|
||||
tests_failed = 0
|
||||
tests_unknown = 0
|
||||
|
||||
with open(vmlinux, 'rb') as f:
|
||||
elffile = ELFFile(f)
|
||||
text_section = elffile.get_section_by_name('.text')
|
||||
|
||||
for i in range(0, len(sites_offset)):
|
||||
site = retpolines_start_kcore + sites_offset[i]
|
||||
vmlinux_site = retpolines_start_vmlinux + sites_offset[i]
|
||||
passed = unknown = failed = False
|
||||
try:
|
||||
vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site)
|
||||
kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0]
|
||||
operand = kcore_insn.op_str
|
||||
insn_end = site + kcore_insn.size - 1 # TODO handle Jcc.32 __x86_indirect_thunk_\reg
|
||||
safe_site = insn_end & 0x20
|
||||
site_status = "" if safe_site else "(unsafe)"
|
||||
|
||||
ksft.print_msg(f"\nSite {i}: {identify_address(prog, site)} <0x{site:x}> {site_status}")
|
||||
ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}")
|
||||
ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}")
|
||||
|
||||
if (site & 0x20) ^ (insn_end & 0x20):
|
||||
ksft.print_msg(f"\tSite at safe/unsafe boundary: {str(kcore_insn.bytes)} {kcore_insn.mnemonic} {operand}")
|
||||
if safe_site:
|
||||
tests_passed += 1
|
||||
passed = True
|
||||
ksft.print_msg(f"\tPASSED: At safe address")
|
||||
continue
|
||||
|
||||
if operand.startswith('0xffffffff'):
|
||||
thunk = int(operand, 16)
|
||||
if thunk > x86_indirect_its_thunk_r15:
|
||||
insn_at_thunk = list(cap.disasm(prog.read(thunk, 16), thunk))[0]
|
||||
operand += ' -> ' + insn_at_thunk.mnemonic + ' ' + insn_at_thunk.op_str + ' <dynamic-thunk?>'
|
||||
if 'jmp' in insn_at_thunk.mnemonic and thunk & 0x20:
|
||||
ksft.print_msg(f"\tPASSED: Found {operand} at safe address")
|
||||
passed = True
|
||||
if not passed:
|
||||
if kcore_insn.operands[0].type == capstone.CS_OP_IMM:
|
||||
operand += ' <' + prog.symbol(int(operand, 16)) + '>'
|
||||
if '__x86_indirect_its_thunk_' in operand:
|
||||
ksft.print_msg(f"\tPASSED: Found {operand}")
|
||||
else:
|
||||
ksft.print_msg(f"\tPASSED: Found direct branch: {kcore_insn}, ITS thunk not required.")
|
||||
passed = True
|
||||
else:
|
||||
unknown = True
|
||||
if passed:
|
||||
tests_passed += 1
|
||||
elif unknown:
|
||||
ksft.print_msg(f"UNKNOWN: unexpected operand: {kcore_insn}")
|
||||
tests_unknown += 1
|
||||
else:
|
||||
ksft.print_msg(f'\t************* FAILED *************')
|
||||
ksft.print_msg(f"\tFound {kcore_insn.bytes} {kcore_insn.mnemonic} {operand}")
|
||||
ksft.print_msg(f'\t**********************************')
|
||||
tests_failed += 1
|
||||
except Exception as e:
|
||||
ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}")
|
||||
tests_unknown += 1
|
||||
|
||||
ksft.print_msg(f"\n\nSummary:")
|
||||
ksft.print_msg(f"PASS: \t{tests_passed} \t/ {total_retpoline_tests}")
|
||||
ksft.print_msg(f"FAIL: \t{tests_failed} \t/ {total_retpoline_tests}")
|
||||
ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_retpoline_tests}")
|
||||
|
||||
if tests_failed == 0:
|
||||
ksft.test_result_pass("All ITS return thunk sites passed")
|
||||
else:
|
||||
ksft.test_result_fail(f"{tests_failed} ITS return thunk sites failed")
|
||||
ksft.finished()
|
109
tools/testing/selftests/x86/bugs/its_permutations.py
Executable file
109
tools/testing/selftests/x86/bugs/its_permutations.py
Executable file
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Intel Corporation
|
||||
#
|
||||
# Test for indirect target selection (ITS) cmdline permutations with other bugs
|
||||
# like spectre_v2 and retbleed.
|
||||
|
||||
import os, sys, subprocess, itertools, re, shutil
|
||||
|
||||
test_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, test_dir + '/../../kselftest')
|
||||
import ksft
|
||||
import common as c
|
||||
|
||||
bug = "indirect_target_selection"
|
||||
mitigation = c.get_sysfs(bug)
|
||||
|
||||
if not mitigation or "Not affected" in mitigation:
|
||||
ksft.test_result_skip("Skipping its_permutations.py: not applicable")
|
||||
ksft.finished()
|
||||
|
||||
if shutil.which('vng') is None:
|
||||
ksft.test_result_skip("Skipping its_permutations.py: virtme-ng ('vng') not found in PATH.")
|
||||
ksft.finished()
|
||||
|
||||
TEST = f"{test_dir}/its_sysfs.py"
|
||||
default_kparam = ['clearcpuid=hypervisor', 'panic=5', 'panic_on_warn=1', 'oops=panic', 'nmi_watchdog=1', 'hung_task_panic=1']
|
||||
|
||||
DEBUG = " -v "
|
||||
|
||||
# Install dependencies
|
||||
# https://github.com/arighi/virtme-ng
|
||||
# apt install virtme-ng
|
||||
BOOT_CMD = f"vng --run {test_dir}/../../../../../arch/x86/boot/bzImage "
|
||||
#BOOT_CMD += DEBUG
|
||||
|
||||
bug = "indirect_target_selection"
|
||||
|
||||
input_options = {
|
||||
'indirect_target_selection' : ['off', 'on', 'stuff', 'vmexit'],
|
||||
'retbleed' : ['off', 'stuff', 'auto'],
|
||||
'spectre_v2' : ['off', 'on', 'eibrs', 'retpoline', 'ibrs', 'eibrs,retpoline'],
|
||||
}
|
||||
|
||||
def pretty_print(output):
|
||||
OKBLUE = '\033[94m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
|
||||
# Define patterns and their corresponding colors
|
||||
patterns = {
|
||||
r"^ok \d+": OKGREEN,
|
||||
r"^not ok \d+": FAIL,
|
||||
r"^# Testing .*": OKBLUE,
|
||||
r"^# Found: .*": WARNING,
|
||||
r"^# Totals: .*": BOLD,
|
||||
r"pass:([1-9]\d*)": OKGREEN,
|
||||
r"fail:([1-9]\d*)": FAIL,
|
||||
r"skip:([1-9]\d*)": WARNING,
|
||||
}
|
||||
|
||||
# Apply colors based on patterns
|
||||
for pattern, color in patterns.items():
|
||||
output = re.sub(pattern, lambda match: f"{color}{match.group(0)}{ENDC}", output, flags=re.MULTILINE)
|
||||
|
||||
print(output)
|
||||
|
||||
combinations = list(itertools.product(*input_options.values()))
|
||||
ksft.print_header()
|
||||
ksft.set_plan(len(combinations))
|
||||
|
||||
logs = ""
|
||||
|
||||
for combination in combinations:
|
||||
append = ""
|
||||
log = ""
|
||||
for p in default_kparam:
|
||||
append += f' --append={p}'
|
||||
command = BOOT_CMD + append
|
||||
test_params = ""
|
||||
for i, key in enumerate(input_options.keys()):
|
||||
param = f'{key}={combination[i]}'
|
||||
test_params += f' {param}'
|
||||
command += f" --append={param}"
|
||||
command += f" -- {TEST}"
|
||||
test_name = f"{bug} {test_params}"
|
||||
pretty_print(f'# Testing {test_name}')
|
||||
t = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
t.wait()
|
||||
output, _ = t.communicate()
|
||||
if t.returncode == 0:
|
||||
ksft.test_result_pass(test_name)
|
||||
else:
|
||||
ksft.test_result_fail(test_name)
|
||||
output = output.decode()
|
||||
log += f" {output}"
|
||||
pretty_print(log)
|
||||
logs += output + "\n"
|
||||
|
||||
# Optionally use tappy to parse the output
|
||||
# apt install python3-tappy
|
||||
with open("logs.txt", "w") as f:
|
||||
f.write(logs)
|
||||
|
||||
ksft.finished()
|
139
tools/testing/selftests/x86/bugs/its_ret_alignment.py
Executable file
139
tools/testing/selftests/x86/bugs/its_ret_alignment.py
Executable file
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Intel Corporation
|
||||
#
|
||||
# Test for indirect target selection (ITS) mitigation.
|
||||
#
|
||||
# Tests if the RETs are correctly patched by evaluating the
|
||||
# vmlinux .return_sites in /proc/kcore.
|
||||
#
|
||||
# Install dependencies
|
||||
# add-apt-repository ppa:michel-slm/kernel-utils
|
||||
# apt update
|
||||
# apt install -y python3-drgn python3-pyelftools python3-capstone
|
||||
#
|
||||
# Run on target machine
|
||||
# mkdir -p /usr/lib/debug/lib/modules/$(uname -r)
|
||||
# cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
|
||||
#
|
||||
# Usage: ./its_ret_alignment.py
|
||||
|
||||
import os, sys, argparse
|
||||
from pathlib import Path
|
||||
|
||||
this_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, this_dir + '/../../kselftest')
|
||||
import ksft
|
||||
import common as c
|
||||
|
||||
bug = "indirect_target_selection"
|
||||
mitigation = c.get_sysfs(bug)
|
||||
if not mitigation or "Aligned branch/return thunks" not in mitigation:
|
||||
ksft.test_result_skip("Skipping its_ret_alignment.py: Aligned branch/return thunks not enabled")
|
||||
ksft.finished()
|
||||
|
||||
c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_ret_alignment.py")
|
||||
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from drgn.helpers.common.memory import identify_address
|
||||
|
||||
cap = c.init_capstone()
|
||||
|
||||
if len(os.sys.argv) > 1:
|
||||
arg_vmlinux = os.sys.argv[1]
|
||||
if not os.path.exists(arg_vmlinux):
|
||||
ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at user-supplied path: {arg_vmlinux}")
|
||||
ksft.exit_fail()
|
||||
os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True)
|
||||
os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux')
|
||||
|
||||
vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
|
||||
if not os.path.exists(vmlinux):
|
||||
ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at {vmlinux}")
|
||||
ksft.exit_fail()
|
||||
|
||||
ksft.print_msg(f"Using vmlinux: {vmlinux}")
|
||||
|
||||
rethunks_start_vmlinux, rethunks_sec_offset, size = c.get_section_info(vmlinux, '.return_sites')
|
||||
ksft.print_msg(f"vmlinux: Section .return_sites (0x{rethunks_start_vmlinux:x}) found at 0x{rethunks_sec_offset:x} with size 0x{size:x}")
|
||||
|
||||
sites_offset = c.get_patch_sites(vmlinux, rethunks_sec_offset, size)
|
||||
total_rethunk_tests = len(sites_offset)
|
||||
ksft.print_msg(f"Found {total_rethunk_tests} rethunk sites")
|
||||
|
||||
prog = c.get_runtime_kernel()
|
||||
rethunks_start_kcore = prog.symbol('__return_sites').address
|
||||
ksft.print_msg(f'kcore: __rethunk_sites: 0x{rethunks_start_kcore:x}')
|
||||
|
||||
its_return_thunk = prog.symbol('its_return_thunk').address
|
||||
ksft.print_msg(f'kcore: its_return_thunk: 0x{its_return_thunk:x}')
|
||||
|
||||
tests_passed = 0
|
||||
tests_failed = 0
|
||||
tests_unknown = 0
|
||||
tests_skipped = 0
|
||||
|
||||
with open(vmlinux, 'rb') as f:
|
||||
elffile = ELFFile(f)
|
||||
text_section = elffile.get_section_by_name('.text')
|
||||
|
||||
for i in range(len(sites_offset)):
|
||||
site = rethunks_start_kcore + sites_offset[i]
|
||||
vmlinux_site = rethunks_start_vmlinux + sites_offset[i]
|
||||
try:
|
||||
passed = unknown = failed = skipped = False
|
||||
|
||||
symbol = identify_address(prog, site)
|
||||
vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site)
|
||||
kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0]
|
||||
|
||||
insn_end = site + kcore_insn.size - 1
|
||||
|
||||
safe_site = insn_end & 0x20
|
||||
site_status = "" if safe_site else "(unsafe)"
|
||||
|
||||
ksft.print_msg(f"\nSite {i}: {symbol} <0x{site:x}> {site_status}")
|
||||
ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}")
|
||||
ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}")
|
||||
|
||||
if safe_site:
|
||||
tests_passed += 1
|
||||
passed = True
|
||||
ksft.print_msg(f"\tPASSED: At safe address")
|
||||
continue
|
||||
|
||||
if "jmp" in kcore_insn.mnemonic:
|
||||
passed = True
|
||||
elif "ret" not in kcore_insn.mnemonic:
|
||||
skipped = True
|
||||
|
||||
if passed:
|
||||
ksft.print_msg(f"\tPASSED: Found {kcore_insn.mnemonic} {kcore_insn.op_str}")
|
||||
tests_passed += 1
|
||||
elif skipped:
|
||||
ksft.print_msg(f"\tSKIPPED: Found '{kcore_insn.mnemonic}'")
|
||||
tests_skipped += 1
|
||||
elif unknown:
|
||||
ksft.print_msg(f"UNKNOWN: An unknown instruction: {kcore_insn}")
|
||||
tests_unknown += 1
|
||||
else:
|
||||
ksft.print_msg(f'\t************* FAILED *************')
|
||||
ksft.print_msg(f"\tFound {kcore_insn.mnemonic} {kcore_insn.op_str}")
|
||||
ksft.print_msg(f'\t**********************************')
|
||||
tests_failed += 1
|
||||
except Exception as e:
|
||||
ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}")
|
||||
tests_unknown += 1
|
||||
|
||||
ksft.print_msg(f"\n\nSummary:")
|
||||
ksft.print_msg(f"PASSED: \t{tests_passed} \t/ {total_rethunk_tests}")
|
||||
ksft.print_msg(f"FAILED: \t{tests_failed} \t/ {total_rethunk_tests}")
|
||||
ksft.print_msg(f"SKIPPED: \t{tests_skipped} \t/ {total_rethunk_tests}")
|
||||
ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_rethunk_tests}")
|
||||
|
||||
if tests_failed == 0:
|
||||
ksft.test_result_pass("All ITS return thunk sites passed.")
|
||||
else:
|
||||
ksft.test_result_fail(f"{tests_failed} failed sites need ITS return thunks.")
|
||||
ksft.finished()
|
65
tools/testing/selftests/x86/bugs/its_sysfs.py
Executable file
65
tools/testing/selftests/x86/bugs/its_sysfs.py
Executable file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Intel Corporation
|
||||
#
|
||||
# Test for Indirect Target Selection(ITS) mitigation sysfs status.
|
||||
|
||||
import sys, os, re
|
||||
this_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, this_dir + '/../../kselftest')
|
||||
import ksft
|
||||
|
||||
from common import *
|
||||
|
||||
bug = "indirect_target_selection"
|
||||
mitigation = get_sysfs(bug)
|
||||
|
||||
ITS_MITIGATION_ALIGNED_THUNKS = "Mitigation: Aligned branch/return thunks"
|
||||
ITS_MITIGATION_RETPOLINE_STUFF = "Mitigation: Retpolines, Stuffing RSB"
|
||||
ITS_MITIGATION_VMEXIT_ONLY = "Mitigation: Vulnerable, KVM: Not affected"
|
||||
ITS_MITIGATION_VULNERABLE = "Vulnerable"
|
||||
|
||||
def check_mitigation():
|
||||
if mitigation == ITS_MITIGATION_ALIGNED_THUNKS:
|
||||
if cmdline_has(f'{bug}=stuff') and sysfs_has("spectre_v2", "Retpolines"):
|
||||
bug_check_fail(bug, ITS_MITIGATION_ALIGNED_THUNKS, ITS_MITIGATION_RETPOLINE_STUFF)
|
||||
return
|
||||
if cmdline_has(f'{bug}=vmexit') and cpuinfo_has('its_native_only'):
|
||||
bug_check_fail(bug, ITS_MITIGATION_ALIGNED_THUNKS, ITS_MITIGATION_VMEXIT_ONLY)
|
||||
return
|
||||
bug_check_pass(bug, ITS_MITIGATION_ALIGNED_THUNKS)
|
||||
return
|
||||
|
||||
if mitigation == ITS_MITIGATION_RETPOLINE_STUFF:
|
||||
if cmdline_has(f'{bug}=stuff') and sysfs_has("spectre_v2", "Retpolines"):
|
||||
bug_check_pass(bug, ITS_MITIGATION_RETPOLINE_STUFF)
|
||||
return
|
||||
if sysfs_has('retbleed', 'Stuffing'):
|
||||
bug_check_pass(bug, ITS_MITIGATION_RETPOLINE_STUFF)
|
||||
return
|
||||
bug_check_fail(bug, ITS_MITIGATION_RETPOLINE_STUFF, ITS_MITIGATION_ALIGNED_THUNKS)
|
||||
|
||||
if mitigation == ITS_MITIGATION_VMEXIT_ONLY:
|
||||
if cmdline_has(f'{bug}=vmexit') and cpuinfo_has('its_native_only'):
|
||||
bug_check_pass(bug, ITS_MITIGATION_VMEXIT_ONLY)
|
||||
return
|
||||
bug_check_fail(bug, ITS_MITIGATION_VMEXIT_ONLY, ITS_MITIGATION_ALIGNED_THUNKS)
|
||||
|
||||
if mitigation == ITS_MITIGATION_VULNERABLE:
|
||||
if sysfs_has("spectre_v2", "Vulnerable"):
|
||||
bug_check_pass(bug, ITS_MITIGATION_VULNERABLE)
|
||||
else:
|
||||
bug_check_fail(bug, "Mitigation", ITS_MITIGATION_VULNERABLE)
|
||||
|
||||
bug_status_unknown(bug, mitigation)
|
||||
return
|
||||
|
||||
ksft.print_header()
|
||||
ksft.set_plan(1)
|
||||
ksft.print_msg(f'{bug}: {mitigation} ...')
|
||||
|
||||
if not basic_checks_sufficient(bug, mitigation):
|
||||
check_mitigation()
|
||||
|
||||
ksft.finished()
|
Loading…
Add table
Reference in a new issue