CertC++-ENV32¶
All exit handlers must return normally
Required inputs: IR
The C Standard provides three functions that cause an application to terminate
normally:
_Exit(),
exit(), and
quick_exit(). These are collectively called exit
functions. When the
exit() function is called, or control transfers out of the
main() entry point function, functions registered with
atexit() are called (but not
at_quick_exit()). When the
quick_exit() function is called, functions registered with
at_quick_exit() (but not
atexit()) are called. These functions are collectively
called exit handlers. When the
_Exit() function is called, no exit handlers or signal handlers
are called.
Exit handlers must terminate by returning. It is important and potentially
safety-critical for all exit handlers to be allowed to perform their cleanup
actions. This is particularly true because the application programmer does not
always know about handlers that may have been installed by support libraries.
Two specific issues include nested calls to an exit function and terminating a
call to an exit handler by invoking
longjmp.
A nested call to an exit function is undefined behavior. (See undefined behavior 182.) This behavior can occur only when an exit function is invoked from an exit handler or when an exit function is called from within a signal handler. (See SIG30-C. Call only asynchronous-safe functions within signal handlers.)
If a call to the
longjmp() function is made that would terminate the call to a
function registered with
atexit(), the behavior is
undefined.
Noncompliant Code Example
In this noncompliant code example, the
exit1() and
exit2() functions are registered by
atexit() to perform required cleanup upon program termination.
However, if
some_condition evaluates to true,
exit() is called a second time, resulting in
undefined
behavior.
#include <stdlib.h>
void exit1(void) {
/* ... Cleanup code ... */
return;
}
void exit2(void) {
extern int some_condition;
if (some_condition) {
/* ... More cleanup code ... */
exit(0);
}
return;
}
int main(void) {
if (atexit(exit1) != 0) {
/* Handle error */
}
if (atexit(exit2) != 0) {
/* Handle error */
}
/* ... Program code ... */
return 0;
}
Functions registered by the
atexit() function are called in the reverse order from which they
were registered. Consequently, if
exit2() exits in any way other than by returning,
exit1() will not be executed. The same may also be true for
atexit() handlers installed by support libraries.
Compliant Solution
A function that is registered as an exit handler by
atexit() must exit by returning, as in this compliant solution:
#include <stdlib.h>
void exit1(void) {
/* ... Cleanup code ... */
return;
}
void exit2(void) {
extern int some_condition;
if (some_condition) {
/* ... More cleanup code ... */
}
return;
}
int main(void) {
if (atexit(exit1) != 0) {
/* Handle error */
}
if (atexit(exit2) != 0) {
/* Handle error */
}
/* ... Program code ... */
return 0;
}
Noncompliant Code Example
In this noncompliant code example,
exit1() is registered by
atexit() so that upon program termination,
exit1() is called. The
exit1() function jumps back to
main() to return, with undefined results.
#include <stdlib.h>
#include <setjmp.h>
jmp_buf env;
int val;
void exit1(void) {
longjmp(env, 1);
}
int main(void) {
if (atexit(exit1) != 0) {
/* Handle error */
}
if (setjmp(env) == 0) {
exit(0);
} else {
return 0;
}
}
Compliant Solution
This compliant solution does not call
longjmp()
but instead returns from the exit
handler normally:
#include <stdlib.h>
void exit1(void) {
return;
}
int main(void) {
if (atexit(exit1) != 0) {
/* Handle error */
}
return 0;
}
Risk Assessment
Terminating a call to an exit handler in any way other than by returning is undefined behavior and may result in abnormal program termination or other unpredictable behavior. It may also prevent other registered handlers from being invoked.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| ENV32-C | Medium | Likely | Medium | P12 | L1 |
Related Guidelines
| Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CERT C Secure Coding Standard | SIG30-C. Call only asynchronous-safe functions within signal handlers | Prior to 2018-01-12: CERT: Unspecified Relationship |
| ISO/IEC TR 24772:2013 | Structured Programming [EWD] | Prior to 2018-01-12: CERT: Unspecified Relationship |
| ISO/IEC TR 24772:2013 | Termination Strategy [REU] | Prior to 2018-01-12: CERT: Unspecified Relationship |
| CWE 2.11 | CWE-705, Incorrect Control Flow Scoping | 2017-07-10: CERT: Rule subset of CWE |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
non_normal_return_exit_handler |
All exit handlers must return normally. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
exit_functions¶
exit_functions : set[bauhaus.analysis.config.QualifiedName] = {'exit', 'longjmp', 'quick_exit'}