Most functions defined by the C Standard, Annex K Bounds-checking interfaces, include, as part of their specification, a list of runtime constraints, violations of which can be consistently handled at runtime. Library implementations must verify that the runtime constraints for a function are not violated by the program. If a runtime constraint is violated, the runtime-constraint handler currently registered with set_constraint_handler_s()
is called.
Annex K, subclause K.3.6.1.1, of the C Standard [ISO/IEC 9899:2011] states:
When the handler is called, it is passed the following arguments in the following order:
- A pointer to a character string describing the runtime-constraint violation.
- A null pointer or a pointer to an implementation-defined object.
- If the function calling the handler has a return type declared as
errno_t
, the return value of the function is passed. Otherwise, a positive value of typeerrno_t
is passed.The implementation has a default constraint handler that is used if no calls to the
set_constraint_handler_s()
function have been made or the handler argument toset_constraint_handler_s()
is a null pointer. The behavior of the default handler is implementation-defined, and it may cause the program to exit or abort.
And subclause K.3.1.4 states:
The runtime-constraint handler might not return. If the handler does return, the library function whose runtime-constraint was violated shall return some indication of failure as given by the returns section in the function's specification.
These runtime-constraint handlers mitigate some of the potential insecurity caused by in-band error indicators. (See ERR02-C. Avoid in-band error indicators.)
Noncompliant Code Example (C11 Annex K)
In this noncompliant code example, the strcpy_s()
function is called, but no runtime-constraint handler has been explicitly registered. As a result, the implementation-defined default handler is called on a runtime error.
Code Block | ||||
---|---|---|---|---|
| ||||
errno_t function(char *dst1, size_t size){
char src1[100] = "hello";
if (strcpy_s(dst1, size, src1) != 0) {
return -1;
}
/* ... */
return 0;
}
|
The result is inconsistent behavior across implementations and possible termination of the program instead of a graceful exit. The implementation-defined default handler performs a default action consistent with a particular implementation. However, this may not be the desired action, and because the behavior is implementation-defined, it is not guaranteed to be the same on all implementations.
It is therefore prudent to explicitly install a runtime-constraint handler to ensure consistent behavior across implementations.
Compliant Solution (C11 Annex K)
This compliant solution explicitly installs a runtime-constraint handler by invoking the set_constraint_handler_s()
function. It would typically be performed during system initialization and before any functions that used the mechanism were invoked.
Code Block | ||||
---|---|---|---|---|
| ||||
constraint_handler_t handle_errors(void) {
/* Handle runtime-constraint error */
}
/* ... */
set_constraint_handler_s(handle_errors);
/* ... */
/* Returns zero on success */
errno_t function(char *dst1, size_t size){
char src1[100] = "hello";
if (strcpy_s(dst1, size, src1) != 0) {
return -1;
}
/* ... */
return 0;
}
|
Compliant Solution (Visual Studio 2008 and later)
Although the C11 Annex K functions were created by Microsoft, Microsoft Visual Studio does not support the same interface defined by the technical report for installing runtime-constraint handlers. Visual Studio calls these functions invalid parameter handlers, and they are installed by calling the _set_invalid_parameter_handler()
function. The signature of the handler is also significantly different [MSDN].
Code Block | ||||
---|---|---|---|---|
| ||||
_invalid_parameter_handler handle_errors(
const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved
) {
/* Handle invalid parameter */
}
/* ... */
_set_invalid_parameter_handler(handle_errors)
/* ... */
errno_t function(char *dst1, size_t size) {
char src1[100] = "hello";
if (strcpy_s(dst1, size, src1 |
This indicator is currently a stub.
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 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)). Another problematic use of in-band error indicators from the C standard involving the size_t
and time_t
types is described by MSC31-C. Ensure that return values are compared against the proper type.
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(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 on error conditions such as an encoding error. If this happens on the first call (which is likely), the count
variable, already at zero, is decremented. If this index is subsequently used, it will result in an out-of-bounds read or write.
Compliant Solution (sprintf_m()
)
Wiki Markup |
---|
This compliant solution shows the redesigned API for {{sprintf()}} from the CERT managed string library \[[Burch 06|AA. C References#Burch06]\]. |
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 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.
One can thus amend the previous code example thus:
Code Block | ||
---|---|---|
| ||
int i; rsize_t count = 0; errno_t err; for (i = 0; i < 9; ++i) { if ((err = sprintf_m( buf + count, "%02x ", &count, ((u8 *)&slreg_num)[i])) != 0) { /* handle print error */ } } if ((err = sprintf_m( buf + count, "%02x ", &count, ((u8 *)&slreg_num)[i]) ) != 0) { return -1; } /* handle print error */... */ return 0; } |
Exceptions
ERR02-EX1: Null pointers are another example of an in-band error indicator. Use of the null pointers is not quite as bad because it is supported for 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.
Risk Analysis
Risk Assessment
C11 Annex K indicates that if no constraint handler is set, a default one executes when errors arise. The default handler is implementation-defined and "may cause the program to exit or abort" [ISO/IEC 9899:2011]. It is important to understand the behavior of the default handler for all implementations being used and replace it if the behavior is inappropriate for the applicationThe 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 or incorrectly checking status code, the consequences can be more severe.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
ERR02ERR03-A C | low Low | unlikely Unlikely | high Medium | P1 P2 | L3 |
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 ERR03-CPP. Use runtime-constraint handlers when calling functions defined by TR24731-1 |
Bibliography
...
9899:2011] | Subclause K.3.1.4, "Runtime-Constraint Violations" |
[MSDN] | "Parameter Validation" |
...
"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"ERR02-A. Avoid in-band error indicators 13. 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