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

发布于 2024-10-22 01:44:42 字数 5074 浏览 5 评论 0原文

背景

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

问题

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

异常类型: System.OverflowException

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

堆栈跟踪: 在 System.IntPtr..ctor(Int64 值) 在 LiquidHtmlFlowManager.StackManagement.CheckForSufficientStack(UInt64 bytes) in C:\SVN\LiquidHtml\Trunk\LiquidHtmlFlowManager\StackManagement.cs:line 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) 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);
    }

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

乖不如嘢 2024-10-29 01:44:42

演员阵容是错误的。 stackinfo的地址是一个64位值。如果不冒 OverflowException 的风险,就无法将其转换为 uint。减去 4096 也是没有意义的,VirtualQuery() 无论如何都会找到基地址。修复:

 IntPtr currentAddr = new IntPtr(&stackInfo);

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文