[clang] [llvm] [analysis] Software Bill of Mitigations (PR #130103)
Matthew Levy via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 6 05:20:52 PST 2025
https://github.com/matthewlevy97 created https://github.com/llvm/llvm-project/pull/130103
The goal of this stack is to provide a metric to audit the deployed mitigations in a binary and where they are enabled at function level granularity. This will enable tracking of where we do and don't have mitigations, versus the current approach of tracking where flags are passed.
Two flags are added to control this behavior:
1) Compile time `-fmitigation-analysis` to generate the metadata pertaining to mitigation enablement/disablement
2) Link time `-enable-mitigation-analysis` to generate the output JSON when building with LTO
>From fb04b7bf5f2b668bf354632fc53e7521f44880c9 Mon Sep 17 00:00:00 2001
From: Matt Levy <levymatt0 at gmail.com>
Date: Wed, 5 Mar 2025 12:36:02 -0500
Subject: [PATCH 1/2] [clang][CodeGen] Software Bill of Mitigations Metadata
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.
There are some challenges posed by this like:
- Some mitigations are not emitted by the compiler, but the preprocessor
- Some mitigations are lowered later during IR -> MIR (stack cookies)
---
clang/include/clang/Basic/CodeGenOptions.def | 1 +
clang/include/clang/Driver/Options.td | 6 ++
clang/lib/CodeGen/CGBuiltin.cpp | 10 +++
clang/lib/CodeGen/CGClass.cpp | 3 +
clang/lib/CodeGen/CGDecl.cpp | 4 +
clang/lib/CodeGen/CGExpr.cpp | 5 ++
clang/lib/CodeGen/CGExprCXX.cpp | 6 ++
clang/lib/CodeGen/CMakeLists.txt | 1 +
clang/lib/CodeGen/CodeGenModule.cpp | 22 +++++
clang/lib/CodeGen/MitigationTagging.cpp | 84 ++++++++++++++++++++
clang/lib/CodeGen/MitigationTagging.h | 45 +++++++++++
clang/lib/Driver/ToolChains/Clang.cpp | 3 +
12 files changed, 190 insertions(+)
create mode 100644 clang/lib/CodeGen/MitigationTagging.cpp
create mode 100644 clang/lib/CodeGen/MitigationTagging.h
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index a7f5f1abbb825..76a46ac3e592b 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -191,6 +191,7 @@ CODEGENOPT(NoTypeCheck , 1, 0) ///< Set when -Wa,--no-type-check is enable
CODEGENOPT(MisExpect , 1, 0) ///< Set when -Wmisexpect is enabled
CODEGENOPT(EnableSegmentedStacks , 1, 0) ///< Set when -fsplit-stack is enabled.
CODEGENOPT(StackClashProtector, 1, 0) ///< Set when -fstack-clash-protection is enabled.
+CODEGENOPT(MitigationAnalysis, 1, 0) ///< Set when -fmitigation-analysis is enabled.
CODEGENOPT(NoImplicitFloat , 1, 0) ///< Set when -mno-implicit-float is enabled.
CODEGENOPT(NullPointerIsValid , 1, 0) ///< Assume Null pointer deference is defined.
CODEGENOPT(OpenCLCorrectlyRoundedDivSqrt, 1, 0) ///< -cl-fp32-correctly-rounded-divide-sqrt
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index d0414aba35209..e50bb5c1c2cb4 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3891,6 +3891,12 @@ defm split_stack : BoolFOption<"split-stack",
CodeGenOpts<"EnableSegmentedStacks">, DefaultFalse,
NegFlag<SetFalse, [], [ClangOption], "Wouldn't use segmented stack">,
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Use segmented stack">>;
+defm mitigation_analysis : BoolFOption<"mitigation-analysis",
+ CodeGenOpts<"MitigationAnalysis">, DefaultFalse,
+ PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
+ NegFlag<SetFalse, [], [ClangOption], "Disable">,
+ BothFlags<[], [ClangOption], " mitigation analysis">>,
+ DocBrief<"Instrument mitigations (CFI, Stack Protectors, Auto-Var-Init, StackClashProtection) to analyze their coverage">;
def fstack_protector_all : Flag<["-"], "fstack-protector-all">, Group<f_Group>,
HelpText<"Enable stack protectors for all functions">;
defm stack_clash_protection : BoolFOption<"stack-clash-protection",
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index ab8f19b25fa66..4e180bb1a87cf 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -21,6 +21,7 @@
#include "CodeGenFunction.h"
#include "CodeGenModule.h"
#include "ConstantEmitter.h"
+#include "MitigationTagging.h"
#include "PatternInit.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
@@ -83,6 +84,8 @@ static void initializeAlloca(CodeGenFunction &CGF, AllocaInst *AI, Value *Size,
switch (CGF.getLangOpts().getTrivialAutoVarInit()) {
case LangOptions::TrivialAutoVarInitKind::Uninitialized:
// Nothing to initialize.
+ AttachMitigationMetadataToFunction(CGF, MitigationKey::AUTO_VAR_INIT,
+ false);
return;
case LangOptions::TrivialAutoVarInitKind::Zero:
Byte = CGF.Builder.getInt8(0x00);
@@ -94,6 +97,7 @@ static void initializeAlloca(CodeGenFunction &CGF, AllocaInst *AI, Value *Size,
break;
}
}
+ AttachMitigationMetadataToFunction(CGF, MitigationKey::AUTO_VAR_INIT, true);
if (CGF.CGM.stopAutoInit())
return;
auto *I = CGF.Builder.CreateMemSet(AI, Byte, Size, AlignmentInBytes);
@@ -4642,6 +4646,9 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
AI->setAlignment(SuitableAlignmentInBytes);
if (BuiltinID != Builtin::BI__builtin_alloca_uninitialized)
initializeAlloca(*this, AI, Size, SuitableAlignmentInBytes);
+ else
+ AttachMitigationMetadataToFunction(*this, MitigationKey::AUTO_VAR_INIT,
+ false);
LangAS AAS = getASTAllocaAddressSpace();
LangAS EAS = E->getType()->getPointeeType().getAddressSpace();
if (AAS != EAS) {
@@ -4664,6 +4671,9 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
AI->setAlignment(AlignmentInBytes);
if (BuiltinID != Builtin::BI__builtin_alloca_with_align_uninitialized)
initializeAlloca(*this, AI, Size, AlignmentInBytes);
+ else
+ AttachMitigationMetadataToFunction(*this, MitigationKey::AUTO_VAR_INIT,
+ false);
LangAS AAS = getASTAllocaAddressSpace();
LangAS EAS = E->getType()->getPointeeType().getAddressSpace();
if (AAS != EAS) {
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index e54fd543f217b..6858cd35615d3 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -16,6 +16,7 @@
#include "CGDebugInfo.h"
#include "CGRecordLayout.h"
#include "CodeGenFunction.h"
+#include "MitigationTagging.h"
#include "TargetInfo.h"
#include "clang/AST/Attr.h"
#include "clang/AST/CXXInheritance.h"
@@ -2847,6 +2848,8 @@ void CodeGenFunction::EmitTypeMetadataCodeForVCall(const CXXRecordDecl *RD,
Builder.CreateCall(CGM.getIntrinsic(IID), {VTable, TypeId});
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::assume), TypeTest);
}
+
+ AttachMitigationMetadataToFunction(*this, MitigationKey::CFI_VCALL, false);
}
void CodeGenFunction::EmitVTablePtrCheckForCall(const CXXRecordDecl *RD,
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 9cd5885aaae51..39f94b093b23c 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -20,6 +20,7 @@
#include "CodeGenModule.h"
#include "ConstantEmitter.h"
#include "EHScopeStack.h"
+#include "MitigationTagging.h"
#include "PatternInit.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
@@ -1974,6 +1975,9 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
? LangOptions::TrivialAutoVarInitKind::Uninitialized
: getContext().getLangOpts().getTrivialAutoVarInit());
+ AttachMitigationMetadataToFunction(
+ *this, MitigationKey::AUTO_VAR_INIT,
+ trivialAutoVarInit != LangOptions::TrivialAutoVarInitKind::Uninitialized);
auto initializeWhatIsTechnicallyUninitialized = [&](Address Loc) {
if (trivialAutoVarInit ==
LangOptions::TrivialAutoVarInitKind::Uninitialized)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 191912ca7d800..dda5ea63b93d1 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -22,6 +22,7 @@
#include "CodeGenFunction.h"
#include "CodeGenModule.h"
#include "ConstantEmitter.h"
+#include "MitigationTagging.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTLambda.h"
@@ -6091,6 +6092,10 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
// function pointer is a member of the bit set for the function type.
if (SanOpts.has(SanitizerKind::CFIICall) &&
(!TargetDecl || !isa<FunctionDecl>(TargetDecl))) {
+ AttachMitigationMetadataToFunction(
+ *this, MitigationKey::CFI_ICALL,
+ SanOpts.has(clang::SanitizerKind::CFIICall));
+
SanitizerScope SanScope(this);
EmitSanitizerStatReport(llvm::SanStat_CFI_ICall);
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index d4e14f4574b87..9ae192d60ca91 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -16,6 +16,7 @@
#include "CGObjCRuntime.h"
#include "CodeGenFunction.h"
#include "ConstantEmitter.h"
+#include "MitigationTagging.h"
#include "TargetInfo.h"
#include "clang/Basic/CodeGenOptions.h"
#include "clang/CodeGen/CGFunctionInfo.h"
@@ -416,6 +417,11 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
std::tie(VTable, RD) = CGM.getCXXABI().LoadVTablePtr(
*this, This.getAddress(), CalleeDecl->getParent());
EmitVTablePtrCheckForCall(RD, VTable, CFITCK_NVCall, CE->getBeginLoc());
+ AttachMitigationMetadataToFunction(*this, MitigationKey::CFI_NVCALL,
+ true);
+ } else if (MD->getParent()->isDynamicClass()) {
+ AttachMitigationMetadataToFunction(*this, MitigationKey::CFI_NVCALL,
+ false);
}
if (getLangOpts().AppleKext && MD->isVirtual() && HasQualifier)
diff --git a/clang/lib/CodeGen/CMakeLists.txt b/clang/lib/CodeGen/CMakeLists.txt
index 05ab6671453f8..56b98742d35af 100644
--- a/clang/lib/CodeGen/CMakeLists.txt
+++ b/clang/lib/CodeGen/CMakeLists.txt
@@ -110,6 +110,7 @@ add_clang_library(clangCodeGen
LinkInModulesPass.cpp
MacroPPCallbacks.cpp
MicrosoftCXXABI.cpp
+ MitigationTagging.cpp
ModuleBuilder.cpp
ObjectFilePCHContainerWriter.cpp
PatternInit.cpp
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index cec8f1233b663..f8c65a21b0783 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -26,6 +26,7 @@
#include "CodeGenPGO.h"
#include "ConstantEmitter.h"
#include "CoverageMappingGen.h"
+#include "MitigationTagging.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTLambda.h"
@@ -2490,6 +2491,8 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D,
if ((!D || !D->hasAttr<NoUwtableAttr>()) && CodeGenOpts.UnwindTables)
B.addUWTableAttr(llvm::UWTableKind(CodeGenOpts.UnwindTables));
+ AttachMitigationMetadataToFunction(*F, MitigationKey::STACK_CLASH_PROTECTION,
+ CodeGenOpts.StackClashProtector);
if (CodeGenOpts.StackClashProtector)
B.addAttribute("probe-stack", "inline-asm");
@@ -2512,6 +2515,25 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D,
else if (isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPReq))
B.addAttribute(llvm::Attribute::StackProtectReq);
+ bool noStackProtectionAttr = D && D->hasAttr<NoStackProtectorAttr>();
+ AttachMitigationMetadataToFunction(
+ *F, MitigationKey::STACK_PROTECTOR,
+ !noStackProtectionAttr &&
+ (isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPOn) ||
+ isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPStrong) ||
+ isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPReq)));
+
+ AttachMitigationMetadataToFunction(
+ *F, MitigationKey::STACK_PROTECTOR_STRONG,
+ !noStackProtectionAttr &&
+ (isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPStrong) ||
+ isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPReq)));
+
+ AttachMitigationMetadataToFunction(
+ *F, MitigationKey::STACK_PROTECTOR_ALL,
+ !noStackProtectionAttr &&
+ isStackProtectorOn(LangOpts, getTriple(), LangOptions::SSPReq));
+
if (!D) {
// Non-entry HLSL functions must always be inlined.
if (getLangOpts().HLSL && !F->hasFnAttribute(llvm::Attribute::NoInline))
diff --git a/clang/lib/CodeGen/MitigationTagging.cpp b/clang/lib/CodeGen/MitigationTagging.cpp
new file mode 100644
index 0000000000000..d247dcab84059
--- /dev/null
+++ b/clang/lib/CodeGen/MitigationTagging.cpp
@@ -0,0 +1,84 @@
+//===--- MitigationTagging.cpp - Emit LLVM Code from ASTs for a Module ----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This enables tagging functions with metadata to indicate mitigations are
+// applied to them.
+//
+//===----------------------------------------------------------------------===//
+
+#include "MitigationTagging.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Metadata.h"
+
+#include <string>
+#include <vector>
+
+namespace clang {
+namespace CodeGen {
+
+inline static std::string
+MitigationKeyToString(enum MitigationKey key) noexcept {
+ switch (key) {
+ case MitigationKey::AUTO_VAR_INIT:
+ return "auto-var-init";
+ case MitigationKey::STACK_CLASH_PROTECTION:
+ return "stack-clash-protection";
+ case MitigationKey::STACK_PROTECTOR:
+ return "stack-protector";
+ case MitigationKey::STACK_PROTECTOR_STRONG:
+ return "stack-protector-strong";
+ case MitigationKey::STACK_PROTECTOR_ALL:
+ return "stack-protector-all";
+ case MitigationKey::CFI_VCALL:
+ return "cfi-vcall";
+ case MitigationKey::CFI_ICALL:
+ return "cfi-icall";
+ case MitigationKey::CFI_NVCALL:
+ return "cfi-nvcall";
+ }
+}
+
+void AttachMitigationMetadataToFunction(llvm::Function &F,
+ enum MitigationKey key, bool enabled) {
+ llvm::LLVMContext &Context = F.getContext();
+
+ unsigned kindID = Context.getMDKindID("security_mitigations");
+
+ llvm::Metadata *ValueMD = llvm::ConstantAsMetadata::get(
+ llvm::ConstantInt::get(llvm::Type::getInt1Ty(Context), enabled));
+ llvm::MDString *KeyMD =
+ llvm::MDString::get(Context, MitigationKeyToString(key));
+
+ llvm::MDNode *NewMD = llvm::MDNode::get(Context, {KeyMD, ValueMD});
+ llvm::MDNode *ExistingMD = F.getMetadata(kindID);
+
+ if (ExistingMD) {
+ std::vector<llvm::Metadata *> MDs;
+ for (unsigned i = 0, e = ExistingMD->getNumOperands(); i != e; ++i) {
+ MDs.push_back(ExistingMD->getOperand(i));
+ }
+ MDs.push_back(NewMD);
+
+ llvm::MDNode *CombinedMD = llvm::MDNode::get(Context, MDs);
+ F.setMetadata(kindID, CombinedMD);
+ } else {
+ F.setMetadata(kindID, NewMD);
+ }
+}
+
+void AttachMitigationMetadataToFunction(CodeGenFunction &CGF,
+ enum MitigationKey key, bool enabled) {
+ if (!CGF.CGM.getCodeGenOpts().MitigationAnalysis) {
+ return;
+ }
+ AttachMitigationMetadataToFunction(*(CGF.CurFn), key, enabled);
+}
+
+} // namespace CodeGen
+} // namespace clang
diff --git a/clang/lib/CodeGen/MitigationTagging.h b/clang/lib/CodeGen/MitigationTagging.h
new file mode 100644
index 0000000000000..fa80f63439734
--- /dev/null
+++ b/clang/lib/CodeGen/MitigationTagging.h
@@ -0,0 +1,45 @@
+//===--- MitigationTagging.h - Emit LLVM Code from ASTs for a Module ------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This enables tagging functions with metadata to indicate mitigations are
+// applied to them.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_CODEGEN_MITIGATIONTAGGING_H
+#define LLVM_CLANG_LIB_CODEGEN_MITIGATIONTAGGING_H
+
+#include "CodeGenFunction.h"
+#include "llvm/IR/Function.h"
+
+namespace clang {
+namespace CodeGen {
+
+enum class MitigationKey {
+ AUTO_VAR_INIT,
+
+ STACK_CLASH_PROTECTION,
+
+ STACK_PROTECTOR,
+ STACK_PROTECTOR_STRONG,
+ STACK_PROTECTOR_ALL,
+
+ CFI_VCALL,
+ CFI_ICALL,
+ CFI_NVCALL,
+};
+
+void AttachMitigationMetadataToFunction(llvm::Function &F,
+ enum MitigationKey key, bool enabled);
+void AttachMitigationMetadataToFunction(CodeGenFunction &CGF,
+ enum MitigationKey key, bool enabled);
+
+} // namespace CodeGen
+} // namespace clang
+
+#endif
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 4ebbd241d2f0b..b1d960c41a274 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7033,6 +7033,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
RenderSCPOptions(TC, Args, CmdArgs);
RenderTrivialAutoVarInitOptions(D, TC, Args, CmdArgs);
+ Args.addOptInFlag(CmdArgs, options::OPT_fmitigation_analysis,
+ options::OPT_fno_mitigation_analysis);
+
Args.AddLastArg(CmdArgs, options::OPT_fswift_async_fp_EQ);
Args.addOptInFlag(CmdArgs, options::OPT_mstackrealign,
>From a4b4ee141193f13cffa42308392b246414573375 Mon Sep 17 00:00:00 2001
From: Matt Levy <levymatt0 at gmail.com>
Date: Wed, 5 Mar 2025 13:27:47 -0500
Subject: [PATCH 2/2] [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.
---
.../llvm/Analysis/MitigationAnalysis.h | 27 ++
llvm/lib/Analysis/CMakeLists.txt | 1 +
llvm/lib/Analysis/MitigationAnalysis.cpp | 304 ++++++++++++++++++
llvm/lib/Passes/PassBuilder.cpp | 1 +
llvm/lib/Passes/PassBuilderPipelines.cpp | 9 +
llvm/lib/Passes/PassRegistry.def | 1 +
6 files changed, 343 insertions(+)
create mode 100644 llvm/include/llvm/Analysis/MitigationAnalysis.h
create mode 100644 llvm/lib/Analysis/MitigationAnalysis.cpp
diff --git a/llvm/include/llvm/Analysis/MitigationAnalysis.h b/llvm/include/llvm/Analysis/MitigationAnalysis.h
new file mode 100644
index 0000000000000..c32f55f79fdb9
--- /dev/null
+++ b/llvm/include/llvm/Analysis/MitigationAnalysis.h
@@ -0,0 +1,27 @@
+#ifndef LLVM_ANALYSIS_MITIGATIONANALYSIS_H
+#define LLVM_ANALYSIS_MITIGATIONANALYSIS_H
+
+#include "llvm/IR/InstrTypes.h"
+#include "llvm/IR/Intrinsics.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/IR/ValueHandle.h"
+#include "llvm/Pass.h"
+
+namespace llvm {
+
+class MitigationAnalysis : public AnalysisInfoMixin<MitigationAnalysis> {
+ friend AnalysisInfoMixin<MitigationAnalysis>;
+ static AnalysisKey Key;
+
+ static constexpr const char *kMitigationAnalysisDebugType =
+ "mitigation_analysis";
+
+public:
+ using Result = PreservedAnalyses;
+ Result run(Module &M, ModuleAnalysisManager &AM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_ANALYSIS_MITIGATIONANALYSIS_H
diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt
index a44f6c6a135ef..662e170c3c247 100644
--- a/llvm/lib/Analysis/CMakeLists.txt
+++ b/llvm/lib/Analysis/CMakeLists.txt
@@ -103,6 +103,7 @@ add_llvm_component_library(LLVMAnalysis
MemoryProfileInfo.cpp
MemorySSA.cpp
MemorySSAUpdater.cpp
+ MitigationAnalysis.cpp
ModelUnderTrainingRunner.cpp
ModuleDebugInfoPrinter.cpp
ModuleSummaryAnalysis.cpp
diff --git a/llvm/lib/Analysis/MitigationAnalysis.cpp b/llvm/lib/Analysis/MitigationAnalysis.cpp
new file mode 100644
index 0000000000000..08cbd3ae9ce48
--- /dev/null
+++ b/llvm/lib/Analysis/MitigationAnalysis.cpp
@@ -0,0 +1,304 @@
+#include "llvm/Analysis/MitigationAnalysis.h"
+#include "llvm/IR/DebugInfo.h"
+#include "llvm/IR/DebugLoc.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+#include <unordered_map>
+
+using namespace llvm;
+
+AnalysisKey MitigationAnalysis::Key;
+
+// Add a command line flag for the module name
+static cl::opt<std::string>
+ ClOutputModuleName("mitigation-analysis-dso-name", cl::Optional,
+ cl::desc("DSO name for the module"),
+ cl::init("unknown"));
+
+enum class MitigationState { Ineligible, EligibleDisabled, EligibleEnabled };
+
+static const std::unordered_map<MitigationState, std::string> mapStateToString =
+ {
+ {MitigationState::Ineligible, "N/A"},
+ {MitigationState::EligibleDisabled, "Disabled"},
+ {MitigationState::EligibleEnabled, "Enabled"},
+};
+
+struct MitigationInfo {
+ MitigationState auto_var_init = MitigationState::Ineligible;
+ MitigationState cfi_icall = MitigationState::Ineligible;
+ MitigationState cfi_vcall = MitigationState::Ineligible;
+ MitigationState cfi_nvcall = MitigationState::Ineligible;
+ MitigationState stack_clash_protection = MitigationState::Ineligible;
+ MitigationState stack_protector = MitigationState::Ineligible;
+ MitigationState stack_protector_strong = MitigationState::Ineligible;
+ MitigationState stack_protector_all = MitigationState::Ineligible;
+ MitigationState libcpp_hardening_mode = MitigationState::Ineligible;
+ std::string source_mapping = "(unknown)";
+ std::string type_signature = "??";
+ uint64_t type_id = 0;
+ std::string function;
+ std::string gmodule;
+};
+
+/// Convert an integer value (0 or 1) to the appropriate MitigationState.
+static inline MitigationState valToState(int value) {
+ switch (value) {
+ case 0:
+ return MitigationState::EligibleDisabled;
+ case 1:
+ return MitigationState::EligibleEnabled;
+ default:
+ return MitigationState::Ineligible;
+ }
+}
+
+/// Print out fields in MitigationInfo for debugging/verification purposes.
+#ifndef NDEBUG
+static void printInfo(const MitigationInfo &info) {
+ dbgs() << "module: " << info.gmodule << "\n";
+ dbgs() << "function: " << info.function << "\n";
+ dbgs() << "source_location: " << info.source_mapping << "\n";
+ dbgs() << "auto-var-init: " << mapStateToString.at(info.auto_var_init)
+ << "\n";
+ dbgs() << "cfi-icall: " << mapStateToString.at(info.cfi_icall) << "\n";
+ dbgs() << "cfi-vcall: " << mapStateToString.at(info.cfi_vcall) << "\n";
+ dbgs() << "cfi-nvcall: " << mapStateToString.at(info.cfi_nvcall) << "\n";
+ dbgs() << "stack-clash-protection: "
+ << mapStateToString.at(info.stack_clash_protection) << "\n";
+ dbgs() << "stack-protector: " << mapStateToString.at(info.stack_protector)
+ << "\n";
+ dbgs() << "stack-protector-strong: "
+ << mapStateToString.at(info.stack_protector_strong) << "\n";
+ dbgs() << "stack-protector-all: "
+ << mapStateToString.at(info.stack_protector_all) << "\n";
+ dbgs() << "libcpp-hardening-mode: "
+ << mapStateToString.at(info.libcpp_hardening_mode) << "\n";
+ dbgs() << "type_signature: " << info.type_signature << "\n";
+ dbgs() << "type_id: " << info.type_id << "\n\n";
+}
+#endif
+
+/// Convert a mitigation key + integer value into the appropriate field
+/// of MitigationInfo. This replaces a long chain of if/else statements.
+static void keyAndValueToInfo(MitigationInfo &info, StringRef key, int value) {
+ static constexpr struct {
+ const StringRef Key;
+ MitigationState MitigationInfo::*Field;
+ } Mappings[] = {
+ {StringRef("auto-var-init"), &MitigationInfo::auto_var_init},
+ {StringRef("cfi-icall"), &MitigationInfo::cfi_icall},
+ {StringRef("cfi-vcall"), &MitigationInfo::cfi_vcall},
+ {StringRef("cfi-nvcall"), &MitigationInfo::cfi_nvcall},
+ {StringRef("stack-clash-protection"),
+ &MitigationInfo::stack_clash_protection},
+ {StringRef("stack-protector"), &MitigationInfo::stack_protector},
+ {StringRef("stack-protector-strong"),
+ &MitigationInfo::stack_protector_strong},
+ {StringRef("stack-protector-all"), &MitigationInfo::stack_protector_all},
+ {StringRef("libcpp-hardening-mode"),
+ &MitigationInfo::libcpp_hardening_mode},
+ };
+
+ for (const auto &Mapping : Mappings) {
+ if (key == Mapping.Key) {
+ info.*(Mapping.Field) = valToState(value);
+ break;
+ }
+ }
+}
+
+/// Retrieve the first valid source path for the given function.
+static std::string getFunctionSourcePath(const Function &F) {
+ if (const DISubprogram *SP = F.getSubprogram()) {
+ std::string Dir = SP->getDirectory().str();
+ std::string File = SP->getFilename().str();
+ unsigned Line = SP->getLine();
+ if (!Dir.empty() && !File.empty())
+ return Dir + "/" + File + ":" + std::to_string(Line);
+ }
+ return "(unknown)";
+}
+
+/// Write the given JSON string to file with a lock. On error, prints to stderr.
+static void writeJsonToFile(const std::string &jsonString,
+ const std::string &fileName,
+ const std::string &errorMsg) {
+ std::error_code errCode;
+ raw_fd_ostream OutputStream(fileName, errCode, sys::fs::CD_CreateAlways,
+ sys::fs::FA_Read | sys::fs::FA_Write,
+ sys::fs::OF_Text | sys::fs::OF_UpdateAtime);
+ if (errCode) {
+ errs() << errorMsg << "\n";
+ errs() << errCode.message() << "\n";
+ return;
+ }
+
+ if (auto lock = OutputStream.lock()) {
+ OutputStream << jsonString << "\n";
+ if (OutputStream.has_error()) {
+ errs() << errorMsg << "\n";
+ errs() << jsonString << "\n";
+ }
+ } else {
+ errs() << errorMsg << "\n";
+ errs() << "Couldn't acquire lock for " << fileName << "\n";
+ }
+}
+
+/// Convert a MitigationInfo struct to a JSON object.
+static json::Object infoToJson(const MitigationInfo &info) {
+ json::Object object;
+ object["auto_var_init"] = mapStateToString.at(info.auto_var_init);
+ object["cfi_icall"] = mapStateToString.at(info.cfi_icall);
+ object["cfi_vcall"] = mapStateToString.at(info.cfi_vcall);
+ object["cfi_nvcall"] = mapStateToString.at(info.cfi_nvcall);
+ object["stack_clash_protection"] =
+ mapStateToString.at(info.stack_clash_protection);
+ object["stack_protector"] = mapStateToString.at(info.stack_protector);
+ object["stack_protector_strong"] =
+ mapStateToString.at(info.stack_protector_strong);
+ object["stack_protector_all"] = mapStateToString.at(info.stack_protector_all);
+ object["libcpp_hardening_mode"] =
+ mapStateToString.at(info.libcpp_hardening_mode);
+ object["source_mapping"] = info.source_mapping;
+ object["function"] = info.function;
+ object["type_signature"] = info.type_signature;
+ object["type_id"] = (uint64_t)info.type_id;
+ object["module"] = info.gmodule;
+ return object;
+}
+
+/// Return true if function F calls a function whose name contains
+/// targetFunctionName.
+static bool functionCallsFunctionWithName(Function &F,
+ StringRef targetFunctionName) {
+ for (Instruction &I : instructions(F)) {
+ auto *callInst = dyn_cast<CallInst>(&I);
+ if (!callInst)
+ continue;
+
+ Function *calledFunction = callInst->getCalledFunction();
+ if (calledFunction &&
+ calledFunction->getName().contains(targetFunctionName))
+ return true;
+ }
+ return false;
+}
+
+/// Extract the first function type signature (that doesn't end with
+/// .generalized) from metadata in Function F.
+static std::string getFirstFunctionTypeSignature(Function &F) {
+ SmallVector<std::pair<unsigned, MDNode *>, 4> MDs;
+ F.getAllMetadata(MDs);
+
+ for (const auto &MD : MDs) {
+ if (MD.first != LLVMContext::MD_type)
+ continue;
+ if (MDNode *Node = MD.second) {
+ if (Node->getNumOperands() <= 1)
+ continue;
+ auto *str = dyn_cast<MDString>(Node->getOperand(1));
+ if (!str)
+ continue;
+
+ std::string signature = str->getString().str();
+ if (!StringRef(signature).ends_with(".generalized"))
+ return signature;
+ }
+ }
+ return "";
+}
+
+/// Extract a type ID from MD_type metadata in Function F (0 if not found).
+static uint64_t getFunctionTypeId(Function &F) {
+ SmallVector<std::pair<unsigned, MDNode *>, 4> MDs;
+ F.getAllMetadata(MDs);
+
+ for (const auto &MD : MDs) {
+ if (MD.first != LLVMContext::MD_type)
+ continue;
+
+ MDNode *Node = MD.second;
+ if (!Node || Node->getNumOperands() <= 1)
+ continue;
+
+ auto *MDInt = dyn_cast<ConstantAsMetadata>(Node->getOperand(1));
+ if (!MDInt)
+ continue;
+
+ auto *CI = dyn_cast<ConstantInt>(MDInt->getValue());
+ if (CI) {
+ return CI->getZExtValue();
+ }
+ }
+ return 0;
+}
+
+/// Detect the libcpp hardening mode from calls in the given function.
+static MitigationState detectLibcppHardeningMode(Function &F) {
+ if (functionCallsFunctionWithName(F, "_libcpp_hardening_mode_enabled"))
+ return MitigationState::EligibleEnabled;
+ if (functionCallsFunctionWithName(F, "_libcpp_hardening_mode_disabled"))
+ return MitigationState::EligibleDisabled;
+ return MitigationState::Ineligible;
+}
+
+PreservedAnalyses MitigationAnalysis::run(Module &M,
+ ModuleAnalysisManager &AM) {
+ json::Array jsonArray;
+
+ for (Function &F : M) {
+ LLVMContext &Context = F.getContext();
+ unsigned kindID = Context.getMDKindID("security_mitigations");
+ MDNode *ExistingMD = F.getMetadata(kindID);
+ if (!ExistingMD)
+ continue;
+
+ MitigationInfo info;
+ info.gmodule = ClOutputModuleName;
+ info.function = F.getName();
+
+ for (unsigned i = 0; i < ExistingMD->getNumOperands(); ++i) {
+ auto *node = dyn_cast<MDNode>(ExistingMD->getOperand(i));
+ if (!node || node->getNumOperands() != 2)
+ continue;
+
+ auto *mds = dyn_cast<MDString>(node->getOperand(0));
+ auto *cam = dyn_cast<ConstantAsMetadata>(node->getOperand(1));
+ if (!mds || !cam)
+ continue;
+
+ if (auto *ci = cam->getValue()) {
+ int value = ci->isOneValue() ? 1 : 0;
+ keyAndValueToInfo(info, mds->getString(), value);
+ }
+ }
+
+ info.libcpp_hardening_mode = detectLibcppHardeningMode(F);
+
+ info.source_mapping = getFunctionSourcePath(F);
+ info.type_signature = getFirstFunctionTypeSignature(F);
+ info.type_id = getFunctionTypeId(F);
+
+ DEBUG_WITH_TYPE(kMitigationAnalysisDebugType, printInfo(info));
+ jsonArray.push_back(infoToJson(info));
+ }
+
+ if (!jsonArray.empty()) {
+ std::string jsonString =
+ formatv("{0}", json::Value(std::move(jsonArray))).str();
+ if (!jsonString.empty()) {
+ writeJsonToFile(jsonString, "mitigation_info.json",
+ "Couldn't write to mitigation_info.json!");
+ }
+ }
+
+ return PreservedAnalyses::all();
+}
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 8080059f0bb03..a4dcafd3b5ab4 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -58,6 +58,7 @@
#include "llvm/Analysis/MemDerefPrinter.h"
#include "llvm/Analysis/MemoryDependenceAnalysis.h"
#include "llvm/Analysis/MemorySSA.h"
+#include "llvm/Analysis/MitigationAnalysis.h"
#include "llvm/Analysis/ModuleDebugInfoPrinter.h"
#include "llvm/Analysis/ModuleSummaryAnalysis.h"
#include "llvm/Analysis/MustExecute.h"
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 546a5eb1ec283..e2c6ebc7017e5 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -21,6 +21,7 @@
#include "llvm/Analysis/CtxProfAnalysis.h"
#include "llvm/Analysis/GlobalsModRef.h"
#include "llvm/Analysis/InlineAdvisor.h"
+#include "llvm/Analysis/MitigationAnalysis.h"
#include "llvm/Analysis/ProfileSummaryInfo.h"
#include "llvm/Analysis/ScopedNoAliasAA.h"
#include "llvm/Analysis/TypeBasedAliasAnalysis.h"
@@ -305,6 +306,11 @@ static cl::opt<std::string> InstrumentColdFuncOnlyPath(
"with --pgo-instrument-cold-function-only)"),
cl::Hidden);
+static cl::opt<bool>
+ EnableMitigationAnalysis("enable-mitigation-analysis", cl::init(false),
+ cl::Hidden,
+ cl::desc("Enable the MitigationAnalysis Pass"));
+
extern cl::opt<std::string> UseCtxProfile;
extern cl::opt<bool> PGOInstrumentColdFunctionOnly;
@@ -1852,6 +1858,9 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level,
// in the current module.
MPM.addPass(CrossDSOCFIPass());
+ if (EnableMitigationAnalysis)
+ MPM.addPass(MitigationAnalysis());
+
if (Level == OptimizationLevel::O0) {
// The WPD and LowerTypeTest passes need to run at -O0 to lower type
// metadata and intrinsics.
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index bfd952df25e98..0a4732ab1e7ff 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -105,6 +105,7 @@ MODULE_PASS("memprof-context-disambiguation", MemProfContextDisambiguation())
MODULE_PASS("memprof-module", ModuleMemProfilerPass())
MODULE_PASS("mergefunc", MergeFunctionsPass())
MODULE_PASS("metarenamer", MetaRenamerPass())
+MODULE_PASS("mitigation-analysis", MitigationAnalysis())
MODULE_PASS("module-inline", ModuleInlinerPass())
MODULE_PASS("name-anon-globals", NameAnonGlobalPass())
MODULE_PASS("no-op-module", NoOpModulePass())
More information about the cfe-commits
mailing list