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. ISO/IEC TR 24731-1-2007 introduces a new type {{rsize_t}}, defined to be {{size_t}} but explicitly used to hold the size of a single object \[[ISO/IEC TR 24731-1-2007|AA. C References#ISO/IEC TR 24731-1-2007]\]. 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-A. Use TR 24731 for remediation of existing string manipulation code] for additional discussion of TR 24731-1. |
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 as rsize_t if available, or otherwise as size_t.
In this non-compliant code example, the dynamically allocated buffer referenced by p overflows for values of n > INT_MAX.
char *copy(size_t n, char const *str) {
int i;
char *p = (char *)malloc(n);
if (p == NULL) {
/* Handle malloc failure */
}
for ( i = 0; i < n; ++i ) {
p[i] = *str++;
}
return p;
}
/* ... */
char str[] = "hi there";
char *p = copy(sizeof(str), 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 > 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)Similar behavior as the case above occurs for values of {{n <= UINT_MAX}}. For values of {{n > UINT_MAX}}, the expression {{\++i}} will wrap around to zero before the condition {{i < n}} ever evaluates to false. This causes all memory within {{\[INT_MIN, INT_MAX\]}} from the beginning of the output buffer to be overwritten in an infinite loop. |
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, char const *str) {
rsize_t i;
char *p;
if (n > RSIZE_MAX) {
/* Handle unreasonable object size error */
}
p = (char *)malloc(n);
if (p == NULL) {
/* Handle malloc failure */
}
for ( i = 0; i < n; ++i ) {
p[i] = *str++;
}
return p;
}
/* ... */
char str[] = "hi there";
char *p = copy(sizeof(str), str);
|
In this non-compliant code example, an integer overflow is specifically looked for by checking whether length + 1 == 0 (that is, integer wrap around has occurred). If the test passes, a wrapper to malloc() is called to allocate the appropriate data block. When UINT_MAX == ULONG_MAX, this code runs as expected, but if ULONG_MAX > UINT_MAX, an integer overflow can occur when length is passed in to alloc() because the result is truncated down to an unsigned int.
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;
}
if (length == ULONG_MAX) {
/* handle integer overflow */
}
data = (unsigned char*)alloc(length + 1);
if (read_network_data(fd, data, length) < 0) {
free(data);
return -1;
}
data[length] = '\0';
/* ... */
free( data);
return 0;
}
|
Declaring both length and the blocksize argument to alloc() as rsize_t eliminates the possibility of truncation.
void *alloc(rsize_t blocksize) {
if (blocksize > RSIZE_MAX) {
/* Handle error */
}
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;
}
if (length >= RSIZE_MAX) {
/* handle integer overflow */
}
data = (unsigned char*)alloc(length + 1);
if (read_network_data(fd, data, length) < 0) {
free(data);
return -1;
}
data[length] = '\0';
/* ... */
free( data);
return 0;
}
|
The improper calculation or manipulation of an object's size can result in exploitable vulnerabilities.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
|---|---|---|---|---|---|
INT01-A |
medium |
probable |
medium |
P8 |
L2 |
Fortify SCA Version 5.0 with CERT C Rule Pack will detect integer operations that cause overflow, but not all cases where size_t is not used.
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
\[[ISO/IEC 9899:1999|AA. C References#ISO/IEC 9899-1999]\] Section 7.17, "Common definitions {{<stddef.h>}}", Section 7.20.3, "Memory management functions"
\[[ISO/IEC TR 24731-1:2007|AA. C References#ISO/IEC TR 24731-1-2007]\] |
INT00-A. Understand the data model used by your implementation(s) 04. Integers (INT) INT02-A. Understand integer conversion rules