Heap Corruption Exception 0xC0000374

Modern computers can execute millions of instructions per second, and if one of them is erroneous, then the process could crash. Often analysis of a crash dump can immediately indicates what the problem is, so the fix is easy.

For example a NullReference exception: declare a Process variable with a null value, then try to find the value of the Id member.

            System.Diagnostics.Process process = null;

var procid = process.Id;

Seeing these two lines of code in the source code, it’s simple to identify the problem. However, it usually takes more than examining 2 lines of code to find the problem: the “process” variable could be coming from some other component. Even so , this kind of problem is far easier to find than a Heap corruption problem.

Heap corruption is a much more difficult problem to solve. The corruption can occur many millions of instructions before a crash occurs. Worse, erroneous code could alter memory that it doesn’t own, such as changing the account balance of your bank account accidentally and not causing a crash.

For more details on memory corruption, see Memory corruption

The result of a Heap Corruption can vary:

1. Sometimes, a heap corruption throws an exception of type 0xC0000374.

clip_image001

2. it could just make the process disappear instantly

3. it could corrupt some real data (like a bank account balance)

4. It could corrupt some data that doesn’t matter, like a few pixels of a picture.

5. It could invoke Windows Error Reporting:

clip_image003

Below is some code with 2 examples of Heap corruption. You can choose 1 or the other by commenting or uncommenting.

1. CorruptHeapAlloc

o shows intentional allocations of 1000 byte blocks, to get a region of memory to which we have rights to write. Then free the memory, but write to it anyway but write to it beyond the original region.

2. SHOW_PINVOKE_BUG

o This one is much more insidious. It was so obscure that it passed several code reviews and actually got checked in to source control. People started to get random errors in their normally reliable code.

To see these in action, File->New->C# Windows Console Application. Paste in the sample code below. Uncomment the appropriate lines to demonstrate the behavior.

Because heap corruption can cause seemingly random behavior many millions of lines of code later, most of the sample code below just does some work, exercising the managed and native heaps.

The 1st heap corruption is pretty straightforward.

The PInvoke corruption fails because the PInvoke signature of the HIGHCONTRAST structure is declared incorrectly. The structure is passed by reference to the SystemParametersInfo API.This API is rather unusual: the type of the 3rd parameter varies depending on the 1st parameter. Sometimes it’s an integer. Other times it’s a structure. In the case of the HCF_HIGHCONTRASTON, it’s a structure with a string parameter. The CLR sees that the structure is passed by reference, and it sees that there’s a string member of the structure. The CLR then takes ownership of the native string, freeing it when the pass by reference structure is freed. The error is to pass the string as an unmanaged string, rather than as a pointer to memory containing the string (which can be retrieved by Marshal.PtrToStringUni.

See also

Managed code using unmanaged memory: HeapCreate, Peek and Poke

<Code Sample>

 // This sample has 2 ways to corrupt the Process heap 
// one is SHOW_PINVOKE_BUG, the other is CorruptHeapAlloc
//#define SHOW_PINVOKE_BUG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int iter = 0; iter < 1000; iter++)
            {
                var highContrastStruct = new NativeMethods.HIGHCONTRAST();
                highContrastStruct.cbSize = Marshal.SizeOf(highContrastStruct);
                var isHighContrast = NativeMethods.SystemParametersInfo(
                    NativeMethods.SPI_GETHIGHCONTRAST,
                    highContrastStruct.cbSize,
                    ref highContrastStruct,
                    fWinIni: 0
                    );
                var IsHighContrast = highContrastStruct.dwFlags & NativeMethods.HCF_HIGHCONTRASTON;
#if SHOW_PINVOKE_BUG
                var name = highContrastStruct.lpszDefaultScheme;
#else
                var name = Marshal.PtrToStringUni(highContrastStruct.lpszDefaultScheme);
#endif

                var txt = string.Format("IsHighContrast = {0}  Name = {1}",
                    IsHighContrast,
                    name);
                Write(txt);

                var types = typeof(int).Assembly.GetTypes();
                var lstAddr = new List<IntPtr>();
                var hp = NativeMethods.GetProcessHeap();
                int cntr = 0;
                foreach (var typ in types)
                {
                    cntr++;
                    var str = typ.Name;
                    Write(string.Format("Iter = {0} Cntr = {1} {2}", iter, cntr, str));
                    var enc = new System.Text.UnicodeEncoding();
                    var bytes = enc.GetBytes(str);
                    // add trailing null terminator (for Unicode, 2 null bytes)
                    Array.Resize<byte>(ref bytes, bytes.Length + 2);
                    // allocate some memory 
                    var addr = NativeMethods.HeapAlloc(hp, 0, bytes.Length);
                    // add the address to the list of memory locations we've allocated
                    // so we can free them later
                    lstAddr.Add(addr);
                    Marshal.Copy(bytes, 0, addr, bytes.Length);

                    //uncomment this code to see another heap corruption
                    // this one allocates 1000 bytes, then frees it
                    // then copies data to it
                    //CorruptHeapAlloc();
                }
                Array.ForEach<IntPtr>(lstAddr.ToArray(), addr =>
                {
                    //now free the allocations
                    NativeMethods.HeapFree(hp, 0, addr);
                });
            }
        }

        private static void CorruptHeapAlloc()
        {
            // Disable vshost process: Project->Properties ->Debug->Enable the Visual Studio Host Process
            // Enable native debugging : Project->Properties ->Debug->Enable native code debugging
            // Disable Just My Code Debugging: Tools->Options->Debugging->General
            // Debug->Exceptions-> enable both Common Langauge Runtime, Win32 Exceptions


            var hp = NativeMethods.GetProcessHeap();
            var addr = NativeMethods.HeapAlloc(hp, 0, 1000);

            Marshal.FreeCoTaskMem(addr); // or NativeMethods.HeapFree
            var enc = new System.Text.UnicodeEncoding();
            // create a string of 1000 "a"
            var str = new string('a', 1000);
            var bytes = enc.GetBytes(str);
            // copy the bytes to some address to which we have write access 
            // but that we don't own.
            Marshal.Copy(bytes, startIndex: 0, destination: addr + 800, length: 800);
        }

        private static void Write(string txt)
        {
            //            Debug.WriteLine(txt);
            Console.WriteLine(txt);
        }
    }
    public class NativeMethods
    {
        public const int HCF_HIGHCONTRASTON = 0x0001;

        public const int SPI_GETHIGHCONTRAST = 0x0042;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct HIGHCONTRAST
        {
            public int cbSize;
            public int dwFlags;
#if SHOW_PINVOKE_BUG
            [MarshalAs(UnmanagedType.LPTStr)]
            public string lpszDefaultScheme;
#else
            public IntPtr lpszDefaultScheme;
#endif
            /*
            /*/
            //*/
        }


        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal extern static bool SystemParametersInfo(
            int uiAction,
            int uiParam,
            ref HIGHCONTRAST pvParam,
            int fWinIni);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, int dwSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcessHeap();
    }
}

</Code Sample>