IsBadReadPtr 的最有效替代品?

Posted

技术标签:

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

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

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

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

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

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

【问题讨论】:

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

【讨论】:

他可以,但对他没有帮助:social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/…【参考方案11】:

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

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

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

它是什么功能?

【讨论】:

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

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

【讨论】:

这是 Winsock 2 分层服务提供者 (LSP) 示例代码。 如果您正在使用 LSP,请准备好几天的拉头发。并从 Microsoft 获得良好的开发支持合同。

以上是关于IsBadReadPtr 的最有效替代品?的主要内容,如果未能解决你的问题,请参考以下文章

Java 泛型:该类型不是有效的替代品

现有 Model.Document 上 .save() 的有效替代方案

此 SQL 查询是不是有更有效的替代方法?

iOS 11 中 UIDocumentMenuViewController 的替代品

合并较大数据的有效替代方法。框架 R

Apache HttpComponents 的替代品? [关闭]