/*++ Copyright (c) 1989 Microsoft Corporation Module Name: lpcrecv.c Abstract: Local Inter-Process Communication (LPC) receive system services. Author: Steve Wood (stevewo) 15-May-1989 Revision History: --*/ #include "lpcp.h" #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,NtReplyWaitReceivePort) #pragma alloc_text(PAGE,NtReplyWaitReceivePortEx) #endif NTSTATUS NtReplyWaitReceivePort ( IN HANDLE PortHandle, OUT PVOID *PortContext OPTIONAL, IN PPORT_MESSAGE ReplyMessage OPTIONAL, OUT PPORT_MESSAGE ReceiveMessage ) /*++ Routine Description: This procedure is used by the server process to wait for a message from a client process A client and server process can receive messages using the NtReplyWaitReceivePort service: If the ReplyMessage parameter is specified, then the reply will be sent using NtReplyPort. If the PortHandle parameter specifies a connection port, then the receive will return whenever a message is sent to a server communication port that does not have its own receive queue and the message is therefore queued to the receive queue of the connection port. If the PortHandle parameter specifies a server communication port that does not have a receive queue, then behaves as if the associated connection port handle was specified. Otherwise the receive will return whenever message is placed in the receive queue associated with the server communication port. The received message will be returned in the variable specified by the ReceiveMessage parameter. If the MapInfoOffset field of the reply message is non-zero, then the PORT_MAP_INFORMATION structure it points to will be processed and the relevant pages will be mapped into the caller's address space. The service returns an error if there is not enough room in the caller's address space to accomodate the mappings. Arguments: PortHandle - Specifies the handle of the connection or communication port to do the receive from. PortContext - Specifies an optional pointer to a variable that is to receive the context value associated with the communication port that the message is being received from. This context variable was specified on the call to the NtAcceptConnectPort service. ReplyMessage - This optional parameter specifies the address of a reply message to be sent. The ClientId and MessageId fields determine which thread will get the reply. See description of NtReplyPort for how the reply is sent. The reply is sent before blocking for the receive. ReceiveMessage - Specifies the address of a variable to receive the message. Return Value: Status code that indicates whether or not the operation was successful. --*/ { PAGED_CODE(); return NtReplyWaitReceivePortEx (PortHandle, PortContext, ReplyMessage, ReceiveMessage, NULL); } NTSTATUS NtReplyWaitReceivePortEx( IN HANDLE PortHandle, OUT PVOID *PortContext OPTIONAL, IN PPORT_MESSAGE ReplyMessage OPTIONAL, OUT PPORT_MESSAGE ReceiveMessage, IN PLARGE_INTEGER Timeout OPTIONAL ) /*++ Routine Description: See NtReplyWaitReceivePort. Arguments: See NtReplyWaitReceivePort. Timeout - Supplies an optional timeout value to use when waiting for a receive. Return Value: See NtReplyWaitReceivePort. --*/ { PLPCP_PORT_OBJECT PortObject; PLPCP_PORT_OBJECT ReceivePort; PORT_MESSAGE CapturedReplyMessage; KPROCESSOR_MODE PreviousMode; KPROCESSOR_MODE WaitMode; NTSTATUS Status; PLPCP_MESSAGE Msg; PETHREAD CurrentThread; PETHREAD WakeupThread; LARGE_INTEGER TimeoutValue ; PLPCP_PORT_OBJECT ConnectionPort = NULL; PAGED_CODE(); CurrentThread = PsGetCurrentThread(); TimeoutValue.QuadPart = 0 ; // // Get previous processor mode // PreviousMode = KeGetPreviousMode(); WaitMode = PreviousMode; if (PreviousMode != KernelMode) { try { if (ARGUMENT_PRESENT( PortContext )) { ProbeForWriteUlong( (PULONG)PortContext ); } if (ARGUMENT_PRESENT( ReplyMessage)) { ProbeForReadSmallStructure( ReplyMessage, sizeof( *ReplyMessage ), sizeof( ULONG )); CapturedReplyMessage = *ReplyMessage; } if (ARGUMENT_PRESENT( Timeout )) { TimeoutValue = ProbeAndReadLargeInteger( Timeout ); Timeout = &TimeoutValue ; } ProbeForWriteSmallStructure( ReceiveMessage, sizeof( *ReceiveMessage ), sizeof( ULONG )); } except( EXCEPTION_EXECUTE_HANDLER ) { return GetExceptionCode(); } } else { // // Kernel mode threads call with wait mode of user so that their // kernel // stacks are swappable. Main consumer of this is // SepRmCommandThread // if ( IS_SYSTEM_THREAD(CurrentThread) ) { WaitMode = UserMode; } if (ARGUMENT_PRESENT( ReplyMessage)) { CapturedReplyMessage = *ReplyMessage; } } if (ARGUMENT_PRESENT( ReplyMessage)) { // // Make sure DataLength is valid with respect to header size and total // length // if ((((CLONG)CapturedReplyMessage.u1.s1.DataLength) + sizeof( PORT_MESSAGE )) > ((CLONG)CapturedReplyMessage.u1.s1.TotalLength)) { return STATUS_INVALID_PARAMETER; } // // Make sure the user didn't give us a bogus reply message id // if (CapturedReplyMessage.MessageId == 0) { return STATUS_INVALID_PARAMETER; } } // // Reference the port object by handle and if that doesn't work try // a waitable port object type // Status = LpcpReferencePortObject( PortHandle, 0, PreviousMode, &PortObject ); if (!NT_SUCCESS( Status )) { Status = ObReferenceObjectByHandle( PortHandle, 0, LpcWaitablePortObjectType, PreviousMode, &PortObject, NULL ); if ( !NT_SUCCESS( Status ) ) { return Status; } } // // Validate the message length // if (ARGUMENT_PRESENT( ReplyMessage )) { if (((ULONG)CapturedReplyMessage.u1.s1.TotalLength > PortObject->MaxMessageLength) || ((ULONG)CapturedReplyMessage.u1.s1.TotalLength <= (ULONG)CapturedReplyMessage.u1.s1.DataLength)) { ObDereferenceObject( PortObject ); return STATUS_PORT_MESSAGE_TOO_LONG; } } // // The receive port we use is either the connection port for the port // object we were given if we were given a client communication port then // we expect to receive the reply on the communication port itself. // if ((PortObject->Flags & PORT_TYPE) != CLIENT_COMMUNICATION_PORT) { LpcpAcquireLpcpLockByThread(CurrentThread); ConnectionPort = ReceivePort = PortObject->ConnectionPort; if (ConnectionPort == NULL) { LpcpReleaseLpcpLock(); ObDereferenceObject( PortObject ); return STATUS_PORT_DISCONNECTED; } ObReferenceObject( ConnectionPort ); LpcpReleaseLpcpLock(); } else { ReceivePort = PortObject; } // // If ReplyMessage argument present, then send reply // if (ARGUMENT_PRESENT( ReplyMessage )) { // // Translate the ClientId from the connection request into a // thread pointer. This is a referenced pointer to keep the thread // from evaporating out from under us. // Status = PsLookupProcessThreadByCid( &CapturedReplyMessage.ClientId, NULL, &WakeupThread ); if (!NT_SUCCESS( Status )) { ObDereferenceObject( PortObject ); if (ConnectionPort) { ObDereferenceObject(ConnectionPort); } return Status; } // // Acquire the global Lpc mutex that guards the LpcReplyMessage // field of the thread and get the pointer to the message that // the thread is waiting for a reply to. // Msg = (PLPCP_MESSAGE)LpcpAllocateFromPortZone( CapturedReplyMessage.u1.s1.TotalLength ); if (Msg == NULL) { if (ConnectionPort) { ObDereferenceObject(ConnectionPort); } ObDereferenceObject( WakeupThread ); ObDereferenceObject( PortObject ); return STATUS_NO_MEMORY; } LpcpAcquireLpcpLockByThread(CurrentThread); // // See if the thread is waiting for a reply to the message // specified on this call. If not then a bogus message // has been specified, so release the mutex, dereference the thread // and return failure. // // We also fail this request if the caller isn't replying to a request // message. For example, if the caller is replying to a connection // request // if ((WakeupThread->LpcReplyMessageId != CapturedReplyMessage.MessageId) || ((LpcpGetThreadMessage(WakeupThread) != NULL) && (LpcpGetThreadMessage(WakeupThread)->Request.u2.s2.Type & ~LPC_KERNELMODE_MESSAGE) != LPC_REQUEST) || (!LpcpValidateClientPort(WakeupThread, PortObject, LPCP_VALIDATE_REASON_REPLY))) { LpcpPrint(( "%s Attempted ReplyWaitReceive to Thread %lx (%s)\n", PsGetCurrentProcess()->ImageFileName, WakeupThread, THREAD_TO_PROCESS( WakeupThread )->ImageFileName )); LpcpPrint(( "failed. MessageId == %u Client Id: %x.%x\n", CapturedReplyMessage.MessageId, CapturedReplyMessage.ClientId.UniqueProcess, CapturedReplyMessage.ClientId.UniqueThread )); LpcpPrint(( " Thread MessageId == %u Client Id: %x.%x\n", WakeupThread->LpcReplyMessageId, WakeupThread->Cid.UniqueProcess, WakeupThread->Cid.UniqueThread )); #if DBG if (LpcpStopOnReplyMismatch) { DbgBreakPoint(); } #endif LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED | LPCP_MUTEX_RELEASE_ON_RETURN ); if (ConnectionPort) { ObDereferenceObject(ConnectionPort); } ObDereferenceObject( WakeupThread ); ObDereferenceObject( PortObject ); return STATUS_REPLY_MESSAGE_MISMATCH; } // // Copy the reply message to the request message buffer. Do this before // we actually fiddle with the wakeup threads fields. Otherwise we // could mess up its state // try { LpcpMoveMessage( &Msg->Request, &CapturedReplyMessage, (ReplyMessage + 1), LPC_REPLY, NULL ); } except( EXCEPTION_EXECUTE_HANDLER ) { LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED | LPCP_MUTEX_RELEASE_ON_RETURN ); if (ConnectionPort) { ObDereferenceObject(ConnectionPort); } ObDereferenceObject( WakeupThread ); ObDereferenceObject( PortObject ); return (Status = GetExceptionCode()); } LpcpTrace(( "%s Sending Reply Msg %lx (%u.%u, %x) [%08x %08x %08x %08x] to Thread %lx (%s)\n", PsGetCurrentProcess()->ImageFileName, Msg, CapturedReplyMessage.MessageId, CapturedReplyMessage.CallbackId, CapturedReplyMessage.u2.s2.DataInfoOffset, *((PULONG)(Msg+1)+0), *((PULONG)(Msg+1)+1), *((PULONG)(Msg+1)+2), *((PULONG)(Msg+1)+3), WakeupThread, THREAD_TO_PROCESS( WakeupThread )->ImageFileName )); // // Locate and free the messsage from the port. This call use to // test for (CapturedReplyMessage.u2.s2.DataInfoOffset != 0) as a // prerequisite for doing the call. // LpcpFreeDataInfoMessage( PortObject, CapturedReplyMessage.MessageId, CapturedReplyMessage.CallbackId ); // // Add an extra reference so LpcExitThread does not evaporate // the pointer before we get to the wait below // ObReferenceObject( WakeupThread ); // // Release the mutex that guards the LpcReplyMessage field // after marking message as being replied to. // Msg->RepliedToThread = WakeupThread; WakeupThread->LpcReplyMessageId = 0; WakeupThread->LpcReplyMessage = (PVOID)Msg; // // Remove the thread from the reply rundown list as we are sending the reply. // if ((!WakeupThread->LpcExitThreadCalled) && (!IsListEmpty( &WakeupThread->LpcReplyChain ))) { RemoveEntryList( &WakeupThread->LpcReplyChain ); InitializeListHead( &WakeupThread->LpcReplyChain ); } if ((CurrentThread->LpcReceivedMsgIdValid) && (CurrentThread->LpcReceivedMessageId == CapturedReplyMessage.MessageId)) { CurrentThread->LpcReceivedMessageId = 0; CurrentThread->LpcReceivedMsgIdValid = FALSE; } LpcpReleaseLpcpLock(); // // Wake up the thread that is waiting for an answer to its request // inside of NtRequestWaitReplyPort or NtReplyWaitReplyPort // KeReleaseSemaphore( &WakeupThread->LpcReplySemaphore, 1, 1, FALSE ); ObDereferenceObject( WakeupThread ); } LpcpTrace(( "%s Waiting for message to Port %x (%s)\n", PsGetCurrentProcess()->ImageFileName, ReceivePort, LpcpGetCreatorName( ReceivePort ))); // // The timeout on this wait and the next wait appear to be the // only substantial difference between NtReplyWaitReceivePort // and NtReplyWaitReceivePortEx Status = KeWaitForSingleObject( ReceivePort->MsgQueue.Semaphore, WrLpcReceive, WaitMode, FALSE, Timeout ); // // Fall into receive code. Client thread reference will be // returned by the client when it wakes up. // // // At this point we've awoke from our wait for a receive // if (Status == STATUS_SUCCESS) { LpcpAcquireLpcpLockByThread(CurrentThread); // // See if we awoke without a message in our receive port // if (IsListEmpty( &ReceivePort->MsgQueue.ReceiveHead )) { if ( ReceivePort->Flags & PORT_WAITABLE ) { KeResetEvent( &ReceivePort->WaitEvent ); } LpcpReleaseLpcpLock(); if (ConnectionPort) { ObDereferenceObject(ConnectionPort); } ObDereferenceObject( PortObject ); return STATUS_UNSUCCESSFUL; } // // We have a message in our receive port. So let's pull it out // Msg = (PLPCP_MESSAGE)RemoveHeadList( &ReceivePort->MsgQueue.ReceiveHead ); if ( IsListEmpty( &ReceivePort->MsgQueue.ReceiveHead ) ) { if ( ReceivePort->Flags & PORT_WAITABLE ) { KeResetEvent( &ReceivePort->WaitEvent ); } } InitializeListHead( &Msg->Entry ); LpcpTrace(( "%s Receive Msg %lx (%u) from Port %lx (%s)\n", PsGetCurrentProcess()->ImageFileName, Msg, Msg->Request.MessageId, ReceivePort, LpcpGetCreatorName( ReceivePort ))); // // Now make the thread state to be the message we're currently // working on // // NOTICE-2002/03/19-ScottMa -- The valid flag is set to TRUE before // filling in the actual Id, so that the Process/Thread rundown code // will not accidentally read the Id field as a Win32StartAddress. // This if safe, since the only code that reads this value is in one // of three places: // 1) LPC files -- All are protected by LpcpLock. // 2) ExpCopyThreadInfo -- This fix makes the refernce safe. // 3) Debugger extensions -- Both !lpc and !process can still // experience a bug displaying the // correct value of this union. // Note that checking the ownership of the LpcpLock would allow // both the debugger extensions and ExpCopyThreadInfo to determine // if the values are in a state of flux. CurrentThread->LpcReceivedMsgIdValid = TRUE; CurrentThread->LpcReceivedMessageId = Msg->Request.MessageId; try { // // Check if the message is a connection request // if ((Msg->Request.u2.s2.Type & ~LPC_KERNELMODE_MESSAGE) == LPC_CONNECTION_REQUEST) { PLPCP_CONNECTION_MESSAGE ConnectMsg; ULONG ConnectionInfoLength; PLPCP_MESSAGE TempMsg; ConnectMsg = (PLPCP_CONNECTION_MESSAGE)(Msg + 1); ConnectionInfoLength = Msg->Request.u1.s1.DataLength - sizeof( *ConnectMsg ); // // Don't free message until NtAcceptConnectPort called, and if it's never called // then we'll keep the message until the client exits. // TempMsg = Msg; Msg = NULL; *ReceiveMessage = TempMsg->Request; ReceiveMessage->u1.s1.TotalLength = (CSHORT)(sizeof( *ReceiveMessage ) + ConnectionInfoLength); ReceiveMessage->u1.s1.DataLength = (CSHORT)ConnectionInfoLength; RtlCopyMemory( ReceiveMessage+1, ConnectMsg + 1, ConnectionInfoLength ); if (ARGUMENT_PRESENT( PortContext )) { *PortContext = NULL; } // // Check if the message is not a reply // } else if ((Msg->Request.u2.s2.Type & ~LPC_KERNELMODE_MESSAGE) != LPC_REPLY) { LpcpMoveMessage( ReceiveMessage, &Msg->Request, (&Msg->Request) + 1, 0, NULL ); if (ARGUMENT_PRESENT( PortContext )) { *PortContext = Msg->PortContext; } // // If message contains DataInfo for access via NtRead/WriteRequestData // then put the message on a list in the communication port and don't // free it. It will be freed when the server replies to the message. // if (Msg->Request.u2.s2.DataInfoOffset != 0) { LpcpSaveDataInfoMessage( PortObject, Msg, LPCP_MUTEX_OWNED ); Msg = NULL; } // // Otherwise this is a reply message we just received // } else { LpcpPrint(( "LPC: Bogus reply message (%08x) in receive queue of connection port %08x\n", Msg, ReceivePort )); KdBreakPoint(); } } except( EXCEPTION_EXECUTE_HANDLER ) { Status = GetExceptionCode(); } // // Acquire the LPC mutex and decrement the reference count for the // message. If the reference count goes to zero the message will be // deleted. // if (Msg != NULL) { LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED | LPCP_MUTEX_RELEASE_ON_RETURN ); } else { LpcpReleaseLpcpLock(); } } if (ConnectionPort) { ObDereferenceObject(ConnectionPort); } ObDereferenceObject( PortObject ); // // And return to our caller // return Status; }