microsoft / microsoft/STL

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

Open
#1,066 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
C++
Stars
11.1k
Forks
1.7k
Avg merge
4d 15h
Merged PRs (30d)
22

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 .

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in stl/inc/xlocinfo at _Getdays and the other similar functions that release buffers returned by the UCRT. Compare the allocation path using _malloc_crt_t with the matching deallocation alias, then run the provided repro.cpp command-line case. Done means custom malloc/free replacements no longer receive mismatched pointers and the repro does not crash.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.