/* * nsiproxy.sys * * Copyright 2021 Huw Davies * * 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 #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" #include "winbase.h" #include "winternl.h" #include "winioctl.h" #include "ddk/wdm.h" #include "ifdef.h" #include "netiodef.h" #include "wine/nsi.h" #include "wine/debug.h" #include "wine/unixlib.h" #include "nsiproxy_private.h" WINE_DEFAULT_DEBUG_CHANNEL(nsi); static unixlib_handle_t nsiproxy_handle; static HANDLE request_event; #define DECLARE_CRITICAL_SECTION(cs) \ static CRITICAL_SECTION cs; \ static CRITICAL_SECTION_DEBUG cs##_debug = \ { 0, 0, &cs, { &cs##_debug.ProcessLocksList, &cs##_debug.ProcessLocksList }, \ 0, 0, { (DWORD_PTR)(__FILE__ ": " # cs) }}; \ static CRITICAL_SECTION cs = { &cs##_debug, -1, 0, 0, 0, 0 }; DECLARE_CRITICAL_SECTION( nsiproxy_cs ); #define LIST_ENTRY_INIT( list ) { .Flink = &(list), .Blink = &(list) } static LIST_ENTRY request_queue = LIST_ENTRY_INIT( request_queue ); static NTSTATUS nsiproxy_call( unsigned int code, void *args ) { return __wine_unix_call( nsiproxy_handle, code, args ); } enum unix_calls { icmp_close, icmp_listen, icmp_send_echo, nsi_enumerate_all_ex, nsi_get_all_parameters_ex, nsi_get_parameter_ex, }; static NTSTATUS nsiproxy_enumerate_all( IRP *irp ) { IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); struct nsiproxy_enumerate_all *in = (struct nsiproxy_enumerate_all *)irp->AssociatedIrp.SystemBuffer; DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength; void *out = irp->AssociatedIrp.SystemBuffer; DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength; struct nsi_enumerate_all_ex enum_all; NTSTATUS status; if (in_len != sizeof(*in)) return STATUS_INVALID_PARAMETER; if (out_len < sizeof(DWORD) + (in->key_size + in->rw_size + in->dynamic_size + in->static_size) * in->count) return STATUS_INVALID_PARAMETER; enum_all.unknown[0] = 0; enum_all.unknown[1] = 0; enum_all.first_arg = in->first_arg; enum_all.second_arg = in->second_arg; enum_all.module = &in->module; enum_all.table = in->table; enum_all.key_data = (BYTE *)out + sizeof(DWORD); enum_all.key_size = in->key_size; enum_all.rw_data = (BYTE *)enum_all.key_data + in->key_size * in->count; enum_all.rw_size = in->rw_size; enum_all.dynamic_data = (BYTE *)enum_all.rw_data + in->rw_size * in->count; enum_all.dynamic_size = in->dynamic_size; enum_all.static_data = (BYTE *)enum_all.dynamic_data + in->dynamic_size * in->count; enum_all.static_size = in->static_size; enum_all.count = in->count; status = nsiproxy_call( nsi_enumerate_all_ex, &enum_all ); if (status == STATUS_SUCCESS || status == STATUS_BUFFER_OVERFLOW) { irp->IoStatus.Information = out_len; *(DWORD *)out = enum_all.count; } else irp->IoStatus.Information = 0; return status; } static NTSTATUS nsiproxy_get_all_parameters( IRP *irp ) { IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); struct nsiproxy_get_all_parameters *in = (struct nsiproxy_get_all_parameters *)irp->AssociatedIrp.SystemBuffer; DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength; BYTE *out = irp->AssociatedIrp.SystemBuffer; DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength; struct nsi_get_all_parameters_ex get_all; NTSTATUS status; if (in_len < offsetof(struct nsiproxy_get_all_parameters, key[0]) || in_len < offsetof(struct nsiproxy_get_all_parameters, key[in->key_size])) return STATUS_INVALID_PARAMETER; if (out_len < in->rw_size + in->dynamic_size + in->static_size) return STATUS_INVALID_PARAMETER; get_all.unknown[0] = 0; get_all.unknown[1] = 0; get_all.first_arg = in->first_arg; get_all.unknown2 = 0; get_all.module = &in->module; get_all.table = in->table; get_all.key = in->key; get_all.key_size = in->key_size; get_all.rw_data = out; get_all.rw_size = in->rw_size; get_all.dynamic_data = out + in->rw_size; get_all.dynamic_size = in->dynamic_size; get_all.static_data = out + in->rw_size + in->dynamic_size; get_all.static_size = in->static_size; status = nsiproxy_call( nsi_get_all_parameters_ex, &get_all ); irp->IoStatus.Information = (status == STATUS_SUCCESS) ? out_len : 0; return status; } static NTSTATUS nsiproxy_get_parameter( IRP *irp ) { IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); struct nsiproxy_get_parameter *in = (struct nsiproxy_get_parameter *)irp->AssociatedIrp.SystemBuffer; DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength; void *out = irp->AssociatedIrp.SystemBuffer; DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength; struct nsi_get_parameter_ex get_param; NTSTATUS status; if (in_len < offsetof(struct nsiproxy_get_parameter, key[0]) || in_len < offsetof(struct nsiproxy_get_parameter, key[in->key_size])) return STATUS_INVALID_PARAMETER; get_param.unknown[0] = 0; get_param.unknown[1] = 0; get_param.first_arg = in->first_arg; get_param.unknown2 = 0; get_param.module = &in->module; get_param.table = in->table; get_param.key = in->key; get_param.key_size = in->key_size; get_param.param_type = in->param_type; get_param.data = out; get_param.data_size = out_len; get_param.data_offset = in->data_offset; status = nsiproxy_call( nsi_get_parameter_ex, &get_param ); irp->IoStatus.Information = (status == STATUS_SUCCESS) ? out_len : 0; return status; } static inline HANDLE irp_get_icmp_handle( IRP *irp ) { return irp->Tail.Overlay.DriverContext[0]; } static inline HANDLE irp_set_icmp_handle( IRP *irp, HANDLE handle ) { return InterlockedExchangePointer( irp->Tail.Overlay.DriverContext, handle ); } static void WINAPI icmp_echo_cancel( DEVICE_OBJECT *device, IRP *irp ) { TRACE( "device %p, irp %p.\n", device, irp ); IoReleaseCancelSpinLock( irp->CancelIrql ); /* FIXME: at the moment just let the request thread bail */ return; } static NTSTATUS nsiproxy_icmp_echo( IRP *irp ) { IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); struct nsiproxy_icmp_echo *in = (struct nsiproxy_icmp_echo *)irp->AssociatedIrp.SystemBuffer; DWORD in_len = irpsp->Parameters.DeviceIoControl.InputBufferLength; DWORD out_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength; TRACE( "\n" ); if (in_len < offsetof(struct nsiproxy_icmp_echo, data[0]) || in_len < offsetof(struct nsiproxy_icmp_echo, data[((in->opt_size + 3) & ~3) + in->req_size]) || out_len < sizeof(struct nsiproxy_icmp_echo_reply)) return STATUS_INVALID_PARAMETER; switch (in->dst.si_family) { case AF_INET: if (in->dst.Ipv4.sin_addr.s_addr == INADDR_ANY) return STATUS_INVALID_ADDRESS_WILDCARD; break; default: return STATUS_INVALID_PARAMETER; } EnterCriticalSection( &nsiproxy_cs ); IoSetCancelRoutine( irp, icmp_echo_cancel ); if (irp->Cancel && !IoSetCancelRoutine( irp, NULL )) { /* IRP was canceled before we set cancel routine */ InitializeListHead( &irp->Tail.Overlay.ListEntry ); LeaveCriticalSection( &nsiproxy_cs ); return STATUS_CANCELLED; } InsertTailList( &request_queue, &irp->Tail.Overlay.ListEntry ); IoMarkIrpPending( irp ); LeaveCriticalSection( &nsiproxy_cs ); SetEvent( request_event ); return STATUS_PENDING; } static NTSTATUS WINAPI nsi_ioctl( DEVICE_OBJECT *device, IRP *irp ) { IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); NTSTATUS status; TRACE( "ioctl %x insize %u outsize %u\n", irpsp->Parameters.DeviceIoControl.IoControlCode, irpsp->Parameters.DeviceIoControl.InputBufferLength, irpsp->Parameters.DeviceIoControl.OutputBufferLength ); switch (irpsp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_NSIPROXY_WINE_ENUMERATE_ALL: status = nsiproxy_enumerate_all( irp ); break; case IOCTL_NSIPROXY_WINE_GET_ALL_PARAMETERS: status = nsiproxy_get_all_parameters( irp ); break; case IOCTL_NSIPROXY_WINE_GET_PARAMETER: status = nsiproxy_get_parameter( irp ); break; case IOCTL_NSIPROXY_WINE_ICMP_ECHO: status = nsiproxy_icmp_echo( irp ); break; default: FIXME( "ioctl %x not supported\n", irpsp->Parameters.DeviceIoControl.IoControlCode ); status = STATUS_NOT_SUPPORTED; break; } if (status != STATUS_PENDING) { irp->IoStatus.Status = status; IoCompleteRequest( irp, IO_NO_INCREMENT ); } return status; } static int add_device( DRIVER_OBJECT *driver ) { UNICODE_STRING name, link; DEVICE_OBJECT *device; NTSTATUS status; RtlInitUnicodeString( &name, L"\\Device\\Nsi" ); RtlInitUnicodeString( &link, L"\\??\\Nsi" ); if (!(status = IoCreateDevice( driver, 0, &name, FILE_DEVICE_NETWORK, FILE_DEVICE_SECURE_OPEN, FALSE, &device ))) status = IoCreateSymbolicLink( &link, &name ); if (status) { FIXME( "failed to create device error %x\n", status ); return 0; } return 1; } static DWORD WINAPI listen_thread_proc( void *arg ) { IRP *irp = arg; IO_STACK_LOCATION *irpsp = IoGetCurrentIrpStackLocation( irp ); struct nsiproxy_icmp_echo *in = irp->AssociatedIrp.SystemBuffer; struct icmp_listen_params params; NTSTATUS status; TRACE( "\n" ); params.handle = irp_get_icmp_handle( irp ); params.timeout = in->timeout; params.reply = irp->AssociatedIrp.SystemBuffer; params.reply_len = irpsp->Parameters.DeviceIoControl.OutputBufferLength; status = nsiproxy_call( icmp_listen, ¶ms ); TRACE( "icmp_listen rets %08x\n", status ); EnterCriticalSection( &nsiproxy_cs ); nsiproxy_call( icmp_close, irp_set_icmp_handle( irp, NULL ) ); irp->IoStatus.Status = status; if (status == STATUS_SUCCESS) irp->IoStatus.Information = params.reply_len; else irp->IoStatus.Information = 0; IoCompleteRequest( irp, IO_NO_INCREMENT ); LeaveCriticalSection( &nsiproxy_cs ); return 0; } static void handle_queued_send_echo( IRP *irp ) { struct nsiproxy_icmp_echo *in = (struct nsiproxy_icmp_echo *)irp->AssociatedIrp.SystemBuffer; struct nsiproxy_icmp_echo_reply *reply = (struct nsiproxy_icmp_echo_reply *)irp->AssociatedIrp.SystemBuffer; struct icmp_send_echo_params params; NTSTATUS status; TRACE( "\n" ); params.request = in->data + ((in->opt_size + 3) & ~3); params.request_size = in->req_size; params.ttl = in->ttl; params.tos = in->tos; params.dst = &in->dst; status = nsiproxy_call( icmp_send_echo, ¶ms ); TRACE( "icmp_send_echo rets %08x\n", status ); if (status != STATUS_PENDING) { irp->IoStatus.Status = status; if (status == STATUS_SUCCESS) { memset( reply, 0, sizeof(*reply) ); reply->status = params.ip_status; irp->IoStatus.Information = sizeof(*reply); } IoCompleteRequest( irp, IO_NO_INCREMENT ); } else { irp_set_icmp_handle( irp, params.handle ); RtlQueueWorkItem( listen_thread_proc, irp, WT_EXECUTELONGFUNCTION ); } } static DWORD WINAPI request_thread_proc( void *arg ) { LIST_ENTRY *entry; while (WaitForSingleObject( request_event, INFINITE ) == WAIT_OBJECT_0) { TRACE( "request_event triggered\n" ); EnterCriticalSection( &nsiproxy_cs ); while ((entry = RemoveHeadList( &request_queue )) != &request_queue ) { IRP *irp = CONTAINING_RECORD( entry, IRP, Tail.Overlay.ListEntry ); if (irp->Cancel) { irp->IoStatus.Status = STATUS_CANCELLED; TRACE( "already cancelled\n" ); IoCompleteRequest( irp, IO_NO_INCREMENT ); continue; } handle_queued_send_echo( irp ); } LeaveCriticalSection( &nsiproxy_cs ); } return 0; } NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) { HMODULE instance; NTSTATUS status; HANDLE thread; TRACE( "(%p, %s)\n", driver, debugstr_w( path->Buffer ) ); RtlPcToFileHeader( &DriverEntry, (void *)&instance ); status = NtQueryVirtualMemory( GetCurrentProcess(), instance, MemoryWineUnixFuncs, &nsiproxy_handle, sizeof(nsiproxy_handle), NULL ); if (status) return status; driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = nsi_ioctl; add_device( driver ); request_event = CreateEventW( NULL, FALSE, FALSE, NULL ); thread = CreateThread( NULL, 0, request_thread_proc, NULL, 0, NULL ); CloseHandle( thread ); return STATUS_SUCCESS; }