Skip to content

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

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 .

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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