Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

!heap doesn't understand the structure of Windows 11 heaps #260

Open
tyotypic opened this issue Feb 10, 2025 · 0 comments
Open

!heap doesn't understand the structure of Windows 11 heaps #260

tyotypic opened this issue Feb 10, 2025 · 0 comments

Comments

@tyotypic
Copy link

tyotypic commented Feb 10, 2025

Hi Team,

(This issue probably subsumes #31, #99, and maybe some others, but I did a bit more analysis).

It looks like the user mode heap extension (!heap) doesn’t understand the structure of Windows 11 heaps.

Here is the c++ code I run: The language level and toolset it’s compiled with don’t seem to make a difference, only the OS that windbg and the executable are running on. I compile this with optimisations disabled.

#include <windows.h>
int main()
{
                const int num_heaps {5};
                HANDLE heaps[num_heaps] {0};

                for (int i {0}; i < num_heaps; ++i)
                {
                                heaps[i] = ::HeapCreate(HEAP_GENERATE_EXCEPTIONS, 10*1024*1024, 0);
                }

                ::DebugBreak();

    return 0;
}

I let WER take the process dump, but it doesn’t make a difference whether I just stop it at a breakpoint and take the dump, or pause the process on a wait for input, so it’s not the DebugBreak() that’s muddying the waters (it does in some instances).
Here is the windbg output when run the code is run on win 11 and the dump is analysed on the same computer:

[…]
Microsoft (R) Windows Debugger Version 10.0.27725.1000 AMD64
[…]
User Mini Dump File with Full Memory: Only application data is available
[…]
Windows 10 Version 26100 MP (8 procs) Free x64
Product: WinNt, suite: SingleUserTS
Edition build lab: 26100.1.amd64fre.ge_release.240331-1435
[…]
0:000> .chain
[…]
Extension DLL chain:
MachOBinComposition: image 10.0.27725.1000, API 0.0.0,
[path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2410.11001.0_x64__8wekyb3d8bbwe\amd64\winext\MachOBinComposition.dll]
ELFBinComposition: image 10.0.27725.1000, API 0.0.0,
[path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2410.11001.0_x64__8wekyb3d8bbwe\amd64\winext\ELFBinComposition.dll]
dbghelp: image 10.0.27725.1000, API 10.0.6,
[path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2410.11001.0_x64__8wekyb3d8bbwe\amd64\dbghelp.dll]
exts: image 10.0.27725.1000, API 1.0.0,
[path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2410.11001.0_x64__8wekyb3d8bbwe\amd64\WINXP\exts.dll]
uext: image 10.0.27725.1000, API 1.0.0,
[path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2410.11001.0_x64__8wekyb3d8bbwe\amd64\winext\uext.dll]
ntsdexts: image 10.0.27725.1000, API 1.0.0,
[path: C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2410.11001.0_x64__8wekyb3d8bbwe\amd64\WINXP\ntsdexts.dll]
0:000> .dumpdebug
----- User Mini Dump Analysis

MINIDUMP_HEADER:
Version A793 (A0F4)
NumberOfStreams 17
Flags 00021826
00000002 MiniDumpWithFullMemory
00000004 MiniDumpWithHandleData
00000020 MiniDumpWithUnloadedModules
00000800 MiniDumpWithFullMemoryInfo
00001000 MiniDumpWithThreadInfo
00020000 MiniDumpIgnoreInaccessibleMemory

Streams:
[…]
Stream 16: type UnusedStream (0), size 00000000, RVA 00000000
0:000> !heap
Heap Address NT/Segment Heap

0000020227b80000              NT Heap

That’s it. One heap listed, the default process heap. The five user-created heaps are not listed. Then,

0:000> dx (ntdll!_HEAP*)0x0000020227b80000
(ntdll!_HEAP*)0x0000020227b80000 : 0x20227b80000 [Type: _HEAP *]
[+0x000] Segment [Type: _HEAP_SEGMENT]
[+0x000] Entry [Type: _HEAP_ENTRY]
[+0x010] SegmentSignature : 0xffeeffee [Type: unsigned long]
[+0x014] SegmentFlags : 0x2 [Type: unsigned long]
[+0x018] SegmentListEntry [Type: _LIST_ENTRY]
[+0x028] Heap : 0x20227b80000 [Type: _HEAP *]
[+0x030] BaseAddress : 0x20227b80000 [Type: void *]
[…]

It looks valid enough (to a layman). If I look at the local variables, in Heaps.exe!main, there are five created heaps and five handles stored. If I dx one of those:

0:000> dx (ntdll!_HEAP*)0x00000149ce710000
(ntdll!_HEAP*)0x00000149ce710000 : 0x149ce710000 [Type: _HEAP *]
[+0x000] Segment [Type: _HEAP_SEGMENT]
[+0x000] Entry [Type: _HEAP_ENTRY]
[+0x010] SegmentSignature : 0xffeeffee [Type: unsigned long]
[+0x014] SegmentFlags : 0x2 [Type: unsigned long]
[+0x018] SegmentListEntry [Type: _LIST_ENTRY]
[+0x028] Heap : 0x149ce710000 [Type: _HEAP *]
[+0x030] BaseAddress : 0x149ce710000 [Type: void *]
[…]

It looks like it has a pointer that points back to itself at the right spot to be valid. So why doesn’t !heap list it? And why does

:000> !heap -v 0
Index Address Name Debugging options enabled
1: 149ce300000
[…]

Only list the default process heap?

If I run the same executable on a Server 2016 machine, generating the same type of dump, and examine it using the same installation of windbg that generated the above output…

Microsoft (R) Windows Debugger Version 10.0.27725.1000 AMD64
[…]
User Mini Dump File with Full Memory: Only application data is available
[…]
Windows 10 Version 14393 MP (4 procs) Free x64
Product: Server, suite: TerminalServer SingleUserTS
Edition build lab: 10.0.14393.6343 (rs1_release.230913-1727)
[…]
0:000> .dumpdebug
----- User Mini Dump Analysis

MINIDUMP_HEADER:
Version A793 (A0F1)
NumberOfStreams 16
Flags 00021826
00000002 MiniDumpWithFullMemory
00000004 MiniDumpWithHandleData
00000020 MiniDumpWithUnloadedModules
00000800 MiniDumpWithFullMemoryInfo
00001000 MiniDumpWithThreadInfo
00020000 MiniDumpIgnoreInaccessibleMemory

Streams:
[…]
Stream 15: type UnusedStream (0), size 00000000, RVA 00000000

Odd that it only has 15 streams…

0:000> !heap
Heap Address NT/Segment Heap

0000023383230000              NT Heap
0000023383050000              NT Heap
0000023383420000              NT Heap
0000023383f30000              NT Heap
00000233849c0000              NT Heap
00000233854c0000              NT Heap
0000023385f00000              NT Heap

Lists all the heaps (plus a mysterious extra). All of the listed heaps show up in the array of heap handles from main().

0:000> !heap -v 0
HEAPEXT: Unable to get address of ntdll!RtlpHeapInvalidBadAddress.
Index Address Name Debugging options enabled
1: 23383230000
[…]
2: 23383050000
[…]
3: 23383420000
Segment at 0000023383420000 to 0000023383e2f000 (00a00000 bytes committed)
Flags: 00001006
ForceFlags: 00000004
Granularity: 16 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000100
DeCommit Total Thres: 00001000
Total Free Size: 0009ff79
Max. Allocation Size: 00007ffffffdefff
Lock Variable at: 00000233834202a0
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 23383420110
Uncommitted ranges: 233834200f0
FreeList[ 00 ] at 0000023383420150: 0000023383420730 . 0000023383e16840 (11 blocks)
PreviousSize field is non-zero when it should be zero to mark first entry
unable to read heap entry at 2338328f780
##The above errors were found in segment at 0x83230000

It lists them all, but it does seem to get errors (remember, the source code does not use the heaps, so they can’t have errors caused by user code). Interestingly, if I !heap -v that same heap, it does not say there are errors.

0:000> !heap -v 0x23383420000
Index Address Name Debugging options enabled
3: 23383420000
Segment at 0000023383420000 to 0000023383e2f000 (00a00000 bytes committed)
Flags: 00001006
ForceFlags: 00000004
Granularity: 16 bytes
Segment Reserve: 00100000
Segment Commit: 00002000
DeCommit Block Thres: 00000100
DeCommit Total Thres: 00001000
Total Free Size: 0009ff79
Max. Allocation Size: 00007ffffffdefff
Lock Variable at: 00000233834202a0
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 23383420110
Uncommitted ranges: 233834200f0
FreeList[ 00 ] at 0000023383420150: 0000023383420730 . 0000023383e16840 (11 blocks)

Something has changed in Windows, and !heap hasn’t changed with it. !heap is critical to us, for identifying heap corruption and memory leaks. It looks like it doesn’t work properly on win 10 either, “!heap -v” showing a difference in whether it analyses a specific heap or the whole lot, but at least it can list all of the heaps in the process.

Please contact me for more information.

Warm Regards,
Ty

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant