https://bugs.winehq.org/show_bug.cgi?id=55842
--- Comment #1 from Paul Gofman pgofman@codeweavers.com --- Thanks for bisecting. I could reproduce the crash with the game.
While attribution to the blamed commit is not exactly random, the problem is pre-existing (probably since the introduction of LFH heap) and the game essentially worked by chance before the blamed commit.
What is a bit specific with the game is that it uses msvcr80._set_sbh_threshold() which triggers the use of a separate heap in msvcr80.msvcrt_heap_alloc(). Then, msvcrt_heap_free() uses HeapValidate() to guess the correct heap:
----- static BOOL msvcrt_heap_free(void *ptr) { if(sb_heap && ptr && !HeapValidate(heap, 0, ptr)) { void **saved = SAVED_PTR(ptr); return HeapFree(sb_heap, 0, *saved); }
return HeapFree(heap, 0, ptr); } -----
The actual problem is that RtlValidateHeap() doesn't work correctly for LFH blocks placed in large blocks which can happen for bigger LFH block bin sizes. Not the most frequent occasion actually but happens with the game, and once during loading it gets LFH block of size 0x4000 allocated this way. Once HeapValidate failed in msvcrt_heap_free for this block (for the correct block without any heap corruption), msvcrt_heap_free dereferences ptr - 4 to free that pointer with HeapFree from another heap. That's where blamed commit comes in. Before that the thing was lucky and was getting the value from (ptr - 4) which could be dereferenced (to some random "pointer"), then HeapFree() was failing to free it (leaking memory) but not crashing allowing the game continue. With the blamed commit the value before ptr has expectedly change and now dereferencing that crashes in msvcrt_heap_free.
I sent a MR which is fixing the true problem (failing HeapValidate): https://gitlab.winehq.org/wine/wine/-/merge_requests/4232