Index: clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -39,6 +39,7 @@ #include "NoEscapeCheck.h" #include "NotNullTerminatedResultCheck.h" #include "ParentVirtualCallCheck.h" +#include "PercentNFormatSpecifierCheck.h" #include "PosixReturnCheck.h" #include "RedundantBranchConditionCheck.h" #include "ReservedIdentifierCheck.h" @@ -138,6 +139,8 @@ "bugprone-not-null-terminated-result"); CheckFactories.registerCheck( "bugprone-parent-virtual-call"); + CheckFactories.registerCheck( + "bugprone-percent-n-format-specifier"); CheckFactories.registerCheck( "bugprone-posix-return"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -34,6 +34,7 @@ NoEscapeCheck.cpp NotNullTerminatedResultCheck.cpp ParentVirtualCallCheck.cpp + PercentNFormatSpecifierCheck.cpp PosixReturnCheck.cpp RedundantBranchConditionCheck.cpp ReservedIdentifierCheck.cpp Index: clang-tools-extra/clang-tidy/bugprone/PercentNFormatSpecifierCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/PercentNFormatSpecifierCheck.h @@ -0,0 +1,36 @@ +//===--- PercentNFormatSpecifierCheck.h - clang-tidy ------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_PERCENTNFORMATSPECIFIERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_PERCENTNFORMATSPECIFIERCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds any usage of the %n inside the format string of a call to printf, +/// scanf or any of their derivatives. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-percent-n-format-specifier.html +class PercentNFormatSpecifierCheck : public ClangTidyCheck { +public: + PercentNFormatSpecifierCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context){}; + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_PERCENTNFORMATSPECIFIERCHECK_H Index: clang-tools-extra/clang-tidy/bugprone/PercentNFormatSpecifierCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/PercentNFormatSpecifierCheck.cpp @@ -0,0 +1,95 @@ +//===--- PercentNFormatSpecifierCheck.cpp - clang-tidy --------------------===// +// +// 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 "PercentNFormatSpecifierCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/FormatString.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +class Handler : public analyze_format_string::FormatStringHandler { + const char *Beg; // Beginning of string literal + + bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS, + const char *startSpecifier, + unsigned specifierLen) override { + const analyze_printf::PrintfConversionSpecifier &CS = + FS.getConversionSpecifier(); + if (CS.getKind() == analyze_format_string::ConversionSpecifier::nArg) { + FormatSpecifierLocations.emplace_back(CS.getStart() - Beg); + return true; + } + return false; + } + +public: + std::vector FormatSpecifierLocations; + Handler(const char *Beg) : Beg(Beg) {} +}; + +void PercentNFormatSpecifierCheck::registerMatchers(MatchFinder *Finder) { + auto PrintfDecl = functionDecl(hasName("::printf")); + auto FprintfDecl = functionDecl(hasName("::fprintf")); + auto VfprintfDecl = functionDecl(hasName("::vfprintf")); + auto SprintfDecl = functionDecl(hasName("::sprintf")); + auto SnprintfDecl = functionDecl(hasName("::snprintf")); + auto VprintfDecl = functionDecl(hasName("::vprintf")); + auto VsprintfDecl = functionDecl(hasName("::vsprintf")); + auto VsnprintfDecl = functionDecl(hasName("::vsnprintf")); + + auto ScanfDecl = functionDecl(hasName("::scanf")); + auto SscanfDecl = functionDecl(hasName("::sscanf")); + auto FscanfDecl = functionDecl(hasName("::fscanf")); + auto VfscanfDecl = functionDecl(hasName("::vfscanf")); + auto VscanfDecl = functionDecl(hasName("::vscanf")); + auto VsscanfDecl = functionDecl(hasName("::vsscanf")); + + Finder->addMatcher( + callExpr(callee(functionDecl( + anyOf(PrintfDecl, VprintfDecl, ScanfDecl, VscanfDecl))), + hasArgument(0, stringLiteral().bind("StringLiteral"))), + this); + Finder->addMatcher( + callExpr(callee(functionDecl(anyOf(FprintfDecl, SprintfDecl, VfprintfDecl, + VsprintfDecl, SscanfDecl, FscanfDecl, + VfscanfDecl, VsscanfDecl))), + hasArgument(1, stringLiteral().bind("StringLiteral"))), + this); + Finder->addMatcher( + callExpr(callee(functionDecl(anyOf(SnprintfDecl, VsnprintfDecl))), + hasArgument(2, stringLiteral().bind("StringLiteral"))), + this); +} + +void PercentNFormatSpecifierCheck::check( + const MatchFinder::MatchResult &Result) { + const StringLiteral *FormatString = + Result.Nodes.getNodeAs("StringLiteral"); + StringRef FormatStringRef = FormatString->getString(); + Handler H(FormatStringRef.begin()); + analyze_format_string::ParsePrintfString( + H, FormatStringRef.begin(), FormatStringRef.end(), getLangOpts(), + Result.Context->getTargetInfo(), false); + for (const auto byteNo : H.FormatSpecifierLocations) { + const auto loc = FormatString->getLocationOfByte( + byteNo, Result.Context->getSourceManager(), getLangOpts(), + Result.Context->getTargetInfo()); + diag(loc, "usage of %%n can lead to unsafe writing to memory"); + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang Index: clang-tools-extra/docs/clang-tidy/checks/bugprone-percent-n-format-specifier.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/bugprone-percent-n-format-specifier.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - bugprone-percent-n-format-specifier + +bugprone-percent-n-format-specifier +=================================== + +Finds any usage of the %n format specifier inside the format string +of a call to printf, scanf, or any of their derivatives. The %n format +specifier can lead to unsafe writing to memory. + +.. code-block:: c++ + void f(int * i, ...) { + char buffer [100]; + FILE * pFile; + pFile = fopen("myFile.txt", "w+"); + va_list args; + va_start(args, i); + + // Will match any of these printf derivatives + printf("%n", i); + fprintf(pFile, "%n", i); + snprintf(buffer, 100, "%n", i); + sprintf(buffer, "%n", i); + vprintf("%n", args); + vfprintf(pFile, "%n", args); + vsnprintf(buffer, 100, "%n", args); + vsprintf(buffer, "%n", args); + + // Will match any of these scanf derivatives + scanf("%n", i); + fscanf(pFile, "%n", i); + sscanf(buffer, "%n", i); + vscanf("%n", args); + vfscanf(pFile, "%n", args); + vsscanf(buffer, "%n", args); + + va_end(args); + } Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-percent-n-format-specifier.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/bugprone-percent-n-format-specifier.cpp @@ -0,0 +1,57 @@ +// RUN: %check_clang_tidy %s bugprone-percent-n-format-specifier %t + +#include + +void testVariableArgumentList(int *I, ...) { + char Buffer[100]; + FILE *pFile; + pFile = fopen("myFile.txt", "w+"); + va_list args; + va_start(args, I); + + // CHECK-MESSAGES: [[@LINE+1]]:21: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + vfprintf(pFile, "%n", args); + // CHECK-MESSAGES: [[@LINE+1]]:13: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + vprintf("%n", args); + // CHECK-MESSAGES: [[@LINE+1]]:28: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + vsnprintf(Buffer, 100, "%n", args); + // CHECK-MESSAGES: [[@LINE+1]]:22: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + vsprintf(Buffer, "%n", args); + + // CHECK-MESSAGES: [[@LINE+1]]:20: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + vfscanf(pFile, "%n", args); + // CHECK-MESSAGES: [[@LINE+1]]:12: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + vscanf("%n", args); + // CHECK-MESSAGES: [[@LINE+1]]:21: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + vsscanf(Buffer, "%n", args); + + va_end(args); +} + +void testPrintf() { + int *I = nullptr; + char Buffer[100]; + FILE *pFile; + pFile = fopen("myFile.txt", "w+"); + // CHECK-MESSAGES: [[@LINE+1]]:12: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + printf("%n", I); + // CHECK-MESSAGES: [[@LINE+1]]:22: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + fprintf(pFile, "I %n", I); + // CHECK-MESSAGES: [[@LINE+1]]:27: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + snprintf(Buffer, 100, "%n", I); + // CHECK-MESSAGES: [[@LINE+1]]:21: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + sprintf(Buffer, "%n", I); +} + +void testScanf() { + int *I = nullptr; + char Buffer[100]; + FILE *pFile; + pFile = fopen("myFile.txt", "w+"); + // CHECK-MESSAGES: [[@LINE+1]]:19: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + fscanf(pFile, "%n", I); + // CHECK-MESSAGES: [[@LINE+1]]:11: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + scanf("%n", I); + // CHECK-MESSAGES: [[@LINE+1]]:20: warning: usage of %n can lead to unsafe writing to memory [bugprone-percent-n-format-specifier] + sscanf(Buffer, "%n", I); +}