-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[BOLT] Gadget scanner: prevent false positives due to jump tables #138884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: users/atrosinenko/bolt-mcinstmatcher
Are you sure you want to change the base?
[BOLT] Gadget scanner: prevent false positives due to jump tables #138884
Conversation
As part of PAuth hardening, AArch64 LLVM backend can use a special BR_JumpTable pseudo (enabled by -faarch64-jump-table-hardening Clang option) which is expanded in the AsmPrinter into a contiguous sequence without unsafe instructions in the middle. This commit adds another target-specific callback to MCPlusBuilder to make it possible to inhibit false positives for known-safe jump table dispatch sequences. Without special handling, the branch instruction is likely to be reported as a non-protected call (as its destination is not produced by an auth instruction, PC-relative address materialization, etc.) and possibly as a tail call being performed with unsafe link register (as the detection whether the branch instruction is a tail call is an heuristic). For now, only the specific instruction sequence used by the AArch64 LLVM backend is matched.
@llvm/pr-subscribers-bolt Author: Anatoly Trosinenko (atrosinenko) ChangesAs part of PAuth hardening, AArch64 LLVM backend can use a special This commit adds another target-specific callback to MCPlusBuilder For now, only the specific instruction sequence used by the AArch64 Patch is 32.03 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/138884.diff 6 Files Affected:
diff --git a/bolt/include/bolt/Core/MCInstUtils.h b/bolt/include/bolt/Core/MCInstUtils.h
index b495eb8ef5eec..5dd0aaa48d6e7 100644
--- a/bolt/include/bolt/Core/MCInstUtils.h
+++ b/bolt/include/bolt/Core/MCInstUtils.h
@@ -158,6 +158,15 @@ class MCInstReference {
return nullptr;
}
+ /// Returns the only preceding instruction, or std::nullopt if multiple or no
+ /// predecessors are possible.
+ ///
+ /// If CFG information is available, basic block boundary can be crossed,
+ /// provided there is exactly one predecessor. If CFG is not available, the
+ /// preceding instruction in the offset order is returned, unless this is the
+ /// first instruction of the function.
+ std::optional<MCInstReference> getSinglePredecessor();
+
raw_ostream &print(raw_ostream &OS) const;
};
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index 87de6754017db..eb93d7de7fee9 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -14,6 +14,7 @@
#ifndef BOLT_CORE_MCPLUSBUILDER_H
#define BOLT_CORE_MCPLUSBUILDER_H
+#include "bolt/Core/MCInstUtils.h"
#include "bolt/Core/MCPlus.h"
#include "bolt/Core/Relocation.h"
#include "llvm/ADT/ArrayRef.h"
@@ -699,6 +700,19 @@ class MCPlusBuilder {
return std::nullopt;
}
+ /// Tests if BranchInst corresponds to an instruction sequence which is known
+ /// to be a safe dispatch via jump table.
+ ///
+ /// The target can decide which instruction sequences to consider "safe" from
+ /// the Pointer Authentication point of view, such as any jump table dispatch
+ /// sequence without function calls inside, any sequence which is contiguous,
+ /// or only some specific well-known sequences.
+ virtual bool
+ isSafeJumpTableBranchForPtrAuth(MCInstReference BranchInst) const {
+ llvm_unreachable("not implemented");
+ return false;
+ }
+
virtual bool isTerminator(const MCInst &Inst) const;
virtual bool isNoop(const MCInst &Inst) const {
diff --git a/bolt/lib/Core/MCInstUtils.cpp b/bolt/lib/Core/MCInstUtils.cpp
index 40f6edd59135c..b7c6d898988af 100644
--- a/bolt/lib/Core/MCInstUtils.cpp
+++ b/bolt/lib/Core/MCInstUtils.cpp
@@ -55,3 +55,23 @@ raw_ostream &MCInstReference::print(raw_ostream &OS) const {
OS << ">";
return OS;
}
+
+std::optional<MCInstReference> MCInstReference::getSinglePredecessor() {
+ if (const RefInBB *Ref = tryGetRefInBB()) {
+ if (Ref->It != Ref->BB->begin())
+ return MCInstReference(Ref->BB, &*std::prev(Ref->It));
+
+ if (Ref->BB->pred_size() != 1)
+ return std::nullopt;
+
+ BinaryBasicBlock *PredBB = *Ref->BB->pred_begin();
+ assert(!PredBB->empty() && "Empty basic blocks are not supported yet");
+ return MCInstReference(PredBB, &*PredBB->rbegin());
+ }
+
+ const RefInBF &Ref = getRefInBF();
+ if (Ref.It == Ref.BF->instrs().begin())
+ return std::nullopt;
+
+ return MCInstReference(Ref.BF, std::prev(Ref.It));
+}
diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp
index 7b8b1513ef33c..4bd1ccb4520c1 100644
--- a/bolt/lib/Passes/PAuthGadgetScanner.cpp
+++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp
@@ -1311,6 +1311,11 @@ shouldReportUnsafeTailCall(const BinaryContext &BC, const BinaryFunction &BF,
return std::nullopt;
}
+ if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
+ LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
+ return std::nullopt;
+ }
+
// Returns at most one report per instruction - this is probably OK...
for (auto Reg : RegsToCheck)
if (!S.TrustedRegs[Reg])
@@ -1341,6 +1346,11 @@ shouldReportCallGadget(const BinaryContext &BC, const MCInstReference &Inst,
if (S.SafeToDerefRegs[DestReg])
return std::nullopt;
+ if (BC.MIB->isSafeJumpTableBranchForPtrAuth(Inst)) {
+ LLVM_DEBUG({ dbgs() << " Safe jump table detected, skipping.\n"; });
+ return std::nullopt;
+ }
+
return make_gadget_report(CallKind, Inst, DestReg);
}
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index 2522de7005c64..423684f5c8cc0 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -532,6 +532,79 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
return std::nullopt;
}
+ bool
+ isSafeJumpTableBranchForPtrAuth(MCInstReference BranchInst) const override {
+ MCInstReference CurRef = BranchInst;
+ auto StepBack = [&]() {
+ do {
+ auto PredInst = CurRef.getSinglePredecessor();
+ if (!PredInst)
+ return false;
+ CurRef = *PredInst;
+ } while (isCFI(CurRef));
+
+ return true;
+ };
+
+ // Match this contiguous sequence:
+ // cmp Xm, #count
+ // csel Xm, Xm, xzr, ls
+ // adrp Xn, .LJTIxyz
+ // add Xn, Xn, :lo12:.LJTIxyz
+ // ldrsw Xm, [Xn, Xm, lsl #2]
+ // .Ltmp:
+ // adr Xn, .Ltmp
+ // add Xm, Xn, Xm
+ // br Xm
+
+ // FIXME: Check label operands of ADR/ADRP+ADD and #count operand of CMP.
+
+ using namespace MCInstMatcher;
+ Reg Xm, Xn;
+
+ if (!matchInst(CurRef, AArch64::BR, Xm) || !StepBack())
+ return false;
+
+ if (!matchInst(CurRef, AArch64::ADDXrs, Xm, Xn, Xm, Imm(0)) || !StepBack())
+ return false;
+
+ if (!matchInst(CurRef, AArch64::ADR, Xn /*, .Ltmp*/) || !StepBack())
+ return false;
+
+ if (!matchInst(CurRef, AArch64::LDRSWroX, Xm, Xn, Xm, Imm(0), Imm(1)) ||
+ !StepBack())
+ return false;
+
+ if (matchInst(CurRef, AArch64::ADR, Xn /*, .LJTIxyz*/)) {
+ if (!StepBack())
+ return false;
+ if (!matchInst(CurRef, AArch64::HINT, Imm(0)) || !StepBack())
+ return false;
+ } else if (matchInst(CurRef, AArch64::ADDXri, Xn,
+ Xn /*, :lo12:.LJTIxyz*/)) {
+ if (!StepBack())
+ return false;
+ if (!matchInst(CurRef, AArch64::ADRP, Xn /*, .LJTIxyz*/) || !StepBack())
+ return false;
+ } else {
+ return false;
+ }
+
+ if (!matchInst(CurRef, AArch64::CSELXr, Xm, Xm, Reg(AArch64::XZR),
+ Imm(AArch64CC::LS)) ||
+ !StepBack())
+ return false;
+
+ if (!matchInst(CurRef, AArch64::SUBSXri, Reg(AArch64::XZR),
+ Xm /*, #count*/))
+ return false;
+
+ // Some platforms treat X16 and X17 as more protected registers, others
+ // do not make such distinction. So far, accept any registers as Xm and Xn.
+
+ return true;
+ }
+
bool isADRP(const MCInst &Inst) const override {
return Inst.getOpcode() == AArch64::ADRP;
}
diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s
new file mode 100644
index 0000000000000..5a42ed078e9c2
--- /dev/null
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s
@@ -0,0 +1,703 @@
+// -Wl,--no-relax prevents converting ADRP+ADD pairs into NOP+ADR.
+// Without -Wl,--emit-relocs BOLT refuses to create CFG information for the below functions.
+
+// RUN: %clang %cflags -march=armv8.3-a -Wl,--no-relax -Wl,--emit-relocs %s -o %t.exe
+// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,CFG %s
+// RUN: %clang %cflags -march=armv8.3-a -Wl,--no-relax %s -o %t.exe
+// RUN: llvm-bolt-binary-analysis --scanners=pauth %t.exe 2>&1 | FileCheck --check-prefixes=CHECK,NOCFG %s
+
+// FIXME: Labels could be further validated. Specifically, it could be checked
+// that the jump table itself is located in a read-only data section.
+
+// FIXME: BOLT does not reconstruct CFG correctly for jump tables yet, thus
+// register state is pessimistically reset to unsafe at the beginning of
+// each basic block without any predecessors.
+// Until CFG reconstruction is fixed, add paciasp+autiasp instructions to
+// silence "non-protected ret" false-positives and explicitly ignore
+// "Warning: the function has unreachable basic blocks..." lines.
+
+ .text
+ .p2align 2
+ .globl good_jump_table
+ .type good_jump_table,@function
+good_jump_table:
+// CHECK-NOT: good_jump_table
+// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function good_jump_table
+// CHECK-NOT: good_jump_table
+ paciasp
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ adrp x17, 4f
+ add x17, x17, :lo12:4f
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size good_jump_table, .-good_jump_table
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+// NOP (HINT #0) before ADR is correct (it can be produced by linker due to
+// relaxing ADRP+ADD sequence), but other HINT instructions are not.
+
+ .text
+ .p2align 2
+ .globl jump_table_relaxed_adrp_add
+ .type jump_table_relaxed_adrp_add,@function
+jump_table_relaxed_adrp_add:
+// CHECK-NOT: jump_table_relaxed_adrp_add
+// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_relaxed_adrp_add
+// CHECK-NOT: jump_table_relaxed_adrp_add
+ paciasp
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ hint #0 // nop
+ adr x17, 4f
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_relaxed_adrp_add, .-jump_table_relaxed_adrp_add
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_hint
+ .type jump_table_wrong_hint,@function
+jump_table_wrong_hint:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_hint, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_hint, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: This happens in the following basic block:
+// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_hint@0x{{[0-9a-f]+}}
+// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+ paciasp
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ hint #20 // unknown hint
+ adr x17, 4f
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_wrong_hint, .-jump_table_wrong_hint
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+// For now, all registers are permitted as temporary ones, not only x16 and x17.
+
+ .text
+ .p2align 2
+ .globl jump_table_unsafe_reg_1
+ .type jump_table_unsafe_reg_1,@function
+jump_table_unsafe_reg_1:
+// CHECK-NOT: jump_table_unsafe_reg_1
+// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_unsafe_reg_1
+// CHECK-NOT: jump_table_unsafe_reg_1
+ paciasp
+ cmp x1, #0x2
+ csel x1, x1, xzr, ls
+ adrp x17, 4f
+ add x17, x17, :lo12:4f
+ ldrsw x1, [x17, x1, lsl #2]
+1:
+ adr x17, 1b
+ add x1, x17, x1
+ br x1
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_unsafe_reg_1, .-jump_table_unsafe_reg_1
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_unsafe_reg_2
+ .type jump_table_unsafe_reg_2,@function
+jump_table_unsafe_reg_2:
+// CHECK-NOT: jump_table_unsafe_reg_2
+// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_unsafe_reg_2
+// CHECK-NOT: jump_table_unsafe_reg_2
+ paciasp
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ adrp x1, 4f
+ add x1, x1, :lo12:4f
+ ldrsw x16, [x1, x16, lsl #2]
+1:
+ adr x1, 1b
+ add x16, x1, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_unsafe_reg_2, .-jump_table_unsafe_reg_2
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+// FIXME: Detect possibility of jump table overflow.
+ .text
+ .p2align 2
+ .globl jump_table_wrong_limit
+ .type jump_table_wrong_limit,@function
+jump_table_wrong_limit:
+// CHECK-NOT: jump_table_wrong_limit
+// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function jump_table_wrong_limit
+// CHECK-NOT: jump_table_wrong_limit
+ paciasp
+ cmp x16, #0x1000
+ csel x16, x16, xzr, ls
+ adrp x17, 4f
+ add x17, x17, :lo12:4f
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_wrong_limit, .-jump_table_wrong_limit
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_unrelated_inst_1
+ .type jump_table_unrelated_inst_1,@function
+jump_table_unrelated_inst_1:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_1, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_1, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: This happens in the following basic block:
+// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_unrelated_inst_1@0x{{[0-9a-f]+}}
+// CFG-NEXT: {{[0-9a-f]+}}: nop
+// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+ paciasp
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ adrp x17, 4f
+ add x17, x17, :lo12:4f
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ nop
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_unrelated_inst_1, .-jump_table_unrelated_inst_1
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_unrelated_inst_2
+ .type jump_table_unrelated_inst_2,@function
+jump_table_unrelated_inst_2:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_2, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_unrelated_inst_2, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+// CHECK-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CHECK-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: This happens in the following basic block:
+// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_unrelated_inst_2@0x{{[0-9a-f]+}}
+// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+ paciasp
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ adrp x17, 4f
+ add x17, x17, :lo12:4f
+ nop
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_unrelated_inst_2, .-jump_table_unrelated_inst_2
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_multiple_predecessors_1
+ .type jump_table_multiple_predecessors_1,@function
+jump_table_multiple_predecessors_1:
+// NOCFG-NOT: jump_table_multiple_predecessors_1
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_multiple_predecessors_1, basic block {{[^,]+}}, at address
+// CFG-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+// CFG-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CFG-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: This happens in the following basic block:
+// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_multiple_predecessors_1@0x{{[0-9a-f]+}}
+// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+ paciasp
+ cbz x1, 1f // this instruction can jump to the middle of the sequence
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ adrp x17, 4f
+ add x17, x17, :lo12:4f
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b // multiple predecessors are possible
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_multiple_predecessors_1, .-jump_table_multiple_predecessors_1
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_multiple_predecessors_2
+ .type jump_table_multiple_predecessors_2,@function
+jump_table_multiple_predecessors_2:
+// NOCFG-NOT: jump_table_multiple_predecessors_2
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_multiple_predecessors_2, basic block {{[^,]+}}, at address
+// CFG-NEXT: The instruction is {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+// CFG-NEXT: The 1 instructions that write to the affected registers after any authentication are:
+// CFG-NEXT: 1. {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: This happens in the following basic block:
+// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_multiple_predecessors_2@0x{{[0-9a-f]+}}
+// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+ paciasp
+ cbz x1, 5f // this instruction can jump to the middle of the sequence
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+5:
+ adrp x17, 4f // multiple predecessors are possible
+ add x17, x17, :lo12:4f
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_multiple_predecessors_2, .-jump_table_multiple_predecessors_2
+ .section .rodata,"a",@progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+// Test a few pattern violations...
+
+ .text
+ .p2align 2
+ ...
[truncated]
|
As part of PAuth hardening, AArch64 LLVM backend can use a special
BR_JumpTable pseudo (enabled by -faarch64-jump-table-hardening
Clang option) which is expanded in the AsmPrinter into a contiguous
sequence without unsafe instructions in the middle.
This commit adds another target-specific callback to MCPlusBuilder
to make it possible to inhibit false positives for known-safe jump
table dispatch sequences. Without special handling, the branch
instruction is likely to be reported as a non-protected call (as its
destination is not produced by an auth instruction, PC-relative address
materialization, etc.) and possibly as a tail call being performed with
unsafe link register (as the detection whether the branch instruction
is a tail call is an heuristic).
For now, only the specific instruction sequence used by the AArch64
LLVM backend is matched.