mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-14 15:56:32 +00:00
[BOLT][binary-analysis] Add initial pac-ret gadget scanner (#122304)
This adds an initial pac-ret gadget scanner to the llvm-bolt-binary-analysis-tool. The scanner is taken from the prototype that was published last year at https://github.com/llvm/llvm-project/compare/main...kbeyls:llvm-project:bolt-gadget-scanner-prototype, and has been discussed in RFC https://discourse.llvm.org/t/rfc-bolt-based-binary-analysis-tool-to-verify-correctness-of-security-hardening/78148 and in the EuroLLVM 2024 keynote "Does LLVM implement security hardenings correctly? A BOLT-based static analyzer to the rescue?" [Video](https://youtu.be/Sn_Fxa0tdpY) [Slides](https://llvm.org/devmtg/2024-04/slides/Keynote/Beyls_EuroLLVM2024_security_hardening_keynote.pdf) In the spirit of incremental development, this PR aims to add a minimal implementation that is "fully working" on its own, but has major limitations, as described in the bolt/docs/BinaryAnalysis.md documentation in this proposed commit. These and other limitations will be fixed in follow-on PRs, mostly based on code already existing in the prototype branch. I hope incrementally upstreaming will make it easier to review the code. Note that I believe that this could also form the basis of a scanner to analyze correct implementation of PAuthABI.
This commit is contained in:
parent
86cb0bd3a2
commit
850b492976
@ -9,9 +9,182 @@ analyses implemented in the BOLT libraries.
|
||||
|
||||
## Which binary analyses are implemented?
|
||||
|
||||
At the moment, no binary analyses are implemented.
|
||||
* [Security scanners](#security-scanners)
|
||||
* [pac-ret analysis](#pac-ret-analysis)
|
||||
|
||||
The goal is to make it easy using a plug-in framework to add your own analyses.
|
||||
### Security scanners
|
||||
|
||||
For the past 25 years, a large numbers of exploits have been built and used in
|
||||
the wild to undermine computer security. The majority of these exploits abuse
|
||||
memory vulnerabilities in programs, see evidence from
|
||||
[Microsoft](https://youtu.be/PjbGojjnBZQ?si=oCHCa0SHgaSNr6Gr&t=836),
|
||||
[Chromium](https://www.chromium.org/Home/chromium-security/memory-safety/) and
|
||||
[Android](https://security.googleblog.com/2021/01/data-driven-security-hardening-in.html).
|
||||
|
||||
It is not surprising therefore, that a large number of mitigations have been
|
||||
added to instruction sets and toolchains to make it harder to build an exploit
|
||||
using a memory vulnerability. Examples are: stack canaries, stack clash,
|
||||
pac-ret, shadow stacks, arm64e, and many more.
|
||||
|
||||
These mitigations guarantee a so-called "security property" on the binaries they
|
||||
produce. For example, for stack canaries, the security property is roughly that
|
||||
a canary is located on the stack between the set of saved registers and the set
|
||||
of local variables. For pac-ret, it is roughly that either the return address is
|
||||
never stored/retrieved to/from memory; or, there are no writes to the register
|
||||
containing the return address between an instruction authenticating it and a
|
||||
return instruction using it.
|
||||
|
||||
From time to time, however, a bug gets found in the implementation of such
|
||||
mitigations in toolchains. Also, code that is written in assembler by hand
|
||||
requires the developer to ensure these security properties by hand.
|
||||
|
||||
In short, it is sometimes found that a few places in the binary code are not
|
||||
protected as well as expected given the requested mitigations. Attackers could
|
||||
make use of those places (sometimes called gadgets) to circumvent the protection
|
||||
that the mitigation should give.
|
||||
|
||||
One of the reasons that such gadgets, or holes in the mitigation implementation,
|
||||
exist is that typically the amount of testing and verification for these
|
||||
security properties is limited to checking results on specific examples.
|
||||
|
||||
In comparison, for testing functional correctness, or for testing performance,
|
||||
toolchain and software in general typically get tested with large test suites
|
||||
and benchmarks. In contrast, this typically does not get done for testing the
|
||||
security properties of binary code.
|
||||
|
||||
Unlike functional correctness where compilation errors result in test failures,
|
||||
and performance where speed and size differences are measurable, broken security
|
||||
properties cannot be easily observed using existing testing and benchmarking
|
||||
tools.
|
||||
|
||||
The security scanners implemented in `llvm-bolt-binary-analysis` aim to enable
|
||||
the testing of security hardening in arbitrary programs and not just specific
|
||||
examples.
|
||||
|
||||
|
||||
#### pac-ret analysis
|
||||
|
||||
`pac-ret` protection is a security hardening scheme implemented in compilers
|
||||
such as GCC and Clang, using the command line option
|
||||
`-mbranch-protection=pac-ret`. This option is enabled by default on most widely
|
||||
used Linux distributions.
|
||||
|
||||
The hardening scheme mitigates
|
||||
[Return-Oriented Programming (ROP)](https://llsoftsec.github.io/llsoftsecbook/#return-oriented-programming)
|
||||
attacks by making sure that return addresses are only ever stored to memory with
|
||||
a cryptographic hash, called a
|
||||
["Pointer Authentication Code" (PAC)](https://llsoftsec.github.io/llsoftsecbook/#pointer-authentication),
|
||||
in the upper bits of the pointer. This makes it substantially harder for
|
||||
attackers to divert control flow by overwriting a return address with a
|
||||
different value.
|
||||
|
||||
The hardening scheme relies on compilers producing appropriate code sequences when
|
||||
processing return addresses, especially when these are stored to and retrieved
|
||||
from memory.
|
||||
|
||||
The `pac-ret` binary analysis can be invoked using the command line option
|
||||
`--scanners=pac-ret`. It makes `llvm-bolt-binary-analysis` scan through the
|
||||
provided binary, checking each function for the following security property:
|
||||
|
||||
> For each procedure and exception return instruction, the destination register
|
||||
> must have one of the following properties:
|
||||
>
|
||||
> 1. be immutable within the function, or
|
||||
> 2. the last write to the register must be by an authenticating instruction. This
|
||||
> includes combined authentication and return instructions such as `RETAA`.
|
||||
|
||||
##### Example 1
|
||||
|
||||
For example, a typical non-pac-ret-protected function looks as follows:
|
||||
|
||||
```
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
mov x29, sp
|
||||
bl g@PLT
|
||||
add x0, x0, #0x3
|
||||
ldp x29, x30, [sp], #0x10
|
||||
ret
|
||||
```
|
||||
|
||||
The return instruction `ret` implicitly uses register `x30` as the address to
|
||||
return to. Register `x30` was last written by instruction `ldp`, which is not an
|
||||
authenticating instruction. `llvm-bolt-binary-analysis --scanners=pac-ret` will
|
||||
report this as follows:
|
||||
|
||||
```
|
||||
GS-PACRET: non-protected ret found in function f1, basic block .LBB00, at address 10310
|
||||
The return instruction is 00010310: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.LBB00:6>, Overwriting:[MCInstBBRef<BB:.LBB00:5> ]>
|
||||
The 1 instructions that write to the return register after any authentication are:
|
||||
1. 0001030c: ldp x29, x30, [sp], #0x10
|
||||
This happens in the following basic block:
|
||||
000102fc: stp x29, x30, [sp, #-0x10]!
|
||||
00010300: mov x29, sp
|
||||
00010304: bl g@PLT
|
||||
00010308: add x0, x0, #0x3
|
||||
0001030c: ldp x29, x30, [sp], #0x10
|
||||
00010310: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.LBB00:6>, Overwriting:[MCInstBBRef<BB:.LBB00:5> ]>
|
||||
```
|
||||
|
||||
The exact format of how `llvm-bolt-binary-analysis` reports this is expected to
|
||||
evolve over time.
|
||||
|
||||
##### Example 2: multiple "last-overwriting" instructions
|
||||
|
||||
A simple example that shows how there can be a set of "last overwriting"
|
||||
instructions of a register follows:
|
||||
|
||||
```
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
ldp x29, x30, [sp], #0x10
|
||||
cbnz x0, 1f
|
||||
autiasp
|
||||
1:
|
||||
ret
|
||||
```
|
||||
|
||||
This will produce the following diagnostic:
|
||||
|
||||
```
|
||||
GS-PACRET: non-protected ret found in function f_crossbb1, basic block .Ltmp0, at address 102dc
|
||||
The return instruction is 000102dc: ret # pacret-gadget: pac-ret-gadget<Ret:MCInstBBRef<BB:.Ltmp0:0>, Overwriting:[MCInstBBRef<BB:.LFT0:0> MCInstBBRef<BB:.LBB00:2> ]>
|
||||
The 2 instructions that write to the return register after any authentication are:
|
||||
1. 000102d0: ldp x29, x30, [sp], #0x10
|
||||
2. 000102d8: autiasp
|
||||
```
|
||||
|
||||
(Yes, this diagnostic could be improved because the second "overwriting"
|
||||
instruction, `autiasp`, is an authenticating instruction...)
|
||||
|
||||
##### Known false positives or negatives
|
||||
|
||||
The following are current known cases of false positives:
|
||||
|
||||
1. Not handling "no-return" functions. See issue
|
||||
[#115154](https://github.com/llvm/llvm-project/issues/115154) for details and
|
||||
pointers to open PRs to fix this.
|
||||
2. Not recognizing that a move of a properly authenticated value between registers,
|
||||
results in the destination register having a properly authenticated value.
|
||||
For example, the scanner currently produces a false negative for the following
|
||||
code sequence:
|
||||
```
|
||||
autiasp
|
||||
mov x16, x30
|
||||
ret x16
|
||||
```
|
||||
|
||||
The following are current known cases of false negatives:
|
||||
|
||||
1. Not handling functions for which the CFG cannot be reconstructed by BOLT. The
|
||||
plan is to implement support for this, picking up the implementation from the
|
||||
[prototype branch](
|
||||
https://github.com/llvm/llvm-project/compare/main...kbeyls:llvm-project:bolt-gadget-scanner-prototype).
|
||||
|
||||
BOLT cannot currently handle functions with `cfi_negate_ra_state` correctly,
|
||||
i.e. any binaries built with `-mbranch-protection=pac-ret`. The scanner is meant
|
||||
to be used on specifically such binaries, so this is a major limitation! Work is
|
||||
going on in PR [#120064](https://github.com/llvm/llvm-project/pull/120064) to
|
||||
fix this.
|
||||
|
||||
## How to add your own binary analysis
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "llvm/MC/MCInstrAnalysis.h"
|
||||
#include "llvm/MC/MCInstrDesc.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCRegister.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
@ -550,6 +551,22 @@ public:
|
||||
return Analysis->isReturn(Inst);
|
||||
}
|
||||
|
||||
virtual ErrorOr<MCPhysReg> getAuthenticatedReg(const MCInst &Inst) const {
|
||||
llvm_unreachable("not implemented");
|
||||
return getNoRegister();
|
||||
}
|
||||
|
||||
virtual bool isAuthenticationOfReg(const MCInst &Inst,
|
||||
MCPhysReg AuthenticatedReg) const {
|
||||
llvm_unreachable("not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual ErrorOr<MCPhysReg> getRegUsedAsRetDest(const MCInst &Inst) const {
|
||||
llvm_unreachable("not implemented");
|
||||
return getNoRegister();
|
||||
}
|
||||
|
||||
virtual bool isTerminator(const MCInst &Inst) const;
|
||||
|
||||
virtual bool isNoop(const MCInst &Inst) const {
|
||||
|
259
bolt/include/bolt/Passes/NonPacProtectedRetAnalysis.h
Normal file
259
bolt/include/bolt/Passes/NonPacProtectedRetAnalysis.h
Normal file
@ -0,0 +1,259 @@
|
||||
//===- bolt/Passes/NonPacProtectedRetAnalysis.h -----------------*- C++ -*-===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef BOLT_PASSES_NONPACPROTECTEDRETANALYSIS_H
|
||||
#define BOLT_PASSES_NONPACPROTECTEDRETANALYSIS_H
|
||||
|
||||
#include "bolt/Core/BinaryContext.h"
|
||||
#include "bolt/Core/BinaryFunction.h"
|
||||
#include "bolt/Passes/BinaryPasses.h"
|
||||
#include "llvm/ADT/SmallSet.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include "llvm/Testing/Annotations/Annotations.h"
|
||||
#include <memory>
|
||||
|
||||
namespace llvm {
|
||||
namespace bolt {
|
||||
|
||||
/// @brief MCInstReference represents a reference to an MCInst as stored either
|
||||
/// in a BinaryFunction (i.e. before a CFG is created), or in a BinaryBasicBlock
|
||||
/// (after a CFG is created). It aims to store the necessary information to be
|
||||
/// able to find the specific MCInst in either the BinaryFunction or
|
||||
/// BinaryBasicBlock data structures later, so that e.g. the InputAddress of
|
||||
/// the corresponding instruction can be computed.
|
||||
|
||||
struct MCInstInBBReference {
|
||||
BinaryBasicBlock *BB;
|
||||
int64_t BBIndex;
|
||||
MCInstInBBReference(BinaryBasicBlock *BB, int64_t BBIndex)
|
||||
: BB(BB), BBIndex(BBIndex) {}
|
||||
MCInstInBBReference() : BB(nullptr), BBIndex(0) {}
|
||||
static MCInstInBBReference get(const MCInst *Inst, BinaryFunction &BF) {
|
||||
for (BinaryBasicBlock &BB : BF)
|
||||
for (size_t I = 0; I < BB.size(); ++I)
|
||||
if (Inst == &BB.getInstructionAtIndex(I))
|
||||
return MCInstInBBReference(&BB, I);
|
||||
return {};
|
||||
}
|
||||
bool operator==(const MCInstInBBReference &RHS) const {
|
||||
return BB == RHS.BB && BBIndex == RHS.BBIndex;
|
||||
}
|
||||
bool operator<(const MCInstInBBReference &RHS) const {
|
||||
if (BB != RHS.BB)
|
||||
return BB < RHS.BB;
|
||||
return BBIndex < RHS.BBIndex;
|
||||
}
|
||||
operator MCInst &() const {
|
||||
assert(BB != nullptr);
|
||||
return BB->getInstructionAtIndex(BBIndex);
|
||||
}
|
||||
uint64_t getAddress() const {
|
||||
// 4 bytes per instruction on AArch64.
|
||||
// FIXME: the assumption of 4 byte per instruction needs to be fixed before
|
||||
// this method gets used on any non-AArch64 binaries (but should be fine for
|
||||
// pac-ret analysis, as that is an AArch64-specific feature).
|
||||
return BB->getFunction()->getAddress() + BB->getOffset() + BBIndex * 4;
|
||||
}
|
||||
};
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const MCInstInBBReference &);
|
||||
|
||||
struct MCInstInBFReference {
|
||||
BinaryFunction *BF;
|
||||
uint64_t Offset;
|
||||
MCInstInBFReference(BinaryFunction *BF, uint64_t Offset)
|
||||
: BF(BF), Offset(Offset) {}
|
||||
MCInstInBFReference() : BF(nullptr), Offset(0) {}
|
||||
bool operator==(const MCInstInBFReference &RHS) const {
|
||||
return BF == RHS.BF && Offset == RHS.Offset;
|
||||
}
|
||||
bool operator<(const MCInstInBFReference &RHS) const {
|
||||
if (BF != RHS.BF)
|
||||
return BF < RHS.BF;
|
||||
return Offset < RHS.Offset;
|
||||
}
|
||||
operator MCInst &() const {
|
||||
assert(BF != nullptr);
|
||||
return *BF->getInstructionAtOffset(Offset);
|
||||
}
|
||||
|
||||
uint64_t getOffset() const { return Offset; }
|
||||
|
||||
uint64_t getAddress() const { return BF->getAddress() + getOffset(); }
|
||||
};
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const MCInstInBFReference &);
|
||||
|
||||
struct MCInstReference {
|
||||
enum Kind { FunctionParent, BasicBlockParent };
|
||||
Kind ParentKind;
|
||||
union U {
|
||||
MCInstInBBReference BBRef;
|
||||
MCInstInBFReference BFRef;
|
||||
U(MCInstInBBReference BBRef) : BBRef(BBRef) {}
|
||||
U(MCInstInBFReference BFRef) : BFRef(BFRef) {}
|
||||
} U;
|
||||
MCInstReference(MCInstInBBReference BBRef)
|
||||
: ParentKind(BasicBlockParent), U(BBRef) {}
|
||||
MCInstReference(MCInstInBFReference BFRef)
|
||||
: ParentKind(FunctionParent), U(BFRef) {}
|
||||
MCInstReference(BinaryBasicBlock *BB, int64_t BBIndex)
|
||||
: MCInstReference(MCInstInBBReference(BB, BBIndex)) {}
|
||||
MCInstReference(BinaryFunction *BF, uint32_t Offset)
|
||||
: MCInstReference(MCInstInBFReference(BF, Offset)) {}
|
||||
|
||||
bool operator<(const MCInstReference &RHS) const {
|
||||
if (ParentKind != RHS.ParentKind)
|
||||
return ParentKind < RHS.ParentKind;
|
||||
switch (ParentKind) {
|
||||
case BasicBlockParent:
|
||||
return U.BBRef < RHS.U.BBRef;
|
||||
case FunctionParent:
|
||||
return U.BFRef < RHS.U.BFRef;
|
||||
}
|
||||
llvm_unreachable("");
|
||||
}
|
||||
|
||||
bool operator==(const MCInstReference &RHS) const {
|
||||
if (ParentKind != RHS.ParentKind)
|
||||
return false;
|
||||
switch (ParentKind) {
|
||||
case BasicBlockParent:
|
||||
return U.BBRef == RHS.U.BBRef;
|
||||
case FunctionParent:
|
||||
return U.BFRef == RHS.U.BFRef;
|
||||
}
|
||||
llvm_unreachable("");
|
||||
}
|
||||
|
||||
operator MCInst &() const {
|
||||
switch (ParentKind) {
|
||||
case BasicBlockParent:
|
||||
return U.BBRef;
|
||||
case FunctionParent:
|
||||
return U.BFRef;
|
||||
}
|
||||
llvm_unreachable("");
|
||||
}
|
||||
|
||||
uint64_t getAddress() const {
|
||||
switch (ParentKind) {
|
||||
case BasicBlockParent:
|
||||
return U.BBRef.getAddress();
|
||||
case FunctionParent:
|
||||
return U.BFRef.getAddress();
|
||||
}
|
||||
llvm_unreachable("");
|
||||
}
|
||||
|
||||
BinaryFunction *getFunction() const {
|
||||
switch (ParentKind) {
|
||||
case FunctionParent:
|
||||
return U.BFRef.BF;
|
||||
case BasicBlockParent:
|
||||
return U.BBRef.BB->getFunction();
|
||||
}
|
||||
llvm_unreachable("");
|
||||
}
|
||||
|
||||
BinaryBasicBlock *getBasicBlock() const {
|
||||
switch (ParentKind) {
|
||||
case FunctionParent:
|
||||
return nullptr;
|
||||
case BasicBlockParent:
|
||||
return U.BBRef.BB;
|
||||
}
|
||||
llvm_unreachable("");
|
||||
}
|
||||
};
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const MCInstReference &);
|
||||
|
||||
struct GeneralDiagnostic {
|
||||
std::string Text;
|
||||
GeneralDiagnostic(const std::string &Text) : Text(Text) {}
|
||||
bool operator==(const GeneralDiagnostic &RHS) const {
|
||||
return Text == RHS.Text;
|
||||
}
|
||||
};
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const GeneralDiagnostic &Diag);
|
||||
|
||||
namespace NonPacProtectedRetAnalysis {
|
||||
struct Annotation {
|
||||
MCInstReference RetInst;
|
||||
Annotation(MCInstReference RetInst) : RetInst(RetInst) {}
|
||||
virtual bool operator==(const Annotation &RHS) const {
|
||||
return RetInst == RHS.RetInst;
|
||||
}
|
||||
Annotation &operator=(const Annotation &Other) {
|
||||
if (this == &Other)
|
||||
return *this;
|
||||
RetInst = Other.RetInst;
|
||||
return *this;
|
||||
}
|
||||
virtual ~Annotation() {}
|
||||
virtual void generateReport(raw_ostream &OS,
|
||||
const BinaryContext &BC) const = 0;
|
||||
};
|
||||
|
||||
struct Gadget : public Annotation {
|
||||
std::vector<MCInstReference> OverwritingRetRegInst;
|
||||
virtual bool operator==(const Gadget &RHS) const {
|
||||
return Annotation::operator==(RHS) &&
|
||||
OverwritingRetRegInst == RHS.OverwritingRetRegInst;
|
||||
}
|
||||
Gadget(MCInstReference RetInst,
|
||||
const std::vector<MCInstReference> &OverwritingRetRegInst)
|
||||
: Annotation(RetInst), OverwritingRetRegInst(OverwritingRetRegInst) {}
|
||||
virtual void generateReport(raw_ostream &OS,
|
||||
const BinaryContext &BC) const override;
|
||||
};
|
||||
|
||||
struct GenDiag : public Annotation {
|
||||
GeneralDiagnostic Diag;
|
||||
virtual bool operator==(const GenDiag &RHS) const {
|
||||
return Annotation::operator==(RHS) && Diag == RHS.Diag;
|
||||
}
|
||||
GenDiag(MCInstReference RetInst, const std::string &Text)
|
||||
: Annotation(RetInst), Diag(Text) {}
|
||||
virtual void generateReport(raw_ostream &OS,
|
||||
const BinaryContext &BC) const override;
|
||||
};
|
||||
|
||||
class PacRetAnalysis;
|
||||
|
||||
struct FunctionAnalysisResult {
|
||||
SmallSet<MCPhysReg, 1> RegistersAffected;
|
||||
std::vector<std::shared_ptr<Annotation>> Diagnostics;
|
||||
};
|
||||
|
||||
class Analysis : public BinaryFunctionPass {
|
||||
void runOnFunction(BinaryFunction &Function,
|
||||
MCPlusBuilder::AllocatorIdTy AllocatorId);
|
||||
FunctionAnalysisResult
|
||||
computeDfState(PacRetAnalysis &PRA, BinaryFunction &BF,
|
||||
MCPlusBuilder::AllocatorIdTy AllocatorId);
|
||||
|
||||
std::map<const BinaryFunction *, FunctionAnalysisResult> AnalysisResults;
|
||||
std::mutex AnalysisResultsMutex;
|
||||
|
||||
public:
|
||||
explicit Analysis() : BinaryFunctionPass(false) {}
|
||||
|
||||
const char *getName() const override { return "non-pac-protected-rets"; }
|
||||
|
||||
/// Pass entry point
|
||||
Error runOnFunctions(BinaryContext &BC) override;
|
||||
};
|
||||
|
||||
} // namespace NonPacProtectedRetAnalysis
|
||||
} // namespace bolt
|
||||
} // namespace llvm
|
||||
|
||||
#endif
|
@ -80,6 +80,10 @@ extern llvm::cl::opt<unsigned> Verbosity;
|
||||
/// Return true if we should process all functions in the binary.
|
||||
bool processAllFunctions();
|
||||
|
||||
enum GadgetScannerKind { GS_PACRET, GS_ALL };
|
||||
|
||||
extern llvm::cl::list<GadgetScannerKind> GadgetScannersToRun;
|
||||
|
||||
} // namespace opts
|
||||
|
||||
namespace llvm {
|
||||
|
@ -23,6 +23,7 @@ add_llvm_library(LLVMBOLTPasses
|
||||
LoopInversionPass.cpp
|
||||
LivenessAnalysis.cpp
|
||||
MCF.cpp
|
||||
NonPacProtectedRetAnalysis.cpp
|
||||
PatchEntries.cpp
|
||||
PettisAndHansen.cpp
|
||||
PLTCall.cpp
|
||||
|
526
bolt/lib/Passes/NonPacProtectedRetAnalysis.cpp
Normal file
526
bolt/lib/Passes/NonPacProtectedRetAnalysis.cpp
Normal file
@ -0,0 +1,526 @@
|
||||
//===- bolt/Passes/NonPacProtectedRetAnalysis.cpp -------------------------===//
|
||||
//
|
||||
// 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 file implements a pass that looks for any AArch64 return instructions
|
||||
// that may not be protected by PAuth authentication instructions when needed.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "bolt/Passes/NonPacProtectedRetAnalysis.h"
|
||||
#include "bolt/Core/ParallelUtilities.h"
|
||||
#include "bolt/Passes/DataflowAnalysis.h"
|
||||
#include "llvm/ADT/SmallSet.h"
|
||||
#include "llvm/MC/MCInst.h"
|
||||
#include "llvm/Support/Format.h"
|
||||
#include <memory>
|
||||
|
||||
#define DEBUG_TYPE "bolt-nonpacprotectedret"
|
||||
|
||||
namespace llvm {
|
||||
namespace bolt {
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const MCInstInBBReference &Ref) {
|
||||
OS << "MCInstBBRef<";
|
||||
if (Ref.BB == nullptr)
|
||||
OS << "BB:(null)";
|
||||
else
|
||||
OS << "BB:" << Ref.BB->getName() << ":" << Ref.BBIndex;
|
||||
OS << ">";
|
||||
return OS;
|
||||
}
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const MCInstInBFReference &Ref) {
|
||||
OS << "MCInstBFRef<";
|
||||
if (Ref.BF == nullptr)
|
||||
OS << "BF:(null)";
|
||||
else
|
||||
OS << "BF:" << Ref.BF->getPrintName() << ":" << Ref.getOffset();
|
||||
OS << ">";
|
||||
return OS;
|
||||
}
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const MCInstReference &Ref) {
|
||||
switch (Ref.ParentKind) {
|
||||
case MCInstReference::BasicBlockParent:
|
||||
OS << Ref.U.BBRef;
|
||||
return OS;
|
||||
case MCInstReference::FunctionParent:
|
||||
OS << Ref.U.BFRef;
|
||||
return OS;
|
||||
}
|
||||
llvm_unreachable("");
|
||||
}
|
||||
|
||||
namespace NonPacProtectedRetAnalysis {
|
||||
|
||||
// The security property that is checked is:
|
||||
// When a register is used as the address to jump to in a return instruction,
|
||||
// that register must either:
|
||||
// (a) never be changed within this function, i.e. have the same value as when
|
||||
// the function started, or
|
||||
// (b) the last write to the register must be by an authentication instruction.
|
||||
|
||||
// This property is checked by using dataflow analysis to keep track of which
|
||||
// registers have been written (def-ed), since last authenticated. Those are
|
||||
// exactly the registers containing values that should not be trusted (as they
|
||||
// could have changed since the last time they were authenticated). For pac-ret,
|
||||
// any return instruction using such a register is a gadget to be reported. For
|
||||
// PAuthABI, probably at least any indirect control flow using such a register
|
||||
// should be reported.
|
||||
|
||||
// Furthermore, when producing a diagnostic for a found non-pac-ret protected
|
||||
// return, the analysis also lists the last instructions that wrote to the
|
||||
// register used in the return instruction.
|
||||
// The total set of registers used in return instructions in a given function is
|
||||
// small. It almost always is just `X30`.
|
||||
// In order to reduce the memory consumption of storing this additional state
|
||||
// during the dataflow analysis, this is computed by running the dataflow
|
||||
// analysis twice:
|
||||
// 1. In the first run, the dataflow analysis only keeps track of the security
|
||||
// property: i.e. which registers have been overwritten since the last
|
||||
// time they've been authenticated.
|
||||
// 2. If the first run finds any return instructions using a register last
|
||||
// written by a non-authenticating instruction, the dataflow analysis will
|
||||
// be run a second time. The first run will return which registers are used
|
||||
// in the gadgets to be reported. This information is used in the second run
|
||||
// to also track which instructions last wrote to those registers.
|
||||
|
||||
struct State {
|
||||
/// A BitVector containing the registers that have been clobbered, and
|
||||
/// not authenticated.
|
||||
BitVector NonAutClobRegs;
|
||||
/// A vector of sets, only used in the second data flow run.
|
||||
/// Each element in the vector represents one of the registers for which we
|
||||
/// track the set of last instructions that wrote to this register. For
|
||||
/// pac-ret analysis, the expectation is that almost all return instructions
|
||||
/// only use register `X30`, and therefore, this vector will probably have
|
||||
/// length 1 in the second run.
|
||||
std::vector<SmallPtrSet<const MCInst *, 4>> LastInstWritingReg;
|
||||
State() {}
|
||||
State(unsigned NumRegs, unsigned NumRegsToTrack)
|
||||
: NonAutClobRegs(NumRegs), LastInstWritingReg(NumRegsToTrack) {}
|
||||
State &operator|=(const State &StateIn) {
|
||||
NonAutClobRegs |= StateIn.NonAutClobRegs;
|
||||
for (unsigned I = 0; I < LastInstWritingReg.size(); ++I)
|
||||
for (const MCInst *J : StateIn.LastInstWritingReg[I])
|
||||
LastInstWritingReg[I].insert(J);
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const State &RHS) const {
|
||||
return NonAutClobRegs == RHS.NonAutClobRegs &&
|
||||
LastInstWritingReg == RHS.LastInstWritingReg;
|
||||
}
|
||||
bool operator!=(const State &RHS) const { return !((*this) == RHS); }
|
||||
};
|
||||
|
||||
static void printLastInsts(
|
||||
raw_ostream &OS,
|
||||
const std::vector<SmallPtrSet<const MCInst *, 4>> &LastInstWritingReg) {
|
||||
OS << "Insts: ";
|
||||
for (unsigned I = 0; I < LastInstWritingReg.size(); ++I) {
|
||||
auto &Set = LastInstWritingReg[I];
|
||||
OS << "[" << I << "](";
|
||||
for (const MCInst *MCInstP : Set)
|
||||
OS << MCInstP << " ";
|
||||
OS << ")";
|
||||
}
|
||||
}
|
||||
|
||||
raw_ostream &operator<<(raw_ostream &OS, const State &S) {
|
||||
OS << "pacret-state<";
|
||||
OS << "NonAutClobRegs: " << S.NonAutClobRegs << ", ";
|
||||
printLastInsts(OS, S.LastInstWritingReg);
|
||||
OS << ">";
|
||||
return OS;
|
||||
}
|
||||
|
||||
class PacStatePrinter {
|
||||
public:
|
||||
void print(raw_ostream &OS, const State &State) const;
|
||||
explicit PacStatePrinter(const BinaryContext &BC) : BC(BC) {}
|
||||
|
||||
private:
|
||||
const BinaryContext &BC;
|
||||
};
|
||||
|
||||
void PacStatePrinter::print(raw_ostream &OS, const State &S) const {
|
||||
RegStatePrinter RegStatePrinter(BC);
|
||||
OS << "pacret-state<";
|
||||
OS << "NonAutClobRegs: ";
|
||||
RegStatePrinter.print(OS, S.NonAutClobRegs);
|
||||
OS << ", ";
|
||||
printLastInsts(OS, S.LastInstWritingReg);
|
||||
OS << ">";
|
||||
}
|
||||
|
||||
class PacRetAnalysis
|
||||
: public DataflowAnalysis<PacRetAnalysis, State, /*Backward=*/false,
|
||||
PacStatePrinter> {
|
||||
using Parent =
|
||||
DataflowAnalysis<PacRetAnalysis, State, false, PacStatePrinter>;
|
||||
friend Parent;
|
||||
|
||||
public:
|
||||
PacRetAnalysis(BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocId,
|
||||
const std::vector<MCPhysReg> &RegsToTrackInstsFor)
|
||||
: Parent(BF, AllocId), NumRegs(BF.getBinaryContext().MRI->getNumRegs()),
|
||||
RegsToTrackInstsFor(RegsToTrackInstsFor),
|
||||
TrackingLastInsts(!RegsToTrackInstsFor.empty()),
|
||||
Reg2StateIdx(RegsToTrackInstsFor.empty()
|
||||
? 0
|
||||
: *llvm::max_element(RegsToTrackInstsFor) + 1,
|
||||
-1) {
|
||||
for (unsigned I = 0; I < RegsToTrackInstsFor.size(); ++I)
|
||||
Reg2StateIdx[RegsToTrackInstsFor[I]] = I;
|
||||
}
|
||||
virtual ~PacRetAnalysis() {}
|
||||
|
||||
protected:
|
||||
const unsigned NumRegs;
|
||||
/// RegToTrackInstsFor is the set of registers for which the dataflow analysis
|
||||
/// must compute which the last set of instructions writing to it are.
|
||||
const std::vector<MCPhysReg> RegsToTrackInstsFor;
|
||||
const bool TrackingLastInsts;
|
||||
/// Reg2StateIdx maps Register to the index in the vector used in State to
|
||||
/// track which instructions last wrote to this register.
|
||||
std::vector<uint16_t> Reg2StateIdx;
|
||||
|
||||
SmallPtrSet<const MCInst *, 4> &lastWritingInsts(State &S,
|
||||
MCPhysReg Reg) const {
|
||||
assert(Reg < Reg2StateIdx.size());
|
||||
assert(isTrackingReg(Reg));
|
||||
return S.LastInstWritingReg[Reg2StateIdx[Reg]];
|
||||
}
|
||||
const SmallPtrSet<const MCInst *, 4> &lastWritingInsts(const State &S,
|
||||
MCPhysReg Reg) const {
|
||||
assert(Reg < Reg2StateIdx.size());
|
||||
assert(isTrackingReg(Reg));
|
||||
return S.LastInstWritingReg[Reg2StateIdx[Reg]];
|
||||
}
|
||||
|
||||
bool isTrackingReg(MCPhysReg Reg) const {
|
||||
return llvm::is_contained(RegsToTrackInstsFor, Reg);
|
||||
}
|
||||
|
||||
void preflight() {}
|
||||
|
||||
State getStartingStateAtBB(const BinaryBasicBlock &BB) {
|
||||
return State(NumRegs, RegsToTrackInstsFor.size());
|
||||
}
|
||||
|
||||
State getStartingStateAtPoint(const MCInst &Point) {
|
||||
return State(NumRegs, RegsToTrackInstsFor.size());
|
||||
}
|
||||
|
||||
void doConfluence(State &StateOut, const State &StateIn) {
|
||||
PacStatePrinter P(BC);
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " PacRetAnalysis::Confluence(\n";
|
||||
dbgs() << " State 1: ";
|
||||
P.print(dbgs(), StateOut);
|
||||
dbgs() << "\n";
|
||||
dbgs() << " State 2: ";
|
||||
P.print(dbgs(), StateIn);
|
||||
dbgs() << ")\n";
|
||||
});
|
||||
|
||||
StateOut |= StateIn;
|
||||
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " merged state: ";
|
||||
P.print(dbgs(), StateOut);
|
||||
dbgs() << "\n";
|
||||
});
|
||||
}
|
||||
|
||||
State computeNext(const MCInst &Point, const State &Cur) {
|
||||
PacStatePrinter P(BC);
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " PacRetAnalysis::ComputeNext(";
|
||||
BC.InstPrinter->printInst(&const_cast<MCInst &>(Point), 0, "", *BC.STI,
|
||||
dbgs());
|
||||
dbgs() << ", ";
|
||||
P.print(dbgs(), Cur);
|
||||
dbgs() << ")\n";
|
||||
});
|
||||
|
||||
State Next = Cur;
|
||||
BitVector Written = BitVector(NumRegs, false);
|
||||
// Assume a call can clobber all registers, including callee-saved
|
||||
// registers. There's a good chance that callee-saved registers will be
|
||||
// saved on the stack at some point during execution of the callee.
|
||||
// Therefore they should also be considered as potentially modified by an
|
||||
// attacker/written to.
|
||||
// Also, not all functions may respect the AAPCS ABI rules about
|
||||
// caller/callee-saved registers.
|
||||
if (BC.MIB->isCall(Point))
|
||||
Written.set();
|
||||
else
|
||||
// FIXME: `getWrittenRegs` only sets the register directly written in the
|
||||
// instruction, and the smaller aliasing registers. It does not set the
|
||||
// larger aliasing registers. To also set the larger aliasing registers,
|
||||
// we'd have to call `getClobberedRegs`.
|
||||
// It is unclear if there is any test case which shows a different
|
||||
// behaviour between using `getWrittenRegs` vs `getClobberedRegs`. We'd
|
||||
// first would like to see such a test case before making a decision
|
||||
// on whether using `getClobberedRegs` below would be better.
|
||||
// Also see the discussion on this at
|
||||
// https://github.com/llvm/llvm-project/pull/122304#discussion_r1939511909
|
||||
BC.MIB->getWrittenRegs(Point, Written);
|
||||
Next.NonAutClobRegs |= Written;
|
||||
// Keep track of this instruction if it writes to any of the registers we
|
||||
// need to track that for:
|
||||
for (MCPhysReg Reg : RegsToTrackInstsFor)
|
||||
if (Written[Reg])
|
||||
lastWritingInsts(Next, Reg) = {&Point};
|
||||
|
||||
ErrorOr<MCPhysReg> AutReg = BC.MIB->getAuthenticatedReg(Point);
|
||||
if (AutReg && *AutReg != BC.MIB->getNoRegister()) {
|
||||
// FIXME: should we use `OnlySmaller=false` below? See similar
|
||||
// FIXME about `getWrittenRegs` above and further discussion about this
|
||||
// at
|
||||
// https://github.com/llvm/llvm-project/pull/122304#discussion_r1939515516
|
||||
Next.NonAutClobRegs.reset(
|
||||
BC.MIB->getAliases(*AutReg, /*OnlySmaller=*/true));
|
||||
if (TrackingLastInsts && isTrackingReg(*AutReg))
|
||||
lastWritingInsts(Next, *AutReg).clear();
|
||||
}
|
||||
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " .. result: (";
|
||||
P.print(dbgs(), Next);
|
||||
dbgs() << ")\n";
|
||||
});
|
||||
|
||||
return Next;
|
||||
}
|
||||
|
||||
StringRef getAnnotationName() const { return StringRef("PacRetAnalysis"); }
|
||||
|
||||
public:
|
||||
std::vector<MCInstReference>
|
||||
getLastClobberingInsts(const MCInst Ret, BinaryFunction &BF,
|
||||
const BitVector &UsedDirtyRegs) const {
|
||||
if (!TrackingLastInsts)
|
||||
return {};
|
||||
auto MaybeState = getStateAt(Ret);
|
||||
if (!MaybeState)
|
||||
llvm_unreachable("Expected State to be present");
|
||||
const State &S = *MaybeState;
|
||||
// Due to aliasing registers, multiple registers may have been tracked.
|
||||
std::set<const MCInst *> LastWritingInsts;
|
||||
for (MCPhysReg TrackedReg : UsedDirtyRegs.set_bits()) {
|
||||
for (const MCInst *Inst : lastWritingInsts(S, TrackedReg))
|
||||
LastWritingInsts.insert(Inst);
|
||||
}
|
||||
std::vector<MCInstReference> Result;
|
||||
for (const MCInst *Inst : LastWritingInsts) {
|
||||
MCInstInBBReference Ref = MCInstInBBReference::get(Inst, BF);
|
||||
assert(Ref.BB != nullptr && "Expected Inst to be found");
|
||||
Result.push_back(MCInstReference(Ref));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
};
|
||||
|
||||
FunctionAnalysisResult
|
||||
Analysis::computeDfState(PacRetAnalysis &PRA, BinaryFunction &BF,
|
||||
MCPlusBuilder::AllocatorIdTy AllocatorId) {
|
||||
PRA.run();
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " After PacRetAnalysis:\n";
|
||||
BF.dump();
|
||||
});
|
||||
|
||||
FunctionAnalysisResult Result;
|
||||
// Now scan the CFG for non-authenticating return instructions that use an
|
||||
// overwritten, non-authenticated register as return address.
|
||||
BinaryContext &BC = BF.getBinaryContext();
|
||||
for (BinaryBasicBlock &BB : BF) {
|
||||
for (int64_t I = BB.size() - 1; I >= 0; --I) {
|
||||
MCInst &Inst = BB.getInstructionAtIndex(I);
|
||||
if (BC.MIB->isReturn(Inst)) {
|
||||
ErrorOr<MCPhysReg> MaybeRetReg = BC.MIB->getRegUsedAsRetDest(Inst);
|
||||
if (MaybeRetReg.getError()) {
|
||||
Result.Diagnostics.push_back(std::make_shared<GenDiag>(
|
||||
MCInstInBBReference(&BB, I),
|
||||
"Warning: pac-ret analysis could not analyze this return "
|
||||
"instruction"));
|
||||
continue;
|
||||
}
|
||||
MCPhysReg RetReg = *MaybeRetReg;
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " Found RET inst: ";
|
||||
BC.printInstruction(dbgs(), Inst);
|
||||
dbgs() << " RetReg: " << BC.MRI->getName(RetReg)
|
||||
<< "; authenticatesReg: "
|
||||
<< BC.MIB->isAuthenticationOfReg(Inst, RetReg) << "\n";
|
||||
});
|
||||
if (BC.MIB->isAuthenticationOfReg(Inst, RetReg))
|
||||
break;
|
||||
BitVector UsedDirtyRegs = PRA.getStateAt(Inst)->NonAutClobRegs;
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " NonAutClobRegs at Ret: ";
|
||||
RegStatePrinter RSP(BC);
|
||||
RSP.print(dbgs(), UsedDirtyRegs);
|
||||
dbgs() << "\n";
|
||||
});
|
||||
UsedDirtyRegs &= BC.MIB->getAliases(RetReg, /*OnlySmaller=*/true);
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " Intersection with RetReg: ";
|
||||
RegStatePrinter RSP(BC);
|
||||
RSP.print(dbgs(), UsedDirtyRegs);
|
||||
dbgs() << "\n";
|
||||
});
|
||||
if (UsedDirtyRegs.any()) {
|
||||
// This return instruction needs to be reported
|
||||
Result.Diagnostics.push_back(std::make_shared<Gadget>(
|
||||
MCInstInBBReference(&BB, I),
|
||||
PRA.getLastClobberingInsts(Inst, BF, UsedDirtyRegs)));
|
||||
for (MCPhysReg RetRegWithGadget : UsedDirtyRegs.set_bits())
|
||||
Result.RegistersAffected.insert(RetRegWithGadget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
void Analysis::runOnFunction(BinaryFunction &BF,
|
||||
MCPlusBuilder::AllocatorIdTy AllocatorId) {
|
||||
LLVM_DEBUG({
|
||||
dbgs() << "Analyzing in function " << BF.getPrintName() << ", AllocatorId "
|
||||
<< AllocatorId << "\n";
|
||||
BF.dump();
|
||||
});
|
||||
|
||||
if (BF.hasCFG()) {
|
||||
PacRetAnalysis PRA(BF, AllocatorId, {});
|
||||
FunctionAnalysisResult FAR = computeDfState(PRA, BF, AllocatorId);
|
||||
if (!FAR.RegistersAffected.empty()) {
|
||||
// Redo the analysis, but now also track which instructions last wrote
|
||||
// to any of the registers in RetRegsWithGadgets, so that better
|
||||
// diagnostics can be produced.
|
||||
std::vector<MCPhysReg> RegsToTrack;
|
||||
for (MCPhysReg R : FAR.RegistersAffected)
|
||||
RegsToTrack.push_back(R);
|
||||
PacRetAnalysis PRWIA(BF, AllocatorId, RegsToTrack);
|
||||
FAR = computeDfState(PRWIA, BF, AllocatorId);
|
||||
}
|
||||
|
||||
// `runOnFunction` is typically getting called from multiple threads in
|
||||
// parallel. Therefore, use a lock to avoid data races when storing the
|
||||
// result of the analysis in the `AnalysisResults` map.
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(AnalysisResultsMutex);
|
||||
AnalysisResults[&BF] = FAR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void printBB(const BinaryContext &BC, const BinaryBasicBlock *BB,
|
||||
size_t StartIndex = 0, size_t EndIndex = -1) {
|
||||
if (EndIndex == (size_t)-1)
|
||||
EndIndex = BB->size() - 1;
|
||||
const BinaryFunction *BF = BB->getFunction();
|
||||
for (unsigned I = StartIndex; I <= EndIndex; ++I) {
|
||||
// FIXME: this assumes all instructions are 4 bytes in size. This is true
|
||||
// for AArch64, but it might be good to extract this function so it can be
|
||||
// used elsewhere and for other targets too.
|
||||
uint64_t Address = BB->getOffset() + BF->getAddress() + 4 * I;
|
||||
const MCInst &Inst = BB->getInstructionAtIndex(I);
|
||||
if (BC.MIB->isCFI(Inst))
|
||||
continue;
|
||||
BC.printInstruction(outs(), Inst, Address, BF);
|
||||
}
|
||||
}
|
||||
|
||||
static void reportFoundGadgetInSingleBBSingleOverwInst(
|
||||
raw_ostream &OS, const BinaryContext &BC, const MCInstReference OverwInst,
|
||||
const MCInstReference RetInst) {
|
||||
BinaryBasicBlock *BB = RetInst.getBasicBlock();
|
||||
assert(OverwInst.ParentKind == MCInstReference::BasicBlockParent);
|
||||
assert(RetInst.ParentKind == MCInstReference::BasicBlockParent);
|
||||
MCInstInBBReference OverwInstBB = OverwInst.U.BBRef;
|
||||
if (BB == OverwInstBB.BB) {
|
||||
// overwriting inst and ret instruction are in the same basic block.
|
||||
assert(OverwInstBB.BBIndex < RetInst.U.BBRef.BBIndex);
|
||||
OS << " This happens in the following basic block:\n";
|
||||
printBB(BC, BB);
|
||||
}
|
||||
}
|
||||
|
||||
void Gadget::generateReport(raw_ostream &OS, const BinaryContext &BC) const {
|
||||
GenDiag(RetInst, "non-protected ret found").generateReport(OS, BC);
|
||||
|
||||
BinaryFunction *BF = RetInst.getFunction();
|
||||
OS << " The " << OverwritingRetRegInst.size()
|
||||
<< " instructions that write to the return register after any "
|
||||
"authentication are:\n";
|
||||
// Sort by address to ensure output is deterministic.
|
||||
std::vector<MCInstReference> ORRI = OverwritingRetRegInst;
|
||||
llvm::sort(ORRI, [](const MCInstReference &A, const MCInstReference &B) {
|
||||
return A.getAddress() < B.getAddress();
|
||||
});
|
||||
for (unsigned I = 0; I < ORRI.size(); ++I) {
|
||||
MCInstReference InstRef = ORRI[I];
|
||||
OS << " " << (I + 1) << ". ";
|
||||
BC.printInstruction(OS, InstRef, InstRef.getAddress(), BF);
|
||||
};
|
||||
LLVM_DEBUG({
|
||||
dbgs() << " .. OverWritingRetRegInst:\n";
|
||||
for (MCInstReference Ref : OverwritingRetRegInst) {
|
||||
dbgs() << " " << Ref << "\n";
|
||||
}
|
||||
});
|
||||
if (OverwritingRetRegInst.size() == 1) {
|
||||
const MCInstReference OverwInst = OverwritingRetRegInst[0];
|
||||
assert(OverwInst.ParentKind == MCInstReference::BasicBlockParent);
|
||||
reportFoundGadgetInSingleBBSingleOverwInst(OS, BC, OverwInst, RetInst);
|
||||
}
|
||||
}
|
||||
|
||||
void GenDiag::generateReport(raw_ostream &OS, const BinaryContext &BC) const {
|
||||
BinaryFunction *BF = RetInst.getFunction();
|
||||
BinaryBasicBlock *BB = RetInst.getBasicBlock();
|
||||
|
||||
OS << "\nGS-PACRET: " << Diag.Text;
|
||||
OS << " in function " << BF->getPrintName();
|
||||
if (BB)
|
||||
OS << ", basic block " << BB->getName();
|
||||
OS << ", at address " << llvm::format("%x", RetInst.getAddress()) << "\n";
|
||||
OS << " The return instruction is ";
|
||||
BC.printInstruction(OS, RetInst, RetInst.getAddress(), BF);
|
||||
}
|
||||
|
||||
Error Analysis::runOnFunctions(BinaryContext &BC) {
|
||||
ParallelUtilities::WorkFuncWithAllocTy WorkFun =
|
||||
[&](BinaryFunction &BF, MCPlusBuilder::AllocatorIdTy AllocatorId) {
|
||||
runOnFunction(BF, AllocatorId);
|
||||
};
|
||||
|
||||
ParallelUtilities::PredicateTy SkipFunc = [&](const BinaryFunction &BF) {
|
||||
return false;
|
||||
};
|
||||
|
||||
ParallelUtilities::runOnEachFunctionWithUniqueAllocId(
|
||||
BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun,
|
||||
SkipFunc, "NonPacProtectedRetAnalysis");
|
||||
|
||||
for (BinaryFunction *BF : BC.getAllBinaryFunctions())
|
||||
if (AnalysisResults.count(BF) > 0) {
|
||||
for (const std::shared_ptr<Annotation> &A :
|
||||
AnalysisResults[BF].Diagnostics)
|
||||
A->generateReport(outs(), BC);
|
||||
}
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
} // namespace NonPacProtectedRetAnalysis
|
||||
} // namespace bolt
|
||||
} // namespace llvm
|
@ -20,6 +20,7 @@
|
||||
#include "bolt/Passes/BinaryPasses.h"
|
||||
#include "bolt/Passes/CacheMetrics.h"
|
||||
#include "bolt/Passes/IdenticalCodeFolding.h"
|
||||
#include "bolt/Passes/NonPacProtectedRetAnalysis.h"
|
||||
#include "bolt/Passes/ReorderFunctions.h"
|
||||
#include "bolt/Profile/BoltAddressTranslation.h"
|
||||
#include "bolt/Profile/DataAggregator.h"
|
||||
@ -245,6 +246,13 @@ static cl::opt<bool> WriteBoltInfoSection(
|
||||
"bolt-info", cl::desc("write bolt info section in the output binary"),
|
||||
cl::init(true), cl::Hidden, cl::cat(BoltOutputCategory));
|
||||
|
||||
cl::list<GadgetScannerKind>
|
||||
GadgetScannersToRun("scanners", cl::desc("which gadget scanners to run"),
|
||||
cl::values(clEnumValN(GS_PACRET, "pacret", "pac-ret"),
|
||||
clEnumValN(GS_ALL, "all", "all")),
|
||||
cl::ZeroOrMore, cl::CommaSeparated,
|
||||
cl::cat(BinaryAnalysisCategory));
|
||||
|
||||
} // namespace opts
|
||||
|
||||
// FIXME: implement a better way to mark sections for replacement.
|
||||
@ -3490,7 +3498,24 @@ void RewriteInstance::runOptimizationPasses() {
|
||||
BC->logBOLTErrorsAndQuitOnFatal(BinaryFunctionPassManager::runAllPasses(*BC));
|
||||
}
|
||||
|
||||
void RewriteInstance::runBinaryAnalyses() {}
|
||||
void RewriteInstance::runBinaryAnalyses() {
|
||||
NamedRegionTimer T("runBinaryAnalyses", "run binary analysis passes",
|
||||
TimerGroupName, TimerGroupDesc, opts::TimeRewrite);
|
||||
BinaryFunctionPassManager Manager(*BC);
|
||||
// FIXME: add a pass that warns about which functions do not have CFG,
|
||||
// and therefore, analysis is most likely to be less accurate.
|
||||
using GSK = opts::GadgetScannerKind;
|
||||
// if no command line option was given, act as if "all" was specified.
|
||||
if (opts::GadgetScannersToRun.empty())
|
||||
opts::GadgetScannersToRun.addValue(GSK::GS_ALL);
|
||||
for (GSK ScannerToRun : opts::GadgetScannersToRun) {
|
||||
if (ScannerToRun == GSK::GS_PACRET || ScannerToRun == GSK::GS_ALL)
|
||||
Manager.registerPass(
|
||||
std::make_unique<NonPacProtectedRetAnalysis::Analysis>());
|
||||
}
|
||||
|
||||
BC->logBOLTErrorsAndQuitOnFatal(Manager.runPasses());
|
||||
}
|
||||
|
||||
void RewriteInstance::preregisterSections() {
|
||||
// Preregister sections before emission to set their order in the output.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "llvm/MC/MCFixupKindInfo.h"
|
||||
#include "llvm/MC/MCInstBuilder.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCRegister.h"
|
||||
#include "llvm/MC/MCRegisterInfo.h"
|
||||
#include "llvm/Support/DataExtractor.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
@ -183,6 +184,88 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
ErrorOr<MCPhysReg> getAuthenticatedReg(const MCInst &Inst) const override {
|
||||
switch (Inst.getOpcode()) {
|
||||
case AArch64::AUTIAZ:
|
||||
case AArch64::AUTIBZ:
|
||||
case AArch64::AUTIASP:
|
||||
case AArch64::AUTIBSP:
|
||||
case AArch64::AUTIASPPCi:
|
||||
case AArch64::AUTIBSPPCi:
|
||||
case AArch64::AUTIASPPCr:
|
||||
case AArch64::AUTIBSPPCr:
|
||||
case AArch64::RETAA:
|
||||
case AArch64::RETAB:
|
||||
case AArch64::RETAASPPCi:
|
||||
case AArch64::RETABSPPCi:
|
||||
case AArch64::RETAASPPCr:
|
||||
case AArch64::RETABSPPCr:
|
||||
return AArch64::LR;
|
||||
|
||||
case AArch64::AUTIA1716:
|
||||
case AArch64::AUTIB1716:
|
||||
case AArch64::AUTIA171615:
|
||||
case AArch64::AUTIB171615:
|
||||
return AArch64::X17;
|
||||
|
||||
case AArch64::ERETAA:
|
||||
case AArch64::ERETAB:
|
||||
// The ERETA{A,B} instructions use either register ELR_EL1, ELR_EL2 or
|
||||
// ELR_EL3, depending on the current Exception Level at run-time.
|
||||
//
|
||||
// Furthermore, these registers are not modelled by LLVM as a regular
|
||||
// MCPhysReg.... So there is no way to indicate that through the current
|
||||
// API.
|
||||
//
|
||||
// Therefore, return an error to indicate that LLVM/BOLT cannot model
|
||||
// this.
|
||||
return make_error_code(std::errc::result_out_of_range);
|
||||
|
||||
case AArch64::AUTIA:
|
||||
case AArch64::AUTIB:
|
||||
case AArch64::AUTDA:
|
||||
case AArch64::AUTDB:
|
||||
case AArch64::AUTIZA:
|
||||
case AArch64::AUTIZB:
|
||||
case AArch64::AUTDZA:
|
||||
case AArch64::AUTDZB:
|
||||
return Inst.getOperand(0).getReg();
|
||||
|
||||
// FIXME: BL?RA(A|B)Z? and LDRA(A|B) should probably be handled here too.
|
||||
|
||||
default:
|
||||
return getNoRegister();
|
||||
}
|
||||
}
|
||||
|
||||
bool isAuthenticationOfReg(const MCInst &Inst, MCPhysReg Reg) const override {
|
||||
if (Reg == getNoRegister())
|
||||
return false;
|
||||
ErrorOr<MCPhysReg> AuthenticatedReg = getAuthenticatedReg(Inst);
|
||||
return AuthenticatedReg.getError() ? false : *AuthenticatedReg == Reg;
|
||||
}
|
||||
|
||||
ErrorOr<MCPhysReg> getRegUsedAsRetDest(const MCInst &Inst) const override {
|
||||
assert(isReturn(Inst));
|
||||
switch (Inst.getOpcode()) {
|
||||
case AArch64::RET:
|
||||
return Inst.getOperand(0).getReg();
|
||||
case AArch64::RETAA:
|
||||
case AArch64::RETAB:
|
||||
case AArch64::RETAASPPCi:
|
||||
case AArch64::RETABSPPCi:
|
||||
case AArch64::RETAASPPCr:
|
||||
case AArch64::RETABSPPCr:
|
||||
return AArch64::LR;
|
||||
case AArch64::ERET:
|
||||
case AArch64::ERETAA:
|
||||
case AArch64::ERETAB:
|
||||
return make_error_code(std::errc::result_out_of_range);
|
||||
default:
|
||||
llvm_unreachable("Unhandled return instruction");
|
||||
}
|
||||
}
|
||||
|
||||
bool isADRP(const MCInst &Inst) const override {
|
||||
return Inst.getOpcode() == AArch64::ADRP;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ NONEXISTINGFILEARG: llvm-bolt-binary-analysis: 'non-existing-file': No suc
|
||||
RUN: not llvm-bolt-binary-analysis %p/Inputs/dummy.txt 2>&1 | FileCheck -check-prefix=NOELFFILEARG %s
|
||||
NOELFFILEARG: llvm-bolt-binary-analysis: '{{.*}}/Inputs/dummy.txt': The file was not recognized as a valid object file.
|
||||
|
||||
RUN: %clang %cflags %p/../../Inputs/asm_foo.s %p/../../Inputs/asm_main.c -o %t.exe
|
||||
RUN: %clang %cflags -Wl,--emit-relocs %p/../../Inputs/asm_foo.s %p/../../Inputs/asm_main.c -o %t.exe
|
||||
RUN: llvm-bolt-binary-analysis %t.exe 2>&1 | FileCheck -check-prefix=VALIDELFFILEARG --allow-empty %s
|
||||
# Check that there are no BOLT-WARNING or BOLT-ERROR output lines
|
||||
VALIDELFFILEARG: BOLT-INFO:
|
||||
@ -30,4 +30,10 @@ HELP-NEXT: USAGE: llvm-bolt-binary-analysis [options] <executable>
|
||||
HELP-EMPTY:
|
||||
HELP-NEXT: OPTIONS:
|
||||
HELP-EMPTY:
|
||||
HELP-NEXT: BinaryAnalysis options:
|
||||
HELP-EMPTY:
|
||||
HELP-NEXT: --scanners=<value> - which gadget scanners to run
|
||||
HELP-NEXT: =pacret - pac-ret
|
||||
HELP-NEXT: =all - all
|
||||
HELP-EMPTY:
|
||||
HELP-NEXT: Generic Options:
|
||||
|
952
bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
Normal file
952
bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
Normal file
@ -0,0 +1,952 @@
|
||||
// RUN: %clang %cflags -march=armv9.5-a+pauth-lr -mbranch-protection=pac-ret %s %p/../../Inputs/asm_main.c -o %t.exe
|
||||
// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck %s
|
||||
|
||||
.text
|
||||
|
||||
.globl f1
|
||||
.type f1,@function
|
||||
f1:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// autiasp
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f1, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f1, .-f1
|
||||
|
||||
|
||||
.globl f_intermediate_overwrite1
|
||||
.type f_intermediate_overwrite1,@function
|
||||
f_intermediate_overwrite1:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
autiasp
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_intermediate_overwrite1, basic block .LBB
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autiasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_intermediate_overwrite1, .-f_intermediate_overwrite1
|
||||
|
||||
.globl f_intermediate_overwrite2
|
||||
.type f_intermediate_overwrite2,@function
|
||||
f_intermediate_overwrite2:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiasp
|
||||
mov x30, x0
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_intermediate_overwrite2, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mov x30, x0
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autiasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x30, x0
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_intermediate_overwrite2, .-f_intermediate_overwrite2
|
||||
|
||||
.globl f_intermediate_read
|
||||
.type f_intermediate_read,@function
|
||||
f_intermediate_read:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiasp
|
||||
mov x0, x30
|
||||
// CHECK-NOT: function f_intermediate_read
|
||||
ret
|
||||
.size f_intermediate_read, .-f_intermediate_read
|
||||
|
||||
.globl f_intermediate_overwrite3
|
||||
.type f_intermediate_overwrite3,@function
|
||||
f_intermediate_overwrite3:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiasp
|
||||
mov w30, w0
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_intermediate_overwrite3, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mov w30, w0
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autiasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov w30, w0
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_intermediate_overwrite3, .-f_intermediate_overwrite3
|
||||
|
||||
.globl f_nonx30_ret
|
||||
.type f_nonx30_ret,@function
|
||||
f_nonx30_ret:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
mov x16, x30
|
||||
autiasp
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_nonx30_ret, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret x16
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mov x16, x30
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x16, x30
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autiasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret x16
|
||||
ret x16
|
||||
.size f_nonx30_ret, .-f_nonx30_ret
|
||||
|
||||
.globl f_nonx30_ret_ok
|
||||
.type f_nonx30_ret_ok,@function
|
||||
f_nonx30_ret_ok:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// FIXME: Should the scanner understand that an authenticated register (below x30,
|
||||
// after the autiasp instruction), is OK to be moved to another register
|
||||
// and then that register being used to return?
|
||||
// This respects that pac-ret hardening intent, but the scanner currently
|
||||
// will produce a false positive for this.
|
||||
// Is it worthwhile to make the scanner more complex for this case?
|
||||
// So far, scanning many millions of instructions across a linux distro,
|
||||
// I haven't encountered such an example.
|
||||
// The ".if 0" block below tests this case and currently fails.
|
||||
.if 0
|
||||
autiasp
|
||||
mov x16, x30
|
||||
.else
|
||||
mov x16, x30
|
||||
autia x16, sp
|
||||
.endif
|
||||
// CHECK-NOT: function f_nonx30_ret_ok
|
||||
ret x16
|
||||
.size f_nonx30_ret_ok, .-f_nonx30_ret_ok
|
||||
|
||||
.globl f_detect_clobbered_x30_passed_to_other
|
||||
.type f_detect_clobbered_x30_passed_to_other,@function
|
||||
f_detect_clobbered_x30_passed_to_other:
|
||||
str x30, [sp]
|
||||
ldr x30, [sp]
|
||||
// FIXME: Ideally, the pac-ret scanner would report on the following instruction, which
|
||||
// performs a tail call, that x30 might be attacker-controlled.
|
||||
// CHECK-NOT: function f_detect_clobbered_x30_passed_to_other
|
||||
b f_tail_called
|
||||
.size f_detect_clobbered_x30_passed_to_other, .-f_detect_clobbered_x30_passed_to_other
|
||||
|
||||
.globl f_tail_called
|
||||
.type f_tail_called,@function
|
||||
f_tail_called:
|
||||
ret
|
||||
.size f_tail_called, .-f_tail_called
|
||||
|
||||
.globl f_nonx30_ret_non_auted
|
||||
.type f_nonx30_ret_non_auted,@function
|
||||
f_nonx30_ret_non_auted:
|
||||
// FIXME: x1 is not authenticated, so should this be reported?
|
||||
// Note that we assume it's fine for x30 to not be authenticated before
|
||||
// returning to, as assuming that x30 is not attacker controlled at function
|
||||
// entry is part (implicitly) of the pac-ret hardening scheme.
|
||||
// It's probably an open question whether for other hardening schemes, such as
|
||||
// PAuthABI, which registers should be considered "clean" or not at function entry.
|
||||
// In other words, which registers have to be authenticated before being used as
|
||||
// a pointer and which ones not?
|
||||
// For a more detailed discussion, see
|
||||
// https://github.com/llvm/llvm-project/pull/122304#discussion_r1923662744
|
||||
// CHECK-NOT: f_nonx30_ret_non_auted
|
||||
ret x1
|
||||
.size f_nonx30_ret_non_auted, .-f_nonx30_ret_non_auted
|
||||
|
||||
|
||||
.globl f_callclobbered_x30
|
||||
.type f_callclobbered_x30,@function
|
||||
f_callclobbered_x30:
|
||||
bl g
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_callclobbered_x30, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_callclobbered_x30, .-f_callclobbered_x30
|
||||
|
||||
.globl f_callclobbered_calleesaved
|
||||
.type f_callclobbered_calleesaved,@function
|
||||
f_callclobbered_calleesaved:
|
||||
bl g
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_callclobbered_calleesaved, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret x19
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret x19
|
||||
// x19, according to the Arm ABI (AAPCS) is a callee-saved register.
|
||||
// Therefore, if function g respects the AAPCS, it should not write
|
||||
// anything to x19. However, we can't know whether function g actually
|
||||
// does respect the AAPCS rules, so the scanner should assume x19 can
|
||||
// get overwritten, and report a gadget if the code does not properly
|
||||
// deal with that.
|
||||
// Furthermore, there's a good chance that callee-saved registers have
|
||||
// been saved on the stack at some point during execution of the callee,
|
||||
// and so should be considered as potentially modified by an
|
||||
// attacker/written to.
|
||||
ret x19
|
||||
.size f_callclobbered_calleesaved, .-f_callclobbered_calleesaved
|
||||
|
||||
|
||||
/// Now do a basic sanity check on every different Authentication instruction:
|
||||
|
||||
.globl f_autiasp
|
||||
.type f_autiasp,@function
|
||||
f_autiasp:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiasp
|
||||
// CHECK-NOT: function f_autiasp
|
||||
ret
|
||||
.size f_autiasp, .-f_autiasp
|
||||
|
||||
.globl f_autibsp
|
||||
.type f_autibsp,@function
|
||||
f_autibsp:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autibsp
|
||||
// CHECK-NOT: function f_autibsp
|
||||
ret
|
||||
.size f_autibsp, .-f_autibsp
|
||||
|
||||
.globl f_autiaz
|
||||
.type f_autiaz,@function
|
||||
f_autiaz:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiaz
|
||||
// CHECK-NOT: function f_autiaz
|
||||
ret
|
||||
.size f_autiaz, .-f_autiaz
|
||||
|
||||
.globl f_autibz
|
||||
.type f_autibz,@function
|
||||
f_autibz:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autibz
|
||||
// CHECK-NOT: function f_autibz
|
||||
ret
|
||||
.size f_autibz, .-f_autibz
|
||||
|
||||
.globl f_autia1716
|
||||
.type f_autia1716,@function
|
||||
f_autia1716:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autia1716
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autia1716, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autia1716
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autia1716, .-f_autia1716
|
||||
|
||||
.globl f_autib1716
|
||||
.type f_autib1716,@function
|
||||
f_autib1716:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autib1716
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autib1716, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autib1716
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autib1716, .-f_autib1716
|
||||
|
||||
.globl f_autiax12
|
||||
.type f_autiax12,@function
|
||||
f_autiax12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autia x12, sp
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autiax12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autia x12, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autiax12, .-f_autiax12
|
||||
|
||||
.globl f_autibx12
|
||||
.type f_autibx12,@function
|
||||
f_autibx12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autib x12, sp
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autibx12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autib x12, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autibx12, .-f_autibx12
|
||||
|
||||
.globl f_autiax30
|
||||
.type f_autiax30,@function
|
||||
f_autiax30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autia x30, sp
|
||||
// CHECK-NOT: function f_autiax30
|
||||
ret
|
||||
.size f_autiax30, .-f_autiax30
|
||||
|
||||
.globl f_autibx30
|
||||
.type f_autibx30,@function
|
||||
f_autibx30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autib x30, sp
|
||||
// CHECK-NOT: function f_autibx30
|
||||
ret
|
||||
.size f_autibx30, .-f_autibx30
|
||||
|
||||
|
||||
.globl f_autdax12
|
||||
.type f_autdax12,@function
|
||||
f_autdax12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autda x12, sp
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autdax12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autda x12, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autdax12, .-f_autdax12
|
||||
|
||||
.globl f_autdbx12
|
||||
.type f_autdbx12,@function
|
||||
f_autdbx12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autdb x12, sp
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autdbx12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autdb x12, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autdbx12, .-f_autdbx12
|
||||
|
||||
.globl f_autdax30
|
||||
.type f_autdax30,@function
|
||||
f_autdax30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autda x30, sp
|
||||
// CHECK-NOT: function f_autdax30
|
||||
ret
|
||||
.size f_autdax30, .-f_autdax30
|
||||
|
||||
.globl f_autdbx30
|
||||
.type f_autdbx30,@function
|
||||
f_autdbx30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autdb x30, sp
|
||||
// CHECK-NOT: function f_autdbx30
|
||||
ret
|
||||
.size f_autdbx30, .-f_autdbx30
|
||||
|
||||
|
||||
.globl f_autizax12
|
||||
.type f_autizax12,@function
|
||||
f_autizax12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiza x12
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autizax12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autiza x12
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autizax12, .-f_autizax12
|
||||
|
||||
.globl f_autizbx12
|
||||
.type f_autizbx12,@function
|
||||
f_autizbx12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autizb x12
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autizbx12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autizb x12
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autizbx12, .-f_autizbx12
|
||||
|
||||
.globl f_autizax30
|
||||
.type f_autizax30,@function
|
||||
f_autizax30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiza x30
|
||||
// CHECK-NOT: function f_autizax30
|
||||
ret
|
||||
.size f_autizax30, .-f_autizax30
|
||||
|
||||
.globl f_autizbx30
|
||||
.type f_autizbx30,@function
|
||||
f_autizbx30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autizb x30
|
||||
// CHECK-NOT: function f_autizbx30
|
||||
ret
|
||||
.size f_autizbx30, .-f_autizbx30
|
||||
|
||||
|
||||
.globl f_autdzax12
|
||||
.type f_autdzax12,@function
|
||||
f_autdzax12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autdza x12
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autdzax12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autdza x12
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autdzax12, .-f_autdzax12
|
||||
|
||||
.globl f_autdzbx12
|
||||
.type f_autdzbx12,@function
|
||||
f_autdzbx12:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autdzb x12
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autdzbx12, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autdzb x12
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autdzbx12, .-f_autdzbx12
|
||||
|
||||
.globl f_autdzax30
|
||||
.type f_autdzax30,@function
|
||||
f_autdzax30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autdza x30
|
||||
// CHECK-NOT: function f_autdzax30
|
||||
ret
|
||||
.size f_autdzax30, .-f_autdzax30
|
||||
|
||||
.globl f_autdzbx30
|
||||
.type f_autdzbx30,@function
|
||||
f_autdzbx30:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autdzb x30
|
||||
// CHECK-NOT: function f_autdzbx30
|
||||
ret
|
||||
.size f_autdzbx30, .-f_autdzbx30
|
||||
|
||||
.globl f_retaa
|
||||
.type f_retaa,@function
|
||||
f_retaa:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-NOT: function f_retaa
|
||||
retaa
|
||||
.size f_retaa, .-f_retaa
|
||||
|
||||
.globl f_retab
|
||||
.type f_retab,@function
|
||||
f_retab:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-NOT: function f_retab
|
||||
retab
|
||||
.size f_retab, .-f_retab
|
||||
|
||||
.globl f_eretaa
|
||||
.type f_eretaa,@function
|
||||
f_eretaa:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-LABEL: GS-PACRET: Warning: pac-ret analysis could not analyze this return instruction in function f_eretaa, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: eretaa
|
||||
eretaa
|
||||
.size f_eretaa, .-f_eretaa
|
||||
|
||||
.globl f_eretab
|
||||
.type f_eretab,@function
|
||||
f_eretab:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-LABEL: GS-PACRET: Warning: pac-ret analysis could not analyze this return instruction in function f_eretab, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: eretab
|
||||
eretab
|
||||
.size f_eretab, .-f_eretab
|
||||
|
||||
.globl f_eret
|
||||
.type f_eret,@function
|
||||
f_eret:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-LABEL: GS-PACRET: Warning: pac-ret analysis could not analyze this return instruction in function f_eret, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: eret
|
||||
eret
|
||||
.size f_eret, .-f_eret
|
||||
|
||||
.globl f_movx30reg
|
||||
.type f_movx30reg,@function
|
||||
f_movx30reg:
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_movx30reg, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: mov x30, x22
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x30, x22
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
mov x30, x22
|
||||
ret
|
||||
.size f_movx30reg, .-f_movx30reg
|
||||
|
||||
.globl f_autiasppci
|
||||
.type f_autiasppci,@function
|
||||
f_autiasppci:
|
||||
0:
|
||||
pacnbiasppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autiasppc 0b
|
||||
// CHECK-NOT: function f_autiasppci
|
||||
ret
|
||||
.size f_autiasppci, .-f_autiasppci
|
||||
|
||||
.globl f_autibsppci
|
||||
.type f_autibsppci,@function
|
||||
f_autibsppci:
|
||||
0:
|
||||
pacnbibsppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autibsppc 0b
|
||||
// CHECK-NOT: function f_autibsppci
|
||||
ret
|
||||
.size f_autibsppci, .-f_autibsppci
|
||||
|
||||
.globl f_autiasppcr
|
||||
.type f_autiasppcr,@function
|
||||
|
||||
f_autiasppcr:
|
||||
0:
|
||||
pacnbiasppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
adr x28, 0b
|
||||
autiasppcr x28
|
||||
// CHECK-NOT: function f_autiasppcr
|
||||
ret
|
||||
.size f_autiasppcr, .-f_autiasppcr
|
||||
|
||||
f_autibsppcr:
|
||||
0:
|
||||
pacnbibsppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
adr x28, 0b
|
||||
autibsppcr x28
|
||||
// CHECK-NOT: function f_autibsppcr
|
||||
ret
|
||||
.size f_autibsppcr, .-f_autibsppcr
|
||||
|
||||
.globl f_retaasppci
|
||||
.type f_retaasppci,@function
|
||||
f_retaasppci:
|
||||
0:
|
||||
pacnbiasppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-NOT: function f_retaasppci
|
||||
retaasppc 0b
|
||||
.size f_retaasppci, .-f_retaasppci
|
||||
|
||||
.globl f_retabsppci
|
||||
.type f_retabsppci,@function
|
||||
f_retabsppci:
|
||||
0:
|
||||
pacnbibsppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
// CHECK-NOT: function f_retabsppci
|
||||
retabsppc 0b
|
||||
.size f_retabsppci, .-f_retabsppci
|
||||
|
||||
.globl f_retaasppcr
|
||||
.type f_retaasppcr,@function
|
||||
|
||||
f_retaasppcr:
|
||||
0:
|
||||
pacnbiasppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
adr x28, 0b
|
||||
// CHECK-NOT: function f_retaasppcr
|
||||
retaasppcr x28
|
||||
.size f_retaasppcr, .-f_retaasppcr
|
||||
|
||||
f_retabsppcr:
|
||||
0:
|
||||
pacnbibsppc
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
adr x28, 0b
|
||||
// CHECK-NOT: function f_retabsppcr
|
||||
retabsppcr x28
|
||||
.size f_retabsppcr, .-f_retabsppcr
|
||||
|
||||
.globl f_autia171615
|
||||
.type f_autia171615,@function
|
||||
f_autia171615:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autia171615
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autia171615, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autia171615
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autia171615, .-f_autia171615
|
||||
|
||||
.globl f_autib171615
|
||||
.type f_autib171615,@function
|
||||
f_autib171615:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
mov x29, sp
|
||||
bl g
|
||||
add x0, x0, #3
|
||||
ldp x29, x30, [sp], #16
|
||||
autib171615
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_autib171615, basic block .LBB{{[0-9]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: This happens in the following basic block:
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: paciasp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: stp x29, x30, [sp, #-0x10]!
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: mov x29, sp
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: bl g@PLT
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: add x0, x0, #0x3
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: autib171615
|
||||
// CHECK-NEXT: {{[0-9a-f]+}}: ret
|
||||
ret
|
||||
.size f_autib171615, .-f_autib171615
|
||||
|
75
bolt/test/binary-analysis/AArch64/gs-pacret-multi-bb.s
Normal file
75
bolt/test/binary-analysis/AArch64/gs-pacret-multi-bb.s
Normal file
@ -0,0 +1,75 @@
|
||||
// RUN: %clang %cflags -march=armv8.3-a -mbranch-protection=pac-ret %s %p/../../Inputs/asm_main.c -o %t.exe
|
||||
// RUN: llvm-bolt-binary-analysis --scanners=pacret %t.exe 2>&1 | FileCheck %s
|
||||
|
||||
|
||||
// Verify that we can also detect gadgets across basic blocks
|
||||
|
||||
.globl f_crossbb1
|
||||
.type f_crossbb1,@function
|
||||
f_crossbb1:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
ldp x29, x30, [sp], #16
|
||||
cbnz x0, 1f
|
||||
autiasp
|
||||
1:
|
||||
ret
|
||||
.size f_crossbb1, .-f_crossbb1
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_crossbb1, basic block .L{{[^,]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 2 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
// CHECK-NEXT: 2. {{[0-9a-f]+}}: autiasp
|
||||
|
||||
// A test that checks that the dataflow state tracking across when merging BBs
|
||||
// seems to work:
|
||||
.globl f_mergebb1
|
||||
.type f_mergebb1,@function
|
||||
f_mergebb1:
|
||||
paciasp
|
||||
2:
|
||||
stp x29, x30, [sp, #-16]!
|
||||
ldp x29, x30, [sp], #16
|
||||
sub x0, x0, #1
|
||||
cbnz x0, 1f
|
||||
autiasp
|
||||
b 2b
|
||||
1:
|
||||
ret
|
||||
.size f_mergebb1, .-f_mergebb1
|
||||
// CHECK-LABEL: GS-PACRET: non-protected ret found in function f_mergebb1, basic block .L{{[^,]+}}, at address
|
||||
// CHECK-NEXT: The return instruction is {{[0-9a-f]+}}: ret
|
||||
// CHECK-NEXT: The 1 instructions that write to the return register after any authentication are:
|
||||
// CHECK-NEXT: 1. {{[0-9a-f]+}}: ldp x29, x30, [sp], #0x10
|
||||
|
||||
.globl f_shrinkwrapping
|
||||
.type f_shrinkwrapping,@function
|
||||
f_shrinkwrapping:
|
||||
cbz x0, 1f
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
ldp x29, x30, [sp], #16
|
||||
autiasp
|
||||
1:
|
||||
ret
|
||||
.size f_shrinkwrapping, .-f_shrinkwrapping
|
||||
// CHECK-NOT: f_shrinkwrapping
|
||||
|
||||
.globl f_multi_auth_insts
|
||||
.type f_multi_auth_insts,@function
|
||||
f_multi_auth_insts:
|
||||
paciasp
|
||||
stp x29, x30, [sp, #-16]!
|
||||
ldp x29, x30, [sp], #16
|
||||
cbnz x0, 1f
|
||||
autibsp
|
||||
b 2f
|
||||
1:
|
||||
autiasp
|
||||
2:
|
||||
ret
|
||||
.size f_multi_auth_insts, .-f_multi_auth_insts
|
||||
// CHECK-NOT: f_multi_auth_insts
|
||||
|
||||
// TODO: also verify that false negatives exist in across-BB gadgets in functions
|
||||
// for which bolt cannot reconstruct the call graph.
|
@ -1,7 +1,7 @@
|
||||
if "AArch64" not in config.root.targets:
|
||||
config.unsupported = True
|
||||
|
||||
flags = "--target=aarch64-linux-gnu -nostartfiles -nostdlib -ffreestanding -Wl,--emit-relocs"
|
||||
flags = "--target=aarch64-linux-gnu -nostartfiles -nostdlib -ffreestanding"
|
||||
|
||||
config.substitutions.insert(0, ("%cflags", f"%cflags {flags}"))
|
||||
config.substitutions.insert(0, ("%cxxflags", f"%cxxflags {flags}"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user