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
32using namespace llvm;
33using namespace llvm::object;
34using namespace llvm::cfi_verify;
35
36static cl::OptionCategory CFIVerifyCategory("CFI Verify Options");
37
38cl::opt<std::string> InputFilename(cl::Positional, cl::desc("<input file>"),
39 cl::Required, cl::cat(CFIVerifyCategory));
40cl::opt<std::string> IgnorelistFilename(cl::Positional,
41 cl::desc("[ignorelist file]"),
42 cl::init(Val: "-"),
43 cl::cat(CFIVerifyCategory));
44cl::opt<bool> PrintGraphs(
45 "print-graphs",
46 cl::desc("Print graphs around indirect CF instructions in DOT format."),
47 cl::init(Val: false), cl::cat(CFIVerifyCategory));
48cl::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(Val: 0), cl::cat(CFIVerifyCategory));
54cl::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(Val: 0), cl::cat(CFIVerifyCategory));
62cl::opt<bool> Summarize("summarize", cl::desc("Print the summary only."),
63 cl::init(Val: false), cl::cat(CFIVerifyCategory));
64
65ExitOnError ExitOnErr;
66
67static void printBlameContext(const DILineInfo &LineInfo, unsigned Context) {
68 auto FileOrErr = MemoryBuffer::getFile(Filename: 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(A&: Lines, Separator: '\n');
77
78 for (unsigned i = std::max<size_t>(a: 1, b: LineInfo.Line - Context);
79 i <
80 std::min<size_t>(a: Lines.size() + 1, b: 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
91static void printInstructionInformation(const FileAnalysis &Analysis,
92 const Instr &InstrMeta,
93 const GraphResult &Graph,
94 CFIProtectionStatus ProtectionStatus) {
95 outs() << "Instruction: " << format_hex(N: InstrMeta.VMAddress, Width: 2) << " ("
96 << stringCFIProtectionStatus(Status: ProtectionStatus) << "): ";
97 Analysis.printInstruction(InstrMeta, OS&: outs());
98 outs() << " \n";
99
100 if (PrintGraphs)
101 Graph.printToDOT(Analysis, OS&: outs());
102}
103
104static 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, Context: PrintBlameContextAll);
116 } else {
117 if (CFIProtected) {
118 outs() << "====> Expected Protected\n";
119 if (PrintBlameContextAll)
120 printBlameContext(LineInfo, Context: PrintBlameContextAll);
121 } else {
122 outs() << "====> Unexpected Unprotected (BAD)\n";
123 if (PrintBlameContext)
124 printBlameContext(LineInfo, Context: PrintBlameContext);
125 }
126 }
127}
128
129static void
130printIndirectCFInstructions(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.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(N: Address.Address, Width: 2)
163 << " with line tables from " << InputFilename << "\n";
164 exit(EXIT_FAILURE);
165 }
166
167 const auto &LineInfo = InliningInfo->getFrame(Index: 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(Index: i);
173 outs() << " " << format_hex(N: Address.Address, Width: 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, Context: PrintBlameContextAll);
183 ExpectedProtected++;
184 } else {
185 if (PrintBlameContext && !Summarize)
186 printBlameContext(LineInfo, Context: 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(Section: K, Prefix: "src", Query: LineInfo.FileName);
197 if (!BlameLine)
198 BlameLine =
199 SpecialCaseList->inSectionBlame(Section: K, Prefix: "fun", Query: 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(Fmt: "\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 Vals&: IndirectCFInstructions, Vals&: ExpectedProtected,
233 Vals: ((double)ExpectedProtected) / IndirectCFInstructions,
234 Vals&: UnexpectedProtected,
235 Vals: ((double)UnexpectedProtected) / IndirectCFInstructions,
236 Vals&: ExpectedUnprotected,
237 Vals: ((double)ExpectedUnprotected) / IndirectCFInstructions,
238 Vals&: UnexpectedUnprotected,
239 Vals: ((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
251int main(int argc, char **argv) {
252 cl::HideUnrelatedOptions(Categories: {&CFIVerifyCategory, &getColorCategory()});
253 cl::ParseCommandLineOptions(
254 argc, argv,
255 Overview: "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(V: PrintBlameContextAll);
267
268 std::unique_ptr<SpecialCaseList> SpecialCaseList;
269 if (IgnorelistFilename != "-") {
270 std::string Error;
271 SpecialCaseList = SpecialCaseList::create(Paths: {IgnorelistFilename},
272 FS&: *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(Filename: InputFilename));
280 printIndirectCFInstructions(Analysis, SpecialCaseList: SpecialCaseList.get());
281
282 return EXIT_SUCCESS;
283}
284