99// / Description: This pass finds Load Value Injection (LVI) gadgets consisting
1010// / of a load from memory (i.e., SOURCE), and any operation that may transmit
1111// / 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).
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.
1336// /
1437// ===----------------------------------------------------------------------===//
1538
3760#include " llvm/Support/CommandLine.h"
3861#include " llvm/Support/DOTGraphTraits.h"
3962#include " llvm/Support/Debug.h"
63+ #include " llvm/Support/DynamicLibrary.h"
4064#include " llvm/Support/GraphWriter.h"
4165#include " llvm/Support/raw_ostream.h"
4266
@@ -45,11 +69,16 @@ using namespace llvm;
4569#define PASS_KEY " x86-lvi-load"
4670#define DEBUG_TYPE PASS_KEY
4771
72+ STATISTIC (NumFences, " Number of LFENCEs inserted for LVI mitigation" );
4873STATISTIC (NumFunctionsConsidered, " Number of functions analyzed" );
4974STATISTIC (NumFunctionsMitigated, " Number of functions for which mitigations "
5075 " were deployed" );
5176STATISTIC (NumGadgets, " Number of LVI gadgets detected during analysis" );
5277
78+ static cl::opt<std::string> OptimizePluginPath (
79+ PASS_KEY " -opt-plugin" ,
80+ cl::desc (" Specify a plugin to optimize LFENCE insertion" ), cl::Hidden);
81+
5382static cl::opt<bool > NoConditionalBranches (
5483 PASS_KEY " -no-cbranch" ,
5584 cl::desc (" Don't treat conditional branches as disclosure gadgets. This "
@@ -74,6 +103,12 @@ static cl::opt<bool> EmitDotVerify(
74103 " potential LVI gadgets, used for testing purposes only" ),
75104 cl::init(false ), cl::Hidden);
76105
106+ static llvm::sys::DynamicLibrary OptimizeDL;
107+ typedef int (*OptimizeCutT)(unsigned int *nodes, unsigned int nodes_size,
108+ unsigned int *edges, int *edge_values,
109+ int *cut_edges /* out */ , unsigned int edges_size);
110+ static OptimizeCutT OptimizeCut = nullptr ;
111+
77112namespace {
78113
79114struct MachineGadgetGraph : ImmutableGraph<MachineInstr *, int > {
@@ -125,7 +160,19 @@ class X86LoadValueInjectionLoadHardeningPass : public MachineFunctionPass {
125160 getGadgetGraph (MachineFunction &MF, const MachineLoopInfo &MLI,
126161 const MachineDominatorTree &MDT,
127162 const MachineDominanceFrontier &MDF) const ;
128-
163+ int hardenLoadsWithPlugin (MachineFunction &MF,
164+ std::unique_ptr<MachineGadgetGraph> Graph) const ;
165+ int hardenLoadsWithGreedyHeuristic (
166+ MachineFunction &MF, 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+ void findAndCutEdges (MachineGadgetGraph &G,
173+ EdgeSet &CutEdges /* out */ ) const ;
174+ int insertFences (MachineFunction &MF, MachineGadgetGraph &G,
175+ EdgeSet &CutEdges /* in, out */ ) const ;
129176 bool instrUsesRegToAccessMemory (const MachineInstr &I, unsigned Reg) const ;
130177 bool instrUsesRegToBranch (const MachineInstr &I, unsigned Reg) const ;
131178 inline bool isFence (const MachineInstr *MI) const {
@@ -252,7 +299,27 @@ bool X86LoadValueInjectionLoadHardeningPass::runOnMachineFunction(
252299 return false ;
253300 }
254301
255- return 0 ;
302+ int FencesInserted;
303+ if (!OptimizePluginPath.empty ()) {
304+ if (!OptimizeDL.isValid ()) {
305+ std::string ErrorMsg;
306+ OptimizeDL = llvm::sys::DynamicLibrary::getPermanentLibrary (
307+ OptimizePluginPath.c_str (), &ErrorMsg);
308+ if (!ErrorMsg.empty ())
309+ report_fatal_error (" Failed to load opt plugin: \" " + ErrorMsg + ' \" ' );
310+ OptimizeCut = (OptimizeCutT)OptimizeDL.getAddressOfSymbol (" optimize_cut" );
311+ if (!OptimizeCut)
312+ report_fatal_error (" Invalid optimization plugin" );
313+ }
314+ FencesInserted = hardenLoadsWithPlugin (MF, std::move (Graph));
315+ } else { // Use the default greedy heuristic
316+ FencesInserted = hardenLoadsWithGreedyHeuristic (MF, std::move (Graph));
317+ }
318+
319+ if (FencesInserted > 0 )
320+ ++NumFunctionsMitigated;
321+ NumFences += FencesInserted;
322+ return (FencesInserted > 0 );
256323}
257324
258325std::unique_ptr<MachineGadgetGraph>
@@ -471,6 +538,242 @@ X86LoadValueInjectionLoadHardeningPass::getGadgetGraph(
471538 return G;
472539}
473540
541+ // Returns the number of remaining gadget edges that could not be eliminated
542+ int X86LoadValueInjectionLoadHardeningPass::elimMitigatedEdgesAndNodes (
543+ MachineGadgetGraph &G, MachineGadgetGraph::EdgeSet &ElimEdges /* in, out */ ,
544+ MachineGadgetGraph::NodeSet &ElimNodes /* in, out */ ) const {
545+ if (G.NumFences > 0 ) {
546+ // Eliminate fences and CFG edges that ingress and egress the fence, as
547+ // they are trivially mitigated.
548+ for (const auto &E : G.edges ()) {
549+ const MachineGadgetGraph::Node *Dest = E.getDest ();
550+ if (isFence (Dest->getValue ())) {
551+ ElimNodes.insert (*Dest);
552+ ElimEdges.insert (E);
553+ for (const auto &DE : Dest->edges ())
554+ ElimEdges.insert (DE);
555+ }
556+ }
557+ }
558+
559+ // Find and eliminate gadget edges that have been mitigated.
560+ int MitigatedGadgets = 0 , RemainingGadgets = 0 ;
561+ MachineGadgetGraph::NodeSet ReachableNodes{G};
562+ for (const auto &RootN : G.nodes ()) {
563+ if (llvm::none_of (RootN.edges (), MachineGadgetGraph::isGadgetEdge))
564+ continue ; // skip this node if it isn't a gadget source
565+
566+ // Find all of the nodes that are CFG-reachable from RootN using DFS
567+ ReachableNodes.clear ();
568+ std::function<void (const MachineGadgetGraph::Node *, bool )>
569+ FindReachableNodes =
570+ [&](const MachineGadgetGraph::Node *N, bool FirstNode) {
571+ if (!FirstNode)
572+ ReachableNodes.insert (*N);
573+ for (const auto &E : N->edges ()) {
574+ const MachineGadgetGraph::Node *Dest = E.getDest ();
575+ if (MachineGadgetGraph::isCFGEdge (E) &&
576+ !ElimEdges.contains (E) && !ReachableNodes.contains (*Dest))
577+ FindReachableNodes (Dest, false );
578+ }
579+ };
580+ FindReachableNodes (&RootN, true );
581+
582+ // Any gadget whose sink is unreachable has been mitigated
583+ for (const auto &E : RootN.edges ()) {
584+ if (MachineGadgetGraph::isGadgetEdge (E)) {
585+ if (ReachableNodes.contains (*E.getDest ())) {
586+ // This gadget's sink is reachable
587+ ++RemainingGadgets;
588+ } else { // This gadget's sink is unreachable, and therefore mitigated
589+ ++MitigatedGadgets;
590+ ElimEdges.insert (E);
591+ }
592+ }
593+ }
594+ }
595+ return RemainingGadgets;
596+ }
597+
598+ std::unique_ptr<MachineGadgetGraph>
599+ X86LoadValueInjectionLoadHardeningPass::trimMitigatedEdges (
600+ std::unique_ptr<MachineGadgetGraph> Graph) const {
601+ MachineGadgetGraph::NodeSet ElimNodes{*Graph};
602+ MachineGadgetGraph::EdgeSet ElimEdges{*Graph};
603+ int RemainingGadgets =
604+ elimMitigatedEdgesAndNodes (*Graph, ElimEdges, ElimNodes);
605+ if (ElimEdges.empty () && ElimNodes.empty ()) {
606+ Graph->NumFences = 0 ;
607+ Graph->NumGadgets = RemainingGadgets;
608+ } else {
609+ Graph = GraphBuilder::trim (*Graph, ElimNodes, ElimEdges, 0 /* NumFences */ ,
610+ RemainingGadgets);
611+ }
612+ return Graph;
613+ }
614+
615+ int X86LoadValueInjectionLoadHardeningPass::hardenLoadsWithPlugin (
616+ MachineFunction &MF, std::unique_ptr<MachineGadgetGraph> Graph) const {
617+ int FencesInserted = 0 ;
618+
619+ do {
620+ LLVM_DEBUG (dbgs () << " Eliminating mitigated paths...\n " );
621+ Graph = trimMitigatedEdges (std::move (Graph));
622+ LLVM_DEBUG (dbgs () << " Eliminating mitigated paths... Done\n " );
623+ if (Graph->NumGadgets == 0 )
624+ break ;
625+
626+ LLVM_DEBUG (dbgs () << " Cutting edges...\n " );
627+ EdgeSet CutEdges{*Graph};
628+ auto Nodes = std::make_unique<unsigned int []>(Graph->nodes_size () +
629+ 1 /* terminator node */ );
630+ auto Edges = std::make_unique<unsigned int []>(Graph->edges_size ());
631+ auto EdgeCuts = std::make_unique<int []>(Graph->edges_size ());
632+ auto EdgeValues = std::make_unique<int []>(Graph->edges_size ());
633+ for (const auto &N : Graph->nodes ()) {
634+ Nodes[Graph->getNodeIndex (N)] = Graph->getEdgeIndex (*N.edges_begin ());
635+ }
636+ Nodes[Graph->nodes_size ()] = Graph->edges_size (); // terminator node
637+ for (const auto &E : Graph->edges ()) {
638+ Edges[Graph->getEdgeIndex (E)] = Graph->getNodeIndex (*E.getDest ());
639+ EdgeValues[Graph->getEdgeIndex (E)] = E.getValue ();
640+ }
641+ OptimizeCut (Nodes.get (), Graph->nodes_size (), Edges.get (), EdgeValues.get (),
642+ EdgeCuts.get (), Graph->edges_size ());
643+ for (int I = 0 ; I < Graph->edges_size (); ++I)
644+ if (EdgeCuts[I])
645+ CutEdges.set (I);
646+ LLVM_DEBUG (dbgs () << " Cutting edges... Done\n " );
647+ LLVM_DEBUG (dbgs () << " Cut " << CutEdges.count () << " edges\n " );
648+
649+ LLVM_DEBUG (dbgs () << " Inserting LFENCEs...\n " );
650+ FencesInserted += insertFences (MF, *Graph, CutEdges);
651+ LLVM_DEBUG (dbgs () << " Inserting LFENCEs... Done\n " );
652+ LLVM_DEBUG (dbgs () << " Inserted " << FencesInserted << " fences\n " );
653+
654+ Graph = GraphBuilder::trim (*Graph, MachineGadgetGraph::NodeSet{*Graph},
655+ CutEdges);
656+ } while (true );
657+
658+ return FencesInserted;
659+ }
660+
661+ int X86LoadValueInjectionLoadHardeningPass::hardenLoadsWithGreedyHeuristic (
662+ MachineFunction &MF, std::unique_ptr<MachineGadgetGraph> Graph) const {
663+ LLVM_DEBUG (dbgs () << " Eliminating mitigated paths...\n " );
664+ Graph = trimMitigatedEdges (std::move (Graph));
665+ LLVM_DEBUG (dbgs () << " Eliminating mitigated paths... Done\n " );
666+ if (Graph->NumGadgets == 0 )
667+ return 0 ;
668+
669+ LLVM_DEBUG (dbgs () << " Cutting edges...\n " );
670+ MachineGadgetGraph::NodeSet ElimNodes{*Graph}, GadgetSinks{*Graph};
671+ MachineGadgetGraph::EdgeSet ElimEdges{*Graph}, CutEdges{*Graph};
672+ auto IsCFGEdge = [&ElimEdges, &CutEdges](const MachineGadgetGraph::Edge &E) {
673+ return !ElimEdges.contains (E) && !CutEdges.contains (E) &&
674+ MachineGadgetGraph::isCFGEdge (E);
675+ };
676+ auto IsGadgetEdge = [&ElimEdges,
677+ &CutEdges](const MachineGadgetGraph::Edge &E) {
678+ return !ElimEdges.contains (E) && !CutEdges.contains (E) &&
679+ MachineGadgetGraph::isGadgetEdge (E);
680+ };
681+
682+ // FIXME: this is O(E^2), we could probably do better.
683+ do {
684+ // Find the cheapest CFG edge that will eliminate a gadget (by being
685+ // egress from a SOURCE node or ingress to a SINK node), and cut it.
686+ const MachineGadgetGraph::Edge *CheapestSoFar = nullptr ;
687+
688+ // First, collect all gadget source and sink nodes.
689+ MachineGadgetGraph::NodeSet GadgetSources{*Graph}, GadgetSinks{*Graph};
690+ for (const auto &N : Graph->nodes ()) {
691+ if (ElimNodes.contains (N))
692+ continue ;
693+ for (const auto &E : N.edges ()) {
694+ if (IsGadgetEdge (E)) {
695+ GadgetSources.insert (N);
696+ GadgetSinks.insert (*E.getDest ());
697+ }
698+ }
699+ }
700+
701+ // Next, look for the cheapest CFG edge which, when cut, is guaranteed to
702+ // mitigate at least one gadget by either:
703+ // (a) being egress from a gadget source, or
704+ // (b) being ingress to a gadget sink.
705+ for (const auto &N : Graph->nodes ()) {
706+ if (ElimNodes.contains (N))
707+ continue ;
708+ for (const auto &E : N.edges ()) {
709+ if (IsCFGEdge (E)) {
710+ if (GadgetSources.contains (N) || GadgetSinks.contains (*E.getDest ())) {
711+ if (!CheapestSoFar || E.getValue () < CheapestSoFar->getValue ())
712+ CheapestSoFar = &E;
713+ }
714+ }
715+ }
716+ }
717+
718+ assert (CheapestSoFar && " Failed to cut an edge" );
719+ CutEdges.insert (*CheapestSoFar);
720+ ElimEdges.insert (*CheapestSoFar);
721+ } while (elimMitigatedEdgesAndNodes (*Graph, ElimEdges, ElimNodes));
722+ LLVM_DEBUG (dbgs () << " Cutting edges... Done\n " );
723+ LLVM_DEBUG (dbgs () << " Cut " << CutEdges.count () << " edges\n " );
724+
725+ LLVM_DEBUG (dbgs () << " Inserting LFENCEs...\n " );
726+ int FencesInserted = insertFences (MF, *Graph, CutEdges);
727+ LLVM_DEBUG (dbgs () << " Inserting LFENCEs... Done\n " );
728+ LLVM_DEBUG (dbgs () << " Inserted " << FencesInserted << " fences\n " );
729+
730+ return FencesInserted;
731+ }
732+
733+ int X86LoadValueInjectionLoadHardeningPass::insertFences (
734+ MachineFunction &MF, MachineGadgetGraph &G,
735+ EdgeSet &CutEdges /* in, out */ ) const {
736+ int FencesInserted = 0 ;
737+ for (const auto &N : G.nodes ()) {
738+ for (const auto &E : N.edges ()) {
739+ if (CutEdges.contains (E)) {
740+ MachineInstr *MI = N.getValue (), *Prev;
741+ MachineBasicBlock *MBB; // Insert an LFENCE in this MBB
742+ MachineBasicBlock::iterator InsertionPt; // ...at this point
743+ if (MI == MachineGadgetGraph::ArgNodeSentinel) {
744+ // insert LFENCE at beginning of entry block
745+ MBB = &MF.front ();
746+ InsertionPt = MBB->begin ();
747+ Prev = nullptr ;
748+ } else if (MI->isBranch ()) { // insert the LFENCE before the branch
749+ MBB = MI->getParent ();
750+ InsertionPt = MI;
751+ Prev = MI->getPrevNode ();
752+ // Remove all egress CFG edges from this branch because the inserted
753+ // LFENCE prevents gadgets from crossing the branch.
754+ for (const auto &E : N.edges ()) {
755+ if (MachineGadgetGraph::isCFGEdge (E))
756+ CutEdges.insert (E);
757+ }
758+ } else { // insert the LFENCE after the instruction
759+ MBB = MI->getParent ();
760+ InsertionPt = MI->getNextNode () ? MI->getNextNode () : MBB->end ();
761+ Prev = InsertionPt == MBB->end ()
762+ ? (MBB->empty () ? nullptr : &MBB->back ())
763+ : InsertionPt->getPrevNode ();
764+ }
765+ // Ensure this insertion is not redundant (two LFENCEs in sequence).
766+ if ((InsertionPt == MBB->end () || !isFence (&*InsertionPt)) &&
767+ (!Prev || !isFence (Prev))) {
768+ BuildMI (*MBB, InsertionPt, DebugLoc (), TII->get (X86::LFENCE));
769+ ++FencesInserted;
770+ }
771+ }
772+ }
773+ }
774+ return FencesInserted;
775+ }
776+
474777bool X86LoadValueInjectionLoadHardeningPass::instrUsesRegToAccessMemory (
475778 const MachineInstr &MI, unsigned Reg) const {
476779 if (!MI.mayLoadOrStore () || MI.getOpcode () == X86::MFENCE ||
0 commit comments