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