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 analysis. 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/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"
33 using namespace llvm::object
;
34 using namespace llvm::cfi_verify
;
36 static cl::OptionCategory
CFIVerifyCategory("CFI Verify Options");
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]"),
43 cl::cat(CFIVerifyCategory
));
44 cl::opt
<bool> PrintGraphs(
46 cl::desc("Print graphs around indirect CF instructions in DOT format."),
47 cl::init(false), cl::cat(CFIVerifyCategory
));
48 cl::opt
<unsigned> PrintBlameContext(
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(
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
));
65 ExitOnError ExitOnErr
;
67 static void printBlameContext(const DILineInfo
&LineInfo
, unsigned Context
) {
68 auto FileOrErr
= MemoryBuffer::getFile(LineInfo
.FileName
);
70 errs() << "Could not open file: " << LineInfo
.FileName
<< "\n";
74 std::unique_ptr
<MemoryBuffer
> File
= std::move(FileOrErr
.get());
75 SmallVector
<StringRef
, 100> Lines
;
76 File
->getBuffer().split(Lines
, '\n');
78 for (unsigned i
= std::max
<size_t>(1, LineInfo
.Line
- Context
);
80 std::min
<size_t>(Lines
.size() + 1, LineInfo
.Line
+ Context
+ 1);
82 if (i
== LineInfo
.Line
)
87 outs() << i
<< ": " << Lines
[i
- 1] << "\n";
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());
101 Graph
.printToDOT(Analysis
, outs());
104 static void printInstructionStatus(unsigned BlameLine
, bool CFIProtected
,
105 const DILineInfo
&LineInfo
) {
107 outs() << "Ignorelist Match: " << IgnorelistFilename
<< ":" << BlameLine
110 outs() << "====> Unexpected Protected\n";
112 outs() << "====> Expected Unprotected\n";
114 if (PrintBlameContextAll
)
115 printBlameContext(LineInfo
, PrintBlameContextAll
);
118 outs() << "====> Expected Protected\n";
119 if (PrintBlameContextAll
)
120 printBlameContext(LineInfo
, PrintBlameContextAll
);
122 outs() << "====> Unexpected Unprotected (BAD)\n";
123 if (PrintBlameContext
)
124 printBlameContext(LineInfo
, PrintBlameContext
);
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;
137 std::map
<unsigned, uint64_t> BlameCounter
;
139 for (object::SectionedAddress Address
: Analysis
.getIndirectInstructions()) {
140 const auto &InstrMeta
= Analysis
.getInstructionOrDie(Address
.Address
);
141 GraphResult Graph
= GraphBuilder::buildFlowGraph(Analysis
, Address
);
143 CFIProtectionStatus ProtectionStatus
=
144 Analysis
.validateCFIProtection(Graph
);
145 bool CFIProtected
= (ProtectionStatus
== CFIProtectionStatus::PROTECTED
);
148 outs() << "-----------------------------------------------------\n";
149 printInstructionInformation(Analysis
, InstrMeta
, Graph
, ProtectionStatus
);
152 if (IgnoreDWARFFlag
) {
156 UnexpectedUnprotected
++;
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";
167 const auto &LineInfo
= InliningInfo
->getFrame(0);
169 // Print the inlining symbolisation of this instruction.
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";
179 if (!SpecialCaseList
) {
181 if (PrintBlameContextAll
&& !Summarize
)
182 printBlameContext(LineInfo
, PrintBlameContextAll
);
185 if (PrintBlameContext
&& !Summarize
)
186 printBlameContext(LineInfo
, PrintBlameContext
);
187 UnexpectedUnprotected
++;
192 unsigned BlameLine
= 0;
193 for (auto &K
: {"cfi-icall", "cfi-vcall"}) {
196 SpecialCaseList
->inSectionBlame(K
, "src", LineInfo
.FileName
);
199 SpecialCaseList
->inSectionBlame(K
, "fun", LineInfo
.FunctionName
);
203 BlameCounter
[BlameLine
]++;
205 UnexpectedProtected
++;
207 ExpectedUnprotected
++;
212 UnexpectedUnprotected
++;
216 printInstructionStatus(BlameLine
, CFIProtected
, LineInfo
);
219 uint64_t IndirectCFInstructions
= ExpectedProtected
+ UnexpectedProtected
+
220 ExpectedUnprotected
+ UnexpectedUnprotected
;
222 if (IndirectCFInstructions
== 0) {
223 outs() << "No indirect CF instructions found.\n";
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
,
235 ((double)UnexpectedProtected
) / IndirectCFInstructions
,
237 ((double)ExpectedUnprotected
) / IndirectCFInstructions
,
238 UnexpectedUnprotected
,
239 ((double)UnexpectedUnprotected
) / IndirectCFInstructions
);
241 if (!SpecialCaseList
)
244 outs() << "\nIgnorelist Results:\n";
245 for (const auto &KV
: BlameCounter
) {
246 outs() << " " << IgnorelistFilename
<< ":" << KV
.first
<< " affects "
247 << KV
.second
<< " indirect CF instructions.\n";
251 int main(int argc
, char **argv
) {
252 cl::HideUnrelatedOptions({&CFIVerifyCategory
, &getColorCategory()});
253 cl::ParseCommandLineOptions(
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.");
260 InitializeAllTargetInfos();
261 InitializeAllTargetMCs();
262 InitializeAllAsmParsers();
263 InitializeAllDisassemblers();
265 if (PrintBlameContextAll
&& !PrintBlameContext
)
266 PrintBlameContext
.setValue(PrintBlameContextAll
);
268 std::unique_ptr
<SpecialCaseList
> SpecialCaseList
;
269 if (IgnorelistFilename
!= "-") {
271 SpecialCaseList
= SpecialCaseList::create({IgnorelistFilename
},
272 *vfs::getRealFileSystem(), Error
);
273 if (!SpecialCaseList
) {
274 errs() << "Failed to get ignorelist: " << Error
<< "\n";
279 FileAnalysis Analysis
= ExitOnErr(FileAnalysis::Create(InputFilename
));
280 printIndirectCFInstructions(Analysis
, SpecialCaseList
.get());