Integer overflows can lead to allocating too little memory, which can often result in an exploitable buffer overflow.
Before each memory allocation, be sure to check the size you're allocating, to make sure it really is big enough to do what you need.
While the following code might seem natural to many, there's the potential for a very big security problem:
char *double_string(char *str, size_t len) {
char *result = (char *)malloc(len*2+1);
if (!result) abort();
strcpy(result, str);
strcat(result, str);
return result;
}
What happens if an attacker can control the value of
len, setting it to
2147483648? Multiplying it by 2 returns 0 as a result, due to an integer overflow (The hex for that value is
0x80000000; the leftmost one gets shifted out of the value). The end result is that we only allocate a single byte of memory. Unless
str happens to have a
NULL as the first byte, the resulting operations will overflow the buffer.
If the length of
str is always tied to the value of
len and
len is an unsigned type instead of a signed type, then an attack is probably not practical, because it would require the actual length of
str to be bigger than any memory value you can allocate (in C and C++ you cannot allocate more than
INT_MAX bytes, whether statically or dynamically).
If you're using a signed value, then the overflow may be practical, depending on how the data is copied. If using something that copies a fixed number of bytes, things probably won't work since the attacker will usually need to overwrite so much data that the program will crash during the overwrite, before ever transferring control over to the attackers code.
However, if using a string operation, then the attacker only needs to get a null character at the point where the overwrite to stop.
Some people might think this sort of problem isn't a big deal, because there is a misconception that heap overflows are almost always harmless. That's far from the truth. Almost inevitably, there are function pointers in the heap, such as the global offset table used for imported libraries, or the addresses of class methods and exception handlers in C++. An attacker just needs to lay down code to execute, replace a function pointer with a pointer to the attack code and then wait until the function pointer gets dereferenced (which is often inevitable).
Consider an example where a network protocol represents the data in a packet using a length field, followed by a payload. The application may read and
malloc() the number of bytes specified by the length, even validating that the packet payload is the right size. But, if the payload happens to have a
NULL in it, and the code uses
strncpy() or something like that, then the actual amount of data copied could be reasonably small.
Protecting against this kind of attack is usually as simple as validating that any calculation did not wrap around. For example, we could modify the code at the beginning of this recipe as such:
char *double_string(char *str, size_t len) {
size_t tomalloc = len*2+1;
char *result;
if (tomalloc < len || !(result = (char *)malloc(tomalloc))) abort();
strcpy(result, str);
strcat(result, str);
return result;
}