blob: c54e5383248247302173ff8f2e664acf44954280 [file] [log] [blame]
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +00001//===-- llvm-cfi-verify.cpp - CFI Verification tool for LLVM --------------===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +00006//
7//===----------------------------------------------------------------------===//
8//
9// This tool verifies Control Flow Integrity (CFI) instrumentation by static
10// binary anaylsis. 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
Vlad Tsyrklevich89c3c8c2017-10-11 20:35:01 +000019#include "lib/FileAnalysis.h"
Mitch Phillips3b9ea322017-11-10 21:00:22 +000020#include "lib/GraphBuilder.h"
Vlad Tsyrklevichb5488a22017-10-10 20:59:08 +000021
Vlad Tsyrklevich89c3c8c2017-10-11 20:35:01 +000022#include "llvm/BinaryFormat/ELF.h"
23#include "llvm/Support/CommandLine.h"
24#include "llvm/Support/Error.h"
Mitch Phillips7db6f7a2017-10-31 23:20:05 +000025#include "llvm/Support/FormatVariadic.h"
Mitch Phillipsc15bdf52017-11-03 20:54:26 +000026#include "llvm/Support/SpecialCaseList.h"
Vlad Tsyrklevich89c3c8c2017-10-11 20:35:01 +000027
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +000028#include <cstdlib>
29
30using namespace llvm;
31using namespace llvm::object;
Vlad Tsyrklevich89c3c8c2017-10-11 20:35:01 +000032using namespace llvm::cfi_verify;
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +000033
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +000034cl::opt<std::string> InputFilename(cl::Positional, cl::desc("<input file>"),
35 cl::Required);
Mitch Phillipsc15bdf52017-11-03 20:54:26 +000036cl::opt<std::string> BlacklistFilename(cl::Positional,
37 cl::desc("[blacklist file]"),
38 cl::init("-"));
Mitch Phillips02993892017-11-14 22:43:13 +000039cl::opt<bool> PrintGraphs(
40 "print-graphs",
41 cl::desc("Print graphs around indirect CF instructions in DOT format."),
42 cl::init(false));
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +000043cl::opt<unsigned> PrintBlameContext(
44 "blame-context",
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."),
48 cl::init(0));
49cl::opt<unsigned> PrintBlameContextAll(
50 "blame-context-all",
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."),
56 cl::init(0));
57cl::opt<bool> Summarize("summarize", cl::desc("Print the summary only."),
58 cl::init(false));
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +000059
Vlad Tsyrklevich89c3c8c2017-10-11 20:35:01 +000060ExitOnError ExitOnErr;
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +000061
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +000062void printBlameContext(const DILineInfo &LineInfo, unsigned Context) {
63 auto FileOrErr = MemoryBuffer::getFile(LineInfo.FileName);
64 if (!FileOrErr) {
65 errs() << "Could not open file: " << LineInfo.FileName << "\n";
66 return;
67 }
68
69 std::unique_ptr<MemoryBuffer> File = std::move(FileOrErr.get());
70 SmallVector<StringRef, 100> Lines;
71 File->getBuffer().split(Lines, '\n');
72
Vlad Tsyrklevich3e0e7cd2018-02-02 00:07:14 +000073 for (unsigned i = std::max<size_t>(1, LineInfo.Line - Context);
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +000074 i <
Vlad Tsyrklevich3e0e7cd2018-02-02 00:07:14 +000075 std::min<size_t>(Lines.size() + 1, LineInfo.Line + Context + 1);
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +000076 ++i) {
77 if (i == LineInfo.Line)
78 outs() << ">";
79 else
80 outs() << " ";
81
82 outs() << i << ": " << Lines[i - 1] << "\n";
83 }
84}
85
86void 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());
93 outs() << " \n";
94
95 if (PrintGraphs)
96 Graph.printToDOT(Analysis, outs());
97}
98
99void printInstructionStatus(unsigned BlameLine, bool CFIProtected,
100 const DILineInfo &LineInfo) {
101 if (BlameLine) {
102 outs() << "Blacklist Match: " << BlacklistFilename << ":" << BlameLine
103 << "\n";
104 if (CFIProtected)
105 outs() << "====> Unexpected Protected\n";
106 else
107 outs() << "====> Expected Unprotected\n";
108
109 if (PrintBlameContextAll)
110 printBlameContext(LineInfo, PrintBlameContextAll);
111 } else {
112 if (CFIProtected) {
113 outs() << "====> Expected Protected\n";
114 if (PrintBlameContextAll)
115 printBlameContext(LineInfo, PrintBlameContextAll);
116 } else {
117 outs() << "====> Unexpected Unprotected (BAD)\n";
118 if (PrintBlameContext)
119 printBlameContext(LineInfo, PrintBlameContext);
120 }
121 }
122}
123
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000124void 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;
130
Mitch Phillipsd64af522017-11-09 00:18:31 +0000131 std::map<unsigned, uint64_t> BlameCounter;
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000132
Alexey Lapshin77fc1f62019-02-27 13:17:36 +0000133 for (object::SectionedAddress Address : Analysis.getIndirectInstructions()) {
134 const auto &InstrMeta = Analysis.getInstructionOrDie(Address.Address);
Mitch Phillips3b9ea322017-11-10 21:00:22 +0000135 GraphResult Graph = GraphBuilder::buildFlowGraph(Analysis, Address);
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000136
Mitch Phillips3b9ea322017-11-10 21:00:22 +0000137 CFIProtectionStatus ProtectionStatus =
138 Analysis.validateCFIProtection(Graph);
139 bool CFIProtected = (ProtectionStatus == CFIProtectionStatus::PROTECTED);
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000140
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000141 if (!Summarize) {
142 outs() << "-----------------------------------------------------\n";
143 printInstructionInformation(Analysis, InstrMeta, Graph, ProtectionStatus);
144 }
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000145
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000146 if (IgnoreDWARFFlag) {
147 if (CFIProtected)
148 ExpectedProtected++;
149 else
150 UnexpectedUnprotected++;
151 continue;
152 }
153
Mitch Phillips3b9ea322017-11-10 21:00:22 +0000154 auto InliningInfo = Analysis.symbolizeInlinedCode(Address);
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000155 if (!InliningInfo || InliningInfo->getNumberOfFrames() == 0) {
Alexey Lapshin77fc1f62019-02-27 13:17:36 +0000156 errs() << "Failed to symbolise " << format_hex(Address.Address, 2)
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000157 << " with line tables from " << InputFilename << "\n";
158 exit(EXIT_FAILURE);
159 }
160
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000161 const auto &LineInfo = InliningInfo->getFrame(0);
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000162
163 // Print the inlining symbolisation of this instruction.
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000164 if (!Summarize) {
165 for (uint32_t i = 0; i < InliningInfo->getNumberOfFrames(); ++i) {
166 const auto &Line = InliningInfo->getFrame(i);
Alexey Lapshin77fc1f62019-02-27 13:17:36 +0000167 outs() << " " << format_hex(Address.Address, 2) << " = "
168 << Line.FileName << ":" << Line.Line << ":" << Line.Column
169 << " (" << Line.FunctionName << ")\n";
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000170 }
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000171 }
172
173 if (!SpecialCaseList) {
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000174 if (CFIProtected) {
175 if (PrintBlameContextAll && !Summarize)
176 printBlameContext(LineInfo, PrintBlameContextAll);
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000177 ExpectedProtected++;
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000178 } else {
179 if (PrintBlameContext && !Summarize)
180 printBlameContext(LineInfo, PrintBlameContext);
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000181 UnexpectedUnprotected++;
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000182 }
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000183 continue;
184 }
185
Mitch Phillipsd64af522017-11-09 00:18:31 +0000186 unsigned BlameLine = 0;
187 for (auto &K : {"cfi-icall", "cfi-vcall"}) {
188 if (!BlameLine)
189 BlameLine =
190 SpecialCaseList->inSectionBlame(K, "src", LineInfo.FileName);
191 if (!BlameLine)
192 BlameLine =
193 SpecialCaseList->inSectionBlame(K, "fun", LineInfo.FunctionName);
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000194 }
195
Mitch Phillipsd64af522017-11-09 00:18:31 +0000196 if (BlameLine) {
Mitch Phillipsd64af522017-11-09 00:18:31 +0000197 BlameCounter[BlameLine]++;
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000198 if (CFIProtected)
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000199 UnexpectedProtected++;
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000200 else
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000201 ExpectedUnprotected++;
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000202 } else {
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000203 if (CFIProtected)
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000204 ExpectedProtected++;
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000205 else
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000206 UnexpectedUnprotected++;
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000207 }
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000208
209 if (!Summarize)
210 printInstructionStatus(BlameLine, CFIProtected, LineInfo);
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +0000211 }
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000212
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000213 uint64_t IndirectCFInstructions = ExpectedProtected + UnexpectedProtected +
214 ExpectedUnprotected + UnexpectedUnprotected;
215
Mitch Phillips6fb35252017-11-06 19:14:09 +0000216 if (IndirectCFInstructions == 0) {
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000217 outs() << "No indirect CF instructions found.\n";
Mitch Phillips6fb35252017-11-06 19:14:09 +0000218 return;
219 }
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000220
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000221 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,
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000227 ((double)ExpectedProtected) / IndirectCFInstructions,
228 UnexpectedProtected,
229 ((double)UnexpectedProtected) / IndirectCFInstructions,
230 ExpectedUnprotected,
231 ((double)ExpectedUnprotected) / IndirectCFInstructions,
232 UnexpectedUnprotected,
233 ((double)UnexpectedUnprotected) / IndirectCFInstructions);
Mitch Phillipsd64af522017-11-09 00:18:31 +0000234
235 if (!SpecialCaseList)
236 return;
237
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000238 outs() << "\nBlacklist Results:\n";
Mitch Phillipsd64af522017-11-09 00:18:31 +0000239 for (const auto &KV : BlameCounter) {
240 outs() << " " << BlacklistFilename << ":" << KV.first << " affects "
241 << KV.second << " indirect CF instructions.\n";
242 }
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +0000243}
244
245int main(int argc, char **argv) {
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000246 cl::ParseCommandLineOptions(
247 argc, argv,
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.");
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +0000252
253 InitializeAllTargetInfos();
254 InitializeAllTargetMCs();
255 InitializeAllAsmParsers();
256 InitializeAllDisassemblers();
257
Vlad Tsyrklevichb2c3ea762018-02-01 23:45:18 +0000258 if (PrintBlameContextAll && !PrintBlameContext)
259 PrintBlameContext.setValue(PrintBlameContextAll);
260
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000261 std::unique_ptr<SpecialCaseList> SpecialCaseList;
262 if (BlacklistFilename != "-") {
263 std::string Error;
264 SpecialCaseList = SpecialCaseList::create({BlacklistFilename}, Error);
265 if (!SpecialCaseList) {
266 errs() << "Failed to get blacklist: " << Error << "\n";
267 exit(EXIT_FAILURE);
268 }
269 }
270
Mitch Phillips7db6f7a2017-10-31 23:20:05 +0000271 FileAnalysis Analysis = ExitOnErr(FileAnalysis::Create(InputFilename));
Mitch Phillipsc15bdf52017-11-03 20:54:26 +0000272 printIndirectCFInstructions(Analysis, SpecialCaseList.get());
Vlad Tsyrklevich31b45312017-09-20 20:38:14 +0000273
274 return EXIT_SUCCESS;
275}