Skip to content

Commit 2530f4e

Browse files
scottconstabletstellar
authored andcommitted
[X86] Add Support for Load Hardening to Mitigate Load Value Injection (LVI)
After finding all such gadgets in a given function, the pass minimally inserts LFENCE instructions in such a manner that the following property is satisfied: for all SOURCE+SINK pairs, all paths in the CFG from SOURCE to SINK contain at least one LFENCE instruction. The algorithm that implements this minimal insertion is influenced by an academic paper that minimally inserts memory fences for high-performance concurrent programs: http://www.cs.ucr.edu/~lesani/companion/oopsla15/OOPSLA15.pdf The algorithm implemented in this pass is as follows: 1. Build a condensed CFG (i.e., a GadgetGraph) consisting only of the following components: -SOURCE instructions (also includes function arguments) -SINK instructions -Basic block entry points -Basic block terminators -LFENCE instructions 2. Analyze the GadgetGraph to determine which SOURCE+SINK pairs (i.e., gadgets) are already mitigated by existing LFENCEs. If all gadgets have been mitigated, go to step 6. 3. Use a heuristic or plugin to approximate minimal LFENCE insertion. 4. Insert one LFENCE along each CFG edge that was cut in step 3. 5. Go to step 2. 6. If any LFENCEs were inserted, return true from runOnFunction() to tell LLVM that the function was modified. By default, the heuristic used in Step 3 is a greedy heuristic that avoids inserting LFENCEs into loops unless absolutely necessary. There is also a CLI option to load a plugin that can provide even better optimization, inserting fewer fences, while still mitigating all of the LVI gadgets. The plugin can be found here: https://github.com/intel/lvi-llvm-optimization-plugin, and a description of the pass's behavior with the plugin can be found here: https://software.intel.com/security-software-guidance/insights/optimized-mitigation-approach-load-value-injection. Differential Revision: https://reviews.llvm.org/D75937
1 parent e3ba468 commit 2530f4e

File tree

2 files changed

+379
-5
lines changed

2 files changed

+379
-5
lines changed

llvm/lib/Target/X86/X86LoadValueInjectionLoadHardening.cpp

Lines changed: 277 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,30 @@
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

@@ -37,6 +60,7 @@
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");
4873
STATISTIC(NumFunctionsConsidered, "Number of functions analyzed");
4974
STATISTIC(NumFunctionsMitigated, "Number of functions for which mitigations "
5075
"were deployed");
5176
STATISTIC(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+
5382
static 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.
257300
int 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

295360
std::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+
464736
bool X86LoadValueInjectionLoadHardeningPass::instrUsesRegToAccessMemory(
465737
const MachineInstr &MI, unsigned Reg) const {
466738
if (!MI.mayLoadOrStore() || MI.getOpcode() == X86::MFENCE ||

0 commit comments

Comments
 (0)