llvm-cfi-verify.cpp 10 KB

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