llvm-cfi-verify.cpp 10 KB

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