brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.4 KiB · b372957 Raw
289 lines · cpp
1//===-- llvm-cfi-verify.cpp - CFI Verification tool for LLVM --------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This tool verifies Control Flow Integrity (CFI) instrumentation by static10// binary analysis. See the design document in /docs/CFIVerify.rst for more11// information.12//13// This tool is currently incomplete. It currently only does disassembly for14// object files, and searches through the code for indirect control flow15// instructions, printing them once found.16//17//===----------------------------------------------------------------------===//18 19#include "lib/FileAnalysis.h"20#include "lib/GraphBuilder.h"21 22#include "llvm/BinaryFormat/ELF.h"23#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"24#include "llvm/Support/CommandLine.h"25#include "llvm/Support/Error.h"26#include "llvm/Support/FormatVariadic.h"27#include "llvm/Support/SpecialCaseList.h"28#include "llvm/Support/VirtualFileSystem.h"29 30#include <cstdlib>31 32using namespace llvm;33using namespace llvm::object;34using namespace llvm::cfi_verify;35 36static cl::OptionCategory CFIVerifyCategory("CFI Verify Options");37 38static cl::opt<std::string> InputFilename(cl::Positional,39                                          cl::desc("<input file>"),40                                          cl::Required,41                                          cl::cat(CFIVerifyCategory));42static cl::opt<std::string> IgnorelistFilename(cl::Positional,43                                               cl::desc("[ignorelist file]"),44                                               cl::init("-"),45                                               cl::cat(CFIVerifyCategory));46static cl::opt<bool> PrintGraphs(47    "print-graphs",48    cl::desc("Print graphs around indirect CF instructions in DOT format."),49    cl::init(false), cl::cat(CFIVerifyCategory));50static cl::opt<unsigned> PrintBlameContext(51    "blame-context",52    cl::desc("Print the blame context (if possible) for BAD instructions. This "53             "specifies the number of lines of context to include, where zero "54             "disables this feature."),55    cl::init(0), cl::cat(CFIVerifyCategory));56static cl::opt<unsigned> PrintBlameContextAll(57    "blame-context-all",58    cl::desc("Prints the blame context (if possible) for ALL instructions. "59             "This specifies the number of lines of context for non-BAD "60             "instructions (see --blame-context). If --blame-context is "61             "unspecified, it prints this number of contextual lines for BAD "62             "instructions as well."),63    cl::init(0), cl::cat(CFIVerifyCategory));64static cl::opt<bool> Summarize("summarize", cl::desc("Print the summary only."),65                               cl::init(false), cl::cat(CFIVerifyCategory));66 67ExitOnError ExitOnErr;68 69static void printBlameContext(const DILineInfo &LineInfo, unsigned Context) {70  auto FileOrErr = MemoryBuffer::getFile(LineInfo.FileName);71  if (!FileOrErr) {72    errs() << "Could not open file: " << LineInfo.FileName << "\n";73    return;74  }75 76  std::unique_ptr<MemoryBuffer> File = std::move(FileOrErr.get());77  SmallVector<StringRef, 100> Lines;78  File->getBuffer().split(Lines, '\n');79 80  for (unsigned i = std::max<size_t>(1, LineInfo.Line - Context);81       i < std::min<size_t>(Lines.size() + 1, LineInfo.Line + Context + 1);82       ++i) {83    if (i == LineInfo.Line)84      outs() << ">";85    else86      outs() << " ";87 88    outs() << i << ": " << Lines[i - 1] << "\n";89  }90}91 92static void printInstructionInformation(const FileAnalysis &Analysis,93                                        const Instr &InstrMeta,94                                        const GraphResult &Graph,95                                        CFIProtectionStatus ProtectionStatus) {96  outs() << "Instruction: " << format_hex(InstrMeta.VMAddress, 2) << " ("97         << stringCFIProtectionStatus(ProtectionStatus) << "): ";98  Analysis.printInstruction(InstrMeta, outs());99  outs() << " \n";100 101  if (PrintGraphs)102    Graph.printToDOT(Analysis, outs());103}104 105static void printInstructionStatus(unsigned BlameLine, bool CFIProtected,106                                   const DILineInfo &LineInfo) {107  if (BlameLine) {108    outs() << "Ignorelist Match: " << IgnorelistFilename << ":" << BlameLine109           << "\n";110    if (CFIProtected)111      outs() << "====> Unexpected Protected\n";112    else113      outs() << "====> Expected Unprotected\n";114 115    if (PrintBlameContextAll)116      printBlameContext(LineInfo, PrintBlameContextAll);117  } else {118    if (CFIProtected) {119      outs() << "====> Expected Protected\n";120      if (PrintBlameContextAll)121        printBlameContext(LineInfo, PrintBlameContextAll);122    } else {123      outs() << "====> Unexpected Unprotected (BAD)\n";124      if (PrintBlameContext)125        printBlameContext(LineInfo, PrintBlameContext);126    }127  }128}129 130static void131printIndirectCFInstructions(FileAnalysis &Analysis,132                            const SpecialCaseList *SpecialCaseList) {133  uint64_t ExpectedProtected = 0;134  uint64_t UnexpectedProtected = 0;135  uint64_t ExpectedUnprotected = 0;136  uint64_t UnexpectedUnprotected = 0;137 138  std::map<unsigned, uint64_t> BlameCounter;139 140  for (object::SectionedAddress Address : Analysis.getIndirectInstructions()) {141    const auto &InstrMeta = Analysis.getInstructionOrDie(Address.Address);142    GraphResult Graph = GraphBuilder::buildFlowGraph(Analysis, Address);143 144    CFIProtectionStatus ProtectionStatus =145        Analysis.validateCFIProtection(Graph);146    bool CFIProtected = (ProtectionStatus == CFIProtectionStatus::PROTECTED);147 148    if (!Summarize) {149      outs() << "-----------------------------------------------------\n";150      printInstructionInformation(Analysis, InstrMeta, Graph, ProtectionStatus);151    }152 153    if (IgnoreDWARFFlag) {154      if (CFIProtected)155        ExpectedProtected++;156      else157        UnexpectedUnprotected++;158      continue;159    }160 161    auto InliningInfo = Analysis.symbolizeInlinedCode(Address);162    if (!InliningInfo || InliningInfo->getNumberOfFrames() == 0) {163      errs() << "Failed to symbolise " << format_hex(Address.Address, 2)164             << " with line tables from " << InputFilename << "\n";165      exit(EXIT_FAILURE);166    }167 168    const auto &LineInfo = InliningInfo->getFrame(0);169 170    // Print the inlining symbolisation of this instruction.171    if (!Summarize) {172      for (uint32_t i = 0; i < InliningInfo->getNumberOfFrames(); ++i) {173        const auto &Line = InliningInfo->getFrame(i);174        outs() << "  " << format_hex(Address.Address, 2) << " = "175               << Line.FileName << ":" << Line.Line << ":" << Line.Column176               << " (" << Line.FunctionName << ")\n";177      }178    }179 180    if (!SpecialCaseList) {181      if (CFIProtected) {182        if (PrintBlameContextAll && !Summarize)183          printBlameContext(LineInfo, PrintBlameContextAll);184        ExpectedProtected++;185      } else {186        if (PrintBlameContext && !Summarize)187          printBlameContext(LineInfo, PrintBlameContext);188        UnexpectedUnprotected++;189      }190      continue;191    }192 193    unsigned BlameLine = 0;194    for (auto &K : {"cfi-icall", "cfi-vcall"}) {195      if (!BlameLine) {196        auto [FileIdx, Line] =197            SpecialCaseList->inSectionBlame(K, "src", LineInfo.FileName);198        BlameLine = Line;199      }200      if (!BlameLine) {201        auto [FileIdx, Line] =202            SpecialCaseList->inSectionBlame(K, "fun", LineInfo.FunctionName);203        BlameLine = Line;204      }205    }206 207    if (BlameLine) {208      BlameCounter[BlameLine]++;209      if (CFIProtected)210        UnexpectedProtected++;211      else212        ExpectedUnprotected++;213    } else {214      if (CFIProtected)215        ExpectedProtected++;216      else217        UnexpectedUnprotected++;218    }219 220    if (!Summarize)221      printInstructionStatus(BlameLine, CFIProtected, LineInfo);222  }223 224  uint64_t IndirectCFInstructions = ExpectedProtected + UnexpectedProtected +225                                    ExpectedUnprotected + UnexpectedUnprotected;226 227  if (IndirectCFInstructions == 0) {228    outs() << "No indirect CF instructions found.\n";229    return;230  }231 232  outs() << formatv("\nTotal Indirect CF Instructions: {0}\n"233                    "Expected Protected: {1} ({2:P})\n"234                    "Unexpected Protected: {3} ({4:P})\n"235                    "Expected Unprotected: {5} ({6:P})\n"236                    "Unexpected Unprotected (BAD): {7} ({8:P})\n",237                    IndirectCFInstructions, ExpectedProtected,238                    ((double)ExpectedProtected) / IndirectCFInstructions,239                    UnexpectedProtected,240                    ((double)UnexpectedProtected) / IndirectCFInstructions,241                    ExpectedUnprotected,242                    ((double)ExpectedUnprotected) / IndirectCFInstructions,243                    UnexpectedUnprotected,244                    ((double)UnexpectedUnprotected) / IndirectCFInstructions);245 246  if (!SpecialCaseList)247    return;248 249  outs() << "\nIgnorelist Results:\n";250  for (const auto &KV : BlameCounter) {251    outs() << "  " << IgnorelistFilename << ":" << KV.first << " affects "252           << KV.second << " indirect CF instructions.\n";253  }254}255 256int main(int argc, char **argv) {257  cl::HideUnrelatedOptions({&CFIVerifyCategory, &getColorCategory()});258  cl::ParseCommandLineOptions(259      argc, argv,260      "Identifies whether Control Flow Integrity protects all indirect control "261      "flow instructions in the provided object file, DSO or binary.\nNote: "262      "Anything statically linked into the provided file *must* be compiled "263      "with '-g'. This can be relaxed through the '--ignore-dwarf' flag.");264 265  InitializeAllTargetInfos();266  InitializeAllTargetMCs();267  InitializeAllAsmParsers();268  InitializeAllDisassemblers();269 270  if (PrintBlameContextAll && !PrintBlameContext)271    PrintBlameContext.setValue(PrintBlameContextAll);272 273  std::unique_ptr<SpecialCaseList> SpecialCaseList;274  if (IgnorelistFilename != "-") {275    std::string Error;276    SpecialCaseList = SpecialCaseList::create({IgnorelistFilename},277                                              *vfs::getRealFileSystem(), Error);278    if (!SpecialCaseList) {279      errs() << "Failed to get ignorelist: " << Error << "\n";280      exit(EXIT_FAILURE);281    }282  }283 284  FileAnalysis Analysis = ExitOnErr(FileAnalysis::Create(InputFilename));285  printIndirectCFInstructions(Analysis, SpecialCaseList.get());286 287  return EXIT_SUCCESS;288}289