简体   繁体   English

算术运算导致不安全的C#溢出

[英]Arithmetic operation resulted in an overflow in unsafe C#

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. 我们一直在使用从Joe Duffy的“Windows上的Concurrent Programming”(第149页)中逐字复制的一些代码,这些代码已经生产了一年多。 The code (below) is used in our Asp.Net web application to probe if there's enough stack space. 代码(如下所示)在我们的Asp.Net Web应用程序中用于探测是否有足够的堆栈空间。 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. 我们的网站允许用户使用简单的专有脚本语言编写自己的网页和控制逻辑脚本 - 用户可以编写令人讨厌的东西并导致堆栈溢出异常,因此我们使用Duffy的代码示例来停止执行错误的脚本无法捕获的StackOverflow异常会占用整个IIS AppPool。 This has been working really well. 这一直很有效。

The problem 问题

All of a sudden this afternoon our logs filled with System.OverflowException errors. 今天下午突然我们的日志填满了System.OverflowException错误。 We got the same exception on every request to that server. 我们对该服务器的每个请求都有相同的异常。 A swift IIS reset cured the problem. 一个快速的IIS重置解决了这个问题。

Exception Type : System.OverflowException 异常类型: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 堆栈跟踪:在C:\\ SVN \\ LiquidHtml \\ Trunk \\ LiquidHtmlFlowManager \\ StackManagement.cs中的LiquidHtmlFlowManager.StackManagement.CheckForSufficientStack(UInt64字节)处的System.IntPtr..ctor(Int64值)处:第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 注意:第47行就是这一行

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? 代码的哪一部分溢出,是指向uint的指针,“ - 4096”操作,还是对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. 操作系统是64位Windows Server 2008,运行带有Intel Zeon(x86)CPU的IIS7。

The parameter passed to the CheckForSufficientStack function is: 传递给CheckForSufficientStack函数的参数是:

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. 我已更新代码以删除强制转换并使用指针大小的变量,以便它在32位和64位工作。 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);
    }

The cast is just wrong. 演员是错的。 The address of stackinfo is a 64-bit value. stackinfo的地址是64位值。 You cannot cast that to an uint without risking OverflowException. 您不能将其转换为uint而不会有OverflowException的风险。 There's no point in subtracting 4096 either, VirtualQuery() will find the base address anyway. 减去4096也没有意义,VirtualQuery()无论如何都会找到基地址。 Fix: 固定:

 IntPtr currentAddr = new IntPtr(&stackInfo);

Duffy's code can only work for 32-bit code. Duffy的代码只能用于32位代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM