windows-nt/Source/XPSP1/NT/base/subsys/sm/server/smloop.c
2020-09-26 16:20:57 +08:00

794 lines
21 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
smloop.c
Abstract:
Session Manager Listen and API loops
Author:
Mark Lucovsky (markl) 04-Oct-1989
Revision History:
--*/
#include "smsrvp.h"
#include <ntosp.h> // Only for the interlocked functions.
#define SM_WORKER_THREADS_LIMIT 4
ULONG_PTR SmUniqueProcessId;
LONG SmpCurrentThreadsLimit = SM_WORKER_THREADS_LIMIT;
LONG SmWorkerThreadsAvailable = 0;
LONG SmTotalApiThreads = 0;
PSMP_CLIENT_CONTEXT SmpDeferredFreeList = NULL;
NTSTATUS
SmpHandleConnectionRequest(
IN HANDLE ConnectionPort,
IN PSBAPIMSG Message
);
PSMAPI SmpApiDispatch[SmMaxApiNumber] = {
SmpCreateForeignSession,
SmpSessionComplete,
SmpTerminateForeignSession,
SmpExecPgm,
SmpLoadDeferedSubsystem,
SmpStartCsr,
SmpStopCsr
};
#if DBG
PSZ SmpApiName[ SmMaxApiNumber+1 ] = {
"SmCreateForeignSession",
"SmSessionComplete",
"SmTerminateForeignSession",
"SmExecPgm",
"SmLoadDeferedSubsystem",
"SmStartCsr",
"SmStopCsr",
"Unknown Sm Api Number"
};
#endif // DBG
EXCEPTION_DISPOSITION
DbgpUnhandledExceptionFilter(
struct _EXCEPTION_POINTERS *ExceptionInfo
);
VOID
SmpFlushDeferredList()
{
PSMP_CLIENT_CONTEXT Head = SmpDeferredFreeList;
SmpDeferredFreeList = NULL;
while (Head != NULL) {
PSMP_CLIENT_CONTEXT ClientContext = Head;
NTSTATUS Status;
Head = Head->Link;
if (ClientContext->ClientProcessHandle) {
Status = NtClose( ClientContext->ClientProcessHandle );
ASSERT(NT_SUCCESS(Status));
}
Status = NtClose( ClientContext->ServerPortHandle );
ASSERT(NT_SUCCESS(Status));
RtlFreeHeap( SmpHeap, 0, ClientContext );
}
SmpCurrentThreadsLimit = SM_WORKER_THREADS_LIMIT;
}
VOID
SmpPushDeferredClientContext(
PSMP_CLIENT_CONTEXT ClientContext
)
{
PSMP_CLIENT_CONTEXT CapturedHead;
do {
CapturedHead = SmpDeferredFreeList;
ClientContext->Link = CapturedHead;
} while ( InterlockedCompareExchangePointer(&SmpDeferredFreeList, ClientContext, CapturedHead) != CapturedHead );
SmpCurrentThreadsLimit = 1;
}
NTSTATUS
SmpApiLoop (
IN PVOID ThreadParameter
)
/*++
Routine Description:
This is the main Session Manager API Loop. It
services session manager API requests.
Arguments:
ThreadParameter - Supplies a handle to the API port used
to receive session manager API requests.
Return Value:
None.
--*/
{
PSMAPIMSG SmApiReplyMsg;
SMMESSAGE_SIZE MsgBuf;
PSMAPIMSG SmApiMsg;
NTSTATUS Status;
HANDLE ConnectionPort;
PSMP_CLIENT_CONTEXT ClientContext;
PSMPKNOWNSUBSYS KnownSubSys;
PROCESS_BASIC_INFORMATION ProcessInfo;
InterlockedIncrement(&SmTotalApiThreads);
RtlSetThreadIsCritical(TRUE, NULL, TRUE);
NtQueryInformationProcess( NtCurrentProcess(),
ProcessBasicInformation,
&ProcessInfo,
sizeof(ProcessInfo),
NULL );
SmUniqueProcessId = ProcessInfo.UniqueProcessId;
ConnectionPort = (HANDLE) ThreadParameter;
SmApiMsg = (PSMAPIMSG)&MsgBuf;
SmApiReplyMsg = NULL;
try {
for(;;) {
{
LONG CapturedThreads;
do {
CapturedThreads = SmWorkerThreadsAvailable;
if (CapturedThreads >= SmpCurrentThreadsLimit) {
if (SmApiReplyMsg) {
Status = NtReplyPort(
ConnectionPort,
(PPORT_MESSAGE) SmApiReplyMsg
);
}
InterlockedDecrement(&SmTotalApiThreads);
RtlSetThreadIsCritical(FALSE, NULL, TRUE);
RtlExitUserThread(STATUS_SUCCESS);
//
// RtlExitUserThread never returns
//
}
} while ( InterlockedCompareExchange(&SmWorkerThreadsAvailable, CapturedThreads + 1, CapturedThreads) != CapturedThreads);
}
if (SmTotalApiThreads == 1) {
SmpFlushDeferredList();
}
Status = NtReplyWaitReceivePort(
ConnectionPort,
(PVOID *) &ClientContext,
(PPORT_MESSAGE) SmApiReplyMsg,
(PPORT_MESSAGE) SmApiMsg
);
//
// Launching at the same time a several subsystems can deadlock smss
// if it has only two worker threads.
// We create more threads if there is no server thread available
//
if (InterlockedDecrement(&SmWorkerThreadsAvailable) == 0) {
NTSTATUS st = RtlCreateUserThread(
NtCurrentProcess(),
NULL,
FALSE,
0L,
0L,
0L,
SmpApiLoop,
(PVOID) ThreadParameter,
NULL,
NULL
);
}
if ( !NT_SUCCESS(Status) ) {
SmApiReplyMsg = NULL;
continue;
} else if ( SmApiMsg->h.u2.s2.Type == LPC_CONNECTION_REQUEST ) {
SmpHandleConnectionRequest( ConnectionPort,
(PSBAPIMSG) SmApiMsg
);
SmApiReplyMsg = NULL;
} else if ( SmApiMsg->h.u2.s2.Type == LPC_PORT_CLOSED ) {
if (ClientContext) {
SmpPushDeferredClientContext(ClientContext);
}
SmApiReplyMsg = NULL;
} else {
if ( !ClientContext ) {
SmApiReplyMsg = NULL;
continue;
}
KnownSubSys = ClientContext->KnownSubSys;
SmApiMsg->ReturnedStatus = STATUS_PENDING;
if ((ULONG) SmApiMsg->ApiNumber >= (ULONG) SmMaxApiNumber ) {
Status = STATUS_NOT_IMPLEMENTED;
} else {
switch (SmApiMsg->ApiNumber) {
case SmExecPgmApi :
Status = (SmpApiDispatch[SmApiMsg->ApiNumber])(
SmApiMsg,
ClientContext,
ConnectionPort);
break;
case SmLoadDeferedSubsystemApi :
Status = (SmpApiDispatch[SmApiMsg->ApiNumber])(
SmApiMsg,
ClientContext,
ConnectionPort);
break;
case SmStopCsrApi :
case SmStartCsrApi :
//
// These Api's can only be called from a system process
//
if (ClientContext->SecurityContext == UNKNOWN_CONTEXT) {
//
// Initialize the client security context
//
ClientContext->SecurityContext =
SmpClientSecurityContext ((PPORT_MESSAGE)SmApiMsg,
ClientContext->ServerPortHandle);
}
if (ClientContext->SecurityContext == SYSTEM_CONTEXT) {
Status = (SmpApiDispatch[SmApiMsg->ApiNumber])(
SmApiMsg,
ClientContext,
ConnectionPort);
} else {
#if DBG
KdPrint(("SMSS: Calling Sm Terminal Server Api from Non-System context.Status = STATUS_ACCESS_DENIED\n"));
#endif
Status = STATUS_ACCESS_DENIED;
}
break;
case SmCreateForeignSessionApi :
case SmSessionCompleteApi :
case SmTerminateForeignSessionApi :
if (!KnownSubSys) {
Status = STATUS_INVALID_PARAMETER;
} else {
Status =
(SmpApiDispatch[SmApiMsg->ApiNumber])(
SmApiMsg,
ClientContext,
ConnectionPort);
}
break;
}
}
SmApiMsg->ReturnedStatus = Status;
SmApiReplyMsg = SmApiMsg;
}
}
} except (DbgpUnhandledExceptionFilter( GetExceptionInformation() )) {
;
}
//
// Make the compiler happy
//
return STATUS_UNSUCCESSFUL;
}
NTSTATUS
SmpHandleConnectionRequest(
IN HANDLE ConnectionPort,
IN PSBAPIMSG Message
)
/*++
Routine Description:
This routine handles connection requests from either known subsystems,
or other clients. Other clients are admin processes.
The protocol for connection from a known subsystem is:
capture the name of the subsystem's Sb API port
Accept the connection
Connect to the subsystems Sb API port
Store the communication port handle in the known subsystem database
signal the event associated with the known subsystem
The protocol for others is to simply validate and accept the connection
request.
Arguments:
Return Value:
None.
--*/
{
NTSTATUS st;
HANDLE CommunicationPort;
REMOTE_PORT_VIEW ClientView;
PSBCONNECTINFO ConnectInfo;
ULONG ConnectInfoLength;
PSMPKNOWNSUBSYS KnownSubSys, KnownSubSys2;
BOOLEAN Accept;
UNICODE_STRING SubSystemPort;
SECURITY_QUALITY_OF_SERVICE DynamicQos;
PSMP_CLIENT_CONTEXT ClientContext;
OBJECT_ATTRIBUTES ObjA;
HANDLE ClientProcessHandle=NULL;
ULONG MuSessionId = 0;
//
// Set up the security quality of service parameters to use over the
// sb API port. Use the most efficient (least overhead) - which is dynamic
// rather than static tracking.
//
DynamicQos.ImpersonationLevel = SecurityIdentification;
DynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
DynamicQos.EffectiveOnly = TRUE;
Accept = TRUE; // Assume we will accept
//
// Get MuSessionId of the client if session manager is not connecting to itself
//
if ( (ULONG_PTR) Message->h.ClientId.UniqueProcess == SmUniqueProcessId ) {
KnownSubSys = NULL;
ClientProcessHandle = NULL;
} else {
InitializeObjectAttributes( &ObjA, NULL, 0, NULL, NULL );
st = NtOpenProcess( &ClientProcessHandle, PROCESS_QUERY_INFORMATION,
&ObjA, &Message->h.ClientId );
if (NT_SUCCESS (st)) {
SmpGetProcessMuSessionId( ClientProcessHandle, &MuSessionId );
} else {
Accept = FALSE;
}
}
ConnectInfo = &Message->ConnectionRequest;
KnownSubSys = SmpLocateKnownSubSysByCid(&Message->h.ClientId);
if ( (KnownSubSys) && (Accept == TRUE) ) {
KnownSubSys2 = SmpLocateKnownSubSysByType(MuSessionId, ConnectInfo->SubsystemImageType);
if (KnownSubSys2 == KnownSubSys ) {
Accept = FALSE;
KdPrint(("SMSS: Connection from SubSystem rejected\n"));
KdPrint(("SMSS: Image type already being served\n"));
} else {
KnownSubSys->ImageType = ConnectInfo->SubsystemImageType;
}
if (KnownSubSys2) {
RtlEnterCriticalSection( &SmpKnownSubSysLock );
SmpDeferenceKnownSubSys(KnownSubSys2);
RtlLeaveCriticalSection( &SmpKnownSubSysLock );
}
}
if (Accept) {
ClientContext = RtlAllocateHeap(SmpHeap, MAKE_TAG( SM_TAG ), sizeof(SMP_CLIENT_CONTEXT));
if ( ClientContext ) {
ClientContext->ClientProcessHandle = ClientProcessHandle;
ClientContext->KnownSubSys = KnownSubSys;
//
// The sm apis used by Terminal Server to start and stop CSR
// do not get called from known subsystems and are restricted
// to system processes only.
//
ClientContext->SecurityContext = UNKNOWN_CONTEXT;
ClientContext->ServerPortHandle = NULL;
} else {
Accept = FALSE;
}
}
ClientView.Length = sizeof(ClientView);
st = NtAcceptConnectPort(
&CommunicationPort,
ClientContext,
(PPORT_MESSAGE)Message,
Accept,
NULL,
&ClientView
);
if ( Accept ) {
if (NT_SUCCESS (st)) {
if (ClientContext) {
ClientContext->ServerPortHandle = CommunicationPort;
}
if ( KnownSubSys ) {
KnownSubSys->SmApiCommunicationPort = CommunicationPort;
}
st = NtCompleteConnectPort(CommunicationPort);
if (!NT_SUCCESS(st)) {
if ( KnownSubSys ) {
KnownSubSys->SmApiCommunicationPort = NULL;
RtlEnterCriticalSection( &SmpKnownSubSysLock );
SmpDeferenceKnownSubSys(KnownSubSys);
RtlLeaveCriticalSection( &SmpKnownSubSysLock );
}
return st;
}
//
// Connect Back to subsystem.
//
if ( KnownSubSys ) {
ConnectInfo->EmulationSubSystemPortName[
sizeof (ConnectInfo->EmulationSubSystemPortName)/sizeof (WCHAR) - 1] = '\0';
RtlCreateUnicodeString( &SubSystemPort,
ConnectInfo->EmulationSubSystemPortName
);
ConnectInfoLength = sizeof( *ConnectInfo );
st = NtConnectPort(
&KnownSubSys->SbApiCommunicationPort,
&SubSystemPort,
&DynamicQos,
NULL,
NULL,
NULL,
NULL,
NULL
);
if ( !NT_SUCCESS(st) ) {
KdPrint(("SMSS: Connect back to Sb %wZ failed %lx\n",&SubSystemPort,st));
}
RtlFreeUnicodeString( &SubSystemPort );
NtSetEvent(KnownSubSys->Active,NULL);
}
} else {
if (ClientProcessHandle) {
NtClose( ClientProcessHandle );
}
RtlFreeHeap( SmpHeap, 0, ClientContext );
}
} else {
if (ClientProcessHandle) {
NtClose( ClientProcessHandle );
}
}
if (KnownSubSys) {
RtlEnterCriticalSection( &SmpKnownSubSysLock );
SmpDeferenceKnownSubSys(KnownSubSys);
RtlLeaveCriticalSection( &SmpKnownSubSysLock );
}
return st;
}
PSMPKNOWNSUBSYS
SmpLocateKnownSubSysByCid(
IN PCLIENT_ID ClientId
)
/*++
Routine Description:
This function scans the known subsystem table looking for
a matching client id (just UniqueProcess portion). If found,
than the connection request is from a known subsystem and
accept is always granted. Otherwise, it must be an administrative
process.
Arguments:
ClientId - Supplies the ClientId whose UniqueProcess field is to be used
in the known subsystem scan.
Return Value:
NULL - The ClientId does not match a known subsystem.
NON-NULL - Returns the address of the known subsystem.
--*/
{
PSMPKNOWNSUBSYS KnownSubSys = NULL;
PLIST_ENTRY Next;
//
// Acquire known subsystem lock.
//
RtlEnterCriticalSection(&SmpKnownSubSysLock);
Next = SmpKnownSubSysHead.Flink;
while ( Next != &SmpKnownSubSysHead ) {
KnownSubSys = CONTAINING_RECORD(Next,SMPKNOWNSUBSYS,Links);
Next = Next->Flink;
if ( (KnownSubSys->InitialClientId.UniqueProcess == ClientId->UniqueProcess) &&
!KnownSubSys->Deleting ) {
SmpReferenceKnownSubSys(KnownSubSys);
break;
} else {
KnownSubSys = NULL;
}
}
//
// Unlock known subsystems.
//
RtlLeaveCriticalSection(&SmpKnownSubSysLock);
return KnownSubSys;
}
PSMPKNOWNSUBSYS
SmpLocateKnownSubSysByType(
IN ULONG MuSessionId,
IN ULONG ImageType
)
/*++
Routine Description:
This function scans the known subsystem table looking for
a matching image type.
Arguments:
ImageType - Supplies the image type whose subsystem is to be located.
Return Value:
NULL - The image type does not match a known subsystem.
NON-NULL - Returns the address of the known subsystem.
--*/
{
PSMPKNOWNSUBSYS KnownSubSys = NULL;
PLIST_ENTRY Next;
//
// Aquire known subsystem lock
//
RtlEnterCriticalSection(&SmpKnownSubSysLock);
Next = SmpKnownSubSysHead.Flink;
while ( Next != &SmpKnownSubSysHead ) {
KnownSubSys = CONTAINING_RECORD(Next,SMPKNOWNSUBSYS,Links);
Next = Next->Flink;
if ( (KnownSubSys->ImageType == ImageType) &&
!KnownSubSys->Deleting &&
(KnownSubSys->MuSessionId == MuSessionId) ) {
SmpReferenceKnownSubSys(KnownSubSys);
break;
} else {
KnownSubSys = NULL;
}
}
//
// Unlock known subsystems.
//
RtlLeaveCriticalSection(&SmpKnownSubSysLock);
return KnownSubSys;
}
ENUMSECURITYCONTEXT
SmpClientSecurityContext (
IN PPORT_MESSAGE Message,
IN HANDLE ServerPortHandle
)
/*++
Routine Description:
Impersonate the client and check if it is running under system security context
Arguments:
PPORT_MESSAGE - LPC message pointer
ServerPortHandle - LPC Port Handle
Return Value:
SYSTEM_CONTEXT - Client is running under system LUID
NONSYSTEM_CONTEXT - Failure or client is not running under system LUID
--*/
{
NTSTATUS NtStatus ;
HANDLE ImpersonationToken;
HANDLE TokenHandle;
TOKEN_STATISTICS TokenStatisticsInformation;
ULONG Size;
ENUMSECURITYCONTEXT retval = NONSYSTEM_CONTEXT;
LUID SystemAuthenticationId = SYSTEM_LUID;
NtStatus = NtImpersonateClientOfPort(ServerPortHandle,
Message);
if (!NT_SUCCESS(NtStatus)) {
#if DBG
KdPrint(( "SMSS: NtImpersonateClientOfPort failed: 0x%lX\n",
NtStatus)) ;
#endif
return NONSYSTEM_CONTEXT ;
}
//
// Get the Token Handle.
//
if (NT_SUCCESS(NtOpenThreadToken (NtCurrentThread(),
TOKEN_IMPERSONATE | TOKEN_QUERY,
FALSE,
&TokenHandle) == FALSE)) {
if (NT_SUCCESS(NtQueryInformationToken(
TokenHandle,
TokenStatistics,
&TokenStatisticsInformation,
sizeof(TokenStatisticsInformation),
&Size
))) {
if ( RtlEqualLuid ( &TokenStatisticsInformation.AuthenticationId,
&SystemAuthenticationId ) ) {
retval = SYSTEM_CONTEXT;
}
}
NtClose(TokenHandle);
} else {
#if DBG
KdPrint(( "SMSS: OpenThreadToken failed\n")) ;
#endif
}
//
//Revert to Self
//
ImpersonationToken = 0;
NtStatus = NtSetInformationThread(NtCurrentThread(),
ThreadImpersonationToken,
&ImpersonationToken,
sizeof(HANDLE));
#if DBG
if (!NT_SUCCESS(NtStatus)) {
KdPrint(( "SMSS: NtSetInformationThread : %lx\n", NtStatus));
}
#endif // DBG
return retval;
}