diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index c57cc66..efc24ae 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ diff --git a/code/build/build.c b/code/build/build.c index a4c255f..0f57097 100644 --- a/code/build/build.c +++ b/code/build/build.c @@ -51,7 +51,7 @@ void create_layout() { "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", "obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", "obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread", - "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present" + "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke" }; for (size_t i = 0; i < NOB_ARRAY_LEN(dirs); i++) nob_mkdir_if_not_exists(dirs[i]); nob_mkdir_if_not_exists(PCI_BUS_DRIVER.obj_dir); @@ -96,7 +96,7 @@ bool clean_build_object_directories(void) { "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", "obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", "obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread", - "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present" + "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke" }; for (size_t i = 0; i < NOB_ARRAY_LEN(dirs); i++) { @@ -167,6 +167,7 @@ int main(int argc, char **argv) { "cl /nologo /c /Foobj\\libc.obj code\\common\\libc.c", nob_temp_sprintf("cl %s /c /Foobj\\runtime\\user_heap.obj code\\user\\runtime\\heap.c", CFLAGS), nob_temp_sprintf("cl %s /c /Foobj\\runtime\\user_heap_exports.obj code\\user\\runtime\\heap_exports.c", CFLAGS), + nob_temp_sprintf("cl %s /c /Foobj\\runtime\\posix_start.obj code\\user\\runtime\\posix_start.c", CFLAGS), // Bootloader. nob_temp_sprintf("cl %s /MP /c /Icode\\boot\\uefi\\include /Foobj\\boot\\ code\\boot\\uefi\\*.c && link %s /FORCE:MULTIPLE /SUBSYSTEM:EFI_APPLICATION /ENTRY:UefiMain /OUT:bin\\bootx64.efi obj\\libc.obj obj\\ini_parser.obj code\\boot\\uefi\\efilib.lib obj\\boot\\*.obj", CFLAGS, LFLAGS), @@ -233,7 +234,7 @@ int main(int argc, char **argv) { // Folder, Libs, Name, Optional Extra Link Args. {"system\\init", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "init", "/ENTRY:_start"}, {"apps\\shell_win32", "bin\\win32.lib obj\\libc.obj", "shell_win32", "/ENTRY:main"}, - {"apps\\shell_posix", "bin\\posix.lib obj\\libc.obj", "shell_posix", "/ENTRY:main"}, + {"apps\\shell_posix", "bin\\posix.lib obj\\libc.obj obj\\runtime\\posix_start.obj", "shell_posix", "/ENTRY:PosixStart"}, {"apps\\shell_native", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "shell_native", "/ENTRY:main"}, {"tests\\async_apc", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "async_apc", "/ENTRY:main"}, {"tests\\apc_return_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "apc_return_smoke", "/ENTRY:main"}, @@ -248,6 +249,9 @@ int main(int argc, char **argv) { {"tests\\file_lock_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "file_lock_smoke", "/ENTRY:main"}, {"tests\\process_protection_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "process_protection_smoke", "/ENTRY:main"}, {"tests\\socket_addr_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "socket_addr_smoke", "/ENTRY:main"}, + {"tests\\net_irp_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "net_irp_smoke", "/ENTRY:main"}, + {"tests\\module_load_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "module_load_smoke", "/ENTRY:main"}, + {"tests\\posix_module_smoke", "bin\\posix.lib obj\\libc.obj obj\\runtime\\posix_start.obj", "posix_module_smoke", "/ENTRY:PosixStart"}, {"tests\\syscall_contract_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "syscall_contract_smoke", "/ENTRY:main"}, {"tests\\would_block_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "would_block_smoke", "/ENTRY:main"}, {"tests\\cwd_peb_smoke", "bin\\syscall.lib obj\\libc.obj obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj", "cwd_peb_smoke", "/ENTRY:main"}, @@ -343,6 +347,9 @@ int main(int argc, char **argv) { {"bin\\file_lock_smoke.exe", "Y:\\apps\\file_lock_smoke.exe"}, {"bin\\process_protection_smoke.exe", "Y:\\apps\\process_protection_smoke.exe"}, {"bin\\socket_addr_smoke.exe", "Y:\\apps\\socket_addr_smoke.exe"}, + {"bin\\net_irp_smoke.exe", "Y:\\apps\\net_irp_smoke.exe"}, + {"bin\\module_load_smoke.exe", "Y:\\apps\\module_load_smoke.exe"}, + {"bin\\posix_module_smoke.exe", "Y:\\apps\\posix_module_smoke.exe"}, {"bin\\syscall_contract_smoke.exe", "Y:\\apps\\syscall_contract_smoke.exe"}, {"bin\\would_block_smoke.exe", "Y:\\apps\\would_block_smoke.exe"}, {"bin\\cwd_peb_smoke.exe", "Y:\\apps\\cwd_peb_smoke.exe"}, diff --git a/code/drivers/net/lwip/lwip_stack.c b/code/drivers/net/lwip/lwip_stack.c index 7ba4ee4..d9d7617 100644 --- a/code/drivers/net/lwip/lwip_stack.c +++ b/code/drivers/net/lwip/lwip_stack.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #define LWIP_PROVIDER_SOCKET_IO_MAX_BYTES (1024u * 1024u) #define LWIP_PROVIDER_SOCKET_UDP_MAX_BYTES 65507u #define LWIP_PROVIDER_SOCKET_RAW_MAX_BYTES 65535u +#define LWIP_PROVIDER_CANCEL_POLL_MS 25u #define LWIP_SETTINGS_DEFAULT_SECTION "network.default" #define LWIP_SETTINGS_SECTION_PREFIX "network." @@ -105,38 +107,28 @@ static KSTATUS LwipProviderSocketCreate(uint32_t domain, uint32_t type, uint32_t static void LwipProviderSocketClose(void* socket); static KSTATUS LwipProviderBind(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); static KSTATUS LwipProviderListen(void* socket, uint32_t backlog); -static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut); -static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); +static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut, volatile uint8_t* cancelRequested); +static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize, volatile uint8_t* cancelRequested); static KSTATUS LwipProviderSend(void* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); -static KSTATUS LwipProviderReceive(void* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); +static KSTATUS LwipProviderReceive(void* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut, volatile uint8_t* cancelRequested); static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t length, uint32_t flags, const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut); -static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut); +static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut, volatile uint8_t* cancelRequested); static KSTATUS LwipProviderShutdown(void* socket, uint32_t how); static KSTATUS LwipProviderSetOption(void* socket, uint32_t option, const void* value, size_t valueSize); static KSTATUS LwipProviderGetOption(void* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut); static KSTATUS LwipProviderPoll(void* socket, uint32_t events, uint32_t* returnedEventsOut); static KSTATUS LwipProviderResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut); +static KSTATUS LwipDispatchInternalDeviceControl(DEVICE_OBJECT* device, IRP* irp); -static const NET_STACK_PROVIDER gLwipProvider = { +static DRIVER_OBJECT gLwipDriver; +static DEVICE_OBJECT* gLwipDevice; + +static NET_STACK_PROVIDER gLwipProvider = { NET_STACK_PROVIDER_VERSION, "lwIP", + NULL, LwipProviderAttachDevice, LwipProviderReceiveFrame, - LwipProviderSocketCreate, - LwipProviderSocketClose, - LwipProviderBind, - LwipProviderListen, - LwipProviderAccept, - LwipProviderConnect, - LwipProviderSend, - LwipProviderReceive, - LwipProviderSendTo, - LwipProviderReceiveFrom, - LwipProviderShutdown, - LwipProviderSetOption, - LwipProviderGetOption, - LwipProviderPoll, - LwipProviderResolveHost, }; // Implements the err To Status operation. @@ -377,10 +369,18 @@ static void LwipSocketCallback(struct netconn* conn, enum netconn_evt evt, u16_t if (!conn) return; - LWIP_PROVIDER_SOCKET* socket = (LWIP_PROVIDER_SOCKET*)netconn_get_callback_arg(conn); - if (!socket) + void* callbackArg = netconn_get_callback_arg(conn); + uintptr_t rawArg = (uintptr_t)callbackArg; + if (!callbackArg) return; + // Accepted netconns inherit the listener callback before the wrapper can + // replace lwIP's internal negative socket sentinel with our socket object. + if (rawArg >= 0x80000000ull && rawArg <= 0xffffffffull) + return; + + LWIP_PROVIDER_SOCKET* socket = (LWIP_PROVIDER_SOCKET*)callbackArg; + switch (evt) { case NETCONN_EVT_RCVPLUS: socket->PollReadEvents++; @@ -426,12 +426,118 @@ static KSTATUS LwipSocketAllocateWithConn(uint32_t domain, uint32_t type, uint32 s->Type = type; s->Protocol = protocol; netconn_set_callback_arg(conn, s); + conn->callback = LwipSocketCallback; netconn_set_recvtimeout(conn, 0); netconn_set_sendtimeout(conn, 0); *out = s; return STATUS_SUCCESS; } + +// Returns whether the IRP cancel token has been set by SysCancelIo. +static int LwipSocketCancelRequested(volatile uint8_t* cancelRequested) { + return cancelRequested && *cancelRequested; +} + +// Computes the bounded wait slice used by cancel-aware receive and accept loops. +static uint32_t LwipSocketCancelWaitSlice(uint32_t remainingMs) { + if (remainingMs != 0 && remainingMs < LWIP_PROVIDER_CANCEL_POLL_MS) + return remainingMs; + return LWIP_PROVIDER_CANCEL_POLL_MS; +} + +// Waits for a receive-style netconn mailbox while periodically observing cancellation. +static KSTATUS LwipSocketWaitRecvNetbuf(LWIP_PROVIDER_SOCKET* s, + struct netbuf** bufferOut, + volatile uint8_t* cancelRequested, + err_t* errOut) { + if (!s || !s->Conn || !bufferOut || !errOut) + return STATUS_INVALID_PARAMETER; + *bufferOut = NULL; + *errOut = ERR_OK; + + if (!cancelRequested || s->NonBlocking) { + *errOut = netconn_recv(s->Conn, bufferOut); + return STATUS_SUCCESS; + } + + uint32_t originalTimeout = s->ReceiveTimeoutMs; + uint32_t elapsed = 0; + for (;;) { + if (LwipSocketCancelRequested(cancelRequested)) { + netconn_set_recvtimeout(s->Conn, originalTimeout); + return STATUS_CANCELLED; + } + + uint32_t remaining = 0; + if (originalTimeout != 0) { + if (elapsed >= originalTimeout) { + netconn_set_recvtimeout(s->Conn, originalTimeout); + *errOut = ERR_TIMEOUT; + return STATUS_SUCCESS; + } + remaining = originalTimeout - elapsed; + } + + uint32_t slice = LwipSocketCancelWaitSlice(remaining); + netconn_set_recvtimeout(s->Conn, slice); + err_t err = netconn_recv(s->Conn, bufferOut); + if (err != ERR_TIMEOUT) { + netconn_set_recvtimeout(s->Conn, originalTimeout); + *errOut = err; + return STATUS_SUCCESS; + } + + elapsed += slice; + } +} + +// Waits for accept while preserving the caller's receive timeout model. +static KSTATUS LwipSocketWaitAccept(LWIP_PROVIDER_SOCKET* s, + struct netconn** acceptedOut, + volatile uint8_t* cancelRequested, + err_t* errOut) { + if (!s || !s->Conn || !acceptedOut || !errOut) + return STATUS_INVALID_PARAMETER; + *acceptedOut = NULL; + *errOut = ERR_OK; + + if (!cancelRequested || s->NonBlocking) { + *errOut = netconn_accept(s->Conn, acceptedOut); + return STATUS_SUCCESS; + } + + uint32_t originalTimeout = s->ReceiveTimeoutMs; + uint32_t elapsed = 0; + for (;;) { + if (LwipSocketCancelRequested(cancelRequested)) { + netconn_set_recvtimeout(s->Conn, originalTimeout); + return STATUS_CANCELLED; + } + + uint32_t remaining = 0; + if (originalTimeout != 0) { + if (elapsed >= originalTimeout) { + netconn_set_recvtimeout(s->Conn, originalTimeout); + *errOut = ERR_TIMEOUT; + return STATUS_SUCCESS; + } + remaining = originalTimeout - elapsed; + } + + uint32_t slice = LwipSocketCancelWaitSlice(remaining); + netconn_set_recvtimeout(s->Conn, slice); + err_t err = netconn_accept(s->Conn, acceptedOut); + if (err != ERR_TIMEOUT) { + netconn_set_recvtimeout(s->Conn, originalTimeout); + *errOut = err; + return STATUS_SUCCESS; + } + + elapsed += slice; + } +} + static int LwipSocketAddressValid(const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { if (!address || addressSize < sizeof(SYS_SOCKET_ADDRESS_K)) return 0; @@ -798,7 +904,7 @@ static KSTATUS LwipProviderSocketCreate(uint32_t domain, uint32_t type, uint32_t return STATUS_NOT_SUPPORTED; } - struct netconn* conn = netconn_new_with_proto_and_callback(netconnType, rawProtocol, LwipSocketCallback); + struct netconn* conn = netconn_new_with_proto_and_callback(netconnType, rawProtocol, NULL); if (!conn) return STATUS_NO_MEMORY; @@ -905,7 +1011,7 @@ static KSTATUS LwipProviderListen(void* socket, uint32_t backlog) { } // Implements the provider Accept operation. -static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut) { +static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut, volatile uint8_t* cancelRequested) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (!s || !acceptedOut) return STATUS_INVALID_PARAMETER; @@ -926,16 +1032,17 @@ static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_A } struct netconn* acceptedConn = NULL; - err_t err = netconn_accept(s->Conn, &acceptedConn); + err_t err = ERR_OK; + st = LwipSocketWaitAccept(s, &acceptedConn, cancelRequested, &err); LwipSocketUnlock(s); + if (!KSUCCESS(st)) + return st; if (err != ERR_OK) return LwipErrToStatus(err); if (!acceptedConn) return STATUS_FAILURE; - acceptedConn->callback = LwipSocketCallback; - LWIP_PROVIDER_SOCKET* accepted = NULL; st = LwipSocketAllocateWithConn(s->Domain, s->Type, s->Protocol, acceptedConn, &accepted); if (!KSUCCESS(st)) { @@ -963,7 +1070,7 @@ static KSTATUS LwipProviderAccept(void* socket, void** acceptedOut, SYS_SOCKET_A } // Implements the provider Connect operation. -static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { +static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize, volatile uint8_t* cancelRequested) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (!s || !address) return STATUS_INVALID_PARAMETER; @@ -994,12 +1101,58 @@ static KSTATUS LwipProviderConnect(void* socket, const SYS_SOCKET_ADDRESS_K* add return st; } - err_t err = netconn_connect(s->Conn, &ip, address->Port); - if (err == ERR_OK) - s->Connected = 1; + if (!cancelRequested || s->Type != SYS_SOCK_STREAM) { + err_t err = netconn_connect(s->Conn, &ip, address->Port); + if (err == ERR_OK) + s->Connected = 1; - LwipSocketUnlock(s); - return LwipErrToStatus(err); + LwipSocketUnlock(s); + return LwipErrToStatus(err); + } + + u8_t wasNonBlocking = netconn_is_nonblocking(s->Conn) ? 1 : 0; + netconn_set_nonblocking(s->Conn, 1); + err_t err = netconn_connect(s->Conn, &ip, address->Port); + if (err == ERR_OK) { + netconn_set_nonblocking(s->Conn, wasNonBlocking || s->NonBlocking); + s->Connected = 1; + LwipSocketUnlock(s); + return STATUS_SUCCESS; + } + if (err != ERR_INPROGRESS) { + netconn_set_nonblocking(s->Conn, wasNonBlocking || s->NonBlocking); + LwipSocketUnlock(s); + return LwipErrToStatus(err); + } + + for (;;) { + if (LwipSocketCancelRequested(cancelRequested)) { + (void)netconn_close(s->Conn); + s->ShutdownRead = 1; + s->ShutdownWrite = 1; + netconn_set_nonblocking(s->Conn, wasNonBlocking || s->NonBlocking); + LwipSocketUnlock(s); + return STATUS_CANCELLED; + } + + if (s->Conn->state != NETCONN_CONNECT) { + err = netconn_err(s->Conn); + netconn_set_nonblocking(s->Conn, wasNonBlocking || s->NonBlocking); + if (err == ERR_OK) { + s->Connected = 1; + LwipSocketUnlock(s); + return STATUS_SUCCESS; + } + LwipSocketUnlock(s); + return LwipErrToStatus(err); + } + + LwipSocketUnlock(s); + (void)KeDelayThread(LWIP_PROVIDER_CANCEL_POLL_MS); + st = LwipSocketLock(s); + if (!KSUCCESS(st)) + return st; + } } // Implements the provider Send operation. @@ -1087,7 +1240,7 @@ static KSTATUS LwipReceiveFromBuffered(LWIP_PROVIDER_SOCKET* s, uint8_t* out, } // Implements the provider Receive operation. -static KSTATUS LwipProviderReceive(void* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut) { +static KSTATUS LwipProviderReceive(void* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut, volatile uint8_t* cancelRequested) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (receivedOut) *receivedOut = 0; @@ -1124,7 +1277,12 @@ static KSTATUS LwipProviderReceive(void* socket, void* buffer, size_t length, ui if (copied == 0 && !s->ReceiveBuffer) { struct netbuf* nb = NULL; - err_t err = netconn_recv(s->Conn, &nb); + err_t err = ERR_OK; + st = LwipSocketWaitRecvNetbuf(s, &nb, cancelRequested, &err); + if (!KSUCCESS(st)) { + LwipSocketUnlock(s); + return st; + } if (err == ERR_CLSD) { s->ShutdownRead = 1; if (receivedOut) @@ -1209,7 +1367,7 @@ static KSTATUS LwipProviderSendTo(void* socket, const void* buffer, size_t lengt } static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length, uint32_t flags, - SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut) { + SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut, volatile uint8_t* cancelRequested) { LWIP_PROVIDER_SOCKET* s = (LWIP_PROVIDER_SOCKET*)socket; if (receivedOut) *receivedOut = 0; @@ -1236,7 +1394,12 @@ static KSTATUS LwipProviderReceiveFrom(void* socket, void* buffer, size_t length } struct netbuf* nb = NULL; - err_t err = netconn_recv(s->Conn, &nb); + err_t err = ERR_OK; + st = LwipSocketWaitRecvNetbuf(s, &nb, cancelRequested, &err); + if (!KSUCCESS(st)) { + LwipSocketUnlock(s); + return st; + } if (err != ERR_OK) { LwipSocketUnlock(s); return LwipErrToStatus(err); @@ -1449,6 +1612,109 @@ static KSTATUS LwipProviderResolveHost(const char* name, uint32_t family, SYS_SO return STATUS_SUCCESS; } + +// Dispatches a provider-private socket request carried by an IRP. +static KSTATUS LwipDispatchSocketRequest(NET_SOCKET_IRP_REQUEST* request) { + if (!request || request->Size != sizeof(*request)) + return STATUS_INVALID_PARAMETER; + + switch (request->Operation) { + case NetSocketIrpCreate: + return LwipProviderSocketCreate(request->u.Create.Domain, + request->u.Create.Type, + request->u.Create.Protocol, + request->u.Create.ProviderSocketOut); + case NetSocketIrpClose: + LwipProviderSocketClose(request->u.Close.Socket); + return STATUS_SUCCESS; + case NetSocketIrpBind: + return LwipProviderBind(request->u.Bind.Socket, + request->u.Bind.Address, + request->u.Bind.AddressSize); + case NetSocketIrpListen: + return LwipProviderListen(request->u.Listen.Socket, + request->u.Listen.Backlog); + case NetSocketIrpAccept: + return LwipProviderAccept(request->u.Accept.Socket, + request->u.Accept.AcceptedOut, + request->u.Accept.RemoteOut, + request->CancelRequested); + case NetSocketIrpConnect: + return LwipProviderConnect(request->u.Connect.Socket, + request->u.Connect.Address, + request->u.Connect.AddressSize, + request->CancelRequested); + case NetSocketIrpSend: + return LwipProviderSend(request->u.Send.Socket, + request->u.Send.Buffer, + request->u.Send.Length, + request->u.Send.Flags, + request->u.Send.SentOut); + case NetSocketIrpReceive: + return LwipProviderReceive(request->u.Receive.Socket, + request->u.Receive.Buffer, + request->u.Receive.Length, + request->u.Receive.Flags, + request->u.Receive.ReceivedOut, + request->CancelRequested); + case NetSocketIrpSendTo: + return LwipProviderSendTo(request->u.SendTo.Socket, + request->u.SendTo.Buffer, + request->u.SendTo.Length, + request->u.SendTo.Flags, + request->u.SendTo.Remote, + request->u.SendTo.RemoteSize, + request->u.SendTo.SentOut); + case NetSocketIrpReceiveFrom: + return LwipProviderReceiveFrom(request->u.ReceiveFrom.Socket, + request->u.ReceiveFrom.Buffer, + request->u.ReceiveFrom.Length, + request->u.ReceiveFrom.Flags, + request->u.ReceiveFrom.RemoteOut, + request->u.ReceiveFrom.ReceivedOut, + request->CancelRequested); + case NetSocketIrpShutdown: + return LwipProviderShutdown(request->u.Shutdown.Socket, + request->u.Shutdown.How); + case NetSocketIrpSetOption: + return LwipProviderSetOption(request->u.SetOption.Socket, + request->u.SetOption.Option, + request->u.SetOption.Value, + request->u.SetOption.ValueSize); + case NetSocketIrpGetOption: + return LwipProviderGetOption(request->u.GetOption.Socket, + request->u.GetOption.Option, + request->u.GetOption.Value, + request->u.GetOption.ValueSize, + request->u.GetOption.ActualSizeOut); + case NetSocketIrpPoll: + return LwipProviderPoll(request->u.Poll.Socket, + request->u.Poll.Events, + request->u.Poll.ReturnedEventsOut); + case NetSocketIrpResolveHost: + return LwipProviderResolveHost(request->u.ResolveHost.Name, + request->u.ResolveHost.Family, + request->u.ResolveHost.AddressOut); + default: + return STATUS_INVALID_PARAMETER; + } +} + +// Dispatches socket internal device controls for the lwIP stack device. +static KSTATUS LwipDispatchInternalDeviceControl(DEVICE_OBJECT* device, IRP* irp) { + (void)device; + IO_STACK_LOCATION* ios = IoGetCurrentIrpStackLocation(irp); + if (!ios) + return STATUS_INVALID_PARAMETER; + if (ios->Parameters.DeviceIoControl.IoControlCode != NET_IOCTL_SOCKET) + return STATUS_NOT_SUPPORTED; + if (ios->Parameters.DeviceIoControl.InputLength < sizeof(NET_SOCKET_IRP_REQUEST)) + return STATUS_BUFFER_TOO_SMALL; + + NET_SOCKET_IRP_REQUEST* request = (NET_SOCKET_IRP_REQUEST*)ios->Parameters.DeviceIoControl.InputBuffer; + return LwipDispatchSocketRequest(request); +} + // Implements the driver Entry operation. KSTATUS DriverEntry(const HAL_API* hal) { DbgPrint("lwIP: driver loading...\n"); @@ -1467,6 +1733,23 @@ KSTATUS DriverEntry(const HAL_API* hal) { return st; } + IoInitializeDriverObject(&gLwipDriver, SYS_SV_LITERAL("lwip-stack")); + gLwipDriver.MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = LwipDispatchInternalDeviceControl; + + st = IoRegisterDriverObject(&gLwipDriver); + if (!KSUCCESS(st)) { + DbgPrint("lwIP: driver object registration failed: 0x%x\n", st); + return st; + } + + st = IoCreateDevice(&gLwipDriver, 0, &gLwipDevice); + if (!KSUCCESS(st)) { + DbgPrint("lwIP: stack device creation failed: 0x%x\n", st); + return st; + } + (void)IoSetDeviceName(gLwipDevice, SYS_SV_LITERAL("lwip-stack")); + gLwipProvider.StackDevice = gLwipDevice; + st = NetRegisterStackProvider(&gLwipProvider); if (!KSUCCESS(st)) { DbgPrint("lwIP: provider registration failed: 0x%x\n", st); diff --git a/code/kernel/include/kernel/io_async.h b/code/kernel/include/kernel/io_async.h new file mode 100644 index 0000000..d484026 --- /dev/null +++ b/code/kernel/include/kernel/io_async.h @@ -0,0 +1,88 @@ +// Kernel async I/O request declarations shared by implementation files and kernel clients. + +#pragma once +// io_async.h - waitable asynchronous I/O request objects. +// Async request objects own the common completion, APC, wait, query, and +// cancellation surface used by file and socket I/O. + +#include +#include +#include +#include +#include + +struct EVENT; +struct KAPC; +struct IRP; +typedef struct MM_LOCKED_USER_BUFFER MM_LOCKED_USER_BUFFER; + +// Completion status visible to async callers. +typedef struct IO_STATUS_BLOCK { + volatile KSTATUS Status; + volatile size_t Information; +} IO_STATUS_BLOCK; + +typedef enum IO_ASYNC_REQUEST_STATE { + IoAsyncRequestQueued = 0, + IoAsyncRequestRunning = 1, + IoAsyncRequestCompleted = 2, + IoAsyncRequestCancelled = 3, +} IO_ASYNC_REQUEST_STATE; + +typedef struct IO_ASYNC_REQUEST_OBJECT IO_ASYNC_REQUEST_OBJECT; + +typedef void (*IO_ASYNC_REQUEST_CLEANUP_ROUTINE)(IO_ASYNC_REQUEST_OBJECT* request, + void* context); +typedef KSTATUS (*IO_ASYNC_REQUEST_CANCEL_ROUTINE)(IO_ASYNC_REQUEST_OBJECT* request, + void* context); + +// Common waitable async I/O request object. +struct IO_ASYNC_REQUEST_OBJECT { + OBJECT Header; + LIST_ENTRY Link; + + IO_ASYNC_REQUEST_STATE State; + uint8_t CancelRequested; + + struct EVENT* WaitEvent; + struct EVENT* CompletionEvent; + struct KAPC* CompletionApc; + IO_STATUS_BLOCK* Iosb; + IO_STATUS_BLOCK IosbSnapshot; + MM_LOCKED_USER_BUFFER* LockedBuffer; + MM_LOCKED_USER_BUFFER* LockedIosb; + struct IRP* Irp; + + IO_ASYNC_REQUEST_CLEANUP_ROUTINE CleanupRoutine; + void* CleanupContext; + IO_ASYNC_REQUEST_CANCEL_ROUTINE CancelRoutine; + void* CancelContext; +}; + +__declspec(dllexport) KSTATUS IoCreateAsyncRequest(size_t objectSize, + IO_ASYNC_REQUEST_CLEANUP_ROUTINE cleanupRoutine, + void* cleanupContext, + IO_ASYNC_REQUEST_CANCEL_ROUTINE cancelRoutine, + void* cancelContext, + IO_ASYNC_REQUEST_OBJECT** requestOut); +__declspec(dllexport) OBJECT* IoAsyncRequestObject(IO_ASYNC_REQUEST_OBJECT* request); +__declspec(dllexport) void IoInitializeAsyncRequestCompletion(IO_ASYNC_REQUEST_OBJECT* request, + IO_STATUS_BLOCK* iosb, + struct EVENT* completionEvent, + struct KAPC* completionApc, + MM_LOCKED_USER_BUFFER* lockedBuffer, + MM_LOCKED_USER_BUFFER* lockedIosb); +__declspec(dllexport) void IoAsyncRequestSetState(IO_ASYNC_REQUEST_OBJECT* request, + IO_ASYNC_REQUEST_STATE state); +__declspec(dllexport) IO_ASYNC_REQUEST_STATE IoAsyncRequestGetState(IO_ASYNC_REQUEST_OBJECT* request); +__declspec(dllexport) void IoAsyncRequestRequestCancel(IO_ASYNC_REQUEST_OBJECT* request); +__declspec(dllexport) uint8_t IoAsyncRequestIsCancelRequested(IO_ASYNC_REQUEST_OBJECT* request); +__declspec(dllexport) struct EVENT* IoGetAsyncRequestWaitEvent(IO_ASYNC_REQUEST_OBJECT* request); +__declspec(dllexport) KSTATUS IoWaitAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request, + uint32_t flags); +__declspec(dllexport) KSTATUS IoCancelAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request); +__declspec(dllexport) KSTATUS IoCompleteAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request, + KSTATUS status, + size_t information); +__declspec(dllexport) KSTATUS IoQueryAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request, + IO_STATUS_BLOCK* iosbOut); diff --git a/code/kernel/include/kernel/io_file.h b/code/kernel/include/kernel/io_file.h index c57f8ba..0cd58b2 100644 --- a/code/kernel/include/kernel/io_file.h +++ b/code/kernel/include/kernel/io_file.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -137,12 +138,6 @@ typedef struct IO_FILESYSTEM { void (*CloseDir)(struct IO_FILESYSTEM* fs, void* dirCtx); } IO_FILESYSTEM; -// IO_STATUS_BLOCK - completion status visible to async callers. -typedef struct IO_STATUS_BLOCK { - volatile KSTATUS Status; - volatile size_t Information; -} IO_STATUS_BLOCK; - struct EVENT; struct KAPC; typedef struct MM_LOCKED_USER_BUFFER MM_LOCKED_USER_BUFFER; diff --git a/code/kernel/include/kernel/ke_process.h b/code/kernel/include/kernel/ke_process.h index 11db8ae..88a69b4 100644 --- a/code/kernel/include/kernel/ke_process.h +++ b/code/kernel/include/kernel/ke_process.h @@ -14,8 +14,9 @@ #define PROCESS_NAME_MAX 64 #define HANDLE_TABLE_SIZE 256 -#define PROCESS_MODULE_MAX 16 -#define PROCESS_CWD_MAX 512 +#define PROCESS_MODULE_MAX 16 +#define PROCESS_MODULE_NAME_MAX 64 +#define PROCESS_CWD_MAX 512 #define KE_PROCESS_FLAG_SYSTEM 0x00000001u #define KE_PROCESS_FLAG_IDLE 0x00000002u @@ -38,9 +39,13 @@ typedef struct HANDLE_ENTRY { } HANDLE_ENTRY; typedef struct PROCESS_MODULE { - char Name[32]; + char Name[PROCESS_MODULE_NAME_MAX]; uintptr_t Base; size_t Size; + uintptr_t EntryPoint; + uint32_t Flags; + uint32_t PublicRefCount; + uint32_t ImportRefCount; } PROCESS_MODULE; typedef struct PROCESS { @@ -93,6 +98,7 @@ typedef struct PROCESS { // Per-process loaded modules (user processes only). PROCESS_MODULE Modules[PROCESS_MODULE_MAX]; uint32_t ModuleCount; + SPINLOCK ModuleLock; // Signaled (manual-reset) when the process exits. // Waiters block on this via KeWaitForEvent. diff --git a/code/kernel/include/kernel/ldr_image.h b/code/kernel/include/kernel/ldr_image.h index a2a46e0..402032b 100644 --- a/code/kernel/include/kernel/ldr_image.h +++ b/code/kernel/include/kernel/ldr_image.h @@ -10,6 +10,7 @@ struct PROCESS; struct FILE_OBJECT; +struct SYS_MODULE_INFO; // Map a PE32+ image from a bootloader/driver raw buffer into kernel space. // User images must be loaded from FILE_OBJECTs so image sections have stable identity. @@ -38,7 +39,14 @@ KSTATUS LdrProtectImageForProcess(struct PROCESS* process, uintptr_t imageBase, // Load a DLL for a specific process, resolve it against that process's // loaded modules, and register it in the process-local registry. -KSTATUS LdrLoadModuleForProcess(struct PROCESS* process, const char* filename, uintptr_t* baseOut); +#define LDR_MODULE_LOAD_IMPORT 0x00000001u +#define LDR_MODULE_LOAD_PUBLIC 0x00000002u + +KSTATUS LdrLoadModuleForProcess(struct PROCESS* process, const char* filename, uint32_t loadFlags, uintptr_t* baseOut); +KSTATUS LdrUnloadModuleForProcess(struct PROCESS* process, uintptr_t imageBase); +KSTATUS LdrQueryModuleForProcess(struct PROCESS* process, uintptr_t imageBase, struct SYS_MODULE_INFO* infoOut); +KSTATUS LdrCopyProcessModuleInfo(struct PROCESS* process, struct SYS_MODULE_INFO* infoOut, size_t maxCount, size_t* countOut); +void* LdrGetModuleExportForProcess(struct PROCESS* process, uintptr_t imageBase, const char* functionName); // Called by KernelMain to register all modules from BOOT_INFO so that // LdrGetExport can find them. diff --git a/code/kernel/include/kernel/net_socket.h b/code/kernel/include/kernel/net_socket.h index 1bd3aa4..81e2728 100644 --- a/code/kernel/include/kernel/net_socket.h +++ b/code/kernel/include/kernel/net_socket.h @@ -11,8 +11,13 @@ #include #include #include +#include struct NET_STACK_PROVIDER; +struct EVENT; +struct KAPC; +struct PROCESS; +typedef struct MM_LOCKED_USER_BUFFER MM_LOCKED_USER_BUFFER; typedef struct SOCKET_OBJECT { OBJECT Header; // must be first @@ -25,17 +30,62 @@ typedef struct SOCKET_OBJECT { uint32_t Protocol; } SOCKET_OBJECT; +typedef struct NET_ASYNC_SOCKET_SUBMIT { + struct PROCESS* OwnerProcess; + SOCKET_OBJECT* Socket; + uint32_t Operation; + uint32_t SocketFlags; + uint32_t Backlog; + uint32_t ShutdownHow; + uint32_t Option; + uint32_t PollEvents; + + void* KernelBuffer; + void* UserBuffer; + size_t Length; + + SYS_SOCKET_ADDRESS_K Address; + size_t AddressSize; + SYS_SOCKET_ADDRESS_K* AddressOut; + size_t* AddressSizeInOut; + + void* OptionValue; + void* UserOptionValue; + size_t OptionValueSize; + size_t* OptionActualSizeOut; + + uint32_t* HandleOut; + uint32_t* PollReturnedEventsOut; + + IO_STATUS_BLOCK* Iosb; + struct EVENT* CompletionEvent; + struct KAPC* CompletionApc; + MM_LOCKED_USER_BUFFER* LockedBuffer; + MM_LOCKED_USER_BUFFER* LockedIosb; + MM_LOCKED_USER_BUFFER* LockedAddressOut; + MM_LOCKED_USER_BUFFER* LockedAddressSize; + MM_LOCKED_USER_BUFFER* LockedOptionValue; + MM_LOCKED_USER_BUFFER* LockedOptionActualSize; + MM_LOCKED_USER_BUFFER* LockedHandleOut; + MM_LOCKED_USER_BUFFER* LockedPollReturnedEvents; +} NET_ASYNC_SOCKET_SUBMIT; + +typedef struct NET_ASYNC_SOCKET_REQUEST_OBJECT NET_ASYNC_SOCKET_REQUEST_OBJECT; + KSTATUS NetSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, SOCKET_OBJECT** out); KSTATUS NetSocketBind(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); KSTATUS NetSocketListen(SOCKET_OBJECT* socket, uint32_t backlog); -KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut); -KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); +KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut, IO_ASYNC_REQUEST_OBJECT* async); +KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize, IO_ASYNC_REQUEST_OBJECT* async); KSTATUS NetSocketSend(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); -KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); +KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut, IO_ASYNC_REQUEST_OBJECT* async); KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut); -KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut); +KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut, IO_ASYNC_REQUEST_OBJECT* async); KSTATUS NetSocketShutdown(SOCKET_OBJECT* socket, uint32_t how); KSTATUS NetSocketSetOption(SOCKET_OBJECT* socket, uint32_t option, const void* value, size_t valueSize); KSTATUS NetSocketGetOption(SOCKET_OBJECT* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut); KSTATUS NetSocketPoll(SOCKET_OBJECT* socket, uint32_t events, uint32_t* returnedEventsOut); KSTATUS NetResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut); +KSTATUS NetSocketSubmitAsyncKernel(const NET_ASYNC_SOCKET_SUBMIT* submit, + NET_ASYNC_SOCKET_REQUEST_OBJECT** requestOut); +OBJECT* NetAsyncSocketRequestObject(NET_ASYNC_SOCKET_REQUEST_OBJECT* request); diff --git a/code/kernel/include/kernel/ob_object.h b/code/kernel/include/kernel/ob_object.h index 7d76772..09b5e1e 100644 --- a/code/kernel/include/kernel/ob_object.h +++ b/code/kernel/include/kernel/ob_object.h @@ -28,6 +28,7 @@ typedef enum OBJECT_TYPE { ObjectTypeInputQueue = 17, ObjectTypeImageSection = 18, ObjectTypeTimer = 19, + ObjectTypeModule = 20, } OBJECT_TYPE; typedef struct OBJECT { diff --git a/code/kernel/include/kernel/syscall_nr.h b/code/kernel/include/kernel/syscall_nr.h index 02260a4..fb8e67c 100644 --- a/code/kernel/include/kernel/syscall_nr.h +++ b/code/kernel/include/kernel/syscall_nr.h @@ -7,7 +7,7 @@ // R10 and R11 carry args 4-5 and are loaded by six-argument stubs. // RAX carries the return value. -// Process, threads, waits, and APC. +// Process, threads, modules, waits, and APC. #define SYS_EXIT_PROCESS 0 #define SYS_EXIT_THREAD 1 #define SYS_CREATE_PROCESS 2 @@ -18,124 +18,130 @@ #define SYS_GET_PROCESS_PID 7 #define SYS_CREATE_THREAD 8 #define SYS_GET_THREAD_LIST 9 -#define SYS_GET_CURRENT_TID 10 -#define SYS_GET_CURRENT_PID 11 -#define SYS_SET_THREAD_PRIORITY 12 -#define SYS_YIELD 13 -#define SYS_SLEEP 14 -#define SYS_WAIT_SINGLE 15 -#define SYS_WAIT_MANY 16 -#define SYS_SET_USER_APC_DISPATCHER 17 -#define SYS_APC_RETURN 18 +#define SYS_LOAD_MODULE 10 +#define SYS_UNLOAD_MODULE 11 +#define SYS_GET_MODULE_INFO 12 +#define SYS_GET_MODULE_EXPORT 13 +#define SYS_GET_MODULE_LIST 14 +#define SYS_GET_CURRENT_TID 15 +#define SYS_GET_CURRENT_PID 16 +#define SYS_SET_THREAD_PRIORITY 17 +#define SYS_YIELD 18 +#define SYS_SLEEP 19 +#define SYS_WAIT_SINGLE 20 +#define SYS_WAIT_MANY 21 +#define SYS_SET_USER_APC_DISPATCHER 22 +#define SYS_APC_RETURN 23 // Virtual memory, mappings, and handles. -#define SYS_ALLOC_VM 19 -#define SYS_FREE_VM 20 -#define SYS_PROTECT_VM 21 -#define SYS_QUERY_VM 22 -#define SYS_CLOSE_HANDLE 23 -#define SYS_DUPLICATE_HANDLE 24 -#define SYS_CREATE_FILE_MAPPING 25 -#define SYS_MAP_FILE 26 +#define SYS_ALLOC_VM 24 +#define SYS_FREE_VM 25 +#define SYS_PROTECT_VM 26 +#define SYS_QUERY_VM 27 +#define SYS_CLOSE_HANDLE 28 +#define SYS_DUPLICATE_HANDLE 29 +#define SYS_CREATE_FILE_MAPPING 30 +#define SYS_MAP_FILE 31 // Synchronization objects. -#define SYS_CREATE_MUTEX 27 -#define SYS_TRY_ACQUIRE_MUTEX 28 -#define SYS_RELEASE_MUTEX 29 -#define SYS_CREATE_SEM 30 -#define SYS_TRY_WAIT_SEM 31 -#define SYS_SIGNAL_SEM 32 -#define SYS_CREATE_EVENT 33 -#define SYS_SIGNAL_EVENT 34 -#define SYS_CLEAR_EVENT 35 +#define SYS_CREATE_MUTEX 32 +#define SYS_TRY_ACQUIRE_MUTEX 33 +#define SYS_RELEASE_MUTEX 34 +#define SYS_CREATE_SEM 35 +#define SYS_TRY_WAIT_SEM 36 +#define SYS_SIGNAL_SEM 37 +#define SYS_CREATE_EVENT 38 +#define SYS_SIGNAL_EVENT 39 +#define SYS_CLEAR_EVENT 40 // Queues and named queues. -#define SYS_CREATE_QUEUE 36 -#define SYS_SEND_QUEUE 37 -#define SYS_RECV_QUEUE 38 -#define SYS_PUBLISH_QUEUE 39 -#define SYS_OPEN_NAMED_QUEUE 40 -#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 41 +#define SYS_CREATE_QUEUE 41 +#define SYS_SEND_QUEUE 42 +#define SYS_RECV_QUEUE 43 +#define SYS_PUBLISH_QUEUE 44 +#define SYS_OPEN_NAMED_QUEUE 45 +#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 46 // Files, directories, and current directory. -#define SYS_OPEN_FILE 42 -#define SYS_READ_FILE 43 -#define SYS_WRITE_FILE 44 -#define SYS_READ_FILE_ASYNC 45 -#define SYS_WRITE_FILE_ASYNC 46 -#define SYS_CANCEL_IO 47 -#define SYS_SEEK_FILE 48 -#define SYS_GET_FILE_POS 49 -#define SYS_GET_FILE_SIZE 50 -#define SYS_GET_FILE_STAT 51 -#define SYS_STAT_FILE 52 -#define SYS_TRUNCATE_FILE 53 -#define SYS_FLUSH_FILE 54 -#define SYS_DELETE_FILE 55 -#define SYS_RENAME_FILE 56 -#define SYS_LOCK_FILE 57 -#define SYS_UNLOCK_FILE 58 -#define SYS_OPEN_DIR 59 -#define SYS_READ_DIR 60 -#define SYS_CREATE_DIR 61 -#define SYS_REMOVE_DIR 62 -#define SYS_SET_CWD 63 -#define SYS_REAL_PATH 64 +#define SYS_OPEN_FILE 47 +#define SYS_READ_FILE 48 +#define SYS_WRITE_FILE 49 +#define SYS_READ_FILE_ASYNC 50 +#define SYS_WRITE_FILE_ASYNC 51 +#define SYS_CANCEL_IO 52 +#define SYS_SEEK_FILE 53 +#define SYS_GET_FILE_POS 54 +#define SYS_GET_FILE_SIZE 55 +#define SYS_GET_FILE_STAT 56 +#define SYS_STAT_FILE 57 +#define SYS_TRUNCATE_FILE 58 +#define SYS_FLUSH_FILE 59 +#define SYS_DELETE_FILE 60 +#define SYS_RENAME_FILE 61 +#define SYS_LOCK_FILE 62 +#define SYS_UNLOCK_FILE 63 +#define SYS_OPEN_DIR 64 +#define SYS_READ_DIR 65 +#define SYS_CREATE_DIR 66 +#define SYS_REMOVE_DIR 67 +#define SYS_SET_CWD 68 +#define SYS_REAL_PATH 69 // Console, display, and surfaces. -#define SYS_WRITE_CONSOLE 65 -#define SYS_GET_CONSOLE_INFO 66 -#define SYS_SET_CONSOLE_CURSOR 67 -#define SYS_FILL_CONSOLE_CHARACTER 68 -#define SYS_FILL_CONSOLE_ATTRIBUTE 69 -#define SYS_PUT_SCREEN_CHAR 70 -#define SYS_CLEAR_SCREEN 71 -#define SYS_GET_SCREEN_SIZE 72 -#define SYS_QUERY_DISPLAY_INFO 73 -#define SYS_CREATE_SURFACE 74 -#define SYS_MAP_SURFACE 75 -#define SYS_QUERY_SURFACE 76 -#define SYS_PRESENT_SURFACE 77 +#define SYS_WRITE_CONSOLE 70 +#define SYS_GET_CONSOLE_INFO 71 +#define SYS_SET_CONSOLE_CURSOR 72 +#define SYS_FILL_CONSOLE_CHARACTER 73 +#define SYS_FILL_CONSOLE_ATTRIBUTE 74 +#define SYS_PUT_SCREEN_CHAR 75 +#define SYS_CLEAR_SCREEN 76 +#define SYS_GET_SCREEN_SIZE 77 +#define SYS_QUERY_DISPLAY_INFO 78 +#define SYS_CREATE_SURFACE 79 +#define SYS_MAP_SURFACE 80 +#define SYS_QUERY_SURFACE 81 +#define SYS_PRESENT_SURFACE 82 // Input. -#define SYS_POLL_KEYBOARD 78 -#define SYS_IS_KEY_DOWN 79 -#define SYS_READ_KEYBOARD_CHAR 80 -#define SYS_READ_KEY 81 -#define SYS_GET_KEY_MODIFIERS 82 -#define SYS_OPEN_INPUT_QUEUE 83 -#define SYS_READ_INPUT_EVENT 84 -#define SYS_PEEK_INPUT_EVENT 85 -#define SYS_PEEK_MOUSE 86 -#define SYS_READ_MOUSE 87 -#define SYS_GET_MOUSE_POS 88 +#define SYS_POLL_KEYBOARD 83 +#define SYS_IS_KEY_DOWN 84 +#define SYS_READ_KEYBOARD_CHAR 85 +#define SYS_READ_KEY 86 +#define SYS_GET_KEY_MODIFIERS 87 +#define SYS_OPEN_INPUT_QUEUE 88 +#define SYS_READ_INPUT_EVENT 89 +#define SYS_PEEK_INPUT_EVENT 90 +#define SYS_PEEK_MOUSE 91 +#define SYS_READ_MOUSE 92 +#define SYS_GET_MOUSE_POS 93 // Time, debug, and system information. -#define SYS_CREATE_TIMER 89 -#define SYS_SET_TIMER 90 -#define SYS_CANCEL_TIMER 91 -#define SYS_GET_TICK 92 -#define SYS_GET_TICK_FREQUENCY 93 -#define SYS_GET_SYSTEM_TIME 94 -#define SYS_PRINT_DEBUG 95 -#define SYS_QUERY_TRAP_FRAME 96 -#define SYS_GET_MEMORY_STATUS 97 -#define SYS_QUERY_KERNEL_STATS 98 +#define SYS_CREATE_TIMER 94 +#define SYS_SET_TIMER 95 +#define SYS_CANCEL_TIMER 96 +#define SYS_GET_TICK 97 +#define SYS_GET_TICK_FREQUENCY 98 +#define SYS_GET_SYSTEM_TIME 99 +#define SYS_PRINT_DEBUG 100 +#define SYS_QUERY_TRAP_FRAME 101 +#define SYS_GET_MEMORY_STATUS 102 +#define SYS_QUERY_KERNEL_STATS 103 // Native sockets and polling. -#define SYS_CREATE_SOCKET 99 -#define SYS_BIND_SOCKET 100 -#define SYS_LISTEN_SOCKET 101 -#define SYS_ACCEPT_SOCKET 102 -#define SYS_CONNECT_SOCKET 103 -#define SYS_SEND_SOCKET 104 -#define SYS_RECV_SOCKET 105 -#define SYS_SHUTDOWN_SOCKET 106 -#define SYS_SEND_TO_SOCKET 107 -#define SYS_RECV_FROM_SOCKET 108 -#define SYS_RESOLVE_HOST 109 -#define SYS_SET_SOCKET_OPTION 110 -#define SYS_GET_SOCKET_OPTION 111 -#define SYS_POLL 112 +#define SYS_CREATE_SOCKET 104 +#define SYS_SOCKET_IO_ASYNC 105 +#define SYS_BIND_SOCKET 106 +#define SYS_LISTEN_SOCKET 107 +#define SYS_ACCEPT_SOCKET 108 +#define SYS_CONNECT_SOCKET 109 +#define SYS_SEND_SOCKET 110 +#define SYS_RECV_SOCKET 111 +#define SYS_SHUTDOWN_SOCKET 112 +#define SYS_SEND_TO_SOCKET 113 +#define SYS_RECV_FROM_SOCKET 114 +#define SYS_RESOLVE_HOST 115 +#define SYS_SET_SOCKET_OPTION 116 +#define SYS_GET_SOCKET_OPTION 117 +#define SYS_POLL 118 -#define SYS_MAX 113 +#define SYS_MAX 119 diff --git a/code/kernel/io_async.c b/code/kernel/io_async.c new file mode 100644 index 0000000..d80fdb3 --- /dev/null +++ b/code/kernel/io_async.c @@ -0,0 +1,202 @@ +// Kernel asynchronous I/O request object support. +// This file owns the common wait, completion, cancellation, APC, IOSB, and +// locked-buffer lifetime rules for all async I/O families. + +#include +#include +#include +#include +#include +#include + +// Releases resources owned by the common async request object. +static void KiIoAsyncRequestDestroy(OBJECT* obj) { + IO_ASYNC_REQUEST_OBJECT* request = + CONTAINING_RECORD(obj, IO_ASYNC_REQUEST_OBJECT, Header); + + if (request->CleanupRoutine) + request->CleanupRoutine(request, request->CleanupContext); + + if (request->WaitEvent) { + ObDereferenceObject(&request->WaitEvent->Header); + request->WaitEvent = (struct EVENT*)0; + } + if (request->CompletionEvent) { + ObDereferenceObject(&request->CompletionEvent->Header); + request->CompletionEvent = (struct EVENT*)0; + } + if (request->LockedBuffer) { + MmUnlockUserBuffer(request->LockedBuffer); + request->LockedBuffer = (MM_LOCKED_USER_BUFFER*)0; + } + if (request->LockedIosb) { + MmUnlockUserBuffer(request->LockedIosb); + request->LockedIosb = (MM_LOCKED_USER_BUFFER*)0; + } +} + +// Allocates a typed async request object with the common header first. +__declspec(dllexport) KSTATUS IoCreateAsyncRequest(size_t objectSize, + IO_ASYNC_REQUEST_CLEANUP_ROUTINE cleanupRoutine, + void* cleanupContext, + IO_ASYNC_REQUEST_CANCEL_ROUTINE cancelRoutine, + void* cancelContext, + IO_ASYNC_REQUEST_OBJECT** requestOut) { + if (!requestOut) return STATUS_INVALID_PARAMETER; + *requestOut = (IO_ASYNC_REQUEST_OBJECT*)0; + if (objectSize < sizeof(IO_ASYNC_REQUEST_OBJECT)) return STATUS_INVALID_PARAMETER; + + IO_ASYNC_REQUEST_OBJECT* request = + (IO_ASYNC_REQUEST_OBJECT*)ObAllocateObject(ObjectTypeAsyncIo, objectSize); + if (!request) return STATUS_NO_MEMORY; + + request->Header.Destroy = KiIoAsyncRequestDestroy; + LIST_INIT(&request->Link); + request->State = IoAsyncRequestQueued; + request->CancelRequested = 0; + request->IosbSnapshot.Status = STATUS_PENDING; + request->IosbSnapshot.Information = 0; + request->CleanupRoutine = cleanupRoutine; + request->CleanupContext = cleanupContext; + request->CancelRoutine = cancelRoutine; + request->CancelContext = cancelContext; + + KSTATUS status = KeEventCreate(EventManualReset, 0, &request->WaitEvent); + if (!KSUCCESS(status)) { + ObDereferenceObject(&request->Header); + return status; + } + + *requestOut = request; + return STATUS_SUCCESS; +} + +// Returns the object header embedded in an async request. +__declspec(dllexport) OBJECT* IoAsyncRequestObject(IO_ASYNC_REQUEST_OBJECT* request) { + return request ? &request->Header : (OBJECT*)0; +} + +// Attaches caller-visible completion state to a new async request. +__declspec(dllexport) void IoInitializeAsyncRequestCompletion(IO_ASYNC_REQUEST_OBJECT* request, + IO_STATUS_BLOCK* iosb, + EVENT* completionEvent, + KAPC* completionApc, + MM_LOCKED_USER_BUFFER* lockedBuffer, + MM_LOCKED_USER_BUFFER* lockedIosb) { + if (!request) return; + + request->CompletionEvent = completionEvent; + request->CompletionApc = completionApc; + request->Iosb = iosb; + request->LockedBuffer = lockedBuffer; + request->LockedIosb = lockedIosb; + request->IosbSnapshot.Status = STATUS_PENDING; + request->IosbSnapshot.Information = 0; + + if (iosb) { + iosb->Status = STATUS_PENDING; + iosb->Information = 0; + } + if (completionEvent) + ObReferenceObject(&completionEvent->Header); +} + +// Updates the lifecycle state owned by the request's operation family. +__declspec(dllexport) void IoAsyncRequestSetState(IO_ASYNC_REQUEST_OBJECT* request, + IO_ASYNC_REQUEST_STATE state) { + if (request) request->State = state; +} + +// Returns the last lifecycle state published by the operation family. +__declspec(dllexport) IO_ASYNC_REQUEST_STATE IoAsyncRequestGetState(IO_ASYNC_REQUEST_OBJECT* request) { + return request ? request->State : IoAsyncRequestCancelled; +} + +// Marks a request as cancelled so providers can observe it before completion. +__declspec(dllexport) void IoAsyncRequestRequestCancel(IO_ASYNC_REQUEST_OBJECT* request) { + if (request) request->CancelRequested = 1; +} + +// Returns whether cancellation has been requested for the async request. +__declspec(dllexport) uint8_t IoAsyncRequestIsCancelRequested(IO_ASYNC_REQUEST_OBJECT* request) { + return request ? request->CancelRequested : 0; +} + +// Returns the wait event owned by an async request. +__declspec(dllexport) EVENT* IoGetAsyncRequestWaitEvent(IO_ASYNC_REQUEST_OBJECT* request) { + return request ? request->WaitEvent : (EVENT*)0; +} + +// Waits for an async request to complete according to wait flags. +__declspec(dllexport) KSTATUS IoWaitAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request, + uint32_t flags) { + if (!request || !request->WaitEvent) return STATUS_INVALID_PARAMETER; + if (flags & ~SYS_WAIT_FLAG_ALERTABLE) return STATUS_INVALID_PARAMETER; + return KeWaitForEvent(request->WaitEvent, + (flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0); +} + +// Cancels an async request through its operation-family cancel routine. +__declspec(dllexport) KSTATUS IoCancelAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request) { + if (!request) return STATUS_INVALID_PARAMETER; + if (request->CancelRoutine) + return request->CancelRoutine(request, request->CancelContext); + + request->CancelRequested = 1; + if (request->State == IoAsyncRequestCompleted || + request->State == IoAsyncRequestCancelled) + return STATUS_OBJECT_DEAD; + return STATUS_PENDING; +} + +// Completes an async request and publishes all caller-visible completion state. +__declspec(dllexport) KSTATUS IoCompleteAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request, + KSTATUS status, + size_t information) { + if (!request) return STATUS_INVALID_PARAMETER; + + request->IosbSnapshot.Status = status; + request->IosbSnapshot.Information = information; + request->State = (status == STATUS_CANCELLED) ? IoAsyncRequestCancelled + : IoAsyncRequestCompleted; + + if (request->Iosb) { + request->Iosb->Status = status; + request->Iosb->Information = information; + } + + // User APCs run against the same IOSB state that waiters will observe. + if (request->CompletionApc) { + if (!KeInsertQueueApc(request->CompletionApc, + request->Iosb, + (void*)(uintptr_t)status)) { + if (request->CompletionApc->RundownRoutine) + request->CompletionApc->RundownRoutine(request->CompletionApc); + } + } + + if (request->CompletionEvent) + KeSignalEvent(request->CompletionEvent); + if (request->WaitEvent) + KeSignalEvent(request->WaitEvent); + + if (request->LockedBuffer) { + MmUnlockUserBuffer(request->LockedBuffer); + request->LockedBuffer = (MM_LOCKED_USER_BUFFER*)0; + } + if (request->LockedIosb) { + MmUnlockUserBuffer(request->LockedIosb); + request->LockedIosb = (MM_LOCKED_USER_BUFFER*)0; + } + + return STATUS_SUCCESS; +} + +// Copies the last completion snapshot out of an async request. +__declspec(dllexport) KSTATUS IoQueryAsyncRequest(IO_ASYNC_REQUEST_OBJECT* request, + IO_STATUS_BLOCK* iosbOut) { + if (!request || !iosbOut) return STATUS_INVALID_PARAMETER; + iosbOut->Status = request->IosbSnapshot.Status; + iosbOut->Information = request->IosbSnapshot.Information; + return STATUS_SUCCESS; +} diff --git a/code/kernel/io_file.c b/code/kernel/io_file.c index d1e0df4..016c0f1 100644 --- a/code/kernel/io_file.c +++ b/code/kernel/io_file.c @@ -32,31 +32,12 @@ typedef enum IO_ASYNC_FILE_OP { IoAsyncFileWrite = 1, } IO_ASYNC_FILE_OP; -typedef enum IO_ASYNC_FILE_STATE { - IoAsyncStateQueued = 0, - IoAsyncStateRunning = 1, - IoAsyncStateCompleted = 2, - IoAsyncStateCancelled = 3, -} IO_ASYNC_FILE_STATE; - struct IO_ASYNC_FILE_REQUEST_OBJECT { - OBJECT Header; // ObjectTypeAsyncIo - LIST_ENTRY Link; // protected by gAsyncFileQueueLock + IO_ASYNC_REQUEST_OBJECT Async; IO_ASYNC_FILE_OP Op; - IO_ASYNC_FILE_STATE State; - uint8_t CancelRequested; - - FILE_OBJECT* File; // referenced until object destruction - EVENT* WaitEvent; // internal completion event - EVENT* CompletionEvent; // optional, referenced until object destruction - KAPC* CompletionApc; // caller-owned APC storage - IO_STATUS_BLOCK* Iosb; // caller/user-owned completion block - IO_STATUS_BLOCK IosbSnapshot; - MM_LOCKED_USER_BUFFER* LockedBuffer; - MM_LOCKED_USER_BUFFER* LockedIosb; - - void* Buffer; // caller-owned; user buffers must be locked until completion/cancel + FILE_OBJECT* File; + void* Buffer; size_t Size; uint64_t Offset; }; @@ -228,72 +209,45 @@ static void KiIoSignalFileLockWaitersLocked(const IO_FILE_IDENTITY* identity) { } } -// Implements the io Async File Request Destroy operation. -static void KiIoAsyncFileRequestDestroy(OBJECT* obj) { +// Releases file-specific references owned by an async file request. +static void KiIoAsyncFileRequestCleanup(IO_ASYNC_REQUEST_OBJECT* async, void* context) { + (void)context; IO_ASYNC_FILE_REQUEST_OBJECT* req = - CONTAINING_RECORD(obj, IO_ASYNC_FILE_REQUEST_OBJECT, Header); + (IO_ASYNC_FILE_REQUEST_OBJECT*)async; - if (req->WaitEvent) { - ObDereferenceObject(&req->WaitEvent->Header); - req->WaitEvent = (EVENT*)0; - } - if (req->CompletionEvent) { - ObDereferenceObject(&req->CompletionEvent->Header); - req->CompletionEvent = (EVENT*)0; - } if (req->File) { ObDereferenceObject(&req->File->Header); req->File = (FILE_OBJECT*)0; } - if (req->LockedBuffer) { - MmUnlockUserBuffer(req->LockedBuffer); - req->LockedBuffer = (MM_LOCKED_USER_BUFFER*)0; - } - if (req->LockedIosb) { - MmUnlockUserBuffer(req->LockedIosb); - req->LockedIosb = (MM_LOCKED_USER_BUFFER*)0; - } } -static void KiIoCompleteAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* req, - KSTATUS status, - size_t information) { - if (!req) return; - - req->IosbSnapshot.Status = status; - req->IosbSnapshot.Information = information; +// Cancels an async file request from the common async request surface. +static KSTATUS KiIoCancelAsyncFileRequest(IO_ASYNC_REQUEST_OBJECT* async, void* context) { + (void)context; + if (!async) return STATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock); - req->State = (status == STATUS_CANCELLED) ? IoAsyncStateCancelled : IoAsyncStateCompleted; + IO_ASYNC_REQUEST_STATE state = IoAsyncRequestGetState(async); + + if (state == IoAsyncRequestQueued) { + ListRemove(&async->Link); + IoAsyncRequestRequestCancel(async); + IoAsyncRequestSetState(async, IoAsyncRequestCancelled); + KeReleaseSpinlock(&gAsyncFileQueueLock, flags); + + IoCompleteAsyncRequest(async, STATUS_CANCELLED, 0); + ObDereferenceObject(&async->Header); + return STATUS_SUCCESS; + } + + if (state == IoAsyncRequestRunning) { + IoAsyncRequestRequestCancel(async); + KeReleaseSpinlock(&gAsyncFileQueueLock, flags); + return STATUS_PENDING; + } + KeReleaseSpinlock(&gAsyncFileQueueLock, flags); - - if (req->Iosb) { - req->Iosb->Status = status; - req->Iosb->Information = information; - } - - // Match IRP completion ordering: queue APC first, then signal the event. - if (req->CompletionApc) { - if (!KeInsertQueueApc(req->CompletionApc, req->Iosb, (void*)(uintptr_t)status)) { - if (req->CompletionApc->RundownRoutine) - req->CompletionApc->RundownRoutine(req->CompletionApc); - } - } - - if (req->CompletionEvent) - KeSignalEvent(req->CompletionEvent); - - if (req->WaitEvent) - KeSignalEvent(req->WaitEvent); - - if (req->LockedBuffer) { - MmUnlockUserBuffer(req->LockedBuffer); - req->LockedBuffer = (MM_LOCKED_USER_BUFFER*)0; - } - if (req->LockedIosb) { - MmUnlockUserBuffer(req->LockedIosb); - req->LockedIosb = (MM_LOCKED_USER_BUFFER*)0; - } + return STATUS_OBJECT_DEAD; } // Implements the io Async File Dequeue operation. @@ -306,12 +260,12 @@ static IO_ASYNC_FILE_REQUEST_OBJECT* KiIoAsyncFileDequeue(void) { } IO_ASYNC_FILE_REQUEST_OBJECT* req = - CONTAINING_RECORD(node, IO_ASYNC_FILE_REQUEST_OBJECT, Link); + (IO_ASYNC_FILE_REQUEST_OBJECT*)CONTAINING_RECORD(node, IO_ASYNC_REQUEST_OBJECT, Link); - if (req->CancelRequested) { - req->State = IoAsyncStateCancelled; + if (IoAsyncRequestIsCancelRequested(&req->Async)) { + IoAsyncRequestSetState(&req->Async, IoAsyncRequestCancelled); } else { - req->State = IoAsyncStateRunning; + IoAsyncRequestSetState(&req->Async, IoAsyncRequestRunning); } KeReleaseSpinlock(&gAsyncFileQueueLock, flags); return req; @@ -331,9 +285,9 @@ static void KiIoAsyncFileWorker(void* arg) { IO_ASYNC_FILE_REQUEST_OBJECT* req = KiIoAsyncFileDequeue(); if (!req) continue; - if (req->CancelRequested || req->State == IoAsyncStateCancelled) { - KiIoCompleteAsyncFileRequest(req, STATUS_CANCELLED, 0); - ObDereferenceObject(&req->Header); // queue/worker reference + if (IoAsyncRequestIsCancelRequested(&req->Async) || IoAsyncRequestGetState(&req->Async) == IoAsyncRequestCancelled) { + IoCompleteAsyncRequest(&req->Async, STATUS_CANCELLED, 0); + ObDereferenceObject(&req->Async.Header); // queue/worker reference continue; } @@ -346,7 +300,7 @@ static void KiIoAsyncFileWorker(void* arg) { req->Size, &done); } - if (req->CancelRequested) { + if (IoAsyncRequestIsCancelRequested(&req->Async)) { // The operation may already have reached the filesystem. The // completion contract still reports cancellation so waiters/APCs do // not mistake a cancelled request for useful data. @@ -354,8 +308,8 @@ static void KiIoAsyncFileWorker(void* arg) { done = 0; } - KiIoCompleteAsyncFileRequest(req, s, done); - ObDereferenceObject(&req->Header); // queue/worker reference + IoCompleteAsyncRequest(&req->Async, s, done); + ObDereferenceObject(&req->Async.Header); // queue/worker reference } } @@ -404,13 +358,13 @@ static void KiIoNativeFsAsyncComplete(void* completeContext, (IO_ASYNC_FILE_REQUEST_OBJECT*)completeContext; if (!req) return; - if (req->CancelRequested) { + if (IoAsyncRequestIsCancelRequested(&req->Async)) { status = STATUS_CANCELLED; information = 0; } - KiIoCompleteAsyncFileRequest(req, status, information); - ObDereferenceObject(&req->Header); // native filesystem provider reference + IoCompleteAsyncRequest(&req->Async, status, information); + ObDereferenceObject(&req->Async.Header); // native filesystem provider reference } static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op, @@ -443,45 +397,30 @@ static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op, iosb->Status = STATUS_PENDING; iosb->Information = 0; - IO_ASYNC_FILE_REQUEST_OBJECT* req = - (IO_ASYNC_FILE_REQUEST_OBJECT*)ObAllocateObject(ObjectTypeAsyncIo, - sizeof(IO_ASYNC_FILE_REQUEST_OBJECT)); - if (!req) { - iosb->Status = STATUS_NO_MEMORY; + IO_ASYNC_REQUEST_OBJECT* async = (IO_ASYNC_REQUEST_OBJECT*)0; + KSTATUS createStatus = IoCreateAsyncRequest(sizeof(IO_ASYNC_FILE_REQUEST_OBJECT), + KiIoAsyncFileRequestCleanup, + (void*)0, + KiIoCancelAsyncFileRequest, + (void*)0, + &async); + if (!KSUCCESS(createStatus)) { + iosb->Status = createStatus; MmUnlockUserBuffer(lockedBuffer); MmUnlockUserBuffer(lockedIosb); - return STATUS_NO_MEMORY; + return createStatus; } - req->Header.Destroy = KiIoAsyncFileRequestDestroy; - LIST_INIT(&req->Link); + IO_ASYNC_FILE_REQUEST_OBJECT* req = (IO_ASYNC_FILE_REQUEST_OBJECT*)async; req->Op = op; - req->State = IoAsyncStateQueued; - req->CancelRequested = 0; - KSTATUS waitStatus = KeEventCreate(EventManualReset, 0, &req->WaitEvent); - if (!KSUCCESS(waitStatus)) { - iosb->Status = waitStatus; - MmUnlockUserBuffer(lockedBuffer); - MmUnlockUserBuffer(lockedIosb); - ObDereferenceObject(&req->Header); - return waitStatus; - } - req->File = file; - req->CompletionEvent = completionEvent; - req->CompletionApc = completionApc; - req->Iosb = iosb; - req->IosbSnapshot.Status = STATUS_PENDING; - req->IosbSnapshot.Information = 0; - req->LockedBuffer = lockedBuffer; - req->LockedIosb = lockedIosb; req->Buffer = buffer; req->Size = size; req->Offset = offset; + IoInitializeAsyncRequestCompletion(&req->Async, iosb, completionEvent, + completionApc, lockedBuffer, lockedIosb); ObReferenceObject(&file->Header); - if (completionEvent) - ObReferenceObject(&completionEvent->Header); IO_FS_ASYNC_COMPLETE complete = KiIoNativeFsAsyncComplete; KSTATUS ns = STATUS_NOT_SUPPORTED; @@ -490,10 +429,10 @@ static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op, if (hasNative) { uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock); - req->State = IoAsyncStateRunning; + IoAsyncRequestSetState(&req->Async, IoAsyncRequestRunning); KeReleaseSpinlock(&gAsyncFileQueueLock, flags); - ObReferenceObject(&req->Header); // native filesystem provider reference + ObReferenceObject(&req->Async.Header); // native filesystem provider reference if (op == IoAsyncFileRead) { ns = file->Fs->ReadAsync(file->Fs, file->FileCtx, offset, buffer, size, complete, req); @@ -505,34 +444,34 @@ static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op, if (requestOut) { *requestOut = req; } else { - ObDereferenceObject(&req->Header); + ObDereferenceObject(&req->Async.Header); } return STATUS_PENDING; } - ObDereferenceObject(&req->Header); // provider rejected synchronously + ObDereferenceObject(&req->Async.Header); // provider rejected synchronously iosb->Status = ns; - req->IosbSnapshot.Status = ns; - req->IosbSnapshot.Information = 0; - ObDereferenceObject(&req->Header); // creator reference + req->Async.IosbSnapshot.Status = ns; + req->Async.IosbSnapshot.Information = 0; + ObDereferenceObject(&req->Async.Header); // creator reference return ns; } KSTATUS s = KiIoEnsureAsyncFileIoStarted(); if (!KSUCCESS(s)) { iosb->Status = s; - req->IosbSnapshot.Status = s; - req->IosbSnapshot.Information = 0; - ObDereferenceObject(&req->Header); + req->Async.IosbSnapshot.Status = s; + req->Async.IosbSnapshot.Information = 0; + ObDereferenceObject(&req->Async.Header); return s; } // Queue ownership keeps the request alive until the worker completes or a // successful queued cancellation completes it from IoCancelAsyncFileRequest. - ObReferenceObject(&req->Header); + ObReferenceObject(&req->Async.Header); uintptr_t qflags = KeAcquireSpinlock(&gAsyncFileQueueLock); - ListInsertTail(&gAsyncFileQueue, &req->Link); + ListInsertTail(&gAsyncFileQueue, &req->Async.Link); KeReleaseSpinlock(&gAsyncFileQueueLock, qflags); KeSignalSemaphore(gAsyncFileSemaphore); @@ -540,7 +479,7 @@ static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op, if (requestOut) { *requestOut = req; // creator reference returned to caller } else { - ObDereferenceObject(&req->Header); + ObDereferenceObject(&req->Async.Header); } return STATUS_PENDING; @@ -574,56 +513,28 @@ __declspec(dllexport) KSTATUS IoWriteFileAsyncKernel(FILE_OBJECT* file, const vo // Implements the async File Request Object operation. __declspec(dllexport) OBJECT* IoAsyncFileRequestObject(IO_ASYNC_FILE_REQUEST_OBJECT* req) { - return req ? &req->Header : (OBJECT*)0; + return req ? &req->Async.Header : (OBJECT*)0; } // Returns the wait event owned by an async file request. __declspec(dllexport) EVENT* IoGetAsyncFileRequestWaitEvent(IO_ASYNC_FILE_REQUEST_OBJECT* req) { - return req ? req->WaitEvent : (EVENT*)0; + return req ? IoGetAsyncRequestWaitEvent(&req->Async) : (EVENT*)0; } // Waits for an async file request to complete according to wait flags. __declspec(dllexport) KSTATUS IoWaitAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* req, uint32_t flags) { - if (!req || !req->WaitEvent) return STATUS_INVALID_PARAMETER; - if (flags & ~SYS_WAIT_FLAG_ALERTABLE) return STATUS_INVALID_PARAMETER; - return KeWaitForEvent(req->WaitEvent, (flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0); + return IoWaitAsyncRequest(req ? &req->Async : (IO_ASYNC_REQUEST_OBJECT*)0, flags); } -// Cancels async File Request. +// Cancels an async file request through the common async I/O object. __declspec(dllexport) KSTATUS IoCancelAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* req) { - if (!req) return STATUS_INVALID_PARAMETER; - - IO_ASYNC_FILE_STATE state; - uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock); - state = req->State; - - if (state == IoAsyncStateQueued) { - ListRemove(&req->Link); - req->CancelRequested = 1; - req->State = IoAsyncStateCancelled; - KeReleaseSpinlock(&gAsyncFileQueueLock, flags); - - KiIoCompleteAsyncFileRequest(req, STATUS_CANCELLED, 0); - ObDereferenceObject(&req->Header); // queue reference removed above - return STATUS_SUCCESS; - } - - if (state == IoAsyncStateRunning) { - req->CancelRequested = 1; - KeReleaseSpinlock(&gAsyncFileQueueLock, flags); - return STATUS_PENDING; - } - - KeReleaseSpinlock(&gAsyncFileQueueLock, flags); - return STATUS_OBJECT_DEAD; + return IoCancelAsyncRequest(req ? &req->Async : (IO_ASYNC_REQUEST_OBJECT*)0); } __declspec(dllexport) KSTATUS IoQueryAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* req, IO_STATUS_BLOCK* iosbOut) { - if (!req || !iosbOut) return STATUS_INVALID_PARAMETER; - iosbOut->Status = req->IosbSnapshot.Status; - iosbOut->Information = req->IosbSnapshot.Information; - return STATUS_SUCCESS; + return IoQueryAsyncRequest(req ? &req->Async : (IO_ASYNC_REQUEST_OBJECT*)0, + iosbOut); } // Current Working Directory diff --git a/code/kernel/ke_process.c b/code/kernel/ke_process.c index a8ae98f..75b1e50 100644 --- a/code/kernel/ke_process.c +++ b/code/kernel/ke_process.c @@ -114,7 +114,9 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { info->ImageBase = p->ImageBase; info->ImageSize = p->ImageSize; info->PebBase = p->UserPebBase; + uintptr_t modFlags = KeAcquireSpinlock(&p->ModuleLock); info->ModuleCount = p->ModuleCount; + KeReleaseSpinlock(&p->ModuleLock, modFlags); uintptr_t handleFlags = KeAcquireSpinlock(&p->HandleLock); uint32_t threadCount = p->ThreadCount; @@ -288,7 +290,9 @@ static void KiProcessRundownResources(PROCESS* proc) { MmVadDestroyAllForProcess(proc); proc->ImageBase = 0; proc->ImageSize = 0; + uintptr_t modFlags = KeAcquireSpinlock(&proc->ModuleLock); proc->ModuleCount = 0; + KeReleaseSpinlock(&proc->ModuleLock, modFlags); proc->UserPebBase = 0; proc->UserPebSize = 0; proc->UserImagePathBase = 0; @@ -360,6 +364,7 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { proc->UserCwdBase = 0; proc->UserCwdSize = 0; proc->ModuleCount = 0; + proc->ModuleLock = (SPINLOCK)SPINLOCK_INIT; proc->CurrentDirectory[0] = '\0'; proc->CurrentDirectoryLength = 0; proc->CurrentDrive = KeGetSystemRootDrive(); diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index cee3e7b..344ea56 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -62,8 +62,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -119,6 +121,45 @@ static void KiFileMappingDestroy(OBJECT* obj) { } } + +typedef struct MODULE_HANDLE_OBJECT { + OBJECT Header; + PROCESS* Owner; + uintptr_t Base; +} MODULE_HANDLE_OBJECT; + +// Releasing the last module handle releases the public load reference it owns. +static void KiModuleHandleDestroy(OBJECT* obj) { + MODULE_HANDLE_OBJECT* module = CONTAINING_RECORD(obj, MODULE_HANDLE_OBJECT, Header); + if (module->Owner && module->Base) { + (void)LdrUnloadModuleForProcess(module->Owner, module->Base); + module->Base = 0; + } + if (module->Owner) { + ObDereferenceObject(&module->Owner->Header); + module->Owner = (PROCESS*)0; + } +} + +// Creates a user-visible module handle for one public load reference. +static KSTATUS KiCreateModuleHandle(PROCESS* owner, uintptr_t base, uint32_t* handleOut) { + if (!owner || !base || !handleOut) return STATUS_INVALID_PARAMETER; + + MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)ObAllocateObject(ObjectTypeModule, sizeof(MODULE_HANDLE_OBJECT)); + if (!module) return STATUS_NO_MEMORY; + module->Header.Destroy = KiModuleHandleDestroy; + module->Owner = owner; + module->Base = base; + ObReferenceObject(&owner->Header); + + KSTATUS s = KeAllocateHandleWithRights(owner, + &module->Header, + HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE, + handleOut); + ObDereferenceObject(&module->Header); + return s; +} + typedef struct SURFACE_OBJECT { OBJECT Header; PROCESS* Owner; @@ -565,7 +606,7 @@ static KSTATUS KiHandleToWaitEvent(PROCESS* proc, uint32_t handle, EVENT** event event = CONTAINING_RECORD(obj, EVENT, Header); break; case ObjectTypeAsyncIo: - event = IoGetAsyncFileRequestWaitEvent((IO_ASYNC_FILE_REQUEST_OBJECT*)obj); + event = IoGetAsyncRequestWaitEvent((IO_ASYNC_REQUEST_OBJECT*)obj); break; case ObjectTypeInputQueue: event = KeInputQueueWaitEvent(CONTAINING_RECORD(obj, INPUT_QUEUE, Header)); @@ -1921,12 +1962,12 @@ static uint64_t SyscallWriteFileAsync(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallCancelIo(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - IO_ASYNC_FILE_REQUEST_OBJECT* req = - (IO_ASYNC_FILE_REQUEST_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeAsyncIo, HANDLE_RIGHT_TERMINATE); + IO_ASYNC_REQUEST_OBJECT* req = + (IO_ASYNC_REQUEST_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeAsyncIo, HANDLE_RIGHT_TERMINATE); if (!req) return STATUS_INVALID_HANDLE; - KSTATUS s = IoCancelAsyncFileRequest(req); - ObDereferenceObject(IoAsyncFileRequestObject(req)); + KSTATUS s = IoCancelAsyncRequest(req); + ObDereferenceObject(IoAsyncRequestObject(req)); return s; } @@ -3324,6 +3365,181 @@ static uint64_t SyscallGetThreadList(uint64_t a0, uint64_t a1, uint64_t a2, return STATUS_SUCCESS; } + +// SYS_LOAD_MODULE a0=*SYS_LOAD_MODULE_REQUEST. +static uint64_t SyscallLoadModule(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_LOAD_MODULE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER; + if (req.Flags != SYS_LOAD_MODULE_FLAG_NONE) return STATUS_INVALID_PARAMETER; + if (!req.ModuleHandleOut) return STATUS_INVALID_PARAMETER; + + s = MmProbeUserWrite(req.ModuleHandleOut, sizeof(*req.ModuleHandleOut)); + if (!KSUCCESS(s)) return s; + if (req.InfoOut) { + s = MmProbeUserWrite(req.InfoOut, sizeof(*req.InfoOut)); + if (!KSUCCESS(s)) return s; + } + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + + uintptr_t base = 0; + s = LdrLoadModuleForProcess(proc, pathBuf, LDR_MODULE_LOAD_PUBLIC, &base); + if (!KSUCCESS(s)) return s; + + uint32_t handle = 0; + s = KiCreateModuleHandle(proc, base, &handle); + if (!KSUCCESS(s)) { + (void)LdrUnloadModuleForProcess(proc, base); + return s; + } + + SYS_MODULE_INFO info; + if (req.InfoOut) { + s = LdrQueryModuleForProcess(proc, base, &info); + if (!KSUCCESS(s)) { + KeCloseHandle(proc, handle); + return s; + } + } + + s = KiCopyU32ToUser((uint64_t)(uintptr_t)req.ModuleHandleOut, handle); + if (!KSUCCESS(s)) { + KeCloseHandle(proc, handle); + return s; + } + if (req.InfoOut) { + s = CopyToUser(req.InfoOut, &info, sizeof(info)); + if (!KSUCCESS(s)) { + KeCloseHandle(proc, handle); + return s; + } + } + + return STATUS_SUCCESS; +} + +// SYS_UNLOAD_MODULE a0=moduleHandle. +static uint64_t SyscallUnloadModule(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + + uint32_t handle = (uint32_t)a0; + MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject(handle, ObjectTypeModule, HANDLE_RIGHT_TERMINATE); + if (!module) return STATUS_INVALID_HANDLE; + + KeCloseHandle(proc, handle); + ObDereferenceObject(&module->Header); + return STATUS_SUCCESS; +} + +// SYS_GET_MODULE_INFO a0=moduleHandle a1=*SYS_MODULE_INFO. +static uint64_t SyscallGetModuleInfo(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + + KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(SYS_MODULE_INFO)); + if (!KSUCCESS(s)) return s; + + MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeModule, HANDLE_RIGHT_QUERY); + if (!module) return STATUS_INVALID_HANDLE; + + SYS_MODULE_INFO info; + s = LdrQueryModuleForProcess(module->Owner, module->Base, &info); + ObDereferenceObject(&module->Header); + if (!KSUCCESS(s)) return s; + + return CopyToUser((void*)(uintptr_t)a1, &info, sizeof(info)); +} + + +// SYS_GET_MODULE_EXPORT a0=*SYS_GET_MODULE_EXPORT_REQUEST. +static uint64_t SyscallGetModuleExport(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_GET_MODULE_EXPORT_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER; + if (!req.AddressOut) return STATUS_INVALID_PARAMETER; + + s = MmProbeUserWrite(req.AddressOut, sizeof(*req.AddressOut)); + if (!KSUCCESS(s)) return s; + + char nameBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW name; + s = KiImportUserTextView(req.Name, nameBuf, sizeof(nameBuf), &name, true); + if (!KSUCCESS(s)) return s; + if (name.Length == 0) return STATUS_INVALID_PARAMETER; + + MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject(req.ModuleHandle, ObjectTypeModule, HANDLE_RIGHT_QUERY); + if (!module) return STATUS_INVALID_HANDLE; + + void* address = LdrGetModuleExportForProcess(module->Owner, module->Base, nameBuf); + ObDereferenceObject(&module->Header); + if (!address) return STATUS_NOT_FOUND; + + uintptr_t userAddress = (uintptr_t)address; + return CopyToUser(req.AddressOut, &userAddress, sizeof(userAddress)); +} + +// SYS_GET_MODULE_LIST a0=*SYS_MODULE_INFO[] a1=maxCount a2=*countOut. +static uint64_t SyscallGetModuleList(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + size_t maxCount = (size_t)a1; + + if (maxCount && !a0) return STATUS_INVALID_PARAMETER; + if (a2) { + KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a2, sizeof(size_t)); + if (!KSUCCESS(s)) return s; + } + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + + size_t total = 0; + KSTATUS s = LdrCopyProcessModuleInfo(proc, NULL, 0, &total); + if (s != STATUS_BUFFER_TOO_SMALL && !KSUCCESS(s)) return s; + + size_t toCopy = total < maxCount ? total : maxCount; + if (toCopy) { + size_t bytes = 0; + if (!KiMulSize(toCopy, sizeof(SYS_MODULE_INFO), &bytes)) return STATUS_INVALID_PARAMETER; + + SYS_MODULE_INFO* tmp = (SYS_MODULE_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('m', 'o', 'd', 'l')); + if (!tmp) return STATUS_NO_MEMORY; + + size_t copiedTotal = 0; + s = LdrCopyProcessModuleInfo(proc, tmp, toCopy, &copiedTotal); + if (s == STATUS_BUFFER_TOO_SMALL) + s = STATUS_SUCCESS; + if (KSUCCESS(s)) + s = CopyToUser((void*)(uintptr_t)a0, tmp, toCopy * sizeof(SYS_MODULE_INFO)); + MmFreePool(tmp); + if (!KSUCCESS(s)) return s; + } + + if (a2) return KiCopySizeToUser(a2, total); + return STATUS_SUCCESS; +} + // SYS_RENAME_FILE (66) a0=*oldPath a1=*newPath. static uint64_t SyscallRenameFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { @@ -3459,6 +3675,39 @@ typedef struct SYS_SOCKET_OPTION_REQUEST_K { size_t* ActualSizeOut; } SYS_SOCKET_OPTION_REQUEST_K; +typedef struct SYS_ASYNC_SOCKET_REQUEST_K { + uint32_t Size; + uint32_t SocketHandle; + uint32_t EventHandle; + uint32_t Operation; + uint32_t Flags; + uint32_t SocketFlags; + uint32_t Backlog; + uint32_t ShutdownHow; + uint32_t Option; + uint32_t PollEvents; + + void* Buffer; + size_t Length; + + const SYS_SOCKET_ADDRESS_K* Address; + size_t AddressSize; + SYS_SOCKET_ADDRESS_K* AddressOut; + size_t* AddressSizeInOut; + + void* OptionValue; + size_t OptionValueSize; + size_t* OptionActualSizeOut; + + uint32_t* HandleOut; + uint32_t* PollReturnedEventsOut; + + SYS_IO_STATUS_BLOCK_K* Iosb; + SYS_USER_APC_ROUTINE_K ApcRoutine; + void* ApcContext; + uint32_t* RequestHandleOut; +} SYS_ASYNC_SOCKET_REQUEST_K; + typedef struct SYS_MEMORY_STATUS_K { uint64_t FreePhysicalBytes; uint64_t KernelPoolBytes; @@ -3557,7 +3806,7 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, SOCKET_OBJECT* accepted = NULL; SYS_SOCKET_ADDRESS_K remote; - KSTATUS s = NetSocketAccept(listener, &accepted, wantRemote ? &remote : NULL); + KSTATUS s = NetSocketAccept(listener, &accepted, wantRemote ? &remote : NULL, (IO_ASYNC_REQUEST_OBJECT*)0); ObDereferenceObject(&listener->Header); if (!KSUCCESS(s)) return s; @@ -3599,7 +3848,7 @@ static uint64_t SyscallConnect(uint64_t a0, uint64_t a1, uint64_t a2, SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) return STATUS_NOT_FOUND; - s = NetSocketConnect(socket, &address, (size_t)a2); + s = NetSocketConnect(socket, &address, (size_t)a2, (IO_ASYNC_REQUEST_OBJECT*)0); ObDereferenceObject(&socket->Header); return s; } @@ -3661,7 +3910,7 @@ static uint64_t SyscallRecv(uint64_t a0, uint64_t a1, uint64_t a2, if (!socket) { MmFreePool(kbuf); return STATUS_NOT_FOUND; } size_t received = 0; - s = NetSocketReceive(socket, kbuf, length, (uint32_t)a3, &received); + s = NetSocketReceive(socket, kbuf, length, (uint32_t)a3, &received, (IO_ASYNC_REQUEST_OBJECT*)0); ObDereferenceObject(&socket->Header); if (KSUCCESS(s) && received) @@ -3772,7 +4021,7 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, SYS_SOCKET_ADDRESS_K remote; memset(&remote, 0, sizeof(remote)); size_t received = 0; - s = NetSocketReceiveFrom(socket, kbuf, req.Length, req.Flags, wantRemote ? &remote : NULL, &received); + s = NetSocketReceiveFrom(socket, kbuf, req.Length, req.Flags, wantRemote ? &remote : NULL, &received, (IO_ASYNC_REQUEST_OBJECT*)0); ObDereferenceObject(&socket->Header); if (KSUCCESS(s) && received) @@ -3958,6 +4207,417 @@ static uint64_t SyscallPoll(uint64_t a0, uint64_t a1, uint64_t a2, return s; } +// Releases resources prepared for an async socket request before queue ownership starts. +static void SyscallAsyncSocketReleaseSubmitResources(NET_ASYNC_SOCKET_SUBMIT* submit) { + if (!submit) + return; + + if (submit->KernelBuffer) { + MmFreePool(submit->KernelBuffer); + submit->KernelBuffer = (void*)0; + } + if (submit->OptionValue) { + MmFreePool(submit->OptionValue); + submit->OptionValue = (void*)0; + } + if (submit->LockedBuffer) { + MmUnlockUserBuffer(submit->LockedBuffer); + submit->LockedBuffer = (MM_LOCKED_USER_BUFFER*)0; + } + if (submit->LockedIosb) { + MmUnlockUserBuffer(submit->LockedIosb); + submit->LockedIosb = (MM_LOCKED_USER_BUFFER*)0; + } + if (submit->LockedAddressOut) { + MmUnlockUserBuffer(submit->LockedAddressOut); + submit->LockedAddressOut = (MM_LOCKED_USER_BUFFER*)0; + } + if (submit->LockedAddressSize) { + MmUnlockUserBuffer(submit->LockedAddressSize); + submit->LockedAddressSize = (MM_LOCKED_USER_BUFFER*)0; + } + if (submit->LockedOptionValue) { + MmUnlockUserBuffer(submit->LockedOptionValue); + submit->LockedOptionValue = (MM_LOCKED_USER_BUFFER*)0; + } + if (submit->LockedOptionActualSize) { + MmUnlockUserBuffer(submit->LockedOptionActualSize); + submit->LockedOptionActualSize = (MM_LOCKED_USER_BUFFER*)0; + } + if (submit->LockedHandleOut) { + MmUnlockUserBuffer(submit->LockedHandleOut); + submit->LockedHandleOut = (MM_LOCKED_USER_BUFFER*)0; + } + if (submit->LockedPollReturnedEvents) { + MmUnlockUserBuffer(submit->LockedPollReturnedEvents); + submit->LockedPollReturnedEvents = (MM_LOCKED_USER_BUFFER*)0; + } +} + +// Locks an optional async socket output pointer for worker-thread completion. +static KSTATUS SyscallAsyncSocketLockOptional(PROCESS* proc, + void* ptr, + size_t size, + MM_LOCKED_USER_BUFFER** out) { + if (out) + *out = (MM_LOCKED_USER_BUFFER*)0; + if (!ptr || size == 0) + return STATUS_SUCCESS; + return MmLockUserBuffer(proc, ptr, size, 1, out); +} + +// Copies an input address into a kernel-owned async socket request. +static KSTATUS SyscallAsyncSocketCopyInputAddress(const SYS_ASYNC_SOCKET_REQUEST_K* req, + NET_ASYNC_SOCKET_SUBMIT* submit) { + if (!req || !submit || !req->Address) + return STATUS_INVALID_PARAMETER; + if (req->AddressSize < sizeof(SYS_SOCKET_ADDRESS_K)) + return STATUS_BUFFER_TOO_SMALL; + + KSTATUS status = CopyFromUser(&submit->Address, req->Address, sizeof(submit->Address)); + if (!KSUCCESS(status)) + return status; + submit->AddressSize = req->AddressSize; + return STATUS_SUCCESS; +} + +// Validates and locks optional remote-address outputs for async accept/recvfrom. +static KSTATUS SyscallAsyncSocketPrepareAddressOutput(PROCESS* proc, + const SYS_ASYNC_SOCKET_REQUEST_K* req, + NET_ASYNC_SOCKET_SUBMIT* submit) { + if (!req || !submit) + return STATUS_INVALID_PARAMETER; + if (!req->AddressOut) + return STATUS_SUCCESS; + if (!req->AddressSizeInOut) + return STATUS_INVALID_PARAMETER; + + size_t capacity = 0; + KSTATUS status = CopyFromUser(&capacity, req->AddressSizeInOut, sizeof(capacity)); + if (!KSUCCESS(status)) + return status; + if (capacity < sizeof(SYS_SOCKET_ADDRESS_K)) { + size_t required = sizeof(SYS_SOCKET_ADDRESS_K); + (void)CopyToUser(req->AddressSizeInOut, &required, sizeof(required)); + return STATUS_BUFFER_TOO_SMALL; + } + + status = MmLockUserBuffer(proc, req->AddressOut, sizeof(SYS_SOCKET_ADDRESS_K), 1, + &submit->LockedAddressOut); + if (!KSUCCESS(status)) + return status; + status = MmLockUserBuffer(proc, req->AddressSizeInOut, sizeof(*req->AddressSizeInOut), 1, + &submit->LockedAddressSize); + if (!KSUCCESS(status)) + return status; + + submit->AddressOut = req->AddressOut; + submit->AddressSizeInOut = req->AddressSizeInOut; + return STATUS_SUCCESS; +} + +// Prepares kernel-owned buffers and locked outputs for one async socket operation. +static KSTATUS SyscallAsyncSocketPrepareOperation(PROCESS* proc, + const SYS_ASYNC_SOCKET_REQUEST_K* req, + NET_ASYNC_SOCKET_SUBMIT* submit) { + if (!proc || !req || !submit) + return STATUS_INVALID_PARAMETER; + + submit->Operation = req->Operation; + submit->SocketFlags = req->SocketFlags; + submit->Backlog = req->Backlog; + submit->ShutdownHow = req->ShutdownHow; + submit->Option = req->Option; + submit->PollEvents = req->PollEvents; + submit->Length = req->Length; + + switch (req->Operation) { + case SYS_ASYNC_SOCKET_OP_BIND: + case SYS_ASYNC_SOCKET_OP_CONNECT: + return SyscallAsyncSocketCopyInputAddress(req, submit); + + case SYS_ASYNC_SOCKET_OP_LISTEN: + case SYS_ASYNC_SOCKET_OP_SHUTDOWN: + return STATUS_SUCCESS; + + case SYS_ASYNC_SOCKET_OP_ACCEPT: { + if (!req->HandleOut) + return STATUS_INVALID_PARAMETER; + uint32_t zero = 0; + KSTATUS status = MmLockUserBuffer(proc, req->HandleOut, sizeof(*req->HandleOut), 1, + &submit->LockedHandleOut); + if (!KSUCCESS(status)) + return status; + *req->HandleOut = zero; + submit->HandleOut = req->HandleOut; + return SyscallAsyncSocketPrepareAddressOutput(proc, req, submit); + } + + case SYS_ASYNC_SOCKET_OP_SEND: + if (req->Length && !req->Buffer) + return STATUS_INVALID_PARAMETER; + if (req->Length > SYSCALL_IO_MAX_BYTES) + return STATUS_INVALID_PARAMETER; + if (req->Length) { + submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 's', 'n')); + if (!submit->KernelBuffer) + return STATUS_NO_MEMORY; + return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length); + } + return STATUS_SUCCESS; + + case SYS_ASYNC_SOCKET_OP_RECV: + if (req->Length && !req->Buffer) + return STATUS_INVALID_PARAMETER; + if (req->Length > SYSCALL_IO_MAX_BYTES) + return STATUS_INVALID_PARAMETER; + if (req->Length) { + KSTATUS status = MmLockUserBuffer(proc, req->Buffer, req->Length, 1, &submit->LockedBuffer); + if (!KSUCCESS(status)) + return status; + submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 'r', 'n')); + if (!submit->KernelBuffer) + return STATUS_NO_MEMORY; + } + submit->UserBuffer = req->Buffer; + return STATUS_SUCCESS; + + case SYS_ASYNC_SOCKET_OP_SEND_TO: { + if (req->Length && !req->Buffer) + return STATUS_INVALID_PARAMETER; + if (req->Length > SYSCALL_IO_MAX_BYTES) + return STATUS_INVALID_PARAMETER; + KSTATUS status = SyscallAsyncSocketCopyInputAddress(req, submit); + if (!KSUCCESS(status)) + return status; + if (req->Length) { + submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 's', 'd')); + if (!submit->KernelBuffer) + return STATUS_NO_MEMORY; + return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length); + } + return STATUS_SUCCESS; + } + + case SYS_ASYNC_SOCKET_OP_RECV_FROM: { + if (req->Length && !req->Buffer) + return STATUS_INVALID_PARAMETER; + if (req->Length > SYSCALL_IO_MAX_BYTES) + return STATUS_INVALID_PARAMETER; + KSTATUS status = SyscallAsyncSocketPrepareAddressOutput(proc, req, submit); + if (!KSUCCESS(status)) + return status; + if (req->Length) { + status = MmLockUserBuffer(proc, req->Buffer, req->Length, 1, &submit->LockedBuffer); + if (!KSUCCESS(status)) + return status; + submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 'r', 'd')); + if (!submit->KernelBuffer) + return STATUS_NO_MEMORY; + } + submit->UserBuffer = req->Buffer; + return STATUS_SUCCESS; + } + + case SYS_ASYNC_SOCKET_OP_SET_OPT: + if (!req->OptionValue) + return STATUS_INVALID_PARAMETER; + if (req->OptionValueSize > sizeof(uint64_t)) + return STATUS_BUFFER_TOO_SMALL; + submit->OptionValueSize = req->OptionValueSize; + if (req->OptionValueSize) { + submit->OptionValue = MmAllocatePoolTag(req->OptionValueSize, MM_POOL_TAG('s', 'a', 'o', 's')); + if (!submit->OptionValue) + return STATUS_NO_MEMORY; + return CopyFromUser(submit->OptionValue, req->OptionValue, req->OptionValueSize); + } + return STATUS_SUCCESS; + + case SYS_ASYNC_SOCKET_OP_GET_OPT: { + if (!req->OptionValue) + return STATUS_INVALID_PARAMETER; + if (req->OptionValueSize > sizeof(uint64_t)) + return STATUS_BUFFER_TOO_SMALL; + submit->OptionValueSize = req->OptionValueSize; + submit->UserOptionValue = req->OptionValue; + KSTATUS status = MmLockUserBuffer(proc, req->OptionValue, req->OptionValueSize, 1, + &submit->LockedOptionValue); + if (!KSUCCESS(status)) + return status; + if (req->OptionActualSizeOut) { + status = MmLockUserBuffer(proc, req->OptionActualSizeOut, sizeof(*req->OptionActualSizeOut), 1, + &submit->LockedOptionActualSize); + if (!KSUCCESS(status)) + return status; + submit->OptionActualSizeOut = req->OptionActualSizeOut; + } + if (req->OptionValueSize) { + submit->OptionValue = MmAllocatePoolTag(req->OptionValueSize, MM_POOL_TAG('s', 'a', 'o', 'g')); + if (!submit->OptionValue) + return STATUS_NO_MEMORY; + } + return STATUS_SUCCESS; + } + + case SYS_ASYNC_SOCKET_OP_POLL: { + if (req->PollReturnedEventsOut) { + KSTATUS status = MmLockUserBuffer(proc, req->PollReturnedEventsOut, + sizeof(*req->PollReturnedEventsOut), 1, + &submit->LockedPollReturnedEvents); + if (!KSUCCESS(status)) + return status; + *req->PollReturnedEventsOut = 0; + submit->PollReturnedEventsOut = req->PollReturnedEventsOut; + } + return STATUS_SUCCESS; + } + + default: + return STATUS_INVALID_PARAMETER; + } +} + +// SYS_SOCKET_IO_ASYNC a0=*SYS_ASYNC_SOCKET_REQUEST. +static uint64_t SyscallSocketIoAsync(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) + return STATUS_INVALID_PARAMETER; + + SYS_ASYNC_SOCKET_REQUEST_K req; + KSTATUS status = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req)); + if (!KSUCCESS(status)) + return status; + if (req.Size != sizeof(req) || req.Flags != SYS_ASYNC_SOCKET_FLAG_NONE || !req.Iosb) + return STATUS_INVALID_PARAMETER; + if (req.RequestHandleOut) { + status = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut)); + if (!KSUCCESS(status)) + return status; + } + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) + return STATUS_FAILURE; + + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.SocketHandle, + ObjectTypeSocket, + HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); + if (!socket) + return STATUS_NOT_FOUND; + + EVENT* event = (EVENT*)0; + if (req.EventHandle) { + event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); + if (!event) { + ObDereferenceObject(&socket->Header); + return STATUS_NOT_FOUND; + } + } + + KAPC* completionApc = (KAPC*)0; + SYS_USER_ASYNC_APC_CONTEXT* apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)0; + if (req.ApcRoutine) { + THREAD* thread = KeGetCurrentThread(); + if (!thread || !thread->UserApcDispatcher) { + if (event) ObDereferenceObject(&event->Header); + ObDereferenceObject(&socket->Header); + return STATUS_INVALID_PARAMETER; + } + + apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)MmAllocatePoolTag(sizeof(SYS_USER_ASYNC_APC_CONTEXT), MM_POOL_TAG('u', 'a', 'p', 'c')); + if (!apcCtx) { + if (event) ObDereferenceObject(&event->Header); + ObDereferenceObject(&socket->Header); + return STATUS_NO_MEMORY; + } + + memset(apcCtx, 0, sizeof(*apcCtx)); + ObReferenceObject(&thread->Header); + apcCtx->TargetThread = thread; + apcCtx->UserRoutine = req.ApcRoutine; + apcCtx->UserContext = req.ApcContext; + KeInitializeApc(&apcCtx->KernelApc, + thread, + SyscallUserAsyncKernelRoutine, + SyscallUserAsyncNormalRoutine, + apcCtx); + KeSetApcRundownRoutine(&apcCtx->KernelApc, SyscallUserAsyncApcRundown); + completionApc = &apcCtx->KernelApc; + } + + NET_ASYNC_SOCKET_SUBMIT submit; + memset(&submit, 0, sizeof(submit)); + submit.OwnerProcess = proc; + submit.Socket = socket; + submit.CompletionEvent = event; + submit.CompletionApc = completionApc; + submit.Iosb = (IO_STATUS_BLOCK*)req.Iosb; + + status = MmLockUserBuffer(proc, req.Iosb, sizeof(*req.Iosb), 1, &submit.LockedIosb); + if (!KSUCCESS(status)) + goto fail; + + status = SyscallAsyncSocketPrepareOperation(proc, &req, &submit); + if (!KSUCCESS(status)) + goto fail; + + NET_ASYNC_SOCKET_REQUEST_OBJECT* asyncReq = (NET_ASYNC_SOCKET_REQUEST_OBJECT*)0; + status = NetSocketSubmitAsyncKernel(&submit, &asyncReq); + if (status == STATUS_PENDING) { + memset(&submit, 0, sizeof(submit)); + } + if (!KSUCCESS(status) && status != STATUS_PENDING) + goto fail; + + if (req.RequestHandleOut && asyncReq) { + uint32_t requestHandle = 0; + KSTATUS hs = KeAllocateHandleWithRights(proc, + NetAsyncSocketRequestObject(asyncReq), + HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE, + &requestHandle); + if (!KSUCCESS(hs)) { + (void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq); + ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq)); + status = hs; + goto fail_after_submit; + } + + KSTATUS cs = CopyToUser(req.RequestHandleOut, &requestHandle, sizeof(requestHandle)); + if (!KSUCCESS(cs)) { + (void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq); + KeCloseHandle(proc, requestHandle); + ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq)); + status = cs; + goto fail_after_submit; + } + } else if (req.RequestHandleOut) { + uint32_t zeroHandle = 0; + KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle)); + if (!KSUCCESS(cs)) { + status = cs; + goto fail_after_submit; + } + } + + if (asyncReq) + ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq)); + if (event) + ObDereferenceObject(&event->Header); + ObDereferenceObject(&socket->Header); + return status; + +fail: + SyscallAsyncSocketReleaseSubmitResources(&submit); + if (apcCtx) + SyscallUserAsyncApcContextFree(apcCtx); +fail_after_submit: + if (event) + ObDereferenceObject(&event->Header); + ObDereferenceObject(&socket->Header); + return status; +} + // SYS_GET_MEMORY_STATUS a0=*SYS_MEMORY_STATUS. static uint64_t SyscallGetMemoryStatus(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { @@ -4332,6 +4992,29 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); } + + case SYS_KERNEL_STATS_NET_IRP: { + NET_IRP_STATS km; + SYS_NET_IRP_STATS um; + NetQueryIrpStats(&km); + memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; + um.Submitted = km.Submitted; + um.Completed = km.Completed; + um.CompletedSynchronously = km.CompletedSynchronously; + um.CompletedAsynchronously = km.CompletedAsynchronously; + um.Pending = km.Pending; + um.CancelRequested = km.CancelRequested; + um.Cancelled = km.Cancelled; + um.WouldBlock = km.WouldBlock; + um.Timeout = km.Timeout; + um.Failed = km.Failed; + for (uint32_t i = 0; i < SYS_NET_IRP_OPERATION_COUNT && i < NetSocketIrpOperationCount; ++i) + um.ByOperation[i] = km.ByOperation[i]; + return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + } + case SYS_KERNEL_STATS_CPU: { struct { SYS_CPU_STATS Header; @@ -4381,7 +5064,7 @@ typedef uint64_t (*SYSCALL_HANDLER)(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { - // Process, threads, waits, and APC. + // Process, threads, modules, waits, and APC. [SYS_EXIT_PROCESS] = SyscallExitProcess, [SYS_EXIT_THREAD] = SyscallExitThread, [SYS_CREATE_PROCESS] = SyscallCreateProcess, @@ -4392,6 +5075,11 @@ static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { [SYS_GET_PROCESS_PID] = SyscallGetProcessPid, [SYS_CREATE_THREAD] = SyscallCreateThread, [SYS_GET_THREAD_LIST] = SyscallGetThreadList, + [SYS_LOAD_MODULE] = SyscallLoadModule, + [SYS_UNLOAD_MODULE] = SyscallUnloadModule, + [SYS_GET_MODULE_INFO] = SyscallGetModuleInfo, + [SYS_GET_MODULE_EXPORT] = SyscallGetModuleExport, + [SYS_GET_MODULE_LIST] = SyscallGetModuleList, [SYS_GET_CURRENT_TID] = SyscallGetCurrentTid, [SYS_GET_CURRENT_PID] = SyscallGetCurrentPid, [SYS_SET_THREAD_PRIORITY] = SyscallSetThreadPriority, @@ -4498,6 +5186,7 @@ static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { // Native sockets and polling. [SYS_CREATE_SOCKET] = SyscallSocket, + [SYS_SOCKET_IO_ASYNC] = SyscallSocketIoAsync, [SYS_BIND_SOCKET] = SyscallBind, [SYS_LISTEN_SOCKET] = SyscallListen, [SYS_ACCEPT_SOCKET] = SyscallAccept, diff --git a/code/kernel/ldr_image.c b/code/kernel/ldr_image.c index 68a6f4c..9ab7d4a 100644 --- a/code/kernel/ldr_image.c +++ b/code/kernel/ldr_image.c @@ -10,6 +10,7 @@ #include #include #include +#include #ifndef LDR_DEBUG_VERBOSE #define LDR_DEBUG_VERBOSE 0 @@ -326,6 +327,24 @@ void* LdrGetExportForProcess(PROCESS* process, const char* moduleName, const cha return KiGetExportAtBase(base, functionName); } +// Looks up an export in a specific process module mapping. +void* LdrGetModuleExportForProcess(PROCESS* process, uintptr_t imageBase, const char* functionName) { + if (!process || !imageBase || !functionName) return (void*)0; + + uintptr_t flags = KeAcquireSpinlock(&process->ModuleLock); + int found = 0; + for (uint32_t i = 0; i < process->ModuleCount; i++) { + if (process->Modules[i].Base == imageBase) { + found = 1; + break; + } + } + KeReleaseSpinlock(&process->ModuleLock, flags); + if (!found) return (void*)0; + + return KiGetExportAtBase(imageBase, functionName); +} + // Implements the ldr Get Image Size operation. size_t LdrGetImageSize(uintptr_t base) { if (!base) return 0; @@ -559,7 +578,7 @@ static KSTATUS LdrResolveImportsCommon( if (!modBase) { KSTATUS s = - LdrLoadModuleForProcess(process, dllName, &modBase); + LdrLoadModuleForProcess(process, dllName, LDR_MODULE_LOAD_IMPORT, &modBase); if (!KSUCCESS(s)) { DbgPrint( @@ -674,38 +693,101 @@ KSTATUS LdrLoadImageFileForProcess(PROCESS* process, FILE_OBJECT* file, uintptr_ return STATUS_SUCCESS; } -// Implements the ldr Register Process Module operation. -static KSTATUS LdrRegisterProcessModule(PROCESS* process, const char* name, uintptr_t base) { - if (!process || !name || !base) return STATUS_INVALID_PARAMETER; +// Converts a process module entry into the public module ABI. +static void KiFillProcessModuleInfo(const PROCESS_MODULE* mod, SYS_MODULE_INFO* info) { + memset(info, 0, sizeof(*info)); + info->Size = sizeof(*info); + info->Version = SYS_MODULE_INFO_VERSION; + info->Flags = mod->Flags; + if (mod->PublicRefCount) + info->Flags |= SYS_MODULE_FLAG_PUBLIC_REF; + if (mod->ImportRefCount) + info->Flags |= SYS_MODULE_FLAG_IMPORT_PINNED; + if (mod->PublicRefCount && mod->ImportRefCount == 0) + info->Flags |= SYS_MODULE_FLAG_UNLOADABLE; + info->Base = mod->Base; + info->ImageSize = mod->Size; + info->EntryPoint = mod->EntryPoint; + info->PublicRefCount = mod->PublicRefCount; + info->ImportRefCount = mod->ImportRefCount; + info->TotalRefCount = mod->PublicRefCount + mod->ImportRefCount; + strncpy(info->Name, mod->Name, sizeof(info->Name) - 1); + info->Name[sizeof(info->Name) - 1] = '\0'; +} +// Adds one reference of the requested class to an existing process module. +static void KiReferenceProcessModule(PROCESS_MODULE* mod, uint32_t loadFlags) { + if (loadFlags & LDR_MODULE_LOAD_PUBLIC) + mod->PublicRefCount++; + if (loadFlags & LDR_MODULE_LOAD_IMPORT) + mod->ImportRefCount++; +} + +// Finds a process module by canonical name while the caller owns ModuleLock. +static PROCESS_MODULE* KiFindProcessModuleByNameLocked(PROCESS* process, const char* name) { for (uint32_t i = 0; i < process->ModuleCount; i++) { - if (_stricmp(process->Modules[i].Name, name) == 0) { - return STATUS_SUCCESS; - } + if (_stricmp(process->Modules[i].Name, name) == 0) + return &process->Modules[i]; + } + return (PROCESS_MODULE*)0; +} + +// Finds a process module by image base while the caller owns ModuleLock. +static PROCESS_MODULE* KiFindProcessModuleByBaseLocked(PROCESS* process, uintptr_t base) { + for (uint32_t i = 0; i < process->ModuleCount; i++) { + if (process->Modules[i].Base == base) + return &process->Modules[i]; + } + return (PROCESS_MODULE*)0; +} + +// Registers a mapped process module or records a reference to an existing one. +static KSTATUS LdrRegisterProcessModule(PROCESS* process, const char* name, uintptr_t base, uint32_t loadFlags, uintptr_t* registeredBaseOut) { + if (!process || !name || !base || !loadFlags) return STATUS_INVALID_PARAMETER; + if (registeredBaseOut) *registeredBaseOut = 0; + + uintptr_t flags = KeAcquireSpinlock(&process->ModuleLock); + PROCESS_MODULE* existing = KiFindProcessModuleByNameLocked(process, name); + if (existing) { + KiReferenceProcessModule(existing, loadFlags); + if (registeredBaseOut) *registeredBaseOut = existing->Base; + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_ALREADY_EXISTS; } - if (process->ModuleCount >= PROCESS_MODULE_MAX) return STATUS_NO_MEMORY; + if (process->ModuleCount >= PROCESS_MODULE_MAX) { + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_NO_MEMORY; + } PROCESS_MODULE* mod = &process->Modules[process->ModuleCount++]; - strncpy(mod->Name, name, 31); - mod->Name[31] = '\0'; + memset(mod, 0, sizeof(*mod)); + strncpy(mod->Name, name, sizeof(mod->Name) - 1); + mod->Name[sizeof(mod->Name) - 1] = '\0'; mod->Base = base; mod->Size = LdrGetMappedImageSize(process, base); + mod->EntryPoint = LdrGetEntryPoint(base); + KiReferenceProcessModule(mod, loadFlags); + if (registeredBaseOut) *registeredBaseOut = mod->Base; + KeReleaseSpinlock(&process->ModuleLock, flags); return STATUS_SUCCESS; } -// Implements the ldr Load Module For Process operation. -KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_t* baseOut) { - if (!process || !filename) return STATUS_INVALID_PARAMETER; +// Loads a DLL-like user module into a process and records the requested reference. +KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uint32_t loadFlags, uintptr_t* baseOut) { + if (!process || !filename || !loadFlags) return STATUS_INVALID_PARAMETER; filename = KiCanonicalModuleName(filename); - for (uint32_t i = 0; i < process->ModuleCount; i++) { - if (_stricmp(process->Modules[i].Name, filename) == 0) { - if (baseOut) *baseOut = process->Modules[i].Base; - return STATUS_SUCCESS; - } + uintptr_t flags = KeAcquireSpinlock(&process->ModuleLock); + PROCESS_MODULE* existing = KiFindProcessModuleByNameLocked(process, filename); + if (existing) { + KiReferenceProcessModule(existing, loadFlags); + if (baseOut) *baseOut = existing->Base; + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_SUCCESS; } + KeReleaseSpinlock(&process->ModuleLock, flags); FILE_OBJECT* f; KSTATUS s = IoOpenFile(SysSvFromCstr(filename), &f); @@ -750,7 +832,13 @@ KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_ return s; } - s = LdrRegisterProcessModule(process, filename, imageBase); + uintptr_t registeredBase = 0; + s = LdrRegisterProcessModule(process, filename, imageBase, loadFlags, ®isteredBase); + if (s == STATUS_ALREADY_EXISTS) { + KiFreeLoadedImageForProcess(process, imageBase, true); + imageBase = registeredBase; + s = STATUS_SUCCESS; + } if (!KSUCCESS(s)) { KiFreeLoadedImageForProcess(process, imageBase, true); return s; @@ -762,6 +850,84 @@ KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_ return STATUS_SUCCESS; } +// Releases one public module load reference and unmaps the image when no references remain. +KSTATUS LdrUnloadModuleForProcess(PROCESS* process, uintptr_t imageBase) { + if (!process || !imageBase) return STATUS_INVALID_PARAMETER; + + char name[PROCESS_MODULE_NAME_MAX]; + uintptr_t base = 0; + int shouldUnmap = 0; + + uintptr_t flags = KeAcquireSpinlock(&process->ModuleLock); + PROCESS_MODULE* mod = KiFindProcessModuleByBaseLocked(process, imageBase); + if (!mod) { + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_NOT_FOUND; + } + if (mod->PublicRefCount == 0) { + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_INVALID_PARAMETER; + } + + mod->PublicRefCount--; + if (mod->PublicRefCount == 0 && mod->ImportRefCount == 0) { + base = mod->Base; + strncpy(name, mod->Name, sizeof(name) - 1); + name[sizeof(name) - 1] = '\0'; + uint32_t index = (uint32_t)(mod - process->Modules); + if (index + 1 < process->ModuleCount) { + memmove(&process->Modules[index], + &process->Modules[index + 1], + (process->ModuleCount - index - 1) * sizeof(process->Modules[0])); + } + process->ModuleCount--; + shouldUnmap = 1; + } + KeReleaseSpinlock(&process->ModuleLock, flags); + + if (shouldUnmap) { + LdrDbg("LDR: unload module pid=%u name=%s base=%016llx\n", + process->ProcessId, name, (unsigned long long)base); + KiFreeLoadedImageForProcess(process, base, true); + } + + return STATUS_SUCCESS; +} + +// Queries one loaded process module by image base. +KSTATUS LdrQueryModuleForProcess(PROCESS* process, uintptr_t imageBase, SYS_MODULE_INFO* infoOut) { + if (!process || !imageBase || !infoOut) return STATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&process->ModuleLock); + PROCESS_MODULE* mod = KiFindProcessModuleByBaseLocked(process, imageBase); + if (!mod) { + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_NOT_FOUND; + } + KiFillProcessModuleInfo(mod, infoOut); + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_SUCCESS; +} + +// Copies a bounded snapshot of the process module table. +KSTATUS LdrCopyProcessModuleInfo(PROCESS* process, SYS_MODULE_INFO* infoOut, size_t maxCount, size_t* countOut) { + if (!process || !countOut) return STATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&process->ModuleLock); + size_t count = process->ModuleCount; + *countOut = count; + size_t toCopy = count; + if (toCopy > maxCount) toCopy = maxCount; + if (toCopy && !infoOut) { + KeReleaseSpinlock(&process->ModuleLock, flags); + return STATUS_INVALID_PARAMETER; + } + for (size_t i = 0; i < toCopy; i++) + KiFillProcessModuleInfo(&process->Modules[i], &infoOut[i]); + KeReleaseSpinlock(&process->ModuleLock, flags); + return (maxCount < count) ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS; +} + // Loads a boot/kernel PE image from a raw memory buffer into kernel space. static KSTATUS KiLdrLoadKernelImageFromBuffer(const void* buffer, size_t size, uintptr_t* baseOut) { if (!buffer || !size || !baseOut) return STATUS_INVALID_PARAMETER; diff --git a/code/kernel/net_socket.c b/code/kernel/net_socket.c index 7666926..6e3f958 100644 --- a/code/kernel/net_socket.c +++ b/code/kernel/net_socket.c @@ -2,25 +2,591 @@ #include #include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #define NET_SOCKET_IO_MAX_BYTES (1024u * 1024u) #define NET_SOCKET_UDP_MAX_BYTES 65507u #define NET_SOCKET_RAW_MAX_BYTES 65535u -// Implements the net Socket Destroy operation. +static SPINLOCK gNetIrpStatsLock = SPINLOCK_INIT; +static NET_IRP_STATS gNetIrpStats; + +// Records that a socket operation entered the IRP boundary. +static void NetIrpStatsSubmit(uint32_t operation) { + uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock); + gNetIrpStats.Submitted++; + if (operation < NetSocketIrpOperationCount) + gNetIrpStats.ByOperation[operation]++; + KeReleaseSpinlock(&gNetIrpStatsLock, flags); +} + +// Records that a submitted socket IRP returned pending to the socket facade. +static void NetIrpStatsPending(void) { + uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock); + gNetIrpStats.Pending++; + KeReleaseSpinlock(&gNetIrpStatsLock, flags); +} + +// Records the final status of a socket IRP after synchronous wait completion. +static void NetIrpStatsComplete(KSTATUS status, bool asynchronous) { + uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock); + gNetIrpStats.Completed++; + if (asynchronous) + gNetIrpStats.CompletedAsynchronously++; + else + gNetIrpStats.CompletedSynchronously++; + + if (status == STATUS_CANCELLED) + gNetIrpStats.Cancelled++; + else if (status == STATUS_WOULD_BLOCK) + gNetIrpStats.WouldBlock++; + else if (status == STATUS_TIMEOUT) + gNetIrpStats.Timeout++; + else if (KFAILED(status)) + gNetIrpStats.Failed++; + + KeReleaseSpinlock(&gNetIrpStatsLock, flags); +} + +// Returns a stable snapshot of socket IRP diagnostics. +__declspec(dllexport) void NetQueryIrpStats(NET_IRP_STATS* out) { + if (!out) + return; + + uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock); + *out = gNetIrpStats; + KeReleaseSpinlock(&gNetIrpStatsLock, flags); +} + +// Records that a public async socket request requested cancellation. +static void NetIrpStatsCancelRequested(void) { + uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock); + gNetIrpStats.CancelRequested++; + KeReleaseSpinlock(&gNetIrpStatsLock, flags); +} + +struct NET_ASYNC_SOCKET_REQUEST_OBJECT { + IO_ASYNC_REQUEST_OBJECT Async; + + PROCESS* OwnerProcess; + SOCKET_OBJECT* Socket; + uint32_t Operation; + uint32_t SocketFlags; + uint32_t Backlog; + uint32_t ShutdownHow; + uint32_t Option; + uint32_t PollEvents; + + void* KernelBuffer; + void* UserBuffer; + size_t Length; + + SYS_SOCKET_ADDRESS_K Address; + size_t AddressSize; + SYS_SOCKET_ADDRESS_K* AddressOut; + size_t* AddressSizeInOut; + + void* OptionValue; + void* UserOptionValue; + size_t OptionValueSize; + size_t* OptionActualSizeOut; + + uint32_t* HandleOut; + uint32_t* PollReturnedEventsOut; + + MM_LOCKED_USER_BUFFER* LockedAddressOut; + MM_LOCKED_USER_BUFFER* LockedAddressSize; + MM_LOCKED_USER_BUFFER* LockedOptionValue; + MM_LOCKED_USER_BUFFER* LockedOptionActualSize; + MM_LOCKED_USER_BUFFER* LockedHandleOut; + MM_LOCKED_USER_BUFFER* LockedPollReturnedEvents; +}; + + +static SPINLOCK gAsyncSocketInitLock = SPINLOCK_INIT; +static int gAsyncSocketInitState; +static SPINLOCK gAsyncSocketQueueLock = SPINLOCK_INIT; +static LIST_ENTRY gAsyncSocketQueue; +static SEMAPHORE* gAsyncSocketSemaphore; + +// Releases operation-specific user locks that the common async object does not own. +static void NetAsyncSocketReleaseExtraLocks(NET_ASYNC_SOCKET_REQUEST_OBJECT* req) { + if (!req) + return; + + if (req->LockedAddressOut) { + MmUnlockUserBuffer(req->LockedAddressOut); + req->LockedAddressOut = (MM_LOCKED_USER_BUFFER*)0; + } + if (req->LockedAddressSize) { + MmUnlockUserBuffer(req->LockedAddressSize); + req->LockedAddressSize = (MM_LOCKED_USER_BUFFER*)0; + } + if (req->LockedOptionValue) { + MmUnlockUserBuffer(req->LockedOptionValue); + req->LockedOptionValue = (MM_LOCKED_USER_BUFFER*)0; + } + if (req->LockedOptionActualSize) { + MmUnlockUserBuffer(req->LockedOptionActualSize); + req->LockedOptionActualSize = (MM_LOCKED_USER_BUFFER*)0; + } + if (req->LockedHandleOut) { + MmUnlockUserBuffer(req->LockedHandleOut); + req->LockedHandleOut = (MM_LOCKED_USER_BUFFER*)0; + } + if (req->LockedPollReturnedEvents) { + MmUnlockUserBuffer(req->LockedPollReturnedEvents); + req->LockedPollReturnedEvents = (MM_LOCKED_USER_BUFFER*)0; + } +} + +// Releases heap storage no longer needed after a socket operation has finished. +static void NetAsyncSocketReleaseKernelStorage(NET_ASYNC_SOCKET_REQUEST_OBJECT* req) { + if (!req) + return; + + if (req->KernelBuffer) { + MmFreePool(req->KernelBuffer); + req->KernelBuffer = (void*)0; + } + if (req->OptionValue) { + MmFreePool(req->OptionValue); + req->OptionValue = (void*)0; + } +} + +// Releases socket-specific references owned by an async socket request. +static void NetAsyncSocketRequestCleanup(IO_ASYNC_REQUEST_OBJECT* async, void* context) { + (void)context; + NET_ASYNC_SOCKET_REQUEST_OBJECT* req = + (NET_ASYNC_SOCKET_REQUEST_OBJECT*)async; + + NetAsyncSocketReleaseExtraLocks(req); + NetAsyncSocketReleaseKernelStorage(req); + + if (req->Socket) { + ObDereferenceObject(&req->Socket->Header); + req->Socket = (SOCKET_OBJECT*)0; + } + if (req->OwnerProcess) { + ObDereferenceObject(&req->OwnerProcess->Header); + req->OwnerProcess = (PROCESS*)0; + } +} + +// Cancels an async socket request from the shared async I/O handle. +static KSTATUS NetCancelAsyncSocketRequest(IO_ASYNC_REQUEST_OBJECT* async, void* context) { + (void)context; + if (!async) + return STATUS_INVALID_PARAMETER; + + NetIrpStatsCancelRequested(); + + uintptr_t flags = KeAcquireSpinlock(&gAsyncSocketQueueLock); + IO_ASYNC_REQUEST_STATE state = IoAsyncRequestGetState(async); + + if (state == IoAsyncRequestQueued) { + ListRemove(&async->Link); + IoAsyncRequestRequestCancel(async); + IoAsyncRequestSetState(async, IoAsyncRequestCancelled); + KeReleaseSpinlock(&gAsyncSocketQueueLock, flags); + + IoCompleteAsyncRequest(async, STATUS_CANCELLED, 0); + ObDereferenceObject(&async->Header); + return STATUS_SUCCESS; + } + + if (state == IoAsyncRequestRunning) { + IoAsyncRequestRequestCancel(async); + KeReleaseSpinlock(&gAsyncSocketQueueLock, flags); + return STATUS_PENDING; + } + + KeReleaseSpinlock(&gAsyncSocketQueueLock, flags); + return STATUS_OBJECT_DEAD; +} + +// Removes one queued async socket request for the worker. +static NET_ASYNC_SOCKET_REQUEST_OBJECT* NetAsyncSocketDequeue(void) { + uintptr_t flags = KeAcquireSpinlock(&gAsyncSocketQueueLock); + LIST_ENTRY* node = ListPopHead(&gAsyncSocketQueue); + if (!node) { + KeReleaseSpinlock(&gAsyncSocketQueueLock, flags); + return (NET_ASYNC_SOCKET_REQUEST_OBJECT*)0; + } + + NET_ASYNC_SOCKET_REQUEST_OBJECT* req = + (NET_ASYNC_SOCKET_REQUEST_OBJECT*)CONTAINING_RECORD(node, IO_ASYNC_REQUEST_OBJECT, Link); + if (IoAsyncRequestIsCancelRequested(&req->Async)) + IoAsyncRequestSetState(&req->Async, IoAsyncRequestCancelled); + else + IoAsyncRequestSetState(&req->Async, IoAsyncRequestRunning); + KeReleaseSpinlock(&gAsyncSocketQueueLock, flags); + return req; +} + +// Publishes a remote address output for accept and recvfrom. +static size_t NetAsyncSocketPublishAddress(NET_ASYNC_SOCKET_REQUEST_OBJECT* req, + const SYS_SOCKET_ADDRESS_K* address) { + if (!req || !req->AddressOut || !address) + return 0; + + memcpy(req->AddressOut, address, sizeof(*address)); + if (req->AddressSizeInOut) + *req->AddressSizeInOut = sizeof(*address); + return sizeof(*address); +} + +// Executes one queued async socket operation on the worker thread. +static void NetAsyncSocketExecute(NET_ASYNC_SOCKET_REQUEST_OBJECT* req, + KSTATUS* statusOut, + size_t* informationOut) { + KSTATUS status = STATUS_INVALID_PARAMETER; + size_t information = 0; + + if (!req || !statusOut || !informationOut) + return; + + switch (req->Operation) { + case SYS_ASYNC_SOCKET_OP_BIND: + status = NetSocketBind(req->Socket, &req->Address, req->AddressSize); + break; + + case SYS_ASYNC_SOCKET_OP_LISTEN: + status = NetSocketListen(req->Socket, req->Backlog); + break; + + case SYS_ASYNC_SOCKET_OP_ACCEPT: { + SOCKET_OBJECT* accepted = (SOCKET_OBJECT*)0; + SYS_SOCKET_ADDRESS_K remote; + memset(&remote, 0, sizeof(remote)); + status = NetSocketAccept(req->Socket, &accepted, req->AddressOut ? &remote : (SYS_SOCKET_ADDRESS_K*)0, &req->Async); + if (KSUCCESS(status)) { + uint32_t handle = 0; + status = KeAllocateHandleWithRights(req->OwnerProcess, + &accepted->Header, + HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, + &handle); + ObDereferenceObject(&accepted->Header); + if (KSUCCESS(status)) { + if (req->HandleOut) + *req->HandleOut = handle; + information = handle; + (void)NetAsyncSocketPublishAddress(req, &remote); + } + } + break; + } + + case SYS_ASYNC_SOCKET_OP_CONNECT: + status = NetSocketConnect(req->Socket, &req->Address, req->AddressSize, &req->Async); + break; + + case SYS_ASYNC_SOCKET_OP_SEND: { + size_t sent = 0; + status = NetSocketSend(req->Socket, req->KernelBuffer, req->Length, req->SocketFlags, &sent); + information = sent; + break; + } + + case SYS_ASYNC_SOCKET_OP_RECV: { + size_t received = 0; + status = NetSocketReceive(req->Socket, req->KernelBuffer, req->Length, req->SocketFlags, &received, &req->Async); + if (KSUCCESS(status) && received) + memcpy(req->UserBuffer, req->KernelBuffer, received); + information = received; + break; + } + + case SYS_ASYNC_SOCKET_OP_SEND_TO: { + size_t sent = 0; + status = NetSocketSendTo(req->Socket, req->KernelBuffer, req->Length, + req->SocketFlags, &req->Address, req->AddressSize, &sent); + information = sent; + break; + } + + case SYS_ASYNC_SOCKET_OP_RECV_FROM: { + size_t received = 0; + SYS_SOCKET_ADDRESS_K remote; + memset(&remote, 0, sizeof(remote)); + status = NetSocketReceiveFrom(req->Socket, req->KernelBuffer, req->Length, + req->SocketFlags, + req->AddressOut ? &remote : (SYS_SOCKET_ADDRESS_K*)0, + &received, + &req->Async); + if (KSUCCESS(status) && received) + memcpy(req->UserBuffer, req->KernelBuffer, received); + if (KSUCCESS(status)) + (void)NetAsyncSocketPublishAddress(req, &remote); + information = received; + break; + } + + case SYS_ASYNC_SOCKET_OP_SHUTDOWN: + status = NetSocketShutdown(req->Socket, req->ShutdownHow); + break; + + case SYS_ASYNC_SOCKET_OP_SET_OPT: + status = NetSocketSetOption(req->Socket, req->Option, req->OptionValue, req->OptionValueSize); + break; + + case SYS_ASYNC_SOCKET_OP_GET_OPT: { + size_t actual = 0; + status = NetSocketGetOption(req->Socket, req->Option, req->OptionValue, req->OptionValueSize, &actual); + if (KSUCCESS(status) && actual && req->UserOptionValue) + memcpy(req->UserOptionValue, req->OptionValue, actual); + if ((KSUCCESS(status) || status == STATUS_BUFFER_TOO_SMALL) && req->OptionActualSizeOut) + *req->OptionActualSizeOut = actual; + information = actual; + break; + } + + case SYS_ASYNC_SOCKET_OP_POLL: { + uint32_t returnedEvents = 0; + status = NetSocketPoll(req->Socket, req->PollEvents, &returnedEvents); + if (req->PollReturnedEventsOut) + *req->PollReturnedEventsOut = returnedEvents; + information = returnedEvents; + break; + } + + default: + status = STATUS_INVALID_PARAMETER; + break; + } + + *statusOut = status; + *informationOut = information; +} + +// Worker for public async socket requests. +static void NetAsyncSocketWorker(void* arg) { + (void)arg; + + for (;;) { + KSTATUS waitStatus = KeWaitSemaphore(gAsyncSocketSemaphore); + if (!KSUCCESS(waitStatus)) { + KeYield(); + continue; + } + + NET_ASYNC_SOCKET_REQUEST_OBJECT* req = NetAsyncSocketDequeue(); + if (!req) + continue; + + KSTATUS status = STATUS_CANCELLED; + size_t information = 0; + if (!IoAsyncRequestIsCancelRequested(&req->Async) && + IoAsyncRequestGetState(&req->Async) != IoAsyncRequestCancelled) { + NetAsyncSocketExecute(req, &status, &information); + } + + NetAsyncSocketReleaseExtraLocks(req); + NetAsyncSocketReleaseKernelStorage(req); + IoCompleteAsyncRequest(&req->Async, status, information); + ObDereferenceObject(&req->Async.Header); + } +} + +// Starts the async socket worker on first use. +static KSTATUS NetEnsureAsyncSocketWorkerStarted(void) { + uintptr_t flags = KeAcquireSpinlock(&gAsyncSocketInitLock); + if (gAsyncSocketInitState == 2) { + KeReleaseSpinlock(&gAsyncSocketInitLock, flags); + return STATUS_SUCCESS; + } + if (gAsyncSocketInitState == 1) { + KeReleaseSpinlock(&gAsyncSocketInitLock, flags); + while (gAsyncSocketInitState == 1) + KeYield(); + return (gAsyncSocketInitState == 2) ? STATUS_SUCCESS : STATUS_FAILURE; + } + + gAsyncSocketInitState = 1; + KeReleaseSpinlock(&gAsyncSocketInitLock, flags); + + LIST_INIT(&gAsyncSocketQueue); + + KSTATUS status = KeSemaphoreCreate(0, 0x7fffffff, &gAsyncSocketSemaphore); + if (KSUCCESS(status)) { + THREAD* worker = (THREAD*)0; + status = KeCreateKernelThread((uintptr_t)NetAsyncSocketWorker, + 0, + PriorityNormal, + &worker); + (void)worker; + } + + flags = KeAcquireSpinlock(&gAsyncSocketInitLock); + gAsyncSocketInitState = KSUCCESS(status) ? 2 : 0; + KeReleaseSpinlock(&gAsyncSocketInitLock, flags); + return status; +} + +// Submits a public async socket request to the socket worker. +KSTATUS NetSocketSubmitAsyncKernel(const NET_ASYNC_SOCKET_SUBMIT* submit, + NET_ASYNC_SOCKET_REQUEST_OBJECT** requestOut) { + if (requestOut) + *requestOut = (NET_ASYNC_SOCKET_REQUEST_OBJECT*)0; + if (!submit || !submit->OwnerProcess || !submit->Socket || !submit->Iosb) + return STATUS_INVALID_PARAMETER; + + KSTATUS status = NetEnsureAsyncSocketWorkerStarted(); + if (!KSUCCESS(status)) + return status; + + IO_ASYNC_REQUEST_OBJECT* async = (IO_ASYNC_REQUEST_OBJECT*)0; + status = IoCreateAsyncRequest(sizeof(NET_ASYNC_SOCKET_REQUEST_OBJECT), + NetAsyncSocketRequestCleanup, + (void*)0, + NetCancelAsyncSocketRequest, + (void*)0, + &async); + if (!KSUCCESS(status)) + return status; + + NET_ASYNC_SOCKET_REQUEST_OBJECT* req = (NET_ASYNC_SOCKET_REQUEST_OBJECT*)async; + req->OwnerProcess = submit->OwnerProcess; + req->Socket = submit->Socket; + req->Operation = submit->Operation; + req->SocketFlags = submit->SocketFlags; + req->Backlog = submit->Backlog; + req->ShutdownHow = submit->ShutdownHow; + req->Option = submit->Option; + req->PollEvents = submit->PollEvents; + req->KernelBuffer = submit->KernelBuffer; + req->UserBuffer = submit->UserBuffer; + req->Length = submit->Length; + req->Address = submit->Address; + req->AddressSize = submit->AddressSize; + req->AddressOut = submit->AddressOut; + req->AddressSizeInOut = submit->AddressSizeInOut; + req->OptionValue = submit->OptionValue; + req->UserOptionValue = submit->UserOptionValue; + req->OptionValueSize = submit->OptionValueSize; + req->OptionActualSizeOut = submit->OptionActualSizeOut; + req->HandleOut = submit->HandleOut; + req->PollReturnedEventsOut = submit->PollReturnedEventsOut; + req->LockedAddressOut = submit->LockedAddressOut; + req->LockedAddressSize = submit->LockedAddressSize; + req->LockedOptionValue = submit->LockedOptionValue; + req->LockedOptionActualSize = submit->LockedOptionActualSize; + req->LockedHandleOut = submit->LockedHandleOut; + req->LockedPollReturnedEvents = submit->LockedPollReturnedEvents; + + ObReferenceObject(&req->OwnerProcess->Header); + ObReferenceObject(&req->Socket->Header); + IoInitializeAsyncRequestCompletion(&req->Async, + submit->Iosb, + submit->CompletionEvent, + submit->CompletionApc, + submit->LockedBuffer, + submit->LockedIosb); + + ObReferenceObject(&req->Async.Header); + uintptr_t qflags = KeAcquireSpinlock(&gAsyncSocketQueueLock); + ListInsertTail(&gAsyncSocketQueue, &req->Async.Link); + KeReleaseSpinlock(&gAsyncSocketQueueLock, qflags); + KeSignalSemaphore(gAsyncSocketSemaphore); + + if (requestOut) + *requestOut = req; + else + ObDereferenceObject(&req->Async.Header); + return STATUS_PENDING; +} + +// Returns the object header embedded in a socket async request. +OBJECT* NetAsyncSocketRequestObject(NET_ASYNC_SOCKET_REQUEST_OBJECT* request) { + return request ? &request->Async.Header : (OBJECT*)0; +} + +// Submits a socket provider request through the active stack DEVICE_OBJECT. +static KSTATUS NetSubmitSocketIrp(const NET_STACK_PROVIDER* provider, + NET_SOCKET_IRP_REQUEST* request, + IO_ASYNC_REQUEST_OBJECT* async) { + if (!provider || !provider->StackDevice || !request) + return STATUS_INVALID_PARAMETER; + if (request->Size != sizeof(*request)) + return STATUS_INVALID_PARAMETER; + if (request->Operation == NetSocketIrpInvalid || + request->Operation >= NetSocketIrpOperationCount) + return STATUS_INVALID_PARAMETER; + + request->CancelRequested = async ? &async->CancelRequested : (volatile uint8_t*)0; + + EVENT* event = NULL; + KSTATUS st = KeEventCreate(EventAutoReset, 0, &event); + if (!KSUCCESS(st)) + return st; + + IRP irp; + IoInitializeIrp(&irp, 1); + IoBuildDeviceIoControlIrp(&irp, + IRP_MJ_INTERNAL_DEVICE_CONTROL, + NET_IOCTL_SOCKET, + request, + sizeof(*request), + NULL, + 0); + irp.CompletionEvent = event; + + NetIrpStatsSubmit(request->Operation); + st = IoSubmitIrp(provider->StackDevice, &irp); + + bool asynchronous = false; + if (st == STATUS_PENDING) { + asynchronous = true; + NetIrpStatsPending(); + (void)KeWaitForEvent(event, 0); + st = irp.Status; + } + + NetIrpStatsComplete(st, asynchronous); + ObDereferenceObject(&event->Header); + return st; +} + + +// Closes a provider socket through the IRP path while object teardown is still safe. +static void NetSocketCloseProvider(const NET_STACK_PROVIDER* provider, void* providerSocket) { + if (!provider || !providerSocket) + return; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpClose; + request.u.Close.Socket = providerSocket; + (void)NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); +} + +// Releases provider state owned by a process socket object. static void NetSocketDestroy(OBJECT* object) { SOCKET_OBJECT* socket = (SOCKET_OBJECT*)object; if (socket->Provider && socket->ProviderSocket) { - socket->Provider->SocketClose(socket->ProviderSocket); + NetSocketCloseProvider(socket->Provider, socket->ProviderSocket); socket->ProviderSocket = NULL; } } +// Allocates a process-visible socket object around provider-owned state. static KSTATUS NetSocketAllocate(const NET_STACK_PROVIDER* provider, void* providerSocket, uint32_t domain, @@ -44,6 +610,7 @@ static KSTATUS NetSocketAllocate(const NET_STACK_PROVIDER* provider, return STATUS_SUCCESS; } +// Returns the provider and provider-private state behind a live socket object. static KSTATUS NetSocketGetProviderSocket(SOCKET_OBJECT* socket, const NET_STACK_PROVIDER** providerOut, void** providerSocketOut) { @@ -57,7 +624,7 @@ static KSTATUS NetSocketGetProviderSocket(SOCKET_OBJECT* socket, return STATUS_SUCCESS; } -// Implements the net Socket Create operation. +// Creates a socket by submitting a create request to the active stack device. KSTATUS NetSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, SOCKET_OBJECT** out) { if (!out) return STATUS_INVALID_PARAMETER; @@ -68,7 +635,16 @@ KSTATUS NetSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, SOCKE return STATUS_NOT_SUPPORTED; void* providerSocket = NULL; - KSTATUS st = provider->SocketCreate(domain, type, protocol, &providerSocket); + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpCreate; + request.u.Create.Domain = domain; + request.u.Create.Type = type; + request.u.Create.Protocol = protocol; + request.u.Create.ProviderSocketOut = &providerSocket; + + KSTATUS st = NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); if (!KSUCCESS(st)) return st; if (!providerSocket) @@ -76,28 +652,49 @@ KSTATUS NetSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, SOCKE st = NetSocketAllocate(provider, providerSocket, domain, type, protocol, out); if (!KSUCCESS(st)) - provider->SocketClose(providerSocket); + NetSocketCloseProvider(provider, providerSocket); return st; } -// Implements the net Socket Bind operation. +// Binds a socket address through the stack device IRP path. KSTATUS NetSocketBind(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->Bind(providerSocket, address, addressSize) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpBind; + request.u.Bind.Socket = providerSocket; + request.u.Bind.Address = address; + request.u.Bind.AddressSize = addressSize; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -// Implements the net Socket Listen operation. +// Marks a stream socket as listening through the stack device IRP path. KSTATUS NetSocketListen(SOCKET_OBJECT* socket, uint32_t backlog) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->Listen(providerSocket, backlog) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpListen; + request.u.Listen.Socket = providerSocket; + request.u.Listen.Backlog = backlog; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -// Implements the net Socket Accept operation. -KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut) { +// Accepts a provider socket through IRP and wraps the result in a process socket object. +KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, + SYS_SOCKET_ADDRESS_K* remoteOut, + IO_ASYNC_REQUEST_OBJECT* async) { if (!acceptedOut) return STATUS_INVALID_PARAMETER; *acceptedOut = NULL; @@ -109,7 +706,15 @@ KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_ return st; void* acceptedProviderSocket = NULL; - st = provider->Accept(providerSocket, &acceptedProviderSocket, remoteOut); + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpAccept; + request.u.Accept.Socket = providerSocket; + request.u.Accept.AcceptedOut = &acceptedProviderSocket; + request.u.Accept.RemoteOut = remoteOut; + + st = NetSubmitSocketIrp(provider, &request, async); if (!KSUCCESS(st)) return st; if (!acceptedProviderSocket) @@ -122,19 +727,32 @@ KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut, SYS_ socket->Protocol, acceptedOut); if (!KSUCCESS(st)) - provider->SocketClose(acceptedProviderSocket); + NetSocketCloseProvider(provider, acceptedProviderSocket); return st; } -// Implements the net Socket Connect operation. -KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) { + +// Connects a socket through the stack device IRP path. +KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, + size_t addressSize, IO_ASYNC_REQUEST_OBJECT* async) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->Connect(providerSocket, address, addressSize) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpConnect; + request.u.Connect.Socket = providerSocket; + request.u.Connect.Address = address; + request.u.Connect.AddressSize = addressSize; + return NetSubmitSocketIrp(provider, &request, async); } -// Implements the net Socket Send operation. + +// Sends stream bytes through the stack device IRP path. KSTATUS NetSocketSend(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut) { if (sentOut) *sentOut = 0; @@ -146,11 +764,25 @@ KSTATUS NetSocketSend(SOCKET_OBJECT* socket, const void* buffer, size_t length, const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->Send(providerSocket, buffer, length, flags, sentOut) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpSend; + request.u.Send.Socket = providerSocket; + request.u.Send.Buffer = buffer; + request.u.Send.Length = length; + request.u.Send.Flags = flags; + request.u.Send.SentOut = sentOut; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -// Implements the net Socket Receive operation. -KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut) { +// Receives stream bytes through the stack device IRP path. +KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length, + uint32_t flags, size_t* receivedOut, + IO_ASYNC_REQUEST_OBJECT* async) { if (receivedOut) *receivedOut = 0; if (!buffer && length) @@ -161,9 +793,23 @@ KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length, uin const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->Receive(providerSocket, buffer, length, flags, receivedOut) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpReceive; + request.u.Receive.Socket = providerSocket; + request.u.Receive.Buffer = buffer; + request.u.Receive.Length = length; + request.u.Receive.Flags = flags; + request.u.Receive.ReceivedOut = receivedOut; + return NetSubmitSocketIrp(provider, &request, async); } + +// Sends datagram bytes through the stack device IRP path. KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut) { if (sentOut) @@ -180,11 +826,27 @@ KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->SendTo(providerSocket, buffer, length, flags, remote, remoteSize, sentOut) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpSendTo; + request.u.SendTo.Socket = providerSocket; + request.u.SendTo.Buffer = buffer; + request.u.SendTo.Length = length; + request.u.SendTo.Flags = flags; + request.u.SendTo.Remote = remote; + request.u.SendTo.RemoteSize = remoteSize; + request.u.SendTo.SentOut = sentOut; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, uint32_t flags, - SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut) { +// Receives datagram bytes through the stack device IRP path. +KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, + uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut, + size_t* receivedOut, IO_ASYNC_REQUEST_OBJECT* async) { if (receivedOut) *receivedOut = 0; if (!buffer && length) @@ -199,34 +861,80 @@ KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length, const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->ReceiveFrom(providerSocket, buffer, length, flags, remoteOut, receivedOut) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpReceiveFrom; + request.u.ReceiveFrom.Socket = providerSocket; + request.u.ReceiveFrom.Buffer = buffer; + request.u.ReceiveFrom.Length = length; + request.u.ReceiveFrom.Flags = flags; + request.u.ReceiveFrom.RemoteOut = remoteOut; + request.u.ReceiveFrom.ReceivedOut = receivedOut; + return NetSubmitSocketIrp(provider, &request, async); } -// Implements the net Socket Shutdown operation. + +// Shuts down one or both socket directions through the stack device IRP path. KSTATUS NetSocketShutdown(SOCKET_OBJECT* socket, uint32_t how) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->Shutdown(providerSocket, how) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpShutdown; + request.u.Shutdown.Socket = providerSocket; + request.u.Shutdown.How = how; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -// Implements the net Socket Set Option operation. +// Sets a socket option through the stack device IRP path. KSTATUS NetSocketSetOption(SOCKET_OBJECT* socket, uint32_t option, const void* value, size_t valueSize) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->SetOption(providerSocket, option, value, valueSize) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpSetOption; + request.u.SetOption.Socket = providerSocket; + request.u.SetOption.Option = option; + request.u.SetOption.Value = value; + request.u.SetOption.ValueSize = valueSize; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -// Implements the net Socket Get Option operation. +// Reads a socket option through the stack device IRP path. KSTATUS NetSocketGetOption(SOCKET_OBJECT* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut) { const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->GetOption(providerSocket, option, value, valueSize, actualSizeOut) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpGetOption; + request.u.GetOption.Socket = providerSocket; + request.u.GetOption.Option = option; + request.u.GetOption.Value = value; + request.u.GetOption.ValueSize = valueSize; + request.u.GetOption.ActualSizeOut = actualSizeOut; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -// Implements the net Socket Poll operation. +// Polls socket readiness through the stack device IRP path. KSTATUS NetSocketPoll(SOCKET_OBJECT* socket, uint32_t events, uint32_t* returnedEventsOut) { if (!returnedEventsOut) return STATUS_INVALID_PARAMETER; @@ -235,10 +943,20 @@ KSTATUS NetSocketPoll(SOCKET_OBJECT* socket, uint32_t events, uint32_t* returned const NET_STACK_PROVIDER* provider = NULL; void* providerSocket = NULL; KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket); - return KSUCCESS(st) ? provider->Poll(providerSocket, events, returnedEventsOut) : st; + if (!KSUCCESS(st)) + return st; + + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpPoll; + request.u.Poll.Socket = providerSocket; + request.u.Poll.Events = events; + request.u.Poll.ReturnedEventsOut = returnedEventsOut; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } -// Implements the net Resolve Host operation. +// Resolves a host name through the stack device IRP path. KSTATUS NetResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut) { if (!name || !addressOut) return STATUS_INVALID_PARAMETER; @@ -247,5 +965,12 @@ KSTATUS NetResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* if (!provider) return STATUS_NOT_SUPPORTED; - return provider->ResolveHost(name, family, addressOut); + NET_SOCKET_IRP_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Operation = NetSocketIrpResolveHost; + request.u.ResolveHost.Name = name; + request.u.ResolveHost.Family = family; + request.u.ResolveHost.AddressOut = addressOut; + return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0); } diff --git a/code/kernel/net_stack.c b/code/kernel/net_stack.c index 24e4d41..368b5ec 100644 --- a/code/kernel/net_stack.c +++ b/code/kernel/net_stack.c @@ -6,6 +6,7 @@ #include #include #include +#include static const NET_STACK_PROVIDER* gNetStackProvider = 0; static SPINLOCK gNetStackLock = SPINLOCK_INIT; @@ -15,13 +16,12 @@ static KSTATUS NetValidateStackProvider(const NET_STACK_PROVIDER* p) { if (!p || p->Version != NET_STACK_PROVIDER_VERSION || !p->Name) return STATUS_INVALID_PARAMETER; - if (!p->AttachDevice || !p->ReceiveFrame || !p->SocketCreate || !p->SocketClose || - !p->Bind || !p->Listen || !p->Accept || !p->Connect || - !p->Send || !p->Receive || !p->SendTo || !p->ReceiveFrom || - !p->Shutdown || !p->SetOption || !p->GetOption || !p->Poll || - !p->ResolveHost) { + if (!p->StackDevice || !p->AttachDevice || !p->ReceiveFrame) + return STATUS_INVALID_PARAMETER; + + if (!p->StackDevice->DriverObject || + !p->StackDevice->DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL]) return STATUS_INVALID_PARAMETER; - } return STATUS_SUCCESS; } diff --git a/code/kernel/ob_object.c b/code/kernel/ob_object.c index 2445529..36ac075 100644 --- a/code/kernel/ob_object.c +++ b/code/kernel/ob_object.c @@ -27,6 +27,7 @@ static uint32_t KiObjectPoolTag(OBJECT_TYPE type) { case ObjectTypeInputQueue: return MM_POOL_TAG('i', 'n', 'p', 'q'); case ObjectTypeImageSection: return MM_POOL_TAG('i', 'm', 's', 'e'); case ObjectTypeTimer: return MM_POOL_TAG('t', 'm', 'r', ' '); + case ObjectTypeModule: return MM_POOL_TAG('m', 'o', 'd', 'u'); default: return MM_POOL_TAG('o', 'b', 'j', ' '); } } diff --git a/code/public/ntlite/ddi/net_stack.h b/code/public/ntlite/ddi/net_stack.h index 7331aaa..cf5f35f 100644 --- a/code/public/ntlite/ddi/net_stack.h +++ b/code/public/ntlite/ddi/net_stack.h @@ -4,7 +4,8 @@ // net_stack.h - provider interface between kernel socket facade and network stack drivers. // The kernel owns process handles, user-memory copying, NET_DEVICE registration, -// and SOCKET_OBJECT lifetime. A stack driver owns protocol implementation. +// and SOCKET_OBJECT lifetime. A stack driver owns protocol implementation behind +// a DEVICE_OBJECT so socket operations enter the same IRP pipe as other I/O. #include #include @@ -12,39 +13,165 @@ #include #include -#define NET_STACK_PROVIDER_VERSION 1u +struct DEVICE_OBJECT; + +#define NET_STACK_PROVIDER_VERSION 2u +#define NET_IOCTL_SOCKET 0x4E455401u + +typedef enum NET_SOCKET_IRP_OPERATION { + NetSocketIrpInvalid = 0, + NetSocketIrpCreate = 1, + NetSocketIrpClose = 2, + NetSocketIrpBind = 3, + NetSocketIrpListen = 4, + NetSocketIrpAccept = 5, + NetSocketIrpConnect = 6, + NetSocketIrpSend = 7, + NetSocketIrpReceive = 8, + NetSocketIrpSendTo = 9, + NetSocketIrpReceiveFrom = 10, + NetSocketIrpShutdown = 11, + NetSocketIrpSetOption = 12, + NetSocketIrpGetOption = 13, + NetSocketIrpPoll = 14, + NetSocketIrpResolveHost = 15, + NetSocketIrpOperationCount, +} NET_SOCKET_IRP_OPERATION; + +typedef struct NET_SOCKET_IRP_REQUEST { + uint32_t Size; + uint32_t Operation; + volatile uint8_t* CancelRequested; + + union { + struct { + uint32_t Domain; + uint32_t Type; + uint32_t Protocol; + void** ProviderSocketOut; + } Create; + + struct { + void* Socket; + } Close; + + struct { + void* Socket; + const SYS_SOCKET_ADDRESS_K* Address; + size_t AddressSize; + } Bind; + + struct { + void* Socket; + uint32_t Backlog; + } Listen; + + struct { + void* Socket; + void** AcceptedOut; + SYS_SOCKET_ADDRESS_K* RemoteOut; + } Accept; + + struct { + void* Socket; + const SYS_SOCKET_ADDRESS_K* Address; + size_t AddressSize; + } Connect; + + struct { + void* Socket; + const void* Buffer; + size_t Length; + uint32_t Flags; + size_t* SentOut; + } Send; + + struct { + void* Socket; + void* Buffer; + size_t Length; + uint32_t Flags; + size_t* ReceivedOut; + } Receive; + + struct { + void* Socket; + const void* Buffer; + size_t Length; + uint32_t Flags; + const SYS_SOCKET_ADDRESS_K* Remote; + size_t RemoteSize; + size_t* SentOut; + } SendTo; + + struct { + void* Socket; + void* Buffer; + size_t Length; + uint32_t Flags; + SYS_SOCKET_ADDRESS_K* RemoteOut; + size_t* ReceivedOut; + } ReceiveFrom; + + struct { + void* Socket; + uint32_t How; + } Shutdown; + + struct { + void* Socket; + uint32_t Option; + const void* Value; + size_t ValueSize; + } SetOption; + + struct { + void* Socket; + uint32_t Option; + void* Value; + size_t ValueSize; + size_t* ActualSizeOut; + } GetOption; + + struct { + void* Socket; + uint32_t Events; + uint32_t* ReturnedEventsOut; + } Poll; + + struct { + const char* Name; + uint32_t Family; + SYS_SOCKET_ADDRESS_K* AddressOut; + } ResolveHost; + } u; +} NET_SOCKET_IRP_REQUEST; + +typedef struct NET_IRP_STATS { + uint64_t Submitted; + uint64_t Completed; + uint64_t CompletedSynchronously; + uint64_t CompletedAsynchronously; + uint64_t Pending; + uint64_t CancelRequested; + uint64_t Cancelled; + uint64_t WouldBlock; + uint64_t Timeout; + uint64_t Failed; + uint64_t ByOperation[NetSocketIrpOperationCount]; +} NET_IRP_STATS; typedef struct NET_STACK_PROVIDER NET_STACK_PROVIDER; struct NET_STACK_PROVIDER { uint32_t Version; const char* Name; + struct DEVICE_OBJECT* StackDevice; KSTATUS (*AttachDevice)(NET_DEVICE* device); KSTATUS (*ReceiveFrame)(NET_DEVICE* device, const void* frame, size_t length); - - KSTATUS (*SocketCreate)(uint32_t domain, uint32_t type, uint32_t protocol, void** outSocket); - void (*SocketClose)(void* socket); - - KSTATUS (*Bind)(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); - KSTATUS (*Listen)(void* socket, uint32_t backlog); - KSTATUS (*Accept)(void* socket, void** acceptedOut, SYS_SOCKET_ADDRESS_K* remoteOut); - KSTATUS (*Connect)(void* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize); - - KSTATUS (*Send)(void* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); - KSTATUS (*Receive)(void* socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); - KSTATUS (*SendTo)(void* socket, const void* buffer, size_t length, uint32_t flags, - const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut); - KSTATUS (*ReceiveFrom)(void* socket, void* buffer, size_t length, uint32_t flags, - SYS_SOCKET_ADDRESS_K* remoteOut, size_t* receivedOut); - - KSTATUS (*Shutdown)(void* socket, uint32_t how); - KSTATUS (*SetOption)(void* socket, uint32_t option, const void* value, size_t valueSize); - KSTATUS (*GetOption)(void* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut); - KSTATUS (*Poll)(void* socket, uint32_t events, uint32_t* returnedEventsOut); - - KSTATUS (*ResolveHost)(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut); }; __declspec(dllexport) KSTATUS NetRegisterStackProvider(const NET_STACK_PROVIDER* provider); __declspec(dllexport) const NET_STACK_PROVIDER* NetGetStackProvider(void); +__declspec(dllexport) void NetQueryIrpStats(NET_IRP_STATS* out); diff --git a/code/public/ntlite/syscall.h b/code/public/ntlite/syscall.h index f6f6a31..24b49d7 100644 --- a/code/public/ntlite/syscall.h +++ b/code/public/ntlite/syscall.h @@ -431,6 +431,8 @@ typedef struct SYS_PROCESS_INFO { uint32_t ThreadCount; uint32_t HandleCount; + // ModuleCount is the number of DLL-like process modules recorded by the loader. + // The main executable image is reported separately through ImageBase/ImageSize. uint32_t ModuleCount; uint32_t CurrentDrive; @@ -452,6 +454,54 @@ uint32_t SysGetProcessList(SYS_PROCESS_INFO* processesOut, size_t maxCount, size uint32_t SysGetProcessPid(uint32_t handle, uint32_t* pidOut); uint32_t SysGetThreadList(uint32_t pid, SYS_THREAD_INFO* threadsOut, size_t maxCount, size_t* countOut); +#define SYS_MODULE_NAME_MAX 64u +#define SYS_MODULE_INFO_VERSION 1u + +#define SYS_LOAD_MODULE_FLAG_NONE 0u + +#define SYS_MODULE_FLAG_PUBLIC_REF 0x00000001u +#define SYS_MODULE_FLAG_IMPORT_PINNED 0x00000002u +#define SYS_MODULE_FLAG_UNLOADABLE 0x00000004u + +typedef struct SYS_MODULE_INFO { + uint32_t Size; + uint32_t Version; + uint32_t Flags; + uint32_t Reserved; + + uintptr_t Base; + size_t ImageSize; + uintptr_t EntryPoint; + + uint32_t PublicRefCount; + uint32_t ImportRefCount; + uint32_t TotalRefCount; + uint32_t Reserved2; + + char Name[SYS_MODULE_NAME_MAX]; +} SYS_MODULE_INFO; + +typedef struct SYS_LOAD_MODULE_REQUEST { + uint32_t Size; + uint32_t Flags; + SYS_STRING_VIEW Path; + uint32_t* ModuleHandleOut; + SYS_MODULE_INFO* InfoOut; +} SYS_LOAD_MODULE_REQUEST; + +typedef struct SYS_GET_MODULE_EXPORT_REQUEST { + uint32_t Size; + uint32_t ModuleHandle; + SYS_STRING_VIEW Name; + uintptr_t* AddressOut; +} SYS_GET_MODULE_EXPORT_REQUEST; + +uint32_t SysLoadModule(const SYS_LOAD_MODULE_REQUEST* request); +uint32_t SysUnloadModule(uint32_t moduleHandle); +uint32_t SysGetModuleInfo(uint32_t moduleHandle, SYS_MODULE_INFO* infoOut); +uint32_t SysGetModuleExport(const SYS_GET_MODULE_EXPORT_REQUEST* request); +uint32_t SysGetModuleList(SYS_MODULE_INFO* modulesOut, size_t maxCount, size_t* countOut); + uint32_t SysCreateThread(const SYS_CREATE_THREAD_REQUEST* request); uint32_t SysGetCurrentTid(void); uint32_t SysGetCurrentPid(void); @@ -722,6 +772,60 @@ typedef struct SYS_POLLFD { uint32_t ReturnedEvents; } SYS_POLLFD; +#define SYS_ASYNC_SOCKET_FLAG_NONE 0x00000000u + +#define SYS_ASYNC_SOCKET_OP_BIND 1u +#define SYS_ASYNC_SOCKET_OP_LISTEN 2u +#define SYS_ASYNC_SOCKET_OP_ACCEPT 3u +#define SYS_ASYNC_SOCKET_OP_CONNECT 4u +#define SYS_ASYNC_SOCKET_OP_SEND 5u +#define SYS_ASYNC_SOCKET_OP_RECV 6u +#define SYS_ASYNC_SOCKET_OP_SEND_TO 7u +#define SYS_ASYNC_SOCKET_OP_RECV_FROM 8u +#define SYS_ASYNC_SOCKET_OP_SHUTDOWN 9u +#define SYS_ASYNC_SOCKET_OP_SET_OPT 10u +#define SYS_ASYNC_SOCKET_OP_GET_OPT 11u +#define SYS_ASYNC_SOCKET_OP_POLL 12u + +// Async socket requests return operation-specific byte counts through Iosb. +// Information is the byte count for send/receive, the accepted handle for +// accept, returned poll events for poll, and the actual option byte count for +// get-option. RequestHandleOut receives a waitable/cancellable async I/O handle +// when the request is queued. A zero handle means the operation failed before +// queueing. +typedef struct SYS_ASYNC_SOCKET_REQUEST { + uint32_t Size; + uint32_t SocketHandle; + uint32_t EventHandle; + uint32_t Operation; + uint32_t Flags; + uint32_t SocketFlags; + uint32_t Backlog; + uint32_t ShutdownHow; + uint32_t Option; + uint32_t PollEvents; + + void* Buffer; + size_t Length; + + const SYS_SOCKET_ADDRESS* Address; + size_t AddressSize; + SYS_SOCKET_ADDRESS* AddressOut; + size_t* AddressSizeInOut; + + void* OptionValue; + size_t OptionValueSize; + size_t* OptionActualSizeOut; + + uint32_t* HandleOut; + uint32_t* PollReturnedEventsOut; + + SYS_IO_STATUS_BLOCK* Iosb; + SYS_USER_APC_ROUTINE ApcRoutine; + void* ApcContext; + uint32_t* RequestHandleOut; +} SYS_ASYNC_SOCKET_REQUEST; + uint32_t SysCreateSocket(uint32_t domain, uint32_t type, uint32_t protocol, uint32_t* handleOut); uint32_t SysBindSocket(uint32_t socket, const SYS_SOCKET_ADDRESS* address, size_t addressSize); uint32_t SysListenSocket(uint32_t socket, uint32_t backlog); @@ -736,6 +840,7 @@ uint32_t SysResolveHost(const SYS_RESOLVE_HOST_REQUEST* request); uint32_t SysSetSocketOption(uint32_t socket, const SYS_SOCKET_OPTION_REQUEST* request); uint32_t SysGetSocketOption(uint32_t socket, const SYS_SOCKET_OPTION_REQUEST* request); uint32_t SysPoll(SYS_POLLFD* fds, size_t count, uint32_t timeoutMs, size_t* readyOut); +uint32_t SysSocketIoAsync(const SYS_ASYNC_SOCKET_REQUEST* request); typedef struct SYS_MEMORY_STATUS { uint64_t FreePhysicalBytes; @@ -760,6 +865,7 @@ typedef struct SYS_MEMORY_STATUS { #define SYS_KERNEL_STATS_SURFACE 9u #define SYS_KERNEL_STATS_FILESYSTEM 10u #define SYS_KERNEL_STATS_DISK 11u +#define SYS_KERNEL_STATS_NET_IRP 12u // Public stats payloads start with Size and Version. Size is the byte count // returned for that payload, including any trailing records. Version describes @@ -1007,6 +1113,40 @@ typedef struct SYS_DISK_STATS { uint64_t MaxReadaheadSectors; } SYS_DISK_STATS; +#define SYS_NET_IRP_OPERATION_COUNT 16u +#define SYS_NET_IRP_OP_INVALID 0u +#define SYS_NET_IRP_OP_CREATE 1u +#define SYS_NET_IRP_OP_CLOSE 2u +#define SYS_NET_IRP_OP_BIND 3u +#define SYS_NET_IRP_OP_LISTEN 4u +#define SYS_NET_IRP_OP_ACCEPT 5u +#define SYS_NET_IRP_OP_CONNECT 6u +#define SYS_NET_IRP_OP_SEND 7u +#define SYS_NET_IRP_OP_RECEIVE 8u +#define SYS_NET_IRP_OP_SEND_TO 9u +#define SYS_NET_IRP_OP_RECEIVE_FROM 10u +#define SYS_NET_IRP_OP_SHUTDOWN 11u +#define SYS_NET_IRP_OP_SET_OPTION 12u +#define SYS_NET_IRP_OP_GET_OPTION 13u +#define SYS_NET_IRP_OP_POLL 14u +#define SYS_NET_IRP_OP_RESOLVE_HOST 15u + +typedef struct SYS_NET_IRP_STATS { + uint32_t Size; + uint32_t Version; + uint64_t Submitted; + uint64_t Completed; + uint64_t CompletedSynchronously; + uint64_t CompletedAsynchronously; + uint64_t Pending; + uint64_t CancelRequested; + uint64_t Cancelled; + uint64_t WouldBlock; + uint64_t Timeout; + uint64_t Failed; + uint64_t ByOperation[SYS_NET_IRP_OPERATION_COUNT]; +} SYS_NET_IRP_STATS; + typedef struct SYS_SWAP_STATS { uint32_t Size; uint32_t Version; diff --git a/code/user/apps/shell_posix/shell_posix.c b/code/user/apps/shell_posix/shell_posix.c index f962360..7734fd2 100644 --- a/code/user/apps/shell_posix/shell_posix.c +++ b/code/user/apps/shell_posix/shell_posix.c @@ -26,7 +26,6 @@ extern int waitpid(int pid, int *status, int options); extern void exit(int status); extern void clearscreen(void); extern void clearkeyboard(void); -extern void PosixFdInit(void); static int hIn = 0; static int hOut = 1; @@ -176,7 +175,6 @@ static int runCommand(const char* cmd) { // Runs the requested operation. int main(void) { - PosixFdInit(); const char* cmdline = SysGetPeb()->CommandLine.Data; char lineBuf[MAX_CMD]; diff --git a/code/user/personalities/posix/posix.h b/code/user/personalities/posix/posix.h index 6db5764..1d74c3d 100644 --- a/code/user/personalities/posix/posix.h +++ b/code/user/personalities/posix/posix.h @@ -84,6 +84,12 @@ extern int errno; // defined in posix_fd.c; single global in v1 #define MS_ASYNC 0x01 #define MS_INVALIDATE 0x02 +// Dynamic loader flags. +#define RTLD_LAZY 0x0001 +#define RTLD_NOW 0x0002 +#define RTLD_LOCAL 0x0004 +#define RTLD_GLOBAL 0x0008 + // Scalar types. typedef int32_t pid_t; typedef int32_t off_t; @@ -131,6 +137,11 @@ __declspec(dllexport) void* mmap(void* addr, size_t length, int prot, int flags, __declspec(dllexport) int munmap(void* addr, size_t length); __declspec(dllexport) int msync(void* addr, size_t length, int flags); +__declspec(dllexport) void* dlopen(const char* filename, int flags); +__declspec(dllexport) void* dlsym(void* handle, const char* symbol); +__declspec(dllexport) int dlclose(void* handle); +__declspec(dllexport) char* dlerror(void); + // Sockets. typedef uint16_t sa_family_t; typedef uint16_t in_port_t; diff --git a/code/user/personalities/posix/posix_dlfcn.c b/code/user/personalities/posix/posix_dlfcn.c new file mode 100644 index 0000000..590047d --- /dev/null +++ b/code/user/personalities/posix/posix_dlfcn.c @@ -0,0 +1,118 @@ +// POSIX compatibility support for dynamic module loading. + +#include +#include +#include +#include "posix.h" +#include "posix_path.h" +#include "posix_fd.h" + +static char gDlError[160]; +static int gDlErrorPending; + +// Clears the process-local dynamic-loader error string. +static void DlClearError(void) { + gDlError[0] = '\0'; + gDlErrorPending = 0; +} + +// Stores one process-local dynamic-loader error string. +static void DlSetError(const char* message) { + if (!message) message = "dynamic loader error"; + strncpy(gDlError, message, sizeof(gDlError) - 1); + gDlError[sizeof(gDlError) - 1] = '\0'; + gDlErrorPending = 1; +} + +// Converts one native loader status to errno and a dlerror message. +static void DlSetStatusError(const char* operation, uint32_t status) { + (void)status; + errno = PosixStatusToErrno(status); + DlSetError(operation); +} + +// Loads one DLL-like user module through the native module loader. +__declspec(dllexport) void* dlopen(const char* filename, int flags) { + (void)flags; + + DlClearError(); + if (!filename || !filename[0]) { + errno = EINVAL; + DlSetError("dlopen requires a module path"); + return 0; + } + + char native[512]; + const char* path = PosixTranslatePath(filename, native, sizeof(native)); + if (!path || strcmp(path, SYNTH_DEVNULL) == 0 || strcmp(path, SYNTH_DEVZERO) == 0) { + errno = EINVAL; + DlSetError("dlopen path is not a loadable module"); + return 0; + } + + uint32_t handle = 0; + SYS_LOAD_MODULE_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Path = SysSvFromCstr(path); + req.ModuleHandleOut = &handle; + + uint32_t status = SysLoadModule(&req); + if (status != STATUS_SUCCESS || handle == 0) { + DlSetStatusError("dlopen failed", status); + return 0; + } + + return (void*)(uintptr_t)handle; +} + +// Resolves one named export from a native module handle. +__declspec(dllexport) void* dlsym(void* handle, const char* symbol) { + DlClearError(); + if (!handle || !symbol || !symbol[0]) { + errno = EINVAL; + DlSetError("dlsym requires a module handle and symbol name"); + return 0; + } + + uintptr_t address = 0; + SYS_GET_MODULE_EXPORT_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.ModuleHandle = (uint32_t)(uintptr_t)handle; + req.Name = SysSvFromCstr(symbol); + req.AddressOut = &address; + + uint32_t status = SysGetModuleExport(&req); + if (status != STATUS_SUCCESS || address == 0) { + DlSetStatusError("dlsym failed", status); + return 0; + } + + return (void*)address; +} + +// Releases one module handle returned by dlopen. +__declspec(dllexport) int dlclose(void* handle) { + DlClearError(); + if (!handle) { + errno = EINVAL; + DlSetError("dlclose requires a module handle"); + return -1; + } + + uint32_t status = SysUnloadModule((uint32_t)(uintptr_t)handle); + if (status != STATUS_SUCCESS) { + DlSetStatusError("dlclose failed", status); + return -1; + } + + return 0; +} + +// Returns and clears the most recent dynamic-loader error string. +__declspec(dllexport) char* dlerror(void) { + if (!gDlErrorPending || !gDlError[0]) return 0; + gDlErrorPending = 0; + return gDlError; +} diff --git a/code/user/personalities/posix/posix_fd.h b/code/user/personalities/posix/posix_fd.h index d4af5e5..661b0e2 100644 --- a/code/user/personalities/posix/posix_fd.h +++ b/code/user/personalities/posix/posix_fd.h @@ -24,7 +24,7 @@ typedef struct { #define FD_MAX 64 #define FD_TABLE_PIDS 32 // circular, indexed by pid % FD_TABLE_PIDS -// Called from _start before main(); sets up fds 0/1/2 for this process. +// Called from PosixStart before main(); sets up fds 0/1/2 for this process. __declspec(dllexport) void PosixFdInit(void); // Allocate the lowest free fd >= 3. Returns -1 and sets errno=EMFILE on failure. diff --git a/code/user/personalities/win32/kernel32.c b/code/user/personalities/win32/kernel32.c index 3bac136..592dd8b 100644 --- a/code/user/personalities/win32/kernel32.c +++ b/code/user/personalities/win32/kernel32.c @@ -1102,15 +1102,27 @@ __declspec(dllexport) HANDLE GetModuleHandleW(const unsigned short* lpModuleName return lpModuleName ? NULL : WIN32_HMODULE_EXE; } -// Loads library A. +// Loads a user-mode DLL through the native module loader. __declspec(dllexport) HANDLE LoadLibraryA(const char* lpLibFileName) { if (KiIsWin32DllNameA(lpLibFileName)) return WIN32_HMODULE_WIN32; - gLastError = 2; // ERROR_FILE_NOT_FOUND - return NULL; + if (!lpLibFileName) { gLastError = 87; return NULL; } + + uint32_t handle = 0; + SYS_LOAD_MODULE_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Path = SysSvFromCstr(lpLibFileName); + req.ModuleHandleOut = &handle; + + uint32_t status = SysLoadModule(&req); + if (status != STATUS_SUCCESS) { + gLastError = 126; + return NULL; + } + return (HANDLE)(uintptr_t)handle; } -// Minimal GetProcAddress for code that probes kernel32 functions already present -// in this win32.dll image. Full dynamic DLL loading remains a kernel-loader job. +// GetProcAddress handles both built-in win32 exports and native module handles. __declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule); __declspec(dllexport) void Sleep(DWORD dwMilliseconds); __declspec(dllexport) BOOL GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, @@ -1129,7 +1141,7 @@ __declspec(dllexport) BOOL SetConsoleCursorPosition(HANDLE hConsoleOutput, COORD dwCursorPosition); // Returns proc Address. __declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcName) { - if (hModule != WIN32_HMODULE_WIN32 || !lpProcName) { gLastError = 127; return NULL; } + if (!hModule || !lpProcName) { gLastError = 127; return NULL; } // Ordinal probes are represented as small integer pointers. Do not treat // them as strings while debugging misses. @@ -1139,6 +1151,19 @@ __declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcNam return NULL; } + if (hModule != WIN32_HMODULE_WIN32) { + uintptr_t address = 0; + SYS_GET_MODULE_EXPORT_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.ModuleHandle = (uint32_t)(uintptr_t)hModule; + req.Name = SysSvFromCstr(lpProcName); + req.AddressOut = &address; + uint32_t status = SysGetModuleExport(&req); + if (status != STATUS_SUCCESS || !address) { gLastError = 127; return NULL; } + return (void*)address; + } + #define GP(name) if (strcmp(lpProcName, #name) == 0) return (void*)&name GP(GetLastError); GP(SetLastError); GP(LoadLibraryA); GP(GetProcAddress); GP(FreeLibrary); GP(VirtualAlloc); GP(VirtualFree); GP(VirtualProtect); @@ -1154,9 +1179,15 @@ __declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcNam return NULL; } -// Releases library. +// Releases one native module handle returned by LoadLibraryA. __declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule) { - (void)hLibModule; + if (!hLibModule || hLibModule == WIN32_HMODULE_WIN32 || hLibModule == WIN32_HMODULE_EXE) + return TRUE; + uint32_t status = SysUnloadModule((uint32_t)(uintptr_t)hLibModule); + if (status != STATUS_SUCCESS) { + gLastError = 126; + return FALSE; + } return TRUE; } diff --git a/code/user/runtime/posix_start.c b/code/user/runtime/posix_start.c new file mode 100644 index 0000000..391e7a8 --- /dev/null +++ b/code/user/runtime/posix_start.c @@ -0,0 +1,13 @@ +// POSIX process startup. This file owns process-local POSIX personality initialization before main runs. + +extern int main(void); +extern void exit(int status); +extern void PosixFdInit(void); + +// Initializes POSIX process state and exits with main's return value. +void PosixStart(void) { + PosixFdInit(); + int status = main(); + exit(status); + for (;;) {} +} diff --git a/code/user/syscall/syscall_nr.inc b/code/user/syscall/syscall_nr.inc index 226358f..cfd7a83 100644 --- a/code/user/syscall/syscall_nr.inc +++ b/code/user/syscall/syscall_nr.inc @@ -1,134 +1,140 @@ ; Syscall numbers shared by MASM user-mode stubs. -; Process, threads, waits, and APC. -SYS_EXIT_PROCESS EQU 0 -SYS_EXIT_THREAD EQU 1 -SYS_CREATE_PROCESS EQU 2 -SYS_OPEN_PROCESS EQU 3 -SYS_GET_PROCESS_EXIT_CODE EQU 4 -SYS_KILL_PROCESS EQU 5 -SYS_GET_PROCESS_LIST EQU 6 -SYS_GET_PROCESS_PID EQU 7 -SYS_CREATE_THREAD EQU 8 -SYS_GET_THREAD_LIST EQU 9 -SYS_GET_CURRENT_TID EQU 10 -SYS_GET_CURRENT_PID EQU 11 -SYS_SET_THREAD_PRIORITY EQU 12 -SYS_YIELD EQU 13 -SYS_SLEEP EQU 14 -SYS_WAIT_SINGLE EQU 15 -SYS_WAIT_MANY EQU 16 -SYS_SET_USER_APC_DISPATCHER EQU 17 -SYS_APC_RETURN EQU 18 +; Process, threads, modules, waits, and APC. +SYS_EXIT_PROCESS EQU 0 +SYS_EXIT_THREAD EQU 1 +SYS_CREATE_PROCESS EQU 2 +SYS_OPEN_PROCESS EQU 3 +SYS_GET_PROCESS_EXIT_CODE EQU 4 +SYS_KILL_PROCESS EQU 5 +SYS_GET_PROCESS_LIST EQU 6 +SYS_GET_PROCESS_PID EQU 7 +SYS_CREATE_THREAD EQU 8 +SYS_GET_THREAD_LIST EQU 9 +SYS_LOAD_MODULE EQU 10 +SYS_UNLOAD_MODULE EQU 11 +SYS_GET_MODULE_INFO EQU 12 +SYS_GET_MODULE_EXPORT EQU 13 +SYS_GET_MODULE_LIST EQU 14 +SYS_GET_CURRENT_TID EQU 15 +SYS_GET_CURRENT_PID EQU 16 +SYS_SET_THREAD_PRIORITY EQU 17 +SYS_YIELD EQU 18 +SYS_SLEEP EQU 19 +SYS_WAIT_SINGLE EQU 20 +SYS_WAIT_MANY EQU 21 +SYS_SET_USER_APC_DISPATCHER EQU 22 +SYS_APC_RETURN EQU 23 ; Virtual memory, mappings, and handles. -SYS_ALLOC_VM EQU 19 -SYS_FREE_VM EQU 20 -SYS_PROTECT_VM EQU 21 -SYS_QUERY_VM EQU 22 -SYS_CLOSE_HANDLE EQU 23 -SYS_DUPLICATE_HANDLE EQU 24 -SYS_CREATE_FILE_MAPPING EQU 25 -SYS_MAP_FILE EQU 26 +SYS_ALLOC_VM EQU 24 +SYS_FREE_VM EQU 25 +SYS_PROTECT_VM EQU 26 +SYS_QUERY_VM EQU 27 +SYS_CLOSE_HANDLE EQU 28 +SYS_DUPLICATE_HANDLE EQU 29 +SYS_CREATE_FILE_MAPPING EQU 30 +SYS_MAP_FILE EQU 31 ; Synchronization objects. -SYS_CREATE_MUTEX EQU 27 -SYS_TRY_ACQUIRE_MUTEX EQU 28 -SYS_RELEASE_MUTEX EQU 29 -SYS_CREATE_SEM EQU 30 -SYS_TRY_WAIT_SEM EQU 31 -SYS_SIGNAL_SEM EQU 32 -SYS_CREATE_EVENT EQU 33 -SYS_SIGNAL_EVENT EQU 34 -SYS_CLEAR_EVENT EQU 35 +SYS_CREATE_MUTEX EQU 32 +SYS_TRY_ACQUIRE_MUTEX EQU 33 +SYS_RELEASE_MUTEX EQU 34 +SYS_CREATE_SEM EQU 35 +SYS_TRY_WAIT_SEM EQU 36 +SYS_SIGNAL_SEM EQU 37 +SYS_CREATE_EVENT EQU 38 +SYS_SIGNAL_EVENT EQU 39 +SYS_CLEAR_EVENT EQU 40 ; Queues and named queues. -SYS_CREATE_QUEUE EQU 36 -SYS_SEND_QUEUE EQU 37 -SYS_RECV_QUEUE EQU 38 -SYS_PUBLISH_QUEUE EQU 39 -SYS_OPEN_NAMED_QUEUE EQU 40 -SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 41 +SYS_CREATE_QUEUE EQU 41 +SYS_SEND_QUEUE EQU 42 +SYS_RECV_QUEUE EQU 43 +SYS_PUBLISH_QUEUE EQU 44 +SYS_OPEN_NAMED_QUEUE EQU 45 +SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 46 ; Files, directories, and current directory. -SYS_OPEN_FILE EQU 42 -SYS_READ_FILE EQU 43 -SYS_WRITE_FILE EQU 44 -SYS_READ_FILE_ASYNC EQU 45 -SYS_WRITE_FILE_ASYNC EQU 46 -SYS_CANCEL_IO EQU 47 -SYS_SEEK_FILE EQU 48 -SYS_GET_FILE_POS EQU 49 -SYS_GET_FILE_SIZE EQU 50 -SYS_GET_FILE_STAT EQU 51 -SYS_STAT_FILE EQU 52 -SYS_TRUNCATE_FILE EQU 53 -SYS_FLUSH_FILE EQU 54 -SYS_DELETE_FILE EQU 55 -SYS_RENAME_FILE EQU 56 -SYS_LOCK_FILE EQU 57 -SYS_UNLOCK_FILE EQU 58 -SYS_OPEN_DIR EQU 59 -SYS_READ_DIR EQU 60 -SYS_CREATE_DIR EQU 61 -SYS_REMOVE_DIR EQU 62 -SYS_SET_CWD EQU 63 -SYS_REAL_PATH EQU 64 +SYS_OPEN_FILE EQU 47 +SYS_READ_FILE EQU 48 +SYS_WRITE_FILE EQU 49 +SYS_READ_FILE_ASYNC EQU 50 +SYS_WRITE_FILE_ASYNC EQU 51 +SYS_CANCEL_IO EQU 52 +SYS_SEEK_FILE EQU 53 +SYS_GET_FILE_POS EQU 54 +SYS_GET_FILE_SIZE EQU 55 +SYS_GET_FILE_STAT EQU 56 +SYS_STAT_FILE EQU 57 +SYS_TRUNCATE_FILE EQU 58 +SYS_FLUSH_FILE EQU 59 +SYS_DELETE_FILE EQU 60 +SYS_RENAME_FILE EQU 61 +SYS_LOCK_FILE EQU 62 +SYS_UNLOCK_FILE EQU 63 +SYS_OPEN_DIR EQU 64 +SYS_READ_DIR EQU 65 +SYS_CREATE_DIR EQU 66 +SYS_REMOVE_DIR EQU 67 +SYS_SET_CWD EQU 68 +SYS_REAL_PATH EQU 69 ; Console, display, and surfaces. -SYS_WRITE_CONSOLE EQU 65 -SYS_GET_CONSOLE_INFO EQU 66 -SYS_SET_CONSOLE_CURSOR EQU 67 -SYS_FILL_CONSOLE_CHARACTER EQU 68 -SYS_FILL_CONSOLE_ATTRIBUTE EQU 69 -SYS_PUT_SCREEN_CHAR EQU 70 -SYS_CLEAR_SCREEN EQU 71 -SYS_GET_SCREEN_SIZE EQU 72 -SYS_QUERY_DISPLAY_INFO EQU 73 -SYS_CREATE_SURFACE EQU 74 -SYS_MAP_SURFACE EQU 75 -SYS_QUERY_SURFACE EQU 76 -SYS_PRESENT_SURFACE EQU 77 +SYS_WRITE_CONSOLE EQU 70 +SYS_GET_CONSOLE_INFO EQU 71 +SYS_SET_CONSOLE_CURSOR EQU 72 +SYS_FILL_CONSOLE_CHARACTER EQU 73 +SYS_FILL_CONSOLE_ATTRIBUTE EQU 74 +SYS_PUT_SCREEN_CHAR EQU 75 +SYS_CLEAR_SCREEN EQU 76 +SYS_GET_SCREEN_SIZE EQU 77 +SYS_QUERY_DISPLAY_INFO EQU 78 +SYS_CREATE_SURFACE EQU 79 +SYS_MAP_SURFACE EQU 80 +SYS_QUERY_SURFACE EQU 81 +SYS_PRESENT_SURFACE EQU 82 ; Input. -SYS_POLL_KEYBOARD EQU 78 -SYS_IS_KEY_DOWN EQU 79 -SYS_READ_KEYBOARD_CHAR EQU 80 -SYS_READ_KEY EQU 81 -SYS_GET_KEY_MODIFIERS EQU 82 -SYS_OPEN_INPUT_QUEUE EQU 83 -SYS_READ_INPUT_EVENT EQU 84 -SYS_PEEK_INPUT_EVENT EQU 85 -SYS_PEEK_MOUSE EQU 86 -SYS_READ_MOUSE EQU 87 -SYS_GET_MOUSE_POS EQU 88 +SYS_POLL_KEYBOARD EQU 83 +SYS_IS_KEY_DOWN EQU 84 +SYS_READ_KEYBOARD_CHAR EQU 85 +SYS_READ_KEY EQU 86 +SYS_GET_KEY_MODIFIERS EQU 87 +SYS_OPEN_INPUT_QUEUE EQU 88 +SYS_READ_INPUT_EVENT EQU 89 +SYS_PEEK_INPUT_EVENT EQU 90 +SYS_PEEK_MOUSE EQU 91 +SYS_READ_MOUSE EQU 92 +SYS_GET_MOUSE_POS EQU 93 ; Time, debug, and system information. -SYS_CREATE_TIMER EQU 89 -SYS_SET_TIMER EQU 90 -SYS_CANCEL_TIMER EQU 91 -SYS_GET_TICK EQU 92 -SYS_GET_TICK_FREQUENCY EQU 93 -SYS_GET_SYSTEM_TIME EQU 94 -SYS_PRINT_DEBUG EQU 95 -SYS_QUERY_TRAP_FRAME EQU 96 -SYS_GET_MEMORY_STATUS EQU 97 -SYS_QUERY_KERNEL_STATS EQU 98 +SYS_CREATE_TIMER EQU 94 +SYS_SET_TIMER EQU 95 +SYS_CANCEL_TIMER EQU 96 +SYS_GET_TICK EQU 97 +SYS_GET_TICK_FREQUENCY EQU 98 +SYS_GET_SYSTEM_TIME EQU 99 +SYS_PRINT_DEBUG EQU 100 +SYS_QUERY_TRAP_FRAME EQU 101 +SYS_GET_MEMORY_STATUS EQU 102 +SYS_QUERY_KERNEL_STATS EQU 103 ; Native sockets and polling. -SYS_CREATE_SOCKET EQU 99 -SYS_BIND_SOCKET EQU 100 -SYS_LISTEN_SOCKET EQU 101 -SYS_ACCEPT_SOCKET EQU 102 -SYS_CONNECT_SOCKET EQU 103 -SYS_SEND_SOCKET EQU 104 -SYS_RECV_SOCKET EQU 105 -SYS_SHUTDOWN_SOCKET EQU 106 -SYS_SEND_TO_SOCKET EQU 107 -SYS_RECV_FROM_SOCKET EQU 108 -SYS_RESOLVE_HOST EQU 109 -SYS_SET_SOCKET_OPTION EQU 110 -SYS_GET_SOCKET_OPTION EQU 111 -SYS_POLL EQU 112 +SYS_CREATE_SOCKET EQU 104 +SYS_SOCKET_IO_ASYNC EQU 105 +SYS_BIND_SOCKET EQU 106 +SYS_LISTEN_SOCKET EQU 107 +SYS_ACCEPT_SOCKET EQU 108 +SYS_CONNECT_SOCKET EQU 109 +SYS_SEND_SOCKET EQU 110 +SYS_RECV_SOCKET EQU 111 +SYS_SHUTDOWN_SOCKET EQU 112 +SYS_SEND_TO_SOCKET EQU 113 +SYS_RECV_FROM_SOCKET EQU 114 +SYS_RESOLVE_HOST EQU 115 +SYS_SET_SOCKET_OPTION EQU 116 +SYS_GET_SOCKET_OPTION EQU 117 +SYS_POLL EQU 118 -SYS_MAX EQU 113 +SYS_MAX EQU 119 diff --git a/code/user/syscall/syscall_stubs.asm b/code/user/syscall/syscall_stubs.asm index a9e0a5e..14c721a 100644 --- a/code/user/syscall/syscall_stubs.asm +++ b/code/user/syscall/syscall_stubs.asm @@ -89,7 +89,12 @@ SYSCALL_STUB2 OpenProcess, SYS_OPEN_PROCESS SYSCALL_STUB2 KillProcess, SYS_KILL_PROCESS SYSCALL_STUB2 GetProcessList, SYS_GET_PROCESS_LIST SYSCALL_STUB2 GetProcessPid, SYS_GET_PROCESS_PID -SYSCALL_STUB2 GetThreadList, SYS_GET_THREAD_LIST +SYSCALL_STUB2 GetThreadList, SYS_GET_THREAD_LIST +SYSCALL_STUB2 LoadModule, SYS_LOAD_MODULE +SYSCALL_STUB2 UnloadModule, SYS_UNLOAD_MODULE +SYSCALL_STUB2 GetModuleInfo, SYS_GET_MODULE_INFO +SYSCALL_STUB2 GetModuleExport, SYS_GET_MODULE_EXPORT +SYSCALL_STUB2 GetModuleList, SYS_GET_MODULE_LIST SYSCALL_STUB2 CreateThread, SYS_CREATE_THREAD SYSCALL_STUB2 GetCurrentTid, SYS_GET_CURRENT_TID @@ -239,6 +244,7 @@ SYSCALL_STUB2 QueryTrapFrame, SYS_QUERY_TRAP_FRAME ; Native sockets ; --------------------------------------------------------------------------- SYSCALL_STUB2 CreateSocket, SYS_CREATE_SOCKET +SYSCALL_STUB2 SocketIoAsync, SYS_SOCKET_IO_ASYNC SYSCALL_STUB2 BindSocket, SYS_BIND_SOCKET SYSCALL_STUB2 ListenSocket, SYS_LISTEN_SOCKET SYSCALL_STUB2 AcceptSocket, SYS_ACCEPT_SOCKET @@ -248,10 +254,10 @@ SYSCALL_STUB6 RecvSocket, SYS_RECV_SOCKET SYSCALL_STUB2 ShutdownSocket, SYS_SHUTDOWN_SOCKET SYSCALL_STUB2 SendToSocket, SYS_SEND_TO_SOCKET SYSCALL_STUB2 RecvFromSocket, SYS_RECV_FROM_SOCKET -SYSCALL_STUB2 ResolveHost, SYS_RESOLVE_HOST -SYSCALL_STUB2 SetSocketOption, SYS_SET_SOCKET_OPTION -SYSCALL_STUB2 GetSocketOption, SYS_GET_SOCKET_OPTION -SYSCALL_STUB2 Poll, SYS_POLL +SYSCALL_STUB2 ResolveHost, SYS_RESOLVE_HOST +SYSCALL_STUB2 SetSocketOption, SYS_SET_SOCKET_OPTION +SYSCALL_STUB2 GetSocketOption, SYS_GET_SOCKET_OPTION +SYSCALL_STUB2 Poll, SYS_POLL SYSCALL_STUB2 GetMemoryStatus, SYS_GET_MEMORY_STATUS SYSCALL_STUB2 QueryKernelStats, SYS_QUERY_KERNEL_STATS diff --git a/code/user/tests/module_load_smoke/module_load_smoke.c b/code/user/tests/module_load_smoke/module_load_smoke.c new file mode 100644 index 0000000..2e195fd --- /dev/null +++ b/code/user/tests/module_load_smoke/module_load_smoke.c @@ -0,0 +1,156 @@ +// User-mode smoke test for public dynamic module loading. + +#include +#include +#include +#include + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current process. +static void exit_now(int32_t code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one message. +static void fail(const char* message) { + print("module_load_smoke: "); + print(message); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("module_load_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Checks whether one public module snapshot has the expected shape. +static void check_module_info(const SYS_MODULE_INFO* info) { + if (!info) fail("null module info"); + if (info->Size != sizeof(*info)) fail("module info size mismatch"); + if (info->Version != SYS_MODULE_INFO_VERSION) fail("module info version mismatch"); + if (info->Base == 0 || info->ImageSize == 0) fail("module has empty mapping"); + if ((info->Flags & SYS_MODULE_FLAG_PUBLIC_REF) == 0) fail("module missing public reference flag"); + if (info->PublicRefCount == 0 || info->TotalRefCount == 0) fail("module reference counts missing"); + if (strcmp(info->Name, "posix.dll") != 0) fail("module name mismatch"); +} + +// Returns whether the current module list contains the specified base address. +static int module_list_contains(uintptr_t base) { + size_t count = 0; + uint32_t status = SysGetModuleList(0, 0, &count); + if (status != STATUS_SUCCESS) fail_status("query module count", status); + if (count == 0) return 0; + if (count > 32) fail("module count unexpectedly large"); + + SYS_MODULE_INFO modules[32]; + memset(modules, 0, sizeof(modules)); + status = SysGetModuleList(modules, count, &count); + if (status != STATUS_SUCCESS) fail_status("query module list", status); + + for (size_t i = 0; i < count; i++) { + if (modules[i].Base == base) + return 1; + } + return 0; +} + + +// Verifies export lookup through a module handle. +static void check_export_lookup(void) { + uint32_t handle = 0; + SYS_LOAD_MODULE_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Path = SysSvFromCstr("syscall.dll"); + req.ModuleHandleOut = &handle; + + uint32_t status = SysLoadModule(&req); + if (status != STATUS_SUCCESS) fail_status("load syscall.dll", status); + if (handle == 0) fail("syscall load returned null handle"); + + uintptr_t address = 0; + SYS_GET_MODULE_EXPORT_REQUEST exportReq; + memset(&exportReq, 0, sizeof(exportReq)); + exportReq.Size = sizeof(exportReq); + exportReq.ModuleHandle = handle; + exportReq.Name = SysSvFromCstr("SysGetCurrentPid"); + exportReq.AddressOut = &address; + + status = SysGetModuleExport(&exportReq); + if (status != STATUS_SUCCESS) fail_status("lookup SysGetCurrentPid", status); + if (address == 0) fail("export address is zero"); + + status = SysUnloadModule(handle); + if (status != STATUS_SUCCESS) fail_status("unload syscall.dll handle", status); +} + +// Runs the public dynamic module loader smoke test. +int main(void) { + print("module_load_smoke: start\n"); + check_export_lookup(); + + uint32_t handle = 0; + SYS_MODULE_INFO info; + memset(&info, 0, sizeof(info)); + + SYS_LOAD_MODULE_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Path = SysSvFromCstr("posix.dll"); + req.ModuleHandleOut = &handle; + req.InfoOut = &info; + + uint32_t status = SysLoadModule(&req); + if (status != STATUS_SUCCESS) fail_status("load posix.dll", status); + if (handle == 0) fail("load returned null handle"); + check_module_info(&info); + if (!module_list_contains(info.Base)) fail("loaded module absent from list"); + + SYS_MODULE_INFO byHandle; + memset(&byHandle, 0, sizeof(byHandle)); + status = SysGetModuleInfo(handle, &byHandle); + if (status != STATUS_SUCCESS) fail_status("get module info", status); + check_module_info(&byHandle); + if (byHandle.Base != info.Base) fail("module handle base mismatch"); + + status = SysUnloadModule(handle); + if (status != STATUS_SUCCESS) fail_status("unload module", status); + + memset(&byHandle, 0, sizeof(byHandle)); + status = SysGetModuleInfo(handle, &byHandle); + if (status != STATUS_INVALID_HANDLE) fail_status("query unloaded handle", status); + + print("module_load_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/net_irp_smoke/net_irp_smoke.c b/code/user/tests/net_irp_smoke/net_irp_smoke.c new file mode 100644 index 0000000..d2eeaa3 --- /dev/null +++ b/code/user/tests/net_irp_smoke/net_irp_smoke.c @@ -0,0 +1,347 @@ +// User-mode smoke test for socket IRP dispatch, public async sockets, cancellation, and statistics. + +#include +#include +#include +#include + +#define EVENT_AUTO_RESET 0u +#define NET_IRP_SMOKE_UDP_PORT 40219u +#define NET_IRP_SMOKE_HTTP_PORT 80u + +// Prints one NUL-terminated string to the kernel console. +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +// Prints one unsigned integer to the kernel console. +static void print_u64(uint64_t v) { + char buf[32]; + int i = 0; + if (v == 0) { + print("0"); + return; + } + while (v && i < (int)sizeof(buf)) { + buf[i++] = (char)('0' + (v % 10)); + v /= 10; + } + while (i--) { + char c = buf[i]; + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); + } +} + +// Terminates the current process. +static void exit_now(int32_t code) { + for (;;) SysExitProcess(code); +} + +// Fails the test with one message. +static void fail(const char* message) { + print("net_irp_smoke: "); + print(message); + print("\n"); + exit_now(1); +} + +// Fails the test with one status value. +static void fail_status(const char* what, uint32_t status) { + print("net_irp_smoke: "); + print(what); + print(" status="); + print_u64(status); + print("\n"); + exit_now(1); +} + +// Reads the public network IRP statistics snapshot. +static SYS_NET_IRP_STATS query_stats(void) { + SYS_NET_IRP_STATS stats; + memset(&stats, 0, sizeof(stats)); + uint32_t status = SysQueryKernelStats(SYS_KERNEL_STATS_NET_IRP, &stats, sizeof(stats), 0); + if (status != STATUS_SUCCESS) + fail_status("query net irp stats", status); + if (stats.Size != sizeof(stats)) + fail("net irp stats size mismatch"); + if (stats.Version != SYS_STATS_VERSION) + fail("net irp stats version mismatch"); + return stats; +} + +// Builds an IPv4 socket address. +static SYS_SOCKET_ADDRESS make_ipv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint16_t port) { + SYS_SOCKET_ADDRESS address; + memset(&address, 0, sizeof(address)); + address.Size = sizeof(address); + address.Family = SYS_AF_INET; + address.Port = port; + address.Address[0] = a; + address.Address[1] = b; + address.Address[2] = c; + address.Address[3] = d; + return address; +} + +// Sets one uint32 socket option. +static void set_u32_option(uint32_t socket, uint32_t option, uint32_t value) { + SYS_SOCKET_OPTION_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Option = option; + req.Value = &value; + req.ValueSize = sizeof(value); + uint32_t status = SysSetSocketOption(socket, &req); + if (status != STATUS_SUCCESS) + fail_status("set socket option", status); +} + +// Waits for an async socket request to publish a terminal IOSB status. +static void wait_for_async(uint32_t event, SYS_IO_STATUS_BLOCK* iosb, const char* what) { + for (uint32_t i = 0; i < 200u && iosb->Status == STATUS_PENDING; ++i) { + uint32_t status = SysWait(event, 0); + if (status != STATUS_SUCCESS && status != STATUS_ALERTED && + status != STATUS_KERNEL_APC && status != STATUS_USER_APC) { + fail_status(what, status); + } + } + if (iosb->Status == STATUS_PENDING) + fail("async request stayed pending"); +} + +// Verifies nonblocking UDP receive returns would-block through the IRP path. +static void check_nonblocking_receive(void) { + uint32_t udp = 0; + uint32_t status = SysCreateSocket(SYS_AF_INET, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &udp); + if (status != STATUS_SUCCESS) + fail_status("create udp socket", status); + + SYS_SOCKET_ADDRESS bindAddress = make_ipv4(0, 0, 0, 0, NET_IRP_SMOKE_UDP_PORT); + status = SysBindSocket(udp, &bindAddress, sizeof(bindAddress)); + if (status != STATUS_SUCCESS) { + SysCloseHandle(udp); + fail_status("bind udp socket", status); + } + + set_u32_option(udp, SYS_SOCKET_OPT_NONBLOCKING, 1); + + char buffer[16]; + SYS_SOCKET_ADDRESS remote; + size_t got = 0; + size_t remoteSize = sizeof(remote); + memset(buffer, 0, sizeof(buffer)); + memset(&remote, 0, sizeof(remote)); + + SYS_SOCKET_RECV_FROM_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Buffer = buffer; + req.Length = sizeof(buffer); + req.RemoteAddressOut = &remote; + req.RemoteAddressSizeInOut = &remoteSize; + req.ReceivedOut = &got; + + status = SysRecvFromSocket(udp, &req); + SysCloseHandle(udp); + if (status != STATUS_WOULD_BLOCK) + fail_status("nonblocking udp recvfrom", status); +} + +// Verifies a pending async UDP receive can be cancelled and waited through its request handle. +static void check_async_receive_cancel(void) { + uint32_t udp = 0; + uint32_t status = SysCreateSocket(SYS_AF_INET, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &udp); + if (status != STATUS_SUCCESS) + fail_status("create cancel udp socket", status); + + SYS_SOCKET_ADDRESS bindAddress = make_ipv4(0, 0, 0, 0, NET_IRP_SMOKE_UDP_PORT + 1u); + status = SysBindSocket(udp, &bindAddress, sizeof(bindAddress)); + if (status != STATUS_SUCCESS) { + SysCloseHandle(udp); + fail_status("bind cancel udp socket", status); + } + + uint32_t event = 0; + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &event); + if (status != STATUS_SUCCESS) { + SysCloseHandle(udp); + fail_status("create event", status); + } + + char buffer[16]; + SYS_SOCKET_ADDRESS remote; + size_t remoteSize = sizeof(remote); + uint32_t requestHandle = 0; + SYS_IO_STATUS_BLOCK iosb; + memset(buffer, 0, sizeof(buffer)); + memset(&remote, 0, sizeof(remote)); + iosb.Status = 0; + iosb.Information = 0; + + SYS_ASYNC_SOCKET_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.SocketHandle = udp; + req.EventHandle = event; + req.Operation = SYS_ASYNC_SOCKET_OP_RECV_FROM; + req.Buffer = buffer; + req.Length = sizeof(buffer); + req.AddressOut = &remote; + req.AddressSizeInOut = &remoteSize; + req.Iosb = &iosb; + req.RequestHandleOut = &requestHandle; + + SYS_NET_IRP_STATS beforeSubmit = query_stats(); + status = SysSocketIoAsync(&req); + if (status != STATUS_PENDING || requestHandle == 0) { + SysCloseHandle(event); + SysCloseHandle(udp); + fail_status("submit async recvfrom", status); + } + + uint32_t enteredIrp = 0; + for (uint32_t i = 0; i < 40u; ++i) { + SYS_NET_IRP_STATS now = query_stats(); + if (now.ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM] > + beforeSubmit.ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM]) { + enteredIrp = 1; + break; + } + SysSleep(10); + } + if (!enteredIrp) + fail("async recvfrom did not enter the IRP path"); + + uint32_t cancelStatus = SysCancelIo(requestHandle); + if (cancelStatus != STATUS_SUCCESS && cancelStatus != STATUS_PENDING) + fail_status("cancel async recvfrom", cancelStatus); + + wait_for_async(event, &iosb, "wait async recvfrom cancel"); + if (iosb.Status != STATUS_CANCELLED) + fail_status("async recvfrom iosb", iosb.Status); + if (iosb.Information != 0) + fail("async recvfrom cancel transferred bytes"); + + status = SysWaitSingle(requestHandle, 0, 0); + if (status != STATUS_SUCCESS) + fail_status("wait cancelled request handle", status); + + SysCloseHandle(requestHandle); + SysCloseHandle(event); + SysCloseHandle(udp); +} + +// Tries a host-backed HTTP transfer to prove async send and receive success paths. +static void try_http_async_success(void) { + uint32_t tcp = 0; + uint32_t status = SysCreateSocket(SYS_AF_INET, SYS_SOCK_STREAM, SYS_IPPROTO_TCP, &tcp); + if (status != STATUS_SUCCESS) { + print("net_irp_smoke: http success path skipped create status="); + print_u64(status); + print("\n"); + return; + } + + SYS_SOCKET_ADDRESS address = make_ipv4(10, 0, 2, 2, NET_IRP_SMOKE_HTTP_PORT); + status = SysConnectSocket(tcp, &address, sizeof(address)); + if (status != STATUS_SUCCESS) { + print("net_irp_smoke: http success path skipped connect status="); + print_u64(status); + print("\n"); + SysCloseHandle(tcp); + return; + } + + uint32_t event = 0; + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &event); + if (status != STATUS_SUCCESS) { + SysCloseHandle(tcp); + fail_status("create http event", status); + } + + static const char requestText[] = "GET / HTTP/1.0\r\nHost: 10.0.2.2\r\n\r\n"; + SYS_IO_STATUS_BLOCK sendIosb; + uint32_t sendRequest = 0; + memset(&sendIosb, 0, sizeof(sendIosb)); + + SYS_ASYNC_SOCKET_REQUEST sendReq; + memset(&sendReq, 0, sizeof(sendReq)); + sendReq.Size = sizeof(sendReq); + sendReq.SocketHandle = tcp; + sendReq.EventHandle = event; + sendReq.Operation = SYS_ASYNC_SOCKET_OP_SEND; + sendReq.Buffer = (void*)requestText; + sendReq.Length = sizeof(requestText) - 1u; + sendReq.Iosb = &sendIosb; + sendReq.RequestHandleOut = &sendRequest; + + status = SysSocketIoAsync(&sendReq); + if (status != STATUS_PENDING || sendRequest == 0) + fail_status("submit async send", status); + wait_for_async(event, &sendIosb, "wait async send"); + if (sendIosb.Status != STATUS_SUCCESS) + fail_status("async send iosb", sendIosb.Status); + if (sendIosb.Information != sizeof(requestText) - 1u) + fail("async send byte count mismatch"); + SysCloseHandle(sendRequest); + + char response[128]; + SYS_IO_STATUS_BLOCK recvIosb; + uint32_t recvRequest = 0; + memset(response, 0, sizeof(response)); + memset(&recvIosb, 0, sizeof(recvIosb)); + + SYS_ASYNC_SOCKET_REQUEST recvReq; + memset(&recvReq, 0, sizeof(recvReq)); + recvReq.Size = sizeof(recvReq); + recvReq.SocketHandle = tcp; + recvReq.EventHandle = event; + recvReq.Operation = SYS_ASYNC_SOCKET_OP_RECV; + recvReq.Buffer = response; + recvReq.Length = sizeof(response) - 1u; + recvReq.Iosb = &recvIosb; + recvReq.RequestHandleOut = &recvRequest; + + status = SysSocketIoAsync(&recvReq); + if (status != STATUS_PENDING || recvRequest == 0) + fail_status("submit async recv", status); + wait_for_async(event, &recvIosb, "wait async recv"); + if (recvIosb.Status != STATUS_SUCCESS) + fail_status("async recv iosb", recvIosb.Status); + if (recvIosb.Information == 0) + fail("async recv returned no bytes"); + SysCloseHandle(recvRequest); + + SysCloseHandle(event); + SysCloseHandle(tcp); +} + +// Verifies counters prove socket requests entered and completed through the IRP path. +static void check_stats_delta(const SYS_NET_IRP_STATS* before) { + SYS_NET_IRP_STATS after = query_stats(); + if (after.Submitted <= before->Submitted) + fail("net irp submitted counter did not advance"); + if (after.Completed <= before->Completed) + fail("net irp completed counter did not advance"); + if (after.WouldBlock <= before->WouldBlock) + fail("net irp would-block counter did not advance"); + if (after.CancelRequested <= before->CancelRequested) + fail("net irp cancel-request counter did not advance"); + if (after.Cancelled <= before->Cancelled) + fail("net irp cancelled counter did not advance"); + if (after.ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM] <= before->ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM]) + fail("net irp recvfrom operation counter did not advance"); +} + +// Runs the network IRP smoke test. +int main(void) { + print("net_irp_smoke: start\n"); + SYS_NET_IRP_STATS before = query_stats(); + check_nonblocking_receive(); + check_async_receive_cancel(); + try_http_async_success(); + check_stats_delta(&before); + print("net_irp_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/posix_module_smoke/posix_module_smoke.c b/code/user/tests/posix_module_smoke/posix_module_smoke.c new file mode 100644 index 0000000..67ee83d --- /dev/null +++ b/code/user/tests/posix_module_smoke/posix_module_smoke.c @@ -0,0 +1,65 @@ +// User-mode smoke test for POSIX dynamic module loading. + +#include +#include +#include + +#define RTLD_NOW 0x0002 +#define RTLD_LOCAL 0x0004 + +typedef int64_t ssize_t; + +extern ssize_t write(int fd, const void* buf, size_t count); +extern void exit(int status); +extern void* dlopen(const char* filename, int flags); +extern void* dlsym(void* handle, const char* symbol); +extern int dlclose(void* handle); +extern char* dlerror(void); + +// Writes one NUL-terminated string to stdout. +static void print(const char* s) { + write(1, s, strlen(s)); +} + +// Terminates the current process. +static void exit_now(int code) { + for (;;) exit(code); +} + +// Fails the smoke test with one message. +static void fail(const char* message) { + print("posix_module_smoke: "); + print(message); + print("\n"); + exit_now(1); +} + +// Verifies that dlerror reports and clears one loader error. +static void check_dlerror_clear(void) { + void* missing = dlsym(0, "definitely_missing"); + if (missing) fail("dlsym unexpectedly succeeded"); + char* first = dlerror(); + if (!first || !first[0]) fail("dlerror did not report the failure"); + char* second = dlerror(); + if (second) fail("dlerror did not clear the failure"); +} + +// Runs the POSIX dynamic loader smoke test. +int main(void) { + print("posix_module_smoke: start\n"); + check_dlerror_clear(); + + void* module = dlopen("syscall.dll", RTLD_NOW | RTLD_LOCAL); + if (!module) fail("dlopen syscall.dll failed"); + + typedef uint32_t (__stdcall *GETPID_FN)(void); + GETPID_FN getpid_native = (GETPID_FN)dlsym(module, "SysGetCurrentPid"); + if (!getpid_native) fail("dlsym SysGetCurrentPid failed"); + if (getpid_native() == 0) fail("resolved SysGetCurrentPid returned zero"); + + if (dlclose(module) != 0) fail("dlclose syscall.dll failed"); + + print("posix_module_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/docs.md b/docs.md index 80605ce..6298943 100644 --- a/docs.md +++ b/docs.md @@ -5,6 +5,7 @@ nt-lite is a clean-design hobby kernel. It is not a stable compatibility platfor ## Principles - Break old code when the new model is better. +- Breaking ABI is encouraged. - Do not keep compatibility paths unless they have a clear purpose. - Plan the model before writing the patch. - Make one architectural move at a time.