1 /*
2  * PROJECT:     ReactOS API Tests
3  * LICENSE:     LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:     Tests for system calls
5  * COPYRIGHT:   Copyright 2024 Timo Kreuzer <timo.kreuzer@reactos.org>
6  */
7 
8 #include "precomp.h"
9 
10 #define EFLAGS_TF               0x100L
11 #define EFLAGS_INTERRUPT_MASK   0x200L
12 
13 ULONG g_NoopSyscallNumber = 0;
14 ULONG g_HandlerCalled = 0;
15 ULONG g_RandomSeed = 0x63c28b49;
16 
17 VOID
18 DoSyscallAndCaptureContext(
19     _In_ ULONG SyscallNumber,
20     _Out_ PCONTEXT PreContext,
21     _Out_ PCONTEXT PostContext);
22 
23 extern const UCHAR SyscallReturn;
24 
25 ULONG_PTR
26 DoSyscallWithUnalignedStack(
27     _In_ ULONG64 SyscallNumber);
28 
29 #ifdef _M_IX86
30 __declspec(dllimport)
31 VOID
32 NTAPI
33 KiFastSystemCallRet(VOID);
34 #endif
35 
36 static
37 BOOLEAN
38 InitSysCalls()
39 {
40     /* Scan instructions in NtFlushWriteBuffer to find the syscall number
41        for NtFlushWriteBuffer, which is a noop syscall on x86/x64 */
42     PUCHAR Instructions = (PUCHAR)NtFlushWriteBuffer;
43     for (ULONG i = 0; i < 32; i++)
44     {
45         if (Instructions[i] == 0xB8)
46         {
47             g_NoopSyscallNumber = *(PULONG)&Instructions[i + 1];
48             return TRUE;
49         }
50     }
51 
52     return FALSE;
53 }
54 
55 static
56 VOID
57 LoadUser32()
58 {
59     HMODULE hUser32 = LoadLibraryW(L"user32.dll");
60     ok(hUser32 != NULL, "Failed to load user32.dll\n");
61 }
62 
63 static
64 LONG
65 WINAPI
66 VectoredExceptionHandlerForUserModeCallback(
67     struct _EXCEPTION_POINTERS *ExceptionInfo)
68 {
69     g_HandlerCalled++;
70 
71     /* Return from the callback */
72     NtCallbackReturn(NULL, 0, 0xdeadbeef);
73 
74     /* If that failed, we were not in a callback, keep searching */
75     return EXCEPTION_CONTINUE_SEARCH;
76 }
77 
78 VOID
79 ValidateSyscall_(
80     _In_ PCCH File,
81     _In_ ULONG Line,
82     _In_ ULONG_PTR SyscallId,
83     _In_ ULONG_PTR Result)
84 {
85     CONTEXT PreContext, PostContext;
86 
87 #ifdef _M_IX86
88     DoSyscallAndCaptureContext(SyscallId, &PreContext, &PostContext);
89 
90     /* Non-volatile registers and rsp are unchanged */
91     ok_eq_hex_(File, Line, PostContext.Esp, PreContext.Esp);
92     ok_eq_hex_(File, Line, PostContext.Ebx, PreContext.Ebx);
93     ok_eq_hex_(File, Line, PostContext.Esi, PreContext.Esi);
94     ok_eq_hex_(File, Line, PostContext.Edi, PreContext.Edi);
95     ok_eq_hex_(File, Line, PostContext.Ebp, PreContext.Ebp);
96 
97     /* Special cases */
98     ok_eq_hex_(File, Line, PostContext.Ecx, PreContext.Esp - 0x4C);
99     ok_eq_hex_(File, Line, PostContext.Edx, (ULONG)KiFastSystemCallRet);
100     ok_eq_hex_(File, Line, PostContext.Eax, Result);
101 
102 #elif defined(_M_AMD64)
103     /* Initiaize the pre-contex with random numbers */
104     PULONG64 IntegerRegs = &PreContext.Rax;
105     PM128A XmmRegs = &PreContext.Xmm0;
106     for (ULONG Index = 0; Index < 16; Index++)
107     {
108         IntegerRegs[Index] = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed);
109         XmmRegs[Index].Low = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed);
110         XmmRegs[Index].High = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed);
111     }
112     PreContext.EFlags = RtlRandom(&g_RandomSeed);
113     PreContext.EFlags &= ~(EFLAGS_TF | 0x20 | 0x40000);
114     PreContext.EFlags |= EFLAGS_INTERRUPT_MASK;
115 
116     PreContext.SegDs = 0; //0x0028;
117     PreContext.SegEs = 0; //0x002B;
118     PreContext.SegFs = 0; //0x0053;
119     PreContext.SegGs = 0; //0x002B;
120     PreContext.SegSs = 0; // 0x002B;
121 
122     DoSyscallAndCaptureContext(SyscallId, &PreContext, &PostContext);
123 
124     /* Non-volatile registers and rsp are unchanged */
125     ok_eq_hex64_(File, Line, PostContext.Rsp, PreContext.Rsp);
126     ok_eq_hex64_(File, Line, PostContext.Rbx, PreContext.Rbx);
127     ok_eq_hex64_(File, Line, PostContext.Rsi, PreContext.Rsi);
128     ok_eq_hex64_(File, Line, PostContext.Rdi, PreContext.Rdi);
129     ok_eq_hex64_(File, Line, PostContext.Rbp, PreContext.Rbp);
130     ok_eq_hex64_(File, Line, PostContext.R12, PreContext.R12);
131     ok_eq_hex64_(File, Line, PostContext.R13, PreContext.R13);
132     ok_eq_hex64_(File, Line, PostContext.R14, PreContext.R14);
133     ok_eq_hex64_(File, Line, PostContext.R15, PreContext.R15);
134     ok_eq_hex64_(File, Line, PostContext.Xmm6.Low, PreContext.Xmm6.Low);
135     ok_eq_hex64_(File, Line, PostContext.Xmm6.High, PreContext.Xmm6.High);
136     ok_eq_hex64_(File, Line, PostContext.Xmm7.Low, PreContext.Xmm7.Low);
137     ok_eq_hex64_(File, Line, PostContext.Xmm7.High, PreContext.Xmm7.High);
138     ok_eq_hex64_(File, Line, PostContext.Xmm8.Low, PreContext.Xmm8.Low);
139     ok_eq_hex64_(File, Line, PostContext.Xmm8.High, PreContext.Xmm8.High);
140     ok_eq_hex64_(File, Line, PostContext.Xmm9.Low, PreContext.Xmm9.Low);
141     ok_eq_hex64_(File, Line, PostContext.Xmm9.High, PreContext.Xmm9.High);
142     ok_eq_hex64_(File, Line, PostContext.Xmm10.Low, PreContext.Xmm10.Low);
143     ok_eq_hex64_(File, Line, PostContext.Xmm10.High, PreContext.Xmm10.High);
144     ok_eq_hex64_(File, Line, PostContext.Xmm11.Low, PreContext.Xmm11.Low);
145     ok_eq_hex64_(File, Line, PostContext.Xmm11.High, PreContext.Xmm11.High);
146     ok_eq_hex64_(File, Line, PostContext.Xmm12.Low, PreContext.Xmm12.Low);
147     ok_eq_hex64_(File, Line, PostContext.Xmm12.High, PreContext.Xmm12.High);
148     ok_eq_hex64_(File, Line, PostContext.Xmm13.Low, PreContext.Xmm13.Low);
149     ok_eq_hex64_(File, Line, PostContext.Xmm13.High, PreContext.Xmm13.High);
150     ok_eq_hex64_(File, Line, PostContext.Xmm14.Low, PreContext.Xmm14.Low);
151     ok_eq_hex64_(File, Line, PostContext.Xmm14.High, PreContext.Xmm14.High);
152     ok_eq_hex64_(File, Line, PostContext.Xmm15.Low, PreContext.Xmm15.Low);
153     ok_eq_hex64_(File, Line, PostContext.Xmm15.High, PreContext.Xmm15.High);
154 
155     /* Parity flag is flaky */
156     ok_eq_hex64_(File, Line, PostContext.EFlags & ~0x4, PreContext.EFlags & ~0x9F5);
157 
158     ok_eq_hex64_(File, Line, PostContext.SegCs, 0x0033);
159     ok_eq_hex64_(File, Line, PostContext.SegSs, 0x002B);
160     ok_(File, Line)(PostContext.SegDs == PreContext.SegDs || PostContext.SegDs == 0x002B,
161         "Expected 0x002B, got 0x%04X\n", PostContext.SegDs);
162     ok_(File, Line)(PostContext.SegEs == PreContext.SegEs || PostContext.SegEs == 0x002B,
163         "Expected 0x002B, got 0x%04X\n", PostContext.SegEs);
164     ok_(File, Line)(PostContext.SegFs == PreContext.SegFs || PostContext.SegFs == 0x0053,
165         "Expected 0x002B, got 0x%04X\n", PostContext.SegFs);
166     ok_(File, Line)(PostContext.SegGs == PreContext.SegGs || PostContext.SegGs == 0x002B,
167         "Expected 0x002B, got 0x%04X\n", PostContext.SegGs);
168     ok_eq_hex64_(File, Line, PostContext.SegSs, 0x002B);
169 
170     /* These volatile registers are zeroed */
171     ok_eq_hex64_(File, Line, PostContext.Rdx, 0);
172     ok_eq_hex64_(File, Line, PostContext.R10, 0);
173     ok_eq_hex64_(File, Line, PostContext.Xmm0.Low, 0);
174     ok_eq_hex64_(File, Line, PostContext.Xmm0.High, 0);
175     ok_eq_hex64_(File, Line, PostContext.Xmm1.Low, 0);
176     ok_eq_hex64_(File, Line, PostContext.Xmm1.High, 0);
177     ok_eq_hex64_(File, Line, PostContext.Xmm2.Low, 0);
178     ok_eq_hex64_(File, Line, PostContext.Xmm2.High, 0);
179     ok_eq_hex64_(File, Line, PostContext.Xmm3.Low, 0);
180     ok_eq_hex64_(File, Line, PostContext.Xmm3.High, 0);
181     ok_eq_hex64_(File, Line, PostContext.Xmm4.Low, 0);
182     ok_eq_hex64_(File, Line, PostContext.Xmm4.High, 0);
183     ok_eq_hex64_(File, Line, PostContext.Xmm5.Low, 0);
184     ok_eq_hex64_(File, Line, PostContext.Xmm5.High, 0);
185 
186     /* Special cases */
187     ok_eq_hex64_(File, Line, PostContext.Rax, Result);
188     ok_eq_hex64_(File, Line, PostContext.Rcx, (ULONG64)&SyscallReturn);
189     ok_eq_hex64_(File, Line, PostContext.R8, PreContext.Rsp);
190     ok_eq_hex64_(File, Line, PostContext.R9, PreContext.Rbp);
191     ok_eq_hex64_(File, Line, PostContext.R11, PostContext.EFlags);
192 
193     // TODO:Debug regs, mxcsr, floating point, etc.
194 #else
195 #error Unsupported architecture
196 #endif
197 }
198 
199 #define ValidateSyscall(SyscallId, Result) ValidateSyscall_(__FILE__, __LINE__, SyscallId, Result)
200 
201 static
202 VOID
203 Test_SyscallNumbers()
204 {
205     BOOL Wow64Process;
206 
207     if (IsWow64Process(NtCurrentProcess(), &Wow64Process) && Wow64Process)
208     {
209         skip("Skipping syscall tests on WOW64\n");
210         return;
211     }
212 
213     /* Test valid syscall number */
214     ValidateSyscall(g_NoopSyscallNumber, STATUS_SUCCESS);
215 
216     /* Test invalid syscall number */
217     ValidateSyscall(0x0FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);
218 
219     /* Add a vectored exception handler to catch the exception we will get
220        when KiUserCallbackDispatcher is called and user32.dll is not loaded
221        We cannot use SEH here, because the exception is outside of the try block */
222     PVOID hHandler = AddVectoredExceptionHandler(TRUE, VectoredExceptionHandlerForUserModeCallback);
223     ok(hHandler != NULL, "Failed to add vectored exception handler\n");
224 
225     /* Test win32k syscall number without user32.dll loaded */
226 #ifdef _M_AMD64
227     ValidateSyscall(0x1000, STATUS_SUCCESS);
228 #else
229     ValidateSyscall(0x1000, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);
230 #endif
231     ok_eq_ulong(g_HandlerCalled, 1UL);
232 
233     /* Test invalid win32k syscall number without user32.dll loaded */
234 #ifdef _M_IX86
235     ValidateSyscall(0x1FFF, 0xffffffbf);
236 #else
237     ValidateSyscall(0x1FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);
238 #endif
239 
240     ok_eq_ulong(g_HandlerCalled, 2UL);
241 
242     RemoveVectoredExceptionHandler(hHandler);
243 
244     LoadUser32();
245 
246     /* Test invalid win32k syscall number */
247 #ifdef _M_IX86
248     ValidateSyscall(0x1FFF, 0xffffffbf);
249 #else
250     ValidateSyscall(0x1FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);
251 #endif
252 
253     /* Test invalid syscall table number */
254     ValidateSyscall(0x2000 + g_NoopSyscallNumber, STATUS_SUCCESS);
255     ValidateSyscall(0x3000 + g_NoopSyscallNumber, STATUS_SUCCESS);
256 
257 #if 0 // This only happens, when running the test from VS, but not from the command line
258     /* For some unknown reason the result gets sign extended in this case */
259     ULONG64 Result = DoSyscallWithUnalignedStack(0x2000);
260     ok_eq_hex64(Result, (LONG)STATUS_ACCESS_VIOLATION);
261 #endif
262 
263     /* Test invalid upper bits in syscall number */
264     ValidateSyscall(0xFFFFFFFFFFF70000ULL + g_NoopSyscallNumber, STATUS_SUCCESS);
265 }
266 
267 static
268 VOID
269 Test_SyscallPerformance()
270 {
271     ULONG64 Start, End, Cycles;
272     ULONG64 TotalCycles = 0, Min = -1, Max = 0;
273     ULONG64 Count = 100000;
274     ULONG Outliers = 0;
275     ULONG_PTR OldAffinityMask;
276     double AvgCycles;
277 
278     OldAffinityMask = SetThreadAffinityMask(GetCurrentThread(), 1);
279 
280     for (ULONG64 i = 0; i < Count; i++)
281     {
282         Start = __rdtsc();
283         NtFlushWriteBuffer();
284         End = __rdtsc();
285         Cycles = End - Start;
286         if (Cycles > 2000)
287         {
288             Outliers++;
289             continue;
290         }
291         TotalCycles += Cycles;
292         Min = min(Min, Cycles);
293         Max = max(Max, Cycles);
294     }
295 
296     AvgCycles = (double)TotalCycles / (Count - Outliers);
297 
298     trace("NtFlushWriteBuffer: avg %.2f cycles, min %I64u, max %I64u, Outliers %lu\n",
299           AvgCycles, Min, Max, Outliers);
300 
301     SetThreadAffinityMask(GetCurrentThread(), OldAffinityMask);
302 }
303 
304 START_TEST(SystemCall)
305 {
306     if (!InitSysCalls())
307     {
308         skip("Failed to initialize.\n");
309         return;
310     }
311 
312     Test_SyscallNumbers();
313     Test_SyscallPerformance();
314 }
315