-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Describe the bug
The VC runtime explicitly supports user replacement of the malloc / free family of heap functions with custom implementations. For example, in malloc_base.cpp in the UCRT source there is this comment at the top:
// Implementation of _malloc_base(). This is defined in a different source file
// from the malloc() function to allow malloc() to be replaced by the user.
There is also this comment before the _malloc_base function:
// This function implements the logic of malloc(). It is called directly by the
// malloc() function in the Release CRT and is called by the debug heap in the
// Debug CRT.
//
// This function must be marked noinline, otherwise malloc and
// _malloc_base will have identical COMDATs, and the linker will fold
// them when calling one from the CRT. This is necessary because malloc
// needs to support users patching in custom implementations.
extern "C" __declspec(noinline) _CRTRESTRICT void* __cdecl _malloc_base(size_t const size)
However, the STL's _Locinfo class in the <xlocinfo> header has a problem where it will call free to free an object that was allocated by the CRT with an explicit call to _malloc_base. If the user has replaced malloc and free with custom implementations, then the STL will call the user's free with a pointer that was not allocated by the user's malloc function.
In particular, the _Getdays function (and other similar functions on that file) calls the UCRT's ::_Getdays export, which ends up doing this:
__crt_unique_heap_ptr<char> buffer(_malloc_crt_t(char, length + 1));
Where _malloc_crt_t is a macro that expands to _malloc_base, i.e. the CRT's default malloc implementation. And then, the STL calls free to free this object.
Instead of calling free, the STL should call the matching _free_crt_t (which is an alias for _free_base).
Command-line test case
C:\Temp>type repro.cpp
#include <Windows.h>
#include <sstream>
#include <iomanip>
#include <cassert>
HANDLE heap = HeapCreate(0, 0, 0);
extern "C" void* malloc(size_t size)
{
void* ptr = HeapAlloc(heap, 0, size);
printf("malloc: %p\n", ptr);
return ptr;
}
extern "C" void free(void* ptr)
{
printf("free: %p\n", ptr);
HeapFree(heap, 0, ptr);
}
int main(int argc, char** argv)
{
std::tm tm;
std::istringstream stream{"Thu, 01 Jan 1970 00:00:00 GMT"};
stream >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S %Z");
printf("OK\n");
return 0;
}
C:\Temp>cl /EHsc /MT repro.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.27.29016 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
repro.cpp
Microsoft (R) Incremental Linker Version 14.27.29016.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:repro.exe
repro.obj
C:\Temp>repro
malloc: 015B05B8
malloc: 015B05E0
malloc: 015B05F0
malloc: 015B0618
free: 015B0618
malloc: 015B0618
malloc: 015B0628
malloc: 015B0648
malloc: 015B0658
free: 015B0658
free: 015B0648
malloc: 015B0648
malloc: 015B0658
malloc: 015B0668
free: 015B05B8
malloc: 015B0690
malloc: 015B05B8
malloc: 015B05C8
malloc: 015B06E0
free: 012858B8 // <---- NOTE: Not allocated by user malloc
C:\Temp>echo %ERRORLEVEL%
-1073740940 // <---- executable crashed
The callstack for the crash is:
> ntdll.dll!_RtlReportCriticalFailure
ntdll.dll!_RtlpReportHeapFailure
ntdll.dll!_RtlpHpHeapHandleError
ntdll.dll!_RtlpLogHeapFailure
ntdll.dll!_RtlpAnalyzeHeapFailure
ntdll.dll!@RtlpFreeHeap
ntdll.dll!_RtlpFreeHeapInternal
ntdll.dll!RtlFreeHeap
Expected behavior
The expectation is that the STL never uses a mismatched call to free an object. If the object was allocated with malloc then it should use free, and if it was allocated by _malloc_base then it should use _free_base.
STL version
Microsoft Visual Studio Professional 2019
Version 16.6.4
Also tracked by DevCom-246248 and Microsoft-internal VSO-470455 / AB#470455 .