IsBadXxxPtr Is Really Harmful – Please Don’t Use These Functions
You might have stumbled upon posts like this one that tell you there’s a simple solution for verifying that a memory location is OK for read/write/execution: Just use the “IsBadXxxPtr” family of functions that take a memory address and return a BOOL indicating if you can use the memory.
It turns out (and I’m hardly the first to blog about it) that calling these functions causes more problems than it solves, and causes bugs rather than fixes them. Please heed the collective advice and stay away from these APIs.
Here are some reasons why:
Non-atomicity
If you want to test a page for reading, a standard approach would be to call IsBadReadPtr, get a FALSE result and then try reading from the page, right? Well, what stops some other thread (or even your thread in an APC, for example) from unmapping that page and rendering it invalid for reading just after your checked but before you had a chance to read from it? Guess what, you crash – and this time you crash in a very surprising location. If you try debugging, you’ll see that your app causes a read access violation after testing the address with IsBadReadPtr, and head-scratching is guaranteed.
“Corrupt memory if possible”
IsBadWritePtr is even worse. As Raymond Chen aptly put it, this method should really be called CorruptMemoryIfPossible. What this method does is try to write a single byte to the beginning of every page in the range that you passed to it, and then fix up the old value of that byte if the write succeeded.
Two horrible things come to mind: First, another thread can observe the changed byte before the API had a change to fix it up to the old value. This is clearly very bad. Another horrible thing (which is far less likely though) is that the page becomes read-only after the test byte has been written and before the API had a chance to fix it to the old value. Both of these things are random corruptions that you have no hopes of detecting.
Guard pages
Not many Windows programmers are familiar with guard pages, but they are very important nonetheless. A guard page is a page protected by the PAGE_GUARD page protection flag, which causes a one-shot page guard exception when the page is accessed. After the page is accessed, however, the guard is removed and will not be triggered again unless the page is protected by PAGE_GUARD again.
Guard pages are used by a variety of applications, but they have one very important role: Ensuring that thread stacks are lazily expanded. The way thread stacks work on Windows is that although by default 1MB of stack space is reserved for each thread, the individual pages in that range are committed page-by-page, when the thread attempts to expand the stack by calling a method or allocating stack-based memory. What happens is that the next page that is about to be used for the thread’s stack is a guard page, and there’s an exception handler that catches the page guard exception, properly commits the page and, most importantly, places a guard on the next stack page.
I suppose you can already see the potential for trouble if you touch guard pages using IsBadXxxPtr. The API will swallow the page guard exception like any other, and the exception handler responsible for placing a guard on the next stack page will not run. This means you just created a potential stack overflow where there was previously no reason for one to occur.
There are also other examples of why guard pages can be useful, and in most of these cases the IsBadXxxPtr family of APIs defeats the mechanism behind guard pages in varyingly fatal ways.
Not to mention that bad pointers will crash you anyway
There’s also the more philosophical argument here. If you get a bad pointer (and I’m not talking about a NULL pointer here, which can be easily detected* by other means), what do you think you can do about it? If you have the privilege of returning an error code, do you think the program that gave you an invalid pointer in the first place is going to check the error code?
What you often do by checking for “bad” pointers is turning a fail-fast crash into a subtle bug to be discovered maybe hours later. The worst thing that can happen if you have a memory corruption is that your application continues running. If you check for bad pointers, it’s as if you’re trying to guarantee that your application continues running with a memory corruption.
* By “easily detected” I mean that NULL pointers are not really that hard to identify. Windows guarantees that the first 64KB of memory are a PAGE_NOACCESS region, so there’s absolutely no chance that someone is handing you a valid address that is strictly smaller than 65,536. Corollary: It’s a moot point to try detecting NULL pointers by using IsBadXxxPtr. It’s true that a NULL pointer is not always NULL, e.g. if you’re accidentally obtaining a pointer to a field in a NULL structure pointer or obtaining a pointer to a member using a NULL this pointer. However, in all these cases (unless you have structures bigger than 64KB, which is a whole different problem) you can simply test the address for < 64KB.