/* * PROJECT: ReactOS Windows-Compatible Session Manager * LICENSE: BSD 2-Clause License * FILE: base/system/smss/smss.c * PURPOSE: Main SMSS Code * PROGRAMMERS: Alex Ionescu */ /* INCLUDES *******************************************************************/ #include "smss.h" #include #define NDEBUG #include /* GLOBALS ********************************************************************/ UNICODE_STRING SmpSystemRoot; ULONG AttachedSessionId = -1; BOOLEAN SmpDebug, SmpEnableDots; HANDLE SmApiPort; HANDLE SmpInitialCommandProcessId; /* FUNCTIONS ******************************************************************/ NTSTATUS NTAPI SmpExecuteImage(IN PUNICODE_STRING FileName, IN PUNICODE_STRING Directory, IN PUNICODE_STRING CommandLine, IN ULONG MuSessionId, IN ULONG Flags, IN PRTL_USER_PROCESS_INFORMATION ProcessInformation) { PRTL_USER_PROCESS_INFORMATION ProcessInfo; NTSTATUS Status; RTL_USER_PROCESS_INFORMATION LocalProcessInfo; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; /* Use the input process information if we have it, otherwise use local */ ProcessInfo = ProcessInformation; if (!ProcessInfo) ProcessInfo = &LocalProcessInfo; /* Create parameters for the target process */ Status = RtlCreateProcessParameters(&ProcessParameters, FileName, SmpDefaultLibPath.Length ? &SmpDefaultLibPath : NULL, Directory, CommandLine, SmpDefaultEnvironment, NULL, NULL, NULL, 0); if (!NT_SUCCESS(Status)) { /* This is a pretty bad failure. ASSERT on checked builds and exit */ ASSERTMSG("RtlCreateProcessParameters failed.\n", NT_SUCCESS(Status)); DPRINT1("SMSS: RtlCreateProcessParameters failed for %wZ - Status == %lx\n", FileName, Status); return Status; } /* Set the size field as required */ ProcessInfo->Size = sizeof(*ProcessInfo); /* Check if the debug flag was requested */ if (Flags & SMP_DEBUG_FLAG) { /* Write it in the process parameters */ ProcessParameters->DebugFlags = 1; } else { /* Otherwise inherit the flag that was passed to SMSS itself */ ProcessParameters->DebugFlags = SmpDebug; } /* Subsystems get the first 1MB of memory reserved for DOS/IVT purposes */ if (Flags & SMP_SUBSYSTEM_FLAG) { ProcessParameters->Flags |= RTL_USER_PROCESS_PARAMETERS_RESERVE_1MB; } /* And always force NX for anything that SMSS launches */ ProcessParameters->Flags |= RTL_USER_PROCESS_PARAMETERS_NX; /* Now create the process in suspended state */ Status = RtlCreateUserProcess(FileName, OBJ_CASE_INSENSITIVE, ProcessParameters, NULL, NULL, NULL, FALSE, NULL, NULL, ProcessInfo); RtlDestroyProcessParameters(ProcessParameters); if (!NT_SUCCESS(Status)) { /* If we couldn't create it, fail back to the caller */ DPRINT1("SMSS: Failed load of %wZ - Status == %lx\n", FileName, Status); return Status; } /* Associate a session with this process */ Status = SmpSetProcessMuSessionId(ProcessInfo->ProcessHandle, MuSessionId); /* If the application is deferred (suspended), there's nothing to do */ if (Flags & SMP_DEFERRED_FLAG) return Status; /* Otherwise, get ready to start it, but make sure it's a native app */ if (ProcessInfo->ImageInformation.SubSystemType == IMAGE_SUBSYSTEM_NATIVE) { /* Resume it */ NtResumeThread(ProcessInfo->ThreadHandle, NULL); if (!(Flags & SMP_ASYNC_FLAG)) { /* Block on it unless Async was requested */ NtWaitForSingleObject(ProcessInfo->ThreadHandle, FALSE, NULL); } /* It's up and running now, close our handles */ NtClose(ProcessInfo->ThreadHandle); NtClose(ProcessInfo->ProcessHandle); } else { /* This image is invalid, so kill it, close our handles, and fail */ Status = STATUS_INVALID_IMAGE_FORMAT; NtTerminateProcess(ProcessInfo->ProcessHandle, Status); NtWaitForSingleObject(ProcessInfo->ThreadHandle, FALSE, NULL); NtClose(ProcessInfo->ThreadHandle); NtClose(ProcessInfo->ProcessHandle); DPRINT1("SMSS: Not an NT image - %wZ\n", FileName); } /* Return the outcome of the process create */ return Status; } NTSTATUS NTAPI SmpInvokeAutoChk(IN PUNICODE_STRING FileName, IN PUNICODE_STRING Directory, IN PUNICODE_STRING Arguments, IN ULONG Flags) { ANSI_STRING MessageString; CHAR MessageBuffer[256]; UNICODE_STRING Destination; WCHAR Buffer[1024]; BOOLEAN BootState, BootOkay, ShutdownOkay; /* Check if autochk should show dots (if the user booted with /SOS) */ if (SmpQueryRegistrySosOption()) SmpEnableDots = FALSE; /* Make sure autochk was actually found */ if (Flags & SMP_INVALID_PATH) { /* It wasn't, so create an error message to print on the screen */ RtlStringCbPrintfA(MessageBuffer, sizeof(MessageBuffer), "%wZ program not found - skipping AUTOCHECK\r\n", FileName); RtlInitAnsiString(&MessageString, MessageBuffer); if (NT_SUCCESS(RtlAnsiStringToUnicodeString(&Destination, &MessageString, TRUE))) { /* And show it */ NtDisplayString(&Destination); RtlFreeUnicodeString(&Destination); } } else { /* Autochk is there, so record the BSD state */ BootState = SmpSaveAndClearBootStatusData(&BootOkay, &ShutdownOkay); /* Build the path to autochk and place its arguments */ RtlInitEmptyUnicodeString(&Destination, Buffer, sizeof(Buffer)); RtlAppendUnicodeStringToString(&Destination, FileName); RtlAppendUnicodeToString(&Destination, L" "); RtlAppendUnicodeStringToString(&Destination, Arguments); /* Execute it */ SmpExecuteImage(FileName, Directory, &Destination, 0, Flags & ~SMP_AUTOCHK_FLAG, NULL); /* Restore the BSD state */ if (BootState) SmpRestoreBootStatusData(BootOkay, ShutdownOkay); } /* We're all done! */ return STATUS_SUCCESS; } NTSTATUS NTAPI SmpExecuteCommand(IN PUNICODE_STRING CommandLine, IN ULONG MuSessionId, OUT PHANDLE ProcessId, IN ULONG Flags) { NTSTATUS Status; UNICODE_STRING Arguments, Directory, FileName; /* There's no longer a debugging subsystem */ if (Flags & SMP_DEBUG_FLAG) return STATUS_SUCCESS; /* Parse the command line to see what execution flags are requested */ Status = SmpParseCommandLine(CommandLine, &Flags, &FileName, &Directory, &Arguments); if (!NT_SUCCESS(Status)) { /* Fail if we couldn't do that */ DPRINT1("SMSS: SmpParseCommandLine( %wZ ) failed - Status == %lx\n", CommandLine, Status); return Status; } /* Check if autochk is requested */ if (Flags & SMP_AUTOCHK_FLAG) { /* Run it */ Status = SmpInvokeAutoChk(&FileName, &Directory, &Arguments, Flags); } else if (Flags & SMP_SUBSYSTEM_FLAG) { Status = SmpLoadSubSystem(&FileName, &Directory, CommandLine, MuSessionId, ProcessId, Flags); } else if (Flags & SMP_INVALID_PATH) { /* An invalid image was specified, fail */ DPRINT1("SMSS: Image file (%wZ) not found\n", &FileName); Status = STATUS_OBJECT_NAME_NOT_FOUND; } else { /* An actual image name was present, execute it */ Status = SmpExecuteImage(&FileName, &Directory, CommandLine, MuSessionId, Flags, NULL); } /* Free all the token parameters */ if (FileName.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer); if (Directory.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Directory.Buffer); if (Arguments.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Arguments.Buffer); /* Return to the caller */ if (!NT_SUCCESS(Status)) { DPRINT1("SMSS: Command '%wZ' failed - Status == %x\n", CommandLine, Status); } return Status; } NTSTATUS NTAPI SmpExecuteInitialCommand(IN ULONG MuSessionId, IN PUNICODE_STRING InitialCommand, IN HANDLE InitialCommandProcess, OUT PHANDLE ReturnPid) { NTSTATUS Status; RTL_USER_PROCESS_INFORMATION ProcessInfo; UNICODE_STRING Arguments, Directory, FileName; ULONG Flags = 0; /* Check if we haven't yet connected to ourselves */ if (!SmApiPort) { /* Connect to ourselves, as a client */ Status = SmConnectToSm(NULL, NULL, 0, &SmApiPort); if (!NT_SUCCESS(Status)) { DPRINT1("SMSS: Unable to connect to SM - Status == %lx\n", Status); return Status; } } /* Parse the initial command line */ Status = SmpParseCommandLine(InitialCommand, &Flags, &FileName, &Directory, &Arguments); if (Flags & SMP_INVALID_PATH) { /* Fail if it doesn't exist */ DPRINT1("SMSS: Initial command image (%wZ) not found\n", &FileName); if (FileName.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer); return STATUS_OBJECT_NAME_NOT_FOUND; } /* And fail if any other reason is also true */ if (!NT_SUCCESS(Status)) { DPRINT1("SMSS: SmpParseCommandLine( %wZ ) failed - Status == %lx\n", InitialCommand, Status); return Status; } /* Execute the initial command, but defer its full execution */ Status = SmpExecuteImage(&FileName, &Directory, InitialCommand, MuSessionId, SMP_DEFERRED_FLAG, &ProcessInfo); /* Free all the token parameters */ if (FileName.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, FileName.Buffer); if (Directory.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Directory.Buffer); if (Arguments.Buffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Arguments.Buffer); /* Bail out if we couldn't execute the initial command */ if (!NT_SUCCESS(Status)) return Status; /* Now duplicate the handle to this process */ Status = NtDuplicateObject(NtCurrentProcess(), ProcessInfo.ProcessHandle, NtCurrentProcess(), InitialCommandProcess, PROCESS_ALL_ACCESS, 0, 0); if (!NT_SUCCESS(Status)) { /* Kill it utterly if duplication failed */ DPRINT1("SMSS: DupObject Failed. Status == %lx\n", Status); NtTerminateProcess(ProcessInfo.ProcessHandle, Status); NtResumeThread(ProcessInfo.ThreadHandle, NULL); NtClose(ProcessInfo.ThreadHandle); NtClose(ProcessInfo.ProcessHandle); return Status; } /* Return PID to the caller, and set this as the initial command PID */ if (ReturnPid) *ReturnPid = ProcessInfo.ClientId.UniqueProcess; if (!MuSessionId) SmpInitialCommandProcessId = ProcessInfo.ClientId.UniqueProcess; /* Now call our server execution function to wrap up its initialization */ Status = SmExecPgm(SmApiPort, &ProcessInfo, FALSE); if (!NT_SUCCESS(Status)) DPRINT1("SMSS: SmExecPgm Failed. Status == %lx\n", Status); return Status; } NTSTATUS NTAPI SmpTerminate(IN PULONG_PTR Parameters, IN ULONG ParameterMask, IN ULONG ParameterCount) { NTSTATUS Status; BOOLEAN Old; ULONG Response; /* Give the shutdown privilege to the thread */ if (RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, TRUE, &Old) == STATUS_NO_TOKEN) { /* Thread doesn't have a token, give it to the entire process */ RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, FALSE, &Old); } /* Take down the process/machine with a hard error */ Status = NtRaiseHardError(STATUS_SYSTEM_PROCESS_TERMINATED, ParameterCount, ParameterMask, Parameters, OptionShutdownSystem, &Response); /* Terminate the process if the hard error didn't already */ return NtTerminateProcess(NtCurrentProcess(), Status); } LONG SmpUnhandledExceptionFilter(IN PEXCEPTION_POINTERS ExceptionInfo) { ULONG_PTR Parameters[4]; UNICODE_STRING ErrorString; /* Print and breakpoint into the debugger */ DbgPrint("SMSS: Unhandled exception - Status == %x IP == %p\n", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); DbgPrint(" Memory Address: %x Read/Write: %x\n", ExceptionInfo->ExceptionRecord->ExceptionInformation[0], ExceptionInfo->ExceptionRecord->ExceptionInformation[1]); DbgBreakPoint(); /* Build the hard error and terminate */ RtlInitUnicodeString(&ErrorString, L"Unhandled Exception in Session Manager"); Parameters[0] = (ULONG_PTR)&ErrorString; Parameters[1] = ExceptionInfo->ExceptionRecord->ExceptionCode; Parameters[2] = (ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress; Parameters[3] = (ULONG_PTR)ExceptionInfo->ContextRecord; SmpTerminate(Parameters, 1, RTL_NUMBER_OF(Parameters)); /* We should never get here */ ASSERT(FALSE); return EXCEPTION_EXECUTE_HANDLER; } NTSTATUS __cdecl _main(IN INT argc, IN PCHAR argv[], IN PCHAR envp[], IN ULONG DebugFlag) { NTSTATUS Status; KPRIORITY SetBasePriority; ULONG_PTR Parameters[4]; HANDLE Handles[2]; PVOID State; ULONG Flags; PROCESS_BASIC_INFORMATION ProcessInfo; UNICODE_STRING DbgString, InitialCommand; /* Make us critical */ RtlSetProcessIsCritical(TRUE, NULL, FALSE); RtlSetThreadIsCritical(TRUE, NULL, FALSE); /* Raise our priority */ SetBasePriority = 11; Status = NtSetInformationProcess(NtCurrentProcess(), ProcessBasePriority, (PVOID)&SetBasePriority, sizeof(SetBasePriority)); ASSERT(NT_SUCCESS(Status)); /* Save the debug flag if it was passed */ if (DebugFlag) SmpDebug = DebugFlag != 0; /* Build the hard error parameters */ Parameters[0] = (ULONG_PTR)&DbgString; Parameters[1] = Parameters[2] = Parameters[3] = 0; /* Enter SEH so we can terminate correctly if anything goes wrong */ _SEH2_TRY { /* Initialize SMSS */ Status = SmpInit(&InitialCommand, &Handles[0]); if (!NT_SUCCESS(Status)) { DPRINT1("SMSS: SmpInit return failure - Status == %x\n", Status); RtlInitUnicodeString(&DbgString, L"Session Manager Initialization"); Parameters[1] = Status; _SEH2_LEAVE; } /* Get the global flags */ Status = NtQuerySystemInformation(SystemFlagsInformation, &Flags, sizeof(Flags), NULL); ASSERT(NT_SUCCESS(Status)); /* Before executing the initial command check if the debug flag is on */ if (Flags & (FLG_DEBUG_INITIAL_COMMAND | FLG_DEBUG_INITIAL_COMMAND_EX)) { /* SMSS should launch ntsd with a few parameters at this point */ DPRINT1("Global Flags Set to SMSS Debugging: Not yet supported\n"); } /* Execute the initial command (Winlogon.exe) */ Status = SmpExecuteInitialCommand(0, &InitialCommand, &Handles[1], NULL); if (!NT_SUCCESS(Status)) { /* Fail and raise a hard error */ DPRINT1("SMSS: Execute Initial Command failed\n"); RtlInitUnicodeString(&DbgString, L"Session Manager ExecuteInitialCommand"); Parameters[1] = Status; _SEH2_LEAVE; } /* Check if we're already attached to a session */ Status = SmpAcquirePrivilege(SE_LOAD_DRIVER_PRIVILEGE, &State); if (AttachedSessionId != -1) { /* Detach from it, we should be in no session right now */ Status = NtSetSystemInformation(SystemSessionDetach, &AttachedSessionId, sizeof(AttachedSessionId)); ASSERT(NT_SUCCESS(Status)); AttachedSessionId = -1; } SmpReleasePrivilege(State); /* Wait on either CSRSS or Winlogon to die */ Status = NtWaitForMultipleObjects(RTL_NUMBER_OF(Handles), Handles, WaitAny, FALSE, NULL); if (Status == STATUS_WAIT_0) { /* CSRSS is dead, get exit code and prepare for the hard error */ RtlInitUnicodeString(&DbgString, L"Windows SubSystem"); Status = NtQueryInformationProcess(Handles[0], ProcessBasicInformation, &ProcessInfo, sizeof(ProcessInfo), NULL); DPRINT1("SMSS: Windows subsystem terminated when it wasn't supposed to.\n"); } else { /* The initial command is dead or we have another failure */ RtlInitUnicodeString(&DbgString, L"Windows Logon Process"); if (Status == STATUS_WAIT_1) { /* Winlogon.exe got terminated, get its exit code */ Status = NtQueryInformationProcess(Handles[1], ProcessBasicInformation, &ProcessInfo, sizeof(ProcessInfo), NULL); } else { /* Something else satisfied our wait, so set the wait status */ ProcessInfo.ExitStatus = Status; Status = STATUS_SUCCESS; } DPRINT1("SMSS: Initial command '%wZ' terminated when it wasn't supposed to.\n", &InitialCommand); } /* Check if NtQueryInformationProcess was successful */ if (NT_SUCCESS(Status)) { /* Then we must have a valid exit status in the structure, use it */ Parameters[1] = ProcessInfo.ExitStatus; } else { /* We really don't know what happened, so set a generic error */ Parameters[1] = STATUS_UNSUCCESSFUL; } } _SEH2_EXCEPT(SmpUnhandledExceptionFilter(_SEH2_GetExceptionInformation())) { /* The filter should never return here */ ASSERT(FALSE); } _SEH2_END; /* Something in the init loop failed, terminate SMSS */ return SmpTerminate(Parameters, 1, RTL_NUMBER_OF(Parameters)); } /* EOF */