diff --git a/rostests/apitests/ntdll/CMakeLists.txt b/rostests/apitests/ntdll/CMakeLists.txt index 57bb3056167..a8247ed4e22 100644 --- a/rostests/apitests/ntdll/CMakeLists.txt +++ b/rostests/apitests/ntdll/CMakeLists.txt @@ -27,6 +27,7 @@ list(APPEND SOURCE RtlGetLongestNtPathLength.c RtlInitializeBitMap.c RtlMemoryStream.c + StackOverflow.c SystemInfo.c Timer.c testlist.c) diff --git a/rostests/apitests/ntdll/StackOverflow.c b/rostests/apitests/ntdll/StackOverflow.c new file mode 100644 index 00000000000..9cd710753b6 --- /dev/null +++ b/rostests/apitests/ntdll/StackOverflow.c @@ -0,0 +1,184 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPL - See COPYING in the top level directory + * PURPOSE: Test for stack overflow + * PROGRAMMER: Jérôme Gardou + */ + +#define WIN32_NO_STATUS +#include +#include +#include +#include + +static int iteration = 0; +static PVOID StackAllocationBase; +static PVOID LastStackAllocation; +static ULONG StackSize; + +static +void +infinite_recursive(void) +{ + MEMORY_BASIC_INFORMATION MemoryBasicInfo; + NTSTATUS Status; + char Buffer[0x500]; + + sprintf(Buffer, "Iteration %d.\n", iteration++); + + Status = NtQueryVirtualMemory( + NtCurrentProcess(), + &Buffer[0], + MemoryBasicInformation, + &MemoryBasicInfo, + sizeof(MemoryBasicInfo), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + /* This never changes */ + ok_ptr(MemoryBasicInfo.AllocationBase, StackAllocationBase); + /* Stack is committed one page at a time */ + ok_ptr(MemoryBasicInfo.BaseAddress, (PVOID)PAGE_ROUND_DOWN(&Buffer[0])); + /* This is the protection of the memory when it was reserved. */ + ok_long(MemoryBasicInfo.AllocationProtect, PAGE_READWRITE); + /* Windows commits the whole used stack at once, +2 pages. */ +#if 0 + ok_long(MemoryBasicInfo.RegionSize, ((ULONG_PTR)StackAllocationBase + StackSize + 2* PAGE_SIZE) - PAGE_ROUND_DOWN(&Buffer[0])); +#endif + /* This is the state of the queried address */ + ok_long(MemoryBasicInfo.State, MEM_COMMIT); + /* This is the protection of the queried address */ + ok_long(MemoryBasicInfo.Protect, PAGE_READWRITE); + /* Of course this is private memory. */ + ok_long(MemoryBasicInfo.Type, MEM_PRIVATE); + + LastStackAllocation = &Buffer[-0x500]; + + infinite_recursive(); +} + +START_TEST(StackOverflow) +{ + NTSTATUS Status; + MEMORY_BASIC_INFORMATION MemoryBasicInfo; + + /* Get the base of the stack */ + Status = NtQueryVirtualMemory( + NtCurrentProcess(), + &Status, + MemoryBasicInformation, + &MemoryBasicInfo, + sizeof(MemoryBasicInfo), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + StackAllocationBase = MemoryBasicInfo.AllocationBase; + trace("Stack allocation base is %p.\n", StackAllocationBase); + StackSize = MemoryBasicInfo.RegionSize; + + /* Check TEB attributes */ + ok_ptr(NtCurrentTeb()->DeallocationStack, StackAllocationBase); + ok_ptr(NtCurrentTeb()->NtTib.StackBase, (PVOID)((ULONG_PTR)MemoryBasicInfo.BaseAddress + MemoryBasicInfo.RegionSize)); + ok_ptr(NtCurrentTeb()->NtTib.StackLimit, (PVOID)((ULONG_PTR)MemoryBasicInfo.BaseAddress - PAGE_SIZE)); + trace("Guaranteed stack size is %lu.\n", NtCurrentTeb()->GuaranteedStackBytes); + + /* Get its size */ + Status = NtQueryVirtualMemory( + NtCurrentProcess(), + StackAllocationBase, + MemoryBasicInformation, + &MemoryBasicInfo, + sizeof(MemoryBasicInfo), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + + /* This is the complete stack size */ + StackSize += MemoryBasicInfo.RegionSize; + trace("Stack size is 0x%lx.\n", StackSize); + + trace("Stack limit %p, stack base %p.\n", NtCurrentTeb()->NtTib.StackLimit, NtCurrentTeb()->NtTib.StackBase); + + /* Take a look at what is beyond the stack limit */ + Status = NtQueryVirtualMemory( + NtCurrentProcess(), + NtCurrentTeb()->NtTib.StackLimit, + MemoryBasicInformation, + &MemoryBasicInfo, + sizeof(MemoryBasicInfo), + NULL); + ok_ntstatus(Status, STATUS_SUCCESS); + + ok_ptr(MemoryBasicInfo.BaseAddress, NtCurrentTeb()->NtTib.StackLimit); + ok_ptr(MemoryBasicInfo.AllocationBase, StackAllocationBase); + ok_long(MemoryBasicInfo.AllocationProtect, PAGE_READWRITE); + ok_long(MemoryBasicInfo.RegionSize, 2 * PAGE_SIZE); + ok_long(MemoryBasicInfo.State, MEM_COMMIT); + ok_long(MemoryBasicInfo.Protect, PAGE_READWRITE); + ok_long(MemoryBasicInfo.Type, MEM_PRIVATE); + + /* Accessing below stack limit is OK, as long as we don't starve the reserved space. */ + _SEH2_TRY + { + volatile CHAR* Pointer = (PVOID)((ULONG_PTR)NtCurrentTeb()->NtTib.StackLimit - PAGE_SIZE / 2); + CHAR Value = *Pointer; + (void)Value; + Status = STATUS_SUCCESS; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + ok_ntstatus(Status, STATUS_SUCCESS); + + _SEH2_TRY + { + infinite_recursive(); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + trace("Exception after %d iteration.\n", iteration); + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + + ok_ntstatus(Status, STATUS_STACK_OVERFLOW); + + /* Windows lets 2 pages between the reserved memory and the smallest possible stack address */ + ok((ULONG_PTR)LastStackAllocation > (ULONG_PTR)StackAllocationBase, "\n"); + ok_long(PAGE_ROUND_DOWN(LastStackAllocation), (ULONG_PTR)StackAllocationBase + 2 * PAGE_SIZE); + + /* And in fact, this is the true condition of the stack overflow */ + ok_ptr(NtCurrentTeb()->NtTib.StackLimit, (PVOID)((ULONG_PTR)StackAllocationBase + PAGE_SIZE)); + + trace("Stack limit %p, stack base %p.\n", NtCurrentTeb()->NtTib.StackLimit, NtCurrentTeb()->NtTib.StackBase); + + /* Of course, accessing above the stack limit is OK. */ + _SEH2_TRY + { + volatile CHAR* Pointer = (PVOID)((ULONG_PTR)NtCurrentTeb()->NtTib.StackLimit + PAGE_SIZE / 2); + CHAR Value = *Pointer; + (void)Value; + Status = STATUS_SUCCESS; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + ok_ntstatus(Status, STATUS_SUCCESS); + + /* But once stack is starved, it's starved. */ + _SEH2_TRY + { + volatile CHAR* Pointer = (PVOID)((ULONG_PTR)NtCurrentTeb()->NtTib.StackLimit - PAGE_SIZE / 2); + CHAR Value = *Pointer; + (void)Value; + Status = STATUS_SUCCESS; + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + trace("Exception after %d iteration.\n", iteration); + Status = _SEH2_GetExceptionCode(); + } + _SEH2_END; + ok_ntstatus(Status, STATUS_ACCESS_VIOLATION); +} diff --git a/rostests/apitests/ntdll/testlist.c b/rostests/apitests/ntdll/testlist.c index 365c7d4e7f2..7d27f30093a 100644 --- a/rostests/apitests/ntdll/testlist.c +++ b/rostests/apitests/ntdll/testlist.c @@ -31,6 +31,7 @@ extern void func_RtlGetLengthWithoutTrailingPathSeperators(void); extern void func_RtlGetLongestNtPathLength(void); extern void func_RtlInitializeBitMap(void); extern void func_RtlMemoryStream(void); +extern void func_StackOverflow(void); extern void func_TimerResolution(void); const struct test winetest_testlist[] = @@ -63,6 +64,7 @@ const struct test winetest_testlist[] = { "RtlGetLongestNtPathLength", func_RtlGetLongestNtPathLength }, { "RtlInitializeBitMap", func_RtlInitializeBitMap }, { "RtlMemoryStream", func_RtlMemoryStream }, + { "StackOverflow", func_StackOverflow }, { "TimerResolution", func_TimerResolution }, { 0, 0 }