From 11d11f93eeaa7f426b938c4cd937732d1efadf7c Mon Sep 17 00:00:00 2001 From: Thomas Faber Date: Sat, 11 Dec 2021 15:44:21 -0500 Subject: [PATCH] [NTDLL_APITEST] Add tests for NtCreateProfile and NtStartProfile. --- .../rostests/apitests/ntdll/CMakeLists.txt | 2 + .../rostests/apitests/ntdll/NtCreateProfile.c | 352 ++++++++++++++++++ .../rostests/apitests/ntdll/NtStartProfile.c | 143 +++++++ modules/rostests/apitests/ntdll/testlist.c | 4 + 4 files changed, 501 insertions(+) create mode 100644 modules/rostests/apitests/ntdll/NtCreateProfile.c create mode 100644 modules/rostests/apitests/ntdll/NtStartProfile.c diff --git a/modules/rostests/apitests/ntdll/CMakeLists.txt b/modules/rostests/apitests/ntdll/CMakeLists.txt index e371e95305f..f8882b1ebea 100644 --- a/modules/rostests/apitests/ntdll/CMakeLists.txt +++ b/modules/rostests/apitests/ntdll/CMakeLists.txt @@ -25,6 +25,7 @@ list(APPEND SOURCE NtContinue.c NtCreateFile.c NtCreateKey.c + NtCreateProfile.c NtCreateThread.c NtDeleteKey.c NtDuplicateObject.c @@ -59,6 +60,7 @@ list(APPEND SOURCE NtSetInformationToken.c NtSetValueKey.c NtSetVolumeInformationFile.c + NtStartProfile.c NtUnloadDriver.c NtWriteFile.c probelib.c diff --git a/modules/rostests/apitests/ntdll/NtCreateProfile.c b/modules/rostests/apitests/ntdll/NtCreateProfile.c new file mode 100644 index 00000000000..a29f4787613 --- /dev/null +++ b/modules/rostests/apitests/ntdll/NtCreateProfile.c @@ -0,0 +1,352 @@ +/* + * PROJECT: ReactOS API Tests + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Test for NtCreateProfile + * COPYRIGHT: Copyright 2021 Thomas Faber (thomas.faber@reactos.org) + */ + +#include "precomp.h" + +#define SIZEOF_MDL (5 * sizeof(PVOID) + 2 * sizeof(ULONG)) +typedef ULONG_PTR PFN_NUMBER; +/* Maximum size that can be described by an MDL on 2003 and earlier */ +#define MAX_MDL_BUFFER_SIZE ((MAXUSHORT - SIZEOF_MDL) / sizeof(PFN_NUMBER) * PAGE_SIZE + PAGE_SIZE - 1) + +#define broken(cond) (strcmp(winetest_platform, "windows") ? 0 : cond) + +static BOOL IsWow64; + +static +void +TestParameterValidation(void) +{ + NTSTATUS Status; + HANDLE ProfileHandle; + + Status = NtCreateProfile(NULL, + NULL, + NULL, + 0, + 0, + NULL, + 0, + ProfileTime, + 1); + ok_hex(Status, STATUS_INVALID_PARAMETER_7); + + /* For addresses below 0x10000, there's a special check for BufferSize<4 -- on x86 only */ + { + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0xFFFF, + 0, + 0, + NULL, + 3, + ProfileTime, + 1); + if (sizeof(PVOID) > sizeof(ULONG) || IsWow64) + { + ok_hex(Status, STATUS_INVALID_PARAMETER); + } + else + { + ok_hex(Status, STATUS_INVALID_PARAMETER_7); + } + + /* Increasing the pointer gets us past this */ + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + 0, + 0, + NULL, + 3, + ProfileTime, + 1); + ok_hex(Status, STATUS_INVALID_PARAMETER); + + /* So does increasing the size */ + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0xFFFF, + 0, + 0, + NULL, + 4, + ProfileTime, + 1); + ok_hex(Status, STATUS_INVALID_PARAMETER); + + /* ... or, specifying a bucket size */ + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0xFFFF, + 0, + 1, + NULL, + 4, + ProfileTime, + 1); + ok_hex(Status, STATUS_INVALID_PARAMETER); + } + + /* Bucket sizes less than two or larger than 31 are invalid */ + { + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + 0x80000000, + 1, + NULL, + 1, + ProfileTime, + 1); + ok_hex(Status, STATUS_INVALID_PARAMETER); + + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + 0x80000000, + 32, + NULL, + 1, + ProfileTime, + 1); + ok_hex(Status, STATUS_INVALID_PARAMETER); + + /* But 2 and 31 are valid */ + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + 0x80000000, + 2, + NULL, + 1, + ProfileTime, + 1); + ok_hex(Status, STATUS_BUFFER_TOO_SMALL); + + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + 0x80000000, + 31, + NULL, + 1, + ProfileTime, + 1); + ok_hex(Status, STATUS_BUFFER_TOO_SMALL); + } + + /* RangeSize validation has its own function */ + + /* RangeBase+RangeSize can overflow into kernel space, but can't wrap around. + * Note that a Wow64 test will never achieve overflow. + */ + { + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + SIZE_MAX / 2, + 31, + NULL, + 0x80000000, + ProfileTime, + 1); + ok_hex(Status, STATUS_ACCESS_VIOLATION); + + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + SIZE_MAX - 0x10000, + 31, + NULL, + 0x80000000, + ProfileTime, + 1); + ok_hex(Status, STATUS_ACCESS_VIOLATION); + + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + SIZE_MAX - 0x10000 + 1, + 31, + NULL, + 0x80000000, + ProfileTime, + 1); + ok_hex(Status, IsWow64 ? STATUS_ACCESS_VIOLATION : STATUS_BUFFER_OVERFLOW); + + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + SIZE_MAX, + 31, + NULL, + 0x80000000, + ProfileTime, + 1); + ok_hex(Status, IsWow64 ? STATUS_ACCESS_VIOLATION : STATUS_BUFFER_OVERFLOW); + } + + /* Handle is probed first and requires no alignment, buffer requires ULONG alignment */ + { + ULONG Buffer[1]; + + Status = NtCreateProfile((PHANDLE)(ULONG_PTR)1, + (HANDLE)(ULONG_PTR)1, + (PVOID)(ULONG_PTR)0x10002, + 0x1000, + 31, + (PVOID)(ULONG_PTR)2, + sizeof(ULONG), + ProfileTime, + 1); + ok_hex(Status, STATUS_ACCESS_VIOLATION); + + Status = NtCreateProfile(&ProfileHandle, + (HANDLE)(ULONG_PTR)1, + (PVOID)(ULONG_PTR)0x10000, + 0x1000, + 31, + (PVOID)(ULONG_PTR)2, + sizeof(ULONG), + ProfileTime, + 1); + ok_hex(Status, STATUS_DATATYPE_MISALIGNMENT); + + Status = NtCreateProfile(&ProfileHandle, + (HANDLE)(ULONG_PTR)1, + (PVOID)(ULONG_PTR)0x10000, + 0x1000, + 31, + (PVOID)(ULONG_PTR)4, + sizeof(ULONG), + ProfileTime, + 1); + ok_hex(Status, STATUS_ACCESS_VIOLATION); + + Status = NtCreateProfile(&ProfileHandle, + (HANDLE)(ULONG_PTR)1, + (PVOID)(ULONG_PTR)0x10000, + 0x1000, + 31, + Buffer, + sizeof(ULONG), + ProfileTime, + 1); + ok_hex(Status, STATUS_INVALID_HANDLE); + } +} + +/* There are bugs in this validation all the way through early Win10. + * Therefore we test this more thoroughly. + * See https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/profile/bugdemo.htm + */ +static +void +TestBufferSizeValidation(void) +{ + static const struct + { + INT Line; + SIZE_T RangeSize; + ULONG BucketSize; + ULONG BufferSize; + NTSTATUS ExpectedStatus; + NTSTATUS BrokenStatus; + } Tests[] = + { + /* RangeSize=(1 << BucketSize) means we'll need exactly one ULONG */ + { __LINE__, 0x4, 2, sizeof(ULONG) - 1, STATUS_BUFFER_TOO_SMALL }, + { __LINE__, 0x4, 2, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 0x8, 3, sizeof(ULONG) - 1, STATUS_BUFFER_TOO_SMALL }, + { __LINE__, 0x8, 3, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 0x400, 10, sizeof(ULONG) - 1, STATUS_BUFFER_TOO_SMALL }, + { __LINE__, 0x400, 10, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 0x40000000, 30, sizeof(ULONG) - 1, STATUS_BUFFER_TOO_SMALL }, + { __LINE__, 0x40000000, 30, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 0x80000000, 31, sizeof(ULONG) - 1, STATUS_BUFFER_TOO_SMALL }, + { __LINE__, 0x80000000, 31, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + + /* RangeSize<(1 << BucketSize) also means we'll need one ULONG. + * However, old Windows versions get this wrong. + */ + { __LINE__, 3, 2, sizeof(ULONG) - 1, STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 3, 2, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 1, 2, sizeof(ULONG) - 1, STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + + /* Various sizes to show that the bug allows buffers that are a quarter of a bucket too big. */ + { __LINE__, 8, 3, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 9, 3, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 10, 3, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL }, + + { __LINE__, 16, 4, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 17, 4, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 18, 4, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 19, 4, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 20, 4, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL }, + + { __LINE__, 32, 5, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 33, 5, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 39, 5, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 40, 5, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL }, + + { __LINE__, 256, 8, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 257, 8, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 319, 8, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 320, 8, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL }, + + { __LINE__, 256, 8, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 257, 8, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 319, 8, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 320, 8, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL }, + + { __LINE__, 0x80000000, 31, sizeof(ULONG), STATUS_ACCESS_VIOLATION }, + { __LINE__, 0x80000001, 31, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 0xBFFFFFFF, 31, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL, STATUS_ACCESS_VIOLATION }, + { __LINE__, 0xA0000000, 31, sizeof(ULONG), STATUS_BUFFER_TOO_SMALL }, + + /* Nothing checks against the max MDL size */ + { __LINE__, 3, 2, MAX_MDL_BUFFER_SIZE, STATUS_ACCESS_VIOLATION }, + { __LINE__, 3, 2, MAX_MDL_BUFFER_SIZE + 1, STATUS_ACCESS_VIOLATION }, + { __LINE__, 3, 2, (MAX_MDL_BUFFER_SIZE + 1) * 2, STATUS_ACCESS_VIOLATION }, + + }; + NTSTATUS Status; + ULONG i; + + for (i = 0; i < RTL_NUMBER_OF(Tests); i++) + { + Status = NtCreateProfile(NULL, + NULL, + (PVOID)(ULONG_PTR)0x10000, + Tests[i].RangeSize, + Tests[i].BucketSize, + NULL, + Tests[i].BufferSize, + ProfileTime, + 1); + if (Tests[i].BrokenStatus) + { + ok(Status == Tests[i].ExpectedStatus || + broken(Status == Tests[i].BrokenStatus), + "[L%d] For RangeSize 0x%Ix, BucketSize %lu, BufferSize %lu, expected 0x%lx, got 0x%lx\n", + Tests[i].Line, Tests[i].RangeSize, Tests[i].BucketSize, Tests[i].BufferSize, Tests[i].ExpectedStatus, Status); + } + else + { + ok(Status == Tests[i].ExpectedStatus, + "[L%d] For RangeSize 0x%Ix, BucketSize %lu, BufferSize %lu, expected 0x%lx, got 0x%lx\n", + Tests[i].Line, Tests[i].RangeSize, Tests[i].BucketSize, Tests[i].BufferSize, Tests[i].ExpectedStatus, Status); + } + } +} + +START_TEST(NtCreateProfile) +{ + IsWow64Process(GetCurrentProcess(), &IsWow64); + TestParameterValidation(); + TestBufferSizeValidation(); +} diff --git a/modules/rostests/apitests/ntdll/NtStartProfile.c b/modules/rostests/apitests/ntdll/NtStartProfile.c new file mode 100644 index 00000000000..56f66eedd7d --- /dev/null +++ b/modules/rostests/apitests/ntdll/NtStartProfile.c @@ -0,0 +1,143 @@ +/* + * PROJECT: ReactOS API Tests + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Test for NtStartProfile + * COPYRIGHT: Copyright 2021 Thomas Faber (thomas.faber@reactos.org) + */ + +#include "precomp.h" +#include + +#define SIZEOF_MDL (5 * sizeof(PVOID) + 2 * sizeof(ULONG)) +typedef ULONG_PTR PFN_NUMBER; +/* Maximum size that can be described by an MDL on 2003 and earlier */ +#define MAX_MDL_BUFFER_SIZE ((MAXUSHORT - SIZEOF_MDL) / sizeof(PFN_NUMBER) * PAGE_SIZE + PAGE_SIZE - 1) + +static BOOL IsWow64; +static KAFFINITY SystemAffinityMask; +static ULONG DummyBuffer[4096]; + +/* The "Buffer[Offset]++;" should likely be within 128 bytes of the start + * of the function on any architecture we support + */ +#define LOOP_FUNCTION_SIZE_SHIFT 7 +#define LOOP_FUNCTION_SIZE (1UL << LOOP_FUNCTION_SIZE_SHIFT) +C_ASSERT(LOOP_FUNCTION_SIZE == 128); +typedef void LOOP_FUNCTION(volatile ULONG *, ULONG, ULONG); +static +void +LoopFunction( + _Inout_updates_all_(BufferSize) volatile ULONG *Buffer, + _In_ ULONG BufferSizeInElements, + _In_ ULONG LoopCount) +{ + ULONG i; + ULONG Offset; + + for (i = 0; i < LoopCount; i++) + { + for (Offset = 0; Offset < BufferSizeInElements; Offset++) + { + Buffer[Offset]++; + } + } +} + +static +void +ProfileLoopFunction( + _In_ LOOP_FUNCTION *Function, + _Out_writes_bytes_(BufferSize) PULONG Buffer, + _In_ ULONG BufferSize, + _In_range_(0, BufferSize / sizeof(ULONG)) ULONG BufferOffset + ) +{ + NTSTATUS Status; + HANDLE ProfileHandle; + ULONG Buffer1Value; + + Status = NtCreateProfile(&ProfileHandle, + NtCurrentProcess(), + (PVOID)((ULONG_PTR)Function - LOOP_FUNCTION_SIZE), + 3 * LOOP_FUNCTION_SIZE, + LOOP_FUNCTION_SIZE_SHIFT, + Buffer, + BufferSize, + ProfileTime, + SystemAffinityMask); + ok_hex(Status, STATUS_SUCCESS); + if (!NT_SUCCESS(Status)) + { + skip("Failed to create profile\n"); + return; + } + + Status = NtStartProfile(ProfileHandle); + ok_hex(Status, STATUS_SUCCESS); + + /* Can't validate Buffer contents here, since we don't know what's next to our function */ + + /* + * This takes around 10-12 seconds on my machine, which is not ideal. + * But on my Win2003 VM it only results in counts of 10-12, + * which means we can't really make it shorter. + */ + + /* Run a long loop */ + Function(DummyBuffer, + RTL_NUMBER_OF(DummyBuffer), + 1000000); + + /* The buffer should get live updates */ + Buffer1Value = Buffer[BufferOffset]; + ok(Buffer1Value != 0, "Buffer[%lu] = %lu\n", BufferOffset, Buffer1Value); + + /* Run a shorter loop, we should see a smaller increase */ + Function(DummyBuffer, + RTL_NUMBER_OF(DummyBuffer), + 200000); + + ok(Buffer[BufferOffset] > Buffer1Value, + "Buffer[%lu] = %lu, expected more than %lu\n", + BufferOffset, Buffer[BufferOffset], Buffer1Value); + + Status = NtStopProfile(ProfileHandle); + ok_hex(Status, STATUS_SUCCESS); + + /* The expectation is that Buffer[BufferOffset] is somewhere around 20% larger than Buffer1Value. + * Allow anywhere from one more count to twice as many to make the test robust. + */ + ok(Buffer[BufferOffset] > Buffer1Value, + "Buffer[%lu] = %lu, expected more than %lu\n", BufferOffset, Buffer[BufferOffset], Buffer1Value); + ok(Buffer[BufferOffset] < 2 * Buffer1Value, + "Buffer[%lu] = %lu, expected less than %lu\n", BufferOffset, Buffer[BufferOffset], 2 * Buffer1Value); + + trace("Buffer1Value = %lu\n", Buffer1Value); + trace("Buffer[%lu] = %lu\n", BufferOffset - 1, Buffer[BufferOffset - 1]); + trace("Buffer[%lu] = %lu\n", BufferOffset, Buffer[BufferOffset]); + trace("Buffer[%lu] = %lu\n", BufferOffset + 1, Buffer[BufferOffset + 1]); + + Status = NtClose(ProfileHandle); + ok_hex(Status, STATUS_SUCCESS); +} + +START_TEST(NtStartProfile) +{ + NTSTATUS Status; + ULONG StackBuffer[3] = { 0 }; + DWORD_PTR ProcessAffinityMask; + + IsWow64Process(GetCurrentProcess(), &IsWow64); + + GetProcessAffinityMask(GetCurrentProcess(), &ProcessAffinityMask, &SystemAffinityMask); + + /* Parameter validation is pretty simple... */ + Status = NtStartProfile(NULL); + ok_hex(Status, STATUS_INVALID_HANDLE); + + /* Do an actual simple profile */ + ProfileLoopFunction(LoopFunction, + StackBuffer, + sizeof(StackBuffer), + 1); +} diff --git a/modules/rostests/apitests/ntdll/testlist.c b/modules/rostests/apitests/ntdll/testlist.c index c4da00ff0c0..acf25984ea4 100644 --- a/modules/rostests/apitests/ntdll/testlist.c +++ b/modules/rostests/apitests/ntdll/testlist.c @@ -19,6 +19,7 @@ extern void func_NtCompareTokens(void); extern void func_NtContinue(void); extern void func_NtCreateFile(void); extern void func_NtCreateKey(void); +extern void func_NtCreateProfile(void); extern void func_NtCreateThread(void); extern void func_NtDeleteKey(void); extern void func_NtDuplicateObject(void); @@ -53,6 +54,7 @@ extern void func_NtSetInformationThread(void); extern void func_NtSetInformationToken(void); extern void func_NtSetValueKey(void); extern void func_NtSetVolumeInformationFile(void); +extern void func_NtStartProfile(void); extern void func_NtSystemInformation(void); extern void func_NtUnloadDriver(void); extern void func_NtWriteFile(void); @@ -124,6 +126,7 @@ const struct test winetest_testlist[] = { "NtContinue", func_NtContinue }, { "NtCreateFile", func_NtCreateFile }, { "NtCreateKey", func_NtCreateKey }, + { "NtCreateProfile", func_NtCreateProfile }, { "NtCreateThread", func_NtCreateThread }, { "NtDeleteKey", func_NtDeleteKey }, { "NtDuplicateObject", func_NtDuplicateObject }, @@ -158,6 +161,7 @@ const struct test winetest_testlist[] = { "NtSetInformationToken", func_NtSetInformationToken }, { "NtSetValueKey", func_NtSetValueKey}, { "NtSetVolumeInformationFile", func_NtSetVolumeInformationFile }, + { "NtStartProfile", func_NtStartProfile }, { "NtSystemInformation", func_NtSystemInformation }, { "NtUnloadDriver", func_NtUnloadDriver }, { "NtWriteFile", func_NtWriteFile },