The size_t
type is the unsigned integer type of the result of the sizeof
operator. The underlying representation of variables 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 which that is used to represent the size of an object, including , but not limited to, integer values used as sizes, indices, loop counters, and lengths, should be declared as rsize_t
, if available. Otherwise, it should be declared size_t
.
...
Noncompliant Code Example
...
In the following this noncompliant code example, the dynamically allocated buffer referenced by p
will overflow overflows for values of n > INT_MAX
.:
Code Block | ||||
---|---|---|---|---|
| ||||
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(20, "hi there"); |
...
(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 sizersize_
teliminates 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
:
Code Block | ||||
---|---|---|---|---|
| ||||
char *copy(sizersize_t n, const char *c_str) { sizersize_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(20, "hi there"(sizeof(c_str), c_str); |
...
Noncompliant Code Example
...
The user defined function calc_size
(not shown) used to calculate the size of the string other_srting
. The result of calc_size
is a signed int
returned into str_size
. Given that there is no check on str_size
, it is impossible to tell whether the result of calc_size
is an appropriate parameter for malloc that is, a positive integer that can be properly represented by a signed int
type.
Code Block |
---|
int str_size = calc_size(other_string);
char *str_copy = malloc(str_size);
|
Compliant Code Example 2
By changing str_size
to a variable of type size_t
, it can be assured that the call to malloc()
is, at the least, supplied a non-negative number.
Code Block |
---|
size_t str_size = calc_size(other_string);
char *str_copy = malloc(str_size);
|
Non-compliant Code Example 2
Add an example using size_t as an index
References
...
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()
. Both read functions return zero on success and nonzero on failure.
Code Block | ||||
---|---|---|---|---|
| ||||
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)) {
return -1;
}
data = (unsigned char*)alloc(length+1);
if (data == NULL) {
return -1; /* Indicate failure */
}
if (read_network_data(fd, data, length)) {
free(data);
return -1;
}
data[length] = '\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.
Code Block | ||||
---|---|---|---|---|
| ||||
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)) {
return -1;
}
data = (unsigned char*)alloc(length+1);
if (data == NULL) {
return -1; /* Indicate failure */
}
if (read_network_data(fd, data, length)) {
free(data);
return -1;
}
data[length] = '\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 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Axivion Bauhaus Suite |
| CertC-INT01 | |||||||
CodeSonar |
| LANG.TYPE.BASIC | Basic numerical type used | ||||||
Compass/ROSE | Can detect violations of this recommendation. In particular, it catches comparisons and operations where one operand is of type | ||||||||
Splint |
|
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
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
...
...