276 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===-- llvm-cfi-verify.cpp - CFI Verification tool for LLVM --------------===//
 | |
| //
 | |
| // 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 tool verifies Control Flow Integrity (CFI) instrumentation by static
 | |
| // binary anaylsis. See the design document in /docs/CFIVerify.rst for more
 | |
| // information.
 | |
| //
 | |
| // This tool is currently incomplete. It currently only does disassembly for
 | |
| // object files, and searches through the code for indirect control flow
 | |
| // instructions, printing them once found.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "lib/FileAnalysis.h"
 | |
| #include "lib/GraphBuilder.h"
 | |
| 
 | |
| #include "llvm/BinaryFormat/ELF.h"
 | |
| #include "llvm/Support/CommandLine.h"
 | |
| #include "llvm/Support/Error.h"
 | |
| #include "llvm/Support/FormatVariadic.h"
 | |
| #include "llvm/Support/SpecialCaseList.h"
 | |
| 
 | |
| #include <cstdlib>
 | |
| 
 | |
| using namespace llvm;
 | |
| using namespace llvm::object;
 | |
| using namespace llvm::cfi_verify;
 | |
| 
 | |
| cl::opt<std::string> InputFilename(cl::Positional, cl::desc("<input file>"),
 | |
|                                    cl::Required);
 | |
| cl::opt<std::string> BlacklistFilename(cl::Positional,
 | |
|                                        cl::desc("[blacklist file]"),
 | |
|                                        cl::init("-"));
 | |
| cl::opt<bool> PrintGraphs(
 | |
|     "print-graphs",
 | |
|     cl::desc("Print graphs around indirect CF instructions in DOT format."),
 | |
|     cl::init(false));
 | |
| cl::opt<unsigned> PrintBlameContext(
 | |
|     "blame-context",
 | |
|     cl::desc("Print the blame context (if possible) for BAD instructions. This "
 | |
|              "specifies the number of lines of context to include, where zero "
 | |
|              "disables this feature."),
 | |
|     cl::init(0));
 | |
| cl::opt<unsigned> PrintBlameContextAll(
 | |
|     "blame-context-all",
 | |
|     cl::desc("Prints the blame context (if possible) for ALL instructions. "
 | |
|              "This specifies the number of lines of context for non-BAD "
 | |
|              "instructions (see --blame-context). If --blame-context is "
 | |
|              "unspecified, it prints this number of contextual lines for BAD "
 | |
|              "instructions as well."),
 | |
|     cl::init(0));
 | |
| cl::opt<bool> Summarize("summarize", cl::desc("Print the summary only."),
 | |
|                         cl::init(false));
 | |
| 
 | |
| ExitOnError ExitOnErr;
 | |
| 
 | |
| void printBlameContext(const DILineInfo &LineInfo, unsigned Context) {
 | |
|   auto FileOrErr = MemoryBuffer::getFile(LineInfo.FileName);
 | |
|   if (!FileOrErr) {
 | |
|     errs() << "Could not open file: " << LineInfo.FileName << "\n";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   std::unique_ptr<MemoryBuffer> File = std::move(FileOrErr.get());
 | |
|   SmallVector<StringRef, 100> Lines;
 | |
|   File->getBuffer().split(Lines, '\n');
 | |
| 
 | |
|   for (unsigned i = std::max<size_t>(1, LineInfo.Line - Context);
 | |
|        i <
 | |
|        std::min<size_t>(Lines.size() + 1, LineInfo.Line + Context + 1);
 | |
|        ++i) {
 | |
|     if (i == LineInfo.Line)
 | |
|       outs() << ">";
 | |
|     else
 | |
|       outs() << " ";
 | |
| 
 | |
|     outs() << i << ": " << Lines[i - 1] << "\n";
 | |
|   }
 | |
| }
 | |
| 
 | |
| void printInstructionInformation(const FileAnalysis &Analysis,
 | |
|                                  const Instr &InstrMeta,
 | |
|                                  const GraphResult &Graph,
 | |
|                                  CFIProtectionStatus ProtectionStatus) {
 | |
|   outs() << "Instruction: " << format_hex(InstrMeta.VMAddress, 2) << " ("
 | |
|          << stringCFIProtectionStatus(ProtectionStatus) << "): ";
 | |
|   Analysis.printInstruction(InstrMeta, outs());
 | |
|   outs() << " \n";
 | |
| 
 | |
|   if (PrintGraphs)
 | |
|     Graph.printToDOT(Analysis, outs());
 | |
| }
 | |
| 
 | |
| void printInstructionStatus(unsigned BlameLine, bool CFIProtected,
 | |
|                             const DILineInfo &LineInfo) {
 | |
|   if (BlameLine) {
 | |
|     outs() << "Blacklist Match: " << BlacklistFilename << ":" << BlameLine
 | |
|            << "\n";
 | |
|     if (CFIProtected)
 | |
|       outs() << "====> Unexpected Protected\n";
 | |
|     else
 | |
|       outs() << "====> Expected Unprotected\n";
 | |
| 
 | |
|     if (PrintBlameContextAll)
 | |
|       printBlameContext(LineInfo, PrintBlameContextAll);
 | |
|   } else {
 | |
|     if (CFIProtected) {
 | |
|       outs() << "====> Expected Protected\n";
 | |
|       if (PrintBlameContextAll)
 | |
|         printBlameContext(LineInfo, PrintBlameContextAll);
 | |
|     } else {
 | |
|       outs() << "====> Unexpected Unprotected (BAD)\n";
 | |
|       if (PrintBlameContext)
 | |
|         printBlameContext(LineInfo, PrintBlameContext);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void printIndirectCFInstructions(FileAnalysis &Analysis,
 | |
|                                  const SpecialCaseList *SpecialCaseList) {
 | |
|   uint64_t ExpectedProtected = 0;
 | |
|   uint64_t UnexpectedProtected = 0;
 | |
|   uint64_t ExpectedUnprotected = 0;
 | |
|   uint64_t UnexpectedUnprotected = 0;
 | |
| 
 | |
|   std::map<unsigned, uint64_t> BlameCounter;
 | |
| 
 | |
|   for (object::SectionedAddress Address : Analysis.getIndirectInstructions()) {
 | |
|     const auto &InstrMeta = Analysis.getInstructionOrDie(Address.Address);
 | |
|     GraphResult Graph = GraphBuilder::buildFlowGraph(Analysis, Address);
 | |
| 
 | |
|     CFIProtectionStatus ProtectionStatus =
 | |
|         Analysis.validateCFIProtection(Graph);
 | |
|     bool CFIProtected = (ProtectionStatus == CFIProtectionStatus::PROTECTED);
 | |
| 
 | |
|     if (!Summarize) {
 | |
|       outs() << "-----------------------------------------------------\n";
 | |
|       printInstructionInformation(Analysis, InstrMeta, Graph, ProtectionStatus);
 | |
|     }
 | |
| 
 | |
|     if (IgnoreDWARFFlag) {
 | |
|       if (CFIProtected)
 | |
|         ExpectedProtected++;
 | |
|       else
 | |
|         UnexpectedUnprotected++;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     auto InliningInfo = Analysis.symbolizeInlinedCode(Address);
 | |
|     if (!InliningInfo || InliningInfo->getNumberOfFrames() == 0) {
 | |
|       errs() << "Failed to symbolise " << format_hex(Address.Address, 2)
 | |
|              << " with line tables from " << InputFilename << "\n";
 | |
|       exit(EXIT_FAILURE);
 | |
|     }
 | |
| 
 | |
|     const auto &LineInfo = InliningInfo->getFrame(0);
 | |
| 
 | |
|     // Print the inlining symbolisation of this instruction.
 | |
|     if (!Summarize) {
 | |
|       for (uint32_t i = 0; i < InliningInfo->getNumberOfFrames(); ++i) {
 | |
|         const auto &Line = InliningInfo->getFrame(i);
 | |
|         outs() << "  " << format_hex(Address.Address, 2) << " = "
 | |
|                << Line.FileName << ":" << Line.Line << ":" << Line.Column
 | |
|                << " (" << Line.FunctionName << ")\n";
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!SpecialCaseList) {
 | |
|       if (CFIProtected) {
 | |
|         if (PrintBlameContextAll && !Summarize)
 | |
|           printBlameContext(LineInfo, PrintBlameContextAll);
 | |
|         ExpectedProtected++;
 | |
|       } else {
 | |
|         if (PrintBlameContext && !Summarize)
 | |
|           printBlameContext(LineInfo, PrintBlameContext);
 | |
|         UnexpectedUnprotected++;
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     unsigned BlameLine = 0;
 | |
|     for (auto &K : {"cfi-icall", "cfi-vcall"}) {
 | |
|       if (!BlameLine)
 | |
|         BlameLine =
 | |
|             SpecialCaseList->inSectionBlame(K, "src", LineInfo.FileName);
 | |
|       if (!BlameLine)
 | |
|         BlameLine =
 | |
|             SpecialCaseList->inSectionBlame(K, "fun", LineInfo.FunctionName);
 | |
|     }
 | |
| 
 | |
|     if (BlameLine) {
 | |
|       BlameCounter[BlameLine]++;
 | |
|       if (CFIProtected)
 | |
|         UnexpectedProtected++;
 | |
|       else
 | |
|         ExpectedUnprotected++;
 | |
|     } else {
 | |
|       if (CFIProtected)
 | |
|         ExpectedProtected++;
 | |
|       else
 | |
|         UnexpectedUnprotected++;
 | |
|     }
 | |
| 
 | |
|     if (!Summarize)
 | |
|       printInstructionStatus(BlameLine, CFIProtected, LineInfo);
 | |
|   }
 | |
| 
 | |
|   uint64_t IndirectCFInstructions = ExpectedProtected + UnexpectedProtected +
 | |
|                                     ExpectedUnprotected + UnexpectedUnprotected;
 | |
| 
 | |
|   if (IndirectCFInstructions == 0) {
 | |
|     outs() << "No indirect CF instructions found.\n";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   outs() << formatv("\nTotal Indirect CF Instructions: {0}\n"
 | |
|                     "Expected Protected: {1} ({2:P})\n"
 | |
|                     "Unexpected Protected: {3} ({4:P})\n"
 | |
|                     "Expected Unprotected: {5} ({6:P})\n"
 | |
|                     "Unexpected Unprotected (BAD): {7} ({8:P})\n",
 | |
|                     IndirectCFInstructions, ExpectedProtected,
 | |
|                     ((double)ExpectedProtected) / IndirectCFInstructions,
 | |
|                     UnexpectedProtected,
 | |
|                     ((double)UnexpectedProtected) / IndirectCFInstructions,
 | |
|                     ExpectedUnprotected,
 | |
|                     ((double)ExpectedUnprotected) / IndirectCFInstructions,
 | |
|                     UnexpectedUnprotected,
 | |
|                     ((double)UnexpectedUnprotected) / IndirectCFInstructions);
 | |
| 
 | |
|   if (!SpecialCaseList)
 | |
|     return;
 | |
| 
 | |
|   outs() << "\nBlacklist Results:\n";
 | |
|   for (const auto &KV : BlameCounter) {
 | |
|     outs() << "  " << BlacklistFilename << ":" << KV.first << " affects "
 | |
|            << KV.second << " indirect CF instructions.\n";
 | |
|   }
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv) {
 | |
|   cl::ParseCommandLineOptions(
 | |
|       argc, argv,
 | |
|       "Identifies whether Control Flow Integrity protects all indirect control "
 | |
|       "flow instructions in the provided object file, DSO or binary.\nNote: "
 | |
|       "Anything statically linked into the provided file *must* be compiled "
 | |
|       "with '-g'. This can be relaxed through the '--ignore-dwarf' flag.");
 | |
| 
 | |
|   InitializeAllTargetInfos();
 | |
|   InitializeAllTargetMCs();
 | |
|   InitializeAllAsmParsers();
 | |
|   InitializeAllDisassemblers();
 | |
| 
 | |
|   if (PrintBlameContextAll && !PrintBlameContext)
 | |
|     PrintBlameContext.setValue(PrintBlameContextAll);
 | |
| 
 | |
|   std::unique_ptr<SpecialCaseList> SpecialCaseList;
 | |
|   if (BlacklistFilename != "-") {
 | |
|     std::string Error;
 | |
|     SpecialCaseList = SpecialCaseList::create({BlacklistFilename}, Error);
 | |
|     if (!SpecialCaseList) {
 | |
|       errs() << "Failed to get blacklist: " << Error << "\n";
 | |
|       exit(EXIT_FAILURE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   FileAnalysis Analysis = ExitOnErr(FileAnalysis::Create(InputFilename));
 | |
|   printIndirectCFInstructions(Analysis, SpecialCaseList.get());
 | |
| 
 | |
|   return EXIT_SUCCESS;
 | |
| }
 |