================ @@ -0,0 +1,290 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "UnsafeFormatStringCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Regex.h" +#include <string> + +using namespace clang::ast_matchers; + +namespace clang::tidy::bugprone { + +static constexpr llvm::StringRef OptionNameCustomPrintfFunctions = + "CustomPrintfFunctions"; +static constexpr llvm::StringRef OptionNameCustomScanfFunctions = + "CustomScanfFunctions"; + +static constexpr llvm::StringRef BuiltInFormatBind = "format"; +static constexpr llvm::StringRef BuiltInCallBind = "call"; +static constexpr llvm::StringRef PrintfCallBind = "printfcall"; +static constexpr llvm::StringRef ScanfCallBind = "scanfcall"; + +static std::vector<UnsafeFormatStringCheck::CheckedFunction> +parseCheckedFunctions(StringRef OptionName, StringRef Option, + ClangTidyContext *Context) { + const std::vector<StringRef> Functions = + utils::options::parseStringList(Option); + std::vector<UnsafeFormatStringCheck::CheckedFunction> Result; + Result.reserve(Functions.size()); + + for (const StringRef Function : Functions) { + if (Function.empty()) + continue; + const auto [Name, ParamCount] = Function.split(','); + unsigned long Count = 0; + if (Name.trim().empty() || ParamCount.trim().empty() || + ParamCount.trim().getAsInteger(10, Count)) { + Context->configurationDiag( + "invalid configuration value for option '%0'; " + "expected <functionname>, <paramcount>; pairs.") + << OptionName; + continue; + } + Result.push_back( + {Name.trim().str(), + matchers::MatchesAnyListedRegexNameMatcher::NameMatcher(Name.trim()), + Count}); + } + + return Result; +} + +UnsafeFormatStringCheck::UnsafeFormatStringCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CustomPrintfFunctions(parseCheckedFunctions( + OptionNameCustomPrintfFunctions, + Options.get(OptionNameCustomPrintfFunctions, ""), Context)), + CustomScanfFunctions(parseCheckedFunctions( + OptionNameCustomScanfFunctions, + Options.get(OptionNameCustomScanfFunctions, ""), Context)) {} + +void UnsafeFormatStringCheck::registerMatchers(MatchFinder *Finder) { + // Matches sprintf and scanf family functions in std namespace in C++ and + // globally in C. + const auto VulnerableFunctionsArg0 = + hasAnyName("scanf", "vscanf", "wscanf", "vwscanf"); + const auto VulnerableFunctionsArg1 = + hasAnyName("sprintf", "vsprintf", "fscanf", "sscanf", "vfscanf", + "vsscanf", "fwscanf", "swscanf", "vfwscanf", "vswscanf"); + Finder->addMatcher( + callExpr( + callee(functionDecl(VulnerableFunctionsArg0, + anyOf(isInStdNamespace(), + hasDeclContext(translationUnitDecl())))), + hasArgument(0, stringLiteral().bind(BuiltInFormatBind))) + .bind(BuiltInCallBind), + this); + + Finder->addMatcher( + callExpr( + callee(functionDecl(VulnerableFunctionsArg1, + anyOf(isInStdNamespace(), + hasDeclContext(translationUnitDecl())))), + hasArgument(1, stringLiteral().bind(BuiltInFormatBind))) + .bind(BuiltInCallBind), + this); + + if (!CustomPrintfFunctions.empty()) { + std::vector<llvm::StringRef> FunctionNames; + FunctionNames.reserve(CustomPrintfFunctions.size()); + + for (const CheckedFunction &Entry : CustomPrintfFunctions) + FunctionNames.emplace_back(Entry.Name); + + const auto CustomFunctionsMatcher = + matchers::matchesAnyListedRegexName(FunctionNames); + + Finder->addMatcher(callExpr(callee(functionDecl(CustomFunctionsMatcher))) + .bind(PrintfCallBind), + this); + } + + if (!CustomScanfFunctions.empty()) { + std::vector<llvm::StringRef> FunctionNames; + FunctionNames.reserve(CustomScanfFunctions.size()); + + for (const CheckedFunction &Entry : CustomScanfFunctions) + FunctionNames.emplace_back(Entry.Name); + + const auto CustomFunctionsMatcher = + matchers::matchesAnyListedRegexName(FunctionNames); + + Finder->addMatcher(callExpr(callee(functionDecl(CustomFunctionsMatcher))) + .bind(ScanfCallBind), + this); + } +} + +std::string UnsafeFormatStringCheck::serializeConfig( + const std::vector<CheckedFunction> &Functions) { + std::vector<std::string> Result; + Result.reserve(Functions.size()); + for (const auto &Entry : Functions) + Result.push_back(Entry.Name + "," + + std::to_string(Entry.FormatStringLocation)); + return llvm::join(Result, ";"); +} + +void UnsafeFormatStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, OptionNameCustomPrintfFunctions, + serializeConfig(CustomPrintfFunctions)); + Options.store(Opts, OptionNameCustomScanfFunctions, + serializeConfig(CustomScanfFunctions)); +} + +const StringLiteral *UnsafeFormatStringCheck::getFormatLiteral( + const CallExpr *Call, const std::vector<CheckedFunction> &CustomFunctions) { + const FunctionDecl *FD = Call->getDirectCallee(); + if (!FD) + return nullptr; + for (const CheckedFunction &Entry : CustomFunctions) { + if (Entry.Pattern.match(*FD)) { + if (Entry.FormatStringLocation >= Call->getNumArgs()) + return nullptr; + const Expr *Arg = + Call->getArg(Entry.FormatStringLocation)->IgnoreImpCasts(); + return dyn_cast<StringLiteral>(Arg); + } + } + llvm_unreachable( + "The Call must match one of the functions in CustomFunctions."); +} + +static bool hasUnboundedStringSpecifier(StringRef Fmt, bool IsScanfFamily) { + size_t Pos = 0; + const size_t N = Fmt.size(); + while ((Pos = Fmt.find('%', Pos)) != StringRef::npos) { + if (Pos + 1 >= N) + break; + + // Skip %% + if (Fmt[Pos + 1] == '%') { + Pos += 2; + continue; + } + + size_t SpecPos = Pos + 1; + + // Skip flags + while (SpecPos < N && + (Fmt[SpecPos] == '-' || Fmt[SpecPos] == '+' || Fmt[SpecPos] == ' ' || + Fmt[SpecPos] == '#' || Fmt[SpecPos] == '0')) { + SpecPos++; + } + + // Check for field width + bool HasFieldWidth = false; + if (SpecPos < N && Fmt[SpecPos] == '*') { + HasFieldWidth = true; + SpecPos++; + } else { + while (SpecPos < N && isdigit(Fmt[SpecPos])) { + HasFieldWidth = true; + SpecPos++; + } + } + + // Check for precision + bool HasPrecision = false; + if (SpecPos < N && Fmt[SpecPos] == '.') { + SpecPos++; + if (SpecPos < N && Fmt[SpecPos] == '*') { + // precision in argument + HasPrecision = true; + SpecPos++; + } else if (SpecPos < N && Fmt[SpecPos] == 's') { + // precision is 0 + HasPrecision = true; + } else { + while (SpecPos < N && isdigit(Fmt[SpecPos])) { + HasPrecision = true; + SpecPos++; + } + } + } ---------------- dkrupp wrote:
done https://github.com/llvm/llvm-project/pull/168691 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
