...
Code Block | ||
---|---|---|
| ||
char *copy(size_t n, const char *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 illustrates why this should be avoided.
Wiki Markup |
---|
The {{size_t}} type is typically represented by the same number of bits as {{int}}, that is, {{sizeof(size_t) == sizeof(int))}}. AssumingIn quiet wraparound on signed overflowthis case, {{n}} might be greater than {{INT_MAX}}. Assuming quiet wraparound on Thesigned loopoverflow, the however,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. |
Wiki Markup |
---|
IfUnder 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 are 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.
Therefore, 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
.
...
In this non-compliant code example, an integer overflow is specifically checked for by checking if 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. Tthe 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.
...