Versions Compared

Key

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

The std::abort() and std::_Exit() functions are used to terminate the program in an immediate fashion. They do so without calling exit handlers registered with std::atexit(), and without executing destructors for objects with automatic, thread, or static storage duration. It is implementation-defined as to whether open streams with unwritten buffered data are flushed, open streams are closed, or temporary files are removed [ISO/IEC 9899:1999]. Because these functions can leave external resources in an indeterminate state, they should only be called in direct response to a critical error in the application.

The std::terminate() function calls the current terminate_handler function, which defaults to calling std::abort().

The C++ Standard defines several ways in which std::terminate() may be called implicitly by an implementation Thrown exceptions that are not explicitly caught subject a running program to implementation-defined behavior culminating in abnormal termination. According to section 15.3 "Handling an Exception" of C++2003 [ISO/IEC 14882-2003]:

If no matching handler is found in a program, the function std::terminate() is called; whether or not the stack is unwound before this call to std::terminate() is implementation-defined (15.5.1).

The effects of std::terminate() are to call the terminate_handler function in effect immediately after evaluating the throw-expression. The default terminate_handler calls std::abort(), which has the effect of causing abnormal process termination to occur. Abnormal process termination is the typical vector for denial of service attacks.

Consequently, programs should take steps to prevent std::terminate() from being invoked for at least two reasons:

  1. If the stack is not unwound then destructors of local objects are not invoked, acquired system-wide or application-wide resources may not be released, file buffers are not flushed, database transactions are not committed or rolled back, etc.
  2. Since failing to catch an exception involves implementation-defined behavior, to comply MSC14-CPP. Do not introduce unnecessary platform dependencies.

2014]:

  • When the exception handling mechanism, after completing the initialization of the exception object but before activation of a handler for the exception, calls a function that exits via an exception. ([except.throw], paragraph 7)
  • When a throw-expression with no operand attempts to rethrow an exception and no exception is being handled. ([except.throw], paragraph 9)
  • When the exception handling mechanism cannot find a handler for a thrown exception. ([except.handle], paragraph 9)
  • When the search for a handler encounters the outermost block of a function with a noexcept-specification that does not allow the exception. ([except.spec], paragraph 9)
  • When the destruction of an object during stack unwinding terminates by throwing an exception. ([except.ctor], paragraph 3)
  • When initialization of a non-local variable with static or thread storage duration exits via an exception. ([basic.start.init], paragraph 6)
  • When destruction of an object with static or thread storage duration exits via an exception. ([basic.start.term], paragraph 1)
  • When execution of a function registered with std::atexit() or std::at_quick_exit() exits via an exception. ([support.start.term], paragraphs 8 and 12)
  • When the implementation’s default unexpected exception handler is called. ([except.unexpected], paragraph 2) Note that std::unexpected() is currently deprecated; see MSC23-CPP. Do not use deprecated or obsolescent functionality for further information.
  • When std::unexpected() throws an exception which is not allowed by the previously violated dynamic-exception-specification, and std::bad_exception() is not included in that dynamic-exception-specification. ([except.unexpected], paragraph 3)
  • When the function std::nested_exception::rethrow_nested() is called for an object that has captured no exception. ([except.nested], paragraph 4)
  • When execution of the initial function of a thread exits via an exception. ([thread.thread.constr], paragraph 5)
  • When the destructor is invoked on an object of type std::thread that refers to a joinable thread. ([thread.thread.destr], paragraph 1)
  • When the copy assignment operator is invoked on an object of type std::thread that refers to a joinable thread. ([thread.thread.assign], paragraph 1)
  • When calling condition_variable::wait()condition_variable::wait_until(), or condition_variable::wait_for() results in a failure to meet the post-condition: lock.owns_lock() == true or lock.mutex() is not locked by the calling thread. ([thread.condition.condvar], paragraphs 11, 16, 21, 28, 33, and 40)
  • When calling condition_variable_any::wait()condition_variable_any::wait_until(), or condition_variable_any::wait_for() results in a failure to meet the post-condition: lock is not locked by the calling thread. ([thread.condition.condvarany], paragraphs 11, 16, and 22)

In many circumstances, the call stack will not be unwound in response to the implicit call to std::terminate(), and in a few cases, it is implementation-defined as to whether stack unwinding will occur or not. The C++ Standard, [except.terminate], paragraph 2 [ISO/IEC 14882-2014], states, in part:

In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. In the situation where the search for a handler encounters the outermost block of a function with a noexcept-specification that does not allow the exception, it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before std::terminate() is called. In all other situations, the stack shall not be unwound before std::terminate() is called.

Do not allow an implicit call to std::abort() or std::_Exit(). When the default terminate_handler is installed, or the current terminate_handler responds by calling std::abort() or std::_Exit(), do not allow an implicit call to std::terminate(). Abnormal process termination is the typical vector for denial of service attacks.

It is acceptable to call std::abort()std::_Exit(), or std::terminate() in response to a critical program error for which no recovery is possible, Rather, programs should catch all exceptions and attempt to recover at the earliest opportunity. Under the rare circumstances when recovery is not feasible (for example, when a logic error is detected), programs should gracefully terminate after indicating the nature of the problem to the operator. See also  See also ERR04-CPP. Choose an appropriate termination strategy.

...

Noncompliant Code Example

...

In this noncompliant code example, main() does several useful work but does not catch any exceptions. Consequently, any exceptions thrown will call the call to C::f() may result in an exception being thrown. Since C::f() is called from a destructor, this can result in a call to std::terminate(), and might not destroy any objects owned by the program if a non-local object of type C has static or thread storage duration, or if an object of type C is destroyed during stack unwinding (as in this example).

Code Block
bgColor#FFcccc
langcpp
int main(int argc, char** argvclass C {
  void f() noexcept(false);
 
public:
  ~C() { f(); }
};
 
void throwing_func() noexcept(false);
 
void f() noexcept(false) {
  ObjectC objectc; // might not get destroyed if exception thrown
  // do useful work
  return 0;
}

Compliant Solution (main())


  throwing_func();
}
 
void g() noexcept(true) {
  try {
    f();
  } catch (...) {
    // Handle error
  }
}

If throwing_func() throws an exception, f() does not attempt to catch it and it will be handled by g(). However, during stack unwinding to reach the exception handler in g(), the automatic local variable c will be destroyed, resulting in a call to C::~C(). When the destructor attempts to throw an exception, std::terminate() will be called instead of throwing.

Note, the declaration for C::~C() does not comply with DCL40-CPP. Destructors and deallocation functions must be declared noexcept because destructors are implicitly declared noexcept(true), and this destructor allows exceptions by virtue of calling a function marked noexcept(false).

Compliant Solution

In this compliant solution, the destructor for C handles all exceptions and does not rethrow. When the automatic local variable c is destroyed, no exception is triggered from the C::~C() call, and the exception thrown by throwing_func() will be caught by the handler in g(In this code example, all exceptions are caught, allowing normal termination, even in the face of unexpected errors (albeit with an exit status indicating that an error occurred).

Code Block
bgColor#ccccff
langcpp
int main(int argc, char** argvclass C {
  void f() noexcept(false);
 
public:
  ~C() {
  Object object  try {
      f();
  int exit_status = EXIT_SUCCESS;

  try  } catch (...) {
      // Handle error
    }
  }
};
 
void throwing_func() noexcept(false);
 
void f() noexcept(false) {
  C c;
  throwing_func();
}
 
void g() noexcept(true) {
  try {
 // do useful workf();
  } catch (...) {
    // Handle error
  }
}

Noncompliant Code Example

In this noncompliant code example, the call to f(), which was registered as an exit

...

handler with std::at_exit(), may result in a call to std::terminate() because throwing_func() may throw an exception:

Code Block
bgColor#FFcccc
langcpp
#include <cstdlib>
 
void throwing_func() noexcept(false);
 
void f() {
  throwing_func();
}
 
int main() {
  if (0 != std::at_exit(f)) {
    // Handle error
  }
  // ...
}

Compliant Solution

In this compliant solution, f() handles all exceptions thrown by throwing_func(), and does not rethrow:

Compliant Solution (main())

An alternative is to wrap all of main()'s functionality inside a try-catch block and catch and handle exceptions by exiting with a status indicating an error to the invoking process.

Code Block
bgColor#ccccff
langcpp
int main(int argc, char** argv#include <cstdlib>

void throwing_func() noexcept(false);

void f() {
  try {
    Object objectthrowing_func();
  }  // do useful workcatch (...) {
    return 0; // objectHandle getserror
 destroyed here}
  }

int catch main(...) {
  if (0 != std::at_exit(EXIT_FAILURE);  f)) {
    // Handle error
  }
  // ...
}

Risk Assessment

Failing to handle exceptions Allowing the application to abnormally terminate in an implicit manner can lead to resources not being freed, closed, etc. It is frequently a vector for denial-of-service attacks.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

ERR30-CPP

lowLow

unlikelyProbable

lowMedium

P3 P4

L3

Automated Detection

Tool

Version

Checker

Description

 PRQA QA-C++

 
Include Page
PRQA QA-C++_V
PRQA QA-C++_V

4037, 4038, 4636, 4637

 

Other Languages

Related Vulnerabilities

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

Related Guidelines

...

Bibliography

...

2014]

15.5.1, "The std::terminate() Function"
18.5, "Start and Termination" 

[ISO/IEC 9899:1999]7.20.4.1, "The abort Function"
7.20.4.4, "The _Exit Function"
[MISRA 08]Rule 15-3-2, "There should be at least one exception handler to catch all otherwise unhandled exceptions"
Rule 15-3-4, "Each exception explicitly thrown in the code shall have a handler of a compatible type in all call paths that could lead to that point"