This time, I’m not going to set up a story. I’m just going to go straight to the punch line.
A customer overrode the new operator in order to add
additional instrumentation.
Something like this:
struct EXTRASTUFF
{
DWORD Awesome1;
DWORD Awesome2;
};
// error checking elided for expository purposes
void *operator new(size_t n)
{
EXTRASTUFF *extra = (EXTRASTUFF)malloc(sizeof(EXTRASTUFF) + n);
extra->Awesome1 = get_awesome_1();
extra->Awesome2 = get_awesome_2();
return ((BYTE *)extra) + sizeof(EXTRASTUFF);
}
// use your imagination to implement
// operators new[], delete, and delete[]
This worked out okay on 32-bit systems because in 32-bit Windows,
MEMORY_ALLOCATION_ALIGNMENT is 8,
and sizeof(EXTRASTUFF) is also 8.
If you start with a value that is a multiple of 8,
then add 8 to it,
the result is still a multiple of 8,
so the pointer returned by the custom
operator new remains properly aligned.
But on 64-bit systems, things went awry.
On 64-bit systems,
MEMORY_ALLOCATION_ALIGNMENT is 16,
As a result, the custom
operator new handed out guaranteed-misaligned memory.
The misalignment went undetected for a long time,
but the sleeping bug finally woke up when somebody
allocated a structure that contained an
SLIST_ENTRY.
As we saw earlier,
the
SLIST_ENTRY
really does need to be aligned according to the
MEMORY_ALLOCATION_ALIGNMENT,
especially on 64-bit systems,
because 64-bit Windows takes advantage of the extra “guaranteed to be zero”
bits that 16-byte alignment gives you.
If your
SLIST_ENTRY is not 16-byte aligned,
then those “guaranteed to be zero” bits are not actually zero,
and then the algorithm breaks down.
Result: Memory corruption and eventually a crash.
0 comments