reactos/ntoskrnl/lpc/connect.c
2023-05-17 17:40:37 +02:00

798 lines
26 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/lpc/connect.c
* PURPOSE: Local Procedure Call: Connection Management
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
/* INCLUDES ******************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* PRIVATE FUNCTIONS *********************************************************/
PVOID
NTAPI
LpcpFreeConMsg(IN OUT PLPCP_MESSAGE *Message,
IN OUT PLPCP_CONNECTION_MESSAGE *ConnectMessage,
IN PETHREAD CurrentThread)
{
PVOID SectionToMap;
PLPCP_MESSAGE ReplyMessage;
/* Acquire the LPC lock */
KeAcquireGuardedMutex(&LpcpLock);
/* Check if the reply chain is not empty */
if (!IsListEmpty(&CurrentThread->LpcReplyChain))
{
/* Remove this entry and re-initialize it */
RemoveEntryList(&CurrentThread->LpcReplyChain);
InitializeListHead(&CurrentThread->LpcReplyChain);
}
/* Check if there's a reply message */
ReplyMessage = LpcpGetMessageFromThread(CurrentThread);
if (ReplyMessage)
{
/* Get the message */
*Message = ReplyMessage;
/* Check if it's got messages */
if (!IsListEmpty(&ReplyMessage->Entry))
{
/* Clear the list */
RemoveEntryList(&ReplyMessage->Entry);
InitializeListHead(&ReplyMessage->Entry);
}
/* Clear message data */
CurrentThread->LpcReceivedMessageId = 0;
CurrentThread->LpcReplyMessage = NULL;
/* Get the connection message and clear the section */
*ConnectMessage = (PLPCP_CONNECTION_MESSAGE)(ReplyMessage + 1);
SectionToMap = (*ConnectMessage)->SectionToMap;
(*ConnectMessage)->SectionToMap = NULL;
}
else
{
/* No message to return */
*Message = NULL;
SectionToMap = NULL;
}
/* Release the lock and return the section */
KeReleaseGuardedMutex(&LpcpLock);
return SectionToMap;
}
/* PUBLIC FUNCTIONS **********************************************************/
/*
* @implemented
*/
NTSTATUS
NTAPI
NtSecureConnectPort(OUT PHANDLE PortHandle,
IN PUNICODE_STRING PortName,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos,
IN OUT PPORT_VIEW ClientView OPTIONAL,
IN PSID ServerSid OPTIONAL,
IN OUT PREMOTE_PORT_VIEW ServerView OPTIONAL,
OUT PULONG MaxMessageLength OPTIONAL,
IN OUT PVOID ConnectionInformation OPTIONAL,
IN OUT PULONG ConnectionInformationLength OPTIONAL)
{
NTSTATUS Status = STATUS_SUCCESS;
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
PETHREAD Thread = PsGetCurrentThread();
#if DBG
UNICODE_STRING CapturedPortName;
#endif
SECURITY_QUALITY_OF_SERVICE CapturedQos;
PORT_VIEW CapturedClientView;
PSID CapturedServerSid;
ULONG ConnectionInfoLength = 0;
PLPCP_PORT_OBJECT Port, ClientPort;
PLPCP_MESSAGE Message;
PLPCP_CONNECTION_MESSAGE ConnectMessage;
ULONG PortMessageLength;
HANDLE Handle;
PVOID SectionToMap;
LARGE_INTEGER SectionOffset;
PTOKEN Token;
PTOKEN_USER TokenUserInfo;
PAGED_CODE();
/* Check if the call comes from user mode */
if (PreviousMode != KernelMode)
{
/* Enter SEH for probing the parameters */
_SEH2_TRY
{
/* Probe the PortHandle */
ProbeForWriteHandle(PortHandle);
/* Probe and capture the QoS */
ProbeForRead(SecurityQos, sizeof(*SecurityQos), sizeof(ULONG));
CapturedQos = *(volatile SECURITY_QUALITY_OF_SERVICE*)SecurityQos;
/* NOTE: Do not care about CapturedQos.Length */
/* The following parameters are optional */
/* Capture the client view */
if (ClientView)
{
ProbeForWrite(ClientView, sizeof(*ClientView), sizeof(ULONG));
CapturedClientView = *(volatile PORT_VIEW*)ClientView;
/* Validate the size of the client view */
if (CapturedClientView.Length != sizeof(CapturedClientView))
{
/* Invalid size */
_SEH2_YIELD(return STATUS_INVALID_PARAMETER);
}
}
/* Capture the server view */
if (ServerView)
{
ProbeForWrite(ServerView, sizeof(*ServerView), sizeof(ULONG));
/* Validate the size of the server view */
if (((volatile REMOTE_PORT_VIEW*)ServerView)->Length != sizeof(*ServerView))
{
/* Invalid size */
_SEH2_YIELD(return STATUS_INVALID_PARAMETER);
}
}
if (MaxMessageLength)
ProbeForWriteUlong(MaxMessageLength);
/* Capture connection information length */
if (ConnectionInformationLength)
{
ProbeForWriteUlong(ConnectionInformationLength);
ConnectionInfoLength = *(volatile ULONG*)ConnectionInformationLength;
}
/* Probe the ConnectionInformation */
if (ConnectionInformation)
ProbeForWrite(ConnectionInformation, ConnectionInfoLength, sizeof(ULONG));
CapturedServerSid = ServerSid;
if (ServerSid)
{
/* Capture it */
Status = SepCaptureSid(ServerSid,
PreviousMode,
PagedPool,
TRUE,
&CapturedServerSid);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed to capture ServerSid!\n");
_SEH2_YIELD(return Status);
}
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* There was an exception, return the exception code */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
else
{
CapturedQos = *SecurityQos;
/* NOTE: Do not care about CapturedQos.Length */
/* The following parameters are optional */
/* Capture the client view */
if (ClientView)
{
/* Validate the size of the client view */
if (ClientView->Length != sizeof(*ClientView))
{
/* Invalid size */
return STATUS_INVALID_PARAMETER;
}
CapturedClientView = *ClientView;
}
/* Capture the server view */
if (ServerView)
{
/* Validate the size of the server view */
if (ServerView->Length != sizeof(*ServerView))
{
/* Invalid size */
return STATUS_INVALID_PARAMETER;
}
}
/* Capture connection information length */
if (ConnectionInformationLength)
ConnectionInfoLength = *ConnectionInformationLength;
CapturedServerSid = ServerSid;
}
#if DBG
/* Capture the port name for DPRINT only - ObReferenceObjectByName does
* its own capture. As it is used only for debugging, ignore any failure;
* the string is zeroed out in such case. */
ProbeAndCaptureUnicodeString(&CapturedPortName, PreviousMode, PortName);
LPCTRACE(LPC_CONNECT_DEBUG,
"Name: %wZ. SecurityQos: %p. Views: %p/%p. Sid: %p\n",
&CapturedPortName,
SecurityQos,
ClientView,
ServerView,
ServerSid);
#endif
/* Get the port */
Status = ObReferenceObjectByName(PortName,
0,
NULL,
PORT_CONNECT,
LpcPortObjectType,
PreviousMode,
NULL,
(PVOID*)&Port);
if (!NT_SUCCESS(Status))
{
#if DBG
DPRINT1("Failed to reference port '%wZ': 0x%lx\n", &CapturedPortName, Status);
ReleaseCapturedUnicodeString(&CapturedPortName, PreviousMode);
#endif
if (CapturedServerSid != ServerSid)
SepReleaseSid(CapturedServerSid, PreviousMode, TRUE);
return Status;
}
/* This has to be a connection port */
if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_CONNECTION_PORT)
{
#if DBG
DPRINT1("Port '%wZ' is not a connection port (Flags: 0x%lx)\n", &CapturedPortName, Port->Flags);
ReleaseCapturedUnicodeString(&CapturedPortName, PreviousMode);
#endif
/* It isn't, so fail */
ObDereferenceObject(Port);
if (CapturedServerSid != ServerSid)
SepReleaseSid(CapturedServerSid, PreviousMode, TRUE);
return STATUS_INVALID_PORT_HANDLE;
}
/* Check if we have a (captured) SID */
if (ServerSid)
{
/* Make sure that we have a server */
if (Port->ServerProcess)
{
/* Get its token and query user information */
Token = PsReferencePrimaryToken(Port->ServerProcess);
Status = SeQueryInformationToken(Token, TokenUser, (PVOID*)&TokenUserInfo);
PsDereferencePrimaryToken(Token);
/* Check for success */
if (NT_SUCCESS(Status))
{
/* Compare the SIDs */
if (!RtlEqualSid(CapturedServerSid, TokenUserInfo->User.Sid))
{
/* Fail */
#if DBG
DPRINT1("Port '%wZ': server SID mismatch\n", &CapturedPortName);
#endif
Status = STATUS_SERVER_SID_MISMATCH;
}
/* Free token information */
ExFreePoolWithTag(TokenUserInfo, TAG_SE);
}
}
else
{
/* Invalid SID */
#if DBG
DPRINT1("Port '%wZ': server SID mismatch\n", &CapturedPortName);
#endif
Status = STATUS_SERVER_SID_MISMATCH;
}
/* Finally release the captured SID, we don't need it anymore */
if (CapturedServerSid != ServerSid)
SepReleaseSid(CapturedServerSid, PreviousMode, TRUE);
}
#if DBG
ReleaseCapturedUnicodeString(&CapturedPortName, PreviousMode);
#endif
/* Check if SID failed */
if (ServerSid && !NT_SUCCESS(Status))
{
/* Quit */
ObDereferenceObject(Port);
return Status;
}
/* Create the client port */
Status = ObCreateObject(PreviousMode,
LpcPortObjectType,
NULL,
PreviousMode,
NULL,
sizeof(LPCP_PORT_OBJECT),
0,
0,
(PVOID*)&ClientPort);
if (!NT_SUCCESS(Status))
{
/* Failed, dereference the server port and return */
DPRINT1("Failed to create Port object: 0x%lx\n", Status);
ObDereferenceObject(Port);
return Status;
}
/*
* Setup the client port -- From now on, dereferencing the client port
* will automatically dereference the connection port too.
*/
RtlZeroMemory(ClientPort, sizeof(LPCP_PORT_OBJECT));
ClientPort->Flags = LPCP_CLIENT_PORT;
ClientPort->ConnectionPort = Port;
ClientPort->MaxMessageLength = Port->MaxMessageLength;
ClientPort->SecurityQos = CapturedQos;
InitializeListHead(&ClientPort->LpcReplyChainHead);
InitializeListHead(&ClientPort->LpcDataInfoChainHead);
/* Check if we have dynamic security */
if (CapturedQos.ContextTrackingMode == SECURITY_DYNAMIC_TRACKING)
{
/* Remember that */
ClientPort->Flags |= LPCP_SECURITY_DYNAMIC;
}
else
{
/* Create our own client security */
Status = SeCreateClientSecurity(Thread,
&CapturedQos,
FALSE,
&ClientPort->StaticSecurity);
if (!NT_SUCCESS(Status))
{
/* Security failed, dereference and return */
DPRINT1("SeCreateClientSecurity failed: 0x%lx\n", Status);
ObDereferenceObject(ClientPort);
return Status;
}
}
/* Initialize the port queue */
Status = LpcpInitializePortQueue(ClientPort);
if (!NT_SUCCESS(Status))
{
/* Failed */
DPRINT1("LpcpInitializePortQueue failed: 0x%lx\n", Status);
ObDereferenceObject(ClientPort);
return Status;
}
/* Check if we have a client view */
if (ClientView)
{
/* Get the section handle */
Status = ObReferenceObjectByHandle(CapturedClientView.SectionHandle,
SECTION_MAP_READ |
SECTION_MAP_WRITE,
MmSectionObjectType,
PreviousMode,
(PVOID*)&SectionToMap,
NULL);
if (!NT_SUCCESS(Status))
{
/* Fail */
DPRINT1("Failed to reference port section handle: 0x%lx\n", Status);
ObDereferenceObject(ClientPort);
return Status;
}
/* Set the section offset */
SectionOffset.QuadPart = CapturedClientView.SectionOffset;
/* Map it */
Status = MmMapViewOfSection(SectionToMap,
PsGetCurrentProcess(),
&ClientPort->ClientSectionBase,
0,
0,
&SectionOffset,
&CapturedClientView.ViewSize,
ViewUnmap,
0,
PAGE_READWRITE);
/* Update the offset */
CapturedClientView.SectionOffset = SectionOffset.LowPart;
/* Check for failure */
if (!NT_SUCCESS(Status))
{
/* Fail */
DPRINT1("Failed to map port section: 0x%lx\n", Status);
ObDereferenceObject(SectionToMap);
ObDereferenceObject(ClientPort);
return Status;
}
/* Update the base */
CapturedClientView.ViewBase = ClientPort->ClientSectionBase;
/* Reference and remember the process */
ClientPort->MappingProcess = PsGetCurrentProcess();
ObReferenceObject(ClientPort->MappingProcess);
}
else
{
/* No section */
SectionToMap = NULL;
}
/* Normalize connection information */
if (ConnectionInfoLength > Port->MaxConnectionInfoLength)
{
/* Use the port's maximum allowed value */
ConnectionInfoLength = Port->MaxConnectionInfoLength;
}
/* Allocate a message from the port zone */
Message = LpcpAllocateFromPortZone();
if (!Message)
{
/* Fail if we couldn't allocate a message */
DPRINT1("LpcpAllocateFromPortZone failed\n");
if (SectionToMap) ObDereferenceObject(SectionToMap);
ObDereferenceObject(ClientPort);
return STATUS_NO_MEMORY;
}
/* Set pointer to the connection message and fill in the CID */
ConnectMessage = (PLPCP_CONNECTION_MESSAGE)(Message + 1);
Message->Request.ClientId = Thread->Cid;
/* Check if we have a client view */
if (ClientView)
{
/* Set the view size */
Message->Request.ClientViewSize = CapturedClientView.ViewSize;
/* Copy the client view and clear the server view */
RtlCopyMemory(&ConnectMessage->ClientView,
&CapturedClientView,
sizeof(CapturedClientView));
RtlZeroMemory(&ConnectMessage->ServerView, sizeof(REMOTE_PORT_VIEW));
}
else
{
/* Set the size to 0 and clear the connect message */
Message->Request.ClientViewSize = 0;
RtlZeroMemory(ConnectMessage, sizeof(LPCP_CONNECTION_MESSAGE));
}
/* Set the section and client port. Port is NULL for now */
ConnectMessage->ClientPort = NULL;
ConnectMessage->SectionToMap = SectionToMap;
/* Set the data for the connection request message */
Message->Request.u1.s1.DataLength = (CSHORT)ConnectionInfoLength +
sizeof(LPCP_CONNECTION_MESSAGE);
Message->Request.u1.s1.TotalLength = sizeof(LPCP_MESSAGE) +
Message->Request.u1.s1.DataLength;
Message->Request.u2.s2.Type = LPC_CONNECTION_REQUEST;
/* Check if we have connection information */
if (ConnectionInformation)
{
_SEH2_TRY
{
/* Copy it in */
RtlCopyMemory(ConnectMessage + 1,
ConnectionInformation,
ConnectionInfoLength);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
DPRINT1("Exception 0x%lx when copying connection info to user mode\n",
_SEH2_GetExceptionCode());
/* Cleanup and return the exception code */
/* Free the message we have */
LpcpFreeToPortZone(Message, 0);
/* Dereference other objects */
if (SectionToMap) ObDereferenceObject(SectionToMap);
ObDereferenceObject(ClientPort);
/* Return status */
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
}
/* Reset the status code */
Status = STATUS_SUCCESS;
/* Acquire the port lock */
KeAcquireGuardedMutex(&LpcpLock);
/* Check if someone already deleted the port name */
if (Port->Flags & LPCP_NAME_DELETED)
{
/* Fail the request */
Status = STATUS_OBJECT_NAME_NOT_FOUND;
}
else
{
/* Associate no thread yet */
Message->RepliedToThread = NULL;
/* Generate the Message ID and set it */
Message->Request.MessageId = LpcpNextMessageId++;
if (!LpcpNextMessageId) LpcpNextMessageId = 1;
Thread->LpcReplyMessageId = Message->Request.MessageId;
/* Insert the message into the queue and thread chain */
InsertTailList(&Port->MsgQueue.ReceiveHead, &Message->Entry);
InsertTailList(&Port->LpcReplyChainHead, &Thread->LpcReplyChain);
Thread->LpcReplyMessage = Message;
/* Now we can finally reference the client port and link it */
ObReferenceObject(ClientPort);
ConnectMessage->ClientPort = ClientPort;
/* Enter a critical region */
KeEnterCriticalRegion();
}
/* Add another reference to the port */
ObReferenceObject(Port);
/* Release the lock */
KeReleaseGuardedMutex(&LpcpLock);
/* Check for success */
if (NT_SUCCESS(Status))
{
LPCTRACE(LPC_CONNECT_DEBUG,
"Messages: %p/%p. Ports: %p/%p. Status: %lx\n",
Message,
ConnectMessage,
Port,
ClientPort,
Status);
/* If this is a waitable port, set the event */
if (Port->Flags & LPCP_WAITABLE_PORT)
KeSetEvent(&Port->WaitEvent, 1, FALSE);
/* Release the queue semaphore and leave the critical region */
LpcpCompleteWait(Port->MsgQueue.Semaphore);
KeLeaveCriticalRegion();
/* Now wait for a reply and set 'Status' */
LpcpConnectWait(&Thread->LpcReplySemaphore, PreviousMode);
}
/* Now, always free the connection message */
SectionToMap = LpcpFreeConMsg(&Message, &ConnectMessage, Thread);
/* Check for failure */
if (!NT_SUCCESS(Status))
{
/* Check if the semaphore got signaled in the meantime */
if (KeReadStateSemaphore(&Thread->LpcReplySemaphore))
{
/* Wait on it */
KeWaitForSingleObject(&Thread->LpcReplySemaphore,
WrExecutive,
KernelMode,
FALSE,
NULL);
}
goto Failure;
}
/* Check if we got a message back */
if (Message)
{
/* Check for new return length */
if ((Message->Request.u1.s1.DataLength -
sizeof(LPCP_CONNECTION_MESSAGE)) < ConnectionInfoLength)
{
/* Set new normalized connection length */
ConnectionInfoLength = Message->Request.u1.s1.DataLength -
sizeof(LPCP_CONNECTION_MESSAGE);
}
/* Check if the caller had connection information */
if (ConnectionInformation)
{
_SEH2_TRY
{
/* Return the connection information length if needed */
if (ConnectionInformationLength)
*ConnectionInformationLength = ConnectionInfoLength;
/* Return the connection information */
RtlCopyMemory(ConnectionInformation,
ConnectMessage + 1,
ConnectionInfoLength);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Cleanup and return the exception code */
Status = _SEH2_GetExceptionCode();
_SEH2_YIELD(goto Failure);
}
_SEH2_END;
}
/* Make sure we had a connected port */
if (ClientPort->ConnectedPort)
{
/* Get the message length before the port might get killed */
PortMessageLength = Port->MaxMessageLength;
/* Insert the client port */
Status = ObInsertObject(ClientPort,
NULL,
PORT_ALL_ACCESS,
0,
NULL,
&Handle);
if (NT_SUCCESS(Status))
{
LPCTRACE(LPC_CONNECT_DEBUG,
"Handle: %p. Length: %lx\n",
Handle,
PortMessageLength);
_SEH2_TRY
{
/* Return the handle */
*PortHandle = Handle;
/* Check if maximum length was requested */
if (MaxMessageLength)
*MaxMessageLength = PortMessageLength;
/* Check if we had a client view */
if (ClientView)
{
/* Copy it back */
RtlCopyMemory(ClientView,
&ConnectMessage->ClientView,
sizeof(*ClientView));
}
/* Check if we had a server view */
if (ServerView)
{
/* Copy it back */
RtlCopyMemory(ServerView,
&ConnectMessage->ServerView,
sizeof(*ServerView));
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* An exception happened, close the opened handle */
ObCloseHandle(Handle, PreviousMode);
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
}
else
{
/* No connection port, we failed */
if (SectionToMap) ObDereferenceObject(SectionToMap);
/* Acquire the lock */
KeAcquireGuardedMutex(&LpcpLock);
/* Check if it's because the name got deleted */
if (!(ClientPort->ConnectionPort) ||
(Port->Flags & LPCP_NAME_DELETED))
{
/* Set the correct status */
Status = STATUS_OBJECT_NAME_NOT_FOUND;
}
else
{
/* Otherwise, the caller refused us */
Status = STATUS_PORT_CONNECTION_REFUSED;
}
/* Release the lock */
KeReleaseGuardedMutex(&LpcpLock);
/* Kill the port */
ObDereferenceObject(ClientPort);
}
/* Free the message */
LpcpFreeToPortZone(Message, 0);
}
else
{
/* No reply message, fail */
Status = STATUS_PORT_CONNECTION_REFUSED;
goto Failure;
}
ObDereferenceObject(Port);
/* Return status */
return Status;
Failure:
/* Check if we had a message and free it */
if (Message) LpcpFreeToPortZone(Message, 0);
/* Dereference other objects */
if (SectionToMap) ObDereferenceObject(SectionToMap);
ObDereferenceObject(ClientPort);
ObDereferenceObject(Port);
/* Return status */
return Status;
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtConnectPort(OUT PHANDLE PortHandle,
IN PUNICODE_STRING PortName,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos,
IN OUT PPORT_VIEW ClientView OPTIONAL,
IN OUT PREMOTE_PORT_VIEW ServerView OPTIONAL,
OUT PULONG MaxMessageLength OPTIONAL,
IN OUT PVOID ConnectionInformation OPTIONAL,
IN OUT PULONG ConnectionInformationLength OPTIONAL)
{
/* Call the newer API */
return NtSecureConnectPort(PortHandle,
PortName,
SecurityQos,
ClientView,
NULL,
ServerView,
MaxMessageLength,
ConnectionInformation,
ConnectionInformationLength);
}
/* EOF */