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