Арифметическая операция привела к переполнению в небезопасном C#

Задний план

Мы уже более года используем некоторый код, дословно скопированный из книги Джо Даффи «Параллельное программирование в Windows» (стр. 149). Код (ниже) используется в нашем веб-приложении Asp.Net для проверки достаточности места в стеке. Наш сайт позволяет пользователям создавать свои собственные веб-страницы и управлять логикой на простом собственном языке сценариев — пользователь может написать что-то неприятное и вызвать исключение переполнения стека, поэтому мы используем пример кода Даффи, чтобы остановить выполнение ошибочного сценария до того, как неуловимое исключение StackOverflow уничтожает весь AppPool IIS. Это работает очень хорошо.

Проблема

Сегодня днем ​​наши журналы внезапно заполнились ошибками System.OverflowException. Мы получали одно и то же исключение при каждом запросе на этот сервер. Быстрый сброс IIS вылечил проблему.

Тип исключения: System.OverflowException

Сообщение об исключении: арифметическая операция привела к переполнению.

Трассировка стека: в System.IntPtr..ctor (значение Int64) в LiquidHtmlFlowManager.StackManagement.CheckForSufficientStack (UInt64 байта) в C:\SVN\LiquidHtml\Trunk\LiquidHtmlFlowManager\StackManagement.cs:строка 47

Код:

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);
}

ПРИМЕЧАНИЕ. Строка 47 — это

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

Вопрос:

Какая часть кода переполняется, это приведение указателя к uint, операция "- 4096" или приведение к Int64?

Любые идеи, как сделать это более надежным?

Еще немного информации:

ОС представляет собой 64-разрядную версию Windows Server 2008, работающую под управлением IIS7 с процессором Intel Zeon (x86).

В функцию CheckForSufficientStack передается следующий параметр:

private const Int32 _minimumStackSpaceLimit = 48 * 1024;

РЕДАКТИРОВАТЬ: Спасибо за ответ. Я обновил код, чтобы удалить приведения и использовать переменные размера указателя, чтобы он работал как в 32-битной, так и в 64-битной версии. Вот он, если кому-то это нужно:

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);
    }

person Daniel James Bryars    schedule 14.03.2011    source источник


Ответы (1)


Актерский состав просто неправильный. Адрес stackinfo представляет собой 64-битное значение. Вы не можете привести это к uint, не рискуя OverflowException. Вычитать 4096 тоже нет смысла, VirtualQuery() все равно найдет базовый адрес. Исправить:

 IntPtr currentAddr = new IntPtr(&stackInfo);

Код Даффи может работать только для 32-битного кода.

person Hans Passant    schedule 14.03.2011
comment
Большое Вам спасибо. Когда вы говорите, что код Даффи может работать только для 32-битного кода, я думаю, мне нужно изменить структуру MEMORY_BASIC_INFORMATION в зависимости от того, является ли это 32- или 64-битной платформой, и позже удалить uint-приведения. Я потрачу некоторое время, чтобы написать это, а затем, вероятно, задам еще один вопрос о SO, чтобы убедиться, что я правильно понял. - person Daniel James Bryars; 14.03.2011
comment
Правильно, это тоже неправильно. VirtualQuery просто терпит неудачу. Непроверенный код никогда не работает с первого раза :) - person Hans Passant; 14.03.2011