xref: /reactos/base/system/smss/smss.c (revision 84344399)
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(*ProcessInfo);
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 in suspended state */
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, FALSE, NULL);
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, Directory, FileName;
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                                  &FileName,
309                                  &Directory,
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", &FileName);
315         if (FileName.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.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(&FileName,
329                              &Directory,
330                              InitialCommand,
331                              MuSessionId,
332                              SMP_DEFERRED_FLAG,
333                              &ProcessInfo);
334 
335     /* Free all the token parameters */
336     if (FileName.Buffer)  RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer);
337     if (Directory.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Directory.Buffer);
338     if (Arguments.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Arguments.Buffer);
339 
340     /* Bail out if we couldn't execute the initial command */
341     if (!NT_SUCCESS(Status)) return Status;
342 
343     /* Now duplicate the handle to this process */
344     Status = NtDuplicateObject(NtCurrentProcess(),
345                                ProcessInfo.ProcessHandle,
346                                NtCurrentProcess(),
347                                InitialCommandProcess,
348                                PROCESS_ALL_ACCESS,
349                                0,
350                                0);
351     if (!NT_SUCCESS(Status))
352     {
353         /* Kill it utterly if duplication failed */
354         DPRINT1("SMSS: DupObject Failed. Status == %lx\n", Status);
355         NtTerminateProcess(ProcessInfo.ProcessHandle, Status);
356         NtResumeThread(ProcessInfo.ThreadHandle, NULL);
357         NtClose(ProcessInfo.ThreadHandle);
358         NtClose(ProcessInfo.ProcessHandle);
359         return Status;
360     }
361 
362     /* Return PID to the caller, and set this as the initial command PID */
363     if (ReturnPid) *ReturnPid = ProcessInfo.ClientId.UniqueProcess;
364     if (!MuSessionId) SmpInitialCommandProcessId = ProcessInfo.ClientId.UniqueProcess;
365 
366     /* Now call our server execution function to wrap up its initialization */
367     Status = SmExecPgm(SmApiPort, &ProcessInfo, FALSE);
368     if (!NT_SUCCESS(Status)) DPRINT1("SMSS: SmExecPgm Failed. Status == %lx\n", Status);
369     return Status;
370 }
371 
372 NTSTATUS
373 NTAPI
374 SmpTerminate(IN PULONG_PTR Parameters,
375              IN ULONG ParameterMask,
376              IN ULONG ParameterCount)
377 {
378     NTSTATUS Status;
379     BOOLEAN Old;
380     ULONG Response;
381 
382     /* Give the shutdown privilege to the thread */
383     if (RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, TRUE, &Old) ==
384         STATUS_NO_TOKEN)
385     {
386         /* Thread doesn't have a token, give it to the entire process */
387         RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, FALSE, &Old);
388     }
389 
390     /* Take down the process/machine with a hard error */
391     Status = NtRaiseHardError(STATUS_SYSTEM_PROCESS_TERMINATED,
392                               ParameterCount,
393                               ParameterMask,
394                               Parameters,
395                               OptionShutdownSystem,
396                               &Response);
397 
398     /* Terminate the process if the hard error didn't already */
399     return NtTerminateProcess(NtCurrentProcess(), Status);
400 }
401 
402 LONG
403 SmpUnhandledExceptionFilter(IN PEXCEPTION_POINTERS ExceptionInfo)
404 {
405     ULONG_PTR Parameters[4];
406     UNICODE_STRING ErrorString;
407 
408     /* Print and breakpoint into the debugger */
409     DbgPrint("SMSS: Unhandled exception - Status == %x  IP == %p\n",
410              ExceptionInfo->ExceptionRecord->ExceptionCode,
411              ExceptionInfo->ExceptionRecord->ExceptionAddress);
412     DbgPrint("      Memory Address: %x  Read/Write: %x\n",
413              ExceptionInfo->ExceptionRecord->ExceptionInformation[0],
414              ExceptionInfo->ExceptionRecord->ExceptionInformation[1]);
415     DbgBreakPoint();
416 
417     /* Build the hard error and terminate */
418     RtlInitUnicodeString(&ErrorString, L"Unhandled Exception in Session Manager");
419     Parameters[0] = (ULONG_PTR)&ErrorString;
420     Parameters[1] = ExceptionInfo->ExceptionRecord->ExceptionCode;
421     Parameters[2] = (ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress;
422     Parameters[3] = (ULONG_PTR)ExceptionInfo->ContextRecord;
423     SmpTerminate(Parameters, 1, RTL_NUMBER_OF(Parameters));
424 
425     /* We should never get here */
426     ASSERT(FALSE);
427     return EXCEPTION_EXECUTE_HANDLER;
428 }
429 
430 NTSTATUS
431 __cdecl
432 _main(IN INT argc,
433       IN PCHAR argv[],
434       IN PCHAR envp[],
435       IN ULONG DebugFlag)
436 {
437     NTSTATUS Status;
438     KPRIORITY SetBasePriority;
439     ULONG_PTR Parameters[4];
440     HANDLE Handles[2];
441     PVOID State;
442     ULONG Flags;
443     PROCESS_BASIC_INFORMATION ProcessInfo;
444     UNICODE_STRING DbgString, InitialCommand;
445 
446     /* Make us critical */
447     RtlSetProcessIsCritical(TRUE, NULL, FALSE);
448     RtlSetThreadIsCritical(TRUE, NULL, FALSE);
449 
450     /* Raise our priority */
451     SetBasePriority = 11;
452     Status = NtSetInformationProcess(NtCurrentProcess(),
453                                      ProcessBasePriority,
454                                      (PVOID)&SetBasePriority,
455                                      sizeof(SetBasePriority));
456     ASSERT(NT_SUCCESS(Status));
457 
458     /* Save the debug flag if it was passed */
459     if (DebugFlag) SmpDebug = DebugFlag != 0;
460 
461     /* Build the hard error parameters */
462     Parameters[0] = (ULONG_PTR)&DbgString;
463     Parameters[1] = Parameters[2] = Parameters[3] = 0;
464 
465     /* Enter SEH so we can terminate correctly if anything goes wrong */
466     _SEH2_TRY
467     {
468         /* Initialize SMSS */
469         Status = SmpInit(&InitialCommand, &Handles[0]);
470         if (!NT_SUCCESS(Status))
471         {
472             DPRINT1("SMSS: SmpInit return failure - Status == %x\n", Status);
473             RtlInitUnicodeString(&DbgString, L"Session Manager Initialization");
474             Parameters[1] = Status;
475             _SEH2_LEAVE;
476         }
477 
478         /* Get the global flags */
479         Status = NtQuerySystemInformation(SystemFlagsInformation,
480                                           &Flags,
481                                           sizeof(Flags),
482                                           NULL);
483         ASSERT(NT_SUCCESS(Status));
484 
485         /* Before executing the initial command check if the debug flag is on */
486         if (Flags & (FLG_DEBUG_INITIAL_COMMAND | FLG_DEBUG_INITIAL_COMMAND_EX))
487         {
488             /* SMSS should launch ntsd with a few parameters at this point */
489             DPRINT1("Global Flags Set to SMSS Debugging: Not yet supported\n");
490         }
491 
492         /* Execute the initial command (Winlogon.exe) */
493         Status = SmpExecuteInitialCommand(0, &InitialCommand, &Handles[1], NULL);
494         if (!NT_SUCCESS(Status))
495         {
496             /* Fail and raise a hard error */
497             DPRINT1("SMSS: Execute Initial Command failed\n");
498             RtlInitUnicodeString(&DbgString,
499                                  L"Session Manager ExecuteInitialCommand");
500             Parameters[1] = Status;
501             _SEH2_LEAVE;
502         }
503 
504         /*  Check if we're already attached to a session */
505         Status = SmpAcquirePrivilege(SE_LOAD_DRIVER_PRIVILEGE, &State);
506         if (AttachedSessionId != -1)
507         {
508             /* Detach from it, we should be in no session right now */
509             Status = NtSetSystemInformation(SystemSessionDetach,
510                                             &AttachedSessionId,
511                                             sizeof(AttachedSessionId));
512             ASSERT(NT_SUCCESS(Status));
513             AttachedSessionId = -1;
514         }
515         SmpReleasePrivilege(State);
516 
517         /* Wait on either CSRSS or Winlogon to die */
518         Status = NtWaitForMultipleObjects(RTL_NUMBER_OF(Handles),
519                                           Handles,
520                                           WaitAny,
521                                           FALSE,
522                                           NULL);
523         if (Status == STATUS_WAIT_0)
524         {
525             /* CSRSS is dead, get exit code and prepare for the hard error */
526             RtlInitUnicodeString(&DbgString, L"Windows SubSystem");
527             Status = NtQueryInformationProcess(Handles[0],
528                                                ProcessBasicInformation,
529                                                &ProcessInfo,
530                                                sizeof(ProcessInfo),
531                                                NULL);
532             DPRINT1("SMSS: Windows subsystem terminated when it wasn't supposed to.\n");
533         }
534         else
535         {
536             /* The initial command is dead or we have another failure */
537             RtlInitUnicodeString(&DbgString, L"Windows Logon Process");
538             if (Status == STATUS_WAIT_1)
539             {
540                 /* Winlogon.exe got terminated, get its exit code */
541                 Status = NtQueryInformationProcess(Handles[1],
542                                                    ProcessBasicInformation,
543                                                    &ProcessInfo,
544                                                    sizeof(ProcessInfo),
545                                                    NULL);
546             }
547             else
548             {
549                 /* Something else satisfied our wait, so set the wait status */
550                 ProcessInfo.ExitStatus = Status;
551                 Status = STATUS_SUCCESS;
552             }
553             DPRINT1("SMSS: Initial command '%wZ' terminated when it wasn't supposed to.\n",
554                     &InitialCommand);
555         }
556 
557         /* Check if NtQueryInformationProcess was successful */
558         if (NT_SUCCESS(Status))
559         {
560             /* Then we must have a valid exit status in the structure, use it */
561             Parameters[1] = ProcessInfo.ExitStatus;
562         }
563         else
564         {
565             /* We really don't know what happened, so set a generic error */
566             Parameters[1] = STATUS_UNSUCCESSFUL;
567         }
568     }
569     _SEH2_EXCEPT(SmpUnhandledExceptionFilter(_SEH2_GetExceptionInformation()))
570     {
571         /* The filter should never return here */
572         ASSERT(FALSE);
573     }
574     _SEH2_END;
575 
576     /* Something in the init loop failed, terminate SMSS */
577     return SmpTerminate(Parameters, 1, RTL_NUMBER_OF(Parameters));
578 }
579 
580 /* EOF */
581