Skip to content

Commit a4b4ee1

Browse files
committed
[pass] Software Bill of Mitigations Output
The goal of this stack is to create a high fidelity mapping of mitigations to their possible insertion points and their actual insertion points. This would let us track where we do and don't have mitigations rather than the current approach of tracking where we have the flag. This diff outputs what mitigations are enabled for all functions during the default LTO pipeline pass using the metadata generated in the previous diff.
1 parent fb04b7b commit a4b4ee1

File tree

6 files changed

+343
-0
lines changed

6 files changed

+343
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef LLVM_ANALYSIS_MITIGATIONANALYSIS_H
2+
#define LLVM_ANALYSIS_MITIGATIONANALYSIS_H
3+
4+
#include "llvm/IR/InstrTypes.h"
5+
#include "llvm/IR/Intrinsics.h"
6+
#include "llvm/IR/Module.h"
7+
#include "llvm/IR/PassManager.h"
8+
#include "llvm/IR/ValueHandle.h"
9+
#include "llvm/Pass.h"
10+
11+
namespace llvm {
12+
13+
class MitigationAnalysis : public AnalysisInfoMixin<MitigationAnalysis> {
14+
friend AnalysisInfoMixin<MitigationAnalysis>;
15+
static AnalysisKey Key;
16+
17+
static constexpr const char *kMitigationAnalysisDebugType =
18+
"mitigation_analysis";
19+
20+
public:
21+
using Result = PreservedAnalyses;
22+
Result run(Module &M, ModuleAnalysisManager &AM);
23+
};
24+
25+
} // end namespace llvm
26+
27+
#endif // LLVM_ANALYSIS_MITIGATIONANALYSIS_H

llvm/lib/Analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ add_llvm_component_library(LLVMAnalysis
103103
MemoryProfileInfo.cpp
104104
MemorySSA.cpp
105105
MemorySSAUpdater.cpp
106+
MitigationAnalysis.cpp
106107
ModelUnderTrainingRunner.cpp
107108
ModuleDebugInfoPrinter.cpp
108109
ModuleSummaryAnalysis.cpp
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
#include "llvm/Analysis/MitigationAnalysis.h"
2+
#include "llvm/IR/DebugInfo.h"
3+
#include "llvm/IR/DebugLoc.h"
4+
#include "llvm/IR/Function.h"
5+
#include "llvm/IR/InstIterator.h"
6+
#include "llvm/IR/Metadata.h"
7+
#include "llvm/Support/Debug.h"
8+
#include "llvm/Support/FileSystem.h"
9+
#include "llvm/Support/JSON.h"
10+
#include "llvm/Support/raw_ostream.h"
11+
#include <string>
12+
#include <unordered_map>
13+
14+
using namespace llvm;
15+
16+
AnalysisKey MitigationAnalysis::Key;
17+
18+
// Add a command line flag for the module name
19+
static cl::opt<std::string>
20+
ClOutputModuleName("mitigation-analysis-dso-name", cl::Optional,
21+
cl::desc("DSO name for the module"),
22+
cl::init("unknown"));
23+
24+
enum class MitigationState { Ineligible, EligibleDisabled, EligibleEnabled };
25+
26+
static const std::unordered_map<MitigationState, std::string> mapStateToString =
27+
{
28+
{MitigationState::Ineligible, "N/A"},
29+
{MitigationState::EligibleDisabled, "Disabled"},
30+
{MitigationState::EligibleEnabled, "Enabled"},
31+
};
32+
33+
struct MitigationInfo {
34+
MitigationState auto_var_init = MitigationState::Ineligible;
35+
MitigationState cfi_icall = MitigationState::Ineligible;
36+
MitigationState cfi_vcall = MitigationState::Ineligible;
37+
MitigationState cfi_nvcall = MitigationState::Ineligible;
38+
MitigationState stack_clash_protection = MitigationState::Ineligible;
39+
MitigationState stack_protector = MitigationState::Ineligible;
40+
MitigationState stack_protector_strong = MitigationState::Ineligible;
41+
MitigationState stack_protector_all = MitigationState::Ineligible;
42+
MitigationState libcpp_hardening_mode = MitigationState::Ineligible;
43+
std::string source_mapping = "(unknown)";
44+
std::string type_signature = "??";
45+
uint64_t type_id = 0;
46+
std::string function;
47+
std::string gmodule;
48+
};
49+
50+
/// Convert an integer value (0 or 1) to the appropriate MitigationState.
51+
static inline MitigationState valToState(int value) {
52+
switch (value) {
53+
case 0:
54+
return MitigationState::EligibleDisabled;
55+
case 1:
56+
return MitigationState::EligibleEnabled;
57+
default:
58+
return MitigationState::Ineligible;
59+
}
60+
}
61+
62+
/// Print out fields in MitigationInfo for debugging/verification purposes.
63+
#ifndef NDEBUG
64+
static void printInfo(const MitigationInfo &info) {
65+
dbgs() << "module: " << info.gmodule << "\n";
66+
dbgs() << "function: " << info.function << "\n";
67+
dbgs() << "source_location: " << info.source_mapping << "\n";
68+
dbgs() << "auto-var-init: " << mapStateToString.at(info.auto_var_init)
69+
<< "\n";
70+
dbgs() << "cfi-icall: " << mapStateToString.at(info.cfi_icall) << "\n";
71+
dbgs() << "cfi-vcall: " << mapStateToString.at(info.cfi_vcall) << "\n";
72+
dbgs() << "cfi-nvcall: " << mapStateToString.at(info.cfi_nvcall) << "\n";
73+
dbgs() << "stack-clash-protection: "
74+
<< mapStateToString.at(info.stack_clash_protection) << "\n";
75+
dbgs() << "stack-protector: " << mapStateToString.at(info.stack_protector)
76+
<< "\n";
77+
dbgs() << "stack-protector-strong: "
78+
<< mapStateToString.at(info.stack_protector_strong) << "\n";
79+
dbgs() << "stack-protector-all: "
80+
<< mapStateToString.at(info.stack_protector_all) << "\n";
81+
dbgs() << "libcpp-hardening-mode: "
82+
<< mapStateToString.at(info.libcpp_hardening_mode) << "\n";
83+
dbgs() << "type_signature: " << info.type_signature << "\n";
84+
dbgs() << "type_id: " << info.type_id << "\n\n";
85+
}
86+
#endif
87+
88+
/// Convert a mitigation key + integer value into the appropriate field
89+
/// of MitigationInfo. This replaces a long chain of if/else statements.
90+
static void keyAndValueToInfo(MitigationInfo &info, StringRef key, int value) {
91+
static constexpr struct {
92+
const StringRef Key;
93+
MitigationState MitigationInfo::*Field;
94+
} Mappings[] = {
95+
{StringRef("auto-var-init"), &MitigationInfo::auto_var_init},
96+
{StringRef("cfi-icall"), &MitigationInfo::cfi_icall},
97+
{StringRef("cfi-vcall"), &MitigationInfo::cfi_vcall},
98+
{StringRef("cfi-nvcall"), &MitigationInfo::cfi_nvcall},
99+
{StringRef("stack-clash-protection"),
100+
&MitigationInfo::stack_clash_protection},
101+
{StringRef("stack-protector"), &MitigationInfo::stack_protector},
102+
{StringRef("stack-protector-strong"),
103+
&MitigationInfo::stack_protector_strong},
104+
{StringRef("stack-protector-all"), &MitigationInfo::stack_protector_all},
105+
{StringRef("libcpp-hardening-mode"),
106+
&MitigationInfo::libcpp_hardening_mode},
107+
};
108+
109+
for (const auto &Mapping : Mappings) {
110+
if (key == Mapping.Key) {
111+
info.*(Mapping.Field) = valToState(value);
112+
break;
113+
}
114+
}
115+
}
116+
117+
/// Retrieve the first valid source path for the given function.
118+
static std::string getFunctionSourcePath(const Function &F) {
119+
if (const DISubprogram *SP = F.getSubprogram()) {
120+
std::string Dir = SP->getDirectory().str();
121+
std::string File = SP->getFilename().str();
122+
unsigned Line = SP->getLine();
123+
if (!Dir.empty() && !File.empty())
124+
return Dir + "/" + File + ":" + std::to_string(Line);
125+
}
126+
return "(unknown)";
127+
}
128+
129+
/// Write the given JSON string to file with a lock. On error, prints to stderr.
130+
static void writeJsonToFile(const std::string &jsonString,
131+
const std::string &fileName,
132+
const std::string &errorMsg) {
133+
std::error_code errCode;
134+
raw_fd_ostream OutputStream(fileName, errCode, sys::fs::CD_CreateAlways,
135+
sys::fs::FA_Read | sys::fs::FA_Write,
136+
sys::fs::OF_Text | sys::fs::OF_UpdateAtime);
137+
if (errCode) {
138+
errs() << errorMsg << "\n";
139+
errs() << errCode.message() << "\n";
140+
return;
141+
}
142+
143+
if (auto lock = OutputStream.lock()) {
144+
OutputStream << jsonString << "\n";
145+
if (OutputStream.has_error()) {
146+
errs() << errorMsg << "\n";
147+
errs() << jsonString << "\n";
148+
}
149+
} else {
150+
errs() << errorMsg << "\n";
151+
errs() << "Couldn't acquire lock for " << fileName << "\n";
152+
}
153+
}
154+
155+
/// Convert a MitigationInfo struct to a JSON object.
156+
static json::Object infoToJson(const MitigationInfo &info) {
157+
json::Object object;
158+
object["auto_var_init"] = mapStateToString.at(info.auto_var_init);
159+
object["cfi_icall"] = mapStateToString.at(info.cfi_icall);
160+
object["cfi_vcall"] = mapStateToString.at(info.cfi_vcall);
161+
object["cfi_nvcall"] = mapStateToString.at(info.cfi_nvcall);
162+
object["stack_clash_protection"] =
163+
mapStateToString.at(info.stack_clash_protection);
164+
object["stack_protector"] = mapStateToString.at(info.stack_protector);
165+
object["stack_protector_strong"] =
166+
mapStateToString.at(info.stack_protector_strong);
167+
object["stack_protector_all"] = mapStateToString.at(info.stack_protector_all);
168+
object["libcpp_hardening_mode"] =
169+
mapStateToString.at(info.libcpp_hardening_mode);
170+
object["source_mapping"] = info.source_mapping;
171+
object["function"] = info.function;
172+
object["type_signature"] = info.type_signature;
173+
object["type_id"] = (uint64_t)info.type_id;
174+
object["module"] = info.gmodule;
175+
return object;
176+
}
177+
178+
/// Return true if function F calls a function whose name contains
179+
/// targetFunctionName.
180+
static bool functionCallsFunctionWithName(Function &F,
181+
StringRef targetFunctionName) {
182+
for (Instruction &I : instructions(F)) {
183+
auto *callInst = dyn_cast<CallInst>(&I);
184+
if (!callInst)
185+
continue;
186+
187+
Function *calledFunction = callInst->getCalledFunction();
188+
if (calledFunction &&
189+
calledFunction->getName().contains(targetFunctionName))
190+
return true;
191+
}
192+
return false;
193+
}
194+
195+
/// Extract the first function type signature (that doesn't end with
196+
/// .generalized) from metadata in Function F.
197+
static std::string getFirstFunctionTypeSignature(Function &F) {
198+
SmallVector<std::pair<unsigned, MDNode *>, 4> MDs;
199+
F.getAllMetadata(MDs);
200+
201+
for (const auto &MD : MDs) {
202+
if (MD.first != LLVMContext::MD_type)
203+
continue;
204+
if (MDNode *Node = MD.second) {
205+
if (Node->getNumOperands() <= 1)
206+
continue;
207+
auto *str = dyn_cast<MDString>(Node->getOperand(1));
208+
if (!str)
209+
continue;
210+
211+
std::string signature = str->getString().str();
212+
if (!StringRef(signature).ends_with(".generalized"))
213+
return signature;
214+
}
215+
}
216+
return "";
217+
}
218+
219+
/// Extract a type ID from MD_type metadata in Function F (0 if not found).
220+
static uint64_t getFunctionTypeId(Function &F) {
221+
SmallVector<std::pair<unsigned, MDNode *>, 4> MDs;
222+
F.getAllMetadata(MDs);
223+
224+
for (const auto &MD : MDs) {
225+
if (MD.first != LLVMContext::MD_type)
226+
continue;
227+
228+
MDNode *Node = MD.second;
229+
if (!Node || Node->getNumOperands() <= 1)
230+
continue;
231+
232+
auto *MDInt = dyn_cast<ConstantAsMetadata>(Node->getOperand(1));
233+
if (!MDInt)
234+
continue;
235+
236+
auto *CI = dyn_cast<ConstantInt>(MDInt->getValue());
237+
if (CI) {
238+
return CI->getZExtValue();
239+
}
240+
}
241+
return 0;
242+
}
243+
244+
/// Detect the libcpp hardening mode from calls in the given function.
245+
static MitigationState detectLibcppHardeningMode(Function &F) {
246+
if (functionCallsFunctionWithName(F, "_libcpp_hardening_mode_enabled"))
247+
return MitigationState::EligibleEnabled;
248+
if (functionCallsFunctionWithName(F, "_libcpp_hardening_mode_disabled"))
249+
return MitigationState::EligibleDisabled;
250+
return MitigationState::Ineligible;
251+
}
252+
253+
PreservedAnalyses MitigationAnalysis::run(Module &M,
254+
ModuleAnalysisManager &AM) {
255+
json::Array jsonArray;
256+
257+
for (Function &F : M) {
258+
LLVMContext &Context = F.getContext();
259+
unsigned kindID = Context.getMDKindID("security_mitigations");
260+
MDNode *ExistingMD = F.getMetadata(kindID);
261+
if (!ExistingMD)
262+
continue;
263+
264+
MitigationInfo info;
265+
info.gmodule = ClOutputModuleName;
266+
info.function = F.getName();
267+
268+
for (unsigned i = 0; i < ExistingMD->getNumOperands(); ++i) {
269+
auto *node = dyn_cast<MDNode>(ExistingMD->getOperand(i));
270+
if (!node || node->getNumOperands() != 2)
271+
continue;
272+
273+
auto *mds = dyn_cast<MDString>(node->getOperand(0));
274+
auto *cam = dyn_cast<ConstantAsMetadata>(node->getOperand(1));
275+
if (!mds || !cam)
276+
continue;
277+
278+
if (auto *ci = cam->getValue()) {
279+
int value = ci->isOneValue() ? 1 : 0;
280+
keyAndValueToInfo(info, mds->getString(), value);
281+
}
282+
}
283+
284+
info.libcpp_hardening_mode = detectLibcppHardeningMode(F);
285+
286+
info.source_mapping = getFunctionSourcePath(F);
287+
info.type_signature = getFirstFunctionTypeSignature(F);
288+
info.type_id = getFunctionTypeId(F);
289+
290+
DEBUG_WITH_TYPE(kMitigationAnalysisDebugType, printInfo(info));
291+
jsonArray.push_back(infoToJson(info));
292+
}
293+
294+
if (!jsonArray.empty()) {
295+
std::string jsonString =
296+
formatv("{0}", json::Value(std::move(jsonArray))).str();
297+
if (!jsonString.empty()) {
298+
writeJsonToFile(jsonString, "mitigation_info.json",
299+
"Couldn't write to mitigation_info.json!");
300+
}
301+
}
302+
303+
return PreservedAnalyses::all();
304+
}

llvm/lib/Passes/PassBuilder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
#include "llvm/Analysis/MemDerefPrinter.h"
5959
#include "llvm/Analysis/MemoryDependenceAnalysis.h"
6060
#include "llvm/Analysis/MemorySSA.h"
61+
#include "llvm/Analysis/MitigationAnalysis.h"
6162
#include "llvm/Analysis/ModuleDebugInfoPrinter.h"
6263
#include "llvm/Analysis/ModuleSummaryAnalysis.h"
6364
#include "llvm/Analysis/MustExecute.h"

llvm/lib/Passes/PassBuilderPipelines.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "llvm/Analysis/CtxProfAnalysis.h"
2222
#include "llvm/Analysis/GlobalsModRef.h"
2323
#include "llvm/Analysis/InlineAdvisor.h"
24+
#include "llvm/Analysis/MitigationAnalysis.h"
2425
#include "llvm/Analysis/ProfileSummaryInfo.h"
2526
#include "llvm/Analysis/ScopedNoAliasAA.h"
2627
#include "llvm/Analysis/TypeBasedAliasAnalysis.h"
@@ -305,6 +306,11 @@ static cl::opt<std::string> InstrumentColdFuncOnlyPath(
305306
"with --pgo-instrument-cold-function-only)"),
306307
cl::Hidden);
307308

309+
static cl::opt<bool>
310+
EnableMitigationAnalysis("enable-mitigation-analysis", cl::init(false),
311+
cl::Hidden,
312+
cl::desc("Enable the MitigationAnalysis Pass"));
313+
308314
extern cl::opt<std::string> UseCtxProfile;
309315
extern cl::opt<bool> PGOInstrumentColdFunctionOnly;
310316

@@ -1852,6 +1858,9 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level,
18521858
// in the current module.
18531859
MPM.addPass(CrossDSOCFIPass());
18541860

1861+
if (EnableMitigationAnalysis)
1862+
MPM.addPass(MitigationAnalysis());
1863+
18551864
if (Level == OptimizationLevel::O0) {
18561865
// The WPD and LowerTypeTest passes need to run at -O0 to lower type
18571866
// metadata and intrinsics.

llvm/lib/Passes/PassRegistry.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ MODULE_PASS("memprof-context-disambiguation", MemProfContextDisambiguation())
105105
MODULE_PASS("memprof-module", ModuleMemProfilerPass())
106106
MODULE_PASS("mergefunc", MergeFunctionsPass())
107107
MODULE_PASS("metarenamer", MetaRenamerPass())
108+
MODULE_PASS("mitigation-analysis", MitigationAnalysis())
108109
MODULE_PASS("module-inline", ModuleInlinerPass())
109110
MODULE_PASS("name-anon-globals", NameAnonGlobalPass())
110111
MODULE_PASS("no-op-module", NoOpModulePass())

0 commit comments

Comments
 (0)