Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit de40ef2

Browse files
authored
[BOLT][BTI] Patch LLD-generated PLTs to contain BTI landing pad (#173245)
This patch adds the patchPLTEntryForBTI to enable patching PLT entries generated by LLD. ## Context: To keep BTI consistent, targets of stubs inserted in LongJmp need to be patched. As PLTs are not optimized and emitted by BOLT, this patch adds a helper for patching them in the original .plt section. For PLTs generated by LLD, this is safe as LLD inserts extra nops to PLTs which don't already contain a BTI. PLT entry before patching: ``` adrp x16, Page(&(.got.plt[n])) ldr x17, [x16, Offset(&(.got.plt[n]))] add x16, x16, Offset(&(.got.plt[n])) br x17 nop nop ``` PLT entry after patching: ``` bti c adrp x16, Page(&(.got.plt[n])) ldr x17, [x16, Offset(&(.got.plt[n]))] add x16, x16, Offset(&(.got.plt[n])) br x17 nop ``` ## Safety considerations: The PLT entry can become incorrect if shifting the ADRP moves it across a page boundary. The PLT entry is 24 bytes, and page size is 4096 (or 16384) bytes. Their GCD is 8 bytes, meaning that shifting the ADRP is safe, as long as it's shifted by less than 8 bytes. The introduced function only shifts the ADRP by one instruction (4 bytes), meaning there is no need to recompute the ADRP offset.
1 parent c91fbbd commit de40ef2

4 files changed

Lines changed: 106 additions & 4 deletions

File tree

bolt/include/bolt/Core/MCPlusBuilder.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,10 @@ class MCPlusBuilder {
17831783
return 0;
17841784
}
17851785

1786+
virtual void patchPLTEntryForBTI(BinaryFunction &PLTFunction, MCInst &Call) {
1787+
llvm_unreachable("not implemented");
1788+
}
1789+
17861790
virtual bool analyzeVirtualMethodCall(InstructionIterator Begin,
17871791
InstructionIterator End,
17881792
std::vector<MCInst *> &MethodFetchInsns,

bolt/lib/Passes/LongJmp.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,11 @@ Error LongJmpPass::relaxStub(BinaryBasicBlock &StubBB, bool &Modified) {
499499
<< RealTargetSym->getName() << "\n";
500500
exit(1);
501501
}
502+
if (TargetFunction && TargetFunction->isPLTFunction()) {
503+
BC.MIB->patchPLTEntryForBTI(*TargetFunction,
504+
*StubBB.getLastNonPseudoInstr());
505+
return;
506+
}
502507
if (TargetFunction && TargetFunction->isIgnored()) {
503508
// Includes PLT functions.
504509
BC.errs() << "BOLT-ERROR: Cannot add BTI landing pad to ignored function "

bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,70 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
16691669
return Base + Offset;
16701670
}
16711671

1672+
/// This function is used to patch PLT entries to include a BTI instruction.
1673+
/// This currently only works for binaries linked using LLD.
1674+
///
1675+
/// PLT entry before patching:
1676+
///
1677+
/// adrp x16, Page(&(.got.plt[n]))
1678+
/// ldr x17, [x16, Offset(&(.got.plt[n]))]
1679+
/// add x16, x16, Offset(&(.got.plt[n]))
1680+
/// br x17
1681+
/// nop
1682+
/// nop
1683+
///
1684+
/// PLT entry after patching:
1685+
///
1686+
/// bti c
1687+
/// adrp x16, Page(&(.got.plt[n]))
1688+
/// ldr x17, [x16, Offset(&(.got.plt[n]))]
1689+
/// add x16, x16, Offset(&(.got.plt[n]))
1690+
/// br x17
1691+
/// nop
1692+
///
1693+
/// Safety considerations:
1694+
///
1695+
/// The PLT entry will become incorrect if shifting the ADRP by one
1696+
/// instruction (4 bytes) moves it across a page boundary.
1697+
///
1698+
/// The PLT entry is 24 bytes, and page size is 4096 (or 16384) bytes.
1699+
/// Their GCD is 8 bytes, meaning that shifting the ADRP is safe, as long as
1700+
/// it is shifted by less than 8 bytes.
1701+
///
1702+
/// If the PLT entry does not contain extra nops, this function will create an
1703+
/// error. This can happen in binaries linked using BFD.
1704+
void patchPLTEntryForBTI(BinaryFunction &PLTFunction, MCInst &Call) override {
1705+
BinaryContext &BC = PLTFunction.getBinaryContext();
1706+
assert(PLTFunction.isPLTFunction() &&
1707+
"patchPLTEntryForBTI called on a non-PLT function");
1708+
// Checking if the PLT entry already starts with the BTI needed for Call.
1709+
auto FirstBBI = PLTFunction.begin();
1710+
auto FirstII = FirstBBI->begin();
1711+
assert(FirstII != FirstBBI->end() && "Cannot patch empty PLT entry");
1712+
if (isCallCoveredByBTI(Call, *FirstII))
1713+
return;
1714+
// Checking if there are extra nops at the end. If not, BOLT cannot patch
1715+
// the PLT entry.
1716+
auto LastBBI = std::prev(PLTFunction.end());
1717+
auto LastII = std::prev(LastBBI->end());
1718+
if (!isNoop(*LastII)) {
1719+
errs() << "BOLT-ERROR: Cannot patch PLT entry "
1720+
<< PLTFunction.getPrintName()
1721+
<< " to have a BTI landing pad. Relink the binary using LLD.\n";
1722+
exit(1);
1723+
}
1724+
// If the PLT does not have a BTI, and it has nops, create a new instruction
1725+
// sequence to patch the entry with.
1726+
InstructionListType NewPLTSeq;
1727+
MCInst BTIInst;
1728+
createBTI(BTIInst, BTIKind::C);
1729+
NewPLTSeq.push_back(BTIInst);
1730+
// Only adding the instructions from the first BB (adrp, ldr, add, br) to
1731+
// NewPLTSeq.
1732+
NewPLTSeq.insert(NewPLTSeq.end(), FirstBBI->begin(), FirstBBI->end());
1733+
BC.createInstructionPatch(PLTFunction.getAddress(), NewPLTSeq);
1734+
}
1735+
16721736
unsigned getInvertedBranchOpcode(unsigned Opcode) const {
16731737
switch (Opcode) {
16741738
default:

bolt/test/runtime/AArch64/long-jmp-bti-plt.c

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
/* This test checks that LongJmp can (not) add BTI instructions to PLT entries
2-
when targeting them using stubs.
1+
// This test checks that LongJmp can add BTI instructions to PLT entries when
2+
// targeting them using stubs.
3+
//
4+
// The test uses a non-default layout, where the .plt section is placed before
5+
// the text section. This is needed so the new code placed under the .data
6+
// section is far enough from the PLt to trigger shortJmp stubs.
7+
//
8+
// In practice, the PLT section can also be placed after the original text
9+
// section. In this scenario, the text section has to be large enough to trigger
10+
// shortJmp stubs.
311

412
// REQUIRES: system-linux
513

@@ -8,10 +16,31 @@
816
// RUN: %clang --target=aarch64-unknown-linux-gnu -mbranch-protection=standard \
917
// RUN: -no-pie %t/bti-plt.c -o %t.exe -Wl,-q -fuse-ld=lld \
1018
// RUN: -Wl,-T,%t/link.ld -Wl,-z,force-bti
11-
// RUN: not llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck %s
19+
// RUN: llvm-bolt %t.exe -o %t.bolt | FileCheck %s
1220
// CHECK: BOLT-INFO: binary is using BTI
13-
// CHECK: BOLT-ERROR: Cannot add BTI landing pad to ignored function abort@PLT
1421

22+
// Checking PLT entries before running BOLT
23+
// RUN: llvm-objdump -d -j .plt %t.exe | FileCheck %s --check-prefix=CHECK-EXE
24+
// CHECK-EXE: <abort@plt>
25+
// CHECK-EXE-NEXT: adrp x16, 0x8230000
26+
// CHECK-EXE-NEXT: ldr x17, [x16, #0xaf0]
27+
// CHECK-EXE-NEXT: add x16, x16, #0xaf0
28+
// CHECK-EXE-NEXT: br x17
29+
// CHECK-EXE-NEXT: nop
30+
// CHECK-EXE-NEXT: nop
31+
32+
// Checking PLT entries after patching them in BOLT
33+
// RUN: llvm-objdump -d -j .plt %t.bolt | FileCheck %s \
34+
// RUN: --check-prefix=CHECK-BOLT
35+
// CHECK-BOLT: <abort@plt>
36+
// CHECK-BOLT-NEXT: bti c
37+
// CHECK-BOLT-NEXT: adrp x16, 0x8230000
38+
// CHECK-BOLT-NEXT: ldr x17, [x16, #0xaf0]
39+
// CHECK-BOLT-NEXT: add x16, x16, #0xaf0
40+
// CHECK-BOLT-NEXT: br x17
41+
// CHECK-BOLT-NEXT: nop
42+
43+
/*
1544
#--- link.ld
1645
1746
SECTIONS {

0 commit comments

Comments
 (0)