Avoid in-band error indicators while designing interfaces. This practice is commonly used by C library functions but is not recommended. One example from the C standard Standard of a troublesome in-band error indicator is EOF
(see FIO34-C. Use int to capture the return value of character IO functions and FIO35-C. Use feof() and ferror() to detect end-of-file and file errors when sizeof(int) == sizeof(char)Distinguish between characters read from a file and EOF or WEOF). Another problematic use of in-band error indicators from the C standard Standard involving the size_t
and time_t
types is described by MSC31by
...
- integer conversions do not result in lost or misinterpreted data
- FLP07-C. Cast the return value of a function that returns a floating-point type
- INT18-C. Evaluate integer expressions in a larger size before comparing or assigning to that size
Noncompliant Code Example (sprintf()
)
This noncompliant
...
Non-Compliant Code Example
This specific non-compliant code example is from the Linux Kernel Mailing List archive site at http://lkml.org/, although similar examples are common.:
Code Block | ||||
---|---|---|---|---|
| ||||
int i; ssize_t count = 0; for (i = 0; i < 9; ++i) { count += sprintf( sprintf(buf + count, "%02x ", ((u8 *)&slreg_num)[i] ); } count += sprintf(buf + count, "\n"); |
The sprintf()
function returns the number of characters written in the array, not counting the terminating null character. This number is frequently added to an existing counter to keep track of the location of the index into the array. However, the call to sprintf()
can (and will) return -1 −1 on error conditions, such as an encoding error. If this error happens on the first call (which is likely), the count
variable, already at zero0, is decremented. If this index is subsequently used, it will result in an out-of-bounds read or write.
Compliant Solution (sprintf_m()
)
This compliant solution shows the redesigned API for {{ Wiki Markup sprintf()
}} from the CERT managed string library \ [[Burch 06|AA. C References#Burch06]\].Burch 2006]:
Code Block | ||||
---|---|---|---|---|
| ||||
errno_t sprintf_m(
string_m buf,
const string_m fmt,
int *count,
...
);
|
The sprintf_m()
API separates out the return status of the function from information about the number of characters written. In this case, *count
is set to the number of characters written in buf
, while and the return value indicates the return status. Returning the status as the return value of the function increases the likelihood that a programmer will check the return status of the function.
The previous The preceding code example can be amended as follows:
Code Block | ||||
---|---|---|---|---|
| ||||
int i; rsize_t count = 0; errno_t err; for (i = 0; i < 9; ++i) { err = sprintf_m( buf + count, "%02x ", &count, ((u8 *)&slreg_num)[i] ); if (err != 0) { /* handleHandle print error */ } } err = sprintf_m( buf + count, "%02x ", &count, ((u8 *)&slreg_num)[i] ); if (err != 0) { /* handleHandle print error */ } |
Exceptions
ERR02-EX1: Null pointers are another example of an in-band error indicator. Use of null pointers is not quite as bad because it is supported by the language. According to C99 Section 6.3.2.3, "Pointers":
If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
ERR02-EX2: One may use a function returning in-band error indicators if one can securely guarantee the program will not try to continue processing should an error occur in the function.
For example, the functions defined in TR24731-1 provide hooks for internal constraint violations. If a constraint violation handler is guaranteed not to return upon an error occurring, then one may safely ignore errors returned by these functions. One might accomplish this by having the constraint violation handler call abort()
, or longjmp()
, for instance.
See ERR03-A. Use runtime-constraint handlers when calling functions defined by TR24731-1 for more on the functions defined in TR24731-1.
Non-Compliant Code Example (TR24731-1)
Noncompliant Code Example (POSIX ssize_t
)
The ssize_t
data type is designed as a "signed representation of size_t
." Consequently, it is often used as a return type for functions that can return an unsigned value upon success and a negative value upon error. For instance, the POSIX read()
function has the following signature:
Code Block | ||||
---|---|---|---|---|
| ||||
ssize_t read(int fildes, void *buf, size_t nbyte);
|
read()
returns −1 if an error occurs; if no errors occur, it returns the number of bytes actually read.
As with all in-band error indicators, this type is not recommended because developers are tempted to ignore the possibility that a ssize_t
value is negative.
Compliant Solution (POSIX size_t
)
An alternative hypothetical signature for the read()
function is
Code Block | ||||
---|---|---|---|---|
| ||||
errno_t read(int fildes, void *buf, size_t nbyte, size_t* rbytes);
|
where rbytes
is a pointer to a size_t
. If no error occurs, and rbytes
is not NULL
, its value is set to the total number of bytes read, and read()
returns 0. If an error occurs, read()
returns a nonzero value indicating the error.
Noncompliant Code Example (C11, Annex K)
In this noncompliant code In this example, the error handler returns normally, while but the strcpy_s()
function's return value is not checked.:
Code Block | ||||
---|---|---|---|---|
| ||||
constraint_handler_t handle_errors(void) { constraint_handler_t data; /* defineDefine what to do when error occurs */ return data; } /* ... */ set_constraint_handler(handle_errors); /* ... */ /* Returns zero on success */ errno_t function(char *dst1, size_t dst_size) { char src1[100] = "hello"; strcpy_s(dst1, sizeof(dst1)dst_size, src1); /* * At this point strcpy_s may have yielded an * error, and handle_errors() might have returned. */ /* ... */ return 0; } |
Compliant
...
Solution (C11, Annex K)
In this compliant solution, the error handler terminates the program, ensuring Here the code is rectified by having the error handler terminate the program, which ensures that strcpy_s()
never returns unless it worked perfectly.fully succeeds:
Code Block | ||||
---|---|---|---|---|
| ||||
/* * The abort_handler_s() function writes * a message on the * standard error stream and * then calls the abort() function. */ set_constraint_handler(abort_handler_s); /* ... */ /* Returns zero on success */ errno_t function(char *dst1, size_t dst_size) { char src1[100] = "hello"; strcpy_s(dst1, sizeof(dst1)dst_size, src1); /* * Because abort_handler_s() never returns, * we only get here only if strcpy_s() succeeds. */ /* ... */ return 0; } |
Risk Analysis
Exceptions
ERR02-EX1: Null pointers are another example of an in-band error indicator. Use of null pointers is allowed because it is supported by the language. According to the C Standard, subclause 6.3.2.3 [ISO/IEC 9899:2011]:
If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
ERR02-EX2: You may use a function returning in-band error indicators if you can securely guarantee the program will not try to continue processing should an error occur in the function. For example, the functions defined in C11 Annex K provide hooks for internal constraint violations. If a constraint violation handler is guaranteed not to return upon an error, then you may safely ignore errors returned by these functions. You might accomplish this by having the constraint-violation handler call abort()
or longjmp()
, for instance.
See ERR03-C. Use runtime-constraint handlers when calling the bounds-checking interfaces for more on the functions defined in C11 Annex K.
Risk Assessment
The risk in The risk of using in-band error indicators is difficult to quantify and is consequently given as low. However, if the use of in-band error indicators results in programmers' failing to check status codes or incorrectly checking them, the consequences can be more severe.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ERR02- |
C |
Low |
Unlikely |
High | P1 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Parasoft C/C++test |
| CERT_C-ERR02-a | The input/output functions from the 'cstdio' and 'cwchar' libraries should not be used |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[Burch 06|AA. C References#Burch06]\]
\[[ISO/IEC 9899:1999|AA. C References#ISO/IEC 9899-1999]\] Section 6.2.4, "Storage durations of objects," and Section 7.20.3, "Memory management functions"
\[[ISO/IEC PDTR 24772|AA. C References#ISO/IEC PDTR 24772]\] "NZN Returning error status"
\[[ISO/IEC TR 24731-1:2007|AA. C References#ISO/IEC TR 24731-1-2007]\] |
Related Guidelines
Bibliography
[Burch 2006] | |
[ISO/IEC 9899:2011] | Subclause 6.3.2, "Other Operands" |
...
ERR01-A. Use ferror() rather than errno to check for FILE stream errors 12. Error Handling (ERR) ERR03-A. Use runtime-constraint handlers when calling functions defined by TR24731-1