Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

The C++14 Standard, [support.runtime], paragraph 10 [ISO/IEC 14882-2014], states the following:

The common subset of the C and C++ languages consists of all declarations, definitions, and expressions that may appear in a well-formed C++ program and also in a conforming C program. A POF (“plain old function”) is a function that uses only features from this common subset, and that does not directly or indirectly use any function that is not a POF, except that it may use plain lock-free atomic operations. A plain A plain lock-free atomic operation is an invocation of a function f from Clause 29, such that f is not a member function, and either f is the function atomic_is_lock_free, or for every atomic argument A passed to fA passed to f, atomic_is_lock_free(A) yields true. All signal handlers shall have C linkage. The behavior of any function other than a POF used as a signal handler in a C++ program is implementation-defined.228

Footnote 228 states the following:

In particular, a signal handler using exception handling is very likely to have problems. Also, invoking std::exit may cause destruction of objects, including those of the standard library implementation, which, in general, yields undefined behavior in a signal handler.

...

...

In C++17, the wording has changed and relaxed some of the constraints on signal handlers. Section [support.signal], paragraph 3 says:

An evaluation is signal-safe unless it includes one of the following:

— a call to any standard library function, except for plain lock-free atomic operations and functions explicitly identified as signal-safe. [ Note: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. — end note ]
— an access to an object with thread storage duration;
— a 
dynamic_cast expression;
— throwing of an exception;
— control entering a try-block or function-try-block;
— initialization of a variable with static storage duration requiring dynamic initialization (6.6.39.7)220; or
— waiting for the completion of the initialization of a variable with static storage duration (9.7).

A signal handler invocation has undefined behavior if it includes an evaluation that is not signal-safe.

Signal handlers in code that will be executed on C++17-compliant platforms must be signal-safe, in addition to the restrictions placed on signal handlers in a C program. See 11. Signals (SIG) for rules regarding conforming use of signals in a C program.

Noncompliant Code Example

In this noncompliant code example, the signal handler is declared as a static function. However, since all signal handler functions must have C language linkage, and C++ is the default language linkage for functions in C++, calling the signal handler results in undefined behavior.

Code Block
bgColor#FFcccc
langcpp
#include <csignal>
 
static void sig_handler(int sig) {
  // Implementation details elided.
}

void finstall_signal_handler() {
  if (SIG_ERR == std::signal(SIGTERM, sig_handler)) {
    // Handle error
  }
}

Compliant Solution

This compliant solution defines sig_handler() as having C language linkage. Note that this requires all As a consequence of declaring the signal handler functions to be declared with external linkage instead of with C language linkage, the signal handler will have external linkage rather than internal linkage.

Code Block
bgColor#ccccff
langcpp
#include <csignal>
 
extern "C" void sig_handler(int sig) {
  // Implementation details elided.
}

void finstall_signal_handler() {
  if (SIG_ERR == std::signal(SIGTERM, sig_handler)) {
    // Handle error
  }
}

Noncompliant Code Example

In this noncompliant code example, a signal handler calls a function which that allows exceptions, and it attempts to handle any exceptions thrown. Since Because exceptions are not part of the common subset of C and C++ features, this example results in implementation-defined behavior. However, it is unlikely that the implementation's behavior will be suitable. For instance, on a stack-based architecture where a signal is generated asynchronously (instead of as a result of a call to std:abort() or std::raise()), it is possible that the stack frame is not properly initialized, causing stack tracing to be unreliable , and preventing the exception from being caught properly.

Code Block
bgColor#FFcccc
langcpp
#include <csignal>

static void g() noexcept(false);

extern "C" void sig_handler(int sig) {
  try {
    g();
  } catch (...) {
    // Handle error
  }
}
 
void finstall_signal_handler() {
  if (SIG_ERR == std::signal(SIGTERM, sig_handler)) {
    // Handle error
  }
}

Compliant Solution

There is no compliant solution where whereby g() can be called from the signal handler because it allows exceptions. Even if g() were implemented such that it handled all exceptions and was marked noexcept(true), it would still be noncompliant to call it g() from a signal handler because g() would still use a feature that is not a part of the common subset of C and C++ features allowed by a signal handler. Therefore, this compliant solution removes the call to g() from the signal handler and instead polls a variable of type volatile sig_atomic_t periodically; if the variable is set to 1 in the signal handler, then g(): is called to respond to the signal.

Code Block
bgColor#ccccff
langcpp
#include <csignal>

volatile sig_atomic_t signal_flag = 0;
static void g() noexcept(false); // Not called

extern "C" void sig_handler(int sig) {
  // Implement g()'s behavior as best as possible given the
  // constraints placed on a signal handler function.
}

void fsignal_flag = 1;
}

void install_signal_handler() {
  if (SIG_ERR == std::signal(SIGTERM, sig_handler)) {
    // Handle error
  }
}
 
// Called periodically to poll the signal flag.
void poll_signal_flag() {
  if (SIGsignal_ERRflag == std::signal(SIGTERM, sig_handler)1) {
    signal_flag = 0;
    try {
      g();
    } catch(...) {
      // Handle error
    }
  }
}

Risk Assessment

Failing to use a Plain Old Function plain old function as a signal handler can result in in implementation-defined behavior as well as undefined behavior. Given the number of features that exist in C++ that do not also exist in C, the consequences that arise from failure to comply with this rule can have benign consequences, range from benign (harmless) behavior to abnormal program termination, or possibly even arbitrary code execution.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MSC38

MSC54-CPP

Medium

High

Probable

High

P4

P6

L3

L2

Automated Detection

Tool

Version

Checker

Description

   
Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C++2888
Klocwork
Include Page
Klocwork_V
Klocwork_V
CERT.MSC.SIG_HANDLER.POF
Parasoft C/C++test
Include Page
Parasoft_V
Parasoft_V

CERT_CPP-MSC54-a

Properly define signal handlers
Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: MSC54-CPP

Checks for unsafe signal handlers (rule fully covered)
 

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]

Subclause 18.10, "Other Runtime Support"


...

Image Modified Image Modified Image Modified