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. [[TR 24731-1]] introduces a new type rsize_t
, defined to be size_t
but explicitly used to hold the size of a single object. 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, size_t
if not.
Non-Compliant Code Example
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 *p = copy(9, "hi there");
Signed integer overflow causes undefined behavior, so nothing can be guaranteed about the program afterward. The following is one possible scenario that shows why this should be avoided.
The size_t
type is typically represented by the same number of bits as int
, that is, sizeof(size_t) == sizeof(int))
. In this case, n
might be 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.
Under the same assumption, if size_t
is represented by a greater number of bits than int
, that is, sizeof(size_t) > sizeof(int)
, the same behavior occurs for values of n <= UINT_MAX
. For values of n > UINT_MAX
, all of memory within [INT_MIN, INT_MAX]
from the beginning of the output buffer is overwritten in an infinite loop. This is because the expression ++i
will wrap around to zero before the condition i < n
ever evaluates to false.
Note that in a preemptive multithreaded program, only one thread is in the infinite loop, so it is still significant that out-of-bounds memory is changed.
So even under the most restrictive of assumptions, there are serious problems with the program. Undefined behavior gives license for the implementation to do anything at all, which could be far worse.
Compliant Solution
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 *p = copy(9, "hi there");
Non-Compliant Code Example
In this non-compliant code example, an integer overflow is specifically checked for by checking whether length + 1 == 0
(that is, integer wrap has occurred). If the test passes, a wrapper to malloc()
is called to allocate the appropriate data block (this is a common idiom). In a program compiled using an ILP32 compiler, this code runs as expected, but in an LP64 environment, an integer overflow can occur because length
is now a 64-bit value. The result of the expression, however, is truncated to 32 bits when passed as an argument to alloc()
because it takes an unsigned int
argument.
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 + 1 == 0) { /* 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; }
Compliant Solution
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 + 1 == 0) { /* 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; }
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-A |
medium |
probable |
medium |
P8 |
L2 |
Automated Detection
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.
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
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]]
INT00-A. Understand the data model used by your implementation(s) 04. Integers (INT) INT02-A. Understand integer conversion rules