Reputation: 11
I am adding the PAGE_GUARD
protection to pages of the current thread's stack via VirtualProtect
. However, on accessing the stack, the exception handler I have installed is never executed.
I assume the OS just silently swallows the Guard-Page-Violation Exceptions because it uses guard pages to manage stack growth and stack overflows.
Is it in this case possible to catch these exceptions at all ?
Here is my C-code
#include <stdio.h>
#include <Windows.h>
#include <assert.h>
LONG WINAPI BlanketExceptionHandler(struct _EXCEPTION_POINTERS *ExceptionInfo) {
printf("exception code: %lx\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
return EXCEPTION_CONTINUE_SEARCH;
}
int main(void) {
// Install exception handler
SetUnhandledExceptionFilter(BlanketExceptionHandler);
ULONG_PTR stackBot = 0, stackTop = 0;
GetCurrentThreadStackLimits(&stackBot, &stackTop);
assert(stackBot < stackTop);
// turn all pages in the stack into guard-pages
DWORD savedProtect = 0;
if(!VirtualProtect((LPVOID) stackBot, stackTop - stackBot, PAGE_READWRITE | PAGE_GUARD, &savedProtect)){
fprintf(stderr, "[Error]: Could not add guard pages: %ld\n", GetLastError());
return 1;
}
// access some stack memory page. This should trigger the registered exception handler!
*(DWORD*) (stackTop - 0x1500) = 0xdeadbeef;
return 0;
}
Code needs to be compiled with
cl /nologo main.c /link /STACK:0x100000,0x100000
Running the code gives no output from the BlanketExceptionHandler
. But, using VirtualQuery
, I have observed that the stack pages have the correct protections.
I have also tried installing the exception handler through AddVectoredExceptionHandler
but that did not work either.
Upvotes: 0
Views: 433
Reputation: 33754
Is it in this case possible to catch these exceptions at all ?
no. if you access guard page, exception (as any exception) first will be handle by kernel code. if exception ocur in current thread stack range - the kernel handler remove PAGE_GUARD
from page, where exeception was, ensure that bellow this page - several PAGE_GUARD
exist and adjust StackLimit := PAGE_ALIGN(MemmoryAccessAddress)
in NT_TIB
(you can check this). and not call user mode exception handlers (if this not the last page in stack)
code example:
PCSTR GetStateSz(ULONG State)
{
switch (State)
{
case MEM_FREE: return "FREE";
case MEM_COMMIT: return "COMMIT";
case MEM_RESERVE: return "RESERVE";
}
return "?";
}
PCSTR GetTypeSz(ULONG Type)
{
switch (Type)
{
case MEM_IMAGE: return "IMAGE";
case MEM_MAPPED: return "MAPPED";
case MEM_PRIVATE: return "PRIVATE";
}
return "?";
}
void FormatProtect(ULONG Protect, PSTR psz, ULONG cch)
{
static const ULONG pp[] = {
PAGE_NOACCESS,
PAGE_READONLY,
PAGE_READWRITE,
PAGE_WRITECOPY,
PAGE_EXECUTE,
PAGE_EXECUTE_READ,
PAGE_EXECUTE_READWRITE,
PAGE_EXECUTE_WRITECOPY,
PAGE_GUARD,
PAGE_NOCACHE,
PAGE_WRITECOMBINE,
};
static const PCSTR ss[] = {
"NOACCESS",
"READONLY",
"READWRITE",
"WRITECOPY",
"EXECUTE",
"EXECUTE_READ",
"EXECUTE_READWRITE",
"EXECUTE_WRITECOPY",
"GUARD",
"NOCACHE",
"WRITECOMBINE",
};
ULONG n = _countof(pp);
do
{
if (Protect & pp[--n])
{
int len = sprintf_s(psz, cch, " | %s", ss[n]);
if (0 >= len)
{
break;
}
psz += len, cch -= len;
}
} while (n);
}
ULONG WINAPI PrintProtect_I(PVOID BaseAddress)
{
PVOID AllocationBase = BaseAddress;
::MEMORY_BASIC_INFORMATION mbi;
while (VirtualQuery(BaseAddress, &mbi, sizeof(mbi)) &&
mbi.AllocationBase == AllocationBase)
{
CHAR szProtect[0x100];
FormatProtect(mbi.Protect, szProtect, _countof(szProtect));
DbgPrint("[%p, %p) [%X] %s %s { %s }\n", mbi.BaseAddress,
BaseAddress = (PBYTE)mbi.BaseAddress + mbi.RegionSize,
mbi.RegionSize >> PAGE_SHIFT, GetStateSz(mbi.State), GetTypeSz(mbi.Type), szProtect);
}
return 0;
}
void PrintProtect(PVOID BaseAddress)
{
if (HANDLE hThread = CreateThread(0, 0, PrintProtect_I, BaseAddress, 0, 0))
{
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
PNT_TIB tib = reinterpret_cast<PNT_TIB>(NtCurrentTeb());
DbgPrint("[%p, %p) << Current Stack\n\n", tib->StackLimit, tib->StackBase);
}
ULONG WINAPI SetGuard(PVOID pv)
{
ULONG op;
return VirtualProtect(pv, PAGE_SIZE, PAGE_EXECUTE_READWRITE|PAGE_GUARD, &op);
}
void GDemo()
{
PVOID stack = alloca(0x10000);
PBYTE pb = (PBYTE)PAGE_ALIGN((PBYTE)stack + 0x8000);
ULONG_PTR a, b;
GetCurrentThreadStackLimits(&a, &b);
DbgPrint("[%p, %p) << Stack Region\n\n", a, b);
PrintProtect((PVOID)a);
if (HANDLE hThread = CreateThread(0, 0, SetGuard, pb, 0, 0))
{
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
PrintProtect((PVOID)a);
*pb = 0;
PrintProtect((PVOID)a);
}
}
and output:
[000000EAAE4C0000, 000000EAAE5C0000) << Stack Region
[000000EAAE4C0000, 000000EAAE5AB000) [EB] RESERVE PRIVATE { }
[000000EAAE5AB000, 000000EAAE5AE000) [3] COMMIT PRIVATE { | GUARD | READWRITE }
[000000EAAE5AE000, 000000EAAE5C0000) [12] COMMIT PRIVATE { | READWRITE }
[000000EAAE5AE000, 000000EAAE5C0000) << Current Stack
[000000EAAE4C0000, 000000EAAE5AB000) [EB] RESERVE PRIVATE { }
[000000EAAE5AB000, 000000EAAE5AE000) [3] COMMIT PRIVATE { | GUARD | READWRITE }
[000000EAAE5AE000, 000000EAAE5B7000) [9] COMMIT PRIVATE { | READWRITE }
[000000EAAE5B7000, 000000EAAE5B8000) [1] COMMIT PRIVATE { | GUARD | EXECUTE_READWRITE }
[000000EAAE5B8000, 000000EAAE5C0000) [8] COMMIT PRIVATE { | READWRITE }
[000000EAAE5AE000, 000000EAAE5C0000) << Current Stack
[000000EAAE4C0000, 000000EAAE5AB000) [EB] RESERVE PRIVATE { }
[000000EAAE5AB000, 000000EAAE5AE000) [3] COMMIT PRIVATE { | GUARD | READWRITE }
[000000EAAE5AE000, 000000EAAE5B4000) [6] COMMIT PRIVATE { | READWRITE }
[000000EAAE5B4000, 000000EAAE5B7000) [3] COMMIT PRIVATE { | GUARD | READWRITE }
[000000EAAE5B7000, 000000EAAE5B8000) [1] COMMIT PRIVATE { | EXECUTE_READWRITE }
[000000EAAE5B8000, 000000EAAE5C0000) [8] COMMIT PRIVATE { | READWRITE }
[000000EAAE5B7000, 000000EAAE5C0000) << Current Stack
Upvotes: 1