|
| 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 | +} |
0 commit comments