【问题标题】:Most efficient replacement for IsBadReadPtr?IsBadReadPtr 的最有效替代品?
【发布时间】:2010-10-04 12:09:41
【问题描述】:

我有一些 Visual C++ 代码接收指向缓冲区的指针,该缓冲区包含需要由我的代码处理的数据以及该缓冲区的长度。由于我无法控制的错误,有时此指针未初始化或不适合读取的代码进入我的代码(即当我尝试访问缓冲区中的数据时它会导致崩溃。)

所以,我需要在使用它之前验证这个指针。我不想使用 IsBadReadPtr 或 IsBadWritePtr,因为每个人都同意它们是错误的。 (谷歌他们的例子。)它们也不是线程安全的——在这种情况下这可能不是问题,尽管线程安全的解决方案会很好。

我在网上看到了通过使用 VirtualQuery 或仅在异常处理程序中执行 memcpy 来完成此任务的建议。但是,需要执行此检查的代码是时间敏感的,因此我需要最有效的检查,而且也是 100% 有效的。任何想法将不胜感激。

要明确一点:我知道最好的做法是只读取错误的指针,让它导致异常,然后将其追溯到源头并修复实际问题。但是,在这种情况下,错误指针来自我无法控制的 Microsoft 代码,因此我必须对其进行验证。

还要注意,我不在乎指向的数据是否有效。我的代码正在寻找特定的数据模式,如果没有找到它们将忽略这些数据。我只是想防止在对这些数据运行 memcpy 时发生崩溃,并且在尝试 memcpy 时处理异常将需要更改遗留代码中的十几个位置(但如果我有类似 IsBadReadPtr 的调用我只会必须在一处更改代码)。

【问题讨论】:

  • 你能发布一个调用栈(一直到 main 或 WinMain)吗?
  • 我在我的回答中添加了一段:如果您之前接触过所有堆栈页面,也许您可​​以安全地使用 IsBadReadPtr。
  • 我不知道您使用 VirtualQuery 的示例代码有什么问题。一个普遍的问题是,即使 VirtualQuery 说内存很好,另一个线程可能会在您测试它之后但在您尝试读取它之前释放内存:因此,考虑到多线程,提前测试它不是 ...
  • ,,, 在异常处理程序中保护您的读取尝试的替代品。

标签: c++ windows visual-c++ memory


【解决方案1】:

这是一个老问题,但这部分:

需要进行此检查的代码对时间敏感,所以我需要 最有效的检查,也是 100% 有效的

VirtualQuery() 接受内核调用,因此在内存大部分时间都可以读取的情况下,异常处理程序中的简单 memcpy() 会更快。

__try
{
  memcpy(dest, src, size);
}__except(1){}

当没有异常时,所有都保持在用户模式。对于内存不好读多于好的用例可能会慢一些(因为它会触发一个异常,即通过内核往返)。

您还可以使用自定义 memcpy 循环和 *size 对其进行扩展,这样您就可以准确返回实际读取的字节数。

【讨论】:

    【解决方案2】:

    这是我使用的,它只是通过使用#define 替换了官方的微软,这样你就可以使用微软的,而不用担心它们会让你失望。

    // Check memory address access
    const DWORD dwForbiddenArea = PAGE_GUARD | PAGE_NOACCESS;
    const DWORD dwReadRights = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
    const DWORD dwWriteRights = PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
    
    template<DWORD dwAccessRights>
    bool CheckAccess(void* pAddress, size_t nSize)
    {
        if (!pAddress || !nSize)
        {
            return false;
        }
    
        MEMORY_BASIC_INFORMATION sMBI;
        bool bRet = false;
    
        UINT_PTR pCurrentAddress = UINT_PTR(pAddress);
        UINT_PTR pEndAdress = pCurrentAddress + (nSize - 1);
    
        do
        {
            ZeroMemory(&sMBI, sizeof(sMBI));
            VirtualQuery(LPCVOID(pCurrentAddress), &sMBI, sizeof(sMBI));
    
            bRet = (sMBI.State & MEM_COMMIT) // memory allocated and
                && !(sMBI.Protect & dwForbiddenArea) // access to page allowed and
                && (sMBI.Protect & dwAccessRights); // the required rights
    
            pCurrentAddress = (UINT_PTR(sMBI.BaseAddress) + sMBI.RegionSize);
        } while (bRet && pCurrentAddress <= pEndAdress);
    
        return bRet;
    }
    
    #define IsBadWritePtr(p,n) (!CheckAccess<dwWriteRights>(p,n))
    #define IsBadReadPtr(p,n) (!CheckAccess<dwReadRights>(p,n))
    #define IsBadStringPtrW(p,n) (!CheckAccess<dwReadRights>(p,n*2))
    

    此方法基于我对 Raymond Chen 的博文If I'm not supposed to call IsBadXxxPtr, how can I check if a pointer is bad?的理解

    【讨论】:

      【解决方案3】:

      如果您必须求助于检查数据中的模式,这里有一些提示:

      • 如果您提到使用 IsBadReadPtr,您可能正在为 Windows x86 或 x64 进行开发。

      • 您也许可以对指针进行范围检查。指向对象的指针将是字对齐的。在 32 位窗口中,用户空间指针在 0x00401000-0x7FFFFFFF 范围内,或者对于大地址感知应用程序,改为 0x00401000-0xBFFFFFFF(编辑:0x00401000-0xFFFF0000 用于 64 位窗口上的 32 位程序) .上面的 2GB/1GB 是为内核空间指针保留的。

      • 对象本身将存在于不可执行的读/写内存中。它可能存在于堆中,也可能是全局变量。如果它是一个全局变量,您可以验证它是否存在于正确的模块中。

      • 如果您的对象有一个 VTable,并且您没有使用其他类,请将其 VTable 指针与来自已知良好对象的另一个 VTable 指针进行比较。

      • 范围检查变量以查看它们是否可能有效。例如,bools 只能是 1 或 0,所以如果你看到一个值为 242,那显然是错误的。指针也可以进行范围检查和对齐检查。

      • 如果其中包含对象,请同时检查其 VTable 和数据。

      • 如果有指向其他对象的指针,您可以检查该对象是否存在于可读写且不可执行的内存中,如果适用,请检查 VTable,以及范围检查数据。

      如果没有已知 VTable 地址的好对象,可以使用这些规则来检查 VTable 是否有效:

      • 虽然对象存在于读/写内存中,并且 VTable 指针是对象的一部分,但 VTable 本身将存在于只读且不可执行的内存中,并将与字边界对齐。它也属于该模块。
      • VTable 的条目是指向代码的指针,它是只读和可执行的,而不是可写的。代码地址没有对齐限制。代码将属于该模块。

      【讨论】:

        【解决方案4】:

        如果变量未初始化,您将被冲洗掉。迟早它会成为你不想玩的东西的地址(比如你自己的堆栈)。

        如果你认为你需要这个,并且 (uintptr_t)var

        【讨论】:

          【解决方案5】:

          我能想到的最快的解决方案是使用VirtualQuery 咨询虚拟内存管理器,看看给定地址是否有可读页面,然后缓存结果(但是任何缓存都会降低检查的准确性)。

          示例(无缓存):

          BOOL CanRead(LPVOID p)
          {
            MEMORY_BASIC_INFORMATION mbi;
            mbi.Protect = 0;
            ::VirtualQuery(((LPCSTR)p) + len - 1, &mbi, sizeof(mbi));
            return ((mbi.Protect & 0xE6) != 0 && (mbi.Protect & PAGE_GUARD) == 0);
          }
          

          【讨论】:

            【解决方案6】:
            bool IsBadReadPtr(void* p)
            {
                MEMORY_BASIC_INFORMATION mbi = {0};
                if (::VirtualQuery(p, &mbi, sizeof(mbi)))
                {
                    DWORD mask = (PAGE_READONLY|PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY);
                    bool b = !(mbi.Protect & mask);
                    // check the page is not a guard page
                    if (mbi.Protect & (PAGE_GUARD|PAGE_NOACCESS)) b = true;
            
                    return b;
                }
                return true;
            }
            

            【讨论】:

            • 我个人觉得这段代码非常有用且易于理解。
            • 说明你的用法和理解,不要只写cmets。 :P
            • 它打印 valid ptr is i will write (LPVOID) base + 0xFFFFFFF 0xFFFFFFF is invalid location from base address的过程。 .
            • @HaSeeBMiR 这取决于,在 64 位应用程序中,这可能是一个有效地址。我还想补充一点,0xFFFFFFF 不是 32 位最大值。只有 7 个 F,32 位直到 8 或 0xFFFFFFFF。
            【解决方案7】:

            任何检查内存有效性的实现都受制于使 IsBadReadPtr 失败的相同约束。您可以发布一个示例调用堆栈,以检查从 Windows 传递给您的指针的内存有效性吗?这可能有助于其他人(包括我)首先诊断您为什么需要这样做。

            【讨论】:

              【解决方案8】:

              如果您使用的是 VC++,那么我建议使用微软特定的关键字 __try __except 捕获硬件异常

              【讨论】:

              • 仍然在 __try \ __except 内崩溃:(
              【解决方案9】:

              线程安全的解决方案会很好

              我猜只有 IsBadWritePtr 不是线程安全的。

              只是在异常处理程序中执行 memcpy

              这实际上是 IsBadReadPtr 正在做的事情......如果您在代码中这样做,那么您的代码将与 IsBadReadPtr 实现具有相同的错误:http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx

              --编辑:--

              我读过的关于 IsBadReadPtr 的唯一问题是错误指针可能指向(因此您可能会不小心触及)堆栈的保护页。也许您可以通过以下方式避免这个问题(并因此安全地使用 IsBadReadPtr):

              • 了解您的进程中正在运行哪些线程
              • 了解线程堆栈的位置以及它们的大小
              • 在开始调用 isBadReadPtr 之前,遍历每个堆栈,故意触摸堆栈的每个页面至少一次

              另外,与上述 URL 相关的一些 cmets 也建议使用 VirtualQuery。

              【讨论】:

              • 谢谢。我认为 VirtualQuery 的想法是最有前途的,但我不是很理解。你知道为什么上面的代码不能可靠地工作吗?
              • 这通常不起作用。当您触摸堆栈保护页面时,只有当您从拥有它的线程中触摸它时,它才会增加堆栈!
              • 一个问题是,如果你得到一个指向真实内存的虚假指针,可能是虽然从该内存区域的前几次读取可以向下几个字节(它跨越页面边界) ) 可能是未分配且无效的内存。
              【解决方案10】:

              为什么不能调用api

              AfxIsValidAddress((p), sizeof(type), FALSE));

              【讨论】:

              【解决方案11】:

              这些函数不好用的原因是问题不能被可靠地解决。

              如果您调用的函数返回一个指向已分配内存的指针,那么它看起来是有效的,但它指向的是其他不相关的数据,如果您使用它会损坏您的应用程序.

              很可能,您调用的函数实际上表现正确,而您正在滥用它。 (不能保证,但经常就是这种情况。)

              它是什么功能?

              【讨论】:

              • 如果数据可以读取但是伪造的,这实际上可能不是问题,因为我的代码正在寻找一些特定的数据模式,如果它们不存在,它将忽略数据。这是我正在尝试修复的随机崩溃。 :-)
              • 但这仍然意味着您调用的函数已经冒险进入了未定义的领域。你不能真的依赖它来不再破坏你的程序状态。当然,如果你愿意忍受,只需抓住访问违规并假装什么都没发生。
              • 在它指向的随机数据中也有可能发现这些位模式,这意味着您将成为破坏程序状态的人;)
              【解决方案12】:

              恐怕你不走运 - 没有办法可靠地检查指针的有效性。哪些 Microsoft 代码给了你不好的指示?

              【讨论】:

              • 这是 Winsock 2 分层服务提供者 (LSP) 示例代码。
              • 如果您正在使用 LSP,请准备好几天的拉头发。并从 Microsoft 获得良好的开发支持合同。
              猜你喜欢
              • 1970-01-01
              • 2015-05-10
              • 2011-04-08
              • 1970-01-01
              • 2019-05-29
              • 1970-01-01
              • 2021-11-02
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多