Integer overflows are becoming a new security attack vector. Mike Howard’s article discusses some of the ways you can protect yourself against integer overflow attacks.
One attack vector he neglects to mention is integer overflow in the new[] operator. This operator performs an implicit multiplication that is unchecked:
int *allocate_integers(int howmany) { return new int[howmany]; }
If you study the code generation for this, it comes out to
mov eax, [esp+4] ; eax = howmany shl eax, 2 ; eax = howmany * sizeof(int) push eax call operator new ; allocate that many bytes pop ecx retd 4
Notice that the multiplication by sizeof(int) is not checked for overflow. Somebody can trick you into under-allocating memory by passing a value like howmany = 0x40000001. For larger structures, multiplication overflow happens sooner.
Let’s look at a slightly longer example:
class MyClass { public: MyClass(); // constructor int stuff[256]; }; MyClass *allocate_myclass(int howmany) { return new MyClass[howmany]; }
This class also contains a constructor, so allocating an array of them involves two steps: allocate the memory, then construct each object. The allocate_myclass function compiles to this:
mov eax, [esp+4] ; howmany shl eax, 10 ; howmany * sizeof(MyClass) push esi push eax call operator new ; allocate that many bytes mov esi, eax test esi, esi pop ecx je fail push OFFSET MyClass::MyClass push [esp+12] ; howmany push 1024 ; sizeof(MyClass) push esi ; memory block call `vector constructor iterator` mov eax, esi jmp loop fail: xor eax, eax done: pop esi retd 4
This function does an unchecked multiplication of the size, then tries to allocate that many bytes, then tells the vector constructor iterator to call the constructor (MyClass::MyClass) that many times.
If somebody tricks you into calling allocate_myclass(0x200001), the multiplication overflows and only 1024 bytes are allocated. This allocation succeeds, and then the vector constructor tries to initialize 0x200001 of those items, even though in reality only one of them got allocated. So you walk off the end of the memory block and start corrupting memory.
That’s a bad thing.
To protect against this, you can wrap an integer overflow check around the array allocation.
template<typename T> T* NewArray(size_t n) { if (n <= (size_t)-1 / sizeof(T)) return new T[n]; // n is too large - act as if we // ran out of memory return NULL; }
Note: If you use a throwing “new”, then replace the “return NULL” with an appropriate throw.
You can now use this template to allocate arrays in an overflow-safe manner.
MyClass *allocate_myclass(int howmany) { return NewArray<MyClass>(howmany); }
This generates the following code:
push edi mov edi, [esp+8] ; howmany cmp edi, 4194303 ; overflow? ja overflow mov eax, edi shl eax, 10 push esi push eax call operator new mov esi, eax test esi, esi pop ecx je failed push OFFSET MyClass::MyClass push edi push 1024 push esi call call `vector constructor iterator` mov eax, esi jmp done failed: xor eax, eax done: pop esi jmp exit overflow: xor eax, eax exit: pop edi retd 4
Notice the new code that checks for a possible integer multiplication overflow.
But how could you get tricked into an overflow situation?
The most common way of doing this is by reading the value out of a file or some other storage location. For example, if your code is parsing a file that has a section whose format is “length followed by data”, somebody could intentionally put an overflow-inducing value into the “length” field, then get somebody else to try to load the file.
This is particularly dangerous if the filetype is something that is generally considered “not dangerous”, like a JPG.
0 comments