Many functions return either a valid value or a value of the correct return type that indicates an error (for example, −1 or a null pointer). Assuming that all calls to such functions will succeed and failing to check the return value for an indication of an error is a dangerous practice that may lead to unexpected or undefined behavior when an error occurs. It is essential that programs detect and appropriately handle all errors in accordance with an error-handling policy, as discussed in ERR00-C. Adopt and implement a consistent and comprehensive error-handling policy.
Noncompliant Code Example (setlocale()
)
In this noncompliant example, the function utf8_to_ucs()
attempts to convert a sequence of UTF-8 characters to UCS. It first invokes setlocale()
to set the global locale to "en_US.UTF-8"
but does not check for failure. setlocale()
will fail by returning a null pointer, for example, when the locale is not installed. (The function may fail for other reasons, as well, such as the lack of resources.) Depending on the sequence of characters pointed to by utf8
, the subsequent call to mbstowcs()
may fail or result in the function storing an unexpected sequence of wide characters in the supplied buffer ucs
.
size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) { setlocale(LC_CTYPE, "en_US.UTF-8"); return mbstowcs(ucs, utf8, n); }
Compliant Solution (setlocale()
)
A compliant solution checks the value returned by setlocale()
and avoids calling mbstowcs()
if the function fails. The function also takes care to restore the locale to its initial setting before returning control to the caller.
size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) { const char *save; save = setlocale(LC_CTYPE, "en_US.UTF-8"); if (NULL == save) { /* Propagate error to caller */ return (size_t)-1; } n = mbstowcs(ucs, utf8, n); if (NULL == setlocale(LC_CTYPE, save)) n = -1; return n; }
Noncompliant Code Example (signal()
)
In this noncompliant example, the function signal()
is invoked to install a handler for the SIGINT
signal. signal()
returns a pointer to the previously installed handler on success and the value SIG_ERR
on failure. However, because the caller fails to check for errors, when signal()
fails, the function may proceed with the lengthy computation without the ability to interrupt it.
volatile sig_atomic_t interrupted; void handle_interrupt(int signo) { interrupted = 1; } int f() { int result = 0; signal(SIGINT, handle_interrupt); while (0 == result && 0 == interrupted) { /* Perform a lengthy computation */ } /* Indicate success or failure */ return interrupted ? -1 : result; }
Compliant Solution (signal()
)
A compliant solution checks the value returned by the signal()
function and avoids performing the lengthy computation when signal()
fails. The calling function also takes care to restore the disposition for the SIGINT
signal to its initial setting before returning control to the caller.
volatile sig_atomic_t interrupted; void handle_interrupt(int signo) { interrupted = 1; } int f() { int result = 0; void (*saved_handler)(int); saved_handler = signal(SIGINT, handle_interrupt); if (SIG_ERR == saved_handler) { /* Indicate failure */ return -1; } while (0 == result && 0 == interrupted) { /* Perform a lengthy computation */ } if (SIG_ERR == signal(SIGINT, saved_handler)) return -1; /* Indicate success or failure */ return interrupted ? -1 : result; }
Noncompliant Code Example (malloc()
)
In this noncompliant code example, temp_num
, tmp2
, and num_of_records
are under the control of a malicious user. The attacker can easily cause malloc()
to fail by providing a large value for num_of_records
.
signal_info * start = malloc(num_of_records * sizeof(signal_info)); signal_info * point = (signal_info *)start; point = start + temp_num - 1; memcpy(point->sig_desc, tmp2, strlen(tmp2)); /* ... */
When malloc()
fails, it returns a null pointer that is assigned to start
. If start
is a null, an attacker can provide a value for temp_num
that, when scaled by the the sizeof signal_info
, references a writable address to which control is eventually transferred. The contents of the string referenced by tmp2
can then be used to overwrite the address, resulting in an arbitrary code execution vulnerability.
Compliant Solution (malloc()
)
To correct this error, ensure the pointer returned by malloc()
is not null. This also ensures compliance with MEM32-C. Detect and handle memory allocation errors.
signal_info * start = malloc(num_of_records * sizeof(signal_info)); if (start == NULL) { /* Handle Allocation Error */ } signal_info * point = (signal_info *)start; point = start + temp_num - 1; memcpy(point->sig_desc, tmp2, strlen(tmp2)); /* ... */
Noncompliant Code Example (malloc()
)
In this noncompliant code example, input_string
is copied into dynamically allocated memory referenced by str
. However, the result of malloc()
is not checked before str
is referenced. Consequently, if malloc()
fails, the program has undefined behavior. (See undefined behavior 109 in Annex J of the C Standard.) In practice, an abnormal termination of the process typically occurs, providing an opportunity for a denial-of-service attack. In some cases, it may be the source of other vulnerabilities, as well. (See the ERR33-C. Detect and handle errors section.) See also MEM32-C. Detect and handle memory allocation errors.
void f(char *input_string) { size_t size = strlen(input_string) + 1; char *str = (char *)malloc(size); strcpy(str, input_string); /* ... */ }
Compliant Solution (malloc()
)
The malloc()
function, as well as the other memory allocation functions, returns either a null pointer or a pointer to the allocated space. Always test the returned pointer to ensure it is not NULL
before referencing the pointer. Handle the error condition appropriately when the returned pointer is NULL
. When recovery from the allocation failure is not possible, propagate the failure to the caller.
int f(char *input_string) { size_t size = strlen(input_string) + 1; char *str = (char *)malloc(size); if (str == NULL) { /* Handle allocation failure and return error status */ return -1; } strcpy(str, input_string); /* ... */ free(str); return 0; }
Risk Assessment
Failing to detect error conditions can lead to unpredictable results, including abnormal program termination and denial-of-service attacks or, in some situations, could even allow an attacker to run arbitrary code.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ERR33-C | high | likely | medium | P18 | L1 |
Related Coding Practices
Coding Practice | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
medium | unlikely | medium | P2 | L3 | |
low | probable | high | P2 | L3 | |
FLP32-C. Prevent or detect domain and range errors in math functions | medium | probable | medium | P8 | L2 |
high | likely | medium | P18 | L1 | |
medium | probable | high | P4 | L3 | |
FIO33-C. Detect and handle input output errors resulting in undefined behavior | high | probable | medium | P12 | L1 |
low | unlikely | medium | P2 | L3 | |
ERR00-C. Adopt and implement a consistent and comprehensive error-handling policy | medium | probable | high | P4 | L3 |
low | unlikely | high | P1 | L3 | |
medium | probable | high | P4 | L3 | |
API04-C. Provide a consistent and usable error-checking mechanism | medium | unlikely | medium | P2 | L3 |
low | likely | medium | P6 | L2 | |
low | likely | medium | P6 | L2 |
Related Guidelines
CERT C++ Secure Coding Standard: ERR10-CPP. Check for error conditions
MITRE CWE: CWE-252, "Unchecked return value"
MITRE CWE: CWE-253, "Incorrect check of function return value"
MITRE CWE: CWE-390, "Detection of error condition without action"
MITRE CWE: CWE-391, "Unchecked error condition"
Bibliography
[DHS 2006]. Handle All Errors Safely.
[Henricson 1997] Recommendation 12.1, Check for all errors reported from functions