Files

1688 lines
68 KiB
C

// Kernel Port IPC mechanism. Ports provide copied messaging, synchronous calls, waitability, and capability transfer.
#include <ntlite/atomic.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <intrin.h>
#include <kernel/ke_port.h>
#include <kernel/ke_system_service.h>
#include <kernel/ke_handle.h>
#include <kernel/ke_process.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_list.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_time.h>
#include <kernel/ke_id.h>
#include <kernel/mm_virt.h>
#include <kernel/mm_pool.h>
#include <kernel/ke_debug.h>
#include <kernel/se_security.h>
#define PORT_KIND_LISTENER 1u
#define PORT_KIND_ENDPOINT 2u
#define PORT_CALL_PENDING 1u
#define PORT_CALL_DELIVERED 2u
#define PORT_CALL_REPLYING 3u
#define PORT_CALL_COMPLETED 4u
#define PORT_CALL_CANCELLED 5u
#define PORT_PROCESS_MESSAGE_LIMIT 256u
#define PORT_PROCESS_BYTE_LIMIT (256u * 1024u)
#define PORT_PROCESS_HANDLE_LIMIT 512u
#define PORT_PROCESS_CALL_LIMIT 128u
typedef struct PORT_CONNECTION PORT_CONNECTION;
typedef struct PORT_MESSAGE PORT_MESSAGE;
typedef struct PORT_CALL PORT_CALL;
typedef struct PORT_RECEIVE_WAIT PORT_RECEIVE_WAIT;
typedef struct PORT_PENDING_CONNECTION {
LIST_ENTRY Link;
struct PORT* ServerEndpoint;
SYS_PORT_CONNECTION_INFO Info;
} PORT_PENDING_CONNECTION;
struct PORT_MESSAGE {
LIST_ENTRY Link;
SYS_PORT_MESSAGE_INFO Info;
void* Payload;
struct PROCESS* AccountingProcess;
uint32_t AccountedBytes;
uint32_t AccountedHandles;
struct KE_CAPTURED_HANDLE* Captured[SYS_PORT_MAX_TRANSFER_HANDLES];
uint32_t CapturedCount;
PORT_CALL* Call;
};
struct PORT_RECEIVE_WAIT {
LIST_ENTRY Link;
struct PROCESS* Process;
void* Payload;
uint32_t PayloadCapacity;
uint32_t HandleCapacity;
MM_LOCKED_USER_BUFFER* LockedPayload;
EVENT* CompletionEvent;
SYS_PORT_MESSAGE_INFO Info;
SYS_HANDLE Handles[SYS_PORT_MAX_TRANSFER_HANDLES];
uint32_t HandleCount;
KSTATUS Status;
volatile int32_t Claimed;
volatile int32_t Completed;
};
struct PORT_CALL {
LIST_ENTRY Link;
volatile int32_t RefCount;
PORT_CONNECTION* Connection;
uint32_t MessageId;
uint32_t CallerSide;
volatile int32_t State;
struct PROCESS* CallerProcess;
EVENT* CompletionEvent;
MM_LOCKED_USER_BUFFER* LockedReplyPayload;
void* ReplyPayload;
uint32_t ReplyCapacity;
uint32_t ReplyHandleCapacity;
SYS_PORT_MESSAGE_INFO ReplyInfo;
SYS_HANDLE ReplyHandles[SYS_PORT_MAX_TRANSFER_HANDLES];
uint32_t ReplyHandleCount;
KSTATUS Status;
};
struct PORT_CONNECTION {
volatile int32_t RefCount;
SPINLOCK Lock;
struct PORT* Endpoints[2];
struct PROCESS* PeerProcesses[2];
SYS_RIGHTS PeerProcessRights[2];
uint8_t Disconnected[2];
LIST_ENTRY Calls;
};
typedef struct PORT_LISTENER_STATE {
SPINLOCK Lock;
LIST_ENTRY Pending;
uint32_t PendingCount;
uint32_t Backlog;
SYS_RIGHTS PeerProcessRights;
EVENT* WaitEvent;
} PORT_LISTENER_STATE;
typedef struct PORT_ENDPOINT_STATE {
PORT_CONNECTION* Connection;
uint32_t Side;
EVENT* WaitEvent;
EVENT* SpaceEvent;
LIST_ENTRY Incoming;
uint32_t IncomingCount;
uint32_t IncomingBytes;
uint32_t IncomingHandles;
LIST_ENTRY WaitingReceivers;
} PORT_ENDPOINT_STATE;
struct PORT {
OBJECT Header;
uint32_t Kind;
union {
PORT_LISTENER_STATE Listener;
PORT_ENDPOINT_STATE Endpoint;
} u;
};
static KE_ID_ALLOCATOR gPortMessageIds = KE_ID_ALLOCATOR_INIT(1u);
typedef struct PORT_COUNTERS {
volatile int64_t ConnectionsCreated;
volatile int64_t DirectDeliveries;
volatile int64_t DirectBytes;
volatile int64_t QueuedMessages;
volatile int64_t QueuedBytes;
volatile int64_t QueuedDeliveries;
volatile int64_t RepliesCompleted;
volatile int64_t Disconnects;
volatile int64_t ProcessQuotaRejects;
} PORT_COUNTERS;
static PORT_COUNTERS gPortCounters;
// Reads one 64-bit diagnostic counter atomically without changing it.
static uint64_t KiPortReadCounter(volatile int64_t* counter) {
return (uint64_t)NtlAtomicLoad64(counter);
}
// Copies current Port transport diagnostics for the kernel stats syscall.
void KePortQueryStats(KE_PORT_STATS* statsOut) {
if (!statsOut)
return;
statsOut->ConnectionsCreated = KiPortReadCounter(&gPortCounters.ConnectionsCreated);
statsOut->DirectDeliveries = KiPortReadCounter(&gPortCounters.DirectDeliveries);
statsOut->DirectBytes = KiPortReadCounter(&gPortCounters.DirectBytes);
statsOut->QueuedMessages = KiPortReadCounter(&gPortCounters.QueuedMessages);
statsOut->QueuedBytes = KiPortReadCounter(&gPortCounters.QueuedBytes);
statsOut->QueuedDeliveries = KiPortReadCounter(&gPortCounters.QueuedDeliveries);
statsOut->RepliesCompleted = KiPortReadCounter(&gPortCounters.RepliesCompleted);
statsOut->Disconnects = KiPortReadCounter(&gPortCounters.Disconnects);
statsOut->ProcessQuotaRejects = KiPortReadCounter(&gPortCounters.ProcessQuotaRejects);
}
// Returns the complete capability rights granted to a newly created endpoint handle.
static SYS_RIGHTS KiPortEndpointRights(bool openPeerProcess) {
SYS_RIGHTS rights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE |
SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY |
SYS_PORT_RIGHT_DISCONNECT;
if (openPeerProcess) rights |= SYS_PORT_RIGHT_OPEN_PEER_PROCESS;
return rights;
}
// Returns one process security identity for kernel-stamped IPC metadata.
static uint32_t KiPortSecurityId(const PROCESS* process) {
return process && process->SecurityContext ? process->SecurityContext->SecurityId : 0u;
}
// Charges one process for the kernel resources retained by a submitted Port message.
static KSTATUS KiPortChargeProcess(PROCESS* process, uint32_t payloadBytes, uint32_t handleCount) {
if (!process || payloadBytes > SYS_PORT_MAX_INLINE_BYTES || handleCount > SYS_PORT_MAX_TRANSFER_HANDLES)
return NTLSTATUS_INVALID_PARAMETER;
if (!ObTryReferenceObject(&process->Header))
return NTLSTATUS_OBJECT_DEAD;
uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock);
if (process->PortOutstandingMessages >= PORT_PROCESS_MESSAGE_LIMIT ||
payloadBytes > PORT_PROCESS_BYTE_LIMIT - process->PortOutstandingBytes ||
handleCount > PORT_PROCESS_HANDLE_LIMIT - process->PortOutstandingHandles) {
KeReleaseSpinlock(&process->PortQuotaLock, flags);
ObDereferenceObject(&process->Header);
NtlAtomicIncrement64(&gPortCounters.ProcessQuotaRejects);
return NTLSTATUS_LIMIT_REACHED;
}
process->PortOutstandingMessages++;
process->PortOutstandingBytes += payloadBytes;
process->PortOutstandingHandles += handleCount;
KeReleaseSpinlock(&process->PortQuotaLock, flags);
return NTLSTATUS_SUCCESS;
}
// Releases one process Port quota charge after delivery, cancellation, or disconnect.
static void KiPortReleaseProcessCharge(PROCESS* process, uint32_t payloadBytes, uint32_t handleCount) {
if (!process)
return;
uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock);
if (process->PortOutstandingMessages == 0 ||
process->PortOutstandingBytes < payloadBytes ||
process->PortOutstandingHandles < handleCount) {
KeReleaseSpinlock(&process->PortQuotaLock, flags);
KePanic(KE_PANIC_CODE_GENERIC,
"port: process quota accounting underflow pid=%u\n",
process->ProcessId);
}
process->PortOutstandingMessages--;
process->PortOutstandingBytes -= payloadBytes;
process->PortOutstandingHandles -= handleCount;
KeReleaseSpinlock(&process->PortQuotaLock, flags);
ObDereferenceObject(&process->Header);
}
// Charges one caller for a retained synchronous call context and locked reply buffer.
static KSTATUS KiPortChargeCall(PROCESS* process) {
if (!process)
return NTLSTATUS_INVALID_PARAMETER;
if (!ObTryReferenceObject(&process->Header))
return NTLSTATUS_OBJECT_DEAD;
uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock);
if (process->PortOutstandingCalls >= PORT_PROCESS_CALL_LIMIT) {
KeReleaseSpinlock(&process->PortQuotaLock, flags);
ObDereferenceObject(&process->Header);
NtlAtomicIncrement64(&gPortCounters.ProcessQuotaRejects);
return NTLSTATUS_LIMIT_REACHED;
}
process->PortOutstandingCalls++;
KeReleaseSpinlock(&process->PortQuotaLock, flags);
return NTLSTATUS_SUCCESS;
}
// Releases a retained synchronous call quota charge and its process reference.
static void KiPortReleaseCallCharge(PROCESS* process) {
if (!process)
return;
uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock);
if (process->PortOutstandingCalls == 0) {
KeReleaseSpinlock(&process->PortQuotaLock, flags);
KePanic(KE_PANIC_CODE_GENERIC,
"port: process call quota underflow pid=%u\n",
process->ProcessId);
}
process->PortOutstandingCalls--;
KeReleaseSpinlock(&process->PortQuotaLock, flags);
ObDereferenceObject(&process->Header);
}
// Drops one shared connection reference after an endpoint object releases it.
static void KiPortDereferenceConnection(PORT_CONNECTION* connection) {
if (!connection)
return;
int32_t refs = NtlAtomicDecrement32(&connection->RefCount);
if (refs > 0)
return;
if (refs < 0) {
KePanic(KE_PANIC_CODE_GENERIC,
"port: connection ref underflow connection=0x%016llX refs=%ld\n",
(uint64_t)(uintptr_t)connection,
refs);
}
if (!LIST_IS_EMPTY(&connection->Calls)) {
KePanic(KE_PANIC_CODE_GENERIC,
"port: destroying connection with pending calls connection=0x%016llX\n",
(uint64_t)(uintptr_t)connection);
}
for (uint32_t side = 0; side < 2u; ++side) {
if (connection->PeerProcesses[side]) {
ObDereferenceObject(&connection->PeerProcesses[side]->Header);
connection->PeerProcesses[side] = (struct PROCESS*)0;
}
}
MmFreePool(connection);
}
// Releases one synchronous-call reference and destroys the context after the last owner leaves.
static void KiPortReleaseCall(PORT_CALL* call);
// Releases every capability capture and payload buffer owned by one queued message.
static void KiPortFreeMessage(PORT_MESSAGE* message) {
if (!message)
return;
for (uint32_t i = 0; i < message->CapturedCount; ++i) {
if (message->Captured[i]) {
KeReleaseCapturedHandle(message->Captured[i]);
message->Captured[i] = (struct KE_CAPTURED_HANDLE*)0;
}
}
if (message->Payload)
MmFreePool(message->Payload);
if (message->Call) {
KiPortReleaseCall(message->Call);
message->Call = (PORT_CALL*)0;
}
if (message->AccountingProcess) {
KiPortReleaseProcessCharge(message->AccountingProcess,
message->AccountedBytes,
message->AccountedHandles);
message->AccountingProcess = (PROCESS*)0;
}
MmFreePool(message);
}
// Closes a batch of handles that were materialized but could not be returned to the receiver.
static void KiPortCloseHandles(PROCESS* process, SYS_HANDLE* handles, uint32_t count) {
if (!process || !handles)
return;
for (uint32_t i = 0; i < count; ++i) {
if (handles[i] != SYS_HANDLE_INVALID) {
(void)KeCloseHandle(process, handles[i]);
handles[i] = SYS_HANDLE_INVALID;
}
}
}
// Captures user-specified handles as kernel capability grants for later delivery.
static KSTATUS KiPortCaptureHandles(PROCESS* sourceProcess,
const SYS_PORT_HANDLE_TRANSFER* transfers,
uint32_t count,
struct KE_CAPTURED_HANDLE** captured) {
if (count == 0)
return NTLSTATUS_SUCCESS;
if (!sourceProcess || !transfers || !captured || count > SYS_PORT_MAX_TRANSFER_HANDLES)
return NTLSTATUS_INVALID_PARAMETER;
for (uint32_t i = 0; i < count; ++i)
captured[i] = (struct KE_CAPTURED_HANDLE*)0;
for (uint32_t i = 0; i < count; ++i) {
uint32_t flags = 0;
if (transfers[i].Flags & SYS_PORT_TRANSFER_FLAG_SAME_RIGHTS)
flags |= KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS;
if (transfers[i].Flags & SYS_PORT_TRANSFER_FLAG_DERIVE)
flags |= KE_HANDLE_TRANSFER_FLAG_DERIVE;
if (transfers[i].Flags & ~(SYS_PORT_TRANSFER_FLAG_SAME_RIGHTS |
SYS_PORT_TRANSFER_FLAG_DERIVE)) {
for (uint32_t j = 0; j < i; ++j)
KeReleaseCapturedHandle(captured[j]);
return NTLSTATUS_INVALID_PARAMETER;
}
KSTATUS status = KeCaptureHandleForTransfer(sourceProcess,
transfers[i].Handle,
transfers[i].DesiredRights,
flags,
&captured[i]);
if (!KSUCCESS(status)) {
for (uint32_t j = 0; j < i; ++j)
KeReleaseCapturedHandle(captured[j]);
return status;
}
}
return NTLSTATUS_SUCCESS;
}
// Allocates one message header and captures its monotonic identity and sender metadata.
static KSTATUS KiPortCreateMessage(PROCESS* senderProcess,
THREAD* senderThread,
uint32_t type,
uint32_t messageId,
uint32_t payloadLength,
const SYS_PORT_HANDLE_TRANSFER* transfers,
uint32_t transferCount,
PORT_CALL* call,
PORT_MESSAGE** messageOut) {
if (!senderProcess || !senderThread || !messageOut)
return NTLSTATUS_INVALID_PARAMETER;
*messageOut = (PORT_MESSAGE*)0;
if (payloadLength > SYS_PORT_MAX_INLINE_BYTES || transferCount > SYS_PORT_MAX_TRANSFER_HANDLES)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS quotaStatus = KiPortChargeProcess(senderProcess, payloadLength, transferCount);
if (!KSUCCESS(quotaStatus))
return quotaStatus;
PORT_MESSAGE* message = (PORT_MESSAGE*)MmAllocatePoolTag(sizeof(PORT_MESSAGE),
MM_POOL_TAG('p', 'm', 's', 'g'));
if (!message) {
KiPortReleaseProcessCharge(senderProcess, payloadLength, transferCount);
return NTLSTATUS_NO_MEMORY;
}
memset(message, 0, sizeof(*message));
LIST_INIT(&message->Link);
message->AccountingProcess = senderProcess;
message->AccountedBytes = payloadLength;
message->AccountedHandles = transferCount;
if (messageId == 0) {
KSTATUS status = KeAllocateId32(&gPortMessageIds, &messageId);
if (!KSUCCESS(status)) {
KiPortFreeMessage(message);
return status;
}
}
message->Info.Size = sizeof(message->Info);
message->Info.MessageId = messageId;
message->Info.Type = type;
message->Info.PayloadLength = payloadLength;
message->Info.SenderProcessId = senderProcess->ProcessId;
message->Info.SenderThreadId = senderThread->ThreadId;
message->Info.SenderSecurityId = KiPortSecurityId(senderProcess);
message->CapturedCount = transferCount;
message->Info.HandleCount = transferCount;
message->Call = call;
if (call)
NtlAtomicIncrement32(&call->RefCount);
KSTATUS status = KiPortCaptureHandles(senderProcess,
transfers,
transferCount,
message->Captured);
if (!KSUCCESS(status)) {
KiPortFreeMessage(message);
return status;
}
*messageOut = message;
return NTLSTATUS_SUCCESS;
}
// Copies the sender payload into kernel-owned storage when immediate rendezvous is unavailable.
static KSTATUS KiPortSnapshotPayload(PORT_MESSAGE* message, const void* payload, uint32_t payloadLength) {
if (!message)
return NTLSTATUS_INVALID_PARAMETER;
if (message->Payload)
return NTLSTATUS_SUCCESS;
if (payloadLength == 0) {
message->Info.PayloadLength = 0;
return NTLSTATUS_SUCCESS;
}
if (!payload || payloadLength > SYS_PORT_MAX_INLINE_BYTES)
return NTLSTATUS_INVALID_PARAMETER;
void* copy = MmAllocatePoolTag(payloadLength, MM_POOL_TAG('p', 'd', 'a', 't'));
if (!copy)
return NTLSTATUS_NO_MEMORY;
KSTATUS status = CopyFromUser(copy, payload, payloadLength);
if (!KSUCCESS(status)) {
MmFreePool(copy);
return status;
}
message->Payload = copy;
message->Info.PayloadLength = payloadLength;
return NTLSTATUS_SUCCESS;
}
// Updates endpoint queue events after one message is removed.
static void KiPortNoteDequeuedLocked(PORT* endpoint, PORT_MESSAGE* message) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !message)
return;
PORT_ENDPOINT_STATE* state = &endpoint->u.Endpoint;
if (state->IncomingCount == 0 || state->IncomingBytes < message->Info.PayloadLength ||
state->IncomingHandles < message->CapturedCount) {
KePanic(KE_PANIC_CODE_GENERIC, "port: queue accounting underflow\n");
}
state->IncomingCount--;
state->IncomingBytes -= message->Info.PayloadLength;
state->IncomingHandles -= message->CapturedCount;
KeSignalEvent(state->SpaceEvent);
if (state->IncomingCount == 0 &&
!state->Connection->Disconnected[1u - state->Side])
KeClearEvent(state->WaitEvent);
}
// Returns whether one queued message can fit within the peer endpoint quotas.
static bool KiPortMessageFitsLocked(const PORT* endpoint, const PORT_MESSAGE* message) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !message)
return false;
const PORT_ENDPOINT_STATE* state = &endpoint->u.Endpoint;
if (state->IncomingCount >= SYS_PORT_QUEUE_MESSAGE_LIMIT)
return false;
if (message->Info.PayloadLength > SYS_PORT_QUEUE_BYTE_LIMIT - state->IncomingBytes)
return false;
if (message->CapturedCount > SYS_PORT_QUEUE_HANDLE_LIMIT - state->IncomingHandles)
return false;
return true;
}
// Completes a direct receive rendezvous after the connection lock has claimed the waiting receiver.
static KSTATUS KiPortDeliverDirect(PORT* targetEndpoint,
PORT_RECEIVE_WAIT* receiver,
PORT_MESSAGE* message,
PROCESS* senderProcess,
const void* sourcePayload) {
if (!targetEndpoint || !receiver || !message || !senderProcess)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = NTLSTATUS_SUCCESS;
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0};
uint32_t handleCount = message->CapturedCount;
if (handleCount) {
status = KeMaterializeCapturedHandles(receiver->Process,
message->Captured,
handleCount,
handles);
}
if (KSUCCESS(status) && message->Info.PayloadLength) {
status = MmCopyUserToUser(senderProcess,
sourcePayload,
receiver->Process,
receiver->Payload,
message->Info.PayloadLength);
}
if (!KSUCCESS(status)) {
KiPortCloseHandles(receiver->Process, handles, handleCount);
receiver->HandleCount = 0;
} else {
memcpy(receiver->Handles, handles, sizeof(SYS_HANDLE) * handleCount);
receiver->HandleCount = handleCount;
}
receiver->Info = message->Info;
receiver->Status = status;
if (KSUCCESS(status)) {
NtlAtomicIncrement64(&gPortCounters.DirectDeliveries);
NtlAtomicExchangeAdd64(&gPortCounters.DirectBytes, (int64_t)message->Info.PayloadLength);
}
NtlAtomicExchange32(&receiver->Completed, 1);
KeSignalEvent(receiver->CompletionEvent);
return status;
}
// Completes one synchronous call with an error that does not include a reply payload.
static void KiPortCompleteCallError(PORT_CALL* call, KSTATUS status) {
if (!call)
return;
call->Status = status;
call->ReplyHandleCount = 0;
NtlAtomicExchange32(&call->State, PORT_CALL_COMPLETED);
KeSignalEvent(call->CompletionEvent);
}
// Removes one queued request belonging to a call while the connection lock is held.
static PORT_MESSAGE* KiPortRemoveQueuedCallLocked(PORT_CONNECTION* connection, PORT_CALL* call) {
if (!connection || !call)
return (PORT_MESSAGE*)0;
uint32_t targetSide = 1u - call->CallerSide;
PORT* endpoint = connection->Endpoints[targetSide];
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT)
return (PORT_MESSAGE*)0;
LIST_ENTRY* head = &endpoint->u.Endpoint.Incoming;
for (LIST_ENTRY* link = head->Fwd; link != head; link = link->Fwd) {
PORT_MESSAGE* message = CONTAINING_RECORD(link, PORT_MESSAGE, Link);
if (message->Call == call) {
KeListRemoveChecked(&message->Link);
KiPortNoteDequeuedLocked(endpoint, message);
return message;
}
}
return (PORT_MESSAGE*)0;
}
// Disconnects one endpoint side, drains undelivered messages, and completes pending calls.
static void KiPortMarkDisconnected(PORT* endpoint, bool removeEndpointPointer) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT)
return;
PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection;
if (!connection)
return;
LIST_ENTRY abandonedMessages;
LIST_ENTRY completedCalls;
LIST_INIT(&abandonedMessages);
LIST_INIT(&completedCalls);
uint32_t side = endpoint->u.Endpoint.Side;
uintptr_t flags = KeAcquireSpinlock(&connection->Lock);
if (!connection->Disconnected[side]) {
connection->Disconnected[side] = 1;
NtlAtomicIncrement64(&gPortCounters.Disconnects);
}
if (removeEndpointPointer && connection->Endpoints[side] == endpoint)
connection->Endpoints[side] = (PORT*)0;
while (!LIST_IS_EMPTY(&endpoint->u.Endpoint.WaitingReceivers)) {
LIST_ENTRY* link = KeListPopHeadChecked(&endpoint->u.Endpoint.WaitingReceivers);
PORT_RECEIVE_WAIT* waiter = CONTAINING_RECORD(link, PORT_RECEIVE_WAIT, Link);
if (NtlAtomicCompareExchange32(&waiter->Claimed, 1, 0) == 0) {
waiter->Status = NTLSTATUS_PORT_DISCONNECTED;
NtlAtomicExchange32(&waiter->Completed, 1);
KeSignalEvent(waiter->CompletionEvent);
}
}
while (!LIST_IS_EMPTY(&endpoint->u.Endpoint.Incoming)) {
LIST_ENTRY* link = KeListPopHeadChecked(&endpoint->u.Endpoint.Incoming);
PORT_MESSAGE* message = CONTAINING_RECORD(link, PORT_MESSAGE, Link);
if (endpoint->u.Endpoint.IncomingCount)
endpoint->u.Endpoint.IncomingCount--;
if (endpoint->u.Endpoint.IncomingBytes >= message->Info.PayloadLength)
endpoint->u.Endpoint.IncomingBytes -= message->Info.PayloadLength;
if (endpoint->u.Endpoint.IncomingHandles >= message->CapturedCount)
endpoint->u.Endpoint.IncomingHandles -= message->CapturedCount;
KeListInsertTailChecked(&abandonedMessages, &message->Link);
}
LIST_ENTRY* callHead = &connection->Calls;
LIST_ENTRY* callLink = callHead->Fwd;
while (callLink != callHead) {
PORT_CALL* call = CONTAINING_RECORD(callLink, PORT_CALL, Link);
callLink = callLink->Fwd;
int32_t state = NtlAtomicLoad32(&call->State);
if (state == PORT_CALL_REPLYING || state == PORT_CALL_COMPLETED || state == PORT_CALL_CANCELLED)
continue;
PORT_MESSAGE* queuedMessage = KiPortRemoveQueuedCallLocked(connection, call);
if (queuedMessage)
KeListInsertTailChecked(&abandonedMessages, &queuedMessage->Link);
KeListRemoveChecked(&call->Link);
call->Status = NTLSTATUS_PORT_DISCONNECTED;
NtlAtomicExchange32(&call->State, PORT_CALL_COMPLETED);
KeListInsertTailChecked(&completedCalls, &call->Link);
}
PORT* peer = connection->Endpoints[1u - side];
if (peer) {
KeSignalEvent(peer->u.Endpoint.WaitEvent);
KeSignalEvent(peer->u.Endpoint.SpaceEvent);
}
KeSignalEvent(endpoint->u.Endpoint.WaitEvent);
KeSignalEvent(endpoint->u.Endpoint.SpaceEvent);
KeReleaseSpinlock(&connection->Lock, flags);
while (!LIST_IS_EMPTY(&abandonedMessages)) {
LIST_ENTRY* link = KeListPopHeadChecked(&abandonedMessages);
PORT_MESSAGE* message = CONTAINING_RECORD(link, PORT_MESSAGE, Link);
KiPortFreeMessage(message);
}
while (!LIST_IS_EMPTY(&completedCalls)) {
LIST_ENTRY* link = KeListPopHeadChecked(&completedCalls);
PORT_CALL* call = CONTAINING_RECORD(link, PORT_CALL, Link);
KeSignalEvent(call->CompletionEvent);
}
}
// Destroys either a listener or endpoint Port object.
static void KiPortDestroy(OBJECT* object) {
PORT* port = (PORT*)object;
if (!port)
return;
if (port->Kind == PORT_KIND_LISTENER) {
KeSystemServiceUnregisterListener(port);
while (true) {
PORT_PENDING_CONNECTION* pending = (PORT_PENDING_CONNECTION*)0;
uintptr_t flags = KeAcquireSpinlock(&port->u.Listener.Lock);
LIST_ENTRY* link = KeListPopHeadChecked(&port->u.Listener.Pending);
if (link) {
pending = CONTAINING_RECORD(link, PORT_PENDING_CONNECTION, Link);
if (port->u.Listener.PendingCount)
port->u.Listener.PendingCount--;
}
KeReleaseSpinlock(&port->u.Listener.Lock, flags);
if (!pending)
break;
ObDereferenceObject((OBJECT*)pending->ServerEndpoint);
MmFreePool(pending);
}
if (port->u.Listener.WaitEvent)
ObDereferenceObject(&port->u.Listener.WaitEvent->Header);
return;
}
if (port->Kind == PORT_KIND_ENDPOINT) {
KiPortMarkDisconnected(port, true);
if (port->u.Endpoint.WaitEvent)
ObDereferenceObject(&port->u.Endpoint.WaitEvent->Header);
if (port->u.Endpoint.SpaceEvent)
ObDereferenceObject(&port->u.Endpoint.SpaceEvent->Header);
KiPortDereferenceConnection(port->u.Endpoint.Connection);
port->u.Endpoint.Connection = (PORT_CONNECTION*)0;
}
}
// Allocates one endpoint object attached to a shared connection side.
static KSTATUS KiPortCreateEndpoint(PORT_CONNECTION* connection, uint32_t side, PORT** endpointOut) {
if (!connection || !endpointOut || side > 1u)
return NTLSTATUS_INVALID_PARAMETER;
*endpointOut = (PORT*)0;
PORT* endpoint = (PORT*)ObAllocateObject(ObjectTypePort, sizeof(PORT));
if (!endpoint)
return NTLSTATUS_NO_MEMORY;
endpoint->Header.Destroy = KiPortDestroy;
endpoint->Kind = PORT_KIND_ENDPOINT;
endpoint->u.Endpoint.Connection = connection;
endpoint->u.Endpoint.Side = side;
LIST_INIT(&endpoint->u.Endpoint.Incoming);
LIST_INIT(&endpoint->u.Endpoint.WaitingReceivers);
KSTATUS status = KeEventCreate(EventManualReset, 0, &endpoint->u.Endpoint.WaitEvent);
if (!KSUCCESS(status)) {
endpoint->u.Endpoint.Connection = (PORT_CONNECTION*)0;
ObDereferenceObject(&endpoint->Header);
return status;
}
status = KeEventCreate(EventManualReset, 1, &endpoint->u.Endpoint.SpaceEvent);
if (!KSUCCESS(status)) {
endpoint->u.Endpoint.Connection = (PORT_CONNECTION*)0;
ObDereferenceObject(&endpoint->Header);
return status;
}
*endpointOut = endpoint;
return NTLSTATUS_SUCCESS;
}
// Allocates one paired connection and its two endpoint objects.
static KSTATUS KiPortCreateConnection(PROCESS* clientProcess,
SYS_RIGHTS serverPeerProcessRights,
PORT** clientOut,
PORT** serverOut) {
if (!clientProcess || !clientOut || !serverOut)
return NTLSTATUS_INVALID_PARAMETER;
*clientOut = (PORT*)0;
*serverOut = (PORT*)0;
PORT_CONNECTION* connection = (PORT_CONNECTION*)MmAllocatePoolTag(sizeof(PORT_CONNECTION),
MM_POOL_TAG('p', 'c', 'o', 'n'));
if (!connection)
return NTLSTATUS_NO_MEMORY;
memset(connection, 0, sizeof(*connection));
connection->RefCount = 2;
connection->Lock = (SPINLOCK)SPINLOCK_INIT;
LIST_INIT(&connection->Calls);
if (serverPeerProcessRights != 0) {
if (!ObTryReferenceObject(&clientProcess->Header)) {
connection->RefCount = 1;
KiPortDereferenceConnection(connection);
return NTLSTATUS_OBJECT_DEAD;
}
connection->PeerProcesses[1] = clientProcess;
connection->PeerProcessRights[1] = serverPeerProcessRights;
}
PORT* client = (PORT*)0;
PORT* server = (PORT*)0;
KSTATUS status = KiPortCreateEndpoint(connection, 0, &client);
if (!KSUCCESS(status)) {
connection->RefCount = 1;
KiPortDereferenceConnection(connection);
return status;
}
status = KiPortCreateEndpoint(connection, 1, &server);
if (!KSUCCESS(status)) {
connection->RefCount = 1;
ObDereferenceObject(&client->Header);
return status;
}
uintptr_t flags = KeAcquireSpinlock(&connection->Lock);
connection->Endpoints[0] = client;
connection->Endpoints[1] = server;
KeReleaseSpinlock(&connection->Lock, flags);
*clientOut = client;
*serverOut = server;
return NTLSTATUS_SUCCESS;
}
// Creates one bounded listener Port with a waitable pending-connection event.
KSTATUS KePortCreate(uint32_t backlog, SYS_RIGHTS peerProcessRights, PORT** portOut) {
if (!portOut)
return NTLSTATUS_INVALID_PARAMETER;
*portOut = (PORT*)0;
if ((peerProcessRights & ~KeGetValidHandleRights(ObjectTypeProcess)) != 0)
return NTLSTATUS_INVALID_PARAMETER;
if (backlog == 0)
backlog = SYS_PORT_DEFAULT_BACKLOG;
if (backlog > SYS_PORT_MAX_BACKLOG)
return NTLSTATUS_INVALID_PARAMETER;
PORT* port = (PORT*)ObAllocateObject(ObjectTypePort, sizeof(PORT));
if (!port)
return NTLSTATUS_NO_MEMORY;
port->Header.Destroy = KiPortDestroy;
port->Kind = PORT_KIND_LISTENER;
port->u.Listener.Lock = (SPINLOCK)SPINLOCK_INIT;
port->u.Listener.Backlog = backlog;
port->u.Listener.PeerProcessRights = peerProcessRights;
LIST_INIT(&port->u.Listener.Pending);
KSTATUS status = KeEventCreate(EventManualReset, 0, &port->u.Listener.WaitEvent);
if (!KSUCCESS(status)) {
ObDereferenceObject(&port->Header);
return status;
}
*portOut = port;
return NTLSTATUS_SUCCESS;
}
// Returns whether one Port object is a listener.
bool KePortIsListener(const PORT* port) {
return port && port->Kind == PORT_KIND_LISTENER;
}
// Returns the event whose signaled state represents listener or endpoint readiness.
EVENT* KePortWaitEvent(PORT* port) {
if (!port)
return (EVENT*)0;
if (port->Kind == PORT_KIND_LISTENER)
return port->u.Listener.WaitEvent;
if (port->Kind == PORT_KIND_ENDPOINT)
return port->u.Endpoint.WaitEvent;
return (EVENT*)0;
}
// Connects one client process to a listener by queueing the server endpoint for later acceptance.
KSTATUS KePortConnect(PORT* listener,
PROCESS* clientProcess,
THREAD* clientThread,
SYS_HANDLE* endpointHandleOut) {
if (!listener || listener->Kind != PORT_KIND_LISTENER || !clientProcess || !clientThread || !endpointHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
*endpointHandleOut = SYS_HANDLE_INVALID;
uintptr_t flags = KeAcquireSpinlock(&listener->u.Listener.Lock);
if (listener->u.Listener.PendingCount >= listener->u.Listener.Backlog) {
KeReleaseSpinlock(&listener->u.Listener.Lock, flags);
return NTLSTATUS_LIMIT_REACHED;
}
KeReleaseSpinlock(&listener->u.Listener.Lock, flags);
PORT* clientEndpoint = (PORT*)0;
PORT* serverEndpoint = (PORT*)0;
KSTATUS status = KiPortCreateConnection(clientProcess,
listener->u.Listener.PeerProcessRights,
&clientEndpoint,
&serverEndpoint);
if (!KSUCCESS(status))
return status;
SYS_HANDLE clientHandle = SYS_HANDLE_INVALID;
status = KeCreateHandle(clientProcess,
&clientEndpoint->Header,
KiPortEndpointRights(false),
&clientHandle);
if (!KSUCCESS(status)) {
ObDereferenceObject(&serverEndpoint->Header);
ObDereferenceObject(&clientEndpoint->Header);
return status;
}
ObDereferenceObject(&clientEndpoint->Header);
PORT_PENDING_CONNECTION* pending = (PORT_PENDING_CONNECTION*)MmAllocatePoolTag(sizeof(*pending),
MM_POOL_TAG('p', 'c', 'n', 'q'));
if (!pending) {
(void)KeCloseHandle(clientProcess, clientHandle);
ObDereferenceObject(&serverEndpoint->Header);
return NTLSTATUS_NO_MEMORY;
}
memset(pending, 0, sizeof(*pending));
LIST_INIT(&pending->Link);
pending->ServerEndpoint = serverEndpoint;
pending->Info.Size = sizeof(pending->Info);
pending->Info.ClientProcessId = clientProcess->ProcessId;
pending->Info.ClientThreadId = clientThread->ThreadId;
pending->Info.ClientSecurityId = KiPortSecurityId(clientProcess);
flags = KeAcquireSpinlock(&listener->u.Listener.Lock);
if (listener->u.Listener.PendingCount >= listener->u.Listener.Backlog) {
KeReleaseSpinlock(&listener->u.Listener.Lock, flags);
MmFreePool(pending);
ObDereferenceObject(&serverEndpoint->Header);
(void)KeCloseHandle(clientProcess, clientHandle);
return NTLSTATUS_LIMIT_REACHED;
}
KeListInsertTailChecked(&listener->u.Listener.Pending, &pending->Link);
listener->u.Listener.PendingCount++;
NtlAtomicIncrement64(&gPortCounters.ConnectionsCreated);
KeSignalEvent(listener->u.Listener.WaitEvent);
KeReleaseSpinlock(&listener->u.Listener.Lock, flags);
*endpointHandleOut = clientHandle;
return NTLSTATUS_SUCCESS;
}
// Accepts the oldest queued connection, waiting on the listener event when requested.
KSTATUS KePortAccept(PORT* listener,
PROCESS* serverProcess,
uint64_t timeoutMilliseconds,
SYS_HANDLE* endpointHandleOut,
SYS_PORT_CONNECTION_INFO* infoOut) {
if (!listener || listener->Kind != PORT_KIND_LISTENER || !serverProcess || !endpointHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
*endpointHandleOut = SYS_HANDLE_INVALID;
uint64_t deadline = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE)
deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds);
while (true) {
PORT_PENDING_CONNECTION* pending = (PORT_PENDING_CONNECTION*)0;
uintptr_t flags = KeAcquireSpinlock(&listener->u.Listener.Lock);
LIST_ENTRY* link = KeListPopHeadChecked(&listener->u.Listener.Pending);
if (link) {
pending = CONTAINING_RECORD(link, PORT_PENDING_CONNECTION, Link);
if (listener->u.Listener.PendingCount == 0) {
KeReleaseSpinlock(&listener->u.Listener.Lock, flags);
KePanic(KE_PANIC_CODE_GENERIC, "port: listener pending count underflow\n");
}
listener->u.Listener.PendingCount--;
}
if (listener->u.Listener.PendingCount == 0)
KeClearEvent(listener->u.Listener.WaitEvent);
KeReleaseSpinlock(&listener->u.Listener.Lock, flags);
if (pending) {
SYS_HANDLE handle = SYS_HANDLE_INVALID;
KSTATUS status = KeCreateHandle(serverProcess,
&pending->ServerEndpoint->Header,
KiPortEndpointRights(listener->u.Listener.PeerProcessRights != 0),
&handle);
if (KSUCCESS(status)) {
if (infoOut)
*infoOut = pending->Info;
*endpointHandleOut = handle;
}
ObDereferenceObject(&pending->ServerEndpoint->Header);
MmFreePool(pending);
return status;
}
if (timeoutMilliseconds == 0)
return NTLSTATUS_WOULD_BLOCK;
uint64_t wait = timeoutMilliseconds;
if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) {
wait = KeMonotonicMillisecondsUntil(deadline);
if (wait == 0)
return NTLSTATUS_TIMEOUT;
}
EVENT* events[1] = { listener->u.Listener.WaitEvent };
KSTATUS status = KeWaitForMultipleEvents(events, 1, 0, wait, (uint32_t*)0);
if (!KSUCCESS(status))
return status;
}
}
// Submits a prepared message to the peer, using direct rendezvous when a receiver is already waiting.
// This function consumes the message on every return path; queued success transfers ownership to the peer queue.
static KSTATUS KiPortSubmitMessage(PORT* endpoint,
PROCESS* senderProcess,
const void* sourcePayload,
uint32_t payloadLength,
PORT_MESSAGE* message,
uint64_t timeoutMilliseconds) {
if (!message)
return NTLSTATUS_INVALID_PARAMETER;
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !senderProcess ||
payloadLength > SYS_PORT_MAX_INLINE_BYTES || (payloadLength && !sourcePayload)) {
KiPortFreeMessage(message);
return NTLSTATUS_INVALID_PARAMETER;
}
message->Info.PayloadLength = payloadLength;
PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection;
uint32_t peerSide = 1u - endpoint->u.Endpoint.Side;
uint64_t deadline = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE)
deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds);
bool snapshotted = false;
while (true) {
PORT* peer = (PORT*)0;
PORT_RECEIVE_WAIT* receiver = (PORT_RECEIVE_WAIT*)0;
uintptr_t flags = KeAcquireSpinlock(&connection->Lock);
if (connection->Disconnected[endpoint->u.Endpoint.Side] ||
connection->Disconnected[peerSide] ||
!(peer = connection->Endpoints[peerSide])) {
KeReleaseSpinlock(&connection->Lock, flags);
KiPortFreeMessage(message);
return NTLSTATUS_PORT_DISCONNECTED;
}
if (!snapshotted && peer->u.Endpoint.IncomingCount == 0) {
LIST_ENTRY* waitHead = &peer->u.Endpoint.WaitingReceivers;
for (LIST_ENTRY* link = waitHead->Fwd; link != waitHead; link = link->Fwd) {
PORT_RECEIVE_WAIT* candidate = CONTAINING_RECORD(link, PORT_RECEIVE_WAIT, Link);
if (payloadLength > candidate->PayloadCapacity ||
message->CapturedCount > candidate->HandleCapacity)
continue;
if (NtlAtomicCompareExchange32(&candidate->Claimed, 1, 0) != 0)
continue;
receiver = candidate;
KeListRemoveChecked(&receiver->Link);
break;
}
if (receiver) {
if (message->Call)
NtlAtomicExchange32(&message->Call->State, PORT_CALL_DELIVERED);
if (!ObTryReferenceObject(&peer->Header)) {
receiver->Claimed = 0;
KeListInsertHeadChecked(&peer->u.Endpoint.WaitingReceivers, &receiver->Link);
KeReleaseSpinlock(&connection->Lock, flags);
KiPortFreeMessage(message);
return NTLSTATUS_OBJECT_DEAD;
}
KeReleaseSpinlock(&connection->Lock, flags);
KSTATUS status = KiPortDeliverDirect(peer,
receiver,
message,
senderProcess,
sourcePayload);
ObDereferenceObject(&peer->Header);
KiPortFreeMessage(message);
return status;
}
}
KeReleaseSpinlock(&connection->Lock, flags);
if (!snapshotted) {
KSTATUS status = KiPortSnapshotPayload(message, sourcePayload, payloadLength);
if (!KSUCCESS(status)) {
KiPortFreeMessage(message);
return status;
}
snapshotted = true;
}
flags = KeAcquireSpinlock(&connection->Lock);
if (connection->Disconnected[endpoint->u.Endpoint.Side] ||
connection->Disconnected[peerSide] ||
!(peer = connection->Endpoints[peerSide])) {
KeReleaseSpinlock(&connection->Lock, flags);
KiPortFreeMessage(message);
return NTLSTATUS_PORT_DISCONNECTED;
}
if (KiPortMessageFitsLocked(peer, message)) {
KeListInsertTailChecked(&peer->u.Endpoint.Incoming, &message->Link);
peer->u.Endpoint.IncomingCount++;
peer->u.Endpoint.IncomingBytes += message->Info.PayloadLength;
peer->u.Endpoint.IncomingHandles += message->CapturedCount;
NtlAtomicIncrement64(&gPortCounters.QueuedMessages);
NtlAtomicExchangeAdd64(&gPortCounters.QueuedBytes, (int64_t)message->Info.PayloadLength);
if (message->Call)
NtlAtomicExchange32(&message->Call->State, PORT_CALL_PENDING);
KeSignalEvent(peer->u.Endpoint.WaitEvent);
if (peer->u.Endpoint.IncomingCount >= SYS_PORT_QUEUE_MESSAGE_LIMIT ||
peer->u.Endpoint.IncomingBytes >= SYS_PORT_QUEUE_BYTE_LIMIT ||
peer->u.Endpoint.IncomingHandles >= SYS_PORT_QUEUE_HANDLE_LIMIT)
KeClearEvent(peer->u.Endpoint.SpaceEvent);
KeReleaseSpinlock(&connection->Lock, flags);
return NTLSTATUS_SUCCESS;
}
KeClearEvent(peer->u.Endpoint.SpaceEvent);
EVENT* spaceEvent = peer->u.Endpoint.SpaceEvent;
if (!ObTryReferenceObject(&spaceEvent->Header)) {
KeReleaseSpinlock(&connection->Lock, flags);
KiPortFreeMessage(message);
return NTLSTATUS_OBJECT_DEAD;
}
KeReleaseSpinlock(&connection->Lock, flags);
if (timeoutMilliseconds == 0) {
ObDereferenceObject(&spaceEvent->Header);
KiPortFreeMessage(message);
return NTLSTATUS_WOULD_BLOCK;
}
uint64_t wait = timeoutMilliseconds;
if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) {
wait = KeMonotonicMillisecondsUntil(deadline);
if (wait == 0) {
ObDereferenceObject(&spaceEvent->Header);
KiPortFreeMessage(message);
return NTLSTATUS_TIMEOUT;
}
}
EVENT* events[1] = { spaceEvent };
KSTATUS status = KeWaitForMultipleEvents(events, 1, 0, wait, (uint32_t*)0);
ObDereferenceObject(&spaceEvent->Header);
if (!KSUCCESS(status)) {
KiPortFreeMessage(message);
return status;
}
}
}
// Sends one one-way copied notification to the peer endpoint.
KSTATUS KePortSend(PORT* endpoint,
PROCESS* senderProcess,
THREAD* senderThread,
const void* payload,
uint32_t payloadLength,
const SYS_PORT_HANDLE_TRANSFER* transfers,
uint32_t transferCount,
uint64_t timeoutMilliseconds) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !senderProcess || !senderThread)
return NTLSTATUS_INVALID_PARAMETER;
PORT_MESSAGE* message = (PORT_MESSAGE*)0;
KSTATUS status = KiPortCreateMessage(senderProcess,
senderThread,
SYS_PORT_MESSAGE_NOTIFICATION,
0,
payloadLength,
transfers,
transferCount,
(PORT_CALL*)0,
&message);
if (!KSUCCESS(status))
return status;
return KiPortSubmitMessage(endpoint,
senderProcess,
payload,
payloadLength,
message,
timeoutMilliseconds);
}
// Delivers one already-dequeued kernel-buffered message to the current receiver.
static KSTATUS KiPortDeliverQueued(PORT* endpoint,
PROCESS* receiverProcess,
PORT_MESSAGE* message,
void* payload,
SYS_HANDLE* handlesOut,
SYS_PORT_MESSAGE_INFO* infoOut) {
if (!endpoint || !receiverProcess || !message || !infoOut)
return NTLSTATUS_INVALID_PARAMETER;
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0};
KSTATUS status = NTLSTATUS_SUCCESS;
if (message->CapturedCount) {
status = KeMaterializeCapturedHandles(receiverProcess,
message->Captured,
message->CapturedCount,
handles);
}
if (KSUCCESS(status) && message->Info.PayloadLength)
status = CopyToUser(payload, message->Payload, message->Info.PayloadLength);
if (!KSUCCESS(status)) {
KiPortCloseHandles(receiverProcess, handles, message->CapturedCount);
if (message->Call) {
PORT_CALL* call = message->Call;
PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection;
bool completeCall = false;
uintptr_t flags = KeAcquireSpinlock(&connection->Lock);
int32_t callState = NtlAtomicLoad32(&call->State);
if (callState != PORT_CALL_CANCELLED && callState != PORT_CALL_COMPLETED) {
if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link)
KeListRemoveChecked(&call->Link);
completeCall = true;
}
KeReleaseSpinlock(&connection->Lock, flags);
if (completeCall)
KiPortCompleteCallError(call, status);
}
return status;
}
if (message->CapturedCount)
memcpy(handlesOut, handles, sizeof(SYS_HANDLE) * message->CapturedCount);
*infoOut = message->Info;
NtlAtomicIncrement64(&gPortCounters.QueuedDeliveries);
return NTLSTATUS_SUCCESS;
}
// Receives one copied message or registers one direct-delivery rendezvous while blocked.
KSTATUS KePortReceive(PORT* endpoint,
PROCESS* receiverProcess,
void* payload,
uint32_t payloadCapacity,
SYS_HANDLE* handlesOut,
uint32_t handleCapacity,
SYS_PORT_MESSAGE_INFO* infoOut,
uint64_t timeoutMilliseconds) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !receiverProcess || !infoOut)
return NTLSTATUS_INVALID_PARAMETER;
if (payloadCapacity > SYS_PORT_MAX_INLINE_BYTES ||
handleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES ||
(payloadCapacity && !payload) || (handleCapacity && !handlesOut))
return NTLSTATUS_INVALID_PARAMETER;
PORT_RECEIVE_WAIT wait;
memset(&wait, 0, sizeof(wait));
LIST_INIT(&wait.Link);
wait.Process = receiverProcess;
wait.Payload = payload;
wait.PayloadCapacity = payloadCapacity;
wait.HandleCapacity = handleCapacity;
wait.Status = NTLSTATUS_PENDING;
if (payloadCapacity) {
KSTATUS status = MmLockUserBuffer(receiverProcess,
payload,
payloadCapacity,
1,
&wait.LockedPayload);
if (!KSUCCESS(status))
return status;
}
if (timeoutMilliseconds != 0) {
KSTATUS status = KeEventCreate(EventAutoReset, 0, &wait.CompletionEvent);
if (!KSUCCESS(status)) {
if (wait.LockedPayload)
MmUnlockUserBuffer(wait.LockedPayload);
return status;
}
}
PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection;
uint64_t deadline = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE)
deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds);
KSTATUS result = NTLSTATUS_FAILURE;
while (true) {
PORT_MESSAGE* message = (PORT_MESSAGE*)0;
uintptr_t flags = KeAcquireSpinlock(&connection->Lock);
if (!LIST_IS_EMPTY(&endpoint->u.Endpoint.Incoming)) {
message = CONTAINING_RECORD(endpoint->u.Endpoint.Incoming.Fwd,
PORT_MESSAGE,
Link);
if (message->Info.PayloadLength > payloadCapacity ||
message->CapturedCount > handleCapacity) {
*infoOut = message->Info;
KeReleaseSpinlock(&connection->Lock, flags);
result = NTLSTATUS_BUFFER_TOO_SMALL;
break;
}
KeListRemoveChecked(&message->Link);
KiPortNoteDequeuedLocked(endpoint, message);
if (message->Call)
NtlAtomicExchange32(&message->Call->State, PORT_CALL_DELIVERED);
KeReleaseSpinlock(&connection->Lock, flags);
result = KiPortDeliverQueued(endpoint,
receiverProcess,
message,
payload,
handlesOut,
infoOut);
KiPortFreeMessage(message);
break;
}
uint32_t peerSide = 1u - endpoint->u.Endpoint.Side;
if (connection->Disconnected[endpoint->u.Endpoint.Side] ||
connection->Disconnected[peerSide]) {
KeReleaseSpinlock(&connection->Lock, flags);
result = NTLSTATUS_PORT_DISCONNECTED;
break;
}
if (timeoutMilliseconds == 0) {
KeReleaseSpinlock(&connection->Lock, flags);
result = NTLSTATUS_WOULD_BLOCK;
break;
}
if (wait.Link.Fwd == &wait.Link && wait.Link.Bwd == &wait.Link)
KeListInsertTailChecked(&endpoint->u.Endpoint.WaitingReceivers, &wait.Link);
KeClearEvent(endpoint->u.Endpoint.WaitEvent);
KeReleaseSpinlock(&connection->Lock, flags);
uint64_t waitMilliseconds = timeoutMilliseconds;
if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) {
waitMilliseconds = KeMonotonicMillisecondsUntil(deadline);
if (waitMilliseconds == 0)
waitMilliseconds = 1;
}
EVENT* events[2] = { wait.CompletionEvent, endpoint->u.Endpoint.WaitEvent };
KSTATUS waitStatus = KeWaitForMultipleEvents(events,
2,
0,
waitMilliseconds,
(uint32_t*)0);
flags = KeAcquireSpinlock(&connection->Lock);
if (NtlAtomicLoad32(&wait.Completed) != 0) {
if (wait.Link.Fwd != &wait.Link || wait.Link.Bwd != &wait.Link)
KeListRemoveChecked(&wait.Link);
if (endpoint->u.Endpoint.IncomingCount == 0 &&
!connection->Disconnected[1u - endpoint->u.Endpoint.Side])
KeClearEvent(endpoint->u.Endpoint.WaitEvent);
KeReleaseSpinlock(&connection->Lock, flags);
result = wait.Status;
if (KSUCCESS(result)) {
*infoOut = wait.Info;
if (wait.HandleCount)
memcpy(handlesOut, wait.Handles, sizeof(SYS_HANDLE) * wait.HandleCount);
}
break;
}
if (NtlAtomicLoad32(&wait.Claimed) != 0) {
KeReleaseSpinlock(&connection->Lock, flags);
EVENT* claimedEvents[1] = { wait.CompletionEvent };
(void)KeWaitForMultipleEvents(claimedEvents,
1,
0,
SYS_PORT_TIMEOUT_INFINITE,
(uint32_t*)0);
continue;
}
if (wait.Link.Fwd != &wait.Link || wait.Link.Bwd != &wait.Link)
KeListRemoveChecked(&wait.Link);
KeReleaseSpinlock(&connection->Lock, flags);
if (!KSUCCESS(waitStatus)) {
result = waitStatus;
break;
}
if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE &&
KeMonotonicMillisecondsUntil(deadline) == 0) {
result = NTLSTATUS_TIMEOUT;
break;
}
}
if (wait.CompletionEvent)
ObDereferenceObject(&wait.CompletionEvent->Header);
if (wait.LockedPayload)
MmUnlockUserBuffer(wait.LockedPayload);
return result;
}
// Creates one synchronous call context and holds caller resources until reply, timeout, or disconnect.
static KSTATUS KiPortCreateCall(PORT* endpoint,
PROCESS* callerProcess,
void* replyPayload,
uint32_t replyCapacity,
uint32_t replyHandleCapacity,
PORT_CALL** callOut) {
if (!endpoint || !callerProcess || !callOut)
return NTLSTATUS_INVALID_PARAMETER;
*callOut = (PORT_CALL*)0;
KSTATUS quotaStatus = KiPortChargeCall(callerProcess);
if (!KSUCCESS(quotaStatus))
return quotaStatus;
PORT_CALL* call = (PORT_CALL*)MmAllocatePoolTag(sizeof(PORT_CALL), MM_POOL_TAG('p', 'c', 'a', 'l'));
if (!call) {
KiPortReleaseCallCharge(callerProcess);
return NTLSTATUS_NO_MEMORY;
}
memset(call, 0, sizeof(*call));
LIST_INIT(&call->Link);
call->RefCount = 1;
call->Connection = endpoint->u.Endpoint.Connection;
call->CallerSide = endpoint->u.Endpoint.Side;
call->State = PORT_CALL_PENDING;
call->CallerProcess = callerProcess;
call->ReplyPayload = replyPayload;
call->ReplyCapacity = replyCapacity;
call->ReplyHandleCapacity = replyHandleCapacity;
call->Status = NTLSTATUS_PENDING;
KSTATUS status = KeAllocateId32(&gPortMessageIds, &call->MessageId);
if (!KSUCCESS(status)) {
KiPortReleaseCallCharge(callerProcess);
MmFreePool(call);
return status;
}
status = KeEventCreate(EventManualReset, 0, &call->CompletionEvent);
if (!KSUCCESS(status)) {
KiPortReleaseCallCharge(callerProcess);
MmFreePool(call);
return status;
}
if (replyCapacity) {
status = MmLockUserBuffer(callerProcess,
replyPayload,
replyCapacity,
1,
&call->LockedReplyPayload);
if (!KSUCCESS(status)) {
ObDereferenceObject(&call->CompletionEvent->Header);
KiPortReleaseCallCharge(callerProcess);
MmFreePool(call);
return status;
}
}
uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock);
if (call->Connection->Disconnected[call->CallerSide] ||
call->Connection->Disconnected[1u - call->CallerSide]) {
KeReleaseSpinlock(&call->Connection->Lock, flags);
if (call->LockedReplyPayload) MmUnlockUserBuffer(call->LockedReplyPayload);
ObDereferenceObject(&call->CompletionEvent->Header);
KiPortReleaseCallCharge(callerProcess);
MmFreePool(call);
return NTLSTATUS_PORT_DISCONNECTED;
}
KeListInsertTailChecked(&call->Connection->Calls, &call->Link);
KeReleaseSpinlock(&call->Connection->Lock, flags);
*callOut = call;
return NTLSTATUS_SUCCESS;
}
// Releases one synchronous-call reference and frees its retained resources after the final owner leaves.
static void KiPortReleaseCall(PORT_CALL* call) {
if (!call)
return;
int32_t refs = NtlAtomicDecrement32(&call->RefCount);
if (refs > 0)
return;
if (refs < 0) {
KePanic(KE_PANIC_CODE_GENERIC,
"port: call ref underflow message=%u refs=%ld\n",
call->MessageId,
refs);
}
if (call->LockedReplyPayload)
MmUnlockUserBuffer(call->LockedReplyPayload);
if (call->CompletionEvent)
ObDereferenceObject(&call->CompletionEvent->Header);
if (call->CallerProcess)
KiPortReleaseCallCharge(call->CallerProcess);
MmFreePool(call);
}
// Performs one synchronous Port request and waits for exactly one matching reply.
KSTATUS KePortCall(PORT* endpoint,
PROCESS* callerProcess,
THREAD* callerThread,
const void* requestPayload,
uint32_t requestLength,
const SYS_PORT_HANDLE_TRANSFER* requestTransfers,
uint32_t requestTransferCount,
void* replyPayload,
uint32_t replyCapacity,
SYS_HANDLE* replyHandlesOut,
uint32_t replyHandleCapacity,
SYS_PORT_MESSAGE_INFO* replyInfoOut,
uint64_t timeoutMilliseconds) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !callerProcess || !callerThread)
return NTLSTATUS_INVALID_PARAMETER;
if (timeoutMilliseconds == 0)
return NTLSTATUS_WOULD_BLOCK;
if (requestLength > SYS_PORT_MAX_INLINE_BYTES || replyCapacity > SYS_PORT_MAX_INLINE_BYTES ||
requestTransferCount > SYS_PORT_MAX_TRANSFER_HANDLES ||
replyHandleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES ||
(requestLength && !requestPayload) || (replyCapacity && !replyPayload) ||
(replyHandleCapacity && !replyHandlesOut))
return NTLSTATUS_INVALID_PARAMETER;
uint64_t deadline = 0;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE)
deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds);
PORT_CALL* call = (PORT_CALL*)0;
KSTATUS status = KiPortCreateCall(endpoint,
callerProcess,
replyPayload,
replyCapacity,
replyHandleCapacity,
&call);
if (!KSUCCESS(status))
return status;
PORT_MESSAGE* message = (PORT_MESSAGE*)0;
status = KiPortCreateMessage(callerProcess,
callerThread,
SYS_PORT_MESSAGE_REQUEST,
call->MessageId,
requestLength,
requestTransfers,
requestTransferCount,
call,
&message);
if (!KSUCCESS(status)) {
uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock);
if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link)
KeListRemoveChecked(&call->Link);
KeReleaseSpinlock(&call->Connection->Lock, flags);
KiPortReleaseCall(call);
return status;
}
uint64_t submitTimeout = timeoutMilliseconds;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE)
submitTimeout = KeMonotonicMillisecondsUntil(deadline);
status = KiPortSubmitMessage(endpoint,
callerProcess,
requestPayload,
requestLength,
message,
submitTimeout);
if (!KSUCCESS(status)) {
uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock);
if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link)
KeListRemoveChecked(&call->Link);
KeReleaseSpinlock(&call->Connection->Lock, flags);
KiPortReleaseCall(call);
return status;
}
uint64_t replyWait = timeoutMilliseconds;
if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE)
replyWait = KeMonotonicMillisecondsUntil(deadline);
EVENT* events[1] = { call->CompletionEvent };
KSTATUS waitStatus = KeWaitForMultipleEvents(events,
1,
0,
replyWait,
(uint32_t*)0);
if (!KSUCCESS(waitStatus)) {
PORT_MESSAGE* queuedMessage = (PORT_MESSAGE*)0;
uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock);
int32_t state = NtlAtomicLoad32(&call->State);
if (state == PORT_CALL_REPLYING) {
KeReleaseSpinlock(&call->Connection->Lock, flags);
(void)KeWaitForMultipleEvents(events, 1, 0, SYS_PORT_TIMEOUT_INFINITE, (uint32_t*)0);
} else if (state == PORT_CALL_COMPLETED) {
KeReleaseSpinlock(&call->Connection->Lock, flags);
} else {
if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link)
KeListRemoveChecked(&call->Link);
NtlAtomicExchange32(&call->State, PORT_CALL_CANCELLED);
queuedMessage = KiPortRemoveQueuedCallLocked(call->Connection, call);
KeReleaseSpinlock(&call->Connection->Lock, flags);
if (queuedMessage)
KiPortFreeMessage(queuedMessage);
KiPortReleaseCall(call);
return waitStatus;
}
}
status = call->Status;
if (KSUCCESS(status)) {
if (replyInfoOut)
*replyInfoOut = call->ReplyInfo;
if (call->ReplyHandleCount && replyHandlesOut)
memcpy(replyHandlesOut,
call->ReplyHandles,
sizeof(SYS_HANDLE) * call->ReplyHandleCount);
} else if (call->ReplyHandleCount) {
KiPortCloseHandles(callerProcess, call->ReplyHandles, call->ReplyHandleCount);
call->ReplyHandleCount = 0;
}
KiPortReleaseCall(call);
return status;
}
// Replies to one delivered synchronous request and directly copies the reply into the blocked caller buffer.
KSTATUS KePortReply(PORT* endpoint,
PROCESS* senderProcess,
THREAD* senderThread,
uint32_t messageId,
const void* payload,
uint32_t payloadLength,
const SYS_PORT_HANDLE_TRANSFER* transfers,
uint32_t transferCount) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !senderProcess || !senderThread || messageId == 0)
return NTLSTATUS_INVALID_PARAMETER;
if (payloadLength > SYS_PORT_MAX_INLINE_BYTES || transferCount > SYS_PORT_MAX_TRANSFER_HANDLES ||
(payloadLength && !payload))
return NTLSTATUS_INVALID_PARAMETER;
struct KE_CAPTURED_HANDLE* captured[SYS_PORT_MAX_TRANSFER_HANDLES] = {0};
KSTATUS status = KiPortCaptureHandles(senderProcess,
transfers,
transferCount,
captured);
if (!KSUCCESS(status))
return status;
PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection;
PORT_CALL* call = (PORT_CALL*)0;
uintptr_t flags = KeAcquireSpinlock(&connection->Lock);
LIST_ENTRY* head = &connection->Calls;
for (LIST_ENTRY* link = head->Fwd; link != head; link = link->Fwd) {
PORT_CALL* candidate = CONTAINING_RECORD(link, PORT_CALL, Link);
if (candidate->MessageId == messageId &&
candidate->CallerSide != endpoint->u.Endpoint.Side) {
call = candidate;
break;
}
}
if (!call || NtlAtomicLoad32(&call->State) != PORT_CALL_DELIVERED) {
KeReleaseSpinlock(&connection->Lock, flags);
for (uint32_t i = 0; i < transferCount; ++i)
KeReleaseCapturedHandle(captured[i]);
return NTLSTATUS_NOT_FOUND;
}
if (payloadLength > call->ReplyCapacity || transferCount > call->ReplyHandleCapacity) {
KeReleaseSpinlock(&connection->Lock, flags);
for (uint32_t i = 0; i < transferCount; ++i)
KeReleaseCapturedHandle(captured[i]);
return NTLSTATUS_BUFFER_TOO_SMALL;
}
NtlAtomicExchange32(&call->State, PORT_CALL_REPLYING);
KeListRemoveChecked(&call->Link);
KeReleaseSpinlock(&connection->Lock, flags);
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0};
if (transferCount)
status = KeMaterializeCapturedHandles(call->CallerProcess, captured, transferCount, handles);
if (KSUCCESS(status) && payloadLength) {
status = MmCopyUserToUser(senderProcess,
payload,
call->CallerProcess,
call->ReplyPayload,
payloadLength);
}
if (!KSUCCESS(status)) {
KiPortCloseHandles(call->CallerProcess, handles, transferCount);
for (uint32_t i = 0; i < transferCount; ++i)
if (captured[i]) KeReleaseCapturedHandle(captured[i]);
call->ReplyHandleCount = 0;
} else {
memcpy(call->ReplyHandles, handles, sizeof(SYS_HANDLE) * transferCount);
call->ReplyHandleCount = transferCount;
call->ReplyInfo.Size = sizeof(call->ReplyInfo);
call->ReplyInfo.MessageId = messageId;
call->ReplyInfo.Type = SYS_PORT_MESSAGE_REPLY;
call->ReplyInfo.PayloadLength = payloadLength;
call->ReplyInfo.HandleCount = transferCount;
call->ReplyInfo.SenderProcessId = senderProcess->ProcessId;
call->ReplyInfo.SenderThreadId = senderThread->ThreadId;
call->ReplyInfo.SenderSecurityId = KiPortSecurityId(senderProcess);
}
if (KSUCCESS(status))
NtlAtomicIncrement64(&gPortCounters.RepliesCompleted);
call->Status = status;
NtlAtomicExchange32(&call->State, PORT_CALL_COMPLETED);
KeSignalEvent(call->CompletionEvent);
return status;
}
// Explicitly disconnects one endpoint capability without requiring its last handle to close.
KSTATUS KePortDisconnect(PORT* endpoint) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT)
return NTLSTATUS_INVALID_PARAMETER;
KiPortMarkDisconnected(endpoint, false);
return NTLSTATUS_SUCCESS;
}
// Opens one process capability through an accepted endpoint's peer-authority ceiling.
KSTATUS KePortOpenPeerProcess(PORT* endpoint,
PROCESS* callerProcess,
SYS_RIGHTS desiredRights,
SYS_HANDLE* processHandleOut) {
if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !callerProcess ||
desiredRights == 0 || !processHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection;
uint32_t side = endpoint->u.Endpoint.Side;
if (!connection || side > 1u) return NTLSTATUS_PORT_DISCONNECTED;
PROCESS* peer = connection->PeerProcesses[side];
SYS_RIGHTS ceiling = connection->PeerProcessRights[side];
if (!peer || ceiling == 0 || (desiredRights & ~ceiling) != 0)
return NTLSTATUS_ACCESS_DENIED;
return KeCreateHandle(callerProcess, &peer->Header, desiredRights, processHandleOut);
}