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 的最有效替代品?的主要内容,如果未能解决你的问题,请参考以下文章
现有 Model.Document 上 .save() 的有效替代方案