[ARM] Implement setjmp BTI placement for PACBTI-M

This patch intends to guard indirect branches performed by longjmp
by inserting BTI instructions after calls to setjmp.

Calls with 'returns-twice' are lowered to a new pseudo-instruction
named t2CALL_BTI that is later expanded to a bundle of {tBL,t2BTI}.

This patch is part of a series that adds support for the PACBTI-M extension of
the Armv8.1-M architecture, as detailed here:

https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/armv8-1-m-pointer-authentication-and-branch-target-identification-extension

The PACBTI-M specification can be found in the Armv8-M Architecture Reference
Manual:

https://developer.arm.com/documentation/ddi0553/latest

The following people contributed to this patch:

- Alexandros Lamprineas
- Ties Stuij

Reviewed By: labrinea

Differential Revision: https://reviews.llvm.org/D112427
This commit is contained in:
Ties Stuij 2021-12-06 11:00:10 +00:00
parent 6b41eb7f26
commit 0fbb17458a
12 changed files with 206 additions and 1 deletions

View File

@ -3260,6 +3260,11 @@ Thread pointer access method (AArch32/AArch64 only)
Allow memory accesses to be unaligned (AArch32/AArch64 only)
.. option:: -mno-bti-at-return-twice
Do not add a BTI instruction after a setjmp or other return-twice construct (Arm
only)
Hexagon
-------
.. option:: -mieee-rnd-near

View File

@ -3338,6 +3338,11 @@ def mno_fix_cortex_a53_835769 : Flag<["-"], "mno-fix-cortex-a53-835769">,
def mmark_bti_property : Flag<["-"], "mmark-bti-property">,
Group<m_aarch64_Features_Group>,
HelpText<"Add .note.gnu.property with BTI to assembly files (AArch64 only)">;
def mno_bti_at_return_twice : Flag<["-"], "mno-bti-at-return-twice">,
Group<m_arm_Features_Group>,
HelpText<"Do not add a BTI instruction after a setjmp or other"
" return-twice construct (Arm only)">;
foreach i = {1-31} in
def ffixed_x#i : Flag<["-"], "ffixed-x"#i>, Group<m_Group>,
HelpText<"Reserve the x"#i#" register (AArch64/RISC-V only)">;

View File

@ -875,6 +875,8 @@ fp16_fml_fallthrough:
}
}
if (Args.getLastArg(options::OPT_mno_bti_at_return_twice))
Features.push_back("+no-bti-at-return-twice");
}
std::string arm::getARMArch(StringRef Arch, const llvm::Triple &Triple) {

View File

@ -0,0 +1,7 @@
// RUN: %clang -target arm-arm-none-eabi -march=armv8-m.main -mbranch-protection=bti \
// RUN: -mno-bti-at-return-twice -### %s 2>&1 | FileCheck %s --check-prefix=FEAT
// RUN: %clang -target arm-arm-none-eabi -march=armv8-m.main -mbranch-protection=bti \
// RUN: -### %s 2>&1 | FileCheck %s --check-prefix=NOFEAT
// FEAT: "+no-bti-at-return-twice"
// NOFEAT-NOT: "+no-bti-at-return-twice"

View File

@ -446,6 +446,11 @@ def FeaturePACBTI : SubtargetFeature<"pacbti", "HasPACBTI", "true",
"Enable Pointer Authentication and Branch "
"Target Identification">;
def FeatureNoBTIAtReturnTwice : SubtargetFeature<"no-bti-at-return-twice",
"NoBTIAtReturnTwice", "true",
"Don't place a BTI instruction "
"after a return-twice">;
//===----------------------------------------------------------------------===//
// ARM architecture class
//

View File

@ -3073,6 +3073,22 @@ bool ARMExpandPseudo::ExpandMI(MachineBasicBlock &MBB,
MI.eraseFromParent();
return true;
}
case ARM::t2CALL_BTI: {
MachineFunction &MF = *MI.getMF();
MachineInstrBuilder MIB =
BuildMI(MF, MI.getDebugLoc(), TII->get(ARM::tBL));
MIB.cloneMemRefs(MI);
for (unsigned i = 0; i < MI.getNumOperands(); ++i)
MIB.add(MI.getOperand(i));
if (MI.isCandidateForCallSiteEntry())
MF.moveCallSiteInfo(&MI, MIB.getInstr());
MIBundleBuilder Bundler(MBB, MI);
Bundler.append(MIB);
Bundler.append(BuildMI(MF, MI.getDebugLoc(), TII->get(ARM::t2BTI)));
finalizeBundle(MBB, Bundler.begin(), Bundler.end());
MI.eraseFromParent();
return true;
}
case ARM::LOADDUAL:
case ARM::STOREDUAL: {
Register PairReg = MI.getOperand(0).getReg();

View File

@ -1658,6 +1658,7 @@ const char *ARMTargetLowering::getTargetNodeName(unsigned Opcode) const {
MAKE_CASE(ARMISD::CALL_PRED)
MAKE_CASE(ARMISD::CALL_NOLINK)
MAKE_CASE(ARMISD::tSECALL)
MAKE_CASE(ARMISD::t2CALL_BTI)
MAKE_CASE(ARMISD::BRCOND)
MAKE_CASE(ARMISD::BR_JT)
MAKE_CASE(ARMISD::BR2_JT)
@ -2321,6 +2322,12 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
bool isCmseNSCall = false;
bool isSibCall = false;
bool PreferIndirect = false;
bool GuardWithBTI = false;
// Lower 'returns_twice' calls to a pseudo-instruction.
if (CLI.CB && CLI.CB->getAttributes().hasFnAttr(Attribute::ReturnsTwice) &&
!Subtarget->getNoBTIAtReturnTwice())
GuardWithBTI = AFI->branchTargetEnforcement();
// Determine whether this is a non-secure function call.
if (CLI.CB && CLI.CB->getAttributes().hasFnAttr("cmse_nonsecure_call"))
@ -2726,7 +2733,9 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
// FIXME: handle tail calls differently.
unsigned CallOpc;
if (Subtarget->isThumb()) {
if (isCmseNSCall)
if (GuardWithBTI)
CallOpc = ARMISD::t2CALL_BTI;
else if (isCmseNSCall)
CallOpc = ARMISD::tSECALL;
else if ((!isDirect || isARMFunc) && !Subtarget->hasV5TOps())
CallOpc = ARMISD::CALL_NOLINK;

View File

@ -69,6 +69,7 @@ class VectorType;
CALL_PRED, // Function call that's predicable.
CALL_NOLINK, // Function call with branch not branch-and-link.
tSECALL, // CMSE non-secure function call.
t2CALL_BTI, // Thumb function call followed by BTI instruction.
BRCOND, // Conditional branch.
BR_JT, // Jumptable branch.
BR2_JT, // Jumptable branch (2 level - jumptable entry is a jump).

View File

@ -5736,3 +5736,10 @@ def t2BTI : PACBTIHintSpaceNoOpsInst<"bti", 0b00001111>;
def t2AUT : PACBTIHintSpaceUseInst<"aut", 0b00101101> {
let hasSideEffects = 1;
}
def ARMt2CallBTI : SDNode<"ARMISD::t2CALL_BTI", SDT_ARMcall,
[SDNPHasChain, SDNPOptInGlue, SDNPOutGlue, SDNPVariadic]>;
def t2CALL_BTI : PseudoInst<(outs), (ins pred:$p, thumb_bl_target:$func),
IIC_Br, [(ARMt2CallBTI tglobaladdr:$func)]>,
Requires<[IsThumb2]>, Sched<[WriteBrL]>;

View File

@ -534,6 +534,10 @@ protected:
/// Selected instruction itineraries (one entry per itinerary class.)
InstrItineraryData InstrItins;
/// NoBTIAtReturnTwice - Don't place a BTI instruction after
/// return-twice constructs (setjmp)
bool NoBTIAtReturnTwice = false;
/// Options passed via command line that could influence the target
const TargetOptions &Options;
@ -948,6 +952,8 @@ public:
bool hardenSlsRetBr() const { return HardenSlsRetBr; }
bool hardenSlsBlr() const { return HardenSlsBlr; }
bool hardenSlsNoComdat() const { return HardenSlsNoComdat; }
bool getNoBTIAtReturnTwice() const { return NoBTIAtReturnTwice; }
};
} // end namespace llvm

View File

@ -0,0 +1,50 @@
; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi < %s | FileCheck %s --check-prefix=BTI
; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -mattr=+no-bti-at-return-twice < %s | \
; RUN: FileCheck %s --check-prefix=NOBTI
; C source
; --------
; jmp_buf buf;
;
; extern void bar(int x);
;
; int foo(int x) {
; if (setjmp(buf))
; x = 0;
; else
; bar(x);
; return x;
; }
@buf = global [20 x i64] zeroinitializer, align 8
define i32 @foo(i32 %x) {
; BTI-LABEL: foo:
; BTI: bl setjmp
; BTI-NEXT: bti
; NOBTI-LABEL: foo:
; NOBTI: bl setjmp
; NOBTI-NOT: bti
entry:
%call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0
%tobool.not = icmp eq i32 %call, 0
br i1 %tobool.not, label %if.else, label %if.end
if.else: ; preds = %entry
call void @bar(i32 %x)
br label %if.end
if.end: ; preds = %entry, %if.else
%x.addr.0 = phi i32 [ %x, %if.else ], [ 0, %entry ]
ret i32 %x.addr.0
}
declare void @bar(i32)
declare i32 @setjmp(i64*) #0
attributes #0 = { returns_twice }
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"branch-target-enforcement", i32 1}

View File

@ -0,0 +1,92 @@
; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -enable-machine-outliner < %s | \
; RUN: FileCheck %s --check-prefix=BTI
; RUN: llc -mtriple=thumbv8.1m.main-arm-none-eabi -enable-machine-outliner -mattr=+no-bti-at-return-twice < %s | FileCheck %s --check-prefix=NOBTI
; C source
; --------
; jmp_buf buf;
;
; extern void h(int a, int b, int *c);
;
; int f(int a, int b, int c, int d) {
; if (setjmp(buf) != 0)
; return -1;
; h(a, b, &a);
; return 2 + a * (a + b) / (c + d);
; }
;
; int g(int a, int b, int c, int d) {
; if (setjmp(buf) != 0)
; return -1;
; h(a, b, &a);
; return 1 + a * (a + b) / (c + d);
; }
@buf = global [20 x i64] zeroinitializer, align 8
define i32 @f(i32 %a, i32 %b, i32 %c, i32 %d) {
; BTI-LABEL: f:
; BTI: bl OUTLINED_FUNCTION_0
; BTI-NEXT: bti
; NOBTI-LABEL: f:
; NOBTI: bl OUTLINED_FUNCTION_0
; NOBTI-NEXT: cbz r0, .LBB0_2
entry:
%a.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
%call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0
%cmp.not = icmp eq i32 %call, 0
br i1 %cmp.not, label %if.end, label %return
if.end: ; preds = %entry
call void @h(i32 %a, i32 %b, i32* nonnull %a.addr)
%0 = load i32, i32* %a.addr, align 4
%add = add nsw i32 %0, %b
%mul = mul nsw i32 %add, %0
%add1 = add nsw i32 %d, %c
%div = sdiv i32 %mul, %add1
%add2 = add nsw i32 %div, 2
br label %return
return: ; preds = %entry, %if.end
%retval.0 = phi i32 [ %add2, %if.end ], [ -1, %entry ]
ret i32 %retval.0
}
define i32 @g(i32 %a, i32 %b, i32 %c, i32 %d) {
; BTI-LABEL: g:
; BTI: bl OUTLINED_FUNCTION_0
; BTI-NEXT: bti
; NOBTI-LABEL: g:
; NOBTI: bl OUTLINED_FUNCTION_0
; NOBTI-NEXT: cbz r0, .LBB1_2
entry:
%a.addr = alloca i32, align 4
store i32 %a, i32* %a.addr, align 4
%call = call i32 @setjmp(i64* getelementptr inbounds ([20 x i64], [20 x i64]* @buf, i32 0, i32 0)) #0
%cmp.not = icmp eq i32 %call, 0
br i1 %cmp.not, label %if.end, label %return
if.end: ; preds = %entry
call void @h(i32 %a, i32 %b, i32* nonnull %a.addr)
%0 = load i32, i32* %a.addr, align 4
%add = add nsw i32 %0, %b
%mul = mul nsw i32 %add, %0
%add1 = add nsw i32 %d, %c
%div = sdiv i32 %mul, %add1
%add2 = add nsw i32 %div, 1
br label %return
return: ; preds = %entry, %if.end
%retval.0 = phi i32 [ %add2, %if.end ], [ -1, %entry ]
ret i32 %retval.0
}
declare void @h(i32, i32, i32*)
declare i32 @setjmp(i64*) #0
attributes #0 = { returns_twice }
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"branch-target-enforcement", i32 1}