xref: /reactos/ntoskrnl/ke/amd64/usercall.c (revision c8d07514)
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     KiServiceExit(&CallbackTrapFrame, 0);
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 size */
257         UserArguments = (PUCHAR)ALIGN_DOWN_POINTER_BY(OldStack - ArgumentLength, sizeof(PVOID));
258 
259         /* The callout frame is below the arguments */
260         CalloutFrame = ((PUCALLOUT_FRAME)UserArguments) - 1;
261 
262         /* Make sure it's all writable */
263         ProbeForWrite(CalloutFrame,
264                       sizeof(PUCALLOUT_FRAME) + ArgumentLength,
265                       sizeof(PVOID));
266 
267         /* Copy the buffer into the stack */
268         RtlCopyMemory(UserArguments, Argument, ArgumentLength);
269 
270         /* Write the arguments */
271         KiSetupUserCalloutFrame(CalloutFrame,
272                                 KeGetCurrentThread()->TrapFrame,
273                                 RoutineIndex,
274                                 UserArguments,
275                                 ArgumentLength);
276 
277         /* Save the exception list */
278         Teb = KeGetCurrentThread()->Teb;
279 #ifdef _M_IX86
280         ExceptionList = Teb->NtTib.ExceptionList;
281 #endif // _M_IX86
282 
283         /* Jump to user mode */
284         *UserStackPointer = (ULONG_PTR)CalloutFrame;
285         CallbackStatus = KiCallUserMode(Result, ResultLength);
286         if (CallbackStatus != STATUS_CALLBACK_POP_STACK)
287         {
288 #ifdef _M_IX86
289             /* Only restore the exception list if we didn't crash in ring 3 */
290             Teb->NtTib.ExceptionList = ExceptionList;
291 #endif // _M_IX86
292         }
293         else
294         {
295             /* Otherwise, pop the stack */
296             OldStack = *UserStackPointer;
297         }
298 
299         /* Read the GDI Batch count */
300         GdiBatchCount = Teb->GdiBatchCount;
301     }
302     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
303     {
304         /* Get the SEH exception */
305         _SEH2_YIELD(return _SEH2_GetExceptionCode());
306     }
307     _SEH2_END;
308 
309     /* Check if we have GDI Batch operations */
310     if (GdiBatchCount)
311     {
312         *UserStackPointer -= 256;
313         KeGdiFlushUserBatch();
314     }
315 
316     /* Restore stack and return */
317     *UserStackPointer = OldStack;
318 #ifdef _M_AMD64 // could probably  move the update to TrapFrame->Rsp from the C handler to the asm code
319     __writegsqword(FIELD_OFFSET(KIPCR, UserRsp), OldStack);
320 #endif
321     return CallbackStatus;
322 }
323 
324 NTSTATUS
325 NTAPI
326 NtCallbackReturn(
327     _In_ PVOID Result,
328     _In_ ULONG ResultLength,
329     _In_ NTSTATUS CallbackStatus)
330 {
331     PKTHREAD CurrentThread;
332     PKCALLOUT_FRAME CalloutFrame;
333     PKTRAP_FRAME CallbackTrapFrame, TrapFrame;
334     PKIPCR Pcr;
335 
336     /* Get the current thread and make sure we have a callback stack */
337     CurrentThread = KeGetCurrentThread();
338     CalloutFrame = CurrentThread->CallbackStack;
339     if (CalloutFrame == NULL)
340     {
341         return STATUS_NO_CALLBACK_ACTIVE;
342     }
343 
344     /* Store the results in the callback stack */
345     *((PVOID*)CalloutFrame->OutputBuffer) = Result;
346     *((ULONG*)CalloutFrame->OutputLength) = ResultLength;
347 
348     /* Get the trap frame */
349     CallbackTrapFrame = CurrentThread->TrapFrame;
350 
351     /* Disable interrupts for NPX save and stack switch */
352     _disable();
353 
354     /* Restore the exception list */
355     Pcr = (PKIPCR)KeGetPcr();
356 
357     /* Get the previous trap frame */
358     TrapFrame = (PKTRAP_FRAME)CalloutFrame->TrapFrame;
359 
360     /* Check if we failed in user mode */
361     if (CallbackStatus == STATUS_CALLBACK_POP_STACK)
362     {
363         *TrapFrame = *CallbackTrapFrame;
364     }
365 
366     /* Clear DR7 */
367     TrapFrame->Dr7 = 0;
368 
369     /* Check if debugging was active */
370     if (CurrentThread->Header.DebugActive & 0xFF)
371     {
372         /* Copy debug registers data from it */
373         TrapFrame->Dr0 = CallbackTrapFrame->Dr0;
374         TrapFrame->Dr1 = CallbackTrapFrame->Dr1;
375         TrapFrame->Dr2 = CallbackTrapFrame->Dr2;
376         TrapFrame->Dr3 = CallbackTrapFrame->Dr3;
377         TrapFrame->Dr6 = CallbackTrapFrame->Dr6;
378         TrapFrame->Dr7 = CallbackTrapFrame->Dr7;
379     }
380 
381     /* Switch the stack back to the previous value */
382     Pcr->TssBase->Rsp0 = CalloutFrame->InitialStack;
383     Pcr->Prcb.RspBase = CalloutFrame->InitialStack;
384 
385     /* Get the initial stack and restore it */
386     CurrentThread->InitialStack = (PVOID)CalloutFrame->InitialStack;
387 
388     /* Restore the trap frame and the previous callback stack */
389     CurrentThread->TrapFrame = TrapFrame;
390     CurrentThread->CallbackStack = (PVOID)CalloutFrame->CallbackStack;
391 
392     /* Bring interrupts back */
393     _enable();
394 
395     /* Now switch back to the old stack */
396     KiCallbackReturn(CalloutFrame, CallbackStatus);
397 }
398 
399