All The majority of the standard library functions, including I/O functions and memory allocation 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.
The successful completion or failure of each of the standard library functions listed in the following table shall be determined either by comparing the function’s return value with the value listed in the column labeled “Error Return” or by calling one of the library functions mentioned in the footnotes to the same column..
Standard Library Functions
Function | Successful Return | Error Return |
---|---|---|
| Pointer to space |
|
|
| Nonzero |
|
| Nonzero |
|
| Nonzero |
| Pointer to matching element |
|
| Pointer to matching element |
|
| Converted wide character |
|
| Number of bytes |
|
| Number of bytes |
|
| Pointer to space |
|
| Processor time |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| Nonzero |
|
|
|
|
|
|
| Character read |
|
|
| Nonzero, |
| Pointer to string |
|
| Wide character read |
|
| Pointer to stream |
|
|
| Nonzero |
| Number of characters (nonnegative) | Negative |
| Number of characters (nonnegative) | Negative |
| Character written |
|
| Nonnegative |
|
fputwc() | Wide character written | WEOF |
| Nonnegative |
|
| Elements read | Elements read |
| Pointer to stream |
|
|
| Nonzero |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
|
| Nonzero |
|
| Nonzero, |
| File position |
|
| Number of wide characters (nonnegative) | Negative |
| Number of wide characters (nonnegative) | Negative |
| Elements written | Elements written |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Character read |
|
| Character read |
|
| Pointer to string |
|
| Pointer to string |
|
| Pointer to string |
|
| Wide character read |
|
| Wide character read |
|
| Pointer to broken-down time |
|
| Pointer to broken-down time |
|
| Pointer to broken-down time |
|
| Pointer to broken-down time |
|
| Pointer to space |
|
| Number of bytes |
|
| Number of bytes or status |
|
| Number of bytes or status |
|
| Number of bytes or status |
|
| Number of bytes or status |
|
| Number of non-null elements |
|
|
| Nonzero |
| Number of non-null elements |
|
|
| Nonzero |
| Number of bytes |
|
| Pointer to located character |
|
| Calendar time |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| Number of characters (nonnegative) | Negative |
| Character written |
|
| Wide character written |
|
|
| Nonzero |
| Pointer to space |
|
|
| Nonzero |
|
| Nonzero |
| Pointer to string |
|
|
| Nonzero |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Pointer to previous function |
|
| Number of characters that would be written (nonnegative) | Negative |
| Number of characters that would be written (nonnegative) | Negative |
| Number of non-null characters written | Negative |
| Number of non-null characters written | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Pointer to located character |
|
|
| Nonzero |
| Number of non-null characters |
|
| Pointer to located character |
|
| Pointer to located character |
|
| Pointer to located string |
|
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Pointer to first character of a token |
|
| Pointer to first character of a token |
|
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Length of transformed string |
|
| Number of non-null wide characters | Negative |
| Number of non-null wide characters | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
|
|
|
|
|
|
|
|
|
|
|
negative
Negative | ||
| Calendar time |
|
| Base |
|
| Pointer to stream |
|
|
| Nonzero |
| Non-null pointer |
|
|
| Nonzero |
|
|
|
| Value of thread-specific storage |
|
|
|
|
| Character pushed back |
|
| Character pushed back |
|
| Number of characters (nonnegative) | Negative |
| Number of characters (nonnegative) | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Number of wide characters (nonnegative) | Negative |
| Number of wide characters (nonnegative) | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Number of characters (nonnegative) | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Number of characters that would be written (nonnegative) | Negative |
| Number of characters that would be written (nonnegative) | Negative |
| Number of non-null characters (nonnegative) | Negative |
| Number of non-null characters (nonnegative) | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Number of non-null wide characters | Negative |
| Number of non-null wide characters | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Number of wide characters (nonnegative) | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
| Number of bytes stored |
|
| Pointer to located wide character |
|
| Number of non-null wide characters |
|
| Pointer to located wide character |
|
| Pointer to located wide character |
|
| Number of non-null bytes |
|
|
| Nonzero |
| Pointer to located wide string |
|
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Pointer to first wide character of a token |
|
| Pointer to first wide character of a token |
|
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Number of non-null bytes |
|
|
| Nonzero |
| Converted value |
|
| Converted value |
|
| Converted value |
|
| Length of transformed wide string |
|
| Converted character |
|
| Number of bytes stored |
|
| Number of bytes stored |
|
| Valid argument to |
|
| Valid argument to |
|
| Pointer to located wide character |
|
| Number of wide characters (nonnegative) | Negative |
| Number of conversions (nonnegative) |
|
| Number of conversions (nonnegative) |
|
When Note: According to FIO35-C. Use feof() and ferror() to detect end-of-file and file errors when sizeof(int) == sizeof(char) applies, callers shall determine the success or failure of should verify end-of-file and file errors for the functions in this table as follows:
1 By calling ferror()
and feof()
2 By calling ferror()
The ungetc()
function does not set the error indicator even when it fails, so it is not possible to check for errors reliably unless it is known that the argument is not equal to EOF
.
The C Standard 7.31.3.10 paragraph 3 [ISO/IEC 9899:20112024] states that "one
)ne wide character of pushback is guaranteed...
," so this should not be an issue if, at most, one character is ever pushed back before reading again. (See FIO13-C. Never push back anything other than one read character.)
Noncompliant Code Example (setlocale()
)
In this noncompliant code example, the function utf8_to_ucswcs()
attempts to convert a sequence of UTF-8 characters to UCS wide characters. It first invokes setlocale()
to set the global locale to "
enthe implementation-defined en_US.UTF-8"
but does not check for failure. The setlocale()
function 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
wcs
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <locale.h> #include <stdlib.h> size_tint utf8_to_ucswcs(wchar_t *ucswcs, size_t n, const char *utf8, size_t *size) { if (NULL == size) { return -1; } setlocale(LC_CTYPE, "en_US.UTF-8"); *size return= mbstowcs(ucswcs, utf8, n); return 0; } |
...
Compliant Solution (Compliant Solution (setlocale()
)
A compliant This 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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <locale.h> #include <stdlib.h> size_tint utf8_to_ucswcs(wchar_t *ucswcs, size_t n, const char *utf8, size_t *size) { if (NULL == size) { const charreturn *save; -1; } const char *save = setlocale(LC_CTYPE, "en_US.UTF-8"); if (NULL == save) { /* Propagate error to caller */ return (size_t)-1; } nreturn -1; } *size = mbstowcs(ucswcs, utf8, n); if (NULL == setlocale(LC_CTYPE, save)) { n =return -1; } return n0; } |
...
Noncompliant Code Example (
...
calloc()
)
In this noncompliant code 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., temp_num
, tmp2
, and num_of_records
are derived from a tainted source. Consequently, an attacker can easily cause calloc()
to fail by providing a large value for num_of_records
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal<stdlib.h> #include <string.h> volatileenum { sigSIG_atomic_t interruptedDESC_SIZE = 32 }; void handle_interrupt(int signo) {typedef struct { interrupted = 1char sig_desc[SIG_DESC_SIZE]; } 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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <signal.h> 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 signal_info; void func(size_t num_of_records, size_t temp_num, const char *tmp2, size_t tmp2_size_bytes) { signal_info *start = (signal_info *)calloc(num_of_records, sizeof(signal_info)); if (tmp2 == NULL) { /* Handle error */ } else if (temp_num > num_of_records || temp_num == 0) { /* Handle error */ } else if (tmp2_size_bytes < SIG_DESC_SIZE) { /* IndicateHandle failureerror */ return -1; } while (0signal_info *point == resultstart && 0 == interrupted) { /* Perform a lengthy computation */ } if (SIG_ERR == signal(SIGINT, saved_handler)) return -1; + temp_num - 1; memcpy(point->sig_desc, tmp2, SIG_DESC_SIZE); point->sig_desc[SIG_DESC_SIZE - 1] = '\0'; /* Indicate success or failure */ return interrupted ? -1 : result... */ free(start); } |
...
When calloc()
fails, it returns a null pointer that is assigned to start
. If start
is null, an attacker can provide a value for temp_num
that, when scaled by 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 (calloc()
)
To correct this error, ensure the pointer returned by calloc()
is not null:
Code Block | ||
---|---|---|
|
...
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
.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h> #include <string.h> typedef structenum { SIG_DESC_SIZE = 32 }; typedef struct { char sig_desc[32SIG_DESC_SIZE]; } signal_info; void func(size_t num_of_records, size_t temp_num, const char *tmp2, size_t tmp2_size_bytes) { signal_info *start = (signal_info *)malloccalloc(num_of_records, * sizeof(signal_info)); signal_info *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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h> #include <string.h> typedef struct { char sig_desc[32]; } signal_info; void func(size_t num_of_records, size_t temp_num, const char *tmp2) {if (start == NULL) { /* Handle allocation error */ } else if (tmp2 == NULL) { /* Handle error */ } else if (temp_num > num_of_records || temp_num == 0) { /* Handle error */ } else if (tmp2_size_bytes < SIG_DESC_SIZE) { /* Handle error */ } signal_info *point; signal_info * = start =+ (signal_info *)malloc(num_of_records * sizeof(signal_info)); if (start == NULL) { /* Handle allocationtemp_num - 1; memcpy(point->sig_desc, tmp2, SIG_DESC_SIZE); point->sig_desc[SIG_DESC_SIZE - 1] = '\0'; /* ... */ free(start); } |
Noncompliant Code Example (realloc()
)
This noncompliant code example calls realloc()
to resize the memory referred to by p
. However, if realloc()
fails, it returns a null pointer and the connection between the original block of memory and p
is lost, resulting in a memory leak.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h> void *p; void func(size_t new_size) { if (new_size == 0) { /* Handle error */ } pointp = 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 Related Vulnerabilities section.)
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
#include <string.h>
void f(char *input_string) {
size_t size = strlen(input_string) + 1;
char *str = (char *)malloc(size);
strcpy(str, input_string);
/* ... */
}
|
...
realloc(p, new_size);
if (p == NULL) {
/* Handle error */
}
} |
This code example complies with MEM04-C. Do not perform zero-length allocations.
Compliant Solution (realloc()
)
In this compliant solution, the result of realloc()
is assigned to the temporary pointer q
and validated before it is assigned to the original pointer p
:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
void *p;
void func(size_t new_size) {
void *q;
if (new_size == 0) {
/* Handle error */
}
q = realloc(p, new_size);
if (q == NULL) {
/* Handle error */
} else {
p = q;
}
} |
Noncompliant Code Example (fseek()
)
In this noncompliant code example, the fseek()
function is used to set the file position to a location offset
in the file referred to by file
prior to reading a sequence of bytes from the file. However, if an I/O error occurs during the seek operation, the subsequent read will fill the buffer with the wrong contents.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
size_t read_at(FILE *file, long offset,
void *buf, size_t nbytes) {
fseek(file, offset, SEEK_SET);
return fread(buf, 1, nbytes, file);
}
|
Compliant Solution (fseek()
)
According to the C Standard, the fseek()
function returns a nonzero value to indicate that an error occurred. This compliant solution tests for this condition before reading from a file to eliminate the chance of operating on the wrong portion of the file if fseek()
fails:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
size_t read_at(FILE *file, long offset,
void *buf, size_t nbytes) {
if (fseek(file, offset, SEEK_SET) != 0) {
/* Indicate error to caller */
return 0;
}
return fread(buf, 1, nbytes, file);
}
|
Noncompliant Code Example (snprintf()
)
In this noncompliant code example, snprintf()
is assumed to succeed. However, if the call fails (for example, because of insufficient memory, as described in GNU libc bug 441945), the subsequent call to log_message()
has undefined behavior because the character buffer is uninitialized and need not be null-terminated.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
extern void log_message(const char *);
void f(int i, int width, int prec) {
char buf[40];
snprintf(buf, sizeof(buf), "i = %*.*i", width, prec, i);
log_message(buf);
/* ... */
}
|
Compliant Solution (snprintf()
)
This compliant solution does not assume that snprintf()
will succeed regardless of its arguments. It tests the return value of snprintf()
before subsequently using the formatted buffer. This compliant solution also treats the case where the static buffer is not large enough for snprintf()
to append the terminating null character as an error.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
extern void log_message(const char *);
void f(int i, int width, int prec) {
char buf[40];
int n;
n = snprintf(buf, sizeof(buf), "i = %*.*i", width, prec, i);
if (n < 0 || n >= sizeof(buf)) {
/* Handle snprintf() error */
strcpy(buf, "unknown error");
}
log_message(buf |
...
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.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
#include <string.h>
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;
}
|
Noncompliant Code Example (realloc()
)
This noncompliant code example calls realloc()
to resize the memory referred to by p
. However, if realloc()
fails, it returns a null pointer. Consequently, the connection between the original block of memory and p
is severed, resulting in a memory leak.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
void *p;
void func(size_t new_size) {
p = realloc(p, new_size);
if (p == NULL) {
/* Handle error */
}
} |
When using realloc()
, it is important to account for 0-byte allocations. (See MEM04-C. Do not perform zero-length allocations.)
...
Compliant Solution (realloc()
)
In this compliant solution, the result of realloc()
is assigned to the temporary pointer q
and validated before it is assigned to the original pointer p
:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdlib.h>
void *p;
void func(size_t new_size) {
void *q;
q = realloc(p, new_size);
if (q == NULL) {
/* Handle error */
} else {
p = q;
}
} |
Noncompliant Code Example (fseek()
)
In this noncompliant code example, the fseek()
function is used to set the file position to a location offset
in the file referred to by file
prior to reading a sequence of bytes from the file. However, if an I/O error occurs during the seek operation, the subsequent read will fill the buffer with the wrong contents.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
size_t read_at(FILE *file, long offset, void *buf,
size_t nbytes) {
fseek(file, offset, SEEK_SET);
return fread(buf, 1, nbytes, file);
}
|
Compliant Solution (
...
snprintf(
null)
)
According to the C Standard, the fseek()
function returns a nonzero value to indicate that an error occurred. Testing for this condition before proceeding to read from the file eliminates the chance of operating on the wrong portion of the file if fseek()
failed. Always test the returned value to make sure an error did not occur before operating on the file. If an error does occur, handle it appropriately.If unknown, the length of the formatted string can be discovered by invoking snprintf()
with a null buffer pointer to determine the size required for the output, then dynamically allocating a buffer of sufficient size, and finally calling snprintf()
again to format the output into the dynamically allocated buffer. Even with this approach, the success of all calls still needs to be tested, and any errors must be appropriately handled. A possible optimization is to first attempt to format the string into a reasonably small buffer allocated on the stack and, only when the buffer turns out to be too small, dynamically allocate one of a sufficient size:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <stdlib.h> #include <string.h> size_t read_at(FILE *file, long offset, void *buf, size_t nbytes) { if (fseek(file, offset, SEEK_SET) != 0) { /* Indicate error to caller */ return 0; } return fread(buf, 1, nbytes, file); } |
Noncompliant Code Example (snprintf()
)
In the following noncompliant code example, snprinf()
is assumed to succeed. However, if the call fails (for example, because of insufficient memory, as described in GNU libc bug 441945), the subsequent call to log_message()
is likely to result in undefined behavior because the character buffer is not initialized and need not be null-terminated.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> extern void log_message(const char *); void f(int i, int width, int prec) { char buf[40]; snprintf(buf, sizeof(buf), "i = %*.*i"extern void log_message(const char *); void f(int i, int width, int prec) { char buffer[20]; char *buf = buffer; int n = sizeof(buffer); const char fmt[] = "i = %*.*i"; n = snprintf(buf, n, fmt, width, prec, i); if (n < 0) { /* Handle snprintf() error */ strcpy(buffer, "unknown error"); goto write_log; } if (n < sizeof(buffer)) { goto write_log; } buf = (char *)malloc(n + 1); if (NULL == buf) { /* Handle malloc() error */ strcpy(buffer, "unknown error"); goto write_log; } n = snprintf(buf, n, fmt, width, prec, i); if log_message(buf);(n < 0) { /* ... Handle snprintf() error */ strcpy(buffer, "unknown error"); } |
Compliant Solution (snprintf()
)
A compliant solution avoids assuming that snprintf()
succeeds regardless of its arguments and tests the return value of the function before using the buffer the function was passed to format output into. In addition, a compliant solution takes care to null-terminate the formatted string in case the provided buffer wasn't large enough for snprintf()
to append the terminating null.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
extern void log_message(const char *);
void f(int i, int width, int prec) {
char buf[40];
int n;
n = snprintf(buf, sizeof(buf), "i = %*.*i", width, prec, i);
if (n < 0) {
/* Handle snprintf() error */
strcpy(buf, "unknown error");
}
/* Null-terminate buffer in case of overflow */
buf[sizeof(buf) - 1] = '\0';
log_message(buf);
}
|
When the length of the formatted string is unbounded, it is best to invoke the function twice, once with a NULL
buffer to determine the size of output, then dynamically allocate a buffer of sufficient size, and finally call snprintf()
again to format the output into the dynamically allocated buffer. Even with this approach, the success of all calls still needs to be tested, and any errors still must be appropriately handled. A possible optimization is to first attempt to format the string into a reasonably small buffer allocated on the stack and, only when the buffer turns out to be too small, allocate one of a sufficient size dynamically. This approach is shown in the following compliant solution:
Note that this solution uses the goto
statement, as suggested in MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources.
Implementation Details
POSIX
In addition to the C standard library functions mentioned earlier, the following functions defined in POSIX require error checking (list is not all-inclusive).
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern void log_message(const char *);
void f(int i, int width, int prec) {
char buffer[20];
char *buf = buffer;
int n = sizeof(buffer);
const char fmt[] = "i = %*.*i";
n = snprintf(buf, n, fmt, width, prec, i);
if (n < 0) {
/* Handle snprintf() error */
strcpy(buffer, "unknown error");
goto write_log;
}
if (n < sizeof(buffer)) {
goto write_log;
}
buf = (char *)malloc(n + 1);
if (NULL == buf) {
/* Handle malloc() error */
strcpy(buffer, "unknown error");
goto write_log;
}
n = snprintf(buf, n, fmt, width, prec, i);
if (n < 0) {
/* Handle snprintf() error */
strcpy(buffer, "unknown error");
}
write_log:
log_message(buf);
if (buf != buffer) {
free(buf);
}
}
|
Function | Successful Return | Error Return |
|
---|---|---|---|
| Pointer to a |
|
|
| Pointer to a |
|
|
|
| Nonzero | Unchanged |
Setting errno
is a POSIX [ISO/IEC 9945:2008] extension to the C Standard. On error, posix_memalign()
returns a value that corresponds to one of the constants defined in the <errno.h>
header. The function does not set errno
. The posix_memalign()
function is optional and is not required to be provided by POSIX-conforming implementations.
Noncompliant Code Example (POSIX)
In the following noncompliant code example, fmemopen()
and open_memstream()
are assumed to succeed. However, if the calls fail, the two file pointers in
and out
will be null and the program will have undefined behavior.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
FILE *out;
FILE *in;
size_t size;
char *ptr;
if (argc != 2) {
/* Handle error */
}
in = fmemopen(argv[1], strlen(argv[1]), "r");
/* Use in */
out = open_memstream(&ptr, &size);
/* Use out */
return 0;
} |
Compliant Solution (POSIX)
A compliant solution avoids assuming that fmemopen()
and open_memstream()
succeed regardless of its arguments and tests the return value of the function before using the file pointers in
and out
:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
FILE *out;
FILE *in;
size_t size;
char *ptr;
if (argc != 2) {
/* Handle error */
}
in = fmemopen(argv[1], strlen(argv[1]), "r");
if (in == NULL){
/* Handle error */
}
/* Use in */
out = open_memstream(&ptr, &size);
if (out == NULL){
/* Handle error */
}
/* Use out */
return 0;
} |
Exceptions
...
write_log:
log_message(buf);
if (buf != buffer) {
free(buf);
}
}
|
This solution uses the goto
statement, as suggested in MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources.
Exceptions
ERR33-C-EX1: It is acceptable to ignore the return value of a function if:
- that function cannot fail.
- its return value is inconsequential; that is, it does not indicate an error.
- it is one of a handful of functions whose return values are not traditionally checked.
These functions are listed in the following table:
Functions for which Return Values Need Not Be Checked
Function | Successful Return | Error Return |
---|---|---|
| Character written |
|
| Wide character written |
|
| Nonnegative |
|
| Nonnegative |
|
| Number of characters (nonnegative) | Negative |
| Number of wide characters (nonnegative) | Negative |
kill_dependency() | The input parameter | NA |
memcpy() , wmemcpy() | The destination input parameter | NA |
memmove() , wmemmove() | The destination input parameter | NA |
strcpy() , wcscpy() | The destination input parameter | NA |
strncpy() , wcsncpy() | The destination input parameter | NA |
strcat() , wcscat() | The destination input parameter | NA |
strncat() , wcsncat() | The destination input parameter | NA |
memset() , wmemset() | The destination input parameter | NA |
The return value of a call to fprintf()
or one of its variants (vfprintf()
, wfprintf()
, vwfprintf()
) or one of the file output functions fputc()
, fputwc()
, fputs()
, fputws()
may be ignored if the output is being directed to stdout
or stderr
. Otherwise, the return value must be checked.
If a function's return value is to be ignored, it is recommended that the function's return value should be explicitly cast to void to signify the programmer's intent:
Code Block | ||||
---|---|---|---|---|
| ||||
int main() {
(void) fprintf(stdout, "Hello, world\n"); // fprintf() return value safely ignored
}
|
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 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| error-information-unused error-information-unused-computed | Partially checked | ||||||
Axivion Bauhaus Suite |
| CertC-ERR33 | |||||||
CodeSonar |
| LANG.FUNCS.IRV | Ignored return value Missing Test of Error Code Non-zero Error Code | ||||||
Compass/ROSE | Can detect violations of this recommendation when checking for violations of EXP12-C. Do not ignore values returned by functions |
...
Coverity |
| MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 | Implemented | ||||||
Helix QAC |
| C3200 C++3802, C++3803, C++3804 DF2820, DF2821, DF2822, DF2823, DF2824, DF2930, DF2931, DF2932, DF2933, DF2934 | |||||||
Klocwork |
| NPD.CHECK.MUST | |||||||
LDRA tool suite |
| 80 D | Partially implemented | ||||||
Parasoft C/C++test |
| CERT_C-ERR33-a | The value returned by a standard library function that may return an error should be used | ||||||
Parasoft Insure++ | Runtime analysis | ||||||||
PC-lint Plus |
| 534 | Partially supported | ||||||
| Checks for:
Rule partially covered. | ||||||||
RuleChecker |
| error-information-unused | Partially checked | ||||||
TrustInSoft Analyzer |
| pointer arithmetic | Exhaustively verified. |
Related Vulnerabilities
The vulnerability in Adobe Flash [VU#159523] arises because Flash neglects to check the return value from calloc()
. Even when calloc()
returns a null pointer, Flash writes to an offset from the return value. Dereferencing a null pointer usually results in a program crash, but dereferencing an offset from a null pointer allows an exploit to succeed without crashing the program.
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|---|---|
CERT C Secure Coding Standard | ERR00-C. Adopt and implement a consistent and comprehensive error-handling policy | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C Secure Coding Standard | EXP34-C. Do not dereference null pointers | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C Secure Coding Standard | FIO13-C. Never push back anything other than one read character | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C Secure Coding Standard | MEM04-C. Do not perform zero-length allocations | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C Secure Coding Standard | MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C | ERR10-CPP. Check for error conditions | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C | FIO04-CPP. Detect and handle input and output errors | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TS 17961:2013 | Failing to detect and handle standard library errors [liberr] | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-252, Unchecked Return Value | 2017-07-06: CERT: Partial overlap |
CWE 2.11 | CWE-253, Incorrect Check of Function Return Value | 2017-07-06: CERT: Partial overlap |
CWE 2.11 | CWE-391, Unchecked Error Condition | 2017-07-06: CERT: Rule subset of CWE |
CERT-CWE Mapping Notes
Key here for mapping notes
CWE-252/CWE-253/CWE-391 and ERR33-C/POS34-C
Independent( ERR33-C, POS54-C, FLP32-C, ERR34-C) Intersection( CWE-252, CWE-253) = Ø CWE-391 = Union( CWE-252, CWE-253) CWE-391 = Union( ERR33-C, POS34-C, list) where list =
- Ignoring return values of functions outside the C or POSIX standard libraries
ERR33-EX1: Ignore the return value of a function that cannot fail or whose return value cannot signify that an error condition need not be diagnosed. For example, strcpy()
is one such function.
Return values from the following functions do not need to be checked because their historical use has overwhelmingly omitted error checking, and the consequences are not relevant to security.
Function | Successful Return | Error Return |
---|---|---|
| Number of characters (nonnegative) | Negative |
| Character written |
|
| Nonnegative |
|
| Wide character written |
|
| Number of characters (nonnegative) | Negative |
| Number of wide characters (nonnegative) | Negative |
| Number of wide characters (nonnegative) | Negative |
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 |
Automated Detection
...
Tool
...
Version
...
Checker
...
Description
...
...
Can detect violations of this recommendation when checking for violations of EXP12-C. Do not ignore values returned by functions and EXP34-C. Do not dereference null pointers
...
...
CHECKED_RETURN
...
Finds inconsistencies in how function call return values are handled. Coverity Prevent cannot discover all violations of this recommendation, so further verification is necessary
...
...
5.0
...
...
...
80 D
...
3200
...
Related Vulnerabilities
The vulnerability in Adobe Flash [VU#159523] arises because Flash neglects to check the return value from calloc()
. Even when calloc()
returns NULL
, Flash writes to an offset from the return value. Dereferencing NULL
usually results in a program crash, but dereferencing an offset from NULL
allows an exploit to succeed without crashing the program.
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
...
API04-C. Provide a consistent and usable error-checking mechanism
ERR00-C. Adopt and implement a consistent and comprehensive error-handling policy
ERR02-C. Avoid in-band error indicators
ERR05-C. Application-independent code should provide error detection without dictating error handling
EXP12-C. Do not ignore values returned by functions
EXP34-C. Do not dereference null pointers
FIO10-C. Take care when using the rename() function
FIO13-C. Never push back anything other than one read character
FIO33-C. Detect and handle input output errors resulting in undefined behavior
FIO35-C. Use feof() and ferror() to detect end-of-file and file errors when sizeof(int) == sizeof(char)
FLP03-C. Detect and handle floating-point errors
FLP32-C. Prevent or detect domain and range errors in math functions
MEM04-C. Do not perform zero-length allocations
MEM12-C. Consider using a goto chain when leaving a function on error when using and releasing resources
...
Bibliography
[DHS 2006] | Handle All Errors Safely |
[Henricson 1997] | Recommendation 12.1, "Check for All Errors Reported from Functions" |
[ISO/IEC 9899: |
2024] | Subclause 7. |
31. |
3.10, "The ungetc Function" |
...
...