1 | //==-- X86LoadValueInjectionLoadHardening.cpp - LVI load hardening for x86 --=// |
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 | /// Description: This pass finds Load Value Injection (LVI) gadgets consisting |
10 | /// of a load from memory (i.e., SOURCE), and any operation that may transmit |
11 | /// the value loaded from memory over a covert channel, or use the value loaded |
12 | /// from memory to determine a branch/call target (i.e., SINK). After finding |
13 | /// all such gadgets in a given function, the pass minimally inserts LFENCE |
14 | /// instructions in such a manner that the following property is satisfied: for |
15 | /// all SOURCE+SINK pairs, all paths in the CFG from SOURCE to SINK contain at |
16 | /// least one LFENCE instruction. The algorithm that implements this minimal |
17 | /// insertion is influenced by an academic paper that minimally inserts memory |
18 | /// fences for high-performance concurrent programs: |
19 | /// http://www.cs.ucr.edu/~lesani/companion/oopsla15/OOPSLA15.pdf |
20 | /// The algorithm implemented in this pass is as follows: |
21 | /// 1. Build a condensed CFG (i.e., a GadgetGraph) consisting only of the |
22 | /// following components: |
23 | /// - SOURCE instructions (also includes function arguments) |
24 | /// - SINK instructions |
25 | /// - Basic block entry points |
26 | /// - Basic block terminators |
27 | /// - LFENCE instructions |
28 | /// 2. Analyze the GadgetGraph to determine which SOURCE+SINK pairs (i.e., |
29 | /// gadgets) are already mitigated by existing LFENCEs. If all gadgets have been |
30 | /// mitigated, go to step 6. |
31 | /// 3. Use a heuristic or plugin to approximate minimal LFENCE insertion. |
32 | /// 4. Insert one LFENCE along each CFG edge that was cut in step 3. |
33 | /// 5. Go to step 2. |
34 | /// 6. If any LFENCEs were inserted, return `true` from runOnMachineFunction() |
35 | /// to tell LLVM that the function was modified. |
36 | /// |
37 | //===----------------------------------------------------------------------===// |
38 | |
39 | #include "ImmutableGraph.h" |
40 | #include "X86.h" |
41 | #include "X86Subtarget.h" |
42 | #include "X86TargetMachine.h" |
43 | #include "llvm/ADT/DenseMap.h" |
44 | #include "llvm/ADT/STLExtras.h" |
45 | #include "llvm/ADT/SmallSet.h" |
46 | #include "llvm/ADT/Statistic.h" |
47 | #include "llvm/ADT/StringRef.h" |
48 | #include "llvm/CodeGen/MachineBasicBlock.h" |
49 | #include "llvm/CodeGen/MachineDominanceFrontier.h" |
50 | #include "llvm/CodeGen/MachineDominators.h" |
51 | #include "llvm/CodeGen/MachineFunction.h" |
52 | #include "llvm/CodeGen/MachineFunctionPass.h" |
53 | #include "llvm/CodeGen/MachineInstr.h" |
54 | #include "llvm/CodeGen/MachineInstrBuilder.h" |
55 | #include "llvm/CodeGen/MachineLoopInfo.h" |
56 | #include "llvm/CodeGen/RDFGraph.h" |
57 | #include "llvm/CodeGen/RDFLiveness.h" |
58 | #include "llvm/InitializePasses.h" |
59 | #include "llvm/Support/CommandLine.h" |
60 | #include "llvm/Support/DOTGraphTraits.h" |
61 | #include "llvm/Support/Debug.h" |
62 | #include "llvm/Support/DynamicLibrary.h" |
63 | #include "llvm/Support/GraphWriter.h" |
64 | #include "llvm/Support/raw_ostream.h" |
65 | |
66 | using namespace llvm; |
67 | |
68 | #define PASS_KEY "x86-lvi-load" |
69 | #define DEBUG_TYPE PASS_KEY |
70 | |
71 | STATISTIC(NumFences, "Number of LFENCEs inserted for LVI mitigation" ); |
72 | STATISTIC(NumFunctionsConsidered, "Number of functions analyzed" ); |
73 | STATISTIC(NumFunctionsMitigated, "Number of functions for which mitigations " |
74 | "were deployed" ); |
75 | STATISTIC(NumGadgets, "Number of LVI gadgets detected during analysis" ); |
76 | |
77 | static cl::opt<std::string> OptimizePluginPath( |
78 | PASS_KEY "-opt-plugin" , |
79 | cl::desc("Specify a plugin to optimize LFENCE insertion" ), cl::Hidden); |
80 | |
81 | static cl::opt<bool> NoConditionalBranches( |
82 | PASS_KEY "-no-cbranch" , |
83 | cl::desc("Don't treat conditional branches as disclosure gadgets. This " |
84 | "may improve performance, at the cost of security." ), |
85 | cl::init(Val: false), cl::Hidden); |
86 | |
87 | static cl::opt<bool> EmitDot( |
88 | PASS_KEY "-dot" , |
89 | cl::desc( |
90 | "For each function, emit a dot graph depicting potential LVI gadgets" ), |
91 | cl::init(Val: false), cl::Hidden); |
92 | |
93 | static cl::opt<bool> EmitDotOnly( |
94 | PASS_KEY "-dot-only" , |
95 | cl::desc("For each function, emit a dot graph depicting potential LVI " |
96 | "gadgets, and do not insert any fences" ), |
97 | cl::init(Val: false), cl::Hidden); |
98 | |
99 | static cl::opt<bool> EmitDotVerify( |
100 | PASS_KEY "-dot-verify" , |
101 | cl::desc("For each function, emit a dot graph to stdout depicting " |
102 | "potential LVI gadgets, used for testing purposes only" ), |
103 | cl::init(Val: false), cl::Hidden); |
104 | |
105 | static llvm::sys::DynamicLibrary OptimizeDL; |
106 | typedef int (*OptimizeCutT)(unsigned int *Nodes, unsigned int NodesSize, |
107 | unsigned int *Edges, int *EdgeValues, |
108 | int *CutEdges /* out */, unsigned int EdgesSize); |
109 | static OptimizeCutT OptimizeCut = nullptr; |
110 | |
111 | namespace { |
112 | |
113 | struct MachineGadgetGraph : ImmutableGraph<MachineInstr *, int> { |
114 | static constexpr int GadgetEdgeSentinel = -1; |
115 | static constexpr MachineInstr *const ArgNodeSentinel = nullptr; |
116 | |
117 | using GraphT = ImmutableGraph<MachineInstr *, int>; |
118 | using Node = typename GraphT::Node; |
119 | using Edge = typename GraphT::Edge; |
120 | using size_type = typename GraphT::size_type; |
121 | MachineGadgetGraph(std::unique_ptr<Node[]> Nodes, |
122 | std::unique_ptr<Edge[]> Edges, size_type NodesSize, |
123 | size_type EdgesSize, int NumFences = 0, int NumGadgets = 0) |
124 | : GraphT(std::move(Nodes), std::move(Edges), NodesSize, EdgesSize), |
125 | NumFences(NumFences), NumGadgets(NumGadgets) {} |
126 | static inline bool isCFGEdge(const Edge &E) { |
127 | return E.getValue() != GadgetEdgeSentinel; |
128 | } |
129 | static inline bool isGadgetEdge(const Edge &E) { |
130 | return E.getValue() == GadgetEdgeSentinel; |
131 | } |
132 | int NumFences; |
133 | int NumGadgets; |
134 | }; |
135 | |
136 | class X86LoadValueInjectionLoadHardeningPass : public MachineFunctionPass { |
137 | public: |
138 | X86LoadValueInjectionLoadHardeningPass() : MachineFunctionPass(ID) {} |
139 | |
140 | StringRef getPassName() const override { |
141 | return "X86 Load Value Injection (LVI) Load Hardening" ; |
142 | } |
143 | void getAnalysisUsage(AnalysisUsage &AU) const override; |
144 | bool runOnMachineFunction(MachineFunction &MF) override; |
145 | |
146 | static char ID; |
147 | |
148 | private: |
149 | using GraphBuilder = ImmutableGraphBuilder<MachineGadgetGraph>; |
150 | using Edge = MachineGadgetGraph::Edge; |
151 | using Node = MachineGadgetGraph::Node; |
152 | using EdgeSet = MachineGadgetGraph::EdgeSet; |
153 | using NodeSet = MachineGadgetGraph::NodeSet; |
154 | |
155 | const X86Subtarget *STI = nullptr; |
156 | const TargetInstrInfo *TII = nullptr; |
157 | const TargetRegisterInfo *TRI = nullptr; |
158 | |
159 | std::unique_ptr<MachineGadgetGraph> |
160 | getGadgetGraph(MachineFunction &MF, const MachineLoopInfo &MLI, |
161 | const MachineDominatorTree &MDT, |
162 | const MachineDominanceFrontier &MDF) const; |
163 | int hardenLoadsWithPlugin(MachineFunction &MF, |
164 | std::unique_ptr<MachineGadgetGraph> Graph) const; |
165 | int hardenLoadsWithHeuristic(MachineFunction &MF, |
166 | std::unique_ptr<MachineGadgetGraph> Graph) const; |
167 | int elimMitigatedEdgesAndNodes(MachineGadgetGraph &G, |
168 | EdgeSet &ElimEdges /* in, out */, |
169 | NodeSet &ElimNodes /* in, out */) const; |
170 | std::unique_ptr<MachineGadgetGraph> |
171 | trimMitigatedEdges(std::unique_ptr<MachineGadgetGraph> Graph) const; |
172 | int insertFences(MachineFunction &MF, MachineGadgetGraph &G, |
173 | EdgeSet &CutEdges /* in, out */) const; |
174 | bool instrUsesRegToAccessMemory(const MachineInstr &I, Register Reg) const; |
175 | bool instrUsesRegToBranch(const MachineInstr &I, Register Reg) const; |
176 | inline bool isFence(const MachineInstr *MI) const { |
177 | return MI && (MI->getOpcode() == X86::LFENCE || |
178 | (STI->useLVIControlFlowIntegrity() && MI->isCall())); |
179 | } |
180 | }; |
181 | |
182 | } // end anonymous namespace |
183 | |
184 | namespace llvm { |
185 | |
186 | template <> |
187 | struct GraphTraits<MachineGadgetGraph *> |
188 | : GraphTraits<ImmutableGraph<MachineInstr *, int> *> {}; |
189 | |
190 | template <> |
191 | struct DOTGraphTraits<MachineGadgetGraph *> : DefaultDOTGraphTraits { |
192 | using GraphType = MachineGadgetGraph; |
193 | using Traits = llvm::GraphTraits<GraphType *>; |
194 | using NodeRef = typename Traits::NodeRef; |
195 | using EdgeRef = typename Traits::EdgeRef; |
196 | using ChildIteratorType = typename Traits::ChildIteratorType; |
197 | using ChildEdgeIteratorType = typename Traits::ChildEdgeIteratorType; |
198 | |
199 | DOTGraphTraits(bool IsSimple = false) : DefaultDOTGraphTraits(IsSimple) {} |
200 | |
201 | std::string getNodeLabel(NodeRef Node, GraphType *) { |
202 | if (Node->getValue() == MachineGadgetGraph::ArgNodeSentinel) |
203 | return "ARGS" ; |
204 | |
205 | std::string Str; |
206 | raw_string_ostream OS(Str); |
207 | OS << *Node->getValue(); |
208 | return OS.str(); |
209 | } |
210 | |
211 | static std::string getNodeAttributes(NodeRef Node, GraphType *) { |
212 | MachineInstr *MI = Node->getValue(); |
213 | if (MI == MachineGadgetGraph::ArgNodeSentinel) |
214 | return "color = blue" ; |
215 | if (MI->getOpcode() == X86::LFENCE) |
216 | return "color = green" ; |
217 | return "" ; |
218 | } |
219 | |
220 | static std::string getEdgeAttributes(NodeRef, ChildIteratorType E, |
221 | GraphType *) { |
222 | int EdgeVal = (*E.getCurrent()).getValue(); |
223 | return EdgeVal >= 0 ? "label = " + std::to_string(val: EdgeVal) |
224 | : "color = red, style = \"dashed\"" ; |
225 | } |
226 | }; |
227 | |
228 | } // end namespace llvm |
229 | |
230 | constexpr MachineInstr *MachineGadgetGraph::ArgNodeSentinel; |
231 | constexpr int MachineGadgetGraph::GadgetEdgeSentinel; |
232 | |
233 | char X86LoadValueInjectionLoadHardeningPass::ID = 0; |
234 | |
235 | void X86LoadValueInjectionLoadHardeningPass::getAnalysisUsage( |
236 | AnalysisUsage &AU) const { |
237 | MachineFunctionPass::getAnalysisUsage(AU); |
238 | AU.addRequired<MachineLoopInfoWrapperPass>(); |
239 | AU.addRequired<MachineDominatorTreeWrapperPass>(); |
240 | AU.addRequired<MachineDominanceFrontier>(); |
241 | AU.setPreservesCFG(); |
242 | } |
243 | |
244 | static void writeGadgetGraph(raw_ostream &OS, MachineFunction &MF, |
245 | MachineGadgetGraph *G) { |
246 | WriteGraph(O&: OS, G, /*ShortNames*/ false, |
247 | Title: "Speculative gadgets for \"" + MF.getName() + "\" function" ); |
248 | } |
249 | |
250 | bool X86LoadValueInjectionLoadHardeningPass::runOnMachineFunction( |
251 | MachineFunction &MF) { |
252 | LLVM_DEBUG(dbgs() << "***** " << getPassName() << " : " << MF.getName() |
253 | << " *****\n" ); |
254 | STI = &MF.getSubtarget<X86Subtarget>(); |
255 | if (!STI->useLVILoadHardening()) |
256 | return false; |
257 | |
258 | // FIXME: support 32-bit |
259 | if (!STI->is64Bit()) |
260 | report_fatal_error(reason: "LVI load hardening is only supported on 64-bit" , gen_crash_diag: false); |
261 | |
262 | // Don't skip functions with the "optnone" attr but participate in opt-bisect. |
263 | const Function &F = MF.getFunction(); |
264 | if (!F.hasOptNone() && skipFunction(F)) |
265 | return false; |
266 | |
267 | ++NumFunctionsConsidered; |
268 | TII = STI->getInstrInfo(); |
269 | TRI = STI->getRegisterInfo(); |
270 | LLVM_DEBUG(dbgs() << "Building gadget graph...\n" ); |
271 | const auto &MLI = getAnalysis<MachineLoopInfoWrapperPass>().getLI(); |
272 | const auto &MDT = getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree(); |
273 | const auto &MDF = getAnalysis<MachineDominanceFrontier>(); |
274 | std::unique_ptr<MachineGadgetGraph> Graph = getGadgetGraph(MF, MLI, MDT, MDF); |
275 | LLVM_DEBUG(dbgs() << "Building gadget graph... Done\n" ); |
276 | if (Graph == nullptr) |
277 | return false; // didn't find any gadgets |
278 | |
279 | if (EmitDotVerify) { |
280 | writeGadgetGraph(OS&: outs(), MF, G: Graph.get()); |
281 | return false; |
282 | } |
283 | |
284 | if (EmitDot || EmitDotOnly) { |
285 | LLVM_DEBUG(dbgs() << "Emitting gadget graph...\n" ); |
286 | std::error_code FileError; |
287 | std::string FileName = "lvi." ; |
288 | FileName += MF.getName(); |
289 | FileName += ".dot" ; |
290 | raw_fd_ostream FileOut(FileName, FileError); |
291 | if (FileError) |
292 | errs() << FileError.message(); |
293 | writeGadgetGraph(OS&: FileOut, MF, G: Graph.get()); |
294 | FileOut.close(); |
295 | LLVM_DEBUG(dbgs() << "Emitting gadget graph... Done\n" ); |
296 | if (EmitDotOnly) |
297 | return false; |
298 | } |
299 | |
300 | int FencesInserted; |
301 | if (!OptimizePluginPath.empty()) { |
302 | if (!OptimizeDL.isValid()) { |
303 | std::string ErrorMsg; |
304 | OptimizeDL = llvm::sys::DynamicLibrary::getPermanentLibrary( |
305 | filename: OptimizePluginPath.c_str(), errMsg: &ErrorMsg); |
306 | if (!ErrorMsg.empty()) |
307 | report_fatal_error(reason: Twine("Failed to load opt plugin: \"" ) + ErrorMsg + |
308 | "\"" ); |
309 | OptimizeCut = (OptimizeCutT)OptimizeDL.getAddressOfSymbol(symbolName: "optimize_cut" ); |
310 | if (!OptimizeCut) |
311 | report_fatal_error(reason: "Invalid optimization plugin" ); |
312 | } |
313 | FencesInserted = hardenLoadsWithPlugin(MF, Graph: std::move(Graph)); |
314 | } else { // Use the default greedy heuristic |
315 | FencesInserted = hardenLoadsWithHeuristic(MF, Graph: std::move(Graph)); |
316 | } |
317 | |
318 | if (FencesInserted > 0) |
319 | ++NumFunctionsMitigated; |
320 | NumFences += FencesInserted; |
321 | return (FencesInserted > 0); |
322 | } |
323 | |
324 | std::unique_ptr<MachineGadgetGraph> |
325 | X86LoadValueInjectionLoadHardeningPass::getGadgetGraph( |
326 | MachineFunction &MF, const MachineLoopInfo &MLI, |
327 | const MachineDominatorTree &MDT, |
328 | const MachineDominanceFrontier &MDF) const { |
329 | using namespace rdf; |
330 | |
331 | // Build the Register Dataflow Graph using the RDF framework |
332 | DataFlowGraph DFG{MF, *TII, *TRI, MDT, MDF}; |
333 | DFG.build(); |
334 | Liveness L{MF.getRegInfo(), DFG}; |
335 | L.computePhiInfo(); |
336 | |
337 | GraphBuilder Builder; |
338 | using GraphIter = typename GraphBuilder::BuilderNodeRef; |
339 | DenseMap<MachineInstr *, GraphIter> NodeMap; |
340 | int FenceCount = 0, GadgetCount = 0; |
341 | auto MaybeAddNode = [&NodeMap, &Builder](MachineInstr *MI) { |
342 | auto [Ref, Inserted] = NodeMap.try_emplace(Key: MI); |
343 | if (Inserted) { |
344 | auto I = Builder.addVertex(V: MI); |
345 | Ref->second = I; |
346 | return std::pair<GraphIter, bool>{I, true}; |
347 | } |
348 | return std::pair<GraphIter, bool>{Ref->getSecond(), false}; |
349 | }; |
350 | |
351 | // The `Transmitters` map memoizes transmitters found for each def. If a def |
352 | // has not yet been analyzed, then it will not appear in the map. If a def |
353 | // has been analyzed and was determined not to have any transmitters, then |
354 | // its list of transmitters will be empty. |
355 | DenseMap<NodeId, std::vector<NodeId>> Transmitters; |
356 | |
357 | // Analyze all machine instructions to find gadgets and LFENCEs, adding |
358 | // each interesting value to `Nodes` |
359 | auto AnalyzeDef = [&](NodeAddr<DefNode *> SourceDef) { |
360 | SmallSet<NodeId, 8> UsesVisited, DefsVisited; |
361 | std::function<void(NodeAddr<DefNode *>)> AnalyzeDefUseChain = |
362 | [&](NodeAddr<DefNode *> Def) { |
363 | if (Transmitters.contains(Val: Def.Id)) |
364 | return; // Already analyzed `Def` |
365 | |
366 | // Use RDF to find all the uses of `Def` |
367 | rdf::NodeSet Uses; |
368 | RegisterRef DefReg = Def.Addr->getRegRef(G: DFG); |
369 | for (auto UseID : L.getAllReachedUses(RefRR: DefReg, DefA: Def)) { |
370 | auto Use = DFG.addr<UseNode *>(N: UseID); |
371 | if (Use.Addr->getFlags() & NodeAttrs::PhiRef) { // phi node |
372 | NodeAddr<PhiNode *> Phi = Use.Addr->getOwner(G: DFG); |
373 | for (const auto& I : L.getRealUses(P: Phi.Id)) { |
374 | if (DFG.getPRI().alias(RA: RegisterRef(I.first), RB: DefReg)) { |
375 | for (const auto &UA : I.second) |
376 | Uses.emplace(args: UA.first); |
377 | } |
378 | } |
379 | } else { // not a phi node |
380 | Uses.emplace(args&: UseID); |
381 | } |
382 | } |
383 | |
384 | // For each use of `Def`, we want to know whether: |
385 | // (1) The use can leak the Def'ed value, |
386 | // (2) The use can further propagate the Def'ed value to more defs |
387 | for (auto UseID : Uses) { |
388 | if (!UsesVisited.insert(V: UseID).second) |
389 | continue; // Already visited this use of `Def` |
390 | |
391 | auto Use = DFG.addr<UseNode *>(N: UseID); |
392 | assert(!(Use.Addr->getFlags() & NodeAttrs::PhiRef)); |
393 | MachineOperand &UseMO = Use.Addr->getOp(); |
394 | MachineInstr &UseMI = *UseMO.getParent(); |
395 | assert(UseMO.isReg()); |
396 | |
397 | // We naively assume that an instruction propagates any loaded |
398 | // uses to all defs unless the instruction is a call, in which |
399 | // case all arguments will be treated as gadget sources during |
400 | // analysis of the callee function. |
401 | if (UseMI.isCall()) |
402 | continue; |
403 | |
404 | // Check whether this use can transmit (leak) its value. |
405 | if (instrUsesRegToAccessMemory(I: UseMI, Reg: UseMO.getReg()) || |
406 | (!NoConditionalBranches && |
407 | instrUsesRegToBranch(I: UseMI, Reg: UseMO.getReg()))) { |
408 | Transmitters[Def.Id].push_back(x: Use.Addr->getOwner(G: DFG).Id); |
409 | if (UseMI.mayLoad()) |
410 | continue; // Found a transmitting load -- no need to continue |
411 | // traversing its defs (i.e., this load will become |
412 | // a new gadget source anyways). |
413 | } |
414 | |
415 | // Check whether the use propagates to more defs. |
416 | NodeAddr<InstrNode *> Owner{Use.Addr->getOwner(G: DFG)}; |
417 | for (const auto &ChildDef : |
418 | Owner.Addr->members_if(P: DataFlowGraph::IsDef, G: DFG)) { |
419 | if (!DefsVisited.insert(V: ChildDef.Id).second) |
420 | continue; // Already visited this def |
421 | if (Def.Addr->getAttrs() & NodeAttrs::Dead) |
422 | continue; |
423 | if (Def.Id == ChildDef.Id) |
424 | continue; // `Def` uses itself (e.g., increment loop counter) |
425 | |
426 | AnalyzeDefUseChain(ChildDef); |
427 | |
428 | // `Def` inherits all of its child defs' transmitters. |
429 | for (auto TransmitterId : Transmitters[ChildDef.Id]) |
430 | Transmitters[Def.Id].push_back(x: TransmitterId); |
431 | } |
432 | } |
433 | |
434 | // Note that this statement adds `Def.Id` to the map if no |
435 | // transmitters were found for `Def`. |
436 | auto &DefTransmitters = Transmitters[Def.Id]; |
437 | |
438 | // Remove duplicate transmitters |
439 | llvm::sort(C&: DefTransmitters); |
440 | DefTransmitters.erase(first: llvm::unique(R&: DefTransmitters), |
441 | last: DefTransmitters.end()); |
442 | }; |
443 | |
444 | // Find all of the transmitters |
445 | AnalyzeDefUseChain(SourceDef); |
446 | auto &SourceDefTransmitters = Transmitters[SourceDef.Id]; |
447 | if (SourceDefTransmitters.empty()) |
448 | return; // No transmitters for `SourceDef` |
449 | |
450 | MachineInstr *Source = SourceDef.Addr->getFlags() & NodeAttrs::PhiRef |
451 | ? MachineGadgetGraph::ArgNodeSentinel |
452 | : SourceDef.Addr->getOp().getParent(); |
453 | auto GadgetSource = MaybeAddNode(Source); |
454 | // Each transmitter is a sink for `SourceDef`. |
455 | for (auto TransmitterId : SourceDefTransmitters) { |
456 | MachineInstr *Sink = DFG.addr<StmtNode *>(N: TransmitterId).Addr->getCode(); |
457 | auto GadgetSink = MaybeAddNode(Sink); |
458 | // Add the gadget edge to the graph. |
459 | Builder.addEdge(E: MachineGadgetGraph::GadgetEdgeSentinel, |
460 | From: GadgetSource.first, To: GadgetSink.first); |
461 | ++GadgetCount; |
462 | } |
463 | }; |
464 | |
465 | LLVM_DEBUG(dbgs() << "Analyzing def-use chains to find gadgets\n" ); |
466 | // Analyze function arguments |
467 | NodeAddr<BlockNode *> EntryBlock = DFG.getFunc().Addr->getEntryBlock(G: DFG); |
468 | for (NodeAddr<PhiNode *> ArgPhi : |
469 | EntryBlock.Addr->members_if(P: DataFlowGraph::IsPhi, G: DFG)) { |
470 | NodeList Defs = ArgPhi.Addr->members_if(P: DataFlowGraph::IsDef, G: DFG); |
471 | llvm::for_each(Range&: Defs, F: AnalyzeDef); |
472 | } |
473 | // Analyze every instruction in MF |
474 | for (NodeAddr<BlockNode *> BA : DFG.getFunc().Addr->members(G: DFG)) { |
475 | for (NodeAddr<StmtNode *> SA : |
476 | BA.Addr->members_if(P: DataFlowGraph::IsCode<NodeAttrs::Stmt>, G: DFG)) { |
477 | MachineInstr *MI = SA.Addr->getCode(); |
478 | if (isFence(MI)) { |
479 | MaybeAddNode(MI); |
480 | ++FenceCount; |
481 | } else if (MI->mayLoad()) { |
482 | NodeList Defs = SA.Addr->members_if(P: DataFlowGraph::IsDef, G: DFG); |
483 | llvm::for_each(Range&: Defs, F: AnalyzeDef); |
484 | } |
485 | } |
486 | } |
487 | LLVM_DEBUG(dbgs() << "Found " << FenceCount << " fences\n" ); |
488 | LLVM_DEBUG(dbgs() << "Found " << GadgetCount << " gadgets\n" ); |
489 | if (GadgetCount == 0) |
490 | return nullptr; |
491 | NumGadgets += GadgetCount; |
492 | |
493 | // Traverse CFG to build the rest of the graph |
494 | SmallSet<MachineBasicBlock *, 8> BlocksVisited; |
495 | std::function<void(MachineBasicBlock *, GraphIter, unsigned)> TraverseCFG = |
496 | [&](MachineBasicBlock *MBB, GraphIter GI, unsigned ParentDepth) { |
497 | unsigned LoopDepth = MLI.getLoopDepth(BB: MBB); |
498 | if (!MBB->empty()) { |
499 | // Always add the first instruction in each block |
500 | auto NI = MBB->begin(); |
501 | auto BeginBB = MaybeAddNode(&*NI); |
502 | Builder.addEdge(E: ParentDepth, From: GI, To: BeginBB.first); |
503 | if (!BlocksVisited.insert(Ptr: MBB).second) |
504 | return; |
505 | |
506 | // Add any instructions within the block that are gadget components |
507 | GI = BeginBB.first; |
508 | while (++NI != MBB->end()) { |
509 | auto Ref = NodeMap.find(Val: &*NI); |
510 | if (Ref != NodeMap.end()) { |
511 | Builder.addEdge(E: LoopDepth, From: GI, To: Ref->getSecond()); |
512 | GI = Ref->getSecond(); |
513 | } |
514 | } |
515 | |
516 | // Always add the terminator instruction, if one exists |
517 | auto T = MBB->getFirstTerminator(); |
518 | if (T != MBB->end()) { |
519 | auto EndBB = MaybeAddNode(&*T); |
520 | if (EndBB.second) |
521 | Builder.addEdge(E: LoopDepth, From: GI, To: EndBB.first); |
522 | GI = EndBB.first; |
523 | } |
524 | } |
525 | for (MachineBasicBlock *Succ : MBB->successors()) |
526 | TraverseCFG(Succ, GI, LoopDepth); |
527 | }; |
528 | // ArgNodeSentinel is a pseudo-instruction that represents MF args in the |
529 | // GadgetGraph |
530 | GraphIter ArgNode = MaybeAddNode(MachineGadgetGraph::ArgNodeSentinel).first; |
531 | TraverseCFG(&MF.front(), ArgNode, 0); |
532 | std::unique_ptr<MachineGadgetGraph> G{Builder.get(Args&: FenceCount, Args&: GadgetCount)}; |
533 | LLVM_DEBUG(dbgs() << "Found " << G->nodes_size() << " nodes\n" ); |
534 | return G; |
535 | } |
536 | |
537 | // Returns the number of remaining gadget edges that could not be eliminated |
538 | int X86LoadValueInjectionLoadHardeningPass::elimMitigatedEdgesAndNodes( |
539 | MachineGadgetGraph &G, EdgeSet &ElimEdges /* in, out */, |
540 | NodeSet &ElimNodes /* in, out */) const { |
541 | if (G.NumFences > 0) { |
542 | // Eliminate fences and CFG edges that ingress and egress the fence, as |
543 | // they are trivially mitigated. |
544 | for (const Edge &E : G.edges()) { |
545 | const Node *Dest = E.getDest(); |
546 | if (isFence(MI: Dest->getValue())) { |
547 | ElimNodes.insert(N: *Dest); |
548 | ElimEdges.insert(E); |
549 | for (const Edge &DE : Dest->edges()) |
550 | ElimEdges.insert(E: DE); |
551 | } |
552 | } |
553 | } |
554 | |
555 | // Find and eliminate gadget edges that have been mitigated. |
556 | int RemainingGadgets = 0; |
557 | NodeSet ReachableNodes{G}; |
558 | for (const Node &RootN : G.nodes()) { |
559 | if (llvm::none_of(Range: RootN.edges(), P: MachineGadgetGraph::isGadgetEdge)) |
560 | continue; // skip this node if it isn't a gadget source |
561 | |
562 | // Find all of the nodes that are CFG-reachable from RootN using DFS |
563 | ReachableNodes.clear(); |
564 | std::function<void(const Node *, bool)> FindReachableNodes = |
565 | [&](const Node *N, bool FirstNode) { |
566 | if (!FirstNode) |
567 | ReachableNodes.insert(N: *N); |
568 | for (const Edge &E : N->edges()) { |
569 | const Node *Dest = E.getDest(); |
570 | if (MachineGadgetGraph::isCFGEdge(E) && !ElimEdges.contains(E) && |
571 | !ReachableNodes.contains(N: *Dest)) |
572 | FindReachableNodes(Dest, false); |
573 | } |
574 | }; |
575 | FindReachableNodes(&RootN, true); |
576 | |
577 | // Any gadget whose sink is unreachable has been mitigated |
578 | for (const Edge &E : RootN.edges()) { |
579 | if (MachineGadgetGraph::isGadgetEdge(E)) { |
580 | if (ReachableNodes.contains(N: *E.getDest())) { |
581 | // This gadget's sink is reachable |
582 | ++RemainingGadgets; |
583 | } else { // This gadget's sink is unreachable, and therefore mitigated |
584 | ElimEdges.insert(E); |
585 | } |
586 | } |
587 | } |
588 | } |
589 | return RemainingGadgets; |
590 | } |
591 | |
592 | std::unique_ptr<MachineGadgetGraph> |
593 | X86LoadValueInjectionLoadHardeningPass::trimMitigatedEdges( |
594 | std::unique_ptr<MachineGadgetGraph> Graph) const { |
595 | NodeSet ElimNodes{*Graph}; |
596 | EdgeSet ElimEdges{*Graph}; |
597 | int RemainingGadgets = |
598 | elimMitigatedEdgesAndNodes(G&: *Graph, ElimEdges, ElimNodes); |
599 | if (ElimEdges.empty() && ElimNodes.empty()) { |
600 | Graph->NumFences = 0; |
601 | Graph->NumGadgets = RemainingGadgets; |
602 | } else { |
603 | Graph = GraphBuilder::trim(G: *Graph, TrimNodes: ElimNodes, TrimEdges: ElimEdges, Args: 0 /* NumFences */, |
604 | Args&: RemainingGadgets); |
605 | } |
606 | return Graph; |
607 | } |
608 | |
609 | int X86LoadValueInjectionLoadHardeningPass::hardenLoadsWithPlugin( |
610 | MachineFunction &MF, std::unique_ptr<MachineGadgetGraph> Graph) const { |
611 | int FencesInserted = 0; |
612 | |
613 | do { |
614 | LLVM_DEBUG(dbgs() << "Eliminating mitigated paths...\n" ); |
615 | Graph = trimMitigatedEdges(Graph: std::move(Graph)); |
616 | LLVM_DEBUG(dbgs() << "Eliminating mitigated paths... Done\n" ); |
617 | if (Graph->NumGadgets == 0) |
618 | break; |
619 | |
620 | LLVM_DEBUG(dbgs() << "Cutting edges...\n" ); |
621 | EdgeSet CutEdges{*Graph}; |
622 | auto Nodes = std::make_unique<unsigned int[]>(num: Graph->nodes_size() + |
623 | 1 /* terminator node */); |
624 | auto Edges = std::make_unique<unsigned int[]>(num: Graph->edges_size()); |
625 | auto EdgeCuts = std::make_unique<int[]>(num: Graph->edges_size()); |
626 | auto EdgeValues = std::make_unique<int[]>(num: Graph->edges_size()); |
627 | for (const Node &N : Graph->nodes()) { |
628 | Nodes[Graph->getNodeIndex(N)] = Graph->getEdgeIndex(E: *N.edges_begin()); |
629 | } |
630 | Nodes[Graph->nodes_size()] = Graph->edges_size(); // terminator node |
631 | for (const Edge &E : Graph->edges()) { |
632 | Edges[Graph->getEdgeIndex(E)] = Graph->getNodeIndex(N: *E.getDest()); |
633 | EdgeValues[Graph->getEdgeIndex(E)] = E.getValue(); |
634 | } |
635 | OptimizeCut(Nodes.get(), Graph->nodes_size(), Edges.get(), EdgeValues.get(), |
636 | EdgeCuts.get(), Graph->edges_size()); |
637 | for (int I = 0; I < Graph->edges_size(); ++I) |
638 | if (EdgeCuts[I]) |
639 | CutEdges.set(I); |
640 | LLVM_DEBUG(dbgs() << "Cutting edges... Done\n" ); |
641 | LLVM_DEBUG(dbgs() << "Cut " << CutEdges.count() << " edges\n" ); |
642 | |
643 | LLVM_DEBUG(dbgs() << "Inserting LFENCEs...\n" ); |
644 | FencesInserted += insertFences(MF, G&: *Graph, CutEdges); |
645 | LLVM_DEBUG(dbgs() << "Inserting LFENCEs... Done\n" ); |
646 | LLVM_DEBUG(dbgs() << "Inserted " << FencesInserted << " fences\n" ); |
647 | |
648 | Graph = GraphBuilder::trim(G: *Graph, TrimNodes: NodeSet{*Graph}, TrimEdges: CutEdges); |
649 | } while (true); |
650 | |
651 | return FencesInserted; |
652 | } |
653 | |
654 | int X86LoadValueInjectionLoadHardeningPass::hardenLoadsWithHeuristic( |
655 | MachineFunction &MF, std::unique_ptr<MachineGadgetGraph> Graph) const { |
656 | // If `MF` does not have any fences, then no gadgets would have been |
657 | // mitigated at this point. |
658 | if (Graph->NumFences > 0) { |
659 | LLVM_DEBUG(dbgs() << "Eliminating mitigated paths...\n" ); |
660 | Graph = trimMitigatedEdges(Graph: std::move(Graph)); |
661 | LLVM_DEBUG(dbgs() << "Eliminating mitigated paths... Done\n" ); |
662 | } |
663 | |
664 | if (Graph->NumGadgets == 0) |
665 | return 0; |
666 | |
667 | LLVM_DEBUG(dbgs() << "Cutting edges...\n" ); |
668 | EdgeSet CutEdges{*Graph}; |
669 | |
670 | // Begin by collecting all ingress CFG edges for each node |
671 | DenseMap<const Node *, SmallVector<const Edge *, 2>> IngressEdgeMap; |
672 | for (const Edge &E : Graph->edges()) |
673 | if (MachineGadgetGraph::isCFGEdge(E)) |
674 | IngressEdgeMap[E.getDest()].push_back(Elt: &E); |
675 | |
676 | // For each gadget edge, make cuts that guarantee the gadget will be |
677 | // mitigated. A computationally efficient way to achieve this is to either: |
678 | // (a) cut all egress CFG edges from the gadget source, or |
679 | // (b) cut all ingress CFG edges to the gadget sink. |
680 | // |
681 | // Moreover, the algorithm tries not to make a cut into a loop by preferring |
682 | // to make a (b)-type cut if the gadget source resides at a greater loop depth |
683 | // than the gadget sink, or an (a)-type cut otherwise. |
684 | for (const Node &N : Graph->nodes()) { |
685 | for (const Edge &E : N.edges()) { |
686 | if (!MachineGadgetGraph::isGadgetEdge(E)) |
687 | continue; |
688 | |
689 | SmallVector<const Edge *, 2> EgressEdges; |
690 | SmallVector<const Edge *, 2> &IngressEdges = IngressEdgeMap[E.getDest()]; |
691 | for (const Edge &EgressEdge : N.edges()) |
692 | if (MachineGadgetGraph::isCFGEdge(E: EgressEdge)) |
693 | EgressEdges.push_back(Elt: &EgressEdge); |
694 | |
695 | int EgressCutCost = 0, IngressCutCost = 0; |
696 | for (const Edge *EgressEdge : EgressEdges) |
697 | if (!CutEdges.contains(E: *EgressEdge)) |
698 | EgressCutCost += EgressEdge->getValue(); |
699 | for (const Edge *IngressEdge : IngressEdges) |
700 | if (!CutEdges.contains(E: *IngressEdge)) |
701 | IngressCutCost += IngressEdge->getValue(); |
702 | |
703 | auto &EdgesToCut = |
704 | IngressCutCost < EgressCutCost ? IngressEdges : EgressEdges; |
705 | for (const Edge *E : EdgesToCut) |
706 | CutEdges.insert(E: *E); |
707 | } |
708 | } |
709 | LLVM_DEBUG(dbgs() << "Cutting edges... Done\n" ); |
710 | LLVM_DEBUG(dbgs() << "Cut " << CutEdges.count() << " edges\n" ); |
711 | |
712 | LLVM_DEBUG(dbgs() << "Inserting LFENCEs...\n" ); |
713 | int FencesInserted = insertFences(MF, G&: *Graph, CutEdges); |
714 | LLVM_DEBUG(dbgs() << "Inserting LFENCEs... Done\n" ); |
715 | LLVM_DEBUG(dbgs() << "Inserted " << FencesInserted << " fences\n" ); |
716 | |
717 | return FencesInserted; |
718 | } |
719 | |
720 | int X86LoadValueInjectionLoadHardeningPass::insertFences( |
721 | MachineFunction &MF, MachineGadgetGraph &G, |
722 | EdgeSet &CutEdges /* in, out */) const { |
723 | int FencesInserted = 0; |
724 | for (const Node &N : G.nodes()) { |
725 | for (const Edge &E : N.edges()) { |
726 | if (CutEdges.contains(E)) { |
727 | MachineInstr *MI = N.getValue(), *Prev; |
728 | MachineBasicBlock *MBB; // Insert an LFENCE in this MBB |
729 | MachineBasicBlock::iterator InsertionPt; // ...at this point |
730 | if (MI == MachineGadgetGraph::ArgNodeSentinel) { |
731 | // insert LFENCE at beginning of entry block |
732 | MBB = &MF.front(); |
733 | InsertionPt = MBB->begin(); |
734 | Prev = nullptr; |
735 | } else if (MI->isBranch()) { // insert the LFENCE before the branch |
736 | MBB = MI->getParent(); |
737 | InsertionPt = MI; |
738 | Prev = MI->getPrevNode(); |
739 | // Remove all egress CFG edges from this branch because the inserted |
740 | // LFENCE prevents gadgets from crossing the branch. |
741 | for (const Edge &E : N.edges()) { |
742 | if (MachineGadgetGraph::isCFGEdge(E)) |
743 | CutEdges.insert(E); |
744 | } |
745 | } else { // insert the LFENCE after the instruction |
746 | MBB = MI->getParent(); |
747 | InsertionPt = MI->getNextNode() ? MI->getNextNode() : MBB->end(); |
748 | Prev = InsertionPt == MBB->end() |
749 | ? (MBB->empty() ? nullptr : &MBB->back()) |
750 | : InsertionPt->getPrevNode(); |
751 | } |
752 | // Ensure this insertion is not redundant (two LFENCEs in sequence). |
753 | if ((InsertionPt == MBB->end() || !isFence(MI: &*InsertionPt)) && |
754 | (!Prev || !isFence(MI: Prev))) { |
755 | BuildMI(BB&: *MBB, I: InsertionPt, MIMD: DebugLoc(), MCID: TII->get(Opcode: X86::LFENCE)); |
756 | ++FencesInserted; |
757 | } |
758 | } |
759 | } |
760 | } |
761 | return FencesInserted; |
762 | } |
763 | |
764 | bool X86LoadValueInjectionLoadHardeningPass::instrUsesRegToAccessMemory( |
765 | const MachineInstr &MI, Register Reg) const { |
766 | if (!MI.mayLoadOrStore() || MI.getOpcode() == X86::MFENCE || |
767 | MI.getOpcode() == X86::SFENCE || MI.getOpcode() == X86::LFENCE) |
768 | return false; |
769 | |
770 | const int MemRefBeginIdx = X86::getFirstAddrOperandIdx(MI); |
771 | if (MemRefBeginIdx < 0) { |
772 | LLVM_DEBUG(dbgs() << "Warning: unable to obtain memory operand for loading " |
773 | "instruction:\n" ; |
774 | MI.print(dbgs()); dbgs() << '\n';); |
775 | return false; |
776 | } |
777 | |
778 | const MachineOperand &BaseMO = |
779 | MI.getOperand(i: MemRefBeginIdx + X86::AddrBaseReg); |
780 | const MachineOperand &IndexMO = |
781 | MI.getOperand(i: MemRefBeginIdx + X86::AddrIndexReg); |
782 | return (BaseMO.isReg() && BaseMO.getReg().isValid() && |
783 | TRI->regsOverlap(RegA: BaseMO.getReg(), RegB: Reg)) || |
784 | (IndexMO.isReg() && IndexMO.getReg().isValid() && |
785 | TRI->regsOverlap(RegA: IndexMO.getReg(), RegB: Reg)); |
786 | } |
787 | |
788 | bool X86LoadValueInjectionLoadHardeningPass::instrUsesRegToBranch( |
789 | const MachineInstr &MI, Register Reg) const { |
790 | if (!MI.isConditionalBranch()) |
791 | return false; |
792 | for (const MachineOperand &Use : MI.uses()) |
793 | if (Use.isReg() && Use.getReg() == Reg) |
794 | return true; |
795 | return false; |
796 | } |
797 | |
798 | INITIALIZE_PASS_BEGIN(X86LoadValueInjectionLoadHardeningPass, PASS_KEY, |
799 | "X86 LVI load hardening" , false, false) |
800 | INITIALIZE_PASS_DEPENDENCY(MachineLoopInfoWrapperPass) |
801 | INITIALIZE_PASS_DEPENDENCY(MachineDominatorTreeWrapperPass) |
802 | INITIALIZE_PASS_DEPENDENCY(MachineDominanceFrontier) |
803 | INITIALIZE_PASS_END(X86LoadValueInjectionLoadHardeningPass, PASS_KEY, |
804 | "X86 LVI load hardening" , false, false) |
805 | |
806 | FunctionPass *llvm::createX86LoadValueInjectionLoadHardeningPass() { |
807 | return new X86LoadValueInjectionLoadHardeningPass(); |
808 | } |
809 | |