[sanitizer] Improve FreeBSD ASLR detection
[llvm-project.git] / llvm / tools / llvm-cfi-verify / llvm-cfi-verify.cpp
blob8c43ea839026aae947a840fbe06fc161ab996890
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 anaylsis. See the design document in /docs/CFIVerify.rst for more
11 // information.
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"
29 #include <cstdlib>
31 using namespace llvm;
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]"),
41 cl::init("-"),
42 cl::cat(CFIVerifyCategory));
43 cl::opt<bool> PrintGraphs(
44 "print-graphs",
45 cl::desc("Print graphs around indirect CF instructions in DOT format."),
46 cl::init(false), cl::cat(CFIVerifyCategory));
47 cl::opt<unsigned> PrintBlameContext(
48 "blame-context",
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(
54 "blame-context-all",
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);
68 if (!FileOrErr) {
69 errs() << "Could not open file: " << LineInfo.FileName << "\n";
70 return;
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);
78 i <
79 std::min<size_t>(Lines.size() + 1, LineInfo.Line + Context + 1);
80 ++i) {
81 if (i == LineInfo.Line)
82 outs() << ">";
83 else
84 outs() << " ";
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());
97 outs() << " \n";
99 if (PrintGraphs)
100 Graph.printToDOT(Analysis, outs());
103 static void printInstructionStatus(unsigned BlameLine, bool CFIProtected,
104 const DILineInfo &LineInfo) {
105 if (BlameLine) {
106 outs() << "Ignorelist Match: " << IgnorelistFilename << ":" << BlameLine
107 << "\n";
108 if (CFIProtected)
109 outs() << "====> Unexpected Protected\n";
110 else
111 outs() << "====> Expected Unprotected\n";
113 if (PrintBlameContextAll)
114 printBlameContext(LineInfo, PrintBlameContextAll);
115 } else {
116 if (CFIProtected) {
117 outs() << "====> Expected Protected\n";
118 if (PrintBlameContextAll)
119 printBlameContext(LineInfo, PrintBlameContextAll);
120 } else {
121 outs() << "====> Unexpected Unprotected (BAD)\n";
122 if (PrintBlameContext)
123 printBlameContext(LineInfo, PrintBlameContext);
128 static void
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);
146 if (!Summarize) {
147 outs() << "-----------------------------------------------------\n";
148 printInstructionInformation(Analysis, InstrMeta, Graph, ProtectionStatus);
151 if (IgnoreDWARFFlag) {
152 if (CFIProtected)
153 ExpectedProtected++;
154 else
155 UnexpectedUnprotected++;
156 continue;
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";
163 exit(EXIT_FAILURE);
166 const auto &LineInfo = InliningInfo->getFrame(0);
168 // Print the inlining symbolisation of this instruction.
169 if (!Summarize) {
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) {
179 if (CFIProtected) {
180 if (PrintBlameContextAll && !Summarize)
181 printBlameContext(LineInfo, PrintBlameContextAll);
182 ExpectedProtected++;
183 } else {
184 if (PrintBlameContext && !Summarize)
185 printBlameContext(LineInfo, PrintBlameContext);
186 UnexpectedUnprotected++;
188 continue;
191 unsigned BlameLine = 0;
192 for (auto &K : {"cfi-icall", "cfi-vcall"}) {
193 if (!BlameLine)
194 BlameLine =
195 SpecialCaseList->inSectionBlame(K, "src", LineInfo.FileName);
196 if (!BlameLine)
197 BlameLine =
198 SpecialCaseList->inSectionBlame(K, "fun", LineInfo.FunctionName);
201 if (BlameLine) {
202 BlameCounter[BlameLine]++;
203 if (CFIProtected)
204 UnexpectedProtected++;
205 else
206 ExpectedUnprotected++;
207 } else {
208 if (CFIProtected)
209 ExpectedProtected++;
210 else
211 UnexpectedUnprotected++;
214 if (!Summarize)
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";
223 return;
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,
233 UnexpectedProtected,
234 ((double)UnexpectedProtected) / IndirectCFInstructions,
235 ExpectedUnprotected,
236 ((double)ExpectedUnprotected) / IndirectCFInstructions,
237 UnexpectedUnprotected,
238 ((double)UnexpectedUnprotected) / IndirectCFInstructions);
240 if (!SpecialCaseList)
241 return;
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(
253 argc, argv,
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 != "-") {
269 std::string Error;
270 SpecialCaseList = SpecialCaseList::create({IgnorelistFilename},
271 *vfs::getRealFileSystem(), Error);
272 if (!SpecialCaseList) {
273 errs() << "Failed to get ignorelist: " << Error << "\n";
274 exit(EXIT_FAILURE);
278 FileAnalysis Analysis = ExitOnErr(FileAnalysis::Create(InputFilename));
279 printIndirectCFInstructions(Analysis, SpecialCaseList.get());
281 return EXIT_SUCCESS;