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

Posted

技术标签:

【中文标题】算术运算导致不安全 C# 中的溢出【英文标题】:Arithmetic operation resulted in an overflow in unsafe C# 【发布时间】:2011-07-15 06:21:06 【问题描述】:

背景

一年多来,我们一直在使用从 Joe Duffy 的“Windows 上的并发编程”(第 149 页)中逐字复制的一些代码。代码(如下)在我们的 Asp.Net Web 应用程序中用于探测是否有足够的堆栈空间。我们的网站允许用户使用简单的专有脚本语言编写自己的网页并控制逻辑 - 用户可能会编写一些讨厌的脚本并导致 *** 异常,因此我们使用 Duffy 的代码示例来停止执行错误的脚本无法捕获的 *** 异常会破坏整个 IIS AppPool。这一直运作良好。

问题

今天下午突然间,我们的日志充满了 System.OverflowException 错误。我们对该服务器的每个请求都遇到了相同的异常。快速的 IIS 重置解决了这个问题。

异常类型: System.OverflowException

异常消息: 算术运算导致溢出。

堆栈跟踪: 在 System.IntPtr..ctor(Int64 值) 在 C:\SVN\Liquidhtml\Trunk\LiquidHtmlFlowManager\StackManagement.cs:line 47 中的 LiquidHtmlFlowManager.StackManagement.CheckForSufficientStack(UInt64 字节)处

代码:

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) CPU。

传递给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);
    

【问题讨论】:

【参考方案1】:

演员阵容错了。 stackinfo 的地址是一个 64 位的值。您不能将其转换为 uint 而不冒溢出异常的风险。减去 4096 也没有意义,VirtualQuery() 无论如何都会找到基地址。修复:

 IntPtr currentAddr = new IntPtr(&stackInfo);

Duffy 的代码只能用于 32 位代码。

【讨论】:

非常感谢。当您说“Duffy 的”代码只能用于 32 位代码时”——我想我需要根据它是 32 位还是 64 位平台来更改 MEMORY_BASIC_INFORMATION 结构,然后再取出 uint 强制转换。我会花一些时间写这个,然后可能会在 SO 上问另一个问题,看看我是否做对了。 对,那也是错的。 VirtualQuery 只是失败了。未经测试的代码在第一次尝试时永远不会工作:)

以上是关于算术运算导致不安全 C# 中的溢出的主要内容,如果未能解决你的问题,请参考以下文章

vb.net 算术运算导致溢出这是咋回事儿?

警告 C26451 算术溢出:使用运算符“-”

c/c++ 运算溢出问题

javascript算术溢出

C ++中的算术溢出添加2个DWORD

linq中的算术运算使用实体框架mvc C#