Louis Kronberg
Louis Kronberg

Reputation: 11

PAGE_GUARD protection on stack pages but exception handler is not executed

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

Answers (1)

RbMm
RbMm

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

Related Questions