xref: /reactos/ntoskrnl/ke/amd64/usercall.c (revision d6eebaa4)
1 /*
2  * PROJECT:     ReactOS Kernel
3  * LICENSE:     GPL - 2.0 + (https ://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     AMD64 User-mode Callout Mechanisms (APC and Win32K Callbacks)
5  * COPYRIGHT:   Timo Kreuzer(timo.kreuzer@reactos.org)
6  */
7 
8 /* INCLUDES ******************************************************************/
9 
10 #include <ntoskrnl.h>
11 #define NDEBUG
12 #include <debug.h>
13 
14 /*!
15  *  \name KiInitializeUserApc
16  *
17  *  \brief
18  *      Prepares the current trap frame (which must have come from user mode)
19  *      with the ntdll.KiUserApcDispatcher entrypoint, copying a CONTEXT
20  *      record with the context from the old trap frame to the threads user
21  *      mode stack.
22  *
23  *  \param ExceptionFrame - Pointer to the Exception Frame
24  *
25  *  \param TrapFrame  Pointer to the Trap Frame.
26  *
27  *  \param NormalRoutine - Pointer to the NormalRoutine to call.
28  *
29  *  \param NormalContext - Pointer to the context to send to the Normal Routine.
30  *
31  *  \param SystemArgument[1-2]
32  *        Pointer to a set of two parameters that contain untyped data.
33  *
34  *  \remarks
35  *      This function is called from KiDeliverApc, when the trap frame came
36  *      from user mode. This happens before a systemcall or interrupt exits back
37  *      to usermode or when a thread is started from PspUserThreadstartup.
38  *      The trap exit code will then leave to KiUserApcDispatcher which in turn
39  *      calls the NormalRoutine, passing NormalContext, SystemArgument1 and
40  *      SystemArgument2 as parameters. When that function returns, it calls
41  *      NtContinue to return back to the kernel, where the old context that was
42  *      saved on the usermode stack is restored and execution is transferred
43  *      back to usermode, where the original trap originated from.
44  *
45 *--*/
46 VOID
47 NTAPI
48 KiInitializeUserApc(
49     _In_ PKEXCEPTION_FRAME ExceptionFrame,
50     _Inout_ PKTRAP_FRAME TrapFrame,
51     _In_ PKNORMAL_ROUTINE NormalRoutine,
52     _In_ PVOID NormalContext,
53     _In_ PVOID SystemArgument1,
54     _In_ PVOID SystemArgument2)
55 {
56     PCONTEXT Context;
57     EXCEPTION_RECORD ExceptionRecord;
58 
59     /* Sanity check, that the trap frame is from user mode */
60     ASSERT((TrapFrame->SegCs & MODE_MASK) != KernelMode);
61 
62     /* Align the user tack to 16 bytes and allocate space for a CONTEXT structure */
63     Context = (PCONTEXT)ALIGN_DOWN_POINTER_BY(TrapFrame->Rsp, 16) - 1;
64 
65     /* Protect with SEH */
66     _SEH2_TRY
67     {
68         /* Probe the context */
69         ProbeForWrite(Context, sizeof(CONTEXT), 16);
70 
71         /* Convert the current trap frame to a context */
72         Context->ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
73         KeTrapFrameToContext(TrapFrame, ExceptionFrame, Context);
74 
75         /* Set parameters for KiUserApcDispatcher */
76         Context->P1Home = (ULONG64)NormalContext;
77         Context->P2Home = (ULONG64)SystemArgument1;
78         Context->P3Home = (ULONG64)SystemArgument2;
79         Context->P4Home = (ULONG64)NormalRoutine;
80     }
81     _SEH2_EXCEPT(ExceptionRecord = *_SEH2_GetExceptionInformation()->ExceptionRecord, EXCEPTION_EXECUTE_HANDLER)
82     {
83         /* Dispatch the exception */
84         ExceptionRecord.ExceptionAddress = (PVOID)TrapFrame->Rip;
85         KiDispatchException(&ExceptionRecord,
86                             ExceptionFrame,
87                             TrapFrame,
88                             UserMode,
89                             TRUE);
90     }
91     _SEH2_END;
92 
93     /* Set the stack pointer to the context record */
94     TrapFrame->Rsp = (ULONG64)Context;
95 
96     /* We jump to KiUserApcDispatcher in ntdll */
97     TrapFrame->Rip = (ULONG64)KeUserApcDispatcher;
98 
99     /* Setup Ring 3 segments */
100     TrapFrame->SegCs = KGDT64_R3_CODE | RPL_MASK;
101     TrapFrame->SegFs = KGDT64_R3_CMTEB | RPL_MASK;
102     TrapFrame->SegGs = KGDT64_R3_DATA | RPL_MASK;
103     TrapFrame->SegSs = KGDT64_R3_DATA | RPL_MASK;
104 
105     /* Sanitize EFLAGS, enable interrupts */
106     TrapFrame->EFlags &= EFLAGS_USER_SANITIZE;
107     TrapFrame->EFlags |= EFLAGS_INTERRUPT_MASK;
108 }
109 
110 /*
111  * Stack layout for KiUserModeCallout:
112  * ----------------------------------
113  * KCALLOUT_FRAME.ResultLength    <= 2nd Parameter to KiCallUserMode
114  * KCALLOUT_FRAME.Result          <= 1st Parameter to KiCallUserMode
115  * KCALLOUT_FRAME.ReturnAddress   <= Return address of KiCallUserMode
116  * KCALLOUT_FRAME.Ebp             \
117  * KCALLOUT_FRAME.Ebx              | = non-volatile registers, pushed
118  * KCALLOUT_FRAME.Esi              |   by KiCallUserMode
119  * KCALLOUT_FRAME.Edi             /
120  * KCALLOUT_FRAME.CallbackStack
121  * KCALLOUT_FRAME.TrapFrame
122  * KCALLOUT_FRAME.InitialStack    <= CalloutFrame points here
123  * ----------------------------------
124  * ~~ optional alignment ~~
125  * ----------------------------------
126  * FX_SAVE_AREA
127  * ----------------------------------
128  * KTRAP_FRAME
129  * ----------------------------------
130  * ~~ begin of stack frame for KiUserModeCallout ~~
131  *
132  */
133 NTSTATUS
134 FASTCALL
135 KiUserModeCallout(
136     _Out_ PKCALLOUT_FRAME CalloutFrame)
137 {
138     PKTHREAD CurrentThread;
139     PKTRAP_FRAME TrapFrame;
140     KTRAP_FRAME CallbackTrapFrame;
141     PKIPCR Pcr;
142     ULONG_PTR InitialStack;
143     NTSTATUS Status;
144 
145     /* Get the current thread */
146     CurrentThread = KeGetCurrentThread();
147 
148     /* Check if we are at pasive level */
149     ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
150 
151     /* Check if we are attached or APCs are disabled */
152     ASSERT((CurrentThread->ApcStateIndex == OriginalApcEnvironment) &&
153         (CurrentThread->CombinedApcDisable == 0));
154 
155     /* Align stack on a 16-byte boundary */
156     InitialStack = (ULONG_PTR)ALIGN_DOWN_POINTER_BY(CalloutFrame, 16);
157 
158     /* Check if we have enough space on the stack */
159     if ((InitialStack - KERNEL_STACK_SIZE) < CurrentThread->StackLimit)
160     {
161         /* We don't, we'll have to grow our stack */
162         Status = MmGrowKernelStack((PVOID)InitialStack);
163 
164         /* Quit if we failed */
165         if (!NT_SUCCESS(Status)) return Status;
166     }
167 
168     /* Save the current callback stack and initial stack */
169     CalloutFrame->CallbackStack = (ULONG_PTR)CurrentThread->CallbackStack;
170     CalloutFrame->InitialStack = (ULONG_PTR)CurrentThread->InitialStack;
171 
172     /* Get and save the trap frame */
173     TrapFrame = CurrentThread->TrapFrame;
174     CalloutFrame->TrapFrame = (ULONG_PTR)TrapFrame;
175 
176     /* Set the new callback stack */
177     CurrentThread->CallbackStack = CalloutFrame;
178 
179     /* Disable interrupts so we can fill the NPX State */
180     _disable();
181 
182     /* Set the stack address */
183     CurrentThread->InitialStack = (PVOID)InitialStack;
184 
185     /* Copy the trap frame to the new location */
186     CallbackTrapFrame = *TrapFrame;
187 
188     /* Get PCR */
189     Pcr = (PKIPCR)KeGetPcr();
190 
191     /* Set user-mode dispatcher address as EIP */
192     Pcr->TssBase->Rsp0 = InitialStack;
193     Pcr->Prcb.RspBase = InitialStack;
194     CallbackTrapFrame.Rip = (ULONG_PTR)KeUserCallbackDispatcher;
195 
196     /* Bring interrupts back */
197     _enable();
198 
199     /* Exit to user-mode */
200     KiUserCallbackExit(&CallbackTrapFrame);
201 }
202 
203 VOID
204 KiSetupUserCalloutFrame(
205     _Out_ PUCALLOUT_FRAME UserCalloutFrame,
206     _In_ PKTRAP_FRAME TrapFrame,
207     _In_ ULONG ApiNumber,
208     _In_ PVOID Buffer,
209     _In_ ULONG BufferLength)
210 {
211 #ifdef _M_IX86
212     CalloutFrame->Reserved = 0;
213     CalloutFrame->ApiNumber = ApiNumber;
214     CalloutFrame->Buffer = (ULONG_PTR)NewStack;
215     CalloutFrame->Length = ArgumentLength;
216 #elif defined(_M_AMD64)
217     UserCalloutFrame->Buffer = (PVOID)(UserCalloutFrame + 1);
218     UserCalloutFrame->Length = BufferLength;
219     UserCalloutFrame->ApiNumber = ApiNumber;
220     UserCalloutFrame->MachineFrame.Rip = TrapFrame->Rip;
221     UserCalloutFrame->MachineFrame.Rsp = TrapFrame->Rsp;
222 #else
223 #error "KiSetupUserCalloutFrame not implemented!"
224 #endif
225 }
226 
227 NTSTATUS
228 NTAPI
229 KeUserModeCallback(
230     IN ULONG RoutineIndex,
231     IN PVOID Argument,
232     IN ULONG ArgumentLength,
233     OUT PVOID *Result,
234     OUT PULONG ResultLength)
235 {
236     ULONG_PTR OldStack;
237     PUCHAR UserArguments;
238     PUCALLOUT_FRAME CalloutFrame;
239     PULONG_PTR UserStackPointer;
240     NTSTATUS CallbackStatus;
241 #ifdef _M_IX86
242     PEXCEPTION_REGISTRATION_RECORD ExceptionList;
243 #endif // _M_IX86
244     PTEB Teb;
245     ULONG GdiBatchCount = 0;
246     ASSERT(KeGetCurrentThread()->ApcState.KernelApcInProgress == FALSE);
247     ASSERT(KeGetPreviousMode() == UserMode);
248 
249     /* Get the current user-mode stack */
250     UserStackPointer = KiGetUserModeStackAddress();
251     OldStack = *UserStackPointer;
252 
253     /* Enter a SEH Block */
254     _SEH2_TRY
255     {
256         /* Calculate and align the stack. This is unaligned by 8 bytes, since the following
257            UCALLOUT_FRAME compensates for that and on entry we already have a full stack
258            frame with home space for the next call, i.e. we are already inside the function
259            body and the stack needs to be 16 byte aligned. */
260         UserArguments = (PUCHAR)ALIGN_DOWN_POINTER_BY(OldStack - ArgumentLength, 16) - 8;
261 
262         /* The callout frame is below the arguments */
263         CalloutFrame = ((PUCALLOUT_FRAME)UserArguments) - 1;
264 
265         /* Make sure it's all writable */
266         ProbeForWrite(CalloutFrame,
267                       sizeof(PUCALLOUT_FRAME) + ArgumentLength,
268                       sizeof(PVOID));
269 
270         /* Copy the buffer into the stack */
271         RtlCopyMemory(UserArguments, Argument, ArgumentLength);
272 
273         /* Write the arguments */
274         KiSetupUserCalloutFrame(CalloutFrame,
275                                 KeGetCurrentThread()->TrapFrame,
276                                 RoutineIndex,
277                                 UserArguments,
278                                 ArgumentLength);
279 
280         /* Save the exception list */
281         Teb = KeGetCurrentThread()->Teb;
282 #ifdef _M_IX86
283         ExceptionList = Teb->NtTib.ExceptionList;
284 #endif // _M_IX86
285 
286         /* Jump to user mode */
287         *UserStackPointer = (ULONG_PTR)CalloutFrame;
288         CallbackStatus = KiCallUserMode(Result, ResultLength);
289         if (CallbackStatus != STATUS_CALLBACK_POP_STACK)
290         {
291 #ifdef _M_IX86
292             /* Only restore the exception list if we didn't crash in ring 3 */
293             Teb->NtTib.ExceptionList = ExceptionList;
294 #endif // _M_IX86
295         }
296         else
297         {
298             /* Otherwise, pop the stack */
299             OldStack = *UserStackPointer;
300         }
301 
302         /* Read the GDI Batch count */
303         GdiBatchCount = Teb->GdiBatchCount;
304     }
305     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
306     {
307         /* Get the SEH exception */
308         _SEH2_YIELD(return _SEH2_GetExceptionCode());
309     }
310     _SEH2_END;
311 
312     /* Check if we have GDI Batch operations */
313     if (GdiBatchCount)
314     {
315         *UserStackPointer -= 256;
316         KeGdiFlushUserBatch();
317     }
318 
319     /* Restore stack and return */
320     *UserStackPointer = OldStack;
321 #ifdef _M_AMD64 // could probably  move the update to TrapFrame->Rsp from the C handler to the asm code
322     __writegsqword(FIELD_OFFSET(KIPCR, UserRsp), OldStack);
323 #endif
324     return CallbackStatus;
325 }
326 
327 NTSTATUS
328 NTAPI
329 NtCallbackReturn(
330     _In_ PVOID Result,
331     _In_ ULONG ResultLength,
332     _In_ NTSTATUS CallbackStatus)
333 {
334     PKTHREAD CurrentThread;
335     PKCALLOUT_FRAME CalloutFrame;
336     PKTRAP_FRAME CallbackTrapFrame, TrapFrame;
337     PKIPCR Pcr;
338 
339     /* Get the current thread and make sure we have a callback stack */
340     CurrentThread = KeGetCurrentThread();
341     CalloutFrame = CurrentThread->CallbackStack;
342     if (CalloutFrame == NULL)
343     {
344         return STATUS_NO_CALLBACK_ACTIVE;
345     }
346 
347     /* Store the results in the callback stack */
348     *((PVOID*)CalloutFrame->OutputBuffer) = Result;
349     *((ULONG*)CalloutFrame->OutputLength) = ResultLength;
350 
351     /* Get the trap frame */
352     CallbackTrapFrame = CurrentThread->TrapFrame;
353 
354     /* Disable interrupts for NPX save and stack switch */
355     _disable();
356 
357     /* Restore the exception list */
358     Pcr = (PKIPCR)KeGetPcr();
359 
360     /* Get the previous trap frame */
361     TrapFrame = (PKTRAP_FRAME)CalloutFrame->TrapFrame;
362 
363     /* Check if we failed in user mode */
364     if (CallbackStatus == STATUS_CALLBACK_POP_STACK)
365     {
366         *TrapFrame = *CallbackTrapFrame;
367     }
368 
369     /* Clear DR7 */
370     TrapFrame->Dr7 = 0;
371 
372     /* Check if debugging was active */
373     if (CurrentThread->Header.DebugActive & 0xFF)
374     {
375         /* Copy debug registers data from it */
376         TrapFrame->Dr0 = CallbackTrapFrame->Dr0;
377         TrapFrame->Dr1 = CallbackTrapFrame->Dr1;
378         TrapFrame->Dr2 = CallbackTrapFrame->Dr2;
379         TrapFrame->Dr3 = CallbackTrapFrame->Dr3;
380         TrapFrame->Dr6 = CallbackTrapFrame->Dr6;
381         TrapFrame->Dr7 = CallbackTrapFrame->Dr7;
382     }
383 
384     /* Switch the stack back to the previous value */
385     Pcr->TssBase->Rsp0 = CalloutFrame->InitialStack;
386     Pcr->Prcb.RspBase = CalloutFrame->InitialStack;
387 
388     /* Get the initial stack and restore it */
389     CurrentThread->InitialStack = (PVOID)CalloutFrame->InitialStack;
390 
391     /* Restore the trap frame and the previous callback stack */
392     CurrentThread->TrapFrame = TrapFrame;
393     CurrentThread->CallbackStack = (PVOID)CalloutFrame->CallbackStack;
394 
395     /* Bring interrupts back */
396     _enable();
397 
398     /* Now switch back to the old stack */
399     KiCallbackReturn(CalloutFrame, CallbackStatus);
400 }
401 
402