llvm-cfi-verify.cpp 9.7 KB

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