最有效的替代IsBadReadPtr?

时间:2009-01-30 16:05:44

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

我有一些Visual C ++代码接收指向缓冲区的指针,该缓冲区包含需要由我的代码处理的数据和该缓冲区的长度。由于我控制之外的错误,有时这个指针未经初始化或未适合阅读(即当我尝试访问缓冲区中的数据时会导致崩溃。)

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

我已经看到了使用VirtualQuery完成此操作的建议,或者只是在异常处理程序中执行memcpy。但是,需要进行此检查的代码是时间敏感的,因此我需要最有效的检查,这也是100%有效。任何想法都将不胜感激。

为了清楚起见:我知道最好的做法是只读坏指针,让它引起异常,然后追溯到源并修复实际问题。但是,在这种情况下,坏指针来自我无法控制的Microsoft代码,所以我必须验证它们。

另请注意,我不关心指向的数据是否有效。我的代码正在寻找特定的数据模式,如果没有找到它们将忽略它们。我只是想防止在对这些数据运行memcpy时发生崩溃,并且在尝试memcpy时处理异常需要在我的代码中更改十几个地方(但是如果我有像IsBadReadPtr这样的东西来调用我只会必须在一个地方更改代码。)

12 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:9)

  

线程安全的解决方案很不错

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

  

在异常处理程序中执行memcpy

这实际上是IsBadReadPtr正在做的......如果你在代码中做了,那么你的代码就会遇到与IsBadReadPtr实现相同的错误:http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx

<强> - 编辑: -

我读过的IsBadReadPtr的唯一问题是坏指针可能指向(因此你可能不小心碰到)堆栈的防护页面。也许你可以通过以下方式避免这个问题(因此安全地使用IsBadReadPtr):

  • 了解流程中正在运行的主题
  • 了解线程堆栈的位置以及它们的大小
  • 走开每个堆栈,在开始调用isBadReadPtr
  • 之前,至少触摸一次堆栈的每一页

此外,与上述URL相关的一些注释也建议使用VirtualQuery。

答案 2 :(得分:6)

这些功能使用不好的原因是问题无法可靠解决。

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

最有可能的是,您调用的函数实际上表现正常,并且您正在滥用它。 (不保证,但经常案例。)

它的功能是什么?

答案 3 :(得分:1)

为什么你不能打电话给api

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

答案 4 :(得分:0)

检查内存有效性的任何实现都受到使IsBadReadPtr失败的相同行为的影响。你可以发布一个示例callstack,你想在哪里检查从Windows传递给你的指针的内存有效性吗?这可能有助于其他人(包括我)在一开始就诊断为什么需要这样做。

答案 5 :(得分:0)

我能想到的最快的解决方案是使用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 :(得分:0)

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

如果您认为需要这个,那么(uintptr_t)var&lt; 65536是不够的(Windows不允许分配底部64k),没有真正的解决方案。 VirtualQuery等似乎“有效”,但迟早会烧毁你。

答案 7 :(得分:0)

如果你不得不求助于检查数据模式,可以参考以下几点:

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

  • 您可以检查指针。指向对象的指针将是单词对齐的。在32位窗口中,用户空间指针的范围为0x00401000-0x7FFFFFFF,或者对于大地址感知应用程序,则为0x00401000-0xBFFFFFFF。高2GB / 1GB是为内核空间指针保留的。

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

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

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

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

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

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

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

答案 8 :(得分:0)

这是我使用的,它只是通过使用 #define 来代替官方的 microsoft 的,这样您就可以使用 microsoft 的,而不必担心它们会让您失望。

// 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?

的理解

答案 9 :(得分:0)

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

<块引用>

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

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

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

在没有例外的情况下都保持在用户模式。对于内存读取多于读取好的用例,可能会慢一点(因为它会触发一个异常,该异常是通过内核来回往返)。

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

答案 10 :(得分:-1)

我担心你运气不好 - 没有办法可靠地检查指针的有效性。什么Microsoft代码给你不好的指针?

答案 11 :(得分:-1)

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