diff --git a/modules/rostests/kmtests/CMakeLists.txt b/modules/rostests/kmtests/CMakeLists.txt index 40c710f0a46..373ffb887c3 100644 --- a/modules/rostests/kmtests/CMakeLists.txt +++ b/modules/rostests/kmtests/CMakeLists.txt @@ -80,6 +80,7 @@ list(APPEND KMTEST_DRV_SOURCE ntos_io/IoIrp.c ntos_io/IoMdl.c ntos_io/IoVolume.c + ntos_kd/KdSystemDebugControl.c ntos_ke/KeApc.c ntos_ke/KeDevQueue.c ntos_ke/KeDpc.c @@ -164,6 +165,7 @@ list(APPEND KMTEST_SOURCE ntos_io/IoCreateFile_user.c ntos_io/IoDeviceObject_user.c ntos_io/IoReadWrite_user.c + ntos_kd/NtSystemDebugControl.c ntos_mm/MmMapLockedPagesSpecifyCache_user.c ntos_mm/NtCreateSection_user.c ntos_po/PoIrp_user.c diff --git a/modules/rostests/kmtests/kmtest/testlist.c b/modules/rostests/kmtests/kmtest/testlist.c index 60ab4bb4534..34e387c43f4 100644 --- a/modules/rostests/kmtests/kmtest/testlist.c +++ b/modules/rostests/kmtests/kmtest/testlist.c @@ -23,6 +23,7 @@ KMT_TESTFUNC Test_IoDeviceObject; KMT_TESTFUNC Test_IoReadWrite; KMT_TESTFUNC Test_MmMapLockedPagesSpecifyCache; KMT_TESTFUNC Test_NtCreateSection; +KMT_TESTFUNC Test_NtSystemDebugControl; KMT_TESTFUNC Test_PoIrp; KMT_TESTFUNC Test_RtlAvlTree; KMT_TESTFUNC Test_RtlCaptureContext; @@ -58,6 +59,7 @@ const KMT_TEST TestList[] = { "IoReadWrite", Test_IoReadWrite }, { "MmMapLockedPagesSpecifyCache", Test_MmMapLockedPagesSpecifyCache }, { "NtCreateSection", Test_NtCreateSection }, + { "NtSystemDebugControl", Test_NtSystemDebugControl }, { "PoIrp", Test_PoIrp }, { "RtlAvlTree", Test_RtlAvlTree }, { "RtlException", Test_RtlException }, diff --git a/modules/rostests/kmtests/kmtest_drv/testlist.c b/modules/rostests/kmtests/kmtest_drv/testlist.c index 9ef992257ee..b863bcf7b04 100644 --- a/modules/rostests/kmtests/kmtest_drv/testlist.c +++ b/modules/rostests/kmtests/kmtest_drv/testlist.c @@ -35,6 +35,7 @@ KMT_TESTFUNC Test_IoInterrupt; KMT_TESTFUNC Test_IoIrp; KMT_TESTFUNC Test_IoMdl; KMT_TESTFUNC Test_IoVolume; +KMT_TESTFUNC Test_KdSystemDebugControl; KMT_TESTFUNC Test_KeApc; KMT_TESTFUNC Test_KeDeviceQueue; KMT_TESTFUNC Test_KeDpc; @@ -118,6 +119,7 @@ const KMT_TEST TestList[] = { "IoIrp", Test_IoIrp }, { "IoMdl", Test_IoMdl }, { "IoVolume", Test_IoVolume }, + { "KdSystemDebugControl", Test_KdSystemDebugControl }, { "KeApc", Test_KeApc }, { "KeDeviceQueue", Test_KeDeviceQueue }, { "KeDpc", Test_KeDpc }, diff --git a/modules/rostests/kmtests/ntos_kd/KdSystemDebugControl.c b/modules/rostests/kmtests/ntos_kd/KdSystemDebugControl.c new file mode 100644 index 00000000000..a4da12f2224 --- /dev/null +++ b/modules/rostests/kmtests/ntos_kd/KdSystemDebugControl.c @@ -0,0 +1,151 @@ +/* + * PROJECT: ReactOS kernel-mode tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Kernel-Mode Test Suite for KdSystemDebugControl (kernel-mode) + * COPYRIGHT: Copyright 2024 Hermès Bélusca-Maïto + */ + +#include +#include + +#define ok_eq_print_test(testid, value, expected, spec) \ + ok((value) == (expected), "In test %lu: " #value " = " spec ", expected " spec "\n", testid, value, expected) + +#define ok_eq_hex_test(testid, value, expected) \ + ok_eq_print_test(testid, value, expected, "0x%08lx") + +#define ok_neq_print_test(testid, value, expected, spec) \ + ok((value) != (expected), "In test %lu: " #value " = " spec ", expected != " spec "\n", testid, value, expected) + +#define ok_neq_hex_test(testid, value, expected) \ + ok_neq_print_test(testid, value, expected, "0x%08lx") + + +BOOLEAN +(NTAPI *pKdRefreshDebuggerNotPresent)(VOID); + +NTSTATUS +(NTAPI *pKdSystemDebugControl)( + _In_ SYSDBG_COMMAND Command, + _In_ PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_ PVOID OutputBuffer, + _In_ ULONG OutputBufferLength, + _Inout_ PULONG ReturnLength, + _In_ KPROCESSOR_MODE PreviousMode); + + +static +NTSTATUS +TestSystemDebugControl( + _In_ SYSDBG_COMMAND Command) +{ + return pKdSystemDebugControl(Command, + NULL, // _In_ PVOID InputBuffer, + 0, // _In_ ULONG InputBufferLength, + NULL, // _Out_ PVOID OutputBuffer, + 0, // _In_ ULONG OutputBufferLength, + NULL, + KernelMode); +} + +START_TEST(KdSystemDebugControl) +{ + NTSTATUS Status; + ULONG Command; + RTL_OSVERSIONINFOEXW verInfo; + UNICODE_STRING fnName; + BOOLEAN IsNT52SP1OrHigher; + BOOLEAN IsVistaOrHigher; + BOOLEAN IsDebuggerActive; + + /* Test for OS version: KdSystemDebugControl() + * exists only on NT 5.2 SP1 and higher */ + verInfo.dwOSVersionInfoSize = sizeof(verInfo); + Status = RtlGetVersion((PRTL_OSVERSIONINFOW)&verInfo); + if (skip(NT_SUCCESS(Status), "RtlGetVersion() returned 0x%08lx\n", Status)) + return; + + // IsWindowsVersionOrGreater(5, 2, 1); + IsNT52SP1OrHigher = + (verInfo.dwMajorVersion > 5) || + (verInfo.dwMajorVersion == 5 && verInfo.dwMinorVersion > 2) || + (verInfo.dwMajorVersion == 5 && verInfo.dwMinorVersion == 2 && verInfo.wServicePackMajor >= 1); + + if (skip(IsNT52SP1OrHigher, "KdSystemDebugControl() only exists on NT 5.2 SP1 and higher\n")) + return; + + // IsWindowsVersionOrGreater(6, 0, 0); + IsVistaOrHigher = (verInfo.dwMajorVersion >= 6); + + + /* Load the Kd routines at runtime */ + + /* Note: KdRefreshDebuggerNotPresent() is NT 5.2+ */ + RtlInitUnicodeString(&fnName, L"KdRefreshDebuggerNotPresent"); + pKdRefreshDebuggerNotPresent = MmGetSystemRoutineAddress(&fnName); + ok(!!pKdRefreshDebuggerNotPresent, + "KdRefreshDebuggerNotPresent() unavailable but OS is NT 5.2 SP1 or higher?\n"); + + RtlInitUnicodeString(&fnName, L"KdSystemDebugControl"); + pKdSystemDebugControl = MmGetSystemRoutineAddress(&fnName); + if (skip(!!pKdSystemDebugControl, "KdSystemDebugControl() unavailable but OS is NT 5.2 SP1 or higher?\n")) + return; + + + /* Check whether the kernel debugger is present or not */ + IsDebuggerActive = (pKdRefreshDebuggerNotPresent + ? !pKdRefreshDebuggerNotPresent() + : (/*KD_DEBUGGER_ENABLED &&*/ !KD_DEBUGGER_NOT_PRESENT)); + + trace("Debugger is %s\n", IsDebuggerActive ? "active" : "inactive"); + + /* Unsupported commands */ + for (Command = 0; Command <= 6; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + /* + * Supported commands: + * + * SysDbgQueryVersion = 7, + * SysDbgReadVirtual = 8, + * SysDbgWriteVirtual = 9, + * SysDbgReadPhysical = 10, + * SysDbgWritePhysical = 11, + * SysDbgReadControlSpace = 12, + * SysDbgWriteControlSpace = 13, + * SysDbgReadIoSpace = 14, + * SysDbgWriteIoSpace = 15, + * SysDbgReadMsr = 16, + * SysDbgWriteMsr = 17, + * SysDbgReadBusData = 18, + * SysDbgWriteBusData = 19, + * SysDbgCheckLowMemory = 20 + */ + for (Command = 7; Command <= 20; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); // Status must be != STATUS_INVALID_INFO_CLASS + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + /* Unsupported commands */ + for (Command = 21; Command <= 40; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } +} + +/* EOF */ diff --git a/modules/rostests/kmtests/ntos_kd/NtSystemDebugControl.c b/modules/rostests/kmtests/ntos_kd/NtSystemDebugControl.c new file mode 100644 index 00000000000..021fc6b0fcf --- /dev/null +++ b/modules/rostests/kmtests/ntos_kd/NtSystemDebugControl.c @@ -0,0 +1,293 @@ +/* + * PROJECT: ReactOS kernel-mode tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Kernel-Mode Test Suite for NtSystemDebugControl (user-mode) + * COPYRIGHT: Copyright 2024 Hermès Bélusca-Maïto + */ + +#include +#include +#include +#include + +#define ok_eq_print_test(testid, value, expected, spec) \ + ok((value) == (expected), "In test %lu: " #value " = " spec ", expected " spec "\n", testid, value, expected) + +#define ok_eq_hex_test(testid, value, expected) \ + ok_eq_print_test(testid, value, expected, "0x%08lx") + +#define ok_neq_print_test(testid, value, expected, spec) \ + ok((value) != (expected), "In test %lu: " #value " = " spec ", expected != " spec "\n", testid, value, expected) + +#define ok_neq_hex_test(testid, value, expected) \ + ok_neq_print_test(testid, value, expected, "0x%08lx") + +ULONG +GetNtDdiVersion(VOID) +{ + RTL_OSVERSIONINFOEXW verInfo; + NTSTATUS Status; + ULONG Version; + + verInfo.dwOSVersionInfoSize = sizeof(verInfo); + Status = RtlGetVersion((PRTL_OSVERSIONINFOW)&verInfo); + if (!NT_SUCCESS(Status)) + { + trace("RtlGetVersion() returned 0x%08lx\n", Status); + return 0; + } + + Version = ((((verInfo.dwMajorVersion & 0xFF) << 8) | + (verInfo.dwMinorVersion & 0xFF)) << 16) | + (((verInfo.wServicePackMajor & 0xFF) << 8) | + (verInfo.wServicePackMinor & 0xFF)); + + return Version; +} + +static +NTSTATUS +TestSystemDebugControl( + _In_ SYSDBG_COMMAND Command) +{ + return NtSystemDebugControl(Command, + NULL, // _In_ PVOID InputBuffer, + 0, // _In_ ULONG InputBufferLength, + NULL, // _Out_ PVOID OutputBuffer, + 0, // _In_ ULONG OutputBufferLength, + NULL); +} + +START_TEST(NtSystemDebugControl) +{ + NTSTATUS Status; + ULONG Command; + ULONG Version; + SYSTEM_KERNEL_DEBUGGER_INFORMATION DebuggerInfo = {0}; + BOOLEAN IsNT52SP1OrHigher; + BOOLEAN IsVistaOrHigher; + BOOLEAN IsDebuggerActive; + BOOLEAN PrivilegeSet[2] = {FALSE}; + BOOLEAN WasDebuggerEnabled; + + /* Test for OS version: KdSystemDebugControl() + * exists only on NT 5.2 SP1 and higher */ + Version = GetNtDdiVersion(); + if (skip(Version != 0, "GetNtDdiVersion() returned 0\n")) + return; + + // IsWindowsVersionOrGreater(5, 2, 1); + IsNT52SP1OrHigher = (Version >= NTDDI_WS03SP1); + + // IsWindowsVersionOrGreater(6, 0, 0); + IsVistaOrHigher = (Version >= NTDDI_WIN6); + + + /* Check whether the kernel debugger is present or not */ + Status = NtQuerySystemInformation(SystemKernelDebuggerInformation, + &DebuggerInfo, + sizeof(DebuggerInfo), + NULL); + + IsDebuggerActive = NT_SUCCESS(Status) && !DebuggerInfo.KernelDebuggerNotPresent; + // DebuggerInfo.KernelDebuggerEnabled; // SharedUserData->KdDebuggerEnabled; + + trace("Debugger is %s\n", IsDebuggerActive ? "active" : "inactive"); + + /* + * Explicitly disable the debug privilege so that we can test + * that NtSystemDebugControl() fails when the privilege is absent. + * Note that SysDbgGetTriageDump (29) is used for testing here, + * because it doesn't require a debugger to be active in order + * to proceed further (privilege check and actual functionality). + */ + Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, FALSE, FALSE, &PrivilegeSet[0]); + ok_eq_hex(Status, STATUS_SUCCESS); + Status = TestSystemDebugControl(SysDbgGetTriageDump /* 29 */); + ok_eq_hex(Status, STATUS_ACCESS_DENIED); + + /* Now, enable the debug privilege for the rest of the tests */ + Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &PrivilegeSet[1]); + ok_eq_hex(Status, STATUS_SUCCESS); + + /* Supported commands */ + for (Command = 0; Command <= 5; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + /* Test SysDbgBreakPoint (6) only when the debugger is inactive + * or disabled; otherwise this call would trigger a breakpoint */ + if (!skip((IsVistaOrHigher && !IsDebuggerActive) || !SharedUserData->KdDebuggerEnabled, + "NtSystemDebugControl(SysDbgBreakPoint) skipped because the debugger is active\n")) + { + Status = TestSystemDebugControl(SysDbgBreakPoint /* 6 */); + if (!SharedUserData->KdDebuggerEnabled /*&& (!IsVistaOrHigher || IsDebuggerActive)*/) + { + ok_eq_hex_test(Command, Status, STATUS_UNSUCCESSFUL); + } + else + { + // ASSERT(IsVistaOrHigher && !IsDebuggerActive); + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + } + + /* + * Commands handled by kernel-mode KdSystemDebugControl(), + * and unsupported in user-mode: + * + * SysDbgQueryVersion = 7, + * SysDbgReadVirtual = 8, + * SysDbgWriteVirtual = 9, + * SysDbgReadPhysical = 10, + * SysDbgWritePhysical = 11, + * SysDbgReadControlSpace = 12, + * SysDbgWriteControlSpace = 13, + * SysDbgReadIoSpace = 14, + * SysDbgWriteIoSpace = 15, + * SysDbgReadMsr = 16, + * SysDbgWriteMsr = 17, + * SysDbgReadBusData = 18, + * SysDbgWriteBusData = 19, + * SysDbgCheckLowMemory = 20 + */ + // TODO: Handle this differently if !IsNT52SP1OrHigher ? + DBG_UNREFERENCED_PARAMETER(IsNT52SP1OrHigher); + for (Command = 7; Command <= 20; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_eq_hex_test(Command, Status, STATUS_NOT_IMPLEMENTED); + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + + /* + * Separately test commands SysDbgEnableKernelDebugger (21) + * and SysDbgDisableKernelDebugger (22), as they influence + * the internal state of the debugger. The order of testing + * matters, depending on whether the debugger was originally + * enabled or disabled. + */ + + /* Save whether the debugger is currently enabled; + * the next tests are going to change its state */ + WasDebuggerEnabled = SharedUserData->KdDebuggerEnabled; + + /* Try to disable or enable the debugger, depending on its original state */ + if (WasDebuggerEnabled) + Command = SysDbgDisableKernelDebugger; // 22 + else + Command = SysDbgEnableKernelDebugger; // 21 + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + { + /* + * KdEnableDebugger() (with lock enabled) wants a KdDisableDebugger() + * first (i.e. that the debugger was previously explicitly disabled) + * in order to return success; otherwise it'll return STATUS_INVALID_PARAMETER. + */ + if (Command == SysDbgEnableKernelDebugger) + { + ok(Status == STATUS_SUCCESS || Status == STATUS_INVALID_PARAMETER, + "In test %lu: Status = 0x%08lx, expected 0x%08lx or 0x%08lx\n", + Command, Status, STATUS_SUCCESS, STATUS_INVALID_PARAMETER); + } + else + { + ok_eq_hex_test(Command, Status, STATUS_SUCCESS); + } + } + else + { + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + /* Re-enable or disable the debugger, depending on its original state */ + if (WasDebuggerEnabled) + Command = SysDbgEnableKernelDebugger; // 21 + else + Command = SysDbgDisableKernelDebugger; // 22 + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_eq_hex_test(Command, Status, STATUS_SUCCESS); + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + + + /* Supported commands */ + for (Command = 23; Command <= 31; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + /* These are Vista+ and depend on the OS version */ + for (Command = 32; Command <= 36; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + { + if (Version >= NTDDI_WIN6) // IsVistaOrHigher + ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + } + else + { + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + } + + Command = 37; // SysDbgGetLiveKernelDump + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + { + if (Version >= NTDDI_WINBLUE) + ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + } + else + { + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + Command = 38; // SysDbgKdPullRemoteFile + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + { + if (Version >= NTDDI_WIN10_VB) + ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + } + else + { + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + /* Unsupported commands */ + for (Command = 39; Command <= 40; ++Command) + { + Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); + if (!IsVistaOrHigher || IsDebuggerActive) + ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); + else + ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); + } + + /* Finally restore the original debug privilege state */ + RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, PrivilegeSet[0], FALSE, &PrivilegeSet[0]); +} + +/* EOF */