Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[clang-tidy] Add signal-handler-check for SEI CERT rule SIG30-C
SIG30-C. Call only asynchronous-safe functions within signal handlers First version of this check, only minimal list of functions is allowed ("strictly conforming" case), for C only. Differential Revision: https://reviews.llvm.org/D87449
- Loading branch information
Showing
12 changed files
with
396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
//===--- 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 <iterator> | ||
#include <queue> | ||
|
||
using namespace clang::ast_matchers; | ||
|
||
namespace clang { | ||
namespace tidy { | ||
namespace bugprone { | ||
|
||
static bool isSystemCall(const FunctionDecl *FD) { | ||
// Find a possible redeclaration in system header. | ||
// FIXME: Looking at the canonical declaration is not the most exact way | ||
// to do this. | ||
|
||
// Most common case will be inclusion directly from a header. | ||
// This works fine by using canonical declaration. | ||
// a.c | ||
// #include <sysheader.h> | ||
|
||
// Next most common case will be extern declaration. | ||
// Can't catch this with either approach. | ||
// b.c | ||
// extern void sysfunc(void); | ||
|
||
// Canonical declaration is the first found declaration, so this works. | ||
// c.c | ||
// #include <sysheader.h> | ||
// extern void sysfunc(void); // redecl won't matter | ||
|
||
// This does not work with canonical declaration. | ||
// Probably this is not a frequently used case but may happen (the first | ||
// declaration can be in a non-system header for example). | ||
// d.c | ||
// extern void sysfunc(void); // Canonical declaration, not in system header. | ||
// #include <sysheader.h> | ||
|
||
return FD->getASTContext().getSourceManager().isInSystemHeader( | ||
FD->getCanonicalDecl()->getLocation()); | ||
} | ||
|
||
AST_MATCHER(FunctionDecl, isSystemCall) { return isSystemCall(&Node); } | ||
|
||
// This is the minimal set of safe functions. | ||
// FIXME: Add checker option to allow a POSIX compliant extended set. | ||
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<CallExpr>("register_call"); | ||
const auto *HandlerDecl = | ||
Result.Nodes.getNodeAs<FunctionDecl>("handler_decl"); | ||
const auto *HandlerExpr = Result.Nodes.getNodeAs<DeclRefExpr>("handler_expr"); | ||
|
||
// Visit each function encountered in the callgraph only once. | ||
llvm::DenseSet<const FunctionDecl *> SeenFunctions; | ||
|
||
// The worklist of the callgraph visitation algorithm. | ||
std::deque<const CallExpr *> 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<CallExpr>("call"); | ||
if (isa<FunctionDecl>(CE->getCalleeDecl())) | ||
CalledFunctions.push_back(CE); | ||
} | ||
|
||
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<FunctionDecl>(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; | ||
|
||
// FIXME: Improve for C++ (check for namespace). | ||
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; | ||
diag(SignalCall->getSourceRange().getBegin(), | ||
"signal handler registered here", DiagnosticIDs::Note); | ||
diag(HandlerDecl->getBeginLoc(), "handler function declared here", | ||
DiagnosticIDs::Note); | ||
} | ||
|
||
} // namespace bugprone | ||
} // namespace tidy | ||
} // namespace clang |
42 changes: 42 additions & 0 deletions
42
clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_BUGPRONE_SIGNALHANDLERCHECK_H | ||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIGNALHANDLERCHECK_H | ||
|
||
#include "../ClangTidyCheck.h" | ||
#include "llvm/ADT/StringSet.h" | ||
|
||
namespace clang { | ||
namespace tidy { | ||
namespace bugprone { | ||
|
||
/// Checker for signal handler functions. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-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 bugprone | ||
} // namespace tidy | ||
} // namespace clang | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIGNALHANDLERCHECK_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
clang-tools-extra/docs/clang-tidy/checks/bugprone-signal-handler.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.. title:: clang-tidy - bugprone-signal-handler | ||
|
||
bugprone-signal-handler | ||
======================= | ||
|
||
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 check handles only C code. | ||
|
||
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). | ||
The check accepts only these calls as asynchronous-safe. | ||
|
||
This check corresponds to the CERT C Coding Standard rule | ||
`SIG30-C. Call only asynchronous-safe functions within signal handlers | ||
<https://www.securecoding.cert.org/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers>`_. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.. title:: clang-tidy - cert-sig30-c | ||
.. meta:: | ||
:http-equiv=refresh: 5;URL=bugprone-signal-handler.html | ||
|
||
cert-sig30-c | ||
============ | ||
|
||
The cert-sig30-c check is an alias, please see | ||
`bugprone-signal-handler <bugprone-signal-handler.html>`_ | ||
for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/signal.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, sighandler_t); | ||
|
||
#endif // _SIGNAL_H_ |
18 changes: 18 additions & 0 deletions
18
clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdlib.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
void quick_exit(int); | ||
|
||
void other_call(int); | ||
|
||
#endif // _STDLIB_H_ |
Oops, something went wrong.