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