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 "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include "clang/Lex/PPCallbacks.h" 13 #include "clang/Lex/Preprocessor.h" 14 #include <cassert> 15 16 using namespace clang::ast_matchers; 17 using namespace llvm; 18 19 namespace clang::tidy::bugprone { 20 21 static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions = 22 "ReportMoreUnsafeFunctions"; 23 24 static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId = 25 "FunctionNamesWithAnnexKReplacement"; 26 static constexpr llvm::StringLiteral FunctionNamesId = "FunctionsNames"; 27 static constexpr llvm::StringLiteral AdditionalFunctionNamesId = 28 "AdditionalFunctionsNames"; 29 static constexpr llvm::StringLiteral DeclRefId = "DRE"; 30 31 static std::optional<std::string> 32 getAnnexKReplacementFor(StringRef FunctionName) { 33 return StringSwitch<std::string>(FunctionName) 34 .Case("strlen", "strnlen_s") 35 .Case("wcslen", "wcsnlen_s") 36 .Default((Twine{FunctionName} + "_s").str()); 37 } 38 39 static StringRef getReplacementFor(StringRef FunctionName, 40 bool IsAnnexKAvailable) { 41 if (IsAnnexKAvailable) { 42 // Try to find a better replacement from Annex K first. 43 StringRef AnnexKReplacementFunction = 44 StringSwitch<StringRef>(FunctionName) 45 .Cases("asctime", "asctime_r", "asctime_s") 46 .Case("gets", "gets_s") 47 .Default({}); 48 if (!AnnexKReplacementFunction.empty()) 49 return AnnexKReplacementFunction; 50 } 51 52 // FIXME: Some of these functions are available in C++ under "std::", and 53 // should be matched and suggested. 54 return StringSwitch<StringRef>(FunctionName) 55 .Cases("asctime", "asctime_r", "strftime") 56 .Case("gets", "fgets") 57 .Case("rewind", "fseek") 58 .Case("setbuf", "setvbuf"); 59 } 60 61 static StringRef getReplacementForAdditional(StringRef FunctionName, 62 bool IsAnnexKAvailable) { 63 if (IsAnnexKAvailable) { 64 // Try to find a better replacement from Annex K first. 65 StringRef AnnexKReplacementFunction = StringSwitch<StringRef>(FunctionName) 66 .Case("bcopy", "memcpy_s") 67 .Case("bzero", "memset_s") 68 .Default({}); 69 70 if (!AnnexKReplacementFunction.empty()) 71 return AnnexKReplacementFunction; 72 } 73 74 return StringSwitch<StringRef>(FunctionName) 75 .Case("bcmp", "memcmp") 76 .Case("bcopy", "memcpy") 77 .Case("bzero", "memset") 78 .Case("getpw", "getpwuid") 79 .Case("vfork", "posix_spawn"); 80 } 81 82 /// \returns The rationale for replacing the function \p FunctionName with the 83 /// safer alternative. 84 static StringRef getRationaleFor(StringRef FunctionName) { 85 return StringSwitch<StringRef>(FunctionName) 86 .Cases("asctime", "asctime_r", "ctime", 87 "is not bounds-checking and non-reentrant") 88 .Cases("bcmp", "bcopy", "bzero", "is deprecated") 89 .Cases("fopen", "freopen", "has no exclusive access to the opened file") 90 .Case("gets", "is insecure, was deprecated and removed in C11 and C++14") 91 .Case("getpw", "is dangerous as it may overflow the provided buffer") 92 .Cases("rewind", "setbuf", "has no error detection") 93 .Case("vfork", "is insecure as it can lead to denial of service " 94 "situations in the parent process") 95 .Default("is not bounds-checking"); 96 } 97 98 /// Calculates whether Annex K is available for the current translation unit 99 /// based on the macro definitions and the language options. 100 /// 101 /// The result is cached and saved in \p CacheVar. 102 static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP, 103 const LangOptions &LO) { 104 if (CacheVar.has_value()) 105 return *CacheVar; 106 107 if (!LO.C11) 108 // TODO: How is "Annex K" available in C++ mode? 109 return (CacheVar = false).value(); 110 111 assert(PP && "No Preprocessor registered."); 112 113 if (!PP->isMacroDefined("__STDC_LIB_EXT1__") || 114 !PP->isMacroDefined("__STDC_WANT_LIB_EXT1__")) 115 return (CacheVar = false).value(); 116 117 const auto *MI = 118 PP->getMacroInfo(PP->getIdentifierInfo("__STDC_WANT_LIB_EXT1__")); 119 if (!MI || MI->tokens_empty()) 120 return (CacheVar = false).value(); 121 122 const Token &T = MI->tokens().back(); 123 if (!T.isLiteral() || !T.getLiteralData()) 124 return (CacheVar = false).value(); 125 126 CacheVar = StringRef(T.getLiteralData(), T.getLength()) == "1"; 127 return CacheVar.value(); 128 } 129 130 UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name, 131 ClangTidyContext *Context) 132 : ClangTidyCheck(Name, Context), 133 ReportMoreUnsafeFunctions( 134 Options.get(OptionNameReportMoreUnsafeFunctions, true)) {} 135 136 void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 137 Options.store(Opts, OptionNameReportMoreUnsafeFunctions, 138 ReportMoreUnsafeFunctions); 139 } 140 141 void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) { 142 if (getLangOpts().C11) { 143 // Matching functions with safe replacements only in Annex K. 144 auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName( 145 "::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen", "::fscanf", 146 "::fwprintf", "::fwscanf", "::getenv", "::gmtime", "::localtime", 147 "::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove", "::memset", 148 "::printf", "::qsort", "::scanf", "::snprintf", "::sprintf", "::sscanf", 149 "::strcat", "::strcpy", "::strerror", "::strlen", "::strncat", 150 "::strncpy", "::strtok", "::swprintf", "::swscanf", "::vfprintf", 151 "::vfscanf", "::vfwprintf", "::vfwscanf", "::vprintf", "::vscanf", 152 "::vsnprintf", "::vsprintf", "::vsscanf", "::vswprintf", "::vswscanf", 153 "::vwprintf", "::vwscanf", "::wcrtomb", "::wcscat", "::wcscpy", 154 "::wcslen", "::wcsncat", "::wcsncpy", "::wcsrtombs", "::wcstok", 155 "::wcstombs", "::wctomb", "::wmemcpy", "::wmemmove", "::wprintf", 156 "::wscanf"); 157 Finder->addMatcher( 158 declRefExpr(to(functionDecl(FunctionNamesWithAnnexKReplacementMatcher) 159 .bind(FunctionNamesWithAnnexKReplacementId))) 160 .bind(DeclRefId), 161 this); 162 } 163 164 // Matching functions with replacements without Annex K. 165 auto FunctionNamesMatcher = 166 hasAnyName("::asctime", "asctime_r", "::gets", "::rewind", "::setbuf"); 167 Finder->addMatcher( 168 declRefExpr(to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId))) 169 .bind(DeclRefId), 170 this); 171 172 if (ReportMoreUnsafeFunctions) { 173 // Matching functions with replacements without Annex K, at user request. 174 auto AdditionalFunctionNamesMatcher = 175 hasAnyName("::bcmp", "::bcopy", "::bzero", "::getpw", "::vfork"); 176 Finder->addMatcher( 177 declRefExpr(to(functionDecl(AdditionalFunctionNamesMatcher) 178 .bind(AdditionalFunctionNamesId))) 179 .bind(DeclRefId), 180 this); 181 } 182 } 183 184 void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) { 185 const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefId); 186 const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl()); 187 assert(DeclRef && FuncDecl && "No valid matched node in check()"); 188 189 const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>( 190 FunctionNamesWithAnnexKReplacementId); 191 const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(FunctionNamesId); 192 const auto *Additional = 193 Result.Nodes.getNodeAs<FunctionDecl>(AdditionalFunctionNamesId); 194 assert((AnnexK || Normal || Additional) && "No valid match category."); 195 196 bool AnnexKIsAvailable = 197 isAnnexKAvailable(IsAnnexKAvailable, PP, getLangOpts()); 198 StringRef FunctionName = FuncDecl->getName(); 199 const std::optional<std::string> ReplacementFunctionName = 200 [&]() -> std::optional<std::string> { 201 if (AnnexK) { 202 if (AnnexKIsAvailable) 203 return getAnnexKReplacementFor(FunctionName); 204 return std::nullopt; 205 } 206 207 if (Normal) 208 return getReplacementFor(FunctionName, AnnexKIsAvailable).str(); 209 210 if (Additional) 211 return getReplacementForAdditional(FunctionName, AnnexKIsAvailable).str(); 212 213 llvm_unreachable("Unhandled match category"); 214 }(); 215 if (!ReplacementFunctionName) 216 return; 217 218 diag(DeclRef->getExprLoc(), "function %0 %1; '%2' should be used instead") 219 << FuncDecl << getRationaleFor(FunctionName) 220 << ReplacementFunctionName.value() << DeclRef->getSourceRange(); 221 } 222 223 void UnsafeFunctionsCheck::registerPPCallbacks( 224 const SourceManager &SM, Preprocessor *PP, 225 Preprocessor * /*ModuleExpanderPP*/) { 226 this->PP = PP; 227 } 228 229 void UnsafeFunctionsCheck::onEndOfTranslationUnit() { 230 this->PP = nullptr; 231 IsAnnexKAvailable.reset(); 232 } 233 234 } // namespace clang::tidy::bugprone 235