Skip to content

<xlocinfo>: Mismatched call to free when malloc is replaced with user implementation #1066

@jblazquez

Description

@jblazquez

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 .

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions