Over a million developers have joined DZone.

Memory Corruption, GC, and Overlapping Objects

·

Dima has brought to my attention a nasty bug probably attributed to a memory corruption. The bug’s manifestation is usually an access violation in a completely unrelated piece of code, oftentimes causing an ExecutionEngineException.

This is an example of an access violation of the above variety (some of the output was snipped for brevity):

0:004> .loadby sos clr
0:004> g
(510.c88): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
00742a11 8b4028          mov     eax,dword ptr [eax+28h] ds:002b:0000002c=????????

0:000> !CLRStack
OS Thread Id: 0xc88 (0)
Child SP IP       Call Site
004bedb8 00742a11 OverlappingObjects.Program.Main(System.String[]) [\OverlappingObjects\Program.cs @ 51]
004beff0 724221bb [GCFrame: 004beff0]

0:000> k
ChildEBP RetAddr 
WARNING: Frame IP not in any known module. Following frames may be wrong.
004bedc4 724221bb 0x742a11
004bedd4 72444be2 clr!CallDescrWorker+0x33
004bee50 72444d84 clr!CallDescrWorkerWithHandler+0x8e
…

0:000> !u 0x742a11
Normal JIT generated code
OverlappingObjects.Program.Main(System.String[])
Begin 00742980, size d7
…
\OverlappingObjects\Program.cs @ 51:
00742a0c mov     ecx,dword ptr [esi+4]
00742a0f mov     eax,dword ptr [ecx]
>>> 00742a11 mov     eax,dword ptr [eax+28h]
00742a14 call    dword ptr [eax+8] 

Hmm. The exception seems to be happening when calling a virtual function on an object stored in the ECX register. From inspecting the source code, this is a virtual function call to GetHashCode.

If this generates an access violation, ECX must be some invalid pointer. (Although if this were a simple null reference exception, we would fail at the previous instruction when trying to dereference ECX.)

0:000> r ecx
ecx=026ae494

0:000> !do 026ae494
<Note: this object has an invalid CLASS field>
Invalid object

0:000> !gcwhere 026ae494
Address    Gen   Heap   segment   
026ae494   2      0     026a0000

Well, this is no null pointer. ECX is a reference into the GC heap, but for some reason calling a method through it fails. What does the method table look like?

0:000> dd 026ae494 L2
026ae494  00000004 00000000

Ow! This is not a method table. So far we have a reference into the GC heap that is not actually a reference to a valid object, so an attempt to call a virtual function on it fails. Let’s look around for some instances:

0:000> !dumpheap -type Overlapping
Address       MT     Size
026ae480 00373534       16    
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
00373534        1           16 OverlappingObjects.ReferenceHolder
Total 1 objects

0:000> !do 026ae480
Name:        OverlappingObjects.ReferenceHolder
MethodTable: 00373534
EEClass:     0062126c
Size:        16(0x10) bytes
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
0060c12c  4000003        8        System.UInt32  1 instance 3405695742 Marker
003734b0  4000004        4 ...bjects.SomeObject  0 instance 026ae494 TheReference

0:000> !do 026ae494
<Note: this object has an invalid CLASS field>
Invalid object

0:000> !dumpheap 026ae494-100 026ae494+100
Address       MT     Size
026ae480 00373534       16    
026ae490 001cdb38       16 Free
026ae4a0 005dc838     4012    
total 0 objects
Statistics:
      MT    Count    TotalSize Class Name
00373534        1           16 OverlappingObjects.ReferenceHolder
001cdb38        1           16      Free
005dc838        1         4012 System.Int32[]
Total 3 objects

What have we here? Our object lies in the range [026ae490…026ae4a0) which is attributed to a free object; i.e. the GC has reclaimed this memory for other uses (and we’re lucky not to see some other object already in this space!).

Moreover, the reference we have is at a four-byte offset from the object’s former resting place—and we obtain this reference from a valid instance of the ReferenceHolder class. Now here is a likely scenario that explains this turn of events:

  • The ReferenceHolder instance contains the only reference to our object.
  • For some reason, e.g. a random memory overwrite, the reference is bumped four bytes forward, so it no longer references a valid object.
  • The GC runs and there is no longer a valid reference to our object, so its memory is reclaimed.
  • The ReferenceHolder instance still thinks it has a valid reference to our object, and when a method call on that reference is attempted, we get the nice access violation.

I would like to reiterate that this access violation is a fairly optimistic outcome. Things could have been much, much worse if another valid object was allocated in the free space, and the GetHashCode method call would magically be invoked on that object. Another alternative is that a large object would occupy the space both before and after the reclaimed memory, and then the invalid reference would actually point in the middle of a valid object, producing the effect of objects overlapping in memory!


Below is the code required to reproduce this scenario. Because it uses memory offsets that may change between CLR versions and OS flavors, to repro you would need a Windows 7 64-bit OS and compile the code as .NET 4.0 Release 32-bit.

namespace OverlappingObjects
{     [StructLayout(LayoutKind.Sequential)]     class SomeObject     {         public uint X;         public uint Y;         ~SomeObject()         {             Console.WriteLine("Finalizer");         }     }     [StructLayout(LayoutKind.Sequential)]     class ReferenceHolder     {         public uint Marker;         public SomeObject TheReference;     }     class Program     {         static void Dismantle(int[] arr)         {             GCHandle gch = GCHandle.Alloc(
                        arr, GCHandleType.Pinned);             IntPtr ptr = gch.AddrOfPinnedObject();             const int OFFSET = 8 + 4000;             Marshal.WriteInt32(ptr, OFFSET,
                Marshal.ReadInt32(ptr, OFFSET) + 4);             gch.Free();         }         static void Main(string[] args)         {             Console.ReadLine();             int[] arr = new int[1000];             ReferenceHolder holder = new ReferenceHolder();             holder.Marker = 0xCAFECAFE;             holder.TheReference = new SomeObject
                   {X = 0xDEADBEEF, Y = 0xBADF00D};             int[] arr2 = new int[1000];             Dismantle(arr);             GC.Collect();             GC.WaitForPendingFinalizers();             GC.Collect();             holder.TheReference.GetHashCode();             Console.WriteLine("MAIN DONE");             Console.ReadLine();             GC.KeepAlive(holder);             GC.KeepAlive(arr);             GC.KeepAlive(arr2);         }     }
}
Topics:

Published at DZone with permission of Sasha Goldshtein, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}