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"
31 using namespace llvm::object
;
32 using namespace llvm::cfi_verify
;
34 cl::opt
<std::string
> InputFilename(cl::Positional
, cl::desc("<input file>"),
36 cl::opt
<std::string
> BlacklistFilename(cl::Positional
,
37 cl::desc("[blacklist file]"),
39 cl::opt
<bool> PrintGraphs(
41 cl::desc("Print graphs around indirect CF instructions in DOT format."),
43 cl::opt
<unsigned> PrintBlameContext(
45 cl::desc("Print the blame context (if possible) for BAD instructions. This "
46 "specifies the number of lines of context to include, where zero "
47 "disables this feature."),
49 cl::opt
<unsigned> PrintBlameContextAll(
51 cl::desc("Prints the blame context (if possible) for ALL instructions. "
52 "This specifies the number of lines of context for non-BAD "
53 "instructions (see --blame-context). If --blame-context is "
54 "unspecified, it prints this number of contextual lines for BAD "
55 "instructions as well."),
57 cl::opt
<bool> Summarize("summarize", cl::desc("Print the summary only."),
60 ExitOnError ExitOnErr
;
62 void printBlameContext(const DILineInfo
&LineInfo
, unsigned Context
) {
63 auto FileOrErr
= MemoryBuffer::getFile(LineInfo
.FileName
);
65 errs() << "Could not open file: " << LineInfo
.FileName
<< "\n";
69 std::unique_ptr
<MemoryBuffer
> File
= std::move(FileOrErr
.get());
70 SmallVector
<StringRef
, 100> Lines
;
71 File
->getBuffer().split(Lines
, '\n');
73 for (unsigned i
= std::max
<size_t>(1, LineInfo
.Line
- Context
);
75 std::min
<size_t>(Lines
.size() + 1, LineInfo
.Line
+ Context
+ 1);
77 if (i
== LineInfo
.Line
)
82 outs() << i
<< ": " << Lines
[i
- 1] << "\n";
86 void printInstructionInformation(const FileAnalysis
&Analysis
,
87 const Instr
&InstrMeta
,
88 const GraphResult
&Graph
,
89 CFIProtectionStatus ProtectionStatus
) {
90 outs() << "Instruction: " << format_hex(InstrMeta
.VMAddress
, 2) << " ("
91 << stringCFIProtectionStatus(ProtectionStatus
) << "): ";
92 Analysis
.printInstruction(InstrMeta
, outs());
96 Graph
.printToDOT(Analysis
, outs());
99 void printInstructionStatus(unsigned BlameLine
, bool CFIProtected
,
100 const DILineInfo
&LineInfo
) {
102 outs() << "Blacklist Match: " << BlacklistFilename
<< ":" << BlameLine
105 outs() << "====> Unexpected Protected\n";
107 outs() << "====> Expected Unprotected\n";
109 if (PrintBlameContextAll
)
110 printBlameContext(LineInfo
, PrintBlameContextAll
);
113 outs() << "====> Expected Protected\n";
114 if (PrintBlameContextAll
)
115 printBlameContext(LineInfo
, PrintBlameContextAll
);
117 outs() << "====> Unexpected Unprotected (BAD)\n";
118 if (PrintBlameContext
)
119 printBlameContext(LineInfo
, PrintBlameContext
);
124 void printIndirectCFInstructions(FileAnalysis
&Analysis
,
125 const SpecialCaseList
*SpecialCaseList
) {
126 uint64_t ExpectedProtected
= 0;
127 uint64_t UnexpectedProtected
= 0;
128 uint64_t ExpectedUnprotected
= 0;
129 uint64_t UnexpectedUnprotected
= 0;
131 std::map
<unsigned, uint64_t> BlameCounter
;
133 for (object::SectionedAddress Address
: Analysis
.getIndirectInstructions()) {
134 const auto &InstrMeta
= Analysis
.getInstructionOrDie(Address
.Address
);
135 GraphResult Graph
= GraphBuilder::buildFlowGraph(Analysis
, Address
);
137 CFIProtectionStatus ProtectionStatus
=
138 Analysis
.validateCFIProtection(Graph
);
139 bool CFIProtected
= (ProtectionStatus
== CFIProtectionStatus::PROTECTED
);
142 outs() << "-----------------------------------------------------\n";
143 printInstructionInformation(Analysis
, InstrMeta
, Graph
, ProtectionStatus
);
146 if (IgnoreDWARFFlag
) {
150 UnexpectedUnprotected
++;
154 auto InliningInfo
= Analysis
.symbolizeInlinedCode(Address
);
155 if (!InliningInfo
|| InliningInfo
->getNumberOfFrames() == 0) {
156 errs() << "Failed to symbolise " << format_hex(Address
.Address
, 2)
157 << " with line tables from " << InputFilename
<< "\n";
161 const auto &LineInfo
= InliningInfo
->getFrame(0);
163 // Print the inlining symbolisation of this instruction.
165 for (uint32_t i
= 0; i
< InliningInfo
->getNumberOfFrames(); ++i
) {
166 const auto &Line
= InliningInfo
->getFrame(i
);
167 outs() << " " << format_hex(Address
.Address
, 2) << " = "
168 << Line
.FileName
<< ":" << Line
.Line
<< ":" << Line
.Column
169 << " (" << Line
.FunctionName
<< ")\n";
173 if (!SpecialCaseList
) {
175 if (PrintBlameContextAll
&& !Summarize
)
176 printBlameContext(LineInfo
, PrintBlameContextAll
);
179 if (PrintBlameContext
&& !Summarize
)
180 printBlameContext(LineInfo
, PrintBlameContext
);
181 UnexpectedUnprotected
++;
186 unsigned BlameLine
= 0;
187 for (auto &K
: {"cfi-icall", "cfi-vcall"}) {
190 SpecialCaseList
->inSectionBlame(K
, "src", LineInfo
.FileName
);
193 SpecialCaseList
->inSectionBlame(K
, "fun", LineInfo
.FunctionName
);
197 BlameCounter
[BlameLine
]++;
199 UnexpectedProtected
++;
201 ExpectedUnprotected
++;
206 UnexpectedUnprotected
++;
210 printInstructionStatus(BlameLine
, CFIProtected
, LineInfo
);
213 uint64_t IndirectCFInstructions
= ExpectedProtected
+ UnexpectedProtected
+
214 ExpectedUnprotected
+ UnexpectedUnprotected
;
216 if (IndirectCFInstructions
== 0) {
217 outs() << "No indirect CF instructions found.\n";
221 outs() << formatv("\nTotal Indirect CF Instructions: {0}\n"
222 "Expected Protected: {1} ({2:P})\n"
223 "Unexpected Protected: {3} ({4:P})\n"
224 "Expected Unprotected: {5} ({6:P})\n"
225 "Unexpected Unprotected (BAD): {7} ({8:P})\n",
226 IndirectCFInstructions
, ExpectedProtected
,
227 ((double)ExpectedProtected
) / IndirectCFInstructions
,
229 ((double)UnexpectedProtected
) / IndirectCFInstructions
,
231 ((double)ExpectedUnprotected
) / IndirectCFInstructions
,
232 UnexpectedUnprotected
,
233 ((double)UnexpectedUnprotected
) / IndirectCFInstructions
);
235 if (!SpecialCaseList
)
238 outs() << "\nBlacklist Results:\n";
239 for (const auto &KV
: BlameCounter
) {
240 outs() << " " << BlacklistFilename
<< ":" << KV
.first
<< " affects "
241 << KV
.second
<< " indirect CF instructions.\n";
245 int main(int argc
, char **argv
) {
246 cl::ParseCommandLineOptions(
248 "Identifies whether Control Flow Integrity protects all indirect control "
249 "flow instructions in the provided object file, DSO or binary.\nNote: "
250 "Anything statically linked into the provided file *must* be compiled "
251 "with '-g'. This can be relaxed through the '--ignore-dwarf' flag.");
253 InitializeAllTargetInfos();
254 InitializeAllTargetMCs();
255 InitializeAllAsmParsers();
256 InitializeAllDisassemblers();
258 if (PrintBlameContextAll
&& !PrintBlameContext
)
259 PrintBlameContext
.setValue(PrintBlameContextAll
);
261 std::unique_ptr
<SpecialCaseList
> SpecialCaseList
;
262 if (BlacklistFilename
!= "-") {
264 SpecialCaseList
= SpecialCaseList::create({BlacklistFilename
}, Error
);
265 if (!SpecialCaseList
) {
266 errs() << "Failed to get blacklist: " << Error
<< "\n";
271 FileAnalysis Analysis
= ExitOnErr(FileAnalysis::Create(InputFilename
));
272 printIndirectCFInstructions(Analysis
, SpecialCaseList
.get());