Skip to content

Commit 8ce078c

Browse files
scottconstabletopperc
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 e97a3e5 commit 8ce078c

File tree

2 files changed

+404
-3
lines changed

2 files changed

+404
-3
lines changed

llvm/lib/Target/X86/X86LoadValueInjectionLoadHardening.cpp

+306-3
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 runOnMachineFunction()
35+
/// to tell 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 "
@@ -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+
77112
namespace {
78113

79114
struct 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

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

0 commit comments

Comments
 (0)