1 /* 2 * PROJECT: ReactOS kernel-mode tests 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Kernel-Mode Test Suite for NtSystemDebugControl (user-mode) 5 * COPYRIGHT: Copyright 2024 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org> 6 */ 7 8 #include <kmt_test.h> 9 #include <ndk/exfuncs.h> 10 #include <ndk/kdfuncs.h> 11 #include <ndk/setypes.h> 12 13 #define ok_eq_print_test(testid, value, expected, spec) \ 14 ok((value) == (expected), "In test %lu: " #value " = " spec ", expected " spec "\n", testid, value, expected) 15 16 #define ok_eq_hex_test(testid, value, expected) \ 17 ok_eq_print_test(testid, value, expected, "0x%08lx") 18 19 #define ok_neq_print_test(testid, value, expected, spec) \ 20 ok((value) != (expected), "In test %lu: " #value " = " spec ", expected != " spec "\n", testid, value, expected) 21 22 #define ok_neq_hex_test(testid, value, expected) \ 23 ok_neq_print_test(testid, value, expected, "0x%08lx") 24 25 ULONG 26 GetNtDdiVersion(VOID) 27 { 28 RTL_OSVERSIONINFOEXW verInfo; 29 NTSTATUS Status; 30 ULONG Version; 31 32 verInfo.dwOSVersionInfoSize = sizeof(verInfo); 33 Status = RtlGetVersion((PRTL_OSVERSIONINFOW)&verInfo); 34 if (!NT_SUCCESS(Status)) 35 { 36 trace("RtlGetVersion() returned 0x%08lx\n", Status); 37 return 0; 38 } 39 40 Version = ((((verInfo.dwMajorVersion & 0xFF) << 8) | 41 (verInfo.dwMinorVersion & 0xFF)) << 16) | 42 (((verInfo.wServicePackMajor & 0xFF) << 8) | 43 (verInfo.wServicePackMinor & 0xFF)); 44 45 return Version; 46 } 47 48 static 49 NTSTATUS 50 TestSystemDebugControl( 51 _In_ SYSDBG_COMMAND Command) 52 { 53 return NtSystemDebugControl(Command, 54 NULL, // _In_ PVOID InputBuffer, 55 0, // _In_ ULONG InputBufferLength, 56 NULL, // _Out_ PVOID OutputBuffer, 57 0, // _In_ ULONG OutputBufferLength, 58 NULL); 59 } 60 61 START_TEST(NtSystemDebugControl) 62 { 63 NTSTATUS Status; 64 ULONG Command; 65 ULONG Version; 66 SYSTEM_KERNEL_DEBUGGER_INFORMATION DebuggerInfo = {0}; 67 BOOLEAN IsNT52SP1OrHigher; 68 BOOLEAN IsVistaOrHigher; 69 BOOLEAN IsDebuggerActive; 70 BOOLEAN PrivilegeSet[2] = {FALSE}; 71 BOOLEAN WasDebuggerEnabled; 72 73 /* Test for OS version: KdSystemDebugControl() 74 * exists only on NT 5.2 SP1 and higher */ 75 Version = GetNtDdiVersion(); 76 if (skip(Version != 0, "GetNtDdiVersion() returned 0\n")) 77 return; 78 79 // IsWindowsVersionOrGreater(5, 2, 1); 80 IsNT52SP1OrHigher = (Version >= NTDDI_WS03SP1); 81 82 // IsWindowsVersionOrGreater(6, 0, 0); 83 IsVistaOrHigher = (Version >= NTDDI_WIN6); 84 85 86 /* Check whether the kernel debugger is present or not */ 87 Status = NtQuerySystemInformation(SystemKernelDebuggerInformation, 88 &DebuggerInfo, 89 sizeof(DebuggerInfo), 90 NULL); 91 92 IsDebuggerActive = NT_SUCCESS(Status) && !DebuggerInfo.KernelDebuggerNotPresent; 93 // DebuggerInfo.KernelDebuggerEnabled; // SharedUserData->KdDebuggerEnabled; 94 95 trace("Debugger is %s\n", IsDebuggerActive ? "active" : "inactive"); 96 97 /* 98 * Explicitly disable the debug privilege so that we can test 99 * that NtSystemDebugControl() fails when the privilege is absent. 100 * Note that SysDbgGetTriageDump (29) is used for testing here, 101 * because it doesn't require a debugger to be active in order 102 * to proceed further (privilege check and actual functionality). 103 */ 104 Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, FALSE, FALSE, &PrivilegeSet[0]); 105 ok_eq_hex(Status, STATUS_SUCCESS); 106 Status = TestSystemDebugControl(SysDbgGetTriageDump /* 29 */); 107 ok_eq_hex(Status, STATUS_ACCESS_DENIED); 108 109 /* Now, enable the debug privilege for the rest of the tests */ 110 Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &PrivilegeSet[1]); 111 ok_eq_hex(Status, STATUS_SUCCESS); 112 113 /* Supported commands */ 114 for (Command = 0; Command <= 5; ++Command) 115 { 116 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 117 if (!IsVistaOrHigher || IsDebuggerActive) 118 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 119 else 120 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 121 } 122 123 /* Test SysDbgBreakPoint (6) only when the debugger is inactive 124 * or disabled; otherwise this call would trigger a breakpoint */ 125 if (!skip((IsVistaOrHigher && !IsDebuggerActive) || !SharedUserData->KdDebuggerEnabled, 126 "NtSystemDebugControl(SysDbgBreakPoint) skipped because the debugger is active\n")) 127 { 128 Status = TestSystemDebugControl(SysDbgBreakPoint /* 6 */); 129 if (!SharedUserData->KdDebuggerEnabled /*&& (!IsVistaOrHigher || IsDebuggerActive)*/) 130 { 131 ok_eq_hex_test(Command, Status, STATUS_UNSUCCESSFUL); 132 } 133 else 134 { 135 // ASSERT(IsVistaOrHigher && !IsDebuggerActive); 136 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 137 } 138 } 139 140 /* 141 * Commands handled by kernel-mode KdSystemDebugControl(), 142 * and unsupported in user-mode: 143 * 144 * SysDbgQueryVersion = 7, 145 * SysDbgReadVirtual = 8, 146 * SysDbgWriteVirtual = 9, 147 * SysDbgReadPhysical = 10, 148 * SysDbgWritePhysical = 11, 149 * SysDbgReadControlSpace = 12, 150 * SysDbgWriteControlSpace = 13, 151 * SysDbgReadIoSpace = 14, 152 * SysDbgWriteIoSpace = 15, 153 * SysDbgReadMsr = 16, 154 * SysDbgWriteMsr = 17, 155 * SysDbgReadBusData = 18, 156 * SysDbgWriteBusData = 19, 157 * SysDbgCheckLowMemory = 20 158 */ 159 // TODO: Handle this differently if !IsNT52SP1OrHigher ? 160 DBG_UNREFERENCED_PARAMETER(IsNT52SP1OrHigher); 161 for (Command = 7; Command <= 20; ++Command) 162 { 163 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 164 if (!IsVistaOrHigher || IsDebuggerActive) 165 ok_eq_hex_test(Command, Status, STATUS_NOT_IMPLEMENTED); 166 else 167 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 168 } 169 170 171 /* 172 * Separately test commands SysDbgEnableKernelDebugger (21) 173 * and SysDbgDisableKernelDebugger (22), as they influence 174 * the internal state of the debugger. The order of testing 175 * matters, depending on whether the debugger was originally 176 * enabled or disabled. 177 */ 178 179 /* Save whether the debugger is currently enabled; 180 * the next tests are going to change its state */ 181 WasDebuggerEnabled = SharedUserData->KdDebuggerEnabled; 182 183 // 184 // FIXME: Re-enable ONCE our KDBG and KDCOM dlls support disabling and re-enabling. 185 // 186 DBG_UNREFERENCED_LOCAL_VARIABLE(WasDebuggerEnabled); 187 #if 0 188 /* Try to disable or enable the debugger, depending on its original state */ 189 if (WasDebuggerEnabled) 190 Command = SysDbgDisableKernelDebugger; // 22 191 else 192 Command = SysDbgEnableKernelDebugger; // 21 193 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 194 if (!IsVistaOrHigher || IsDebuggerActive) 195 { 196 /* 197 * KdEnableDebugger() (with lock enabled) wants a KdDisableDebugger() 198 * first (i.e. that the debugger was previously explicitly disabled) 199 * in order to return success; otherwise it'll return STATUS_INVALID_PARAMETER. 200 */ 201 if (Command == SysDbgEnableKernelDebugger) 202 { 203 ok(Status == STATUS_SUCCESS || Status == STATUS_INVALID_PARAMETER, 204 "In test %lu: Status = 0x%08lx, expected 0x%08lx or 0x%08lx\n", 205 Command, Status, STATUS_SUCCESS, STATUS_INVALID_PARAMETER); 206 } 207 else 208 { 209 ok_eq_hex_test(Command, Status, STATUS_SUCCESS); 210 } 211 } 212 else 213 { 214 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 215 } 216 217 /* Re-enable or disable the debugger, depending on its original state */ 218 if (WasDebuggerEnabled) 219 Command = SysDbgEnableKernelDebugger; // 21 220 else 221 Command = SysDbgDisableKernelDebugger; // 22 222 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 223 if (!IsVistaOrHigher || IsDebuggerActive) 224 ok_eq_hex_test(Command, Status, STATUS_SUCCESS); 225 else 226 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 227 #endif 228 // END FIXME 229 230 231 /* Supported commands */ 232 for (Command = 23; Command <= 31; ++Command) 233 { 234 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 235 if (!IsVistaOrHigher || IsDebuggerActive) 236 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 237 else 238 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 239 } 240 241 /* These are Vista+ and depend on the OS version */ 242 for (Command = 32; Command <= 36; ++Command) 243 { 244 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 245 if (!IsVistaOrHigher || IsDebuggerActive) 246 { 247 if (Version >= NTDDI_WIN6) // IsVistaOrHigher 248 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 249 else 250 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 251 } 252 else 253 { 254 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 255 } 256 } 257 258 Command = 37; // SysDbgGetLiveKernelDump 259 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 260 if (!IsVistaOrHigher || IsDebuggerActive) 261 { 262 if (Version >= NTDDI_WINBLUE) 263 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 264 else 265 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 266 } 267 else 268 { 269 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 270 } 271 272 Command = 38; // SysDbgKdPullRemoteFile 273 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 274 if (!IsVistaOrHigher || IsDebuggerActive) 275 { 276 if (Version >= NTDDI_WIN10_VB) 277 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 278 else 279 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 280 } 281 else 282 { 283 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 284 } 285 286 /* Unsupported commands */ 287 for (Command = 39; Command <= 40; ++Command) 288 { 289 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 290 if (!IsVistaOrHigher || IsDebuggerActive) 291 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 292 else 293 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 294 } 295 296 /* Finally restore the original debug privilege state */ 297 RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, PrivilegeSet[0], FALSE, &PrivilegeSet[0]); 298 } 299 300 /* EOF */ 301