You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1448 lines
40 KiB
1448 lines
40 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
lpcreply.c
|
|
|
|
Abstract:
|
|
|
|
Local Inter-Process Communication (LPC) reply system services.
|
|
|
|
Author:
|
|
|
|
Steve Wood (stevewo) 15-May-1989
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "lpcp.h"
|
|
|
|
NTSTATUS
|
|
LpcpCopyRequestData (
|
|
IN BOOLEAN WriteToMessageData,
|
|
IN HANDLE PortHandle,
|
|
IN PPORT_MESSAGE Message,
|
|
IN ULONG DataEntryIndex,
|
|
IN PVOID Buffer,
|
|
IN SIZE_T BufferSize,
|
|
OUT PSIZE_T NumberOfBytesCopied OPTIONAL
|
|
);
|
|
|
|
#if 0
|
|
VOID
|
|
LpcpAuditInvalidUse (
|
|
IN PVOID Context
|
|
);
|
|
#endif
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE,NtReplyPort)
|
|
#pragma alloc_text(PAGE,NtReplyWaitReplyPort)
|
|
#pragma alloc_text(PAGE,NtReadRequestData)
|
|
#pragma alloc_text(PAGE,NtWriteRequestData)
|
|
#pragma alloc_text(PAGE,LpcpCopyRequestData)
|
|
#pragma alloc_text(PAGE,LpcpValidateClientPort)
|
|
#if 0
|
|
#pragma alloc_text(PAGE,LpcpAuditInvalidUse)
|
|
#endif
|
|
|
|
ULONG LpcMaxEventLogs = 10;
|
|
|
|
#define LPCP_PORT_NAME_MAX 256
|
|
|
|
typedef struct _LPC_WORK_CONTEXT {
|
|
WORK_QUEUE_ITEM WorkItem;
|
|
PVOID Buffer;
|
|
} LPC_WORK_CONTEXT, *PLPC_WORK_CONTEXT;
|
|
|
|
#endif
|
|
|
|
//
|
|
// The current number of events registered
|
|
//
|
|
|
|
ULONG LpcpEventCounts = 0;
|
|
|
|
|
|
NTSTATUS
|
|
NtReplyPort (
|
|
IN HANDLE PortHandle,
|
|
IN PPORT_MESSAGE ReplyMessage
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A client and server process can send a reply to a previous request
|
|
message with the NtReplyPort service:
|
|
|
|
The Type field of the message is set to LPC_REPLY by the service. 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 in the caller's address space will be unmapped.
|
|
|
|
The ClientId and MessageId fields of the ReplyMessage structure are used
|
|
to identify the thread waiting for this reply. If the target thread is
|
|
in fact waiting for this reply message, then the reply message is copied
|
|
into the thread's message buffer and the thread's wait is satisfied.
|
|
|
|
If the thread is not waiting for a reply or is waiting for a reply to
|
|
some other MessageId, then the message is placed in the message queue of
|
|
the port that is connected to the communication port specified by the
|
|
PortHandle parameter and the Type field of the message is set to
|
|
LPC_LOST_REPLY.
|
|
|
|
Arguments:
|
|
|
|
PortHandle - Specifies the handle of the communication port that the
|
|
original message was received from.
|
|
|
|
ReplyMessage - Specifies a pointer to the reply message to be sent.
|
|
The ClientId and MessageId fields determine which thread will
|
|
get the reply.
|
|
|
|
Return Value:
|
|
|
|
Status code that indicates whether or not the operation was
|
|
successful.
|
|
|
|
--*/
|
|
|
|
{
|
|
KPROCESSOR_MODE PreviousMode;
|
|
PLPCP_PORT_OBJECT PortObject;
|
|
PORT_MESSAGE CapturedReplyMessage;
|
|
NTSTATUS Status;
|
|
PLPCP_MESSAGE Msg;
|
|
PETHREAD CurrentThread;
|
|
PETHREAD WakeupThread;
|
|
|
|
PAGED_CODE();
|
|
|
|
CurrentThread = PsGetCurrentThread();
|
|
|
|
//
|
|
// Get previous processor mode and probe output arguments if necessary.
|
|
//
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
|
|
try {
|
|
|
|
ProbeForReadSmallStructure( ReplyMessage,
|
|
sizeof( *ReplyMessage ),
|
|
sizeof( ULONG ));
|
|
|
|
CapturedReplyMessage = *ReplyMessage;
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
return GetExceptionCode();
|
|
}
|
|
|
|
} else {
|
|
|
|
CapturedReplyMessage = *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
|
|
//
|
|
|
|
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 (((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;
|
|
}
|
|
|
|
//
|
|
// 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 );
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Acquire the 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) {
|
|
|
|
LpcpTraceError(STATUS_NO_MEMORY, CurrentThread->Cid, &CapturedReplyMessage);
|
|
|
|
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 reply 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 );
|
|
|
|
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 );
|
|
|
|
ObDereferenceObject( WakeupThread );
|
|
ObDereferenceObject( PortObject );
|
|
|
|
return GetExceptionCode();
|
|
}
|
|
|
|
//
|
|
// At this point we know the thread is waiting for our reply
|
|
//
|
|
|
|
LpcpTrace(( "%s Sending Reply Msg %lx (%u, %x) [%08x %08x %08x %08x] to Thread %lx (%s)\n",
|
|
PsGetCurrentProcess()->ImageFileName,
|
|
Msg,
|
|
CapturedReplyMessage.MessageId,
|
|
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 ));
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
//
|
|
// Locate and free the message 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,
|
|
CapturedReplyMessage.ClientId );
|
|
|
|
LpcpReleaseLpcpLock();
|
|
|
|
//
|
|
// Wake up the thread that is waiting for an answer to its request
|
|
// inside of NtRequestWaitReplyPort or NtReplyWaitReplyPort. That
|
|
// will dereference itself when it wakes up.
|
|
//
|
|
|
|
KeReleaseSemaphore( &WakeupThread->LpcReplySemaphore,
|
|
0,
|
|
1L,
|
|
FALSE );
|
|
|
|
ObDereferenceObject( WakeupThread );
|
|
|
|
//
|
|
// Dereference port object and return the system service status.
|
|
//
|
|
|
|
ObDereferenceObject( PortObject );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtReplyWaitReplyPort (
|
|
IN HANDLE PortHandle,
|
|
IN OUT PPORT_MESSAGE ReplyMessage
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A client and server process can send a reply to a previous message and
|
|
block waiting for a reply using the NtReplyWaitReplyPort service:
|
|
|
|
This service works the same as NtReplyPort, except that after delivering
|
|
the reply message, it blocks waiting for a reply to a previous message.
|
|
When the reply is received, it will be placed in the location specified
|
|
by the ReplyMessage parameter.
|
|
|
|
Arguments:
|
|
|
|
PortHandle - Specifies the handle of the communication port that the
|
|
original message was received from.
|
|
|
|
ReplyMessage - Specifies a pointer to the reply message to be sent.
|
|
The ClientId and MessageId fields determine which thread will
|
|
get the reply. This buffer also receives any reply that comes
|
|
back from the wait.
|
|
|
|
Return Value:
|
|
|
|
Status code that indicates whether or not the operation was
|
|
successful.
|
|
|
|
--*/
|
|
|
|
{
|
|
KPROCESSOR_MODE PreviousMode;
|
|
NTSTATUS Status;
|
|
PLPCP_PORT_OBJECT PortObject;
|
|
PORT_MESSAGE CapturedReplyMessage;
|
|
PLPCP_MESSAGE Msg;
|
|
PETHREAD CurrentThread;
|
|
PETHREAD WakeupThread;
|
|
PLPCP_PORT_OBJECT RundownPort;
|
|
|
|
PAGED_CODE();
|
|
|
|
CurrentThread = PsGetCurrentThread();
|
|
|
|
//
|
|
// Get previous processor mode and probe output arguments if necessary.
|
|
//
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
|
|
try {
|
|
|
|
ProbeForWriteSmallStructure( ReplyMessage,
|
|
sizeof( *ReplyMessage ),
|
|
sizeof( ULONG ));
|
|
|
|
CapturedReplyMessage = *ReplyMessage;
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
return GetExceptionCode();
|
|
}
|
|
|
|
} else {
|
|
|
|
CapturedReplyMessage = *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 communication port object by handle. Return status if
|
|
// unsuccessful.
|
|
//
|
|
|
|
Status = LpcpReferencePortObject( PortHandle,
|
|
0,
|
|
PreviousMode,
|
|
&PortObject );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Validate the message length
|
|
//
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// 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 );
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Acquire the 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) {
|
|
|
|
LpcpTraceError(STATUS_NO_MEMORY, CurrentThread->Cid, &CapturedReplyMessage);
|
|
|
|
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 reply wait reply 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 );
|
|
|
|
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 );
|
|
|
|
ObDereferenceObject( WakeupThread );
|
|
ObDereferenceObject( PortObject );
|
|
|
|
return (Status = GetExceptionCode());
|
|
}
|
|
|
|
//
|
|
// At this point we know the thread is waiting for our reply
|
|
//
|
|
|
|
LpcpTrace(( "%s Sending Reply Wait Reply Msg %lx (%u, %x) [%08x %08x %08x %08x] to Thread %lx (%s)\n",
|
|
PsGetCurrentProcess()->ImageFileName,
|
|
Msg,
|
|
CapturedReplyMessage.MessageId,
|
|
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 ));
|
|
|
|
//
|
|
// 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 );
|
|
}
|
|
|
|
//
|
|
// Set ourselves up to get the following reply
|
|
//
|
|
|
|
CurrentThread->LpcReplyMessageId = CapturedReplyMessage.MessageId;
|
|
CurrentThread->LpcReplyMessage = NULL;
|
|
|
|
if ((CurrentThread->LpcReceivedMsgIdValid) &&
|
|
(CurrentThread->LpcReceivedMessageId == CapturedReplyMessage.MessageId)) {
|
|
|
|
CurrentThread->LpcReceivedMessageId = 0;
|
|
CurrentThread->LpcReceivedMsgIdValid = FALSE;
|
|
}
|
|
|
|
//
|
|
// Insert the current thread into the rundown queue
|
|
//
|
|
|
|
if ((PortObject->Flags & PORT_TYPE) != SERVER_CONNECTION_PORT) {
|
|
|
|
RundownPort = PortObject->ConnectedPort;
|
|
|
|
} else {
|
|
|
|
RundownPort = PortObject;
|
|
}
|
|
|
|
InsertTailList( &RundownPort->LpcReplyChainHead, &CurrentThread->LpcReplyChain );
|
|
|
|
//
|
|
// Save the port context in the current thread, because
|
|
// it waits a reply from the same message
|
|
//
|
|
|
|
LpcpSetPortToThread(CurrentThread, PortObject);
|
|
|
|
//
|
|
// Locate and free the message 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,
|
|
CapturedReplyMessage.ClientId );
|
|
|
|
LpcpReleaseLpcpLock();
|
|
|
|
//
|
|
// Wake up the thread that is waiting for an answer to its request
|
|
// inside of NtRequestWaitReplyPort or NtReplyWaitReplyPort. That
|
|
// will dereference itself when it wakes up.
|
|
//
|
|
|
|
KeReleaseSemaphore( &WakeupThread->LpcReplySemaphore,
|
|
1,
|
|
1,
|
|
FALSE );
|
|
|
|
ObDereferenceObject( WakeupThread );
|
|
|
|
//
|
|
// And wait for a reply
|
|
//
|
|
|
|
Status = KeWaitForSingleObject( &CurrentThread->LpcReplySemaphore,
|
|
Executive,
|
|
PreviousMode,
|
|
FALSE,
|
|
NULL );
|
|
|
|
if (Status == STATUS_USER_APC) {
|
|
|
|
//
|
|
// if the semaphore is signaled, then clear it
|
|
//
|
|
|
|
if (KeReadStateSemaphore( &CurrentThread->LpcReplySemaphore )) {
|
|
|
|
KeWaitForSingleObject( &CurrentThread->LpcReplySemaphore,
|
|
WrExecutive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remove the thread from the reply rundown list in case we did not wakeup due to
|
|
// a reply
|
|
//
|
|
|
|
LpcpAcquireLpcpLockByThread(CurrentThread);
|
|
|
|
if (!IsListEmpty( &CurrentThread->LpcReplyChain )) {
|
|
|
|
RemoveEntryList( &CurrentThread->LpcReplyChain );
|
|
|
|
InitializeListHead( &CurrentThread->LpcReplyChain );
|
|
}
|
|
|
|
//
|
|
// If the wait succeeded, copy the reply to the reply buffer.
|
|
//
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
|
|
//
|
|
// Acquire the mutex that guards the request message queue. Remove
|
|
// the request message from the list of messages being processed and
|
|
// free the message back to the queue's zone. If the zone's free
|
|
// list was zero before freeing this message then pulse the free
|
|
// event after free the message so that threads waiting to allocate
|
|
// a request message buffer will wake up. Finally, release the mutex
|
|
// and return the system service status.
|
|
//
|
|
|
|
Msg = LpcpGetThreadMessage(CurrentThread);
|
|
CurrentThread->LpcReplyMessage = NULL;
|
|
|
|
#if DBG
|
|
if (Msg != NULL) {
|
|
|
|
LpcpTrace(( "%s Got Reply Msg %lx (%u) [%08x %08x %08x %08x] for Thread %lx (%s)\n",
|
|
PsGetCurrentProcess()->ImageFileName,
|
|
Msg,
|
|
Msg->Request.MessageId,
|
|
*((PULONG)(Msg+1)+0),
|
|
*((PULONG)(Msg+1)+1),
|
|
*((PULONG)(Msg+1)+2),
|
|
*((PULONG)(Msg+1)+3),
|
|
CurrentThread,
|
|
THREAD_TO_PROCESS( CurrentThread )->ImageFileName ));
|
|
|
|
if (!IsListEmpty( &Msg->Entry )) {
|
|
|
|
LpcpTrace(( "Reply Msg %lx has non-empty list entry\n", Msg ));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
LpcpReleaseLpcpLock();
|
|
|
|
if (Msg != NULL) {
|
|
|
|
try {
|
|
|
|
LpcpMoveMessage( ReplyMessage,
|
|
&Msg->Request,
|
|
(&Msg->Request) + 1,
|
|
0,
|
|
NULL );
|
|
|
|
} 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.
|
|
//
|
|
|
|
LpcpAcquireLpcpLockByThread(CurrentThread);
|
|
|
|
if (Msg->RepliedToThread != NULL) {
|
|
|
|
ObDereferenceObject( Msg->RepliedToThread );
|
|
|
|
Msg->RepliedToThread = NULL;
|
|
}
|
|
|
|
LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED | LPCP_MUTEX_RELEASE_ON_RETURN );
|
|
|
|
} else {
|
|
|
|
Status = STATUS_LPC_REPLY_LOST;
|
|
}
|
|
}
|
|
else {
|
|
LpcpReleaseLpcpLock();
|
|
}
|
|
|
|
ObDereferenceObject( PortObject );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtReadRequestData (
|
|
IN HANDLE PortHandle,
|
|
IN PPORT_MESSAGE Message,
|
|
IN ULONG DataEntryIndex,
|
|
OUT PVOID Buffer,
|
|
IN SIZE_T BufferSize,
|
|
OUT PSIZE_T NumberOfBytesRead OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to copy data from a port message into the user
|
|
supplied buffer.
|
|
|
|
Arguments:
|
|
|
|
PortHandle - Supplies the port from which the message is being read
|
|
|
|
Message - Supplies the message that we are trying to read
|
|
|
|
DataEntryIndex - Supplies the index of the port data entry in the
|
|
preceeding message that we are reading
|
|
|
|
Buffer - Supplies the location into which the data is to be read
|
|
|
|
BufferSize - Supplies the size, in bytes, of the preceeding buffer
|
|
|
|
NumberOfBytesRead - Optionally returns the number of bytes read into
|
|
the buffer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - An appropriate status value
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
status = LpcpCopyRequestData( FALSE,
|
|
PortHandle,
|
|
Message,
|
|
DataEntryIndex,
|
|
Buffer,
|
|
BufferSize,
|
|
NumberOfBytesRead );
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NtWriteRequestData (
|
|
IN HANDLE PortHandle,
|
|
IN PPORT_MESSAGE Message,
|
|
IN ULONG DataEntryIndex,
|
|
IN PVOID Buffer,
|
|
IN SIZE_T BufferSize,
|
|
OUT PSIZE_T NumberOfBytesWritten OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to copy data from the user supplied buffer into the
|
|
port message
|
|
|
|
Arguments:
|
|
|
|
PortHandle - Supplies the port into which the message is being written
|
|
|
|
Message - Supplies the message that we are trying to write
|
|
|
|
DataEntryIndex - Supplies the index of the port data entry in the
|
|
preceeding message that we are writing
|
|
|
|
Buffer - Supplies the location into which the data is to be written
|
|
|
|
BufferSize - Supplies the size, in bytes, of the preceeding buffer
|
|
|
|
NumberOfBytesWritten - Optionally returns the number of bytes written from
|
|
the buffer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - An appropriate status value
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
status = LpcpCopyRequestData( TRUE,
|
|
PortHandle,
|
|
Message,
|
|
DataEntryIndex,
|
|
Buffer,
|
|
BufferSize,
|
|
NumberOfBytesWritten );
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
NTSTATUS
|
|
LpcpCopyRequestData (
|
|
IN BOOLEAN WriteToMessageData,
|
|
IN HANDLE PortHandle,
|
|
IN PPORT_MESSAGE Message,
|
|
IN ULONG DataEntryIndex,
|
|
IN PVOID Buffer,
|
|
IN SIZE_T BufferSize,
|
|
OUT PSIZE_T NumberOfBytesCopied OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will copy data to or from the user supplied buffer and the
|
|
port message data information buffer
|
|
|
|
Arguments:
|
|
|
|
WriteToMessageData - TRUE if the data is to be copied from the user buffer
|
|
to the message and FALSE otherwise
|
|
|
|
PortHandle - Supplies the port into which the message is being manipulated
|
|
|
|
Message - Supplies the message that we are trying to manipulate
|
|
|
|
DataEntryIndex - Supplies the index of the port data entry in the
|
|
preceeding message that we are transferring
|
|
|
|
Buffer - Supplies the location into which the data is to be transfered
|
|
|
|
BufferSize - Supplies the size, in bytes, of the preceeding buffer
|
|
|
|
NumberOfBytesRead - Optionally returns the number of bytes transfered from
|
|
the buffer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - An appropriate status value
|
|
|
|
--*/
|
|
|
|
{
|
|
KPROCESSOR_MODE PreviousMode;
|
|
PLPCP_PORT_OBJECT PortObject;
|
|
PLPCP_MESSAGE Msg;
|
|
NTSTATUS Status;
|
|
PETHREAD ClientThread;
|
|
PPORT_DATA_INFORMATION DataInfo;
|
|
PPORT_DATA_ENTRY DataEntry;
|
|
PORT_MESSAGE CapturedMessage;
|
|
PORT_DATA_ENTRY CapturedDataEntry;
|
|
SIZE_T BytesCopied;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get previous processor mode and probe output arguments if necessary.
|
|
//
|
|
|
|
PreviousMode = KeGetPreviousMode();
|
|
|
|
if (PreviousMode != KernelMode) {
|
|
|
|
try {
|
|
|
|
//
|
|
// We are either reading or writing the user buffer
|
|
//
|
|
|
|
if (WriteToMessageData) {
|
|
|
|
ProbeForRead( Buffer,
|
|
BufferSize,
|
|
1 );
|
|
|
|
} else {
|
|
|
|
ProbeForWrite( Buffer,
|
|
BufferSize,
|
|
1 );
|
|
}
|
|
|
|
ProbeForReadSmallStructure( Message,
|
|
sizeof( *Message ),
|
|
sizeof( ULONG ));
|
|
|
|
CapturedMessage = *Message;
|
|
|
|
if (ARGUMENT_PRESENT( NumberOfBytesCopied )) {
|
|
|
|
ProbeForWriteUlong_ptr( NumberOfBytesCopied );
|
|
}
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
return GetExceptionCode();
|
|
}
|
|
|
|
} else {
|
|
|
|
CapturedMessage = *Message;
|
|
}
|
|
|
|
//
|
|
// The message better have at least one data entry
|
|
//
|
|
|
|
if (CapturedMessage.u2.s2.DataInfoOffset == 0) {
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Reference the port object by handle
|
|
//
|
|
|
|
Status = LpcpReferencePortObject( PortHandle,
|
|
0,
|
|
PreviousMode,
|
|
&PortObject );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// 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( &CapturedMessage.ClientId,
|
|
NULL,
|
|
&ClientThread );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
ObDereferenceObject( PortObject );
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Acquire the 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.
|
|
//
|
|
|
|
LpcpAcquireLpcpLock();
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
if ( (ClientThread->LpcReplyMessageId != CapturedMessage.MessageId) ||
|
|
!LpcpValidateClientPort(ClientThread, PortObject, LPCP_VALIDATE_REASON_WRONG_DATA) ) {
|
|
|
|
Status = STATUS_REPLY_MESSAGE_MISMATCH;
|
|
|
|
} else {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
Msg = LpcpFindDataInfoMessage( PortObject,
|
|
CapturedMessage.MessageId,
|
|
CapturedMessage.CallbackId,
|
|
CapturedMessage.ClientId );
|
|
|
|
if (Msg != NULL) {
|
|
|
|
DataInfo = (PPORT_DATA_INFORMATION)((PUCHAR)&Msg->Request +
|
|
Msg->Request.u2.s2.DataInfoOffset);
|
|
|
|
//
|
|
// Make sure the caller isn't asking for an index beyond what's
|
|
// in the message
|
|
//
|
|
|
|
if (DataInfo->CountDataEntries > DataEntryIndex) {
|
|
|
|
DataEntry = &DataInfo->DataEntries[ DataEntryIndex ];
|
|
CapturedDataEntry = *DataEntry;
|
|
|
|
if (CapturedDataEntry.Size >= BufferSize) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Release the mutex that guards the LpcReplyMessage field
|
|
//
|
|
|
|
LpcpReleaseLpcpLock();
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
ObDereferenceObject( ClientThread );
|
|
ObDereferenceObject( PortObject );
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Copy the message data
|
|
//
|
|
|
|
if (WriteToMessageData) {
|
|
|
|
Status = MmCopyVirtualMemory( PsGetCurrentProcess(),
|
|
Buffer,
|
|
THREAD_TO_PROCESS( ClientThread ),
|
|
CapturedDataEntry.Base,
|
|
BufferSize,
|
|
PreviousMode,
|
|
&BytesCopied );
|
|
|
|
} else {
|
|
|
|
Status = MmCopyVirtualMemory( THREAD_TO_PROCESS( ClientThread ),
|
|
CapturedDataEntry.Base,
|
|
PsGetCurrentProcess(),
|
|
Buffer,
|
|
BufferSize,
|
|
PreviousMode,
|
|
&BytesCopied );
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT( NumberOfBytesCopied )) {
|
|
|
|
try {
|
|
|
|
*NumberOfBytesCopied = BytesCopied;
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
|
|
NOTHING;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Dereference client thread and return the system service status.
|
|
//
|
|
|
|
ObDereferenceObject( ClientThread );
|
|
ObDereferenceObject( PortObject );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
FASTCALL
|
|
LpcpValidateClientPort(
|
|
IN PETHREAD Thread,
|
|
IN PLPCP_PORT_OBJECT ReplyPort,
|
|
IN ULONG Reason
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine validates whether the reply for a request come from
|
|
an appropriate port
|
|
|
|
Arguments:
|
|
|
|
Thread - The thread waiting for a reply
|
|
|
|
ReplyPort - The port object that is replying
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE if the reply come from a valid port
|
|
|
|
Environment:
|
|
|
|
This is called holding the global LPC mutex.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLPCP_PORT_OBJECT PortThread;
|
|
|
|
PortThread = LpcpGetThreadPort(Thread);
|
|
|
|
//
|
|
// The thread must have a port set
|
|
//
|
|
|
|
if (PortThread == NULL) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// We only allow a port connected with the requestor.
|
|
// Also csrss is giving the server connection port to clients
|
|
// and we can have a client sending a request from a server connection port
|
|
// and csrss can reply with a server communication port
|
|
//
|
|
|
|
if ( ( ReplyPort == PortThread->ConnectionPort )
|
|
||
|
|
( ReplyPort == PortThread->ConnectedPort )
|
|
||
|
|
( ReplyPort == PortThread )
|
|
||
|
|
(
|
|
((ReplyPort->Flags & PORT_TYPE) == SERVER_COMMUNICATION_PORT)
|
|
&&
|
|
(ReplyPort->ConnectionPort == PortThread)
|
|
)
|
|
) {
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0
|
|
if (LpcpEventCounts < LpcMaxEventLogs) {
|
|
|
|
PUNICODE_STRING StrReason;
|
|
POBJECT_NAME_INFORMATION ObjectNameInfo;
|
|
NTSTATUS Status;
|
|
ULONG Length;
|
|
PLPC_WORK_CONTEXT AuditItem;
|
|
|
|
if (PortThread->ConnectionPort) {
|
|
|
|
ObjectNameInfo = ExAllocatePoolWithTag(PagedPool, LPCP_PORT_NAME_MAX + sizeof (UNICODE_STRING), 'ScpL');
|
|
if (ObjectNameInfo != NULL) {
|
|
|
|
Status = ObQueryNameString( PortThread->ConnectionPort,
|
|
ObjectNameInfo,
|
|
LPCP_PORT_NAME_MAX,
|
|
&Length
|
|
);
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// Audit the event. Use a worker thread to avoid burning
|
|
// up a bunch of cycles since the global mutex is held.
|
|
//
|
|
|
|
StrReason = (PUNICODE_STRING)((ULONG_PTR) ObjectNameInfo + LPCP_PORT_NAME_MAX);
|
|
switch (Reason) {
|
|
case LPCP_VALIDATE_REASON_IMPERSONATION:
|
|
RtlInitUnicodeString( StrReason, L"impersonation" );
|
|
break;
|
|
|
|
case LPCP_VALIDATE_REASON_REPLY:
|
|
RtlInitUnicodeString( StrReason, L"reply" );
|
|
break;
|
|
|
|
case LPCP_VALIDATE_REASON_WRONG_DATA:
|
|
RtlInitUnicodeString( StrReason, L"data access" );
|
|
break;
|
|
}
|
|
|
|
AuditItem = ExAllocatePoolWithTag (NonPagedPool,
|
|
sizeof(LPC_WORK_CONTEXT),
|
|
'wcpL');
|
|
|
|
if (AuditItem != NULL) {
|
|
AuditItem->Buffer = (PVOID) ObjectNameInfo;
|
|
ExInitializeWorkItem (&AuditItem->WorkItem,
|
|
LpcpAuditInvalidUse,
|
|
(PVOID) AuditItem);
|
|
|
|
ExQueueWorkItem (&AuditItem->WorkItem, DelayedWorkQueue);
|
|
LpcpEventCounts += 1;
|
|
}
|
|
else {
|
|
ExFreePool (ObjectNameInfo);
|
|
}
|
|
}
|
|
else {
|
|
ExFreePool (ObjectNameInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DBG
|
|
if (LpcpStopOnReplyMismatch) {
|
|
|
|
DbgBreakPoint();
|
|
}
|
|
#endif
|
|
|
|
}
|
|
#endif
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#if 0
|
|
|
|
VOID
|
|
LpcpAuditInvalidUse (
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the worker routine which logs security items.
|
|
|
|
Arguments:
|
|
|
|
Context - Supplies a pointer to the LPC_WORK_CONTEXT for the audit event.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode, PASSIVE_LEVEL.
|
|
|
|
--*/
|
|
|
|
{
|
|
PUNICODE_STRING StrReason;
|
|
POBJECT_NAME_INFORMATION ObjectNameInfo;
|
|
PLPC_WORK_CONTEXT AuditItem;
|
|
|
|
PAGED_CODE();
|
|
|
|
AuditItem = (PLPC_WORK_CONTEXT) Context;
|
|
|
|
ObjectNameInfo = (POBJECT_NAME_INFORMATION) AuditItem->Buffer;
|
|
|
|
StrReason = (PUNICODE_STRING)((ULONG_PTR) ObjectNameInfo + LPCP_PORT_NAME_MAX);
|
|
|
|
SeAuditLPCInvalidUse (StrReason, &ObjectNameInfo->Name);
|
|
|
|
ExFreePool (ObjectNameInfo);
|
|
|
|
ExFreePool (AuditItem);
|
|
}
|
|
#endif
|