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 /* Try to disable or enable the debugger, depending on its original state */ 184 if (WasDebuggerEnabled) 185 Command = SysDbgDisableKernelDebugger; // 22 186 else 187 Command = SysDbgEnableKernelDebugger; // 21 188 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 189 if (!IsVistaOrHigher || IsDebuggerActive) 190 { 191 /* 192 * KdEnableDebugger() (with lock enabled) wants a KdDisableDebugger() 193 * first (i.e. that the debugger was previously explicitly disabled) 194 * in order to return success; otherwise it'll return STATUS_INVALID_PARAMETER. 195 */ 196 if (Command == SysDbgEnableKernelDebugger) 197 { 198 ok(Status == STATUS_SUCCESS || Status == STATUS_INVALID_PARAMETER, 199 "In test %lu: Status = 0x%08lx, expected 0x%08lx or 0x%08lx\n", 200 Command, Status, STATUS_SUCCESS, STATUS_INVALID_PARAMETER); 201 } 202 else 203 { 204 ok_eq_hex_test(Command, Status, STATUS_SUCCESS); 205 } 206 } 207 else 208 { 209 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 210 } 211 212 /* Re-enable or disable the debugger, depending on its original state */ 213 if (WasDebuggerEnabled) 214 Command = SysDbgEnableKernelDebugger; // 21 215 else 216 Command = SysDbgDisableKernelDebugger; // 22 217 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 218 if (!IsVistaOrHigher || IsDebuggerActive) 219 ok_eq_hex_test(Command, Status, STATUS_SUCCESS); 220 else 221 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 222 223 224 /* Supported commands */ 225 for (Command = 23; Command <= 31; ++Command) 226 { 227 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 228 if (!IsVistaOrHigher || IsDebuggerActive) 229 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 230 else 231 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 232 } 233 234 /* These are Vista+ and depend on the OS version */ 235 for (Command = 32; Command <= 36; ++Command) 236 { 237 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 238 if (!IsVistaOrHigher || IsDebuggerActive) 239 { 240 if (Version >= NTDDI_WIN6) // IsVistaOrHigher 241 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 242 else 243 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 244 } 245 else 246 { 247 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 248 } 249 } 250 251 Command = 37; // SysDbgGetLiveKernelDump 252 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 253 if (!IsVistaOrHigher || IsDebuggerActive) 254 { 255 if (Version >= NTDDI_WINBLUE) 256 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 257 else 258 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 259 } 260 else 261 { 262 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 263 } 264 265 Command = 38; // SysDbgKdPullRemoteFile 266 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 267 if (!IsVistaOrHigher || IsDebuggerActive) 268 { 269 if (Version >= NTDDI_WIN10_VB) 270 ok_neq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 271 else 272 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 273 } 274 else 275 { 276 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 277 } 278 279 /* Unsupported commands */ 280 for (Command = 39; Command <= 40; ++Command) 281 { 282 Status = TestSystemDebugControl((SYSDBG_COMMAND)Command); 283 if (!IsVistaOrHigher || IsDebuggerActive) 284 ok_eq_hex_test(Command, Status, STATUS_INVALID_INFO_CLASS); 285 else 286 ok_eq_hex_test(Command, Status, STATUS_DEBUGGER_INACTIVE); 287 } 288 289 /* Finally restore the original debug privilege state */ 290 RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, PrivilegeSet[0], FALSE, &PrivilegeSet[0]); 291 } 292 293 /* EOF */ 294