Share via


Heap issues

Windows split the VA space into several parts, one important part is Heaps.

Instead of put the local variables in Stacks, we often allocate the memory in Heaps, use it incorrectly or maybe forget to release it, that's humanity - make mistakes.

Generally there are two kinds of mistakes: Heap corruption and Heap leak

Heap corruptions
================================
use a free block
double free
cross-heap free
random pointer free
buffer over-run
random memory corruption
miscellaneous locking issues

Heap leak
================================
forget to do release the seat...

<><><>

Heap system contains several important structures:

0:018> dt ntdll!_HEAP
   +0x000 Entry            : _HEAP_ENTRY
   +0x008 Signature        : Uint4B    <<<<< 0xeeffeeff
   +0x00c Flags            : Uint4B
   +0x010 ForceFlags       : Uint4B
   +0x014 VirtualMemoryThreshold : Uint4B
   +0x018 SegmentReserve   : Uint4B
   +0x01c SegmentCommit    : Uint4B
   +0x020 DeCommitFreeBlockThreshold : Uint4B
   +0x024 DeCommitTotalFreeThreshold : Uint4B
   +0x028 TotalFreeSize    : Uint4B
   +0x02c MaximumAllocationSize : Uint4B
   +0x030 ProcessHeapsListIndex : Uint2B
   +0x032 HeaderValidateLength : Uint2B
   +0x034 HeaderValidateCopy : Ptr32 Void
   +0x038 NextAvailableTagIndex : Uint2B
   +0x03a MaximumTagIndex  : Uint2B
   +0x03c TagEntries       : Ptr32 _HEAP_TAG_ENTRY
   +0x040 UCRSegments      : Ptr32 _HEAP_UCR_SEGMENT
   +0x044 UnusedUnCommittedRanges : Ptr32 _HEAP_UNCOMMMTTED_RANGE
   +0x048 AlignRound       : Uint4B
   +0x04c AlignMask        : Uint4B
   +0x050 VirtualAllocdBlocks : _LIST_ENTRY
   +0x058 Segments         : [64] Ptr32 _HEAP_SEGMENT
   +0x158 u                : __unnamed
   +0x168 u2               : __unnamed
   +0x16a AllocatorBackTraceIndex : Uint2B
   +0x16c NonDedicatedListLength : Uint4B
   +0x170 LargeBlocksIndex : Ptr32 Void
   +0x174 PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY
   +0x178 FreeLists        : [128] _LIST_ENTRY  <<<<< 1024/8==128
   +0x578 LockVariable     : Ptr32 _HEAP_LOCK
   +0x57c CommitRoutine    : Ptr32  
   +0x580 FrontEndHeap     : Ptr32 Void
   +0x584 FrontHeapLockCount : Uint2B
   +0x586 FrontEndHeapType : UChar
   +0x587 LastSegmentIndex : UChar

ntdll!_HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY
   +0x008 Signature        : Uint4B
   +0x00c Flags            : Uint4B
   +0x010 Heap             : Ptr32 _HEAP
   +0x014 LargestUnCommittedRange : Uint4B
   +0x018 BaseAddress      : Ptr32 Void
   +0x01c NumberOfPages    : Uint4B
   +0x020 FirstEntry       : Ptr32 _HEAP_ENTRY
   +0x024 LastValidEntry   : Ptr32 _HEAP_ENTRY
   +0x028 NumberOfUnCommittedPages : Uint4B
   +0x02c NumberOfUnCommittedRanges : Uint4B
   +0x030 UnCommittedRanges : Ptr32 _HEAP_UNCOMMMTTED_RANGE
   +0x034 AllocatorBackTraceIndex : Uint2B
   +0x036 Reserved         : Uint2B
   +0x038 LastEntryInSegment : Ptr32 _HEAP_ENTRY

ntdll!_HEAP_ENTRY
   +0x000 Size             : Uint2B  <<<<< for direct chain, Size+basePoint = next entry
   +0x002 PreviousSize     : Uint2B  <<<<< for reverse chain, basePoint-PreviousSize = previous entry
   +0x000 SubSegmentCode   : Ptr32 Void
   +0x004 SmallTagIndex    : UChar
   +0x005 Flags            : UChar  <<<<< flags
   +0x006 UnusedBytes      : UChar
   +0x007 SegmentIndex     : UChar

ntdll!_HEAP_FREE_ENTRY
    ... _HEAP_ENTRY
   +0x008 FreeList         : _LIST_ENTRY  <<<<< point to next _LIST_ENTRY

About the flags:
0x01 - HEAP_ENTRY_BUSY
0x02 - HEAP_ENTRY_EXTRA_PRESENT
0x04 - HEAP_ENTRY_FILL_PATTERN
0x08 - HEAP_ENTRY_VIRTUAL_ALLOC
0x10 - HEAP_ENTRY_LAST_ENTRY
0x20 - HEAP_ENTRY_SETTABLE_FLAG1
0x40 - HEAP_ENTRY_SETTABLE_FLAG2
0x80 - HEAP_ENTRY_SETTABLE_FLAG3

Do something real:

0:006> !heap -s
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
                    (k)     (k)    (k)     (k) length      blocks cont. heap
-----------------------------------------------------------------------------
00150000 00000002    1024    480    480     11     2     1    0      0   L 
00250000 00001002      64     24     24      3     1     1    0      0   L 
00260000 00008000      64     12     12     10     1     1    0      0     
00390000 00000002      64      8      8      0     0     1    0      0   L 
00030000 00001002    1088    232    332     31     2     2    0      0   L 
003b0000 00001002      64     64     64      1     1     0    0      0   L 
00900000 00001002     256     12     12      4     1     1    0      0   L 
00940000 00001002    1024   1024   1024   1016     2     0    0      0   L 
00a50000 00001002      64     16     16      1     1     1    0      0   L 
00a60000 00001002      64     16     16      0     0     1    0      0   L 
00ab0000 00001002     256     12     12      4     1     1    0      0   L 
00f60000 00001002      64     28     28      3     1     1    0      0   L 
00f70000 00001002      64     24     24      5     1     1    0      0   L 
01030000 00001002      64     16     16      3     1     1    0      0   L 
01140000 00001002      64     24     24      3     1     1    0      0   L 
01a90000 00001002    4096   4096   4096   4088     9     0    0      0   L 
    External fragmentation  99 % (9 free blocks)
01150000 00001002     256     12     12      4     1     1    0      0   L 
01190000 00001002     256     12     12      4     1     1    0      0   L 
01ea0000 00000002    1024     24     24      3     1     1    0      0   L 
01220000 00001002      64     16     16      0     0     1    0      0   L 
-----------------------------------------------------------------------------
0:006> !heap -v 00030000
Index   Address  Name      Debugging options enabled
  5:   00030000
    Segment at 00030000 to 00040000 (00010000 bytes committed)
    Segment at 020a0000 to 021a0000 (0002a000 bytes committed)
    Flags:                00001002
    ForceFlags:           00000000
    Granularity:          8 bytes
    Segment Reserve:      00200000
    Segment Commit:       00002000
    DeCommit Block Thres: 00000200
    DeCommit Total Thres: 00002000
    Total Free Size:      00000fb5
    Max. Allocation Size: 7ffdefff
    Lock Variable at:     00030608
    Next TagIndex:        0000
    Maximum TagIndex:     0000
    Tag Entries:          00000000
    PsuedoTag Entries:    00000000
    Virtual Alloc List:   00030050
    UCR FreeList:        000305a8
    FreeList Usage:      00000004 00000000 00000000 00000000
    FreeList[ 00 ] at 00030178: 020a0048 . 0003d468   (2 blocks)
    FreeList[ 02 ] at 00030188: 020e2ff8 . 000344d8   (5 blocks)

00030178 point to the _LIST_ENTRY, so we need to decrease 8 offset to dump the _HEAP_ENTRY (_HEAP_FREE_ENTRY)

0:006> dt _HEAP_FREE_ENTRY 00030178-8
ntdll!_HEAP_FREE_ENTRY
   +0x000 Size             : 0
   +0x002 PreviousSize     : 0
   +0x000 SubSegmentCode   : (null)
   +0x004 SmallTagIndex    : 0 ''
   +0x005 Flags            : 0 ''
   +0x006 UnusedBytes      : 0 ''
   +0x007 SegmentIndex     : 0 ''
   +0x008 FreeList         : _LIST_ENTRY [ 0x3d468 - 0x20a0048 ]
0:006> dt _HEAP_FREE_ENTRY 0x3d468-8
ntdll!_HEAP_FREE_ENTRY
   +0x000 Size             : 0x1b3
   +0x002 PreviousSize     : 0x12
   +0x000 SubSegmentCode   : 0x001201b3
   +0x004 SmallTagIndex    : 0x61 'a'
   +0x005 Flags            : 0 ''
   +0x006 UnusedBytes      : 0x73 's'
   +0x007 SegmentIndex     : 0 ''
   +0x008 FreeList         : _LIST_ENTRY [ 0x20a0048 - 0x30178 ]
0:006> dt _HEAP_FREE_ENTRY 0x20a0048-8
ntdll!_HEAP_FREE_ENTRY
   +0x000 Size             : 0xdf8
   +0x002 PreviousSize     : 8
   +0x000 SubSegmentCode   : 0x00080df8
   +0x004 SmallTagIndex    : 0xbb ''
   +0x005 Flags            : 0x10 ''  <<<<< last entry
   +0x006 UnusedBytes      : 0xc ''
   +0x007 SegmentIndex     : 0x1 ''
   +0x008 FreeList         : _LIST_ENTRY [ 0x30178 - 0x3d468 ]

it is a circle :) then, let's try to verify the former and latter entry via Size and PreviousSize offset.

0x3d468-8=0x3d460
next entry: 0x3d460 + 0x1b3*8 = 0003e1f8
previous entry: 0x3d460 - 0x12*8 = 0003d3d0

0:006> dt _HEAP_ENTRY 0003e1f8
ntdll!_HEAP_ENTRY
   +0x000 Size             : 0x22
   +0x002 PreviousSize     : 0x1b3
   +0x000 SubSegmentCode   : 0x01b30022
   +0x004 SmallTagIndex    : 0x8c ''
   +0x005 Flags            : 0x1 ''  <<<<< busy
   +0x006 UnusedBytes      : 0xc ''
   +0x007 SegmentIndex     : 0 ''

0:006> dt _HEAP_ENTRY 0003d3d0
ntdll!_HEAP_ENTRY
   +0x000 Size             : 0x12
   +0x002 PreviousSize     : 3
   +0x000 SubSegmentCode   : 0x00030012
   +0x004 SmallTagIndex    : 0xc9 ''
   +0x005 Flags            : 0x1 ''  <<<<< busy
   +0x006 UnusedBytes      : 0x8 ''
   +0x007 SegmentIndex     : 0 ''

When writing data into a free block, it may break the _HEAP_ENTRY->FreeList structure.

When buffer overrun into the next block, it may break the _HEAP_ENTRY whole structure.

<><><>

 To enable pageheap to check these heap corruption issues:

"gflags.exe -p APPLICATION_NAME /enable /full /decommit"
https://support.microsoft.com/kb/286470

It will add the NO_ACCESS page after one block.

To troubleshoot the memory/heap leak issue, use command: !heap -l or LeakDiag