Skip to content

Commit

Permalink
[clang-tidy] Add signal-handler-check for SEI CERT rule SIG30-C
Browse files Browse the repository at this point in the history
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
balazske committed Nov 4, 2020
1 parent ea606cc commit d1b2a52
Show file tree
Hide file tree
Showing 12 changed files with 396 additions and 0 deletions.
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Expand Up @@ -40,6 +40,7 @@
#include "PosixReturnCheck.h"
#include "RedundantBranchConditionCheck.h"
#include "ReservedIdentifierCheck.h"
#include "SignalHandlerCheck.h"
#include "SignedCharMisuseCheck.h"
#include "SizeofContainerCheck.h"
#include "SizeofExpressionCheck.h"
Expand Down Expand Up @@ -133,6 +134,7 @@ class BugproneModule : public ClangTidyModule {
"bugprone-posix-return");
CheckFactories.registerCheck<ReservedIdentifierCheck>(
"bugprone-reserved-identifier");
CheckFactories.registerCheck<SignalHandlerCheck>("bugprone-signal-handler");
CheckFactories.registerCheck<SignedCharMisuseCheck>(
"bugprone-signed-char-misuse");
CheckFactories.registerCheck<SizeofContainerCheck>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Expand Up @@ -35,6 +35,7 @@ add_clang_library(clangTidyBugproneModule
PosixReturnCheck.cpp
RedundantBranchConditionCheck.cpp
ReservedIdentifierCheck.cpp
SignalHandlerCheck.cpp
SignedCharMisuseCheck.cpp
SizeofContainerCheck.cpp
SizeofExpressionCheck.cpp
Expand Down
186 changes: 186 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.cpp
@@ -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 clang-tools-extra/clang-tidy/bugprone/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_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
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp
Expand Up @@ -11,6 +11,7 @@
#include "../ClangTidyModuleRegistry.h"
#include "../bugprone/BadSignalToKillThreadCheck.h"
#include "../bugprone/ReservedIdentifierCheck.h"
#include "../bugprone/SignalHandlerCheck.h"
#include "../bugprone/SignedCharMisuseCheck.h"
#include "../bugprone/SpuriouslyWakeUpFunctionsCheck.h"
#include "../bugprone/UnhandledSelfAssignmentCheck.h"
Expand Down Expand Up @@ -109,6 +110,8 @@ class CERTModule : public ClangTidyModule {
// POS
CheckFactories.registerCheck<bugprone::BadSignalToKillThreadCheck>(
"cert-pos44-c");
// SIG
CheckFactories.registerCheck<bugprone::SignalHandlerCheck>("cert-sig30-c");
// STR
CheckFactories.registerCheck<bugprone::SignedCharMisuseCheck>(
"cert-str34-c");
Expand Down
12 changes: 12 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Expand Up @@ -106,6 +106,18 @@ New checks
Finds condition variables in nested ``if`` statements that were also checked
in the outer ``if`` statement and were not changed.

- New :doc:`bugprone-signal-handler
<clang-tidy/checks/bugprone-signal-handler>` check.

Finds functions registered as signal handlers that call non asynchronous-safe
functions.

- New :doc:`cert-sig30-c
<clang-tidy/checks/cert-sig30-c>` check.

Alias to the :doc:`bugprone-signal-handler
<clang-tidy/checks/bugprone-signal-handler>` check.

- New :doc:`readability-function-cognitive-complexity
<clang-tidy/checks/readability-function-cognitive-complexity>` check.

Expand Down
@@ -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>`_.
10 changes: 10 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/cert-sig30-c.rst
@@ -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.
2 changes: 2 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Expand Up @@ -77,6 +77,7 @@ Clang-Tidy Checks
`bugprone-posix-return <bugprone-posix-return.html>`_, "Yes"
`bugprone-redundant-branch-condition <bugprone-redundant-branch-condition.html>`_, "Yes"
`bugprone-reserved-identifier <bugprone-reserved-identifier.html>`_, "Yes"
`bugprone-signal-handler <bugprone-signal-handler.html>`_,
`bugprone-signed-char-misuse <bugprone-signed-char-misuse.html>`_,
`bugprone-sizeof-container <bugprone-sizeof-container.html>`_,
`bugprone-sizeof-expression <bugprone-sizeof-expression.html>`_,
Expand Down Expand Up @@ -115,6 +116,7 @@ Clang-Tidy Checks
`cert-msc51-cpp <cert-msc51-cpp.html>`_,
`cert-oop57-cpp <cert-oop57-cpp.html>`_,
`cert-oop58-cpp <cert-oop58-cpp.html>`_,
`cert-sig30-c <cert-sig30-c.html>`_,
`clang-analyzer-core.DynamicTypePropagation <clang-analyzer-core.DynamicTypePropagation.html>`_,
`clang-analyzer-core.uninitialized.CapturedBlockVariable <clang-analyzer-core.uninitialized.CapturedBlockVariable.html>`_,
`clang-analyzer-cplusplus.InnerPointer <clang-analyzer-cplusplus.InnerPointer.html>`_,
Expand Down
22 changes: 22 additions & 0 deletions 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, sighandler_t);

#endif // _SIGNAL_H_
18 changes: 18 additions & 0 deletions 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);
void quick_exit(int);

void other_call(int);

#endif // _STDLIB_H_

0 comments on commit d1b2a52

Please sign in to comment.