diff options
Diffstat (limited to 'bolt/lib/Passes/MarkRAStates.cpp')
-rw-r--r-- | bolt/lib/Passes/MarkRAStates.cpp | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/bolt/lib/Passes/MarkRAStates.cpp b/bolt/lib/Passes/MarkRAStates.cpp new file mode 100644 index 0000000..2c5ce4a --- /dev/null +++ b/bolt/lib/Passes/MarkRAStates.cpp @@ -0,0 +1,152 @@ +//===- 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. +// Three CFIs have an influence on the RA State of an instruction: +// - NegateRAState flips the RA State, +// - RememberState pushes the RA State to a stack, +// - RestoreState pops the RA State from the stack. +// These are saved as MCAnnotations on instructions they refer to at CFI +// reading (in CFIReaderWriter::fillCFIInfoFor). In this pass, we can work out +// the RA State of each instruction, and save it as new MCAnnotations. The new +// annotations are Signing, Signed, Authenticating and Unsigned. After +// optimizations, .cfi_negate_ra_state CFIs are added to the places where the +// state changes in InsertNegateRAStatePass. +// +//===----------------------------------------------------------------------===// +#include "bolt/Passes/MarkRAStates.h" +#include "bolt/Core/BinaryFunction.h" +#include "bolt/Core/ParallelUtilities.h" +#include <cstdlib> +#include <optional> +#include <stack> + +using namespace llvm; + +namespace llvm { +namespace bolt { + +bool MarkRAStates::runOnFunction(BinaryFunction &BF) { + + BinaryContext &BC = BF.getBinaryContext(); + + for (const BinaryBasicBlock &BB : BF) { + for (const MCInst &Inst : BB) { + if ((BC.MIB->isPSignOnLR(Inst) || + (BC.MIB->isPAuthOnLR(Inst) && !BC.MIB->isPAuthAndRet(Inst))) && + !BC.MIB->hasNegateRAState(Inst)) { + // Not all functions have .cfi_negate_ra_state in them. But if one does, + // we expect psign/pauth instructions to have the hasNegateRAState + // annotation. + BF.setIgnored(); + BC.outs() << "BOLT-INFO: inconsistent RAStates in function " + << BF.getPrintName() + << ": ptr sign/auth inst without .cfi_negate_ra_state\n"; + return false; + } + } + } + + bool RAState = BF.getInitialRAState(); + std::stack<bool> RAStateStack; + RAStateStack.push(RAState); + + for (BinaryBasicBlock &BB : BF) { + for (MCInst &Inst : BB) { + if (BC.MIB->isCFI(Inst)) + continue; + + if (BC.MIB->isPSignOnLR(Inst)) { + if (RAState) { + // RA signing instructions should only follow unsigned RA state. + BC.outs() << "BOLT-INFO: inconsistent RAStates in function " + << BF.getPrintName() + << ": ptr signing inst encountered in Signed RA state\n"; + BF.setIgnored(); + return false; + } + // The signing instruction itself is unsigned, the next will be + // signed. + BC.MIB->setRAUnsigned(Inst); + } else if (BC.MIB->isPAuthOnLR(Inst)) { + if (!RAState) { + // RA authenticating instructions should only follow signed RA state. + BC.outs() << "BOLT-INFO: inconsistent RAStates in function " + << BF.getPrintName() + << ": ptr authenticating inst encountered in Unsigned RA " + "state\n"; + BF.setIgnored(); + return false; + } + // The authenticating instruction itself is signed, but the next will be + // unsigned. + BC.MIB->setRASigned(Inst); + } else if (RAState) { + BC.MIB->setRASigned(Inst); + } else { + BC.MIB->setRAUnsigned(Inst); + } + + // Updating RAState. All updates are valid from the next instruction. + // Because the same instruction can have remember and restore, the order + // here is relevant. This is the reason to loop over Annotations instead + // of just checking each in a predefined order. + for (unsigned int Idx = 0; Idx < Inst.getNumOperands(); Idx++) { + std::optional<int64_t> Annotation = + BC.MIB->getAnnotationAtOpIndex(Inst, Idx); + if (!Annotation) + continue; + if (Annotation == MCPlus::MCAnnotation::kNegateState) + RAState = !RAState; + else if (Annotation == MCPlus::MCAnnotation::kRememberState) + RAStateStack.push(RAState); + else if (Annotation == MCPlus::MCAnnotation::kRestoreState) { + RAState = RAStateStack.top(); + RAStateStack.pop(); + } + } + } + } + return true; +} + +Error MarkRAStates::runOnFunctions(BinaryContext &BC) { + std::atomic<uint64_t> FunctionsIgnored{0}; + ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) { + if (!runOnFunction(BF)) { + FunctionsIgnored++; + } + }; + + 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(); + }; + + int Total = llvm::count_if( + BC.getBinaryFunctions(), + [&](std::pair<const unsigned long, BinaryFunction> &P) { + return P.second.containedNegateRAState() && !P.second.isIgnored(); + }); + + ParallelUtilities::runOnEachFunction( + BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun, + SkipPredicate, "MarkRAStates"); + BC.outs() << "BOLT-INFO: MarkRAStates ran on " << Total + << " functions. Ignored " << FunctionsIgnored << " functions " + << format("(%.2lf%%)", (100.0 * FunctionsIgnored) / Total) + << " because of CFI inconsistencies\n"; + + return Error::success(); +} + +} // end namespace bolt +} // end namespace llvm |