Skip to content

[BOLT][AArch64] Handle OpNegateRAState to enable optimizing binaries with pac-ret hardening #120064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions bolt/include/bolt/Core/BinaryFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,51 @@ class BinaryFunction {

void setHasInferredProfile(bool Inferred) { HasInferredProfile = Inferred; }

/// Find corrected offset the same way addCFIInstruction does it to skip NOPs.
std::optional<uint64_t> getCorrectedCFIOffset(uint64_t Offset) {
assert(!Instructions.empty());
auto I = Instructions.lower_bound(Offset);
if (Offset == getSize()) {
assert(I == Instructions.end() && "unexpected iterator value");
// Sometimes compiler issues restore_state after all instructions
// in the function (even after nop).
--I;
Offset = I->first;
}
assert(I->first == Offset && "CFI pointing to unknown instruction");
if (I == Instructions.begin())
return {};

--I;
while (I != Instructions.begin() && BC.MIB->isNoop(I->second)) {
Offset = I->first;
--I;
}
return Offset;
}

void setInstModifiesRAState(uint8_t CFIOpcode, uint64_t Offset) {
std::optional<uint64_t> CorrectedOffset = getCorrectedCFIOffset(Offset);
if (CorrectedOffset) {
auto I = Instructions.lower_bound(*CorrectedOffset);
I--;

switch (CFIOpcode) {
case dwarf::DW_CFA_AARCH64_negate_ra_state:
BC.MIB->setNegateRAState(I->second);
break;
case dwarf::DW_CFA_remember_state:
BC.MIB->setRememberState(I->second);
break;
case dwarf::DW_CFA_restore_state:
BC.MIB->setRestoreState(I->second);
break;
default:
assert(0 && "CFI Opcode not covered by function");
}
}
}

void addCFIInstruction(uint64_t Offset, MCCFIInstruction &&Inst) {
assert(!Instructions.empty());

Expand Down
9 changes: 8 additions & 1 deletion bolt/include/bolt/Core/MCPlus.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@ class MCAnnotation {
kLabel, /// MCSymbol pointing to this instruction.
kSize, /// Size of the instruction.
kDynamicBranch, /// Jit instruction patched at runtime.
kGeneric /// First generic annotation.
kSigning, /// Inst is a signing instruction (paciasp, etc.).
kSigned, /// Inst is in a range where RA is signed.
kAuthenticating, /// Authenticating inst (e.g. autiasp).
kUnsigned, /// Inst is in a range where RA is unsigned.
kRememberState, /// Inst has rememberState CFI.
kRestoreState, /// Inst has restoreState CFI.
kNegateState, /// Inst has OpNegateRAState CFI.
kGeneric, /// First generic annotation.
};

virtual void print(raw_ostream &OS) const = 0;
Expand Down
74 changes: 74 additions & 0 deletions bolt/include/bolt/Core/MCPlusBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ class MCPlusBuilder {
public:
using AllocatorIdTy = uint16_t;

std::optional<int64_t> getAnnotationAtOpIndex(const MCInst &Inst,
unsigned OpIndex) const {
std::optional<unsigned> FirstAnnotationOp = getFirstAnnotationOpIndex(Inst);
if (!FirstAnnotationOp)
return std::nullopt;

if (*FirstAnnotationOp > OpIndex || Inst.getNumOperands() < OpIndex)
return std::nullopt;

auto Op = Inst.begin() + OpIndex;
const int64_t ImmValue = Op->getImm();
return extractAnnotationIndex(ImmValue);
}

private:
/// A struct that represents a single annotation allocator
struct AnnotationAllocator {
Expand Down Expand Up @@ -567,6 +581,21 @@ class MCPlusBuilder {
return getNoRegister();
}

virtual bool isPSignOnLR(const MCInst &Inst) const {
llvm_unreachable("not implemented");
return false;
}

virtual bool isPAuthOnLR(const MCInst &Inst) const {
llvm_unreachable("not implemented");
return false;
}

virtual bool isPAuthAndRet(const MCInst &Inst) const {
llvm_unreachable("not implemented");
return false;
}

virtual bool isAuthenticationOfReg(const MCInst &Inst,
MCPhysReg AuthenticatedReg) const {
llvm_unreachable("not implemented");
Expand Down Expand Up @@ -1254,6 +1283,51 @@ class MCPlusBuilder {
/// Return true if the instruction is a tail call.
bool isTailCall(const MCInst &Inst) const;

/// Stores NegateRAState annotation on \p Inst.
void setNegateRAState(MCInst &Inst) const;

/// Return true if \p Inst has NegateRAState annotation.
bool hasNegateRAState(const MCInst &Inst) const;

/// Sets RememberState annotation on \p Inst.
void setRememberState(MCInst &Inst) const;

/// Return true if \p Inst has RememberState annotation.
bool hasRememberState(const MCInst &Inst) const;

/// Stores RestoreState annotation on \p Inst.
void setRestoreState(MCInst &Inst) const;

/// Return true if \p Inst has RestoreState annotation.
bool hasRestoreState(const MCInst &Inst) const;

/// Stores RA Signed annotation on \p Inst.
void setRASigned(MCInst &Inst) const;

/// Return true if \p Inst has Signed RA annotation.
bool isRASigned(const MCInst &Inst) const;

/// Stores RA Signing annotation on \p Inst.
void setRASigning(MCInst &Inst) const;

/// Return true if \p Inst has Signing RA annotation.
bool isRASigning(const MCInst &Inst) const;

/// Stores Authenticating annotation on \p Inst.
void setAuthenticating(MCInst &Inst) const;

/// Return true if \p Inst has Authenticating annotation.
bool isAuthenticating(const MCInst &Inst) const;

/// Stores RA Unsigned annotation on \p Inst.
void setRAUnsigned(MCInst &Inst) const;

/// Return true if \p Inst has Unsigned RA annotation.
bool isRAUnsigned(const MCInst &Inst) const;

/// Return true if \p Inst doesn't have any annotation related to RA state.
bool isRAStateUnknown(const MCInst &Inst) const;

/// Return true if the instruction is a call with an exception handling info.
virtual bool isInvoke(const MCInst &Inst) const {
return isCall(Inst) && getEHInfo(Inst);
Expand Down
46 changes: 46 additions & 0 deletions bolt/include/bolt/Passes/InsertNegateRAStatePass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===- bolt/Passes/InsertNegateRAStatePass.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 the InsertNegateRAStatePass class.
//
//===----------------------------------------------------------------------===//
#ifndef BOLT_PASSES_INSERT_NEGATE_RA_STATE_PASS
#define BOLT_PASSES_INSERT_NEGATE_RA_STATE_PASS

#include "bolt/Passes/BinaryPasses.h"
#include <stack>

namespace llvm {
namespace bolt {

class InsertNegateRAState : public BinaryFunctionPass {
public:
explicit InsertNegateRAState() : BinaryFunctionPass(false) {}

const char *getName() const override { return "insert-negate-ra-state-pass"; }

/// Pass entry point
Error runOnFunctions(BinaryContext &BC) override;
void runOnFunction(BinaryFunction &BF);

private:
/// Loops over all instructions and adds OpNegateRAState CFI
/// after any pointer signing or authenticating instructions,
/// which operate on the LR, except fused ptrauth + ret instructions
/// (such as RETAA).
/// Returns true, if any OpNegateRAState CFIs were added.
bool addNegateRAStateAfterPacOrAuth(BinaryFunction &BF);
/// Because states are tracked as MCAnnotations on individual instructions,
/// newly inserted instructions do not have a state associated with them.
/// New states are "inherited" from the last known state.
void fixUnknownStates(BinaryFunction &BF);
};

} // namespace bolt
} // namespace llvm
#endif
33 changes: 33 additions & 0 deletions bolt/include/bolt/Passes/MarkRAStates.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===- bolt/Passes/MarkRAStates.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 the MarkRAStates class.
//
//===----------------------------------------------------------------------===//
#ifndef BOLT_PASSES_MARK_RA_STATES
#define BOLT_PASSES_MARK_RA_STATES

#include "bolt/Passes/BinaryPasses.h"

namespace llvm {
namespace bolt {

class MarkRAStates : public BinaryFunctionPass {
public:
explicit MarkRAStates() : BinaryFunctionPass(false) {}

const char *getName() const override { return "mark-ra-states"; }

/// Pass entry point
Error runOnFunctions(BinaryContext &BC) override;
void runOnFunction(BinaryFunction &BF);
};

} // namespace bolt
} // namespace llvm
#endif
1 change: 1 addition & 0 deletions bolt/include/bolt/Utils/CommandLineOpts.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ extern llvm::cl::opt<std::string> OutputFilename;
extern llvm::cl::opt<std::string> PerfData;
extern llvm::cl::opt<bool> PrintCacheMetrics;
extern llvm::cl::opt<bool> PrintSections;
extern llvm::cl::opt<bool> AllowPacret;

// The format to use with -o in aggregation mode (perf2bolt)
enum ProfileFormatKind { PF_Fdata, PF_YAML };
Expand Down
6 changes: 5 additions & 1 deletion bolt/lib/Core/BinaryBasicBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,11 @@ int32_t BinaryBasicBlock::getCFIStateAtInstr(const MCInst *Instr) const {
InstrSeen = (&Inst == Instr);
continue;
}
if (Function->getBinaryContext().MIB->isCFI(Inst)) {
// Ignoring OpNegateRAState CFIs here, as they dont have a "State"
// number associated with them.
if (Function->getBinaryContext().MIB->isCFI(Inst) &&
(Function->getCFIFor(Inst)->getOperation() !=
MCCFIInstruction::OpNegateRAState)) {
LastCFI = &Inst;
break;
}
Expand Down
28 changes: 13 additions & 15 deletions bolt/lib/Core/BinaryFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ extern cl::opt<bool> Instrument;
extern cl::opt<bool> StrictMode;
extern cl::opt<bool> UpdateDebugSections;
extern cl::opt<unsigned> Verbosity;
extern cl::opt<bool> AllowPacret;

extern bool BinaryAnalysisMode;
extern HeatmapModeKind HeatmapMode;
Expand Down Expand Up @@ -178,6 +179,15 @@ template <typename R> static bool emptyRange(const R &Range) {
return Range.begin() == Range.end();
}

static void checkFlagsForPacRet() {
if (!(opts::BinaryAnalysisMode || opts::HeatmapMode || opts::AllowPacret)) {
llvm_unreachable(
"BOLT-ERROR: support for binaries using pac-ret hardening (e.g. as "
"produced by '-mbranch-protection=pac-ret') is experimental\n"
"BOLT-ERROR: set --allow-experimental-pacret to allow processing");
}
}

/// Gets debug line information for the instruction located at the given
/// address in the original binary. The SMLoc's pointer is used
/// to point to this information, which is represented by a
Expand Down Expand Up @@ -2785,11 +2795,7 @@ struct CFISnapshot {
llvm_unreachable("unsupported CFI opcode");
break;
case MCCFIInstruction::OpNegateRAState:
if (!(opts::BinaryAnalysisMode || opts::HeatmapMode)) {
llvm_unreachable("BOLT-ERROR: binaries using pac-ret hardening (e.g. "
"as produced by '-mbranch-protection=pac-ret') are "
"currently not supported by BOLT.");
}
checkFlagsForPacRet();
break;
case MCCFIInstruction::OpRememberState:
case MCCFIInstruction::OpRestoreState:
Expand Down Expand Up @@ -2931,11 +2937,7 @@ struct CFISnapshotDiff : public CFISnapshot {
llvm_unreachable("unsupported CFI opcode");
return false;
case MCCFIInstruction::OpNegateRAState:
if (!(opts::BinaryAnalysisMode || opts::HeatmapMode)) {
llvm_unreachable("BOLT-ERROR: binaries using pac-ret hardening (e.g. "
"as produced by '-mbranch-protection=pac-ret') are "
"currently not supported by BOLT.");
}
checkFlagsForPacRet();
break;
case MCCFIInstruction::OpRememberState:
case MCCFIInstruction::OpRestoreState:
Expand Down Expand Up @@ -3088,11 +3090,7 @@ BinaryFunction::unwindCFIState(int32_t FromState, int32_t ToState,
llvm_unreachable("unsupported CFI opcode");
break;
case MCCFIInstruction::OpNegateRAState:
if (!(opts::BinaryAnalysisMode || opts::HeatmapMode)) {
llvm_unreachable("BOLT-ERROR: binaries using pac-ret hardening (e.g. "
"as produced by '-mbranch-protection=pac-ret') are "
"currently not supported by BOLT.");
}
checkFlagsForPacRet();
break;
case MCCFIInstruction::OpGnuArgsSize:
// do not affect CFI state
Expand Down
28 changes: 26 additions & 2 deletions bolt/lib/Core/Exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,10 +568,21 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const {
case DW_CFA_remember_state:
Function.addCFIInstruction(
Offset, MCCFIInstruction::createRememberState(nullptr));

if (Function.getBinaryContext().isAArch64())
// Support for pointer authentication:
// We need to annotate instructions that modify the RA State, to work
// out the state of each instruction in MarkRAStates Pass.
Function.setInstModifiesRAState(DW_CFA_remember_state, Offset);
break;
case DW_CFA_restore_state:
Function.addCFIInstruction(Offset,
MCCFIInstruction::createRestoreState(nullptr));
if (Function.getBinaryContext().isAArch64())
// Support for pointer authentication:
// We need to annotate instructions that modify the RA State, to work
// out the state of each instruction in MarkRAStates Pass.
Function.setInstModifiesRAState(DW_CFA_restore_state, Offset);
break;
case DW_CFA_def_cfa:
Function.addCFIInstruction(
Expand Down Expand Up @@ -629,9 +640,22 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const {
BC.errs() << "BOLT-WARNING: DW_CFA_MIPS_advance_loc unimplemented\n";
return false;
case DW_CFA_GNU_window_save:
// DW_CFA_GNU_window_save and DW_CFA_GNU_NegateRAState just use the same
// id but mean different things. The latter is used in AArch64.
// DW_CFA_GNU_window_save and DW_CFA_AARCH64_negate_ra_state just use the
// same id but mean different things. The latter is used in AArch64.
if (Function.getBinaryContext().isAArch64()) {
// The location OpNegateRAState CFIs are needed
// depends on the order of BasicBlocks, which changes during
// optimizations. Instead of adding OpNegateRAState CFIs, an annotation
// is added to the instruction, to mark that the instruction modifies
// the RA State. The actual state for instructions are worked out in
// MarkRAStates based on these annotations.
Function.setInstModifiesRAState(DW_CFA_AARCH64_negate_ra_state, Offset);
// To have the --allow-experimental-pacret flag, we have to add the
// OpNegateRAState CFI, and remove it later in MarkRAStates. Unittests
// on AArch64 would be broken otherwise, as some AArch64 platforms will
// have pac-ret for linker inserted functions, e.g.
// __do_global_dtors_aux. The user cannot remove the
// .cfi_negate_ra_state from such functions.
Function.addCFIInstruction(
Offset, MCCFIInstruction::createNegateRAState(nullptr));
break;
Expand Down
Loading
Loading