#![allow(non_upper_case_globals, dead_code, non_snake_case)]
use crate::codegen::{
MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, PhysReg, VirtReg,
};
use crate::x86::x86_instr_info::{X86InstrDesc, X86InstrInfo, X86Opcode};
use crate::x86::X86Subtarget;
use std::arch::asm;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
pub const X86_LFENCE_OPCODE: [u8; 3] = [0x0F, 0xAE, 0xE8];
pub const X86_MFENCE_OPCODE: [u8; 3] = [0x0F, 0xAE, 0xF0];
pub const X86_SFENCE_OPCODE: [u8; 3] = [0x0F, 0xAE, 0xF8];
pub const X86_IBPB_MSR: u32 = 0x49;
pub const X86_IBPB_CMD: u64 = 0x01;
pub const X86_SPEC_CTRL_MSR: u32 = 0x48;
pub const X86_STIBP_BIT: u64 = 0x02;
pub const X86_IBRS_BIT: u64 = 0x01;
pub const X86_SSBD_BIT: u64 = 0x04;
pub const X86_PSFD_BIT: u64 = 0x08;
pub const X86_ARCH_CAP_MSR: u32 = 0x10A;
pub const X86_ARCH_CAP_IBRS_ALL: u64 = 0x01;
pub const X86_ARCH_CAP_RDCL_NO: u64 = 0x02;
pub const X86_ARCH_CAP_IBPB_ALL: u64 = 0x04;
pub const X86_ARCH_CAP_RSBA: u64 = 0x08;
pub const X86_ARCH_CAP_SKIP_VMENTRY_L1DFLUSH: u64 = 0x10;
pub const X86_ARCH_CAP_SSB_NO: u64 = 0x20;
pub const X86_ARCH_CAP_MDS_NO: u64 = 0x40;
pub const X86_ARCH_CAP_PSCHANGE_MC_NO: u64 = 0x80;
pub const X86_ARCH_CAP_TSX_CTRL: u64 = 0x100;
pub const X86_ARCH_CAP_TAA_NO: u64 = 0x200;
pub const X86_ARCH_CAP_FBSDP_NO: u64 = 0x400;
pub const X86_ARCH_CAP_PBRSB_NO: u64 = 0x800;
pub const X86_ARCH_CAP_GDS_NO: u64 = 0x1000;
pub const X86_ARCH_CAP_RFDS_NO: u64 = 0x2000;
pub const X86_ARCH_CAP_RFDS_CLEAR: u64 = 0x4000;
pub const X86_RSB_DEPTH_INTEL_SKYLAKE: usize = 16;
pub const X86_RSB_DEPTH_INTEL_ICELAKE: usize = 32;
pub const X86_RSB_DEPTH_INTEL_ALDERLAKE: usize = 34;
pub const X86_RSB_DEPTH_AMD_ZEN: usize = 32;
pub const X86_RSB_DEPTH_AMD_ZEN3: usize = 32;
pub const X86_RSB_DEPTH_AMD_ZEN4: usize = 32;
pub const X86_RSB_DEPTH_DEFAULT: usize = 16;
pub const X86_RSB_STUFF_MAX_DEPTH: usize = 64;
pub const X86_RSB_STUFF_COUNT: usize = 32;
pub const X86_ENHANCED_IBRS_BIT: u64 = 0x08;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86IBRSMode {
None,
KernelOnly,
Enhanced,
RetpolineAware,
}
impl fmt::Display for X86IBRSMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86IBRSMode::None => write!(f, "none"),
X86IBRSMode::KernelOnly => write!(f, "kernel-only"),
X86IBRSMode::Enhanced => write!(f, "enhanced"),
X86IBRSMode::RetpolineAware => write!(f, "retpoline-aware"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86IBPBTrigger {
Never,
OnContextSwitch,
OnPrivilegeTransition,
OnIndirectBranch,
AlwaysOnUserReturn,
}
impl fmt::Display for X86IBPBTrigger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86IBPBTrigger::Never => write!(f, "never"),
X86IBPBTrigger::OnContextSwitch => write!(f, "on-context-switch"),
X86IBPBTrigger::OnPrivilegeTransition => write!(f, "on-privilege-transition"),
X86IBPBTrigger::OnIndirectBranch => write!(f, "on-indirect-branch"),
X86IBPBTrigger::AlwaysOnUserReturn => write!(f, "always-on-user-return"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86STIBPMode {
Off,
SMTOnly,
Always,
PerProcess,
Conditional,
}
impl fmt::Display for X86STIBPMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86STIBPMode::Off => write!(f, "off"),
X86STIBPMode::SMTOnly => write!(f, "smt-only"),
X86STIBPMode::Always => write!(f, "always"),
X86STIBPMode::PerProcess => write!(f, "per-process"),
X86STIBPMode::Conditional => write!(f, "conditional"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86SSBDMode {
Off,
ViaMSR,
PerThread,
ViaLSConfig,
ForceOn,
Auto,
}
impl fmt::Display for X86SSBDMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86SSBDMode::Off => write!(f, "off"),
X86SSBDMode::ViaMSR => write!(f, "via-msr"),
X86SSBDMode::PerThread => write!(f, "per-thread"),
X86SSBDMode::ViaLSConfig => write!(f, "via-ls-cfg"),
X86SSBDMode::ForceOn => write!(f, "force-on"),
X86SSBDMode::Auto => write!(f, "auto"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86SpecBarrier {
LFENCE,
MFENCE,
SFENCE,
CPUID,
IRETToSelf,
RDTSCPSerialize,
Serialize,
IBPB,
IBRS_ON,
IBRS_OFF,
STIBP_ON,
STIBP_OFF,
SSBD_ON,
SSBD_OFF,
PSFD_ON,
PSFD_OFF,
VERW,
RSBStuff,
FillBuffers,
}
impl fmt::Display for X86SpecBarrier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
X86SpecBarrier::LFENCE => "LFENCE",
X86SpecBarrier::MFENCE => "MFENCE",
X86SpecBarrier::SFENCE => "SFENCE",
X86SpecBarrier::CPUID => "CPUID",
X86SpecBarrier::IRETToSelf => "IRET-to-self",
X86SpecBarrier::RDTSCPSerialize => "RDTSCP",
X86SpecBarrier::Serialize => "SERIALIZE",
X86SpecBarrier::IBPB => "IBPB",
X86SpecBarrier::IBRS_ON => "IBRS-ON",
X86SpecBarrier::IBRS_OFF => "IBRS-OFF",
X86SpecBarrier::STIBP_ON => "STIBP-ON",
X86SpecBarrier::STIBP_OFF => "STIBP-OFF",
X86SpecBarrier::SSBD_ON => "SSBD-ON",
X86SpecBarrier::SSBD_OFF => "SSBD-OFF",
X86SpecBarrier::PSFD_ON => "PSFD-ON",
X86SpecBarrier::PSFD_OFF => "PSFD-OFF",
X86SpecBarrier::VERW => "VERW",
X86SpecBarrier::RSBStuff => "RSB-STUFF",
X86SpecBarrier::FillBuffers => "FILL-BUFFERS",
};
write!(f, "{}", s)
}
}
const RAX: PhysReg = 0;
const RCX: PhysReg = 1;
const RDX: PhysReg = 2;
const RBX: PhysReg = 3;
const RSP: PhysReg = 4;
const RBP: PhysReg = 5;
const RSI: PhysReg = 6;
const RDI: PhysReg = 7;
const R8: PhysReg = 8;
const R9: PhysReg = 9;
const R10: PhysReg = 10;
const R11: PhysReg = 11;
const R12: PhysReg = 12;
const R13: PhysReg = 13;
const R14: PhysReg = 14;
const R15: PhysReg = 15;
const EAX: PhysReg = 16;
const ECX: PhysReg = 17;
const EDX: PhysReg = 18;
const EBX: PhysReg = 19;
const ESP: PhysReg = 20;
const EBP: PhysReg = 21;
const ESI: PhysReg = 22;
const EDI: PhysReg = 23;
const R8D: PhysReg = 24;
const R9D: PhysReg = 25;
const R10D: PhysReg = 26;
const R11D: PhysReg = 27;
const R12D: PhysReg = 28;
const R13D: PhysReg = 29;
const R14D: PhysReg = 30;
const R15D: PhysReg = 31;
const AX: PhysReg = 32;
const CX: PhysReg = 33;
const DX: PhysReg = 34;
const BX: PhysReg = 35;
fn opc(op: X86Opcode) -> u32 {
op as u32
}
fn is_phys_reg(op: &MachineOperand, reg: PhysReg) -> bool {
matches!(op, MachineOperand::PhysReg(r) if *r == reg)
}
fn is_any_phys_reg(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::PhysReg(_))
}
fn get_phys_reg(op: &MachineOperand) -> Option<PhysReg> {
match op {
MachineOperand::PhysReg(r) => Some(*r),
_ => None,
}
}
fn get_imm(op: &MachineOperand) -> Option<i64> {
match op {
MachineOperand::Imm(v) => Some(*v),
_ => None,
}
}
#[derive(Debug, Clone)]
pub struct X86RetireControl {
pub subtarget: X86Subtarget,
pub ibrs_mode: X86IBRSMode,
pub ibpb_trigger: X86IBPBTrigger,
pub stibp_mode: X86STIBPMode,
pub ssbd_mode: X86SSBDMode,
pub lfence_is_serializing: bool,
pub has_serialize: bool,
pub has_enhanced_ibrs: bool,
pub has_ibpb_all: bool,
pub has_rsba: bool,
pub ssb_no: bool,
pub has_md_clear: bool,
pub pbrsb_no: bool,
pub has_psfd: bool,
pub force_barriers: bool,
pub use_verw_mitigation: bool,
pub rsb_stuff_depth: usize,
pub rsb_stuff_iterations: usize,
pub enable_rsb_stuffing: bool,
pub align_rsb_stuffing: bool,
pub stats: RetireControlStats,
}
#[derive(Debug, Clone, Default)]
pub struct RetireControlStats {
pub lfence_inserted: usize,
pub mfence_inserted: usize,
pub sfence_inserted: usize,
pub cpuid_inserted: usize,
pub iret_to_self_inserted: usize,
pub serialize_inserted: usize,
pub ibpb_inserted: usize,
pub ibrs_on_inserted: usize,
pub ibrs_off_inserted: usize,
pub stibp_on_inserted: usize,
pub stibp_off_inserted: usize,
pub ssbd_on_inserted: usize,
pub ssbd_off_inserted: usize,
pub psfd_on_inserted: usize,
pub psfd_off_inserted: usize,
pub verw_inserted: usize,
pub rsb_stuff_sequences: usize,
pub rsb_stuff_total_instructions: usize,
pub fill_buffers_inserted: usize,
pub indirect_branches_protected: usize,
pub slh_loads_hardened: usize,
pub bti_barriers_inserted: usize,
pub retbleed_barriers_inserted: usize,
pub total_barriers: usize,
}
impl RetireControlStats {
pub fn merge(&mut self, other: &RetireControlStats) {
self.lfence_inserted += other.lfence_inserted;
self.mfence_inserted += other.mfence_inserted;
self.sfence_inserted += other.sfence_inserted;
self.cpuid_inserted += other.cpuid_inserted;
self.iret_to_self_inserted += other.iret_to_self_inserted;
self.serialize_inserted += other.serialize_inserted;
self.ibpb_inserted += other.ibpb_inserted;
self.ibrs_on_inserted += other.ibrs_on_inserted;
self.ibrs_off_inserted += other.ibrs_off_inserted;
self.stibp_on_inserted += other.stibp_on_inserted;
self.stibp_off_inserted += other.stibp_off_inserted;
self.ssbd_on_inserted += other.ssbd_on_inserted;
self.ssbd_off_inserted += other.ssbd_off_inserted;
self.psfd_on_inserted += other.psfd_on_inserted;
self.psfd_off_inserted += other.psfd_off_inserted;
self.verw_inserted += other.verw_inserted;
self.rsb_stuff_sequences += other.rsb_stuff_sequences;
self.rsb_stuff_total_instructions += other.rsb_stuff_total_instructions;
self.fill_buffers_inserted += other.fill_buffers_inserted;
self.indirect_branches_protected += other.indirect_branches_protected;
self.slh_loads_hardened += other.slh_loads_hardened;
self.bti_barriers_inserted += other.bti_barriers_inserted;
self.retbleed_barriers_inserted += other.retbleed_barriers_inserted;
self.total_barriers += other.total_barriers;
}
pub fn summary(&self) -> String {
format!(
"X86RetireControl: total_barriers={}, lfence={}, ibrs={}/{}, ibpb={}, \
rsb_stuff={}, verw={}, slh={}, bti={}, retbleed={}",
self.total_barriers,
self.lfence_inserted,
self.ibrs_on_inserted,
self.ibrs_off_inserted,
self.ibpb_inserted,
self.rsb_stuff_sequences,
self.verw_inserted,
self.slh_loads_hardened,
self.bti_barriers_inserted,
self.retbleed_barriers_inserted,
)
}
pub fn made_progress(&self) -> bool {
self.total_barriers > 0
}
}
impl X86RetireControl {
pub fn new(subtarget: &X86Subtarget) -> Self {
let lfence_is_serializing = detect_lfence_serializing(&subtarget.cpu);
let has_serialize = subtarget.features.contains("serialize");
let has_enhanced_ibrs = subtarget.features.contains("enhanced-ibrs");
let has_ibpb_all = !subtarget.features.contains("no-ibpb-all");
let has_rsba = subtarget.features.contains("rsba");
let ssb_no = subtarget.features.contains("ssb-no");
let has_md_clear = subtarget.features.contains("md-clear");
let pbrsb_no = subtarget.features.contains("pbrsb-no");
let has_psfd = subtarget.features.contains("psfd");
Self {
subtarget: subtarget.clone(),
ibrs_mode: X86IBRSMode::None,
ibpb_trigger: X86IBPBTrigger::Never,
stibp_mode: X86STIBPMode::Off,
ssbd_mode: X86SSBDMode::Off,
lfence_is_serializing,
has_serialize,
has_enhanced_ibrs,
has_ibpb_all,
has_rsba,
ssb_no,
has_md_clear,
pbrsb_no,
has_psfd,
force_barriers: false,
use_verw_mitigation: false,
rsb_stuff_depth: X86_RSB_DEPTH_DEFAULT,
rsb_stuff_iterations: 1,
enable_rsb_stuffing: true,
align_rsb_stuffing: false,
stats: RetireControlStats::default(),
}
}
pub fn set_ibrs_mode(&mut self, mode: X86IBRSMode) {
self.ibrs_mode = mode;
}
pub fn set_ibpb_trigger(&mut self, trigger: X86IBPBTrigger) {
self.ibpb_trigger = trigger;
}
pub fn set_stibp_mode(&mut self, mode: X86STIBPMode) {
self.stibp_mode = mode;
}
pub fn set_ssbd_mode(&mut self, mode: X86SSBDMode) {
self.ssbd_mode = mode;
}
pub fn set_force_barriers(&mut self, force: bool) {
self.force_barriers = force;
}
pub fn set_use_verw_mitigation(&mut self, verw: bool) {
self.use_verw_mitigation = verw;
}
pub fn set_rsb_stuff_depth(&mut self, depth: usize) {
self.rsb_stuff_depth = depth.min(X86_RSB_STUFF_MAX_DEPTH);
}
pub fn set_rsb_stuff_iterations(&mut self, iterations: usize) {
self.rsb_stuff_iterations = iterations.max(1);
}
pub fn set_enable_rsb_stuffing(&mut self, enable: bool) {
self.enable_rsb_stuffing = enable;
}
pub fn set_align_rsb_stuffing(&mut self, align: bool) {
self.align_rsb_stuffing = align;
}
}
fn detect_lfence_serializing(cpu: &str) -> bool {
let cpu_lower = cpu.to_lowercase();
if cpu_lower.contains("skylake")
|| cpu_lower.contains("cannonlake")
|| cpu_lower.contains("icelake")
|| cpu_lower.contains("tigerlake")
|| cpu_lower.contains("rocketlake")
|| cpu_lower.contains("alderlake")
|| cpu_lower.contains("raptorlake")
|| cpu_lower.contains("meteorlake")
|| cpu_lower.contains("cascadelake")
|| cpu_lower.contains("cooperlake")
|| cpu_lower.contains("sapphirerapids")
|| cpu_lower.contains("emeraldrapids")
|| cpu_lower.contains("graniterapids")
|| cpu_lower.contains("sierraforest")
|| cpu_lower.contains("grandridge")
{
return true;
}
if cpu_lower.contains("znver2")
|| cpu_lower.contains("znver3")
|| cpu_lower.contains("znver4")
|| cpu_lower.contains("znver5")
{
return true;
}
false
}
impl X86RetireControl {
pub fn create_lfence_instr(&self) -> MachineInstr {
let mut instr = MachineInstr::new(opc(X86Opcode::NOP));
instr
.operands
.push(MachineOperand::Imm(0x0FAEE8_u32 as i64));
instr
}
pub fn insert_lfence_barrier(&mut self, bb: &mut MachineBasicBlock) {
let instr = self.create_lfence_instr();
bb.instructions.push(instr);
self.stats.lfence_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_slh_barriers(&mut self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let old_len = block.instructions.len();
for (i, instr) in block.instructions.iter().enumerate() {
if self.instr_may_load(instr) {
new_instrs.push(self.create_lfence_instr());
self.stats.lfence_inserted += 1;
self.stats.slh_loads_hardened += 1;
self.stats.total_barriers += 1;
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
pub fn insert_serialize_barrier(&mut self, bb: &mut MachineBasicBlock) {
let mut instr = MachineInstr::new(0);
if self.has_serialize {
instr.opcode = opc(X86Opcode::NOP);
instr
.operands
.push(MachineOperand::Imm(0xF30F01E8_u32 as i64));
self.stats.serialize_inserted += 1;
} else {
instr.opcode = opc(X86Opcode::NOP);
instr.operands.push(MachineOperand::Imm(0x0FA2_u32 as i64));
self.stats.cpuid_inserted += 1;
}
bb.instructions.push(instr);
self.stats.total_barriers += 1;
}
fn instr_may_load(&self, instr: &MachineInstr) -> bool {
let op = instr.opcode;
if op == opc(X86Opcode::MOV) || op == opc(X86Opcode::MOVSX) || op == opc(X86Opcode::MOVZX) {
return true;
}
if op == opc(X86Opcode::ADD)
|| op == opc(X86Opcode::SUB)
|| op == opc(X86Opcode::AND)
|| op == opc(X86Opcode::OR)
|| op == opc(X86Opcode::XOR)
|| op == opc(X86Opcode::CMP)
{
return true;
}
if op == opc(X86Opcode::MOVSS)
|| op == opc(X86Opcode::MOVSD)
|| op == opc(X86Opcode::MOVD)
|| op == opc(X86Opcode::MOVQ)
{
return true;
}
false
}
pub fn is_indirect_branch(&self, instr: &MachineInstr) -> bool {
let op = instr.opcode;
op == opc(X86Opcode::JMP) || op == opc(X86Opcode::CALL) || op == opc(X86Opcode::RET)
}
pub fn is_indirect_call(&self, instr: &MachineInstr) -> bool {
instr.opcode == opc(X86Opcode::CALL)
}
pub fn is_return(&self, instr: &MachineInstr) -> bool {
instr.opcode == opc(X86Opcode::RET)
}
}
impl X86RetireControl {
pub fn insert_ibpb(&mut self, bb: &mut MachineBasicBlock) {
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx
.operands
.push(MachineOperand::Imm(X86_IBPB_MSR as i64));
bb.instructions.push(mov_ecx);
let mut mov_eax = MachineInstr::new(opc(X86Opcode::MOV));
mov_eax.operands.push(MachineOperand::PhysReg(EAX));
mov_eax
.operands
.push(MachineOperand::Imm(X86_IBPB_CMD as i64));
bb.instructions.push(mov_eax);
let mut xor_edx = MachineInstr::new(opc(X86Opcode::XOR));
xor_edx.operands.push(MachineOperand::PhysReg(EDX));
xor_edx.operands.push(MachineOperand::PhysReg(EDX));
bb.instructions.push(xor_edx);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64)); bb.instructions.push(wrmsr);
self.stats.ibpb_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_ibrs_on(&mut self, bb: &mut MachineBasicBlock) {
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut or_eax = MachineInstr::new(opc(X86Opcode::OR));
or_eax.operands.push(MachineOperand::PhysReg(EAX));
or_eax.operands.push(MachineOperand::Imm(0x1));
bb.instructions.push(or_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.ibrs_on_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_ibrs_off(&mut self, bb: &mut MachineBasicBlock) {
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut and_eax = MachineInstr::new(opc(X86Opcode::AND));
and_eax.operands.push(MachineOperand::PhysReg(EAX));
and_eax.operands.push(MachineOperand::Imm(!0x1i64));
bb.instructions.push(and_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.ibrs_off_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_stibp_on(&mut self, bb: &mut MachineBasicBlock) {
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut or_eax = MachineInstr::new(opc(X86Opcode::OR));
or_eax.operands.push(MachineOperand::PhysReg(EAX));
or_eax.operands.push(MachineOperand::Imm(0x2));
bb.instructions.push(or_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.stibp_on_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_stibp_off(&mut self, bb: &mut MachineBasicBlock) {
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut and_eax = MachineInstr::new(opc(X86Opcode::AND));
and_eax.operands.push(MachineOperand::PhysReg(EAX));
and_eax.operands.push(MachineOperand::Imm(!0x2i64));
bb.instructions.push(and_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.stibp_off_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_full_indirect_barrier(&mut self, bb: &mut MachineBasicBlock) {
self.insert_ibpb(bb);
self.insert_ibrs_on(bb);
if self.stibp_mode != X86STIBPMode::Off {
self.insert_stibp_on(bb);
}
}
pub fn insert_indirect_barrier_clear(&mut self, bb: &mut MachineBasicBlock) {
if self.stibp_mode != X86STIBPMode::Off {
self.insert_stibp_off(bb);
}
self.insert_ibrs_off(bb);
}
}
impl X86RetireControl {
pub fn insert_ssbd_on(&mut self, bb: &mut MachineBasicBlock) {
if self.ssb_no {
return; }
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut or_eax = MachineInstr::new(opc(X86Opcode::OR));
or_eax.operands.push(MachineOperand::PhysReg(EAX));
or_eax.operands.push(MachineOperand::Imm(0x4));
bb.instructions.push(or_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.ssbd_on_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_ssbd_off(&mut self, bb: &mut MachineBasicBlock) {
if self.ssb_no {
return;
}
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut and_eax = MachineInstr::new(opc(X86Opcode::AND));
and_eax.operands.push(MachineOperand::PhysReg(EAX));
and_eax.operands.push(MachineOperand::Imm(!0x4i64));
bb.instructions.push(and_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.ssbd_off_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_ssb_barrier(&mut self, bb: &mut MachineBasicBlock) {
if self.lfence_is_serializing {
self.insert_lfence_barrier(bb);
}
}
}
impl X86RetireControl {
pub fn insert_psfd_on(&mut self, bb: &mut MachineBasicBlock) {
if !self.has_psfd {
return;
}
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut or_eax = MachineInstr::new(opc(X86Opcode::OR));
or_eax.operands.push(MachineOperand::PhysReg(EAX));
or_eax.operands.push(MachineOperand::Imm(0x8)); bb.instructions.push(or_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.psfd_on_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_psfd_off(&mut self, bb: &mut MachineBasicBlock) {
if !self.has_psfd {
return;
}
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
let mut and_eax = MachineInstr::new(opc(X86Opcode::AND));
and_eax.operands.push(MachineOperand::PhysReg(EAX));
and_eax.operands.push(MachineOperand::Imm(!0x8i64));
bb.instructions.push(and_eax);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.psfd_off_inserted += 1;
self.stats.total_barriers += 1;
}
}
impl X86RetireControl {
pub fn stuff_return_stack_buffer(&mut self, mf: &mut MachineFunction, depth: usize) {
if !self.enable_rsb_stuffing {
return;
}
let actual_depth = depth.min(self.rsb_stuff_depth).min(X86_RSB_STUFF_MAX_DEPTH);
if actual_depth == 0 {
return;
}
if mf.blocks.is_empty() {
return;
}
for _iter in 0..self.rsb_stuff_iterations {
for i in 0..actual_depth {
let label = format!(".Lrsb_stuff_{}", i);
let mut call_instr = MachineInstr::new(opc(X86Opcode::CALL));
call_instr
.operands
.push(MachineOperand::Label(label.clone()));
mf.blocks[0].instructions.push(call_instr);
let target_label = format!(".Lrsb_target_{}", i);
let past_label = format!(".Lrsb_past_{}", i);
let mut pause_instr = MachineInstr::new(opc(X86Opcode::NOP));
pause_instr
.operands
.push(MachineOperand::Imm(0xF390_u32 as i64)); self.stats.rsb_stuff_total_instructions += 1;
}
let mut add_rsp = MachineInstr::new(opc(X86Opcode::ADD));
add_rsp.operands.push(MachineOperand::PhysReg(RSP));
add_rsp
.operands
.push(MachineOperand::Imm((actual_depth * 8) as i64));
mf.blocks[0].instructions.push(add_rsp);
self.stats.rsb_stuff_total_instructions += 1;
}
self.stats.rsb_stuff_sequences += 1;
self.stats.total_barriers += 1;
}
pub fn stuff_rsb_intel_sequence(&mut self, bb: &mut MachineBasicBlock, depth: usize) {
let actual_depth = depth.min(self.rsb_stuff_depth).min(X86_RSB_STUFF_MAX_DEPTH);
if actual_depth == 0 {
return;
}
let mut mov_counter = MachineInstr::new(opc(X86Opcode::MOV));
mov_counter.operands.push(MachineOperand::PhysReg(R11));
mov_counter
.operands
.push(MachineOperand::Imm(actual_depth as i64));
bb.instructions.push(mov_counter);
let mut dec_counter = MachineInstr::new(opc(X86Opcode::DEC));
dec_counter.operands.push(MachineOperand::PhysReg(R11));
bb.instructions.push(dec_counter);
let exit_label = ".Lrsb_loop_exit";
let mut js_exit = MachineInstr::new(opc(X86Opcode::JS));
js_exit
.operands
.push(MachineOperand::Label(exit_label.to_string()));
bb.instructions.push(js_exit);
let call_label = ".Lrsb_capture";
let mut call_instr = MachineInstr::new(opc(X86Opcode::CALL));
call_instr
.operands
.push(MachineOperand::Label(call_label.to_string()));
bb.instructions.push(call_instr);
let mut pause_instr = MachineInstr::new(opc(X86Opcode::NOP));
pause_instr
.operands
.push(MachineOperand::Imm(0xF390_u32 as i64));
bb.instructions.push(pause_instr);
let mut lfence_instr = self.create_lfence_instr();
bb.instructions.push(lfence_instr);
let loop_label = ".Lrsb_loop_top";
let mut jmp_back = MachineInstr::new(opc(X86Opcode::JMP));
jmp_back
.operands
.push(MachineOperand::Label(loop_label.to_string()));
bb.instructions.push(jmp_back);
let mut add_rsp = MachineInstr::new(opc(X86Opcode::ADD));
add_rsp.operands.push(MachineOperand::PhysReg(RSP));
add_rsp
.operands
.push(MachineOperand::Imm((actual_depth * 8) as i64));
bb.instructions.push(add_rsp);
self.stats.rsb_stuff_sequences += 1;
self.stats.rsb_stuff_total_instructions += 5 + actual_depth * 4;
}
pub fn stuff_rsb_amd_sequence(&mut self, bb: &mut MachineBasicBlock, depth: usize) {
let actual_depth = depth.min(self.rsb_stuff_depth).min(X86_RSB_STUFF_MAX_DEPTH);
if actual_depth == 0 {
return;
}
for i in 0..actual_depth {
let label = format!(".Lrsb_amd_{}", i);
let mut call_instr = MachineInstr::new(opc(X86Opcode::CALL));
call_instr
.operands
.push(MachineOperand::Label(label.clone()));
bb.instructions.push(call_instr);
let mut int3_instr = MachineInstr::new(opc(X86Opcode::INT3));
bb.instructions.push(int3_instr);
let mut lfence = self.create_lfence_instr();
bb.instructions.push(lfence);
}
let mut add_rsp = MachineInstr::new(opc(X86Opcode::ADD));
add_rsp.operands.push(MachineOperand::PhysReg(RSP));
add_rsp
.operands
.push(MachineOperand::Imm((actual_depth * 8) as i64));
bb.instructions.push(add_rsp);
self.stats.rsb_stuff_sequences += 1;
self.stats.rsb_stuff_total_instructions += actual_depth * 3 + 1;
}
pub fn stuff_rsb_auto(&mut self, mf: &mut MachineFunction, depth: usize) {
let cpu_lower = self.subtarget.cpu.to_lowercase();
let entry_name = mf.blocks[0].name.clone();
if cpu_lower.contains("znver") || cpu_lower.contains("amd") {
for block in &mut mf.blocks {
if block.name.contains("entry") || block.name == entry_name {
self.stuff_rsb_amd_sequence(block, depth);
break;
}
}
} else {
for block in &mut mf.blocks {
if block.name.contains("entry") || block.name == entry_name {
self.stuff_rsb_intel_sequence(block, depth);
break;
}
}
}
}
}
impl X86RetireControl {
pub fn insert_bti_barriers(&mut self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &block.instructions {
if self.is_indirect_branch(instr) || self.is_indirect_call(instr) {
new_instrs.push(self.create_lfence_instr());
self.stats.lfence_inserted += 1;
self.stats.bti_barriers_inserted += 1;
self.stats.total_barriers += 1;
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
pub fn insert_ibpb_before_indirect_branches(&mut self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in block.instructions.iter() {
if self.is_indirect_branch(instr) {
new_instrs.push(self.create_lfence_instr());
self.stats.ibpb_inserted += 1;
self.stats.bti_barriers_inserted += 1;
self.stats.total_barriers += 1;
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
}
impl X86RetireControl {
pub fn apply_speculative_load_hardening(&mut self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &block.instructions {
let is_load = self.instr_may_load(instr);
new_instrs.push(instr.clone());
if is_load {
if self.lfence_is_serializing || self.force_barriers {
new_instrs.push(self.create_lfence_instr());
self.stats.lfence_inserted += 1;
self.stats.slh_loads_hardened += 1;
self.stats.total_barriers += 1;
}
}
}
block.instructions = new_instrs;
}
}
pub fn load_feeds_address_in_window(
&self,
_block: &MachineBasicBlock,
_load_idx: usize,
_window: usize,
) -> bool {
true }
pub fn apply_optimized_slh(&mut self, mf: &mut MachineFunction, window: usize) {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for (i, instr) in block.instructions.iter().enumerate() {
let is_load = self.instr_may_load(instr);
new_instrs.push(instr.clone());
if is_load && self.load_feeds_address_in_window(block, i, window) {
if self.lfence_is_serializing || self.force_barriers {
new_instrs.push(self.create_lfence_instr());
self.stats.lfence_inserted += 1;
self.stats.slh_loads_hardened += 1;
self.stats.total_barriers += 1;
}
}
}
block.instructions = new_instrs;
}
}
}
impl X86RetireControl {
pub fn insert_verw_buffer_clear(&mut self, bb: &mut MachineBasicBlock) {
let mut sub_rsp = MachineInstr::new(opc(X86Opcode::SUB));
sub_rsp.operands.push(MachineOperand::PhysReg(RSP));
sub_rsp.operands.push(MachineOperand::Imm(16));
bb.instructions.push(sub_rsp);
let mut verw_instr = MachineInstr::new(opc(X86Opcode::VERW));
verw_instr.operands.push(MachineOperand::PhysReg(RSP));
verw_instr.operands.push(MachineOperand::Imm(0)); bb.instructions.push(verw_instr);
let mut add_rsp = MachineInstr::new(opc(X86Opcode::ADD));
add_rsp.operands.push(MachineOperand::PhysReg(RSP));
add_rsp.operands.push(MachineOperand::Imm(16));
bb.instructions.push(add_rsp);
self.stats.verw_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_verw_on_user_return(&mut self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &block.instructions {
if instr.opcode == opc(X86Opcode::SYSRET) || instr.opcode == opc(X86Opcode::SYSEXIT)
{
let mut sub_rsp = MachineInstr::new(opc(X86Opcode::SUB));
sub_rsp.operands.push(MachineOperand::PhysReg(RSP));
sub_rsp.operands.push(MachineOperand::Imm(16));
new_instrs.push(sub_rsp);
let mut verw = MachineInstr::new(opc(X86Opcode::VERW));
verw.operands.push(MachineOperand::PhysReg(RSP));
verw.operands.push(MachineOperand::Imm(0));
new_instrs.push(verw);
let mut add_rsp = MachineInstr::new(opc(X86Opcode::ADD));
add_rsp.operands.push(MachineOperand::PhysReg(RSP));
add_rsp.operands.push(MachineOperand::Imm(16));
new_instrs.push(add_rsp);
self.stats.verw_inserted += 1;
self.stats.total_barriers += 1;
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
}
impl X86RetireControl {
pub fn insert_l1d_flush(&mut self, bb: &mut MachineBasicBlock) {
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x10B));
bb.instructions.push(mov_ecx);
let mut mov_eax = MachineInstr::new(opc(X86Opcode::MOV));
mov_eax.operands.push(MachineOperand::PhysReg(EAX));
mov_eax.operands.push(MachineOperand::Imm(0x1));
bb.instructions.push(mov_eax);
let mut xor_edx = MachineInstr::new(opc(X86Opcode::XOR));
xor_edx.operands.push(MachineOperand::PhysReg(EDX));
xor_edx.operands.push(MachineOperand::PhysReg(EDX));
bb.instructions.push(xor_edx);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.fill_buffers_inserted += 1;
self.stats.total_barriers += 1;
}
pub fn insert_buffer_clear_on_entry(&mut self, mf: &mut MachineFunction) {
if mf.blocks.is_empty() {
return;
}
if self.has_md_clear {
self.insert_verw_buffer_clear(&mut mf.blocks[0]);
} else {
self.insert_l1d_flush(&mut mf.blocks[0]);
}
}
}
impl X86RetireControl {
pub fn mitigate_retbleed(&mut self, mf: &mut MachineFunction) {
let cpu_lower = self.subtarget.cpu.to_lowercase();
let is_intel = !cpu_lower.contains("znver")
&& !cpu_lower.contains("amd")
&& !cpu_lower.contains("generic");
if self.has_enhanced_ibrs && self.ibrs_mode != X86IBRSMode::None {
return;
}
if self.enable_rsb_stuffing {
self.stuff_rsb_auto(mf, self.rsb_stuff_depth);
self.stats.retbleed_barriers_inserted += 1;
}
if is_intel
&& !self.has_enhanced_ibrs
&& (cpu_lower.contains("skylake")
|| cpu_lower.contains("cascadelake")
|| cpu_lower.contains("cooperlake"))
{
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &block.instructions {
if self.is_return(instr) {
new_instrs.push(self.create_lfence_instr());
self.stats.lfence_inserted += 1;
self.stats.retbleed_barriers_inserted += 1;
self.stats.total_barriers += 1;
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
if cpu_lower.contains("znver1") || cpu_lower.contains("znver2") {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &block.instructions {
if self.is_return(instr) {
new_instrs.push(self.create_lfence_instr());
self.stats.lfence_inserted += 1;
self.stats.retbleed_barriers_inserted += 1;
self.stats.total_barriers += 1;
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
}
pub fn insert_retpoline_for_indirect_branch(
&mut self,
bb: &mut MachineBasicBlock,
instr_idx: usize,
) {
if instr_idx >= bb.instructions.len() {
return;
}
let mut new_seq: Vec<MachineInstr> = Vec::new();
let mut pause = MachineInstr::new(opc(X86Opcode::NOP));
pause.operands.push(MachineOperand::Imm(0xF390_u32 as i64));
new_seq.push(pause);
new_seq.push(self.create_lfence_instr());
let mut int3 = MachineInstr::new(opc(X86Opcode::INT3));
new_seq.push(int3);
bb.instructions.splice(instr_idx..=instr_idx, new_seq);
self.stats.bti_barriers_inserted += 1;
self.stats.total_barriers += 1;
}
}
impl X86RetireControl {
pub fn apply_mitigations(&mut self, mf: &mut MachineFunction) {
if self.ibrs_mode == X86IBRSMode::KernelOnly {
if let Some(entry_bb) = mf.blocks.first_mut() {
self.insert_ibrs_on(entry_bb);
}
}
if self.ibpb_trigger != X86IBPBTrigger::Never {
self.insert_ibpb_barriers_by_policy(mf);
}
if self.enable_rsb_stuffing && !mf.blocks.is_empty() {
let depth = self.rsb_stuff_depth;
self.stuff_return_stack_buffer(mf, depth);
}
if self.ssbd_mode != X86SSBDMode::Off {
for block in &mut mf.blocks {
self.insert_ssbd_on(block);
}
}
if self.has_psfd {
for block in &mut mf.blocks {
self.insert_psfd_on(block);
}
}
self.apply_speculative_load_hardening(mf);
if self.ibpb_trigger == X86IBPBTrigger::OnIndirectBranch
|| self.ibrs_mode != X86IBRSMode::None
{
self.insert_bti_barriers(mf);
}
self.mitigate_retbleed(mf);
if self.use_verw_mitigation && self.has_md_clear {
self.insert_verw_on_user_return(mf);
}
}
fn insert_ibpb_barriers_by_policy(&mut self, mf: &mut MachineFunction) {
match self.ibpb_trigger {
X86IBPBTrigger::Never => {}
X86IBPBTrigger::OnContextSwitch => {
if let Some(entry_bb) = mf.blocks.first_mut() {
self.insert_ibpb(entry_bb);
}
}
X86IBPBTrigger::OnPrivilegeTransition => {
if let Some(entry_bb) = mf.blocks.first_mut() {
self.insert_ibpb(entry_bb);
}
if let Some(exit_bb) = mf.blocks.last_mut() {
self.insert_ibpb(exit_bb);
}
}
X86IBPBTrigger::OnIndirectBranch => {
self.insert_ibpb_before_indirect_branches(mf);
}
X86IBPBTrigger::AlwaysOnUserReturn => {
for block in &mut mf.blocks {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &block.instructions {
if instr.opcode == opc(X86Opcode::SYSRET)
|| instr.opcode == opc(X86Opcode::SYSEXIT)
|| self.is_return(instr)
{
new_instrs.push(self.create_lfence_instr());
self.stats.ibpb_inserted += 1;
self.stats.total_barriers += 1;
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
}
}
}
impl X86RetireControl {
pub fn emit_set_spec_ctrl_msr(&mut self, bb: &mut MachineBasicBlock, value: u64) {
let low = (value & 0xFFFF_FFFF) as i64;
let high = ((value >> 32) & 0xFFFF_FFFF) as i64;
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut mov_eax = MachineInstr::new(opc(X86Opcode::MOV));
mov_eax.operands.push(MachineOperand::PhysReg(EAX));
mov_eax.operands.push(MachineOperand::Imm(low));
bb.instructions.push(mov_eax);
let mut mov_edx = MachineInstr::new(opc(X86Opcode::MOV));
mov_edx.operands.push(MachineOperand::PhysReg(EDX));
mov_edx.operands.push(MachineOperand::Imm(high));
bb.instructions.push(mov_edx);
let mut wrmsr = MachineInstr::new(opc(X86Opcode::NOP));
wrmsr.operands.push(MachineOperand::Imm(0x0F30_u32 as i64));
bb.instructions.push(wrmsr);
self.stats.total_barriers += 1;
}
pub fn emit_read_spec_ctrl_msr(&mut self, bb: &mut MachineBasicBlock) {
let mut mov_ecx = MachineInstr::new(opc(X86Opcode::MOV));
mov_ecx.operands.push(MachineOperand::PhysReg(ECX));
mov_ecx.operands.push(MachineOperand::Imm(0x48));
bb.instructions.push(mov_ecx);
let mut rdmsr = MachineInstr::new(opc(X86Opcode::NOP));
rdmsr.operands.push(MachineOperand::Imm(0x0F32_u32 as i64));
bb.instructions.push(rdmsr);
}
pub fn emit_mitigation_prologue(&mut self, bb: &mut MachineBasicBlock) {
if self.ibrs_mode == X86IBRSMode::KernelOnly {
self.insert_ibrs_on(bb);
}
if self.ibpb_trigger == X86IBPBTrigger::OnContextSwitch
|| self.ibpb_trigger == X86IBPBTrigger::OnPrivilegeTransition
{
self.insert_ibpb(bb);
}
if self.enable_rsb_stuffing {
self.stuff_rsb_intel_sequence(bb, self.rsb_stuff_depth);
}
if self.ssbd_mode == X86SSBDMode::ForceOn || self.ssbd_mode == X86SSBDMode::ViaMSR {
self.insert_ssbd_on(bb);
}
if self.has_psfd {
self.insert_psfd_on(bb);
}
if self.use_verw_mitigation && self.has_md_clear {
self.insert_verw_buffer_clear(bb);
}
}
pub fn emit_mitigation_epilogue(&mut self, bb: &mut MachineBasicBlock) {
if self.use_verw_mitigation && self.has_md_clear {
self.insert_verw_buffer_clear(bb);
}
if self.stibp_mode == X86STIBPMode::PerProcess || self.stibp_mode == X86STIBPMode::SMTOnly {
self.insert_stibp_off(bb);
}
if self.ibrs_mode == X86IBRSMode::KernelOnly {
self.insert_ibrs_off(bb);
}
}
}
impl X86RetireControl {
pub fn is_vulnerable_to_spectre_v2(&self) -> bool {
if self.has_enhanced_ibrs {
return false; }
if self.ibrs_mode == X86IBRSMode::None && self.ibpb_trigger == X86IBPBTrigger::Never {
return true; }
false
}
pub fn is_vulnerable_to_spectre_v4(&self) -> bool {
if self.ssb_no {
return false; }
self.ssbd_mode == X86SSBDMode::Off
}
pub fn is_vulnerable_to_retbleed(&self) -> bool {
if self.has_enhanced_ibrs && self.ibrs_mode == X86IBRSMode::Enhanced {
return false;
}
if self.pbrsb_no {
return false; }
let cpu_lower = self.subtarget.cpu.to_lowercase();
cpu_lower.contains("skylake")
|| cpu_lower.contains("cascadelake")
|| cpu_lower.contains("cooperlake")
|| cpu_lower.contains("znver1")
|| cpu_lower.contains("znver2")
}
pub fn mitigation_report(&self) -> String {
let mut report = String::new();
report.push_str("=== X86 Retire Control: Mitigation Status ===\n");
report.push_str(&format!(" CPU: {}\n", self.subtarget.cpu));
report.push_str(&format!(
" LFENCE is serializing: {}\n",
self.lfence_is_serializing
));
report.push_str(&format!(" IBRS mode: {}\n", self.ibrs_mode));
report.push_str(&format!(" IBPB trigger: {}\n", self.ibpb_trigger));
report.push_str(&format!(" STIBP mode: {}\n", self.stibp_mode));
report.push_str(&format!(" SSBD mode: {}\n", self.ssbd_mode));
report.push_str(&format!(" RSB stuff depth: {}\n", self.rsb_stuff_depth));
report.push_str(&format!(" Enhanced IBRS: {}\n", self.has_enhanced_ibrs));
report.push_str(&format!(" IBPB on all CPUs: {}\n", self.has_ibpb_all));
report.push_str(&format!(" RSBA (RSB alt): {}\n", self.has_rsba));
report.push_str(&format!(" SSB not vulnerable: {}\n", self.ssb_no));
report.push_str(&format!(" MD_CLEAR supported: {}\n", self.has_md_clear));
report.push_str(&format!(" PBRSB protected: {}\n", self.pbrsb_no));
report.push_str(&format!(" PSFD supported: {}\n", self.has_psfd));
report.push_str(&format!(
" VERW mitigation: {}\n",
self.use_verw_mitigation
));
report.push_str(&format!(
" Spectre v2 vulnerable: {}\n",
self.is_vulnerable_to_spectre_v2()
));
report.push_str(&format!(
" Spectre v4 vulnerable: {}\n",
self.is_vulnerable_to_spectre_v4()
));
report.push_str(&format!(
" Retbleed vulnerable: {}\n",
self.is_vulnerable_to_retbleed()
));
report.push_str(&format!("\n {}\n", self.stats.summary()));
report
}
pub fn reset_stats(&mut self) {
self.stats = RetireControlStats::default();
}
}
#[derive(Debug, Clone, Default)]
pub struct X86SpecCtrlFeatures {
pub has_ibrs_ibpb: bool, pub has_stibp: bool, pub has_l1d_flush: bool, pub has_arch_cap: bool, pub has_ssbd: bool, pub has_md_clear: bool, pub has_serialize: bool, pub has_tsx_ctrl: bool, pub has_psfd: bool, }
impl X86SpecCtrlFeatures {
pub fn from_cpuid_edx(edx: u32) -> Self {
Self {
has_ibrs_ibpb: (edx & (1 << 26)) != 0,
has_stibp: (edx & (1 << 27)) != 0,
has_l1d_flush: (edx & (1 << 28)) != 0,
has_arch_cap: (edx & (1 << 29)) != 0,
has_ssbd: (edx & (1 << 31)) != 0,
has_md_clear: (edx & (1 << 10)) != 0,
has_serialize: (edx & (1 << 14)) != 0,
has_tsx_ctrl: (edx & (1 << 13)) != 0,
has_psfd: false, }
}
}
pub fn detect_spec_ctrl_features(subtarget: &X86Subtarget) -> X86SpecCtrlFeatures {
X86SpecCtrlFeatures {
has_ibrs_ibpb: subtarget.features.contains("ibrs") || subtarget.features.contains("ibpb"),
has_stibp: subtarget.features.contains("stibp"),
has_l1d_flush: subtarget.features.contains("l1d-flush"),
has_arch_cap: subtarget.features.contains("arch-capabilities"),
has_ssbd: subtarget.features.contains("ssbd"),
has_md_clear: subtarget.features.contains("md-clear"),
has_serialize: subtarget.features.contains("serialize"),
has_tsx_ctrl: subtarget.features.contains("tsx-ctrl"),
has_psfd: subtarget.features.contains("psfd"),
}
}
pub fn rsb_depth_for_cpu(cpu: &str) -> usize {
let cpu_lower = cpu.to_lowercase();
if cpu_lower.contains("alderlake") || cpu_lower.contains("raptorlake") {
X86_RSB_DEPTH_INTEL_ALDERLAKE
} else if cpu_lower.contains("icelake")
|| cpu_lower.contains("tigerlake")
|| cpu_lower.contains("rocketlake")
|| cpu_lower.contains("meteorlake")
|| cpu_lower.contains("sapphirerapids")
{
X86_RSB_DEPTH_INTEL_ICELAKE
} else if cpu_lower.contains("znver3")
|| cpu_lower.contains("znver4")
|| cpu_lower.contains("znver5")
{
X86_RSB_DEPTH_AMD_ZEN3
} else if cpu_lower.contains("znver1") || cpu_lower.contains("znver2") {
X86_RSB_DEPTH_AMD_ZEN
} else if cpu_lower.contains("skylake")
|| cpu_lower.contains("cascadelake")
|| cpu_lower.contains("cooperlake")
|| cpu_lower.contains("cannonlake")
|| cpu_lower.contains("haswell")
|| cpu_lower.contains("broadwell")
{
X86_RSB_DEPTH_INTEL_SKYLAKE
} else {
X86_RSB_DEPTH_DEFAULT
}
}
pub fn detect_ibrs_capabilities(subtarget: &X86Subtarget) -> (bool, bool, bool) {
let cpu_lower = subtarget.cpu.to_lowercase();
let has_enhanced_ibrs = subtarget.features.contains("enhanced-ibrs")
|| cpu_lower.contains("icelake")
|| cpu_lower.contains("tigerlake")
|| cpu_lower.contains("rocketlake")
|| cpu_lower.contains("alderlake")
|| cpu_lower.contains("raptorlake")
|| cpu_lower.contains("meteorlake")
|| cpu_lower.contains("sapphirerapids")
|| cpu_lower.contains("znver3")
|| cpu_lower.contains("znver4")
|| cpu_lower.contains("znver5");
let has_ibpb = subtarget.features.contains("ibpb") || !cpu_lower.contains("nehalem");
let has_stibp = subtarget.features.contains("stibp")
|| cpu_lower.contains("skylake")
|| cpu_lower.contains("icelake")
|| cpu_lower.contains("znver2");
(has_enhanced_ibrs, has_ibpb, has_stibp)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::{MachineBasicBlock, MachineFunction};
fn make_test_subtarget() -> X86Subtarget {
X86Subtarget::new("x86_64-unknown-linux-gnu", "skylake", "+sse2,+avx2")
}
fn make_test_mf() -> MachineFunction {
let mut mf = MachineFunction::new("test_fn");
let mut bb = MachineBasicBlock {
id: 0,
name: "entry".to_string(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: true,
};
mf.push_block(bb);
mf
}
#[test]
fn test_retire_control_creation() {
let subtarget = make_test_subtarget();
let rc = X86RetireControl::new(&subtarget);
assert_eq!(rc.ibrs_mode, X86IBRSMode::None);
assert_eq!(rc.ibpb_trigger, X86IBPBTrigger::Never);
assert_eq!(rc.lfence_is_serializing, true); assert_eq!(rc.stats.total_barriers, 0);
}
#[test]
fn test_lfence_barrier_insertion() {
let subtarget = make_test_subtarget();
let mut rc = X86RetireControl::new(&subtarget);
let mut mf = make_test_mf();
rc.insert_lfence_barrier(&mut mf.blocks[0]);
assert_eq!(rc.stats.lfence_inserted, 1);
assert_eq!(rc.stats.total_barriers, 1);
assert!(mf.blocks[0].instructions.len() > 0);
}
#[test]
fn test_ibrs_mode_display() {
assert_eq!(format!("{}", X86IBRSMode::None), "none");
assert_eq!(format!("{}", X86IBRSMode::KernelOnly), "kernel-only");
assert_eq!(format!("{}", X86IBRSMode::Enhanced), "enhanced");
assert_eq!(
format!("{}", X86IBRSMode::RetpolineAware),
"retpoline-aware"
);
}
#[test]
fn test_ibpb_trigger_display() {
assert_eq!(format!("{}", X86IBPBTrigger::Never), "never");
assert_eq!(
format!("{}", X86IBPBTrigger::OnContextSwitch),
"on-context-switch"
);
assert_eq!(
format!("{}", X86IBPBTrigger::AlwaysOnUserReturn),
"always-on-user-return"
);
}
#[test]
fn test_stibp_mode_display() {
assert_eq!(format!("{}", X86STIBPMode::Off), "off");
assert_eq!(format!("{}", X86STIBPMode::Always), "always");
}
#[test]
fn test_ssbd_mode_display() {
assert_eq!(format!("{}", X86SSBDMode::Off), "off");
assert_eq!(format!("{}", X86SSBDMode::ForceOn), "force-on");
}
#[test]
fn test_spec_barrier_display() {
assert_eq!(format!("{}", X86SpecBarrier::LFENCE), "LFENCE");
assert_eq!(format!("{}", X86SpecBarrier::IBPB), "IBPB");
assert_eq!(format!("{}", X86SpecBarrier::RSBStuff), "RSB-STUFF");
assert_eq!(format!("{}", X86SpecBarrier::FillBuffers), "FILL-BUFFERS");
}
#[test]
fn test_rsb_depth_for_known_cpus() {
assert_eq!(rsb_depth_for_cpu("skylake"), X86_RSB_DEPTH_INTEL_SKYLAKE);
assert_eq!(rsb_depth_for_cpu("icelake"), X86_RSB_DEPTH_INTEL_ICELAKE);
assert_eq!(rsb_depth_for_cpu("znver3"), X86_RSB_DEPTH_AMD_ZEN3);
assert_eq!(rsb_depth_for_cpu("unknown"), X86_RSB_DEPTH_DEFAULT);
}
#[test]
fn test_ibrs_capability_detection() {
let sub = X86Subtarget::new("x86_64-unknown-linux-gnu", "icelake", "");
let (enh, ibpb, stibp) = detect_ibrs_capabilities(&sub);
assert!(enh);
assert!(ibpb);
assert!(stibp);
}
#[test]
fn test_vulnerability_checks() {
let subtarget = make_test_subtarget();
let mut rc = X86RetireControl::new(&subtarget);
assert!(rc.is_vulnerable_to_spectre_v2());
assert!(rc.is_vulnerable_to_retbleed());
}
#[test]
fn test_mitigation_report() {
let subtarget = make_test_subtarget();
let rc = X86RetireControl::new(&subtarget);
let report = rc.mitigation_report();
assert!(report.contains("skylake"));
assert!(report.contains("LFENCE is serializing: true"));
}
#[test]
fn test_stats_merge() {
let mut s1 = RetireControlStats::default();
s1.lfence_inserted = 5;
s1.total_barriers = 5;
let mut s2 = RetireControlStats::default();
s2.lfence_inserted = 3;
s2.ibpb_inserted = 2;
s2.total_barriers = 5;
s1.merge(&s2);
assert_eq!(s1.lfence_inserted, 8);
assert_eq!(s1.ibpb_inserted, 2);
assert_eq!(s1.total_barriers, 10);
}
#[test]
fn test_detect_lfence_serializing() {
assert!(detect_lfence_serializing("skylake"));
assert!(detect_lfence_serializing("icelake-server"));
assert!(detect_lfence_serializing("znver3"));
assert!(!detect_lfence_serializing("haswell"));
assert!(!detect_lfence_serializing("pentium4"));
}
#[test]
fn test_spec_ctrl_features_from_cpuid() {
let raw_edx: u32 = (1 << 26) | (1 << 27) | (1 << 29) | (1 << 31);
let features = X86SpecCtrlFeatures::from_cpuid_edx(raw_edx);
assert!(features.has_ibrs_ibpb);
assert!(features.has_stibp);
assert!(features.has_arch_cap);
assert!(features.has_ssbd);
assert!(!features.has_md_clear);
}
#[test]
fn test_set_configuration() {
let subtarget = make_test_subtarget();
let mut rc = X86RetireControl::new(&subtarget);
rc.set_ibrs_mode(X86IBRSMode::KernelOnly);
rc.set_ibpb_trigger(X86IBPBTrigger::OnContextSwitch);
rc.set_stibp_mode(X86STIBPMode::SMTOnly);
rc.set_ssbd_mode(X86SSBDMode::ForceOn);
rc.set_rsb_stuff_depth(24);
rc.set_rsb_stuff_iterations(2);
rc.set_force_barriers(true);
rc.set_use_verw_mitigation(true);
assert_eq!(rc.ibrs_mode, X86IBRSMode::KernelOnly);
assert_eq!(rc.ibpb_trigger, X86IBPBTrigger::OnContextSwitch);
assert_eq!(rc.stibp_mode, X86STIBPMode::SMTOnly);
assert_eq!(rc.ssbd_mode, X86SSBDMode::ForceOn);
assert_eq!(rc.rsb_stuff_depth, 24);
assert_eq!(rc.rsb_stuff_iterations, 2);
assert!(rc.force_barriers);
assert!(rc.use_verw_mitigation);
}
#[test]
fn test_rsb_stuff_depth_clamp() {
let subtarget = make_test_subtarget();
let mut rc = X86RetireControl::new(&subtarget);
rc.set_rsb_stuff_depth(X86_RSB_STUFF_MAX_DEPTH * 2); assert_eq!(rc.rsb_stuff_depth, X86_RSB_STUFF_MAX_DEPTH);
}
#[test]
fn test_is_indirect_branch_detection() {
let subtarget = make_test_subtarget();
let rc = X86RetireControl::new(&subtarget);
let call = MachineInstr::new(opc(X86Opcode::CALL));
let ret = MachineInstr::new(opc(X86Opcode::RET));
let add = MachineInstr::new(opc(X86Opcode::ADD));
assert!(rc.is_indirect_branch(&call));
assert!(rc.is_indirect_branch(&ret));
assert!(!rc.is_indirect_branch(&add));
}
}