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 runOnFunction() to tell
35
+ // / 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 "
@@ -80,6 +109,12 @@ static cl::opt<bool> NoFixedLoads(
80
109
" may improve performance, at the cost of security." ),
81
110
cl::init(false ), cl::Hidden);
82
111
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
+
83
118
#define ARG_NODE nullptr
84
119
#define GADGET_EDGE ((int )(-1 ))
85
120
#define WEIGHT (EdgeValue ) ((double )(2 * (EdgeValue) + 1 ))
@@ -139,6 +174,11 @@ class X86LoadValueInjectionLoadHardeningPass : public MachineFunctionPass {
139
174
getGadgetGraph (MachineFunction &MF, const MachineLoopInfo &MLI,
140
175
const MachineDominatorTree &MDT,
141
176
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 ;
142
182
143
183
bool instrUsesRegToAccessMemory (const MachineInstr &I, unsigned Reg) const ;
144
184
bool instrUsesRegToBranch (const MachineInstr &I, unsigned Reg) const ;
@@ -241,21 +281,26 @@ bool X86LoadValueInjectionLoadHardeningPass::runOnMachineFunction(
241
281
TII = STI->getInstrInfo ();
242
282
TRI = STI->getRegisterInfo ();
243
283
LLVM_DEBUG (dbgs () << " Hardening data-dependent loads...\n " );
244
- hardenLoads (MF, false );
284
+ int FencesInserted = hardenLoads (MF, false );
245
285
LLVM_DEBUG (dbgs () << " Hardening data-dependent loads... Done\n " );
246
286
if (!NoFixedLoads) {
247
287
LLVM_DEBUG (dbgs () << " Hardening fixed loads...\n " );
248
- hardenLoads (MF, true );
288
+ FencesInserted += hardenLoads (MF, true );
249
289
LLVM_DEBUG (dbgs () << " Hardening fixed loads... Done\n " );
250
290
}
251
- return false ;
291
+ if (FencesInserted > 0 )
292
+ ++NumFunctionsMitigated;
293
+ NumFences += FencesInserted;
294
+ return (FencesInserted > 0 );
252
295
}
253
296
254
297
// Apply the mitigation to `MF`, return the number of fences inserted.
255
298
// If `FixedLoads` is `true`, then the mitigation will be applied to fixed
256
299
// loads; otherwise, mitigation will be applied to non-fixed loads.
257
300
int X86LoadValueInjectionLoadHardeningPass::hardenLoads (MachineFunction &MF,
258
301
bool FixedLoads) const {
302
+ int FencesInserted = 0 ;
303
+
259
304
LLVM_DEBUG (dbgs () << " Building gadget graph...\n " );
260
305
const auto &MLI = getAnalysis<MachineLoopInfo>();
261
306
const auto &MDT = getAnalysis<MachineDominatorTree>();
@@ -289,7 +334,27 @@ int X86LoadValueInjectionLoadHardeningPass::hardenLoads(MachineFunction &MF,
289
334
return 0 ;
290
335
}
291
336
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;
293
358
}
294
359
295
360
std::unique_ptr<X86LoadValueInjectionLoadHardeningPass::MachineGadgetGraph>
@@ -461,6 +526,213 @@ X86LoadValueInjectionLoadHardeningPass::getGadgetGraph(
461
526
return G;
462
527
}
463
528
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
+
464
736
bool X86LoadValueInjectionLoadHardeningPass::instrUsesRegToAccessMemory (
465
737
const MachineInstr &MI, unsigned Reg) const {
466
738
if (!MI.mayLoadOrStore () || MI.getOpcode () == X86::MFENCE ||
0 commit comments