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