[llvm-branch-commits] [llvm] [BOLT] Gadget scanner: prevent false positives due to jump tables (PR #138884)
Anatoly Trosinenko via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Aug 26 13:48:53 PDT 2025
https://github.com/atrosinenko updated https://github.com/llvm/llvm-project/pull/138884
>From b5a5ea9b2fc7a85760064994bea7153bb91b746b Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Wed, 7 May 2025 16:42:00 +0300
Subject: [PATCH 1/3] [BOLT] Introduce helpers to match `MCInst`s one at a time
(NFC)
Introduce matchInst helper function to capture and/or match the operands
of MCInst. Unlike the existing `MCPlusBuilder::MCInstMatcher` machinery,
matchInst is intended for the use cases when precise control over the
instruction order is required. For example, when validating PtrAuth
hardening, all registers are usually considered unsafe after a function
call, even though callee-saved registers should preserve their old
values *under normal operation*.
---
bolt/include/bolt/Core/MCInstUtils.h | 128 ++++++++++++++++++
.../Target/AArch64/AArch64MCPlusBuilder.cpp | 90 +++++-------
2 files changed, 162 insertions(+), 56 deletions(-)
diff --git a/bolt/include/bolt/Core/MCInstUtils.h b/bolt/include/bolt/Core/MCInstUtils.h
index 2e93dfaf4c275..40731a4fed701 100644
--- a/bolt/include/bolt/Core/MCInstUtils.h
+++ b/bolt/include/bolt/Core/MCInstUtils.h
@@ -149,6 +149,134 @@ static inline raw_ostream &operator<<(raw_ostream &OS,
return Ref.print(OS);
}
+/// Instruction-matching helpers operating on a single instruction at a time.
+///
+/// Unlike MCPlusBuilder::MCInstMatcher, this matchInst() function focuses on
+/// the cases where a precise control over the instruction order is important:
+///
+/// // Bring the short names into the local scope:
+/// using namespace MCInstMatcher;
+/// // Declare the registers to capture:
+/// Reg Xn, Xm;
+/// // Capture the 0th and 1st operands, match the 2nd operand against the
+/// // just captured Xm register, match the 3rd operand against literal 0:
+/// if (!matchInst(MaybeAdd, AArch64::ADDXrs, Xm, Xn, Xm, Imm(0))
+/// return AArch64::NoRegister;
+/// // Match the 0th operand against Xm:
+/// if (!matchInst(MaybeBr, AArch64::BR, Xm))
+/// return AArch64::NoRegister;
+/// // Return the matched register:
+/// return Xm.get();
+namespace MCInstMatcher {
+
+// The base class to match an operand of type T.
+//
+// The subclasses of OpMatcher are intended to be allocated on the stack and
+// to only be used by passing them to matchInst() and by calling their get()
+// function, thus the peculiar `mutable` specifiers: to make the calling code
+// compact and readable, the templated matchInst() function has to accept both
+// long-lived Imm/Reg wrappers declared as local variables (intended to capture
+// the first operand's value and match the subsequent operands, whether inside
+// a single instruction or across multiple instructions), as well as temporary
+// wrappers around literal values to match, f.e. Imm(42) or Reg(AArch64::XZR).
+template <typename T> class OpMatcher {
+ mutable std::optional<T> Value;
+ mutable std::optional<T> SavedValue;
+
+ // Remember/restore the last Value - to be called by matchInst.
+ void remember() const { SavedValue = Value; }
+ void restore() const { Value = SavedValue; }
+
+ template <class... OpMatchers>
+ friend bool matchInst(const MCInst &, unsigned, const OpMatchers &...);
+
+protected:
+ OpMatcher(std::optional<T> ValueToMatch) : Value(ValueToMatch) {}
+
+ bool matchValue(T OpValue) const {
+ // Check that OpValue does not contradict the existing Value.
+ bool MatchResult = !Value || *Value == OpValue;
+ // If MatchResult is false, all matchers will be reset before returning from
+ // matchInst, including this one, thus no need to assign conditionally.
+ Value = OpValue;
+
+ return MatchResult;
+ }
+
+public:
+ /// Returns the captured value.
+ T get() const {
+ assert(Value.has_value());
+ return *Value;
+ }
+};
+
+class Reg : public OpMatcher<MCPhysReg> {
+ bool matches(const MCOperand &Op) const {
+ if (!Op.isReg())
+ return false;
+
+ return matchValue(Op.getReg());
+ }
+
+ template <class... OpMatchers>
+ friend bool matchInst(const MCInst &, unsigned, const OpMatchers &...);
+
+public:
+ Reg(std::optional<MCPhysReg> RegToMatch = std::nullopt)
+ : OpMatcher<MCPhysReg>(RegToMatch) {}
+};
+
+class Imm : public OpMatcher<int64_t> {
+ bool matches(const MCOperand &Op) const {
+ if (!Op.isImm())
+ return false;
+
+ return matchValue(Op.getImm());
+ }
+
+ template <class... OpMatchers>
+ friend bool matchInst(const MCInst &, unsigned, const OpMatchers &...);
+
+public:
+ Imm(std::optional<int64_t> ImmToMatch = std::nullopt)
+ : OpMatcher<int64_t>(ImmToMatch) {}
+};
+
+/// Tries to match Inst and updates Ops on success.
+///
+/// If Inst has the specified Opcode and its operand list prefix matches Ops,
+/// this function returns true and updates Ops, otherwise false is returned and
+/// values of Ops are kept as before matchInst was called.
+///
+/// Please note that while Ops are technically passed by a const reference to
+/// make invocations like `matchInst(MI, Opcode, Imm(42))` possible, all their
+/// fields are marked mutable.
+template <class... OpMatchers>
+bool matchInst(const MCInst &Inst, unsigned Opcode, const OpMatchers &...Ops) {
+ if (Inst.getOpcode() != Opcode)
+ return false;
+ assert(sizeof...(Ops) <= Inst.getNumOperands() &&
+ "Too many operands are matched for the Opcode");
+
+ // Ask each matcher to remember its current value in case of rollback.
+ (Ops.remember(), ...);
+
+ // Check if all matchers match the corresponding operands.
+ auto It = Inst.begin();
+ auto AllMatched = (Ops.matches(*(It++)) && ... && true);
+
+ // If match failed, restore the original captured values.
+ if (!AllMatched) {
+ (Ops.restore(), ...);
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace MCInstMatcher
+
} // namespace bolt
} // namespace llvm
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index 72f95cea6fa1d..c1052ad5c0ba9 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -19,6 +19,7 @@
#include "Utils/AArch64BaseInfo.h"
#include "bolt/Core/BinaryBasicBlock.h"
#include "bolt/Core/BinaryFunction.h"
+#include "bolt/Core/MCInstUtils.h"
#include "bolt/Core/MCPlusBuilder.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCContext.h"
@@ -389,81 +390,58 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
// Iterate over the instructions of BB in reverse order, matching opcodes
// and operands.
- MCPhysReg TestedReg = 0;
- MCPhysReg ScratchReg = 0;
+
auto It = BB.end();
- auto StepAndGetOpcode = [&It, &BB]() -> int {
- if (It == BB.begin())
- return -1;
- --It;
- return It->getOpcode();
+ auto StepBack = [&]() {
+ while (It != BB.begin()) {
+ --It;
+ if (!isCFI(*It))
+ return true;
+ }
+ return false;
};
-
- switch (StepAndGetOpcode()) {
- default:
- // Not matched the branch instruction.
+ // Step to the last non-CFI instruction.
+ if (!StepBack())
return std::nullopt;
- case AArch64::Bcc:
- // Bcc EQ, .Lon_success
- if (It->getOperand(0).getImm() != AArch64CC::EQ)
- return std::nullopt;
- // Not checking .Lon_success (see above).
- // SUBSXrs XZR, TestedReg, ScratchReg, 0 (used by "CMP reg, reg" alias)
- if (StepAndGetOpcode() != AArch64::SUBSXrs ||
- It->getOperand(0).getReg() != AArch64::XZR ||
- It->getOperand(3).getImm() != 0)
+ using namespace llvm::bolt::MCInstMatcher;
+ Reg TestedReg;
+ Reg ScratchReg;
+
+ if (matchInst(*It, AArch64::Bcc, Imm(AArch64CC::EQ) /*, .Lon_success*/)) {
+ if (!StepBack() || !matchInst(*It, AArch64::SUBSXrs, Reg(AArch64::XZR),
+ TestedReg, ScratchReg, Imm(0)))
return std::nullopt;
- TestedReg = It->getOperand(1).getReg();
- ScratchReg = It->getOperand(2).getReg();
// Either XPAC(I|D) ScratchReg, ScratchReg
// or XPACLRI
- switch (StepAndGetOpcode()) {
- default:
+ if (!StepBack())
return std::nullopt;
- case AArch64::XPACLRI:
+ if (matchInst(*It, AArch64::XPACLRI)) {
// No operands to check, but using XPACLRI forces TestedReg to be X30.
- if (TestedReg != AArch64::LR)
- return std::nullopt;
- break;
- case AArch64::XPACI:
- case AArch64::XPACD:
- if (It->getOperand(0).getReg() != ScratchReg ||
- It->getOperand(1).getReg() != ScratchReg)
+ if (TestedReg.get() != AArch64::LR)
return std::nullopt;
- break;
+ } else if (!matchInst(*It, AArch64::XPACI, ScratchReg, ScratchReg) &&
+ !matchInst(*It, AArch64::XPACD, ScratchReg, ScratchReg)) {
+ return std::nullopt;
}
- // ORRXrs ScratchReg, XZR, TestedReg, 0 (used by "MOV reg, reg" alias)
- if (StepAndGetOpcode() != AArch64::ORRXrs)
+ if (!StepBack() || !matchInst(*It, AArch64::ORRXrs, ScratchReg,
+ Reg(AArch64::XZR), TestedReg, Imm(0)))
return std::nullopt;
- if (It->getOperand(0).getReg() != ScratchReg ||
- It->getOperand(1).getReg() != AArch64::XZR ||
- It->getOperand(2).getReg() != TestedReg ||
- It->getOperand(3).getImm() != 0)
- return std::nullopt;
-
- return std::make_pair(TestedReg, &*It);
- case AArch64::TBZX:
- // TBZX ScratchReg, 62, .Lon_success
- ScratchReg = It->getOperand(0).getReg();
- if (It->getOperand(1).getImm() != 62)
- return std::nullopt;
- // Not checking .Lon_success (see above).
+ return std::make_pair(TestedReg.get(), &*It);
+ }
- // EORXrs ScratchReg, TestedReg, TestedReg, 1
- if (StepAndGetOpcode() != AArch64::EORXrs)
- return std::nullopt;
- TestedReg = It->getOperand(1).getReg();
- if (It->getOperand(0).getReg() != ScratchReg ||
- It->getOperand(2).getReg() != TestedReg ||
- It->getOperand(3).getImm() != 1)
+ if (matchInst(*It, AArch64::TBZX, ScratchReg, Imm(62) /*, .Lon_success*/)) {
+ if (!StepBack() || !matchInst(*It, AArch64::EORXrs, Reg(ScratchReg),
+ TestedReg, TestedReg, Imm(1)))
return std::nullopt;
- return std::make_pair(TestedReg, &*It);
+ return std::make_pair(TestedReg.get(), &*It);
}
+
+ return std::nullopt;
}
std::optional<MCPhysReg> getAuthCheckedReg(const MCInst &Inst,
>From 750f4224326073253eecd81d16a1e410b2f489ef Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Tue, 6 May 2025 11:31:03 +0300
Subject: [PATCH 2/3] [BOLT] Gadget scanner: prevent false positives due to
jump tables
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.
---
bolt/include/bolt/Core/MCInstUtils.h | 9 +
bolt/include/bolt/Core/MCPlusBuilder.h | 14 +
bolt/lib/Core/MCInstUtils.cpp | 20 +
bolt/lib/Passes/PAuthGadgetScanner.cpp | 10 +
.../Target/AArch64/AArch64MCPlusBuilder.cpp | 73 ++
.../AArch64/gs-pauth-jump-table.s | 703 ++++++++++++++++++
6 files changed, 829 insertions(+)
create mode 100644 bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s
diff --git a/bolt/include/bolt/Core/MCInstUtils.h b/bolt/include/bolt/Core/MCInstUtils.h
index 40731a4fed701..f6ce5591ae57d 100644
--- a/bolt/include/bolt/Core/MCInstUtils.h
+++ b/bolt/include/bolt/Core/MCInstUtils.h
@@ -141,6 +141,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 ae04891e791f9..f13e41c75c2ac 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"
@@ -711,6 +712,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 3cdb9673d4dc0..39bc96f087f8e 100644
--- a/bolt/lib/Core/MCInstUtils.cpp
+++ b/bolt/lib/Core/MCInstUtils.cpp
@@ -54,3 +54,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 5d884e2d37354..3a6c3e3d925cf 100644
--- a/bolt/lib/Passes/PAuthGadgetScanner.cpp
+++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp
@@ -1370,6 +1370,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])
@@ -1400,6 +1405,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 c1052ad5c0ba9..751768df793ae 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, at 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", at 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, at 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", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_hint
+ .type jump_table_wrong_hint, at 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 at 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", at 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, at 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", at 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, at 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", at 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, at 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", at 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, at 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 at 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", at 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, at 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 at 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", at 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, at 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 at 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", at 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, at 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 at 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", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+// Test a few pattern violations...
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_reg_1
+ .type jump_table_wrong_reg_1, at function
+jump_table_wrong_reg_1:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_1, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_1, at address
+// CHECK-NEXT: The instruction is {{[0-9a-f]+}}: br x1 # UNKNOWN CONTROL FLOW
+// CHECK-NEXT: The 0 instructions that write to the affected registers after any authentication are:
+ 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 x1 // wrong reg
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_wrong_reg_1, .-jump_table_wrong_reg_1
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_reg_2
+ .type jump_table_wrong_reg_2, at function
+jump_table_wrong_reg_2:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_2, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_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, x1
+// CFG-NEXT: This happens in the following basic block:
+// CFG-NEXT: {{[0-9a-f]+}}: adr x17, __ENTRY_jump_table_wrong_reg_2 at 0x{{[0-9a-f]+}}
+// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x1
+// 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
+ add x16, x17, x1 // wrong reg
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_wrong_reg_2, .-jump_table_wrong_reg_2
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_reg_3
+ .type jump_table_wrong_reg_3, at function
+jump_table_wrong_reg_3:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_3, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_3, 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_reg_3 at 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, x1, :lo12:4f // wrong reg
+ 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_reg_3, .-jump_table_wrong_reg_3
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_reg_4
+ .type jump_table_wrong_reg_4, at function
+jump_table_wrong_reg_4:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_4, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_reg_4, 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_reg_4 at 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, x1, ls // wrong reg
+ 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_reg_4, .-jump_table_wrong_reg_4
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_imm_1
+ .type jump_table_wrong_imm_1, at function
+jump_table_wrong_imm_1:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_1, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_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_wrong_imm_1 at 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
+ ldrsw x16, [x17, x16, sxtx #2] // wrong: sxtx instead of lsl
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_wrong_imm_1, .-jump_table_wrong_imm_1
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_imm_2
+ .type jump_table_wrong_imm_2, at function
+jump_table_wrong_imm_2:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_2, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_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_wrong_imm_2 at 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, lt // wrong: lt instead of 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_imm_2, .-jump_table_wrong_imm_2
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl jump_table_wrong_imm_3
+ .type jump_table_wrong_imm_3, at function
+jump_table_wrong_imm_3:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_3, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function jump_table_wrong_imm_3, 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_imm_3 at 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
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16, lsl #2 // wrong: lsl #2
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size jump_table_wrong_imm_3, .-jump_table_wrong_imm_3
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+// CFI instructions should be skipped and should not prevent matching
+// the instruction sequence.
+
+ .text
+ .p2align 2
+ .globl skip_cfi_instructions
+ .type skip_cfi_instructions, at function
+skip_cfi_instructions:
+ .cfi_startproc
+// CHECK-NOT: skip_cfi_instructions
+// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function skip_cfi_instructions
+// CHECK-NOT: skip_cfi_instructions
+ paciasp
+ cmp x16, #0x2
+ csel x16, x16, xzr, ls
+ adrp x17, 4f
+ .cfi_def_cfa_offset 16 // should be skipped over when matching the sequence
+ 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 skip_cfi_instructions, .-skip_cfi_instructions
+ .cfi_endproc
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .p2align 2
+ .globl incomplete_jump_table
+ .type incomplete_jump_table, at function
+incomplete_jump_table:
+// CFG-LABEL: GS-PAUTH: non-protected call found in function incomplete_jump_table, basic block {{[^,]+}}, at address
+// NOCFG-LABEL: GS-PAUTH: non-protected call found in function incomplete_jump_table, 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_incomplete_jump_table at 0x{{[0-9a-f]+}}
+// CFG-NEXT: {{[0-9a-f]+}}: add x16, x17, x16
+// CFG-NEXT: {{[0-9a-f]+}}: br x16 # UNKNOWN CONTROL FLOW
+ // Do not try to step past the start of the function.
+ ldrsw x16, [x17, x16, lsl #2]
+1:
+ adr x17, 1b
+ add x16, x17, x16
+ br x16
+2:
+ autiasp
+ ret
+3:
+ autiasp
+ ret
+ .size incomplete_jump_table, .-incomplete_jump_table
+ .section .rodata,"a", at progbits
+ .p2align 2, 0x0
+4:
+ .word 2b-1b
+ .word 3b-1b
+
+ .text
+ .globl main
+ .type main, at function
+main:
+ mov x0, 0
+ ret
+ .size main, .-main
>From ca11a615dfd5e589cbc864aacbb5bc165b81ad05 Mon Sep 17 00:00:00 2001
From: Anatoly Trosinenko <atrosinenko at accesssoftek.com>
Date: Mon, 23 Jun 2025 17:32:19 +0300
Subject: [PATCH 3/3] Update warning message in tests
---
.../binary-analysis/AArch64/gs-pauth-jump-table.s | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s
index 5a42ed078e9c2..8af93ca8d9da5 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-jump-table.s
@@ -22,7 +22,7 @@
.type good_jump_table, at 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
+// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function good_jump_table
// CHECK-NOT: good_jump_table
paciasp
cmp x16, #0x2
@@ -56,7 +56,7 @@ good_jump_table:
.type jump_table_relaxed_adrp_add, at 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
+// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_relaxed_adrp_add
// CHECK-NOT: jump_table_relaxed_adrp_add
paciasp
cmp x16, #0x2
@@ -126,7 +126,7 @@ jump_table_wrong_hint:
.type jump_table_unsafe_reg_1, at 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
+// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_unsafe_reg_1
// CHECK-NOT: jump_table_unsafe_reg_1
paciasp
cmp x1, #0x2
@@ -157,7 +157,7 @@ jump_table_unsafe_reg_1:
.type jump_table_unsafe_reg_2, at 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
+// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_unsafe_reg_2
// CHECK-NOT: jump_table_unsafe_reg_2
paciasp
cmp x16, #0x2
@@ -189,7 +189,7 @@ jump_table_unsafe_reg_2:
.type jump_table_wrong_limit, at 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
+// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function jump_table_wrong_limit
// CHECK-NOT: jump_table_wrong_limit
paciasp
cmp x16, #0x1000
@@ -634,7 +634,7 @@ jump_table_wrong_imm_3:
skip_cfi_instructions:
.cfi_startproc
// CHECK-NOT: skip_cfi_instructions
-// CFG: GS-PAUTH: Warning: the function has unreachable basic blocks (possibly incomplete CFG) in function skip_cfi_instructions
+// CFG: GS-PAUTH: Warning: possibly imprecise CFG, the analysis quality may be degraded in this function. According to BOLT, unreachable code is found in function skip_cfi_instructions
// CHECK-NOT: skip_cfi_instructions
paciasp
cmp x16, #0x2
More information about the llvm-branch-commits
mailing list