diff --git a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp --- a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp @@ -31,6 +31,7 @@ #include "PostfixOperatorCheck.h" #include "ProperlySeededRandomGeneratorCheck.h" #include "SetLongJmpCheck.h" +#include "SignalHandlerCheck.h" #include "StaticObjectExceptionCheck.h" #include "StrToNumCheck.h" #include "ThrownExceptionTypeCheck.h" @@ -109,6 +110,8 @@ // POS CheckFactories.registerCheck( "cert-pos44-c"); + // SIG + CheckFactories.registerCheck("cert-sig30-c"); // STR CheckFactories.registerCheck( "cert-str34-c"); diff --git a/clang-tools-extra/clang-tidy/cert/CMakeLists.txt b/clang-tools-extra/clang-tidy/cert/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/cert/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cert/CMakeLists.txt @@ -15,6 +15,7 @@ PostfixOperatorCheck.cpp ProperlySeededRandomGeneratorCheck.cpp SetLongJmpCheck.cpp + SignalHandlerCheck.cpp StaticObjectExceptionCheck.cpp StrToNumCheck.cpp ThrownExceptionTypeCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cert/SignalHandlerCheck.h b/clang-tools-extra/clang-tidy/cert/SignalHandlerCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cert/SignalHandlerCheck.h @@ -0,0 +1,42 @@ +//===--- SignalHandlerCheck.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_CERT_SIGNALHANDLERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_SIGNALHANDLERCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/StringSet.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checker for SEI CERT rule SIG30-C +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-signal-handler-check.html +class SignalHandlerCheck : public ClangTidyCheck { +public: + SignalHandlerCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void reportBug(const FunctionDecl *CalledFunction, const Expr *CallOrRef, + const CallExpr *SignalCall, const FunctionDecl *HandlerDecl); + bool isSystemCallAllowed(const FunctionDecl *FD) const; + + static llvm::StringSet<> StrictConformingFunctions; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_SIGNALHANDLERCHECK_H diff --git a/clang-tools-extra/clang-tidy/cert/SignalHandlerCheck.cpp b/clang-tools-extra/clang-tidy/cert/SignalHandlerCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cert/SignalHandlerCheck.cpp @@ -0,0 +1,184 @@ +//===--- SignalHandlerCheck.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 "SignalHandlerCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/CallGraph.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +static bool isSystemCall(const FunctionDecl *FD) { + // Find a possible redeclaration in system header. + return llvm::any_of(FD->redecls(), [](const FunctionDecl *D) { + return D->getASTContext().getSourceManager().isInSystemHeader( + D->getLocation()); + }); +} + +AST_MATCHER(FunctionDecl, isSystemCall) { return isSystemCall(&Node); } + +namespace { +class FunctionCallCollector + : public RecursiveASTVisitor { +public: + using CallbackType = std::function; + + FunctionCallCollector(CallbackType FindCallback) + : FindCallback{FindCallback} {} + + bool VisitCallExpr(const CallExpr *CE) { + FindCallback(CE); + return true; + } + +private: + CallbackType FindCallback; +}; +} // namespace + +llvm::StringSet<> SignalHandlerCheck::StrictConformingFunctions{ + "signal", "abort", "_Exit", "quick_exit"}; + +SignalHandlerCheck::SignalHandlerCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +bool SignalHandlerCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + // FIXME: Make the checker useful on C++ code. + if (LangOpts.CPlusPlus) + return false; + + return true; +} + +void SignalHandlerCheck::registerMatchers(MatchFinder *Finder) { + auto SignalFunction = functionDecl(hasAnyName("::signal", "::std::signal"), + parameterCountIs(2), isSystemCall()); + auto HandlerExpr = + declRefExpr(hasDeclaration(functionDecl().bind("handler_decl")), + unless(isExpandedFromMacro("SIG_IGN")), + unless(isExpandedFromMacro("SIG_DFL"))) + .bind("handler_expr"); + Finder->addMatcher( + callExpr(callee(SignalFunction), hasArgument(1, HandlerExpr)) + .bind("register_call"), + this); +} + +void SignalHandlerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *SignalCall = Result.Nodes.getNodeAs("register_call"); + const auto *HandlerDecl = + Result.Nodes.getNodeAs("handler_decl"); + const auto *HandlerExpr = Result.Nodes.getNodeAs("handler_expr"); + + // Visit each function encountered in the callgraph only once. + llvm::DenseSet SeenFunctions; + + // The worklist of the callgraph visitation algorithm. + std::deque CalledFunctions; + + auto ProcessFunction = [&](const FunctionDecl *F, const Expr *CallOrRef) { + // Ensure that canonical declaration is used. + F = F->getCanonicalDecl(); + + // Do not visit function if already encountered. + if (!SeenFunctions.insert(F).second) + return true; + + // Check if the call is allowed. + // Non-system calls are not considered. + if (isSystemCall(F)) { + if (isSystemCallAllowed(F)) + return true; + + reportBug(F, CallOrRef, SignalCall, HandlerDecl); + + return false; + } + + // Get the body of the encountered non-system call function. + const FunctionDecl *FBody; + if (!F->hasBody(FBody)) { + reportBug(F, CallOrRef, SignalCall, HandlerDecl); + return false; + } + + // Collect all called functions. + auto Matches = match(decl(forEachDescendant(callExpr().bind("call"))), + *FBody, FBody->getASTContext()); + for (const auto &Match : Matches) { + const auto *CE = Match.getNodeAs("call"); + if (isa(CE->getCalleeDecl())) + CalledFunctions.push_back(CE); + } + /*FunctionCallCollector Collector{[&CalledFunctions](const CallExpr *CE) { + if (isa(CE->getCalleeDecl())) + CalledFunctions.push_back(CE); + }}; + Collector.TraverseStmt(FBody->getBody());*/ + + return true; + }; + + if (!ProcessFunction(HandlerDecl, HandlerExpr)) + return; + + // Visit the definition of every function referenced by the handler function. + // Check for allowed function calls. + while (!CalledFunctions.empty()) { + const CallExpr *FunctionCall = CalledFunctions.front(); + CalledFunctions.pop_front(); + // At insertion we have already ensured that only function calls are there. + const auto *F = cast(FunctionCall->getCalleeDecl()); + + if (!ProcessFunction(F, FunctionCall)) + break; + } +} + +bool SignalHandlerCheck::isSystemCallAllowed(const FunctionDecl *FD) const { + const IdentifierInfo *II = FD->getIdentifier(); + // Unnamed functions are not explicitly allowed. + if (!II) + return false; + + if (StrictConformingFunctions.count(II->getName())) + return true; + + return false; +} + +void SignalHandlerCheck::reportBug(const FunctionDecl *CalledFunction, + const Expr *CallOrRef, + const CallExpr *SignalCall, + const FunctionDecl *HandlerDecl) { + diag(CallOrRef->getBeginLoc(), + "'%0' may not be asynchronous-safe; " + "calling it from a signal handler may be dangerous") + << CalledFunction->getNameAsString(); + diag(SignalCall->getSourceRange().getBegin(), + "signal handler registered here", DiagnosticIDs::Note); + diag(HandlerDecl->getBeginLoc(), "handler function declared here", + DiagnosticIDs::Note); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -103,6 +103,15 @@ Added an option `GetConfigPerFile` to support including files which use different naming styles. +New checks +^^^^^^^^^^ + +- New :doc:`cert-sig30-c + ` check. + + Finds functions registered as signal handlers that call non asynchronous-safe + functions. + Improvements to include-fixer ----------------------------- diff --git a/clang-tools-extra/docs/clang-tidy/checks/cert-sig30-c.rst b/clang-tools-extra/docs/clang-tidy/checks/cert-sig30-c.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cert-sig30-c.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - cert-sig30-c + +cert-sig30-c +============ + +Finds functions registered as signal handlers that call non asynchronous-safe +functions. Any function that cannot be determined to be an asynchronous-safe +function call is assumed to be non-asynchronous-safe by the checker, +including user functions for which only the declaration is visible. +User function calls with visible definition are checked recursively. + +The minimal list of asynchronous-safe system functions is: +``abort()``, ``_Exit()``, ``quick_exit()`` and ``signal()`` +(for ``signal`` there are additional conditions that are not checked). + +This check corresponds to the CERT C Coding Standard rule +`SIG30-C. Call only asynchronous-safe functions within signal handlers +`_. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -115,6 +115,7 @@ `cert-msc51-cpp `_, `cert-oop57-cpp `_, `cert-oop58-cpp `_, + `cert-sig30-c `_, `clang-analyzer-core.DynamicTypePropagation `_, `clang-analyzer-core.uninitialized.CapturedBlockVariable `_, `clang-analyzer-cplusplus.InnerPointer `_, diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cert-sig30-c_cpp.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cert-sig30-c_cpp.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cert-sig30-c_cpp.h @@ -0,0 +1,38 @@ +//===--- Header for test cert-sig30-c.cpp -----------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#define SIGINT 1 +#define SIG_IGN std::_sig_ign +#define SIG_DFL std::_sig_dfl + +namespace std { + +void _sig_ign(int); +void _sig_dfl(int); + +typedef void (*sighandler_t)(int); +sighandler_t signal(int signum, sighandler_t handler); + +void abort(); +void _Exit(int __status); +void quick_exit(int __status); + +void something(int); + +struct SysStruct { + void operator<<(int); +}; + +} // namespace std + +namespace system_other { + +typedef void (*sighandler_t)(int); +sighandler_t signal(int signum, sighandler_t handler); + +} // namespace system_other diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/signal.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/signal.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/signal.h @@ -0,0 +1,22 @@ +//===--- signal.h - Stub header for tests -----------------------*- 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 _SIGNAL_H_ +#define _SIGNAL_H_ + +void _sig_ign(int); +void _sig_dfl(int); + +#define SIGINT 1 +#define SIG_IGN _sig_ign +#define SIG_DFL _sig_dfl + +typedef void (*sighandler_t)(int); +sighandler_t signal(int signum, sighandler_t handler); + +#endif // _SIGNAL_H_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdlib.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdlib.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdlib.h @@ -0,0 +1,18 @@ +//===--- stdlib.h - Stub header for tests -----------------------*- 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 _STDLIB_H_ +#define _STDLIB_H_ + +void abort(void); +void _Exit(int __status); +void quick_exit(int __status); + +void other_call(int); + +#endif // _STDLIB_H_ diff --git a/clang-tools-extra/test/clang-tidy/checkers/cert-sig30-c.c b/clang-tools-extra/test/clang-tidy/checkers/cert-sig30-c.c new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cert-sig30-c.c @@ -0,0 +1,81 @@ +// RUN: %check_clang_tidy %s cert-sig30-c %t -- -- -isystem %S/Inputs/Headers + +// The function should be classified as system call even if there is a +// declaration before or after the one in a system header. +int printf(const char *, ...); + +typedef void (*sighandler_t)(int); +sighandler_t signal(int signum, sighandler_t handler); + +#include "signal.h" +#include "stdio.h" +#include "stdlib.h" + +int printf(const char *, ...); + +sighandler_t signal(int signum, sighandler_t handler); + +void handler_abort(int) { + abort(); +} + +void handler__Exit(int) { + _Exit(0); +} + +void handler_quick_exit(int) { + quick_exit(0); +} + +void handler_other(int) { + printf("1234"); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'printf' may not be asynchronous-safe; calling it from a signal handler may be dangerous [cert-sig30-c] +} + +void handler_signal(int) { + // FIXME: It is only OK to call signal with the current signal number. + signal(0, SIG_DFL); +} + +void f_ok() { + abort(); +} + +void f_bad() { + printf("1234"); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'printf' may not be asynchronous-safe; calling it from a signal handler may be dangerous [cert-sig30-c] +} + +void f_extern(); + +void handler_ok(int) { + f_ok(); +} + +void handler_bad(int) { + f_bad(); +} + +void handler_extern(int) { + f_extern(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'f_extern' may not be asynchronous-safe; calling it from a signal handler may be dangerous [cert-sig30-c] +} + +void test() { + signal(SIGINT, handler_abort); + signal(SIGINT, handler__Exit); + signal(SIGINT, handler_quick_exit); + signal(SIGINT, handler_signal); + signal(SIGINT, handler_other); + + signal(SIGINT, handler_ok); + signal(SIGINT, handler_bad); + signal(SIGINT, handler_extern); + + signal(SIGINT, quick_exit); + signal(SIGINT, other_call); + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 'other_call' may not be asynchronous-safe; calling it from a signal handler may be dangerous [cert-sig30-c] + + signal(SIGINT, SIG_IGN); + signal(SIGINT, SIG_DFL); +}