CertC-INT01¶
Use rsize_t or size_t for all integer values representing the size of an object
Required inputs: IR
The
size_t type is the unsigned integer type of the result of the
sizeof operator. Variables of type
size_t are guaranteed to be of sufficient precision to represent
the size of an object. The limit of
size_t is specified by the
SIZE_MAX macro.
The type
size_t generally covers the entire address space. The C
Standard, Annex K (normative), "Bounds-checking interfaces," introduces a
new type,
rsize_t, defined to be
size_t but explicitly used to hold the size of a single object [
Meyers
2004]. In code that documents this purpose by using the type
rsize_t, the size of an object can be checked to verify that it is
no larger than
RSIZE_MAX, the maximum size of a normal single object, which
provides additional input validation for library functions. See
STR07-C.
Use the bounds-checking interfaces for string manipulation for additional
discussion of C11 Annex K.
Any variable that is used to represent the size of an object, including integer
values used as sizes, indices, loop counters, and lengths, should be declared
rsize_t, if available. Otherwise, it should be declared
size_t.
Noncompliant Code Example
In this noncompliant code example, the dynamically allocated buffer referenced
by
p overflows for values of
n > INT_MAX:
char *copy(size_t n, const char *c_str) {
int i;
char *p;
if (n == 0) {
/* Handle unreasonable object size error */
}
p = (char *)malloc(n);
if (p == NULL) {
return NULL; /* Indicate malloc failure */
}
for ( i = 0; i < n; ++i ) {
p[i] = *c_str++;
}
return p;
}
/* ... */
char c_str[] = "hi there";
char *p = copy(sizeof(c_str), c_str);
Signed integer overflow causes undefined behavior. The following are two possible conditions under which this code constitutes a serious vulnerability:
sizeof(size_t) == sizeof(int)
The unsigned
n may contain a value greater than
INT_MAX. Assuming quiet wraparound on signed overflow, the loop
executes
n times because the comparison
i < n is an unsigned comparison. Once
i is incremented beyond
INT_MAX,
i takes on negative values starting with
(INT_MIN). Consequently, the memory locations referenced by
p[i] precede the memory referenced by
p, and a write outside array bounds occurs.
sizeof(size_t) > sizeof(int)
For values of
n where
0 < n <= INT_MAX, the loop executes
n times, as expected.
For values of
n where
INT_MAX < n <= (size_t)INT_MIN, the loop executes
INT_MAX times. Once
i becomes negative, the loop stops, and
i remains in the range
0 through
INT_MAX.
For values of
n where
(size_t)INT_MIN < n <= SIZE_MAX,
i wraps and takes the values
INT_MIN to
INT_MIN + (n - (size_t)INT_MIN - 1). Execution of the loop
overwrites memory from
p[INT_MIN] through
p[INT_MIN + (n - (size_t)INT_MIN - 1)].
Compliant Solution (C11, Annex K)
Declaring
i to be of type
rsize_t eliminates the possible integer overflow condition (in
this example). Also, the argument
n is changed to be of type
rsize_t to document additional
validation
in the form of a check against
RSIZE_MAX:
char *copy(rsize_t n, const char *c_str) {
rsize_t i;
char *p;
if (n == 0 || n > RSIZE_MAX) {
/* Handle unreasonable object size error */
}
p = (char *)malloc(n);
if (p == NULL) {
return NULL; /* Indicate malloc failure */
}
for (i = 0; i < n; ++i) {
p[i] = *c_str++;
}
return p;
}
/* ... */
char c_str[] = "hi there";
char *p = copy(sizeof(c_str), c_str);
Noncompliant Code Example
In this noncompliant code example, the value of
length is read from a network connection and passed as an argument
to a wrapper to
malloc() to allocate the appropriate data block. Provided that the
size of an
unsigned long is equal to the size of an
unsigned int, and both sizes are equal to or smaller than the size
of
size_t, this code runs as expected. However, if the size of an
unsigned long is greater than the size of an
unsigned int, the value stored in
length may be truncated when passed as an argument to
alloc().
void *alloc(unsigned int blocksize) {
return malloc(blocksize);
}
int read_counted_string(int fd) {
unsigned long length;
unsigned char *data;
if (read_integer_from_network(fd, &length) < 0) {
return -1;
}
data = (unsigned char*)alloc(length);
if (data == NULL) {
return -1; /* Indicate failure */
}
if (read_network_data(fd, data, length) < 0) {
free(data);
return -1;
}
data[length-1] = '\0';
/* ... */
free( data);
return 0;
}
Compliant Solution (C11, Annex K)
Declaring both
length and the
blocksize argument to
alloc() as
rsize_t eliminates the possibility of truncation. This compliant
solution assumes that
read_integer_from_network() and
read_network_data() can also be modified to accept a
length argument of type pointer to
rsize_t and
rsize_t, respectively. If these functions are part of an external
library that cannot be updated, care must be taken when casting
length into an
unsigned long to ensure that integer truncation does not occur.
void *alloc(rsize_t blocksize) {
if (blocksize == 0 || blocksize > RSIZE_MAX) {
return NULL; /* Indicate failure */
}
return malloc(blocksize);
}
int read_counted_string(int fd) {
rsize_t length;
unsigned char *data;
if (read_integer_from_network(fd, &length) < 0) {
return -1;
}
data = (unsigned char*)alloc(length);
if (data == NULL) {
return -1; /* Indicate failure */
}
if (read_network_data(fd, data, length) < 0) {
free(data);
return -1;
}
data[length-1] = '\0';
/* ... */
free( data);
return 0;
}
Risk Assessment
The improper calculation or manipulation of an object's size can result in exploitable vulnerabilities.
| Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| INT01-C | Medium | Probable | Medium | P8 | L2 |
Related Guidelines
| SEI CERT C++ Coding Standard | VOID INT01-CPP. Use rsize_t or size_t for all integer values representing the size of an object |
Bibliography
| [ Meyers 2004] |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
use_rsize_t_or_size_t_for_both_ops |
Use rsize_t or size_t for both arguments of the relational operator. |
None |
False |
use_rsize_t_or_size_t_in_alloc |
Use rsize_t or size_t for the argument of the call to a memory allocating function. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
object_allocators¶
object_allocators
The list of functions that allocate objects, with positions of arguments representing an object size.Type: dict[bauhaus.analysis.config.QualifiedName, list[int]]
Default:
{ 'aligned_alloc': [1], 'calloc': [1], 'malloc': [0], 'realloc': [1] }