9
9
// / Description: This pass finds Load Value Injection (LVI) gadgets consisting
10
10
// / of a load from memory (i.e., SOURCE), and any operation that may transmit
11
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).
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.
13
36
// /
14
37
// ===----------------------------------------------------------------------===//
15
38
37
60
#include " llvm/Support/CommandLine.h"
38
61
#include " llvm/Support/DOTGraphTraits.h"
39
62
#include " llvm/Support/Debug.h"
63
+ #include " llvm/Support/DynamicLibrary.h"
40
64
#include " llvm/Support/GraphWriter.h"
41
65
#include " llvm/Support/raw_ostream.h"
42
66
@@ -45,11 +69,16 @@ using namespace llvm;
45
69
#define PASS_KEY " x86-lvi-load"
46
70
#define DEBUG_TYPE PASS_KEY
47
71
72
+ STATISTIC (NumFences, " Number of LFENCEs inserted for LVI mitigation" );
48
73
STATISTIC (NumFunctionsConsidered, " Number of functions analyzed" );
49
74
STATISTIC (NumFunctionsMitigated, " Number of functions for which mitigations "
50
75
" were deployed" );
51
76
STATISTIC (NumGadgets, " Number of LVI gadgets detected during analysis" );
52
77
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
+
53
82
static cl::opt<bool > NoConditionalBranches (
54
83
PASS_KEY " -no-cbranch" ,
55
84
cl::desc (" Don't treat conditional branches as disclosure gadgets. This "
@@ -74,6 +103,12 @@ static cl::opt<bool> EmitDotVerify(
74
103
" potential LVI gadgets, used for testing purposes only" ),
75
104
cl::init(false ), cl::Hidden);
76
105
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
+
77
112
namespace {
78
113
79
114
struct MachineGadgetGraph : ImmutableGraph<MachineInstr *, int > {
@@ -125,7 +160,19 @@ class X86LoadValueInjectionLoadHardeningPass : public MachineFunctionPass {
125
160
getGadgetGraph (MachineFunction &MF, const MachineLoopInfo &MLI,
126
161
const MachineDominatorTree &MDT,
127
162
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 ;
129
176
bool instrUsesRegToAccessMemory (const MachineInstr &I, unsigned Reg) const ;
130
177
bool instrUsesRegToBranch (const MachineInstr &I, unsigned Reg) const ;
131
178
inline bool isFence (const MachineInstr *MI) const {
@@ -252,7 +299,27 @@ bool X86LoadValueInjectionLoadHardeningPass::runOnMachineFunction(
252
299
return false ;
253
300
}
254
301
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 );
256
323
}
257
324
258
325
std::unique_ptr<MachineGadgetGraph>
@@ -471,6 +538,242 @@ X86LoadValueInjectionLoadHardeningPass::getGadgetGraph(
471
538
return G;
472
539
}
473
540
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
+
474
777
bool X86LoadValueInjectionLoadHardeningPass::instrUsesRegToAccessMemory (
475
778
const MachineInstr &MI, unsigned Reg) const {
476
779
if (!MI.mayLoadOrStore () || MI.getOpcode () == X86::MFENCE ||
0 commit comments