xref: /reactos/base/system/smss/smss.c (revision 0bc6bd64)
1 /*
2  * PROJECT:         ReactOS Windows-Compatible Session Manager
3  * LICENSE:         BSD 2-Clause License
4  * FILE:            base/system/smss/smss.c
5  * PURPOSE:         Main SMSS Code
6  * PROGRAMMERS:     Alex Ionescu
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "smss.h"
12 
13 #include <pseh/pseh2.h>
14 
15 #define NDEBUG
16 #include <debug.h>
17 
18 /* GLOBALS ********************************************************************/
19 
20 UNICODE_STRING SmpSystemRoot;
21 ULONG AttachedSessionId = -1;
22 BOOLEAN SmpDebug, SmpEnableDots;
23 HANDLE SmApiPort;
24 HANDLE SmpInitialCommandProcessId;
25 
26 /* FUNCTIONS ******************************************************************/
27 
28 NTSTATUS
29 NTAPI
30 SmpExecuteImage(IN PUNICODE_STRING FileName,
31                 IN PUNICODE_STRING Directory,
32                 IN PUNICODE_STRING CommandLine,
33                 IN ULONG MuSessionId,
34                 IN ULONG Flags,
35                 IN PRTL_USER_PROCESS_INFORMATION ProcessInformation)
36 {
37     PRTL_USER_PROCESS_INFORMATION ProcessInfo;
38     NTSTATUS Status;
39     RTL_USER_PROCESS_INFORMATION LocalProcessInfo;
40     PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
41 
42     /* Use the input process information if we have it, otherwise use local */
43     ProcessInfo = ProcessInformation;
44     if (!ProcessInfo) ProcessInfo = &LocalProcessInfo;
45 
46     /* Create parameters for the target process */
47     Status = RtlCreateProcessParameters(&ProcessParameters,
48                                         FileName,
49                                         SmpDefaultLibPath.Length ?
50                                         &SmpDefaultLibPath : NULL,
51                                         Directory,
52                                         CommandLine,
53                                         SmpDefaultEnvironment,
54                                         NULL,
55                                         NULL,
56                                         NULL,
57                                         0);
58     if (!NT_SUCCESS(Status))
59     {
60         /* This is a pretty bad failure. ASSERT on checked builds and exit */
61         ASSERTMSG("RtlCreateProcessParameters failed.\n", NT_SUCCESS(Status));
62         DPRINT1("SMSS: RtlCreateProcessParameters failed for %wZ - Status == %lx\n",
63                 FileName, Status);
64         return Status;
65     }
66 
67     /* Set the size field as required */
68     ProcessInfo->Size = sizeof(RTL_USER_PROCESS_INFORMATION);
69 
70     /* Check if the debug flag was requested */
71     if (Flags & SMP_DEBUG_FLAG)
72     {
73         /* Write it in the process parameters */
74         ProcessParameters->DebugFlags = 1;
75     }
76     else
77     {
78         /* Otherwise inherit the flag that was passed to SMSS itself */
79         ProcessParameters->DebugFlags = SmpDebug;
80     }
81 
82     /* Subsystems get the first 1MB of memory reserved for DOS/IVT purposes */
83     if (Flags & SMP_SUBSYSTEM_FLAG)
84     {
85         ProcessParameters->Flags |= RTL_USER_PROCESS_PARAMETERS_RESERVE_1MB;
86     }
87 
88     /* And always force NX for anything that SMSS launches */
89     ProcessParameters->Flags |= RTL_USER_PROCESS_PARAMETERS_NX;
90 
91     /* Now create the process */
92     Status = RtlCreateUserProcess(FileName,
93                                   OBJ_CASE_INSENSITIVE,
94                                   ProcessParameters,
95                                   NULL,
96                                   NULL,
97                                   NULL,
98                                   FALSE,
99                                   NULL,
100                                   NULL,
101                                   ProcessInfo);
102     RtlDestroyProcessParameters(ProcessParameters);
103     if (!NT_SUCCESS(Status))
104     {
105         /* If we couldn't create it, fail back to the caller */
106         DPRINT1("SMSS: Failed load of %wZ - Status  == %lx\n",
107                 FileName, Status);
108         return Status;
109     }
110 
111     /* Associate a session with this process */
112     Status = SmpSetProcessMuSessionId(ProcessInfo->ProcessHandle, MuSessionId);
113 
114     /* If the application is deferred (suspended), there's nothing to do */
115     if (Flags & SMP_DEFERRED_FLAG) return Status;
116 
117     /* Otherwise, get ready to start it, but make sure it's a native app */
118     if (ProcessInfo->ImageInformation.SubSystemType == IMAGE_SUBSYSTEM_NATIVE)
119     {
120         /* Resume it */
121         NtResumeThread(ProcessInfo->ThreadHandle, NULL);
122         if (!(Flags & SMP_ASYNC_FLAG))
123         {
124             /* Block on it unless Async was requested */
125             NtWaitForSingleObject(ProcessInfo->ThreadHandle, FALSE, NULL);
126         }
127 
128         /* It's up and running now, close our handles */
129         NtClose(ProcessInfo->ThreadHandle);
130         NtClose(ProcessInfo->ProcessHandle);
131     }
132     else
133     {
134         /* This image is invalid, so kill it, close our handles, and fail */
135         Status = STATUS_INVALID_IMAGE_FORMAT;
136         NtTerminateProcess(ProcessInfo->ProcessHandle, Status);
137         NtWaitForSingleObject(ProcessInfo->ThreadHandle, 0, 0);
138         NtClose(ProcessInfo->ThreadHandle);
139         NtClose(ProcessInfo->ProcessHandle);
140         DPRINT1("SMSS: Not an NT image - %wZ\n", FileName);
141     }
142 
143     /* Return the outcome of the process create */
144     return Status;
145 }
146 
147 NTSTATUS
148 NTAPI
149 SmpInvokeAutoChk(IN PUNICODE_STRING FileName,
150                  IN PUNICODE_STRING Directory,
151                  IN PUNICODE_STRING Arguments,
152                  IN ULONG Flags)
153 {
154     ANSI_STRING MessageString;
155     CHAR MessageBuffer[256];
156     UNICODE_STRING Destination;
157     WCHAR Buffer[1024];
158     BOOLEAN BootState, BootOkay, ShutdownOkay;
159 
160     /* Check if autochk should show dots (if the user booted with /SOS) */
161     if (SmpQueryRegistrySosOption()) SmpEnableDots = FALSE;
162 
163     /* Make sure autochk was actually found */
164     if (Flags & SMP_INVALID_PATH)
165     {
166         /* It wasn't, so create an error message to print on the screen */
167         RtlStringCbPrintfA(MessageBuffer,
168                            sizeof(MessageBuffer),
169                            "%wZ program not found - skipping AUTOCHECK\r\n",
170                            FileName);
171         RtlInitAnsiString(&MessageString, MessageBuffer);
172         if (NT_SUCCESS(RtlAnsiStringToUnicodeString(&Destination,
173                                                     &MessageString,
174                                                     TRUE)))
175         {
176             /* And show it */
177             NtDisplayString(&Destination);
178             RtlFreeUnicodeString(&Destination);
179         }
180     }
181     else
182     {
183         /* Autochk is there, so record the BSD state */
184         BootState = SmpSaveAndClearBootStatusData(&BootOkay, &ShutdownOkay);
185 
186         /* Build the path to autochk and place its arguments */
187         RtlInitEmptyUnicodeString(&Destination, Buffer, sizeof(Buffer));
188         RtlAppendUnicodeStringToString(&Destination, FileName);
189         RtlAppendUnicodeToString(&Destination, L" ");
190         RtlAppendUnicodeStringToString(&Destination, Arguments);
191 
192         /* Execute it */
193         SmpExecuteImage(FileName,
194                         Directory,
195                         &Destination,
196                         0,
197                         Flags & ~SMP_AUTOCHK_FLAG,
198                         NULL);
199 
200         /* Restore the BSD state */
201         if (BootState) SmpRestoreBootStatusData(BootOkay, ShutdownOkay);
202     }
203 
204     /* We're all done! */
205     return STATUS_SUCCESS;
206 }
207 
208 NTSTATUS
209 NTAPI
210 SmpExecuteCommand(IN PUNICODE_STRING CommandLine,
211                   IN ULONG MuSessionId,
212                   OUT PHANDLE ProcessId,
213                   IN ULONG Flags)
214 {
215     NTSTATUS Status;
216     UNICODE_STRING Arguments, Directory, FileName;
217 
218     /* There's no longer a debugging subsystem */
219     if (Flags & SMP_DEBUG_FLAG) return STATUS_SUCCESS;
220 
221     /* Parse the command line to see what execution flags are requested */
222     Status = SmpParseCommandLine(CommandLine,
223                                  &Flags,
224                                  &FileName,
225                                  &Directory,
226                                  &Arguments);
227     if (!NT_SUCCESS(Status))
228     {
229         /* Fail if we couldn't do that */
230         DPRINT1("SMSS: SmpParseCommandLine( %wZ ) failed - Status == %lx\n",
231                 CommandLine, Status);
232         return Status;
233     }
234 
235     /* Check if autochk is requested */
236     if (Flags & SMP_AUTOCHK_FLAG)
237     {
238         /* Run it */
239         Status = SmpInvokeAutoChk(&FileName, &Directory, &Arguments, Flags);
240     }
241     else if (Flags & SMP_SUBSYSTEM_FLAG)
242     {
243         Status = SmpLoadSubSystem(&FileName,
244                                   &Directory,
245                                   CommandLine,
246                                   MuSessionId,
247                                   ProcessId,
248                                   Flags);
249     }
250     else if (Flags & SMP_INVALID_PATH)
251     {
252         /* An invalid image was specified, fail */
253         DPRINT1("SMSS: Image file (%wZ) not found\n", &FileName);
254         Status = STATUS_OBJECT_NAME_NOT_FOUND;
255     }
256     else
257     {
258         /* An actual image name was present -- execute it */
259         Status = SmpExecuteImage(&FileName,
260                                  &Directory,
261                                  CommandLine,
262                                  MuSessionId,
263                                  Flags,
264                                  NULL);
265     }
266 
267     /* Free all the token parameters */
268     if (FileName.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
269     if (Directory.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Directory.Buffer);
270     if (Arguments.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Arguments.Buffer);
271 
272     /* Return to the caller */
273     if (!NT_SUCCESS(Status))
274     {
275         DPRINT1("SMSS: Command '%wZ' failed - Status == %x\n",
276                 CommandLine, Status);
277     }
278     return Status;
279 }
280 
281 NTSTATUS
282 NTAPI
283 SmpExecuteInitialCommand(IN ULONG MuSessionId,
284                          IN PUNICODE_STRING InitialCommand,
285                          IN HANDLE InitialCommandProcess,
286                          OUT PHANDLE ReturnPid)
287 {
288     NTSTATUS Status;
289     RTL_USER_PROCESS_INFORMATION ProcessInfo;
290     UNICODE_STRING Arguments, ImageFileDirectory, ImageFileName;
291     ULONG Flags = 0;
292 
293     /* Check if we haven't yet connected to ourselves */
294     if (!SmApiPort)
295     {
296         /* Connect to ourselves, as a client */
297         Status = SmConnectToSm(NULL, NULL, 0, &SmApiPort);
298         if (!NT_SUCCESS(Status))
299         {
300             DPRINT1("SMSS: Unable to connect to SM - Status == %lx\n", Status);
301             return Status;
302         }
303     }
304 
305     /* Parse the initial command line */
306     Status = SmpParseCommandLine(InitialCommand,
307                                  &Flags,
308                                  &ImageFileName,
309                                  &ImageFileDirectory,
310                                  &Arguments);
311     if (Flags & SMP_INVALID_PATH)
312     {
313         /* Fail if it doesn't exist */
314         DPRINT1("SMSS: Initial command image (%wZ) not found\n", &ImageFileName);
315         if (ImageFileName.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, ImageFileName.Buffer);
316         return STATUS_OBJECT_NAME_NOT_FOUND;
317     }
318 
319     /* And fail if any other reason is also true */
320     if (!NT_SUCCESS(Status))
321     {
322         DPRINT1("SMSS: SmpParseCommandLine( %wZ ) failed - Status == %lx\n",
323                 InitialCommand, Status);
324         return Status;
325     }
326 
327     /* Execute the initial command -- but defer its full execution */
328     Status = SmpExecuteImage(&ImageFileName,
329                              &ImageFileDirectory,
330                              InitialCommand,
331                              MuSessionId,
332                              SMP_DEFERRED_FLAG,
333                              &ProcessInfo);
334 
335     /* Free any buffers we had lying around */
336     if (ImageFileName.Buffer)
337     {
338         RtlFreeHeap(RtlGetProcessHeap(), 0, ImageFileName.Buffer);
339     }
340     if (ImageFileDirectory.Buffer)
341     {
342         RtlFreeHeap(RtlGetProcessHeap(), 0, ImageFileDirectory.Buffer);
343     }
344     if (Arguments.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Arguments.Buffer);
345 
346     /* Bail out if we couldn't execute the initial command */
347     if (!NT_SUCCESS(Status)) return Status;
348 
349     /* Now duplicate the handle to this process */
350     Status = NtDuplicateObject(NtCurrentProcess(),
351                                ProcessInfo.ProcessHandle,
352                                NtCurrentProcess(),
353                                InitialCommandProcess,
354                                PROCESS_ALL_ACCESS,
355                                0,
356                                0);
357     if (!NT_SUCCESS(Status))
358     {
359         /* Kill it utterly if duplication failed */
360         DPRINT1("SMSS: DupObject Failed. Status == %lx\n", Status);
361         NtTerminateProcess(ProcessInfo.ProcessHandle, Status);
362         NtResumeThread(ProcessInfo.ThreadHandle, NULL);
363         NtClose(ProcessInfo.ThreadHandle);
364         NtClose(ProcessInfo.ProcessHandle);
365         return Status;
366     }
367 
368     /* Return PID to the caller, and set this as the initial command PID */
369     if (ReturnPid) *ReturnPid = ProcessInfo.ClientId.UniqueProcess;
370     if (!MuSessionId) SmpInitialCommandProcessId = ProcessInfo.ClientId.UniqueProcess;
371 
372     /* Now call our server execution function to wrap up its initialization */
373     Status = SmExecPgm(SmApiPort, &ProcessInfo, FALSE);
374     if (!NT_SUCCESS(Status)) DPRINT1("SMSS: SmExecPgm Failed. Status == %lx\n", Status);
375     return Status;
376 }
377 
378 NTSTATUS
379 NTAPI
380 SmpTerminate(IN PULONG_PTR Parameters,
381              IN ULONG ParameterMask,
382              IN ULONG ParameterCount)
383 {
384     NTSTATUS Status;
385     BOOLEAN Old;
386     ULONG Response;
387 
388     /* Give the shutdown privilege to the thread */
389     if (RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, TRUE, &Old) ==
390         STATUS_NO_TOKEN)
391     {
392         /* Thread doesn't have a token, give it to the entire process */
393         RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, FALSE, &Old);
394     }
395 
396     /* Take down the process/machine with a hard error */
397     Status = NtRaiseHardError(STATUS_SYSTEM_PROCESS_TERMINATED,
398                               ParameterCount,
399                               ParameterMask,
400                               Parameters,
401                               OptionShutdownSystem,
402                               &Response);
403 
404     /* Terminate the process if the hard error didn't already */
405     return NtTerminateProcess(NtCurrentProcess(), Status);
406 }
407 
408 LONG
409 SmpUnhandledExceptionFilter(IN PEXCEPTION_POINTERS ExceptionInfo)
410 {
411     ULONG_PTR Parameters[4];
412     UNICODE_STRING DestinationString;
413 
414     /* Print and breakpoint into the debugger */
415     DbgPrint("SMSS: Unhandled exception - Status == %x  IP == %p\n",
416              ExceptionInfo->ExceptionRecord->ExceptionCode,
417              ExceptionInfo->ExceptionRecord->ExceptionAddress);
418     DbgPrint("      Memory Address: %x  Read/Write: %x\n",
419              ExceptionInfo->ExceptionRecord->ExceptionInformation[0],
420              ExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
421     DbgBreakPoint();
422 
423     /* Build the hard error and terminate */
424     RtlInitUnicodeString(&DestinationString, L"Unhandled Exception in Session Manager");
425     Parameters[0] = (ULONG_PTR)&DestinationString;
426     Parameters[1] = ExceptionInfo->ExceptionRecord->ExceptionCode;
427     Parameters[2] = (ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress;
428     Parameters[3] = (ULONG_PTR)ExceptionInfo->ContextRecord;
429     SmpTerminate(Parameters, 1, RTL_NUMBER_OF(Parameters));
430 
431     /* We should never get here */
432     ASSERT(FALSE);
433     return EXCEPTION_EXECUTE_HANDLER;
434 }
435 
436 NTSTATUS
437 __cdecl
438 _main(IN INT argc,
439       IN PCHAR argv[],
440       IN PCHAR envp[],
441       IN ULONG DebugFlag)
442 {
443     NTSTATUS Status;
444     KPRIORITY SetBasePriority;
445     ULONG_PTR Parameters[4];
446     HANDLE Handles[2];
447     PVOID State;
448     ULONG Flags;
449     PROCESS_BASIC_INFORMATION ProcessInfo;
450     UNICODE_STRING DbgString, InitialCommand;
451 
452     /* Make us critical */
453     RtlSetProcessIsCritical(TRUE, NULL, FALSE);
454     RtlSetThreadIsCritical(TRUE, NULL, FALSE);
455 
456     /* Raise our priority */
457     SetBasePriority = 11;
458     Status = NtSetInformationProcess(NtCurrentProcess(),
459                                      ProcessBasePriority,
460                                      (PVOID)&SetBasePriority,
461                                      sizeof(SetBasePriority));
462     ASSERT(NT_SUCCESS(Status));
463 
464     /* Save the debug flag if it was passed */
465     if (DebugFlag) SmpDebug = DebugFlag != 0;
466 
467     /* Build the hard error parameters */
468     Parameters[0] = (ULONG_PTR)&DbgString;
469     Parameters[1] = Parameters[2] = Parameters[3] = 0;
470 
471     /* Enter SEH so we can terminate correctly if anything goes wrong */
472     _SEH2_TRY
473     {
474         /* Initialize SMSS */
475         Status = SmpInit(&InitialCommand, &Handles[0]);
476         if (!NT_SUCCESS(Status))
477         {
478             DPRINT1("SMSS: SmpInit return failure - Status == %x\n", Status);
479             RtlInitUnicodeString(&DbgString, L"Session Manager Initialization");
480             Parameters[1] = Status;
481             _SEH2_LEAVE;
482         }
483 
484         /* Get the global flags */
485         Status = NtQuerySystemInformation(SystemFlagsInformation,
486                                           &Flags,
487                                           sizeof(Flags),
488                                           NULL);
489         ASSERT(NT_SUCCESS(Status));
490 
491         /* Before executing the initial command check if the debug flag is on */
492         if (Flags & (FLG_DEBUG_INITIAL_COMMAND | FLG_DEBUG_INITIAL_COMMAND_EX))
493         {
494             /* SMSS should launch ntsd with a few parameters at this point */
495             DPRINT1("Global Flags Set to SMSS Debugging: Not yet supported\n");
496         }
497 
498         /* Execute the initial command (Winlogon.exe) */
499         Status = SmpExecuteInitialCommand(0, &InitialCommand, &Handles[1], NULL);
500         if (!NT_SUCCESS(Status))
501         {
502             /* Fail and raise a hard error */
503             DPRINT1("SMSS: Execute Initial Command failed\n");
504             RtlInitUnicodeString(&DbgString,
505                                  L"Session Manager ExecuteInitialCommand");
506             Parameters[1] = Status;
507             _SEH2_LEAVE;
508         }
509 
510         /*  Check if we're already attached to a session */
511         Status = SmpAcquirePrivilege(SE_LOAD_DRIVER_PRIVILEGE, &State);
512         if (AttachedSessionId != -1)
513         {
514             /* Detach from it, we should be in no session right now */
515             Status = NtSetSystemInformation(SystemSessionDetach,
516                                             &AttachedSessionId,
517                                             sizeof(AttachedSessionId));
518             ASSERT(NT_SUCCESS(Status));
519             AttachedSessionId = -1;
520         }
521         SmpReleasePrivilege(State);
522 
523         /* Wait on either CSRSS or Winlogon to die */
524         Status = NtWaitForMultipleObjects(RTL_NUMBER_OF(Handles),
525                                           Handles,
526                                           WaitAny,
527                                           FALSE,
528                                           NULL);
529         if (Status == STATUS_WAIT_0)
530         {
531             /* CSRSS is dead, get exit code and prepare for the hard error */
532             RtlInitUnicodeString(&DbgString, L"Windows SubSystem");
533             Status = NtQueryInformationProcess(Handles[0],
534                                                ProcessBasicInformation,
535                                                &ProcessInfo,
536                                                sizeof(ProcessInfo),
537                                                NULL);
538             DPRINT1("SMSS: Windows subsystem terminated when it wasn't supposed to.\n");
539         }
540         else
541         {
542             /* The initial command is dead or we have another failure */
543             RtlInitUnicodeString(&DbgString, L"Windows Logon Process");
544             if (Status == STATUS_WAIT_1)
545             {
546                 /* Winlogon.exe got terminated, get its exit code */
547                 Status = NtQueryInformationProcess(Handles[1],
548                                                    ProcessBasicInformation,
549                                                    &ProcessInfo,
550                                                    sizeof(ProcessInfo),
551                                                    NULL);
552             }
553             else
554             {
555                 /* Something else satisfied our wait, so set the wait status */
556                 ProcessInfo.ExitStatus = Status;
557                 Status = STATUS_SUCCESS;
558             }
559             DPRINT1("SMSS: Initial command '%wZ' terminated when it wasn't supposed to.\n",
560                     &InitialCommand);
561         }
562 
563         /* Check if NtQueryInformationProcess was successful */
564         if (NT_SUCCESS(Status))
565         {
566             /* Then we must have a valid exit status in the structure, use it */
567             Parameters[1] = ProcessInfo.ExitStatus;
568         }
569         else
570         {
571             /* We really don't know what happened, so set a generic error */
572             Parameters[1] = STATUS_UNSUCCESSFUL;
573         }
574     }
575     _SEH2_EXCEPT(SmpUnhandledExceptionFilter(_SEH2_GetExceptionInformation()))
576     {
577         /* The filter should never return here */
578         ASSERT(FALSE);
579     }
580     _SEH2_END;
581 
582     /* Something in the init loop failed, terminate SMSS */
583     return SmpTerminate(Parameters, 1, RTL_NUMBER_OF(Parameters));
584 }
585 
586 /* EOF */
587