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