llvm-project/llvm/lib/Target/ARM/ARMSLSHardening.cpp
Anatoly Trosinenko b12449fb28
[CodeGen] Refactor and document ThunkInserter (#97468)
In preparation for supporting BLRA* instructions in SLS Hardening on
AArch64, refactor ThunkInserter class.

The main intention of this commit is to document the way to merge the
BLR-rewriting logic of the AArch64SLSHardening pass into the
SLSBLRThunkInserter class. This makes it possible to only call
createThunkFunction for the thunks that are actually referenced.
Ultimately, it will prevent SLSBLRThunkInserter from unconditionally
generating about 1800 thunk functions corresponding to every possible
combination of operands passed to BLRAA or BLRAB instructions.

This particular commit does not affect the generated machine code and
consists of the following changes:
* document the existing behavior of ThunkInserter class
* introduce ThunkInserterPass template class to get rid of mostly
identical boilerplate code in ARM, AArch64 and X86 implementations
* move the InsertedThunks parameter from `mayUseThunk` to `insertThunks`
method
2024-07-04 17:03:47 +03:00

403 lines
15 KiB
C++

//===- ARMSLSHardening.cpp - Harden Straight Line Missspeculation ---------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file contains a pass to insert code to mitigate against side channel
// vulnerabilities that may happen under straight line miss-speculation.
//
//===----------------------------------------------------------------------===//
#include "ARM.h"
#include "ARMInstrInfo.h"
#include "ARMSubtarget.h"
#include "llvm/CodeGen/IndirectThunks.h"
#include "llvm/CodeGen/MachineBasicBlock.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/IR/DebugLoc.h"
#include <cassert>
using namespace llvm;
#define DEBUG_TYPE "arm-sls-hardening"
#define ARM_SLS_HARDENING_NAME "ARM sls hardening pass"
namespace {
class ARMSLSHardening : public MachineFunctionPass {
public:
const TargetInstrInfo *TII;
const ARMSubtarget *ST;
static char ID;
ARMSLSHardening() : MachineFunctionPass(ID) {
initializeARMSLSHardeningPass(*PassRegistry::getPassRegistry());
}
bool runOnMachineFunction(MachineFunction &Fn) override;
StringRef getPassName() const override { return ARM_SLS_HARDENING_NAME; }
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
MachineFunctionPass::getAnalysisUsage(AU);
}
private:
bool hardenReturnsAndBRs(MachineBasicBlock &MBB) const;
bool hardenIndirectCalls(MachineBasicBlock &MBB) const;
MachineBasicBlock &
ConvertIndirectCallToIndirectJump(MachineBasicBlock &MBB,
MachineBasicBlock::iterator) const;
};
} // end anonymous namespace
char ARMSLSHardening::ID = 0;
INITIALIZE_PASS(ARMSLSHardening, "arm-sls-hardening",
ARM_SLS_HARDENING_NAME, false, false)
static void insertSpeculationBarrier(const ARMSubtarget *ST,
MachineBasicBlock &MBB,
MachineBasicBlock::iterator MBBI,
DebugLoc DL,
bool AlwaysUseISBDSB = false) {
assert(MBBI != MBB.begin() &&
"Must not insert SpeculationBarrierEndBB as only instruction in MBB.");
assert(std::prev(MBBI)->isBarrier() &&
"SpeculationBarrierEndBB must only follow unconditional control flow "
"instructions.");
assert(std::prev(MBBI)->isTerminator() &&
"SpeculationBarrierEndBB must only follow terminators.");
const TargetInstrInfo *TII = ST->getInstrInfo();
assert(ST->hasDataBarrier() || ST->hasSB());
bool ProduceSB = ST->hasSB() && !AlwaysUseISBDSB;
unsigned BarrierOpc =
ProduceSB ? (ST->isThumb() ? ARM::t2SpeculationBarrierSBEndBB
: ARM::SpeculationBarrierSBEndBB)
: (ST->isThumb() ? ARM::t2SpeculationBarrierISBDSBEndBB
: ARM::SpeculationBarrierISBDSBEndBB);
if (MBBI == MBB.end() || !isSpeculationBarrierEndBBOpcode(MBBI->getOpcode()))
BuildMI(MBB, MBBI, DL, TII->get(BarrierOpc));
}
bool ARMSLSHardening::runOnMachineFunction(MachineFunction &MF) {
ST = &MF.getSubtarget<ARMSubtarget>();
TII = MF.getSubtarget().getInstrInfo();
bool Modified = false;
for (auto &MBB : MF) {
Modified |= hardenReturnsAndBRs(MBB);
Modified |= hardenIndirectCalls(MBB);
}
return Modified;
}
bool ARMSLSHardening::hardenReturnsAndBRs(MachineBasicBlock &MBB) const {
if (!ST->hardenSlsRetBr())
return false;
assert(!ST->isThumb1Only());
bool Modified = false;
MachineBasicBlock::iterator MBBI = MBB.getFirstTerminator(), E = MBB.end();
MachineBasicBlock::iterator NextMBBI;
for (; MBBI != E; MBBI = NextMBBI) {
MachineInstr &MI = *MBBI;
NextMBBI = std::next(MBBI);
if (isIndirectControlFlowNotComingBack(MI)) {
assert(MI.isTerminator());
assert(!TII->isPredicated(MI));
insertSpeculationBarrier(ST, MBB, std::next(MBBI), MI.getDebugLoc());
Modified = true;
}
}
return Modified;
}
static const char SLSBLRNamePrefix[] = "__llvm_slsblr_thunk_";
static const struct ThunkNameRegMode {
const char* Name;
Register Reg;
bool isThumb;
} SLSBLRThunks[] = {
{"__llvm_slsblr_thunk_arm_r0", ARM::R0, false},
{"__llvm_slsblr_thunk_arm_r1", ARM::R1, false},
{"__llvm_slsblr_thunk_arm_r2", ARM::R2, false},
{"__llvm_slsblr_thunk_arm_r3", ARM::R3, false},
{"__llvm_slsblr_thunk_arm_r4", ARM::R4, false},
{"__llvm_slsblr_thunk_arm_r5", ARM::R5, false},
{"__llvm_slsblr_thunk_arm_r6", ARM::R6, false},
{"__llvm_slsblr_thunk_arm_r7", ARM::R7, false},
{"__llvm_slsblr_thunk_arm_r8", ARM::R8, false},
{"__llvm_slsblr_thunk_arm_r9", ARM::R9, false},
{"__llvm_slsblr_thunk_arm_r10", ARM::R10, false},
{"__llvm_slsblr_thunk_arm_r11", ARM::R11, false},
{"__llvm_slsblr_thunk_arm_sp", ARM::SP, false},
{"__llvm_slsblr_thunk_arm_pc", ARM::PC, false},
{"__llvm_slsblr_thunk_thumb_r0", ARM::R0, true},
{"__llvm_slsblr_thunk_thumb_r1", ARM::R1, true},
{"__llvm_slsblr_thunk_thumb_r2", ARM::R2, true},
{"__llvm_slsblr_thunk_thumb_r3", ARM::R3, true},
{"__llvm_slsblr_thunk_thumb_r4", ARM::R4, true},
{"__llvm_slsblr_thunk_thumb_r5", ARM::R5, true},
{"__llvm_slsblr_thunk_thumb_r6", ARM::R6, true},
{"__llvm_slsblr_thunk_thumb_r7", ARM::R7, true},
{"__llvm_slsblr_thunk_thumb_r8", ARM::R8, true},
{"__llvm_slsblr_thunk_thumb_r9", ARM::R9, true},
{"__llvm_slsblr_thunk_thumb_r10", ARM::R10, true},
{"__llvm_slsblr_thunk_thumb_r11", ARM::R11, true},
{"__llvm_slsblr_thunk_thumb_sp", ARM::SP, true},
{"__llvm_slsblr_thunk_thumb_pc", ARM::PC, true},
};
// An enum for tracking whether Arm and Thumb thunks have been inserted into the
// current module so far.
enum ArmInsertedThunks { NoThunk = 0, ArmThunk = 1, ThumbThunk = 2 };
inline ArmInsertedThunks &operator|=(ArmInsertedThunks &X,
ArmInsertedThunks Y) {
return X = static_cast<ArmInsertedThunks>(X | Y);
}
namespace {
struct SLSBLRThunkInserter
: ThunkInserter<SLSBLRThunkInserter, ArmInsertedThunks> {
const char *getThunkPrefix() { return SLSBLRNamePrefix; }
bool mayUseThunk(const MachineFunction &MF) {
ComdatThunks &= !MF.getSubtarget<ARMSubtarget>().hardenSlsNoComdat();
return MF.getSubtarget<ARMSubtarget>().hardenSlsBlr();
}
ArmInsertedThunks insertThunks(MachineModuleInfo &MMI, MachineFunction &MF,
ArmInsertedThunks InsertedThunks);
void populateThunk(MachineFunction &MF);
private:
bool ComdatThunks = true;
};
} // namespace
ArmInsertedThunks
SLSBLRThunkInserter::insertThunks(MachineModuleInfo &MMI, MachineFunction &MF,
ArmInsertedThunks InsertedThunks) {
if ((InsertedThunks & ArmThunk &&
!MF.getSubtarget<ARMSubtarget>().isThumb()) ||
(InsertedThunks & ThumbThunk &&
MF.getSubtarget<ARMSubtarget>().isThumb()))
return NoThunk;
// FIXME: It probably would be possible to filter which thunks to produce
// based on which registers are actually used in indirect calls in this
// function. But would that be a worthwhile optimization?
const ARMSubtarget *ST = &MF.getSubtarget<ARMSubtarget>();
for (auto T : SLSBLRThunks)
if (ST->isThumb() == T.isThumb)
createThunkFunction(MMI, T.Name, ComdatThunks,
T.isThumb ? "+thumb-mode" : "");
return ST->isThumb() ? ThumbThunk : ArmThunk;
}
void SLSBLRThunkInserter::populateThunk(MachineFunction &MF) {
assert(MF.getFunction().hasComdat() == ComdatThunks &&
"ComdatThunks value changed since MF creation");
// FIXME: How to better communicate Register number, rather than through
// name and lookup table?
assert(MF.getName().starts_with(getThunkPrefix()));
auto ThunkIt = llvm::find_if(
SLSBLRThunks, [&MF](auto T) { return T.Name == MF.getName(); });
assert(ThunkIt != std::end(SLSBLRThunks));
Register ThunkReg = ThunkIt->Reg;
bool isThumb = ThunkIt->isThumb;
const TargetInstrInfo *TII = MF.getSubtarget<ARMSubtarget>().getInstrInfo();
MachineBasicBlock *Entry = &MF.front();
Entry->clear();
// These thunks need to consist of the following instructions:
// __llvm_slsblr_thunk_(arm/thumb)_rN:
// bx rN
// barrierInsts
Entry->addLiveIn(ThunkReg);
if (isThumb)
BuildMI(Entry, DebugLoc(), TII->get(ARM::tBX))
.addReg(ThunkReg)
.add(predOps(ARMCC::AL));
else
BuildMI(Entry, DebugLoc(), TII->get(ARM::BX))
.addReg(ThunkReg);
// Make sure the thunks do not make use of the SB extension in case there is
// a function somewhere that will call to it that for some reason disabled
// the SB extension locally on that function, even though it's enabled for
// the module otherwise. Therefore set AlwaysUseISBSDB to true.
insertSpeculationBarrier(&MF.getSubtarget<ARMSubtarget>(), *Entry,
Entry->end(), DebugLoc(), true /*AlwaysUseISBDSB*/);
}
MachineBasicBlock &ARMSLSHardening::ConvertIndirectCallToIndirectJump(
MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI) const {
// Transform an indirect call to an indirect jump as follows:
// Before:
// |-----------------------------|
// | ... |
// | instI |
// | BLX rN |
// | instJ |
// | ... |
// |-----------------------------|
//
// After:
// |---------- -------------------------|
// | ... |
// | instI |
// | *call* __llvm_slsblr_thunk_mode_xN |
// | instJ |
// | ... |
// |--------------------------------------|
//
// __llvm_slsblr_thunk_mode_xN:
// |-----------------------------|
// | BX rN |
// | barrierInsts |
// |-----------------------------|
//
// The __llvm_slsblr_thunk_mode_xN thunks are created by the
// SLSBLRThunkInserter.
// This function merely needs to transform an indirect call to a direct call
// to __llvm_slsblr_thunk_xN.
MachineInstr &IndirectCall = *MBBI;
assert(isIndirectCall(IndirectCall) && !IndirectCall.isReturn());
int RegOpIdxOnIndirectCall = -1;
bool isThumb;
switch (IndirectCall.getOpcode()) {
case ARM::BLX: // !isThumb2
case ARM::BLX_noip: // !isThumb2
isThumb = false;
RegOpIdxOnIndirectCall = 0;
break;
case ARM::tBLXr: // isThumb2
case ARM::tBLXr_noip: // isThumb2
isThumb = true;
RegOpIdxOnIndirectCall = 2;
break;
default:
llvm_unreachable("unhandled Indirect Call");
}
Register Reg = IndirectCall.getOperand(RegOpIdxOnIndirectCall).getReg();
// Since linkers are allowed to clobber R12 on function calls, the above
// mitigation only works if the original indirect call instruction was not
// using R12. Code generation before must make sure that no indirect call
// using R12 was produced if the mitigation is enabled.
// Also, the transformation is incorrect if the indirect call uses LR, so
// also have to avoid that.
assert(Reg != ARM::R12 && Reg != ARM::LR);
bool RegIsKilled = IndirectCall.getOperand(RegOpIdxOnIndirectCall).isKill();
DebugLoc DL = IndirectCall.getDebugLoc();
MachineFunction &MF = *MBBI->getMF();
auto ThunkIt = llvm::find_if(SLSBLRThunks, [Reg, isThumb](auto T) {
return T.Reg == Reg && T.isThumb == isThumb;
});
assert(ThunkIt != std::end(SLSBLRThunks));
Module *M = MF.getFunction().getParent();
const GlobalValue *GV = cast<GlobalValue>(M->getNamedValue(ThunkIt->Name));
MachineInstr *BL =
isThumb ? BuildMI(MBB, MBBI, DL, TII->get(ARM::tBL))
.addImm(IndirectCall.getOperand(0).getImm())
.addReg(IndirectCall.getOperand(1).getReg())
.addGlobalAddress(GV)
: BuildMI(MBB, MBBI, DL, TII->get(ARM::BL)).addGlobalAddress(GV);
// Now copy the implicit operands from IndirectCall to BL and copy other
// necessary info.
// However, both IndirectCall and BL instructions implictly use SP and
// implicitly define LR. Blindly copying implicit operands would result in SP
// and LR operands to be present multiple times. While this may not be too
// much of an issue, let's avoid that for cleanliness, by removing those
// implicit operands from the BL created above before we copy over all
// implicit operands from the IndirectCall.
int ImpLROpIdx = -1;
int ImpSPOpIdx = -1;
for (unsigned OpIdx = BL->getNumExplicitOperands();
OpIdx < BL->getNumOperands(); OpIdx++) {
MachineOperand Op = BL->getOperand(OpIdx);
if (!Op.isReg())
continue;
if (Op.getReg() == ARM::LR && Op.isDef())
ImpLROpIdx = OpIdx;
if (Op.getReg() == ARM::SP && !Op.isDef())
ImpSPOpIdx = OpIdx;
}
assert(ImpLROpIdx != -1);
assert(ImpSPOpIdx != -1);
int FirstOpIdxToRemove = std::max(ImpLROpIdx, ImpSPOpIdx);
int SecondOpIdxToRemove = std::min(ImpLROpIdx, ImpSPOpIdx);
BL->removeOperand(FirstOpIdxToRemove);
BL->removeOperand(SecondOpIdxToRemove);
// Now copy over the implicit operands from the original IndirectCall
BL->copyImplicitOps(MF, IndirectCall);
MF.moveCallSiteInfo(&IndirectCall, BL);
// Also add the register called in the IndirectCall as being used in the
// called thunk.
BL->addOperand(MachineOperand::CreateReg(Reg, false /*isDef*/, true /*isImp*/,
RegIsKilled /*isKill*/));
// Remove IndirectCallinstruction
MBB.erase(MBBI);
return MBB;
}
bool ARMSLSHardening::hardenIndirectCalls(MachineBasicBlock &MBB) const {
if (!ST->hardenSlsBlr())
return false;
bool Modified = false;
MachineBasicBlock::iterator MBBI = MBB.begin(), E = MBB.end();
MachineBasicBlock::iterator NextMBBI;
for (; MBBI != E; MBBI = NextMBBI) {
MachineInstr &MI = *MBBI;
NextMBBI = std::next(MBBI);
// Tail calls are both indirect calls and "returns".
// They are also indirect jumps, so should be handled by sls-harden-retbr,
// rather than sls-harden-blr.
if (isIndirectCall(MI) && !MI.isReturn()) {
ConvertIndirectCallToIndirectJump(MBB, MBBI);
Modified = true;
}
}
return Modified;
}
FunctionPass *llvm::createARMSLSHardeningPass() {
return new ARMSLSHardening();
}
namespace {
class ARMIndirectThunks : public ThunkInserterPass<SLSBLRThunkInserter> {
public:
static char ID;
ARMIndirectThunks() : ThunkInserterPass(ID) {}
StringRef getPassName() const override { return "ARM Indirect Thunks"; }
};
} // end anonymous namespace
char ARMIndirectThunks::ID = 0;
FunctionPass *llvm::createARMIndirectThunks() {
return new ARMIndirectThunks();
}