1 //===--- UnsafeFunctionsCheck.cpp - clang-tidy ----------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "UnsafeFunctionsCheck.h" 10 #include "../utils/OptionsUtils.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/Lex/PPCallbacks.h" 14 #include "clang/Lex/Preprocessor.h" 15 #include <cassert> 16 17 using namespace clang::ast_matchers; 18 using namespace llvm; 19 20 namespace clang::tidy::bugprone { 21 22 static constexpr llvm::StringLiteral OptionNameCustomFunctions = 23 "CustomFunctions"; 24 static constexpr llvm::StringLiteral OptionNameReportDefaultFunctions = 25 "ReportDefaultFunctions"; 26 static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions = 27 "ReportMoreUnsafeFunctions"; 28 29 static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId = 30 "FunctionNamesWithAnnexKReplacement"; 31 static constexpr llvm::StringLiteral FunctionNamesId = "FunctionsNames"; 32 static constexpr llvm::StringLiteral AdditionalFunctionNamesId = 33 "AdditionalFunctionsNames"; 34 static constexpr llvm::StringLiteral CustomFunctionNamesId = 35 "CustomFunctionNames"; 36 static constexpr llvm::StringLiteral DeclRefId = "DRE"; 37 38 static std::optional<std::string> 39 getAnnexKReplacementFor(StringRef FunctionName) { 40 return StringSwitch<std::string>(FunctionName) 41 .Case("strlen", "strnlen_s") 42 .Case("wcslen", "wcsnlen_s") 43 .Default((Twine{FunctionName} + "_s").str()); 44 } 45 46 static StringRef getReplacementFor(StringRef FunctionName, 47 bool IsAnnexKAvailable) { 48 if (IsAnnexKAvailable) { 49 // Try to find a better replacement from Annex K first. 50 StringRef AnnexKReplacementFunction = 51 StringSwitch<StringRef>(FunctionName) 52 .Cases("asctime", "asctime_r", "asctime_s") 53 .Case("gets", "gets_s") 54 .Default({}); 55 if (!AnnexKReplacementFunction.empty()) 56 return AnnexKReplacementFunction; 57 } 58 59 // FIXME: Some of these functions are available in C++ under "std::", and 60 // should be matched and suggested. 61 return StringSwitch<StringRef>(FunctionName) 62 .Cases("asctime", "asctime_r", "strftime") 63 .Case("gets", "fgets") 64 .Case("rewind", "fseek") 65 .Case("setbuf", "setvbuf"); 66 } 67 68 static StringRef getReplacementForAdditional(StringRef FunctionName, 69 bool IsAnnexKAvailable) { 70 if (IsAnnexKAvailable) { 71 // Try to find a better replacement from Annex K first. 72 StringRef AnnexKReplacementFunction = StringSwitch<StringRef>(FunctionName) 73 .Case("bcopy", "memcpy_s") 74 .Case("bzero", "memset_s") 75 .Default({}); 76 77 if (!AnnexKReplacementFunction.empty()) 78 return AnnexKReplacementFunction; 79 } 80 81 return StringSwitch<StringRef>(FunctionName) 82 .Case("bcmp", "memcmp") 83 .Case("bcopy", "memcpy") 84 .Case("bzero", "memset") 85 .Case("getpw", "getpwuid") 86 .Case("vfork", "posix_spawn"); 87 } 88 89 /// \returns The rationale for replacing the function \p FunctionName with the 90 /// safer alternative. 91 static StringRef getRationaleFor(StringRef FunctionName) { 92 return StringSwitch<StringRef>(FunctionName) 93 .Cases("asctime", "asctime_r", "ctime", 94 "is not bounds-checking and non-reentrant") 95 .Cases("bcmp", "bcopy", "bzero", "is deprecated") 96 .Cases("fopen", "freopen", "has no exclusive access to the opened file") 97 .Case("gets", "is insecure, was deprecated and removed in C11 and C++14") 98 .Case("getpw", "is dangerous as it may overflow the provided buffer") 99 .Cases("rewind", "setbuf", "has no error detection") 100 .Case("vfork", "is insecure as it can lead to denial of service " 101 "situations in the parent process") 102 .Default("is not bounds-checking"); 103 } 104 105 /// Calculates whether Annex K is available for the current translation unit 106 /// based on the macro definitions and the language options. 107 /// 108 /// The result is cached and saved in \p CacheVar. 109 static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP, 110 const LangOptions &LO) { 111 if (CacheVar.has_value()) 112 return *CacheVar; 113 114 if (!LO.C11) 115 // TODO: How is "Annex K" available in C++ mode? 116 return (CacheVar = false).value(); 117 118 assert(PP && "No Preprocessor registered."); 119 120 if (!PP->isMacroDefined("__STDC_LIB_EXT1__") || 121 !PP->isMacroDefined("__STDC_WANT_LIB_EXT1__")) 122 return (CacheVar = false).value(); 123 124 const auto *MI = 125 PP->getMacroInfo(PP->getIdentifierInfo("__STDC_WANT_LIB_EXT1__")); 126 if (!MI || MI->tokens_empty()) 127 return (CacheVar = false).value(); 128 129 const Token &T = MI->tokens().back(); 130 if (!T.isLiteral() || !T.getLiteralData()) 131 return (CacheVar = false).value(); 132 133 CacheVar = StringRef(T.getLiteralData(), T.getLength()) == "1"; 134 return CacheVar.value(); 135 } 136 137 static std::vector<UnsafeFunctionsCheck::CheckedFunction> 138 parseCheckedFunctions(StringRef Option, ClangTidyContext *Context) { 139 const std::vector<StringRef> Functions = 140 utils::options::parseStringList(Option); 141 std::vector<UnsafeFunctionsCheck::CheckedFunction> Result; 142 Result.reserve(Functions.size()); 143 144 for (StringRef Function : Functions) { 145 if (Function.empty()) 146 continue; 147 148 const auto [Name, Rest] = Function.split(','); 149 const auto [Replacement, Reason] = Rest.split(','); 150 151 if (Name.trim().empty()) { 152 Context->configurationDiag("invalid configuration value for option '%0'; " 153 "expected the name of an unsafe function") 154 << OptionNameCustomFunctions; 155 continue; 156 } 157 158 Result.push_back( 159 {Name.trim().str(), 160 matchers::MatchesAnyListedNameMatcher::NameMatcher(Name.trim()), 161 Replacement.trim().str(), Reason.trim().str()}); 162 } 163 164 return Result; 165 } 166 167 static std::string serializeCheckedFunctions( 168 const std::vector<UnsafeFunctionsCheck::CheckedFunction> &Functions) { 169 std::vector<std::string> Result; 170 Result.reserve(Functions.size()); 171 172 for (const auto &Entry : Functions) { 173 if (Entry.Reason.empty()) 174 Result.push_back(Entry.Name + "," + Entry.Replacement); 175 else 176 Result.push_back(Entry.Name + "," + Entry.Replacement + "," + 177 Entry.Reason); 178 } 179 180 return llvm::join(Result, ";"); 181 } 182 183 UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name, 184 ClangTidyContext *Context) 185 : ClangTidyCheck(Name, Context), 186 CustomFunctions(parseCheckedFunctions( 187 Options.get(OptionNameCustomFunctions, ""), Context)), 188 ReportDefaultFunctions( 189 Options.get(OptionNameReportDefaultFunctions, true)), 190 ReportMoreUnsafeFunctions( 191 Options.get(OptionNameReportMoreUnsafeFunctions, true)) {} 192 193 void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 194 Options.store(Opts, OptionNameCustomFunctions, 195 serializeCheckedFunctions(CustomFunctions)); 196 Options.store(Opts, OptionNameReportDefaultFunctions, ReportDefaultFunctions); 197 Options.store(Opts, OptionNameReportMoreUnsafeFunctions, 198 ReportMoreUnsafeFunctions); 199 } 200 201 void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) { 202 if (ReportDefaultFunctions) { 203 if (getLangOpts().C11) { 204 // Matching functions with safe replacements only in Annex K. 205 auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName( 206 "::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen", 207 "::fscanf", "::fwprintf", "::fwscanf", "::getenv", "::gmtime", 208 "::localtime", "::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove", 209 "::memset", "::printf", "::qsort", "::scanf", "::snprintf", 210 "::sprintf", "::sscanf", "::strcat", "::strcpy", "::strerror", 211 "::strlen", "::strncat", "::strncpy", "::strtok", "::swprintf", 212 "::swscanf", "::vfprintf", "::vfscanf", "::vfwprintf", "::vfwscanf", 213 "::vprintf", "::vscanf", "::vsnprintf", "::vsprintf", "::vsscanf", 214 "::vswprintf", "::vswscanf", "::vwprintf", "::vwscanf", "::wcrtomb", 215 "::wcscat", "::wcscpy", "::wcslen", "::wcsncat", "::wcsncpy", 216 "::wcsrtombs", "::wcstok", "::wcstombs", "::wctomb", "::wmemcpy", 217 "::wmemmove", "::wprintf", "::wscanf"); 218 Finder->addMatcher( 219 declRefExpr(to(functionDecl(FunctionNamesWithAnnexKReplacementMatcher) 220 .bind(FunctionNamesWithAnnexKReplacementId))) 221 .bind(DeclRefId), 222 this); 223 } 224 225 // Matching functions with replacements without Annex K. 226 auto FunctionNamesMatcher = 227 hasAnyName("::asctime", "asctime_r", "::gets", "::rewind", "::setbuf"); 228 Finder->addMatcher( 229 declRefExpr( 230 to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId))) 231 .bind(DeclRefId), 232 this); 233 234 if (ReportMoreUnsafeFunctions) { 235 // Matching functions with replacements without Annex K, at user request. 236 auto AdditionalFunctionNamesMatcher = 237 hasAnyName("::bcmp", "::bcopy", "::bzero", "::getpw", "::vfork"); 238 Finder->addMatcher( 239 declRefExpr(to(functionDecl(AdditionalFunctionNamesMatcher) 240 .bind(AdditionalFunctionNamesId))) 241 .bind(DeclRefId), 242 this); 243 } 244 } 245 246 if (!CustomFunctions.empty()) { 247 std::vector<llvm::StringRef> FunctionNames; 248 FunctionNames.reserve(CustomFunctions.size()); 249 250 for (const auto &Entry : CustomFunctions) 251 FunctionNames.push_back(Entry.Name); 252 253 auto CustomFunctionsMatcher = matchers::matchesAnyListedName(FunctionNames); 254 255 Finder->addMatcher(declRefExpr(to(functionDecl(CustomFunctionsMatcher) 256 .bind(CustomFunctionNamesId))) 257 .bind(DeclRefId), 258 this); 259 } 260 } 261 262 void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) { 263 const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefId); 264 const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl()); 265 assert(DeclRef && FuncDecl && "No valid matched node in check()"); 266 267 // Only one of these are matched at a time. 268 const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>( 269 FunctionNamesWithAnnexKReplacementId); 270 const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(FunctionNamesId); 271 const auto *Additional = 272 Result.Nodes.getNodeAs<FunctionDecl>(AdditionalFunctionNamesId); 273 const auto *Custom = 274 Result.Nodes.getNodeAs<FunctionDecl>(CustomFunctionNamesId); 275 assert((AnnexK || Normal || Additional || Custom) && 276 "No valid match category."); 277 278 bool AnnexKIsAvailable = 279 isAnnexKAvailable(IsAnnexKAvailable, PP, getLangOpts()); 280 StringRef FunctionName = FuncDecl->getName(); 281 282 if (Custom) { 283 for (const auto &Entry : CustomFunctions) { 284 if (Entry.Pattern.match(*FuncDecl)) { 285 StringRef Reason = 286 Entry.Reason.empty() ? "is marked as unsafe" : Entry.Reason.c_str(); 287 288 if (Entry.Replacement.empty()) { 289 diag(DeclRef->getExprLoc(), "function %0 %1; it should not be used") 290 << FuncDecl << Reason << Entry.Replacement 291 << DeclRef->getSourceRange(); 292 } else { 293 diag(DeclRef->getExprLoc(), 294 "function %0 %1; '%2' should be used instead") 295 << FuncDecl << Reason << Entry.Replacement 296 << DeclRef->getSourceRange(); 297 } 298 299 return; 300 } 301 } 302 303 llvm_unreachable("No custom function was matched."); 304 return; 305 } 306 307 const std::optional<std::string> ReplacementFunctionName = 308 [&]() -> std::optional<std::string> { 309 if (AnnexK) { 310 if (AnnexKIsAvailable) 311 return getAnnexKReplacementFor(FunctionName); 312 return std::nullopt; 313 } 314 315 if (Normal) 316 return getReplacementFor(FunctionName, AnnexKIsAvailable).str(); 317 318 if (Additional) 319 return getReplacementForAdditional(FunctionName, AnnexKIsAvailable).str(); 320 321 llvm_unreachable("Unhandled match category"); 322 }(); 323 if (!ReplacementFunctionName) 324 return; 325 326 diag(DeclRef->getExprLoc(), "function %0 %1; '%2' should be used instead") 327 << FuncDecl << getRationaleFor(FunctionName) 328 << ReplacementFunctionName.value() << DeclRef->getSourceRange(); 329 } 330 331 void UnsafeFunctionsCheck::registerPPCallbacks( 332 const SourceManager &SM, Preprocessor *PP, 333 Preprocessor * /*ModuleExpanderPP*/) { 334 this->PP = PP; 335 } 336 337 void UnsafeFunctionsCheck::onEndOfTranslationUnit() { 338 this->PP = nullptr; 339 IsAnnexKAvailable.reset(); 340 } 341 342 } // namespace clang::tidy::bugprone 343