xref: /llvm-project/llvm/tools/llvm-cfi-verify/llvm-cfi-verify.cpp (revision 4071dcf3549f9936a4004b0b34b67b871eba241e)
131b4531aSVlad Tsyrklevich //===-- llvm-cfi-verify.cpp - CFI Verification tool for LLVM --------------===//
231b4531aSVlad Tsyrklevich //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
631b4531aSVlad Tsyrklevich //
731b4531aSVlad Tsyrklevich //===----------------------------------------------------------------------===//
831b4531aSVlad Tsyrklevich //
931b4531aSVlad Tsyrklevich // This tool verifies Control Flow Integrity (CFI) instrumentation by static
10*4071dcf3SPriyanshi Agarwal // binary analysis. See the design document in /docs/CFIVerify.rst for more
1131b4531aSVlad Tsyrklevich // information.
1231b4531aSVlad Tsyrklevich //
1331b4531aSVlad Tsyrklevich // This tool is currently incomplete. It currently only does disassembly for
1431b4531aSVlad Tsyrklevich // object files, and searches through the code for indirect control flow
1531b4531aSVlad Tsyrklevich // instructions, printing them once found.
1631b4531aSVlad Tsyrklevich //
1731b4531aSVlad Tsyrklevich //===----------------------------------------------------------------------===//
1831b4531aSVlad Tsyrklevich 
1989c3c8c4SVlad Tsyrklevich #include "lib/FileAnalysis.h"
203b9ea32eSMitch Phillips #include "lib/GraphBuilder.h"
21b5488a22SVlad Tsyrklevich 
2289c3c8c4SVlad Tsyrklevich #include "llvm/BinaryFormat/ELF.h"
23db29f437Sserge-sans-paille #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
2489c3c8c4SVlad Tsyrklevich #include "llvm/Support/CommandLine.h"
2589c3c8c4SVlad Tsyrklevich #include "llvm/Support/Error.h"
267db6f7a3SMitch Phillips #include "llvm/Support/FormatVariadic.h"
27c15bdf55SMitch Phillips #include "llvm/Support/SpecialCaseList.h"
28aa981c18SIlya Biryukov #include "llvm/Support/VirtualFileSystem.h"
2989c3c8c4SVlad Tsyrklevich 
3031b4531aSVlad Tsyrklevich #include <cstdlib>
3131b4531aSVlad Tsyrklevich 
3231b4531aSVlad Tsyrklevich using namespace llvm;
3331b4531aSVlad Tsyrklevich using namespace llvm::object;
3489c3c8c4SVlad Tsyrklevich using namespace llvm::cfi_verify;
3531b4531aSVlad Tsyrklevich 
3669a56845STimm Bäder static cl::OptionCategory CFIVerifyCategory("CFI Verify Options");
3769a56845STimm Bäder 
3831b4531aSVlad Tsyrklevich cl::opt<std::string> InputFilename(cl::Positional, cl::desc("<input file>"),
3969a56845STimm Bäder                                    cl::Required, cl::cat(CFIVerifyCategory));
40c4396b77SZarko Todorovski cl::opt<std::string> IgnorelistFilename(cl::Positional,
41c4396b77SZarko Todorovski                                         cl::desc("[ignorelist file]"),
4269a56845STimm Bäder                                         cl::init("-"),
4369a56845STimm Bäder                                         cl::cat(CFIVerifyCategory));
4402993892SMitch Phillips cl::opt<bool> PrintGraphs(
4502993892SMitch Phillips     "print-graphs",
4602993892SMitch Phillips     cl::desc("Print graphs around indirect CF instructions in DOT format."),
4769a56845STimm Bäder     cl::init(false), cl::cat(CFIVerifyCategory));
48b2c3ea76SVlad Tsyrklevich cl::opt<unsigned> PrintBlameContext(
49b2c3ea76SVlad Tsyrklevich     "blame-context",
50b2c3ea76SVlad Tsyrklevich     cl::desc("Print the blame context (if possible) for BAD instructions. This "
51b2c3ea76SVlad Tsyrklevich              "specifies the number of lines of context to include, where zero "
52b2c3ea76SVlad Tsyrklevich              "disables this feature."),
5369a56845STimm Bäder     cl::init(0), cl::cat(CFIVerifyCategory));
54b2c3ea76SVlad Tsyrklevich cl::opt<unsigned> PrintBlameContextAll(
55b2c3ea76SVlad Tsyrklevich     "blame-context-all",
56b2c3ea76SVlad Tsyrklevich     cl::desc("Prints the blame context (if possible) for ALL instructions. "
57b2c3ea76SVlad Tsyrklevich              "This specifies the number of lines of context for non-BAD "
58b2c3ea76SVlad Tsyrklevich              "instructions (see --blame-context). If --blame-context is "
59b2c3ea76SVlad Tsyrklevich              "unspecified, it prints this number of contextual lines for BAD "
60b2c3ea76SVlad Tsyrklevich              "instructions as well."),
6169a56845STimm Bäder     cl::init(0), cl::cat(CFIVerifyCategory));
62b2c3ea76SVlad Tsyrklevich cl::opt<bool> Summarize("summarize", cl::desc("Print the summary only."),
6369a56845STimm Bäder                         cl::init(false), cl::cat(CFIVerifyCategory));
6431b4531aSVlad Tsyrklevich 
6589c3c8c4SVlad Tsyrklevich ExitOnError ExitOnErr;
6631b4531aSVlad Tsyrklevich 
printBlameContext(const DILineInfo & LineInfo,unsigned Context)6720e9c36cSFangrui Song static void printBlameContext(const DILineInfo &LineInfo, unsigned Context) {
68b2c3ea76SVlad Tsyrklevich   auto FileOrErr = MemoryBuffer::getFile(LineInfo.FileName);
69b2c3ea76SVlad Tsyrklevich   if (!FileOrErr) {
70b2c3ea76SVlad Tsyrklevich     errs() << "Could not open file: " << LineInfo.FileName << "\n";
71b2c3ea76SVlad Tsyrklevich     return;
72b2c3ea76SVlad Tsyrklevich   }
73b2c3ea76SVlad Tsyrklevich 
74b2c3ea76SVlad Tsyrklevich   std::unique_ptr<MemoryBuffer> File = std::move(FileOrErr.get());
75b2c3ea76SVlad Tsyrklevich   SmallVector<StringRef, 100> Lines;
76b2c3ea76SVlad Tsyrklevich   File->getBuffer().split(Lines, '\n');
77b2c3ea76SVlad Tsyrklevich 
783e0e7cd9SVlad Tsyrklevich   for (unsigned i = std::max<size_t>(1, LineInfo.Line - Context);
79b2c3ea76SVlad Tsyrklevich        i <
803e0e7cd9SVlad Tsyrklevich        std::min<size_t>(Lines.size() + 1, LineInfo.Line + Context + 1);
81b2c3ea76SVlad Tsyrklevich        ++i) {
82b2c3ea76SVlad Tsyrklevich     if (i == LineInfo.Line)
83b2c3ea76SVlad Tsyrklevich       outs() << ">";
84b2c3ea76SVlad Tsyrklevich     else
85b2c3ea76SVlad Tsyrklevich       outs() << " ";
86b2c3ea76SVlad Tsyrklevich 
87b2c3ea76SVlad Tsyrklevich     outs() << i << ": " << Lines[i - 1] << "\n";
88b2c3ea76SVlad Tsyrklevich   }
89b2c3ea76SVlad Tsyrklevich }
90b2c3ea76SVlad Tsyrklevich 
printInstructionInformation(const FileAnalysis & Analysis,const Instr & InstrMeta,const GraphResult & Graph,CFIProtectionStatus ProtectionStatus)9120e9c36cSFangrui Song static void printInstructionInformation(const FileAnalysis &Analysis,
92b2c3ea76SVlad Tsyrklevich                                         const Instr &InstrMeta,
93b2c3ea76SVlad Tsyrklevich                                         const GraphResult &Graph,
94b2c3ea76SVlad Tsyrklevich                                         CFIProtectionStatus ProtectionStatus) {
95b2c3ea76SVlad Tsyrklevich   outs() << "Instruction: " << format_hex(InstrMeta.VMAddress, 2) << " ("
96b2c3ea76SVlad Tsyrklevich          << stringCFIProtectionStatus(ProtectionStatus) << "): ";
97b2c3ea76SVlad Tsyrklevich   Analysis.printInstruction(InstrMeta, outs());
98b2c3ea76SVlad Tsyrklevich   outs() << " \n";
99b2c3ea76SVlad Tsyrklevich 
100b2c3ea76SVlad Tsyrklevich   if (PrintGraphs)
101b2c3ea76SVlad Tsyrklevich     Graph.printToDOT(Analysis, outs());
102b2c3ea76SVlad Tsyrklevich }
103b2c3ea76SVlad Tsyrklevich 
printInstructionStatus(unsigned BlameLine,bool CFIProtected,const DILineInfo & LineInfo)10420e9c36cSFangrui Song static void printInstructionStatus(unsigned BlameLine, bool CFIProtected,
105b2c3ea76SVlad Tsyrklevich                                    const DILineInfo &LineInfo) {
106b2c3ea76SVlad Tsyrklevich   if (BlameLine) {
107c4396b77SZarko Todorovski     outs() << "Ignorelist Match: " << IgnorelistFilename << ":" << BlameLine
108b2c3ea76SVlad Tsyrklevich            << "\n";
109b2c3ea76SVlad Tsyrklevich     if (CFIProtected)
110b2c3ea76SVlad Tsyrklevich       outs() << "====> Unexpected Protected\n";
111b2c3ea76SVlad Tsyrklevich     else
112b2c3ea76SVlad Tsyrklevich       outs() << "====> Expected Unprotected\n";
113b2c3ea76SVlad Tsyrklevich 
114b2c3ea76SVlad Tsyrklevich     if (PrintBlameContextAll)
115b2c3ea76SVlad Tsyrklevich       printBlameContext(LineInfo, PrintBlameContextAll);
116b2c3ea76SVlad Tsyrklevich   } else {
117b2c3ea76SVlad Tsyrklevich     if (CFIProtected) {
118b2c3ea76SVlad Tsyrklevich       outs() << "====> Expected Protected\n";
119b2c3ea76SVlad Tsyrklevich       if (PrintBlameContextAll)
120b2c3ea76SVlad Tsyrklevich         printBlameContext(LineInfo, PrintBlameContextAll);
121b2c3ea76SVlad Tsyrklevich     } else {
122b2c3ea76SVlad Tsyrklevich       outs() << "====> Unexpected Unprotected (BAD)\n";
123b2c3ea76SVlad Tsyrklevich       if (PrintBlameContext)
124b2c3ea76SVlad Tsyrklevich         printBlameContext(LineInfo, PrintBlameContext);
125b2c3ea76SVlad Tsyrklevich     }
126b2c3ea76SVlad Tsyrklevich   }
127b2c3ea76SVlad Tsyrklevich }
128b2c3ea76SVlad Tsyrklevich 
12920e9c36cSFangrui Song static void
printIndirectCFInstructions(FileAnalysis & Analysis,const SpecialCaseList * SpecialCaseList)13020e9c36cSFangrui Song printIndirectCFInstructions(FileAnalysis &Analysis,
131c15bdf55SMitch Phillips                             const SpecialCaseList *SpecialCaseList) {
132c15bdf55SMitch Phillips   uint64_t ExpectedProtected = 0;
133c15bdf55SMitch Phillips   uint64_t UnexpectedProtected = 0;
134c15bdf55SMitch Phillips   uint64_t ExpectedUnprotected = 0;
135c15bdf55SMitch Phillips   uint64_t UnexpectedUnprotected = 0;
136c15bdf55SMitch Phillips 
137d64af525SMitch Phillips   std::map<unsigned, uint64_t> BlameCounter;
1387db6f7a3SMitch Phillips 
13977fc1f60SAlexey Lapshin   for (object::SectionedAddress Address : Analysis.getIndirectInstructions()) {
14077fc1f60SAlexey Lapshin     const auto &InstrMeta = Analysis.getInstructionOrDie(Address.Address);
1413b9ea32eSMitch Phillips     GraphResult Graph = GraphBuilder::buildFlowGraph(Analysis, Address);
1427db6f7a3SMitch Phillips 
1433b9ea32eSMitch Phillips     CFIProtectionStatus ProtectionStatus =
1443b9ea32eSMitch Phillips         Analysis.validateCFIProtection(Graph);
1453b9ea32eSMitch Phillips     bool CFIProtected = (ProtectionStatus == CFIProtectionStatus::PROTECTED);
146c15bdf55SMitch Phillips 
147b2c3ea76SVlad Tsyrklevich     if (!Summarize) {
148b2c3ea76SVlad Tsyrklevich       outs() << "-----------------------------------------------------\n";
149b2c3ea76SVlad Tsyrklevich       printInstructionInformation(Analysis, InstrMeta, Graph, ProtectionStatus);
150b2c3ea76SVlad Tsyrklevich     }
1517db6f7a3SMitch Phillips 
152c15bdf55SMitch Phillips     if (IgnoreDWARFFlag) {
153c15bdf55SMitch Phillips       if (CFIProtected)
154c15bdf55SMitch Phillips         ExpectedProtected++;
155c15bdf55SMitch Phillips       else
156c15bdf55SMitch Phillips         UnexpectedUnprotected++;
157c15bdf55SMitch Phillips       continue;
158c15bdf55SMitch Phillips     }
159c15bdf55SMitch Phillips 
1603b9ea32eSMitch Phillips     auto InliningInfo = Analysis.symbolizeInlinedCode(Address);
161c15bdf55SMitch Phillips     if (!InliningInfo || InliningInfo->getNumberOfFrames() == 0) {
16277fc1f60SAlexey Lapshin       errs() << "Failed to symbolise " << format_hex(Address.Address, 2)
163c15bdf55SMitch Phillips              << " with line tables from " << InputFilename << "\n";
164c15bdf55SMitch Phillips       exit(EXIT_FAILURE);
165c15bdf55SMitch Phillips     }
166c15bdf55SMitch Phillips 
167b2c3ea76SVlad Tsyrklevich     const auto &LineInfo = InliningInfo->getFrame(0);
168c15bdf55SMitch Phillips 
169c15bdf55SMitch Phillips     // Print the inlining symbolisation of this instruction.
170b2c3ea76SVlad Tsyrklevich     if (!Summarize) {
171c15bdf55SMitch Phillips       for (uint32_t i = 0; i < InliningInfo->getNumberOfFrames(); ++i) {
172c15bdf55SMitch Phillips         const auto &Line = InliningInfo->getFrame(i);
17377fc1f60SAlexey Lapshin         outs() << "  " << format_hex(Address.Address, 2) << " = "
17477fc1f60SAlexey Lapshin                << Line.FileName << ":" << Line.Line << ":" << Line.Column
17577fc1f60SAlexey Lapshin                << " (" << Line.FunctionName << ")\n";
176b2c3ea76SVlad Tsyrklevich       }
1777db6f7a3SMitch Phillips     }
178c15bdf55SMitch Phillips 
179c15bdf55SMitch Phillips     if (!SpecialCaseList) {
180b2c3ea76SVlad Tsyrklevich       if (CFIProtected) {
181b2c3ea76SVlad Tsyrklevich         if (PrintBlameContextAll && !Summarize)
182b2c3ea76SVlad Tsyrklevich           printBlameContext(LineInfo, PrintBlameContextAll);
183c15bdf55SMitch Phillips         ExpectedProtected++;
184b2c3ea76SVlad Tsyrklevich       } else {
185b2c3ea76SVlad Tsyrklevich         if (PrintBlameContext && !Summarize)
186b2c3ea76SVlad Tsyrklevich           printBlameContext(LineInfo, PrintBlameContext);
187c15bdf55SMitch Phillips         UnexpectedUnprotected++;
188b2c3ea76SVlad Tsyrklevich       }
189c15bdf55SMitch Phillips       continue;
190c15bdf55SMitch Phillips     }
191c15bdf55SMitch Phillips 
192d64af525SMitch Phillips     unsigned BlameLine = 0;
193d64af525SMitch Phillips     for (auto &K : {"cfi-icall", "cfi-vcall"}) {
194d64af525SMitch Phillips       if (!BlameLine)
195d64af525SMitch Phillips         BlameLine =
196d64af525SMitch Phillips             SpecialCaseList->inSectionBlame(K, "src", LineInfo.FileName);
197d64af525SMitch Phillips       if (!BlameLine)
198d64af525SMitch Phillips         BlameLine =
199d64af525SMitch Phillips             SpecialCaseList->inSectionBlame(K, "fun", LineInfo.FunctionName);
200c15bdf55SMitch Phillips     }
201c15bdf55SMitch Phillips 
202d64af525SMitch Phillips     if (BlameLine) {
203d64af525SMitch Phillips       BlameCounter[BlameLine]++;
204b2c3ea76SVlad Tsyrklevich       if (CFIProtected)
205c15bdf55SMitch Phillips         UnexpectedProtected++;
206b2c3ea76SVlad Tsyrklevich       else
207c15bdf55SMitch Phillips         ExpectedUnprotected++;
208c15bdf55SMitch Phillips     } else {
209b2c3ea76SVlad Tsyrklevich       if (CFIProtected)
210c15bdf55SMitch Phillips         ExpectedProtected++;
211b2c3ea76SVlad Tsyrklevich       else
212c15bdf55SMitch Phillips         UnexpectedUnprotected++;
213c15bdf55SMitch Phillips     }
214b2c3ea76SVlad Tsyrklevich 
215b2c3ea76SVlad Tsyrklevich     if (!Summarize)
216b2c3ea76SVlad Tsyrklevich       printInstructionStatus(BlameLine, CFIProtected, LineInfo);
21731b4531aSVlad Tsyrklevich   }
21831b4531aSVlad Tsyrklevich 
219c15bdf55SMitch Phillips   uint64_t IndirectCFInstructions = ExpectedProtected + UnexpectedProtected +
220c15bdf55SMitch Phillips                                     ExpectedUnprotected + UnexpectedUnprotected;
221c15bdf55SMitch Phillips 
2226fb35251SMitch Phillips   if (IndirectCFInstructions == 0) {
2237db6f7a3SMitch Phillips     outs() << "No indirect CF instructions found.\n";
2246fb35251SMitch Phillips     return;
2256fb35251SMitch Phillips   }
226c15bdf55SMitch Phillips 
227b2c3ea76SVlad Tsyrklevich   outs() << formatv("\nTotal Indirect CF Instructions: {0}\n"
228b2c3ea76SVlad Tsyrklevich                     "Expected Protected: {1} ({2:P})\n"
229b2c3ea76SVlad Tsyrklevich                     "Unexpected Protected: {3} ({4:P})\n"
230b2c3ea76SVlad Tsyrklevich                     "Expected Unprotected: {5} ({6:P})\n"
231b2c3ea76SVlad Tsyrklevich                     "Unexpected Unprotected (BAD): {7} ({8:P})\n",
232b2c3ea76SVlad Tsyrklevich                     IndirectCFInstructions, ExpectedProtected,
233c15bdf55SMitch Phillips                     ((double)ExpectedProtected) / IndirectCFInstructions,
234c15bdf55SMitch Phillips                     UnexpectedProtected,
235c15bdf55SMitch Phillips                     ((double)UnexpectedProtected) / IndirectCFInstructions,
236c15bdf55SMitch Phillips                     ExpectedUnprotected,
237c15bdf55SMitch Phillips                     ((double)ExpectedUnprotected) / IndirectCFInstructions,
238c15bdf55SMitch Phillips                     UnexpectedUnprotected,
239c15bdf55SMitch Phillips                     ((double)UnexpectedUnprotected) / IndirectCFInstructions);
240d64af525SMitch Phillips 
241d64af525SMitch Phillips   if (!SpecialCaseList)
242d64af525SMitch Phillips     return;
243d64af525SMitch Phillips 
244c4396b77SZarko Todorovski   outs() << "\nIgnorelist Results:\n";
245d64af525SMitch Phillips   for (const auto &KV : BlameCounter) {
246c4396b77SZarko Todorovski     outs() << "  " << IgnorelistFilename << ":" << KV.first << " affects "
247d64af525SMitch Phillips            << KV.second << " indirect CF instructions.\n";
248d64af525SMitch Phillips   }
2497db6f7a3SMitch Phillips }
2507db6f7a3SMitch Phillips 
main(int argc,char ** argv)25131b4531aSVlad Tsyrklevich int main(int argc, char **argv) {
25269a56845STimm Bäder   cl::HideUnrelatedOptions({&CFIVerifyCategory, &getColorCategory()});
2537db6f7a3SMitch Phillips   cl::ParseCommandLineOptions(
2547db6f7a3SMitch Phillips       argc, argv,
2557db6f7a3SMitch Phillips       "Identifies whether Control Flow Integrity protects all indirect control "
2567db6f7a3SMitch Phillips       "flow instructions in the provided object file, DSO or binary.\nNote: "
2577db6f7a3SMitch Phillips       "Anything statically linked into the provided file *must* be compiled "
2587db6f7a3SMitch Phillips       "with '-g'. This can be relaxed through the '--ignore-dwarf' flag.");
25931b4531aSVlad Tsyrklevich 
26031b4531aSVlad Tsyrklevich   InitializeAllTargetInfos();
26131b4531aSVlad Tsyrklevich   InitializeAllTargetMCs();
26231b4531aSVlad Tsyrklevich   InitializeAllAsmParsers();
26331b4531aSVlad Tsyrklevich   InitializeAllDisassemblers();
26431b4531aSVlad Tsyrklevich 
265b2c3ea76SVlad Tsyrklevich   if (PrintBlameContextAll && !PrintBlameContext)
266b2c3ea76SVlad Tsyrklevich     PrintBlameContext.setValue(PrintBlameContextAll);
267b2c3ea76SVlad Tsyrklevich 
268c15bdf55SMitch Phillips   std::unique_ptr<SpecialCaseList> SpecialCaseList;
269c4396b77SZarko Todorovski   if (IgnorelistFilename != "-") {
270c15bdf55SMitch Phillips     std::string Error;
271c4396b77SZarko Todorovski     SpecialCaseList = SpecialCaseList::create({IgnorelistFilename},
272aa981c18SIlya Biryukov                                               *vfs::getRealFileSystem(), Error);
273c15bdf55SMitch Phillips     if (!SpecialCaseList) {
274c4396b77SZarko Todorovski       errs() << "Failed to get ignorelist: " << Error << "\n";
275c15bdf55SMitch Phillips       exit(EXIT_FAILURE);
276c15bdf55SMitch Phillips     }
277c15bdf55SMitch Phillips   }
278c15bdf55SMitch Phillips 
2797db6f7a3SMitch Phillips   FileAnalysis Analysis = ExitOnErr(FileAnalysis::Create(InputFilename));
280c15bdf55SMitch Phillips   printIndirectCFInstructions(Analysis, SpecialCaseList.get());
28131b4531aSVlad Tsyrklevich 
28231b4531aSVlad Tsyrklevich   return EXIT_SUCCESS;
28331b4531aSVlad Tsyrklevich }
284