A Thread’s Stack

It’s common and known that each thread has its own stack. The default thread stack size is 1 MB but you can override this value easily. The system reserves in the process virtual memory a stack for the thread and commits few pages of the reserved pages (i.e. one or two pages) and sets the guard protector in the pre-last reserved page. When the stack uses the committed pages, the system tries to commit the next resaved page. This continues happening until the guarded page is touched, until then the OS throws a EXCEPTION_STACK_OVERFLOW
and uses this page. If the program handles this exception, the pre-last page is reused. After that, if the program uses the last reserved page, the system throws EXCEPTION _ACCESS_VIOLATION (through windows error reporting service) and terminates the process!

The system raises an EXCEPTION_STACK_OVERFLOW exception when a thread’s last guard page is touched. If this exception is caught and the thread’s execution continues, the system will not raise the exception for this thread again because there are no more guard pages. To receive future EXCEPTION_STACK_OVERFLOW exceptions for this thread, your application must reset the guard page. This is easily accomplished by calling the C run-time library’s _resetstkoflw function (defined in malloc.h)

The bottommost page of a stack’s region is always reserved. Doing so protects against accidental overwriting of other data being used by the process. That the stack grows from top to down.

Another difficult bug to catch is stack underflow. To see what a stack underflow is, examine the following code:

   PTSTR pszCmdLine, int nCmdShow) {
   BYTE aBytes[100];
   aBytes[10000] = 0; // Stack underflow

When this function’s assignment statement is executed, an attempt is made to access memory beyond the end of the thread’s stack. Of course, the compiler and the linker will not catch the bug in the code just shown, and an access violation will not necessarily be raised when the statement executes because it is possible to have another region immediately after your thread’s stack. If this happens and you attempt to access memory beyond your stack, you might corrupt memory related to another part of your process—and the system will not detect this corruption. Here is a code snippet that shows a case where the stack underflow will always trigger a corruption because a memory block is allocated just after the stack of a thread:

DWORD WINAPI ThreadFunc(PVOID pvParam) {
   BYTE aBytes[0x10];
   // Figure out where the stack is in the virtual address space
   SIZE_T size = VirtualQuery(aBytes, &mbi, sizeof(mbi));
   // Allocate a block of memory just after the 1 MB stack
   SIZE_T s = (SIZE_T)mbi.AllocationBase + 1024*1024;
   PBYTE pAddress = (PBYTE)s;
   BYTE* pBytes = (BYTE*)VirtualAlloc(pAddress, 0x10000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
   // Trigger an unnoticeable stack underflow
   aBytes[0x10000] = 1; // Write in the allocated block, past the stack

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s