mi_rezalloc() zero initialization is unsafe

In _mi_realloc_zero() and _mi_heap_malloc_zero() only the requested size and not the possibly larger allocated size is initialized to zero.

This may lead to uninitialized 'new' memory from the applications point of view in a following call to _mi_realloc_zero(), where the 'fits' test using mi_usable_size(p) and the memcpy(newp, p, ...) relies on a defined state of the previously allocated and not only the previously requested memory range.

Also take into account, that using mi_rezalloc() on a pointer from a call to mi_malloc() is legal but would lead to uninitialized memory in the range from the requested size to the allocated size too. It seems to me that the rezalloc feature requires to store the requested size or at least to zero out the implicit allocated range form requested size to allocated size in all allocations.

I wonder if this is why posix doesn't specify rezalloc().


On my windows machine this code sample can reproduce the problem:

int main()
{
  //  mi_rezalloc() is not safe!

  //  test precondition: first allocation on page!

  //  force allocating the last block in the page next, so it will be 'recycled' immediately...
  for (int idx = 0; idx < 127; ++idx)
    ::mi_free(::mi_malloc(128));

  //  force known state of 'uninitialized' memory (by mi_malloc debug initialization or explicit memset)
  auto uninitialized = static_cast< unsigned char* >(::mi_malloc(128));
  assert(mi_usable_size(uninitialized) == 128);
  ::memset(uninitialized, 0xD0, 128);
  ::mi_free(uninitialized);

  //  this initialized allocation recycles the uninitialized pointer (if not, adapt forcing loop above or use a different size class...)
  auto initialized = static_cast< unsigned char* >(::mi_rezalloc(nullptr, 121));
  assert(mi_usable_size(initialized) == 128);
  assert(uninitialized == initialized);
  assert(initialized[  0] ==    0); // access in requested range; mimalloc has initialized this!
  assert(initialized[120] ==    0); // access in requested range; mimalloc has initialized this!
  assert(initialized[121] == 0xd0); // access in verified usable range; mimalloc has not initialized this!
  initialized[  0] = 'A';
  initialized[120] = 'Z';

  //  grow with initialization! remember what the api documentation says:
  //  > If the newsize is larger than the original allocated size of p, the extra bytes are initialized to zero.
  initialized = static_cast< unsigned char* >(::mi_rezalloc(initialized, 122));
  assert(mi_usable_size(initialized) == 128);
  assert(uninitialized == initialized);
  assert(initialized[  0] ==  'A'); // access in requested old range; mimalloc has not re-initialized this!
  assert(initialized[120] ==  'Z'); // access in requested old range; mimalloc has not re-initialized this!
  assert(initialized[121] == 0x00); // access in requested initialized new memory; mimalloc has not initialized this!
}