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