[PATCH v12 0/13] MR10611: LPC: Implement proper LPC system
Until now, LPC was just a stub, any application that relied on LPC for communication would fail. This change implements parts of LPC, to allow for IPC between programs. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=30069 - fully fixed, sqlservr is able to run as expected Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59494 - partially fixed, starmoney doesn't flat out crash anymore, but it doesn't start fully, possibly because it uses the ALPC system which was introduced in Windows Vista, the version Starmoney 10 was advertised for -- v12: ntdll/tests: Add LPC port error handling tests. server: Handle LPC port close to break circular references. ntdll: Implement NtRegisterThreadTerminatePort. server: Implement LPC thread termination notification. ntdll: Implement NtReplyPort, NtReplyWaitReceivePort and NtReplyWaitReceivePortEx. ntdll: Implement NtRequestPort and NtRequestWaitReplyPort. server: Implement LPC message request and reply handlers. ntdll: Implement NtListenPort, NtAcceptConnectPort and NtCompleteConnectPort. ntdll: Implement NtConnectPort and NtSecureConnectPort. server: Implement LPC connection acceptance and completion. server: Implement LPC connection request and listen handlers. ntdll: Implement NtCreatePort and NtCreateWaitablePort. server: Add LPC port object infrastructure. https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Add the basic LPC port object type with support for creating named ports. This implements the server-side create_lpc_port request which will be used by NtCreatePort and NtCreateWaitablePort. The LPC port object supports: - Named ports in the object namespace - Waitable ports (PORT_FLAG_WAITABLE) that can be used with WaitForSingleObject - Maximum message length and connection info size limits --- dlls/ntdll/signal_arm64ec.c | 1 + server/Makefile.in | 1 + server/lpc_port.c | 213 ++++++++++++++++++++++++++++++++++++ server/protocol.def | 12 ++ 4 files changed, 227 insertions(+) create mode 100644 server/lpc_port.c diff --git a/dlls/ntdll/signal_arm64ec.c b/dlls/ntdll/signal_arm64ec.c index 954ace7824e..517f7690bf1 100644 --- a/dlls/ntdll/signal_arm64ec.c +++ b/dlls/ntdll/signal_arm64ec.c @@ -400,6 +400,7 @@ DEFINE_SYSCALL(NtCreateTimer, (HANDLE *handle, ACCESS_MASK access, const OBJECT_ DEFINE_SYSCALL(NtCreateToken, (HANDLE *handle, ACCESS_MASK access, OBJECT_ATTRIBUTES *attr, TOKEN_TYPE type, LUID *token_id, LARGE_INTEGER *expire, TOKEN_USER *user, TOKEN_GROUPS *groups, TOKEN_PRIVILEGES *privs, TOKEN_OWNER *owner, TOKEN_PRIMARY_GROUP *group, TOKEN_DEFAULT_DACL *dacl, TOKEN_SOURCE *source)) DEFINE_SYSCALL(NtCreateTransaction, (HANDLE *handle, ACCESS_MASK mask, OBJECT_ATTRIBUTES *obj_attr, GUID *guid, HANDLE tm, ULONG options, ULONG isol_level, ULONG isol_flags, PLARGE_INTEGER timeout, UNICODE_STRING *description)) DEFINE_SYSCALL(NtCreateUserProcess, (HANDLE *process_handle_ptr, HANDLE *thread_handle_ptr, ACCESS_MASK process_access, ACCESS_MASK thread_access, OBJECT_ATTRIBUTES *process_attr, OBJECT_ATTRIBUTES *thread_attr, ULONG process_flags, ULONG thread_flags, RTL_USER_PROCESS_PARAMETERS *params, PS_CREATE_INFO *info, PS_ATTRIBUTE_LIST *ps_attr)) +DEFINE_SYSCALL(NtCreateWaitablePort, (HANDLE *handle, OBJECT_ATTRIBUTES *attr, ULONG info_len, ULONG data_len, ULONG reserved)) DEFINE_SYSCALL(NtDebugActiveProcess, (HANDLE process, HANDLE debug)) DEFINE_SYSCALL(NtDebugContinue, (HANDLE handle, CLIENT_ID *client, NTSTATUS status)) DEFINE_SYSCALL(NtDelayExecution, (BOOLEAN alertable, const LARGE_INTEGER *timeout)) diff --git a/server/Makefile.in b/server/Makefile.in index 84a6bd74d9d..b6dc080896c 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -18,6 +18,7 @@ SOURCES = \ handle.c \ hook.c \ inproc_sync.c \ + lpc_port.c \ mach.c \ mailslot.c \ main.c \ diff --git a/server/lpc_port.c b/server/lpc_port.c new file mode 100644 index 00000000000..3c4ef23a4fb --- /dev/null +++ b/server/lpc_port.c @@ -0,0 +1,213 @@ +/* + * Server-side LPC port management + * + * Copyright 2026 Wine project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "config.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winternl.h" + +#include "handle.h" +#include "thread.h" +#include "process.h" +#include "request.h" +#include "security.h" +#include "object.h" + +/* Port access rights */ +#define PORT_CONNECT 0x0001 +#define PORT_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | PORT_CONNECT) + +/* Port types */ +#define PORT_TYPE_SERVER 0x01 /* Named port that server listens on */ + +/* Port flags */ +#define PORT_FLAG_WAITABLE 0x0001 + +/* Maximum message size */ +#define MAX_LPC_MESSAGE_SIZE 0x40000 + +static const WCHAR lpc_port_name[] = {'L','P','C',' ','P','o','r','t'}; + +struct type_descr lpc_port_type = +{ + { lpc_port_name, sizeof(lpc_port_name) }, /* name */ + PORT_ALL_ACCESS, /* valid_access */ + { /* mapping */ + STANDARD_RIGHTS_READ | PORT_CONNECT, + STANDARD_RIGHTS_WRITE, + STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE, + PORT_ALL_ACCESS + }, +}; + +/* LPC port object */ +struct lpc_port +{ + struct object obj; /* object header */ + unsigned int port_type; /* PORT_TYPE_* */ + unsigned int flags; /* PORT_FLAG_* */ + struct list msg_queue; /* list of pending messages */ + struct object *queue_event; /* event signaled when message arrives */ + unsigned int max_msg_len; /* maximum message length */ + unsigned int max_connect_info;/* maximum connection info length */ + struct object *wait_event; /* event for WaitForSingleObject (waitable ports) */ + struct process *server_process; /* server process (for named ports) */ +}; + +static void lpc_port_dump( struct object *obj, int verbose ); +static struct object *lpc_port_get_sync( struct object *obj ); +static void lpc_port_destroy( struct object *obj ); + +static const struct object_ops lpc_port_ops = +{ + sizeof(struct lpc_port), /* size */ + &lpc_port_type, /* type */ + lpc_port_dump, /* dump */ + NULL, /* add_queue */ + NULL, /* remove_queue */ + NULL, /* signaled */ + NULL, /* satisfied */ + no_signal, /* signal */ + no_get_fd, /* get_fd */ + lpc_port_get_sync, /* get_sync */ + default_map_access, /* map_access */ + default_get_sd, /* get_sd */ + default_set_sd, /* set_sd */ + default_get_full_name, /* get_full_name */ + no_lookup_name, /* lookup_name */ + directory_link_name, /* link_name */ + default_unlink_name, /* unlink_name */ + no_open_file, /* open_file */ + no_kernel_obj_list, /* get_kernel_obj_list */ + no_close_handle, /* close_handle */ + lpc_port_destroy /* destroy */ +}; + +static struct lpc_port *create_lpc_port( struct object *root, const struct unicode_str *name, + unsigned int attr, unsigned int flags, + unsigned int max_msg_len, unsigned int max_connect_info, + const struct security_descriptor *sd ) +{ + struct lpc_port *port; + + if (max_msg_len > MAX_LPC_MESSAGE_SIZE) + max_msg_len = MAX_LPC_MESSAGE_SIZE; + if (max_connect_info > MAX_LPC_MESSAGE_SIZE) + max_connect_info = MAX_LPC_MESSAGE_SIZE; + + if ((port = create_named_object( root, &lpc_port_ops, name, attr, sd ))) + { + if (get_error() != STATUS_OBJECT_NAME_EXISTS) + { + port->port_type = PORT_TYPE_SERVER; + port->flags = flags; + list_init( &port->msg_queue ); + port->queue_event = create_internal_sync( 0, 0 ); + port->max_msg_len = max_msg_len ? max_msg_len : MAX_LPC_MESSAGE_SIZE; + port->max_connect_info = max_connect_info ? max_connect_info : 256; + port->wait_event = NULL; + port->server_process = (struct process *)grab_object( current->process ); + + if (!port->queue_event) + { + release_object( port ); + return NULL; + } + + if (flags & PORT_FLAG_WAITABLE) + { + port->wait_event = create_internal_sync( 1, 0 ); + if (!port->wait_event) + { + release_object( port ); + return NULL; + } + } + } + } + return port; +} + +static void lpc_port_dump( struct object *obj, int verbose ) +{ + struct lpc_port *port = (struct lpc_port *)obj; + + assert( obj->ops == &lpc_port_ops ); + fprintf( stderr, "LPC Port type=%s flags=%04x max_msg=%u max_connect=%u\n", + port->port_type == PORT_TYPE_SERVER ? "SERVER" : "?", + port->flags, port->max_msg_len, port->max_connect_info ); +} + +static struct object *lpc_port_get_sync( struct object *obj ) +{ + struct lpc_port *port = (struct lpc_port *)obj; + + if (port->wait_event) + return grab_object( port->wait_event ); + + if (port->queue_event) + return grab_object( port->queue_event ); + + return NULL; +} + +static void lpc_port_destroy( struct object *obj ) +{ + struct lpc_port *port = (struct lpc_port *)obj; + + assert( obj->ops == &lpc_port_ops ); + + if (port->queue_event) release_object( port->queue_event ); + if (port->wait_event) release_object( port->wait_event ); + if (port->server_process) release_object( port->server_process ); +} + +/* Create an LPC port */ +DECL_HANDLER(create_lpc_port) +{ + struct lpc_port *port; + struct unicode_str name; + struct object *root; + const struct security_descriptor *sd; + const struct object_attributes *objattr = get_req_object_attributes( &sd, &name, &root ); + + if (!objattr) return; + + if ((port = create_lpc_port( root, &name, objattr->attributes, req->flags, + req->max_msg_len, req->max_connect_info, sd ))) + { + if (get_error() == STATUS_OBJECT_NAME_EXISTS) + reply->handle = alloc_handle( current->process, port, req->access, objattr->attributes ); + else + reply->handle = alloc_handle_no_access_check( current->process, port, + req->access, objattr->attributes ); + release_object( port ); + } + + if (root) release_object( root ); +} diff --git a/server/protocol.def b/server/protocol.def index 5bca381fd91..af8011611b3 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -4274,3 +4274,15 @@ enum inproc_sync_type data_size_t runtime_size; /* size of client runtime data */ VARARG(runtime,bytes); /* client runtime data */ @END + + +/* Create an LPC port */ +@REQ(create_lpc_port) + unsigned int access; /* desired access */ + unsigned int flags; /* port flags (waitable) */ + unsigned int max_msg_len; /* maximum message length */ + unsigned int max_connect_info; /* maximum connection info length */ + VARARG(objattr,object_attributes);/* object attributes */ +@REPLY + obj_handle_t handle; /* port handle */ +@END -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> These functions create LPC (Local Procedure Call) ports for inter-process communication. NtCreatePort creates a regular port while NtCreateWaitablePort creates a port that can be waited on using WaitForSingleObject. --- dlls/ntdll/ntdll.spec | 4 +-- dlls/ntdll/unix/sync.c | 61 ++++++++++++++++++++++++++++++++++++++++-- dlls/wow64/sync.c | 22 +++++++++++++++ include/winternl.h | 1 + 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index f396c334e2d..5aa620bd60b 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -206,7 +206,7 @@ @ stdcall -syscall NtCreateToken(ptr long ptr long ptr ptr ptr ptr ptr ptr ptr ptr ptr) @ stdcall -syscall NtCreateTransaction(ptr long ptr ptr long long long long ptr ptr) @ stdcall -syscall NtCreateUserProcess(ptr ptr long long ptr ptr long long ptr ptr ptr) -# @ stub NtCreateWaitablePort +@ stdcall -syscall NtCreateWaitablePort(ptr ptr long long long) @ stdcall -arch=i386 NtCurrentTeb() @ stdcall -syscall NtDebugActiveProcess(long long) @ stdcall -syscall NtDebugContinue(long ptr long) @@ -1280,7 +1280,7 @@ @ stdcall -private ZwCreateToken(ptr long ptr long ptr ptr ptr ptr ptr ptr ptr ptr ptr) NtCreateToken @ stdcall -private ZwCreateTransaction(ptr long ptr ptr long long long long ptr ptr) NtCreateTransaction @ stdcall -private ZwCreateUserProcess(ptr ptr long long ptr ptr long long ptr ptr ptr) NtCreateUserProcess -# @ stub ZwCreateWaitablePort +@ stdcall -private ZwCreateWaitablePort(ptr ptr long long long) NtCreateWaitablePort @ stdcall -private ZwDebugActiveProcess(long long) NtDebugActiveProcess @ stdcall -private ZwDebugContinue(long ptr long) NtDebugContinue @ stdcall -private ZwDelayExecution(long ptr) NtDelayExecution diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index bc9bbf2c30d..1ffa0577573 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3062,14 +3062,71 @@ NTSTATUS WINAPI NtOpenSection( HANDLE *handle, ACCESS_MASK access, const OBJECT_ } +/* LPC port access rights */ +#define PORT_CONNECT 0x0001 +#define PORT_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | PORT_CONNECT) + /*********************************************************************** * NtCreatePort (NTDLL.@) */ NTSTATUS WINAPI NtCreatePort( HANDLE *handle, OBJECT_ATTRIBUTES *attr, ULONG info_len, ULONG data_len, ULONG *reserved ) { - FIXME( "(%p,%p,%u,%u,%p),stub!\n", handle, attr, info_len, data_len, reserved ); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + data_size_t len; + struct object_attributes *objattr; + + TRACE( "(%p,%p,%u,%u,%p)\n", handle, attr, info_len, data_len, reserved ); + + *handle = 0; + if ((ret = alloc_object_attributes( attr, &objattr, &len ))) + return ret; + + SERVER_START_REQ( create_lpc_port ) + { + req->access = PORT_ALL_ACCESS; + req->flags = 0; + req->max_msg_len = data_len; + req->max_connect_info = info_len; + wine_server_add_data( req, objattr, len ); + if (!(ret = wine_server_call( req ))) + *handle = wine_server_ptr_handle( reply->handle ); + } + SERVER_END_REQ; + free( objattr ); + return ret; +} + + +/*********************************************************************** + * NtCreateWaitablePort (NTDLL.@) + */ +NTSTATUS WINAPI NtCreateWaitablePort( HANDLE *handle, OBJECT_ATTRIBUTES *attr, ULONG info_len, + ULONG data_len, ULONG reserved ) +{ + unsigned int ret; + data_size_t len; + struct object_attributes *objattr; + + TRACE( "(%p,%p,%u,%u,%u)\n", handle, attr, info_len, data_len, reserved ); + + *handle = 0; + if ((ret = alloc_object_attributes( attr, &objattr, &len ))) + return ret; + + SERVER_START_REQ( create_lpc_port ) + { + req->access = PORT_ALL_ACCESS; + req->flags = 0x0001; /* PORT_FLAG_WAITABLE */ + req->max_msg_len = data_len; + req->max_connect_info = info_len; + wine_server_add_data( req, objattr, len ); + if (!(ret = wine_server_call( req ))) + *handle = wine_server_ptr_handle( reply->handle ); + } + SERVER_END_REQ; + free( objattr ); + return ret; } diff --git a/dlls/wow64/sync.c b/dlls/wow64/sync.c index d5b52a5c815..2f0a617b70c 100644 --- a/dlls/wow64/sync.c +++ b/dlls/wow64/sync.c @@ -403,6 +403,28 @@ NTSTATUS WINAPI wow64_NtCreateSection( UINT *args ) } +/********************************************************************** + * wow64_NtCreateWaitablePort + */ +NTSTATUS WINAPI wow64_NtCreateWaitablePort( UINT *args ) +{ + ULONG *handle_ptr = get_ptr( &args ); + OBJECT_ATTRIBUTES32 *attr32 = get_ptr( &args ); + ULONG info_len = get_ulong( &args ); + ULONG data_len = get_ulong( &args ); + ULONG reserved = get_ulong( &args ); + + struct object_attr64 attr; + HANDLE handle = 0; + NTSTATUS status; + + *handle_ptr = 0; + status = NtCreateWaitablePort( &handle, objattr_32to64( &attr, attr32 ), info_len, data_len, reserved ); + put_handle( handle_ptr, handle ); + return status; +} + + /********************************************************************** * wow64_NtCreateSemaphore */ diff --git a/include/winternl.h b/include/winternl.h index a20aca55a0e..9b2847a9419 100644 --- a/include/winternl.h +++ b/include/winternl.h @@ -4784,6 +4784,7 @@ NTSYSAPI NTSTATUS WINAPI NtCreateTimer(HANDLE*, ACCESS_MASK, const OBJECT_ATTRI NTSYSAPI NTSTATUS WINAPI NtCreateToken(PHANDLE,ACCESS_MASK,POBJECT_ATTRIBUTES,TOKEN_TYPE,PLUID,PLARGE_INTEGER,PTOKEN_USER,PTOKEN_GROUPS,PTOKEN_PRIVILEGES,PTOKEN_OWNER,PTOKEN_PRIMARY_GROUP,PTOKEN_DEFAULT_DACL,PTOKEN_SOURCE); NTSYSAPI NTSTATUS WINAPI NtCreateTransaction(PHANDLE,ACCESS_MASK,POBJECT_ATTRIBUTES,LPGUID,HANDLE,ULONG,ULONG,ULONG,PLARGE_INTEGER,PUNICODE_STRING); NTSYSAPI NTSTATUS WINAPI NtCreateUserProcess(HANDLE*,HANDLE*,ACCESS_MASK,ACCESS_MASK,OBJECT_ATTRIBUTES*,OBJECT_ATTRIBUTES*,ULONG,ULONG,RTL_USER_PROCESS_PARAMETERS*,PS_CREATE_INFO*,PS_ATTRIBUTE_LIST*); +NTSYSAPI NTSTATUS WINAPI NtCreateWaitablePort(HANDLE*,OBJECT_ATTRIBUTES*,ULONG,ULONG,ULONG); NTSYSAPI NTSTATUS WINAPI NtDebugActiveProcess(HANDLE,HANDLE); NTSYSAPI NTSTATUS WINAPI NtDebugContinue(HANDLE,CLIENT_ID*,NTSTATUS); NTSYSAPI NTSTATUS WINAPI NtDelayExecution(BOOLEAN,const LARGE_INTEGER*); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Add support for clients to connect to LPC ports and servers to listen for connection requests: - Add lpc_message structure for internal message handling - Add global_pending_connects list to track pending connections - Add connect_lpc_port handler to create client ports and queue connection requests - Add listen_lpc_port handler to retrieve pending connection requests The connection flow works as follows: 1. Client calls connect_lpc_port which creates a client port and queues a connection request message on the server port 2. Server calls listen_lpc_port to receive connection request details 3. Server will accept/reject with accept_lpc_connect (next commit) --- server/lpc_port.c | 271 +++++++++++++++++++++++++++++++++++++++++++- server/protocol.def | 26 +++++ 2 files changed, 295 insertions(+), 2 deletions(-) diff --git a/server/lpc_port.c b/server/lpc_port.c index 3c4ef23a4fb..a6f2649c424 100644 --- a/server/lpc_port.c +++ b/server/lpc_port.c @@ -44,12 +44,23 @@ /* Port types */ #define PORT_TYPE_SERVER 0x01 /* Named port that server listens on */ +#define PORT_TYPE_CLIENT 0x02 /* Client's end of connection */ /* Port flags */ #define PORT_FLAG_WAITABLE 0x0001 +/* LPC message types */ +#define LPC_CONNECTION_REQUEST 10 + /* Maximum message size */ #define MAX_LPC_MESSAGE_SIZE 0x40000 +#define MAX_LPC_DATA_SIZE 0x100000 + +/* Global counter for generating unique message IDs */ +static unsigned int global_msg_id_counter = 0; + +/* Global list of pending connection requests */ +static struct list global_pending_connects = LIST_INIT(global_pending_connects); static const WCHAR lpc_port_name[] = {'L','P','C',' ','P','o','r','t'}; @@ -65,18 +76,40 @@ struct type_descr lpc_port_type = }, }; +/* Internal message structure */ +struct lpc_message +{ + struct list entry; /* queue entry */ + struct list global_entry; /* entry in global_pending_connects */ + struct lpc_port *sender_port; /* port that sent this message */ + struct lpc_port *server_port; /* server port (for connection requests) */ + struct thread *sender_thread; /* thread that sent this message */ + unsigned int msg_id; /* unique message ID */ + unsigned int msg_type; /* LPC message type */ + process_id_t client_pid; /* sender's process ID */ + thread_id_t client_tid; /* sender's thread ID */ + data_size_t data_size; /* size of message data */ + char data[1]; /* variable-length message data */ +}; + /* LPC port object */ struct lpc_port { struct object obj; /* object header */ unsigned int port_type; /* PORT_TYPE_* */ unsigned int flags; /* PORT_FLAG_* */ + struct lpc_port *connection_port; /* reference to connection port */ + struct lpc_port *connected_port; /* paired port: client <-> channel */ struct list msg_queue; /* list of pending messages */ + struct list pending_connects;/* list of pending connection messages */ struct object *queue_event; /* event signaled when message arrives */ unsigned int max_msg_len; /* maximum message length */ unsigned int max_connect_info;/* maximum connection info length */ struct object *wait_event; /* event for WaitForSingleObject (waitable ports) */ struct process *server_process; /* server process (for named ports) */ + struct thread *client_thread; /* client thread (for NtCompleteConnectPort) */ + struct object *connect_event; /* event signaled when connection completes */ + unsigned int connect_status; /* STATUS_SUCCESS or error code from accept */ }; static void lpc_port_dump( struct object *obj, int verbose ); @@ -108,6 +141,58 @@ static const struct object_ops lpc_port_ops = lpc_port_destroy /* destroy */ }; +/* Allocate a new message with the given data size */ +static struct lpc_message *alloc_lpc_message( data_size_t data_size ) +{ + struct lpc_message *msg; + data_size_t alloc_size; + + if (data_size > MAX_LPC_DATA_SIZE) + { + set_error( STATUS_INVALID_PARAMETER ); + return NULL; + } + + alloc_size = max( sizeof(struct lpc_message), offsetof(struct lpc_message, data) + data_size ); + msg = mem_alloc( alloc_size ); + if (msg) + { + memset( msg, 0, alloc_size ); + list_init( &msg->global_entry ); + msg->data_size = data_size; + } + return msg; +} + +/* Free a message */ +static void free_lpc_message( struct lpc_message *msg ) +{ + if (msg) + { + if (!list_empty( &msg->global_entry )) + list_remove( &msg->global_entry ); + if (msg->sender_port) release_object( msg->sender_port ); + if (msg->server_port) release_object( msg->server_port ); + if (msg->sender_thread) release_object( msg->sender_thread ); + free( msg ); + } +} + +/* Get the next globally unique message ID */ +static unsigned int get_next_msg_id( void ) +{ + return ++global_msg_id_counter; +} + +/* Signal the port's queue event to wake waiting threads */ +static void signal_port_queue( struct lpc_port *port ) +{ + if (port->queue_event) + signal_sync( port->queue_event ); + if (port->wait_event) + signal_sync( port->wait_event ); +} + static struct lpc_port *create_lpc_port( struct object *root, const struct unicode_str *name, unsigned int attr, unsigned int flags, unsigned int max_msg_len, unsigned int max_connect_info, @@ -126,12 +211,18 @@ static struct lpc_port *create_lpc_port( struct object *root, const struct unico { port->port_type = PORT_TYPE_SERVER; port->flags = flags; + port->connection_port = (struct lpc_port *)grab_object( port ); + port->connected_port = NULL; list_init( &port->msg_queue ); + list_init( &port->pending_connects ); port->queue_event = create_internal_sync( 0, 0 ); port->max_msg_len = max_msg_len ? max_msg_len : MAX_LPC_MESSAGE_SIZE; port->max_connect_info = max_connect_info ? max_connect_info : 256; port->wait_event = NULL; port->server_process = (struct process *)grab_object( current->process ); + port->client_thread = NULL; + port->connect_event = NULL; + port->connect_status = STATUS_PENDING; if (!port->queue_event) { @@ -153,19 +244,67 @@ static struct lpc_port *create_lpc_port( struct object *root, const struct unico return port; } +/* Create a client port (internal, no name) */ +static struct lpc_port *create_port_internal( unsigned int port_type, struct lpc_port *connection_port ) +{ + struct lpc_port *port; + + port = alloc_object( &lpc_port_ops ); + if (!port) return NULL; + + port->port_type = port_type; + port->flags = 0; + port->connection_port = (struct lpc_port *)grab_object( connection_port ); + port->connected_port = NULL; + list_init( &port->msg_queue ); + list_init( &port->pending_connects ); + port->queue_event = create_internal_sync( 0, 0 ); + port->max_msg_len = connection_port->max_msg_len; + port->max_connect_info = connection_port->max_connect_info; + port->wait_event = NULL; + port->server_process = NULL; + port->client_thread = NULL; + port->connect_event = NULL; + port->connect_status = STATUS_PENDING; + + if (port_type == PORT_TYPE_CLIENT) + { + port->connect_event = (struct object *)create_server_internal_sync( 1, 0 ); + if (!port->connect_event) + { + release_object( port ); + return NULL; + } + } + + if (!port->queue_event) + { + release_object( port ); + return NULL; + } + + return port; +} + static void lpc_port_dump( struct object *obj, int verbose ) { struct lpc_port *port = (struct lpc_port *)obj; + static const char *type_names[] = { "???", "SERVER", "CLIENT", "CHANNEL" }; + const char *type_name = port->port_type < 4 ? type_names[port->port_type] : "???"; assert( obj->ops == &lpc_port_ops ); fprintf( stderr, "LPC Port type=%s flags=%04x max_msg=%u max_connect=%u\n", - port->port_type == PORT_TYPE_SERVER ? "SERVER" : "?", - port->flags, port->max_msg_len, port->max_connect_info ); + type_name, port->flags, port->max_msg_len, port->max_connect_info ); } static struct object *lpc_port_get_sync( struct object *obj ) { struct lpc_port *port = (struct lpc_port *)obj; + assert( obj->ops == &lpc_port_ops ); + + /* For clients with a connect_event, return it for connection wait */ + if (port->port_type == PORT_TYPE_CLIENT && port->connect_event) + return grab_object( port->connect_event ); if (port->wait_event) return grab_object( port->wait_event ); @@ -179,12 +318,31 @@ static struct object *lpc_port_get_sync( struct object *obj ) static void lpc_port_destroy( struct object *obj ) { struct lpc_port *port = (struct lpc_port *)obj; + struct lpc_message *msg, *next_msg; assert( obj->ops == &lpc_port_ops ); + LIST_FOR_EACH_ENTRY_SAFE( msg, next_msg, &port->msg_queue, struct lpc_message, entry ) + { + list_remove( &msg->entry ); + free_lpc_message( msg ); + } + + LIST_FOR_EACH_ENTRY_SAFE( msg, next_msg, &port->pending_connects, struct lpc_message, entry ) + { + list_remove( &msg->entry ); + free_lpc_message( msg ); + } + if (port->queue_event) release_object( port->queue_event ); if (port->wait_event) release_object( port->wait_event ); + if (port->connect_event) release_object( port->connect_event ); + if (port->connection_port && port->connection_port != port) + release_object( port->connection_port ); + if (port->connected_port && port->connected_port != port) + release_object( port->connected_port ); if (port->server_process) release_object( port->server_process ); + if (port->client_thread) release_object( port->client_thread ); } /* Create an LPC port */ @@ -211,3 +369,112 @@ DECL_HANDLER(create_lpc_port) if (root) release_object( root ); } + +/* Connect to an LPC port */ +DECL_HANDLER(connect_lpc_port) +{ + struct lpc_port *connection_port; + struct lpc_port *client_port; + struct lpc_message *msg; + struct unicode_str name; + struct object *root; + const struct security_descriptor *sd; + const struct object_attributes *objattr = get_req_object_attributes( &sd, &name, &root ); + data_size_t info_size = req->info_size; + + if (!objattr) return; + + connection_port = (struct lpc_port *)open_named_object( root, &lpc_port_ops, &name, objattr->attributes ); + if (root) release_object( root ); + + if (!connection_port) + { + set_error( STATUS_OBJECT_NAME_NOT_FOUND ); + return; + } + + if (connection_port->port_type != PORT_TYPE_SERVER) + { + set_error( STATUS_INVALID_PORT_HANDLE ); + release_object( connection_port ); + return; + } + + /* Create client port */ + client_port = create_port_internal( PORT_TYPE_CLIENT, connection_port ); + if (!client_port) + { + release_object( connection_port ); + return; + } + + /* Create connection request message */ + msg = alloc_lpc_message( info_size ); + if (!msg) + { + release_object( client_port ); + release_object( connection_port ); + return; + } + + msg->msg_id = get_next_msg_id(); + msg->msg_type = LPC_CONNECTION_REQUEST; + msg->sender_port = (struct lpc_port *)grab_object( client_port ); + msg->server_port = (struct lpc_port *)grab_object( connection_port ); + msg->sender_thread = (struct thread *)grab_object( current ); + msg->client_pid = current->process->id; + msg->client_tid = current->id; + if (info_size) memcpy( msg->data, get_req_data(), info_size ); + + /* Queue on server's pending_connects list and global list */ + list_add_tail( &connection_port->pending_connects, &msg->entry ); + list_add_tail( &global_pending_connects, &msg->global_entry ); + + /* Signal server that connection request is pending */ + signal_port_queue( connection_port ); + + /* Return handle to client port */ + reply->handle = alloc_handle_no_access_check( current->process, client_port, + req->access, objattr->attributes ); + release_object( client_port ); + release_object( connection_port ); +} + +/* Listen for connection requests */ +DECL_HANDLER(listen_lpc_port) +{ + struct lpc_port *port; + struct lpc_message *msg; + + port = (struct lpc_port *)get_handle_obj( current->process, req->handle, + 0, &lpc_port_ops ); + if (!port) return; + + if (port->port_type != PORT_TYPE_SERVER) + { + set_error( STATUS_INVALID_PORT_HANDLE ); + release_object( port ); + return; + } + + /* Get first pending connection request */ + if (list_empty( &port->pending_connects )) + { + set_error( STATUS_PENDING ); + release_object( port ); + return; + } + + msg = LIST_ENTRY( list_head( &port->pending_connects ), struct lpc_message, entry ); + + reply->msg_size = msg->data_size; + reply->client_pid = msg->client_pid; + reply->client_tid = msg->client_tid; + reply->msg_id = msg->msg_id; + reply->message = 0; + + if (msg->data_size) + set_reply_data( msg->data, min( msg->data_size, get_reply_max_size() ) ); + + release_object( port ); +} diff --git a/server/protocol.def b/server/protocol.def index af8011611b3..5efd5d54cca 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -4286,3 +4286,29 @@ enum inproc_sync_type @REPLY obj_handle_t handle; /* port handle */ @END + + +/* Connect to an LPC port */ +@REQ(connect_lpc_port) + unsigned int access; /* desired access rights */ + data_size_t info_size; /* size of connection info */ + VARARG(objattr,object_attributes);/* object attributes */ + VARARG(info,bytes); /* connection info data */ +@REPLY + obj_handle_t handle; /* handle to client port */ + data_size_t info_size; /* size of returned info */ + VARARG(info,bytes); /* returned connection info */ +@END + + +/* Listen for connection requests */ +@REQ(listen_lpc_port) + obj_handle_t handle; /* handle to server port */ +@REPLY + client_ptr_t message; /* message pointer (unused) */ + data_size_t msg_size; /* size of connection info */ + process_id_t client_pid; /* client process ID */ + thread_id_t client_tid; /* client thread ID */ + unsigned int msg_id; /* message ID for accept */ + VARARG(info,bytes); /* connection info data */ +@END -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Add server-side handlers for accepting connections and completing the handshake: - accept_lpc_connect: Accept or reject a pending connection request and create a communication channel port - complete_lpc_connect: Signal the client that connection is complete - get_lpc_connect_status: Allow client to query connection result This implements the server side of NtAcceptConnectPort, NtCompleteConnectPort, and supports NtConnectPort completion. --- server/lpc_port.c | 134 ++++++++++++++++++++++++++++++++++++++++++++ server/protocol.def | 26 +++++++++ 2 files changed, 160 insertions(+) diff --git a/server/lpc_port.c b/server/lpc_port.c index a6f2649c424..20e5336f7d7 100644 --- a/server/lpc_port.c +++ b/server/lpc_port.c @@ -45,6 +45,7 @@ /* Port types */ #define PORT_TYPE_SERVER 0x01 /* Named port that server listens on */ #define PORT_TYPE_CLIENT 0x02 /* Client's end of connection */ +#define PORT_TYPE_CHANNEL 0x03 /* Server's per-client communication channel */ /* Port flags */ #define PORT_FLAG_WAITABLE 0x0001 @@ -107,6 +108,7 @@ struct lpc_port unsigned int max_connect_info;/* maximum connection info length */ struct object *wait_event; /* event for WaitForSingleObject (waitable ports) */ struct process *server_process; /* server process (for named ports) */ + client_ptr_t port_context; /* user-defined port context */ struct thread *client_thread; /* client thread (for NtCompleteConnectPort) */ struct object *connect_event; /* event signaled when connection completes */ unsigned int connect_status; /* STATUS_SUCCESS or error code from accept */ @@ -220,6 +222,7 @@ static struct lpc_port *create_lpc_port( struct object *root, const struct unico port->max_connect_info = max_connect_info ? max_connect_info : 256; port->wait_event = NULL; port->server_process = (struct process *)grab_object( current->process ); + port->port_context = 0; port->client_thread = NULL; port->connect_event = NULL; port->connect_status = STATUS_PENDING; @@ -263,6 +266,7 @@ static struct lpc_port *create_port_internal( unsigned int port_type, struct lpc port->max_connect_info = connection_port->max_connect_info; port->wait_event = NULL; port->server_process = NULL; + port->port_context = 0; port->client_thread = NULL; port->connect_event = NULL; port->connect_status = STATUS_PENDING; @@ -478,3 +482,133 @@ DECL_HANDLER(listen_lpc_port) release_object( port ); } + +/* Find a pending connection message by message ID */ +static struct lpc_message *find_pending_connect_global( unsigned int msg_id ) +{ + struct lpc_message *msg; + + LIST_FOR_EACH_ENTRY( msg, &global_pending_connects, struct lpc_message, global_entry ) + { + if (msg->msg_id == msg_id) + return msg; + } + return NULL; +} + +/* Accept or reject a connection */ +DECL_HANDLER(accept_lpc_connect) +{ + struct lpc_port *connection_port; + struct lpc_port *comm_port = NULL; + struct lpc_port *client_port; + struct lpc_message *msg; + + msg = find_pending_connect_global( req->msg_id ); + if (!msg) + { + set_error( STATUS_INVALID_CID ); + return; + } + + connection_port = msg->server_port; + if (!connection_port || connection_port->port_type != PORT_TYPE_SERVER) + { + set_error( STATUS_INVALID_PORT_HANDLE ); + return; + } + + client_port = msg->sender_port; + + /* Remove from port's pending list */ + list_remove( &msg->entry ); + + if (req->accept) + { + comm_port = create_port_internal( PORT_TYPE_CHANNEL, connection_port ); + if (!comm_port) + { + free_lpc_message( msg ); + return; + } + + comm_port->connected_port = (struct lpc_port *)grab_object( client_port ); + client_port->connected_port = (struct lpc_port *)grab_object( comm_port ); + client_port->connect_status = STATUS_SUCCESS; + comm_port->port_context = req->context; + + if (msg->sender_thread) + comm_port->client_thread = (struct thread *)grab_object( msg->sender_thread ); + + reply->handle = alloc_handle_no_access_check( current->process, comm_port, + PORT_ALL_ACCESS, 0 ); + release_object( comm_port ); + } + else + { + client_port->connect_status = STATUS_PORT_CONNECTION_REFUSED; + if (client_port->connect_event) + signal_sync( client_port->connect_event ); + reply->handle = 0; + } + + free_lpc_message( msg ); +} + +/* Complete the connection (wake the client) */ +DECL_HANDLER(complete_lpc_connect) +{ + struct lpc_port *port; + struct lpc_port *client_port; + + port = (struct lpc_port *)get_handle_obj( current->process, req->handle, + PORT_CONNECT, &lpc_port_ops ); + if (!port) return; + + if (port->port_type != PORT_TYPE_CHANNEL) + { + set_error( STATUS_INVALID_PORT_HANDLE ); + release_object( port ); + return; + } + + client_port = port->connected_port; + if (client_port) + { + /* Signal the connect_event to wake the client. */ + if (client_port->connect_event) + { + signal_sync( client_port->connect_event ); + release_object( client_port->connect_event ); + client_port->connect_event = NULL; + } + } + + if (port->client_thread) + { + release_object( port->client_thread ); + port->client_thread = NULL; + } + + release_object( port ); +} + +/* Get connection status for client port */ +DECL_HANDLER(get_lpc_connect_status) +{ + struct lpc_port *port; + + port = (struct lpc_port *)get_handle_obj( current->process, req->handle, + PORT_CONNECT, &lpc_port_ops ); + if (!port) return; + + if (port->port_type != PORT_TYPE_CLIENT) + { + set_error( STATUS_INVALID_PORT_HANDLE ); + release_object( port ); + return; + } + + reply->status = port->connect_status; + release_object( port ); +} diff --git a/server/protocol.def b/server/protocol.def index 5efd5d54cca..20cdb456e1b 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -4312,3 +4312,29 @@ enum inproc_sync_type unsigned int msg_id; /* message ID for accept */ VARARG(info,bytes); /* connection info data */ @END + + +/* Accept or reject an LPC connection */ +@REQ(accept_lpc_connect) + obj_handle_t handle; /* handle (unused, connection found by msg_id) */ + int accept; /* accept or reject */ + unsigned int msg_id; /* message ID from listen */ + client_ptr_t context; /* port context */ + VARARG(info,bytes); /* connection info to return */ +@REPLY + obj_handle_t handle; /* handle to communication port */ +@END + + +/* Complete the connection */ +@REQ(complete_lpc_connect) + obj_handle_t handle; /* handle to communication port */ +@END + + +/* Get connection status for client port */ +@REQ(get_lpc_connect_status) + obj_handle_t handle; /* handle to client port */ +@REPLY + unsigned int status; /* connection status */ +@END -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Implement client-side LPC port connection. The function connects to a named server port, waits for the server to accept/reject the connection, and returns a handle to the client port on success. NtSecureConnectPort is implemented as a wrapper around NtConnectPort with a FIXME for SID verification. --- dlls/ntdll/tests/port.c | 2 +- dlls/ntdll/unix/sync.c | 100 ++++++++++++++++++++++++++++++++++++---- dlls/wow64/sync.c | 23 ++++++--- 3 files changed, 109 insertions(+), 16 deletions(-) diff --git a/dlls/ntdll/tests/port.c b/dlls/ntdll/tests/port.c index 80c60d09683..0b7cd92d739 100644 --- a/dlls/ntdll/tests/port.c +++ b/dlls/ntdll/tests/port.c @@ -239,7 +239,7 @@ static DWORD WINAPI test_ports_client(LPVOID arg) sqos.EffectiveOnly = TRUE; status = pNtConnectPort(&PortHandle, &port, &sqos, 0, 0, &len, NULL, NULL); - todo_wine ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %lx\n", status); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %lx\n", status); if (status != STATUS_SUCCESS) return 1; status = pNtRegisterThreadTerminatePort(PortHandle); diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index 1ffa0577573..8e1f9f76d7d 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3137,23 +3137,105 @@ NTSTATUS WINAPI NtConnectPort( HANDLE *handle, UNICODE_STRING *name, SECURITY_QU LPC_SECTION_WRITE *write, LPC_SECTION_READ *read, ULONG *max_len, void *info, ULONG *info_len ) { - FIXME( "(%p,%s,%p,%p,%p,%p,%p,%p),stub!\n", handle, debugstr_us(name), qos, - write, read, max_len, info, info_len ); - if (info && info_len) TRACE("msg = %s\n", debugstr_an( info, *info_len )); - return STATUS_NOT_IMPLEMENTED; -} + unsigned int ret; + data_size_t len; + struct object_attributes *objattr; + OBJECT_ATTRIBUTES attr; + ULONG in_len = (info && info_len) ? *info_len : 0; + HANDLE port_handle; + + TRACE( "(%p,%s,%p,%p,%p,%p,%p,%p)\n", handle, debugstr_us(name), qos, write, read, max_len, info, info_len ); + + if (write) + FIXME( "LPC_SECTION_WRITE not supported\n" ); + if (read) + FIXME( "LPC_SECTION_READ not supported\n" ); + + *handle = 0; + attr.Length = sizeof(attr); + attr.RootDirectory = 0; + attr.ObjectName = name; + attr.Attributes = 0; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = qos; + + if ((ret = alloc_object_attributes( &attr, &objattr, &len ))) + return ret; + + SERVER_START_REQ( connect_lpc_port ) + { + req->access = PORT_ALL_ACCESS; + req->info_size = in_len; + wine_server_add_data( req, objattr, len ); + if (in_len) wine_server_add_data( req, info, in_len ); + if (!(ret = wine_server_call( req ))) + { + port_handle = wine_server_ptr_handle( reply->handle ); + if (info_len) + *info_len = reply->info_size; + } + } + SERVER_END_REQ; + free( objattr ); + + if (ret) return ret; + + /* Wait for the connection to be accepted/rejected by the server */ + for (;;) + { + unsigned int connect_status; + + SERVER_START_REQ( get_lpc_connect_status ) + { + req->handle = wine_server_obj_handle( port_handle ); + ret = wine_server_call( req ); + connect_status = reply->status; + } + SERVER_END_REQ; + + if (ret) + { + NtClose( port_handle ); + return ret; + } + + if (connect_status != STATUS_PENDING) + { + if (connect_status == STATUS_SUCCESS) + { + *handle = port_handle; + return STATUS_SUCCESS; + } + else + { + NtClose( port_handle ); + return connect_status; + } + } + + ret = NtWaitForSingleObject( port_handle, FALSE, NULL ); + if (ret) + { + NtClose( port_handle ); + return ret; + } + } +} /*********************************************************************** - * NtSecureConnectPort (NTDLL.@) + * NtReplyWaitReceivePort (NTDLL.@) */ NTSTATUS WINAPI NtSecureConnectPort( HANDLE *handle, UNICODE_STRING *name, SECURITY_QUALITY_OF_SERVICE *qos, LPC_SECTION_WRITE *write, PSID sid, LPC_SECTION_READ *read, ULONG *max_len, void *info, ULONG *info_len ) { - FIXME( "(%p,%s,%p,%p,%p,%p,%p,%p,%p),stub!\n", handle, debugstr_us(name), qos, - write, sid, read, max_len, info, info_len ); - return STATUS_NOT_IMPLEMENTED; + TRACE( "(%p,%s,%p,%p,%p,%p,%p,%p,%p)\n", handle, debugstr_us(name), qos, write, sid, read, max_len, info, info_len ); + + if (sid) + FIXME( "SID verification not implemented\n" ); + + return NtConnectPort( handle, name, qos, write, read, max_len, info, info_len ); } diff --git a/dlls/wow64/sync.c b/dlls/wow64/sync.c index 2f0a617b70c..f1ddfe5853d 100644 --- a/dlls/wow64/sync.c +++ b/dlls/wow64/sync.c @@ -205,9 +205,15 @@ NTSTATUS WINAPI wow64_NtConnectPort( UINT *args ) void *info = get_ptr( &args ); ULONG *info_len = get_ptr( &args ); - FIXME( "%p %p %p %p %p %p %p %p: stub\n", - handle_ptr, name32, qos, write, read, max_len, info, info_len ); - return STATUS_NOT_IMPLEMENTED; + UNICODE_STRING name; + HANDLE handle = 0; + NTSTATUS status; + + unicode_str_32to64( &name, name32 ); + + status = NtConnectPort( &handle, &name, qos, write, read, max_len, info, info_len ); + put_handle( handle_ptr, handle ); + return status; } @@ -1395,9 +1401,14 @@ NTSTATUS WINAPI wow64_NtSecureConnectPort( UINT *args ) void *info = get_ptr( &args ); ULONG *info_len = get_ptr( &args ); - FIXME( "%p %p %p %p %p %p %p %p %p: stub\n", - handle_ptr, name32, qos, write, sid, read, max_len, info, info_len ); - return STATUS_NOT_IMPLEMENTED; + UNICODE_STRING name; + HANDLE handle = 0; + NTSTATUS status; + + status = NtSecureConnectPort( &handle, unicode_str_32to64( &name, name32 ), + qos, write, sid, read, max_len, info, info_len ); + put_handle( handle_ptr, handle ); + return status; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Implement server-side LPC connection handling: - NtListenPort: Wait for incoming connection requests on a server port - NtAcceptConnectPort: Accept or reject a connection and create a communication port - NtCompleteConnectPort: Signal the client that connection is complete --- dlls/ntdll/unix/sync.c | 70 ++++++++++++++++++++++++++++++++++++++---- dlls/wow64/sync.c | 11 ++++--- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index 8e1f9f76d7d..9265b438ff9 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3244,8 +3244,37 @@ NTSTATUS WINAPI NtSecureConnectPort( HANDLE *handle, UNICODE_STRING *name, SECUR */ NTSTATUS WINAPI NtListenPort( HANDLE handle, LPC_MESSAGE *msg ) { - FIXME("(%p,%p),stub!\n", handle, msg ); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + + TRACE( "(%p,%p)\n", handle, msg ); + + for (;;) + { + SERVER_START_REQ( listen_lpc_port ) + { + req->handle = wine_server_obj_handle( handle ); + wine_server_set_reply( req, msg ? msg->Data : NULL, msg ? 0x1000 : 0 ); + ret = wine_server_call( req ); + if (!ret && msg) + { + msg->DataSize = reply->msg_size; + msg->MessageSize = sizeof(*msg) + reply->msg_size; + msg->MessageType = 10; /* LPC_CONNECTION_REQUEST */ + msg->VirtualRangesOffset = 0; + msg->ClientId.UniqueProcess = ULongToHandle( reply->client_pid ); + msg->ClientId.UniqueThread = ULongToHandle( reply->client_tid ); + msg->MessageId = reply->msg_id; + msg->SectionSize = 0; + } + } + SERVER_END_REQ; + + if (ret != STATUS_PENDING) break; + + ret = NtWaitForSingleObject( handle, FALSE, NULL ); + if (ret) break; + } + return ret; } @@ -3255,8 +3284,28 @@ NTSTATUS WINAPI NtListenPort( HANDLE handle, LPC_MESSAGE *msg ) NTSTATUS WINAPI NtAcceptConnectPort( HANDLE *handle, ULONG id, LPC_MESSAGE *msg, BOOLEAN accept, LPC_SECTION_WRITE *write, LPC_SECTION_READ *read ) { - FIXME("(%p,%u,%p,%d,%p,%p),stub!\n", handle, id, msg, accept, write, read ); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + + TRACE( "(%p,%u,%p,%d,%p,%p)\n", handle, id, msg, accept, write, read ); + + if (write) + FIXME( "LPC_SECTION_WRITE not supported\n" ); + if (read) + FIXME( "LPC_SECTION_READ not supported\n" ); + + *handle = 0; + + SERVER_START_REQ( accept_lpc_connect ) + { + req->handle = 0; + req->accept = accept; + req->msg_id = msg ? msg->MessageId : 0; + req->context = id; + if (!(ret = wine_server_call( req )) && accept) + *handle = wine_server_ptr_handle( reply->handle ); + } + SERVER_END_REQ; + return ret; } @@ -3265,8 +3314,17 @@ NTSTATUS WINAPI NtAcceptConnectPort( HANDLE *handle, ULONG id, LPC_MESSAGE *msg, */ NTSTATUS WINAPI NtCompleteConnectPort( HANDLE handle ) { - FIXME( "(%p),stub!\n", handle ); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + + TRACE( "(%p)\n", handle ); + + SERVER_START_REQ( complete_lpc_connect ) + { + req->handle = wine_server_obj_handle( handle ); + ret = wine_server_call( req ); + } + SERVER_END_REQ; + return ret; } diff --git a/dlls/wow64/sync.c b/dlls/wow64/sync.c index f1ddfe5853d..fad6e1ca304 100644 --- a/dlls/wow64/sync.c +++ b/dlls/wow64/sync.c @@ -140,8 +140,12 @@ NTSTATUS WINAPI wow64_NtAcceptConnectPort( UINT *args ) LPC_SECTION_WRITE *write = get_ptr( &args ); LPC_SECTION_READ *read = get_ptr( &args ); - FIXME( "%p %lu %p %u %p %p: stub\n", handle_ptr, id, msg, accept, write, read ); - return STATUS_NOT_IMPLEMENTED; + HANDLE handle = 0; + NTSTATUS status; + + status = NtAcceptConnectPort( &handle, id, msg, accept, write, read ); + put_handle( handle_ptr, handle ); + return status; } @@ -569,8 +573,7 @@ NTSTATUS WINAPI wow64_NtListenPort( UINT *args ) HANDLE handle = get_handle( &args ); LPC_MESSAGE *msg = get_ptr( &args ); - FIXME( "%p %p: stub\n", handle, msg ); - return STATUS_NOT_IMPLEMENTED; + return NtListenPort( handle, msg ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Add server-side handlers for sending and receiving LPC messages: - request_lpc_reply: Send a request message through a port and track it for reply correlation - reply_wait_receive_lpc: Receive the next message from a port and optionally send a reply to a previous request Also adds tracking for pending requests to route replies back to the correct client port. --- server/lpc_port.c | 223 ++++++++++++++++++++++++++++++++++++++++++++ server/protocol.def | 29 ++++++ 2 files changed, 252 insertions(+) diff --git a/server/lpc_port.c b/server/lpc_port.c index 20e5336f7d7..73da2ab648a 100644 --- a/server/lpc_port.c +++ b/server/lpc_port.c @@ -51,6 +51,9 @@ #define PORT_FLAG_WAITABLE 0x0001 /* LPC message types */ +#define LPC_REQUEST 1 +#define LPC_REPLY 2 +#define LPC_DATAGRAM 3 #define LPC_CONNECTION_REQUEST 10 /* Maximum message size */ @@ -63,6 +66,17 @@ static unsigned int global_msg_id_counter = 0; /* Global list of pending connection requests */ static struct list global_pending_connects = LIST_INIT(global_pending_connects); +/* Global list of pending requests awaiting replies */ +static struct list global_pending_requests = LIST_INIT(global_pending_requests); + +/* Pending request tracking for request/reply correlation */ +struct pending_request +{ + struct list entry; /* entry in global_pending_requests */ + unsigned int msg_id; /* message ID waiting for reply */ + struct lpc_port *client_port; /* client port to deliver reply to */ +}; + static const WCHAR lpc_port_name[] = {'L','P','C',' ','P','o','r','t'}; struct type_descr lpc_port_type = @@ -89,6 +103,7 @@ struct lpc_message unsigned int msg_type; /* LPC message type */ process_id_t client_pid; /* sender's process ID */ thread_id_t client_tid; /* sender's thread ID */ + client_ptr_t port_context; /* port context for this message */ data_size_t data_size; /* size of message data */ char data[1]; /* variable-length message data */ }; @@ -195,6 +210,48 @@ static void signal_port_queue( struct lpc_port *port ) signal_sync( port->wait_event ); } +/* Reset the port's queue event after receiving a message */ +static void reset_port_queue( struct lpc_port *port ) +{ + if (list_empty( &port->msg_queue ) && list_empty( &port->pending_connects )) + { + if (port->queue_event) + reset_sync( port->queue_event ); + if (port->wait_event) + reset_sync( port->wait_event ); + } +} + +/* Track a pending request awaiting reply */ +static void track_pending_request( unsigned int msg_id, struct lpc_port *client_port ) +{ + struct pending_request *pr = mem_alloc( sizeof(*pr) ); + if (pr) + { + pr->msg_id = msg_id; + pr->client_port = (struct lpc_port *)grab_object( client_port ); + list_add_tail( &global_pending_requests, &pr->entry ); + } +} + +/* Find and remove a pending request by message ID */ +static struct lpc_port *find_pending_request_client( unsigned int msg_id ) +{ + struct pending_request *pr; + + LIST_FOR_EACH_ENTRY( pr, &global_pending_requests, struct pending_request, entry ) + { + if (pr->msg_id == msg_id) + { + struct lpc_port *client = pr->client_port; + list_remove( &pr->entry ); + free( pr ); + return client; + } + } + return NULL; +} + static struct lpc_port *create_lpc_port( struct object *root, const struct unicode_str *name, unsigned int attr, unsigned int flags, unsigned int max_msg_len, unsigned int max_connect_info, @@ -612,3 +669,169 @@ DECL_HANDLER(get_lpc_connect_status) reply->status = port->connect_status; release_object( port ); } + +/* Send a request message and get a message ID for tracking the reply */ +DECL_HANDLER(request_lpc_reply) +{ + struct lpc_port *port; + struct lpc_port *target_port; + struct lpc_message *msg; + data_size_t data_size = get_req_data_size(); + + port = (struct lpc_port *)get_handle_obj( current->process, req->handle, + PORT_CONNECT, &lpc_port_ops ); + if (!port) return; + + if (port->port_type == PORT_TYPE_CLIENT) + { + if (!port->connected_port || !port->connected_port->connection_port) + { + set_error( STATUS_PORT_DISCONNECTED ); + release_object( port ); + return; + } + target_port = port->connected_port->connection_port; + } + else if (port->port_type == PORT_TYPE_CHANNEL) + { + if (!port->connected_port) + { + set_error( STATUS_PORT_DISCONNECTED ); + release_object( port ); + return; + } + target_port = port->connected_port; + } + else + { + set_error( STATUS_INVALID_PORT_HANDLE ); + release_object( port ); + return; + } + + msg = alloc_lpc_message( data_size ); + if (!msg) + { + release_object( port ); + return; + } + + msg->sender_port = (struct lpc_port *)grab_object( port ); + msg->sender_thread = (struct thread *)grab_object( current ); + msg->msg_id = get_next_msg_id(); + msg->msg_type = req->msg_type ? req->msg_type : LPC_REQUEST; + msg->client_pid = current->process->id; + msg->client_tid = current->id; + msg->port_context = port->port_context; + if (data_size) + memcpy( msg->data, get_req_data(), data_size ); + + if (msg->msg_type == LPC_REQUEST && port->port_type == PORT_TYPE_CLIENT) + track_pending_request( msg->msg_id, port ); + + list_add_tail( &target_port->msg_queue, &msg->entry ); + signal_port_queue( target_port ); + + reply->msg_id = msg->msg_id; + + release_object( port ); +} + +/* Receive a message and optionally reply to a previous message */ +DECL_HANDLER(reply_wait_receive_lpc) +{ + struct lpc_port *port; + struct lpc_port *receive_port; + struct lpc_message *msg; + data_size_t reply_size = get_req_data_size(); + + port = (struct lpc_port *)get_handle_obj( current->process, req->handle, + PORT_CONNECT, &lpc_port_ops ); + if (!port) return; + + /* Handle reply to previous message */ + if (req->reply_msg_id) + { + struct lpc_port *client_port; + struct lpc_message *reply_msg; + + client_port = find_pending_request_client( req->reply_msg_id ); + if (client_port) + { + reply_msg = alloc_lpc_message( reply_size ); + if (reply_msg) + { + reply_msg->msg_type = LPC_REPLY; + reply_msg->msg_id = req->reply_msg_id; + reply_msg->client_pid = current->process->id; + reply_msg->client_tid = current->id; + reply_msg->data_size = reply_size; + if (reply_size) + memcpy( reply_msg->data, get_req_data(), reply_size ); + + list_add_tail( &client_port->msg_queue, &reply_msg->entry ); + signal_port_queue( client_port ); + } + release_object( client_port ); + } + } + + if (port->port_type == PORT_TYPE_CHANNEL || port->port_type == PORT_TYPE_CLIENT) + receive_port = port; + else if (port->port_type == PORT_TYPE_SERVER) + receive_port = port; + else + { + set_error( STATUS_INVALID_PORT_HANDLE ); + release_object( port ); + return; + } + + if (list_empty( &receive_port->msg_queue )) + { + if (port->port_type == PORT_TYPE_SERVER && !list_empty( &port->pending_connects )) + { + msg = LIST_ENTRY( list_head( &port->pending_connects ), struct lpc_message, entry ); + list_remove( &msg->entry ); + + reply->msg_id = msg->msg_id; + reply->msg_type = msg->msg_type; + reply->client_pid = msg->client_pid; + reply->client_tid = msg->client_tid; + reply->context = msg->port_context; + reply->data_size = msg->data_size; + + if (msg->data_size) + set_reply_data( msg->data, min( msg->data_size, get_reply_max_size() ) ); + + reset_port_queue( receive_port ); + release_object( port ); + return; + } + else + { + set_error( STATUS_PENDING ); + release_object( port ); + return; + } + } + else + { + msg = LIST_ENTRY( list_head( &receive_port->msg_queue ), struct lpc_message, entry ); + list_remove( &msg->entry ); + } + + reply->msg_id = msg->msg_id; + reply->msg_type = msg->msg_type; + reply->client_pid = msg->client_pid; + reply->client_tid = msg->client_tid; + reply->context = msg->port_context; + reply->data_size = msg->data_size; + + if (msg->data_size) + set_reply_data( msg->data, min( msg->data_size, get_reply_max_size() ) ); + + free_lpc_message( msg ); + reset_port_queue( receive_port ); + release_object( port ); +} diff --git a/server/protocol.def b/server/protocol.def index 20cdb456e1b..6db622cf03c 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -4338,3 +4338,32 @@ enum inproc_sync_type @REPLY unsigned int status; /* connection status */ @END + + +/* Send request and wait for reply */ +@REQ(request_lpc_reply) + obj_handle_t handle; /* port handle */ + data_size_t data_size; /* size of message data */ + unsigned int msg_type; /* message type */ + VARARG(data,bytes); /* message data */ +@REPLY + unsigned int msg_id; /* assigned message ID */ +@END + + +/* Wait for message and reply to previous */ +@REQ(reply_wait_receive_lpc) + obj_handle_t handle; /* port handle */ + unsigned int reply_msg_id; /* message ID to reply to (0 for no reply) */ + data_size_t reply_size; /* size of reply data */ + timeout_t timeout; /* timeout (unused currently) */ + VARARG(reply,bytes); /* reply data */ +@REPLY + unsigned int msg_id; /* received message ID */ + unsigned int msg_type; /* message type */ + process_id_t client_pid; /* sender's process ID */ + thread_id_t client_tid; /* sender's thread ID */ + client_ptr_t context; /* port context */ + data_size_t data_size; /* size of message data */ + VARARG(data,bytes); /* message data */ +@END -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Implement client-side LPC message sending: - NtRequestPort: Send a datagram message - NtRequestWaitReplyPort: Send a request message and wait for a reply NtRequestWaitReplyPort loops until a reply is received, waiting on the port for message availability. --- dlls/ntdll/ntdll.spec | 4 +- dlls/ntdll/unix/sync.c | 91 ++++++++++++++++++++++++++++++++++++++---- dlls/wow64/sync.c | 18 +++++++-- 3 files changed, 100 insertions(+), 13 deletions(-) diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 5aa620bd60b..0ee1b0303a0 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -370,7 +370,7 @@ @ stdcall -syscall=0x000b NtReplyWaitReceivePort(ptr ptr ptr ptr) @ stdcall -syscall=0x002b NtReplyWaitReceivePortEx(long ptr ptr ptr ptr) # @ stub NtReplyWaitReplyPort -# @ stub NtRequestPort +@ stdcall -syscall NtRequestPort(ptr ptr) @ stdcall -syscall=0x0022 NtRequestWaitReplyPort(ptr ptr ptr) @ stdcall -syscall NtResetEvent(long ptr) @ stdcall -syscall NtResetWriteWatch(long ptr long) @@ -1442,7 +1442,7 @@ @ stdcall -private ZwReplyWaitReceivePort(ptr ptr ptr ptr) NtReplyWaitReceivePort @ stdcall -private ZwReplyWaitReceivePortEx(long ptr ptr ptr ptr) NtReplyWaitReceivePortEx # @ stub ZwReplyWaitReplyPort -# @ stub ZwRequestPort +@ stdcall -private ZwRequestPort(ptr ptr) NtRequestPort @ stdcall -private ZwRequestWaitReplyPort(ptr ptr ptr) NtRequestWaitReplyPort @ stdcall -private ZwResetEvent(long ptr) NtResetEvent @ stdcall -private ZwResetWriteWatch(long ptr long) NtResetWriteWatch diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index 9265b438ff9..d3f82e2cc49 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3364,13 +3364,90 @@ NTSTATUS WINAPI NtRegisterThreadTerminatePort( HANDLE handle ) */ NTSTATUS WINAPI NtRequestWaitReplyPort( HANDLE handle, LPC_MESSAGE *msg_in, LPC_MESSAGE *msg_out ) { - FIXME( "(%p,%p,%p),stub!\n", handle, msg_in, msg_out ); - if (msg_in) - TRACE("datasize %u msgsize %u type %u ranges %u client %p/%p msgid %lu size %lu data %s\n", - msg_in->DataSize, msg_in->MessageSize, msg_in->MessageType, msg_in->VirtualRangesOffset, - msg_in->ClientId.UniqueProcess, msg_in->ClientId.UniqueThread, msg_in->MessageId, - msg_in->SectionSize, debugstr_an( (const char *)msg_in->Data, msg_in->DataSize )); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + USHORT data_size; + + TRACE( "(%p,%p,%p)\n", handle, msg_in, msg_out ); + + if (!msg_in || !msg_out) return STATUS_INVALID_PARAMETER; + + data_size = msg_in->DataSize; + + /* Send the request message */ + SERVER_START_REQ( request_lpc_reply ) + { + req->handle = wine_server_obj_handle( handle ); + req->data_size = data_size; + req->msg_type = 1; /* request */ + wine_server_add_data( req, msg_in->Data, data_size ); + ret = wine_server_call( req ); + } + SERVER_END_REQ; + + if (ret) return ret; + + /* Wait for and receive the reply */ + for (;;) + { + SERVER_START_REQ( reply_wait_receive_lpc ) + { + req->handle = wine_server_obj_handle( handle ); + req->reply_msg_id = 0; + req->reply_size = 0; + req->timeout = TIMEOUT_INFINITE; + /* Use a reasonable max size for LPC message data. + * sizeof(msg_out->Data) is just 1 due to ANYSIZE_ARRAY. */ + wine_server_set_reply( req, msg_out->Data, 0x1000 ); + ret = wine_server_call( req ); + if (!ret) + { + msg_out->DataSize = reply->data_size; + msg_out->MessageSize = sizeof(*msg_out) + reply->data_size; + msg_out->MessageType = reply->msg_type; + msg_out->VirtualRangesOffset = 0; + msg_out->ClientId.UniqueProcess = ULongToHandle( reply->client_pid ); + msg_out->ClientId.UniqueThread = ULongToHandle( reply->client_tid ); + msg_out->MessageId = reply->msg_id; + msg_out->SectionSize = 0; + } + } + SERVER_END_REQ; + + if (ret != STATUS_PENDING) break; + + /* Wait on port for message availability */ + ret = NtWaitForSingleObject( handle, FALSE, NULL ); + if (ret) break; + } + return ret; +} + + +/*********************************************************************** + * NtRequestPort (NTDLL.@) + */ +NTSTATUS WINAPI NtRequestPort( HANDLE handle, LPC_MESSAGE *msg ) +{ + unsigned int ret; + USHORT data_size; + + TRACE( "(%p,%p)\n", handle, msg ); + + if (!msg) + return STATUS_INVALID_PARAMETER; + + data_size = msg->DataSize; + + SERVER_START_REQ( request_lpc_reply ) + { + req->handle = wine_server_obj_handle( handle ); + req->data_size = data_size; + req->msg_type = 3; /* datagram */ + wine_server_add_data( req, msg->Data, data_size ); + ret = wine_server_call( req ); + } + SERVER_END_REQ; + return ret; } diff --git a/dlls/wow64/sync.c b/dlls/wow64/sync.c index fad6e1ca304..3b89e633ab1 100644 --- a/dlls/wow64/sync.c +++ b/dlls/wow64/sync.c @@ -1358,8 +1358,19 @@ NTSTATUS WINAPI wow64_NtReplyWaitReceivePortEx( UINT *args ) LPC_MESSAGE *msg = get_ptr( &args ); LARGE_INTEGER *timeout = get_ptr( &args ); - FIXME( "%p %p %p %p %p: stub\n", handle, id, reply, msg, timeout ); - return STATUS_NOT_IMPLEMENTED; + return NtReplyWaitReceivePortEx( handle, id, reply, msg, timeout ); +} + + +/********************************************************************** + * wow64_NtRequestPort + */ +NTSTATUS WINAPI wow64_NtRequestPort( UINT *args ) +{ + HANDLE handle = get_handle( &args ); + LPC_MESSAGE *msg = get_ptr( &args ); + + return NtRequestPort( handle, msg ); } @@ -1372,8 +1383,7 @@ NTSTATUS WINAPI wow64_NtRequestWaitReplyPort( UINT *args ) LPC_MESSAGE *msg_in = get_ptr( &args ); LPC_MESSAGE *msg_out = get_ptr( &args ); - FIXME( "%p %p %p: stub\n", handle, msg_in, msg_out ); - return STATUS_NOT_IMPLEMENTED; + return NtRequestWaitReplyPort( handle, msg_in, msg_out ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Implement server-side LPC message receiving and replying: - NtReplyPort: Send a reply to a previous request message - NtReplyWaitReceivePort: Wrapper around NtReplyWaitReceivePortEx - NtReplyWaitReceivePortEx: Optionally reply to a previous message and wait for the next incoming message with optional timeout --- dlls/ntdll/tests/port.c | 5 +-- dlls/ntdll/unix/sync.c | 90 ++++++++++++++++++++++++++++++++++++----- dlls/wow64/sync.c | 6 +-- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/dlls/ntdll/tests/port.c b/dlls/ntdll/tests/port.c index 0b7cd92d739..302e766da4e 100644 --- a/dlls/ntdll/tests/port.c +++ b/dlls/ntdll/tests/port.c @@ -322,10 +322,7 @@ static void test_ports_server( HANDLE PortHandle ) while (TRUE) { status = pNtReplyWaitReceivePort(PortHandle, NULL, NULL, &LpcMessage->msg); - todo_wine - { - ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %ld(%lx)\n", status, status); - } + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %ld(%lx)\n", status, status); /* STATUS_INVALID_HANDLE: win2k without admin rights will perform an * endless loop here */ diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index d3f82e2cc49..c81552a7b3a 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3454,31 +3454,103 @@ NTSTATUS WINAPI NtRequestPort( HANDLE handle, LPC_MESSAGE *msg ) /*********************************************************************** * NtReplyPort (NTDLL.@) */ -NTSTATUS WINAPI NtReplyPort( HANDLE handle, LPC_MESSAGE *reply ) +NTSTATUS WINAPI NtReplyPort( HANDLE handle, LPC_MESSAGE *reply_msg ) { - FIXME("(%p,%p),stub!\n", handle, reply ); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + + TRACE( "(%p,%p)\n", handle, reply_msg ); + + if (!reply_msg) + return STATUS_INVALID_PARAMETER; + + SERVER_START_REQ( reply_wait_receive_lpc ) + { + req->handle = wine_server_obj_handle( handle ); + req->reply_msg_id = reply_msg->MessageId; + req->reply_size = reply_msg->DataSize; + req->timeout = 0; /* Don't wait for a new message */ + wine_server_add_data( req, reply_msg->Data, reply_msg->DataSize ); + ret = wine_server_call( req ); + /* STATUS_PENDING just means no new message, which is fine for NtReplyPort */ + if (ret == STATUS_PENDING) + ret = STATUS_SUCCESS; + } + SERVER_END_REQ; + return ret; } /*********************************************************************** * NtReplyWaitReceivePort (NTDLL.@) */ -NTSTATUS WINAPI NtReplyWaitReceivePort( HANDLE handle, ULONG *id, LPC_MESSAGE *reply, LPC_MESSAGE *msg ) +NTSTATUS WINAPI NtReplyWaitReceivePort( HANDLE handle, ULONG *id, LPC_MESSAGE *reply_msg, LPC_MESSAGE *msg ) { - FIXME("(%p,%p,%p,%p),stub!\n", handle, id, reply, msg ); - return STATUS_NOT_IMPLEMENTED; + return NtReplyWaitReceivePortEx( handle, id, reply_msg, msg, NULL ); } /*********************************************************************** * NtReplyWaitReceivePortEx (NTDLL.@) */ -NTSTATUS WINAPI NtReplyWaitReceivePortEx( HANDLE handle, ULONG *id, LPC_MESSAGE *reply, LPC_MESSAGE *msg, +NTSTATUS WINAPI NtReplyWaitReceivePortEx( HANDLE handle, ULONG *id, LPC_MESSAGE *reply_msg, LPC_MESSAGE *msg, LARGE_INTEGER *timeout ) { - FIXME("(%p,%p,%p,%p,%p),stub!\n", handle, id, reply, msg, timeout ); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + timeout_t abs_timeout = timeout ? timeout->QuadPart : TIMEOUT_INFINITE; + unsigned int reply_msg_id = 0; + USHORT reply_size = 0; + + TRACE( "(%p,%p,%p,%p,%p)\n", handle, id, reply_msg, msg, timeout ); + + if (reply_msg) + { + reply_msg_id = reply_msg->MessageId; + reply_size = reply_msg->DataSize; + } + + for (;;) + { + SERVER_START_REQ( reply_wait_receive_lpc ) + { + req->handle = wine_server_obj_handle( handle ); + req->reply_msg_id = reply_msg_id; + req->reply_size = reply_size; + req->timeout = abs_timeout; + if (reply_msg && reply_size) + wine_server_add_data( req, reply_msg->Data, reply_size ); + if (msg) + { + /* Use a reasonable max size for LPC message data. + * sizeof(msg->Data) is just 1 due to ANYSIZE_ARRAY. */ + wine_server_set_reply( req, msg->Data, 0x1000 ); + } + ret = wine_server_call( req ); + if (!ret && msg) + { + msg->DataSize = reply->data_size; + msg->MessageSize = sizeof(*msg) + reply->data_size; + msg->MessageType = reply->msg_type; + msg->VirtualRangesOffset = 0; + msg->ClientId.UniqueProcess = ULongToHandle( reply->client_pid ); + msg->ClientId.UniqueThread = ULongToHandle( reply->client_tid ); + msg->MessageId = reply->msg_id; + msg->SectionSize = 0; + if (id) *id = (ULONG)(ULONG_PTR)reply->context; + } + } + SERVER_END_REQ; + + /* After first iteration, don't send reply again */ + reply_msg_id = 0; + reply_size = 0; + + if (ret != STATUS_PENDING) break; + + /* Wait on port for message availability */ + ret = NtWaitForSingleObject( handle, FALSE, timeout ); + if (ret) break; + } + return ret; } diff --git a/dlls/wow64/sync.c b/dlls/wow64/sync.c index 3b89e633ab1..c4ba357979b 100644 --- a/dlls/wow64/sync.c +++ b/dlls/wow64/sync.c @@ -1327,8 +1327,7 @@ NTSTATUS WINAPI wow64_NtReplyPort( UINT *args ) HANDLE handle = get_handle( &args ); LPC_MESSAGE *reply = get_ptr( &args ); - FIXME( "%p %p: stub\n", handle, reply ); - return STATUS_NOT_IMPLEMENTED; + return NtReplyPort( handle, reply ); } @@ -1342,8 +1341,7 @@ NTSTATUS WINAPI wow64_NtReplyWaitReceivePort( UINT *args ) LPC_MESSAGE *reply = get_ptr( &args ); LPC_MESSAGE *msg = get_ptr( &args ); - FIXME( "%p %p %p %p: stub\n", handle, id, reply, msg ); - return STATUS_NOT_IMPLEMENTED; + return NtReplyWaitReceivePort( handle, id, reply, msg ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Add support for sending LPC_CLIENT_DIED messages when a thread terminates: - New lpc_terminate_ports list in thread structure - register_lpc_terminate_port handler to register client ports - lpc_send_client_died function called during thread cleanup to send termination messages to all registered ports --- server/lpc_port.c | 96 +++++++++++++++++++++++++++++++++++++++++++++ server/protocol.def | 6 +++ server/thread.c | 2 + server/thread.h | 5 +++ 4 files changed, 109 insertions(+) diff --git a/server/lpc_port.c b/server/lpc_port.c index 73da2ab648a..ba5e3d7e9da 100644 --- a/server/lpc_port.c +++ b/server/lpc_port.c @@ -54,6 +54,12 @@ #define LPC_REQUEST 1 #define LPC_REPLY 2 #define LPC_DATAGRAM 3 +#define LPC_LOST_REPLY 4 +#define LPC_PORT_CLOSED 5 +#define LPC_CLIENT_DIED 6 +#define LPC_EXCEPTION 7 +#define LPC_DEBUG_EVENT 8 +#define LPC_ERROR_EVENT 9 #define LPC_CONNECTION_REQUEST 10 /* Maximum message size */ @@ -77,6 +83,13 @@ struct pending_request struct lpc_port *client_port; /* client port to deliver reply to */ }; +/* Entry in thread's list of ports to notify on termination */ +struct lpc_terminate_port_entry +{ + struct list entry; /* entry in thread's lpc_terminate_ports list */ + struct lpc_port *port; /* the LPC port (client port) */ +}; + static const WCHAR lpc_port_name[] = {'L','P','C',' ','P','o','r','t'}; struct type_descr lpc_port_type = @@ -835,3 +848,86 @@ DECL_HANDLER(reply_wait_receive_lpc) reset_port_queue( receive_port ); release_object( port ); } + +/* Register a port for thread termination notification */ +DECL_HANDLER(register_lpc_terminate_port) +{ + struct lpc_port *port; + struct lpc_terminate_port_entry *entry; + + port = (struct lpc_port *)get_handle_obj( current->process, req->handle, + 0, &lpc_port_ops ); + if (!port) return; + + /* Only client ports should be registered for termination */ + if (port->port_type != PORT_TYPE_CLIENT) + { + set_error( STATUS_INVALID_PORT_HANDLE ); + release_object( port ); + return; + } + + /* Check if already registered */ + LIST_FOR_EACH_ENTRY( entry, ¤t->lpc_terminate_ports, struct lpc_terminate_port_entry, entry ) + { + if (entry->port == port) + { + release_object( port ); + return; + } + } + + /* Add to thread's terminate port list */ + entry = mem_alloc( sizeof(*entry) ); + if (entry) + { + entry->port = port; /* transfer the reference */ + list_add_tail( ¤t->lpc_terminate_ports, &entry->entry ); + } + else + { + release_object( port ); + } +} + +/* Send LPC_CLIENT_DIED messages to all registered ports for a thread. + * Called from cleanup_thread in thread.c */ +void lpc_send_client_died( struct thread *thread ) +{ + struct lpc_terminate_port_entry *entry, *next; + + LIST_FOR_EACH_ENTRY_SAFE( entry, next, &thread->lpc_terminate_ports, + struct lpc_terminate_port_entry, entry ) + { + struct lpc_port *client_port = entry->port; + + /* Send LPC_CLIENT_DIED to the server port via the communication channel */ + if (client_port && client_port->connected_port) + { + struct lpc_port *comm_port = client_port->connected_port; + struct lpc_port *server_port = comm_port->connection_port; + + if (server_port && server_port->port_type == PORT_TYPE_SERVER) + { + struct lpc_message *died_msg = alloc_lpc_message( 0 ); + if (died_msg) + { + died_msg->msg_id = get_next_msg_id(); + died_msg->msg_type = LPC_CLIENT_DIED; + died_msg->client_pid = thread->process->id; + died_msg->client_tid = thread->id; + died_msg->port_context = comm_port->port_context; + + /* Queue on server port */ + list_add_tail( &server_port->msg_queue, &died_msg->entry ); + signal_port_queue( server_port ); + } + } + } + + /* Clean up the entry */ + list_remove( &entry->entry ); + release_object( client_port ); + free( entry ); + } +} diff --git a/server/protocol.def b/server/protocol.def index 6db622cf03c..59ef67714cf 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -4367,3 +4367,9 @@ enum inproc_sync_type data_size_t data_size; /* size of message data */ VARARG(data,bytes); /* message data */ @END + + +/* Register port for thread termination notification */ +@REQ(register_lpc_terminate_port) + obj_handle_t handle; /* handle to client port */ +@END diff --git a/server/thread.c b/server/thread.c index 7207f918400..61ae075eb3c 100644 --- a/server/thread.c +++ b/server/thread.c @@ -437,6 +437,7 @@ static inline void init_thread_structure( struct thread *thread ) list_init( &thread->system_apc ); list_init( &thread->user_apc ); list_init( &thread->kernel_object ); + list_init( &thread->lpc_terminate_ports ); for (i = 0; i < MAX_INFLIGHT_FDS; i++) thread->inflight[i].server = thread->inflight[i].client = -1; @@ -609,6 +610,7 @@ static void cleanup_thread( struct thread *thread ) { int i; + lpc_send_client_died( thread ); cleanup_thread_completion( thread ); if (thread->context) { diff --git a/server/thread.h b/server/thread.h index 77ea355483d..e55ad0259f4 100644 --- a/server/thread.h +++ b/server/thread.h @@ -99,6 +99,7 @@ struct thread data_size_t desc_len; /* thread description length in bytes */ WCHAR *desc; /* thread description string */ struct completion_wait *completion_wait; /* completion port wait object the thread is associated with */ + struct list lpc_terminate_ports; /* list of ports to notify on thread termination */ }; extern struct thread *current; @@ -144,6 +145,10 @@ extern void get_thread_context( struct thread *thread, struct context_data *cont extern void set_thread_context( struct thread *thread, const struct context_data *context, unsigned int flags ); extern int send_thread_signal( struct thread *thread, int sig ); +/* LPC functions */ + +extern void lpc_send_client_died( struct thread *thread ); + extern unsigned int global_error; /* global error code for when no thread is current */ static inline unsigned int get_error(void) { return current ? current->error : global_error; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Register a port to receive LPC_CLIENT_DIED messages when the calling thread terminates. --- dlls/ntdll/unix/sync.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index c81552a7b3a..60e611bb25d 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3354,8 +3354,17 @@ NTSTATUS WINAPI NtReadRequestData( HANDLE handle, LPC_MESSAGE *request, ULONG id */ NTSTATUS WINAPI NtRegisterThreadTerminatePort( HANDLE handle ) { - FIXME( "(%p),stub!\n", handle ); - return STATUS_NOT_IMPLEMENTED; + unsigned int ret; + + TRACE( "(%p)\n", handle ); + + SERVER_START_REQ( register_lpc_terminate_port ) + { + req->handle = wine_server_obj_handle( handle ); + ret = wine_server_call( req ); + } + SERVER_END_REQ; + return ret; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> LPC ports have circular references that prevent proper cleanup: - SERVER ports hold a self-reference via connection_port - CLIENT and CHANNEL ports have bidirectional connected_port references - Pending requests hold references to client ports Add a close_handle callback that breaks these circular references when the last handle to a port is closed, allowing the ports to be properly destroyed. --- server/lpc_port.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/server/lpc_port.c b/server/lpc_port.c index ba5e3d7e9da..5682cad1910 100644 --- a/server/lpc_port.c +++ b/server/lpc_port.c @@ -144,6 +144,7 @@ struct lpc_port static void lpc_port_dump( struct object *obj, int verbose ); static struct object *lpc_port_get_sync( struct object *obj ); +static int lpc_port_close_handle( struct object *obj, struct process *process, obj_handle_t handle ); static void lpc_port_destroy( struct object *obj ); static const struct object_ops lpc_port_ops = @@ -167,7 +168,7 @@ static const struct object_ops lpc_port_ops = default_unlink_name, /* unlink_name */ no_open_file, /* open_file */ no_kernel_obj_list, /* get_kernel_obj_list */ - no_close_handle, /* close_handle */ + lpc_port_close_handle, /* close_handle */ lpc_port_destroy /* destroy */ }; @@ -389,6 +390,60 @@ static struct object *lpc_port_get_sync( struct object *obj ) return NULL; } +/* Break circular references when the last handle to a port is closed */ +static int lpc_port_close_handle( struct object *obj, struct process *process, obj_handle_t handle ) +{ + struct lpc_port *port = (struct lpc_port *)obj; + struct pending_request *pr, *next_pr; + + if (obj->handle_count == 1) + { + /* Clean up any pending requests that reference this port */ + LIST_FOR_EACH_ENTRY_SAFE( pr, next_pr, &global_pending_requests, struct pending_request, entry ) + { + if (pr->client_port == port) + { + list_remove( &pr->entry ); + release_object( pr->client_port ); + free( pr ); + } + } + + /* Break the bidirectional connected_port reference */ + if (port->connected_port) + { + struct lpc_port *peer = port->connected_port; + + /* Clear peer's reference to us first */ + if (peer->connected_port == port) + { + peer->connected_port = NULL; + release_object( port ); + } + + /* Clear our reference to peer */ + port->connected_port = NULL; + release_object( peer ); + } + + /* For SERVER ports, release the self-reference from connection_port */ + if (port->port_type == PORT_TYPE_SERVER && port->connection_port == port) + { + port->connection_port = NULL; + release_object( port ); + } + /* For client/channel ports, release the connection_port reference + * to allow the named server port to be destroyed */ + else if (port->port_type != PORT_TYPE_SERVER && port->connection_port) + { + release_object( port->connection_port ); + port->connection_port = NULL; + } + } + + return 1; +} + static void lpc_port_destroy( struct object *obj ) { struct lpc_port *port = (struct lpc_port *)obj; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
From: Rose Hellsing <rose@pinkro.se> Add tests for NtCreatePort and NtConnectPort error paths - NULL/zero-length object attributes - NULL/empty port names - Duplicate port names - Invalid max message/connect info sizes - Non-existent port connections Also add NULL parameter validation to NtConnectPort. --- dlls/ntdll/tests/port.c | 316 ++++++++++++++++++++++++++++++++++++++++ dlls/ntdll/unix/sync.c | 5 + 2 files changed, 321 insertions(+) diff --git a/dlls/ntdll/tests/port.c b/dlls/ntdll/tests/port.c index 302e766da4e..cd62ee4a34c 100644 --- a/dlls/ntdll/tests/port.c +++ b/dlls/ntdll/tests/port.c @@ -119,6 +119,9 @@ static NTSTATUS (WINAPI *pNtAcceptConnectPort)(PHANDLE,ULONG,PLPC_MESSAGE,ULONG, static NTSTATUS (WINAPI *pNtReplyPort)(HANDLE,PLPC_MESSAGE); static NTSTATUS (WINAPI *pNtReplyWaitReceivePort)(PHANDLE,PULONG,PLPC_MESSAGE, PLPC_MESSAGE); +static NTSTATUS (WINAPI *pNtReplyWaitReceivePortEx)(PHANDLE,PULONG,PLPC_MESSAGE, + PLPC_MESSAGE,LARGE_INTEGER*); +static NTSTATUS (WINAPI *pNtListenPort)(HANDLE,PLPC_MESSAGE); static NTSTATUS (WINAPI *pNtCreatePort)(PHANDLE,POBJECT_ATTRIBUTES,ULONG,ULONG,ULONG); static NTSTATUS (WINAPI *pNtRequestWaitReplyPort)(HANDLE,PLPC_MESSAGE,PLPC_MESSAGE); static NTSTATUS (WINAPI *pNtRequestPort)(HANDLE,PLPC_MESSAGE); @@ -143,6 +146,8 @@ static BOOL init_function_ptrs(void) pNtAcceptConnectPort = (void *)GetProcAddress(hntdll, "NtAcceptConnectPort"); pNtReplyPort = (void *)GetProcAddress(hntdll, "NtReplyPort"); pNtReplyWaitReceivePort = (void *)GetProcAddress(hntdll, "NtReplyWaitReceivePort"); + pNtReplyWaitReceivePortEx = (void *)GetProcAddress(hntdll, "NtReplyWaitReceivePortEx"); + pNtListenPort = (void *)GetProcAddress(hntdll, "NtListenPort"); pNtCreatePort = (void *)GetProcAddress(hntdll, "NtCreatePort"); pNtRequestWaitReplyPort = (void *)GetProcAddress(hntdll, "NtRequestWaitReplyPort"); pNtRequestPort = (void *)GetProcAddress(hntdll, "NtRequestPort"); @@ -364,6 +369,279 @@ static void test_ports_server( HANDLE PortHandle ) HeapFree(GetProcessHeap(), 0, LpcMessage); } +static void test_create_port_errors(void) +{ + OBJECT_ATTRIBUTES obj; + HANDLE port_handle; + NTSTATUS status; + UNICODE_STRING name; + static const WCHAR ERR_PORT1[] = {'\\','E','r','r','P','o','r','t','1',0}; + static const WCHAR ERR_PORT2[] = {'\\','E','r','r','P','o','r','t','2',0}; + static const WCHAR ERR_PORT3[] = {'\\','E','r','r','P','o','r','t','3',0}; + + /* Test NULL object attributes - Windows 64-bit returns SUCCESS, Wow64 returns ACCESS_VIOLATION */ + status = pNtCreatePort(&port_handle, NULL, 0, 0, 0); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER || status == STATUS_ACCESS_VIOLATION, + "Expected STATUS_SUCCESS, STATUS_INVALID_PARAMETER or STATUS_ACCESS_VIOLATION, got %08lx\n", status); + if (status == STATUS_SUCCESS) NtClose(port_handle); + + /* Test zero-length object attributes - Windows returns SUCCESS */ + memset(&obj, 0, sizeof(OBJECT_ATTRIBUTES)); + obj.Length = 0; + status = pNtCreatePort(&port_handle, &obj, 0, 0, 0); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER || status == STATUS_INVALID_HANDLE, + "Expected STATUS_SUCCESS, STATUS_INVALID_PARAMETER or STATUS_INVALID_HANDLE, got %08lx\n", status); + if (status == STATUS_SUCCESS) NtClose(port_handle); + + /* Test with valid object attributes but NULL name - Windows returns SUCCESS */ + memset(&obj, 0, sizeof(OBJECT_ATTRIBUTES)); + obj.Length = sizeof(OBJECT_ATTRIBUTES); + obj.ObjectName = NULL; + status = pNtCreatePort(&port_handle, &obj, 0, 0, 0); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER || status == STATUS_OBJECT_NAME_INVALID, + "Expected STATUS_SUCCESS, STATUS_INVALID_PARAMETER or STATUS_OBJECT_NAME_INVALID, got %08lx\n", status); + if (status == STATUS_SUCCESS) NtClose(port_handle); + + /* Test with empty name - Windows returns SUCCESS */ + pRtlInitUnicodeString(&name, L""); + memset(&obj, 0, sizeof(OBJECT_ATTRIBUTES)); + obj.Length = sizeof(OBJECT_ATTRIBUTES); + obj.ObjectName = &name; + status = pNtCreatePort(&port_handle, &obj, 0, 0, 0); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER || status == STATUS_OBJECT_NAME_INVALID, + "Expected STATUS_SUCCESS, STATUS_INVALID_PARAMETER or STATUS_OBJECT_NAME_INVALID, got %08lx\n", status); + if (status == STATUS_SUCCESS) NtClose(port_handle); + + /* Test with max message size too large - use unique name to avoid collision */ + pRtlInitUnicodeString(&name, ERR_PORT1); + memset(&obj, 0, sizeof(OBJECT_ATTRIBUTES)); + obj.Length = sizeof(OBJECT_ATTRIBUTES); + obj.ObjectName = &name; + status = pNtCreatePort(&port_handle, &obj, 0x100001, 0, 0); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER || status == STATUS_SECTION_TOO_BIG || status == STATUS_OBJECT_NAME_COLLISION, + "Expected STATUS_SUCCESS, STATUS_INVALID_PARAMETER, STATUS_SECTION_TOO_BIG or STATUS_OBJECT_NAME_COLLISION, got %08lx\n", status); + if (status == STATUS_SUCCESS) NtClose(port_handle); + + /* Test with max connect info too large - use unique name */ + pRtlInitUnicodeString(&name, ERR_PORT2); + memset(&obj, 0, sizeof(OBJECT_ATTRIBUTES)); + obj.Length = sizeof(OBJECT_ATTRIBUTES); + obj.ObjectName = &name; + status = pNtCreatePort(&port_handle, &obj, 0, 0x100001, 0); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER || status == STATUS_SECTION_TOO_BIG || status == STATUS_OBJECT_NAME_COLLISION, + "Expected STATUS_SUCCESS, STATUS_INVALID_PARAMETER, STATUS_SECTION_TOO_BIG or STATUS_OBJECT_NAME_COLLISION, got %08lx\n", status); + if (status == STATUS_SUCCESS) NtClose(port_handle); + + /* Test creating port with name that already exists */ + pRtlInitUnicodeString(&name, ERR_PORT3); + memset(&obj, 0, sizeof(OBJECT_ATTRIBUTES)); + obj.Length = sizeof(OBJECT_ATTRIBUTES); + obj.ObjectName = &name; + status = pNtCreatePort(&port_handle, &obj, 100, 100, 0); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + if (status == STATUS_SUCCESS) + { + NTSTATUS status2; + HANDLE port_handle2; + status2 = pNtCreatePort(&port_handle2, &obj, 100, 100, 0); + ok(status2 == STATUS_OBJECT_NAME_COLLISION, + "Expected STATUS_OBJECT_NAME_COLLISION, got %08lx\n", status2); + NtClose(port_handle); + } +} + +static void test_connect_port_errors(void) +{ + SECURITY_QUALITY_OF_SERVICE sqos; + HANDLE port_handle; + ULONG len; + NTSTATUS status; + UNICODE_STRING bad_name; + static const WCHAR BAD_NAME[] = {'\\','N','o','n','E','x','i','s','t','e','n','t','P','o','r','t',0}; + + sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); + sqos.ImpersonationLevel = SecurityImpersonation; + sqos.ContextTrackingMode = SECURITY_STATIC_TRACKING; + sqos.EffectiveOnly = TRUE; + + /* Test connecting to non-existent port */ + pRtlInitUnicodeString(&bad_name, BAD_NAME); + status = pNtConnectPort(&port_handle, &bad_name, &sqos, 0, 0, &len, NULL, NULL); + ok(status == STATUS_OBJECT_NAME_NOT_FOUND || status == STATUS_INVALID_PARAMETER, + "Expected STATUS_OBJECT_NAME_NOT_FOUND or STATUS_INVALID_PARAMETER, got %08lx\n", status); + + /* NULL parameter tests crash on Wow64 due to thunking, skip them */ + if (!is_wow64) + { + /* Test with NULL port name - Windows returns STATUS_OBJECT_NAME_INVALID */ + status = pNtConnectPort(&port_handle, NULL, &sqos, 0, 0, &len, NULL, NULL); + ok(status == STATUS_INVALID_PARAMETER || status == STATUS_ACCESS_VIOLATION || status == STATUS_OBJECT_NAME_INVALID, + "Expected STATUS_INVALID_PARAMETER, STATUS_ACCESS_VIOLATION or STATUS_OBJECT_NAME_INVALID, got %08lx\n", status); + + /* Test with NULL handle pointer - Windows returns STATUS_OBJECT_NAME_INVALID */ + status = pNtConnectPort(NULL, &port, &sqos, 0, 0, &len, NULL, NULL); + ok(status == STATUS_ACCESS_VIOLATION || status == STATUS_INVALID_PARAMETER || status == STATUS_OBJECT_NAME_INVALID, + "Expected STATUS_ACCESS_VIOLATION, STATUS_INVALID_PARAMETER or STATUS_OBJECT_NAME_INVALID, got %08lx\n", status); + } +} + +static void test_zero_length_server(HANDLE PortHandle) +{ + HANDLE AcceptPortHandle; + union lpc_message *LpcMessage; + ULONG size; + NTSTATUS status; + BOOL done = FALSE; + + size = FIELD_OFFSET(LPC_MESSAGE, Data) + MAX_MESSAGE_LEN; + LpcMessage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); + + while (!done) + { + status = pNtReplyWaitReceivePort(PortHandle, NULL, NULL, &LpcMessage->msg); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + if (status != STATUS_SUCCESS) break; + + if (is_wow64) + { + switch (LpcMessage->msg64.MessageType) + { + case LPC_CONNECTION_REQUEST: + status = pNtAcceptConnectPort(&AcceptPortHandle, 0, &LpcMessage->msg, 1, NULL, NULL); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + if (status == STATUS_SUCCESS) + { + status = pNtCompleteConnectPort(AcceptPortHandle); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + } + break; + + case LPC_REQUEST: + /* Reply to zero-length request with zero-length reply */ + LpcMessage->msg64.DataSize = 0; + LpcMessage->msg64.MessageSize = FIELD_OFFSET(LPC_MESSAGE64, Data[0]); + status = pNtReplyPort(PortHandle, &LpcMessage->msg); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + done = TRUE; + break; + + case LPC_DATAGRAM: + /* Zero-length datagram received */ + ok(LpcMessage->msg64.DataSize == 0, "Expected DataSize 0, got %u\n", LpcMessage->msg64.DataSize); + break; + + case LPC_CLIENT_DIED: + done = TRUE; + break; + } + } + else + { + switch (LpcMessage->msg.MessageType) + { + case LPC_CONNECTION_REQUEST: + status = pNtAcceptConnectPort(&AcceptPortHandle, 0, &LpcMessage->msg, 1, NULL, NULL); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + if (status == STATUS_SUCCESS) + { + status = pNtCompleteConnectPort(AcceptPortHandle); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + } + break; + + case LPC_REQUEST: + /* Reply to zero-length request with zero-length reply */ + LpcMessage->msg.DataSize = 0; + LpcMessage->msg.MessageSize = FIELD_OFFSET(LPC_MESSAGE, Data[0]); + status = pNtReplyPort(PortHandle, &LpcMessage->msg); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + done = TRUE; + break; + + case LPC_DATAGRAM: + /* Zero-length datagram received */ + ok(LpcMessage->msg.DataSize == 0, "Expected DataSize 0, got %u\n", LpcMessage->msg.DataSize); + break; + + case LPC_CLIENT_DIED: + done = TRUE; + break; + } + } + } + + HeapFree(GetProcessHeap(), 0, LpcMessage); +} + +static DWORD WINAPI zero_length_client(LPVOID arg) +{ + SECURITY_QUALITY_OF_SERVICE sqos; + HANDLE PortHandle; + ULONG len; + NTSTATUS status; + union lpc_message *LpcMessage, *out; + ULONG size; + + sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); + sqos.ImpersonationLevel = SecurityImpersonation; + sqos.ContextTrackingMode = SECURITY_STATIC_TRACKING; + sqos.EffectiveOnly = TRUE; + + status = pNtConnectPort(&PortHandle, &port, &sqos, 0, 0, &len, NULL, NULL); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + if (status != STATUS_SUCCESS) return 1; + + if (is_wow64) + { + size = FIELD_OFFSET(LPC_MESSAGE64, Data[MAX_MESSAGE_LEN]); + LpcMessage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); + out = HeapAlloc(GetProcessHeap(), 0, size); + + /* Send zero-length datagram */ + LpcMessage->msg64.DataSize = 0; + LpcMessage->msg64.MessageSize = FIELD_OFFSET(LPC_MESSAGE64, Data[0]); + status = pNtRequestPort(PortHandle, &LpcMessage->msg); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER, + "Expected STATUS_SUCCESS or STATUS_INVALID_PARAMETER, got %08lx\n", status); + + /* Send zero-length request and wait for reply */ + LpcMessage->msg64.DataSize = 0; + LpcMessage->msg64.MessageSize = FIELD_OFFSET(LPC_MESSAGE64, Data[0]); + status = pNtRequestWaitReplyPort(PortHandle, &LpcMessage->msg, &out->msg); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER, + "Expected STATUS_SUCCESS or STATUS_INVALID_PARAMETER, got %08lx\n", status); + + HeapFree(GetProcessHeap(), 0, out); + HeapFree(GetProcessHeap(), 0, LpcMessage); + } + else + { + size = FIELD_OFFSET(LPC_MESSAGE, Data[MAX_MESSAGE_LEN]); + LpcMessage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); + out = HeapAlloc(GetProcessHeap(), 0, size); + + /* Send zero-length datagram */ + LpcMessage->msg.DataSize = 0; + LpcMessage->msg.MessageSize = FIELD_OFFSET(LPC_MESSAGE, Data[0]); + status = pNtRequestPort(PortHandle, &LpcMessage->msg); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER, + "Expected STATUS_SUCCESS or STATUS_INVALID_PARAMETER, got %08lx\n", status); + + /* Send zero-length request and wait for reply */ + LpcMessage->msg.DataSize = 0; + LpcMessage->msg.MessageSize = FIELD_OFFSET(LPC_MESSAGE, Data[0]); + status = pNtRequestWaitReplyPort(PortHandle, &LpcMessage->msg, &out->msg); + ok(status == STATUS_SUCCESS || status == STATUS_INVALID_PARAMETER, + "Expected STATUS_SUCCESS or STATUS_INVALID_PARAMETER, got %08lx\n", status); + + HeapFree(GetProcessHeap(), 0, out); + HeapFree(GetProcessHeap(), 0, LpcMessage); + } + + NtClose(PortHandle); + return 0; +} + START_TEST(port) { OBJECT_ATTRIBUTES obj; @@ -393,5 +671,43 @@ START_TEST(port) ok( WaitForSingleObject( thread, 10000 ) == 0, "thread didn't exit\n" ); CloseHandle(thread); } + + test_create_port_errors(); + test_connect_port_errors(); + + if (status == STATUS_SUCCESS) + { + static const WCHAR ZERO_PORT[] = {'\\','Z','e','r','o','P','o','r','t',0}; + UNICODE_STRING zero_name; + HANDLE zero_port; + DWORD id; + HANDLE thread; + + pRtlInitUnicodeString(&zero_name, ZERO_PORT); + memset(&obj, 0, sizeof(OBJECT_ATTRIBUTES)); + obj.Length = sizeof(OBJECT_ATTRIBUTES); + obj.ObjectName = &zero_name; + + status = pNtCreatePort(&zero_port, &obj, 100, 100, 0); + ok(status == STATUS_SUCCESS, "Expected STATUS_SUCCESS, got %08lx\n", status); + if (status == STATUS_SUCCESS) + { + /* Update global port name so client connects to the right port */ + port = zero_name; + thread = CreateThread(NULL, 0, zero_length_client, NULL, 0, &id); + ok(thread != NULL, "Expected non-NULL thread handle!\n"); + + test_zero_length_server(zero_port); + ok(WaitForSingleObject(thread, 10000) == 0, "zero-length thread didn't exit\n"); + CloseHandle(thread); + NtClose(zero_port); + } + } + + + + if (status == STATUS_SUCCESS) + NtClose(port_handle); + FreeLibrary(hntdll); } diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index 60e611bb25d..1f2de575f3d 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3146,6 +3146,11 @@ NTSTATUS WINAPI NtConnectPort( HANDLE *handle, UNICODE_STRING *name, SECURITY_QU TRACE( "(%p,%s,%p,%p,%p,%p,%p,%p)\n", handle, debugstr_us(name), qos, write, read, max_len, info, info_len ); + if (!handle) + return STATUS_ACCESS_VIOLATION; + if (!name) + return STATUS_OBJECT_NAME_INVALID; + if (write) FIXME( "LPC_SECTION_WRITE not supported\n" ); if (read) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10611
participants (2)
-
Rose Hellsing -
Rose Hellsing (@axtlos)