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