blob: 56086da49b0d88c883ac44a855b01be2db64190c [file] [log] [blame]
//===- bolt/Passes/PointerAuthCFIFixup.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 PointerAuthCFIFixup class. It inserts
// OpNegateRAState CFIs to places where the state of two consecutive
// instructions are different.
//
//===----------------------------------------------------------------------===//
#include "bolt/Passes/PointerAuthCFIFixup.h"
#include "bolt/Core/BinaryFunction.h"
#include "bolt/Core/ParallelUtilities.h"
#include <cstdlib>
using namespace llvm;
namespace llvm {
namespace bolt {
static bool PassFailed = false;
void PointerAuthCFIFixup::runOnFunction(BinaryFunction &BF) {
if (PassFailed)
return;
BinaryContext &BC = BF.getBinaryContext();
if (BF.getState() == BinaryFunction::State::Empty)
return;
if (BF.getState() != BinaryFunction::State::CFG &&
BF.getState() != BinaryFunction::State::CFG_Finalized) {
BC.outs() << "BOLT-INFO: no CFG for " << BF.getPrintName()
<< " in PointerAuthCFIFixup\n";
return;
}
inferUnknownStates(BF);
for (FunctionFragment &FF : BF.getLayout().fragments()) {
coverFunctionFragmentStart(BF, FF);
bool FirstIter = true;
bool PrevRAState = false;
// As this pass runs after function splitting, we should only check
// consecutive instructions inside FunctionFragments.
for (BinaryBasicBlock *BB : FF) {
for (auto It = BB->begin(); It != BB->end(); ++It) {
MCInst &Inst = *It;
if (BC.MIB->isCFI(Inst))
continue;
std::optional<bool> RAState = BC.MIB->getRAState(Inst);
if (!RAState.has_value()) {
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates "
<< " in function " << BF.getPrintName() << "\n";
PassFailed = true;
return;
}
if (!FirstIter) {
// Consecutive instructions with different RAState means we need to
// add a OpNegateRAState.
if (*RAState != PrevRAState)
It = BF.addCFIInstruction(
BB, It, MCCFIInstruction::createNegateRAState(nullptr));
} else {
FirstIter = false;
}
PrevRAState = *RAState;
}
}
}
}
void PointerAuthCFIFixup::inferUnknownStates(BinaryFunction &BF) {
BinaryContext &BC = BF.getBinaryContext();
// Fill in missing RAStates in simple cases (inside BBs).
for (BinaryBasicBlock &BB : BF) {
fillUnknownStateInBB(BC, BB);
}
// BasicBlocks which are made entirely of "new instructions" (instructions
// without RAState annotation) are stubs, and do not have correct unwind info.
// We should iterate in layout order and fill them based on previous known
// RAState.
fillUnknownStubs(BF);
}
void PointerAuthCFIFixup::coverFunctionFragmentStart(BinaryFunction &BF,
FunctionFragment &FF) {
BinaryContext &BC = BF.getBinaryContext();
if (FF.empty())
return;
// Find the first BB in the FF which has Instructions.
// BOLT can generate empty BBs at function splitting which are only used as
// target labels. We should add the negate-ra-state CFI to the first
// non-empty BB.
auto *FirstNonEmpty =
std::find_if(FF.begin(), FF.end(), [](BinaryBasicBlock *BB) {
// getFirstNonPseudo returns BB.end() if it does not find any
// Instructions.
return BB->getFirstNonPseudo() != BB->end();
});
// If a function is already split in the input, the first FF can also start
// with Signed state. This covers that scenario as well.
auto II = (*FirstNonEmpty)->getFirstNonPseudo();
std::optional<bool> RAState = BC.MIB->getRAState(*II);
if (!RAState.has_value()) {
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates "
<< " in function " << BF.getPrintName() << "\n";
PassFailed = true;
return;
}
if (*RAState)
BF.addCFIInstruction(*FirstNonEmpty, II,
MCCFIInstruction::createNegateRAState(nullptr));
}
std::optional<bool>
PointerAuthCFIFixup::getFirstKnownRAState(BinaryContext &BC,
BinaryBasicBlock &BB) {
for (const MCInst &Inst : BB) {
if (BC.MIB->isCFI(Inst))
continue;
std::optional<bool> RAState = BC.MIB->getRAState(Inst);
if (RAState.has_value())
return RAState;
}
return std::nullopt;
}
bool PointerAuthCFIFixup::isUnknownBlock(BinaryContext &BC,
BinaryBasicBlock &BB) {
std::optional<bool> FirstRAState = getFirstKnownRAState(BC, BB);
return !FirstRAState.has_value();
}
void PointerAuthCFIFixup::fillUnknownStateInBB(BinaryContext &BC,
BinaryBasicBlock &BB) {
auto First = BB.getFirstNonPseudo();
if (First == BB.end())
return;
// If the first instruction has unknown RAState, we should copy the first
// known RAState.
std::optional<bool> RAState = BC.MIB->getRAState(*First);
if (!RAState.has_value()) {
std::optional<bool> FirstRAState = getFirstKnownRAState(BC, BB);
if (!FirstRAState.has_value())
// We fill unknown BBs later.
return;
BC.MIB->setRAState(*First, *FirstRAState);
}
// At this point we know the RAState of the first instruction,
// so we can propagate the RAStates to all subsequent unknown instructions.
MCInst Prev = *First;
for (auto It = First + 1; It != BB.end(); ++It) {
MCInst &Inst = *It;
if (BC.MIB->isCFI(Inst))
continue;
// No need to check for nullopt: we only entered this loop after the first
// instruction had its RAState set, and RAState is always set for the
// previous instruction in the previous iteration of the loop.
std::optional<bool> PrevRAState = BC.MIB->getRAState(Prev);
std::optional<bool> RAState = BC.MIB->getRAState(Inst);
if (!RAState.has_value()) {
if (BC.MIB->isPSignOnLR(Prev))
PrevRAState = true;
else if (BC.MIB->isPAuthOnLR(Prev))
PrevRAState = false;
BC.MIB->setRAState(Inst, *PrevRAState);
}
Prev = Inst;
}
}
void PointerAuthCFIFixup::markUnknownBlock(BinaryContext &BC,
BinaryBasicBlock &BB, bool State) {
// If we call this when an Instruction has either kRASigned or kRAUnsigned
// annotation, setRASigned or setRAUnsigned would fail.
assert(isUnknownBlock(BC, BB) &&
"markUnknownBlock should only be called on unknown blocks");
for (MCInst &Inst : BB) {
if (BC.MIB->isCFI(Inst))
continue;
BC.MIB->setRAState(Inst, State);
}
}
void PointerAuthCFIFixup::fillUnknownStubs(BinaryFunction &BF) {
BinaryContext &BC = BF.getBinaryContext();
bool FirstIter = true;
MCInst PrevInst;
for (FunctionFragment &FF : BF.getLayout().fragments()) {
for (BinaryBasicBlock *BB : FF) {
if (FirstIter) {
FirstIter = false;
if (isUnknownBlock(BC, *BB))
// If the first BasicBlock is unknown, the function's entry RAState
// should be used.
markUnknownBlock(BC, *BB, BF.getInitialRAState());
} else if (isUnknownBlock(BC, *BB)) {
// As explained in issue #160989, the unwind info is incorrect for
// stubs. Indicating the correct RAState without the rest of the unwind
// info being correct is not useful. Instead, we copy the RAState from
// the previous instruction.
std::optional<bool> PrevRAState = BC.MIB->getRAState(PrevInst);
if (!PrevRAState.has_value()) {
// No non-cfi instruction encountered in the function yet.
// This means the RAState is the same as at the function entry.
markUnknownBlock(BC, *BB, BF.getInitialRAState());
continue;
}
if (BC.MIB->isPSignOnLR(PrevInst))
PrevRAState = true;
else if (BC.MIB->isPAuthOnLR(PrevInst))
PrevRAState = false;
markUnknownBlock(BC, *BB, *PrevRAState);
}
// This function iterates on BasicBlocks, so the PrevInst has to be
// updated to the last instruction of the current BasicBlock. If the
// BasicBlock is empty, or only has PseudoInstructions, PrevInst will not
// be updated.
auto Last = BB->getLastNonPseudo();
if (Last != BB->rend())
PrevInst = *Last;
}
}
}
Error PointerAuthCFIFixup::runOnFunctions(BinaryContext &BC) {
std::atomic<uint64_t> FunctionsModified{0};
ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) {
FunctionsModified++;
runOnFunction(BF);
};
ParallelUtilities::PredicateTy SkipPredicate = [&](const BinaryFunction &BF) {
// We can skip functions which did not include negate-ra-state CFIs. This
// includes code using pac-ret hardening as well, if the binary is
// compiled with `-fno-exceptions -fno-unwind-tables
// -fno-asynchronous-unwind-tables`
return !BF.containedNegateRAState() || BF.isIgnored();
};
ParallelUtilities::runOnEachFunction(
BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun,
SkipPredicate, "PointerAuthCFIFixup");
BC.outs() << "BOLT-INFO: rewritten pac-ret DWARF info in "
<< FunctionsModified << " out of " << BC.getBinaryFunctions().size()
<< " functions "
<< format("(%.2lf%%).\n", (100.0 * FunctionsModified) /
BC.getBinaryFunctions().size());
if (PassFailed)
return createFatalBOLTError("");
return Error::success();
}
} // end namespace bolt
} // end namespace llvm