xref: /reactos/base/system/smss/smloop.c (revision 09dde2cf)
1 /*
2  * PROJECT:         ReactOS Windows-Compatible Session Manager
3  * LICENSE:         BSD 2-Clause License
4  * FILE:            base/system/smss/smloop.c
5  * PURPOSE:         Main SMSS Code
6  * PROGRAMMERS:     Alex Ionescu
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "smss.h"
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 /* GLOBALS ********************************************************************/
17 
18 typedef struct _SMP_CLIENT_CONTEXT
19 {
20     PSMP_SUBSYSTEM Subsystem;
21     HANDLE ProcessHandle;
22     HANDLE PortHandle;
23     PVOID Reserved;
24 } SMP_CLIENT_CONTEXT, *PSMP_CLIENT_CONTEXT;
25 
26 typedef
27 NTSTATUS
28 (NTAPI *PSM_API_HANDLER)(
29     _In_ PSM_API_MSG SmApiMsg,
30     _In_ PSMP_CLIENT_CONTEXT ClientContext,
31     _In_ HANDLE SmApiPort);
32 
33 volatile LONG SmTotalApiThreads;
34 HANDLE SmUniqueProcessId;
35 
36 /* API HANDLERS ***************************************************************/
37 
38 NTSTATUS
39 NTAPI
40 SmpCreateForeignSession(IN PSM_API_MSG SmApiMsg,
41                         IN PSMP_CLIENT_CONTEXT ClientContext,
42                         IN HANDLE SmApiPort)
43 {
44     DPRINT1("%s is not yet implemented\n", __FUNCTION__);
45     return STATUS_NOT_IMPLEMENTED;
46 }
47 
48 NTSTATUS
49 NTAPI
50 SmpSessionComplete(IN PSM_API_MSG SmApiMsg,
51                    IN PSMP_CLIENT_CONTEXT ClientContext,
52                    IN HANDLE SmApiPort)
53 {
54     DPRINT1("%s is not yet implemented\n", __FUNCTION__);
55     return STATUS_NOT_IMPLEMENTED;
56 }
57 
58 NTSTATUS
59 NTAPI
60 SmpTerminateForeignSession(IN PSM_API_MSG SmApiMsg,
61                            IN PSMP_CLIENT_CONTEXT ClientContext,
62                            IN HANDLE SmApiPort)
63 {
64     DPRINT1("%s is not yet implemented\n", __FUNCTION__);
65     return STATUS_NOT_IMPLEMENTED;
66 }
67 
68 NTSTATUS
69 NTAPI
70 SmpExecPgm(IN PSM_API_MSG SmApiMsg,
71            IN PSMP_CLIENT_CONTEXT ClientContext,
72            IN HANDLE SmApiPort)
73 {
74     HANDLE ProcessHandle;
75     NTSTATUS Status;
76     PSM_EXEC_PGM_MSG SmExecPgm;
77     RTL_USER_PROCESS_INFORMATION ProcessInformation;
78     OBJECT_ATTRIBUTES ObjectAttributes;
79 
80     /* Open the client process */
81     InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
82     Status = NtOpenProcess(&ProcessHandle,
83                            PROCESS_DUP_HANDLE,
84                            &ObjectAttributes,
85                            &SmApiMsg->h.ClientId);
86     if (!NT_SUCCESS(Status))
87     {
88         /* Fail */
89         DPRINT1("SmExecPgm: NtOpenProcess Failed %lx\n", Status);
90         return Status;
91     }
92 
93     /* Copy the process information out of the message */
94     SmExecPgm = &SmApiMsg->u.ExecPgm;
95     ProcessInformation = SmExecPgm->ProcessInformation;
96 
97     /* Duplicate the process handle */
98     Status = NtDuplicateObject(ProcessHandle,
99                                SmExecPgm->ProcessInformation.ProcessHandle,
100                                NtCurrentProcess(),
101                                &ProcessInformation.ProcessHandle,
102                                PROCESS_ALL_ACCESS,
103                                0,
104                                0);
105     if (!NT_SUCCESS(Status))
106     {
107         /* Close the handle and fail */
108         NtClose(ProcessHandle);
109         DPRINT1("SmExecPgm: NtDuplicateObject (Process) Failed %lx\n", Status);
110         return Status;
111     }
112 
113     /* Duplicate the thread handle */
114     Status = NtDuplicateObject(ProcessHandle,
115                                SmExecPgm->ProcessInformation.ThreadHandle,
116                                NtCurrentProcess(),
117                                &ProcessInformation.ThreadHandle,
118                                THREAD_ALL_ACCESS,
119                                0,
120                                0);
121     if (!NT_SUCCESS(Status))
122     {
123         /* Close both handles and fail */
124         NtClose(ProcessInformation.ProcessHandle);
125         NtClose(ProcessHandle);
126         DPRINT1("SmExecPgm: NtDuplicateObject (Thread) Failed %lx\n", Status);
127         return Status;
128     }
129 
130     /* Close the process handle and call the internal client API */
131     NtClose(ProcessHandle);
132     return SmpSbCreateSession(NULL,
133                               NULL,
134                               &ProcessInformation,
135                               0,
136                               SmExecPgm->DebugFlag ? &SmApiMsg->h.ClientId : NULL);
137 }
138 
139 NTSTATUS
140 NTAPI
141 SmpLoadDeferedSubsystem(
142     _In_ PSM_API_MSG SmApiMsg,
143     _In_ PSMP_CLIENT_CONTEXT ClientContext,
144     _In_ HANDLE SmApiPort)
145 {
146     NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
147     PSM_LOAD_DEFERED_SUBSYSTEM_MSG SmLoadDefered = &SmApiMsg->u.LoadDefered;
148     UNICODE_STRING DeferedSubsystem;
149     ULONG MuSessionId;
150     PLIST_ENTRY NextEntry;
151     PSMP_REGISTRY_VALUE RegEntry;
152 
153     /* Validate DeferedSubsystem's length */
154     if ((SmLoadDefered->Length <= 0) ||
155         (SmLoadDefered->Length > sizeof(SmLoadDefered->Buffer)))
156     {
157         return STATUS_INVALID_PARAMETER;
158     }
159 
160     /* Get the name of the subsystem to start */
161     DeferedSubsystem.Length = (USHORT)SmLoadDefered->Length;
162     DeferedSubsystem.MaximumLength = DeferedSubsystem.Length;
163     DeferedSubsystem.Buffer = SmLoadDefered->Buffer;
164 
165     /* Find a subsystem responsible for this session */
166     SmpGetProcessMuSessionId(ClientContext->ProcessHandle, &MuSessionId);
167     if (!SmpCheckDuplicateMuSessionId(MuSessionId))
168     {
169         DPRINT1("SMSS: Deferred subsystem load (%wZ) for MuSessionId %u, status=0x%x\n",
170                 &DeferedSubsystem, MuSessionId, Status);
171         return Status;
172     }
173 
174     /* Now process the deferred subsystems list */
175     for (NextEntry = SmpSubSystemsToDefer.Flink;
176          NextEntry != &SmpSubSystemsToDefer;
177          NextEntry = NextEntry->Flink)
178     {
179         /* Get each entry and check if it's the subsystem we are looking for */
180         RegEntry = CONTAINING_RECORD(NextEntry, SMP_REGISTRY_VALUE, Entry);
181         if (RtlEqualUnicodeString(&RegEntry->Name, &DeferedSubsystem, TRUE))
182         {
183             // TODO: One may want to extra-flag the command for
184             // specific POSIX or OS2 processing...
185 
186             /* Load the deferred subsystem */
187             Status = SmpExecuteCommand(&RegEntry->Value,
188                                        MuSessionId,
189                                        NULL,
190                                        SMP_SUBSYSTEM_FLAG);
191             if (!NT_SUCCESS(Status))
192                 DPRINT1("SMSS: Subsystem execute failed (%wZ)\n", &RegEntry->Value);
193 
194             break;
195         }
196     }
197 
198     /* Return status */
199     return Status;
200 }
201 
202 NTSTATUS
203 NTAPI
204 SmpStartCsr(IN PSM_API_MSG SmApiMsg,
205             IN PSMP_CLIENT_CONTEXT ClientContext,
206             IN HANDLE SmApiPort)
207 {
208     PSM_START_CSR_MSG SmStartCsr = &SmApiMsg->u.StartCsr;
209     UNICODE_STRING InitialCommand;
210     HANDLE InitialCommandProcess, InitialCommandProcessId, WindowsSubSysProcessId;
211     NTSTATUS Status;
212 
213     Status = SmpLoadSubSystemsForMuSession(&SmStartCsr->MuSessionId,
214                                            &WindowsSubSysProcessId,
215                                            &InitialCommand);
216     if (!NT_SUCCESS(Status))
217     {
218         DPRINT1("SMSS: SmpLoadSubSystemsForMuSession failed with status 0x%08x\n", Status);
219         return Status;
220     }
221 
222     if (SmStartCsr->Length)
223     {
224         InitialCommand.Length = InitialCommand.MaximumLength = SmStartCsr->Length;
225         InitialCommand.Buffer = SmStartCsr->Buffer;
226     }
227 
228     Status = SmpExecuteInitialCommand(SmStartCsr->MuSessionId,
229                                       &InitialCommand,
230                                       &InitialCommandProcess,
231                                       &InitialCommandProcessId);
232     if (!NT_SUCCESS(Status))
233     {
234         DPRINT1("SMSS: SmpExecuteInitialCommand failed with status 0x%08x\n", Status);
235         /* FIXME: undo effects of SmpLoadSubSystemsForMuSession */
236         ASSERT(FALSE);
237         return Status;
238     }
239 
240     NtClose(InitialCommandProcess);
241 
242     SmStartCsr->WindowsSubSysProcessId = WindowsSubSysProcessId;
243     SmStartCsr->SmpInitialCommandProcessId = InitialCommandProcessId;
244 
245     return STATUS_SUCCESS;
246 }
247 
248 NTSTATUS
249 NTAPI
250 SmpStopCsr(IN PSM_API_MSG SmApiMsg,
251            IN PSMP_CLIENT_CONTEXT ClientContext,
252            IN HANDLE SmApiPort)
253 {
254     DPRINT1("%s is not yet implemented\n", __FUNCTION__);
255     return STATUS_NOT_IMPLEMENTED;
256 }
257 
258 PSM_API_HANDLER SmpApiDispatch[SmpMaxApiNumber - SmpCreateForeignSessionApi] =
259 {
260     SmpCreateForeignSession,
261     SmpSessionComplete,
262     SmpTerminateForeignSession,
263     SmpExecPgm,
264     SmpLoadDeferedSubsystem,
265     SmpStartCsr,
266     SmpStopCsr
267 };
268 
269 /* FUNCTIONS ******************************************************************/
270 
271 NTSTATUS
272 NTAPI
273 SmpHandleConnectionRequest(IN HANDLE SmApiPort,
274                            IN PSB_API_MSG SbApiMsg)
275 {
276     BOOLEAN Accept = TRUE;
277     HANDLE PortHandle, ProcessHandle;
278     ULONG SessionId;
279     UNICODE_STRING SubsystemPort;
280     PSMP_CLIENT_CONTEXT ClientContext;
281     NTSTATUS Status;
282     OBJECT_ATTRIBUTES ObjectAttributes;
283     REMOTE_PORT_VIEW PortView;
284     SECURITY_QUALITY_OF_SERVICE SecurityQos;
285     PSMP_SUBSYSTEM CidSubsystem, TypeSubsystem;
286 
287     /* Initialize QoS data */
288     SecurityQos.ImpersonationLevel = SecurityIdentification;
289     SecurityQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
290     SecurityQos.EffectiveOnly = TRUE;
291 
292     /* Check if this is SM connecting to itself */
293     if (SbApiMsg->h.ClientId.UniqueProcess == SmUniqueProcessId)
294     {
295         /* No need to get any handle -- assume session 0 */
296         ProcessHandle = NULL;
297         SessionId = 0;
298     }
299     else
300     {
301         /* Reference the foreign process */
302         InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
303         Status = NtOpenProcess(&ProcessHandle,
304                                PROCESS_QUERY_INFORMATION,
305                                &ObjectAttributes,
306                                &SbApiMsg->h.ClientId);
307         if (!NT_SUCCESS(Status)) Accept = FALSE;
308 
309         /* Get its session ID */
310         SmpGetProcessMuSessionId(ProcessHandle, &SessionId);
311     }
312 
313     /* See if we already know about the caller's subsystem */
314     CidSubsystem = SmpLocateKnownSubSysByCid(&SbApiMsg->h.ClientId);
315     if ((CidSubsystem) && (Accept))
316     {
317         /* Check if we already have a subsystem for this kind of image */
318         TypeSubsystem = SmpLocateKnownSubSysByType(SessionId,
319                                                    SbApiMsg->ConnectionInfo.SubsystemType);
320         if (TypeSubsystem == CidSubsystem)
321         {
322             /* Someone is trying to take control of an existing subsystem, fail */
323             Accept = FALSE;
324             DPRINT1("SMSS: Connection from SubSystem rejected\n");
325             DPRINT1("SMSS: Image type already being served\n");
326         }
327         else
328         {
329             /* Set this image type as the type for this subsystem */
330             CidSubsystem->ImageType = SbApiMsg->ConnectionInfo.SubsystemType;
331         }
332 
333         /* Drop the reference we had acquired */
334         if (TypeSubsystem) SmpDereferenceSubsystem(TypeSubsystem);
335     }
336 
337     /* Check if we'll be accepting the connection */
338     if (Accept)
339     {
340         /* We will, so create a client context for it */
341         ClientContext = RtlAllocateHeap(SmpHeap, 0, sizeof(SMP_CLIENT_CONTEXT));
342         if (ClientContext)
343         {
344             ClientContext->ProcessHandle = ProcessHandle;
345             ClientContext->Subsystem = CidSubsystem;
346             ClientContext->Reserved = NULL;
347             ClientContext->PortHandle = NULL;
348         }
349         else
350         {
351             /* Failed to allocate a client context, so reject the connection */
352             DPRINT1("Rejecting connection due to lack of memory\n");
353             Accept = FALSE;
354         }
355     }
356     else
357     {
358         /* Use a bogus context since we're going to reject the message */
359         ClientContext = (PSMP_CLIENT_CONTEXT)SbApiMsg;
360     }
361 
362     /* Now send the actual accept reply (which could be a rejection) */
363     PortView.Length = sizeof(PortView);
364     Status = NtAcceptConnectPort(&PortHandle,
365                                  ClientContext,
366                                  &SbApiMsg->h,
367                                  Accept,
368                                  NULL,
369                                  &PortView);
370     if (!(Accept) || !(NT_SUCCESS(Status)))
371     {
372         /* Close the process handle, reference the subsystem, and exit */
373         DPRINT1("Accept failed or rejected: %lx\n", Status);
374         if (ClientContext != (PVOID)SbApiMsg) RtlFreeHeap(SmpHeap, 0, ClientContext);
375         if (ProcessHandle) NtClose(ProcessHandle);
376         if (CidSubsystem) SmpDereferenceSubsystem(CidSubsystem);
377         return Status;
378     }
379 
380     /* Save the port handle now that we've accepted it */
381     if (ClientContext) ClientContext->PortHandle = PortHandle;
382     if (CidSubsystem) CidSubsystem->PortHandle = PortHandle;
383 
384     /* Complete the port connection */
385     Status = NtCompleteConnectPort(PortHandle);
386     if ((NT_SUCCESS(Status)) && (CidSubsystem))
387     {
388         /* This was an actual subsystem, so connect back to it */
389         SbApiMsg->ConnectionInfo.SbApiPortName[119] = UNICODE_NULL;
390         RtlCreateUnicodeString(&SubsystemPort,
391                                SbApiMsg->ConnectionInfo.SbApiPortName);
392         Status = NtConnectPort(&CidSubsystem->SbApiPort,
393                                &SubsystemPort,
394                                &SecurityQos,
395                                NULL,
396                                NULL,
397                                NULL,
398                                NULL,
399                                NULL);
400         if (!NT_SUCCESS(Status))
401         {
402             DPRINT1("SMSS: Connect back to Sb %wZ failed %lx\n", &SubsystemPort, Status);
403         }
404         RtlFreeUnicodeString(&SubsystemPort);
405 
406         /* Now that we're connected, signal the event handle */
407         NtSetEvent(CidSubsystem->Event, NULL);
408     }
409     else if (CidSubsystem)
410     {
411         /* We failed to complete the connection, so clear the port handle */
412         DPRINT1("Completing the connection failed: %lx\n", Status);
413         CidSubsystem->PortHandle = NULL;
414     }
415 
416     /* Dereference the subsystem and return the result */
417     if (CidSubsystem) SmpDereferenceSubsystem(CidSubsystem);
418     return Status;
419 }
420 
421 ULONG
422 NTAPI
423 SmpApiLoop(IN PVOID Parameter)
424 {
425     HANDLE SmApiPort = (HANDLE)Parameter;
426     NTSTATUS Status;
427     PSMP_CLIENT_CONTEXT ClientContext;
428     PSM_API_MSG ReplyMsg = NULL;
429     SM_API_MSG RequestMsg;
430     PROCESS_BASIC_INFORMATION ProcessInformation;
431     LARGE_INTEGER Timeout;
432 
433     /* Increase the number of API threads for throttling code for later */
434     _InterlockedExchangeAdd(&SmTotalApiThreads, 1);
435 
436     /* Mark us critical */
437     RtlSetThreadIsCritical(TRUE, NULL, TRUE);
438 
439     /* Set the PID of the SM process itself for later checking */
440     NtQueryInformationProcess(NtCurrentProcess(),
441                               ProcessBasicInformation,
442                               &ProcessInformation,
443                               sizeof(ProcessInformation),
444                               NULL);
445     SmUniqueProcessId = (HANDLE)ProcessInformation.UniqueProcessId;
446 
447     /* Now process incoming messages */
448     while (TRUE)
449     {
450         /* Begin waiting on a request */
451         Status = NtReplyWaitReceivePort(SmApiPort,
452                                         (PVOID*)&ClientContext,
453                                         &ReplyMsg->h,
454                                         &RequestMsg.h);
455         if (Status == STATUS_NO_MEMORY)
456         {
457             /* Ran out of memory, so do a little timeout and try again */
458             if (ReplyMsg) DPRINT1("SMSS: Failed to reply to calling thread, retrying.\n");
459             Timeout.QuadPart = -50000000;
460             NtDelayExecution(FALSE, &Timeout);
461             continue;
462         }
463 
464         /* Check what kind of request we received */
465         switch (RequestMsg.h.u2.s2.Type)
466         {
467             /* A new connection */
468             case LPC_CONNECTION_REQUEST:
469                 /* Create the right structures for it */
470                 SmpHandleConnectionRequest(SmApiPort, (PSB_API_MSG)&RequestMsg);
471                 ReplyMsg = NULL;
472                 break;
473 
474             /* A closed connection */
475             case LPC_PORT_CLOSED:
476                 /* Destroy any state we had for this client */
477                 DPRINT1("Port closed\n");
478                 //if (ClientContext) SmpPushDeferredClientContext(ClientContext);
479                 ReplyMsg = NULL;
480                 break;
481 
482             /* An actual API message */
483             default:
484                 if (!ClientContext)
485                 {
486                     ReplyMsg = NULL;
487                     break;
488                 }
489 
490                 RequestMsg.ReturnValue = STATUS_PENDING;
491 
492                 /* Check if the API is valid */
493                 if (RequestMsg.ApiNumber >= SmpMaxApiNumber)
494                 {
495                     /* It isn't, fail */
496                     DPRINT1("Invalid API: %lx\n", RequestMsg.ApiNumber);
497                     Status = STATUS_NOT_IMPLEMENTED;
498                 }
499                 else if ((RequestMsg.ApiNumber <= SmpTerminateForeignSessionApi) &&
500                          !(ClientContext->Subsystem))
501                 {
502                     /* It's valid, but doesn't have a subsystem with it */
503                     DPRINT1("Invalid session API\n");
504                     Status = STATUS_INVALID_PARAMETER;
505                 }
506                 else
507                 {
508                     /* It's totally okay, so call the dispatcher for it */
509                     Status = SmpApiDispatch[RequestMsg.ApiNumber](&RequestMsg,
510                                                                   ClientContext,
511                                                                   SmApiPort);
512                 }
513 
514                 /* Write the result value and return the message back */
515                 RequestMsg.ReturnValue = Status;
516                 ReplyMsg = &RequestMsg;
517                 break;
518         }
519     }
520     return STATUS_SUCCESS;
521 }
522