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 runOnFunction() to tell
35+ // / 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 "
@@ -80,6 +109,12 @@ static cl::opt<bool> NoFixedLoads(
80109 " may improve performance, at the cost of security." ),
81110 cl::init(false ), cl::Hidden);
82111
112+ static llvm::sys::DynamicLibrary OptimizeDL{};
113+ typedef int (*OptimizeCutT)(unsigned int *nodes, unsigned int nodes_size,
114+ unsigned int *edges, int *edge_values,
115+ int *cut_edges /* out */ , unsigned int edges_size);
116+ static OptimizeCutT OptimizeCut = nullptr ;
117+
83118#define ARG_NODE nullptr
84119#define GADGET_EDGE ((int )(-1 ))
85120#define WEIGHT (EdgeValue ) ((double )(2 * (EdgeValue) + 1 ))
@@ -139,6 +174,11 @@ class X86LoadValueInjectionLoadHardeningPass : public MachineFunctionPass {
139174 getGadgetGraph (MachineFunction &MF, const MachineLoopInfo &MLI,
140175 const MachineDominatorTree &MDT,
141176 const MachineDominanceFrontier &MDF, bool FixedLoads) const ;
177+ std::unique_ptr<MachineGadgetGraph>
178+ elimEdges (std::unique_ptr<MachineGadgetGraph> Graph) const ;
179+ void cutEdges (MachineGadgetGraph &G, EdgeSet &CutEdges /* out */ ) const ;
180+ int insertFences (MachineGadgetGraph &G,
181+ EdgeSet &CutEdges /* in, out */ ) const ;
142182
143183 bool instrUsesRegToAccessMemory (const MachineInstr &I, unsigned Reg) const ;
144184 bool instrUsesRegToBranch (const MachineInstr &I, unsigned Reg) const ;
@@ -241,21 +281,26 @@ bool X86LoadValueInjectionLoadHardeningPass::runOnMachineFunction(
241281 TII = STI->getInstrInfo ();
242282 TRI = STI->getRegisterInfo ();
243283 LLVM_DEBUG (dbgs () << " Hardening data-dependent loads...\n " );
244- hardenLoads (MF, false );
284+ int FencesInserted = hardenLoads (MF, false );
245285 LLVM_DEBUG (dbgs () << " Hardening data-dependent loads... Done\n " );
246286 if (!NoFixedLoads) {
247287 LLVM_DEBUG (dbgs () << " Hardening fixed loads...\n " );
248- hardenLoads (MF, true );
288+ FencesInserted += hardenLoads (MF, true );
249289 LLVM_DEBUG (dbgs () << " Hardening fixed loads... Done\n " );
250290 }
251- return false ;
291+ if (FencesInserted > 0 )
292+ ++NumFunctionsMitigated;
293+ NumFences += FencesInserted;
294+ return (FencesInserted > 0 );
252295}
253296
254297// Apply the mitigation to `MF`, return the number of fences inserted.
255298// If `FixedLoads` is `true`, then the mitigation will be applied to fixed
256299// loads; otherwise, mitigation will be applied to non-fixed loads.
257300int X86LoadValueInjectionLoadHardeningPass::hardenLoads (MachineFunction &MF,
258301 bool FixedLoads) const {
302+ int FencesInserted = 0 ;
303+
259304 LLVM_DEBUG (dbgs () << " Building gadget graph...\n " );
260305 const auto &MLI = getAnalysis<MachineLoopInfo>();
261306 const auto &MDT = getAnalysis<MachineDominatorTree>();
@@ -289,7 +334,27 @@ int X86LoadValueInjectionLoadHardeningPass::hardenLoads(MachineFunction &MF,
289334 return 0 ;
290335 }
291336
292- return 0 ;
337+ do {
338+ LLVM_DEBUG (dbgs () << " Eliminating mitigated paths...\n " );
339+ std::unique_ptr<MachineGadgetGraph> ElimGraph = elimEdges (std::move (Graph));
340+ LLVM_DEBUG (dbgs () << " Eliminating mitigated paths... Done\n " );
341+ if (ElimGraph->NumGadgets == 0 )
342+ break ;
343+
344+ EdgeSet CutEdges{*ElimGraph};
345+ LLVM_DEBUG (dbgs () << " Cutting edges...\n " );
346+ cutEdges (*ElimGraph, CutEdges);
347+ LLVM_DEBUG (dbgs () << " Cutting edges... Done\n " );
348+
349+ LLVM_DEBUG (dbgs () << " Inserting LFENCEs...\n " );
350+ FencesInserted += insertFences (*ElimGraph, CutEdges);
351+ LLVM_DEBUG (dbgs () << " Inserting LFENCEs... Done\n " );
352+
353+ Graph.reset (GraphBuilder::trim (
354+ *ElimGraph, MachineGadgetGraph::NodeSet{*ElimGraph}, CutEdges));
355+ } while (true );
356+
357+ return FencesInserted;
293358}
294359
295360std::unique_ptr<X86LoadValueInjectionLoadHardeningPass::MachineGadgetGraph>
@@ -461,6 +526,213 @@ X86LoadValueInjectionLoadHardeningPass::getGadgetGraph(
461526 return G;
462527}
463528
529+ std::unique_ptr<X86LoadValueInjectionLoadHardeningPass::MachineGadgetGraph>
530+ X86LoadValueInjectionLoadHardeningPass::elimEdges (
531+ std::unique_ptr<MachineGadgetGraph> Graph) const {
532+ MachineGadgetGraph::NodeSet ElimNodes{*Graph};
533+ MachineGadgetGraph::EdgeSet ElimEdges{*Graph};
534+
535+ if (Graph->NumFences > 0 ) { // eliminate fences
536+ for (auto EI = Graph->edges_begin (), EE = Graph->edges_end (); EI != EE;
537+ ++EI) {
538+ GTraits::NodeRef Dest = GTraits::edge_dest (*EI);
539+ if (isFence (Dest->value ())) {
540+ ElimNodes.insert (Dest);
541+ ElimEdges.insert (EI);
542+ std::for_each (
543+ GTraits::child_edge_begin (Dest), GTraits::child_edge_end (Dest),
544+ [&ElimEdges](GTraits::EdgeRef E) { ElimEdges.insert (&E); });
545+ }
546+ }
547+ LLVM_DEBUG (dbgs () << " Eliminated " << ElimNodes.count ()
548+ << " fence nodes\n " );
549+ }
550+
551+ // eliminate gadget edges that are mitigated
552+ int NumGadgets = 0 ;
553+ MachineGadgetGraph::NodeSet Visited{*Graph}, GadgetSinks{*Graph};
554+ MachineGadgetGraph::EdgeSet ElimGadgets{*Graph};
555+ for (auto NI = GTraits::nodes_begin (Graph.get ()),
556+ NE = GTraits::nodes_end (Graph.get ());
557+ NI != NE; ++NI) {
558+ // collect the gadgets for this node
559+ for (auto EI = GTraits::child_edge_begin (*NI),
560+ EE = GTraits::child_edge_end (*NI);
561+ EI != EE; ++EI) {
562+ if (MachineGadgetGraph::isGadgetEdge (*EI)) {
563+ ++NumGadgets;
564+ ElimGadgets.insert (EI);
565+ GadgetSinks.insert (GTraits::edge_dest (*EI));
566+ }
567+ }
568+ if (GadgetSinks.empty ())
569+ continue ;
570+ std::function<void (GTraits::NodeRef, bool )> TraverseDFS =
571+ [&](GTraits::NodeRef N, bool FirstNode) {
572+ if (!FirstNode) {
573+ Visited.insert (N);
574+ if (GadgetSinks.contains (N)) {
575+ for (auto CEI = GTraits::child_edge_begin (*NI),
576+ CEE = GTraits::child_edge_end (*NI);
577+ CEI != CEE; ++CEI) {
578+ if (MachineGadgetGraph::isGadgetEdge (*CEI) &&
579+ GTraits::edge_dest (*CEI) == N)
580+ ElimGadgets.erase (CEI);
581+ }
582+ }
583+ }
584+ for (auto CEI = GTraits::child_edge_begin (N),
585+ CEE = GTraits::child_edge_end (N);
586+ CEI != CEE; ++CEI) {
587+ GTraits::NodeRef Dest = GTraits::edge_dest (*CEI);
588+ if (MachineGadgetGraph::isCFGEdge (*CEI) &&
589+ !Visited.contains (Dest) && !ElimEdges.contains (CEI))
590+ TraverseDFS (Dest, false );
591+ }
592+ };
593+ TraverseDFS (*NI, true );
594+ Visited.clear ();
595+ GadgetSinks.clear ();
596+ }
597+ LLVM_DEBUG (dbgs () << " Eliminated " << ElimGadgets.count ()
598+ << " gadget edges\n " );
599+ ElimEdges |= ElimGadgets;
600+
601+ if (!(ElimEdges.empty () && ElimNodes.empty ())) {
602+ int NumRemainingGadgets = NumGadgets - ElimGadgets.count ();
603+ Graph.reset (GraphBuilder::trim (*Graph, ElimNodes, ElimEdges,
604+ 0 /* NumFences */ , NumRemainingGadgets));
605+ } else {
606+ Graph->NumFences = 0 ;
607+ Graph->NumGadgets = NumGadgets;
608+ }
609+ return Graph;
610+ }
611+
612+ void X86LoadValueInjectionLoadHardeningPass::cutEdges (
613+ MachineGadgetGraph &G,
614+ MachineGadgetGraph::EdgeSet &CutEdges /* out */ ) const {
615+ if (!OptimizePluginPath.empty ()) {
616+ if (!OptimizeDL.isValid ()) {
617+ std::string ErrorMsg{};
618+ OptimizeDL = llvm::sys::DynamicLibrary::getPermanentLibrary (
619+ OptimizePluginPath.c_str (), &ErrorMsg);
620+ if (!ErrorMsg.empty ())
621+ report_fatal_error (" Failed to load opt plugin: \" " + ErrorMsg + ' \" ' );
622+ OptimizeCut = (OptimizeCutT)OptimizeDL.getAddressOfSymbol (" optimize_cut" );
623+ if (!OptimizeCut)
624+ report_fatal_error (" Invalid optimization plugin" );
625+ }
626+ auto *Nodes = new unsigned int [G.nodes_size () + 1 /* terminator node */ ];
627+ auto *Edges = new unsigned int [G.edges_size ()];
628+ auto *EdgeCuts = new int [G.edges_size ()];
629+ auto *EdgeValues = new int [G.edges_size ()];
630+ for (auto *NI = G.nodes_begin (), *NE = G.nodes_end (); NI != NE; ++NI) {
631+ Nodes[std::distance (G.nodes_begin (), NI)] =
632+ std::distance (G.edges_begin (), GTraits::child_edge_begin (NI));
633+ }
634+ Nodes[G.nodes_size ()] = G.edges_size (); // terminator node
635+ for (auto *EI = G.edges_begin (), *EE = G.edges_end (); EI != EE; ++EI) {
636+ Edges[std::distance (G.edges_begin (), EI)] =
637+ std::distance (G.nodes_begin (), GTraits::edge_dest (*EI));
638+ EdgeValues[std::distance (G.edges_begin (), EI)] = EI->value ();
639+ }
640+ OptimizeCut (Nodes, G.nodes_size (), Edges, EdgeValues, EdgeCuts,
641+ G.edges_size ());
642+ for (int I = 0 ; I < G.edges_size (); ++I) {
643+ if (EdgeCuts[I])
644+ CutEdges.set (I);
645+ }
646+ delete[] Nodes;
647+ delete[] Edges;
648+ delete[] EdgeCuts;
649+ delete[] EdgeValues;
650+ } else { // Use the default greedy heuristic
651+ // Find the cheapest CFG edge that will eliminate a gadget (by being egress
652+ // from a SOURCE node or ingress to a SINK node), and cut it.
653+ MachineGadgetGraph::NodeSet GadgetSinks{G};
654+ MachineGadgetGraph::Edge *CheapestSoFar = nullptr ;
655+ for (auto NI = GTraits::nodes_begin (&G), NE = GTraits::nodes_end (&G);
656+ NI != NE; ++NI) {
657+ for (auto EI = GTraits::child_edge_begin (*NI),
658+ EE = GTraits::child_edge_end (*NI);
659+ EI != EE; ++EI) {
660+ if (MachineGadgetGraph::isGadgetEdge (*EI)) {
661+ // NI is a SOURCE node. Look for a cheap egress edge
662+ for (auto EEI = GTraits::child_edge_begin (*NI); EEI != EE; ++EEI) {
663+ if (MachineGadgetGraph::isCFGEdge (*EEI)) {
664+ if (!CheapestSoFar || EEI->value () < CheapestSoFar->value ())
665+ CheapestSoFar = EEI;
666+ }
667+ }
668+ GadgetSinks.insert (GTraits::edge_dest (*EI));
669+ } else { // EI is a CFG edge
670+ if (GadgetSinks.contains (GTraits::edge_dest (*EI))) {
671+ // The dest is a SINK node. Hence EI is an ingress edge
672+ if (!CheapestSoFar || EI->value () < CheapestSoFar->value ())
673+ CheapestSoFar = EI;
674+ }
675+ }
676+ }
677+ }
678+ assert (CheapestSoFar && " Failed to cut an edge" );
679+ CutEdges.insert (CheapestSoFar);
680+ }
681+ LLVM_DEBUG (dbgs () << " Cut " << CutEdges.count () << " edges\n " );
682+ }
683+
684+ int X86LoadValueInjectionLoadHardeningPass::insertFences (
685+ MachineGadgetGraph &G, EdgeSet &CutEdges /* in, out */ ) const {
686+ int FencesInserted = 0 , AdditionalEdgesCut = 0 ;
687+ auto CutAllCFGEdges = [&CutEdges, &AdditionalEdgesCut](GTraits::NodeRef N) {
688+ for (auto CEI = GTraits::child_edge_begin (N),
689+ CEE = GTraits::child_edge_end (N);
690+ CEI != CEE; ++CEI) {
691+ if (MachineGadgetGraph::isCFGEdge (*CEI) && !CutEdges.contains (CEI)) {
692+ CutEdges.insert (CEI);
693+ ++AdditionalEdgesCut;
694+ }
695+ }
696+ };
697+ for (auto NI = GTraits::nodes_begin (&G), NE = GTraits::nodes_end (&G);
698+ NI != NE; ++NI) {
699+ for (auto CEI = GTraits::child_edge_begin (*NI),
700+ CEE = GTraits::child_edge_end (*NI);
701+ CEI != CEE; ++CEI) {
702+ if (CutEdges.contains (CEI)) {
703+ MachineInstr *MI = (*NI)->value (), *Prev;
704+ MachineBasicBlock *MBB;
705+ MachineBasicBlock::iterator InsertionPt;
706+ if (MI == ARG_NODE) { // insert LFENCE at beginning of entry block
707+ MBB = &G.getMF ().front ();
708+ InsertionPt = MBB->begin ();
709+ Prev = nullptr ;
710+ } else if (MI->isBranch ()) { // insert the LFENCE before the branch
711+ MBB = MI->getParent ();
712+ InsertionPt = MI;
713+ Prev = MI->getPrevNode ();
714+ CutAllCFGEdges (*NI);
715+ } else { // insert the LFENCE after the instruction
716+ MBB = MI->getParent ();
717+ InsertionPt = MI->getNextNode () ? MI->getNextNode () : MBB->end ();
718+ Prev = InsertionPt == MBB->end ()
719+ ? (MBB->empty () ? nullptr : &MBB->back ())
720+ : InsertionPt->getPrevNode ();
721+ }
722+ if ((InsertionPt == MBB->end () || !isFence (&*InsertionPt)) &&
723+ (!Prev || !isFence (Prev))) {
724+ BuildMI (*MBB, InsertionPt, DebugLoc (), TII->get (X86::LFENCE));
725+ ++FencesInserted;
726+ }
727+ }
728+ }
729+ }
730+ LLVM_DEBUG (dbgs () << " Inserted " << FencesInserted << " fences\n " );
731+ LLVM_DEBUG (dbgs () << " Cut an additional " << AdditionalEdgesCut
732+ << " edges during fence insertion\n " );
733+ return FencesInserted;
734+ }
735+
464736bool X86LoadValueInjectionLoadHardeningPass::instrUsesRegToAccessMemory (
465737 const MachineInstr &MI, unsigned Reg) const {
466738 if (!MI.mayLoadOrStore () || MI.getOpcode () == X86::MFENCE ||
0 commit comments