In 'asm goto' statements ('callbr' in LLVM IR), you can specify one or
more labels / basic blocks in the containing function which the assembly
code might jump to. If you're also compiling with branch target
enforcement via BTI, then previously listing a basic block as a possible
jump destination of an asm goto would cause a BTI instruction to be
placed at the start of the block, in case the assembly code used an
_indirect_ branch instruction (i.e. to a destination address read from a
register) to jump to that location. Now it doesn't do that any more:
branches to destination labels from the assembly code are assumed to be
direct branches (to a relative offset encoded in the instruction), which
don't require a BTI at their destination.
This change was proposed in https://discourse.llvm.org/t/85845 and there
seemed to be no disagreement. The rationale is:
1. it brings clang's handling of asm goto in Arm and AArch64 in line
with gcc's, which didn't generate BTIs at the target labels in the first
place.
2. it improves performance in the Linux kernel, which uses a lot of 'asm
goto' in which the assembly language just contains a NOP, and the
label's address is saved elsewhere to let the kernel self-modify at run
time to swap between the original NOP and a direct branch to the label.
This allows hot code paths to be instrumented for debugging, at only the
cost of a NOP when the instrumentation is turned off, instead of the
larger cost of an indirect branch. In this situation a BTI is
unnecessary (if the branch happens it's direct), and since the code
paths are hot, also a noticeable performance hit.
Implementation:
`SelectionDAGBuilder::visitCallBr` is the place where 'asm goto' target
labels are handled. It calls `setIsInlineAsmBrIndirectTarget()` on each
target `MachineBasicBlock`. Previously it also called
`setMachineBlockAddressTaken()`, which made `hasAddressTaken()` return
true, which caused a BTI to be added in the Arm backends.
Now `visitCallBr` doesn't call `setMachineBlockAddressTaken()` any more
on asm goto targets, but `hasAddressTaken()` also checks the flag set by
`setIsInlineAsmBrIndirectTarget()`. So call sites that were using
`hasAddressTaken()` don't need to be modified. But the Arm backends
don't call `hasAddressTaken()` any more: instead they test two more
specific query functions that cover all the reasons `hasAddressTaken()`
might have returned true _except_ being an asm goto target.
Testing:
The new test `AArch64/callbr-asm-label-bti.ll` is testing the actual
change, where it expects not to see a `bti` instruction after
`[[LABEL]]`. The rest of the test changes are all churn, due to the
flags on basic blocks changing. Actual output code hasn't changed in any
of the existing tests, only comments and diagnostics.
Further work:
`RISCVIndirectBranchTracking.cpp` and `X86IndirectBranchTracking.cpp`
also call `hasAddressTaken()` in a way that might benefit from using the
same more specific check I've put in `ARMBranchTargets.cpp` and
`AArch64BranchTargets.cpp`. But I'm not sure of that, so in this commit
I've only changed the Arm backends, and left those alone.
154 lines
5.8 KiB
C++
154 lines
5.8 KiB
C++
//===-- AArch64BranchTargets.cpp -- Harden code using v8.5-A BTI extension -==//
|
|
//
|
|
// 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 pass inserts BTI instructions at the start of every function and basic
|
|
// block which could be indirectly called. The hardware will (when enabled)
|
|
// trap when an indirect branch or call instruction targets an instruction
|
|
// which is not a valid BTI instruction. This is intended to guard against
|
|
// control-flow hijacking attacks. Note that this does not do anything for RET
|
|
// instructions, as they can be more precisely protected by return address
|
|
// signing.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "AArch64MachineFunctionInfo.h"
|
|
#include "AArch64Subtarget.h"
|
|
#include "llvm/CodeGen/MachineFunctionPass.h"
|
|
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
|
#include "llvm/CodeGen/MachineJumpTableInfo.h"
|
|
#include "llvm/CodeGen/MachineModuleInfo.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "aarch64-branch-targets"
|
|
#define AARCH64_BRANCH_TARGETS_NAME "AArch64 Branch Targets"
|
|
|
|
namespace {
|
|
class AArch64BranchTargets : public MachineFunctionPass {
|
|
public:
|
|
static char ID;
|
|
AArch64BranchTargets() : MachineFunctionPass(ID) {}
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override;
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
StringRef getPassName() const override { return AARCH64_BRANCH_TARGETS_NAME; }
|
|
|
|
private:
|
|
void addBTI(MachineBasicBlock &MBB, bool CouldCall, bool CouldJump,
|
|
bool NeedsWinCFI);
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
char AArch64BranchTargets::ID = 0;
|
|
|
|
INITIALIZE_PASS(AArch64BranchTargets, "aarch64-branch-targets",
|
|
AARCH64_BRANCH_TARGETS_NAME, false, false)
|
|
|
|
void AArch64BranchTargets::getAnalysisUsage(AnalysisUsage &AU) const {
|
|
AU.setPreservesCFG();
|
|
MachineFunctionPass::getAnalysisUsage(AU);
|
|
}
|
|
|
|
FunctionPass *llvm::createAArch64BranchTargetsPass() {
|
|
return new AArch64BranchTargets();
|
|
}
|
|
|
|
bool AArch64BranchTargets::runOnMachineFunction(MachineFunction &MF) {
|
|
if (!MF.getInfo<AArch64FunctionInfo>()->branchTargetEnforcement())
|
|
return false;
|
|
|
|
LLVM_DEBUG(
|
|
dbgs() << "********** AArch64 Branch Targets **********\n"
|
|
<< "********** Function: " << MF.getName() << '\n');
|
|
const Function &F = MF.getFunction();
|
|
|
|
// LLVM does not consider basic blocks which are the targets of jump tables
|
|
// to be address-taken (the address can't escape anywhere else), but they are
|
|
// used for indirect branches, so need BTI instructions.
|
|
SmallPtrSet<MachineBasicBlock *, 8> JumpTableTargets;
|
|
if (auto *JTI = MF.getJumpTableInfo())
|
|
for (auto &JTE : JTI->getJumpTables())
|
|
JumpTableTargets.insert_range(JTE.MBBs);
|
|
|
|
bool MadeChange = false;
|
|
bool HasWinCFI = MF.hasWinCFI();
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
bool CouldCall = false, CouldJump = false;
|
|
// If the function is address-taken or externally-visible, it could be
|
|
// indirectly called. PLT entries and tail-calls use BR, but when they are
|
|
// are in guarded pages should all use x16 or x17 to hold the called
|
|
// address, so we don't need to set CouldJump here. BR instructions in
|
|
// non-guarded pages (which might be non-BTI-aware code) are allowed to
|
|
// branch to a "BTI c" using any register.
|
|
//
|
|
// For ELF targets, this is enough, because AAELF64 says that if the static
|
|
// linker later wants to use an indirect branch instruction in a
|
|
// long-branch thunk, it's also responsible for adding a 'landing pad' with
|
|
// a BTI, and pointing the indirect branch at that. For non-ELF targets we
|
|
// can't rely on that, so we assume that `CouldCall` is _always_ true due
|
|
// to the risk of long-branch thunks at link time.
|
|
if (&MBB == &*MF.begin() &&
|
|
(!MF.getSubtarget<AArch64Subtarget>().isTargetELF() ||
|
|
(F.hasAddressTaken() || !F.hasLocalLinkage())))
|
|
CouldCall = true;
|
|
|
|
// If the block itself is address-taken, it could be indirectly branched
|
|
// to, but not called.
|
|
if (MBB.isMachineBlockAddressTaken() || MBB.isIRBlockAddressTaken() ||
|
|
JumpTableTargets.count(&MBB))
|
|
CouldJump = true;
|
|
|
|
if (CouldCall || CouldJump) {
|
|
addBTI(MBB, CouldCall, CouldJump, HasWinCFI);
|
|
MadeChange = true;
|
|
}
|
|
}
|
|
|
|
return MadeChange;
|
|
}
|
|
|
|
void AArch64BranchTargets::addBTI(MachineBasicBlock &MBB, bool CouldCall,
|
|
bool CouldJump, bool HasWinCFI) {
|
|
LLVM_DEBUG(dbgs() << "Adding BTI " << (CouldJump ? "j" : "")
|
|
<< (CouldCall ? "c" : "") << " to " << MBB.getName()
|
|
<< "\n");
|
|
|
|
const AArch64InstrInfo *TII = static_cast<const AArch64InstrInfo *>(
|
|
MBB.getParent()->getSubtarget().getInstrInfo());
|
|
|
|
unsigned HintNum = 32;
|
|
if (CouldCall)
|
|
HintNum |= 2;
|
|
if (CouldJump)
|
|
HintNum |= 4;
|
|
assert(HintNum != 32 && "No target kinds!");
|
|
|
|
auto MBBI = MBB.begin();
|
|
|
|
// Skip the meta instructions, those will be removed anyway.
|
|
for (; MBBI != MBB.end() &&
|
|
(MBBI->isMetaInstruction() || MBBI->getOpcode() == AArch64::EMITBKEY);
|
|
++MBBI)
|
|
;
|
|
|
|
// SCTLR_EL1.BT[01] is set to 0 by default which means
|
|
// PACI[AB]SP are implicitly BTI C so no BTI C instruction is needed there.
|
|
if (MBBI != MBB.end() && HintNum == 34 &&
|
|
(MBBI->getOpcode() == AArch64::PACIASP ||
|
|
MBBI->getOpcode() == AArch64::PACIBSP))
|
|
return;
|
|
|
|
if (HasWinCFI && MBBI->getFlag(MachineInstr::FrameSetup)) {
|
|
BuildMI(MBB, MBB.begin(), MBB.findDebugLoc(MBB.begin()),
|
|
TII->get(AArch64::SEH_Nop));
|
|
}
|
|
BuildMI(MBB, MBB.begin(), MBB.findDebugLoc(MBB.begin()),
|
|
TII->get(AArch64::HINT))
|
|
.addImm(HintNum);
|
|
}
|