Application-independent code includes :code that is
- Shipped Code shipped with the compiler or operating system
- Code from From a third-party library
- Code developed Developed in-house
When application-specific code detects an error, it can immediately respond on the spot with a specific action, as in:
Code Block | bgColor | #ccffcc
---|
if (something_really_bad_happenedhappens) { take_me_some_place_safe(); } |
This is because This response occurs because the application must both detect errors and provide a mechanism for handling errors. Since applicationApplication-independent code is , by contrast, is not associated with any application, it so it cannot handle errors. But However, it must still detect errors , and report them to an application , so that the application may still handle them.
Error detection and reporting can take several forms:
- a A return value (especially of type
errno_t
) - an An argument passed by address
- a A global object (e.g., such as
errno
) longjmp()
- some Some combination of the above
...
Noncompliant Code Example
Suppose we have the following This noncompliant code example consists of two application-independent code, which is called externally by invoking the f()
functions, f()
and g()
. The f()
function is part of the external API for the module; the g()
function is an internal function.
Code Block | ||||
---|---|---|---|---|
| ||||
void g(void) { /* ... */ if (something_really_bad_happenedhappens) { fprintf( stderr, "Something really bad happened!\n"); abort(); } /* ... */ } void f(void) { g(); /* ... doDo the rest of f ... */ } |
If something_really_bad_happens
in g()
, the function prints an error message to stderr
and then calls abort()
. The problem is that this application-independent code does not know the context in which it is being called, so it is erroneous to handle the error.
“Smart Libraries,” Practice 23 [Miller 2004], says:
When a library aborts due to some kind of anomaly, it is saying there is no hope for execution to proceed normally beyond the point where the anomaly is detected. Nonetheless, it is dictatorially making this decision on behalf of the client. Even if the anomaly turns out to be some kind of internal bug in the library, which obviously cannot be resolved in the current execution, aborting is a bad thing to do. The fact is, a library developer cannot possibly know the fault-tolerant context in which his/her library is being used. The client may indeed be able to recover from the situation even if the library cannot.
It is equally bad to eliminate the call to abort()
from g()
. In this case, the calling function has no indication that an error has In this code, a serious error is detected in g()
which prints the error, but does nothing to alter subsequent program behavior. The application receives no indication the error even occurred.
Compliant Solution (Return Value)
One way to indicate errors to inform the calling function of errors is to return a value indicating success or errors. The following example changes failure. This compliant solution ensures each function to return returns a value of type errno_t
, where 0 indicates that no error has occurred.:
Code Block | ||||
---|---|---|---|---|
| ||||
const errno_t ESOMETHINGREALLYBAD = 1; errno_t g(void) { /* ... */ if (something_really_bad_happenedhappens) { return ESOMETHINGREALLYBAD; } /* ... */ return 0; } errno_t f(void) { errno_t status = g(); if ((status = g()) != 0) { return status; } /* ... doDo the rest of f ... */ return 0; } |
As you can see, calling A call to f()
will return returns a status indicator, which is zero 0 upon success , and a nonzero value upon failure indicating what went wrong.
A return type of errno_t
is encouraged by indicates that the function returns a status indicator (see DCL09-AC. Declare functions that return an errno error code with a return type of errno_t).
While this solution This error-handling approach is secure, but it has the following drawbacks:
- Source code becomes 30-40% largerand object code can significantly increase in size, perhaps by as much as 30 to 40 percent [Saks 2007b].
- All function return values must be checked (see MEM32ERR33-C. Detect and handle memory allocation errors among many others.)standard library errors).
- Functions should not return other values if they return error indicators (see ERR02-AC. Avoid in-band error indicators).)
- A Any function that allocates resources must still free them in spite of the errorensure they are freed in cases where errors occur.
Compliant Solution (Address Argument)
Instead of encoding status indicators in the return value, one can provide each function with can take a pointer as an argument, which the function is expected used to use to indicate errors. In the following example, each function uses a an errno_t\ *
argument to report errors.:
Code Block | ||||
---|---|---|---|---|
| ||||
const errno_t ESOMETHINGREALLYBAD = 1; void g(errno_t * err) { if (err == NULL) { /* handleHandle null pointer */ } /* ... */ if (something_really_bad_happenedhappens) { *err = ESOMETHINGREALLYBAD; } else { /* ... */ *err = 0; } } void f(errno_t * err) { if (err == NULL) { /* handleHandle null pointer */ } g(err); if (*err == 0) { /* ... doDo the rest of f ... */ } return 0; } |
As you can see, calling A call to f()
will provide provides a status indicator which that is zero 0 upon success , and a nonzero value indicating what went wrong.upon failure, assuming the user provided a valid pointer to an object of type errno_t
.
This While this solution is secure, but it has the following drawbacks:
- A return status can be returned only if the caller provides a valid pointer to an object of type
errno_t
. If this argument isNULL
, there is no way to indicate this error. - Source code becomes even larger, due to the larger because of the possibilities of receiving a null pointer.
- All error indicators must be checked after calling functions.
- A Any function that allocates resources must still free them in spite of the error. This is a little easier than with return values, because one has more control over control flow.ensure they are freed in cases where errors occur.
- Unlike return values, static analyais analysis tools generally won't notify you if you fail do not diagnose a failure to check error indicators passed as argument pointers.
Compliant Solution (Global Error Indicator)
Instead of encoding error indicators in the return value or arguments, one can provide a function can indicate its status by assigning a value to a global variable, which functions are expected to use to indicate errors. In the following example, each function uses a static indicator called my_errno
.
The original errno
variable was the Standard standard C library's implementation of error handling using this approach.
Code Block | ||||
---|---|---|---|---|
| ||||
errno_t my_errno; /* alsoAlso declared in a .h file */ const errno_t ESOMETHINGREALLYBAD = 1; void g(void) { /* ... */ if (something_really_bad_happenedhappens) { my_errno = ESOMETHINGREALLYBAD; return; } /* ... */ } void f(void) { my_errno = 0; g(); if (my_errno != 0) { return; } /* ... doDo the rest of f ... */ } |
As you can see, calling The call to f()
will provide provides a status indicator which that is zero 0 upon success , and a nonzero value indicating what went wrongupon failure.
This solution has many of the same properties as those observed with errno
, including advantages and drawbacks.
- Source code still becomes larger, though smaller than the previous examplessize is inflated, though not by as much as in other approaches.
- All error indicators must be checked after calling functions.
- Nesting of function calls that all use this mechanism is problematic.
- A Any function that allocates resources must still free them in spite of the errorensure they are freed in cases where errors occur.
- In general, combining registries of different sets of errors is difficult. For example, changing the above this compliant solution code to use
errno
is difficult and bug-prone ; as one because the programmer must be precisely aware of when C library functions set and clearerrno
, and one also must be aware of all validerrno
values before adding new ones. - Calling
f()
from other application-independent code has major limitations. Becausef()
setsmy_errno
to 0, it may be overwriting a nonzero error value set by another application-independent calling function.
For these reasons, among others, this approach is generally discouraged.
Compliant Solution ( setjmp()
and longjmp()
)
C provides two functions, setjmp()
and longjmp()
, that can be used to alter control flow. This might enable one to ignore Using these functions, a user can ignore error values , and entrust trust that control flow will be correctly diverted in the event of error.
The following example uses setjmp()
and longjmp()
to ensure that control flow is dirupted disrupted in the event of error, along with ; it also uses the my_errno
indicator from the previous example. See MSC22-C. Use the setjmp(), longjmp() facility securely for more information on setjmp()
and longjmp()
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <setjmp.h> const errno_t ESOMETHINGREALLYBAD = 1; jmp_buf exception_env; void g(void) { /* ... */ if (something_really_bad_happenedhappens) { longjmp(exception_env, ESOMETHINGREALLYBAD); } /* ... */ } void f(void) { g(); /* ... doDo the rest of f ... */ } /* ... */ errno_t err; if ((err = setjmp(exception_env)) != 0) { /* * ifIf we get here, an error occurred; * do andnot errcontinue indicatesprocessing. what went wrong */ } /* ... */ f(); /* ifIf we get here, no errors occurred */ /* ... */ |
As you can see, calling Calls to f()
will either succeed , or divert control into to an if
clause designed to catch the error.
- Source The source code will is not become significantly larger , because the function signatures do not change, and neither do functions that neither detect nor handle the error.
- Allocated resources must still be freed despite the error.
- Requires The application to must call
setjmp()
before invoking application-independent code. - Signals are not necessarily preserved through
longjmp()
calls. - The use of
setjmp()
/longjmp()
bypasses the normal function call and return discipline. - Any function that allocates resources must ensure they are freed in cases where errors occur.
Summary
The following table summarizes the characteristics of error-reporting and error-detection mechanisms.
Method | Code Increase | Manages Allocated Resources | Automatically Enforceable |
---|---|---|---|
Return |
value | Big ( |
30–40%) |
No |
Yes |
Address |
argument | Bigger |
No |
No |
Global |
indicator | Medium |
No |
Yes | |
| Small |
No | n/a |
Risk
...
Assessment
A lack Lack of an error-detection mechanism prevents applications from knowing when an error has disrupted normal program behavior.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ERR05- |
high
likely
high
P9
C | Medium | Probable | High | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Compass/ROSE | Could detect violations of this rule merely by reporting functions that call | ||||||||
Parasoft C/C++test |
| CERT_C-ERR05-a | The 'abort()' function from the 'stdlib.h' or 'cstdlib' library shall not be used |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Related Guidelines
SEI CERT C++ Coding Standard | VOID ERR05-CPP. Application-independent code should provide error detection without dictating error handling |
Bibliography
...
ERR04-A. Choose an appropriate termination strategy 12. Error Handling (ERR) ERR30-C. Set errno to zero before calling a function, and use it only after the function returns a value indicating failure