Arithmetic operation resulted in an overflow in unsafe C#

Daniel James Bryars picture Daniel James Bryars · Mar 14, 2011 · Viewed 9.8k times · Source

Background

We've been using some code copied verbatim from Joe Duffy's "Concurrent Programming on Windows" (page 149) in production for over a year. The code (below) is used in our Asp.Net web application to probe if there's enough stack space. Our site allows users to script out their own web pages and control logic in a simple proprietry scripting language - it's possible for a user to script something nasty and cause a stackoverflow exception, so we use Duffy's code example to stop execution of the errant script before the uncatchable StackOverflow exception takes down the whole IIS AppPool. This has been working really well.

The problem

All of a sudden this afternoon our logs filled with System.OverflowException errors. We got the same exception on every request to that server. A swift IIS reset cured the problem.

Exception Type : System.OverflowException

Exception Message : Arithmetic operation resulted in an overflow.

Stack Trace : at System.IntPtr..ctor(Int64 value) at LiquidHtmlFlowManager.StackManagement.CheckForSufficientStack(UInt64 bytes) in C:\SVN\LiquidHtml\Trunk\LiquidHtmlFlowManager\StackManagement.cs:line 47

The code:

public static class StackManagement
{
    [StructLayout(LayoutKind.Sequential)]
    struct MEMORY_BASIC_INFORMATION
    {
        public uint BaseAddress;
        public uint AllocationBase;
        public uint AllocationProtect;
        public uint RegionSize;
        public uint State;
        public uint Protect;
        public uint Type;
    };

    //We are conservative here. We assume that the platform needs a 
    //whole 16 pages to respond to stack overflow (using an X86/X64
    //page-size, not IA64). That's 64KB, which means that for very
    //small stacks (e.g. 128kb) we'll fail a lot of stack checks (say in asp.net)
    //incorrectly.
    private const long STACK_RESERVED_SPACE = 4096 * 16;

    /// <summary>
    /// Checks to see if there is at least "bytes" bytes free on the stack.
    /// </summary>
    /// <param name="bytes">Number of Free bytes in stack we need.</param>
    /// <returns>If true then there is suffient space.</returns>
    public unsafe static bool CheckForSufficientStack(ulong bytes)
    {
        MEMORY_BASIC_INFORMATION stackInfo = new MEMORY_BASIC_INFORMATION();
        //We subtract one page for our request. VirtualQuery rounds up
        //to the next page. But the stack grows down. If we're on the 
        //first page (last page in the VirtualAlloc), we'll be moved to
        //the next page which is off the stack! Note this doesn't work
        //right for IA64 due to bigger pages.
        IntPtr currentAddr = new IntPtr((uint)&stackInfo - 4096);

        //Query for the current stack allocation information.
        VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION));

        //If the current address minus the base (remember: the stack
        //grows downward in the address space) is greater than the 
        //number of bytes requested plus the unreserved space at the end,
        //the request has succeeded.
        System.Diagnostics.Debug.WriteLine(String.Format("CurrentAddr = {0}, stackInfo.AllocationBase = {1}. Space left = {2} bytes.", (uint)currentAddr.ToInt64(),
            stackInfo.AllocationBase,
            ((uint)currentAddr.ToInt64() - stackInfo.AllocationBase)));

        return ((uint)currentAddr.ToInt64() - stackInfo.AllocationBase) > (bytes + STACK_RESERVED_SPACE);
    }

    [DllImport("kernel32.dll")]
    private static extern int VirtualQuery(IntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
}

NOTE: Line 47 is this one

IntPtr currentAddr = new IntPtr((uint)&stackInfo - 4096);

The question:

Which part of the code overflows, is it the cast from the pointer to the uint, the "- 4096" operation, or the cast to the Int64?

Any ideas how to make this more robust?

Some more information:

The OS is 64 bit Windows Server 2008, running IIS7 with an Intel Zeon (x86) CPU.

The parameter passed to the CheckForSufficientStack function is:

private const Int32 _minimumStackSpaceLimit = 48 * 1024;

EDIT: Thanks for the answer. I've updated the code to remove the casts and use pointer sized variables so that it works in both 32 and 64 bit. Here it is should someone else want it:

public static class StackManagement
    {
        [StructLayout(LayoutKind.Sequential)]
        struct MEMORY_BASIC_INFORMATION
        {
            public UIntPtr BaseAddress;
            public UIntPtr AllocationBase;
            public uint AllocationProtect;
            public UIntPtr RegionSize;
            public uint State;
            public uint Protect;
            public uint Type;
        };

        private const long STACK_RESERVED_SPACE = 4096 * 16;

        public unsafe static bool CheckForSufficientStack(UInt64 bytes)
        {
            MEMORY_BASIC_INFORMATION stackInfo = new MEMORY_BASIC_INFORMATION();
            UIntPtr currentAddr = new UIntPtr(&stackInfo);
            VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION));

            UInt64 stackBytesLeft = currentAddr.ToUInt64() - stackInfo.AllocationBase.ToUInt64();

            System.Diagnostics.Debug.WriteLine(String.Format("CurrentAddr = {0}, stackInfo.AllocationBase = {1}. Space left = {2} bytes.", 
                currentAddr,
                stackInfo.AllocationBase,
                stackBytesLeft));

            return stackBytesLeft > (bytes + STACK_RESERVED_SPACE);
        }

        [DllImport("kernel32.dll")]
        private static extern int VirtualQuery(UIntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
    }

Answer

Hans Passant picture Hans Passant · Mar 14, 2011

The cast is just wrong. The address of stackinfo is a 64-bit value. You cannot cast that to an uint without risking OverflowException. There's no point in subtracting 4096 either, VirtualQuery() will find the base address anyway. Fix:

 IntPtr currentAddr = new IntPtr(&stackInfo);

Duffy's code can only work for 32-bit code.