...
When application-specific code detects an error, it can respond on the
spot with a specific action, as in:
...
This is because the application must both detect errors and provide a
mechanism for handling errors. But because application-independent
code is not associated with any application, it cannot handle errors.
However, it must still detect errors, and report them to an
application, so that the application may handle them.
...
This non-compliant code example consists of two
application-independent functions f()
and g()
. The f()
function is part of the external API for the module; the g()
function is an internal function.
...
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.
...
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, there is no indication back to the calling function that
any error has occurred.
Compliant Solution (Return Value)
One way to indicate errors is to return a value indicating success or
errors. This compliant solution changes each function to return 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_happens) { return ESOMETHINGREALLYBAD; } /* ... */ return 0; } errno_t f(void) { errno_t status; if ((status = g()) != 0) return status; /* ... do the rest of f ... */ return 0; } |
A call to f()
returns a status indicator which is zero upon
success, and a non-zero value upon failure indicating what went wrong.
Wiki Markup |
---|
A return type of {{errno_t}} indicates that the function returns a status indicator \[[DCL09-A. Declare functions that return an errno error code with a return type of errno_t]. |
...
Wiki Markup Source and object code can significantly increase in size, perhaps by as much as 30-40% \[[Saks 07b|AA. C References#Saks 07b]\]
Wiki Markup All function return values must be checked (see \[[MEM32-C. Detect and handle memory allocation errors]\] among many others\].)
- Functions should not return other values if they return error
indicators (see ERR02-A. Avoid in-band error indicators].) - Any function that allocates resources must ensure they are freed incases where errors occur.
...
Instead of encoding status indicators in the return value, each
function can take a pointer as an argument, which is used to indicate
errors. In the following example, each function uses a errno_t*
argument to report errors.
...
A call to f()
provides a status indicator which is zero upon
success, and a non-zero value indicating failure, assuming the user
provided a valid pointer to an object of type errno_t
.
...
- A return status can only be returned if the caller provides a valid
pointer to an object of typeerrno_t
. If this argument is null,
there is no way to indicate this error. - Source code becomes even larger, due to the possibilities of
receiving a null pointer. - All error indicators must be checked after calling functions.
- Any function that allocates resources must ensure they are freed incases where errors occur.
- Unlike return values, static analysis tools generally 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,
a functions can indicate its status by assigning a value to a global
variable. In the following example, each function uses a static
indicator called my_errno
.
The original errno
variable was the Standard C library's
implementation of error handling using this approach.
...
The call to f()
provides a status indicator which is zero upon
success, and a non-zero value indicating failure.
This solution has many of the same properties as those observed with
errno
, including advantages and drawbacks.
- Source code size is inflated, though not by as much as other
approaches. - All error indicators must be checked after calling functions.
- Nesting of function calls that all use this mechanism is
problematic. - Any function that allocates resources must ensure they are freed incases where errors occur.
- In general, combining registries of different sets of errors is
difficult. For example, changing the above code to useerrno
is
difficult and bug-prone; as one must be precisely aware of when C
library functions set and clearerrno
, and one must be aware of
all validerrno
values before adding new ones. - There are major limitations on calling
f()
from other application-independent code. Sincef()
setsmy_errno
to 0, it may potentially 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 allows a user of these functions to
ignore error values, and entrust 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 disrupted in the event of error, and also uses
the my_errno
indicator from the previous example.
...
Calls to f()
will either succeed, or divert control into an if
clause designed to catch the error.
- Source code will not become significantly larger, because 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 application to 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 incases where errors occur.
...
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 |
...
A lack of error detection mechanism prevents applications from knowing
when an error has disrupted normal program behavior.
Recommendation | Severity | Likelihood | Remediation Cost | Priority |
---|
...
Level |
---|
...
ERR05-A | high | likely | high | P9 | L2 |
...
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule
on the [CERT
website|https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+ERR05-A].
References
Wiki Markup |
---|
\[[Miller 04|AA. C References#Miller 04]\] \[[Saks 07b|AA. C References#Saks 07b]\] |
...
*[|ERR04-A.
Choose an appropriate termination
strategy]* *[!CERT C Secure
Coding Standard^button_arrow_up.png!|12. Error Handling
(ERR)]* *[!CERT C Secure Coding
Standard^button_arrow_right.png!|ERR30-C. Set errno to zero before
calling a function, and use it only after the function returns a value
indicating failure]*