1 //===-- llvm-cfi-verify.cpp - CFI Verification tool for LLVM --------------===//
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
7 //===----------------------------------------------------------------------===//
9 // This tool verifies Control Flow Integrity (CFI) instrumentation by static
10 // binary anaylsis. See the design document in /docs/CFIVerify.rst for more
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.
17 //===----------------------------------------------------------------------===//
19 #include "lib/FileAnalysis.h"
20 #include "lib/GraphBuilder.h"
22 #include "llvm/BinaryFormat/ELF.h"
23 #include "llvm/Support/CommandLine.h"
24 #include "llvm/Support/Error.h"
25 #include "llvm/Support/FormatVariadic.h"
26 #include "llvm/Support/SpecialCaseList.h"
27 #include "llvm/Support/VirtualFileSystem.h"
32 using namespace llvm::object
;
33 using namespace llvm::cfi_verify
;
35 static cl::OptionCategory
CFIVerifyCategory("CFI Verify Options");
37 cl::opt
<std::string
> InputFilename(cl::Positional
, cl::desc("<input file>"),
38 cl::Required
, cl::cat(CFIVerifyCategory
));
39 cl::opt
<std::string
> IgnorelistFilename(cl::Positional
,
40 cl::desc("[ignorelist file]"),
42 cl::cat(CFIVerifyCategory
));
43 cl::opt
<bool> PrintGraphs(
45 cl::desc("Print graphs around indirect CF instructions in DOT format."),
46 cl::init(false), cl::cat(CFIVerifyCategory
));
47 cl::opt
<unsigned> PrintBlameContext(
49 cl::desc("Print the blame context (if possible) for BAD instructions. This "
50 "specifies the number of lines of context to include, where zero "
51 "disables this feature."),
52 cl::init(0), cl::cat(CFIVerifyCategory
));
53 cl::opt
<unsigned> PrintBlameContextAll(
55 cl::desc("Prints the blame context (if possible) for ALL instructions. "
56 "This specifies the number of lines of context for non-BAD "
57 "instructions (see --blame-context). If --blame-context is "
58 "unspecified, it prints this number of contextual lines for BAD "
59 "instructions as well."),
60 cl::init(0), cl::cat(CFIVerifyCategory
));
61 cl::opt
<bool> Summarize("summarize", cl::desc("Print the summary only."),
62 cl::init(false), cl::cat(CFIVerifyCategory
));
64 ExitOnError ExitOnErr
;
66 static void printBlameContext(const DILineInfo
&LineInfo
, unsigned Context
) {
67 auto FileOrErr
= MemoryBuffer::getFile(LineInfo
.FileName
);
69 errs() << "Could not open file: " << LineInfo
.FileName
<< "\n";
73 std::unique_ptr
<MemoryBuffer
> File
= std::move(FileOrErr
.get());
74 SmallVector
<StringRef
, 100> Lines
;
75 File
->getBuffer().split(Lines
, '\n');
77 for (unsigned i
= std::max
<size_t>(1, LineInfo
.Line
- Context
);
79 std::min
<size_t>(Lines
.size() + 1, LineInfo
.Line
+ Context
+ 1);
81 if (i
== LineInfo
.Line
)
86 outs() << i
<< ": " << Lines
[i
- 1] << "\n";
90 static void printInstructionInformation(const FileAnalysis
&Analysis
,
91 const Instr
&InstrMeta
,
92 const GraphResult
&Graph
,
93 CFIProtectionStatus ProtectionStatus
) {
94 outs() << "Instruction: " << format_hex(InstrMeta
.VMAddress
, 2) << " ("
95 << stringCFIProtectionStatus(ProtectionStatus
) << "): ";
96 Analysis
.printInstruction(InstrMeta
, outs());
100 Graph
.printToDOT(Analysis
, outs());
103 static void printInstructionStatus(unsigned BlameLine
, bool CFIProtected
,
104 const DILineInfo
&LineInfo
) {
106 outs() << "Ignorelist Match: " << IgnorelistFilename
<< ":" << BlameLine
109 outs() << "====> Unexpected Protected\n";
111 outs() << "====> Expected Unprotected\n";
113 if (PrintBlameContextAll
)
114 printBlameContext(LineInfo
, PrintBlameContextAll
);
117 outs() << "====> Expected Protected\n";
118 if (PrintBlameContextAll
)
119 printBlameContext(LineInfo
, PrintBlameContextAll
);
121 outs() << "====> Unexpected Unprotected (BAD)\n";
122 if (PrintBlameContext
)
123 printBlameContext(LineInfo
, PrintBlameContext
);
129 printIndirectCFInstructions(FileAnalysis
&Analysis
,
130 const SpecialCaseList
*SpecialCaseList
) {
131 uint64_t ExpectedProtected
= 0;
132 uint64_t UnexpectedProtected
= 0;
133 uint64_t ExpectedUnprotected
= 0;
134 uint64_t UnexpectedUnprotected
= 0;
136 std::map
<unsigned, uint64_t> BlameCounter
;
138 for (object::SectionedAddress Address
: Analysis
.getIndirectInstructions()) {
139 const auto &InstrMeta
= Analysis
.getInstructionOrDie(Address
.Address
);
140 GraphResult Graph
= GraphBuilder::buildFlowGraph(Analysis
, Address
);
142 CFIProtectionStatus ProtectionStatus
=
143 Analysis
.validateCFIProtection(Graph
);
144 bool CFIProtected
= (ProtectionStatus
== CFIProtectionStatus::PROTECTED
);
147 outs() << "-----------------------------------------------------\n";
148 printInstructionInformation(Analysis
, InstrMeta
, Graph
, ProtectionStatus
);
151 if (IgnoreDWARFFlag
) {
155 UnexpectedUnprotected
++;
159 auto InliningInfo
= Analysis
.symbolizeInlinedCode(Address
);
160 if (!InliningInfo
|| InliningInfo
->getNumberOfFrames() == 0) {
161 errs() << "Failed to symbolise " << format_hex(Address
.Address
, 2)
162 << " with line tables from " << InputFilename
<< "\n";
166 const auto &LineInfo
= InliningInfo
->getFrame(0);
168 // Print the inlining symbolisation of this instruction.
170 for (uint32_t i
= 0; i
< InliningInfo
->getNumberOfFrames(); ++i
) {
171 const auto &Line
= InliningInfo
->getFrame(i
);
172 outs() << " " << format_hex(Address
.Address
, 2) << " = "
173 << Line
.FileName
<< ":" << Line
.Line
<< ":" << Line
.Column
174 << " (" << Line
.FunctionName
<< ")\n";
178 if (!SpecialCaseList
) {
180 if (PrintBlameContextAll
&& !Summarize
)
181 printBlameContext(LineInfo
, PrintBlameContextAll
);
184 if (PrintBlameContext
&& !Summarize
)
185 printBlameContext(LineInfo
, PrintBlameContext
);
186 UnexpectedUnprotected
++;
191 unsigned BlameLine
= 0;
192 for (auto &K
: {"cfi-icall", "cfi-vcall"}) {
195 SpecialCaseList
->inSectionBlame(K
, "src", LineInfo
.FileName
);
198 SpecialCaseList
->inSectionBlame(K
, "fun", LineInfo
.FunctionName
);
202 BlameCounter
[BlameLine
]++;
204 UnexpectedProtected
++;
206 ExpectedUnprotected
++;
211 UnexpectedUnprotected
++;
215 printInstructionStatus(BlameLine
, CFIProtected
, LineInfo
);
218 uint64_t IndirectCFInstructions
= ExpectedProtected
+ UnexpectedProtected
+
219 ExpectedUnprotected
+ UnexpectedUnprotected
;
221 if (IndirectCFInstructions
== 0) {
222 outs() << "No indirect CF instructions found.\n";
226 outs() << formatv("\nTotal Indirect CF Instructions: {0}\n"
227 "Expected Protected: {1} ({2:P})\n"
228 "Unexpected Protected: {3} ({4:P})\n"
229 "Expected Unprotected: {5} ({6:P})\n"
230 "Unexpected Unprotected (BAD): {7} ({8:P})\n",
231 IndirectCFInstructions
, ExpectedProtected
,
232 ((double)ExpectedProtected
) / IndirectCFInstructions
,
234 ((double)UnexpectedProtected
) / IndirectCFInstructions
,
236 ((double)ExpectedUnprotected
) / IndirectCFInstructions
,
237 UnexpectedUnprotected
,
238 ((double)UnexpectedUnprotected
) / IndirectCFInstructions
);
240 if (!SpecialCaseList
)
243 outs() << "\nIgnorelist Results:\n";
244 for (const auto &KV
: BlameCounter
) {
245 outs() << " " << IgnorelistFilename
<< ":" << KV
.first
<< " affects "
246 << KV
.second
<< " indirect CF instructions.\n";
250 int main(int argc
, char **argv
) {
251 cl::HideUnrelatedOptions({&CFIVerifyCategory
, &getColorCategory()});
252 cl::ParseCommandLineOptions(
254 "Identifies whether Control Flow Integrity protects all indirect control "
255 "flow instructions in the provided object file, DSO or binary.\nNote: "
256 "Anything statically linked into the provided file *must* be compiled "
257 "with '-g'. This can be relaxed through the '--ignore-dwarf' flag.");
259 InitializeAllTargetInfos();
260 InitializeAllTargetMCs();
261 InitializeAllAsmParsers();
262 InitializeAllDisassemblers();
264 if (PrintBlameContextAll
&& !PrintBlameContext
)
265 PrintBlameContext
.setValue(PrintBlameContextAll
);
267 std::unique_ptr
<SpecialCaseList
> SpecialCaseList
;
268 if (IgnorelistFilename
!= "-") {
270 SpecialCaseList
= SpecialCaseList::create({IgnorelistFilename
},
271 *vfs::getRealFileSystem(), Error
);
272 if (!SpecialCaseList
) {
273 errs() << "Failed to get ignorelist: " << Error
<< "\n";
278 FileAnalysis Analysis
= ExitOnErr(FileAnalysis::Create(InputFilename
));
279 printIndirectCFInstructions(Analysis
, SpecialCaseList
.get());