...
Signed integer overflow causes undefined behavior. 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, {{are two possible conditions under which this code constitutes a serious vulnerability: Wiki Markup
sizeof(size_t)
...
==
...
sizeof(int))
Wiki Markup |
---|
The unsigned }}. In this case, {{n}} might bemay 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. |
Under the same assumption, if {{size_t}} is represented by a greater number of bits than {{int}}, that is, {{ |
sizeof(size_t)
...
>
...
sizeof(int)
Wiki Markup |
---|
Similar behavior as}}, the samecase behaviorabove 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.
...
This causes all memory within {{\[INT_MIN, INT_MAX\]}} from the beginning of the output buffer is overwritten in an infinite loop. |
Compliant Solution (TR 24731-1)
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
.
...
In this non-compliant code example, an integer overflow is specifically checked 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 (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.
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) < 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 (TR 24731-1)
Declaring both length
and the blocksize
argument to alloc()
as rsize_t
eliminates the possibility of truncation.
...