From f882d339fb4f377a298c218e98cc2f5f4e998cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Wilczy=C5=84ski?= Date: Thu, 20 Aug 2026 12:32:25 +0200 Subject: [PATCH] redo boot around an explicit system volume and fixed kernel/HAL images, redo volumes and filesystem paths around capabilities instead of drive numbers and kernel CWD state, add a capability/security model with derived and revocable handles and security contexts, add port IPC and rssrv to manage the userspace resource namespace and publish the system volume, move process, dll and path loading to userspace and add process builder syscalls, init now supervises rssrv and the session process, redo the syscall ABI around request structs and four arguments, add a volume manager and proper partition arrival/removal handling so FAT and NTFS mount dynamically, clean up the HAL/DDK boundary so drivers don't depend on HAL internals, save fpu state properly now, the serial driver doesn't hang on a missing port, fix AHCI startup races, fix timeout accounting, fix shared mmap writeback, more smoke tests --- OVMF/OVMF_VARS_4M.fd | Bin 540672 -> 540672 bytes OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes code/boot.ini | 12 +- code/boot/uefi/boot_pe.c | 482 ++ code/boot/uefi/boot_pe.h | 45 + code/boot/uefi/main.c | 2178 +++++--- code/build/build.c | 368 +- code/common/boot_volume_selector.c | 99 + .../include/common/boot_volume_selector.h | 36 + code/common/include/common/ini_parser.h | 6 - code/common/ini_parser.c | 55 - code/common/libc.c | 30 +- code/drivers/bus/pci/pci.c | 28 +- code/drivers/bus/pci/pci.h | 1 - code/drivers/fs/fat/fat.c | 914 ++-- code/drivers/fs/fat/fat.h | 44 +- code/drivers/fs/fat/fat_disk.h | 33 - code/drivers/fs/fat/fat_mount.c | 8 +- code/drivers/fs/ntfs/ntfs.c | 493 +- code/drivers/input/serial/serial.c | 44 +- code/drivers/net/lwip/lwip_sys_arch.c | 4 +- code/drivers/storage/ahci/ahci.c | 157 +- code/drivers/storage/ahci/ahci.h | 16 +- code/drivers/storage/ahci/ahci_port.c | 14 +- code/kernel/hal/hal_apic.c | 118 +- code/kernel/hal/hal_main.c | 131 +- code/kernel/hal/hal_output.c | 2 +- code/kernel/hal/hal_pci.c | 3 +- code/kernel/hal/hal_processor.c | 15 +- code/kernel/hal/hal_rtc.c | 2 +- code/kernel/hal/hal_screen.c | 8 +- code/kernel/hal/hal_serial.c | 72 +- code/kernel/hal/hal_timer.c | 40 +- .../include/kernel/hal}/hal_api.h | 109 +- code/kernel/include/kernel/hal/hal_apic.h | 40 +- code/kernel/include/kernel/hal/hal_internal.h | 7 + code/kernel/include/kernel/hal/hal_output.h | 2 +- .../kernel/include/kernel/hal/hal_processor.h | 12 + code/kernel/include/kernel/hal/hal_timer.h | 10 + code/kernel/include/kernel/io_ddk.h | 3 +- code/kernel/include/kernel/io_file.h | 26 +- code/kernel/include/kernel/io_partition.h | 12 + code/kernel/include/kernel/io_volume.h | 111 + code/kernel/include/kernel/ke_audio.h | 13 +- code/kernel/include/kernel/ke_boot_config.h | 20 + code/kernel/include/kernel/ke_cpu.h | 8 +- code/kernel/include/kernel/ke_debug.h | 4 +- code/kernel/include/kernel/ke_display.h | 2 +- code/kernel/include/kernel/ke_hal.h | 23 + code/kernel/include/kernel/ke_handle.h | 85 + code/kernel/include/kernel/ke_id.h | 15 + code/kernel/include/kernel/ke_idt.h | 2 +- code/kernel/include/kernel/ke_input.h | 2 +- code/kernel/include/kernel/ke_irq.h | 4 +- code/kernel/include/kernel/ke_named_queue.h | 3 +- code/kernel/include/kernel/ke_port.h | 98 + code/kernel/include/kernel/ke_process.h | 91 +- code/kernel/include/kernel/ke_queue.h | 2 +- code/kernel/include/kernel/ke_sched.h | 4 +- code/kernel/include/kernel/ke_selftest.h | 2 +- code/kernel/include/kernel/ke_semaphore.h | 2 +- code/kernel/include/kernel/ke_settings.h | 2 +- code/kernel/include/kernel/ke_smp.h | 2 +- code/kernel/include/kernel/ke_syscall.h | 4 +- code/kernel/include/kernel/ke_system_root.h | 19 +- .../kernel/include/kernel/ke_system_service.h | 24 + code/kernel/include/kernel/ke_thread.h | 3 + code/kernel/include/kernel/ke_time.h | 6 + code/kernel/include/kernel/ke_timer.h | 4 +- code/kernel/include/kernel/ke_trap.h | 3 +- code/kernel/include/kernel/ke_userenv.h | 21 +- code/kernel/include/kernel/ke_util.h | 14 +- code/kernel/include/kernel/ldr_image.h | 25 +- code/kernel/include/kernel/mm_file_cache.h | 3 + code/kernel/include/kernel/mm_swap.h | 3 +- code/kernel/include/kernel/mm_virt.h | 3 + code/kernel/include/kernel/net_socket.h | 2 +- code/kernel/include/kernel/se_security.h | 42 + code/kernel/include/kernel/syscall_nr.h | 302 +- code/kernel/io_ddk.c | 41 +- code/kernel/io_file.c | 942 ++-- code/kernel/io_partition.c | 772 +++ code/kernel/io_volume.c | 634 +++ code/kernel/ke_audio.c | 414 +- code/kernel/ke_boot_config.c | 154 + code/kernel/ke_cpu.c | 19 +- code/kernel/ke_debug.c | 63 +- code/kernel/ke_event.c | 4 +- code/kernel/ke_hal.c | 125 + code/kernel/ke_handle.c | 753 +++ code/kernel/ke_id.c | 22 + code/kernel/ke_idt.c | 2 +- code/kernel/ke_input.c | 9 +- code/kernel/ke_irq.c | 17 +- code/kernel/ke_port.c | 1636 ++++++ code/kernel/ke_process.c | 470 +- code/kernel/ke_queue.c | 54 +- code/kernel/ke_sched.c | 89 +- code/kernel/ke_selftest.c | 92 +- code/kernel/ke_semaphore.c | 4 +- code/kernel/ke_settings.c | 150 +- code/kernel/ke_smp.c | 129 +- code/kernel/ke_switch.asm | 2 +- code/kernel/ke_syscall.asm | 88 +- code/kernel/ke_syscall.c | 4481 ++++++++++------- code/kernel/ke_system_root.c | 167 +- code/kernel/ke_system_service.c | 92 + code/kernel/ke_thread.c | 53 +- code/kernel/ke_time.c | 23 +- code/kernel/ke_timer.c | 66 +- code/kernel/ke_trap.c | 12 +- code/kernel/kmain.c | 631 ++- code/kernel/ldr_image.c | 589 +-- code/kernel/ldr_section.c | 17 +- code/kernel/mm_file_cache.c | 67 +- code/kernel/mm_pool.c | 36 +- code/kernel/mm_pte.c | 8 +- code/kernel/mm_swap.c | 15 +- code/kernel/mm_virt.c | 64 +- code/kernel/net_socket.c | 12 +- code/kernel/ob_object.c | 5 +- code/kernel/se_security.c | 130 + code/public/ntlite/audio.h | 31 +- code/public/ntlite/boot_info.h | 104 +- code/public/ntlite/ddi/io_driver.h | 21 + code/public/ntlite/ddi/net_abi.h | 3 +- code/public/ntlite/ddk/debug.h | 30 +- code/public/ntlite/ddk/file.h | 125 +- code/public/ntlite/ddk/interrupt.h | 7 + code/public/ntlite/ddk/irql.h | 6 + code/public/ntlite/ddk/list.h | 2 +- code/public/ntlite/ddk/object.h | 11 +- code/public/ntlite/ddk/platform.h | 2 - code/public/ntlite/ddk/settings.h | 2 +- code/public/ntlite/ddk/sync.h | 9 +- code/public/ntlite/handle.h | 100 + code/public/ntlite/module_loader.h | 76 + code/public/ntlite/path.h | 49 + code/public/ntlite/pe_loader.h | 56 + code/public/ntlite/port.h | 150 + code/public/ntlite/process_loader.h | 71 + code/public/ntlite/resource.h | 96 + code/public/ntlite/security.h | 66 + code/public/ntlite/service.h | 21 + code/public/ntlite/service_control.h | 23 + code/public/ntlite/status.h | 4 + code/public/ntlite/syscall.h | 611 ++- code/public/ntlite/volume_manager.h | 27 + code/user/apps/doom/doom.c | 19 +- code/user/apps/doom/doom_audio.c | 32 +- code/user/apps/edit/edit.c | 26 +- code/user/apps/httpget/httpget.c | 17 +- code/user/apps/memtop/memtop.c | 6 +- code/user/apps/ping/ping.c | 8 +- code/user/apps/resolve/resolve.c | 5 +- code/user/apps/shell_native/shell_native.c | 90 +- code/user/apps/shell_posix/shell_posix.c | 14 +- code/user/apps/tcpserve/tcpserve.c | 24 +- code/user/apps/udp/udp.c | 7 +- code/user/apps/video/video.c | 28 +- code/user/personalities/posix/posix_dir.c | 50 +- code/user/personalities/posix/posix_dlfcn.c | 51 +- code/user/personalities/posix/posix_io.c | 26 +- code/user/personalities/posix/posix_mman.c | 16 +- code/user/personalities/posix/posix_path.c | 114 +- code/user/personalities/posix/posix_path.h | 7 + code/user/personalities/posix/posix_proc.c | 26 +- code/user/personalities/posix/posix_socket.c | 17 +- code/user/personalities/posix/posix_thread.c | 2 +- code/user/personalities/win32/gdi32.c | 2 +- code/user/personalities/win32/kernel32.c | 327 +- code/user/personalities/win32/user32.c | 2 +- code/user/personalities/win32/winmm_waveout.c | 11 +- code/user/syscall/module_loader.c | 796 +++ code/user/syscall/module_loader_internal.h | 30 + code/user/syscall/path.c | 499 ++ code/user/syscall/pe_loader.c | 408 ++ code/user/syscall/process_loader.c | 232 + code/user/syscall/resource.c | 308 ++ code/user/syscall/syscall_nr.inc | 300 +- code/user/syscall/syscall_stubs.asm | 371 +- code/user/system/init/init.c | 480 +- code/user/system/rssrv/rssrv.c | 947 ++++ code/user/tests/all_tests/all_tests.c | 33 +- .../tests/apc_return_smoke/apc_return_smoke.c | 29 +- code/user/tests/async_apc/async_apc.c | 29 +- code/user/tests/async_cancel/async_cancel.c | 29 +- .../audio_stream_smoke/audio_stream_smoke.c | 20 +- code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c | 147 - .../cwd_userspace_smoke/cwd_userspace_smoke.c | 117 + .../tests/dirty_writeback/dirty_writeback.c | 31 +- .../exception_dispatcher_smoke.c | 4 +- .../tests/exception_smoke/exception_smoke.c | 21 +- .../tests/file_lock_smoke/file_lock_smoke.c | 62 +- .../tests/file_stat_smoke/file_stat_smoke.c | 28 +- .../tests/handle_dup_smoke/handle_dup_smoke.c | 302 +- .../tests/input_api_smoke/input_api_smoke.c | 4 +- .../input_model_smoke/input_model_smoke.c | 4 +- code/user/tests/mmap_smoke/mmap_smoke.c | 45 +- .../module_load_smoke/module_load_smoke.c | 178 +- .../named_queue_lifetime_smoke.c | 7 +- .../native_exception_runtime_smoke.c | 4 +- .../native_raise_exception_smoke.c | 4 +- code/user/tests/net_irp_smoke/net_irp_smoke.c | 6 +- .../pcache_invalidate/pcache_invalidate.c | 32 +- .../pcache_mmap_read_reuse.c | 38 +- .../pcache_multithread/pcache_multithread.c | 35 +- .../tests/pcache_pressure/pcache_pressure.c | 14 +- code/user/tests/pcache_smoke/pcache_smoke.c | 37 +- .../tests/port_ipc_smoke/port_ipc_smoke.c | 501 ++ .../process_builder_smoke.c | 139 + .../process_exit_multithread_smoke.c | 129 + .../process_protection_smoke.c | 6 +- .../tests/realpath_smoke/realpath_smoke.c | 172 +- .../resource_service_smoke.c | 200 + .../tests/security_smoke/security_smoke.c | 319 ++ code/user/tests/shared_mmap/shared_mmap.c | 185 +- .../socket_addr_smoke/socket_addr_smoke.c | 6 +- .../tests/stats_abi_smoke/stats_abi_smoke.c | 27 +- .../surface_invalid_rect.c | 4 +- .../surface_multithread_present.c | 6 +- code/user/tests/surface_smoke/surface_smoke.c | 4 +- .../syscall_contract_smoke.c | 40 +- code/user/tests/timer_smoke/timer_smoke.c | 4 +- .../win32_condition_variable_smoke.c | 2 +- .../win32_exception_smoke.c | 4 +- .../win32_module_lookup_smoke.c | 2 +- .../win32_mutex_smoke/win32_mutex_smoke.c | 2 +- .../tests/win32_seh_smoke/win32_seh_smoke.c | 4 +- .../win32_thread_suspend_smoke.c | 2 +- .../winmm_waveout_smoke/winmm_waveout_smoke.c | 2 +- .../would_block_smoke/would_block_smoke.c | 6 +- 232 files changed, 20934 insertions(+), 8898 deletions(-) create mode 100644 code/boot/uefi/boot_pe.c create mode 100644 code/boot/uefi/boot_pe.h create mode 100644 code/common/boot_volume_selector.c create mode 100644 code/common/include/common/boot_volume_selector.h delete mode 100644 code/common/include/common/ini_parser.h delete mode 100644 code/common/ini_parser.c rename code/{public/ntlite/ddi => kernel/include/kernel/hal}/hal_api.h (69%) create mode 100644 code/kernel/include/kernel/hal/hal_internal.h create mode 100644 code/kernel/include/kernel/hal/hal_processor.h create mode 100644 code/kernel/include/kernel/hal/hal_timer.h create mode 100644 code/kernel/include/kernel/io_partition.h create mode 100644 code/kernel/include/kernel/io_volume.h create mode 100644 code/kernel/include/kernel/ke_boot_config.h create mode 100644 code/kernel/include/kernel/ke_hal.h create mode 100644 code/kernel/include/kernel/ke_handle.h create mode 100644 code/kernel/include/kernel/ke_id.h create mode 100644 code/kernel/include/kernel/ke_port.h create mode 100644 code/kernel/include/kernel/ke_system_service.h create mode 100644 code/kernel/include/kernel/se_security.h create mode 100644 code/kernel/io_partition.c create mode 100644 code/kernel/io_volume.c create mode 100644 code/kernel/ke_boot_config.c create mode 100644 code/kernel/ke_hal.c create mode 100644 code/kernel/ke_handle.c create mode 100644 code/kernel/ke_id.c create mode 100644 code/kernel/ke_port.c create mode 100644 code/kernel/ke_system_service.c create mode 100644 code/kernel/se_security.c create mode 100644 code/public/ntlite/handle.h create mode 100644 code/public/ntlite/module_loader.h create mode 100644 code/public/ntlite/path.h create mode 100644 code/public/ntlite/pe_loader.h create mode 100644 code/public/ntlite/port.h create mode 100644 code/public/ntlite/process_loader.h create mode 100644 code/public/ntlite/resource.h create mode 100644 code/public/ntlite/security.h create mode 100644 code/public/ntlite/service.h create mode 100644 code/public/ntlite/service_control.h create mode 100644 code/public/ntlite/volume_manager.h create mode 100644 code/user/syscall/module_loader.c create mode 100644 code/user/syscall/module_loader_internal.h create mode 100644 code/user/syscall/path.c create mode 100644 code/user/syscall/pe_loader.c create mode 100644 code/user/syscall/process_loader.c create mode 100644 code/user/syscall/resource.c create mode 100644 code/user/system/rssrv/rssrv.c delete mode 100644 code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c create mode 100644 code/user/tests/cwd_userspace_smoke/cwd_userspace_smoke.c create mode 100644 code/user/tests/port_ipc_smoke/port_ipc_smoke.c create mode 100644 code/user/tests/process_builder_smoke/process_builder_smoke.c create mode 100644 code/user/tests/process_exit_multithread_smoke/process_exit_multithread_smoke.c create mode 100644 code/user/tests/resource_service_smoke/resource_service_smoke.c create mode 100644 code/user/tests/security_smoke/security_smoke.c diff --git a/OVMF/OVMF_VARS_4M.fd b/OVMF/OVMF_VARS_4M.fd index 5f4d30b6c621ef57f574dcad1f4ade4551a8ac87..28ba5cc4e1f8742da20bddc3217038f977bc4b29 100644 GIT binary patch delta 78 zcmZo@P;6*W+;HssWWOM%%^W{2aczE=yNi`Eb@RlaZUM$LAmu4A`Pg?S#&jU_B3Jt@ fCPpA;+J1|Px#@PB085(yYnuRDn*jSV0S*HITKOLF delta 55 zcmZo@P;6*W+;Hss^ejzAr_CHcE^)QjnJ@w|)Al+O=BC@*1*{mC7`6$pvoX@IZah*vw+S8*2xQu6c}GlK4_#pJz*8&i_Lqqj2NfKEn`%g>|oTu z_-gY(qkaL#*AVIrgnA31-a)AM5b6Vj`Us&uL8#9V>I;PW3ZcG1sP7Q!2ZZ_wp?*QA z-w^5#g!&7i{z0h!V2Xu-oq=JJsmA6vMkxX;jK~}&WDYYjhXt9#ip*g{=CC7kIFLD< z$Q&+Y4mUD~2bsf*%;7`k@FQ~skU4^Ij*`IicRI{YEJ6^G?J>H{OCD{XV9OkkvQ2r=zeL43Q8m^lEED;R8_GPGe>11KbB7vA7AgH1@{P8Fv&F zH(U@Eam6jM^a+d;{?A}@e#!SW=j3Re(mJ&?lQqThL6cLSTrs{HHq(>eL19!$qtZh` zAzMqB_SlHqErgK55?#U;3E>D+nn=fmmaBoU=3N;=1Z%Jk8?Xsmunjv$e%YcFBo;lD6BkkZW1_(6bk(qJGaV{=>`+`~j*~ B!BhYM diff --git a/code/boot.ini b/code/boot.ini index 8fc189f..c4b0d0a 100644 --- a/code/boot.ini +++ b/code/boot.ini @@ -1,6 +1,12 @@ +[boot] +; Structural system-volume selector consumed by the bootloader and kernel storage stack. +system_volume = mbr/4E544C31/2 + [kernel] -; bitmask: 1 = debug prints, 2 = self check, 4 = file log, 4096 = checked intrusive lists -; 0x8000 reports MTRRs. Add 0x10000 for one temporary framebuffer WC experiment. -debug = 0xffff +; Debug bitmask. Values are defined canonically in ntlite/ddk/debug.h. +; 0x001 console output, 0x002 boot self-tests, 0x004 file log. +; 0x008 pool canaries, 0x010 pool poison, 0x020 pool quarantine, 0x040 pool caller tracking. +; 0x080 display assertions, 0x100 intrusive-list checks. +debug = 0x1ff [init] commandline = apps/shell_native.exe diff --git a/code/boot/uefi/boot_pe.c b/code/boot/uefi/boot_pe.c new file mode 100644 index 0000000..641b718 --- /dev/null +++ b/code/boot/uefi/boot_pe.c @@ -0,0 +1,482 @@ +// Portable fixed-image PE32+ loader. This file has no UEFI dependencies so firmware glue stays out of image parsing. + +#include "boot_pe.h" + +#include +#include +#include + +#define BOOT_PE_DOS_MAGIC 0x5A4Du +#define BOOT_PE_NT_MAGIC 0x00004550u +#define BOOT_PE_MACHINE_AMD64 0x8664u +#define BOOT_PE_OPTIONAL_MAGIC_PE32_PLUS 0x020Bu +#define BOOT_PE_ORDINAL_FLAG64 0x8000000000000000ull +#define BOOT_PE_DIRECTORY_EXPORT 0u +#define BOOT_PE_DIRECTORY_IMPORT 1u +#define BOOT_PE_DIRECTORY_RELOCATION 5u +#define BOOT_PE_OPTIONAL_MIN_BYTES 112u +#define BOOT_PE_SECTION_BYTES 40u +#define BOOT_PE_IMPORT_DESCRIPTOR_BYTES 20u +#define BOOT_PE_PAGE_SIZE 4096u + +typedef struct BOOT_PE_VIEW { + const uint8_t* File; + size_t FileSize; + size_t NtOffset; + size_t OptionalOffset; + size_t SectionOffset; + uint16_t SectionCount; + uint16_t OptionalSize; + uint32_t DirectoryCount; + uint64_t ImageBase; + uint32_t SizeOfImage; + uint32_t SizeOfHeaders; + uint32_t EntryRva; + uint32_t Checksum; +} BOOT_PE_VIEW; + +// Returns whether adding two sizes would overflow size_t. +static bool BootPeSizeAdd(size_t a, size_t b, size_t* out) { + if (a > SIZE_MAX - b) + return false; + *out = a + b; + return true; +} + +// Returns whether multiplying two sizes would overflow size_t. +static bool BootPeSizeMultiply(size_t a, size_t b, size_t* out) { + if (a != 0 && b > SIZE_MAX / a) + return false; + *out = a * b; + return true; +} + +// Returns whether one byte range is fully contained in a buffer. +static bool BootPeRangeValid(size_t offset, size_t length, size_t bufferSize) { + return offset <= bufferSize && length <= bufferSize - offset; +} + +// Reads one little-endian 16-bit field without assuming alignment. +static uint16_t BootPeReadU16(const void* address) { + uint16_t value; + memcpy(&value, address, sizeof(value)); + return value; +} + +// Reads one little-endian 32-bit field without assuming alignment. +static uint32_t BootPeReadU32(const void* address) { + uint32_t value; + memcpy(&value, address, sizeof(value)); + return value; +} + +// Reads one little-endian 64-bit field without assuming alignment. +static uint64_t BootPeReadU64(const void* address) { + uint64_t value; + memcpy(&value, address, sizeof(value)); + return value; +} + +// Writes one little-endian 64-bit field without assuming alignment. +static void BootPeWriteU64(void* address, uint64_t value) { + memcpy(address, &value, sizeof(value)); +} + +// Parses the fixed PE header fields shared by file and materialized-image validation. +static BOOT_PE_STATUS BootPeParseHeaders(const void* buffer, size_t bufferSize, + bool requireRawSections, BOOT_PE_VIEW* viewOut) { + if (!buffer || !viewOut || bufferSize < 64u) + return BootPeInvalidImage; + + const uint8_t* bytes = (const uint8_t*)buffer; + if (BootPeReadU16(bytes) != BOOT_PE_DOS_MAGIC) + return BootPeInvalidImage; + + size_t ntOffset = (size_t)BootPeReadU32(bytes + 0x3Cu); + if (!BootPeRangeValid(ntOffset, 24u, bufferSize)) + return BootPeInvalidImage; + if (BootPeReadU32(bytes + ntOffset) != BOOT_PE_NT_MAGIC) + return BootPeInvalidImage; + if (BootPeReadU16(bytes + ntOffset + 4u) != BOOT_PE_MACHINE_AMD64) + return BootPeWrongMachine; + + uint16_t sectionCount = BootPeReadU16(bytes + ntOffset + 6u); + uint16_t optionalSize = BootPeReadU16(bytes + ntOffset + 20u); + size_t optionalOffset = ntOffset + 24u; + if (optionalSize < BOOT_PE_OPTIONAL_MIN_BYTES || + !BootPeRangeValid(optionalOffset, optionalSize, bufferSize)) { + return BootPeInvalidImage; + } + if (BootPeReadU16(bytes + optionalOffset) != BOOT_PE_OPTIONAL_MAGIC_PE32_PLUS) + return BootPeInvalidImage; + + uint32_t entryRva = BootPeReadU32(bytes + optionalOffset + 16u); + uint64_t imageBase = BootPeReadU64(bytes + optionalOffset + 24u); + uint32_t sizeOfImage = BootPeReadU32(bytes + optionalOffset + 56u); + uint32_t sizeOfHeaders = BootPeReadU32(bytes + optionalOffset + 60u); + uint32_t checksum = BootPeReadU32(bytes + optionalOffset + 64u); + uint32_t directoryCount = BootPeReadU32(bytes + optionalOffset + 108u); + + if (imageBase == 0 || (imageBase & (BOOT_PE_PAGE_SIZE - 1u)) != 0 || + sizeOfImage == 0 || sizeOfHeaders == 0 || sizeOfHeaders > sizeOfImage || + entryRva >= sizeOfImage) { + return BootPeInvalidImage; + } + + size_t directoryBytes = 0; + uint32_t availableDirectories = (uint32_t)((optionalSize - BOOT_PE_OPTIONAL_MIN_BYTES) / 8u); + if (directoryCount > availableDirectories) + return BootPeInvalidImage; + if (!BootPeSizeMultiply((size_t)directoryCount, 8u, &directoryBytes) || + !BootPeRangeValid(optionalOffset + BOOT_PE_OPTIONAL_MIN_BYTES, directoryBytes, bufferSize)) { + return BootPeInvalidImage; + } + + size_t sectionOffset = 0; + size_t sectionBytes = 0; + if (!BootPeSizeAdd(optionalOffset, optionalSize, §ionOffset) || + !BootPeSizeMultiply(sectionCount, BOOT_PE_SECTION_BYTES, §ionBytes) || + !BootPeRangeValid(sectionOffset, sectionBytes, bufferSize)) { + return BootPeInvalidImage; + } + + if (requireRawSections) { + if (sizeOfHeaders > bufferSize) + return BootPeInvalidImage; + + for (uint16_t index = 0; index < sectionCount; ++index) { + const uint8_t* section = bytes + sectionOffset + (size_t)index * BOOT_PE_SECTION_BYTES; + uint32_t virtualSize = BootPeReadU32(section + 8u); + uint32_t virtualAddress = BootPeReadU32(section + 12u); + uint32_t rawSize = BootPeReadU32(section + 16u); + uint32_t rawOffset = BootPeReadU32(section + 20u); + uint32_t mappedSize = virtualSize > rawSize ? virtualSize : rawSize; + + if (virtualAddress > sizeOfImage || mappedSize > sizeOfImage - virtualAddress) + return BootPeInvalidImage; + if (rawSize != 0 && !BootPeRangeValid(rawOffset, rawSize, bufferSize)) + return BootPeInvalidImage; + } + } + + BOOT_PE_VIEW view = {0}; + view.File = bytes; + view.FileSize = bufferSize; + view.NtOffset = ntOffset; + view.OptionalOffset = optionalOffset; + view.SectionOffset = sectionOffset; + view.SectionCount = sectionCount; + view.OptionalSize = optionalSize; + view.DirectoryCount = directoryCount; + view.ImageBase = imageBase; + view.SizeOfImage = sizeOfImage; + view.SizeOfHeaders = sizeOfHeaders; + view.EntryRva = entryRva; + view.Checksum = checksum; + *viewOut = view; + return BootPeSuccess; +} + +// Reads one PE data-directory entry when the optional header declares it. +static bool BootPeGetDirectory(const BOOT_PE_VIEW* view, uint32_t index, + uint32_t* rvaOut, uint32_t* sizeOut) { + if (!view || index >= view->DirectoryCount) + return false; + const uint8_t* directory = view->File + view->OptionalOffset + BOOT_PE_OPTIONAL_MIN_BYTES + (size_t)index * 8u; + if (rvaOut) *rvaOut = BootPeReadU32(directory); + if (sizeOut) *sizeOut = BootPeReadU32(directory + 4u); + return true; +} + +// Computes the standard PE checksum while treating the stored CheckSum field as zero. +static uint32_t BootPeComputeChecksum(const uint8_t* file, size_t fileSize, size_t checksumOffset) { + uint64_t sum = 0; + size_t offset = 0; + + while (offset + 1u < fileSize) { + if (offset == checksumOffset || offset == checksumOffset + 2u) { + offset += 2u; + continue; + } + + sum += BootPeReadU16(file + offset); + sum = (sum & 0xFFFFu) + (sum >> 16); + offset += 2u; + } + + if (offset < fileSize) + sum += file[offset]; + + sum = (sum & 0xFFFFu) + (sum >> 16); + sum += sum >> 16; + sum = (sum & 0xFFFFu) + fileSize; + return (uint32_t)sum; +} + +// Returns whether an image RVA range is fully mapped inside SizeOfImage. +static bool BootPeImageRangeValid(uint32_t rva, size_t length, size_t imageSize) { + return (size_t)rva <= imageSize && length <= imageSize - (size_t)rva; +} + +// Returns whether an image-relative string has a NUL terminator before the image ends. +static bool BootPeImageStringValid(const uint8_t* image, size_t imageSize, uint32_t rva) { + if ((size_t)rva >= imageSize) + return false; + for (size_t offset = (size_t)rva; offset < imageSize; ++offset) { + if (image[offset] == 0) + return true; + } + return false; +} + +// Compares two ASCII module names without case sensitivity. +static bool BootPeAsciiEqualInsensitive(const char* a, const char* b) { + if (!a || !b) + return false; + while (*a && *b) { + char ca = *a++; + char cb = *b++; + if (ca >= 'A' && ca <= 'Z') ca = (char)(ca + ('a' - 'A')); + if (cb >= 'A' && cb <= 'Z') cb = (char)(cb + ('a' - 'A')); + if (ca != cb) + return false; + } + return *a == *b; +} + +// Finds one named export RVA and rejects forwarded exports. +static BOOT_PE_STATUS BootPeFindExport(const uint8_t* provider, size_t providerSize, + const BOOT_PE_VIEW* providerView, const char* functionName, + uint32_t* functionRvaOut) { + uint32_t exportRva = 0; + uint32_t exportSize = 0; + if (!BootPeGetDirectory(providerView, BOOT_PE_DIRECTORY_EXPORT, &exportRva, &exportSize) || + exportRva == 0 || exportSize < 40u || !BootPeImageRangeValid(exportRva, exportSize, providerSize)) { + return BootPeImportNotFound; + } + + const uint8_t* exports = provider + exportRva; + uint32_t functionCount = BootPeReadU32(exports + 20u); + uint32_t nameCount = BootPeReadU32(exports + 24u); + uint32_t functionsRva = BootPeReadU32(exports + 28u); + uint32_t namesRva = BootPeReadU32(exports + 32u); + uint32_t ordinalsRva = BootPeReadU32(exports + 36u); + + if (!BootPeImageRangeValid(functionsRva, (size_t)functionCount * 4u, providerSize) || + !BootPeImageRangeValid(namesRva, (size_t)nameCount * 4u, providerSize) || + !BootPeImageRangeValid(ordinalsRva, (size_t)nameCount * 2u, providerSize)) { + return BootPeInvalidImage; + } + + for (uint32_t index = 0; index < nameCount; ++index) { + uint32_t nameRva = BootPeReadU32(provider + namesRva + (size_t)index * 4u); + if (!BootPeImageStringValid(provider, providerSize, nameRva)) + return BootPeInvalidImage; + if (strcmp((const char*)(provider + nameRva), functionName) != 0) + continue; + + uint16_t ordinal = BootPeReadU16(provider + ordinalsRva + (size_t)index * 2u); + if (ordinal >= functionCount) + return BootPeInvalidImage; + + uint32_t functionRva = BootPeReadU32(provider + functionsRva + (size_t)ordinal * 4u); + if (!BootPeImageRangeValid(functionRva, 1u, providerSize)) + return BootPeInvalidImage; + if (functionRva >= exportRva && (uint64_t)functionRva < (uint64_t)exportRva + exportSize) + return BootPeUnsupportedImport; + + *functionRvaOut = functionRva; + return BootPeSuccess; + } + + return BootPeImportNotFound; +} + +// Validates one fixed AMD64 PE32+ file and returns the image metadata required by the bootloader. +BOOT_PE_STATUS BootPeInspect(const void* file, size_t fileSize, BOOT_PE_INFO* infoOut) { + BOOT_PE_VIEW view; + BOOT_PE_STATUS status = BootPeParseHeaders(file, fileSize, true, &view); + if (status != BootPeSuccess) + return status; + + uint32_t relocationRva = 0; + uint32_t relocationSize = 0; + if (BootPeGetDirectory(&view, BOOT_PE_DIRECTORY_RELOCATION, &relocationRva, &relocationSize) && + (relocationRva != 0 || relocationSize != 0)) { + return BootPeRelocationsPresent; + } + + if (view.Checksum == 0) + return BootPeChecksumMissing; + + size_t checksumOffset = view.OptionalOffset + 64u; + uint32_t computed = BootPeComputeChecksum(view.File, view.FileSize, checksumOffset); + if (computed != view.Checksum) + return BootPeChecksumMismatch; + + if (infoOut) { + infoOut->ImageBase = view.ImageBase; + infoOut->SizeOfImage = view.SizeOfImage; + infoOut->SizeOfHeaders = view.SizeOfHeaders; + infoOut->EntryRva = view.EntryRva; + infoOut->Checksum = view.Checksum; + } + return BootPeSuccess; +} + +// Copies a validated fixed image into a zeroed image-sized destination. +BOOT_PE_STATUS BootPeMaterialize(const void* file, size_t fileSize, void* image, + size_t imageCapacity, BOOT_PE_INFO* infoOut) { + BOOT_PE_INFO info; + BOOT_PE_STATUS status = BootPeInspect(file, fileSize, &info); + if (status != BootPeSuccess) + return status; + if (!image || imageCapacity < info.SizeOfImage) + return BootPeBufferTooSmall; + + BOOT_PE_VIEW view; + status = BootPeParseHeaders(file, fileSize, true, &view); + if (status != BootPeSuccess) + return status; + + memset(image, 0, info.SizeOfImage); + memcpy(image, file, info.SizeOfHeaders); + + for (uint16_t index = 0; index < view.SectionCount; ++index) { + const uint8_t* section = view.File + view.SectionOffset + (size_t)index * BOOT_PE_SECTION_BYTES; + uint32_t virtualAddress = BootPeReadU32(section + 12u); + uint32_t rawSize = BootPeReadU32(section + 16u); + uint32_t rawOffset = BootPeReadU32(section + 20u); + if (rawSize != 0) + memcpy((uint8_t*)image + virtualAddress, view.File + rawOffset, rawSize); + } + + if (infoOut) + *infoOut = info; + return BootPeSuccess; +} + +// Returns whether a materialized image declares any import descriptors. +BOOT_PE_STATUS BootPeRequireNoImports(const void* image, size_t imageSize) { + BOOT_PE_VIEW view; + BOOT_PE_STATUS status = BootPeParseHeaders(image, imageSize, false, &view); + if (status != BootPeSuccess) + return status; + + uint32_t importRva = 0; + uint32_t importSize = 0; + if (BootPeGetDirectory(&view, BOOT_PE_DIRECTORY_IMPORT, &importRva, &importSize) && + (importRva != 0 || importSize != 0)) { + return BootPeImportsPresent; + } + return BootPeSuccess; +} + +// Resolves one consumer's named imports against a single fixed provider image. +BOOT_PE_STATUS BootPeResolveImports(void* consumerImage, size_t consumerSize, + const void* providerImage, size_t providerSize, + const char* providerName, uint64_t providerVirtualBase) { + if (!consumerImage || !providerImage || !providerName) + return BootPeInvalidImage; + + BOOT_PE_VIEW consumerView; + BOOT_PE_VIEW providerView; + BOOT_PE_STATUS status = BootPeParseHeaders(consumerImage, consumerSize, false, &consumerView); + if (status != BootPeSuccess) + return status; + status = BootPeParseHeaders(providerImage, providerSize, false, &providerView); + if (status != BootPeSuccess) + return status; + + uint32_t importRva = 0; + uint32_t importSize = 0; + if (!BootPeGetDirectory(&consumerView, BOOT_PE_DIRECTORY_IMPORT, &importRva, &importSize) || + importRva == 0 || importSize == 0) { + return BootPeSuccess; + } + if (importSize < BOOT_PE_IMPORT_DESCRIPTOR_BYTES || + !BootPeImageRangeValid(importRva, importSize, consumerSize)) { + return BootPeInvalidImage; + } + + uint32_t descriptorCount = importSize / BOOT_PE_IMPORT_DESCRIPTOR_BYTES; + bool terminated = false; + uint8_t* consumer = (uint8_t*)consumerImage; + const uint8_t* provider = (const uint8_t*)providerImage; + + for (uint32_t descriptorIndex = 0; descriptorIndex < descriptorCount; ++descriptorIndex) { + uint8_t* descriptor = consumer + importRva + (size_t)descriptorIndex * BOOT_PE_IMPORT_DESCRIPTOR_BYTES; + uint32_t originalThunkRva = BootPeReadU32(descriptor); + uint32_t nameRva = BootPeReadU32(descriptor + 12u); + uint32_t firstThunkRva = BootPeReadU32(descriptor + 16u); + + if (originalThunkRva == 0 && nameRva == 0 && firstThunkRva == 0) { + terminated = true; + break; + } + if (!BootPeImageStringValid(consumer, consumerSize, nameRva) || + !BootPeAsciiEqualInsensitive((const char*)(consumer + nameRva), providerName)) { + return BootPeUnsupportedImport; + } + + uint32_t lookupRva = originalThunkRva ? originalThunkRva : firstThunkRva; + if (!BootPeImageRangeValid(lookupRva, 8u, consumerSize) || + !BootPeImageRangeValid(firstThunkRva, 8u, consumerSize)) { + return BootPeInvalidImage; + } + + size_t lookupCapacity = (consumerSize - lookupRva) / 8u; + size_t writeCapacity = (consumerSize - firstThunkRva) / 8u; + size_t thunkCapacity = lookupCapacity < writeCapacity ? lookupCapacity : writeCapacity; + bool thunkTerminated = false; + + for (size_t thunkIndex = 0; thunkIndex < thunkCapacity; ++thunkIndex) { + uint64_t lookup = BootPeReadU64(consumer + lookupRva + thunkIndex * 8u); + if (lookup == 0) { + thunkTerminated = true; + break; + } + if ((lookup & BOOT_PE_ORDINAL_FLAG64) != 0 || lookup > UINT32_MAX) + return BootPeUnsupportedImport; + + uint32_t importByNameRva = (uint32_t)lookup; + if (!BootPeImageRangeValid(importByNameRva, 3u, consumerSize) || + !BootPeImageStringValid(consumer, consumerSize, importByNameRva + 2u)) { + return BootPeInvalidImage; + } + + const char* functionName = (const char*)(consumer + importByNameRva + 2u); + uint32_t functionRva = 0; + status = BootPeFindExport(provider, providerSize, &providerView, functionName, &functionRva); + if (status != BootPeSuccess) + return status; + + if (providerVirtualBase > UINT64_MAX - functionRva) + return BootPeInvalidImage; + BootPeWriteU64(consumer + firstThunkRva + thunkIndex * 8u, + providerVirtualBase + functionRva); + } + + if (!thunkTerminated) + return BootPeInvalidImage; + } + + return terminated ? BootPeSuccess : BootPeInvalidImage; +} + +// Returns a concise diagnostic name for one portable loader status. +const char* BootPeStatusName(BOOT_PE_STATUS status) { + switch (status) { + case BootPeSuccess: return "success"; + case BootPeInvalidImage: return "invalid image"; + case BootPeWrongMachine: return "wrong machine"; + case BootPeChecksumMissing: return "checksum missing"; + case BootPeChecksumMismatch: return "checksum mismatch"; + case BootPeRelocationsPresent: return "relocations present"; + case BootPeBufferTooSmall: return "buffer too small"; + case BootPeImportsPresent: return "imports present"; + case BootPeUnsupportedImport: return "unsupported import"; + case BootPeImportNotFound: return "import not found"; + default: return "unknown PE status"; + } +} diff --git a/code/boot/uefi/boot_pe.h b/code/boot/uefi/boot_pe.h new file mode 100644 index 0000000..15a988a --- /dev/null +++ b/code/boot/uefi/boot_pe.h @@ -0,0 +1,45 @@ +// Portable fixed-image PE32+ loader declarations used by the UEFI bootloader. + +#pragma once + +#include +#include + +typedef enum BOOT_PE_STATUS { + BootPeSuccess = 0, + BootPeInvalidImage, + BootPeWrongMachine, + BootPeChecksumMissing, + BootPeChecksumMismatch, + BootPeRelocationsPresent, + BootPeBufferTooSmall, + BootPeImportsPresent, + BootPeUnsupportedImport, + BootPeImportNotFound, +} BOOT_PE_STATUS; + +typedef struct BOOT_PE_INFO { + uint64_t ImageBase; + uint32_t SizeOfImage; + uint32_t SizeOfHeaders; + uint32_t EntryRva; + uint32_t Checksum; +} BOOT_PE_INFO; + +// Validates one fixed AMD64 PE32+ file and returns the image metadata required by the bootloader. +BOOT_PE_STATUS BootPeInspect(const void* file, size_t fileSize, BOOT_PE_INFO* infoOut); + +// Copies a validated fixed image into a zeroed image-sized destination. +BOOT_PE_STATUS BootPeMaterialize(const void* file, size_t fileSize, void* image, + size_t imageCapacity, BOOT_PE_INFO* infoOut); + +// Returns whether a materialized image declares any import descriptors. +BOOT_PE_STATUS BootPeRequireNoImports(const void* image, size_t imageSize); + +// Resolves one consumer's named imports against a single fixed provider image. +BOOT_PE_STATUS BootPeResolveImports(void* consumerImage, size_t consumerSize, + const void* providerImage, size_t providerSize, + const char* providerName, uint64_t providerVirtualBase); + +// Returns a concise diagnostic name for one portable loader status. +const char* BootPeStatusName(BOOT_PE_STATUS status); diff --git a/code/boot/uefi/main.c b/code/boot/uefi/main.c index 4027824..cb15f64 100644 --- a/code/boot/uefi/main.c +++ b/code/boot/uefi/main.c @@ -1,24 +1,18 @@ -// UEFI bootloader entry point. This file parses configuration, loads PE images, builds the initial page tables, converts the EFI memory map, and hands a BOOT_INFO block to the kernel. +// UEFI bootloader entry point. This file performs firmware discovery and the minimum fixed-image/raw-file handoff needed to enter the kernel. -// Responsibilities (see design ยง5.2): -// 1. Parse boot.ini -// 2. Read drivers/drivers.ini -// 3. Load PE images (kernel.exe, hal.dll, syscall.dll, win32.dll, *.sys) -// 4. Validate PE checksums -// 5. Map images into the upper half of a new PML4 -// 6. Map all RAM as direct physical window at 0xFFFF880000000000 -// 7. Convert EFI memory map -> MEMORY_MAP -// 8. ExitBootServices -// 9. Set up minimal GDT/IDT, enable NXE, set CR3 -// 10. Fill BOOT_INFO and jump to KernelMain. +// The loader interprets only the [boot] system_volume bootstrap selector. It scans drivers.ini [boot], +// verifies and maps fixed kernel/HAL images, translates firmware state, and exits boot services. #include #include #include +#include +#include #include +#include #include #include -#include +#include "boot_pe.h" #pragma intrinsic(_disable) int _fltused = 0; @@ -32,8 +26,16 @@ int _fltused = 0; #define BOOT_PAGE_CACHE_WC 0x08ULL #define BOOT_PAGE_CACHE_UC 0x10ULL +#ifndef BOOT_VOLUME_DIAGNOSTICS +#if defined(NDEBUG) +#define BOOT_VOLUME_DIAGNOSTICS 0 +#else +#define BOOT_VOLUME_DIAGNOSTICS 1 +#endif +#endif + // Implements the ascii Str To Unicode Str S operation. -static int AsciiStrToUnicodeStrS(const char* Source, uint16_t* Dest, size_t DestMax) { +static int AsciiStrToUnicodeStrS(const char* Source, CHAR16* Dest, size_t DestMax) { if (!Source || !Dest || !DestMax) return -1; for (size_t i = 0; i < DestMax; i++) { @@ -45,7 +47,7 @@ static int AsciiStrToUnicodeStrS(const char* Source, uint16_t* Dest, size_t Dest } // Implements the ascii Str Cpy S operation. -int AsciiStrCpyS(char* Dest, size_t DestMax, const char* Source) { +static int AsciiStrCpyS(char* Dest, size_t DestMax, const char* Source) { if (!Dest || !Source || !DestMax) return -1; for (size_t i = 0; i < DestMax; i++) { @@ -56,56 +58,15 @@ int AsciiStrCpyS(char* Dest, size_t DestMax, const char* Source) { return -1; } -// Implements the unicode Str To Ascii Str S operation. -int UnicodeStrToAsciiStrS(const uint16_t* Source, char* Dest, size_t DestMax) { - if (!Source || !Dest || !DestMax) return -1; - - for (size_t i = 0; i < DestMax; i++) { - // Narrowing: only taking the lower 8 bits. - Dest[i] = (char)(Source[i] & 0xFF); - - if (Source[i] == 0) return 0; - } - - Dest[DestMax - 1] = '\0'; - return -1; -} - -// Validates the PE32+ checksum using the same field layout the Windows loader expects. -static UINT32 ComputePeChecksum(const UINT8* buf, UINTN size) { - UINT64 checksum = 0; - const UINT16* words = (const UINT16 *)buf; - UINTN wordCount = size / 2; - - // Locate and zero the CheckSum field in the optional header. - UINT32 peOff = *(UINT32 *)(buf + 0x3C); - UINTN cksumOff = peOff + 4 + 20 + 64; // COFF hdr + optional hdr offset to CheckSum - UINT16 savedLo = words[cksumOff / 2]; - UINT16 savedHi = words[cksumOff / 2 + 1]; - ((UINT16 *)buf)[cksumOff / 2] = 0; - ((UINT16 *)buf)[cksumOff / 2 + 1] = 0; - - for (UINTN i = 0; i < wordCount; i++) { - checksum += words[i]; - if (checksum > 0xFFFF) checksum = (checksum & 0xFFFF) + (checksum >> 16); - } - if (size & 1) { checksum += buf[size - 1]; } - checksum = (checksum & 0xFFFF) + (checksum >> 16); - checksum += (UINT64)size; - - // Restore the image buffer before returning so checksum validation is non-destructive. - ((UINT16 *)buf)[cksumOff / 2] = savedLo; - ((UINT16 *)buf)[cksumOff / 2 + 1] = savedHi; - return (UINT32)checksum; -} - // Allocates and links the minimal x86-64 page tables used before KernelMain runs. static UINT64* AllocPage(EFI_PHYSICAL_ADDRESS* physOut) { - VOID* mem = NULL; - gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1, (EFI_PHYSICAL_ADDRESS *)&mem); - gBS->SetMem(mem, PAGE_SIZE, 0); - if (physOut) *physOut = (EFI_PHYSICAL_ADDRESS)mem; // UEFI identity maps loader allocations here. - return (UINT64 *)mem; + EFI_PHYSICAL_ADDRESS phys = 0; + EFI_STATUS status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1, &phys); + if (EFI_ERROR(status)) + return NULL; + gBS->SetMem((VOID*)phys, PAGE_SIZE, 0); + if (physOut) *physOut = phys; + return (UINT64*)phys; } #define PML4_IDX(va) (((va) >> 39) & 0x1FF) @@ -120,57 +81,74 @@ static UINT64* gPml4; // Implements the ensure Table operation. static UINT64* EnsureTable(UINT64* parent, UINTN index) { + if (!parent) + return NULL; UINT64* entry = &parent[index]; if (!(*entry & 1)) { EFI_PHYSICAL_ADDRESS p; UINT64* child = AllocPage(&p); + if (!child) + return NULL; *entry = p | 3; return child; } - return (UINT64 *)(*entry & ~0xFFFULL); + return (UINT64*)(*entry & ~0xFFFULL); } -// Implements the map4 K operation. -static void Map4K(UINT64 virt, UINT64 phys, UINT64 flags) { +// Maps one 4 KiB page. +static bool Map4K(UINT64 virt, UINT64 phys, UINT64 flags) { UINT64* pdpt = EnsureTable(gPml4, PML4_IDX(virt)); - UINT64* pdt = EnsureTable(pdpt, PDPT_IDX(virt)); - UINT64* pt = EnsureTable(pdt, PDT_IDX(virt)); + UINT64* pdt = EnsureTable(pdpt, PDPT_IDX(virt)); + UINT64* pt = EnsureTable(pdt, PDT_IDX(virt)); + if (!pt) + return false; pt[PT_IDX(virt)] = phys | flags; + return true; } -// Implements the map2 M operation. -static void Map2M(UINT64 virt, UINT64 phys, UINT64 flags) { +// Maps one 2 MiB page. +static bool Map2M(UINT64 virt, UINT64 phys, UINT64 flags) { UINT64* pdpt = EnsureTable(gPml4, PML4_IDX(virt)); - UINT64* pdt = EnsureTable(pdpt, PDPT_IDX(virt)); - pdt[PDT_IDX(virt)] = phys | flags | 0x80ULL; // PS bit + UINT64* pdt = EnsureTable(pdpt, PDPT_IDX(virt)); + if (!pdt) + return false; + pdt[PDT_IDX(virt)] = phys | flags | 0x80ULL; + return true; } -// Implements the map1 G operation. -static void Map1G(UINT64 virt, UINT64 phys, UINT64 flags) { +// Maps one 1 GiB page. +static bool Map1G(UINT64 virt, UINT64 phys, UINT64 flags) { UINT64* pdpt = EnsureTable(gPml4, PML4_IDX(virt)); - pdpt[PDPT_IDX(virt)] = phys | flags | 0x80ULL; // PS bit + if (!pdpt) + return false; + pdpt[PDPT_IDX(virt)] = phys | flags | 0x80ULL; + return true; } -// Maps range Smart. -static void MapRangeSmart(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags) { +// Maps one range using the largest naturally aligned page size available. +static bool MapRangeSmart(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags) { while (size) { + bool mapped; if ((virt & (PAGE_1G - 1)) == 0 && (phys & (PAGE_1G - 1)) == 0 && size >= PAGE_1G) { - Map1G(virt, phys, flags); + mapped = Map1G(virt, phys, flags); virt += PAGE_1G; phys += PAGE_1G; size -= PAGE_1G; } else if ((virt & (PAGE_2M - 1)) == 0 && (phys & (PAGE_2M - 1)) == 0 && size >= PAGE_2M) { - Map2M(virt, phys, flags); + mapped = Map2M(virt, phys, flags); virt += PAGE_2M; phys += PAGE_2M; size -= PAGE_2M; } else { - Map4K(virt, phys, flags); + mapped = Map4K(virt, phys, flags); virt += PAGE_SIZE; phys += PAGE_SIZE; size -= PAGE_SIZE; } + if (!mapped) + return false; } + return true; } // Returns whether a firmware descriptor describes memory that is safe to expose through the direct map. @@ -202,7 +180,7 @@ static UINT64 DirectMapCacheFlags(const EFI_MEMORY_DESCRIPTOR* desc) { return BOOT_PAGE_CACHE_WB; } -static void MapRangeSkipping(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags, +static bool MapRangeSkipping(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags, const UINT64* holes, UINTN holeCount) { UINT64 curVirt = virt; UINT64 curPhys = phys; @@ -215,7 +193,8 @@ static void MapRangeSkipping(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags if (curVirt < hStart) { UINT64 prefixSize = hStart - curVirt; - MapRangeSmart(curVirt, curPhys, prefixSize, flags); + if (!MapRangeSmart(curVirt, curPhys, prefixSize, flags)) + return false; curPhys += prefixSize; curVirt += prefixSize; } @@ -227,782 +206,1419 @@ static void MapRangeSkipping(UINT64 virt, UINT64 phys, UINT64 size, UINT64 flags } } - if (curVirt < end) { - MapRangeSmart(curVirt, curPhys, end - curVirt, flags); - } + if (curVirt < end && !MapRangeSmart(curVirt, curPhys, end - curVirt, flags)) + return false; + return true; } -// File loading. -static EFI_FILE_PROTOCOL* gRoot; +// File loading and boot handoff helpers. +static EFI_HANDLE gBootDevice; +static EFI_FILE_PROTOCOL* gBootRoot; +static EFI_FILE_PROTOCOL* gSystemRoot; -// Loads file. -static UINT8* LoadFile(CHAR16* path, UINTN* sizeOut) { - EFI_STATUS Status; - EFI_FILE_PROTOCOL* fh; - - // Open the file. - Status = gRoot->Open(gRoot, &fh, path, EFI_FILE_MODE_READ, 0); - if (EFI_ERROR(Status)) return NULL; +typedef struct BOOT_EFI_MEMORY_MAP { + EFI_MEMORY_DESCRIPTOR* Buffer; + UINTN Capacity; + UINTN Size; + UINTN Key; + UINTN DescriptorSize; + UINT32 DescriptorVersion; +} BOOT_EFI_MEMORY_MAP; - // Get the correct buffer size for EFI_FILE_INFO. - EFI_FILE_INFO* info; - UINTN infoSz = 0; - - // This first call will fail but will return the required size in infoSz. - fh->GetInfo(fh, &gEfiFileInfoGuid, &infoSz, NULL); - - // Allocate the buffer for the info struct. - info = AllocatePool(infoSz); - if (info == NULL) { - fh->Close(fh); - return NULL; - } - - // Now actually get the info. - Status = fh->GetInfo(fh, &gEfiFileInfoGuid, &infoSz, info); - if (EFI_ERROR(Status)) { - FreePool(info); - fh->Close(fh); - return NULL; - } - - // Allocate buffer for file content. - UINTN sz = (UINTN)info->FileSize; - FreePool(info); // Don't need this anymore - - UINT8* buf = AllocatePool(sz); - if (buf == NULL) { - fh->Close(fh); - return NULL; - } - - // Read the file into the buffer. - Status = fh->Read(fh, &sz, buf); - fh->Close(fh); - - if (EFI_ERROR(Status)) { - FreePool(buf); - return NULL; - } - - if (sizeOut) *sizeOut = sz; - return buf; +// Allocates zeroed loader-data pages whose physical address remains valid in the boot handoff. +static EFI_STATUS BootAllocatePages(UINTN pageCount, EFI_PHYSICAL_ADDRESS* physicalOut) { + if (!physicalOut || pageCount == 0) + return EFI_INVALID_PARAMETER; + EFI_PHYSICAL_ADDRESS physical = 0; + EFI_STATUS status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, pageCount, &physical); + if (EFI_ERROR(status)) + return status; + gBS->SetMem((VOID*)physical, pageCount * PAGE_SIZE, 0); + *physicalOut = physical; + return EFI_SUCCESS; } -// INI parsing state. -typedef struct { - BOOT_INFO* bi; - CHAR16 drivers[32][64]; - int driverCount; - int debug; - char commandLine[64]; -} PARSE_STATE; +// Opens one boot-volume file and returns its exact byte length. +static EFI_STATUS BootOpenFile(EFI_FILE_PROTOCOL* root, CHAR16* path, + EFI_FILE_PROTOCOL** fileOut, UINTN* sizeOut) { + if (!root || !path || !fileOut || !sizeOut) + return EFI_INVALID_PARAMETER; -// Parses boot Ini Integer. -static int ParseBootIniInteger(const char* val) { - int out = 0; - if (!val) return 0; - while (*val == ' ' || *val == '\t') val++; - int base = 10; - if (val[0] == '0' && (val[1] == 'x' || val[1] == 'X')) { - base = 16; - val += 2; + EFI_FILE_PROTOCOL* file = NULL; + EFI_STATUS status = root->Open(root, &file, path, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(status)) + return status; + + UINTN infoSize = 0; + status = file->GetInfo(file, &gEfiFileInfoGuid, &infoSize, NULL); + if (status != EFI_BUFFER_TOO_SMALL || infoSize < sizeof(EFI_FILE_INFO)) { + file->Close(file); + return EFI_LOAD_ERROR; } - while (*val) { - char c = *val++; - int digit; - if (c >= '0' && c <= '9') digit = c - '0'; - else if (c >= 'a' && c <= 'f') digit = 10 + c - 'a'; - else if (c >= 'A' && c <= 'F') digit = 10 + c - 'A'; - else break; - if (digit >= base) break; - out = out * base + digit; + + EFI_FILE_INFO* info = NULL; + status = gBS->AllocatePool(EfiBootServicesData, infoSize, (VOID**)&info); + if (EFI_ERROR(status)) { + file->Close(file); + return status; } + + status = file->GetInfo(file, &gEfiFileInfoGuid, &infoSize, info); + if (!EFI_ERROR(status)) { + if (info->FileSize > (UINT64)(~(UINTN)0)) + status = EFI_BAD_BUFFER_SIZE; + else + *sizeOut = (UINTN)info->FileSize; + } + + gBS->FreePool(info); + if (EFI_ERROR(status)) { + file->Close(file); + return status; + } + + *fileOut = file; + return EFI_SUCCESS; +} + +// Reads a temporary file into boot-services memory and appends one NUL byte for text-safe inspection. +static EFI_STATUS BootReadPoolFile(EFI_FILE_PROTOCOL* root, CHAR16* path, + UINT8** bufferOut, UINTN* sizeOut) { + if (!root || !path || !bufferOut || !sizeOut) + return EFI_INVALID_PARAMETER; + + *bufferOut = NULL; + *sizeOut = 0; + EFI_FILE_PROTOCOL* file = NULL; + UINTN fileSize = 0; + EFI_STATUS status = BootOpenFile(root, path, &file, &fileSize); + if (EFI_ERROR(status)) + return status; + if (fileSize == ~(UINTN)0) { + file->Close(file); + return EFI_BAD_BUFFER_SIZE; + } + + UINT8* buffer = NULL; + status = gBS->AllocatePool(EfiBootServicesData, fileSize + 1u, (VOID**)&buffer); + if (EFI_ERROR(status)) { + file->Close(file); + return status; + } + gBS->SetMem(buffer, fileSize + 1u, 0); + + UINTN bytesRead = fileSize; + status = file->Read(file, &bytesRead, buffer); + file->Close(file); + if (EFI_ERROR(status) || bytesRead != fileSize) { + gBS->FreePool(buffer); + return EFI_ERROR(status) ? status : EFI_LOAD_ERROR; + } + + *bufferOut = buffer; + *sizeOut = fileSize; + return EFI_SUCCESS; +} + +// Reads a retained raw file into its own loader-data pages and guarantees a trailing NUL byte. +static EFI_STATUS BootReadBlobFile(EFI_FILE_PROTOCOL* root, CHAR16* path, BOOT_BLOB* blobOut) { + if (!root || !path || !blobOut) + return EFI_INVALID_PARAMETER; + + *blobOut = (BOOT_BLOB){0}; + EFI_FILE_PROTOCOL* file = NULL; + UINTN fileSize = 0; + EFI_STATUS status = BootOpenFile(root, path, &file, &fileSize); + if (EFI_ERROR(status)) + return status; + if (fileSize > UINT32_MAX) { + file->Close(file); + return EFI_BAD_BUFFER_SIZE; + } + + UINTN allocationBytes = fileSize + 1u; + UINTN pageCount = (allocationBytes + PAGE_SIZE - 1u) / PAGE_SIZE; + if (pageCount == 0 || pageCount > UINT32_MAX) { + file->Close(file); + return EFI_BAD_BUFFER_SIZE; + } + + EFI_PHYSICAL_ADDRESS physical = 0; + status = BootAllocatePages(pageCount, &physical); + if (EFI_ERROR(status)) { + file->Close(file); + return status; + } + + UINTN bytesRead = fileSize; + status = file->Read(file, &bytesRead, (VOID*)physical); + file->Close(file); + if (EFI_ERROR(status) || bytesRead != fileSize) { + gBS->FreePages(physical, pageCount); + return EFI_ERROR(status) ? status : EFI_LOAD_ERROR; + } + + BOOT_BLOB blob = {0}; + blob.PhysicalBase = (uintptr_t)physical; + blob.Length = (uint32_t)fileSize; + blob.PageCount = (uint32_t)pageCount; + *blobOut = blob; + return EFI_SUCCESS; +} + +// Returns whether one character is horizontal line whitespace in the tiny boot configuration dialect. +static bool BootLineSpace(char c) { + return c == ' ' || c == '\t' || c == '\r'; +} + +// Trims horizontal whitespace from one immutable line span. +static void BootTrimSpan(const char** begin, const char** end) { + while (*begin < *end && BootLineSpace(**begin)) + ++*begin; + while (*end > *begin && BootLineSpace((*end)[-1])) + --*end; +} + +// Returns whether one line span exactly matches a NUL-terminated ASCII string. +static bool BootSpanEquals(const char* begin, const char* end, const char* text) { + while (begin < end && *text) { + if (*begin++ != *text++) + return false; + } + return begin == end && *text == '\0'; +} + +// Extracts and parses only the system_volume key from boot.ini [boot]. +static EFI_STATUS BootParseSystemVolume(const char* text, UINTN length, + BOOT_VOLUME_SELECTOR* selectorOut) { + if (!text || !selectorOut) + return EFI_INVALID_PARAMETER; + + bool inBootSection = false; + bool selectorSeen = false; + BOOT_VOLUME_SELECTOR selector = {0}; + UINTN offset = 0; + while (offset < length) { + UINTN lineStart = offset; + while (offset < length && text[offset] != '\n') + ++offset; + UINTN lineEnd = offset; + if (offset < length) + ++offset; + + const char* begin = text + lineStart; + const char* end = text + lineEnd; + BootTrimSpan(&begin, &end); + if (begin == end || *begin == ';' || *begin == '#') + continue; + + if (*begin == '[' && end[-1] == ']') { + const char* sectionBegin = begin + 1; + const char* sectionEnd = end - 1; + BootTrimSpan(§ionBegin, §ionEnd); + inBootSection = BootSpanEquals(sectionBegin, sectionEnd, "boot"); + continue; + } + if (!inBootSection) + continue; + + const char* equals = NULL; + for (const char* p = begin; p < end; ++p) { + if (*p == '=') { + equals = p; + break; + } + } + if (!equals) + continue; + + const char* keyBegin = begin; + const char* keyEnd = equals; + const char* valueBegin = equals + 1; + const char* valueEnd = end; + BootTrimSpan(&keyBegin, &keyEnd); + if (!BootSpanEquals(keyBegin, keyEnd, "system_volume")) + continue; + + if (selectorSeen) + return EFI_INVALID_PARAMETER; + BootTrimSpan(&valueBegin, &valueEnd); + if (!BootVolumeSelectorParse(valueBegin, (size_t)(valueEnd - valueBegin), &selector)) + return EFI_INVALID_PARAMETER; + selectorSeen = true; + } + + if (!selectorSeen) + return EFI_NOT_FOUND; + *selectorOut = selector; + return EFI_SUCCESS; +} + +// Returns whether one UEFI GUID equals one shared selector GUID. +static bool BootSelectorGuidEquals(const EFI_GUID* guid, const BOOT_VOLUME_GUID* selectorGuid) { + if (!guid || !selectorGuid) + return false; + return guid->Data1 == selectorGuid->Data1 && + guid->Data2 == selectorGuid->Data2 && + guid->Data3 == selectorGuid->Data3 && + memcmp(guid->Data4, selectorGuid->Data4, sizeof(guid->Data4)) == 0; +} + +// Finds the hard-drive media node that identifies one firmware partition handle. +static HARDDRIVE_DEVICE_PATH* BootFindHardDriveNode(EFI_DEVICE_PATH_PROTOCOL* devicePath) { + EFI_DEVICE_PATH_PROTOCOL* node = devicePath; + while (node && !IsDevicePathEnd(node)) { + UINTN nodeLength = DevicePathNodeLength(node); + if (nodeLength < sizeof(EFI_DEVICE_PATH_PROTOCOL)) + return NULL; + if (DevicePathType(node) == MEDIA_DEVICE_PATH && + DevicePathSubType(node) == MEDIA_HARDDRIVE_DP) { + UINTN requiredLength = offsetof(HARDDRIVE_DEVICE_PATH, SignatureType) + + sizeof(((HARDDRIVE_DEVICE_PATH*)0)->SignatureType); + if (nodeLength < requiredLength) + return NULL; + return (HARDDRIVE_DEVICE_PATH*)node; + } + node = NextDevicePathNode(node); + } + return NULL; +} + +#if BOOT_VOLUME_DIAGNOSTICS + +// Appends a fixed-width hexadecimal value to one diagnostic selector string. +static CHAR16* BootAppendHex(CHAR16* out, UINT64 value, UINTN digits) { + static const CHAR16 hex[] = L"0123456789ABCDEF"; + for (UINTN i = digits; i != 0; --i) + *out++ = hex[(value >> ((i - 1u) * 4u)) & 0xFu]; return out; } -static void BootIniCb(const char* section, const char* key, - const char* val, void* ud) { - PARSE_STATE* s = (PARSE_STATE *)ud; - // [kernel] debug = bitmask: 1=console, 2=selftests, 4=file log, 8+=kernel diagnostics. - if (!AsciiStrCmp(section, "kernel") && !AsciiStrCmp(key, "debug")) - s->debug = ParseBootIniInteger(val); - // [init] commandline = hello.exe. - else if (!AsciiStrCmp(section, "init") && !AsciiStrCmp(key, "commandline")) - AsciiStrCpyS(s->commandLine, 64, val); +// Formats the selector represented by one firmware hard-drive device-path node. +static bool BootFormatPartitionSelector(const HARDDRIVE_DEVICE_PATH* hardDrive, + CHAR16* selector, UINTN selectorChars) { + if (!hardDrive || !selector || selectorChars < 16u) + return false; + + CHAR16* out = selector; + if (hardDrive->MBRType == MBR_TYPE_PCAT && + hardDrive->SignatureType == SIGNATURE_TYPE_MBR) { + if (hardDrive->PartitionNumber == 0 || hardDrive->PartitionNumber > 4u) + return false; + uint32_t diskSignature = 0; + memcpy(&diskSignature, hardDrive->Signature, sizeof(diskSignature)); + *out++ = L'm'; *out++ = L'b'; *out++ = L'r'; *out++ = L'/'; + out = BootAppendHex(out, diskSignature, 8u); + *out++ = L'/'; + *out++ = (CHAR16)(L'0' + hardDrive->PartitionNumber); + *out = L'\0'; + return true; + } + + if (hardDrive->MBRType == MBR_TYPE_EFI_PARTITION_TABLE_HEADER && + hardDrive->SignatureType == SIGNATURE_TYPE_GUID) { + if (selectorChars < 41u) + return false; + EFI_GUID partitionGuid; + memcpy(&partitionGuid, hardDrive->Signature, sizeof(partitionGuid)); + *out++ = L'g'; *out++ = L'p'; *out++ = L't'; *out++ = L'/'; + out = BootAppendHex(out, partitionGuid.Data1, 8u); + *out++ = L'-'; + out = BootAppendHex(out, partitionGuid.Data2, 4u); + *out++ = L'-'; + out = BootAppendHex(out, partitionGuid.Data3, 4u); + *out++ = L'-'; + out = BootAppendHex(out, partitionGuid.Data4[0], 2u); + out = BootAppendHex(out, partitionGuid.Data4[1], 2u); + *out++ = L'-'; + for (UINTN i = 2; i < 8; ++i) + out = BootAppendHex(out, partitionGuid.Data4[i], 2u); + *out = L'\0'; + return true; + } + + return false; } -static void DriversIniCb(const char* section, const char* key, - const char* val, void* ud) { - (void)val; - PARSE_STATE* s = (PARSE_STATE *)ud; - if (!AsciiStrCmp(section, "boot") && s->driverCount < 32) { - AsciiStrToUnicodeStrS(key, s->drivers[s->driverCount], 64); - s->driverCount++; +// Prints firmware partition identities so development builds can copy the exact selector firmware exposes. +static void BootPrintPartitionSelectors(void) { + EFI_HANDLE* handles = NULL; + UINTN handleCount = 0; + EFI_STATUS status = gBS->LocateHandleBuffer(ByProtocol, &gEfiBlockIoProtocolGuid, NULL, + &handleCount, &handles); + if (EFI_ERROR(status)) { + Print(L"UEFI partition selector probe failed: %r\n", status); + return; + } + + Print(L"UEFI partition selector candidates:\n"); + UINTN candidateCount = 0; + for (UINTN i = 0; i < handleCount; ++i) { + EFI_BLOCK_IO_PROTOCOL* block = NULL; + status = gBS->HandleProtocol(handles[i], &gEfiBlockIoProtocolGuid, (VOID**)&block); + if (EFI_ERROR(status) || !block || !block->Media || !block->Media->MediaPresent || + !block->Media->LogicalPartition) { + continue; + } + + EFI_DEVICE_PATH_PROTOCOL* path = NULL; + status = gBS->HandleProtocol(handles[i], &gEfiDevicePathProtocolGuid, (VOID**)&path); + if (EFI_ERROR(status) || !path) + continue; + + HARDDRIVE_DEVICE_PATH* hardDrive = BootFindHardDriveNode(path); + if (!hardDrive) + continue; + + CHAR16 selector[48]; + if (BootFormatPartitionSelector(hardDrive, selector, sizeof(selector) / sizeof(selector[0]))) { + Print(L" %s start_lba=%llu sectors=%llu\n", selector, + hardDrive->PartitionStart, hardDrive->PartitionSize); + } else { + Print(L" unsupported partition=%u mbr_type=%u signature_type=%u " + L"start_lba=%llu sectors=%llu signature=", + hardDrive->PartitionNumber, hardDrive->MBRType, hardDrive->SignatureType, + hardDrive->PartitionStart, hardDrive->PartitionSize); + for (UINTN byte = 0; byte < sizeof(hardDrive->Signature); ++byte) + Print(L"%02x", hardDrive->Signature[byte]); + Print(L"\n"); + } + ++candidateCount; + } + + if (candidateCount == 0) + Print(L" \n"); + gBS->FreePool(handles); +} + +#else + +#define BootPrintPartitionSelectors() ((void)0) + +#endif + +// Returns whether one UEFI partition handle structurally matches the configured selector. +static bool BootPartitionMatches(EFI_HANDLE handle, const BOOT_VOLUME_SELECTOR* selector) { + if (!handle || !selector) + return false; + + EFI_BLOCK_IO_PROTOCOL* block = NULL; + EFI_STATUS status = gBS->HandleProtocol(handle, &gEfiBlockIoProtocolGuid, (VOID**)&block); + if (EFI_ERROR(status) || !block || !block->Media || !block->Media->MediaPresent || + !block->Media->LogicalPartition) { + return false; + } + + EFI_DEVICE_PATH_PROTOCOL* path = NULL; + status = gBS->HandleProtocol(handle, &gEfiDevicePathProtocolGuid, (VOID**)&path); + if (EFI_ERROR(status) || !path) + return false; + + HARDDRIVE_DEVICE_PATH* hardDrive = BootFindHardDriveNode(path); + if (!hardDrive) + return false; + + if (selector->Kind == BootVolumeSelectorMbr) { + if (hardDrive->MBRType != MBR_TYPE_PCAT || hardDrive->SignatureType != SIGNATURE_TYPE_MBR || + hardDrive->PartitionNumber != selector->Identity.Mbr.PartitionNumber) { + return false; + } + uint32_t diskSignature = 0; + memcpy(&diskSignature, hardDrive->Signature, sizeof(diskSignature)); + return diskSignature == selector->Identity.Mbr.DiskSignature; + } + + if (selector->Kind == BootVolumeSelectorGpt) { + if (hardDrive->MBRType != MBR_TYPE_EFI_PARTITION_TABLE_HEADER || + hardDrive->SignatureType != SIGNATURE_TYPE_GUID) { + return false; + } + EFI_GUID partitionGuid; + memcpy(&partitionGuid, hardDrive->Signature, sizeof(partitionGuid)); + return BootSelectorGuidEquals(&partitionGuid, &selector->Identity.Gpt.PartitionGuid); + } + + return false; +} + +// Finds exactly one firmware partition handle matching the structural system-volume selector. +static EFI_STATUS BootFindSystemPartition(const BOOT_VOLUME_SELECTOR* selector, EFI_HANDLE* handleOut) { + if (!selector || !handleOut) + return EFI_INVALID_PARAMETER; + + *handleOut = NULL; + EFI_HANDLE* handles = NULL; + UINTN handleCount = 0; + EFI_STATUS status = gBS->LocateHandleBuffer(ByProtocol, &gEfiBlockIoProtocolGuid, NULL, + &handleCount, &handles); + if (EFI_ERROR(status)) + return status; + + EFI_HANDLE match = NULL; + UINTN matchCount = 0; + for (UINTN i = 0; i < handleCount; ++i) { + if (!BootPartitionMatches(handles[i], selector)) + continue; + match = handles[i]; + ++matchCount; + } + gBS->FreePool(handles); + + if (matchCount == 0) + return EFI_NOT_FOUND; + if (matchCount != 1) + return EFI_COMPROMISED_DATA; + *handleOut = match; + return EFI_SUCCESS; +} + +// Asks firmware to recursively connect currently visible controllers before retrying discovery. +static void BootConnectVisibleControllers(void) { + EFI_HANDLE* handles = NULL; + UINTN handleCount = 0; + EFI_STATUS status = gBS->LocateHandleBuffer(AllHandles, NULL, NULL, &handleCount, &handles); + if (EFI_ERROR(status)) + return; + + for (UINTN i = 0; i < handleCount; ++i) + gBS->ConnectController(handles[i], NULL, NULL, TRUE); + gBS->FreePool(handles); +} + +// Returns whether one directory entry names an EFI image. +static bool BootIsEfiImageName(const CHAR16* name) { + if (!name) + return false; + UINTN length = StrLen(name); + if (length < 5) + return false; + const CHAR16* suffix = name + length - 4; + return suffix[0] == L'.' && + (suffix[1] == L'e' || suffix[1] == L'E') && + (suffix[2] == L'f' || suffix[2] == L'F') && + (suffix[3] == L'i' || suffix[3] == L'I'); +} + +// Reads one variable-sized directory entry from a UEFI directory handle. +static EFI_STATUS BootReadDirectoryEntry(EFI_FILE_PROTOCOL* directory, EFI_FILE_INFO** infoOut) { + if (!directory || !infoOut) + return EFI_INVALID_PARAMETER; + + *infoOut = NULL; + UINTN capacity = 512; + for (;;) { + EFI_FILE_INFO* info = NULL; + EFI_STATUS status = gBS->AllocatePool(EfiBootServicesData, capacity, (VOID**)&info); + if (EFI_ERROR(status)) + return status; + + UINTN size = capacity; + status = directory->Read(directory, &size, info); + if (status == EFI_BUFFER_TOO_SMALL) { + gBS->FreePool(info); + if (size <= capacity) + return EFI_LOAD_ERROR; + capacity = size; + continue; + } + if (EFI_ERROR(status)) { + gBS->FreePool(info); + return status; + } + if (size == 0) { + gBS->FreePool(info); + return EFI_SUCCESS; + } + + *infoOut = info; + return EFI_SUCCESS; } } -// EFI Memory Map -> MEMORY_MAP conversion. +// Loads all optional filesystem UEFI drivers bundled on the boot anchor. +static EFI_STATUS BootLoadFilesystemDrivers(EFI_HANDLE imageHandle, UINTN* loadedCountOut) { + if (!imageHandle || !loadedCountOut) + return EFI_INVALID_PARAMETER; -static EFI_PHYSICAL_ADDRESS AllocateApTrampolinePageBelow1M(void) { - // AP startup via SIPI requires the first instruction page to live below - // 1 MiB. Some real UEFI firmware exposes no ConventionalMemory below - // 640 KiB after ExitBootServices, so reserve a loader-data page here while - // boot services can still satisfy AllocateMaxAddress. - EFI_PHYSICAL_ADDRESS phys = 0x0009F000ULL; - EFI_STATUS st = gBS->AllocatePages(AllocateMaxAddress, EfiLoaderData, 1, &phys); - if (EFI_ERROR(st) || phys == 0 || phys >= 0x000A0000ULL || (phys & 0xFFFULL)) { + *loadedCountOut = 0; + EFI_FILE_PROTOCOL* directory = NULL; + EFI_STATUS status = gBootRoot->Open(gBootRoot, &directory, L"EFI\\NTLite\\fs", + EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(status)) + return status; + + for (;;) { + EFI_FILE_INFO* info = NULL; + status = BootReadDirectoryEntry(directory, &info); + if (EFI_ERROR(status)) + break; + if (!info) + break; + + if (!(info->Attribute & EFI_FILE_DIRECTORY) && BootIsEfiImageName(info->FileName)) { + static const CHAR16 prefix[] = L"\\EFI\\NTLite\\fs\\"; + UINTN prefixChars = (sizeof(prefix) / sizeof(prefix[0])) - 1u; + UINTN nameChars = StrLen(info->FileName); + UINTN pathChars = prefixChars + nameChars + 1u; + if (pathChars < prefixChars || pathChars > (~(UINTN)0) / sizeof(CHAR16)) { + gBS->FreePool(info); + status = EFI_OUT_OF_RESOURCES; + break; + } + + CHAR16* path = NULL; + status = gBS->AllocatePool(EfiBootServicesData, pathChars * sizeof(CHAR16), + (VOID**)&path); + if (EFI_ERROR(status)) { + gBS->FreePool(info); + break; + } + memcpy(path, prefix, prefixChars * sizeof(CHAR16)); + memcpy(path + prefixChars, info->FileName, (nameChars + 1u) * sizeof(CHAR16)); + + EFI_DEVICE_PATH* devicePath = FileDevicePath(gBootDevice, path); + if (!devicePath) { + gBS->FreePool(path); + gBS->FreePool(info); + status = EFI_OUT_OF_RESOURCES; + break; + } + + EFI_HANDLE driverImage = NULL; + EFI_STATUS driverStatus = gBS->LoadImage(FALSE, imageHandle, devicePath, NULL, 0, + &driverImage); + gBS->FreePool(devicePath); + if (!EFI_ERROR(driverStatus)) { + UINTN exitDataSize = 0; + CHAR16* exitData = NULL; + driverStatus = gBS->StartImage(driverImage, &exitDataSize, &exitData); + if (exitData) + gBS->FreePool(exitData); + if (EFI_ERROR(driverStatus)) { + gBS->UnloadImage(driverImage); + } else { + ++*loadedCountOut; + Print(L"Loaded filesystem driver %s\n", info->FileName); + } + } + if (EFI_ERROR(driverStatus)) + Print(L"Skipping filesystem driver %s: %r\n", info->FileName, driverStatus); + gBS->FreePool(path); + } + + gBS->FreePool(info); + if (EFI_ERROR(status)) + break; + } + + directory->Close(directory); + if (EFI_ERROR(status)) + return status; + return *loadedCountOut ? EFI_SUCCESS : EFI_NOT_FOUND; +} + +// Opens the selected system volume, loading optional filesystem drivers only when firmware needs them. +static EFI_STATUS BootOpenSystemVolume(EFI_HANDLE imageHandle, + const BOOT_VOLUME_SELECTOR* selector, + EFI_FILE_PROTOCOL** rootOut) { + if (!imageHandle || !selector || !rootOut) + return EFI_INVALID_PARAMETER; + + *rootOut = NULL; + EFI_HANDLE partition = NULL; + EFI_STATUS status = BootFindSystemPartition(selector, &partition); + if (status == EFI_NOT_FOUND) { + BootConnectVisibleControllers(); + BootPrintPartitionSelectors(); + status = BootFindSystemPartition(selector, &partition); + } + if (EFI_ERROR(status)) + return status; + + gBS->ConnectController(partition, NULL, NULL, TRUE); + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs = NULL; + status = gBS->HandleProtocol(partition, &gEfiSimpleFileSystemProtocolGuid, (VOID**)&fs); + if (EFI_ERROR(status)) { + UINTN loadedDrivers = 0; + EFI_STATUS loadStatus = BootLoadFilesystemDrivers(imageHandle, &loadedDrivers); + if (EFI_ERROR(loadStatus) && loadStatus != EFI_NOT_FOUND) + return loadStatus; + if (loadedDrivers != 0) + gBS->ConnectController(partition, NULL, NULL, TRUE); + status = gBS->HandleProtocol(partition, &gEfiSimpleFileSystemProtocolGuid, (VOID**)&fs); + } + if (EFI_ERROR(status) || !fs) + return EFI_ERROR(status) ? status : EFI_NOT_FOUND; + + return fs->OpenVolume(fs, rootOut); +} + +typedef EFI_STATUS (*BOOT_DRIVER_VISITOR)(const char* name, UINTN length, void* context); + +// Visits raw driver filenames from the exact [boot] section without implementing general INI semantics. +static EFI_STATUS BootVisitDriverLines(const char* text, UINTN length, + BOOT_DRIVER_VISITOR visitor, void* context) { + if (!text || !visitor) + return EFI_INVALID_PARAMETER; + + bool inBootSection = false; + UINTN offset = 0; + while (offset < length) { + UINTN lineStart = offset; + while (offset < length && text[offset] != '\n') + ++offset; + UINTN lineEnd = offset; + if (offset < length) + ++offset; + + const char* begin = text + lineStart; + const char* end = text + lineEnd; + BootTrimSpan(&begin, &end); + if (begin == end || *begin == ';' || *begin == '#') + continue; + + if (*begin == '[') { + if (end[-1] != ']') + return EFI_INVALID_PARAMETER; + const char* sectionBegin = begin + 1; + const char* sectionEnd = end - 1; + BootTrimSpan(§ionBegin, §ionEnd); + inBootSection = BootSpanEquals(sectionBegin, sectionEnd, "boot"); + continue; + } + + if (!inBootSection) + continue; + for (const char* p = begin; p < end; ++p) { + if (*p == '=') + return EFI_INVALID_PARAMETER; + } + + EFI_STATUS status = visitor(begin, (UINTN)(end - begin), context); + if (EFI_ERROR(status)) + return status; + } + return EFI_SUCCESS; +} + +typedef struct BOOT_DRIVER_COUNT_CONTEXT { + uint32_t Count; +} BOOT_DRIVER_COUNT_CONTEXT; + +// Counts one boot-driver filename while checking the 32-bit handoff count for overflow. +static EFI_STATUS BootCountDriver(const char* name, UINTN length, void* context) { + (void)name; + if (!context || length == 0 || length >= BOOT_FILE_NAME_MAX) + return EFI_INVALID_PARAMETER; + BOOT_DRIVER_COUNT_CONTEXT* count = (BOOT_DRIVER_COUNT_CONTEXT*)context; + if (count->Count == UINT32_MAX) + return EFI_OUT_OF_RESOURCES; + ++count->Count; + return EFI_SUCCESS; +} + +typedef struct BOOT_DRIVER_LOAD_CONTEXT { + BOOT_FILE* Files; + uint32_t Capacity; + uint32_t Index; +} BOOT_DRIVER_LOAD_CONTEXT; + +// Loads one [boot] driver as a page-backed raw file and records its exact allocation. +static EFI_STATUS BootLoadDriver(const char* name, UINTN length, void* context) { + BOOT_DRIVER_LOAD_CONTEXT* load = (BOOT_DRIVER_LOAD_CONTEXT*)context; + if (!load || load->Index >= load->Capacity || length == 0 || length >= BOOT_FILE_NAME_MAX) + return EFI_INVALID_PARAMETER; + + for (UINTN i = 0; i < length; ++i) { + unsigned char c = (unsigned char)name[i]; + if (c < 0x21u || c > 0x7Eu || name[i] == '/' || name[i] == '\\' || name[i] == '=') + return EFI_INVALID_PARAMETER; + } + + char asciiName[BOOT_FILE_NAME_MAX]; + memcpy(asciiName, name, length); + asciiName[length] = '\0'; + + CHAR16 unicodeName[BOOT_FILE_NAME_MAX]; + if (AsciiStrToUnicodeStrS(asciiName, unicodeName, BOOT_FILE_NAME_MAX) != 0) + return EFI_INVALID_PARAMETER; + + CHAR16 path[128]; + UnicodeSPrint(path, sizeof(path), L"drivers\\%s", unicodeName); + + BOOT_BLOB blob = {0}; + EFI_STATUS status = BootReadBlobFile(gSystemRoot, path, &blob); + if (EFI_ERROR(status)) + return status; + if (blob.Length == 0) { + gBS->FreePages(blob.PhysicalBase, blob.PageCount); + return EFI_LOAD_ERROR; + } + + BOOT_FILE* file = &load->Files[load->Index++]; + AsciiStrCpyS(file->Name, sizeof(file->Name), asciiName); + file->PhysicalBase = blob.PhysicalBase; + file->Length = blob.Length; + file->PageCount = blob.PageCount; + return EFI_SUCCESS; +} + +// Builds the dynamic raw boot-driver descriptor list from drivers.ini [boot]. +static EFI_STATUS BootLoadDrivers(const char* text, UINTN length, BOOT_FILE_LIST* listOut) { + if (!text || !listOut) + return EFI_INVALID_PARAMETER; + + *listOut = (BOOT_FILE_LIST){0}; + BOOT_DRIVER_COUNT_CONTEXT count = {0}; + EFI_STATUS status = BootVisitDriverLines(text, length, BootCountDriver, &count); + if (EFI_ERROR(status)) + return status; + if (count.Count == 0) + return EFI_NOT_FOUND; + + UINTN descriptorBytes = (UINTN)count.Count * sizeof(BOOT_FILE); + if (descriptorBytes / sizeof(BOOT_FILE) != count.Count) + return EFI_OUT_OF_RESOURCES; + UINTN pageCount = (descriptorBytes + PAGE_SIZE - 1u) / PAGE_SIZE; + if (pageCount == 0 || pageCount > UINT32_MAX) + return EFI_OUT_OF_RESOURCES; + + EFI_PHYSICAL_ADDRESS physical = 0; + status = BootAllocatePages(pageCount, &physical); + if (EFI_ERROR(status)) + return status; + + BOOT_DRIVER_LOAD_CONTEXT load = {0}; + load.Files = (BOOT_FILE*)physical; + load.Capacity = count.Count; + status = BootVisitDriverLines(text, length, BootLoadDriver, &load); + if (EFI_ERROR(status) || load.Index != count.Count) { + for (uint32_t i = 0; i < load.Index; ++i) { + if (load.Files[i].PhysicalBase && load.Files[i].PageCount) + gBS->FreePages(load.Files[i].PhysicalBase, load.Files[i].PageCount); + } + gBS->FreePages(physical, pageCount); + return EFI_ERROR(status) ? status : EFI_LOAD_ERROR; + } + + listOut->FilesPhysical = (uintptr_t)physical; + listOut->Count = count.Count; + listOut->PageCount = (uint32_t)pageCount; + return EFI_SUCCESS; +} + +// Allocates the page-backed versioned BOOT_INFO block passed into KernelMain. +static EFI_STATUS BootAllocateInfo(BOOT_INFO** infoOut) { + if (!infoOut) + return EFI_INVALID_PARAMETER; + + *infoOut = NULL; + UINTN pageCount = (sizeof(BOOT_INFO) + PAGE_SIZE - 1u) / PAGE_SIZE; + EFI_PHYSICAL_ADDRESS physical = 0; + EFI_STATUS status = BootAllocatePages(pageCount, &physical); + if (EFI_ERROR(status)) + return status; + + BOOT_INFO* info = (BOOT_INFO*)physical; + info->Magic = BOOT_INFO_MAGIC; + info->Size = sizeof(BOOT_INFO); + info->Version = BOOT_INFO_VERSION; + *infoOut = info; + return EFI_SUCCESS; +} + +// Captures the firmware wall clock before runtime firmware services are abandoned. +static void BootCaptureTime(BOOT_INFO* info) { + EFI_TIME fwTime; + if (EFI_ERROR(gRT->GetTime(&fwTime, NULL)) || + fwTime.Year < 1970 || fwTime.Month < 1 || fwTime.Month > 12 || + fwTime.Day < 1 || fwTime.Day > 31 || fwTime.Hour > 23 || + fwTime.Minute > 59 || fwTime.Second > 59 || + fwTime.Nanosecond >= 1000000000U) { + return; + } + + info->BootTime.Year = fwTime.Year; + info->BootTime.Month = fwTime.Month; + info->BootTime.Day = fwTime.Day; + info->BootTime.Hour = fwTime.Hour; + info->BootTime.Minute = fwTime.Minute; + info->BootTime.Second = fwTime.Second; + info->BootTime.Daylight = fwTime.Daylight; + info->BootTime.Nanosecond = fwTime.Nanosecond; + info->BootTime.TimeZoneMinutes = fwTime.TimeZone; + info->BootTime.Flags = BOOT_TIME_FLAG_VALID | BOOT_TIME_FLAG_UEFI; + if (fwTime.TimeZone != EFI_UNSPECIFIED_TIMEZONE) + info->BootTime.Flags |= BOOT_TIME_FLAG_TZ_KNOWN; +} + +// Returns whether two complete EFI GUIDs are identical. +static bool BootGuidEqual(const EFI_GUID* a, const EFI_GUID* b) { + return a && b && memcmp(a, b, sizeof(EFI_GUID)) == 0; +} + +// Captures the preferred ACPI RSDP pointer from the firmware configuration table. +static uintptr_t BootFindRsdp(EFI_SYSTEM_TABLE* systemTable) { + const EFI_GUID acpi2Guid = { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81} }; + const EFI_GUID acpi1Guid = { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d} }; + uintptr_t acpi1 = 0; + + for (UINTN i = 0; i < systemTable->NumberOfTableEntries; ++i) { + EFI_CONFIGURATION_TABLE* table = &systemTable->ConfigurationTable[i]; + if (BootGuidEqual(&table->VendorGuid, &acpi2Guid)) + return (uintptr_t)table->VendorTable; + if (BootGuidEqual(&table->VendorGuid, &acpi1Guid)) + acpi1 = (uintptr_t)table->VendorTable; + } + return acpi1; +} + +// Reserves the AP SIPI trampoline page below the legacy 640 KiB boundary while firmware can still allocate it. +static EFI_PHYSICAL_ADDRESS BootAllocateApTrampoline(void) { + EFI_PHYSICAL_ADDRESS physical = 0x0009F000ULL; + EFI_STATUS status = gBS->AllocatePages(AllocateMaxAddress, EfiLoaderData, 1, &physical); + if (EFI_ERROR(status) || physical == 0 || physical >= 0x000A0000ULL || (physical & 0xFFFULL)) return 0; - } - gBS->SetMem((VOID*)phys, PAGE_SIZE, 0); - return phys; + gBS->SetMem((VOID*)physical, PAGE_SIZE, 0); + return physical; } -// Converts memory Map. -static void ConvertMemoryMap(MEMORY_MAP* out) { - UINTN mapSize = 0, descSz = 0, mapKey; UINT32 descVer; - gBS->GetMemoryMap(&mapSize, NULL, &mapKey, &descSz, &descVer); - mapSize += 8 * descSz; - EFI_MEMORY_DESCRIPTOR* efiMap = AllocatePool(mapSize); - gBS->GetMemoryMap(&mapSize, efiMap, &mapKey, &descSz, &descVer); +// Grows and refreshes one EFI memory-map buffer using boot-services memory. +static EFI_STATUS BootRefreshMemoryMap(BOOT_EFI_MEMORY_MAP* map) { + if (!map) + return EFI_INVALID_PARAMETER; + + for (;;) { + UINTN size = map->Capacity; + UINTN key = 0; + UINTN descriptorSize = map->DescriptorSize; + UINT32 descriptorVersion = 0; + EFI_STATUS status = gBS->GetMemoryMap(&size, map->Buffer, &key, + &descriptorSize, &descriptorVersion); + if (status == EFI_BUFFER_TOO_SMALL) { + UINTN slack = descriptorSize ? descriptorSize * 16u : 4096u; + if (size > ~(UINTN)0 - slack) + return EFI_OUT_OF_RESOURCES; + UINTN capacity = size + slack; + EFI_MEMORY_DESCRIPTOR* replacement = NULL; + status = gBS->AllocatePool(EfiBootServicesData, capacity, (VOID**)&replacement); + if (EFI_ERROR(status)) + return status; + if (map->Buffer) + gBS->FreePool(map->Buffer); + map->Buffer = replacement; + map->Capacity = capacity; + map->DescriptorSize = descriptorSize; + continue; + } + if (EFI_ERROR(status)) + return status; + + map->Size = size; + map->Key = key; + map->DescriptorSize = descriptorSize; + map->DescriptorVersion = descriptorVersion; + return EFI_SUCCESS; + } +} + +// Releases one temporary EFI memory-map buffer before ExitBootServices. +static void BootFreeMemoryMap(BOOT_EFI_MEMORY_MAP* map) { + if (map && map->Buffer) { + gBS->FreePool(map->Buffer); + map->Buffer = NULL; + map->Capacity = 0; + } +} + +// Maps one firmware memory descriptor to the generic post-ExitBootServices memory type. +static MEMORY_TYPE BootMemoryType(UINT32 type) { + switch ((EFI_MEMORY_TYPE)type) { + case EfiConventionalMemory: + return MemoryUsable; + case EfiMemoryMappedIO: + case EfiMemoryMappedIOPortSpace: + return MemoryMappedIO; + case EfiLoaderCode: + case EfiLoaderData: + return MemoryBootloader; + default: + return MemoryReserved; + } +} + +// Converts one final EFI memory-map snapshot into the compact generic map, coalescing adjacent compatible ranges. +static EFI_STATUS BootConvertMemoryMap(const BOOT_EFI_MEMORY_MAP* efiMap, MEMORY_MAP* out) { + if (!efiMap || !efiMap->Buffer || !efiMap->DescriptorSize || !out) + return EFI_INVALID_PARAMETER; out->Magic = MEMORY_MAP_MAGIC; out->RegionCount = 0; + out->_pad = 0; - EFI_MEMORY_DESCRIPTOR* desc = efiMap; - for (UINTN i = 0; i < mapSize / descSz && out->RegionCount < MEMORY_MAX_REGIONS; i++) { - MEMORY_TYPE type; - switch (desc->Type) { - case EfiConventionalMemory: type = MemoryUsable; break; - case EfiMemoryMappedIO: - case EfiMemoryMappedIOPortSpace: type = MemoryMappedIO; break; - case EfiLoaderCode: - case EfiLoaderData: type = MemoryBootloader; break; - case EfiRuntimeServicesCode: - case EfiRuntimeServicesData: - default: type = MemoryReserved; break; - } - MEMORY_REGION* r = &out->Regions[out->RegionCount++]; - r->Base = desc->PhysicalStart; - r->Length = desc->NumberOfPages * PAGE_SIZE; - r->Type = type; - r->_pad = 0; - desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)desc + descSz); - } - FreePool(efiMap); -} + UINTN descriptorCount = efiMap->Size / efiMap->DescriptorSize; + for (UINTN i = 0; i < descriptorCount; ++i) { + const EFI_MEMORY_DESCRIPTOR* descriptor = + (const EFI_MEMORY_DESCRIPTOR*)((const UINT8*)efiMap->Buffer + i * efiMap->DescriptorSize); + if (descriptor->NumberOfPages == 0) + continue; + if (descriptor->NumberOfPages > UINT64_MAX / PAGE_SIZE) + return EFI_INVALID_PARAMETER; -#pragma pack(push, 1) -typedef struct { - UINT32 Characteristics; - UINT32 TimeDateStamp; - UINT16 MajorVersion; - UINT16 MinorVersion; - UINT32 Name; - UINT32 Base; - UINT32 NumberOfFunctions; - UINT32 NumberOfNames; - UINT32 AddressOfFunctions; // RVA to array of RVAs - UINT32 AddressOfNames; // RVA to array of RVAs - UINT32 AddressOfNameOrdinals; // RVA to array of UINT16 -} IMAGE_EXPORT_DIRECTORY; + uint64_t base = descriptor->PhysicalStart; + uint64_t length = descriptor->NumberOfPages * PAGE_SIZE; + MEMORY_TYPE type = BootMemoryType(descriptor->Type); -typedef struct { - union { - UINT32 Characteristics; - UINT32 OriginalFirstThunk; // RVA to Import Lookup Table - } DUMMYUNIONNAME; - UINT32 TimeDateStamp; - UINT32 ForwarderChain; - UINT32 Name; // RVA to DLL Name - UINT32 FirstThunk; // RVA to IAT (Import Address Table) -} IMAGE_IMPORT_DESCRIPTOR; - -typedef struct { - union { - UINT64 ForwarderString; - UINT64 Function; - UINT64 Ordinal; - UINT64 AddressOfData; // RVA to IMAGE_IMPORT_BY_NAME - } u1; -} IMAGE_THUNK_DATA64; - -typedef struct { - UINT16 Hint; - CHAR8 Name[1]; -} IMAGE_IMPORT_BY_NAME; -#pragma pack(pop) - -#define IMAGE_ORDINAL_FLAG64 0x8000000000000000ULL - -// These PE structures are kept local because the bootloader only needs the subset used by image loading. -#pragma pack(push, 1) -typedef struct { - UINT32 VirtualAddress; - UINT32 Size; -} IMAGE_DATA_DIRECTORY; - -typedef struct { - UINT32 VirtualAddress; - UINT32 SizeOfBlock; -} IMAGE_BASE_RELOCATION; - -typedef struct { - UINT8 Name[8]; - UINT32 VirtualSize; - UINT32 VirtualAddress; - UINT32 SizeOfRawData; - UINT32 PointerToRawData; - UINT32 PointerToRelocations; - UINT32 PointerToLinenumbers; - UINT16 NumberOfRelocations; - UINT16 NumberOfLinenumbers; - UINT32 Characteristics; -} IMAGE_SECTION_HEADER; -#pragma pack(pop) - -// The PE loader maps image sections into their final virtual addresses before the kernel switches page tables. -EFI_STATUS LoadPeImage(UINT8* rawFile, UINT64 virtDest, EFI_PHYSICAL_ADDRESS* outPhys) { - UINT32 peOff = *(UINT32*)(rawFile + 0x3C); - UINT8* ntHeaders = rawFile + peOff; - - // Validate Signature. - if (*(UINT32*)ntHeaders != 0x00004550) { Print(L"Signature mismatch\n"); return EFI_INVALID_PARAMETER; } // "PE\0\0" - - UINT16 sizeOfOptHeader = *(UINT16*)(ntHeaders + 20); - UINT16 numSections = *(UINT16*)(ntHeaders + 6); - - // Offsets for PE32+ (64-bit). - UINT64 imageBase = *(UINT64*)(ntHeaders + 24 + 24); - UINT32 sizeOfImage = *(UINT32*)(ntHeaders + 24 + 56); - UINT32 sizeOfHeaders = *(UINT32*)(ntHeaders + 24 + 60); - - IMAGE_DATA_DIRECTORY* dataDirs = (IMAGE_DATA_DIRECTORY*)(ntHeaders + 24 + 112); - IMAGE_DATA_DIRECTORY* relocDir = &dataDirs[5]; - //IMAGE_DATA_DIRECTORY* importDir = &dataDirs[1]; - - // Allocate Physical Memory for the mapped image. - UINTN pages = (sizeOfImage + PAGE_SIZE - 1) / PAGE_SIZE; - EFI_PHYSICAL_ADDRESS physBase; - gBS->AllocatePages(AllocateAnyPages, EfiLoaderData, pages, &physBase); - gBS->SetMem((VOID*)physBase, pages * PAGE_SIZE, 0); - - // Copy Headers. - gBS->CopyMem((VOID*)physBase, rawFile, sizeOfHeaders); - - // Map Sections to their RVAs. - IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*)(ntHeaders + 24 + sizeOfOptHeader); - for (int i = 0; i < numSections; i++) { - if (sections[i].SizeOfRawData > 0) { - UINT8* dest = (UINT8*)physBase + sections[i].VirtualAddress; - UINT8* src = rawFile + sections[i].PointerToRawData; - gBS->CopyMem(dest, src, sections[i].SizeOfRawData); - } - } - - // Apply Base Relocations (.reloc) - // We calculate the delta between where it wants to be (ImageBase) - // and where it will actually be (virtDest). - UINT64 delta = virtDest - imageBase; - - if (delta != 0 && relocDir->Size > 0) { - IMAGE_BASE_RELOCATION* reloc = (IMAGE_BASE_RELOCATION*)((UINT8*)physBase + relocDir->VirtualAddress); - UINTN endOfRelocs = (UINTN)reloc + relocDir->Size; - - while ((UINTN)reloc < endOfRelocs && reloc->SizeOfBlock > 0) { - UINTN count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(UINT16); - UINT16* entries = (UINT16*)(reloc + 1); - - for (UINTN i = 0; i < count; i++) { - UINT16 type = entries[i] >> 12; - UINT16 offset = entries[i] & 0x0FFF; - - if (type == 10) { // IMAGE_REL_BASED_DIR64 - UINT64* patchAddr = (UINT64*)((UINT8*)physBase + reloc->VirtualAddress + offset); - *patchAddr += delta; - } + if (out->RegionCount != 0) { + MEMORY_REGION* previous = &out->Regions[out->RegionCount - 1u]; + if (previous->Type == type && + previous->Base <= UINT64_MAX - previous->Length && + previous->Base + previous->Length == base && + previous->Length <= UINT64_MAX - length) { + previous->Length += length; + continue; } - reloc = (IMAGE_BASE_RELOCATION*)((UINT8*)reloc + reloc->SizeOfBlock); } - } - *outPhys = physBase; + if (out->RegionCount >= MEMORY_MAX_REGIONS) + return EFI_BUFFER_TOO_SMALL; + MEMORY_REGION* region = &out->Regions[out->RegionCount++]; + region->Base = base; + region->Length = length; + region->Type = type; + region->_pad = 0; + } return EFI_SUCCESS; } -// Returns export Rva. -static UINT32 GetExportRva(UINT8* providerPhys, const char* funcName) { - UINT32 peOff = *(UINT32*)(providerPhys + 0x3C); - UINT8* ntHeaders = providerPhys + peOff; - - // Export Dir is index 0 in the DataDirectory array. - UINT32 exportDirRva = *(UINT32*)(ntHeaders + 24 + 112); - if (!exportDirRva) return 0; // No exports! - - IMAGE_EXPORT_DIRECTORY* exports = (IMAGE_EXPORT_DIRECTORY*)(providerPhys + exportDirRva); - - UINT32* addrOfFunctions = (UINT32*)(providerPhys + exports->AddressOfFunctions); - UINT32* addrOfNames = (UINT32*)(providerPhys + exports->AddressOfNames); - UINT16* addrOfOrdinals = (UINT16*)(providerPhys + exports->AddressOfNameOrdinals); - - // Binary search is faster, but linear is fine for a bootloader. - for (UINT32 i = 0; i < exports->NumberOfNames; i++) { - char* name = (char*)(providerPhys + addrOfNames[i]); - if (AsciiStrCmp(name, funcName) == 0) { - UINT16 ordinal = addrOfOrdinals[i]; - return addrOfFunctions[ordinal]; +// Sorts small virtual hole arrays used while constructing identity and direct mappings. +static void BootSortHoles(UINT64* holes, UINTN holeCount) { + for (UINTN pass = 0; pass < holeCount; ++pass) { + for (UINTN i = 1; i < holeCount; ++i) { + if (holes[i * 2] < holes[(i - 1u) * 2]) { + UINT64 start = holes[(i - 1u) * 2]; + UINT64 end = holes[(i - 1u) * 2 + 1u]; + holes[(i - 1u) * 2] = holes[i * 2]; + holes[(i - 1u) * 2 + 1u] = holes[i * 2 + 1u]; + holes[i * 2] = start; + holes[i * 2 + 1u] = end; + } } } - return 0; // Function not found } -EFI_STATUS ResolveImports(UINT8* consumerPhys, UINT8* providerPhys, UINT64 providerVirt) { - UINT32 peOff = *(UINT32*)(consumerPhys + 0x3C); - UINT8* ntHeaders = consumerPhys + peOff; - - // Import Dir is index 1 in the DataDirectory array. - UINT32 importDirRva = *(UINT32*)(ntHeaders + 24 + 112 + 8); - if (!importDirRva) return EFI_SUCCESS; // No imports to resolve +// Builds the bootstrap identity/direct mappings from one firmware map snapshot. +static EFI_STATUS BootMapFirmwareMemory(const BOOT_EFI_MEMORY_MAP* map, UINT64 framebufferBase, + UINT64 framebufferBytes) { + if (!map || !map->Buffer || !map->DescriptorSize) + return EFI_INVALID_PARAMETER; - IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)(consumerPhys + importDirRva); + UINT64 identityHoles[8]; + UINT64 windowHoles[8]; + UINTN identityHoleCount = 0; + UINTN windowHoleCount = 0; - // Iterate through all imported DLLs. - while (importDesc->Name != 0) { - char* dllName = (char*)(consumerPhys + importDesc->Name); - - // In a full OS, you'd loop through all loaded modules here. - // For now, we assume we are resolving hal.dll. - // Convert to lowercase or do a case-insensitive check if needed! - if (AsciiStrCmp(dllName, "hal.dll") == 0) { - - IMAGE_THUNK_DATA64* origFirstThunk = (IMAGE_THUNK_DATA64*)(consumerPhys + importDesc->DUMMYUNIONNAME.OriginalFirstThunk); - IMAGE_THUNK_DATA64* firstThunk = (IMAGE_THUNK_DATA64*)(consumerPhys + importDesc->FirstThunk); + identityHoles[identityHoleCount * 2] = 0; + identityHoles[identityHoleCount * 2 + 1u] = PAGE_SIZE; + ++identityHoleCount; - if (!importDesc->DUMMYUNIONNAME.OriginalFirstThunk) { - origFirstThunk = firstThunk; // Some compilers omit OriginalFirstThunk - } - - int i = 0; - while (origFirstThunk[i].u1.Function != 0) { - if (origFirstThunk[i].u1.Ordinal & IMAGE_ORDINAL_FLAG64) { - // Import by Ordinal (Usually you don't do this in modern kernels, but good to handle). - Print(L"Error: Ordinal imports not supported in basic loader.\n"); - return EFI_UNSUPPORTED; - } else { - // Import by Name. - IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)(consumerPhys + origFirstThunk[i].u1.AddressOfData); - char* funcName = (char*)importByName->Name; - - UINT32 funcRva = GetExportRva(providerPhys, funcName); - if (funcRva == 0) { - Print(L"FATAL: Unresolved import: %a\n", funcName); - return EFI_NOT_FOUND; - } - - // PATCH THE IAT! - // We write the final Virtual Address (Provider Base + Target RVA) - // into the physical memory of the consumer's IAT. - firstThunk[i].u1.Function = providerVirt + funcRva; - } - i++; - } - } - importDesc++; + if (framebufferBytes) { + identityHoles[identityHoleCount * 2] = framebufferBase; + identityHoles[identityHoleCount * 2 + 1u] = framebufferBase + framebufferBytes; + ++identityHoleCount; + windowHoles[windowHoleCount * 2] = PHYS_MAP_BASE + framebufferBase; + windowHoles[windowHoleCount * 2 + 1u] = PHYS_MAP_BASE + framebufferBase + framebufferBytes; + ++windowHoleCount; + } + + identityHoles[identityHoleCount * 2] = 0xFEC00000ULL; + identityHoles[identityHoleCount * 2 + 1u] = 0xFEE01000ULL; + ++identityHoleCount; + windowHoles[windowHoleCount * 2] = PHYS_MAP_BASE + 0xFEC00000ULL; + windowHoles[windowHoleCount * 2 + 1u] = PHYS_MAP_BASE + 0xFEE01000ULL; + ++windowHoleCount; + + BootSortHoles(identityHoles, identityHoleCount); + BootSortHoles(windowHoles, windowHoleCount); + + UINTN descriptorCount = map->Size / map->DescriptorSize; + for (UINTN i = 0; i < descriptorCount; ++i) { + const EFI_MEMORY_DESCRIPTOR* descriptor = + (const EFI_MEMORY_DESCRIPTOR*)((const UINT8*)map->Buffer + i * map->DescriptorSize); + if (!IsDirectMapRamType(descriptor->Type)) + continue; + + UINT64 start = descriptor->PhysicalStart; + UINT64 size = descriptor->NumberOfPages * PAGE_SIZE; + UINT64 flags = BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE | DirectMapCacheFlags(descriptor); + if (!MapRangeSkipping(start, start, size, flags, identityHoles, identityHoleCount) || + !MapRangeSkipping(PHYS_MAP_BASE + start, start, size, flags, windowHoles, windowHoleCount)) { + return EFI_OUT_OF_RESOURCES; + } + } + + for (UINT64 physical = 0xFEC00000ULL; physical < 0xFEE01000ULL; physical += PAGE_SIZE) { + UINT64 flags = BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE | BOOT_PAGE_CACHE_UC; + if (!Map4K(physical, physical, flags) || + !Map4K(PHYS_MAP_BASE + physical, physical, flags)) { + return EFI_OUT_OF_RESOURCES; + } } - return EFI_SUCCESS; } -// UefiMain. -EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) { - InitializeLib(ImageHandle, SystemTable); - - // Open root filesystem on the device we actually booted from. - EFI_LOADED_IMAGE_PROTOCOL* LoadedImage; - EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs; +// Captures GOP information and maps the visible framebuffer with write-combining bootstrap mappings. +static EFI_STATUS BootCaptureFramebuffer(BOOT_INFO* info, UINT64* baseOut, UINT64* bytesOut) { + if (!info || !baseOut || !bytesOut) + return EFI_INVALID_PARAMETER; - // Get the LoadedImage protocol for this application. - EFI_STATUS Status = gBS->HandleProtocol( - ImageHandle, - &gEfiLoadedImageProtocolGuid, - (VOID **)&LoadedImage - ); - - if (EFI_ERROR(Status)) { - Print(L"Failed to get LoadedImage protocol\n"); - return Status; - } + *baseOut = 0; + *bytesOut = 0; + EFI_GRAPHICS_OUTPUT_PROTOCOL* gop = NULL; + EFI_STATUS status = gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID**)&gop); + if (EFI_ERROR(status)) + return EFI_SUCCESS; + if (!gop || !gop->Mode || !gop->Mode->Info) + return EFI_LOAD_ERROR; - // Get the SimpleFileSystem protocol for the device we were loaded from. - Status = gBS->HandleProtocol( - LoadedImage->DeviceHandle, - &gEfiSimpleFileSystemProtocolGuid, - (VOID **)&fs - ); + info->FramebufferBase = gop->Mode->FrameBufferBase; + info->FramebufferWidth = gop->Mode->Info->HorizontalResolution; + info->FramebufferHeight = gop->Mode->Info->VerticalResolution; + info->FramebufferStride = gop->Mode->Info->PixelsPerScanLine; - if (EFI_ERROR(Status)) { - Print(L"Failed to get SimpleFileSystem protocol on boot device\n"); - return Status; - } + UINT64 rowBytes = (UINT64)info->FramebufferStride * 4u; + if (info->FramebufferHeight != 0 && rowBytes > UINT64_MAX / info->FramebufferHeight) + return EFI_BAD_BUFFER_SIZE; + UINT64 visibleBytes = rowBytes * info->FramebufferHeight; + if (info->FramebufferBase > UINT64_MAX - visibleBytes) + return EFI_BAD_BUFFER_SIZE; - // Open the volume. - Status = fs->OpenVolume(fs, &gRoot); - if (EFI_ERROR(Status)) { - Print(L"Failed to open root volume\n"); - return Status; - } + UINT64 mapBase = info->FramebufferBase & ~(PAGE_SIZE - 1u); + UINT64 mapEnd = info->FramebufferBase + visibleBytes; + if (mapEnd > UINT64_MAX - (PAGE_SIZE - 1u)) + return EFI_BAD_BUFFER_SIZE; + mapEnd = (mapEnd + PAGE_SIZE - 1u) & ~(PAGE_SIZE - 1u); - Print(L"1\n"); - - // boot.ini controls debugger and runtime policy before the kernel has a filesystem service. - UINTN sz; - UINT8* bootIniRaw = LoadFile(L"boot.ini", &sz); - PARSE_STATE ps = {0}; - if (bootIniRaw) { - char* txt = (char *)bootIniRaw; - txt[sz] = '\0'; - IniParse(txt, BootIniCb, &ps); - FreePool(bootIniRaw); - } - - Print(L"2\n"); - - // drivers.ini decides which boot and runtime drivers are loaded into the initial module list. - UINT8* drvIniRaw = LoadFile(L"drivers\\drivers.ini", &sz); - if (drvIniRaw) { - char* txt = (char *)drvIniRaw; - txt[sz] = '\0'; - IniParse(txt, DriversIniCb, &ps); - FreePool(drvIniRaw); - } - - Print(L"3\n"); - - BOOT_INFO* bi = AllocateZeroPool(sizeof(BOOT_INFO)); - bi->Magic = BOOT_INFO_MAGIC; - AsciiStrCpyS(bi->CommandLine, 64, ps.commandLine); - - EFI_TIME fwTime; - if (!EFI_ERROR(gRT->GetTime(&fwTime, NULL)) && - fwTime.Year >= 1970 && fwTime.Month >= 1 && fwTime.Month <= 12 && - fwTime.Day >= 1 && fwTime.Day <= 31 && fwTime.Hour <= 23 && - fwTime.Minute <= 59 && fwTime.Second <= 59 && - fwTime.Nanosecond < 1000000000U) { - bi->BootTime.Year = fwTime.Year; - bi->BootTime.Month = fwTime.Month; - bi->BootTime.Day = fwTime.Day; - bi->BootTime.Hour = fwTime.Hour; - bi->BootTime.Minute = fwTime.Minute; - bi->BootTime.Second = fwTime.Second; - bi->BootTime.Daylight = fwTime.Daylight; - bi->BootTime.Nanosecond = fwTime.Nanosecond; - bi->BootTime.TimeZoneMinutes = fwTime.TimeZone; - bi->BootTime.Flags = BOOT_TIME_FLAG_VALID | BOOT_TIME_FLAG_UEFI; - if (fwTime.TimeZone != EFI_UNSPECIFIED_TIMEZONE) - bi->BootTime.Flags |= BOOT_TIME_FLAG_TZ_KNOWN; - } - - // Build PML4. - gPml4 = AllocPage(NULL); - - // Current mapping cursor for upper half. - UINT64 virtCursor = KERNEL_VIRT_BASE; - - Print(L"4\n"); - - // Core images are loaded first because driver import resolution depends on them. - typedef struct { - CHAR16* path; - char name[32]; - UINT8* physBase; // Temporary storage for import resolution. - UINT64 virtBase; - } MODULE_LOAD_INFO; - - MODULE_LOAD_INFO core[] = { - { L"system\\colonel.exe", "kernel.exe", NULL, KERNEL_VIRT_BASE }, - // The HAL virtual size is filled after the image is loaded because it depends on the PE headers. - { L"system\\hal.dll", "hal.dll", NULL, 0 }, - }; - - for (int i = 0; i < 2; i++) { - // The firmware console can be noisy here, so per-module load prints stay disabled unless early loader debugging is needed. - UINTN rawSize; - UINT8* rawBuf = LoadFile(core[i].path, &rawSize); - if (!rawBuf) { - Print(L"MISSING: %s\n", core[i].path); - while(true); - return EFI_NOT_FOUND; + for (UINT64 physical = mapBase; physical < mapEnd; physical += PAGE_SIZE) { + UINT64 flags = BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE | BOOT_PAGE_CACHE_WC; + if (!Map4K(PHYS_MAP_BASE + physical, physical, flags) || + !Map4K(physical, physical, flags)) { + return EFI_OUT_OF_RESOURCES; } + } - // Map sections and apply base relocations. - EFI_PHYSICAL_ADDRESS physBase; - EFI_STATUS status = LoadPeImage(rawBuf, virtCursor, &physBase); + *baseOut = mapBase; + *bytesOut = mapEnd - mapBase; + return EFI_SUCCESS; +} + +// Returns whether two fixed virtual image ranges overlap or overflow. +static bool BootImageRangesOverlap(const BOOT_PE_INFO* a, const BOOT_PE_INFO* b) { + uint64_t aEnd = a->ImageBase + a->SizeOfImage; + uint64_t bEnd = b->ImageBase + b->SizeOfImage; + if (aEnd < a->ImageBase || bEnd < b->ImageBase) + return true; + return a->ImageBase < bEnd && b->ImageBase < aEnd; +} + +// Materializes and maps one already-inspected fixed PE image. +static EFI_STATUS BootMaterializeCoreImage(const UINT8* raw, UINTN rawSize, + const BOOT_PE_INFO* peInfo, BOOT_IMAGE* imageOut) { + if (!raw || !peInfo || !imageOut) + return EFI_INVALID_PARAMETER; + + *imageOut = (BOOT_IMAGE){0}; + UINTN pageCount = ((UINTN)peInfo->SizeOfImage + PAGE_SIZE - 1u) / PAGE_SIZE; + if (pageCount == 0 || pageCount > UINT32_MAX) + return EFI_BAD_BUFFER_SIZE; + + EFI_PHYSICAL_ADDRESS physical = 0; + EFI_STATUS status = BootAllocatePages(pageCount, &physical); + if (EFI_ERROR(status)) + return status; + + BOOT_PE_INFO loadedInfo; + BOOT_PE_STATUS peStatus = BootPeMaterialize(raw, rawSize, (VOID*)physical, + pageCount * PAGE_SIZE, &loadedInfo); + if (peStatus != BootPeSuccess) { + gBS->FreePages(physical, pageCount); + Print(L"PE materialize failed: %a\n", BootPeStatusName(peStatus)); + return EFI_LOAD_ERROR; + } + + for (UINTN page = 0; page < pageCount; ++page) { + if (!Map4K(loadedInfo.ImageBase + page * PAGE_SIZE, + physical + page * PAGE_SIZE, + BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE)) { + gBS->FreePages(physical, pageCount); + return EFI_OUT_OF_RESOURCES; + } + } + + BOOT_IMAGE image = {0}; + image.VirtualBase = (uintptr_t)loadedInfo.ImageBase; + image.PhysicalBase = (uintptr_t)physical; + image.ImageSize = loadedInfo.SizeOfImage; + image.PageCount = (uint32_t)pageCount; + image.EntryRva = loadedInfo.EntryRva; + *imageOut = image; + return EFI_SUCCESS; +} + +// Releases one materialized core image after a pre-ExitBootServices load failure. +static void BootReleaseCoreImage(BOOT_IMAGE* image) { + if (!image || !image->PhysicalBase || !image->PageCount) + return; + gBS->FreePages((EFI_PHYSICAL_ADDRESS)image->PhysicalBase, image->PageCount); + *image = (BOOT_IMAGE){0}; +} + +// Loads, verifies, maps, and binds the two fixed bootstrap PE images. +static EFI_STATUS BootLoadCoreImages(BOOT_INFO* info) { + UINT8* kernelRaw = NULL; + UINT8* halRaw = NULL; + UINTN kernelRawSize = 0; + UINTN halRawSize = 0; + + EFI_STATUS status = BootReadPoolFile(gSystemRoot, L"system\\colonel.exe", &kernelRaw, &kernelRawSize); + if (EFI_ERROR(status)) { + Print(L"Missing kernel image: %r\n", status); + return status; + } + status = BootReadPoolFile(gSystemRoot, L"system\\hal.dll", &halRaw, &halRawSize); + if (EFI_ERROR(status)) { + Print(L"Missing HAL image: %r\n", status); + gBS->FreePool(kernelRaw); + return status; + } + + BOOT_PE_INFO kernelPe; + BOOT_PE_INFO halPe; + BOOT_PE_STATUS peStatus = BootPeInspect(kernelRaw, kernelRawSize, &kernelPe); + if (peStatus != BootPeSuccess) { + Print(L"Kernel PE validation failed: %a\n", BootPeStatusName(peStatus)); + status = EFI_LOAD_ERROR; + goto cleanup; + } + peStatus = BootPeInspect(halRaw, halRawSize, &halPe); + if (peStatus != BootPeSuccess) { + Print(L"HAL PE validation failed: %a\n", BootPeStatusName(peStatus)); + status = EFI_LOAD_ERROR; + goto cleanup; + } + + if (kernelPe.ImageBase != KERNEL_VIRT_BASE || + kernelPe.ImageBase + kernelPe.SizeOfImage > PHYS_MAP_BASE || + halPe.ImageBase < KERNEL_VIRT_BASE || halPe.ImageBase + halPe.SizeOfImage > PHYS_MAP_BASE || + BootImageRangesOverlap(&kernelPe, &halPe)) { + Print(L"Core image fixed bases are invalid or overlap. kernel=0x%016llX hal=0x%016llX\n", + kernelPe.ImageBase, halPe.ImageBase); + status = EFI_LOAD_ERROR; + goto cleanup; + } + + status = BootMaterializeCoreImage(kernelRaw, kernelRawSize, &kernelPe, &info->KernelImage); + if (EFI_ERROR(status)) + goto cleanup; + status = BootMaterializeCoreImage(halRaw, halRawSize, &halPe, &info->HalImage); + if (EFI_ERROR(status)) + goto cleanup; + + peStatus = BootPeRequireNoImports((const void*)info->HalImage.PhysicalBase, info->HalImage.ImageSize); + if (peStatus != BootPeSuccess) { + Print(L"HAL must be self-contained: %a\n", BootPeStatusName(peStatus)); + status = EFI_LOAD_ERROR; + goto cleanup; + } + + peStatus = BootPeResolveImports((void*)info->KernelImage.PhysicalBase, info->KernelImage.ImageSize, + (const void*)info->HalImage.PhysicalBase, info->HalImage.ImageSize, + "hal.dll", info->HalImage.VirtualBase); + if (peStatus != BootPeSuccess) { + Print(L"Kernel/HAL import binding failed: %a\n", BootPeStatusName(peStatus)); + status = EFI_LOAD_ERROR; + goto cleanup; + } + + status = EFI_SUCCESS; + +cleanup: + gBS->FreePool(kernelRaw); + gBS->FreePool(halRaw); + if (EFI_ERROR(status)) { + BootReleaseCoreImage(&info->HalImage); + BootReleaseCoreImage(&info->KernelImage); + } + return status; +} + +// Converts the final map and exits boot services with the exact map key used for the handoff. +static EFI_STATUS BootExitServices(EFI_HANDLE imageHandle, BOOT_INFO* info) { + BOOT_EFI_MEMORY_MAP map = {0}; + for (uint32_t attempt = 0; attempt < 8u; ++attempt) { + EFI_STATUS status = BootRefreshMemoryMap(&map); if (EFI_ERROR(status)) { - Print(L"PE Load Error: %r\n", status); - while(true); + BootFreeMemoryMap(&map); return status; } - // Store image locations so the import resolver can patch cross-module references. - core[i].physBase = (UINT8*)physBase; - core[i].virtBase = virtCursor; - - // Images are mapped by SizeOfImage so zero-filled sections occupy their final virtual range. - UINT32 peOff = *(UINT32*)(rawBuf + 0x3C); - UINT32 imgLen = *(UINT32*)(rawBuf + peOff + 4 + 20 + 56); // SizeOfImage - imgLen = (imgLen + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); - - for (UINT64 off = 0; off < imgLen; off += PAGE_SIZE) { - // The bootloader keeps the flag literal local because this file owns its minimal page-table encoder. - Map4K(virtCursor + off, physBase + off, 0x03); + status = BootConvertMemoryMap(&map, &info->MemoryMap); + if (EFI_ERROR(status)) { + BootFreeMemoryMap(&map); + return status; } - // Fill BOOT_INFO. - AsciiStrCpyS(bi->Modules[bi->ModuleCount].Name, 32, core[i].name); - bi->Modules[bi->ModuleCount].VirtBase = virtCursor; - bi->Modules[bi->ModuleCount].ImageSize = imgLen; - bi->ModuleCount++; - - // Increment cursor (1MB alignment for safety). - virtCursor = (virtCursor + imgLen + 0xFFFFF) & ~0xFFFFF; - - // We can free the raw file buffer now; sections are in physBase. - FreePool(rawBuf); - } - - if (EFI_ERROR(ResolveImports(core[0].physBase, core[1].physBase, core[1].virtBase))) { - Print(L"Failed to resolve imports.\n"); - while(true); - } - - Print(L"5\n"); - - // Load driver .sys files into raw buffers. - for (int i = 0; i < ps.driverCount; i++) { - CHAR16 path[128]; - UnicodeSPrint(path, sizeof(path), L"drivers\\%s", ps.drivers[i]); - - UINTN imgSz; - UINT8* buf = LoadFile(path, &imgSz); - if (!buf) { - Print(L"Fatal: boot driver not found: %s\n", ps.drivers[i]); - while (true); - return EFI_NOT_FOUND; + status = gBS->ExitBootServices(imageHandle, map.Key); + if (!EFI_ERROR(status)) { + // No firmware interrupt handler remains valid after this instruction. + _disable(); + return EFI_SUCCESS; } - - // Store file info for the kernel to process later. - char name[32]; - UnicodeStrToAsciiStrS(ps.drivers[i], name, 32); - - BOOT_MODULE* mod = &bi->Modules[bi->ModuleCount++]; - AsciiStrCpyS(mod->Name, 32, name); - mod->PhysBase = (uintptr_t)buf; // Raw physical buffer from LoadFile - mod->ImageSize = (size_t)imgSz; - mod->VirtBase = 0; // Kernel will map this later - } - - Print(L"6\n"); - - // Query GOP FIRST, before the GetMemoryMap loop. - EFI_GRAPHICS_OUTPUT_PROTOCOL* gop; - UINT64 fbBase = 0; - UINT64 fbBytes = 0; - if (!EFI_ERROR(gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)&gop))) { - bi->FramebufferBase = gop->Mode->FrameBufferBase; - bi->FramebufferWidth = gop->Mode->Info->HorizontalResolution; - bi->FramebufferHeight = gop->Mode->Info->VerticalResolution; - bi->FramebufferStride = gop->Mode->Info->PixelsPerScanLine; - - // FrameBufferSize is a lie - EFI reports it as Width*Height*4. - // The ACTUAL consumed bytes are Stride*Height*4. - // Add one extra page as a safety margin. - fbBase = gop->Mode->FrameBufferBase; - fbBytes = (UINT64)gop->Mode->Info->PixelsPerScanLine - * (UINT64)gop->Mode->Info->VerticalResolution - * 4 + PAGE_SIZE; - fbBytes = (fbBytes + PAGE_SIZE - 1) & ~(UINT64)(PAGE_SIZE - 1); - - for (UINT64 i = 0; i < fbBytes; i += PAGE_SIZE) { - UINT64 phys = gop->Mode->FrameBufferBase + i; - Map4K(PHYS_MAP_BASE + phys, phys, BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE | BOOT_PAGE_CACHE_WC); - Map4K(phys, phys, BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE | BOOT_PAGE_CACHE_WC); + if (status != EFI_INVALID_PARAMETER) { + BootFreeMemoryMap(&map); + return status; } } - Print(L"7\n"); + BootFreeMemoryMap(&map); + return EFI_ABORTED; +} - bi->ApTrampolinePhys = (uintptr_t)AllocateApTrampolinePageBelow1M(); +// UEFI application entry point. +EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) { + InitializeLib(ImageHandle, SystemTable); - // Usable RAM is mapped both through the physical direct window and identity mappings until the final CR3 switch. - UINTN mapSize = 0, descSz = 0, mapKey; UINT32 descVer; - gBS->GetMemoryMap(&mapSize, NULL, &mapKey, &descSz, &descVer); - mapSize += 8 * descSz; - EFI_MEMORY_DESCRIPTOR* efiMap = AllocatePool(mapSize); - gBS->GetMemoryMap(&mapSize, efiMap, &mapKey, &descSz, &descVer); - { - UINT64 idHoles[8]; - UINT64 winHoles[8]; - UINTN idHoleCount = 0; - UINTN winHoleCount = 0; - - // Leave virtual page zero genuinely unmapped. Mapping it supervisor/NX - // still hides null pointer bugs from the page-fault path and makes - // diagnostics worse. The physical-window alias for frame zero remains - // available at PHYS_MAP_BASE if low physical memory is ever needed. - idHoles[idHoleCount * 2 + 0] = 0; - idHoles[idHoleCount * 2 + 1] = PAGE_SIZE; - idHoleCount++; - - if (fbBytes) { - idHoles[idHoleCount * 2 + 0] = fbBase; - idHoles[idHoleCount * 2 + 1] = fbBase + fbBytes; - idHoleCount++; - - winHoles[winHoleCount * 2 + 0] = PHYS_MAP_BASE + fbBase; - winHoles[winHoleCount * 2 + 1] = PHYS_MAP_BASE + fbBase + fbBytes; - winHoleCount++; - } - - idHoles[idHoleCount * 2 + 0] = 0xFEC00000ULL; - idHoles[idHoleCount * 2 + 1] = 0xFEE01000ULL; - idHoleCount++; - - winHoles[winHoleCount * 2 + 0] = PHYS_MAP_BASE + 0xFEC00000ULL; - winHoles[winHoleCount * 2 + 1] = PHYS_MAP_BASE + 0xFEE01000ULL; - winHoleCount++; - - for (UINTN pass = 0; pass < idHoleCount; pass++) { - for (UINTN i = 1; i < idHoleCount; i++) { - if (idHoles[i * 2] < idHoles[(i - 1) * 2]) { - UINT64 s0 = idHoles[(i - 1) * 2 + 0]; - UINT64 e0 = idHoles[(i - 1) * 2 + 1]; - idHoles[(i - 1) * 2 + 0] = idHoles[i * 2 + 0]; - idHoles[(i - 1) * 2 + 1] = idHoles[i * 2 + 1]; - idHoles[i * 2 + 0] = s0; - idHoles[i * 2 + 1] = e0; - } - } - } - - for (UINTN pass = 0; pass < winHoleCount; pass++) { - for (UINTN i = 1; i < winHoleCount; i++) { - if (winHoles[i * 2] < winHoles[(i - 1) * 2]) { - UINT64 s0 = winHoles[(i - 1) * 2 + 0]; - UINT64 e0 = winHoles[(i - 1) * 2 + 1]; - winHoles[(i - 1) * 2 + 0] = winHoles[i * 2 + 0]; - winHoles[(i - 1) * 2 + 1] = winHoles[i * 2 + 1]; - winHoles[i * 2 + 0] = s0; - winHoles[i * 2 + 1] = e0; - } - } - } - - for (UINTN i = 0; i < mapSize / descSz; i++) { - EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)efiMap + i * descSz); - - // Only firmware-described RAM receives a direct-map alias. Reserved - // and MMIO descriptors stay unmapped until an owning subsystem maps - // them with an explicit PAT policy. - if (IsDirectMapRamType(desc->Type)) { - UINT64 start = desc->PhysicalStart; - UINT64 size = desc->NumberOfPages * PAGE_SIZE; - UINT64 flags = BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE | DirectMapCacheFlags(desc); - - MapRangeSkipping(start, start, size, flags, idHoles, idHoleCount); - MapRangeSkipping(PHYS_MAP_BASE + start, start, size, flags, winHoles, winHoleCount); - } - } - } - FreePool(efiMap); - - // Explicitly Map Standard Architectural MMIO - // Motherboards frequently omit the APIC/IOAPIC/HPET from the UEFI memory map. - // We explicitly map the region from 0xFEC00000 up through 0xFEE00FFF - // to guarantee the HAL can access them via the physical window. - for (UINT64 phys = 0xFEC00000; phys < 0xFEE01000; phys += PAGE_SIZE) { - UINT64 flags = BOOT_PAGE_PRESENT | BOOT_PAGE_WRITE | BOOT_PAGE_CACHE_UC; - Map4K(phys, phys, flags); - Map4K(PHYS_MAP_BASE + phys, phys, flags); + EFI_LOADED_IMAGE_PROTOCOL* loadedImage = NULL; + EFI_STATUS status = gBS->HandleProtocol(ImageHandle, &gEfiLoadedImageProtocolGuid, + (VOID**)&loadedImage); + if (EFI_ERROR(status)) { + Print(L"Failed to get LoadedImage protocol: %r\n", status); + return status; } - Print(L"8\n"); - - // The kernel consumes a compact memory map so it does not need EFI descriptor semantics after boot. - ConvertMemoryMap(&bi->MemoryMap); - - // The RSDP physical address is captured before firmware services are unavailable. - // Define the standard UEFI ACPI GUIDs. - EFI_GUID acpi2Guid = { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81} }; - EFI_GUID acpi1Guid = { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d} }; - - bi->RsdpPhysical = 0; - void* acpi1Table = NULL; - - // Search the UEFI Configuration Table. - for (UINTN i = 0; i < SystemTable->NumberOfTableEntries; i++) { - EFI_GUID* vendorGuid = &SystemTable->ConfigurationTable[i].VendorGuid; - - // Check for ACPI 2.0 (Preferred). - if (vendorGuid->Data1 == acpi2Guid.Data1 && - vendorGuid->Data2 == acpi2Guid.Data2 && - vendorGuid->Data3 == acpi2Guid.Data3 && - vendorGuid->Data4[0] == acpi2Guid.Data4[0] && - vendorGuid->Data4[7] == acpi2Guid.Data4[7]) - { - bi->RsdpPhysical = (UINT64)SystemTable->ConfigurationTable[i].VendorTable; - break; // Found the best one, stop searching - } - - // Check for ACPI 1.0 (Fallback). - if (vendorGuid->Data1 == acpi1Guid.Data1 && - vendorGuid->Data2 == acpi1Guid.Data2 && - vendorGuid->Data3 == acpi1Guid.Data3 && - vendorGuid->Data4[0] == acpi1Guid.Data4[0] && - vendorGuid->Data4[7] == acpi1Guid.Data4[7]) - { - acpi1Table = SystemTable->ConfigurationTable[i].VendorTable; - } + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs = NULL; + status = gBS->HandleProtocol(loadedImage->DeviceHandle, &gEfiSimpleFileSystemProtocolGuid, + (VOID**)&fs); + if (EFI_ERROR(status)) { + Print(L"Failed to get boot filesystem protocol: %r\n", status); + return status; } - // Fallback to ACPI 1.0 if ACPI 2.0 wasn't found. - if (bi->RsdpPhysical == 0 && acpi1Table != NULL) { - bi->RsdpPhysical = (UINT64)acpi1Table; + gBootDevice = loadedImage->DeviceHandle; + status = fs->OpenVolume(fs, &gBootRoot); + if (EFI_ERROR(status)) { + Print(L"Failed to open boot volume: %r\n", status); + return status; } - // Print it out to verify the bootloader actually found it before launching the kernel - Print(L"Found RSDP at: 0x%016llX\n", bi->RsdpPhysical); + BOOT_INFO* info = NULL; + status = BootAllocateInfo(&info); + if (EFI_ERROR(status)) { + Print(L"Failed to allocate BOOT_INFO: %r\n", status); + return status; + } - bi->PhysicalMapBase = PHYS_MAP_BASE; - bi->Debug = (UINT32)ps.debug; + status = BootReadBlobFile(gBootRoot, L"boot.ini", &info->BootIni); + if (EFI_ERROR(status) || info->BootIni.Length == 0) { + if (!EFI_ERROR(status) && info->BootIni.PhysicalBase) + gBS->FreePages(info->BootIni.PhysicalBase, info->BootIni.PageCount); + Print(L"Mandatory boot.ini could not be loaded or is empty: %r\n", + EFI_ERROR(status) ? status : EFI_LOAD_ERROR); + return EFI_ERROR(status) ? status : EFI_LOAD_ERROR; + } - Print(L"9\n"); + BootPrintPartitionSelectors(); - // ExitBootServices is the point of no return for firmware allocation, file I/O, and protocol access. - gBS->GetMemoryMap(&mapSize, NULL, &mapKey, &descSz, &descVer); - gBS->ExitBootServices(ImageHandle, mapKey); + BOOT_VOLUME_SELECTOR systemVolumeSelector = {0}; + status = BootParseSystemVolume((const char*)info->BootIni.PhysicalBase, + info->BootIni.Length, &systemVolumeSelector); + if (EFI_ERROR(status)) { + Print(L"boot.ini [boot] system_volume is missing or invalid: %r\n", status); + return status; + } - // The bootloader installs the CPU state the kernel expects before jumping into the upper half. - // (These are inline since we have no runtime services after ExitBootServices.) - // GDT is set up by the kernel's KeInitializeGdt; we just need a valid one to - // reach KernelMain. The bootloader installed a flat 64-bit GDT earlier via - // EDK2, which is fine for the jump. + status = BootOpenSystemVolume(ImageHandle, &systemVolumeSelector, &gSystemRoot); + if (EFI_ERROR(status)) { + Print(L"Configured system_volume could not be opened: %r\n", status); + return status; + } - // Enable NXE bit in EFER. + UINT8* driversIni = NULL; + UINTN driversIniSize = 0; + status = BootReadPoolFile(gSystemRoot, L"drivers\\drivers.ini", &driversIni, &driversIniSize); + if (EFI_ERROR(status)) { + Print(L"Mandatory drivers.ini could not be loaded: %r\n", status); + return status; + } + status = BootLoadDrivers((const char*)driversIni, driversIniSize, &info->BootDrivers); + gBS->FreePool(driversIni); + if (EFI_ERROR(status)) { + Print(L"drivers.ini [boot] is invalid or a boot driver could not be loaded: %r\n", status); + return status; + } + + BootCaptureTime(info); + info->RsdpPhysical = BootFindRsdp(SystemTable); + info->PhysicalMapBase = PHYS_MAP_BASE; + info->ApTrampolinePhys = (uintptr_t)BootAllocateApTrampoline(); + if (!info->ApTrampolinePhys) { + Print(L"Failed to reserve the AP trampoline page below 640 KiB.\n"); + return EFI_OUT_OF_RESOURCES; + } + + gPml4 = AllocPage(NULL); + if (!gPml4) { + Print(L"Failed to allocate the bootstrap PML4.\n"); + return EFI_OUT_OF_RESOURCES; + } + + status = BootLoadCoreImages(info); + if (EFI_ERROR(status)) + return status; + + UINT64 framebufferBase = 0; + UINT64 framebufferBytes = 0; + status = BootCaptureFramebuffer(info, &framebufferBase, &framebufferBytes); + if (EFI_ERROR(status)) { + Print(L"Failed to map the boot framebuffer: %r\n", status); + return status; + } + + BOOT_EFI_MEMORY_MAP bootstrapMap = {0}; + status = BootRefreshMemoryMap(&bootstrapMap); + if (EFI_ERROR(status)) { + Print(L"Failed to obtain the bootstrap EFI memory map: %r\n", status); + return status; + } + status = BootMapFirmwareMemory(&bootstrapMap, framebufferBase, framebufferBytes); + BootFreeMemoryMap(&bootstrapMap); + if (EFI_ERROR(status)) { + Print(L"Failed to construct bootstrap physical mappings: %r\n", status); + return status; + } + + Print(L"Boot drivers: %u, kernel=0x%016llX, hal=0x%016llX, RSDP=0x%016llX\n", + info->BootDrivers.Count, + info->KernelImage.VirtualBase, + info->HalImage.VirtualBase, + info->RsdpPhysical); + + status = BootExitServices(ImageHandle, info); + if (EFI_ERROR(status)) { + Print(L"ExitBootServices failed: %r\n", status); + return status; + } + + _disable(); UINT64 efer = __readmsr(0xC0000080); __writemsr(0xC0000080, efer | (1ULL << 11)); - - // Switch to our new PML4. __writecr3((UINT64)gPml4); - // KernelMain receives BOOT_INFO in the new address space and takes ownership of the machine. - typedef void (*KERNEL_ENTRY)(BOOT_INFO *); - KERNEL_ENTRY KernelMain = (KERNEL_ENTRY)bi->Modules[0].VirtBase; + typedef void (*KERNEL_ENTRY)(BOOT_INFO*); + KERNEL_ENTRY kernelMain = (KERNEL_ENTRY)(info->KernelImage.VirtualBase + info->KernelImage.EntryRva); + kernelMain(info); - // The PE entry point is at VirtBase + AddressOfEntryPoint. - UINT32 epOff = *(UINT32 *)(bi->Modules[0].VirtBase + *(UINT32 *)(bi->Modules[0].VirtBase + 0x3C) + 4 + 20 + 16); - KernelMain = (KERNEL_ENTRY)(bi->Modules[0].VirtBase + epOff); - KernelMain(bi); - - // Never returns. - _disable(); while (true) __halt(); + _disable(); + for (;;) + __halt(); } diff --git a/code/build/build.c b/code/build/build.c index 91ae5f1..2f1089f 100644 --- a/code/build/build.c +++ b/code/build/build.c @@ -14,30 +14,33 @@ #define USER_HEAP_OBJS_LITERAL "obj\\runtime\\user_heap.obj obj\\runtime\\user_heap_exports.obj" #define NATIVE_RUNTIME_OBJS_LITERAL "obj\\runtime\\native_exception.obj obj\\runtime\\native_start.obj" #define NATIVE_EXE_LIBS_LITERAL "bin\\syscall.lib obj\\libc.obj " USER_HEAP_OBJS_LITERAL " " NATIVE_RUNTIME_OBJS_LITERAL -#define NATIVE_INIT_LIBS_LITERAL "bin\\syscall.lib obj\\libc.obj " USER_HEAP_OBJS_LITERAL " obj\\runtime\\native_exception.obj" +#define NATIVE_INIT_LIBS_LITERAL "bin\\syscall_s.lib obj\\libc.obj obj\\runtime\\native_exception.obj" +#define NATIVE_SERVICE_LIBS_LITERAL "bin\\syscall_s.lib obj\\libc.obj " USER_HEAP_OBJS_LITERAL " obj\\runtime\\native_exception.obj" #define WIN32_EXE_LIBS_LITERAL "bin\\win32.lib bin\\syscall.lib obj\\libc.obj obj\\runtime\\native_start.obj" #define POSIX_EXE_LIBS_LITERAL "bin\\posix.lib bin\\syscall.lib obj\\libc.obj obj\\runtime\\posix_start.obj obj\\runtime\\native_exception.obj" #define NATIVE_EXE_ENTRY_LITERAL "/ENTRY:NtlNativeStart" +#define USER_IMAGE_FLAGS_LITERAL "/DYNAMICBASE /HIGHENTROPYVA" #define LWIP_CFLAGS_LITERAL DRIVER_CFLAGS_LITERAL " /Icode\\drivers\\net\\lwip\\include /Icode\\drivers\\net\\lwip\\third_party\\lwip\\src\\include /wd4005 /wd4100 /wd4103 /wd4127 /wd4244 /wd4267 /wd4706" #define LFLAGS_LITERAL "/nologo /NODEFAULTLIB /DEBUG" // Partition offsets inside disk.vhd #define VHD_PART1 "disk.vhd@@1M" // ESP / Partition 1 (Offset 1MB) #define VHD_PART2 "disk.vhd@@65M" // Test/Data / Partition 2 (Offset 65MB) +#define DEFAULT_VHD_DISK_SIGNATURE 0x4E544C31u -const char *CommonCflags = COMMON_CFLAGS_LITERAL; -const char *KernelCflags = KERNEL_CFLAGS_LITERAL; -const char *UserCflags = USER_CFLAGS_LITERAL; -const char *BootCflags = BOOT_CFLAGS_LITERAL; -const char *LwipCflags = LWIP_CFLAGS_LITERAL; -const char *LFLAGS = LFLAGS_LITERAL; +const char* CommonCflags = COMMON_CFLAGS_LITERAL; +const char* KernelCflags = KERNEL_CFLAGS_LITERAL; +const char* UserCflags = USER_CFLAGS_LITERAL; +const char* BootCflags = BOOT_CFLAGS_LITERAL; +const char* LwipCflags = LWIP_CFLAGS_LITERAL; +const char* LFLAGS = LFLAGS_LITERAL; typedef struct Driver_Target { - const char *name; - const char *obj_dir; - const char *sources; - const char *cflags; - const char *libs; + const char* name; + const char* obj_dir; + const char* sources; + const char* cflags; + const char* libs; } Driver_Target; #define DRIVER_TARGET(name_, obj_dir_, sources_) { (name_), (obj_dir_), (sources_), DRIVER_CFLAGS_LITERAL, "" } @@ -63,13 +66,13 @@ static const Driver_Target STANDALONE_DRIVERS[] = { // Creates layout. void create_layout() { - const char *dirs[] = { + const char* dirs[] = { "obj", "bin", "obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\hal", - "obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\drivers", "obj\\init", + "obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", "obj\\drivers", "obj\\init", "obj\\rssrv", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native", "obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget", "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", "obj\\doom", "obj\\video", "obj\\memtop", - "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\named_queue_lifetime_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\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", + "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\security_smoke", "obj\\port_ipc_smoke", "obj\\resource_service_smoke", "obj\\named_queue_lifetime_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\\process_exit_multithread_smoke", "obj\\process_builder_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_userspace_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\\all_tests", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke" }; @@ -79,7 +82,7 @@ void create_layout() { } // Runs a shell command and waits for it to finish. -bool run_sync(const char *cmd_str) { +bool run_sync(const char* cmd_str) { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c", cmd_str); bool ok = nob_cmd_run_sync(cmd); @@ -88,7 +91,7 @@ bool run_sync(const char *cmd_str) { } // Launches a shell command while the build graph continues. -Nob_Proc run_async(const char *cmd_str) { +Nob_Proc run_async(const char* cmd_str) { Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c", cmd_str); Nob_Proc proc = nob_cmd_run_async(cmd); @@ -97,20 +100,20 @@ Nob_Proc run_async(const char *cmd_str) { } // Deletes stale object files from a target directory before compiling into it. -bool clean_object_directory(const char *obj_dir) { - const char *cmd = nob_temp_sprintf("if exist %s\\*.obj del /q %s\\*.obj", obj_dir, obj_dir); +bool clean_object_directory(const char* obj_dir) { + const char* cmd = nob_temp_sprintf("if exist %s\\*.obj del /q %s\\*.obj", obj_dir, obj_dir); return run_sync(cmd); } // Cleans object directories that are linked through wildcards. bool clean_build_object_directories(void) { - const char *dirs[] = { + const char* dirs[] = { "obj\\boot", "obj\\kernel", "obj\\lwip", "obj\\hal", "obj\\syscall", "obj\\runtime", "obj\\win32", "obj\\posix", - "obj\\init", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native", + "obj\\init", "obj\\rssrv", "obj\\shell_win32", "obj\\shell_posix", "obj\\shell_native", "obj\\async_apc", "obj\\async_cancel", "obj\\apc_return_smoke", "obj\\edit", "obj\\httpget", "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", - "obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\named_queue_lifetime_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\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_peb_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", + "obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\security_smoke", "obj\\port_ipc_smoke", "obj\\resource_service_smoke", "obj\\named_queue_lifetime_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\\process_exit_multithread_smoke", "obj\\process_builder_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_userspace_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\\all_tests", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_present", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke" }; @@ -132,21 +135,43 @@ bool create_usbfatroot_if_missing(void) { return nob_mkdir_if_not_exists("usbfatroot"); } -// Utility to copy a local file into a FAT image using mcopy without admin rights. -bool mcopy_file(const char *src, const char *vhd_target, const char *dst_path) { - const char *cmd = nob_temp_sprintf("mcopy -o -i %s %s ::%s", vhd_target, src, dst_path); +// Copies one local file into a FAT image using mcopy without admin rights. +bool mcopy_file(const char* src, const char* vhd_target, const char* dst_path) { + const char* cmd = nob_temp_sprintf("mcopy -o -i %s %s ::%s", vhd_target, src, dst_path); return run_sync(cmd); } +// Copies several same-name files into one FAT directory with a single mcopy process. +bool mcopy_files_to_dir(const char* vhd_target, + const char* dst_dir, + const char* const* sources, + size_t source_count) { + if (!vhd_target || !dst_dir || (!sources && source_count)) + return false; + if (!source_count) + return true; + + Nob_String_Builder command = {0}; + nob_sb_appendf(&command, "mcopy -o -i %s", vhd_target); + for (size_t i = 0; i < source_count; ++i) + nob_sb_appendf(&command, " %s", sources[i]); + nob_sb_appendf(&command, " ::%s", dst_dir); + nob_sb_append_null(&command); + + bool ok = run_sync(command.items); + nob_sb_free(command); + return ok; +} + // Utility to create a directory in a FAT image using mmd. -bool mmd_dir(const char *vhd_target, const char *dir_path) { - const char *cmd = nob_temp_sprintf("mmd -i %s ::%s 2>nul", vhd_target, dir_path); +bool mmd_dir(const char* vhd_target, const char* dir_path) { + const char* cmd = nob_temp_sprintf("mmd -i %s ::%s 2>nul", vhd_target, dir_path); run_sync(cmd); return true; } // Writes a 512-byte Connectix VHD Fixed Disk Footer at the end of the file. -static void write_vpc_footer(FILE *f, uint64_t disk_size_bytes) { +static void write_vpc_footer(FILE* f, uint64_t disk_size_bytes) { uint8_t footer[512] = {0}; memcpy(&footer[0], "conectix", 8); @@ -208,14 +233,30 @@ static void write_vpc_footer(FILE *f, uint64_t disk_size_bytes) { fwrite(footer, 1, 512, f); } +// Ensures the managed development VHD carries the stable identity used by boot.ini. +static bool ensure_disk_vhd_signature(void) { + FILE* f = fopen("disk.vhd", "r+b"); + if (!f) return false; + + uint32_t signature = DEFAULT_VHD_DISK_SIGNATURE; + if (fseek(f, 440, SEEK_SET) != 0 || + fwrite(&signature, sizeof(signature), 1, f) != 1) { + fclose(f); + return false; + } + + fclose(f); + return true; +} + // Creates a 512 MB partitioned disk.vhd image with a valid VPC footer. bool create_disk_vhd_if_missing(void) { - if (nob_file_exists("disk.vhd")) return true; + if (nob_file_exists("disk.vhd")) return ensure_disk_vhd_signature(); nob_log(NOB_INFO, "disk.vhd missing, creating new 512 MB fixed VHD image..."); const uint64_t disk_size = (uint64_t)512 * 1024 * 1024; // 512 MB Payload - FILE *f = fopen("disk.vhd", "wb"); + FILE* f = fopen("disk.vhd", "wb"); if (!f) { nob_log(NOB_ERROR, "Failed to create disk.vhd file!"); return false; @@ -230,11 +271,13 @@ bool create_disk_vhd_if_missing(void) { // Build standard MBR partition table (Sector 0) uint8_t mbr[512] = {0}; + uint32_t disk_signature = DEFAULT_VHD_DISK_SIGNATURE; + memcpy(&mbr[440], &disk_signature, sizeof(disk_signature)); mbr[510] = 0x55; mbr[511] = 0xAA; // Partition 1 Entry: 64MB FAT32 @ LBA 2048 (1MB offset) - uint8_t *p1 = &mbr[0x01BE]; + uint8_t* p1 = &mbr[0x01BE]; p1[0] = 0x80; // Active / Bootable p1[1] = 0x00; p1[2] = 0x02; p1[3] = 0x00; // CHS Start p1[4] = 0x0C; // FAT32 LBA @@ -245,7 +288,7 @@ bool create_disk_vhd_if_missing(void) { memcpy(&p1[12], &p1_sectors, 4); // Partition 2 Entry: 256MB FAT32 @ LBA 133120 (65MB offset) - uint8_t *p2 = &mbr[0x01CE]; + uint8_t* p2 = &mbr[0x01CE]; p2[0] = 0x00; // Inactive p2[1] = 0x00; p2[2] = 0x01; p2[3] = 0x08; // CHS Start p2[4] = 0x0C; // FAT32 LBA @@ -272,7 +315,7 @@ bool create_disk_vhd_if_missing(void) { } // Runs the requested operation. -int main(int argc, char **argv) { +int main(int argc, char** argv) { NOB_GO_REBUILD_URSELF(argc, argv); if (!create_disk_vhd_if_missing()) return 1; @@ -282,14 +325,25 @@ int main(int argc, char **argv) { nob_log(NOB_INFO, "--- Building Core Subsystems ---"); - const char *core_tasks[] = { + const char* core_tasks[] = { "ml64 /nologo /c /Foobj\\kernel\\ke_switch.obj code\\kernel\\ke_switch.asm", "ml64 /nologo /c /Foobj\\kernel\\ke_syscall_asm.obj code\\kernel\\ke_syscall.asm", "ml64 /nologo /c /Foobj\\kernel\\ke_gdt_reload.obj code\\kernel\\ke_gdt_reload.asm", "ml64 /nologo /c /Foobj\\syscall\\stubs.obj code\\user\\syscall\\syscall_stubs.asm", + "ml64 /nologo /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\stubs_s.obj code\\user\\syscall\\syscall_stubs.asm", + nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\pe_loader.obj code\\user\\syscall\\pe_loader.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\pe_loader_s.obj code\\user\\syscall\\pe_loader.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\module_loader.obj code\\user\\syscall\\module_loader.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\module_loader_s.obj code\\user\\syscall\\module_loader.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\process_loader.obj code\\user\\syscall\\process_loader.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\process_loader_s.obj code\\user\\syscall\\process_loader.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\resource.obj code\\user\\syscall\\resource.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\resource_s.obj code\\user\\syscall\\resource.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_BUILD /c /Foobj\\syscall\\path.obj code\\user\\syscall\\path.c", UserCflags), + nob_temp_sprintf("cl %s /DNTL_SYSCALL_STATIC /c /Foobj\\syscall\\path_s.obj code\\user\\syscall\\path.c", UserCflags), "for %f in (code\\boot\\uefi\\*.asm) do ml64 /nologo /c /Fo\"obj\\boot\\%~nf.obj\" \"%f\"", - nob_temp_sprintf("cl %s /MP /c /Foobj\\ code\\common\\printf.c code\\common\\ini_parser.c", CommonCflags), + nob_temp_sprintf("cl %s /MP /c /Foobj\\ code\\common\\printf.c", CommonCflags), "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", UserCflags), nob_temp_sprintf("cl %s /c /Foobj\\runtime\\user_heap_exports.obj code\\user\\runtime\\heap_exports.c", UserCflags), @@ -298,18 +352,19 @@ int main(int argc, char **argv) { nob_temp_sprintf("cl %s /c /Foobj\\runtime\\posix_start.obj code\\user\\runtime\\posix_start.c", UserCflags), // 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", BootCflags, LFLAGS), + nob_temp_sprintf("cl %s /MP /c /Icode\\boot\\uefi\\include /Foobj\\boot\\ code\\boot\\uefi\\*.c code\\common\\boot_volume_selector.c && link %s /FORCE:MULTIPLE /SUBSYSTEM:EFI_APPLICATION /ENTRY:UefiMain /OUT:bin\\bootx64.efi obj\\libc.obj code\\boot\\uefi\\efilib.lib obj\\boot\\*.obj", BootCflags, LFLAGS), // HAL. - nob_temp_sprintf("cl %s /MP /c /Foobj\\hal\\ code\\kernel\\hal\\*.c && link %s /DLL /OUT:bin\\hal.dll /ENTRY:HalInitialize /IGNORE:4216 obj\\printf.obj obj\\libc.obj obj\\hal\\*.obj", KernelCflags, LFLAGS), + nob_temp_sprintf("cl %s /DNTL_HAL_BUILD /MP /c /Foobj\\hal\\ code\\kernel\\hal\\*.c && link %s /DLL /FIXED /DYNAMICBASE:NO /HIGHENTROPYVA:NO /RELEASE /BASE:0xFFFF800010000000 /OUT:bin\\hal.dll /NOENTRY /IGNORE:4216 obj\\printf.obj obj\\libc.obj obj\\hal\\*.obj", KernelCflags, LFLAGS), // Kernel. - nob_temp_sprintf("cl %s /DKE_DISPLAY_BUILD /MP /c /Foobj\\kernel\\ code\\kernel\\*.c && link %s /SUBSYSTEM:NATIVE /ENTRY:KernelMain /OUT:bin\\kernel.exe bin\\hal.lib obj\\ini_parser.obj obj\\printf.obj obj\\libc.obj obj\\kernel\\*.obj", KernelCflags, LFLAGS), + nob_temp_sprintf("cl %s /DKE_DISPLAY_BUILD /MP /c /Foobj\\kernel\\ code\\kernel\\*.c code\\common\\boot_volume_selector.c && link %s /SUBSYSTEM:NATIVE /FIXED /DYNAMICBASE:NO /HIGHENTROPYVA:NO /RELEASE /BASE:0xFFFF800000000000 /ENTRY:KernelMain /OUT:bin\\kernel.exe bin\\hal.lib obj\\printf.obj obj\\libc.obj obj\\kernel\\*.obj", KernelCflags, LFLAGS), // Subsystem DLLs. - "link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\syscall.dll obj\\syscall\\stubs.obj", - nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /DNTL_EXCEPTION_EXPORTS /MP /c /Foobj\\win32\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\runtime\\native_exception.c code\\user\\personalities\\win32\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\win32.dll bin\\syscall.lib obj\\libc.obj obj\\win32\\*.obj", UserCflags), - nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /MP /c /Foobj\\posix\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\personalities\\posix\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\posix.dll bin\\syscall.lib obj\\libc.obj obj\\posix\\*.obj", UserCflags) + "link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\syscall.dll obj\\syscall\\stubs.obj obj\\syscall\\pe_loader.obj obj\\syscall\\module_loader.obj obj\\syscall\\process_loader.obj obj\\syscall\\resource.obj obj\\syscall\\path.obj obj\\libc.obj", + "lib /nologo /OUT:bin\\syscall_s.lib obj\\syscall\\stubs_s.obj obj\\syscall\\pe_loader_s.obj obj\\syscall\\module_loader_s.obj obj\\syscall\\process_loader_s.obj obj\\syscall\\resource_s.obj obj\\syscall\\path_s.obj", + nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /DNTL_EXCEPTION_EXPORTS /MP /c /Foobj\\win32\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\runtime\\native_exception.c code\\user\\personalities\\win32\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\win32.dll bin\\syscall.lib obj\\libc.obj obj\\win32\\*.obj", UserCflags), + nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /MP /c /Foobj\\posix\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\personalities\\posix\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\posix.dll bin\\syscall.lib obj\\libc.obj obj\\posix\\*.obj", UserCflags) }; for (size_t i = 0; i < NOB_ARRAY_LEN(core_tasks); i++) { @@ -317,8 +372,8 @@ int main(int argc, char **argv) { } nob_log(NOB_INFO, "--- Building Bus Drivers ---"); - const char *pci_cmd = nob_temp_sprintf( - "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys /IMPLIB:bin\\%s.lib /ENTRY:DriverEntry bin\\hal.lib bin\\kernel.lib obj\\printf.obj obj\\libc.obj %s\\*.obj", + const char* pci_cmd = nob_temp_sprintf( + "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys /IMPLIB:bin\\%s.lib /ENTRY:DriverEntry bin\\kernel.lib obj\\printf.obj obj\\libc.obj %s\\*.obj", PCI_BUS_DRIVER.cflags, PCI_BUS_DRIVER.obj_dir, PCI_BUS_DRIVER.sources, PCI_BUS_DRIVER.name, PCI_BUS_DRIVER.name, PCI_BUS_DRIVER.obj_dir ); @@ -328,9 +383,9 @@ int main(int argc, char **argv) { Nob_Procs procs = {0}; for (size_t i = 0; i < NOB_ARRAY_LEN(STANDALONE_DRIVERS); i++) { - const Driver_Target *driver = &STANDALONE_DRIVERS[i]; - const char *cmd = nob_temp_sprintf( - "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys /ENTRY:DriverEntry bin\\hal.lib bin\\kernel.lib %s obj\\libc.obj %s\\*.obj", + const Driver_Target* driver = &STANDALONE_DRIVERS[i]; + const char* cmd = nob_temp_sprintf( + "cl %s /MP /c /Fo%s\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\%s.sys /ENTRY:DriverEntry bin\\kernel.lib %s obj\\libc.obj %s\\*.obj", driver->cflags, driver->obj_dir, driver->sources, driver->name, driver->libs ? driver->libs : "", driver->obj_dir ); @@ -349,11 +404,12 @@ int main(int argc, char **argv) { } } nob_sb_append_null(&lwip_files); - nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\lwip\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\lwip.sys /ENTRY:DriverEntry bin\\hal.lib bin\\kernel.lib obj\\libc.obj obj\\lwip\\*.obj", LwipCflags, lwip_files.items))); + nob_da_append(&procs, run_async(nob_temp_sprintf("cl %s /MP /c /Foobj\\lwip\\ %s && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL /OUT:bin\\lwip.sys /ENTRY:DriverEntry bin\\kernel.lib obj\\libc.obj obj\\lwip\\*.obj", LwipCflags, lwip_files.items))); } - const char *apps[][4] = { + const char* apps[][4] = { {"system\\init", NATIVE_INIT_LIBS_LITERAL, "init", "/ENTRY:_start"}, + {"system\\rssrv", NATIVE_SERVICE_LIBS_LITERAL, "rssrv", "/ENTRY:_start"}, {"tests\\all_tests", NATIVE_EXE_LIBS_LITERAL, "all_tests", NATIVE_EXE_ENTRY_LITERAL}, {"apps\\shell_win32", WIN32_EXE_LIBS_LITERAL, "shell_win32", NATIVE_EXE_ENTRY_LITERAL}, {"apps\\shell_posix", POSIX_EXE_LIBS_LITERAL, "shell_posix", "/ENTRY:PosixStart"}, @@ -363,6 +419,9 @@ int main(int argc, char **argv) { {"tests\\async_cancel", NATIVE_EXE_LIBS_LITERAL, "async_cancel", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\file_stat_smoke", NATIVE_EXE_LIBS_LITERAL, "file_stat_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\handle_dup_smoke", NATIVE_EXE_LIBS_LITERAL, "handle_dup_smoke", NATIVE_EXE_ENTRY_LITERAL}, + {"tests\\security_smoke", NATIVE_EXE_LIBS_LITERAL, "security_smoke", NATIVE_EXE_ENTRY_LITERAL}, + {"tests\\port_ipc_smoke", NATIVE_EXE_LIBS_LITERAL, "port_ipc_smoke", NATIVE_EXE_ENTRY_LITERAL}, + {"tests\\resource_service_smoke", NATIVE_EXE_LIBS_LITERAL, "resource_service_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\named_queue_lifetime_smoke", NATIVE_EXE_LIBS_LITERAL, "named_queue_lifetime_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\input_api_smoke", NATIVE_EXE_LIBS_LITERAL, "input_api_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\input_model_smoke", NATIVE_EXE_LIBS_LITERAL, "input_model_smoke", NATIVE_EXE_ENTRY_LITERAL}, @@ -371,6 +430,8 @@ int main(int argc, char **argv) { {"tests\\realpath_smoke", NATIVE_EXE_LIBS_LITERAL, "realpath_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\file_lock_smoke", NATIVE_EXE_LIBS_LITERAL, "file_lock_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\process_protection_smoke", NATIVE_EXE_LIBS_LITERAL, "process_protection_smoke", NATIVE_EXE_ENTRY_LITERAL}, + {"tests\\process_exit_multithread_smoke", NATIVE_EXE_LIBS_LITERAL, "process_exit_multithread_smoke", NATIVE_EXE_ENTRY_LITERAL}, + {"tests\\process_builder_smoke", "bin\\syscall_s.lib obj\\libc.obj " NATIVE_RUNTIME_OBJS_LITERAL, "process_builder_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\socket_addr_smoke", NATIVE_EXE_LIBS_LITERAL, "socket_addr_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\net_irp_smoke", NATIVE_EXE_LIBS_LITERAL, "net_irp_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\module_load_smoke", NATIVE_EXE_LIBS_LITERAL, "module_load_smoke", NATIVE_EXE_ENTRY_LITERAL}, @@ -389,7 +450,7 @@ int main(int argc, char **argv) { {"tests\\win32_mutex_smoke", WIN32_EXE_LIBS_LITERAL, "win32_mutex_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\win32_thread_suspend_smoke", WIN32_EXE_LIBS_LITERAL, "win32_thread_suspend_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\win32_module_lookup_smoke", WIN32_EXE_LIBS_LITERAL, "win32_module_lookup_smoke", NATIVE_EXE_ENTRY_LITERAL}, - {"tests\\cwd_peb_smoke", NATIVE_EXE_LIBS_LITERAL, "cwd_peb_smoke", NATIVE_EXE_ENTRY_LITERAL}, + {"tests\\cwd_userspace_smoke", NATIVE_EXE_LIBS_LITERAL, "cwd_userspace_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\mmap_smoke", NATIVE_EXE_LIBS_LITERAL, "mmap_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\pcache_smoke", NATIVE_EXE_LIBS_LITERAL, "pcache_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\pcache_pressure", NATIVE_EXE_LIBS_LITERAL, "pcache_pressure", NATIVE_EXE_ENTRY_LITERAL}, @@ -413,8 +474,8 @@ int main(int argc, char **argv) { }; for (size_t i = 0; i < NOB_ARRAY_LEN(apps); i++) { - const char *cmd = nob_temp_sprintf( - "cl %s /MP /c /Foobj\\%s\\ code\\user\\%s\\*.c && link %s /SUBSYSTEM:NATIVE %s /OUT:bin\\%s.exe /PDB:bin\\%s.pdb %s obj\\%s\\*.obj", + const char* cmd = nob_temp_sprintf( + "cl %s /MP /c /Foobj\\%s\\ code\\user\\%s\\*.c && link %s /SUBSYSTEM:NATIVE " USER_IMAGE_FLAGS_LITERAL " %s /OUT:bin\\%s.exe /PDB:bin\\%s.pdb %s obj\\%s\\*.obj", UserCflags, apps[i][2], apps[i][0], LFLAGS, apps[i][3], apps[i][2], apps[i][2], apps[i][1], apps[i][2] ); nob_da_append(&procs, run_async(cmd)); @@ -431,106 +492,129 @@ int main(int argc, char **argv) { run_sync("mformat -i " VHD_PART1 " -F"); run_sync("mformat -i " VHD_PART2 " -F"); - mmd_dir(VHD_PART1, "apps"); - mmd_dir(VHD_PART1, "system"); - mmd_dir(VHD_PART1, "drivers"); mmd_dir(VHD_PART1, "EFI"); mmd_dir(VHD_PART1, "EFI/BOOT"); + mmd_dir(VHD_PART1, "EFI/NTLite"); + mmd_dir(VHD_PART1, "EFI/NTLite/fs"); + mmd_dir(VHD_PART2, "apps"); + mmd_dir(VHD_PART2, "system"); + mmd_dir(VHD_PART2, "drivers"); mmd_dir(VHD_PART2, "tests"); - const char *copies[][3] = { - {"bin\\bootx64.efi", VHD_PART1, "EFI/BOOT/bootx64.efi"}, - {"bin\\kernel.exe", VHD_PART1, "system/colonel.exe"}, - {"bin\\hal.dll", VHD_PART1, "system/hal.dll"}, - {"bin\\syscall.dll", VHD_PART1, "system/syscall.dll"}, - {"bin\\win32.dll", VHD_PART1, "system/win32.dll"}, - {"bin\\posix.dll", VHD_PART1, "system/posix.dll"}, - {"code\\settings.ini", VHD_PART1, "system/settings.ini"}, - {"bin\\init.exe", VHD_PART1, "system/init.exe"}, - - {"bin\\pci.sys", VHD_PART1, "drivers/pci.sys"}, - {"bin\\ps2.sys", VHD_PART1, "drivers/ps2.sys"}, - {"bin\\serial.sys", VHD_PART1, "drivers/serial.sys"}, - {"bin\\hda.sys", VHD_PART1, "drivers/hda.sys"}, - {"bin\\ata.sys", VHD_PART1, "drivers/ata.sys"}, - {"bin\\ahci.sys", VHD_PART1, "drivers/ahci.sys"}, - {"bin\\fat.sys", VHD_PART1, "drivers/fat.sys"}, - {"bin\\ntfs.sys", VHD_PART1, "drivers/ntfs.sys"}, - {"bin\\vmwaresvga.sys", VHD_PART1, "drivers/vmwaresvga.sys"}, - {"bin\\lwip.sys", VHD_PART1, "drivers/lwip.sys"}, - {"bin\\usb.sys", VHD_PART1, "drivers/usb.sys"}, - {"bin\\rtl816x.sys", VHD_PART1, "drivers/rtl816x.sys"}, - {"bin\\rtl8139.sys", VHD_PART1, "drivers/rtl8139.sys"}, - {"code\\drivers\\drivers.ini", VHD_PART1, "drivers/drivers.ini"}, - {"code\\boot.ini", VHD_PART1, "boot.ini"}, + // The ESP is only the boot anchor; the configured system volume carries the OS payload. + if (!mcopy_file("bin\\bootx64.efi", VHD_PART1, "EFI/BOOT/bootx64.efi")) return 1; + if (!mcopy_file("code\\boot.ini", VHD_PART1, "boot.ini")) return 1; + if (!run_sync("if exist code\\boot\\uefi\\fs\\*.efi mcopy -o -i " VHD_PART1 " code\\boot\\uefi\\fs\\*.efi ::EFI/NTLite/fs/")) return 1; - {"bin\\shell_win32.exe", VHD_PART1, "apps/shell_win32.exe"}, - {"bin\\shell_posix.exe", VHD_PART1, "apps/shell_posix.exe"}, - {"bin\\shell_native.exe", VHD_PART1, "apps/shell_native.exe"}, - {"bin\\edit.exe", VHD_PART1, "apps/edit.exe"}, - {"bin\\httpget.exe", VHD_PART1, "apps/httpget.exe"}, - {"bin\\tcpserve.exe", VHD_PART1, "apps/tcpserve.exe"}, - {"bin\\udp.exe", VHD_PART1, "apps/udp.exe"}, - {"bin\\resolve.exe", VHD_PART1, "apps/resolve.exe"}, - {"bin\\ping.exe", VHD_PART1, "apps/ping.exe"}, - {"bin\\doom.exe", VHD_PART1, "apps/doom.exe"}, - {"bin\\video.exe", VHD_PART1, "apps/video.exe"}, - {"bin\\memtop.exe", VHD_PART1, "apps/memtop.exe"}, + if (!mcopy_file("bin\\kernel.exe", VHD_PART2, "system/colonel.exe")) return 1; - // Partition 2 Apps - {"bin\\all_tests.exe", VHD_PART2, "tests/all_tests.exe"}, - {"bin\\async_apc.exe", VHD_PART2, "tests/async_apc.exe"}, - {"bin\\async_cancel.exe", VHD_PART2, "tests/async_cancel.exe"}, - {"bin\\apc_return_smoke.exe", VHD_PART2, "tests/apc_return_smoke.exe"}, - {"bin\\file_stat_smoke.exe", VHD_PART2, "tests/file_stat_smoke.exe"}, - {"bin\\handle_dup_smoke.exe", VHD_PART2, "tests/handle_dup_smoke.exe"}, - {"bin\\named_queue_lifetime_smoke.exe", VHD_PART2, "tests/named_queue_lifetime_smoke.exe"}, - {"bin\\input_api_smoke.exe", VHD_PART2, "tests/input_api_smoke.exe"}, - {"bin\\input_model_smoke.exe", VHD_PART2, "tests/input_model_smoke.exe"}, - {"bin\\timer_smoke.exe", VHD_PART2, "tests/timer_smoke.exe"}, - {"bin\\stats_abi_smoke.exe", VHD_PART2, "tests/stats_abi_smoke.exe"}, - {"bin\\realpath_smoke.exe", VHD_PART2, "tests/realpath_smoke.exe"}, - {"bin\\file_lock_smoke.exe", VHD_PART2, "tests/file_lock_smoke.exe"}, - {"bin\\process_protection_smoke.exe", VHD_PART2, "tests/process_protection_smoke.exe"}, - {"bin\\socket_addr_smoke.exe", VHD_PART2, "tests/socket_addr_smoke.exe"}, - {"bin\\net_irp_smoke.exe", VHD_PART2, "tests/net_irp_smoke.exe"}, - {"bin\\module_load_smoke.exe", VHD_PART2, "tests/module_load_smoke.exe"}, - {"bin\\posix_module_smoke.exe", VHD_PART2, "tests/posix_module_smoke.exe"}, - {"bin\\exception_dispatcher_smoke.exe", VHD_PART2, "tests/exception_dispatcher_smoke.exe"}, - {"bin\\native_exception_runtime_smoke.exe", VHD_PART2, "tests/native_exception_runtime_smoke.exe"}, - {"bin\\native_raise_exception_smoke.exe", VHD_PART2, "tests/native_raise_exception_smoke.exe"}, - {"bin\\exception_smoke.exe", VHD_PART2, "tests/exception_smoke.exe"}, - {"bin\\win32_exception_smoke.exe", VHD_PART2, "tests/win32_exception_smoke.exe"}, - {"bin\\win32_seh_smoke.exe", VHD_PART2, "tests/win32_seh_smoke.exe"}, - {"bin\\syscall_contract_smoke.exe", VHD_PART2, "tests/syscall_contract_smoke.exe"}, - {"bin\\would_block_smoke.exe", VHD_PART2, "tests/would_block_smoke.exe"}, - {"bin\\audio_stream_smoke.exe", VHD_PART2, "tests/audio_stream_smoke.exe"}, - {"bin\\winmm_waveout_smoke.exe", VHD_PART2, "tests/winmm_waveout_smoke.exe"}, - {"bin\\win32_condition_variable_smoke.exe", VHD_PART2, "tests/win32_condition_variable_smoke.exe"}, - {"bin\\win32_mutex_smoke.exe", VHD_PART2, "tests/win32_mutex_smoke.exe"}, - {"bin\\win32_thread_suspend_smoke.exe", VHD_PART2, "tests/win32_thread_suspend_smoke.exe"}, - {"bin\\win32_module_lookup_smoke.exe", VHD_PART2, "tests/win32_module_lookup_smoke.exe"}, - {"bin\\cwd_peb_smoke.exe", VHD_PART2, "tests/cwd_peb_smoke.exe"}, - {"bin\\mmap_smoke.exe", VHD_PART2, "tests/mmap_smoke.exe"}, - {"bin\\pcache_smoke.exe", VHD_PART2, "tests/pcache_smoke.exe"}, - {"bin\\pcache_pressure.exe", VHD_PART2, "tests/pcache_pressure.exe"}, - {"bin\\dirty_writeback.exe", VHD_PART2, "tests/dirty_writeback.exe"}, - {"bin\\shared_mmap.exe", VHD_PART2, "tests/shared_mmap.exe"}, - {"bin\\pcache_invalidate.exe", VHD_PART2, "tests/pcache_invalidate.exe"}, - {"bin\\pcache_mmap_read_reuse.exe", VHD_PART2, "tests/pcache_mmap_read_reuse.exe"}, - {"bin\\pcache_multithread.exe", VHD_PART2, "tests/pcache_multithread.exe"}, - {"bin\\surface_smoke.exe", VHD_PART2, "tests/surface_smoke.exe"}, - {"bin\\surface_invalid_rect.exe", VHD_PART2, "tests/surface_invalid_rect.exe"}, - {"bin\\surface_multithread_present.exe", VHD_PART2, "tests/surface_multithread_present.exe"} + const char* system_files[] = { + "bin\\hal.dll", + "bin\\syscall.dll", + "bin\\win32.dll", + "bin\\posix.dll", + "code\\settings.ini", + "bin\\init.exe", + "bin\\rssrv.exe", }; + if (!mcopy_files_to_dir(VHD_PART2, "system/", system_files, NOB_ARRAY_LEN(system_files))) return 1; - for (size_t i = 0; i < NOB_ARRAY_LEN(copies); i++) { - mcopy_file(copies[i][0], copies[i][1], copies[i][2]); - } + const char* driver_files[] = { + "bin\\pci.sys", + "bin\\ps2.sys", + "bin\\serial.sys", + "bin\\hda.sys", + "bin\\ata.sys", + "bin\\ahci.sys", + "bin\\fat.sys", + "bin\\ntfs.sys", + "bin\\vmwaresvga.sys", + "bin\\lwip.sys", + "bin\\usb.sys", + "bin\\rtl816x.sys", + "bin\\rtl8139.sys", + "code\\drivers\\drivers.ini", + }; + if (!mcopy_files_to_dir(VHD_PART2, "drivers/", driver_files, NOB_ARRAY_LEN(driver_files))) return 1; - if (nob_file_exists("code\\user\\apps\\video\\bink2w64.dll")) mcopy_file("code\\user\\apps\\video\\bink2w64.dll", VHD_PART1, "apps/bink2w64.dll"); - if (nob_file_exists("code\\user\\apps\\video\\video.bk2")) mcopy_file("code\\user\\apps\\video\\video.bk2", VHD_PART1, "apps/video.bk2"); - if (nob_file_exists("code\\user\\apps\\doom\\doom1.wad")) mcopy_file("code\\user\\apps\\doom\\doom1.wad", VHD_PART1, "apps/doom1.wad"); + const char* app_files[] = { + "bin\\shell_win32.exe", + "bin\\shell_posix.exe", + "bin\\shell_native.exe", + "bin\\edit.exe", + "bin\\httpget.exe", + "bin\\tcpserve.exe", + "bin\\udp.exe", + "bin\\resolve.exe", + "bin\\ping.exe", + "bin\\doom.exe", + "bin\\video.exe", + "bin\\memtop.exe", + }; + if (!mcopy_files_to_dir(VHD_PART2, "apps/", app_files, NOB_ARRAY_LEN(app_files))) return 1; + + const char* test_files[] = { + "bin\\all_tests.exe", + "bin\\async_apc.exe", + "bin\\async_cancel.exe", + "bin\\apc_return_smoke.exe", + "bin\\file_stat_smoke.exe", + "bin\\handle_dup_smoke.exe", + "bin\\security_smoke.exe", + "bin\\port_ipc_smoke.exe", + "bin\\resource_service_smoke.exe", + "bin\\named_queue_lifetime_smoke.exe", + "bin\\input_api_smoke.exe", + "bin\\input_model_smoke.exe", + "bin\\timer_smoke.exe", + "bin\\stats_abi_smoke.exe", + "bin\\realpath_smoke.exe", + "bin\\file_lock_smoke.exe", + "bin\\process_protection_smoke.exe", + "bin\\process_exit_multithread_smoke.exe", + "bin\\process_builder_smoke.exe", + "bin\\socket_addr_smoke.exe", + "bin\\net_irp_smoke.exe", + "bin\\module_load_smoke.exe", + "bin\\posix_module_smoke.exe", + "bin\\exception_dispatcher_smoke.exe", + "bin\\native_exception_runtime_smoke.exe", + "bin\\native_raise_exception_smoke.exe", + "bin\\exception_smoke.exe", + "bin\\win32_exception_smoke.exe", + "bin\\win32_seh_smoke.exe", + "bin\\syscall_contract_smoke.exe", + "bin\\would_block_smoke.exe", + "bin\\audio_stream_smoke.exe", + "bin\\winmm_waveout_smoke.exe", + "bin\\win32_condition_variable_smoke.exe", + "bin\\win32_mutex_smoke.exe", + "bin\\win32_thread_suspend_smoke.exe", + "bin\\win32_module_lookup_smoke.exe", + "bin\\cwd_userspace_smoke.exe", + "bin\\mmap_smoke.exe", + "bin\\pcache_smoke.exe", + "bin\\pcache_pressure.exe", + "bin\\dirty_writeback.exe", + "bin\\shared_mmap.exe", + "bin\\pcache_invalidate.exe", + "bin\\pcache_mmap_read_reuse.exe", + "bin\\pcache_multithread.exe", + "bin\\surface_smoke.exe", + "bin\\surface_invalid_rect.exe", + "bin\\surface_multithread_present.exe", + }; + if (!mcopy_files_to_dir(VHD_PART2, "tests/", test_files, NOB_ARRAY_LEN(test_files))) return 1; + + const char* optional_app_files[3]; + size_t optional_app_count = 0; + if (nob_file_exists("code\\user\\apps\\video\\bink2w64.dll")) + optional_app_files[optional_app_count++] = "code\\user\\apps\\video\\bink2w64.dll"; + if (nob_file_exists("code\\user\\apps\\video\\video.bk2")) + optional_app_files[optional_app_count++] = "code\\user\\apps\\video\\video.bk2"; + if (nob_file_exists("code\\user\\apps\\doom\\doom1.wad")) + optional_app_files[optional_app_count++] = "code\\user\\apps\\doom\\doom1.wad"; + if (!mcopy_files_to_dir(VHD_PART2, "apps/", optional_app_files, optional_app_count)) return 1; nob_log(NOB_INFO, "Build complete!"); return 0; diff --git a/code/common/boot_volume_selector.c b/code/common/boot_volume_selector.c new file mode 100644 index 0000000..325ebac --- /dev/null +++ b/code/common/boot_volume_selector.c @@ -0,0 +1,99 @@ +// Shared parser for the canonical structural boot-volume selector grammar. + +#include + +#include + +// Converts one hexadecimal character without accepting non-hexadecimal text. +static bool BootVolumeHexDigit(char c, uint8_t* digitOut) { + if (!digitOut) + return false; + if (c >= '0' && c <= '9') { + *digitOut = (uint8_t)(c - '0'); + return true; + } + if (c >= 'a' && c <= 'f') { + *digitOut = (uint8_t)(10 + c - 'a'); + return true; + } + if (c >= 'A' && c <= 'F') { + *digitOut = (uint8_t)(10 + c - 'A'); + return true; + } + return false; +} + +// Parses an exact hexadecimal integer span into a 32-bit value. +static bool BootVolumeParseHex32(const char* text, size_t length, uint32_t* valueOut) { + if (!text || !valueOut || length == 0 || length > 8) + return false; + + uint32_t value = 0; + for (size_t i = 0; i < length; ++i) { + uint8_t digit = 0; + if (!BootVolumeHexDigit(text[i], &digit)) + return false; + value = (value << 4) | digit; + } + *valueOut = value; + return true; +} + +// Parses one exact GUID hexadecimal group. +static bool BootVolumeParseGuidGroup(const char* text, size_t length, uint32_t* valueOut) { + return BootVolumeParseHex32(text, length, valueOut); +} + +// Parses a canonical 8-4-4-4-12 GUID into EFI/GPT field layout. +static bool BootVolumeParseGuid(const char* text, size_t length, BOOT_VOLUME_GUID* guidOut) { + if (!text || !guidOut || length != 36 || + text[8] != '-' || text[13] != '-' || text[18] != '-' || text[23] != '-') { + return false; + } + + BOOT_VOLUME_GUID guid = {0}; + uint32_t value = 0; + if (!BootVolumeParseGuidGroup(text, 8, &guid.Data1) || + !BootVolumeParseGuidGroup(text + 9, 4, &value)) { + return false; + } + guid.Data2 = (uint16_t)value; + if (!BootVolumeParseGuidGroup(text + 14, 4, &value)) + return false; + guid.Data3 = (uint16_t)value; + + const size_t byteOffsets[8] = {19, 21, 24, 26, 28, 30, 32, 34}; + for (size_t i = 0; i < 8; ++i) { + if (!BootVolumeParseGuidGroup(text + byteOffsets[i], 2, &value)) + return false; + guid.Data4[i] = (uint8_t)value; + } + + *guidOut = guid; + return true; +} + +// Parses one canonical structural system-volume selector. +bool BootVolumeSelectorParse(const char* text, size_t length, BOOT_VOLUME_SELECTOR* selectorOut) { + if (!text || !selectorOut) + return false; + + BOOT_VOLUME_SELECTOR selector = {0}; + if (length == 14 && memcmp(text, "mbr/", 4) == 0 && text[12] == '/' && + BootVolumeParseHex32(text + 4, 8, &selector.Identity.Mbr.DiskSignature) && + text[13] >= '1' && text[13] <= '4') { + selector.Kind = BootVolumeSelectorMbr; + selector.Identity.Mbr.PartitionNumber = (uint32_t)(text[13] - '0'); + *selectorOut = selector; + return true; + } + + if (length == 40 && memcmp(text, "gpt/", 4) == 0 && + BootVolumeParseGuid(text + 4, 36, &selector.Identity.Gpt.PartitionGuid)) { + selector.Kind = BootVolumeSelectorGpt; + *selectorOut = selector; + return true; + } + + return false; +} diff --git a/code/common/include/common/boot_volume_selector.h b/code/common/include/common/boot_volume_selector.h new file mode 100644 index 0000000..b653f09 --- /dev/null +++ b/code/common/include/common/boot_volume_selector.h @@ -0,0 +1,36 @@ +// Shared structural boot-volume selector declarations. + +#pragma once + +#include +#include +#include + +typedef enum BOOT_VOLUME_SELECTOR_KIND { + BootVolumeSelectorInvalid = 0, + BootVolumeSelectorMbr, + BootVolumeSelectorGpt, +} BOOT_VOLUME_SELECTOR_KIND; + +typedef struct BOOT_VOLUME_GUID { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} BOOT_VOLUME_GUID; + +typedef struct BOOT_VOLUME_SELECTOR { + BOOT_VOLUME_SELECTOR_KIND Kind; + union { + struct { + uint32_t DiskSignature; + uint32_t PartitionNumber; + } Mbr; + struct { + BOOT_VOLUME_GUID PartitionGuid; + } Gpt; + } Identity; +} BOOT_VOLUME_SELECTOR; + +// Parses one canonical structural system-volume selector. +bool BootVolumeSelectorParse(const char* text, size_t length, BOOT_VOLUME_SELECTOR* selectorOut); \ No newline at end of file diff --git a/code/common/include/common/ini_parser.h b/code/common/include/common/ini_parser.h deleted file mode 100644 index 9381b6d..0000000 --- a/code/common/include/common/ini_parser.h +++ /dev/null @@ -1,6 +0,0 @@ -// Shared INI parser declarations for boot and kernel configuration readers. - -#pragma once -typedef void (*IniCallback)(const char* section, const char* key, - const char* value, void* userdata); -void IniParse(char* buf, IniCallback cb, void* userdata); diff --git a/code/common/ini_parser.c b/code/common/ini_parser.c deleted file mode 100644 index 0a27732..0000000 --- a/code/common/ini_parser.c +++ /dev/null @@ -1,55 +0,0 @@ -// Minimal INI parser. This file accepts the small configuration dialect used by boot.ini and drivers.ini. - -#include -#include - -// Checks whether ws. -static inline int IsWs(char c) { return c == ' ' || c == '\t' || c == '\r'; } - -// Implements the trim Left operation. -static char* TrimLeft(char* s) { - while (*s && IsWs(*s)) s++; - return s; -} - -// Implements the trim Right operation. -static void TrimRight(char* s) { - char* e = s + strlen(s); - while (e > s && IsWs(*(e-1))) *--e = '\0'; -} - -// Parse 'buf' (null-terminated) for [section] / key=value. -// Calls cb(section, key, value, userdata) for each key found. -void IniParse(char* buf, IniCallback cb, void* userdata) { - char section[64] = ""; - char* line = buf; - - while (*line) { - char* end = line; - while (*end && *end != '\n') end++; - char saved = *end; - *end = '\0'; - - char* p = TrimLeft(line); - if (*p == '[') { - p++; - char* close = strchr(p, ']'); - if (close) { *close = '\0'; strncpy(section, p, 63); section[63] = '\0'; } - } else if (*p && *p != ';' && *p != '#') { - char* eq = strchr(p, '='); - if (eq) { - *eq = '\0'; - char* key = p; TrimRight(key); - char* val = TrimLeft(eq + 1); TrimRight(val); - cb(section, key, val, userdata); - } else { - // Key with no value (e.g., a driver filename in drivers.ini). - TrimRight(p); - if (*p) cb(section, p, "", userdata); - } - } - - *end = saved; - line = end + (*end ? 1 : 0); - } -} diff --git a/code/common/libc.c b/code/common/libc.c index 05e1697..6a02b59 100644 --- a/code/common/libc.c +++ b/code/common/libc.c @@ -20,15 +20,15 @@ char* strncpy(char* dest, const char* src, size_t n) { return dest; } -char *strstr(const char *haystack, const char *needle) { +char* strstr(const char* haystack, const char* needle) { // An empty needle matches the beginning of haystack if (!*needle) { - return (char *)haystack; + return (char*)haystack; } for (; *haystack != '\0'; haystack++) { - const char *h = haystack; - const char *n = needle; + const char* h = haystack; + const char* n = needle; // Match as many characters as possible while (*h != '\0' && *n != '\0' && *h == *n) { @@ -38,7 +38,7 @@ char *strstr(const char *haystack, const char *needle) { // If we reached the end of needle, we found a match if (*n == '\0') { - return (char *)haystack; + return (char*)haystack; } } @@ -83,13 +83,13 @@ int strcmp(const char* s1, const char* s2) { } // Implements the strncmp operation. -int strncmp(const char *s1, const char *s2, size_t n) { +int strncmp(const char* s1, const char* s2, size_t n) { while (n && *s1 && (*s1 == *s2)) { s1++; s2++; n--; } - return n == 0 ? 0 : *(unsigned char *)s1 - *(unsigned char *)s2; + return n == 0 ? 0 : *(unsigned char*)s1 - *(unsigned char*)s2; } // Implements the stricmp operation. @@ -109,7 +109,7 @@ size_t strlen(const char* s) { } // Implements the strnlen operation. -size_t strnlen(const char *s, size_t maxlen) { +size_t strnlen(const char* s, size_t maxlen) { size_t len; for (len = 0; len < maxlen; len++) { if (s[len] == '\0') { @@ -122,7 +122,7 @@ size_t strnlen(const char *s, size_t maxlen) { // Implements the strchr operation. char* strchr(const char* s, int c) { while (*s) { - if (*s == (char)c) return (char *)s; + if (*s == (char)c) return (char*)s; s++; } return NULL; @@ -138,6 +138,18 @@ char* strrchr(const char* s, int c) { return (char*)last; } +void *memchr(const void *s, int c, size_t n) { + const unsigned char *p = s; + unsigned char ch = (unsigned char)c; + while (n--) { + if (*p == ch) { + return (void *)p; + } + p++; + } + return NULL; +} + // Implements the memset operation. void* memset(void* dest, int c, size_t n) { unsigned char* d = (unsigned char*)dest; diff --git a/code/drivers/bus/pci/pci.c b/code/drivers/bus/pci/pci.c index a72a8f1..e25276e 100644 --- a/code/drivers/bus/pci/pci.c +++ b/code/drivers/bus/pci/pci.c @@ -611,15 +611,6 @@ __declspec(dllexport) KSTATUS PciEnableMsi(DEVICE_OBJECT* pdo, uint8_t irqSlot) if (!cap) return NTLSTATUS_NOT_SUPPORTED; - uint32_t bspLapic = gDdk->Platform->GetBspLapicId(); - uint8_t x2Apic = gDdk->Platform->IsX2ApicEnabled(); - if (!x2Apic && bspLapic >= 256u) - return NTLSTATUS_NOT_SUPPORTED; - if (x2Apic && bspLapic >= 256u) { - // Extended x2APIC MSI destinations need interrupt-remapping awareness. - return NTLSTATUS_NOT_SUPPORTED; - } - uint8_t bus = ext->Info.Address.Bus; uint8_t dev = ext->Info.Address.Device; uint8_t fn = ext->Info.Address.Function; @@ -627,18 +618,19 @@ __declspec(dllexport) KSTATUS PciEnableMsi(DEVICE_OBJECT* pdo, uint8_t irqSlot) uint16_t control = PciRawRead16(bus, dev, fn, (uint16_t)(cap + 2u)); bool is64 = (control & PCI_MSI_CTRL_64BIT) != 0; bool pvm = (control & PCI_MSI_CTRL_PVM) != 0; - uint8_t vector = (uint8_t)(PCI_IRQ_VECTOR_BASE + irqSlot); - uint32_t address = PCI_MSI_ADDRESS_BASE | ((bspLapic & 0xFFu) << 12); - uint16_t data = vector; + NTL_DDK_MSI_MESSAGE message; + KSTATUS messageStatus = gDdk->Interrupt->ComposeMsiMessage(irqSlot, &message); + if (!KSUCCESS(messageStatus)) + return messageStatus; - PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 4u), address); + PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 4u), (uint32_t)message.Address); if (is64) { - PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 8u), 0); - PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 12u), data); + PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 8u), (uint32_t)(message.Address >> 32)); + PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 12u), (uint16_t)message.Data); if (pvm) PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 16u), 0); } else { - PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 8u), data); + PciRawWrite16(bus, dev, fn, (uint16_t)(cap + 8u), (uint16_t)message.Data); if (pvm) PciRawWrite32(bus, dev, fn, (uint16_t)(cap + 12u), 0); } @@ -651,8 +643,8 @@ __declspec(dllexport) KSTATUS PciEnableMsi(DEVICE_OBJECT* pdo, uint8_t irqSlot) cmd |= PCI_COMMAND_INTX_DISABLE; PciRawWrite16(bus, dev, fn, PCI_REG_COMMAND_STATUS, cmd); - gDdk->Debug->Print("pci: MSI enabled %02x:%02x.%u cap=0x%02x vector=0x%02x irqslot=%u dest=%u %s\n", - bus, dev, fn, cap, vector, irqSlot, bspLapic, is64 ? "64-bit" : "32-bit"); + gDdk->Debug->Print("pci: MSI enabled %02x:%02x.%u cap=0x%02x vector=0x%02x irqslot=%u %s\n", + bus, dev, fn, cap, (unsigned)(message.Data & 0xFFu), irqSlot, is64 ? "64-bit" : "32-bit"); return NTLSTATUS_SUCCESS; } diff --git a/code/drivers/bus/pci/pci.h b/code/drivers/bus/pci/pci.h index c3212e4..d6a6796 100644 --- a/code/drivers/bus/pci/pci.h +++ b/code/drivers/bus/pci/pci.h @@ -37,7 +37,6 @@ #define PCI_MSI_CTRL_64BIT 0x0080u #define PCI_MSI_CTRL_PVM 0x0100u -#define PCI_MSI_ADDRESS_BASE 0xFEE00000u #define PCI_IRQ_VECTOR_BASE 0x20u typedef struct PCI_PDO_EXTENSION { diff --git a/code/drivers/fs/fat/fat.c b/code/drivers/fs/fat/fat.c index da108f8..5b2b87e 100644 --- a/code/drivers/fs/fat/fat.c +++ b/code/drivers/fs/fat/fat.c @@ -23,7 +23,7 @@ typedef struct FAT_VOL_S FAT_VOL; #define FAT_CACHE_SLOTS 32 typedef struct { - FAT_VOL *vol; + FAT_VOL* vol; uint64_t lba; uint8_t data[512]; bool valid; @@ -103,14 +103,14 @@ static void FatCacheRelease(int held) { } // Raw (non-cached) sector I/O โ€” forward declarations. -static KSTATUS ReadSectorsRaw (FAT_VOL *v, uint64_t lba, uint32_t count, void *buf); -static KSTATUS WriteSectorsRaw(FAT_VOL *v, uint64_t lba, uint32_t count, const void *buf); +static KSTATUS ReadSectorsRaw (FAT_VOL* v, uint64_t lba, uint32_t count, void* buf); +static KSTATUS WriteSectorsRaw(FAT_VOL* v, uint64_t lba, uint32_t count, const void* buf); // Read one FAT sector through the cache. // Caller must hold the FAT cache mutex while using the returned pointer. -static KSTATUS FatCacheRead(FAT_VOL *v, uint64_t lba, uint8_t **out) { +static KSTATUS FatCacheRead(FAT_VOL* v, uint64_t lba, uint8_t** out) { uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS); - FAT_CACHE_ENTRY *e = &gFatCache[slot]; + FAT_CACHE_ENTRY* e = &gFatCache[slot]; if (e->valid && e->vol == v && e->lba == lba) { *out = e->data; @@ -136,13 +136,13 @@ static KSTATUS FatCacheRead(FAT_VOL *v, uint64_t lba, uint8_t **out) { } // Implements the fat Cache Mark Dirty operation. -static void FatCacheMarkDirty(FAT_VOL *v, uint64_t lba) { +static void FatCacheMarkDirty(FAT_VOL* v, uint64_t lba) { uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS); if (gFatCache[slot].valid && gFatCache[slot].vol == v && gFatCache[slot].lba == lba) gFatCache[slot].dirty = true; } -// Implements the fat Cache Invalidate operation. +// Invalidates every FAT sector-cache entry. static void FatCacheInvalidate(void) { int held = FatCacheAcquire(); for (int i = 0; i < FAT_CACHE_SLOTS; i++) { @@ -153,14 +153,29 @@ static void FatCacheInvalidate(void) { FatCacheRelease(held); } +// Discards cache entries owned by one dead or destroyed mounted FAT instance. +static void FatCacheInvalidateVolume(FAT_VOL* volume) { + if (!volume) + return; + int held = FatCacheAcquire(); + for (int i = 0; i < FAT_CACHE_SLOTS; i++) { + if (gFatCache[i].vol != volume) + continue; + gFatCache[i].vol = (FAT_VOL*)0; + gFatCache[i].valid = false; + gFatCache[i].dirty = false; + } + FatCacheRelease(held); +} + // FlushFatCache โ€” write every dirty slot to all FAT copies, clear dirty flag. // Defined after FAT_VOL is fully declared (below). -static void FlushFatCache(FAT_VOL *v); +static void FlushFatCache(FAT_VOL* v); // Driver state. struct FAT_VOL_S { FAT_VARIANT Variant; - uint8_t DriveIndex; // physical disk DEVICE_OBJECT index + uint8_t DriveIndex; uint64_t PartitionLba; // absolute LBA of the mounted volume uint64_t PartitionSize; // sectors, best-effort bounds/documentation uint32_t BytesPerSector; @@ -180,11 +195,13 @@ struct FAT_VOL_S { uint64_t PathCacheGeneration; IO_FILESYSTEM_STATS Stats; FAT_PATH_CACHE_ENTRY PathCache[FAT_PATH_CACHE_SLOTS]; + IO_VOLUME* Volume; // Active mount-list reference. + LIST_ENTRY MountLink; }; typedef struct { uint32_t StartCluster; uint32_t FileSize; - FAT_VOL *Vol; + FAT_VOL* Vol; uint64_t DirentLba; uint32_t DirentOffset; SPINLOCK ChainCacheLock; @@ -193,22 +210,22 @@ typedef struct { } FAT_FILE; // Raw sector I/O (bypasses FAT cache; used for file data and cache eviction). -static KSTATUS ReadSectorsRaw(FAT_VOL *v, uint64_t lba, - uint32_t count, void *buf) { +static KSTATUS ReadSectorsRaw(FAT_VOL* v, uint64_t lba, + uint32_t count, void* buf) { if (!v) return NTLSTATUS_INVALID_PARAMETER; return IoReadDiskSectors(v->DriveIndex, lba, count, buf); } -static KSTATUS WriteSectorsRaw(FAT_VOL *v, uint64_t lba, - uint32_t count, const void *buf) { +static KSTATUS WriteSectorsRaw(FAT_VOL* v, uint64_t lba, + uint32_t count, const void* buf) { if (!v) return NTLSTATUS_INVALID_PARAMETER; return IoWriteDiskSectors(v->DriveIndex, lba, count, buf); } // Convenience aliases for file-data I/O. -static KSTATUS ReadSectors (FAT_VOL *v, uint64_t lba, uint32_t n, void *b) { return ReadSectorsRaw (v, lba, n, b); } +static KSTATUS ReadSectors (FAT_VOL* v, uint64_t lba, uint32_t n, void* b) { return ReadSectorsRaw (v, lba, n, b); } // Updates sectors. -static KSTATUS WriteSectors(FAT_VOL *v, uint64_t lba, uint32_t n, const void *b) { return WriteSectorsRaw(v, lba, n, b); } +static KSTATUS WriteSectors(FAT_VOL* v, uint64_t lba, uint32_t n, const void* b) { return WriteSectorsRaw(v, lba, n, b); } static const char* FatVariantName(FAT_VARIANT variant) { switch (variant) { @@ -247,8 +264,8 @@ static int FatIsFixedRoot(FAT_VOL* v, uint32_t dirCluster) { return v && v->Variant == FatVariant16 && dirCluster == FAT_FIXED_ROOT_CLUSTER; } -static uint64_t ClusterToLba(FAT_VOL *v, uint32_t cluster); -static uint32_t FatEntry(FAT_VOL *v, uint32_t cluster); +static uint64_t ClusterToLba(FAT_VOL* v, uint32_t cluster); +static uint32_t FatEntry(FAT_VOL* v, uint32_t cluster); static int FatDirectoryFirstSegment(FAT_VOL* v, uint32_t dirCluster, uint32_t* cursor, uint64_t* lba, @@ -289,17 +306,17 @@ static int FatDirectoryNextSegment(FAT_VOL* v, uint32_t dirCluster, } // Implements the cluster To Lba operation. -static uint64_t ClusterToLba(FAT_VOL *v, uint32_t cluster) { +static uint64_t ClusterToLba(FAT_VOL* v, uint32_t cluster) { return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster; } // FlushFatCache โ€” needs full FAT_VOL definition. -static void FlushFatCache(FAT_VOL *v) { +static void FlushFatCache(FAT_VOL* v) { uint64_t fat0Start = (uint64_t)v->PartitionLba + v->ReservedSectors; int held = FatCacheAcquire(); for (int i = 0; i < FAT_CACHE_SLOTS; i++) { - FAT_CACHE_ENTRY *e = &gFatCache[i]; + FAT_CACHE_ENTRY* e = &gFatCache[i]; if (!e->valid || !e->dirty || e->vol != v) continue; if (e->lba < fat0Start) continue; @@ -318,7 +335,7 @@ static void FlushFatCache(FAT_VOL *v) { // FAT read / write (through cache). -static uint32_t FatEntry(FAT_VOL *v, uint32_t cluster) { +static uint32_t FatEntry(FAT_VOL* v, uint32_t cluster) { if (!v || !FatIsDataCluster(v, cluster)) return 0; uint32_t bytesPerEntry = v->Variant == FatVariant16 ? 2u : 4u; @@ -328,21 +345,21 @@ static uint32_t FatEntry(FAT_VOL *v, uint32_t cluster) { uint32_t offInSec = fatOffset % v->BytesPerSector; int held = FatCacheAcquire(); - uint8_t *sec; + uint8_t* sec; KSTATUS s = FatCacheRead(v, sector, &sec); uint32_t value = 0; if (KSUCCESS(s)) { if (v->Variant == FatVariant16) - value = *(uint16_t *)(sec + offInSec); + value = *(uint16_t*)(sec + offInSec); else - value = *(uint32_t *)(sec + offInSec) & 0x0FFFFFFFu; + value = *(uint32_t*)(sec + offInSec) & 0x0FFFFFFFu; } FatCacheRelease(held); return value; } // Updates fat Entry. -static KSTATUS WriteFatEntry(FAT_VOL *v, uint32_t cluster, uint32_t value) { +static KSTATUS WriteFatEntry(FAT_VOL* v, uint32_t cluster, uint32_t value) { if (!v || !FatIsDataCluster(v, cluster)) return NTLSTATUS_INVALID_PARAMETER; uint32_t bytesPerEntry = v->Variant == FatVariant16 ? 2u : 4u; @@ -352,7 +369,7 @@ static KSTATUS WriteFatEntry(FAT_VOL *v, uint32_t cluster, uint32_t value) { uint32_t offInSec = fatOffset % v->BytesPerSector; int held = FatCacheAcquire(); - uint8_t *sec; + uint8_t* sec; KSTATUS s = FatCacheRead(v, sector, &sec); if (!KSUCCESS(s)) { FatCacheRelease(held); @@ -360,10 +377,10 @@ static KSTATUS WriteFatEntry(FAT_VOL *v, uint32_t cluster, uint32_t value) { } if (v->Variant == FatVariant16) { - *(uint16_t *)(sec + offInSec) = (uint16_t)(value & 0xFFFFu); + *(uint16_t*)(sec + offInSec) = (uint16_t)(value & 0xFFFFu); } else { - uint32_t existing = *(uint32_t *)(sec + offInSec); - *(uint32_t *)(sec + offInSec) = (existing & 0xF0000000u) + uint32_t existing = *(uint32_t*)(sec + offInSec); + *(uint32_t*)(sec + offInSec) = (existing & 0xF0000000u) | (value & 0x0FFFFFFFu); } FatCacheMarkDirty(v, sector); @@ -372,7 +389,7 @@ static KSTATUS WriteFatEntry(FAT_VOL *v, uint32_t cluster, uint32_t value) { } // Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan). -static uint32_t AllocClusterEx(FAT_VOL *v, int zeroCluster) { +static uint32_t AllocClusterEx(FAT_VOL* v, int zeroCluster) { if (v->TotalClusters == 0) return 0; uint32_t total = v->TotalClusters; @@ -393,7 +410,7 @@ static uint32_t AllocClusterEx(FAT_VOL *v, int zeroCluster) { // it can ever be read back, so skipping these writes avoids a // slow boot-time full-pagefile zero pass. if (zeroCluster) { - uint8_t *zero = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* zero = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (zero) { memset(zero, 0, v->BytesPerCluster); WriteSectors(v, ClusterToLba(v, c), v->SectorsPerCluster, zero); @@ -407,11 +424,11 @@ static uint32_t AllocClusterEx(FAT_VOL *v, int zeroCluster) { return 0; } -static uint32_t AllocCluster(FAT_VOL *v) { +static uint32_t AllocCluster(FAT_VOL* v) { return AllocClusterEx(v, 1); } -static uint32_t AppendClusterEx(FAT_VOL *v, uint32_t lastCluster, int zeroCluster) { +static uint32_t AppendClusterEx(FAT_VOL* v, uint32_t lastCluster, int zeroCluster) { uint32_t newCluster = AllocClusterEx(v, zeroCluster); if (!newCluster) return 0; if (!KSUCCESS(WriteFatEntry(v, lastCluster, newCluster))) return 0; @@ -419,12 +436,12 @@ static uint32_t AppendClusterEx(FAT_VOL *v, uint32_t lastCluster, int zeroCluste } // Appends cluster. -static uint32_t AppendCluster(FAT_VOL *v, uint32_t lastCluster) { +static uint32_t AppendCluster(FAT_VOL* v, uint32_t lastCluster) { return AppendClusterEx(v, lastCluster, 1); } // Returns chain End. -static uint32_t GetChainEnd(FAT_VOL *v, uint32_t start, uint32_t *outCount) { +static uint32_t GetChainEnd(FAT_VOL* v, uint32_t start, uint32_t* outCount) { uint32_t cluster = start, prev = start, count = 0; while (FatIsDataCluster(v, cluster)) { prev = cluster; count++; @@ -435,7 +452,7 @@ static uint32_t GetChainEnd(FAT_VOL *v, uint32_t start, uint32_t *outCount) { } // Releases cluster Chain. -static void FreeClusterChain(FAT_VOL *v, uint32_t start) { +static void FreeClusterChain(FAT_VOL* v, uint32_t start) { uint32_t cluster = start; while (FatIsDataCluster(v, cluster)) { uint32_t next = FatEntry(v, cluster); @@ -583,15 +600,15 @@ static void FatDecodeAccessDate(uint16_t date, SYS_SYSTEM_TIME* out) { FatDecodeDateTime(date, 0, 0, out); } -static KSTATUS DirentUpdateMetadata(FAT_VOL *v, uint64_t lba, +static KSTATUS DirentUpdateMetadata(FAT_VOL* v, uint64_t lba, uint32_t offset, const FAT_DIRENT_UPDATE* update) { if (!v || !update || !update->Flags) return NTLSTATUS_INVALID_PARAMETER; - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return NTLSTATUS_NO_MEMORY; KSTATUS s = ReadSectors(v, lba, 1, sec); if (KSUCCESS(s)) { - DIRENT *d = (DIRENT *)(sec + offset); + DIRENT* d = (DIRENT*)(sec + offset); if (update->Flags & FAT_DIRENT_UPDATE_SIZE) d->FileSize = update->Size; if (update->Flags & FAT_DIRENT_UPDATE_FIRST_CLUSTER) @@ -613,7 +630,7 @@ static KSTATUS DirentUpdateMetadata(FAT_VOL *v, uint64_t lba, return s; } -static KSTATUS DirentUpdateSize(FAT_VOL *v, uint64_t lba, +static KSTATUS DirentUpdateSize(FAT_VOL* v, uint64_t lba, uint32_t offset, uint32_t size) { FAT_DIRENT_UPDATE u; memset(&u, 0, sizeof(u)); @@ -623,7 +640,7 @@ static KSTATUS DirentUpdateSize(FAT_VOL *v, uint64_t lba, return DirentUpdateMetadata(v, lba, offset, &u); } -static KSTATUS DirentUpdateFirstCluster(FAT_VOL *v, uint64_t lba, +static KSTATUS DirentUpdateFirstCluster(FAT_VOL* v, uint64_t lba, uint32_t offset, uint32_t cluster) { FAT_DIRENT_UPDATE u; memset(&u, 0, sizeof(u)); @@ -801,13 +818,13 @@ static uint8_t LfnChecksum(const char name8[8], const char ext3[3]) { } // Implements the lfn Set Char operation. -static void LfnSetChar(LFN_ENTRY *e, int idx, uint16_t value) { +static void LfnSetChar(LFN_ENTRY* e, int idx, uint16_t value) { if (idx < 5) e->Name1[idx] = value; else if (idx < 11) e->Name2[idx - 5] = value; else e->Name3[idx - 11] = value; } -static void BuildLfnEntry(LFN_ENTRY *e, const char *name, size_t nameLen, +static void BuildLfnEntry(LFN_ENTRY* e, const char* name, size_t nameLen, uint32_t ordinal, uint32_t lfnCount, uint8_t checksum) { memset(e, 0, sizeof(*e)); @@ -830,21 +847,21 @@ static void BuildLfnEntry(LFN_ENTRY *e, const char *name, size_t nameLen, } // Creates legacy Broken Dot83. -static void MakeLegacyBrokenDot83(const char *src, char name8[8], char ext3[3]) { +static void MakeLegacyBrokenDot83(const char* src, char name8[8], char ext3[3]) { // This exactly models the pre-fix bug for ".doomrc": empty name + "DOO". memset(name8, ' ', 8); memset(ext3, ' ', 3); - const char *dot = NULL; - for (const char *p = src; *p; p++) if (*p == '.') dot = p; + const char* dot = NULL; + for (const char* p = src; *p; p++) if (*p == '.') dot = p; if (!dot) return; int ei = 0; - for (const char *p = dot + 1; *p && ei < 3; p++) + for (const char* p = dot + 1; *p && ei < 3; p++) ext3[ei++] = FatUpperChar(*p); } -static int ShortNameExistsRaw(FAT_VOL *v, uint32_t dirCluster, +static int ShortNameExistsRaw(FAT_VOL* v, uint32_t dirCluster, const char name8[8], const char ext3[3]) { - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 1; // fail closed; caller should pick another alias uint32_t cursor; uint64_t lba; uint32_t sectors; @@ -857,7 +874,7 @@ static int ShortNameExistsRaw(FAT_VOL *v, uint32_t dirCluster, if (!KSUCCESS(ReadSectors(v, lba, sectors, clusterBuf))) break; uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { if (dir[i].Name[0] == 0x00) { MmFreePool(clusterBuf); return 0; } if ((uint8_t)dir[i].Name[0] == 0xE5 || dir[i].Attr == ATTR_LFN) continue; @@ -873,7 +890,7 @@ static int ShortNameExistsRaw(FAT_VOL *v, uint32_t dirCluster, } // Implements the fat Alias Append Char operation. -static void FatAliasAppendChar(char *dst, int *len, int maxLen, char c) { +static void FatAliasAppendChar(char* dst, int* len, int maxLen, char c) { c = FatUpperChar(c); if (!FatIsValidShortChar(c)) return; if (*len < maxLen) dst[(*len)++] = c; @@ -941,7 +958,7 @@ static void MakeAliasAttempt(const char stem[9], const char extIn[3], memcpy(name8 + keep, suffix, suffixLen); } -static int MakeUniqueShortAliasV(FAT_VOL *v, uint32_t dirCluster, +static int MakeUniqueShortAliasV(FAT_VOL* v, uint32_t dirCluster, SYS_STRING_VIEW longName, char name8[8], char ext3[3]) { char stem[9], extCandidate[3]; @@ -968,14 +985,14 @@ static void MakeLegacyBrokenDot83V(SYS_STRING_VIEW src, char name8[8], char ext3 ext3[ei++] = FatUpperChar(src.Data[i]); } -static KSTATUS DeleteLegacyBrokenDotAliasV(FAT_VOL *v, uint32_t dirCluster, +static KSTATUS DeleteLegacyBrokenDotAliasV(FAT_VOL* v, uint32_t dirCluster, SYS_STRING_VIEW longName) { if (!SysSvIsValid(longName) || longName.Length < 2 || longName.Data[0] != '.') return NTLSTATUS_SUCCESS; char badName[8], badExt[3]; MakeLegacyBrokenDot83V(longName, badName, badExt); - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return NTLSTATUS_NO_MEMORY; uint32_t cursor; uint64_t lba; uint32_t sectors; @@ -989,7 +1006,7 @@ static KSTATUS DeleteLegacyBrokenDotAliasV(FAT_VOL *v, uint32_t dirCluster, if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { if (dir[i].Name[0] == 0x00) { MmFreePool(clusterBuf); return NTLSTATUS_SUCCESS; } if ((uint8_t)dir[i].Name[0] == 0xE5 || dir[i].Attr == ATTR_LFN) continue; @@ -1015,7 +1032,7 @@ static KSTATUS DeleteLegacyBrokenDotAliasV(FAT_VOL *v, uint32_t dirCluster, } // Implements the lfn Append operation. -static void LfnAppend(char *lfn, const LFN_ENTRY *e) { +static void LfnAppend(char* lfn, const LFN_ENTRY* e) { int seq = (e->Order & 0x3F) - 1; if (seq < 0 || seq >= (MAX_LFN / 13)) return; int pos = seq * 13; @@ -1055,7 +1072,7 @@ static int FatNameViewIsValid(SYS_STRING_VIEW name) { } // Implements the lfn Match V operation. -static int LfnMatchV(const char *lfn, SYS_STRING_VIEW want) { +static int LfnMatchV(const char* lfn, SYS_STRING_VIEW want) { if (!lfn || !SysSvIsValid(want)) return 0; size_t i = 0; while (lfn[i] && i < want.Length) { @@ -1068,7 +1085,7 @@ static int LfnMatchV(const char *lfn, SYS_STRING_VIEW want) { return lfn[i] == '\0' && i == want.Length; } -static int MatchName83V(const char *dirName, const char *dirExt, +static int MatchName83V(const char* dirName, const char* dirExt, SYS_STRING_VIEW want) { char buf[13]; int n = 0; @@ -1097,7 +1114,7 @@ static char FatPathCacheChar(char c) { return c; } -// Hashes a drive-relative path using the same case/separator rules as lookup. +// Hashes a volume-relative path using the same case/separator rules as lookup. static uint32_t FatPathCacheHash(SYS_STRING_VIEW path) { uint32_t h = 2166136261u; for (size_t i = 0; i < path.Length; i++) { @@ -1127,7 +1144,7 @@ static void FatPathCacheAddDirectoryStats(FAT_VOL* v, uint64_t clusters, uint64_ KeReleaseSpinlock(&v->PathCacheLock, flags); } -// Looks up a positive path-cache entry for a full drive-relative path. +// Looks up a positive path-cache entry for a full volume-relative path. static bool FatPathCacheLookup(FAT_VOL* v, SYS_STRING_VIEW path, uint32_t* outCluster, uint32_t* outSize, uint8_t* outAttr, uint64_t* outDirentLba, @@ -1201,9 +1218,9 @@ static void FatPathCacheInvalidate(FAT_VOL* v) { } // Returns the current filesystem cache and traversal counters. -void FatQueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) { +void FatQueryStats(IO_VOLUME_PROVIDER* fs, IO_FILESYSTEM_STATS* out) { if (!fs || !out) return; - FAT_VOL* v = (FAT_VOL*)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; if (!v) { memset(out, 0, sizeof(*out)); return; } uintptr_t flags = KeAcquireSpinlock(&v->PathCacheLock); @@ -1350,12 +1367,12 @@ static bool FatBuildReadRun(FAT_FILE* f, uint32_t logicalIndex, return true; } -static int SearchDirV(FAT_VOL *v, uint32_t dirCluster, SYS_STRING_VIEW name, - uint32_t *outCluster, uint32_t *outSize, uint8_t *outAttr, - uint64_t *outDirentLba, uint32_t *outDirentOffset) { +static int SearchDirV(FAT_VOL* v, uint32_t dirCluster, SYS_STRING_VIEW name, + uint32_t* outCluster, uint32_t* outSize, uint8_t* outAttr, + uint64_t* outDirentLba, uint32_t* outDirentOffset) { if (!FatNameViewIsValid(name)) return 0; - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn)); @@ -1375,13 +1392,13 @@ static int SearchDirV(FAT_VOL *v, uint32_t dirCluster, SYS_STRING_VIEW name, clustersRead++; uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { entriesScanned++; if (dir[i].Name[0] == 0x00) goto done; if ((uint8_t)dir[i].Name[0] == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; } - if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); hasLfn = 1; continue; } + if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY*)&dir[i]); hasLfn = 1; continue; } int match = 0; if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name); @@ -1430,10 +1447,10 @@ static int FatPathNextComponent(SYS_STRING_VIEW path, size_t* cursor, return 1; } -static KSTATUS FatLookupPathV(FAT_VOL *v, SYS_STRING_VIEW path, - uint32_t *outCluster, uint32_t *outSize, - uint8_t *outAttr, - uint64_t *outDirentLba, uint32_t *outDirentOffset) { +static KSTATUS FatLookupPathV(FAT_VOL* v, SYS_STRING_VIEW path, + uint32_t* outCluster, uint32_t* outSize, + uint8_t* outAttr, + uint64_t* outDirentLba, uint32_t* outDirentOffset) { if (!v || !outCluster || !outSize || !FatStringViewDataIsPlausible(path)) return NTLSTATUS_INVALID_PARAMETER; if (SysSvContainsByte(path, '\0')) @@ -1480,8 +1497,8 @@ static KSTATUS FatLookupPathV(FAT_VOL *v, SYS_STRING_VIEW path, return NTLSTATUS_SUCCESS; } -static KSTATUS TraverseV(FAT_VOL *v, SYS_STRING_VIEW path, - uint32_t *outCluster, uint32_t *outSize) { +static KSTATUS TraverseV(FAT_VOL* v, SYS_STRING_VIEW path, + uint32_t* outCluster, uint32_t* outSize) { return FatLookupPathV(v, path, outCluster, outSize, NULL, NULL, NULL); } @@ -1515,9 +1532,9 @@ static KSTATUS SplitPathV(SYS_STRING_VIEW path, SYS_STRING_VIEW* parent, } // Find a free DIRENT slot. -static int FindFreeDirent(FAT_VOL *v, uint32_t dirCluster, - uint64_t *outLba, uint32_t *outOffset) { - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); +static int FindFreeDirent(FAT_VOL* v, uint32_t dirCluster, + uint64_t* outLba, uint32_t* outOffset) { + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; uint32_t cursor; uint64_t lba; uint32_t sectors; @@ -1531,7 +1548,7 @@ static int FindFreeDirent(FAT_VOL *v, uint32_t dirCluster, MmFreePool(clusterBuf); return 0; } uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { uint8_t first = (uint8_t)dir[i].Name[0]; if (first == 0xE5 || first == 0x00) { @@ -1560,12 +1577,12 @@ static int FindFreeDirent(FAT_VOL *v, uint32_t dirCluster, // Find a contiguous run of free directory entries. // VFAT LFN creation requires N long-name entries to immediately precede the // final short entry, so the run must live inside one readable/writeable segment. -static int FindFreeDirentRun(FAT_VOL *v, uint32_t dirCluster, uint32_t needed, - uint64_t *outLba, uint32_t *outFirstIndex, - uint32_t *outSectors) { +static int FindFreeDirentRun(FAT_VOL* v, uint32_t dirCluster, uint32_t needed, + uint64_t* outLba, uint32_t* outFirstIndex, + uint32_t* outSectors) { if (!needed) return 0; - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; uint32_t cursor; uint64_t lba; uint32_t sectors; @@ -1581,7 +1598,7 @@ static int FindFreeDirentRun(FAT_VOL *v, uint32_t dirCluster, uint32_t needed, } uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; uint32_t runStart = 0, runLen = 0; for (uint32_t i = 0; i < entries; i++) { uint8_t first = (uint8_t)dir[i].Name[0]; @@ -1621,9 +1638,9 @@ static int FindFreeDirentRun(FAT_VOL *v, uint32_t dirCluster, uint32_t needed, } // Finds the parent directory and final component for a create or delete path. -static KSTATUS FatResolveParentForMutation(FAT_VOL *v, SYS_STRING_VIEW path, - uint32_t *parentClusterOut, - SYS_STRING_VIEW *filenameOut) { +static KSTATUS FatResolveParentForMutation(FAT_VOL* v, SYS_STRING_VIEW path, + uint32_t* parentClusterOut, + SYS_STRING_VIEW* filenameOut) { if (!v || !parentClusterOut || !filenameOut) return NTLSTATUS_INVALID_PARAMETER; @@ -1646,11 +1663,11 @@ static KSTATUS FatResolveParentForMutation(FAT_VOL *v, SYS_STRING_VIEW path, } // Creates a FAT directory entry run for a new file or directory. -static KSTATUS FatCreateDirent(FAT_VOL *v, SYS_STRING_VIEW path, +static KSTATUS FatCreateDirent(FAT_VOL* v, SYS_STRING_VIEW path, uint8_t attr, uint32_t firstCluster, uint32_t fileSize, - uint64_t *direntLbaOut, - uint32_t *direntOffsetOut) { + uint64_t* direntLbaOut, + uint32_t* direntOffsetOut) { if (!v || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; @@ -1690,25 +1707,25 @@ static KSTATUS FatCreateDirent(FAT_VOL *v, SYS_STRING_VIEW path, if (!FindFreeDirentRun(v, parentCluster, neededEntries, &clusterLba, &firstIndex, &entrySectors)) return NTLSTATUS_NO_MEMORY; - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return NTLSTATUS_NO_MEMORY; s = ReadSectors(v, clusterLba, entrySectors, clusterBuf); if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; uint32_t shortIndex = firstIndex + lfnCount; if (useLfn) { uint8_t checksum = LfnChecksum(shortName, shortExt); for (uint32_t slot = 0; slot < lfnCount; slot++) { uint32_t ordinal = lfnCount - slot; - BuildLfnEntry((LFN_ENTRY *)&dir[firstIndex + slot], filenameView.Data, + BuildLfnEntry((LFN_ENTRY*)&dir[firstIndex + slot], filenameView.Data, nameLen, ordinal, lfnCount, checksum); } } - DIRENT *d = &dir[shortIndex]; + DIRENT* d = &dir[shortIndex]; memset(d, 0, sizeof(DIRENT)); memcpy(d->Name, shortName, 8); memcpy(d->Ext, shortExt, 3); @@ -1742,9 +1759,9 @@ static KSTATUS FatCreateDirent(FAT_VOL *v, SYS_STRING_VIEW path, } // Initializes one dot entry in a new directory cluster. -static void FatInitDotEntry(FAT_VOL *v, DIRENT *d, const char *name, +static void FatInitDotEntry(FAT_VOL* v, DIRENT* d, const char* name, uint32_t firstCluster, - const FAT_ENCODED_TIME *now) { + const FAT_ENCODED_TIME* now) { memset(d, 0, sizeof(*d)); memset(d->Name, ' ', sizeof(d->Name)); memset(d->Ext, ' ', sizeof(d->Ext)); @@ -1761,18 +1778,18 @@ static void FatInitDotEntry(FAT_VOL *v, DIRENT *d, const char *name, } // Writes the initial contents of a newly allocated FAT directory cluster. -static KSTATUS FatInitializeDirectoryCluster(FAT_VOL *v, uint32_t dirCluster, +static KSTATUS FatInitializeDirectoryCluster(FAT_VOL* v, uint32_t dirCluster, uint32_t parentCluster) { if (!v || dirCluster < 2) return NTLSTATUS_INVALID_PARAMETER; - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return NTLSTATUS_NO_MEMORY; memset(clusterBuf, 0, v->BytesPerCluster); FAT_ENCODED_TIME now; FatQueryEncodedTime(&now); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; FatInitDotEntry(v, &dir[0], ".", dirCluster, &now); FatInitDotEntry(v, &dir[1], "..", FatIsFixedRoot(v, parentCluster) ? 0u : parentCluster, &now); @@ -1783,7 +1800,7 @@ static KSTATUS FatInitializeDirectoryCluster(FAT_VOL *v, uint32_t dirCluster, } // Tests whether a FAT directory entry is one of the synthetic dot entries. -static int FatIsDotEntry(const DIRENT *d) { +static int FatIsDotEntry(const DIRENT* d) { if (!d || !(d->Attr & ATTR_DIRECTORY)) return 0; if (d->Name[0] != '.') return 0; if (d->Name[1] == ' ') @@ -1792,12 +1809,12 @@ static int FatIsDotEntry(const DIRENT *d) { } // Tests whether a directory contains any real child entries. -static KSTATUS FatDirectoryIsEmpty(FAT_VOL *v, uint32_t dirCluster, - int *emptyOut) { +static KSTATUS FatDirectoryIsEmpty(FAT_VOL* v, uint32_t dirCluster, + int* emptyOut) { if (!v || !emptyOut) return NTLSTATUS_INVALID_PARAMETER; - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return NTLSTATUS_NO_MEMORY; uint32_t cursor; uint64_t lba; uint32_t sectors; @@ -1811,7 +1828,7 @@ static KSTATUS FatDirectoryIsEmpty(FAT_VOL *v, uint32_t dirCluster, if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { uint8_t first = (uint8_t)dir[i].Name[0]; if (first == 0x00) { MmFreePool(clusterBuf); *emptyOut = 1; return NTLSTATUS_SUCCESS; } @@ -1831,12 +1848,12 @@ static KSTATUS FatDirectoryIsEmpty(FAT_VOL *v, uint32_t dirCluster, } // Deletes the directory-entry run for one child name from a parent directory. -static KSTATUS FatDeleteDirentRunV(FAT_VOL *v, uint32_t parentCluster, +static KSTATUS FatDeleteDirentRunV(FAT_VOL* v, uint32_t parentCluster, SYS_STRING_VIEW name) { if (!v || !FatNameViewIsValid(name)) return NTLSTATUS_INVALID_PARAMETER; - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return NTLSTATUS_NO_MEMORY; char lfn[MAX_LFN]; @@ -1859,7 +1876,7 @@ static KSTATUS FatDeleteDirentRunV(FAT_VOL *v, uint32_t parentCluster, if (!KSUCCESS(s)) { MmFreePool(clusterBuf); return s; } uint32_t entries = (sectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; + DIRENT* dir = (DIRENT*)clusterBuf; hasLfn = 0; memset(lfn, 0, sizeof(lfn)); for (uint32_t i = 0; i < entries; i++) { @@ -1868,7 +1885,7 @@ static KSTATUS FatDeleteDirentRunV(FAT_VOL *v, uint32_t parentCluster, if (first == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; } if (dir[i].Attr == ATTR_LFN) { if (!hasLfn) lfnStart = i; - LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); + LfnAppend(lfn, (LFN_ENTRY*)&dir[i]); hasLfn = 1; continue; } @@ -1900,16 +1917,16 @@ static KSTATUS FatDeleteDirentRunV(FAT_VOL *v, uint32_t parentCluster, } // Open / Close. -KSTATUS FatOpen(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { +KSTATUS FatOpen(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) { if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - FAT_VOL *v = (FAT_VOL *)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = FatLookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; - FAT_FILE *f = (FAT_FILE *)MmAllocatePool(sizeof(FAT_FILE)); + FAT_FILE* f = (FAT_FILE*)MmAllocatePool(sizeof(FAT_FILE)); if (!f) return NTLSTATUS_NO_MEMORY; f->StartCluster = cluster; f->FileSize = size; f->Vol = v; f->DirentLba = direntLba; f->DirentOffset = direntOffset; @@ -1919,7 +1936,7 @@ KSTATUS FatOpen(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { } // Flushes FAT metadata and the backing device write cache. -KSTATUS FatFlush(IO_FILESYSTEM* fs, void* fileCtx) { +KSTATUS FatFlush(IO_VOLUME_PROVIDER* fs, void* fileCtx) { (void)fs; FAT_FILE* f = (FAT_FILE*)fileCtx; if (!f || !f->Vol) @@ -1930,9 +1947,9 @@ KSTATUS FatFlush(IO_FILESYSTEM* fs, void* fileCtx) { } // Releases the requested object. -void FatClose(IO_FILESYSTEM *fs, void *fileCtx) { +void FatClose(IO_VOLUME_PROVIDER* fs, void* fileCtx) { (void)fs; - FAT_FILE *f = (FAT_FILE *)fileCtx; + FAT_FILE* f = (FAT_FILE*)fileCtx; FlushFatCache(f->Vol); MmFreePool(fileCtx); } @@ -1942,16 +1959,16 @@ void FatClose(IO_FILESYSTEM *fs, void *fileCtx) { // grow beyond the AHCI bounce size because the device layer now splits large // sector requests for the concrete controller. -KSTATUS FatRead(IO_FILESYSTEM *fs, void *fileCtx, - uint64_t offset, void *buf, - size_t size, size_t *bytesRead) { +KSTATUS FatRead(IO_VOLUME_PROVIDER* fs, void* fileCtx, + uint64_t offset, void* buf, + size_t size, size_t* bytesRead) { (void)fs; if (!bytesRead) return NTLSTATUS_INVALID_PARAMETER; *bytesRead = 0; if (!fileCtx || !buf) return NTLSTATUS_INVALID_PARAMETER; - FAT_FILE *f = (FAT_FILE *)fileCtx; - FAT_VOL *v = f->Vol; + FAT_FILE* f = (FAT_FILE*)fileCtx; + FAT_VOL* v = f->Vol; if (offset >= f->FileSize || size == 0) return NTLSTATUS_SUCCESS; if (size > (size_t)(f->FileSize - offset)) size = (size_t)(f->FileSize - offset); size_t wanted = size; @@ -1963,11 +1980,11 @@ KSTATUS FatRead(IO_FILESYSTEM *fs, void *fileCtx, if (maxUsefulSectors > FAT_READ_RUN_MAX_SECTORS) maxUsefulSectors = FAT_READ_RUN_MAX_SECTORS; - uint8_t *runBuf = (uint8_t *)MmAllocatePool((size_t)maxUsefulSectors * 512u); + uint8_t* runBuf = (uint8_t*)MmAllocatePool((size_t)maxUsefulSectors * 512u); if (!runBuf) return NTLSTATUS_NO_MEMORY; size_t totalRead = 0; - uint8_t *out = (uint8_t *)buf; + uint8_t* out = (uint8_t*)buf; KSTATUS readStatus = NTLSTATUS_SUCCESS; uint32_t logicalCluster = (uint32_t)(offset / v->BytesPerCluster); uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); @@ -2029,15 +2046,15 @@ KSTATUS FatRead(IO_FILESYSTEM *fs, void *fileCtx, // Same strategy as read. Whole-cluster-aligned runs skip the read-modify- // write; partial head/tail clusters still require it. -KSTATUS FatWrite(IO_FILESYSTEM *fs, void *fileCtx, - uint64_t offset, const void *buf, - size_t size, size_t *bytesWritten) { +KSTATUS FatWrite(IO_VOLUME_PROVIDER* fs, void* fileCtx, + uint64_t offset, const void* buf, + size_t size, size_t* bytesWritten) { (void)fs; if (!size) { *bytesWritten = 0; return NTLSTATUS_SUCCESS; } - FAT_FILE *f = (FAT_FILE *)fileCtx; - FAT_VOL *v = f->Vol; - const uint8_t *src = (const uint8_t *)buf; + FAT_FILE* f = (FAT_FILE*)fileCtx; + FAT_VOL* v = f->Vol; + const uint8_t* src = (const uint8_t*)buf; FatFileInvalidateChainCache(f); // Ensure at least one cluster exists. @@ -2063,7 +2080,7 @@ KSTATUS FatWrite(IO_FILESYSTEM *fs, void *fileCtx, cluster = next; } - uint8_t *runBuf = (uint8_t *)MmAllocatePool(FAT_WRITE_RUN_MAX_SECTORS * 512u); + uint8_t* runBuf = (uint8_t*)MmAllocatePool(FAT_WRITE_RUN_MAX_SECTORS * 512u); if (!runBuf) { *bytesWritten = 0; return NTLSTATUS_NO_MEMORY; } size_t written = 0, remaining = size; @@ -2145,18 +2162,18 @@ KSTATUS FatWrite(IO_FILESYSTEM *fs, void *fileCtx, // Create. // Creates the requested file and fails if the name already exists. -KSTATUS FatCreate(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { +KSTATUS FatCreate(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) { if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - FAT_VOL *v = (FAT_VOL *)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; uint64_t direntLba = 0; uint32_t direntOffset = 0; KSTATUS s = FatCreateDirent(v, path, ATTR_ARCHIVE, 0, 0, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; - FAT_FILE *f = (FAT_FILE *)MmAllocatePool(sizeof(FAT_FILE)); + FAT_FILE* f = (FAT_FILE*)MmAllocatePool(sizeof(FAT_FILE)); if (!f) return NTLSTATUS_NO_MEMORY; f->StartCluster = 0; f->FileSize = 0; @@ -2169,11 +2186,11 @@ KSTATUS FatCreate(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { } // Creates a directory and initializes its dot entries. -KSTATUS FatCreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { +KSTATUS FatCreateDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) { if (!fs || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - FAT_VOL *v = (FAT_VOL *)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t parentCluster = 0; SYS_STRING_VIEW filenameView; KSTATUS s = FatResolveParentForMutation(v, path, &parentCluster, &filenameView); @@ -2204,11 +2221,11 @@ KSTATUS FatCreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { } // Removes an empty directory. -KSTATUS FatRemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { +KSTATUS FatRemoveDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) { if (!fs || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - FAT_VOL *v = (FAT_VOL *)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster = 0; uint32_t size = 0; uint8_t attr = 0; @@ -2240,10 +2257,10 @@ KSTATUS FatRemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { } // Truncate. -KSTATUS FatTruncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags) { +KSTATUS FatTruncate(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) { (void)fs; - FAT_FILE *f = (FAT_FILE *)fileCtx; - FAT_VOL *v = f->Vol; + FAT_FILE* f = (FAT_FILE*)fileCtx; + FAT_VOL* v = f->Vol; FatFileInvalidateChainCache(f); int zeroNewClusters = (fileFlags & IO_FILE_FLAG_SKIP_ZERO_EXTEND) ? 0 : 1; if ((uint32_t)newSize == f->FileSize) { @@ -2310,11 +2327,11 @@ update_size: } // Delete. -KSTATUS FatDelete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { +KSTATUS FatDelete(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) { if (!fs || !SysSvIsValid(path) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - FAT_VOL *v = (FAT_VOL *)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; KSTATUS s = FatLookupPathV(v, path, &cluster, &size, NULL, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; @@ -2337,17 +2354,8 @@ KSTATUS FatDelete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { } // Returns size. -uint64_t FatGetSize(IO_FILESYSTEM *fs, void *fileCtx) { - (void)fs; return ((FAT_FILE *)fileCtx)->FileSize; -} - -// Returns a stable volume identity for metadata and cache keys. -static uint64_t FatVolumeIdentity(FAT_VOL *v) { - if (!v) return 0; - return ((uint64_t)v->DriveIndex << 56) ^ - ((uint64_t)v->PartitionLba << 8) ^ - ((uint64_t)v->RootCluster << 1) ^ - 0x4641543332494445ULL; +uint64_t FatGetSize(IO_VOLUME_PROVIDER* fs, void* fileCtx) { + (void)fs; return ((FAT_FILE*)fileCtx)->FileSize; } // Converts FAT attributes to the public file type. @@ -2356,16 +2364,15 @@ static uint32_t FatFileTypeFromAttr(uint8_t attr) { } // Fills public metadata from one FAT directory entry. -static void FatFillStatFromDirent(FAT_VOL *v, const DIRENT *d, +static void FatFillStatFromDirent(FAT_VOL* v, const DIRENT* d, uint64_t direntLba, uint32_t direntOffset, - SYS_FILE_STAT *out) { + SYS_FILE_STAT* out) { memset(out, 0, sizeof(*out)); out->Size = sizeof(*out); out->Type = FatFileTypeFromAttr(d->Attr); out->Attributes = d->Attr; out->LinkCount = 1; out->FileSize = d->FileSize; - out->VolumeId = FatVolumeIdentity(v); uint32_t firstCluster = FatDirentFirstCluster(v, d); out->FileId = direntLba ^ @@ -2379,24 +2386,23 @@ static void FatFillStatFromDirent(FAT_VOL *v, const DIRENT *d, } // Fills public metadata for the filesystem root. -static void FatFillRootStat(FAT_VOL *v, SYS_FILE_STAT *out) { +static void FatFillRootStat(FAT_VOL* v, SYS_FILE_STAT* out) { memset(out, 0, sizeof(*out)); out->Size = sizeof(*out); out->Type = SYS_FILE_TYPE_DIRECTORY; out->Attributes = SYS_FILE_ATTR_DIRECTORY; out->LinkCount = 1; out->FileSize = 0; - out->VolumeId = FatVolumeIdentity(v); out->FileId = ((uint64_t)v->RootCluster << 1) ^ 0x524f4f54ULL; } // Queries metadata for an open FAT file. -KSTATUS FatQueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out) { +KSTATUS FatQueryInfo(IO_VOLUME_PROVIDER* fs, void* fileCtx, SYS_FILE_STAT* out) { (void)fs; if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER; - FAT_FILE *f = (FAT_FILE *)fileCtx; - FAT_VOL *v = f->Vol; + FAT_FILE* f = (FAT_FILE*)fileCtx; + FAT_VOL* v = f->Vol; if (!v) return NTLSTATUS_INVALID_PARAMETER; if (f->DirentLba == 0) { @@ -2409,19 +2415,18 @@ KSTATUS FatQueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out) { out->Attributes = SYS_FILE_ATTR_ARCHIVE; out->LinkCount = 1; out->FileSize = f->FileSize; - out->VolumeId = FatVolumeIdentity(v); - out->FileId = ((uint64_t)f->StartCluster << 1); + out->FileId = ((uint64_t)f->StartCluster << 1); } return NTLSTATUS_SUCCESS; } - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return NTLSTATUS_NO_MEMORY; KSTATUS s = ReadSectors(v, f->DirentLba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } - DIRENT *d = (DIRENT *)(sec + f->DirentOffset); + DIRENT* d = (DIRENT*)(sec + f->DirentOffset); if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { MmFreePool(sec); return NTLSTATUS_NOT_FOUND; @@ -2435,10 +2440,10 @@ KSTATUS FatQueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out) { } // Queries metadata for a FAT path. -KSTATUS FatStat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out) { +KSTATUS FatStat(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) { if (!fs || !out || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER; - FAT_VOL *v = (FAT_VOL *)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; if (!v) return NTLSTATUS_INVALID_PARAMETER; uint32_t cluster = 0; @@ -2455,13 +2460,13 @@ KSTATUS FatStat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out) { return NTLSTATUS_SUCCESS; } - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return NTLSTATUS_NO_MEMORY; s = ReadSectors(v, direntLba, 1, sec); if (!KSUCCESS(s)) { MmFreePool(sec); return s; } - DIRENT *d = (DIRENT *)(sec + direntOffset); + DIRENT* d = (DIRENT*)(sec + direntOffset); if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { MmFreePool(sec); return NTLSTATUS_NOT_FOUND; @@ -2475,7 +2480,7 @@ KSTATUS FatStat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out) { } // Mixes FAT directory metadata into a per-file content generation. -static uint64_t FatDirentGeneration(FAT_VOL* v, const DIRENT *d) { +static uint64_t FatDirentGeneration(FAT_VOL* v, const DIRENT* d) { if (!d) return 0; @@ -2493,13 +2498,13 @@ static uint64_t FatDirentGeneration(FAT_VOL* v, const DIRENT *d) { } // Returns a stable FAT file identity for cross-open image-section caches. -KSTATUS FatQueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out) { +KSTATUS FatQueryIdentity(IO_VOLUME_PROVIDER* fs, void* fileCtx, IO_FILE_IDENTITY* out) { (void)fs; if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER; - FAT_FILE *f = (FAT_FILE *)fileCtx; - FAT_VOL *v = f->Vol; + FAT_FILE* f = (FAT_FILE*)fileCtx; + FAT_VOL* v = f->Vol; if (!v) return NTLSTATUS_INVALID_PARAMETER; @@ -2509,7 +2514,7 @@ KSTATUS FatQueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out ((uint64_t)fileSize << 17); if (f->DirentLba != 0) { - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return NTLSTATUS_NO_MEMORY; @@ -2519,7 +2524,7 @@ KSTATUS FatQueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out return s; } - DIRENT *d = (DIRENT *)(sec + f->DirentOffset); + DIRENT* d = (DIRENT*)(sec + f->DirentOffset); if ((uint8_t)d->Name[0] == 0x00 || (uint8_t)d->Name[0] == 0xE5) { MmFreePool(sec); return NTLSTATUS_NOT_FOUND; @@ -2531,7 +2536,6 @@ KSTATUS FatQueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out MmFreePool(sec); } - out->VolumeId = FatVolumeIdentity(v); out->FileId = f->DirentLba ^ ((uint64_t)f->DirentOffset << 32) ^ ((uint64_t)startCluster << 1); @@ -2546,7 +2550,7 @@ KSTATUS FatQueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out // boundaries. typedef struct { - FAT_VOL *Vol; + FAT_VOL* Vol; uint32_t DirCluster; uint32_t Cursor; uint32_t LoadedCursor; @@ -2555,15 +2559,15 @@ typedef struct { uint32_t EntryIndex; char LfnBuf[MAX_LFN]; int HasLfn; - uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir + uint8_t* ClusterBuf; // allocated once in OpenDir, freed in CloseDir } FAT_DIR_CTX; // Opens dir. -KSTATUS FatOpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx) { +KSTATUS FatOpenDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW dirPath, void** dirCtx) { if (!fs || !dirCtx || !SysSvIsValid(dirPath)) return NTLSTATUS_INVALID_PARAMETER; - FAT_VOL *v = (FAT_VOL *)fs->DriverCtx; + FAT_VOL* v = (FAT_VOL*)fs->Context; uint32_t cluster; if (dirPath.Length == 0) { cluster = v->RootCluster; @@ -2577,10 +2581,10 @@ KSTATUS FatOpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx) { if (!FatDirectoryFirstSegment(v, cluster, &cursor, &lba, §ors)) return NTLSTATUS_NOT_FOUND; - FAT_DIR_CTX *ctx = (FAT_DIR_CTX *)MmAllocatePool(sizeof(FAT_DIR_CTX)); + FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)MmAllocatePool(sizeof(FAT_DIR_CTX)); if (!ctx) return NTLSTATUS_NO_MEMORY; - ctx->ClusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + ctx->ClusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!ctx->ClusterBuf) { MmFreePool(ctx); return NTLSTATUS_NO_MEMORY; } ctx->Vol = v; @@ -2597,10 +2601,10 @@ KSTATUS FatOpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx) { } // Returns dir. -KSTATUS FatReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) { +KSTATUS FatReadDir(IO_VOLUME_PROVIDER* fs, void* dirCtx, KDIR_ENTRY* out) { (void)fs; - FAT_DIR_CTX *ctx = (FAT_DIR_CTX *)dirCtx; - FAT_VOL *v = ctx->Vol; + FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)dirCtx; + FAT_VOL* v = ctx->Vol; while (true) { if (ctx->Cursor != ctx->LoadedCursor) { @@ -2610,13 +2614,13 @@ KSTATUS FatReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) { } uint32_t epc = (ctx->LoadedSectors * v->BytesPerSector) / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)ctx->ClusterBuf; + DIRENT* dir = (DIRENT*)ctx->ClusterBuf; while (ctx->EntryIndex < epc) { - DIRENT *e = &dir[ctx->EntryIndex++]; + DIRENT* e = &dir[ctx->EntryIndex++]; if (e->Name[0] == 0x00) return NTLSTATUS_NO_MORE_FILES; if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } - if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY *)e); ctx->HasLfn = 1; continue; } + if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY*)e); ctx->HasLfn = 1; continue; } if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } memset(out, 0, sizeof(*out)); @@ -2657,34 +2661,14 @@ KSTATUS FatReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) { } // Releases dir. -void FatCloseDir(IO_FILESYSTEM *fs, void *dirCtx) { +void FatCloseDir(IO_VOLUME_PROVIDER* fs, void* dirCtx) { (void)fs; - FAT_DIR_CTX *ctx = (FAT_DIR_CTX *)dirCtx; + FAT_DIR_CTX* ctx = (FAT_DIR_CTX*)dirCtx; MmFreePool(ctx->ClusterBuf); MmFreePool(ctx); } -// Volume scan (MBR + GPT + superfloppy). -static const EFI_GUID GUID_EFI_SYSTEM = { 0xC12A7328, 0xF81F, 0x11D2, {0xBA,0x4B,0x00,0xA0,0xC9,0x3E,0xC9,0x3B} }; -static const EFI_GUID GUID_MS_BASIC_DATA = { 0xEBD0A0A2, 0xB9E5, 0x4433, {0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7} }; -static const EFI_GUID GUID_LINUX_BASIC_DATA = { 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} }; - -#define FAT_MAX_PARTITIONS_PER_DISK 32 - -typedef struct FAT_PARTITION_CANDIDATE { - uint64_t StartLba; - uint64_t SectorCount; -} FAT_PARTITION_CANDIDATE; - -// Returns le32. -static uint32_t ReadLe32(const void* p) { - const uint8_t* b = (const uint8_t*)p; - return (uint32_t)b[0] - | ((uint32_t)b[1] << 8) - | ((uint32_t)b[2] << 16) - | ((uint32_t)b[3] << 24); -} - +// FAT BPB validation. typedef struct FAT_BPB_LAYOUT { FAT_VARIANT Variant; uint32_t TotalSectors; @@ -2741,159 +2725,11 @@ static int FatComputeBpbLayout(const BPB* bpb, FAT_BPB_LAYOUT* out) { return 1; } -// Checks whether fat Vbr. -static int IsFatVbr(FAT_VOL *v, uint64_t lba) { - uint8_t sector[512]; - if (!KSUCCESS(ReadSectors(v, lba, 1, sector))) return 0; - if (sector[510] != 0x55 || sector[511] != 0xAA) return 0; - - FAT_BPB_LAYOUT layout; - if (!FatComputeBpbLayout((BPB*)sector, &layout)) return 0; - if (layout.Variant == FatVariant12) { - FatDebugPrint("FAT: rejecting FAT12 volume at lba=%llu clusters=%u\n", - (unsigned long long)lba, layout.TotalClusters); - return 0; - } - return layout.Variant == FatVariant16 || layout.Variant == FatVariant32; -} - -static int FatCandidateExists(FAT_PARTITION_CANDIDATE* out, - uint32_t count, - uint64_t startLba) { - for (uint32_t i = 0; i < count; i++) { - if (out[i].StartLba == startLba) - return 1; - } - return 0; -} - -static void FatAddCandidate(FAT_VOL* probe, - FAT_PARTITION_CANDIDATE* out, - uint32_t* count, - uint32_t maxCount, - uint64_t startLba, - uint64_t sectorCount) { - if (!out || !count || *count >= maxCount) return; - if (FatCandidateExists(out, *count, startLba)) return; - - uint64_t diskSectors = IoDiskSectorCount(probe->DriveIndex); - if (diskSectors) { - if (startLba >= diskSectors) return; - if (sectorCount == 0 || startLba + sectorCount > diskSectors) - sectorCount = diskSectors - startLba; - } - - if (!IsFatVbr(probe, startLba)) return; - - out[*count].StartLba = startLba; - out[*count].SectorCount = sectorCount; - (*count)++; -} - -static uint32_t FatEnumerateDiskVolumes(uint8_t diskIndex, - FAT_PARTITION_CANDIDATE* out, - uint32_t maxCount, - bool* outMediaReadable) { - if (outMediaReadable) - *outMediaReadable = false; - if (!out || maxCount == 0) return 0; - - FAT_VOL probe; - memset(&probe, 0, sizeof(probe)); - probe.DriveIndex = diskIndex; - - uint32_t count = 0; - uint8_t mbr[512]; - if (!KSUCCESS(ReadSectors(&probe, 0, 1, mbr))) - return 0; - if (outMediaReadable) - *outMediaReadable = true; - - // A disk image may be a raw FAT volume with no partition table. - FatAddCandidate(&probe, out, &count, maxCount, 0, IoDiskSectorCount(diskIndex)); - - if (mbr[510] != 0x55 || mbr[511] != 0xAA) - return count; - - int isGpt = 0; - for (int i = 0; i < 4; i++) { - if (mbr[0x1BE + i * 16 + 4] == 0xEE) { - isGpt = 1; - break; - } - } - - if (isGpt) { - uint8_t gptSector[512]; - if (!KSUCCESS(ReadSectors(&probe, 1, 1, gptSector))) - return count; - - GPT_HEADER* gpt = (GPT_HEADER*)gptSector; - if (memcmp(gpt->Signature, "EFI PART", 8) != 0) - return count; - - uint64_t entryLBA = gpt->PartitionEntryLBA; - uint32_t numEntries = gpt->NumberOfPartitionEntries; - uint32_t entrySize = gpt->SizeOfPartitionEntry; - if (entrySize < sizeof(GPT_ENTRY) || entrySize > 512) - return count; - - uint32_t entriesPerSector = 512 / entrySize; - if (!entriesPerSector) - return count; - - uint8_t entrySector[512]; - uint64_t loadedSector = UINT64_MAX; - static const EFI_GUID emptyGuid = {0}; - - for (uint32_t i = 0; i < numEntries && count < maxCount; i++) { - uint64_t sectorLba = entryLBA + i / entriesPerSector; - if (sectorLba != loadedSector) { - if (!KSUCCESS(ReadSectors(&probe, sectorLba, 1, entrySector))) - return count; - loadedSector = sectorLba; - } - - GPT_ENTRY* entry = (GPT_ENTRY*)(entrySector + (i % entriesPerSector) * entrySize); - if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) - continue; - - if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) != 0 && - memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) != 0 && - memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA, sizeof(EFI_GUID)) != 0) - continue; - - uint64_t start = entry->StartingLBA; - uint64_t end = entry->EndingLBA; - if (end < start) continue; - FatAddCandidate(&probe, out, &count, maxCount, start, end - start + 1); - } - - return count; - } - - for (int i = 0; i < 4 && count < maxCount; i++) { - uint8_t* entry = mbr + 0x1BE + i * 16; - uint8_t type = entry[4]; - uint32_t start = ReadLe32(entry + 8); - uint32_t size = ReadLe32(entry + 12); - if (!start || !size) continue; - - // FAT12/16/32 CHS/LBA plus EFI System Partition. The VBR probe is the - // authority, so accepting 0xEF here lets FAT ESPs mount too. - if (type == 0x04 || type == 0x06 || type == 0x0E || - type == 0x0B || type == 0x0C || type == 0xEF) - FatAddCandidate(&probe, out, &count, maxCount, start, size); - } - - return count; -} - // Native async read/write // Metadata work (FAT traversal/allocation and partial-cluster read-modify // preparation) still runs synchronously in the caller/completion context, but // file data transfers are submitted as disk IRPs and completion is chained from -// the driver's IRP completion callback instead of the generic VFS worker. +// the driver's IRP completion callback instead of the generic I/O worker. typedef struct FAT_ASYNC_RW { LIST_ENTRY Link; @@ -3099,10 +2935,10 @@ static void FatAsyncReadSubmitNext(FAT_ASYNC_RW* ctx) { } } -KSTATUS FatReadAsync(IO_FILESYSTEM *fs, void *fileCtx, - uint64_t offset, void *buf, size_t size, +KSTATUS FatReadAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx, + uint64_t offset, void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, - void *completeContext) { + void* completeContext) { (void)fs; if (!complete) return NTLSTATUS_INVALID_PARAMETER; if (!fileCtx || (!buf && size)) { @@ -3270,10 +3106,10 @@ static void FatAsyncWriteSubmitNext(FAT_ASYNC_RW* ctx) { } } -KSTATUS FatWriteAsync(IO_FILESYSTEM *fs, void *fileCtx, - uint64_t offset, const void *buf, size_t size, +KSTATUS FatWriteAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx, + uint64_t offset, const void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, - void *completeContext) { + void* completeContext) { (void)fs; if (!complete) return NTLSTATUS_INVALID_PARAMETER; if (!fileCtx || (!buf && size)) { @@ -3350,20 +3186,32 @@ KSTATUS FatWriteAsync(IO_FILESYSTEM *fs, void *fileCtx, return NTLSTATUS_PENDING; } -// DriverEntry. -#define FAT_MAX_VOLUMES 8 - -static FAT_VOL gVols[FAT_MAX_VOLUMES]; -static IO_FILESYSTEM gFs[FAT_MAX_VOLUMES]; -static bool gVolumeMounted[FAT_MAX_VOLUMES]; - +// Driver entry and dynamic partition-backed mount instances. +static LIST_ENTRY gFatMounts; static uint32_t gMountedCount; -static bool gDiskScanned[FAT_MAX_VOLUMES]; -static MUTEX gFatMountMutex; +static MUTEX gFatMountMutex; static SPINLOCK gFatMountMutexInitLock = SPINLOCK_INIT; -static int gFatMountMutexReady; +static int gFatMountMutexReady; -// Initializes the mount-table mutex without allocating kernel objects. +// Marks only this FAT mount's cache state unusable when its backing volume dies. +void FatVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason) { + (void)reason; + if (!provider || !provider->Context) + return; + FatCacheInvalidateVolume((FAT_VOL*)provider->Context); +} + +// Releases one dynamically allocated FAT mount after every volume reference has drained. +void FatDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider) { + if (!provider || !provider->Context) + return; + FAT_VOL* volume = (FAT_VOL*)provider->Context; + FatCacheInvalidateVolume(volume); + memset(volume, 0, sizeof(*volume)); + MmFreePool(volume); +} + +// Initializes the mount-list mutex without allocating kernel objects. static void FatMountEnsureMutex(void) { if (gFatMountMutexReady) return; @@ -3385,250 +3233,192 @@ static void FatMountEnsureMutex(void) { KeReleaseSpinlock(&gFatMountMutexInitLock, flags); } -// Returns free Volume Index. -static int FatFindFreeVolumeIndex(void) { - for (uint8_t i = 0; i < FAT_MAX_VOLUMES; i++) { - if (!gVolumeMounted[i] && !IoFilesystemPresent(i)) - return i; +// Returns one active FAT mount for an exact partition extent while the mount mutex is held. +static FAT_VOL* FatFindMountLocked(const IO_PARTITION_INFO* partition) { + if (!partition) + return (FAT_VOL*)0; + for (LIST_ENTRY* entry = gFatMounts.Fwd; entry != &gFatMounts; entry = entry->Fwd) { + FAT_VOL* volume = CONTAINING_RECORD(entry, FAT_VOL, MountLink); + if (volume->DriveIndex == partition->DiskIndex && + volume->PartitionLba == partition->StartLba) { + return volume; + } } - return -1; + return (FAT_VOL*)0; } -// Implements the try Mount Disk operation. -static uint32_t FatTryMountDisk(uint8_t disk) { - if (disk >= FAT_MAX_VOLUMES) - return 0; +// Probes one I/O-core partition extent and mounts it when its VBR describes supported FAT16/FAT32. +static bool FatTryMountPartition(const IO_PARTITION_INFO* partition) { + if (!partition || partition->SectorCount == 0 || !IoDiskDevicePresent(partition->DiskIndex)) + return false; FatMountEnsureMutex(); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gFatMountMutex, KE_WAIT_INFINITE))) - return 0; + return false; - if (gDiskScanned[disk]) { + if (FatFindMountLocked(partition)) { KeReleaseMutex(&gFatMountMutex); - return 0; + return false; } - if (!IoDiskDevicePresent(disk)) { + FAT_VOL* v = (FAT_VOL*)MmAllocatePool(sizeof(FAT_VOL)); + if (!v) { KeReleaseMutex(&gFatMountMutex); - return 0; + return false; } + memset(v, 0, sizeof(*v)); + LIST_INIT(&v->MountLink); + v->DriveIndex = partition->DiskIndex; + v->PartitionLba = partition->StartLba; + v->PartitionSize = partition->SectorCount; - uint32_t mountedHere = 0; - FAT_PARTITION_CANDIDATE parts[FAT_MAX_PARTITIONS_PER_DISK]; - bool mediaReadable = false; - uint32_t partCount = FatEnumerateDiskVolumes(disk, - parts, - FAT_MAX_PARTITIONS_PER_DISK, - &mediaReadable); - if (!mediaReadable) { - DbgPrint("FAT: disk %u: media probe deferred; sector 0 unreadable\n", disk); + uint8_t sector[512]; + if (!KSUCCESS(ReadSectors(v, v->PartitionLba, 1, sector))) { + MmFreePool(v); KeReleaseMutex(&gFatMountMutex); - return 0; + return false; } - // Only remember a completed probe. A transient hotplug/readiness failure - // must not permanently suppress a later mount attempt for the same disk - // index after a device is unplugged and replugged. - gDiskScanned[disk] = true; - - if (!partCount) { - DbgPrint("FAT: disk %u: no FAT volumes\n", disk); - return 0; + BPB* bpb = (BPB*)sector; + FAT_BPB_LAYOUT layout; + if (!FatComputeBpbLayout(bpb, &layout) || + (layout.Variant != FatVariant16 && layout.Variant != FatVariant32)) { + MmFreePool(v); + KeReleaseMutex(&gFatMountMutex); + return false; } - for (uint32_t part = 0; part < partCount; part++) { - int freeVolume = FatFindFreeVolumeIndex(); - if (freeVolume < 0) - break; - uint8_t volumeIndex = (uint8_t)freeVolume; - FAT_VOL* v = &gVols[volumeIndex]; - memset(v, 0, sizeof(*v)); - v->DriveIndex = disk; - v->PartitionLba = parts[part].StartLba; - v->PartitionSize = parts[part].SectorCount; + v->Variant = layout.Variant; + v->BytesPerSector = bpb->BytesPerSector; + v->SectorsPerCluster = bpb->SectorsPerCluster; + v->BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster; + v->ReservedSectors = bpb->ReservedSectors; + v->NumFats = bpb->NumFats; + v->FatSize = layout.FatSize; + v->RootEntryCount = bpb->RootEntryCount; + v->RootDirSectors = layout.RootDirSectors; + v->RootDirStart = v->PartitionLba + bpb->ReservedSectors + + (uint64_t)bpb->NumFats * layout.FatSize; + v->RootCluster = v->Variant == FatVariant16 ? FAT_FIXED_ROOT_CLUSTER : bpb->RootCluster; + v->DataStart = v->RootDirStart + layout.RootDirSectors; + v->NextFreeHint = 2; + v->TotalClusters = layout.TotalClusters; + if (v->PartitionSize > layout.TotalSectors) + v->PartitionSize = layout.TotalSectors; - uint8_t sector[512]; - if (!KSUCCESS(ReadSectors(v, v->PartitionLba, 1, sector))) { - DbgPrint("FAT: disk %u part %u: failed to read VBR\n", disk, part); - continue; - } + FatDebugPrint("FAT: disk=%u lba=%llu %s layout fat=%u root-sectors=%u data=%u clusters=%u\n", + v->DriveIndex, (unsigned long long)v->PartitionLba, + FatVariantName(v->Variant), v->FatSize, + v->RootDirSectors, layout.DataSectors, v->TotalClusters); - BPB *bpb = (BPB *)sector; - FAT_BPB_LAYOUT layout; - if (!FatComputeBpbLayout(bpb, &layout) || - (layout.Variant != FatVariant16 && layout.Variant != FatVariant32)) { - DbgPrint("FAT: disk %u part %u: unsupported FAT variant\n", disk, part); - continue; - } + IO_VOLUME_PROVIDER provider; + FatInitProvider(&provider, v); + IO_VOLUME_SOURCE volParams; + volParams.Type = IoVolumeSourcePartition; + volParams.DiskIndex = v->DriveIndex; + memset(volParams.Reserved, 0, sizeof(volParams.Reserved)); + volParams.StartLba = v->PartitionLba; + volParams.SectorCount = v->PartitionSize; - v->Variant = layout.Variant; - v->BytesPerSector = bpb->BytesPerSector; - v->SectorsPerCluster = bpb->SectorsPerCluster; - v->BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster; - v->ReservedSectors = bpb->ReservedSectors; - v->NumFats = bpb->NumFats; - v->FatSize = layout.FatSize; - v->RootEntryCount = bpb->RootEntryCount; - v->RootDirSectors = layout.RootDirSectors; - v->RootDirStart = v->PartitionLba + bpb->ReservedSectors + - (uint64_t)bpb->NumFats * layout.FatSize; - if (v->Variant == FatVariant16) - v->RootCluster = FAT_FIXED_ROOT_CLUSTER; - else - v->RootCluster = bpb->RootCluster; - v->DataStart = v->RootDirStart + layout.RootDirSectors; - v->NextFreeHint = 2; - v->TotalClusters = layout.TotalClusters; - if (v->PartitionSize == 0 || v->PartitionSize > layout.TotalSectors) - v->PartitionSize = layout.TotalSectors; - - FatDebugPrint("FAT: disk %u part %u %s layout fat=%u root-sectors=%u data=%u clusters=%u\n", - disk, part, FatVariantName(v->Variant), v->FatSize, - v->RootDirSectors, layout.DataSectors, v->TotalClusters); - - FatInitVtable(&gFs[volumeIndex], v); - - IO_VOLUME_PARAMS volParams; - volParams.DiskIndex = v->DriveIndex; - volParams.PartitionLba = v->PartitionLba; - volParams.PartitionSize = v->PartitionSize; - - KSTATUS status = IoRegisterFilesystem(volumeIndex, &gFs[volumeIndex], &volParams); - if (KSUCCESS(status)) { - gVolumeMounted[volumeIndex] = true; - gMountedCount++; - mountedHere++; - DbgPrint("FAT: disk %u part %u %s mounted as drive %u (lba=%llu clusters=%u).\n", - disk, part, FatVariantName(v->Variant), volumeIndex, - (unsigned long long)v->PartitionLba, v->TotalClusters); + IO_VOLUME* volume = (IO_VOLUME*)0; + KSTATUS status = IoCreateVolume(&provider, &volParams, &volume); + if (!KSUCCESS(status)) { + if (volume) { + (void)IoRemoveVolume(volume, IoVolumeUnmounted); + IoReleaseVolume(volume); } else { - DbgPrint("FAT: disk %u part %u mount as drive %u failed status=%d.\n", - disk, part, volumeIndex, status); + MmFreePool(v); } + DbgPrint("FAT: disk=%u lba=%llu mount failed status=0x%x\n", + partition->DiskIndex, (unsigned long long)partition->StartLba, status); + KeReleaseMutex(&gFatMountMutex); + return false; } - if (partCount && !mountedHere && gMountedCount >= FAT_MAX_VOLUMES) { - DbgPrint("FAT: disk %u: volume table full\n", disk); - } + // Retain the IoCreateVolume caller reference while this provider remains on the active mount list. + v->Volume = volume; + ListInsertTail(&gFatMounts, &v->MountLink); + gMountedCount++; + uint32_t instanceId = IoGetVolumeInstanceId(volume); + uint32_t mountedCount = gMountedCount; + DbgPrint("FAT: disk=%u lba=%llu %s mounted volume=%u; total=%u\n", + v->DriveIndex, (unsigned long long)v->PartitionLba, + FatVariantName(v->Variant), instanceId, mountedCount); KeReleaseMutex(&gFatMountMutex); - return mountedHere; + return true; } -// Implements the disk Arrival operation. -static void FatDiskArrival(uint8_t driveIndex, DEVICE_OBJECT* device, void* context) { - (void)device; - (void)context; - uint32_t before = gMountedCount; - uint32_t mounted = FatTryMountDisk(driveIndex); - if (mounted) { - DbgPrint("FAT: hotplug scan mounted %u new volume(s); total=%u\n", - mounted, gMountedCount); - } else if (gMountedCount != before) { - DbgPrint("FAT: hotplug scan total=%u\n", gMountedCount); - } -} - -// Implements the unmount Disk operation. -static void FatUnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) { - uint32_t removed = 0; +// Unmounts one exact FAT provider instance when its backing partition disappears. +static void FatUnmountPartition(const IO_PARTITION_INFO* partition, IO_PARTITION_CHANGE change) { + if (!partition) + return; FatMountEnsureMutex(); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gFatMountMutex, KE_WAIT_INFINITE))) return; - for (uint8_t i = 0; i < FAT_MAX_VOLUMES; i++) { - if (!gVolumeMounted[i]) - continue; - if (gVols[i].DriveIndex != diskIndex) - continue; - - // Surprise removal means the backing device may already be gone; do not - // attempt writeback here. Drop cached FAT sectors instead. - FatCacheInvalidate(); - (void)IoUnregisterFilesystem(i); - memset(&gVols[i], 0, sizeof(gVols[i])); - memset(&gFs[i], 0, sizeof(gFs[i])); - gVolumeMounted[i] = false; - if (gMountedCount) - gMountedCount--; - removed++; - } - - if (diskIndex < FAT_MAX_VOLUMES) - gDiskScanned[diskIndex] = false; - - if (removed) { - DbgPrint("FAT: disk %u %s; unmounted %u volume(s); total=%u\n", - diskIndex, - change == IoDeviceSurpriseRemoved ? "surprise removed" : "removed", - removed, - gMountedCount); + FAT_VOL* v = FatFindMountLocked(partition); + if (!v) { + KeReleaseMutex(&gFatMountMutex); + return; } + IO_VOLUME* volume = v->Volume; + uint32_t instanceId = IoGetVolumeInstanceId(volume); + ListRemove(&v->MountLink); + v->Volume = (IO_VOLUME*)0; + if (gMountedCount) + gMountedCount--; + uint32_t mountedCount = gMountedCount; KeReleaseMutex(&gFatMountMutex); + + (void)IoRemoveVolume(volume, IoVolumeBackingRemoved); + IoReleaseVolume(volume); // Drop the active mount-list reference. + DbgPrint("FAT: disk=%u lba=%llu %s; unmounted volume=%u; total=%u\n", + partition->DiskIndex, (unsigned long long)partition->StartLba, + change == IoPartitionSurpriseRemoved ? "surprise removed" : "removed", + instanceId, mountedCount); } -static void FatDiskChange(uint8_t driveIndex, - DEVICE_OBJECT* device, - IO_DEVICE_CHANGE change, - void* context) { - (void)device; +// Receives immutable partition extents from the I/O core; FAT never parses partition tables. +static void FatPartitionChange(const IO_PARTITION_INFO* partition, + IO_PARTITION_CHANGE change, + void* context) { (void)context; - switch (change) { - case IoDeviceArrived: - FatDiskArrival(driveIndex, device, context); + case IoPartitionArrived: + (void)FatTryMountPartition(partition); break; - case IoDeviceSurpriseRemoved: - case IoDeviceRemoved: - FatUnmountDisk(driveIndex, change); + case IoPartitionSurpriseRemoved: + case IoPartitionRemoved: + FatUnmountPartition(partition, change); break; - case IoDeviceQueryRemove: default: break; } } -// Implements the driver Entry operation. +// Initializes FAT and subscribes to the authoritative kernel partition stream. KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("FAT: Init\n"); - if (!ddk) return NTLSTATUS_INVALID_PARAMETER; + if (!ddk) + return NTLSTATUS_INVALID_PARAMETER; + (void)init; FatCacheInvalidate(); - (void)init; + LIST_INIT(&gFatMounts); gMountedCount = 0; - memset(gDiskScanned, 0, sizeof(gDiskScanned)); - FatMountEnsureMutex(); - KSTATUS enumStatus = IoEnumerateDiskDevices(FatDiskArrival, (void*)0); - if (!KSUCCESS(enumStatus)) { - DbgPrint("FAT: initial disk enumeration failed st=0x%x\n", enumStatus); - } - - KSTATUS cbStatus = IoRegisterDiskChangeCallback(FatDiskChange, (void*)0); - bool callbackRegistered = KSUCCESS(cbStatus); - if (!callbackRegistered) { - DbgPrint("FAT: disk-change callback registration failed st=0x%x\n", cbStatus); - } else { - // Catch disks that appeared while the initial scan was in progress but - // before the callback was registered. Already-scanned disks are skipped. - KSTATUS secondEnumStatus = IoEnumerateDiskDevices(FatDiskArrival, (void*)0); - if (!KSUCCESS(secondEnumStatus)) { - DbgPrint("FAT: post-callback disk enumeration failed st=0x%x\n", secondEnumStatus); - } - } - - if (!gMountedCount && !callbackRegistered) { - DbgPrint("FAT: no mountable FAT volumes found and disk-change callback unavailable.\n"); - return cbStatus; - } - - if (!gMountedCount) { - DbgPrint("FAT: no mountable FAT volumes found; driver resident for disk changes.\n"); + KSTATUS status = IoRegisterPartitionChangeCallback(FatPartitionChange, NULL); + if (!KSUCCESS(status)) { + DbgPrint("FAT: partition callback registration failed status=0x%x\n", status); + return status; } + if (!gMountedCount) + DbgPrint("FAT: no mountable FAT partitions found; driver resident for partition changes.\n"); DbgPrint("FAT: Init complete; mounted %u volume(s).\n", gMountedCount); return NTLSTATUS_SUCCESS; } diff --git a/code/drivers/fs/fat/fat.h b/code/drivers/fs/fat/fat.h index f55d6cb..25e40ef 100644 --- a/code/drivers/fs/fat/fat.h +++ b/code/drivers/fs/fat/fat.h @@ -21,25 +21,27 @@ typedef struct FAT_VOL_S FAT_VOL; -KSTATUS FatOpen(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx); -KSTATUS FatFlush(IO_FILESYSTEM *fs, void *fileCtx); -void FatClose(IO_FILESYSTEM *fs, void *fileCtx); -KSTATUS FatRead(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, void *buf, size_t size, size_t *bytesRead); -KSTATUS FatWrite(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, size_t *bytesWritten); -KSTATUS FatReadAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext); -KSTATUS FatWriteAsync(IO_FILESYSTEM *fs, void *fileCtx, uint64_t offset, const void *buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void *completeContext); -KSTATUS FatCreate(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx); -KSTATUS FatCreateDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path); -KSTATUS FatRemoveDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW path); -KSTATUS FatDelete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path); -KSTATUS FatTruncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize, uint32_t fileFlags); -uint64_t FatGetSize(IO_FILESYSTEM *fs, void *fileCtx); -KSTATUS FatQueryInfo(IO_FILESYSTEM *fs, void *fileCtx, SYS_FILE_STAT *out); -KSTATUS FatStat(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, SYS_FILE_STAT *out); -KSTATUS FatQueryIdentity(IO_FILESYSTEM *fs, void *fileCtx, IO_FILE_IDENTITY *out); -KSTATUS FatOpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx); -KSTATUS FatReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out); -void FatCloseDir(IO_FILESYSTEM *fs, void *dirCtx); -void FatQueryStats(IO_FILESYSTEM *fs, IO_FILESYSTEM_STATS *out); +KSTATUS FatOpen(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx); +KSTATUS FatFlush(IO_VOLUME_PROVIDER* fs, void* fileCtx); +void FatClose(IO_VOLUME_PROVIDER* fs, void* fileCtx); +KSTATUS FatRead(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead); +KSTATUS FatWrite(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten); +KSTATUS FatReadAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext); +KSTATUS FatWriteAsync(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext); +KSTATUS FatCreate(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx); +KSTATUS FatCreateDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path); +KSTATUS FatRemoveDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path); +KSTATUS FatDelete(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path); +KSTATUS FatTruncate(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags); +uint64_t FatGetSize(IO_VOLUME_PROVIDER* fs, void* fileCtx); +KSTATUS FatQueryInfo(IO_VOLUME_PROVIDER* fs, void* fileCtx, SYS_FILE_STAT* out); +KSTATUS FatStat(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out); +KSTATUS FatQueryIdentity(IO_VOLUME_PROVIDER* fs, void* fileCtx, IO_FILE_IDENTITY* out); +KSTATUS FatOpenDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW dirPath, void** dirCtx); +KSTATUS FatReadDir(IO_VOLUME_PROVIDER* fs, void* dirCtx, KDIR_ENTRY* out); +void FatCloseDir(IO_VOLUME_PROVIDER* fs, void* dirCtx); +void FatQueryStats(IO_VOLUME_PROVIDER* fs, IO_FILESYSTEM_STATS* out); -void FatInitVtable(IO_FILESYSTEM* fs, FAT_VOL* vol); +void FatInitProvider(IO_VOLUME_PROVIDER* provider, FAT_VOL* vol); +void FatVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason); +void FatDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider); diff --git a/code/drivers/fs/fat/fat_disk.h b/code/drivers/fs/fat/fat_disk.h index 7d4730d..87bedc4 100644 --- a/code/drivers/fs/fat/fat_disk.h +++ b/code/drivers/fs/fat/fat_disk.h @@ -63,39 +63,6 @@ typedef struct { uint16_t Name3[2]; } LFN_ENTRY; -typedef struct { - uint32_t Data1; - uint16_t Data2; - uint16_t Data3; - uint8_t Data4[8]; -} EFI_GUID; - -typedef struct { - char Signature[8]; - uint32_t Revision; - uint32_t HeaderSize; - uint32_t HeaderCRC32; - uint32_t Reserved; - uint64_t MyLBA; - uint64_t AlternateLBA; - uint64_t FirstUsableLBA; - uint64_t LastUsableLBA; - EFI_GUID DiskGUID; - uint64_t PartitionEntryLBA; - uint32_t NumberOfPartitionEntries; - uint32_t SizeOfPartitionEntry; - uint32_t PartitionEntryArrayCRC32; -} GPT_HEADER; - -typedef struct { - EFI_GUID PartitionTypeGUID; - EFI_GUID UniquePartitionGUID; - uint64_t StartingLBA; - uint64_t EndingLBA; - uint64_t Attributes; - uint16_t PartitionName[36]; -} GPT_ENTRY; - #pragma pack(pop) #define ATTR_VOLUME_ID 0x08 diff --git a/code/drivers/fs/fat/fat_mount.c b/code/drivers/fs/fat/fat_mount.c index d7ba9b1..b6fc0f8 100644 --- a/code/drivers/fs/fat/fat_mount.c +++ b/code/drivers/fs/fat/fat_mount.c @@ -2,8 +2,8 @@ #include "fat.h" -// Initializes vtable. -void FatInitVtable(IO_FILESYSTEM* fs, FAT_VOL* vol) { +// Initializes the FAT volume provider dispatch. +void FatInitProvider(IO_VOLUME_PROVIDER* fs, FAT_VOL* vol) { memset(fs, 0, sizeof(*fs)); fs->Open = FatOpen; fs->Read = FatRead; @@ -21,9 +21,11 @@ void FatInitVtable(IO_FILESYSTEM* fs, FAT_VOL* vol) { fs->QueryIdentity = FatQueryIdentity; fs->Flush = FatFlush; fs->Close = FatClose; - fs->DriverCtx = vol; + fs->Context = vol; fs->OpenDir = FatOpenDir; fs->ReadDir = FatReadDir; fs->CloseDir = FatCloseDir; fs->QueryStats = FatQueryStats; + fs->VolumeDead = FatVolumeDead; + fs->Destroy = FatDestroyVolumeProvider; } diff --git a/code/drivers/fs/ntfs/ntfs.c b/code/drivers/fs/ntfs/ntfs.c index 7c4c5c9..64bd36a 100644 --- a/code/drivers/fs/ntfs/ntfs.c +++ b/code/drivers/fs/ntfs/ntfs.c @@ -1,6 +1,6 @@ // Native read-only NTFS filesystem driver for NTLite. // Portions are based on ntfs-efi by Mark Harmstone (2023), GPLv2-or-later. -// The port removes UEFI protocol/header dependencies and targets NTLite's IO_FILESYSTEM ABI. +// The port removes UEFI protocol/header dependencies and targets NTLite's IO_VOLUME_PROVIDER ABI. #include #include @@ -13,9 +13,8 @@ #include #include #include +#include -#define NTFS_MAX_VOLUMES 8u -#define NTFS_MAX_PARTITIONS_PER_DISK 32u #define NTFS_SECTOR_SIZE 512u #define NTFS_FS_NAME "NTFS " #define NTFS_UPCASE_TABLE_CHARS 0x10000u @@ -203,48 +202,6 @@ typedef struct NTFS_FILE_NAME_ATTR { uint16_t FileName[1]; } NTFS_FILE_NAME_ATTR; -typedef struct NTFS_GPT_GUID { - uint32_t Data1; - uint16_t Data2; - uint16_t Data3; - uint8_t Data4[8]; -} NTFS_GPT_GUID; - -typedef struct NTFS_GPT_HEADER { - char Signature[8]; - uint32_t Revision; - uint32_t HeaderSize; - uint32_t HeaderCRC32; - uint32_t Reserved; - uint64_t MyLBA; - uint64_t AlternateLBA; - uint64_t FirstUsableLBA; - uint64_t LastUsableLBA; - NTFS_GPT_GUID DiskGUID; - uint64_t PartitionEntryLBA; - uint32_t NumberOfPartitionEntries; - uint32_t SizeOfPartitionEntry; - uint32_t PartitionEntryArrayCRC32; -} NTFS_GPT_HEADER; - -typedef struct NTFS_GPT_ENTRY { - NTFS_GPT_GUID PartitionTypeGUID; - NTFS_GPT_GUID UniquePartitionGUID; - uint64_t StartingLBA; - uint64_t EndingLBA; - uint64_t Attributes; - uint16_t PartitionName[36]; -} NTFS_GPT_ENTRY; - -typedef struct NTFS_MBR_PARTITION { - uint8_t BootIndicator; - uint8_t StartingChs[3]; - uint8_t OsType; - uint8_t EndingChs[3]; - uint32_t StartingLba; - uint32_t SizeInLba; -} NTFS_MBR_PARTITION; - #pragma pack(pop) typedef struct NTFS_RUN { @@ -257,7 +214,6 @@ typedef struct NTFS_RUN { typedef struct NTFS_VOLUME { uint8_t DriveIndex; - uint8_t VolumeIndex; uint64_t PartitionLba; uint64_t PartitionSize; uint64_t SerialNumber; @@ -269,6 +225,8 @@ typedef struct NTFS_VOLUME { uint64_t FileRecordSize; LIST_ENTRY MftRuns; uint16_t* Upcase; + IO_VOLUME* Volume; // Active mount-list reference. + LIST_ENTRY MountLink; } NTFS_VOLUME; typedef struct NTFS_FILE { @@ -306,11 +264,6 @@ typedef struct NTFS_DIR_CTX { uint32_t Position; } NTFS_DIR_CTX; -typedef struct NTFS_PARTITION_CANDIDATE { - uint64_t StartLba; - uint64_t SectorCount; -} NTFS_PARTITION_CANDIDATE; - typedef struct NTFS_ATTR_VIEW { const NTFS_ATTRIBUTE_RECORD_HEADER* Attr; const uint8_t* ResidentData; @@ -332,13 +285,11 @@ typedef struct NTFS_COLLECT_CTX { bool HideNtfsMetadata; } NTFS_COLLECT_CTX; -static NTFS_VOLUME gVolumes[NTFS_MAX_VOLUMES]; -static IO_FILESYSTEM gFilesystems[NTFS_MAX_VOLUMES]; -static bool gVolumeMounted[NTFS_MAX_VOLUMES]; -static bool gVolumeBusy[NTFS_MAX_VOLUMES]; -static bool gDiskScanned[NTFS_MAX_VOLUMES]; -static SPINLOCK gNtfsMountLock = SPINLOCK_INIT; +static LIST_ENTRY gNtfsMounts; static uint32_t gMountedCount; +static MUTEX gNtfsMountMutex; +static SPINLOCK gNtfsMountMutexInitLock = SPINLOCK_INIT; +static int gNtfsMountMutexReady; static uint64_t NtfsAlignUp(uint64_t value, uint64_t alignment) { if (alignment == 0) return value; @@ -937,7 +888,6 @@ static void NtfsFillStat(NTFS_FILE* file, SYS_FILE_STAT* out) { out->Attributes = NtfsSysAttrsFromNtfs(file->Attributes, file->IsDirectory); out->LinkCount = 1; out->FileSize = file->Size; - out->VolumeId = file->Vol->SerialNumber; out->FileId = file->Inode; NtfsWinTimeToSystemTime(file->StandardInfo.CreationTime, &out->CreationTime); NtfsWinTimeToSystemTime(file->StandardInfo.LastAccessTime, &out->LastAccessTime); @@ -1139,9 +1089,9 @@ static bool NtfsCollectEntryCallback(const NTFS_FILE_NAME_ATTR* fn, uint64_t ino return true; } -static KSTATUS NtfsOpen(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) { +static KSTATUS NtfsOpen(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) { if (!fs || !fileCtx || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER; - NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx; + NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->Context; if (!vol) return NTLSTATUS_INVALID_PARAMETER; uint64_t inode; @@ -1156,7 +1106,7 @@ static KSTATUS NtfsOpen(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) return NTLSTATUS_SUCCESS; } -static void NtfsClose(IO_FILESYSTEM* fs, void* fileCtx) { +static void NtfsClose(IO_VOLUME_PROVIDER* fs, void* fileCtx) { (void)fs; NTFS_FILE* file = (NTFS_FILE*)fileCtx; if (!file) return; @@ -1164,46 +1114,46 @@ static void NtfsClose(IO_FILESYSTEM* fs, void* fileCtx) { MmFreePool(file); } -static KSTATUS NtfsRead(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) { +static KSTATUS NtfsRead(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead) { (void)fs; return NtfsReadFileData((NTFS_FILE*)fileCtx, offset, buf, size, bytesRead); } -static KSTATUS NtfsWrite(IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) { +static KSTATUS NtfsWrite(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten) { (void)fs; (void)fileCtx; (void)offset; (void)buf; (void)size; if (bytesWritten) *bytesWritten = 0; return NTLSTATUS_NOT_SUPPORTED; } -static KSTATUS NtfsCreate(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx) { +static KSTATUS NtfsCreate(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, void** fileCtx) { (void)fs; (void)path; (void)fileCtx; return NTLSTATUS_NOT_SUPPORTED; } -static KSTATUS NtfsDelete(IO_FILESYSTEM* fs, SYS_STRING_VIEW path) { +static KSTATUS NtfsDelete(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path) { (void)fs; (void)path; return NTLSTATUS_NOT_SUPPORTED; } -static KSTATUS NtfsTruncate(IO_FILESYSTEM* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) { +static KSTATUS NtfsTruncate(IO_VOLUME_PROVIDER* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags) { (void)fs; (void)fileCtx; (void)newSize; (void)fileFlags; return NTLSTATUS_NOT_SUPPORTED; } -static uint64_t NtfsGetSize(IO_FILESYSTEM* fs, void* fileCtx) { +static uint64_t NtfsGetSize(IO_VOLUME_PROVIDER* fs, void* fileCtx) { (void)fs; NTFS_FILE* file = (NTFS_FILE*)fileCtx; return file ? file->Size : 0; } -static KSTATUS NtfsQueryInfo(IO_FILESYSTEM* fs, void* fileCtx, SYS_FILE_STAT* out) { +static KSTATUS NtfsQueryInfo(IO_VOLUME_PROVIDER* fs, void* fileCtx, SYS_FILE_STAT* out) { (void)fs; if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER; NtfsFillStat((NTFS_FILE*)fileCtx, out); return NTLSTATUS_SUCCESS; } -static KSTATUS NtfsStat(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) { +static KSTATUS NtfsStat(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out) { if (!fs || !out || !SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER; void* ctx = NULL; KSTATUS s = NtfsOpen(fs, path, &ctx); @@ -1213,27 +1163,27 @@ static KSTATUS NtfsStat(IO_FILESYSTEM* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* return s; } -static KSTATUS NtfsQueryIdentity(IO_FILESYSTEM* fs, void* fileCtx, IO_FILE_IDENTITY* out) { +static KSTATUS NtfsQueryIdentity(IO_VOLUME_PROVIDER* fs, void* fileCtx, IO_FILE_IDENTITY* out) { (void)fs; if (!fileCtx || !out) return NTLSTATUS_INVALID_PARAMETER; NTFS_FILE* file = (NTFS_FILE*)fileCtx; - out->VolumeId = file->Vol->SerialNumber; out->FileId = file->Inode; out->Generation = (uint64_t)file->StandardInfo.LastWriteTime ^ ((uint64_t)file->StandardInfo.ChangeTime << 1u); out->Size = file->Size; return NTLSTATUS_SUCCESS; } -static KSTATUS NtfsOpenDir(IO_FILESYSTEM* fs, SYS_STRING_VIEW dirPath, void** dirCtx) { +static KSTATUS NtfsOpenDir(IO_VOLUME_PROVIDER* fs, SYS_STRING_VIEW dirPath, void** dirCtx) { if (!fs || !dirCtx || !SysSvIsValid(dirPath)) return NTLSTATUS_INVALID_PARAMETER; - NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->DriverCtx; + NTFS_VOLUME* vol = (NTFS_VOLUME*)fs->Context; if (!vol) return NTLSTATUS_INVALID_PARAMETER; uint64_t inode; KSTATUS s = NtfsLookupPath(vol, dirPath, &inode); if (!KSUCCESS(s)) { - NtfsTrace("NTFS: opendir: lookup failed drive=%u pathLen=%llu st=0x%x\n", - vol->VolumeIndex, (unsigned long long)dirPath.Length, s); + NtfsTrace("NTFS: opendir: lookup failed disk=%u lba=%llu pathLen=%llu st=0x%x\n", + vol->DriveIndex, (unsigned long long)vol->PartitionLba, + (unsigned long long)dirPath.Length, s); return s; } @@ -1295,7 +1245,7 @@ static KSTATUS NtfsOpenDir(IO_FILESYSTEM* fs, SYS_STRING_VIEW dirPath, void** di return NTLSTATUS_SUCCESS; } -static KSTATUS NtfsReadDir(IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out) { +static KSTATUS NtfsReadDir(IO_VOLUME_PROVIDER* fs, void* dirCtx, KDIR_ENTRY* out) { (void)fs; if (!dirCtx || !out) return NTLSTATUS_INVALID_PARAMETER; NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx; @@ -1311,7 +1261,7 @@ static KSTATUS NtfsReadDir(IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out) { return NTLSTATUS_SUCCESS; } -static void NtfsCloseDir(IO_FILESYSTEM* fs, void* dirCtx) { +static void NtfsCloseDir(IO_VOLUME_PROVIDER* fs, void* dirCtx) { (void)fs; NTFS_DIR_CTX* ctx = (NTFS_DIR_CTX*)dirCtx; if (!ctx) return; @@ -1319,12 +1269,15 @@ static void NtfsCloseDir(IO_FILESYSTEM* fs, void* dirCtx) { MmFreePool(ctx); } -static void NtfsQueryStats(IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out) { +static void NtfsQueryStats(IO_VOLUME_PROVIDER* fs, IO_FILESYSTEM_STATS* out) { (void)fs; if (out) memset(out, 0, sizeof(*out)); } -static void NtfsInitVtable(IO_FILESYSTEM* fs, NTFS_VOLUME* vol) { +static void NtfsVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason); +static void NtfsDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider); + +static void NtfsInitProvider(IO_VOLUME_PROVIDER* fs, NTFS_VOLUME* vol) { memset(fs, 0, sizeof(*fs)); fs->Open = NtfsOpen; fs->Read = NtfsRead; @@ -1338,15 +1291,12 @@ static void NtfsInitVtable(IO_FILESYSTEM* fs, NTFS_VOLUME* vol) { fs->QueryIdentity = NtfsQueryIdentity; fs->Close = NtfsClose; fs->QueryStats = NtfsQueryStats; - fs->DriverCtx = vol; + fs->Context = vol; fs->OpenDir = NtfsOpenDir; fs->ReadDir = NtfsReadDir; fs->CloseDir = NtfsCloseDir; -} - -static bool NtfsGuidIsZero(const NTFS_GPT_GUID* guid) { - static const NTFS_GPT_GUID zeroGuid = {0}; - return memcmp(guid, &zeroGuid, sizeof(*guid)) == 0; + fs->VolumeDead = NtfsVolumeDead; + fs->Destroy = NtfsDestroyVolumeProvider; } static bool NtfsIsValidBootSector(const NTFS_BOOT_SECTOR* bs) { @@ -1425,6 +1375,22 @@ static void NtfsCleanupVolume(NTFS_VOLUME* vol) { } } +// Provider death blocks new I/O in the I/O core; NTFS state is retained for safe close teardown. +static void NtfsVolumeDead(IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason) { + (void)provider; + (void)reason; +} + +// Releases one dynamically allocated NTFS mount after every volume reference has drained. +static void NtfsDestroyVolumeProvider(IO_VOLUME_PROVIDER* provider) { + if (!provider || !provider->Context) + return; + NTFS_VOLUME* volume = (NTFS_VOLUME*)provider->Context; + NtfsCleanupVolume(volume); + memset(volume, 0, sizeof(*volume)); + MmFreePool(volume); +} + static void NtfsTryLoadUpcase(NTFS_VOLUME* vol) { NTFS_FILE upcase; if (!KSUCCESS(NtfsLoadFile(vol, NTFS_UPCASE_INODE, &upcase))) return; @@ -1443,6 +1409,7 @@ static KSTATUS NtfsProbeAndPrepareVolume(NTFS_VOLUME* vol, uint8_t disk, uint64_ vol->PartitionSize = sectors; vol->BytesPerSector = NTFS_SECTOR_SIZE; NtfsInitRunList(&vol->MftRuns); + LIST_INIT(&vol->MountLink); uint8_t sector[NTFS_SECTOR_SIZE]; memset(sector, 0, sizeof(sector)); @@ -1472,252 +1439,182 @@ static KSTATUS NtfsProbeAndPrepareVolume(NTFS_VOLUME* vol, uint8_t disk, uint64_ return NTLSTATUS_SUCCESS; } -static uint32_t NtfsEnumerateDiskPartitions(uint8_t disk, NTFS_PARTITION_CANDIDATE* parts, uint32_t maxParts, bool* mediaReadable) { - if (mediaReadable) *mediaReadable = false; - if (!parts || maxParts == 0) return 0; - uint8_t sector[NTFS_SECTOR_SIZE]; - NTFS_VOLUME raw; - memset(&raw, 0, sizeof(raw)); - raw.DriveIndex = disk; - raw.PartitionLba = 0; - raw.PartitionSize = IoDiskSectorCount(disk); - raw.BytesPerSector = NTFS_SECTOR_SIZE; - NtfsInitRunList(&raw.MftRuns); +// Initializes the dynamic NTFS mount-list mutex without allocating kernel objects. +static void NtfsMountEnsureMutex(void) { + if (gNtfsMountMutexReady) + return; - if (!KSUCCESS(NtfsReadSectors(&raw, 0, 1, sector))) return 0; - if (mediaReadable) *mediaReadable = true; - - uint32_t count = 0; - NTFS_BOOT_SECTOR* vbr = (NTFS_BOOT_SECTOR*)sector; - if (NtfsIsValidBootSector(vbr)) { - parts[count].StartLba = 0; - parts[count].SectorCount = vbr->TotalSectors; - count++; - if (count >= maxParts) return count; + uintptr_t flags = KeAcquireSpinlock(&gNtfsMountMutexInitLock); + if (!gNtfsMountMutexReady) { + memset(&gNtfsMountMutex, 0, sizeof(gNtfsMountMutex)); + ObInitializeObjectHeader(&gNtfsMountMutex.Header, ObjectTypeMutex); + gNtfsMountMutex.Lock = (SPINLOCK)SPINLOCK_INIT; + gNtfsMountMutex.Owner = (THREAD*)0; + LIST_INIT(&gNtfsMountMutex.WaitList); + LIST_INIT(&gNtfsMountMutex.OwnerLink); + gNtfsMountMutex.DonationClass = KeSchedulingNormal; + gNtfsMountMutex.DonationPriority = PriorityLow; + gNtfsMountMutex.HasDonation = 0; + gNtfsMountMutex.Abandoned = 0; + gNtfsMountMutexReady = 1; } - - if (sector[510] != 0x55u || sector[511] != 0xaau) return count; - - NTFS_MBR_PARTITION* mbr = (NTFS_MBR_PARTITION*)(sector + 446u); - bool protectiveMbr = false; - for (uint32_t i = 0; i < 4u; i++) if (mbr[i].OsType == 0xeeu) protectiveMbr = true; - - if (protectiveMbr) { - uint8_t gptSector[NTFS_SECTOR_SIZE]; - if (!KSUCCESS(NtfsReadSectors(&raw, 1, 1, gptSector))) return count; - NTFS_GPT_HEADER* hdr = (NTFS_GPT_HEADER*)gptSector; - if (memcmp(hdr->Signature, "EFI PART", 8) != 0 || hdr->SizeOfPartitionEntry < sizeof(NTFS_GPT_ENTRY)) return count; - uint32_t entries = hdr->NumberOfPartitionEntries; - if (entries > 256u) entries = 256u; - uint32_t entrySize = hdr->SizeOfPartitionEntry; - uint8_t entrySector[NTFS_SECTOR_SIZE]; - for (uint32_t i = 0; i < entries && count < maxParts; i++) { - uint64_t byteOff = (uint64_t)i * entrySize; - uint64_t lba = hdr->PartitionEntryLBA + byteOff / NTFS_SECTOR_SIZE; - uint32_t inSector = (uint32_t)(byteOff % NTFS_SECTOR_SIZE); - if (inSector + sizeof(NTFS_GPT_ENTRY) > NTFS_SECTOR_SIZE) continue; - if (!KSUCCESS(NtfsReadSectors(&raw, lba, 1, entrySector))) break; - NTFS_GPT_ENTRY* e = (NTFS_GPT_ENTRY*)(entrySector + inSector); - if (NtfsGuidIsZero(&e->PartitionTypeGUID)) continue; - if (e->EndingLBA < e->StartingLBA) continue; - parts[count].StartLba = e->StartingLBA; - parts[count].SectorCount = e->EndingLBA - e->StartingLBA + 1ull; - count++; - } - return count; - } - - for (uint32_t i = 0; i < 4u && count < maxParts; i++) { - if (mbr[i].OsType == 0 || mbr[i].SizeInLba == 0) continue; - parts[count].StartLba = mbr[i].StartingLba; - parts[count].SectorCount = mbr[i].SizeInLba; - count++; - } - return count; + KeReleaseSpinlock(&gNtfsMountMutexInitLock, flags); } -static int NtfsReserveFreeVolumeIndex(void) { - uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); - for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) { - if (!gVolumeMounted[i] && !gVolumeBusy[i] && !IoFilesystemPresent(i)) { - gVolumeBusy[i] = true; - KeReleaseSpinlock(&gNtfsMountLock, flags); - return (int)i; +// Returns one active NTFS mount for an exact partition extent while the mount mutex is held. +static NTFS_VOLUME* NtfsFindMountLocked(const IO_PARTITION_INFO* partition) { + if (!partition) + return (NTFS_VOLUME*)0; + for (LIST_ENTRY* entry = gNtfsMounts.Fwd; entry != &gNtfsMounts; entry = entry->Fwd) { + NTFS_VOLUME* volume = CONTAINING_RECORD(entry, NTFS_VOLUME, MountLink); + if (volume->DriveIndex == partition->DiskIndex && + volume->PartitionLba == partition->StartLba) { + return volume; } } - KeReleaseSpinlock(&gNtfsMountLock, flags); - return -1; + return (NTFS_VOLUME*)0; } -static void NtfsReleaseReservedVolumeIndex(uint8_t volumeIndex) { - if (volumeIndex >= NTFS_MAX_VOLUMES) return; - uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); - gVolumeBusy[volumeIndex] = false; - KeReleaseSpinlock(&gNtfsMountLock, flags); -} +// Probes one I/O-core partition extent and mounts it only when its VBR is valid NTFS. +static bool NtfsTryMountPartition(const IO_PARTITION_INFO* partition) { + if (!partition || partition->SectorCount == 0 || !IoDiskDevicePresent(partition->DiskIndex)) + return false; -static bool NtfsBeginDiskScan(uint8_t disk) { - if (disk >= NTFS_MAX_VOLUMES) return false; - uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); - if (gDiskScanned[disk]) { - KeReleaseSpinlock(&gNtfsMountLock, flags); + NtfsMountEnsureMutex(); + if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gNtfsMountMutex, KE_WAIT_INFINITE))) + return false; + + if (NtfsFindMountLocked(partition)) { + KeReleaseMutex(&gNtfsMountMutex); return false; } - gDiskScanned[disk] = true; - KeReleaseSpinlock(&gNtfsMountLock, flags); + + NTFS_VOLUME* mount = (NTFS_VOLUME*)MmAllocatePool(sizeof(NTFS_VOLUME)); + if (!mount) { + KeReleaseMutex(&gNtfsMountMutex); + return false; + } + + KSTATUS status = NtfsProbeAndPrepareVolume(mount, + partition->DiskIndex, + partition->StartLba, + partition->SectorCount); + if (!KSUCCESS(status)) { + NtfsCleanupVolume(mount); + MmFreePool(mount); + KeReleaseMutex(&gNtfsMountMutex); + return false; + } + + IO_VOLUME_PROVIDER provider; + NtfsInitProvider(&provider, mount); + IO_VOLUME_SOURCE params; + params.Type = IoVolumeSourcePartition; + params.DiskIndex = partition->DiskIndex; + memset(params.Reserved, 0, sizeof(params.Reserved)); + params.StartLba = partition->StartLba; + params.SectorCount = mount->PartitionSize; + + IO_VOLUME* volume = (IO_VOLUME*)0; + status = IoCreateVolume(&provider, ¶ms, &volume); + if (!KSUCCESS(status)) { + if (volume) { + (void)IoRemoveVolume(volume, IoVolumeUnmounted); + IoReleaseVolume(volume); + } else { + NtfsCleanupVolume(mount); + MmFreePool(mount); + } + DbgPrint("NTFS: disk=%u lba=%llu mount failed status=0x%x\n", + partition->DiskIndex, (unsigned long long)partition->StartLba, status); + KeReleaseMutex(&gNtfsMountMutex); + return false; + } + + // Retain the IoCreateVolume caller reference while this provider remains on the active mount list. + mount->Volume = volume; + ListInsertTail(&gNtfsMounts, &mount->MountLink); + gMountedCount++; + uint32_t mountedCount = gMountedCount; + uint32_t instanceId = IoGetVolumeInstanceId(volume); + uint64_t recordSize = mount->FileRecordSize; + + DbgPrint("NTFS: disk=%u lba=%llu mounted volume=%u (mft_record=%llu); total=%u\n", + partition->DiskIndex, (unsigned long long)partition->StartLba, + instanceId, (unsigned long long)recordSize, mountedCount); + KeReleaseMutex(&gNtfsMountMutex); return true; } -static void NtfsEndDiskScan(uint8_t disk, bool completed) { - if (disk >= NTFS_MAX_VOLUMES) return; - if (completed) return; - uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); - gDiskScanned[disk] = false; - KeReleaseSpinlock(&gNtfsMountLock, flags); +// Unmounts one exact dynamic NTFS provider instance when its backing partition disappears. +static void NtfsUnmountPartition(const IO_PARTITION_INFO* partition, IO_PARTITION_CHANGE change) { + if (!partition) + return; + + NtfsMountEnsureMutex(); + if (!KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(&gNtfsMountMutex, KE_WAIT_INFINITE))) + return; + + NTFS_VOLUME* mount = NtfsFindMountLocked(partition); + if (!mount) { + KeReleaseMutex(&gNtfsMountMutex); + return; + } + + IO_VOLUME* volume = mount->Volume; + uint32_t instanceId = IoGetVolumeInstanceId(volume); + ListRemove(&mount->MountLink); + mount->Volume = (IO_VOLUME*)0; + if (gMountedCount) + gMountedCount--; + uint32_t mountedCount = gMountedCount; + KeReleaseMutex(&gNtfsMountMutex); + + (void)IoRemoveVolume(volume, IoVolumeBackingRemoved); + IoReleaseVolume(volume); // Drop the active mount-list reference. + DbgPrint("NTFS: disk=%u lba=%llu %s; unmounted volume=%u; total=%u\n", + partition->DiskIndex, (unsigned long long)partition->StartLba, + change == IoPartitionSurpriseRemoved ? "surprise removed" : "removed", + instanceId, mountedCount); } -static uint32_t NtfsTryMountDisk(uint8_t disk) { - if (disk >= NTFS_MAX_VOLUMES) return 0; - if (!IoDiskDevicePresent(disk)) return 0; - if (!NtfsBeginDiskScan(disk)) return 0; - - bool scanCompleted = false; - bool mediaReadable = false; - NTFS_PARTITION_CANDIDATE parts[NTFS_MAX_PARTITIONS_PER_DISK]; - uint32_t partCount = NtfsEnumerateDiskPartitions(disk, parts, NTFS_MAX_PARTITIONS_PER_DISK, &mediaReadable); - if (!mediaReadable) { - DbgPrint("NTFS: disk %u: media probe deferred; sector 0 unreadable\n", disk); - NtfsEndDiskScan(disk, false); - return 0; - } - scanCompleted = true; - - uint32_t mounted = 0; - for (uint32_t p = 0; p < partCount; p++) { - int freeVolume = NtfsReserveFreeVolumeIndex(); - if (freeVolume < 0) break; - uint8_t volumeIndex = (uint8_t)freeVolume; - KSTATUS s = NtfsProbeAndPrepareVolume(&gVolumes[volumeIndex], disk, parts[p].StartLba, parts[p].SectorCount); - if (!KSUCCESS(s)) { - NtfsCleanupVolume(&gVolumes[volumeIndex]); - memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex])); - NtfsReleaseReservedVolumeIndex(volumeIndex); - continue; - } - gVolumes[volumeIndex].VolumeIndex = volumeIndex; - - NtfsInitVtable(&gFilesystems[volumeIndex], &gVolumes[volumeIndex]); - IO_VOLUME_PARAMS params; - params.DiskIndex = disk; - params.PartitionLba = parts[p].StartLba; - params.PartitionSize = gVolumes[volumeIndex].PartitionSize; - s = IoRegisterFilesystem(volumeIndex, &gFilesystems[volumeIndex], ¶ms); - if (KSUCCESS(s)) { - uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); - gVolumeMounted[volumeIndex] = true; - gVolumeBusy[volumeIndex] = false; - gMountedCount++; - KeReleaseSpinlock(&gNtfsMountLock, flags); - mounted++; - DbgPrint("NTFS: disk %u part %u mounted as drive %u (lba=%llu mft_record=%llu).\n", - disk, p, volumeIndex, - (unsigned long long)parts[p].StartLba, - (unsigned long long)gVolumes[volumeIndex].FileRecordSize); - } else { - NtfsCleanupVolume(&gVolumes[volumeIndex]); - memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex])); - memset(&gFilesystems[volumeIndex], 0, sizeof(gFilesystems[volumeIndex])); - NtfsReleaseReservedVolumeIndex(volumeIndex); - DbgPrint("NTFS: disk %u part %u mount failed status=0x%x.\n", disk, p, s); - } - } - NtfsEndDiskScan(disk, scanCompleted); - if (partCount && !mounted && gMountedCount >= NTFS_MAX_VOLUMES) - DbgPrint("NTFS: disk %u: volume table full\n", disk); - return mounted; -} - -static void NtfsUnmountDisk(uint8_t diskIndex, IO_DEVICE_CHANGE change) { - uint32_t removed = 0; - for (uint8_t i = 0; i < NTFS_MAX_VOLUMES; i++) { - if (!gVolumeMounted[i] || gVolumes[i].DriveIndex != diskIndex) continue; - (void)IoUnregisterFilesystem(i); - NtfsCleanupVolume(&gVolumes[i]); - memset(&gVolumes[i], 0, sizeof(gVolumes[i])); - memset(&gFilesystems[i], 0, sizeof(gFilesystems[i])); - uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); - gVolumeMounted[i] = false; - gVolumeBusy[i] = false; - if (gMountedCount) gMountedCount--; - KeReleaseSpinlock(&gNtfsMountLock, flags); - removed++; - } - if (diskIndex < NTFS_MAX_VOLUMES) { - uintptr_t flags = KeAcquireSpinlock(&gNtfsMountLock); - gDiskScanned[diskIndex] = false; - KeReleaseSpinlock(&gNtfsMountLock, flags); - } - if (removed) { - DbgPrint("NTFS: disk %u %s; unmounted %u volume(s); total=%u\n", - diskIndex, - change == IoDeviceSurpriseRemoved ? "surprise removed" : "removed", - removed, - gMountedCount); - } -} - -static void NtfsDiskArrival(uint8_t driveIndex, DEVICE_OBJECT* device, void* context) { - (void)device; - (void)context; - uint32_t mounted = NtfsTryMountDisk(driveIndex); - if (mounted) DbgPrint("NTFS: hotplug scan mounted %u new volume(s); total=%u\n", mounted, gMountedCount); -} - -static void NtfsDiskChange(uint8_t driveIndex, DEVICE_OBJECT* device, IO_DEVICE_CHANGE change, void* context) { - (void)device; +// Receives immutable partition extents from the I/O core; NTFS never parses partition tables. +static void NtfsPartitionChange(const IO_PARTITION_INFO* partition, + IO_PARTITION_CHANGE change, + void* context) { (void)context; switch (change) { - case IoDeviceArrived: - NtfsDiskArrival(driveIndex, device, context); + case IoPartitionArrived: + (void)NtfsTryMountPartition(partition); break; - case IoDeviceSurpriseRemoved: - case IoDeviceRemoved: - NtfsUnmountDisk(driveIndex, change); + case IoPartitionSurpriseRemoved: + case IoPartitionRemoved: + NtfsUnmountPartition(partition, change); break; - case IoDeviceQueryRemove: default: break; } } +// Initializes NTFS and subscribes to the authoritative kernel partition stream. KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { DbgPrint("NTFS: Init\n"); - if (!ddk) return NTLSTATUS_INVALID_PARAMETER; + if (!ddk) + return NTLSTATUS_INVALID_PARAMETER; (void)init; - memset(gVolumes, 0, sizeof(gVolumes)); - memset(gFilesystems, 0, sizeof(gFilesystems)); - memset(gVolumeMounted, 0, sizeof(gVolumeMounted)); - memset(gVolumeBusy, 0, sizeof(gVolumeBusy)); - memset(gDiskScanned, 0, sizeof(gDiskScanned)); + + LIST_INIT(&gNtfsMounts); gMountedCount = 0; + NtfsMountEnsureMutex(); - KSTATUS cbStatus = IoRegisterDiskChangeCallback(NtfsDiskChange, (void*)0); - bool callbackRegistered = KSUCCESS(cbStatus); - if (!callbackRegistered) - DbgPrint("NTFS: disk-change callback registration failed st=0x%x\n", cbStatus); - - KSTATUS enumStatus = IoEnumerateDiskDevices(NtfsDiskArrival, (void*)0); - if (!KSUCCESS(enumStatus)) - DbgPrint("NTFS: initial disk enumeration failed st=0x%x\n", enumStatus); - - if (!gMountedCount && !callbackRegistered) { - DbgPrint("NTFS: no mountable NTFS volumes found and disk-change callback unavailable.\n"); - return cbStatus; + KSTATUS status = IoRegisterPartitionChangeCallback(NtfsPartitionChange, NULL); + if (!KSUCCESS(status)) { + DbgPrint("NTFS: partition callback registration failed status=0x%x\n", status); + return status; } if (!gMountedCount) - DbgPrint("NTFS: no mountable NTFS volumes found; driver resident for disk changes.\n"); - + DbgPrint("NTFS: no mountable NTFS partitions found; driver resident for partition changes.\n"); DbgPrint("NTFS: Init complete; mounted %u volume(s).\n", gMountedCount); return NTLSTATUS_SUCCESS; } diff --git a/code/drivers/input/serial/serial.c b/code/drivers/input/serial/serial.c index 03b4ab4..cffebea 100644 --- a/code/drivers/input/serial/serial.c +++ b/code/drivers/input/serial/serial.c @@ -22,6 +22,7 @@ #define SERIAL_REG_LCR 3u #define SERIAL_REG_MCR 4u #define SERIAL_REG_LSR 5u +#define SERIAL_REG_SCR 7u #define SERIAL_LSR_DATA_READY 0x01u #define SERIAL_LSR_TX_EMPTY 0x20u @@ -37,6 +38,7 @@ #define SERIAL_FCR_TRIGGER_14 0xC0u #define SERIAL_RX_QUEUE_SIZE 1024u +#define SERIAL_RX_DRAIN_LIMIT 256u #define SERIAL_RX_QUEUE_MASK (SERIAL_RX_QUEUE_SIZE - 1u) typedef enum SERIAL_ESCAPE_STATE { @@ -65,6 +67,19 @@ static void SerialWrite(uint16_t reg, uint8_t value) { __outbyte((unsigned short)(SERIAL_COM1_BASE + reg), value); } +// Probes the 16550 scratch register so an absent COM1 device is rejected before any receive loop is entered. +static bool SerialProbeCom1(void) { + uint8_t original = SerialRead(SERIAL_REG_SCR); + + SerialWrite(SERIAL_REG_SCR, 0x5Au); + uint8_t first = SerialRead(SERIAL_REG_SCR); + SerialWrite(SERIAL_REG_SCR, 0xA5u); + uint8_t second = SerialRead(SERIAL_REG_SCR); + SerialWrite(SERIAL_REG_SCR, original); + + return first == 0x5Au && second == 0xA5u; +} + // Submits one input key transition. static void SerialSubmitTransition(INPUT_KEY key, bool pressed) { INPUT_KEY_EVENT event; @@ -301,8 +316,8 @@ static void SerialIsr(void) { } } -// Initializes COM1 as an 8N1 115200 baud input device. -static void SerialInitializeCom1(void) { +// Configures COM1 as an 8N1 115200 baud input device while leaving receive interrupts disabled. +static KSTATUS SerialConfigureCom1(void) { SerialWrite(SERIAL_REG_IER, 0x00u); SerialWrite(SERIAL_REG_LCR, SERIAL_LCR_DLAB); SerialWrite(SERIAL_REG_DATA, 0x01u); @@ -312,10 +327,16 @@ static void SerialInitializeCom1(void) { SERIAL_FCR_CLEAR_TX | SERIAL_FCR_TRIGGER_14); SerialWrite(SERIAL_REG_MCR, SERIAL_MCR_DTR | SERIAL_MCR_RTS | SERIAL_MCR_OUT2); - while (SerialRead(SERIAL_REG_LSR) & SERIAL_LSR_DATA_READY) + for (uint32_t i = 0; i < SERIAL_RX_DRAIN_LIMIT; i++) { + uint8_t lsr = SerialRead(SERIAL_REG_LSR); + if (lsr == 0xFFu) + return NTLSTATUS_NOT_FOUND; + if ((lsr & SERIAL_LSR_DATA_READY) == 0) + return NTLSTATUS_SUCCESS; (void)SerialRead(SERIAL_REG_DATA); + } - SerialWrite(SERIAL_REG_IER, SERIAL_IER_RX_READY); + return NTLSTATUS_DEVICE_ERROR; } // DriverEntry registers COM1 as an input source. @@ -323,8 +344,19 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { (void)ddk; (void)init; + if (!SerialProbeCom1()) { + DbgPrint("serial: COM1 not present; input driver disabled\n"); + return NTLSTATUS_NOT_FOUND; + } + + KSTATUS st = SerialConfigureCom1(); + if (!KSUCCESS(st)) { + DbgPrint("serial: COM1 did not become idle during initialization, status=0x%08X\n", st); + return st; + } + uint32_t deviceId = 0; - KSTATUS st = KeInputRegisterDevice("serial-com1", 0, &deviceId); + st = KeInputRegisterDevice("serial-com1", 0, &deviceId); if (!KSUCCESS(st)) return st; @@ -336,8 +368,8 @@ KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) { gSerialRxTail = 0; gSerialRxDropped = 0; - SerialInitializeCom1(); KeIrqRegister(SERIAL_COM1_IRQ, SerialIsr); + SerialWrite(SERIAL_REG_IER, SERIAL_IER_RX_READY); DbgPrint("serial: initialized COM1 input device=%u irq=%u\n", deviceId, SERIAL_COM1_IRQ); return NTLSTATUS_SUCCESS; diff --git a/code/drivers/net/lwip/lwip_sys_arch.c b/code/drivers/net/lwip/lwip_sys_arch.c index b30f635..6151d6b 100644 --- a/code/drivers/net/lwip/lwip_sys_arch.c +++ b/code/drivers/net/lwip/lwip_sys_arch.c @@ -349,10 +349,10 @@ sys_thread_t sys_thread_new(const char* name, lwip_thread_fn thread, void* arg, } sys_prot_t sys_arch_protect(void) { - return (sys_prot_t)HalDisableInterrupts(); + return (sys_prot_t)KeDisableInterrupts(); } // Restores the interrupt state saved by sys_arch_protect. void sys_arch_unprotect(sys_prot_t pval) { - HalRestoreInterrupts((uintptr_t)pval); + KeRestoreInterrupts((uintptr_t)pval); } diff --git a/code/drivers/storage/ahci/ahci.c b/code/drivers/storage/ahci/ahci.c index 55d1b28..156009c 100644 --- a/code/drivers/storage/ahci/ahci.c +++ b/code/drivers/storage/ahci/ahci.c @@ -29,28 +29,28 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl); static void AhciUnregisterController(AHCI_CONTROLLER* ctl); // Accessors for port structure memory at known CPU and device-address offsets. -static inline HBA_CMD_HEADER *cmd_list(AHCI_DRIVE *drv) { - return (HBA_CMD_HEADER *)((uint8_t *)drv->structVirt + PORT_OFF_CMDLIST); +static inline HBA_CMD_HEADER* cmd_list(AHCI_DRIVE* drv) { + return (HBA_CMD_HEADER*)((uint8_t*)drv->structVirt + PORT_OFF_CMDLIST); } // Returns the command-list device address. -static inline uint64_t cmd_list_device_address(AHCI_DRIVE *drv) { +static inline uint64_t cmd_list_device_address(AHCI_DRIVE* drv) { return drv->structDeviceAddress + PORT_OFF_CMDLIST; } // Returns the received-FIS device address. -static inline uint64_t fis_device_address(AHCI_DRIVE *drv) { +static inline uint64_t fis_device_address(AHCI_DRIVE* drv) { return drv->structDeviceAddress + PORT_OFF_FIS; } -static inline HBA_CMD_TBL *cmd_tbl(AHCI_DRIVE *drv, int slot) { - return (HBA_CMD_TBL *)((uint8_t *)drv->structVirt +static inline HBA_CMD_TBL* cmd_tbl(AHCI_DRIVE* drv, int slot) { + return (HBA_CMD_TBL*)((uint8_t*)drv->structVirt + PORT_OFF_CMDTBLS + (uint32_t)slot * PORT_CMDTBL_SIZE); } // Returns one command-table device address. -static inline uint64_t cmd_tbl_device_address(AHCI_DRIVE *drv, int slot) { +static inline uint64_t cmd_tbl_device_address(AHCI_DRIVE* drv, int slot) { return drv->structDeviceAddress + PORT_OFF_CMDTBLS + (uint64_t)slot * PORT_CMDTBL_SIZE; } @@ -118,7 +118,7 @@ static void AhciReleaseDriveDma(AHCI_DRIVE* drv) { } // Allocates and programs one port's command, FIS, and bounce buffers. -static KSTATUS port_init_memory(AHCI_DRIVE *drv) { +static KSTATUS port_init_memory(AHCI_DRIVE* drv) { if (!drv || !drv->controller || !drv->controller->dmaAdapter) return NTLSTATUS_INVALID_PARAMETER; @@ -142,7 +142,7 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) { drv->bounceVirt = gAhciDdk->Dma->GetCommonBufferAddress(drv->bounceDma); drv->bounceDeviceAddress = gAhciDdk->Dma->GetCommonBufferDeviceAddress(drv->bounceDma); - HBA_PORT *port = drv->port; + HBA_PORT* port = drv->port; port_stop(port); port->clb = (uint32_t)(cmd_list_device_address(drv) & 0xFFFFFFFF); @@ -150,7 +150,7 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) { port->fb = (uint32_t)(fis_device_address(drv) & 0xFFFFFFFF); port->fbu = (uint32_t)(fis_device_address(drv) >> 32); - HBA_CMD_HEADER *hdrs = cmd_list(drv); + HBA_CMD_HEADER* hdrs = cmd_list(drv); for (int i = 0; i < NUM_CMD_SLOTS; i++) { hdrs[i].prdtl = PORT_PRDT_COUNT; uint64_t table = cmd_tbl_device_address(drv, i); @@ -168,8 +168,8 @@ static KSTATUS port_init_memory(AHCI_DRIVE *drv) { // before the scheduler and interrupt handler are live). // Returns NTLSTATUS_SUCCESS, NTLSTATUS_DEVICE_ERROR, or NTLSTATUS_TIMEOUT. -static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) { - HBA_PORT *port = drv->port; +static KSTATUS port_issue_cmd_poll(AHCI_DRIVE* drv) { + HBA_PORT* port = drv->port; // Clear pending interrupts before issuing. port->is = (uint32_t)-1; @@ -214,11 +214,11 @@ static KSTATUS port_issue_cmd_poll(AHCI_DRIVE *drv) { } // Recovers a port after a command that cannot be trusted to have retired. -static void AhciRecoverPort(AHCI_DRIVE *drv) { +static void AhciRecoverPort(AHCI_DRIVE* drv) { if (!drv || !drv->port) return; - HBA_PORT *port = drv->port; + HBA_PORT* port = drv->port; uint32_t savedIe = port->ie; // Keep reset-generated FIS traffic from satisfying a command waiter. @@ -240,13 +240,13 @@ static void AhciRecoverPort(AHCI_DRIVE *drv) { // Interrupt bottom half. The ISR owns hardware acknowledgement and status // capture; this DPC wakes the worker thread after the hard IRQ has unwound. -static void AhciCompletionDpc(KDPC *dpc, void *context, - void *systemArgument1, void *systemArgument2) { +static void AhciCompletionDpc(KDPC* dpc, void* context, + void* systemArgument1, void* systemArgument2) { (void)dpc; (void)systemArgument1; (void)systemArgument2; - AHCI_DRIVE *drv = (AHCI_DRIVE *)context; + AHCI_DRIVE* drv = (AHCI_DRIVE*)context; if (drv && drv->completionEvent) KeSignalEvent(drv->completionEvent); } @@ -267,7 +267,7 @@ static void AhciIsr(void) { continue; for (int i = 0; i < ctl->driveCount; i++) { - AHCI_DRIVE *drv = &ctl->drives[i]; + AHCI_DRIVE* drv = &ctl->drives[i]; uint32_t portBit = (1u << drv->portNo); if (!(pending & portBit)) continue; @@ -286,7 +286,7 @@ static void AhciIsr(void) { // command completion. Only wake the worker for D2H completion or // an error that terminates the command. if (portIs & AHCI_PORT_TERMINAL_MASK) - KeInsertQueueDpc(&drv->completionDpc, (void *)0, (void *)0); + KeInsertQueueDpc(&drv->completionDpc, (void*)0, (void*)0); } } } @@ -295,11 +295,11 @@ static void AhciIsr(void) { // Caller has already built slot 0 via build_rw_cmd / port_identify. // Blocks the current thread until the ISR signals completionEvent. -static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) { +static KSTATUS port_issue_cmd(AHCI_DRIVE* drv) { if (drv->controller && drv->controller->pollingMode) return port_issue_cmd_poll(drv); - HBA_PORT *port = drv->port; + HBA_PORT* port = drv->port; // A serialized slot must be idle before reuse. A stale active bit means a // prior command escaped normal completion and must be recovered first. @@ -330,10 +330,10 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) { // Issue slot 0, then block with a finite deadline. port->ci = (1u << 0); - EVENT *events[1] = { drv->completionEvent }; + EVENT* events[1] = { drv->completionEvent }; KSTATUS waitStatus = KeWaitForMultipleEvents(events, 1, 0, AHCI_COMMAND_TIMEOUT_MS, - (uint32_t *)0); + (uint32_t*)0); // Merge any live status that was not consumed by the ISR. This lets a // missed IRQ still report the hardware error that accompanied it. @@ -378,10 +378,10 @@ static KSTATUS port_issue_cmd(AHCI_DRIVE *drv) { // Build a standard DMA read or write command in slot 0. -static void build_rw_cmd(AHCI_DRIVE *drv, uint64_t lba, uint32_t sectors, +static void build_rw_cmd(AHCI_DRIVE* drv, uint64_t lba, uint32_t sectors, uint64_t bufferDeviceAddress, bool write) { - HBA_CMD_HEADER *hdr = cmd_list(drv); // slot 0 - HBA_CMD_TBL *tbl = cmd_tbl(drv, 0); + HBA_CMD_HEADER* hdr = cmd_list(drv); // slot 0 + HBA_CMD_TBL* tbl = cmd_tbl(drv, 0); memset(tbl, 0, PORT_CMDTBL_SIZE); @@ -411,7 +411,7 @@ static void build_rw_cmd(AHCI_DRIVE *drv, uint64_t lba, uint32_t sectors, } // Build H2D FIS. - FIS_REG_H2D *fis = (FIS_REG_H2D *)tbl->cfis; + FIS_REG_H2D* fis = (FIS_REG_H2D*)tbl->cfis; memset(fis, 0, sizeof(*fis)); fis->fis_type = FIS_TYPE_REG_H2D; fis->c = 1; @@ -456,12 +456,12 @@ static KSTATUS do_flush(AHCI_DRIVE* drv) { // IDENTIFY command - fills a 512-byte buffer and returns it. -static KSTATUS port_identify(AHCI_DRIVE *drv) { +static KSTATUS port_identify(AHCI_DRIVE* drv) { // Reuse bounce buffer for IDENTIFY data (512 bytes). memset(drv->bounceVirt, 0, 512); - HBA_CMD_HEADER *hdr = cmd_list(drv); - HBA_CMD_TBL *tbl = cmd_tbl(drv, 0); + HBA_CMD_HEADER* hdr = cmd_list(drv); + HBA_CMD_TBL* tbl = cmd_tbl(drv, 0); memset(tbl, 0, PORT_CMDTBL_SIZE); @@ -475,7 +475,7 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) { tbl->prdt_entry[0].dbc = 512 - 1; tbl->prdt_entry[0].i = 1; - FIS_REG_H2D *fis = (FIS_REG_H2D *)tbl->cfis; + FIS_REG_H2D* fis = (FIS_REG_H2D*)tbl->cfis; memset(fis, 0, sizeof(*fis)); fis->fis_type = FIS_TYPE_REG_H2D; fis->c = 1; @@ -487,7 +487,7 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) { if (!KSUCCESS(st)) return st; // Parse sector count from IDENTIFY data. - const uint16_t *id = (const uint16_t *)drv->bounceVirt; + const uint16_t* id = (const uint16_t*)drv->bounceVirt; if (id[ATA_IDENT_W83] & (1u << 10)) { // 48-bit LBA supported. @@ -505,9 +505,9 @@ static KSTATUS port_identify(AHCI_DRIVE *drv) { } // Core read logic. -static KSTATUS do_read(AHCI_DRIVE *drv, uint64_t lba, - uint32_t count, void *buf) { - uint8_t *out = (uint8_t *)buf; +static KSTATUS do_read(AHCI_DRIVE* drv, uint64_t lba, + uint32_t count, void* buf) { + uint8_t* out = (uint8_t*)buf; while (count > 0) { uint32_t batch = (count > BOUNCE_SECTORS) ? BOUNCE_SECTORS : count; @@ -525,9 +525,9 @@ static KSTATUS do_read(AHCI_DRIVE *drv, uint64_t lba, } // Core write logic. -static KSTATUS do_write(AHCI_DRIVE *drv, uint64_t lba, - uint32_t count, const void *buf) { - const uint8_t *in = (const uint8_t *)buf; +static KSTATUS do_write(AHCI_DRIVE* drv, uint64_t lba, + uint32_t count, const void* buf) { + const uint8_t* in = (const uint8_t*)buf; while (count > 0) { uint32_t batch = (count > BOUNCE_SECTORS) ? BOUNCE_SECTORS : count; @@ -571,9 +571,9 @@ static KSTATUS AhciGetIrpReadWrite(IRP* irp, bool* outWrite, uint64_t* outLba, // Accepted IRPs are queued to the per-drive worker and completed later with // IoCompleteRequest(). Rejected IRPs are returned synchronously and are not // completed. -static KSTATUS AhciDispatchReadWrite(DEVICE_OBJECT *device, IRP *irp) { +static KSTATUS AhciDispatchReadWrite(DEVICE_OBJECT* device, IRP* irp) { if (!device || !irp) return NTLSTATUS_INVALID_PARAMETER; - AHCI_DRIVE *drv = (AHCI_DRIVE *)device->DeviceExtension; + AHCI_DRIVE* drv = (AHCI_DRIVE*)device->DeviceExtension; if (!drv) return NTLSTATUS_INVALID_PARAMETER; KSTATUS st = IoAcquireRemoveLock(&drv->removeLock, irp); @@ -762,7 +762,7 @@ static KSTATUS AhciDispatchPnp(DEVICE_OBJECT* device, IRP* irp) { // record slotIrp[slot] = irp, and issue via port->sact + port->ci. // 5. Return immediately so the ISR can drive all completions. static void AhciWorker(uintptr_t arg) { - AHCI_DRIVE *drv = (AHCI_DRIVE *)arg; + AHCI_DRIVE* drv = (AHCI_DRIVE*)arg; while (drv && drv->workerRunning) { // Block until AHCI dispatch signals a new IRP. @@ -770,7 +770,7 @@ static void AhciWorker(uintptr_t arg) { if (!drv->workerRunning) break; - IRP *irp = IrpQueueDequeue(&drv->irpQueue); + IRP* irp = IrpQueueDequeue(&drv->irpQueue); if (!irp) continue; @@ -954,11 +954,17 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { return st; } + // IoRegisterDiskDevice publishes arrival synchronously, and partition/filesystem + // consumers may issue reads from that callback. Install the controller ISR before + // any disk is published so an interrupt-driven drive can complete those reads. + if (!ctl->pollingMode) + KeIrqRegister(ctl->irq, AhciIsr); + uint32_t pi = ctl->hba->pi; for (uint32_t portno = 0; portno < 32 && ctl->driveCount < MAX_AHCI_DRIVES; portno++) { if (!(pi & (1u << portno))) continue; - HBA_PORT *port = &ctl->hba->ports[portno]; + HBA_PORT* port = &ctl->hba->ports[portno]; // Phase 1: physical link check. if (!port_link_up(port)) { @@ -969,7 +975,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { DbgPrint("AHCI: port %u: link up, initializing...\n", portno); int driveIdx = ctl->driveCount; - AHCI_DRIVE *drv = &ctl->drives[driveIdx]; + AHCI_DRIVE* drv = &ctl->drives[driveIdx]; memset(drv, 0, sizeof(*drv)); drv->magic = AHCI_DRIVE_MAGIC; drv->controller = ctl; @@ -1013,7 +1019,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { // Phase 5b: create the IRP queue semaphore and initialise the queue. // The semaphore starts at 0; each submitted IRP increments it by one. - SEMAPHORE *irpSem = (SEMAPHORE *)0; + SEMAPHORE* irpSem = (SEMAPHORE*)0; st = KeSemaphoreCreate(0, 0x7FFFFFFF, &irpSem); if (!KSUCCESS(st)) { DbgPrint("AHCI: port %u: semaphore create failed (0x%x)\n", portno, st); @@ -1031,7 +1037,7 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { } drv->removeLockReady = true; - // Phase 6: IDENTIFY (polled - scheduler not yet running). + // Phase 6: IDENTIFY remains polled until the disk runtime path is ready. st = port_identify(drv); if (!KSUCCESS(st)) { DbgPrint("AHCI: port %u: IDENTIFY failed (0x%x)\n", portno, st); @@ -1045,12 +1051,8 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { (unsigned long long)drv->sectorCount, (unsigned long long)(drv->sectorCount / 2048)); - // Phase 7: enable per-port interrupts. - // Clear any stale IS first so we don't get a spurious signal immediately. - port->is = (uint32_t)-1; - port->ie = AHCI_PORT_TERMINAL_MASK; - - // Phase 8: create and register a disk DEVICE_OBJECT. + // Phase 7: create the disk DEVICE_OBJECT, but do not publish it until the + // asynchronous runtime path is fully operational. st = IoCreateDevice(&g_driver, 0, &drv->deviceObject); if (!KSUCCESS(st)) { DbgPrint("AHCI: drive %d: IoCreateDevice failed (0x%x)\n", g_nextDiskIndex, st); @@ -1063,24 +1065,9 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { drv->deviceObject->u.Disk.SectorSize = 512; drv->deviceObject->AlignmentRequirement = 512; - uint8_t diskIndex = g_nextDiskIndex; - st = IoRegisterDiskDevice(diskIndex, drv->deviceObject); - if (KSUCCESS(st)) { - DbgPrint("AHCI: drive %u registered\n", diskIndex); - ctl->driveCount++; - g_nextDiskIndex++; - } else { - DbgPrint("AHCI: IoRegisterDiskDevice(%u) failed (0x%x)\n", diskIndex, st); - IoDeleteDevice(drv->deviceObject); - drv->deviceObject = (DEVICE_OBJECT*)0; - AhciReleaseDriveRuntime(drv); - AhciReleaseDriveDma(drv); - continue; - } - - // Phase 9: spawn the per-drive IRP worker thread. - // The worker owns do_read / do_write exclusively once the scheduler - // is live; it is the only thread that may call port_issue_cmd(). + // Phase 8: start the per-drive IRP worker before publishing the disk. + // IoRegisterDiskDevice synchronously notifies partition consumers, which may + // immediately submit reads that must have a live worker and completion path. st = KeEventCreate(EventManualReset, 0, &drv->workerFinished); if (KSUCCESS(st)) { drv->workerRunning = true; @@ -1092,9 +1079,34 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { } if (!KSUCCESS(st)) { drv->workerRunning = false; - DbgPrint("AHCI: drive %u: worker creation failed (0x%x)\n", diskIndex, st); - (void)IoUnregisterDiskDevice(diskIndex); + DbgPrint("AHCI: drive %u: worker creation failed (0x%x)\n", g_nextDiskIndex, st); + IoDeleteDevice(drv->deviceObject); + drv->deviceObject = (DEVICE_OBJECT*)0; + AhciReleaseDriveRuntime(drv); + AhciReleaseDriveDma(drv); + continue; + } + + // Make the initialized drive visible to the controller ISR, then arm its + // interrupt source. No consumer can submit I/O until publication below. + ctl->driveCount++; + port->is = (uint32_t)-1; + port->ie = AHCI_PORT_TERMINAL_MASK; + + // Phase 9: publish only after reads and writes can complete synchronously. + uint8_t diskIndex = g_nextDiskIndex; + st = IoRegisterDiskDevice(diskIndex, drv->deviceObject); + if (KSUCCESS(st)) { + DbgPrint("AHCI: drive %u registered\n", diskIndex); + g_nextDiskIndex++; + } else { + DbgPrint("AHCI: IoRegisterDiskDevice(%u) failed (0x%x)\n", diskIndex, st); + port->ie = 0; ctl->driveCount--; + IoDeleteDevice(drv->deviceObject); + drv->deviceObject = (DEVICE_OBJECT*)0; + AhciReleaseDriveRuntime(drv); + AhciReleaseDriveDma(drv); continue; } } @@ -1106,7 +1118,6 @@ static KSTATUS AhciStartController(AHCI_CONTROLLER* ctl) { ctl->pciInfo.Address.Function); if (!ctl->pollingMode) { - KeIrqRegister(ctl->irq, AhciIsr); DbgPrint("AHCI: ISR registered on IRQ %u (%s); controller drives=%d\n", (unsigned)ctl->irq, ctl->usingMsi ? "MSI" : "INTx", ctl->driveCount); } else { diff --git a/code/drivers/storage/ahci/ahci.h b/code/drivers/storage/ahci/ahci.h index f80dea8..36a219a 100644 --- a/code/drivers/storage/ahci/ahci.h +++ b/code/drivers/storage/ahci/ahci.h @@ -317,7 +317,7 @@ typedef struct AHCI_CONTROLLER AHCI_CONTROLLER; typedef struct { uint32_t magic; // AHCI_DRIVE_MAGIC AHCI_CONTROLLER* controller; // owning AHCI controller FDO extension - HBA_PORT *port; // MMIO port registers (virtual) + HBA_PORT* port; // MMIO port registers (virtual) uint64_t sectorCount; // Total LBA sectors on device uint8_t portNo; // Physical port index (for ISR dispatch) @@ -333,7 +333,7 @@ typedef struct { // Signaled by the completion DPC when the port raises an interrupt. // Auto-reset: one Wait consumes one completion. - EVENT *completionEvent; + EVENT* completionEvent; // Last IS snapshot captured by the ISR (for error checking post-wait). volatile uint32_t lastIs; @@ -389,10 +389,10 @@ extern const NTL_DDK* gAhciDdk; extern DRIVER_OBJECT g_driver; KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length); -void port_stop(HBA_PORT *port); -void port_start(HBA_PORT *port); -KSTATUS hba_reset(HBA_MEM *hba); -bool port_link_up(HBA_PORT *port); -bool port_is_sata_disk(HBA_PORT *port); +void port_stop(HBA_PORT* port); +void port_start(HBA_PORT* port); +KSTATUS hba_reset(HBA_MEM* hba); +bool port_link_up(HBA_PORT* port); +bool port_is_sata_disk(HBA_PORT* port); void ahci_delay_ms(uint32_t ms); -KSTATUS port_comreset(AHCI_DRIVE *drv); +KSTATUS port_comreset(AHCI_DRIVE* drv); diff --git a/code/drivers/storage/ahci/ahci_port.c b/code/drivers/storage/ahci/ahci_port.c index 6d82b81..cc6199a 100644 --- a/code/drivers/storage/ahci/ahci_port.c +++ b/code/drivers/storage/ahci/ahci_port.c @@ -26,7 +26,7 @@ KSTATUS ahci_map_mmio(uint64_t physBase, uint64_t length) { } // Implements the port stop operation. -void port_stop(HBA_PORT *port) { +void port_stop(HBA_PORT* port) { // Clear ST (bit 0). port->cmd &= ~PORT_CMD_ST; // Wait for CR (bit 15) to clear (command list DMA stops). @@ -42,7 +42,7 @@ void port_stop(HBA_PORT *port) { } // Implements the port start operation. -void port_start(HBA_PORT *port) { +void port_start(HBA_PORT* port) { // Wait until CR is clear before setting ST. for (int i = 0; i < POLL_TIMEOUT; i++) { if (!(port->cmd & PORT_CMD_CR)) break; @@ -52,7 +52,7 @@ void port_start(HBA_PORT *port) { } // Implements the hba reset operation. -KSTATUS hba_reset(HBA_MEM *hba) { +KSTATUS hba_reset(HBA_MEM* hba) { // BIOS/OS handoff (spec ยง10.6.3). if (hba->cap2 & CAP2_BOH) { hba->bohc |= BOHC_OOS; @@ -86,14 +86,14 @@ KSTATUS hba_reset(HBA_MEM *hba) { } // Implements the port link up operation. -bool port_link_up(HBA_PORT *port) { +bool port_link_up(HBA_PORT* port) { uint32_t det = port->ssts & 0x0F; uint32_t ipm = (port->ssts >> 8) & 0x0F; return (det == PORT_DET_PRESENT && ipm == PORT_IPM_ACTIVE); } // Implements the port is sata disk operation. -bool port_is_sata_disk(HBA_PORT *port) { +bool port_is_sata_disk(HBA_PORT* port) { return port->sig == SATA_SIG_ATA; } @@ -104,8 +104,8 @@ void ahci_delay_ms(uint32_t ms) { } // Implements the port comreset operation. -KSTATUS port_comreset(AHCI_DRIVE *drv) { - HBA_PORT *port = drv->port; +KSTATUS port_comreset(AHCI_DRIVE* drv) { + HBA_PORT* port = drv->port; port->cmd &= ~PORT_CMD_ST; for (int i = 0; i < POLL_TIMEOUT; i++) diff --git a/code/kernel/hal/hal_apic.c b/code/kernel/hal/hal_apic.c index 6408924..b119b84 100644 --- a/code/kernel/hal/hal_apic.c +++ b/code/kernel/hal/hal_apic.c @@ -1,15 +1,16 @@ // HAL APIC support. This file owns local APIC, I/O APIC, and interrupt-controller setup. #include -#include +#include +#include +#include #include #include -extern HAL_API gApi; // ---- Local APIC ----. -static volatile uint32_t* gLapic = (volatile uint32_t *)0; -#define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off))) +static volatile uint32_t* gLapic = (volatile uint32_t*)0; +#define LAPIC_REG(off) (*(volatile uint32_t*)((uint8_t*)gLapic + (off))) #define LAPIC_ID 0x020 #define LAPIC_EOI 0x0B0 #define LAPIC_SPURIOUS 0x0F0 @@ -27,10 +28,9 @@ static volatile uint32_t* gLapic = (volatile uint32_t *)0; #define CPUID_FEATURE_ECX_X2APIC (1u << 21) -#define APIC_VECTOR_RESCHEDULE 0xF0u // ---- I/O APIC ----. -static volatile uint32_t* gIoapic = (volatile uint32_t *)0; +static volatile uint32_t* gIoapic = (volatile uint32_t*)0; static uint32_t gBspLapicId = 0; static uint8_t gX2ApicSupported = 0; static uint8_t gX2ApicEnabled = 0; @@ -39,13 +39,13 @@ static uint8_t gX2ApicEnabled = 0; // Implements the ioapic Read operation. static uint32_t IoapicRead(uint8_t reg) { - *(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_IDX) = reg; - return *(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_DATA); + *(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_IDX) = reg; + return *(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_DATA); } // Implements the ioapic Write operation. static void IoapicWrite(uint8_t reg, uint32_t val) { - *(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_IDX) = reg; - *(volatile uint32_t *)((uint8_t *)gIoapic + IOAPIC_DATA) = val; + *(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_IDX) = reg; + *(volatile uint32_t*)((uint8_t*)gIoapic + IOAPIC_DATA) = val; } // Implements the apic Detect Mode operation. @@ -58,17 +58,9 @@ static void HalApicDetectMode(void) { gX2ApicEnabled = (gX2ApicSupported && (apicBase & IA32_APIC_BASE_X2APIC)) ? 1u : 0u; } -// Implements the apic Stall Fallback operation. -static void HalApicStallFallback(uint32_t loops) { - for (volatile uint32_t i = 0; i < loops; i++) { - _mm_pause(); - } -} - -// Implements the apic Stall Us operation. +// Stalls for the requested bootstrap interval using the HAL timer fallback chain. static void HalApicStallUs(uint32_t us) { - if (gApi.Timer.Stall) gApi.Timer.Stall(us); - else HalApicStallFallback(us * 128u + 1u); + HalTimerStall(us); } // Implements the apic Wait Icr Idle operation. @@ -102,25 +94,25 @@ static void HalApicSendIpi(uint32_t lapicId, uint32_t low) { // Interrupt vector base: IRQ 0 -> vector 0x20, IRQ 1 -> vector 0x21, ... #define IRQ_VECTOR_BASE 0x20 -// Returns bsp Lapic Id. -__declspec(dllexport) uint32_t HalGetBspLapicId(void) { +// Returns the local interrupt-controller hardware ID of the boot processor. +uint32_t HalApicGetBootProcessorId(void) { return gBspLapicId; } -// Implements the apic Using X2 Apic operation. -__declspec(dllexport) uint8_t HalApicUsingX2Apic(void) { +// Returns whether the local interrupt controller is using x2APIC mode. +uint8_t HalApicUsingX2Apic(void) { return gX2ApicEnabled; } -// Implements the apic Can Address Id operation. -__declspec(dllexport) uint8_t HalApicCanAddressId(uint32_t lapicId) { +// Returns whether the active local interrupt-controller mode can address one hardware ID. +uint8_t HalApicCanAddressId(uint32_t hardwareId) { if (gX2ApicEnabled) return 1u; - return (lapicId < 256u) ? 1u : 0u; + return (hardwareId < 256u) ? 1u : 0u; } -// Implements the apic Enable Current Cpu operation. -__declspec(dllexport) void HalApicEnableCurrentCpu(void) { +// Initializes the local interrupt controller on the current processor. +void HalApicInitializeCurrentProcessor(void) { uint64_t apicBase = __readmsr(IA32_APIC_BASE) | IA32_APIC_BASE_ENABLE; if (gX2ApicEnabled) { @@ -135,8 +127,8 @@ __declspec(dllexport) void HalApicEnableCurrentCpu(void) { LAPIC_REG(LAPIC_SPURIOUS) = LAPIC_ENABLE | 0xFF; } -// Implements the apic Send Panic Ipi operation. -__declspec(dllexport) void HalApicSendPanicIpi(void) { +// Broadcasts an NMI to every processor except the caller. +void HalApicBroadcastNmi(void) { if (!gLapic && !gX2ApicEnabled) return; // Stop-the-world panic IPI. Use NMI delivery instead of a normal fixed @@ -146,42 +138,56 @@ __declspec(dllexport) void HalApicSendPanicIpi(void) { HalApicSendIpi(0, (3u << 18) | (4u << 8)); // all-excluding-self, NMI } -// Implements the apic Send Reschedule Ipi operation. -__declspec(dllexport) void HalApicSendRescheduleIpi(uint32_t lapicId) { - if (!gLapic && !gX2ApicEnabled) return; - HalApicSendIpi(lapicId, APIC_VECTOR_RESCHEDULE); // fixed delivery, physical destination +// Sends one fixed-vector processor interrupt without exposing APIC mechanics. +void HalApicSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector) { + if ((!gLapic && !gX2ApicEnabled) || vector < 0x20u || !HalApicCanAddressId(hardwareId)) + return; + HalApicSendIpi(hardwareId, vector); } -// Implements the apic Send Tlb Shootdown Ipi operation. -__declspec(dllexport) void HalApicSendTlbShootdownIpi(uint32_t lapicId) { - if (!gLapic && !gX2ApicEnabled) return; - - // TLB shootdown must reach CPUs that may be spinning with IF=0 inside - // high-IRQL/spinlock regions, so use NMI delivery rather than a fixed - // vector. The NMI stub handles active shootdowns and returns; unexpected - // NMIs still go through the normal fault/panic path. - HalApicSendIpi(lapicId, (4u << 8)); // NMI delivery, physical destination +// Sends one non-maskable processor interrupt. +void HalApicSendProcessorNmi(uint32_t hardwareId) { + if ((!gLapic && !gX2ApicEnabled) || !HalApicCanAddressId(hardwareId)) + return; + HalApicSendIpi(hardwareId, (4u << 8)); } -// Starts application Processor. -__declspec(dllexport) KSTATUS HalStartApplicationProcessor(uint32_t lapicId, uint8_t startupVector) { - if ((!gLapic && !gX2ApicEnabled) || !startupVector || lapicId == gBspLapicId) +// Starts one secondary processor through the local interrupt-controller startup protocol. +KSTATUS HalApicStartProcessor(uint32_t hardwareId, uintptr_t startPhysical) { + if ((!gLapic && !gX2ApicEnabled) || !startPhysical || + (startPhysical & 0xFFFu) != 0 || startPhysical >= 0x100000u || + hardwareId == gBspLapicId) { return NTLSTATUS_INVALID_PARAMETER; - if (!HalApicCanAddressId(lapicId)) + } + if (!HalApicCanAddressId(hardwareId)) return NTLSTATUS_NOT_SUPPORTED; - // INIT assert/deassert, then two SIPIs. The target begins at startupVector << 12. - HalApicSendIpi(lapicId, 0x0000C500u); // INIT, level assert, level-triggered + uint8_t startupVector = (uint8_t)(startPhysical >> 12); + // INIT assert/deassert, then two SIPIs. The target begins at startPhysical. + HalApicSendIpi(hardwareId, 0x0000C500u); // INIT, level assert, level-triggered HalApicStallUs(10000); - HalApicSendIpi(lapicId, 0x00008500u); // INIT, level deassert + HalApicSendIpi(hardwareId, 0x00008500u); // INIT, level deassert HalApicStallUs(200); - HalApicSendIpi(lapicId, 0x00000600u | startupVector); // SIPI + HalApicSendIpi(hardwareId, 0x00000600u | startupVector); // SIPI HalApicStallUs(200); - HalApicSendIpi(lapicId, 0x00000600u | startupVector); // SIPI retry + HalApicSendIpi(hardwareId, 0x00000600u | startupVector); // SIPI retry HalApicStallUs(200); return NTLSTATUS_SUCCESS; } +// Composes an MSI message targeting the boot processor without exposing APIC mode. +KSTATUS HalApicComposeMsi(uint8_t vector, HAL_MSI_MESSAGE* messageOut) { + if (!messageOut || vector < 0x20u) + return NTLSTATUS_INVALID_PARAMETER; + if (gBspLapicId >= 256u) + return NTLSTATUS_NOT_SUPPORTED; + + messageOut->Address = 0xFEE00000ull | ((uint64_t)(gBspLapicId & 0xFFu) << 12); + messageOut->Data = vector; + messageOut->Reserved = 0; + return NTLSTATUS_SUCCESS; +} + // Implements the apic Route Irq operation. void HalApicRouteIrq(uint8_t irq) { // Full initial setup of the redirection entry. @@ -220,8 +226,8 @@ void HalApicSendEoi(void) { void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys) { HalApicDetectMode(); - gLapic = (volatile uint32_t *)(gApi.PhysToVirt(lapicPhys)); - gIoapic = (volatile uint32_t *)(gApi.PhysToVirt(ioapicPhys)); + gLapic = (volatile uint32_t*)(HalPhysicalToVirtual(lapicPhys)); + gIoapic = (volatile uint32_t*)(HalPhysicalToVirtual(ioapicPhys)); // The BSP is the CPU executing HAL init. Keep all external/device IRQs // routed to this LAPIC; CPU-local IPIs use the local APIC directly. @@ -230,7 +236,7 @@ void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys) { else gBspLapicId = (LAPIC_REG(LAPIC_ID) >> 24) & 0xFF; - HalApicEnableCurrentCpu(); + HalApicInitializeCurrentProcessor(); // Mask all IOAPIC lines initially. uint32_t maxRedir = (IoapicRead(1) >> 16) & 0xFF; diff --git a/code/kernel/hal/hal_main.c b/code/kernel/hal/hal_main.c index 2ceda33..6863ccc 100644 --- a/code/kernel/hal/hal_main.c +++ b/code/kernel/hal/hal_main.c @@ -1,8 +1,11 @@ // HAL initialization entry point. This file discovers platform tables and publishes the HAL API to the kernel. -#include +#include #include #include +#include +#include +#include #include #include #include @@ -11,7 +14,6 @@ void HalScreenInitialize(void* (*AllocatePool)(size_t size), uintptr_t fb, int w, int h, int stride, HAL_SCREEN* out); -KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *out); void HalPciInitialize(HAL_PCI* out); void HalRtcInitialize(HAL_RTC* out); void HalRtcSetCenturyRegister(uint8_t centuryRegister); @@ -20,9 +22,34 @@ extern void HalSetDebugEnabled(int v); // Defined in mm_virt.c - HAL needs it for MMIO window access. static uint64_t gPhysBase = 0xFFFF880000000000ULL; -HAL_API gApi; +static KSTATUS HalQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* out); + +static HAL_API gApi = { + .Size = sizeof(HAL_API), + .Version = HAL_API_VERSION, + .Processor = { + .DisableInterrupts = HalDisableInterrupts, + .RestoreInterrupts = HalRestoreInterrupts, + .EnableInterrupts = HalEnableInterrupts, + .WaitForInterrupt = HalWaitForInterrupt, + .Halt = HalHalt, + .Stop = HalStopProcessor, + .QueryTopology = HalQueryProcessorTopology, + .Start = HalApicStartProcessor, + }, + .InterruptController = { + .RouteIrq = HalApicRouteIrq, + .MaskIrq = HalApicMaskIrq, + .UnmaskIrq = HalApicUnmaskIrq, + .SendEoi = HalApicSendEoi, + .InitializeCurrentProcessor = HalApicInitializeCurrentProcessor, + .SendProcessorInterrupt = HalApicSendProcessorInterrupt, + .SendProcessorNmi = HalApicSendProcessorNmi, + .BroadcastNmi = HalApicBroadcastNmi, + .ComposeMsi = HalApicComposeMsi, + }, +}; static int gInitialised = 0; -extern void (*gTimerCallback)(void); // ACPI table walker - minimal, just enough to find APIC and HPET addresses. #define ACPI_SIG(a,b,c,d) ((uint32_t)(a)|((uint32_t)(b)<<8)|((uint32_t)(c)<<16)|((uint32_t)(d)<<24)) @@ -81,7 +108,7 @@ typedef struct { static uintptr_t gLapicPhys = 0xFEE00000; static uintptr_t gIoapicPhys = 0xFEC00000; static uintptr_t gHpetPhys = 0xFED00000; -static HAL_CPU_TOPOLOGY gCpuTopology; +static HAL_PROCESSOR_TOPOLOGY gCpuTopology; // ACPI power-off state (filled in by ParseFadt / ScanDsdtForS5). // Do not guess SLP_TYP on real hardware: these values are firmware-specific. @@ -122,8 +149,8 @@ void HalDbgPrint(const char* fmt, ...) { gApi.Output.Write(&record); } -static void *HalPhysToVirt(uint64_t phys) { - return (void *)(phys + gPhysBase); +void* HalPhysicalToVirtual(uint64_t phys) { + return (void*)(phys + gPhysBase); } // FADT (Fixed ACPI Description Table) offsets used here. ACPI 2.0+ @@ -360,18 +387,18 @@ static void ParseFadt(const ACPISDTHeader* header) { // Implements the add Cpu Record operation. static void HalAddCpuRecord(uint32_t uid, uint32_t lapicId, uint32_t flags) { - if (gCpuTopology.CpuCount >= HAL_MAX_CPUS) + if (gCpuTopology.Count >= HAL_MAX_PROCESSORS) return; - for (uint32_t i = 0; i < gCpuTopology.CpuCount; i++) { - if (gCpuTopology.Cpus[i].LapicId == lapicId) + for (uint32_t i = 0; i < gCpuTopology.Count; i++) { + if (gCpuTopology.Processors[i].HardwareId == lapicId) return; } - HAL_CPU_INFO* cpu = &gCpuTopology.Cpus[gCpuTopology.CpuCount++]; + HAL_PROCESSOR_INFO* cpu = &gCpuTopology.Processors[gCpuTopology.Count++]; memset(cpu, 0, sizeof(*cpu)); - cpu->ProcessorUid = uid; - cpu->LapicId = lapicId; + cpu->FirmwareId = uid; + cpu->HardwareId = lapicId; cpu->Flags = flags; cpu->Enabled = (flags & 1u) ? 1u : 0u; } @@ -379,8 +406,8 @@ static void HalAddCpuRecord(uint32_t uid, uint32_t lapicId, uint32_t flags) { // Parses madt. static void ParseMadt(MADT* madt) { gLapicPhys = madt->LocalAPICAddress; - uint8_t* p = (uint8_t *)(madt + 1); - uint8_t* end = (uint8_t *)madt + madt->Header.Length; + uint8_t* p = (uint8_t*)(madt + 1); + uint8_t* end = (uint8_t*)madt + madt->Header.Length; while (p + 2 <= end) { uint8_t type = p[0], len = p[1]; if (!len || p + len > end) break; @@ -388,17 +415,17 @@ static void ParseMadt(MADT* madt) { if (type == 0 && len >= 8) { // Processor Local APIC uint32_t uid = p[2]; uint32_t lapicId = p[3]; - uint32_t flags = *(uint32_t *)(p + 4); + uint32_t flags = *(uint32_t*)(p + 4); if (flags & 1u) HalAddCpuRecord(uid, lapicId, flags); } else if (type == 1 && len >= 12) { // I/O APIC entry - gIoapicPhys = *(uint32_t *)(p + 4); + gIoapicPhys = *(uint32_t*)(p + 4); } else if (type == 5 && len >= 12) { // Local APIC Address Override - gLapicPhys = (uintptr_t)*(uint64_t *)(p + 4); + gLapicPhys = (uintptr_t)*(uint64_t*)(p + 4); } else if (type == 9 && len >= 16) { // Processor Local x2APIC - uint32_t lapicId = *(uint32_t *)(p + 4); - uint32_t flags = *(uint32_t *)(p + 8); - uint32_t uid = *(uint32_t *)(p + 12); + uint32_t lapicId = *(uint32_t*)(p + 4); + uint32_t flags = *(uint32_t*)(p + 8); + uint32_t uid = *(uint32_t*)(p + 12); if (flags & 1u) HalAddCpuRecord(uid, lapicId, flags); } @@ -512,47 +539,52 @@ static void HalShutdown(void) { HalStopProcessor(); } -__declspec(dllexport) KSTATUS HalGetProcessorTopology(HAL_CPU_TOPOLOGY* out) { +static KSTATUS HalQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* out) { if (!out) return NTLSTATUS_INVALID_PARAMETER; memset(out, 0, sizeof(*out)); *out = gCpuTopology; - if (!out->CpuCount) { - out->CpuCount = 1; - out->BspLapicId = HalGetBspLapicId(); - out->Cpus[0].LapicId = out->BspLapicId; - out->Cpus[0].ProcessorUid = 0; - out->Cpus[0].Flags = 1; - out->Cpus[0].Enabled = 1; - out->Cpus[0].IsBsp = 1; + if (!out->Count) { + out->Count = 1; + out->BootProcessorHardwareId = HalApicGetBootProcessorId(); + out->Processors[0].HardwareId = out->BootProcessorHardwareId; + out->Processors[0].FirmwareId = 0; + out->Processors[0].Flags = 1; + out->Processors[0].Enabled = 1; + out->Processors[0].IsBootProcessor = 1; + out->Processors[0].Startable = 1; return NTLSTATUS_SUCCESS; } - uint32_t bsp = HalGetBspLapicId(); - out->BspLapicId = bsp; + uint32_t bsp = HalApicGetBootProcessorId(); + out->BootProcessorHardwareId = bsp; int foundBsp = 0; - for (uint32_t i = 0; i < out->CpuCount; i++) { - out->Cpus[i].IsBsp = (out->Cpus[i].LapicId == bsp) ? 1u : 0u; - if (out->Cpus[i].IsBsp) foundBsp = 1; + for (uint32_t i = 0; i < out->Count; i++) { + out->Processors[i].IsBootProcessor = (out->Processors[i].HardwareId == bsp) ? 1u : 0u; + out->Processors[i].Startable = out->Processors[i].IsBootProcessor || HalApicCanAddressId(out->Processors[i].HardwareId); + if (out->Processors[i].IsBootProcessor) foundBsp = 1; } - if (!foundBsp && out->CpuCount < HAL_MAX_CPUS) { - HAL_CPU_INFO* cpu = &out->Cpus[out->CpuCount++]; + if (!foundBsp && out->Count < HAL_MAX_PROCESSORS) { + HAL_PROCESSOR_INFO* cpu = &out->Processors[out->Count++]; memset(cpu, 0, sizeof(*cpu)); - cpu->LapicId = bsp; - cpu->ProcessorUid = bsp; + cpu->HardwareId = bsp; + cpu->FirmwareId = bsp; cpu->Flags = 1; cpu->Enabled = 1; - cpu->IsBsp = 1; + cpu->IsBootProcessor = 1; + cpu->Startable = 1; } return NTLSTATUS_SUCCESS; } -__declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { +HAL_ENTRY KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { + if (!initParams || initParams->Size != sizeof(HAL_INIT_PARAMS) || initParams->Flags != 0 || + !initParams->PhysicalWindowBase || !initParams->AllocatePool) + return NTLSTATUS_INVALID_PARAMETER; if (gInitialised) return NTLSTATUS_ALREADY_EXISTS; gPhysBase = initParams->PhysicalWindowBase; - gApi.PhysToVirt = HalPhysToVirt; debugEnabled = initParams->Debug; HalOutputInitialize(&gApi.Output); @@ -577,21 +609,15 @@ __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { HalApicInitialize(gLapicPhys, gIoapicPhys); HalDbgPrint("HAL: local APIC mode=%s BSP_ID=%u\n", HalApicUsingX2Apic() ? "x2APIC" : "xAPIC", - HalGetBspLapicId()); + HalApicGetBootProcessorId()); KSTATUS ts = HalTimerInitialize(gLapicPhys, gHpetPhys, &gApi.Timer); if (!KSUCCESS(ts)) HalDbgPrint("HAL: HPET unavailable, using fallback\n"); HalRtcInitialize(&gApi.Rtc); HalPciInitialize(&gApi.Pci); - gApi.Intc.RouteIrq = HalApicRouteIrq; - gApi.Intc.MaskIrq = HalApicMaskIrq; - gApi.Intc.UnmaskIrq = HalApicUnmaskIrq; - gApi.Intc.SendEoi = HalApicSendEoi; - gApi.Power.Shutdown = HalShutdown; - gApi.Version = HAL_API_VERSION; gInitialised = 1; // The 8042 can leave output bytes pending while the keyboard/mouse drivers @@ -609,10 +635,5 @@ __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) { return NTLSTATUS_SUCCESS; } -// Registers scheduler. -__declspec(dllexport) void HalRegisterScheduler(void (*pKeSchedulerTick)(void)) { - gTimerCallback = pKeSchedulerTick; -} - // Returns api. -__declspec(dllexport) const HAL_API* HalGetApi(void) { return &gApi; } +HAL_ENTRY const HAL_API* HalGetApi(void) { return &gApi; } diff --git a/code/kernel/hal/hal_output.c b/code/kernel/hal/hal_output.c index 4e2b1b2..401dee0 100644 --- a/code/kernel/hal/hal_output.c +++ b/code/kernel/hal/hal_output.c @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #define HAL_OUTPUT_ENDPOINT_CAPACITY 8u diff --git a/code/kernel/hal/hal_pci.c b/code/kernel/hal/hal_pci.c index 5cf281e..1e0ec66 100644 --- a/code/kernel/hal/hal_pci.c +++ b/code/kernel/hal/hal_pci.c @@ -1,7 +1,8 @@ // HAL PCI configuration access. This file owns legacy mechanism-1 PCI config port I/O. #include -#include +#include +#include #define PCI_CONFIG_ADDR 0xCF8 #define PCI_CONFIG_DATA 0xCFC diff --git a/code/kernel/hal/hal_processor.c b/code/kernel/hal/hal_processor.c index 4ac4efe..64ef454 100644 --- a/code/kernel/hal/hal_processor.c +++ b/code/kernel/hal/hal_processor.c @@ -1,41 +1,42 @@ // HAL processor control exports. This file owns interrupt masking and processor halt instructions. #include +#include #include -#include +#include #pragma intrinsic(_disable) #pragma intrinsic(_enable) // Masks interrupts and returns the previous RFLAGS value. -__declspec(dllexport) uintptr_t HalDisableInterrupts(void) { +uintptr_t HalDisableInterrupts(void) { uintptr_t flags = __readeflags(); _disable(); return flags; } // Restores a previously saved RFLAGS value. -__declspec(dllexport) void HalRestoreInterrupts(uintptr_t flags) { +void HalRestoreInterrupts(uintptr_t flags) { __writeeflags(flags); } // Enables maskable interrupts. -__declspec(dllexport) void HalEnableInterrupts(void) { +void HalEnableInterrupts(void) { _enable(); } // Enables interrupts and halts until an interrupt arrives. -__declspec(dllexport) void HalWaitForInterrupt(void) { +void HalWaitForInterrupt(void) { _enable(); __halt(); } // Halts once without changing interrupt state. -__declspec(dllexport) void HalHalt(void) { +void HalHalt(void) { __halt(); } // Masks interrupts and permanently stops the current processor. -__declspec(dllexport) _Noreturn void HalStopProcessor(void) { +_Noreturn void HalStopProcessor(void) { _disable(); while (true) __halt(); diff --git a/code/kernel/hal/hal_rtc.c b/code/kernel/hal/hal_rtc.c index 8c370f8..a8983dd 100644 --- a/code/kernel/hal/hal_rtc.c +++ b/code/kernel/hal/hal_rtc.c @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #define CMOS_INDEX_PORT 0x70 diff --git a/code/kernel/hal/hal_screen.c b/code/kernel/hal/hal_screen.c index 6574c1b..40933cd 100644 --- a/code/kernel/hal/hal_screen.c +++ b/code/kernel/hal/hal_screen.c @@ -5,12 +5,12 @@ // each byte is the leftmost pixel. #include #include -#include +#include #include #include -static uint32_t* gFb = (uint32_t *)0; -static uint32_t* gBackbuffer = (uint32_t *)0; +static uint32_t* gFb = (uint32_t*)0; +static uint32_t* gBackbuffer = (uint32_t*)0; static int gWidth = 0; static int gHeight = 0; static int gStride = 0; // pixels per row @@ -343,7 +343,7 @@ void HalScreenRegisterOutputEndpoint(HAL_OUTPUT_ROUTER* output) { // Populate the HAL_SCREEN vtable. void HalScreenInitialize(void* (*AllocatePool)(size_t size), uintptr_t fb, int w, int h, int stride, HAL_SCREEN* out) { - gFb = (uint32_t *)fb; + gFb = (uint32_t*)fb; gWidth = w; gHeight = h; gStride = stride; diff --git a/code/kernel/hal/hal_serial.c b/code/kernel/hal/hal_serial.c index 909242e..2bd3b6b 100644 --- a/code/kernel/hal/hal_serial.c +++ b/code/kernel/hal/hal_serial.c @@ -1,26 +1,51 @@ // HAL serial output endpoint. This file owns polling COM1 writes and subscribes serial to HAL text streams. #include +#include #include -#include +#include #include +#include -#define HAL_COM1_DATA_PORT 0x03F8u -#define HAL_COM1_STATUS_PORT 0x03FDu -#define HAL_COM1_TX_READY 0x20u +#define HAL_COM1_BASE 0x03F8u +#define HAL_COM1_DATA_PORT (HAL_COM1_BASE + 0u) +#define HAL_COM1_STATUS_PORT (HAL_COM1_BASE + 5u) +#define HAL_COM1_SCRATCH_PORT (HAL_COM1_BASE + 7u) +#define HAL_COM1_TX_READY 0x20u +#define HAL_SERIAL_TX_SPIN_LIMIT 1000000u static volatile long gSerialWriteLock; +static volatile long gSerialPresent; -// Waits until COM1 can accept one byte. -static void HalSerialWaitForTransmit(void) { - while ((__inbyte(HAL_COM1_STATUS_PORT) & HAL_COM1_TX_READY) == 0) - _mm_pause(); +// Probes the 16550 scratch register without relying on line-status values from an absent port. +static bool HalSerialProbeCom1(void) { + uint8_t original = __inbyte(HAL_COM1_SCRATCH_PORT); + + __outbyte(HAL_COM1_SCRATCH_PORT, 0x5Au); + uint8_t first = __inbyte(HAL_COM1_SCRATCH_PORT); + __outbyte(HAL_COM1_SCRATCH_PORT, 0xA5u); + uint8_t second = __inbyte(HAL_COM1_SCRATCH_PORT); + __outbyte(HAL_COM1_SCRATCH_PORT, original); + + return first == 0x5Au && second == 0xA5u; } -// Writes one byte to COM1. -static void HalSerialWriteByte(uint8_t value) { - HalSerialWaitForTransmit(); +// Waits for COM1 transmit readiness without allowing broken or removed hardware to stall the machine forever. +static bool HalSerialWaitForTransmit(void) { + for (uint32_t spin = 0; spin < HAL_SERIAL_TX_SPIN_LIMIT; spin++) { + if ((__inbyte(HAL_COM1_STATUS_PORT) & HAL_COM1_TX_READY) != 0) + return true; + _mm_pause(); + } + return false; +} + +// Writes one byte to COM1 when the device remains responsive. +static bool HalSerialWriteByte(uint8_t value) { + if (!HalSerialWaitForTransmit()) + return false; __outbyte(HAL_COM1_DATA_PORT, value); + return true; } // Acquires the normal serial writer lock. @@ -37,7 +62,8 @@ static void HalSerialReleaseWriteLock(void) { // Writes one output record through polling COM1. static void HalSerialWriteRecord(void* context, const HAL_OUTPUT_RECORD* record) { (void)context; - if (!record || !record->Buffer) + if (!record || !record->Buffer || + _InterlockedCompareExchange(&gSerialPresent, 0, 0) == 0) return; bool emergency = (record->Flags & HAL_OUTPUT_WRITE_EMERGENCY) != 0; @@ -47,24 +73,36 @@ static void HalSerialWriteRecord(void* context, const HAL_OUTPUT_RECORD* record) HalSerialAcquireWriteLock(); } - for (size_t i = 0; i < record->Length; i++) { + bool responsive = true; + for (size_t i = 0; i < record->Length && responsive; i++) { char c = record->Buffer[i]; if (c == '\n') - HalSerialWriteByte('\r'); - HalSerialWriteByte((uint8_t)c); + responsive = HalSerialWriteByte('\r'); + if (responsive) + responsive = HalSerialWriteByte((uint8_t)c); } + if (!responsive) + _InterlockedExchange(&gSerialPresent, 0); + if (!emergency) { HalSerialReleaseWriteLock(); HalRestoreInterrupts(interruptFlags); } } -// Registers the polling serial endpoint. +// Registers the polling serial endpoint only when a 16550-compatible COM1 device is present. void HalSerialInitialize(HAL_OUTPUT_ROUTER* output) { if (!output || !output->RegisterEndpoint) return; + gSerialWriteLock = 0; + gSerialPresent = 0; + if (!HalSerialProbeCom1()) + return; + + gSerialPresent = 1; + HAL_OUTPUT_ENDPOINT endpoint; endpoint.Name = "serial0"; endpoint.StreamMask = HAL_OUTPUT_STREAM_ALL; @@ -75,4 +113,6 @@ void HalSerialInitialize(HAL_OUTPUT_ROUTER* output) { uint32_t endpointId = HAL_OUTPUT_ENDPOINT_INVALID; if (KSUCCESS(output->RegisterEndpoint(&endpoint, &endpointId))) output->SerialEndpointId = endpointId; + else + gSerialPresent = 0; } diff --git a/code/kernel/hal/hal_timer.c b/code/kernel/hal/hal_timer.c index 8e08d48..c56d66f 100644 --- a/code/kernel/hal/hal_timer.c +++ b/code/kernel/hal/hal_timer.c @@ -2,8 +2,9 @@ #include #include -#include -#include +#include +#include +#include #include #define LAPIC_TIMER_LVT 0x320 @@ -19,27 +20,24 @@ #define TIMER_PERIOD_MS 10 #define PIT_HZ 1193182UL -extern HAL_API gApi; static volatile uint32_t* gLapic = NULL; static uint32_t gApicTicksPerUs = 0; static uint32_t gApicTicksPerQuantum = 0; static uint64_t gHpetTicksPerUs = 0; static uint64_t gHpetHz = 0; static volatile uint64_t* gHpet = NULL; -#define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off))) +#define LAPIC_REG(off) (*(volatile uint32_t*)((uint8_t*)gLapic + (off))) static volatile uint64_t gTicks = 0; -void (* volatile gTimerCallback)(void) = NULL; // Implements the outb operation. static inline void outb(uint16_t port, uint8_t val) { __outbyte(port, val); } // Implements the inb operation. static inline uint8_t inb(uint16_t port) { return __inbyte(port); } -// Implements the timer Isr operation. -static void TimerIsr(void) { +// Records one local timer interrupt for HAL timekeeping fallback. +static void TimerInterrupt(void) { gTicks++; - if (gTimerCallback) gTimerCallback(); // EOI is sent by KeIrqDispatch after this returns. } @@ -87,7 +85,7 @@ static uint64_t QueryTickFrequency(void) { } // Implements the stall operation. -static void Stall(uint32_t us) { +void HalTimerStall(uint32_t us) { // Priority 1: Use HPET (Highest Precision). if (gHpet && gHpetTicksPerUs > 0) { uint64_t start = gHpet[30]; // Offset 0xF0 is the Main Counter @@ -128,7 +126,7 @@ static void Stall(uint32_t us) { // Returns HPET ticks/second, or 0 if HPET is unavailable. static uint64_t HpetInit(uintptr_t hpetPhys) { // HPET register block, accessed through physical window. - volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys); + volatile uint64_t* hpet = (volatile uint64_t*)HalPhysicalToVirtual(hpetPhys); // General Capabilities register (offset 0): // Bits 63:32 = counter clock period in femtoseconds. @@ -167,7 +165,7 @@ static uint32_t CalibrateApicTimerViaPit(void) { // Use HPET main counter to measure how many APIC ticks elapse in 10 ms, then return ticks/ms. static uint32_t CalibrateApicTimerViaHpet(uintptr_t hpetPhys) { - volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys); + volatile uint64_t* hpet = (volatile uint64_t*)HalPhysicalToVirtual(hpetPhys); uint64_t hpetHz = HpetInit(hpetPhys); if (!hpetHz) return 0; @@ -187,7 +185,7 @@ static uint32_t CalibrateApicTimerViaHpet(uintptr_t hpetPhys) { } // Implements the timer Configure Current Cpu operation. -__declspec(dllexport) void HalTimerConfigureCurrentCpu(void) { +void HalTimerConfigureCurrentProcessor(void) { if (!gLapic || !gApicTicksPerQuantum) return; LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16; LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_PERIODIC | TIMER_VECTOR; @@ -195,8 +193,8 @@ __declspec(dllexport) void HalTimerConfigureCurrentCpu(void) { } // Implements the timer Initialize operation. -KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *out) { - gLapic = (volatile uint32_t *)gApi.PhysToVirt(lapicPhys); +KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER* out) { + gLapic = (volatile uint32_t*)HalPhysicalToVirtual(lapicPhys); HalDbgPrint("HAL: lapicPhys=0x%016llX, lapicVirt=0x%016llX, hpetPhys=0x%016llX\n", lapicPhys, (uintptr_t)gLapic, hpetPhys); uint32_t ticksPerMs = 0; @@ -217,7 +215,7 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o gApicTicksPerQuantum = ticksPerMs * TIMER_PERIOD_MS; if (hpetPhys) { - gHpet = (volatile uint64_t*)gApi.PhysToVirt(hpetPhys); + gHpet = (volatile uint64_t*)HalPhysicalToVirtual(hpetPhys); uint64_t hpetHz = HpetInit(hpetPhys); gHpetHz = hpetHz; gHpetTicksPerUs = hpetHz / 1000000; @@ -225,14 +223,14 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o HalDbgPrint("HAL: APIC timer: %u ticks/ms\n", ticksPerMs); - // Configure this CPU's LAPIC timer; APs call HalTimerConfigureCurrentCpu() - // after enabling their local APIC. - HalTimerConfigureCurrentCpu(); + // Leave the BSP timer masked until the kernel registers its scheduler + // callback and IRQ route. APs arm their local timer after per-CPU setup. out->QueryTicks = QueryTicks; out->QueryTickFrequency = QueryTickFrequency; - out->Stall = Stall; - out->Isr = TimerIsr; - out->IrqNumber = 0; + out->Stall = HalTimerStall; + out->HandleInterrupt = TimerInterrupt; + out->ConfigureCurrentProcessor = HalTimerConfigureCurrentProcessor; + out->IrqNumber = 0; return NTLSTATUS_SUCCESS; } diff --git a/code/public/ntlite/ddi/hal_api.h b/code/kernel/include/kernel/hal/hal_api.h similarity index 69% rename from code/public/ntlite/ddi/hal_api.h rename to code/kernel/include/kernel/hal/hal_api.h index 04c5f5d..33a03c8 100644 --- a/code/public/ntlite/ddi/hal_api.h +++ b/code/kernel/include/kernel/hal/hal_api.h @@ -1,13 +1,50 @@ -// Public NTLite hal api ABI declarations shared between kernel, drivers, and user mode. +// Private kernel-to-HAL interface table. This ABI is not exposed to drivers or user mode. #pragma once -// hal_api.h - HAL interface table exported as HalGetApi(). #include -#include #include #include -typedef struct BOOT_INFO BOOT_INFO; +#ifdef NTL_HAL_BUILD +#define HAL_ENTRY __declspec(dllexport) +#else +#define HAL_ENTRY __declspec(dllimport) +#endif + +#define HAL_MAX_PROCESSORS 32u + +typedef struct HAL_PROCESSOR_INFO { + uint32_t FirmwareId; + uint32_t HardwareId; + uint32_t Flags; + uint8_t Enabled; + uint8_t IsBootProcessor; + uint8_t Startable; + uint8_t Reserved; +} HAL_PROCESSOR_INFO; + +typedef struct HAL_PROCESSOR_TOPOLOGY { + uint32_t Count; + uint32_t BootProcessorHardwareId; + HAL_PROCESSOR_INFO Processors[HAL_MAX_PROCESSORS]; +} HAL_PROCESSOR_TOPOLOGY; + +typedef struct HAL_PROCESSOR { + uintptr_t (*DisableInterrupts)(void); + void (*RestoreInterrupts)(uintptr_t flags); + void (*EnableInterrupts)(void); + void (*WaitForInterrupt)(void); + void (*Halt)(void); + void (*Stop)(void); + KSTATUS (*QueryTopology)(HAL_PROCESSOR_TOPOLOGY* out); + KSTATUS (*Start)(uint32_t hardwareId, uintptr_t startPhysical); +} HAL_PROCESSOR; + +typedef struct HAL_MSI_MESSAGE { + uint64_t Address; + uint32_t Data; + uint32_t Reserved; +} HAL_MSI_MESSAGE; typedef struct HAL_FRAMEBUFFER_INFO { uintptr_t Base; // kernel virtual address of the visible framebuffer @@ -91,9 +128,10 @@ typedef struct HAL_OUTPUT_ROUTER { typedef struct HAL_TIMER { uint64_t (*QueryTicks)(void); uint64_t (*QueryTickFrequency)(void); - void (*Stall)(uint32_t us); - void (*Isr)(void); - uint8_t IrqNumber; + void (*Stall)(uint32_t us); + void (*HandleInterrupt)(void); + void (*ConfigureCurrentProcessor)(void); + uint8_t IrqNumber; } HAL_TIMER; #define HAL_RTC_TIME_FLAG_VALID 0x0001u @@ -133,12 +171,17 @@ typedef struct HAL_PCI { uint32_t value); } HAL_PCI; -typedef struct HAL_INTC { +typedef struct HAL_INTERRUPT_CONTROLLER { void (*RouteIrq)(uint8_t irq); void (*MaskIrq)(uint8_t irq); void (*UnmaskIrq)(uint8_t irq); void (*SendEoi)(void); -} HAL_INTC; + void (*InitializeCurrentProcessor)(void); + void (*SendProcessorInterrupt)(uint32_t hardwareId, uint8_t vector); + void (*SendProcessorNmi)(uint32_t hardwareId); + void (*BroadcastNmi)(void); + KSTATUS (*ComposeMsi)(uint8_t vector, HAL_MSI_MESSAGE* messageOut); +} HAL_INTERRUPT_CONTROLLER; typedef struct HAL_POWER { // ACPI S5 soft-off. Disables interrupts, writes SLP_EN to PM1a/b_CNT, @@ -147,18 +190,22 @@ typedef struct HAL_POWER { } HAL_POWER; typedef struct HAL_API { - uint32_t Version; - void *(*PhysToVirt)(uint64_t physAddr); - HAL_SCREEN Screen; + uint32_t Size; + uint32_t Version; + HAL_PROCESSOR Processor; + HAL_SCREEN Screen; HAL_OUTPUT_ROUTER Output; - HAL_TIMER Timer; - HAL_RTC Rtc; - HAL_PCI Pci; - HAL_INTC Intc; - HAL_POWER Power; + HAL_TIMER Timer; + HAL_RTC Rtc; + HAL_PCI Pci; + HAL_INTERRUPT_CONTROLLER InterruptController; + HAL_POWER Power; } HAL_API; typedef struct HAL_INIT_PARAMS { + uint32_t Size; + uint32_t Flags; + // Framebuffer. uintptr_t FramebufferBase; uint32_t FramebufferWidth; @@ -177,27 +224,11 @@ typedef struct HAL_INIT_PARAMS { void* (*AllocatePool)(size_t size); } HAL_INIT_PARAMS; -// Increment when the HAL API layout or semantics change when we go from indev to alpha. -#define HAL_API_VERSION 1 +// Increment whenever the kernel/HAL table ABI changes. +#define HAL_API_VERSION 2u -__declspec(dllexport) const HAL_API* HalGetApi(void); -__declspec(dllexport) void HalRegisterScheduler(void (*pKeSchedulerTick)(void)); -__declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* params); +// Returns the HAL table. Processor interrupt-state operations are valid before HalInitialize(). +HAL_ENTRY const HAL_API* HalGetApi(void); -// Masks interrupts and returns the previous RFLAGS value. -__declspec(dllexport) uintptr_t HalDisableInterrupts(void); - -// Restores the RFLAGS value returned by HalDisableInterrupts. -__declspec(dllexport) void HalRestoreInterrupts(uintptr_t flags); - -// Enables maskable interrupts. -__declspec(dllexport) void HalEnableInterrupts(void); - -// Enables interrupts and halts until the processor receives an interrupt. -__declspec(dllexport) void HalWaitForInterrupt(void); - -// Halts once without changing the interrupt state. -__declspec(dllexport) void HalHalt(void); - -// Masks interrupts and permanently stops the current processor. -__declspec(dllexport) _Noreturn void HalStopProcessor(void); +// Initializes platform discovery and the runtime portions of the HAL table. +HAL_ENTRY KSTATUS HalInitialize(const HAL_INIT_PARAMS* params); diff --git a/code/kernel/include/kernel/hal/hal_apic.h b/code/kernel/include/kernel/hal/hal_apic.h index fb3ff65..418503b 100644 --- a/code/kernel/include/kernel/hal/hal_apic.h +++ b/code/kernel/include/kernel/hal/hal_apic.h @@ -1,38 +1,22 @@ -// Kernel hal apic declarations shared by implementation files and kernel clients. +// HAL APIC implementation declarations. APIC details do not cross the HAL ABI. #pragma once + #include #include +#include void HalApicRouteIrq(uint8_t irq); void HalApicMaskIrq(uint8_t irq); void HalApicUnmaskIrq(uint8_t irq); void HalApicSendEoi(void); void HalApicInitialize(uintptr_t lapicPhys, uintptr_t ioapicPhys); - -#define HAL_MAX_CPUS 32u - -typedef struct HAL_CPU_INFO { - uint32_t ProcessorUid; - uint32_t LapicId; - uint32_t Flags; - uint8_t Enabled; - uint8_t IsBsp; -} HAL_CPU_INFO; - -typedef struct HAL_CPU_TOPOLOGY { - uint32_t CpuCount; - uint32_t BspLapicId; - HAL_CPU_INFO Cpus[HAL_MAX_CPUS]; -} HAL_CPU_TOPOLOGY; - -__declspec(dllexport) KSTATUS HalGetProcessorTopology(HAL_CPU_TOPOLOGY* out); -__declspec(dllexport) uint32_t HalGetBspLapicId(void); -__declspec(dllexport) uint8_t HalApicUsingX2Apic(void); -__declspec(dllexport) uint8_t HalApicCanAddressId(uint32_t lapicId); -__declspec(dllexport) void HalApicEnableCurrentCpu(void); -__declspec(dllexport) void HalApicSendPanicIpi(void); -__declspec(dllexport) void HalApicSendRescheduleIpi(uint32_t lapicId); -__declspec(dllexport) void HalApicSendTlbShootdownIpi(uint32_t lapicId); -__declspec(dllexport) void HalTimerConfigureCurrentCpu(void); -__declspec(dllexport) KSTATUS HalStartApplicationProcessor(uint32_t lapicId, uint8_t startupVector); +uint32_t HalApicGetBootProcessorId(void); +uint8_t HalApicUsingX2Apic(void); +uint8_t HalApicCanAddressId(uint32_t hardwareId); +void HalApicInitializeCurrentProcessor(void); +void HalApicBroadcastNmi(void); +void HalApicSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector); +void HalApicSendProcessorNmi(uint32_t hardwareId); +KSTATUS HalApicStartProcessor(uint32_t hardwareId, uintptr_t startPhysical); +KSTATUS HalApicComposeMsi(uint8_t vector, HAL_MSI_MESSAGE* messageOut); diff --git a/code/kernel/include/kernel/hal/hal_internal.h b/code/kernel/include/kernel/hal/hal_internal.h new file mode 100644 index 0000000..ee90b98 --- /dev/null +++ b/code/kernel/include/kernel/hal/hal_internal.h @@ -0,0 +1,7 @@ +// Shared HAL implementation helpers that are not part of the kernel/HAL ABI. + +#pragma once + +#include + +void* HalPhysicalToVirtual(uint64_t physicalAddress); diff --git a/code/kernel/include/kernel/hal/hal_output.h b/code/kernel/include/kernel/hal/hal_output.h index 7f9bf95..59e3af1 100644 --- a/code/kernel/include/kernel/hal/hal_output.h +++ b/code/kernel/include/kernel/hal/hal_output.h @@ -2,7 +2,7 @@ #pragma once -#include +#include // Initializes the fixed HAL output endpoint table. void HalOutputInitialize(HAL_OUTPUT_ROUTER* output); diff --git a/code/kernel/include/kernel/hal/hal_processor.h b/code/kernel/include/kernel/hal/hal_processor.h new file mode 100644 index 0000000..5334c64 --- /dev/null +++ b/code/kernel/include/kernel/hal/hal_processor.h @@ -0,0 +1,12 @@ +// HAL processor-control declarations used within hal.dll. + +#pragma once + +#include + +uintptr_t HalDisableInterrupts(void); +void HalRestoreInterrupts(uintptr_t flags); +void HalEnableInterrupts(void); +void HalWaitForInterrupt(void); +void HalHalt(void); +_Noreturn void HalStopProcessor(void); diff --git a/code/kernel/include/kernel/hal/hal_timer.h b/code/kernel/include/kernel/hal/hal_timer.h new file mode 100644 index 0000000..f8531fd --- /dev/null +++ b/code/kernel/include/kernel/hal/hal_timer.h @@ -0,0 +1,10 @@ +// Internal HAL timer declarations shared by HAL implementation files. + +#pragma once + +#include +#include +#include + +KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER* out); +void HalTimerStall(uint32_t microseconds); diff --git a/code/kernel/include/kernel/io_ddk.h b/code/kernel/include/kernel/io_ddk.h index a2c5b12..398de0f 100644 --- a/code/kernel/include/kernel/io_ddk.h +++ b/code/kernel/include/kernel/io_ddk.h @@ -1,6 +1,7 @@ // Kernel DDK-table provider declarations. #pragma once +#include #include @@ -8,7 +9,7 @@ struct HAL_API; typedef struct HAL_API HAL_API; // Initializes the kernel-owned DDK table from platform services. -void IoInitializeDdk(const HAL_API* hal); +void IoInitializeDdk(const HAL_API* hal, uintptr_t physicalWindowBase); // Returns the kernel-owned DDK table passed to driver entrypoints. const NTL_DDK* IoGetDdk(void); diff --git a/code/kernel/include/kernel/io_file.h b/code/kernel/include/kernel/io_file.h index 34982d6..af53b73 100644 --- a/code/kernel/include/kernel/io_file.h +++ b/code/kernel/include/kernel/io_file.h @@ -1,26 +1,26 @@ // Kernel I/O file declarations shared by filesystem, cache, and syscall code. #pragma once -// io_file.h - VFS and filesystem provider interface. -// Current kernel-mode filesystem drivers still use the legacy file-provider -// layout. The shared declarations live in the DDK while the real provider split -// is being designed; this private header adds kernel-only async and lock entry -// points without redefining the shared types. +// The public DDK defines IO_VOLUME and provider dispatch. This private header +// adds kernel-only async and file-lock entry points. #include #include struct PROCESS; -// Initializes filesystem namespace change notification before drivers register volumes. -KSTATUS IoInitializeVolumeNamespace(void); +// Capability-relative opens rooted at an already-open directory. +KSTATUS IoOpenFileAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, FILE_OBJECT** out); +KSTATUS IoCreateFileAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, FILE_OBJECT** out); +KSTATUS IoOpenDirAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, DIR_OBJECT** out); -// Returns the generation of the mounted-volume namespace. -uint64_t IoGetVolumeNamespaceGeneration(void); - -// Waits until the mounted-volume namespace changes or the timeout expires. -KSTATUS IoWaitForVolumeNamespaceChange(uint64_t observedGeneration, - uint64_t timeoutMilliseconds); +// Generic capability-rooted namespace operations used by the syscall layer. +KSTATUS IoStatAtRoot(OBJECT* root, SYS_STRING_VIEW path, SYS_FILE_STAT* out); +KSTATUS IoDeleteFileAtRoot(OBJECT* root, SYS_STRING_VIEW path); +KSTATUS IoRenameFileAtRoots(OBJECT* oldRoot, SYS_STRING_VIEW oldPath, + OBJECT* newRoot, SYS_STRING_VIEW newPath); +KSTATUS IoCreateDirAtRoot(OBJECT* root, SYS_STRING_VIEW path); +KSTATUS IoRemoveDirAtRoot(OBJECT* root, SYS_STRING_VIEW path); __declspec(dllexport) KSTATUS IoReadFileAsyncKernel(FILE_OBJECT* file, void* buf, diff --git a/code/kernel/include/kernel/io_partition.h b/code/kernel/include/kernel/io_partition.h new file mode 100644 index 0000000..387c61b --- /dev/null +++ b/code/kernel/include/kernel/io_partition.h @@ -0,0 +1,12 @@ +// Kernel partition discovery declarations. This file keeps structural partition identity private to the I/O core. + +#pragma once + +#include +#include + +// Initializes dynamic partition discovery and the mandatory structural system-volume selector. +KSTATUS IoInitializePartitionManager(const char* systemVolumeSelector); + +// Returns the uniquely selected system partition, or NOT_FOUND/COLLISION while discovery is incomplete or ambiguous. +KSTATUS IoQuerySystemPartition(IO_PARTITION_INFO* partitionOut); diff --git a/code/kernel/include/kernel/io_volume.h b/code/kernel/include/kernel/io_volume.h new file mode 100644 index 0000000..55df4f9 --- /dev/null +++ b/code/kernel/include/kernel/io_volume.h @@ -0,0 +1,111 @@ +// Kernel mounted-volume core declarations. + +#pragma once + +#include +#include +#include + +typedef enum IO_VOLUME_STATE { + IoVolumeMounted = 1, + IoVolumeDead, +} IO_VOLUME_STATE; + +typedef struct IO_VOLUME_MANAGER { + OBJECT Header; +} IO_VOLUME_MANAGER; + +typedef struct IO_VOLUME_SNAPSHOT_ENTRY { + uint32_t InstanceId; +} IO_VOLUME_SNAPSHOT_ENTRY; + +typedef struct IO_VOLUME_STATS { + uint64_t CreatedVolumes; + uint64_t MountedVolumes; + uint64_t RemovedVolumes; + uint64_t DeadIoRejects; + uint64_t ProviderDestroys; +} IO_VOLUME_STATS; + +typedef struct IO_VOLUME_MANAGER_STATS { + uint64_t Snapshots; + uint64_t VolumeOpens; + uint64_t Waits; + uint64_t Wakeups; +} IO_VOLUME_MANAGER_STATS; + +struct IO_VOLUME { + OBJECT Header; + LIST_ENTRY RegistryLink; + SPINLOCK Lock; + uint32_t InstanceId; + uint32_t State; + uint32_t RemovalReason; + uint8_t RegistryLinked; + IO_VOLUME_SOURCE Source; + DEVICE_OBJECT* BackingDevice; + IO_VOLUME_PROVIDER Provider; +}; + +// Initializes dynamic mounted-volume discovery notifications before providers start. +KSTATUS IoInitializeVolumeManager(void); + +// References the persistent kernel volume-manager authority object. +KSTATUS IoReferenceVolumeManager(IO_VOLUME_MANAGER** managerOut); + +// Returns a stable value snapshot of every currently mounted volume. The caller frees the returned array with MmFreePool. +KSTATUS IoSnapshotVolumes(IO_VOLUME_SNAPSHOT_ENTRY** entriesOut, uint32_t* countOut, uint64_t* generationOut); + +// References one mounted volume by its monotonic runtime instance identity. +KSTATUS IoReferenceVolumeByInstanceId(uint32_t instanceId, IO_VOLUME** volumeOut); + +// Returns the current dynamic mounted-volume registry generation. +uint64_t IoGetVolumeGeneration(void); + +// Waits until the dynamic mounted-volume registry changes or the timeout expires. +KSTATUS IoWaitForVolumeChange(uint64_t observedGeneration, uint64_t timeoutMilliseconds); + +// Acquires a reference only while the volume is still mounted. +bool IoVolumeTryReferenceMounted(IO_VOLUME* volume); + +// References the unique mounted volume backed by one exact disk extent start. +KSTATUS IoReferenceVolumeByBacking(uint8_t diskIndex, uint64_t startLba, IO_VOLUME** volumeOut); + +// Returns whether provider I/O is still permitted on this volume. +bool IoVolumeIsMounted(const IO_VOLUME* volume); + +// Returns whether the mounted provider implements native async file I/O. +bool IoVolumeSupportsReadAsync(const IO_VOLUME* volume); +bool IoVolumeSupportsWriteAsync(const IO_VOLUME* volume); + +void IoQueryVolumeStats(IO_VOLUME_STATS* out); +void IoQueryVolumeManagerStats(IO_VOLUME_MANAGER_STATS* out); + +// Provider dispatch. All operations except Close/CloseDir reject dead volumes. +KSTATUS IoVolumeOpen(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx); +KSTATUS IoVolumeRead(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + void* buffer, size_t size, size_t* bytesRead); +KSTATUS IoVolumeWrite(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + const void* buffer, size_t size, size_t* bytesWritten); +KSTATUS IoVolumeReadAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete, + void* completeContext); +KSTATUS IoVolumeWriteAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + const void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete, + void* completeContext); +KSTATUS IoVolumeCreate(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx); +KSTATUS IoVolumeDelete(IO_VOLUME* volume, SYS_STRING_VIEW path); +KSTATUS IoVolumeRename(IO_VOLUME* volume, SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath); +KSTATUS IoVolumeCreateDir(IO_VOLUME* volume, SYS_STRING_VIEW path); +KSTATUS IoVolumeRemoveDir(IO_VOLUME* volume, SYS_STRING_VIEW path); +KSTATUS IoVolumeTruncate(IO_VOLUME* volume, void* fileCtx, uint64_t newSize, uint32_t fileFlags); +KSTATUS IoVolumeGetSize(IO_VOLUME* volume, void* fileCtx, uint64_t* sizeOut); +KSTATUS IoVolumeQueryInfo(IO_VOLUME* volume, void* fileCtx, SYS_FILE_STAT* out); +KSTATUS IoVolumeStat(IO_VOLUME* volume, SYS_STRING_VIEW path, SYS_FILE_STAT* out); +KSTATUS IoVolumeQueryIdentity(IO_VOLUME* volume, void* fileCtx, IO_FILE_IDENTITY* out); +KSTATUS IoVolumeFlush(IO_VOLUME* volume, void* fileCtx); +void IoVolumeClose(IO_VOLUME* volume, void* fileCtx); +void IoVolumeQueryStats(IO_VOLUME* volume, IO_FILESYSTEM_STATS* out); +KSTATUS IoVolumeOpenDir(IO_VOLUME* volume, SYS_STRING_VIEW path, void** dirCtx); +KSTATUS IoVolumeReadDir(IO_VOLUME* volume, void* dirCtx, KDIR_ENTRY* out); +void IoVolumeCloseDir(IO_VOLUME* volume, void* dirCtx); diff --git a/code/kernel/include/kernel/ke_audio.h b/code/kernel/include/kernel/ke_audio.h index eea1974..4e081d1 100644 --- a/code/kernel/include/kernel/ke_audio.h +++ b/code/kernel/include/kernel/ke_audio.h @@ -1,4 +1,4 @@ -// Kernel audio-core declarations shared by the mixer, syscall layer, and selftests. +// Kernel audio-core declarations shared by the mixer and syscall layer. #pragma once @@ -52,7 +52,7 @@ typedef struct KE_AUDIO_STREAM KE_AUDIO_STREAM; // Reports one endpoint snapshot without exposing provider-owned pointers. typedef struct KE_AUDIO_ENDPOINT_INFO { - uint64_t EndpointId; + uint32_t EndpointId; IO_AUDIO_ENDPOINT_KIND Kind; KE_AUDIO_ENDPOINT_STATE State; uint32_t Capabilities; @@ -123,10 +123,10 @@ KSTATUS KeAudioQueryEndpoints(KE_AUDIO_ENDPOINT_INFO* endpoints, uint32_t* countOut); // Returns the currently selected endpoint identifier, or zero when none is usable. -uint64_t KeAudioGetDefaultEndpointId(void); +uint32_t KeAudioGetDefaultEndpointId(void); // Creates one fixed-format logical render stream and returns a caller-owned reference. -KSTATUS KeAudioCreateStream(uint64_t endpointId, +KSTATUS KeAudioCreateStream(uint32_t endpointId, const IO_AUDIO_FORMAT* format, KE_AUDIO_STREAM** outStream); @@ -174,10 +174,7 @@ KSTATUS KeAudioSetStreamVolume(KE_AUDIO_STREAM* stream, bool muted); // Updates endpoint master software gain and separate logical mute state. -KSTATUS KeAudioSetMasterVolume(uint64_t endpointId, float volume, bool muted); +KSTATUS KeAudioSetMasterVolume(uint32_t endpointId, float volume, bool muted); // Copies one coherent stream snapshot. KSTATUS KeAudioQueryStream(KE_AUDIO_STREAM* stream, KE_AUDIO_STREAM_INFO* infoOut); - -// Runs the final render path selftest behind KE_DEBUG_AUDIO_SELFTEST. -void KiRunAudioFinalPathSelfTest(void); diff --git a/code/kernel/include/kernel/ke_boot_config.h b/code/kernel/include/kernel/ke_boot_config.h new file mode 100644 index 0000000..684b7a1 --- /dev/null +++ b/code/kernel/include/kernel/ke_boot_config.h @@ -0,0 +1,20 @@ +// Kernel boot configuration declarations for parsing the raw mandatory boot.ini handoff. + +#pragma once + +#include +#include +#include + +#define KE_BOOT_COMMAND_LINE_MAX 256u +#define KE_BOOT_SYSTEM_VOLUME_MAX 128u + +typedef struct KE_BOOT_CONFIG { + uint32_t Debug; + // Opaque system-volume selector interpreted by storage policy, never as a legacy drive slot. + char SystemVolume[KE_BOOT_SYSTEM_VOLUME_MAX]; + char CommandLine[KE_BOOT_COMMAND_LINE_MAX]; +} KE_BOOT_CONFIG; + +// Parses the kernel-owned boot.ini settings from the raw bootloader string. +KSTATUS KeParseBootConfiguration(const char* text, size_t length, KE_BOOT_CONFIG* configOut); diff --git a/code/kernel/include/kernel/ke_cpu.h b/code/kernel/include/kernel/ke_cpu.h index 7e373a4..de05b5d 100644 --- a/code/kernel/include/kernel/ke_cpu.h +++ b/code/kernel/include/kernel/ke_cpu.h @@ -33,15 +33,15 @@ typedef struct KCPU { struct KCPU* Self; uint32_t CpuIndex; - uint32_t LapicId; - uint32_t AcpiProcessorId; + uint32_t HardwareId; + uint32_t FirmwareId; uint32_t BootProcessorIndex; volatile long State; - uint8_t IsBsp; + uint8_t IsBootProcessor; // CPUID/MADT-derived SMT topology. PackageId/CoreId/ThreadId are decoded - // from the APIC ID using CPUID leaf 0x1F/0x0B when available, with a + // from the processor hardware ID using CPUID leaf 0x1F/0x0B when available, with a // conservative CPUID(1)/CPUID(4) fallback. PhysicalCoreIndex is a compact // kernel-local core number used by the scheduler. CoreSiblingMask contains // all logical CPUs that share this physical core. PackageMask contains all diff --git a/code/kernel/include/kernel/ke_debug.h b/code/kernel/include/kernel/ke_debug.h index 946a061..de7bdce 100644 --- a/code/kernel/include/kernel/ke_debug.h +++ b/code/kernel/include/kernel/ke_debug.h @@ -7,14 +7,14 @@ #include #include #include -#include +#include #include void KeInitDebug(const HAL_API* hal, uint32_t flags); uint32_t KeGetDebugFlags(void); __declspec(dllexport) void DbgPrint(const char* fmt, ...); __declspec(dllexport) void KePrintConsole(const char* buf, size_t len); -__declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path); +KSTATUS KeFlushDebugLogToSystemRoot(const char* relativePath); __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...); __declspec(dllexport) int KeHandleNmiIpi(void); diff --git a/code/kernel/include/kernel/ke_display.h b/code/kernel/include/kernel/ke_display.h index 66204b0..c1b7897 100644 --- a/code/kernel/include/kernel/ke_display.h +++ b/code/kernel/include/kernel/ke_display.h @@ -2,7 +2,7 @@ #pragma once #include -#include +#include #ifndef DISPLAY_API #ifdef KE_DISPLAY_BUILD diff --git a/code/kernel/include/kernel/ke_hal.h b/code/kernel/include/kernel/ke_hal.h new file mode 100644 index 0000000..03555cf --- /dev/null +++ b/code/kernel/include/kernel/ke_hal.h @@ -0,0 +1,23 @@ +// Kernel HAL binding. This file keeps direct hal.dll imports out of kernel subsystems. + +#pragma once + +#include +#include +#include +#include + +KSTATUS KeBindBootstrapHal(void); +KSTATUS KeInitializeHal(const HAL_INIT_PARAMS* params); +const HAL_API* KeGetHal(void); +void KeEnableInterrupts(void); +void KeWaitForInterrupt(void); +void KeHaltProcessor(void); +_Noreturn void KeStopProcessor(void); +KSTATUS KeQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* topologyOut); +KSTATUS KeStartProcessor(uint32_t hardwareId, uintptr_t startPhysical); +void KeInitializeCurrentProcessorInterruptController(void); +void KeSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector); +void KeSendProcessorNmi(uint32_t hardwareId); +void KeBroadcastProcessorNmi(void); +void KeConfigureCurrentProcessorTimer(void); diff --git a/code/kernel/include/kernel/ke_handle.h b/code/kernel/include/kernel/ke_handle.h new file mode 100644 index 0000000..bda4c41 --- /dev/null +++ b/code/kernel/include/kernel/ke_handle.h @@ -0,0 +1,85 @@ +// Kernel capability handle declarations shared by processes and syscall code. + +#pragma once + +#include +#include +#include +#include +#include + +#define KE_HANDLE_BUCKET_COUNT 256u +#define KE_CAPABILITY_MAX_DEPTH 16u + +struct PROCESS; +struct KE_HANDLE_ENTRY; +struct KE_CAPTURED_HANDLE; + +typedef struct KE_HANDLE_TABLE { + struct KE_HANDLE_ENTRY* Buckets[KE_HANDLE_BUCKET_COUNT]; + SYS_HANDLE NextHandle; + uint32_t Count; + uint32_t Rundown; + SPINLOCK Lock; +} KE_HANDLE_TABLE; + +// Initializes an empty process-local handle namespace. +void KeInitializeHandleTable(KE_HANDLE_TABLE* table); + +// Releases every handle and capability grant owned by one process. +void KeRundownHandleTable(struct PROCESS* process); + +// Creates one root capability handle for an object. +KSTATUS KeCreateHandle(struct PROCESS* process, OBJECT* object, SYS_RIGHTS access, SYS_HANDLE* handleOut); + +// Closes one process-local handle without reusing its numeric identity. +KSTATUS KeCloseHandle(struct PROCESS* process, SYS_HANDLE handle); + +// References the target object after validating type, revocation, and rights. +KSTATUS KeReferenceHandle(struct PROCESS* process, + SYS_HANDLE handle, + OBJECT_TYPE expectedType, + SYS_RIGHTS requiredRights, + OBJECT** objectOut); + +// Duplicates one alias of the same capability grant into another process. +KSTATUS KeDuplicateHandle(struct PROCESS* sourceProcess, + SYS_HANDLE sourceHandle, + struct PROCESS* targetProcess, + SYS_RIGHTS desiredRights, + uint32_t duplicateFlags, + SYS_HANDLE* targetHandleOut); + +// Derives one child grant whose maximum rights are an attenuated subset. +KSTATUS KeDeriveHandle(struct PROCESS* process, + SYS_HANDLE sourceHandle, + SYS_RIGHTS desiredRights, + SYS_HANDLE* derivedHandleOut); + +// Revokes one grant and all grants derived from it. +KSTATUS KeRevokeHandle(struct PROCESS* process, SYS_HANDLE handle); + +#define KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS 0x00000001u +#define KE_HANDLE_TRANSFER_FLAG_DERIVE 0x00000002u + +// Captures one transferable capability independently of the sender's handle lifetime. +KSTATUS KeCaptureHandleForTransfer(struct PROCESS* sourceProcess, + SYS_HANDLE sourceHandle, + SYS_RIGHTS desiredRights, + uint32_t flags, + struct KE_CAPTURED_HANDLE** capturedOut); + +// Releases one captured capability that was not materialized. +void KeReleaseCapturedHandle(struct KE_CAPTURED_HANDLE* captured); + +// Atomically materializes captured capabilities as new handles in one target process. +KSTATUS KeMaterializeCapturedHandles(struct PROCESS* targetProcess, + struct KE_CAPTURED_HANDLE** captured, + uint32_t count, + SYS_HANDLE* handlesOut); + +// Returns the number of currently live handles in one process. +uint32_t KeGetHandleCount(struct PROCESS* process); + +// Returns the object-type-specific rights mask accepted by the handle core. +SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type); diff --git a/code/kernel/include/kernel/ke_id.h b/code/kernel/include/kernel/ke_id.h new file mode 100644 index 0000000..d7bdefd --- /dev/null +++ b/code/kernel/include/kernel/ke_id.h @@ -0,0 +1,15 @@ +// Kernel monotonic identifier allocator declarations. + +#pragma once + +#include +#include + +typedef struct KE_ID_ALLOCATOR { + volatile long long Next; +} KE_ID_ALLOCATOR; + +#define KE_ID_ALLOCATOR_INIT(first_) { (long long)(uint32_t)(first_) } + +// Allocates one 32-bit identifier and never reuses an issued value during the boot. +KSTATUS KeAllocateId32(KE_ID_ALLOCATOR* allocator, uint32_t* idOut); diff --git a/code/kernel/include/kernel/ke_idt.h b/code/kernel/include/kernel/ke_idt.h index f26c869..5a4ca92 100644 --- a/code/kernel/include/kernel/ke_idt.h +++ b/code/kernel/include/kernel/ke_idt.h @@ -52,5 +52,5 @@ extern void KeFaultUD(void); // #UD invalid opcode extern void KeNmiIpiStub(void); // returning NMI SMP-control stub // CPU-local IPI vector. This uses the common interrupt entry machinery but is -// not an external/device IRQ and is never routed through the I/O APIC. +// not an external/device IRQ and is never routed through the external interrupt controller. #define KE_VECTOR_RESCHEDULE_IPI 0xF0u diff --git a/code/kernel/include/kernel/ke_input.h b/code/kernel/include/kernel/ke_input.h index 96ea98b..ba14b33 100644 --- a/code/kernel/include/kernel/ke_input.h +++ b/code/kernel/include/kernel/ke_input.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/code/kernel/include/kernel/ke_irq.h b/code/kernel/include/kernel/ke_irq.h index f4c2560..51ca8a1 100644 --- a/code/kernel/include/kernel/ke_irq.h +++ b/code/kernel/include/kernel/ke_irq.h @@ -2,10 +2,10 @@ #pragma once #include -#include +#include #include -void KeIrqInitialize(const HAL_API *hal); +void KeIrqInitialize(const HAL_API* hal); __declspec(dllexport) KSTATUS KeIrqAllocateMsiVector(uint8_t* outIrq); __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)); void KeIrqDispatch(uint8_t irq); // called from ke_syscall.asm diff --git a/code/kernel/include/kernel/ke_named_queue.h b/code/kernel/include/kernel/ke_named_queue.h index 1434662..90982b0 100644 --- a/code/kernel/include/kernel/ke_named_queue.h +++ b/code/kernel/include/kernel/ke_named_queue.h @@ -3,9 +3,10 @@ #pragma once #include +#include struct PROCESS; struct OBJECT; // Removes registry entries owned by the given process handle/object pair. -void KeNamedQueueUnpublishHandle(struct PROCESS* process, uint32_t handle, struct OBJECT* object); +void KeNamedQueueUnpublishHandle(struct PROCESS* process, SYS_HANDLE handle, struct OBJECT* object); diff --git a/code/kernel/include/kernel/ke_port.h b/code/kernel/include/kernel/ke_port.h new file mode 100644 index 0000000..1846c47 --- /dev/null +++ b/code/kernel/include/kernel/ke_port.h @@ -0,0 +1,98 @@ +// Kernel Port IPC declarations. + +#pragma once + +#include +#include +#include +#include + +struct PROCESS; +struct THREAD; + +typedef struct PORT PORT; + +typedef struct KE_PORT_STATS { + uint64_t ConnectionsCreated; + uint64_t DirectDeliveries; + uint64_t DirectBytes; + uint64_t QueuedMessages; + uint64_t QueuedBytes; + uint64_t QueuedDeliveries; + uint64_t RepliesCompleted; + uint64_t Disconnects; + uint64_t ProcessQuotaRejects; +} KE_PORT_STATS; + +// Copies a lock-free diagnostic snapshot of Port transport counters. +void KePortQueryStats(KE_PORT_STATS* statsOut); + +// Creates one waitable connection-listener Port. +KSTATUS KePortCreate(uint32_t backlog, PORT** portOut); + +// Returns whether one Port object is a listener rather than a connected endpoint. +bool KePortIsListener(const PORT* port); + +// Returns the passive wait event for a listener or endpoint. +EVENT* KePortWaitEvent(PORT* port); + +// Connects one process to a listener and returns its new endpoint handle. +KSTATUS KePortConnect(PORT* listener, + struct PROCESS* clientProcess, + struct THREAD* clientThread, + SYS_HANDLE* endpointHandleOut); + +// Accepts one queued connection and returns a new server endpoint handle. +KSTATUS KePortAccept(PORT* listener, + struct PROCESS* serverProcess, + uint64_t timeoutMilliseconds, + SYS_HANDLE* endpointHandleOut, + SYS_PORT_CONNECTION_INFO* infoOut); + +// Sends one copied one-way notification, with direct user-to-user delivery when a receiver is already waiting. +KSTATUS KePortSend(PORT* endpoint, + struct PROCESS* senderProcess, + struct THREAD* senderThread, + const void* payload, + uint32_t payloadLength, + const SYS_PORT_HANDLE_TRANSFER* transfers, + uint32_t transferCount, + uint64_t timeoutMilliseconds); + +// Receives one request or notification and materializes transferred capabilities in the receiving process. +KSTATUS KePortReceive(PORT* endpoint, + struct PROCESS* receiverProcess, + void* payload, + uint32_t payloadCapacity, + SYS_HANDLE* handlesOut, + uint32_t handleCapacity, + SYS_PORT_MESSAGE_INFO* infoOut, + uint64_t timeoutMilliseconds); + +// Performs one synchronous copied request/reply exchange. +KSTATUS KePortCall(PORT* endpoint, + struct PROCESS* callerProcess, + struct THREAD* callerThread, + const void* requestPayload, + uint32_t requestLength, + const SYS_PORT_HANDLE_TRANSFER* requestTransfers, + uint32_t requestTransferCount, + void* replyPayload, + uint32_t replyCapacity, + SYS_HANDLE* replyHandlesOut, + uint32_t replyHandleCapacity, + SYS_PORT_MESSAGE_INFO* replyInfoOut, + uint64_t timeoutMilliseconds); + +// Replies to one request previously delivered on this endpoint. +KSTATUS KePortReply(PORT* endpoint, + struct PROCESS* senderProcess, + struct THREAD* senderThread, + uint32_t messageId, + const void* payload, + uint32_t payloadLength, + const SYS_PORT_HANDLE_TRANSFER* transfers, + uint32_t transferCount); + +// Disconnects one endpoint and wakes peer waiters and pending synchronous callers. +KSTATUS KePortDisconnect(PORT* endpoint); diff --git a/code/kernel/include/kernel/ke_process.h b/code/kernel/include/kernel/ke_process.h index b33d805..9cfd08a 100644 --- a/code/kernel/include/kernel/ke_process.h +++ b/code/kernel/include/kernel/ke_process.h @@ -8,48 +8,26 @@ #include #include #include +#include #include #include #include +#include #define PROCESS_NAME_MAX 64 -#define HANDLE_TABLE_SIZE 256 -#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 #define KE_PROCESS_FLAG_TERMINATION_PROTECTED 0x00000004u -#define HANDLE_RIGHT_READ 0x00000001u -#define HANDLE_RIGHT_WRITE 0x00000002u -#define HANDLE_RIGHT_WAIT 0x00000004u -#define HANDLE_RIGHT_SIGNAL 0x00000008u -#define HANDLE_RIGHT_DUPLICATE 0x00000010u -#define HANDLE_RIGHT_QUERY 0x00000020u -#define HANDLE_RIGHT_TERMINATE 0x00000040u -#define HANDLE_RIGHT_LOCK 0x00000080u -#define HANDLE_RIGHT_RESUME 0x00000100u -#define HANDLE_RIGHT_ALL 0xFFFFFFFFu +#define KE_PROCESS_BUILD_STARTED 0l +#define KE_PROCESS_BUILD_CONSTRUCTING 1l +#define KE_PROCESS_BUILD_STARTING 2l +#define KE_PROCESS_BUILD_DEAD 3l struct SYS_EXCEPTION_STATS; +struct SECURITY_CONTEXT; -typedef struct HANDLE_ENTRY { - OBJECT* Object; - uint32_t Rights; - uint32_t Flags; -} HANDLE_ENTRY; - -typedef struct PROCESS_MODULE { - 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 { OBJECT Header; // must be first @@ -58,27 +36,36 @@ typedef struct PROCESS { uint32_t Flags; char Name[PROCESS_NAME_MAX]; // UTF-8 - char CurrentDirectory[PROCESS_CWD_MAX]; - size_t CurrentDirectoryLength; - uint8_t CurrentDrive; - SPINLOCK CwdLock; // Live thread set. Dead THREAD objects may outlive this membership while // retained handles remain waitable and queryable. LIST_ENTRY ThreadList; // links THREAD.ProcessLink uint32_t ThreadCount; + SPINLOCK ThreadLock; - // Handle table. - // Index 0 is always empty (the invalid handle). - // Each live entry holds one object reference plus rights metadata. - HANDLE_ENTRY Handles[HANDLE_TABLE_SIZE]; - SPINLOCK HandleLock; + // Process-local capability namespace. Handle IDs are monotonic and never reused. + KE_HANDLE_TABLE HandleTable; + + // Port IPC kernel-resource accounting. Direct and queued messages are charged to the sender. + SPINLOCK PortQuotaLock; + uint32_t PortOutstandingMessages; + uint32_t PortOutstandingBytes; + uint32_t PortOutstandingHandles; + uint32_t PortOutstandingCalls; + + // Immutable process authority. Address-space reachability is not security authority. + struct SECURITY_CONTEXT* SecurityContext; int32_t ExitCode; volatile long Terminating; volatile long Exited; volatile long ResourcesReleased; + // Userspace image construction is authorized by SYS_PROCESS_RIGHT_BUILD. + // BuildOperations closes the race between memory construction and the one-way start transition. + volatile long BuildState; + volatile long BuildOperations; + // Main executable image. Used for cleanup. uintptr_t ImageBase; size_t ImageSize; @@ -90,8 +77,8 @@ typedef struct PROCESS { size_t UserImagePathSize; uintptr_t UserCmdlineBase; size_t UserCmdlineSize; - uintptr_t UserCwdBase; - size_t UserCwdSize; + uintptr_t UserStartupCapabilitiesBase; + size_t UserStartupCapabilitiesSize; // Per-process user virtual regions. The actual VA arena is global because // NT-Lite currently uses one shared address space; this list tracks @@ -99,11 +86,6 @@ typedef struct PROCESS { LIST_ENTRY VadList; SPINLOCK VadLock; - // Per-process loaded modules (user processes only). - PROCESS_MODULE Modules[PROCESS_MODULE_MAX]; - uint32_t ModuleCount; - SPINLOCK ModuleLock; - // Native structured-exception dispatcher registration. The user-mode // personality owns VEH, SEH, signal, and unwind policy behind this hook. uintptr_t ExceptionDispatcher; @@ -129,12 +111,16 @@ typedef struct PROCESS { LIST_ENTRY GlobalLink; // node in the global process list } PROCESS; -KSTATUS KeCreateProcess(const char* name, PROCESS **out); +KSTATUS KeCreateProcess(const char* name, struct SECURITY_CONTEXT* securityContext, PROCESS** out); KSTATUS KeCreateSystemProcess(PROCESS** out); KSTATUS KeSetupUserProcessEnvironment(PROCESS* process, SYS_STRING_VIEW imagePath, - SYS_STRING_VIEW commandLine, - SYS_STRING_VIEW currentDirectory); + SYS_STRING_VIEW commandLine); +KSTATUS KeSetProcessStartupCapabilities(PROCESS* process, + const SYS_STARTUP_CAPABILITY* capabilities, + uint32_t count); +KSTATUS KeSetProcessLoaderData(PROCESS* process, const void* loaderData); +void KeStopProcessConstruction(PROCESS* process); KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode); KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode); void KeCompleteProcessExitIfLastThread(PROCESS* process); @@ -143,15 +129,6 @@ int KeIsProcessTerminationProtected(PROCESS* process); KSTATUS KeGetProcessExitCode(PROCESS* process, int32_t* exitCodeOut); void KeQueryExceptionStats(struct SYS_EXCEPTION_STATS* statsOut); -// Handle table operations. -KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut); -KSTATUS KeAllocateHandleWithRights(PROCESS* process, OBJECT* object, uint32_t rights, uint32_t* handleOut); -void KeCloseHandle(PROCESS* process, uint32_t handle); -OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle); -OBJECT* KeLookupHandleWithRights(PROCESS* process, uint32_t handle, uint32_t requiredRights); -uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle); -KSTATUS KeDuplicateHandleToProcess(PROCESS* sourceProcess, uint32_t sourceHandle, PROCESS* targetProcess, uint32_t desiredRights, uint32_t duplicateFlags, uint32_t* targetHandleOut); - // Return the global process list head (kernel-internal). LIST_ENTRY* KeGetProcessList(void); PROCESS* KeGetCurrentProcess(void); diff --git a/code/kernel/include/kernel/ke_queue.h b/code/kernel/include/kernel/ke_queue.h index b429752..0917ed5 100644 --- a/code/kernel/include/kernel/ke_queue.h +++ b/code/kernel/include/kernel/ke_queue.h @@ -33,6 +33,6 @@ typedef struct QUEUE { EVENT* NotFullEvent; } QUEUE; -KSTATUS KeQueueCreate(QUEUE **out); +KSTATUS KeQueueCreate(QUEUE** out); KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds); KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilliseconds); diff --git a/code/kernel/include/kernel/ke_sched.h b/code/kernel/include/kernel/ke_sched.h index e9bb39a..1379d3e 100644 --- a/code/kernel/include/kernel/ke_sched.h +++ b/code/kernel/include/kernel/ke_sched.h @@ -2,7 +2,7 @@ #pragma once // The scheduler combines dynamic normal queues with static real-time queues. -// LAPIC timer IRQs drive KeSchedulerTick. +// Per-processor timer IRQs drive KeSchedulerTick. #include struct KCPU; @@ -97,7 +97,7 @@ void KeReadyThread(THREAD* thread); void KeUnreadyThread(THREAD* thread); // Called from each CPU timer ISR every SCHED_TICK_MS. Every CPU performs local -// quantum accounting and may request a local reschedule. Only the BSP advances +// quantum accounting and may request a local reschedule. Only the boot processor advances // global scheduler time and services sleepers, waitable timers, input repeat, // and priority aging. The actual context switch happens from KiExitInterrupt // after the IRQ handler has returned and EOI has been sent. diff --git a/code/kernel/include/kernel/ke_selftest.h b/code/kernel/include/kernel/ke_selftest.h index 530ee41..7fb76fd 100644 --- a/code/kernel/include/kernel/ke_selftest.h +++ b/code/kernel/include/kernel/ke_selftest.h @@ -3,7 +3,7 @@ #pragma once // ke_selftest.h - kernel bring-up and regression self-tests. -#include +#include void KiRunCoreHardeningSelfTests(void); void KiRunEarlyKernelSelfTests(void); diff --git a/code/kernel/include/kernel/ke_semaphore.h b/code/kernel/include/kernel/ke_semaphore.h index a9be982..991698f 100644 --- a/code/kernel/include/kernel/ke_semaphore.h +++ b/code/kernel/include/kernel/ke_semaphore.h @@ -16,6 +16,6 @@ typedef struct SEMAPHORE { LIST_ENTRY WaitList; } SEMAPHORE; -__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE **out); +__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE** out); __declspec(dllexport) KSTATUS KeWaitSemaphore(SEMAPHORE* semaphore, uint64_t timeoutMilliseconds); __declspec(dllexport) void KeSignalSemaphore(SEMAPHORE* semaphore); diff --git a/code/kernel/include/kernel/ke_settings.h b/code/kernel/include/kernel/ke_settings.h index 8f4cbad..7fca08f 100644 --- a/code/kernel/include/kernel/ke_settings.h +++ b/code/kernel/include/kernel/ke_settings.h @@ -5,7 +5,7 @@ #include #include -#define KE_SETTINGS_PATH "system\\settings.ini" +#define KE_SETTINGS_PATH "system/settings.ini" __declspec(dllexport) KSTATUS KeGetSettingString(const char* section, const char* key, diff --git a/code/kernel/include/kernel/ke_smp.h b/code/kernel/include/kernel/ke_smp.h index 28aa2ce..b1d6a35 100644 --- a/code/kernel/include/kernel/ke_smp.h +++ b/code/kernel/include/kernel/ke_smp.h @@ -22,5 +22,5 @@ void KeBuildProcessorTopology(void); // Run explicit AP-affine kernel workers that exercise the global scheduler, // reschedule IPIs, yield, and event wait/wake. Normal kernel/user/driver -// threads remain BSP-affine unless their affinity is explicitly widened. +// threads remain boot-processor-affine unless their affinity is explicitly widened. KSTATUS KeRunApplicationProcessorSchedulerSelfTests(void); diff --git a/code/kernel/include/kernel/ke_syscall.h b/code/kernel/include/kernel/ke_syscall.h index 9f675df..4fc9628 100644 --- a/code/kernel/include/kernel/ke_syscall.h +++ b/code/kernel/include/kernel/ke_syscall.h @@ -3,11 +3,11 @@ #pragma once #include -#include +#include // Set the HAL API pointer for syscalls. void KeSyscallInit(const HAL_API* hal); // Syscall dispatcher, called from assembly entry point. uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, - uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5); + uint64_t a2, uint64_t a3); diff --git a/code/kernel/include/kernel/ke_system_root.h b/code/kernel/include/kernel/ke_system_root.h index 729fab1..92fd09a 100644 --- a/code/kernel/include/kernel/ke_system_root.h +++ b/code/kernel/include/kernel/ke_system_root.h @@ -1,25 +1,20 @@ -// System root volume declarations. This file exposes the one selected kernel root volume to loader and process code. +// System root volume declarations. This file exposes the one selected mounted volume to bootstrap code. #pragma once #include -#include -#include #include #include -#define KE_SYSTEM_ROOT_MAX_DRIVES 8 - -// Selects the mounted volume that contains the kernel/system tree. +// Selects the mounted volume backed by the configured structural system partition. KSTATUS KeInitializeSystemRootVolume(void); // Returns whether the system root volume has been selected. bool KeSystemRootReady(void); -// Returns the selected system root drive, or zero before discovery. -uint8_t KeGetSystemRootDrive(void); +// References the selected mounted system volume. The caller releases the returned object reference. +KSTATUS KeReferenceSystemRootVolume(IO_VOLUME** volumeOut); -// Builds an absolute path rooted at the selected system volume. -bool KeBuildSystemRootPath(const char* relative, char* out, size_t outSize); - -// Opens a file rooted at the selected system volume. +// Opens, creates, or deletes canonical paths rooted directly at the selected system volume. KSTATUS KeOpenSystemRootFile(const char* relative, FILE_OBJECT** out); +KSTATUS KeCreateSystemRootFile(const char* relative, FILE_OBJECT** out); +KSTATUS KeDeleteSystemRootFile(const char* relative); diff --git a/code/kernel/include/kernel/ke_system_service.h b/code/kernel/include/kernel/ke_system_service.h new file mode 100644 index 0000000..bcac900 --- /dev/null +++ b/code/kernel/include/kernel/ke_system_service.h @@ -0,0 +1,24 @@ +// Kernel well-known system-service registry declarations. + +#pragma once + +#include +#include +#include + +struct PORT; + +// Registers one Port listener under a numeric well-known service identity. +KSTATUS KeSystemServiceRegister(SYS_SERVICE_ID serviceId, struct PORT* listener); + +// Removes a registration only when it still refers to the supplied listener. +KSTATUS KeSystemServiceUnregister(SYS_SERVICE_ID serviceId, struct PORT* listener); + +// References the listener currently registered for one service identity. +KSTATUS KeSystemServiceReference(SYS_SERVICE_ID serviceId, struct PORT** listenerOut); + +// Removes every service entry that refers to a listener being destroyed. +void KeSystemServiceUnregisterListener(struct PORT* listener); + +// Returns the security-context claim required to register one service identity. +SYS_SERVICE_CLAIMS KeSystemServiceRequiredClaim(SYS_SERVICE_ID serviceId); diff --git a/code/kernel/include/kernel/ke_thread.h b/code/kernel/include/kernel/ke_thread.h index 201d446..8b15d04 100644 --- a/code/kernel/include/kernel/ke_thread.h +++ b/code/kernel/include/kernel/ke_thread.h @@ -20,6 +20,9 @@ typedef struct THREAD THREAD; struct MUTEX; +// Allocates one boot-unique thread identifier. +KSTATUS KeAllocateThreadId(uint32_t* threadIdOut); + #ifndef NTL_THREAD_PRIORITY_DEFINED typedef enum THREAD_PRIORITY { PriorityLow = 0, diff --git a/code/kernel/include/kernel/ke_time.h b/code/kernel/include/kernel/ke_time.h index 8870f7a..e880bd7 100644 --- a/code/kernel/include/kernel/ke_time.h +++ b/code/kernel/include/kernel/ke_time.h @@ -14,3 +14,9 @@ __declspec(dllexport) uint64_t KeQueryUnixTimeSeconds(void); // Returns the monotonic platform counter in microseconds. uint64_t KeQueryMonotonicTicks(void); + +// Returns a saturated monotonic deadline for a relative millisecond interval. +uint64_t KeMonotonicDeadlineAfterMilliseconds(uint64_t milliseconds); + +// Returns the remaining whole milliseconds, rounded up, until a monotonic deadline. +uint64_t KeMonotonicMillisecondsUntil(uint64_t deadlineMicroseconds); diff --git a/code/kernel/include/kernel/ke_timer.h b/code/kernel/include/kernel/ke_timer.h index 1cf7040..c67a45a 100644 --- a/code/kernel/include/kernel/ke_timer.h +++ b/code/kernel/include/kernel/ke_timer.h @@ -13,8 +13,8 @@ typedef struct KTIMER { OBJECT Header; EVENT* Event; LIST_ENTRY Link; - uint64_t DueTick; - uint64_t PeriodTicks; + uint64_t DueMicroseconds; + uint64_t PeriodMicroseconds; uint8_t Active; uint8_t Linked; } KTIMER; diff --git a/code/kernel/include/kernel/ke_trap.h b/code/kernel/include/kernel/ke_trap.h index 312d6e1..73817e3 100644 --- a/code/kernel/include/kernel/ke_trap.h +++ b/code/kernel/include/kernel/ke_trap.h @@ -82,6 +82,5 @@ void KiExitTrapFrame(KTRAP_FRAME* frame); KTRAP_FRAME* KeGetCurrentTrapFrame(void); int KeTrapFrameFromUser(const KTRAP_FRAME* frame); void KiValidateSyscallTrapFrame(uint64_t nr, uint64_t a0, uint64_t a1, - uint64_t a2, uint64_t a3, uint64_t a4, - uint64_t a5); + uint64_t a2, uint64_t a3); KSTATUS KeQueryCurrentTrapFrame(SYS_TRAP_FRAME_INFO_K* out); diff --git a/code/kernel/include/kernel/ke_userenv.h b/code/kernel/include/kernel/ke_userenv.h index 16dc516..8479f2f 100644 --- a/code/kernel/include/kernel/ke_userenv.h +++ b/code/kernel/include/kernel/ke_userenv.h @@ -7,15 +7,26 @@ #include #include #include +#include #define SYS_PEB_MAGIC 0x30424550u // "PEB0" #define SYS_TEB_MAGIC 0x30424554u // "TEB0" -#define SYS_ENV_VERSION 1u -#define SYS_CWD_MAX 520u +#define SYS_ENV_VERSION 4u #ifndef SYS_USER_ENV_TYPES_DEFINED #define SYS_USER_ENV_TYPES_DEFINED 1 +#define SYS_STARTUP_CAP_SYSTEM_VOLUME 1u +#define SYS_STARTUP_CAP_SERVICE_CONTROL 2u +#define SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE 3u +#define SYS_STARTUP_CAP_VOLUME_MANAGER 4u +#define SYS_STARTUP_CAPABILITY_MAX 32u + +typedef struct SYS_STARTUP_CAPABILITY { + uint32_t Tag; + SYS_HANDLE Handle; +} SYS_STARTUP_CAPABILITY; + typedef struct SYS_PEB { uint32_t Magic; uint32_t Version; @@ -23,7 +34,11 @@ typedef struct SYS_PEB { SYS_STRING_VIEW ImagePath; SYS_STRING_VIEW CommandLine; - SYS_STRING_VIEW CurrentDirectory; + const SYS_STARTUP_CAPABILITY* StartupCapabilities; + uint32_t StartupCapabilityCount; + uint32_t Reserved; + + const void* LoaderData; } SYS_PEB; typedef struct SYS_TEB { diff --git a/code/kernel/include/kernel/ke_util.h b/code/kernel/include/kernel/ke_util.h index 8b20bf4..68c274e 100644 --- a/code/kernel/include/kernel/ke_util.h +++ b/code/kernel/include/kernel/ke_util.h @@ -25,25 +25,25 @@ typedef struct { #define _ARR_INIT_CAPACITY 2 -#define _arr_header(arr) ((_ArrHeader *)(arr) - 1) +#define _arr_header(arr) ((_ArrHeader*)(arr) - 1) #define arr_len(arr) ((arr) ? _arr_header(arr)->count : 0u) #define _arr_maybe_grow(arr) \ do { \ if (!(arr)) { \ - _ArrHeader *_h = MmAllocatePool(sizeof(_ArrHeader) + \ + _ArrHeader* _h = MmAllocatePool(sizeof(_ArrHeader) + \ sizeof(*(arr)) * _ARR_INIT_CAPACITY); \ _h->count = 0; \ _h->capacity = _ARR_INIT_CAPACITY; \ - (arr) = (void *)(_h + 1); \ + (arr) = (void*)(_h + 1); \ } \ - _ArrHeader *_h = _arr_header(arr); \ + _ArrHeader* _h = _arr_header(arr); \ if (_h->count >= _h->capacity) { \ _h->capacity *= 2; \ _h = MmReallocatePool(_h, sizeof(_ArrHeader) + \ sizeof(*(arr)) * _h->capacity); \ - (arr) = (void *)(_h + 1); \ + (arr) = (void*)(_h + 1); \ } \ } while (0) @@ -56,7 +56,7 @@ typedef struct { #define arr_push_front(arr, x) \ do { \ _arr_maybe_grow(arr); \ - _ArrHeader *_h = _arr_header(arr); \ + _ArrHeader* _h = _arr_header(arr); \ memmove(&(arr)[1], &(arr)[0], sizeof(*(arr)) * _h->count); \ (arr)[0] = (x); \ _h->count++; \ @@ -65,7 +65,7 @@ typedef struct { #define arr_remove(arr, index) \ do { \ if ((arr) && (size_t)(index) < arr_len(arr)) { \ - _ArrHeader *_h = _arr_header(arr); \ + _ArrHeader* _h = _arr_header(arr); \ memmove(&(arr)[index], &(arr)[(index) + 1], \ sizeof(*(arr)) * (_h->count - (size_t)(index) - 1)); \ _h->count--; \ diff --git a/code/kernel/include/kernel/ldr_image.h b/code/kernel/include/kernel/ldr_image.h index 402032b..4504124 100644 --- a/code/kernel/include/kernel/ldr_image.h +++ b/code/kernel/include/kernel/ldr_image.h @@ -10,7 +10,6 @@ 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. @@ -22,7 +21,7 @@ size_t LdrGetImageSize(uintptr_t base); size_t LdrGetMappedImageSize(struct PROCESS* process, uintptr_t base); // Look up a named export in a loaded module. -// 'moduleName' must match a name in BOOT_INFO.Modules (e.g., "hal.dll"). +// 'moduleName' must match a registered kernel module name such as "hal.dll". void* LdrGetExport(const char* moduleName, const char* functionName); // Get the entry point address of a loaded image. @@ -31,26 +30,14 @@ uintptr_t LdrGetEntryPoint(uintptr_t base); // Resolve the import table of an already-loaded image using the global registry. KSTATUS LdrResolveImports(uintptr_t imageBase); -// Resolve imports using a process-local module registry. -KSTATUS LdrResolveImportsForProcess(struct PROCESS* process, uintptr_t imageBase); +// Requires that one mapped bootstrap user image declare no import table. +KSTATUS LdrRequireNoImports(uintptr_t imageBase); // Apply final page permissions for a loaded image after relocations/imports. KSTATUS LdrProtectImageForProcess(struct PROCESS* process, uintptr_t imageBase, bool user); -// Load a DLL for a specific process, resolve it against that process's -// loaded modules, and register it in the process-local registry. -#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. +// Called by KernelMain to register the fixed kernel/HAL bootstrap images so LdrGetExport can find them. struct BOOT_INFO; // forward declaration -void LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo); -void LdrRegisterModule(const char* name, uintptr_t base); +KSTATUS LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo); +KSTATUS LdrRegisterModule(const char* name, uintptr_t base); const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, size_t* sizeOut); diff --git a/code/kernel/include/kernel/mm_file_cache.h b/code/kernel/include/kernel/mm_file_cache.h index a31e13e..626b80b 100644 --- a/code/kernel/include/kernel/mm_file_cache.h +++ b/code/kernel/include/kernel/mm_file_cache.h @@ -10,6 +10,8 @@ struct FILE_OBJECT; typedef struct FILE_OBJECT FILE_OBJECT; +struct IO_VOLUME; +typedef struct IO_VOLUME IO_VOLUME; typedef struct MM_FILE_CACHE_PAGE { FILE_OBJECT* File; @@ -74,6 +76,7 @@ KSTATUS MmFileCacheFlushFile(FILE_OBJECT* file); // by callers before destructive metadata operations. void MmFileCacheInvalidate(FILE_OBJECT* file, uint64_t offset, size_t size); void MmFileCacheInvalidateAll(FILE_OBJECT* file); +void MmFileCacheDiscardVolume(IO_VOLUME* volume); // Reclaims one cache-owned page if possible. Safe to call under memory pressure; // it never writes back because v1 stores only clean file data. diff --git a/code/kernel/include/kernel/mm_swap.h b/code/kernel/include/kernel/mm_swap.h index 0b61c29..9aef4fb 100644 --- a/code/kernel/include/kernel/mm_swap.h +++ b/code/kernel/include/kernel/mm_swap.h @@ -5,7 +5,6 @@ #include #include #include -#include #include typedef struct MM_SWAP_STATS { @@ -23,7 +22,7 @@ typedef struct MM_SWAP_STATS { uint64_t SlotAllocationFailures; } MM_SWAP_STATS; -KSTATUS MmSwapInitialize(SYS_STRING_VIEW path, uint64_t sizeBytes); +KSTATUS MmSwapInitialize(const char* relativePath, uint64_t sizeBytes); int MmSwapIsEnabled(void); KSTATUS MmSwapPageOut(PHYSICAL_ADDRESS frame, uint32_t* slotOut); KSTATUS MmSwapPageIn(uint32_t slot, PHYSICAL_ADDRESS frame); diff --git a/code/kernel/include/kernel/mm_virt.h b/code/kernel/include/kernel/mm_virt.h index 6691d3c..737bbec 100644 --- a/code/kernel/include/kernel/mm_virt.h +++ b/code/kernel/include/kernel/mm_virt.h @@ -56,6 +56,9 @@ __declspec(dllexport) KSTATUS MmProbeUserWrite(void* ptr, size_t size); // kernel-owned buffers, not raw user pointers, unless explicitly documented. __declspec(dllexport) KSTATUS CopyFromUser(void* kernelDst, const void* userSrc, size_t size); __declspec(dllexport) KSTATUS CopyToUser(void* userDst, const void* kernelSrc, size_t size); +// Copies bytes directly between two process-owned user ranges while pinning both sides. +__declspec(dllexport) KSTATUS MmCopyUserToUser(struct PROCESS* sourceProcess, const void* source, + struct PROCESS* targetProcess, void* target, size_t size); typedef struct MM_PINNED_USER_PAGE { uintptr_t UserPage; diff --git a/code/kernel/include/kernel/net_socket.h b/code/kernel/include/kernel/net_socket.h index 81e2728..86f1979 100644 --- a/code/kernel/include/kernel/net_socket.h +++ b/code/kernel/include/kernel/net_socket.h @@ -54,7 +54,7 @@ typedef struct NET_ASYNC_SOCKET_SUBMIT { size_t OptionValueSize; size_t* OptionActualSizeOut; - uint32_t* HandleOut; + SYS_HANDLE* HandleOut; uint32_t* PollReturnedEventsOut; IO_STATUS_BLOCK* Iosb; diff --git a/code/kernel/include/kernel/se_security.h b/code/kernel/include/kernel/se_security.h new file mode 100644 index 0000000..60f6246 --- /dev/null +++ b/code/kernel/include/kernel/se_security.h @@ -0,0 +1,42 @@ +// Kernel security context declarations. + +#pragma once + +#include +#include +#include + +struct PROCESS; + +typedef struct SECURITY_CONTEXT { + OBJECT Header; + uint32_t SecurityId; + SYS_PRIVILEGES EffectivePrivileges; + SYS_PRIVILEGES DelegablePrivileges; + SYS_SERVICE_CLAIMS ServiceClaims; + SYS_SERVICE_CLAIMS DelegableServiceClaims; + uint32_t Flags; +} SECURITY_CONTEXT; + +// Initializes the immutable root security context used during bootstrap. +KSTATUS SeInitializeSecurity(void); + +// Returns the bootstrap root context. The returned pointer is owned by the security subsystem. +SECURITY_CONTEXT* SeGetRootSecurityContext(void); + +// Derives one immutable child context without allowing privilege or service-claim amplification. +KSTATUS SeDeriveSecurityContext(SECURITY_CONTEXT* source, + SYS_PRIVILEGES effectivePrivileges, + SYS_PRIVILEGES delegablePrivileges, + SYS_SERVICE_CLAIMS serviceClaims, + SYS_SERVICE_CLAIMS delegableServiceClaims, + SECURITY_CONTEXT** contextOut); + +// Returns whether one process currently carries all requested ambient privileges. +bool SeProcessHasPrivilege(const struct PROCESS* process, SYS_PRIVILEGES privileges); + +// Returns whether one security context carries all requested service claims. +bool SeContextHasServiceClaims(const SECURITY_CONTEXT* context, SYS_SERVICE_CLAIMS claims); + +// Copies one immutable security context into the public query layout. +void SeQuerySecurityContext(const SECURITY_CONTEXT* context, SYS_SECURITY_CONTEXT_INFO* infoOut); diff --git a/code/kernel/include/kernel/syscall_nr.h b/code/kernel/include/kernel/syscall_nr.h index 4be8df4..4f97310 100644 --- a/code/kernel/include/kernel/syscall_nr.h +++ b/code/kernel/include/kernel/syscall_nr.h @@ -4,167 +4,185 @@ // INT 0x40 calling convention, MS x64. // RAX selects the syscall number. RCX, RDX, R8, and R9 carry args 0-3. -// R10 and R11 carry args 4-5 and are loaded by six-argument stubs. +// Syscalls use at most four register arguments; larger operations use request structures. // RAX carries the return value. -// Process, threads, modules, waits, APC, and exceptions. +// Process, threads, waits, APC, and exceptions. #define SYS_EXIT_PROCESS 0 #define SYS_EXIT_THREAD 1 -#define SYS_CREATE_PROCESS 2 -#define SYS_OPEN_PROCESS 3 -#define SYS_GET_PROCESS_EXIT_CODE 4 -#define SYS_KILL_PROCESS 5 -#define SYS_GET_PROCESS_LIST 6 -#define SYS_GET_PROCESS_PID 7 -#define SYS_CREATE_THREAD 8 -#define SYS_RESUME_THREAD 9 -#define SYS_GET_THREAD_LIST 10 -#define SYS_LOAD_MODULE 11 -#define SYS_UNLOAD_MODULE 12 -#define SYS_GET_MODULE_INFO 13 -#define SYS_GET_MODULE_EXPORT 14 -#define SYS_GET_MODULE_LIST 15 -#define SYS_GET_CURRENT_TID 16 -#define SYS_GET_CURRENT_PID 17 -#define SYS_SET_THREAD_PRIORITY 18 -#define SYS_YIELD 19 -#define SYS_SLEEP 20 -#define SYS_WAIT_SINGLE 21 -#define SYS_WAIT_MANY 22 -#define SYS_SET_USER_APC_DISPATCHER 23 -#define SYS_APC_RETURN 24 -#define SYS_SET_EXCEPTION_DISPATCHER 25 -#define SYS_GET_EXCEPTION_DISPATCHER 26 -#define SYS_CONTINUE_EXCEPTION 27 -#define SYS_RAISE_EXCEPTION 28 +#define SYS_CREATE_PROCESS_OBJECT 2 +#define SYS_ALLOC_PROCESS_VM 3 +#define SYS_PROTECT_PROCESS_VM 4 +#define SYS_START_PROCESS 5 +#define SYS_OPEN_PROCESS 6 +#define SYS_GET_PROCESS_EXIT_CODE 7 +#define SYS_KILL_PROCESS 8 +#define SYS_GET_PROCESS_LIST 9 +#define SYS_GET_PROCESS_PID 10 +#define SYS_CREATE_THREAD 11 +#define SYS_RESUME_THREAD 12 +#define SYS_GET_THREAD_LIST 13 +#define SYS_GET_CURRENT_TID 14 +#define SYS_GET_CURRENT_PID 15 +#define SYS_SET_THREAD_PRIORITY 16 +#define SYS_YIELD 17 +#define SYS_SLEEP 18 +#define SYS_WAIT_SINGLE 19 +#define SYS_WAIT_MANY 20 +#define SYS_SET_USER_APC_DISPATCHER 21 +#define SYS_APC_RETURN 22 +#define SYS_SET_EXCEPTION_DISPATCHER 23 +#define SYS_GET_EXCEPTION_DISPATCHER 24 +#define SYS_CONTINUE_EXCEPTION 25 +#define SYS_RAISE_EXCEPTION 26 // Virtual memory, mappings, and handles. -#define SYS_ALLOC_VM 29 -#define SYS_FREE_VM 30 -#define SYS_PROTECT_VM 31 -#define SYS_QUERY_VM 32 -#define SYS_CLOSE_HANDLE 33 -#define SYS_DUPLICATE_HANDLE 34 -#define SYS_CREATE_FILE_MAPPING 35 -#define SYS_MAP_FILE 36 +#define SYS_ALLOC_VM 27 +#define SYS_FREE_VM 28 +#define SYS_PROTECT_VM 29 +#define SYS_QUERY_VM 30 +#define SYS_CLOSE_HANDLE 31 +#define SYS_DUPLICATE_HANDLE 32 +#define SYS_DERIVE_HANDLE 33 +#define SYS_REVOKE_HANDLE 34 +#define SYS_DERIVE_SECURITY_CONTEXT 35 +#define SYS_QUERY_SECURITY_CONTEXT 36 +#define SYS_CREATE_FILE_MAPPING 37 +#define SYS_MAP_FILE 38 // Synchronization objects. -#define SYS_CREATE_MUTEX 37 -#define SYS_TRY_ACQUIRE_MUTEX 38 -#define SYS_RELEASE_MUTEX 39 -#define SYS_CREATE_SEM 40 -#define SYS_TRY_WAIT_SEM 41 -#define SYS_SIGNAL_SEM 42 -#define SYS_CREATE_EVENT 43 -#define SYS_SIGNAL_EVENT 44 -#define SYS_CLEAR_EVENT 45 +#define SYS_CREATE_MUTEX 39 +#define SYS_TRY_ACQUIRE_MUTEX 40 +#define SYS_RELEASE_MUTEX 41 +#define SYS_CREATE_SEM 42 +#define SYS_TRY_WAIT_SEM 43 +#define SYS_SIGNAL_SEM 44 +#define SYS_CREATE_EVENT 45 +#define SYS_SIGNAL_EVENT 46 +#define SYS_CLEAR_EVENT 47 // Queues and named queues. -#define SYS_CREATE_QUEUE 46 -#define SYS_SEND_QUEUE 47 -#define SYS_RECV_QUEUE 48 -#define SYS_PUBLISH_QUEUE 49 -#define SYS_OPEN_NAMED_QUEUE 50 -#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 51 +#define SYS_CREATE_QUEUE 48 +#define SYS_SEND_QUEUE 49 +#define SYS_RECV_QUEUE 50 +#define SYS_PUBLISH_QUEUE 51 +#define SYS_OPEN_NAMED_QUEUE 52 +#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 53 +#define SYS_UNPUBLISH_QUEUE 54 -// Files, directories, and current directory. -#define SYS_OPEN_FILE 52 -#define SYS_READ_FILE 53 -#define SYS_WRITE_FILE 54 -#define SYS_READ_FILE_ASYNC 55 -#define SYS_WRITE_FILE_ASYNC 56 -#define SYS_CANCEL_IO 57 -#define SYS_SEEK_FILE 58 -#define SYS_GET_FILE_POS 59 -#define SYS_GET_FILE_SIZE 60 -#define SYS_GET_FILE_STAT 61 -#define SYS_STAT_FILE 62 -#define SYS_TRUNCATE_FILE 63 -#define SYS_FLUSH_FILE 64 -#define SYS_DELETE_FILE 65 -#define SYS_RENAME_FILE 66 -#define SYS_LOCK_FILE 67 -#define SYS_UNLOCK_FILE 68 -#define SYS_OPEN_DIR 69 -#define SYS_READ_DIR 70 -#define SYS_CREATE_DIR 71 -#define SYS_REMOVE_DIR 72 -#define SYS_SET_CWD 73 -#define SYS_REAL_PATH 74 +// LPC-like Ports and well-known system services. +#define SYS_CREATE_PORT 55 +#define SYS_CONNECT_PORT 56 +#define SYS_ACCEPT_PORT 57 +#define SYS_SEND_PORT 58 +#define SYS_RECEIVE_PORT 59 +#define SYS_CALL_PORT 60 +#define SYS_REPLY_PORT 61 +#define SYS_DISCONNECT_PORT 62 +#define SYS_REGISTER_SYSTEM_SERVICE 63 +#define SYS_UNREGISTER_SYSTEM_SERVICE 64 +#define SYS_CONNECT_SYSTEM_SERVICE 65 + +// Mounted-volume manager control plane. +#define SYS_QUERY_VOLUMES 66 +#define SYS_OPEN_VOLUME 67 +#define SYS_WAIT_VOLUME_CHANGE 68 + +// Files and directories. +#define SYS_OPEN_FILE_AT 69 +#define SYS_OPEN_DIR_AT 70 +#define SYS_STAT_AT 71 +#define SYS_DELETE_AT 72 +#define SYS_RENAME_AT 73 +#define SYS_CREATE_DIR_AT 74 +#define SYS_REMOVE_DIR_AT 75 +#define SYS_READ_FILE 76 +#define SYS_WRITE_FILE 77 +#define SYS_READ_FILE_ASYNC 78 +#define SYS_WRITE_FILE_ASYNC 79 +#define SYS_CANCEL_IO 80 +#define SYS_SEEK_FILE 81 +#define SYS_GET_FILE_POS 82 +#define SYS_GET_FILE_SIZE 83 +#define SYS_GET_FILE_STAT 84 +#define SYS_TRUNCATE_FILE 85 +#define SYS_FLUSH_FILE 86 +#define SYS_LOCK_FILE 87 +#define SYS_UNLOCK_FILE 88 +#define SYS_READ_DIR 89 // Console, display, and surfaces. -#define SYS_WRITE_CONSOLE 75 -#define SYS_GET_CONSOLE_INFO 76 -#define SYS_SET_CONSOLE_CURSOR 77 -#define SYS_FILL_CONSOLE_CHARACTER 78 -#define SYS_FILL_CONSOLE_ATTRIBUTE 79 -#define SYS_PUT_SCREEN_CHAR 80 -#define SYS_CLEAR_SCREEN 81 -#define SYS_GET_SCREEN_SIZE 82 -#define SYS_QUERY_DISPLAY_INFO 83 -#define SYS_OPEN_DISPLAY_CLOCK 84 -#define SYS_CREATE_SURFACE 85 -#define SYS_MAP_SURFACE 86 -#define SYS_QUERY_SURFACE 87 -#define SYS_PRESENT_SURFACE 88 +#define SYS_WRITE_CONSOLE 90 +#define SYS_GET_CONSOLE_INFO 91 +#define SYS_SET_CONSOLE_CURSOR 92 +#define SYS_FILL_CONSOLE_CHARACTER 93 +#define SYS_FILL_CONSOLE_ATTRIBUTE 94 +#define SYS_PUT_SCREEN_CHAR 95 +#define SYS_CLEAR_SCREEN 96 +#define SYS_GET_SCREEN_SIZE 97 +#define SYS_QUERY_DISPLAY_INFO 98 +#define SYS_OPEN_DISPLAY_CLOCK 99 +#define SYS_CREATE_SURFACE 100 +#define SYS_MAP_SURFACE 101 +#define SYS_QUERY_SURFACE 102 +#define SYS_PRESENT_SURFACE 103 // Native render audio. -#define SYS_AUDIO_ENUMERATE_ENDPOINTS 89 -#define SYS_AUDIO_OPEN_STREAM 90 -#define SYS_AUDIO_WRITE 91 -#define SYS_AUDIO_ARM_WAIT 92 -#define SYS_AUDIO_QUERY_STREAM 93 -#define SYS_AUDIO_START 94 -#define SYS_AUDIO_PAUSE 95 -#define SYS_AUDIO_RESUME 96 -#define SYS_AUDIO_DRAIN 97 -#define SYS_AUDIO_STOP 98 -#define SYS_AUDIO_RESET 99 -#define SYS_AUDIO_SET_VOLUME 100 -#define SYS_AUDIO_SET_MASTER_VOLUME 101 +#define SYS_AUDIO_ENUMERATE_ENDPOINTS 104 +#define SYS_AUDIO_OPEN_STREAM 105 +#define SYS_AUDIO_WRITE 106 +#define SYS_AUDIO_ARM_WAIT 107 +#define SYS_AUDIO_QUERY_STREAM 108 +#define SYS_AUDIO_START 109 +#define SYS_AUDIO_PAUSE 110 +#define SYS_AUDIO_RESUME 111 +#define SYS_AUDIO_DRAIN 112 +#define SYS_AUDIO_STOP 113 +#define SYS_AUDIO_RESET 114 +#define SYS_AUDIO_SET_VOLUME 115 +#define SYS_AUDIO_SET_MASTER_VOLUME 116 // Input. -#define SYS_POLL_KEYBOARD 102 -#define SYS_IS_KEY_DOWN 103 -#define SYS_READ_KEYBOARD_CHAR 104 -#define SYS_READ_KEY 105 -#define SYS_GET_KEY_MODIFIERS 106 -#define SYS_QUERY_GLOBAL_INPUT_STATE 107 -#define SYS_OPEN_INPUT_QUEUE 108 -#define SYS_READ_INPUT_EVENT 109 -#define SYS_PEEK_INPUT_EVENT 110 -#define SYS_PEEK_MOUSE 111 -#define SYS_READ_MOUSE 112 -#define SYS_GET_MOUSE_POS 113 +#define SYS_POLL_KEYBOARD 117 +#define SYS_IS_KEY_DOWN 118 +#define SYS_READ_KEYBOARD_CHAR 119 +#define SYS_READ_KEY 120 +#define SYS_GET_KEY_MODIFIERS 121 +#define SYS_QUERY_GLOBAL_INPUT_STATE 122 +#define SYS_OPEN_INPUT_QUEUE 123 +#define SYS_READ_INPUT_EVENT 124 +#define SYS_PEEK_INPUT_EVENT 125 +#define SYS_PEEK_MOUSE 126 +#define SYS_READ_MOUSE 127 +#define SYS_GET_MOUSE_POS 128 // Time, debug, and system information. -#define SYS_CREATE_TIMER 114 -#define SYS_SET_TIMER 115 -#define SYS_CANCEL_TIMER 116 -#define SYS_GET_TICK 117 -#define SYS_GET_TICK_FREQUENCY 118 -#define SYS_GET_SYSTEM_TIME 119 -#define SYS_PRINT_DEBUG 120 -#define SYS_QUERY_TRAP_FRAME 121 -#define SYS_GET_MEMORY_STATUS 122 -#define SYS_QUERY_KERNEL_STATS 123 +#define SYS_CREATE_TIMER 129 +#define SYS_SET_TIMER 130 +#define SYS_CANCEL_TIMER 131 +#define SYS_GET_TICK 132 +#define SYS_GET_TICK_FREQUENCY 133 +#define SYS_GET_SYSTEM_TIME 134 +#define SYS_PRINT_DEBUG 135 +#define SYS_QUERY_TRAP_FRAME 136 +#define SYS_GET_MEMORY_STATUS 137 +#define SYS_QUERY_KERNEL_STATS 138 // Native sockets and polling. -#define SYS_CREATE_SOCKET 124 -#define SYS_SOCKET_IO_ASYNC 125 -#define SYS_BIND_SOCKET 126 -#define SYS_LISTEN_SOCKET 127 -#define SYS_ACCEPT_SOCKET 128 -#define SYS_CONNECT_SOCKET 129 -#define SYS_SEND_SOCKET 130 -#define SYS_RECV_SOCKET 131 -#define SYS_SHUTDOWN_SOCKET 132 -#define SYS_SEND_TO_SOCKET 133 -#define SYS_RECV_FROM_SOCKET 134 -#define SYS_RESOLVE_HOST 135 -#define SYS_SET_SOCKET_OPTION 136 -#define SYS_GET_SOCKET_OPTION 137 -#define SYS_POLL 138 -#define SYS_UNPUBLISH_QUEUE 139 +#define SYS_CREATE_SOCKET 139 +#define SYS_SOCKET_IO_ASYNC 140 +#define SYS_BIND_SOCKET 141 +#define SYS_LISTEN_SOCKET 142 +#define SYS_ACCEPT_SOCKET 143 +#define SYS_CONNECT_SOCKET 144 +#define SYS_SEND_SOCKET 145 +#define SYS_RECV_SOCKET 146 +#define SYS_SHUTDOWN_SOCKET 147 +#define SYS_SEND_TO_SOCKET 148 +#define SYS_RECV_FROM_SOCKET 149 +#define SYS_RESOLVE_HOST 150 +#define SYS_SET_SOCKET_OPTION 151 +#define SYS_GET_SOCKET_OPTION 152 +#define SYS_POLL 153 -#define SYS_MAX 140 +#define SYS_MAX 154 diff --git a/code/kernel/io_ddk.c b/code/kernel/io_ddk.c index d769d78..852d3a7 100644 --- a/code/kernel/io_ddk.c +++ b/code/kernel/io_ddk.c @@ -2,8 +2,7 @@ #include #include -#include -#include +#include #include #include #include @@ -13,12 +12,13 @@ #include static const HAL_API* gIoDdkHal; +static uintptr_t gIoDdkPhysicalWindowBase; // Returns the kernel direct-map alias for a physical address. static void* IoDdkPhysToVirt(uint64_t physicalAddress) { - if (!gIoDdkHal || !gIoDdkHal->PhysToVirt) + if (!gIoDdkPhysicalWindowBase || physicalAddress > UINTPTR_MAX - gIoDdkPhysicalWindowBase) return (void*)0; - return gIoDdkHal->PhysToVirt(physicalAddress); + return (void*)(gIoDdkPhysicalWindowBase + (uintptr_t)physicalAddress); } // Maps one kernel page through the DDK memory surface. @@ -55,21 +55,28 @@ static void IoDdkPciWrite32(uint16_t segment, gIoDdkHal->Pci.Write32(segment, bus, device, function, offset, value); } -// Returns the BSP local APIC identifier. -static uint32_t IoDdkGetBspLapicId(void) { - return HalGetBspLapicId(); -} - -// Returns whether the platform interrupt controller is using x2APIC mode. -static uint8_t IoDdkIsX2ApicEnabled(void) { - return HalApicUsingX2Apic(); -} - // Allocates one kernel IRQ slot for MSI delivery. static KSTATUS IoDdkAllocateMsiVector(uint8_t* outIrq) { return KeIrqAllocateMsiVector(outIrq); } + +// Composes one platform MSI message for an allocated nt-lite IRQ slot. +static KSTATUS IoDdkComposeMsiMessage(uint8_t irq, NTL_DDK_MSI_MESSAGE* messageOut) { + if (!gIoDdkHal || !messageOut || irq >= 24u) + return NTLSTATUS_INVALID_PARAMETER; + + HAL_MSI_MESSAGE message; + KSTATUS status = gIoDdkHal->InterruptController.ComposeMsi((uint8_t)(0x20u + irq), &message); + if (!KSUCCESS(status)) + return status; + + messageOut->Address = message.Address; + messageOut->Data = message.Data; + messageOut->Reserved = 0; + return NTLSTATUS_SUCCESS; +} + static const NTL_DDK_DEBUG gIoDdkDebug = { .Print = DbgPrint, .GetFlags = KeGetDebugFlags, @@ -77,6 +84,7 @@ static const NTL_DDK_DEBUG gIoDdkDebug = { static const NTL_DDK_INTERRUPT gIoDdkInterrupt = { .AllocateMsiVector = IoDdkAllocateMsiVector, + .ComposeMsiMessage = IoDdkComposeMsiMessage, }; static const NTL_DDK_MEMORY gIoDdkMemory = { @@ -87,8 +95,6 @@ static const NTL_DDK_MEMORY gIoDdkMemory = { static const NTL_DDK_PLATFORM gIoDdkPlatform = { .PciRead32 = IoDdkPciRead32, .PciWrite32 = IoDdkPciWrite32, - .GetBspLapicId = IoDdkGetBspLapicId, - .IsX2ApicEnabled = IoDdkIsX2ApicEnabled, }; static const NTL_DDK_SCHEDULER gIoDdkScheduler = { @@ -114,8 +120,9 @@ static NTL_DDK gIoDdk = { }; // Initializes the kernel-owned DDK table from platform services. -void IoInitializeDdk(const HAL_API* hal) { +void IoInitializeDdk(const HAL_API* hal, uintptr_t physicalWindowBase) { gIoDdkHal = hal; + gIoDdkPhysicalWindowBase = physicalWindowBase; gIoDdk.Audio = KeAudioGetDdk(); gIoDdk.Dma = IoGetDmaDdk(); gIoDdkTimer.QueryTicks = hal ? hal->Timer.QueryTicks : 0; diff --git a/code/kernel/io_file.c b/code/kernel/io_file.c index ad32018..f97d17a 100644 --- a/code/kernel/io_file.c +++ b/code/kernel/io_file.c @@ -4,69 +4,20 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include #include #include #include +#include #include -#define MAX_DRIVES 8 - -typedef struct { - IO_FILESYSTEM *Fs; - IO_VOLUME_PARAMS Vol; -} VOLUME_ENTRY; - -static VOLUME_ENTRY gVolumes[MAX_DRIVES]; - -static EVENT* gVolumeChangeEvent; -static SPINLOCK gVolumeNamespaceLock = SPINLOCK_INIT; -static uint64_t gVolumeNamespaceGeneration; - -// Publishes a mounted-volume namespace change to boot discovery and other waiters. -static void KiIoNotifyVolumeNamespaceChange(void) { - uintptr_t flags = KeAcquireSpinlock(&gVolumeNamespaceLock); - gVolumeNamespaceGeneration++; - KeReleaseSpinlock(&gVolumeNamespaceLock, flags); - if (gVolumeChangeEvent) - KeSignalEvent(gVolumeChangeEvent); -} - -// Initializes the waitable mounted-volume namespace notification. -KSTATUS IoInitializeVolumeNamespace(void) { - if (gVolumeChangeEvent) - return NTLSTATUS_SUCCESS; - return KeEventCreate(EventAutoReset, 0, &gVolumeChangeEvent); -} - -// Returns the current mounted-volume namespace generation. -uint64_t IoGetVolumeNamespaceGeneration(void) { - uintptr_t flags = KeAcquireSpinlock(&gVolumeNamespaceLock); - uint64_t generation = gVolumeNamespaceGeneration; - KeReleaseSpinlock(&gVolumeNamespaceLock, flags); - return generation; -} - -// Waits for a mounted-volume namespace change without polling driver discovery. -KSTATUS IoWaitForVolumeNamespaceChange(uint64_t observedGeneration, - uint64_t timeoutMilliseconds) { - if (IoGetVolumeNamespaceGeneration() != observedGeneration) - return NTLSTATUS_SUCCESS; - if (!gVolumeChangeEvent) - return NTLSTATUS_OBJECT_DEAD; - - EVENT* events[1] = { gVolumeChangeEvent }; - uint32_t selected = 0; - return KeWaitForMultipleEvents(events, 1, 0, timeoutMilliseconds, &selected); -} - // Async file I/O worker. typedef enum IO_ASYNC_FILE_OP { IoAsyncFileRead = 0, @@ -186,49 +137,24 @@ static bool KiIoFileLockHasConflictLocked(const IO_FILE_IDENTITY* identity, return false; } -// Returns the scheduler tick value used by finite file-lock waits. -static uint64_t KiIoFileLockReadSchedulerTick(void) { - KE_SCHED_STATS stats; - KeGetSchedulerStats(&stats); - return stats.Ticks; -} - -// Converts a millisecond timeout to at least one scheduler tick. -static uint64_t KiIoFileLockTimeoutTicks(uint64_t timeoutMilliseconds) { - uint64_t ticks = timeoutMilliseconds / SCHED_TICK_MS; - if ((timeoutMilliseconds % SCHED_TICK_MS) != 0) - ticks++; - return ticks ? ticks : 1; -} - -// Computes an absolute scheduler-tick deadline for a finite timeout. +// Computes an absolute monotonic deadline for a finite file-lock timeout. static uint64_t KiIoFileLockTimeoutDeadline(uint64_t timeoutMilliseconds) { - uint64_t now = KiIoFileLockReadSchedulerTick(); - uint64_t ticks = KiIoFileLockTimeoutTicks(timeoutMilliseconds); - uint64_t deadline = now + ticks; - return deadline < now ? UINT64_MAX : deadline; + return KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds); } -// Computes the remaining wait time for a finite timeout deadline. -static uint64_t KiIoFileLockRemainingMilliseconds(uint64_t deadlineTick) { - uint64_t now = KiIoFileLockReadSchedulerTick(); - if (deadlineTick <= now) - return 0; - - uint64_t ticks = deadlineTick - now; - if (ticks > UINT64_MAX / SCHED_TICK_MS) - return UINT64_MAX; - return ticks * SCHED_TICK_MS; +// Computes the remaining wait time for a finite file-lock timeout deadline. +static uint64_t KiIoFileLockRemainingMilliseconds(uint64_t deadlineMicroseconds) { + return KeMonotonicMillisecondsUntil(deadlineMicroseconds); } // Waits for a file-lock conflict to change. -static KSTATUS KiIoWaitForFileLockChange(EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineTick) { +static KSTATUS KiIoWaitForFileLockChange(EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineMicroseconds) { if (!event) return NTLSTATUS_INVALID_PARAMETER; uint64_t waitMilliseconds = timeoutMilliseconds; if (timeoutMilliseconds != SYS_WAIT_TIMEOUT_INFINITE) { - waitMilliseconds = KiIoFileLockRemainingMilliseconds(deadlineTick); + waitMilliseconds = KiIoFileLockRemainingMilliseconds(deadlineMicroseconds); if (waitMilliseconds == 0) return NTLSTATUS_TIMEOUT; } @@ -466,8 +392,8 @@ static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op, IO_FS_ASYNC_COMPLETE complete = KiIoNativeFsAsyncComplete; KSTATUS ns = NTLSTATUS_NOT_SUPPORTED; - bool hasNative = (op == IoAsyncFileRead) ? (file->Fs->ReadAsync != 0) - : (file->Fs->WriteAsync != 0); + bool hasNative = (op == IoAsyncFileRead) ? IoVolumeSupportsReadAsync(file->Volume) + : IoVolumeSupportsWriteAsync(file->Volume); if (hasNative) { uintptr_t flags = KeAcquireSpinlock(&gAsyncFileQueueLock); @@ -476,11 +402,11 @@ static KSTATUS KiIoSubmitAsyncFileRequest(IO_ASYNC_FILE_OP op, ObReferenceObject(&req->Async.Header); // native filesystem provider reference if (op == IoAsyncFileRead) { - ns = file->Fs->ReadAsync(file->Fs, file->FileCtx, offset, buffer, - size, complete, req); + ns = IoVolumeReadAsync(file->Volume, file->FileCtx, offset, buffer, + size, complete, req); } else { - ns = file->Fs->WriteAsync(file->Fs, file->FileCtx, offset, buffer, - size, complete, req); + ns = IoVolumeWriteAsync(file->Volume, file->FileCtx, offset, buffer, + size, complete, req); } if (ns == NTLSTATUS_PENDING) { if (requestOut) { @@ -579,337 +505,167 @@ __declspec(dllexport) KSTATUS IoQueryAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJE iosbOut); } -// Current Working Directory -// Stored as (CurrentDrive, drive-relative path with no trailing backslash). -// Exported as native "N:/path" so personalities can round-trip volumes. -#define CWD_MAX PROCESS_CWD_MAX +// Validates one canonical native path relative to an already-authoritative root capability. +// Native relative paths use '/' only and never carry drive/CWD or parent-traversal semantics. +static KSTATUS KiValidateNativeRelativePath(SYS_STRING_VIEW path, bool allowEmpty) { + if (!SysSvIsValid(path)) return NTLSTATUS_INVALID_PARAMETER; + if (SysSvContainsByte(path, '\0') || SysSvContainsByte(path, '\\')) + return NTLSTATUS_INVALID_PARAMETER; + if (path.Length == 0) + return allowEmpty ? NTLSTATUS_SUCCESS : NTLSTATUS_INVALID_PARAMETER; + if (path.Length >= SYS_PATH_MAX) + return NTLSTATUS_BUFFER_TOO_SMALL; + if (path.Data[0] == '/' || path.Data[path.Length - 1] == '/') + return NTLSTATUS_INVALID_PARAMETER; -typedef struct KRESOLVED_PATH { - uint8_t Drive; - char Storage[CWD_MAX]; - SYS_STRING_VIEW Path; -} KRESOLVED_PATH; - -// Implements the format Cwd operation. -static size_t KiFormatCwd(uint8_t drive, SYS_STRING_VIEW rel, char* buf, size_t size) { - if (!buf || size == 0 || !SysSvIsValid(rel)) return 0; - if (drive > 7) drive = 0; - - size_t needed = 3 + rel.Length + 1; - if (needed > size) return needed; - - buf[0] = (char)('0' + drive); - buf[1] = ':'; - buf[2] = '/'; - for (size_t i = 0; i < rel.Length; i++) { - char c = rel.Data[i]; - buf[3 + i] = (c == '\\') ? '/' : c; - } - buf[3 + rel.Length] = '\0'; - return needed; -} - -// Implements the sv Has Drive Root operation. -static int KiSvHasDriveRoot(SYS_STRING_VIEW path) { - return path.Length >= 3 && - path.Data[0] >= '0' && path.Data[0] <= '7' && - path.Data[1] == ':' && - (path.Data[2] == '\\' || path.Data[2] == '/'); -} - -static int KiAppendBytes(char* work, size_t* workLen, size_t cap, - const char* data, size_t len) { - if (!work || !workLen || (!data && len)) return 0; - if (*workLen + len >= cap) return 0; - if (len) memcpy(work + *workLen, data, len); - *workLen += len; - work[*workLen] = '\0'; - return 1; -} - -static int KiAppendComponent(char* work, size_t* workLen, size_t cap, - const char* data, size_t len) { - if (!work || !workLen || (!data && len)) return 0; - if (*workLen && work[*workLen - 1] != '\\' && work[*workLen - 1] != '/') { - if (!KiAppendBytes(work, workLen, cap, "\\", 1)) return 0; - } - return KiAppendBytes(work, workLen, cap, data, len); -} - -// Implements the pop Component operation. -static void KiPopComponent(char* work, size_t* workLen) { - if (!work || !workLen || *workLen == 0) return; - size_t i = *workLen; - while (i > 0 && work[i - 1] != '\\' && work[i - 1] != '/') i--; - if (i > 0) i--; - *workLen = i; - work[i] = '\0'; -} - -// Resolve a possibly-relative string view against the CWD. -// The returned view is backed by out->Storage and remains valid while 'out' lives. -static KSTATUS KiResolvePathV(SYS_STRING_VIEW input, KRESOLVED_PATH* out) { - if (!out) return NTLSTATUS_INVALID_PARAMETER; - memset(out, 0, sizeof(*out)); - - if (!SysSvIsValid(input)) return NTLSTATUS_INVALID_PARAMETER; - if (SysSvContainsByte(input, '\0')) return NTLSTATUS_INVALID_PARAMETER; - if (input.Length >= CWD_MAX) return NTLSTATUS_BUFFER_TOO_SMALL; - - uint8_t drive = 0; - PROCESS* proc = KeGetCurrentProcess(); - if (proc) { - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - drive = proc->CurrentDrive; - KeReleaseSpinlock(&proc->CwdLock, flags); - } - - size_t pos = 0; - bool fromDriveRoot = false; - if (KiSvHasDriveRoot(input)) { - drive = (uint8_t)(input.Data[0] - '0'); - pos = 3; - fromDriveRoot = true; - } - - char work[CWD_MAX * 2]; - size_t workLen = 0; - work[0] = '\0'; - - bool absolute = fromDriveRoot || - (pos < input.Length && (input.Data[pos] == '\\' || input.Data[pos] == '/')); - if (!absolute && proc) { - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - size_t cwdLen = proc->CurrentDirectoryLength; - if (cwdLen >= sizeof(work)) cwdLen = sizeof(work) - 1; - memcpy(work, proc->CurrentDirectory, cwdLen); - work[cwdLen] = '\0'; - workLen = cwdLen; - KeReleaseSpinlock(&proc->CwdLock, flags); - } - - while (pos < input.Length) { - while (pos < input.Length && (input.Data[pos] == '\\' || input.Data[pos] == '/')) pos++; - if (pos >= input.Length) break; - - size_t compStart = pos; - while (pos < input.Length && input.Data[pos] != '\\' && input.Data[pos] != '/') pos++; - size_t compLen = pos - compStart; - if (compLen == 0) continue; - - if (compLen == 1 && input.Data[compStart] == '.') { + size_t componentStart = 0; + for (size_t i = 0; i <= path.Length; ++i) { + if (i != path.Length && path.Data[i] != '/') continue; - } - if (compLen == 2 && input.Data[compStart] == '.' && input.Data[compStart + 1] == '.') { - KiPopComponent(work, &workLen); - continue; - } - if (compLen >= 256) return NTLSTATUS_BUFFER_TOO_SMALL; - if (!KiAppendComponent(work, &workLen, sizeof(work), input.Data + compStart, compLen)) + + size_t componentLength = i - componentStart; + if (componentLength == 0 || componentLength >= 256) + return componentLength >= 256 ? NTLSTATUS_BUFFER_TOO_SMALL : NTLSTATUS_INVALID_PARAMETER; + if ((componentLength == 1 && path.Data[componentStart] == '.') || + (componentLength == 2 && path.Data[componentStart] == '.' && + path.Data[componentStart + 1] == '.')) + return NTLSTATUS_INVALID_PARAMETER; + componentStart = i + 1; + } + return NTLSTATUS_SUCCESS; +} + +// Composes a canonical path beneath one already-open directory capability. +static KSTATUS KiComposeDirectoryRelativePath(const DIR_OBJECT* directory, + SYS_STRING_VIEW child, + char* storage, + size_t storageSize, + SYS_STRING_VIEW* pathOut) { + if (!directory || !storage || !pathOut) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = KiValidateNativeRelativePath(child, true); + if (!KSUCCESS(status)) + return status; + + size_t total = directory->PathLength; + if (directory->PathLength && child.Length) { + if (total == SIZE_MAX) return NTLSTATUS_BUFFER_TOO_SMALL; + total++; } + if (child.Length > SIZE_MAX - total) + return NTLSTATUS_BUFFER_TOO_SMALL; + total += child.Length; + if (total >= storageSize) + return NTLSTATUS_BUFFER_TOO_SMALL; - if (workLen + 1 > sizeof(out->Storage)) return NTLSTATUS_BUFFER_TOO_SMALL; - memcpy(out->Storage, work, workLen); - out->Storage[workLen] = '\0'; - out->Drive = drive; - out->Path = SysSvFromParts(out->Storage, workLen); - return NTLSTATUS_SUCCESS; -} - -// Builds a canonical native path using the kernel-owned process CWD. -__declspec(dllexport) KSTATUS IoRealPath(SYS_STRING_VIEW path, char* buffer, size_t bufferSize, size_t* requiredSizeOut) { - if (requiredSizeOut) *requiredSizeOut = 0; - if (path.Length && !path.Data) return NTLSTATUS_INVALID_PARAMETER; - if (path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - if (!buffer && bufferSize != 0) return NTLSTATUS_INVALID_PARAMETER; - - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; - - char formatted[SYS_CWD_MAX]; - size_t required = KiFormatCwd(resolved.Drive, resolved.Path, formatted, sizeof(formatted)); - if (!required || required > sizeof(formatted)) return NTLSTATUS_BUFFER_TOO_SMALL; - - if (requiredSizeOut) *requiredSizeOut = required; - if (!buffer) return NTLSTATUS_SUCCESS; - if (bufferSize < required) return NTLSTATUS_BUFFER_TOO_SMALL; - - memcpy(buffer, formatted, required); - return NTLSTATUS_SUCCESS; -} - -// Updates cwd. -__declspec(dllexport) KSTATUS IoSetCwd(SYS_STRING_VIEW path) { - if (path.Length && !path.Data) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return NTLSTATUS_ACCESS_DENIED; - - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; - - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->OpenDir) return NTLSTATUS_NOT_SUPPORTED; - - void* dirCtx = NULL; - s = fs->OpenDir(fs, resolved.Path, &dirCtx); - if (!KSUCCESS(s)) return s; - fs->CloseDir(fs, dirCtx); - - if (resolved.Path.Length + 1 > sizeof(proc->CurrentDirectory)) return NTLSTATUS_BUFFER_TOO_SMALL; - - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - proc->CurrentDrive = resolved.Drive; - proc->CurrentDirectoryLength = resolved.Path.Length; - memcpy(proc->CurrentDirectory, resolved.Path.Data, resolved.Path.Length); - proc->CurrentDirectory[resolved.Path.Length] = '\0'; - - if (proc->UserCwdBase && proc->UserCwdSize) { - size_t formatted = KiFormatCwd(resolved.Drive, resolved.Path, (char*)proc->UserCwdBase, proc->UserCwdSize); - size_t copyLen = (formatted && formatted <= proc->UserCwdSize) ? formatted - 1 : proc->UserCwdSize - 1; - - SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase; - if (peb && peb->Magic == SYS_PEB_MAGIC) { - peb->CurrentDirectory = SysSvFromParts((const char*)proc->UserCwdBase, copyLen); - } + size_t offset = 0; + if (directory->PathLength) { + memcpy(storage, directory->Path, directory->PathLength); + offset = directory->PathLength; } - - KeReleaseSpinlock(&proc->CwdLock, flags); - return NTLSTATUS_SUCCESS; -} - -// Registration. -__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex, - IO_FILESYSTEM *fs, - const IO_VOLUME_PARAMS *vol) { - if (volumeIndex >= MAX_DRIVES || !fs || !vol) - return NTLSTATUS_INVALID_PARAMETER; - if (gVolumes[volumeIndex].Fs) - return NTLSTATUS_ALREADY_EXISTS; - gVolumes[volumeIndex].Fs = fs; - gVolumes[volumeIndex].Vol = *vol; - KiIoNotifyVolumeNamespaceChange(); - return NTLSTATUS_SUCCESS; -} - -// Implements the unregister Filesystem operation. -__declspec(dllexport) KSTATUS IoUnregisterFilesystem(uint8_t volumeIndex) { - if (volumeIndex >= MAX_DRIVES) - return NTLSTATUS_INVALID_PARAMETER; - if (!gVolumes[volumeIndex].Fs) - return NTLSTATUS_NOT_FOUND; - - memset(&gVolumes[volumeIndex], 0, sizeof(gVolumes[volumeIndex])); - KiIoNotifyVolumeNamespaceChange(); - return NTLSTATUS_SUCCESS; -} - -// Implements the unregister Filesystems For Disk operation. -__declspec(dllexport) KSTATUS IoUnregisterFilesystemsForDisk(uint8_t diskIndex) { - KSTATUS last = NTLSTATUS_NOT_FOUND; - bool changed = false; - for (uint8_t i = 0; i < MAX_DRIVES; i++) { - if (gVolumes[i].Fs && gVolumes[i].Vol.DiskIndex == diskIndex) { - memset(&gVolumes[i], 0, sizeof(gVolumes[i])); - last = NTLSTATUS_SUCCESS; - changed = true; - } - } - if (changed) - KiIoNotifyVolumeNamespaceChange(); - return last; -} - -// Implements the filesystem Present operation. -__declspec(dllexport) bool IoFilesystemPresent(uint8_t volumeIndex) { - if (volumeIndex >= MAX_DRIVES) - return false; - return gVolumes[volumeIndex].Fs != (IO_FILESYSTEM*)0; -} - -// Aggregates filesystem traversal statistics across mounted volumes. -__declspec(dllexport) void IoQueryFilesystemStats(IO_FILESYSTEM_STATS* out) { - if (!out) return; - memset(out, 0, sizeof(*out)); - - for (uint8_t i = 0; i < MAX_DRIVES; i++) { - IO_FILESYSTEM* fs = gVolumes[i].Fs; - if (!fs || !fs->QueryStats) continue; - - IO_FILESYSTEM_STATS one; - memset(&one, 0, sizeof(one)); - fs->QueryStats(fs, &one); - - out->PathLookups += one.PathLookups; - out->PathCacheHits += one.PathCacheHits; - out->PathCacheMisses += one.PathCacheMisses; - out->PathCacheStores += one.PathCacheStores; - out->PathCacheInvalidations += one.PathCacheInvalidations; - if (one.PathCacheGeneration > out->PathCacheGeneration) - out->PathCacheGeneration = one.PathCacheGeneration; - out->DirectorySearches += one.DirectorySearches; - out->DirectoryBlocksRead += one.DirectoryBlocksRead; - out->DirectoryEntriesScanned += one.DirectoryEntriesScanned; - out->DataReadRuns += one.DataReadRuns; - out->DataReadSectors += one.DataReadSectors; - out->DataReadRunMerges += one.DataReadRunMerges; - if (one.MaxDataReadSpanSectors > out->MaxDataReadSpanSectors) - out->MaxDataReadSpanSectors = one.MaxDataReadSpanSectors; + if (directory->PathLength && child.Length) + storage[offset++] = '/'; + if (child.Length) { + memcpy(storage + offset, child.Data, child.Length); + offset += child.Length; } + storage[offset] = '\0'; + *pathOut = SysSvFromParts(storage, offset); + return NTLSTATUS_SUCCESS; } // Destructor. static void KiFileDestroy(OBJECT* obj) { FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header); - if (f->Fs && f->FileCtx) { - // Close drains kernel-owned dirty pages, but stable-media semantics - // remain explicit through IoFlushFile so every close does not issue a - // whole-device cache flush. - (void)MmVadSyncFileMappings(f); - (void)MmFileCacheFlushFile(f); + if (f->Volume && f->FileCtx) { + // Dead volumes must never attempt writeback. The provider context remains + // alive only so file-specific state can be closed safely. + if (IoVolumeIsMounted(f->Volume)) { + (void)MmVadSyncFileMappings(f); + (void)MmFileCacheFlushFile(f); + } MmFileCacheInvalidateAll(f); - f->Fs->Close(f->Fs, f->FileCtx); + IoVolumeClose(f->Volume, f->FileCtx); + ObDereferenceObject(&f->Volume->Header); + f->Volume = (IO_VOLUME*)0; } } -// IoOpenFile. -__declspec(dllexport) KSTATUS IoOpenFile(SYS_STRING_VIEW path, FILE_OBJECT **out) { - if ((!path.Data && path.Length) || !out) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - if (path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; +// Opens or creates a file using one already-owned mounted-volume reference. +// The reference is consumed on every path and becomes FILE_OBJECT ownership on success. +static KSTATUS KiOpenFileWithVolumeReference(IO_VOLUME* volume, + SYS_STRING_VIEW path, + bool create, + FILE_OBJECT** out) { + if (!volume || !out) { + if (volume) ObDereferenceObject(&volume->Header); + return NTLSTATUS_INVALID_PARAMETER; + } + *out = (FILE_OBJECT*)0; - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; + void* fileCtx = (void*)0; + KSTATUS status = create + ? IoVolumeCreate(volume, path, &fileCtx) + : IoVolumeOpen(volume, path, &fileCtx); + if (!KSUCCESS(status)) { + ObDereferenceObject(&volume->Header); + return status; + } - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; + uint64_t fileSize = 0; + if (!create) { + status = IoVolumeGetSize(volume, fileCtx, &fileSize); + if (!KSUCCESS(status)) { + IoVolumeClose(volume, fileCtx); + ObDereferenceObject(&volume->Header); + return status; + } + } - void* fileCtx; - s = fs->Open(fs, resolved.Path, &fileCtx); - if (!KSUCCESS(s)) return s; + FILE_OBJECT* file = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile, sizeof(FILE_OBJECT)); + if (!file) { + IoVolumeClose(volume, fileCtx); + ObDereferenceObject(&volume->Header); + return NTLSTATUS_NO_MEMORY; + } - // File objects reuse the generic object slot because the handle table only needs the shared header contract here. - FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile, - sizeof(FILE_OBJECT)); - if (!f) { fs->Close(fs, fileCtx); return NTLSTATUS_NO_MEMORY; } - f->Header.Destroy = KiFileDestroy; - f->Fs = fs; - f->FileCtx = fileCtx; - f->Lock = (SPINLOCK)SPINLOCK_INIT; - f->Size = fs->GetSize(fs, fileCtx); - f->Offset = 0; - f->Flags = 0; - - *out = f; + file->Header.Destroy = KiFileDestroy; + file->Volume = volume; + file->FileCtx = fileCtx; + file->Lock = (SPINLOCK)SPINLOCK_INIT; + file->Size = fileSize; + file->Offset = 0; + file->Flags = 0; + *out = file; return NTLSTATUS_SUCCESS; } +// Opens one canonical native path relative to a mounted volume capability. +__declspec(dllexport) KSTATUS IoOpenFileAtVolume(IO_VOLUME* volume, + SYS_STRING_VIEW path, + FILE_OBJECT** out) { + if (!volume || !out) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = KiValidateNativeRelativePath(path, false); + if (!KSUCCESS(status)) return status; + if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; + if (!IoVolumeTryReferenceMounted(volume)) return NTLSTATUS_OBJECT_DEAD; + return KiOpenFileWithVolumeReference(volume, path, false, out); +} + +// Creates one canonical native path relative to a mounted volume capability. +__declspec(dllexport) KSTATUS IoCreateFileAtVolume(IO_VOLUME* volume, + SYS_STRING_VIEW path, + FILE_OBJECT** out) { + if (!volume || !out) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = KiValidateNativeRelativePath(path, false); + if (!KSUCCESS(status)) return status; + if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; + if (!IoVolumeTryReferenceMounted(volume)) return NTLSTATUS_OBJECT_DEAD; + return KiOpenFileWithVolumeReference(volume, path, true, out); +} + // Explicit-offset file I/O. Async I/O is built on these routines so it does // not race the sequential FILE_OBJECT.Offset used by legacy read/write calls. __declspec(dllexport) KSTATUS IoReadFileAt(FILE_OBJECT* file, uint64_t offset, @@ -918,19 +674,20 @@ __declspec(dllexport) KSTATUS IoReadFileAt(FILE_OBJECT* file, uint64_t offset, if (bytesRead) *bytesRead = 0; if (!file || !buf || !bytesRead) return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - if (size == 0) return NTLSTATUS_SUCCESS; + if (size == 0) return IoVolumeIsMounted(file->Volume) ? NTLSTATUS_SUCCESS : NTLSTATUS_OBJECT_DEAD; uintptr_t flags = KeAcquireSpinlock(&file->Lock); uint64_t fileSize = file->Size; KeReleaseSpinlock(&file->Lock, flags); - if (offset >= fileSize) return NTLSTATUS_SUCCESS; + if (offset >= fileSize) + return IoVolumeIsMounted(file->Volume) ? NTLSTATUS_SUCCESS : NTLSTATUS_OBJECT_DEAD; size_t remaining = (size_t)(fileSize - offset); if (size > remaining) size = remaining; if ((file->Flags & IO_FILE_FLAG_NO_CACHE) || size < 512) - return file->Fs->Read(file->Fs, file->FileCtx, offset, buf, size, bytesRead); + return IoVolumeRead(file->Volume, file->FileCtx, offset, buf, size, bytesRead); size_t total = 0; uint8_t* dst = (uint8_t*)buf; @@ -942,13 +699,13 @@ __declspec(dllexport) KSTATUS IoReadFileAt(FILE_OBJECT* file, uint64_t offset, if (chunk > size - total) chunk = size - total; MM_FILE_CACHE_PAGE page; - KSTATUS s = MmFileCacheGetPage(file, pageOffset, &page); - if (!KSUCCESS(s)) { + KSTATUS readStatus = MmFileCacheGetPage(file, pageOffset, &page); + if (!KSUCCESS(readStatus)) { if (total) { *bytesRead = total; return NTLSTATUS_SUCCESS; } - return s; + return readStatus; } if (inPage >= page.ValidBytes) { @@ -980,8 +737,7 @@ __declspec(dllexport) KSTATUS IoWriteFileAt(FILE_OBJECT* file, uint64_t offset, if (bytesWritten) *bytesWritten = 0; if (!file || !buf || !bytesWritten) return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - if (!file->Fs->Write) return NTLSTATUS_NOT_SUPPORTED; - if (size == 0) return NTLSTATUS_SUCCESS; + if (size == 0) return IoVolumeIsMounted(file->Volume) ? NTLSTATUS_SUCCESS : NTLSTATUS_OBJECT_DEAD; uintptr_t flags = KeAcquireSpinlock(&file->Lock); uint64_t oldSize = file->Size; @@ -990,16 +746,17 @@ __declspec(dllexport) KSTATUS IoWriteFileAt(FILE_OBJECT* file, uint64_t offset, size_t written = 0; KSTATUS s; if (file->Flags & IO_FILE_FLAG_NO_CACHE) { - s = file->Fs->Write(file->Fs, file->FileCtx, offset, buf, size, &written); + s = IoVolumeWrite(file->Volume, file->FileCtx, offset, buf, size, &written); if (KSUCCESS(s) && written) MmFileCacheInvalidate(file, offset, written); } else { + if (!IoVolumeIsMounted(file->Volume)) return NTLSTATUS_OBJECT_DEAD; s = MmFileCacheWrite(file, offset, buf, size, oldSize, &written); } if (KSUCCESS(s)) { flags = KeAcquireSpinlock(&file->Lock); - uint64_t end = offset + written; - if (end > file->Size) file->Size = end; + uint64_t endOffset = offset + written; + if (endOffset > file->Size) file->Size = endOffset; KeReleaseSpinlock(&file->Lock, flags); } @@ -1011,12 +768,13 @@ __declspec(dllexport) KSTATUS IoWriteFileAt(FILE_OBJECT* file, uint64_t offset, __declspec(dllexport) KSTATUS IoFlushFile(FILE_OBJECT* file) { if (!file) return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; + if (!IoVolumeIsMounted(file->Volume)) return NTLSTATUS_OBJECT_DEAD; KSTATUS sync = MmVadSyncFileMappings(file); KSTATUS cache = MmFileCacheFlushFile(file); KSTATUS media = NTLSTATUS_SUCCESS; - if (KSUCCESS(sync) && KSUCCESS(cache) && file->Fs && file->Fs->Flush) - media = file->Fs->Flush(file->Fs, file->FileCtx); + if (KSUCCESS(sync) && KSUCCESS(cache)) + media = IoVolumeFlush(file->Volume, file->FileCtx); if (!KSUCCESS(sync)) return sync; if (!KSUCCESS(cache)) return cache; return media; @@ -1060,121 +818,18 @@ __declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf, return s; } -// IoCreateFile - create a new file and return it open for writing. -__declspec(dllexport) KSTATUS IoCreateFile(SYS_STRING_VIEW path, FILE_OBJECT** out) { - if ((!path.Data && path.Length) || !out || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; - - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->Create) return NTLSTATUS_NOT_SUPPORTED; - - void* fileCtx; - s = fs->Create(fs, resolved.Path, &fileCtx); - if (!KSUCCESS(s)) return s; - - FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile, - sizeof(FILE_OBJECT)); - if (!f) { fs->Close(fs, fileCtx); return NTLSTATUS_NO_MEMORY; } - f->Header.Destroy = KiFileDestroy; - f->Fs = fs; - f->FileCtx = fileCtx; - f->Lock = (SPINLOCK)SPINLOCK_INIT; - f->Size = 0; - f->Offset = 0; - f->Flags = 0; - - *out = f; - return NTLSTATUS_SUCCESS; -} - -// IoDeleteFile - delete a file by path (does not require an open handle). -__declspec(dllexport) KSTATUS IoDeleteFile(SYS_STRING_VIEW path) { - if ((!path.Data && path.Length) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; - - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->Delete) return NTLSTATUS_NOT_SUPPORTED; - - return fs->Delete(fs, resolved.Path); -} - -// Implements the rename File operation. -__declspec(dllexport) KSTATUS IoRenameFile(SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) { - if ((!oldPath.Data && oldPath.Length) || (!newPath.Data && newPath.Length) || - oldPath.Length == 0 || newPath.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - - KRESOLVED_PATH oldResolved; - KRESOLVED_PATH newResolved; - KSTATUS s = KiResolvePathV(oldPath, &oldResolved); - if (!KSUCCESS(s)) return s; - s = KiResolvePathV(newPath, &newResolved); - if (!KSUCCESS(s)) return s; - if (oldResolved.Drive != newResolved.Drive) return NTLSTATUS_NOT_SUPPORTED; - - IO_FILESYSTEM* fs = gVolumes[oldResolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->Rename) return NTLSTATUS_NOT_SUPPORTED; - - return fs->Rename(fs, oldResolved.Path, newResolved.Path); -} - -// Creates dir. -__declspec(dllexport) KSTATUS IoCreateDir(SYS_STRING_VIEW path) { - if ((!path.Data && path.Length) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; - - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->CreateDir) return NTLSTATUS_NOT_SUPPORTED; - - return fs->CreateDir(fs, resolved.Path); -} - -// Releases dir. -__declspec(dllexport) KSTATUS IoRemoveDir(SYS_STRING_VIEW path) { - if ((!path.Data && path.Length) || path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; - - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->RemoveDir) return NTLSTATUS_NOT_SUPPORTED; - - return fs->RemoveDir(fs, resolved.Path); -} - // IoTruncateFile - resize the file; updates the cached Size on success. __declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) { if (!file) return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - if (!file->Fs->Truncate) return NTLSTATUS_NOT_SUPPORTED; KSTATUS flush = IoFlushFile(file); if (!KSUCCESS(flush)) return flush; - KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize, file->Flags); + KSTATUS s = IoVolumeTruncate(file->Volume, file->FileCtx, newSize, file->Flags); if (KSUCCESS(s)) { MmFileCacheInvalidate(file, newSize, (size_t)-1); file->Size = newSize; - // Pull the file pointer back if it now sits past EOF. if (file->Offset > file->Size) file->Offset = file->Size; } @@ -1207,10 +862,9 @@ __declspec(dllexport) KSTATUS IoGetFileStat(FILE_OBJECT* file, SYS_FILE_STAT* ou out->LinkCount = 1; out->FileSize = IoGetFileSize(file); - if (!file->Fs || !file->Fs->QueryInfo) + KSTATUS s = IoVolumeQueryInfo(file->Volume, file->FileCtx, out); + if (s == NTLSTATUS_NOT_SUPPORTED) return NTLSTATUS_SUCCESS; - - KSTATUS s = file->Fs->QueryInfo(file->Fs, file->FileCtx, out); if (KSUCCESS(s)) { uintptr_t flags = KeAcquireSpinlock(&file->Lock); file->Size = out->FileSize; @@ -1219,32 +873,12 @@ __declspec(dllexport) KSTATUS IoGetFileStat(FILE_OBJECT* file, SYS_FILE_STAT* ou return s; } -// Queries metadata for a filesystem path. -__declspec(dllexport) KSTATUS IoStatFile(SYS_STRING_VIEW path, SYS_FILE_STAT* out) { - if ((!path.Data && path.Length) || path.Length == 0 || !out) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - - KRESOLVED_PATH resolved; - KSTATUS s = KiResolvePathV(path, &resolved); - if (!KSUCCESS(s)) return s; - - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->Stat) return NTLSTATUS_NOT_SUPPORTED; - - memset(out, 0, sizeof(*out)); - out->Size = sizeof(*out); - return fs->Stat(fs, resolved.Path, out); -} - -// Queries the filesystem identity for a file object. +// Queries the filesystem identity for a file object. VolumeId is always the IO_VOLUME runtime InstanceId. __declspec(dllexport) KSTATUS IoQueryFileIdentity(FILE_OBJECT* file, IO_FILE_IDENTITY* out) { if (!file || !out) return NTLSTATUS_INVALID_PARAMETER; memset(out, 0, sizeof(*out)); - if (!file->Fs || !file->Fs->QueryIdentity) - return NTLSTATUS_NOT_SUPPORTED; - return file->Fs->QueryIdentity(file->Fs, file->FileCtx, out); + return IoVolumeQueryIdentity(file->Volume, file->FileCtx, out); } // Acquires an advisory process-owned byte-range lock for one file identity. @@ -1262,9 +896,9 @@ __declspec(dllexport) KSTATUS IoLockFile(FILE_OBJECT* file, PROCESS* owner, uint KiIoEnsureFileLockState(); - uint64_t deadlineTick = 0; + uint64_t deadlineMicroseconds = 0; if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_WAIT_TIMEOUT_INFINITE) - deadlineTick = KiIoFileLockTimeoutDeadline(timeoutMilliseconds); + deadlineMicroseconds = KiIoFileLockTimeoutDeadline(timeoutMilliseconds); EVENT* waitEvent = (EVENT*)0; IO_FILE_LOCK_WAITER waiter; @@ -1321,7 +955,7 @@ __declspec(dllexport) KSTATUS IoLockFile(FILE_OBJECT* file, PROCESS* owner, uint ListInsertTail(&gFileLockWaiters, &waiter.Link); KeReleaseSpinlock(&gFileLockLock, flags); - s = KiIoWaitForFileLockChange(waitEvent, timeoutMilliseconds, deadlineTick); + s = KiIoWaitForFileLockChange(waitEvent, timeoutMilliseconds, deadlineMicroseconds); flags = KeAcquireSpinlock(&gFileLockLock); if (waiter.Armed) { @@ -1410,63 +1044,199 @@ __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) { // Directory enumeration. static void KiDirDestroy(OBJECT* obj) { - DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member - if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx); + DIR_OBJECT* dir = (DIR_OBJECT*)obj; // Header is first member. + if (dir->Volume && dir->DirCtx) { + IoVolumeCloseDir(dir->Volume, dir->DirCtx); + ObDereferenceObject(&dir->Volume->Header); + dir->Volume = (IO_VOLUME*)0; + } } -// Opens dir. -__declspec(dllexport) KSTATUS IoOpenDir(SYS_STRING_VIEW dirPath, DIR_OBJECT** out) { - if (!out) return NTLSTATUS_INVALID_PARAMETER; - if (dirPath.Length && !dirPath.Data) return NTLSTATUS_INVALID_PARAMETER; - if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; +// Opens a provider directory using one already-owned mounted-volume reference. +// The reference is consumed on every path and becomes DIR_OBJECT ownership on success. +static KSTATUS KiOpenDirWithVolumeReference(IO_VOLUME* volume, + SYS_STRING_VIEW path, + DIR_OBJECT** out) { + if (!volume || !out) { + if (volume) ObDereferenceObject(&volume->Header); + return NTLSTATUS_INVALID_PARAMETER; + } + *out = (DIR_OBJECT*)0; - KRESOLVED_PATH resolved; - KSTATUS s; - - if (dirPath.Length == 0) { - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return NTLSTATUS_ACCESS_DENIED; - - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - size_t cwdLen = proc->CurrentDirectoryLength; - if (cwdLen + 1 > sizeof(resolved.Storage)) { - KeReleaseSpinlock(&proc->CwdLock, flags); - return NTLSTATUS_BUFFER_TOO_SMALL; - } - memcpy(resolved.Storage, proc->CurrentDirectory, cwdLen + 1); - resolved.Drive = proc->CurrentDrive; - resolved.Path = SysSvFromParts(resolved.Storage, cwdLen); - KeReleaseSpinlock(&proc->CwdLock, flags); - } else { - s = KiResolvePathV(dirPath, &resolved); - if (!KSUCCESS(s)) return s; + void* dirCtx = (void*)0; + KSTATUS status = IoVolumeOpenDir(volume, path, &dirCtx); + if (!KSUCCESS(status)) { + ObDereferenceObject(&volume->Header); + return status; } - IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; - if (!fs) return NTLSTATUS_NOT_FOUND; - if (!fs->OpenDir) return NTLSTATUS_NOT_SUPPORTED; + if (path.Length > SIZE_MAX - sizeof(DIR_OBJECT) - 1) { + IoVolumeCloseDir(volume, dirCtx); + ObDereferenceObject(&volume->Header); + return NTLSTATUS_BUFFER_TOO_SMALL; + } + size_t allocationSize = sizeof(DIR_OBJECT) + path.Length + 1; + DIR_OBJECT* dir = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, allocationSize); + if (!dir) { + IoVolumeCloseDir(volume, dirCtx); + ObDereferenceObject(&volume->Header); + return NTLSTATUS_NO_MEMORY; + } - void* dirCtx = NULL; - s = fs->OpenDir(fs, resolved.Path, &dirCtx); - if (!KSUCCESS(s)) return s; - - DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT)); - if (!d) { fs->CloseDir(fs, dirCtx); return NTLSTATUS_NO_MEMORY; } - - d->Header.Destroy = KiDirDestroy; - d->Fs = fs; - d->DirCtx = dirCtx; - - *out = d; + dir->Header.Destroy = KiDirDestroy; + dir->Volume = volume; + dir->DirCtx = dirCtx; + dir->Path = (char*)(dir + 1); + dir->PathLength = path.Length; + for (size_t i = 0; i < path.Length; ++i) + dir->Path[i] = path.Data[i] == '\\' ? '/' : path.Data[i]; + dir->Path[path.Length] = '\0'; + *out = dir; return NTLSTATUS_SUCCESS; } +// Opens one canonical native directory path relative to a mounted volume capability. +__declspec(dllexport) KSTATUS IoOpenDirAtVolume(IO_VOLUME* volume, + SYS_STRING_VIEW path, + DIR_OBJECT** out) { + if (!volume || !out) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = KiValidateNativeRelativePath(path, true); + if (!KSUCCESS(status)) return status; + if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; + if (!IoVolumeTryReferenceMounted(volume)) return NTLSTATUS_OBJECT_DEAD; + return KiOpenDirWithVolumeReference(volume, path, out); +} + +// Opens one file beneath an already-open directory capability. +KSTATUS IoOpenFileAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, FILE_OBJECT** out) { + if (!directory || !out) return NTLSTATUS_INVALID_PARAMETER; + char storage[SYS_PATH_MAX]; + SYS_STRING_VIEW combined; + KSTATUS status = KiComposeDirectoryRelativePath(directory, path, storage, sizeof(storage), &combined); + if (!KSUCCESS(status)) return status; + if (combined.Length == 0) return NTLSTATUS_INVALID_PARAMETER; + return IoOpenFileAtVolume(directory->Volume, combined, out); +} + +// Creates one file beneath an already-open directory capability. +KSTATUS IoCreateFileAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, FILE_OBJECT** out) { + if (!directory || !out) return NTLSTATUS_INVALID_PARAMETER; + char storage[SYS_PATH_MAX]; + SYS_STRING_VIEW combined; + KSTATUS status = KiComposeDirectoryRelativePath(directory, path, storage, sizeof(storage), &combined); + if (!KSUCCESS(status)) return status; + if (combined.Length == 0) return NTLSTATUS_INVALID_PARAMETER; + return IoCreateFileAtVolume(directory->Volume, combined, out); +} + +// Opens one directory beneath an already-open directory capability. +KSTATUS IoOpenDirAtDirectory(DIR_OBJECT* directory, SYS_STRING_VIEW path, DIR_OBJECT** out) { + if (!directory || !out) return NTLSTATUS_INVALID_PARAMETER; + char storage[SYS_PATH_MAX]; + SYS_STRING_VIEW combined; + KSTATUS status = KiComposeDirectoryRelativePath(directory, path, storage, sizeof(storage), &combined); + if (!KSUCCESS(status)) return status; + return IoOpenDirAtVolume(directory->Volume, combined, out); +} + +// Resolves one capability-relative namespace path to its owning volume and canonical provider path. +static KSTATUS KiResolveAtRootPath(OBJECT* root, + SYS_STRING_VIEW path, + bool allowEmpty, + char* storage, + size_t storageSize, + IO_VOLUME** volumeOut, + SYS_STRING_VIEW* pathOut) { + if (!root || !storage || !volumeOut || !pathOut) + return NTLSTATUS_INVALID_PARAMETER; + *volumeOut = (IO_VOLUME*)0; + *pathOut = SysSvFromParts((const char*)0, 0); + + if (root->Type == ObjectTypeVolume) { + KSTATUS status = KiValidateNativeRelativePath(path, allowEmpty); + if (!KSUCCESS(status)) return status; + *volumeOut = (IO_VOLUME*)root; + *pathOut = path; + return NTLSTATUS_SUCCESS; + } + if (root->Type == ObjectTypeDir) { + DIR_OBJECT* directory = (DIR_OBJECT*)root; + KSTATUS status = KiComposeDirectoryRelativePath(directory, path, storage, storageSize, pathOut); + if (!KSUCCESS(status)) return status; + if (!allowEmpty && pathOut->Length == 0) return NTLSTATUS_INVALID_PARAMETER; + *volumeOut = directory->Volume; + return NTLSTATUS_SUCCESS; + } + return NTLSTATUS_INVALID_HANDLE; +} + +// Queries metadata beneath one mounted-volume or directory capability. +KSTATUS IoStatAtRoot(OBJECT* root, SYS_STRING_VIEW path, SYS_FILE_STAT* out) { + if (!out) return NTLSTATUS_INVALID_PARAMETER; + char storage[SYS_PATH_MAX]; + IO_VOLUME* volume = (IO_VOLUME*)0; + SYS_STRING_VIEW resolved; + KSTATUS status = KiResolveAtRootPath(root, path, true, storage, sizeof(storage), &volume, &resolved); + if (!KSUCCESS(status)) return status; + memset(out, 0, sizeof(*out)); + out->Size = sizeof(*out); + return IoVolumeStat(volume, resolved, out); +} + +// Deletes one file beneath one mounted-volume or directory capability. +KSTATUS IoDeleteFileAtRoot(OBJECT* root, SYS_STRING_VIEW path) { + char storage[SYS_PATH_MAX]; + IO_VOLUME* volume = (IO_VOLUME*)0; + SYS_STRING_VIEW resolved; + KSTATUS status = KiResolveAtRootPath(root, path, false, storage, sizeof(storage), &volume, &resolved); + if (!KSUCCESS(status)) return status; + return IoVolumeDelete(volume, resolved); +} + +// Renames one path between capability roots on the same mounted volume. +KSTATUS IoRenameFileAtRoots(OBJECT* oldRoot, SYS_STRING_VIEW oldPath, + OBJECT* newRoot, SYS_STRING_VIEW newPath) { + char oldStorage[SYS_PATH_MAX]; + char newStorage[SYS_PATH_MAX]; + IO_VOLUME* oldVolume = (IO_VOLUME*)0; + IO_VOLUME* newVolume = (IO_VOLUME*)0; + SYS_STRING_VIEW oldResolved; + SYS_STRING_VIEW newResolved; + KSTATUS status = KiResolveAtRootPath(oldRoot, oldPath, false, oldStorage, sizeof(oldStorage), + &oldVolume, &oldResolved); + if (!KSUCCESS(status)) return status; + status = KiResolveAtRootPath(newRoot, newPath, false, newStorage, sizeof(newStorage), + &newVolume, &newResolved); + if (!KSUCCESS(status)) return status; + if (oldVolume != newVolume) return NTLSTATUS_NOT_SUPPORTED; + return IoVolumeRename(oldVolume, oldResolved, newResolved); +} + +// Creates one directory beneath one mounted-volume or directory capability. +KSTATUS IoCreateDirAtRoot(OBJECT* root, SYS_STRING_VIEW path) { + char storage[SYS_PATH_MAX]; + IO_VOLUME* volume = (IO_VOLUME*)0; + SYS_STRING_VIEW resolved; + KSTATUS status = KiResolveAtRootPath(root, path, false, storage, sizeof(storage), &volume, &resolved); + if (!KSUCCESS(status)) return status; + return IoVolumeCreateDir(volume, resolved); +} + +// Removes one directory beneath one mounted-volume or directory capability. +KSTATUS IoRemoveDirAtRoot(OBJECT* root, SYS_STRING_VIEW path) { + char storage[SYS_PATH_MAX]; + IO_VOLUME* volume = (IO_VOLUME*)0; + SYS_STRING_VIEW resolved; + KSTATUS status = KiResolveAtRootPath(root, path, false, storage, sizeof(storage), &volume, &resolved); + if (!KSUCCESS(status)) return status; + return IoVolumeRemoveDir(volume, resolved); +} + // Returns dir. __declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) { if (!dir || !entryOut) return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - if (!dir->Fs->ReadDir) return NTLSTATUS_NOT_SUPPORTED; - return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut); + return IoVolumeReadDir(dir->Volume, dir->DirCtx, entryOut); } // Releases dir. diff --git a/code/kernel/io_partition.c b/code/kernel/io_partition.c new file mode 100644 index 0000000..9cc84f2 --- /dev/null +++ b/code/kernel/io_partition.c @@ -0,0 +1,772 @@ +// Dynamic partition discovery. This file owns MBR/GPT parsing and keeps structural identity out of filesystem providers. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IOP_SECTOR_SIZE 512u +#define IOP_GPT_SIGNATURE "EFI PART" +#define IOP_GPT_REVISION_1_0 0x00010000u +#define IOP_GPT_MIN_ENTRY_SIZE 128u + +#pragma pack(push, 1) +typedef struct IOP_MBR_PARTITION_ENTRY { + uint8_t BootIndicator; + uint8_t StartingChs[3]; + uint8_t Type; + uint8_t EndingChs[3]; + uint32_t StartingLba; + uint32_t SectorCount; +} IOP_MBR_PARTITION_ENTRY; + +typedef struct IOP_GPT_HEADER { + char Signature[8]; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t HeaderCrc32; + uint32_t Reserved; + uint64_t MyLba; + uint64_t AlternateLba; + uint64_t FirstUsableLba; + uint64_t LastUsableLba; + BOOT_VOLUME_GUID DiskGuid; + uint64_t PartitionEntryLba; + uint32_t NumberOfPartitionEntries; + uint32_t SizeOfPartitionEntry; + uint32_t PartitionEntryArrayCrc32; +} IOP_GPT_HEADER; + +typedef struct IOP_GPT_ENTRY_PREFIX { + BOOT_VOLUME_GUID PartitionTypeGuid; + BOOT_VOLUME_GUID UniquePartitionGuid; + uint64_t StartingLba; + uint64_t EndingLba; + uint64_t Attributes; +} IOP_GPT_ENTRY_PREFIX; +#pragma pack(pop) + +_Static_assert(sizeof(IOP_MBR_PARTITION_ENTRY) == 16u, "MBR partition entry layout must match disk format"); +_Static_assert(sizeof(IOP_GPT_HEADER) == 92u, "GPT header prefix layout must match disk format"); +_Static_assert(sizeof(IOP_GPT_ENTRY_PREFIX) == 56u, "GPT entry prefix layout must match disk format"); + +typedef struct IOP_PARTITION_RECORD { + struct IOP_PARTITION_RECORD* Next; + IO_PARTITION_INFO Info; + BOOT_VOLUME_SELECTOR Identity; +} IOP_PARTITION_RECORD; + +typedef struct IOP_PARTITION_CALLBACK_ENTRY { + struct IOP_PARTITION_CALLBACK_ENTRY* Next; + IO_PARTITION_CHANGE_CALLBACK Callback; + void* Context; +} IOP_PARTITION_CALLBACK_ENTRY; + +typedef struct IOP_GPT_VALIDATED_HEADER { + IOP_GPT_HEADER Header; + uint64_t EntryBytes; + uint64_t EntrySectors; +} IOP_GPT_VALIDATED_HEADER; + +static MUTEX* gPartitionMutex; +static IOP_PARTITION_RECORD* gPartitions; +static IOP_PARTITION_CALLBACK_ENTRY* gPartitionCallbacks; +static BOOT_VOLUME_SELECTOR gSystemVolumeSelector; +static bool gPartitionManagerInitialized; + +// Returns whether two shared GUID values are identical. +static bool IopGuidEquals(const BOOT_VOLUME_GUID* left, const BOOT_VOLUME_GUID* right) { + return left && right && memcmp(left, right, sizeof(*left)) == 0; +} + +// Returns whether one shared GUID value is all zeroes. +static bool IopGuidIsZero(const BOOT_VOLUME_GUID* guid) { + static const BOOT_VOLUME_GUID zero = {0}; + return guid && IopGuidEquals(guid, &zero); +} + +// Returns whether one internal partition identity equals another. +static bool IopIdentityEquals(const BOOT_VOLUME_SELECTOR* left, const BOOT_VOLUME_SELECTOR* right) { + if (!left || !right || left->Kind != right->Kind) + return false; + + if (left->Kind == BootVolumeSelectorMbr) { + return left->Identity.Mbr.DiskSignature == right->Identity.Mbr.DiskSignature && + left->Identity.Mbr.PartitionNumber == right->Identity.Mbr.PartitionNumber; + } + if (left->Kind == BootVolumeSelectorGpt) { + return IopGuidEquals(&left->Identity.Gpt.PartitionGuid, + &right->Identity.Gpt.PartitionGuid); + } + return false; +} + +// Acquires the partition-manager mutex for registry and callback serialization. +static bool IopAcquirePartitionMutex(void) { + if (!gPartitionMutex) + return false; + return KE_MUTEX_ACQUIRE_SUCCEEDED(KeAcquireMutex(gPartitionMutex, KE_WAIT_INFINITE)); +} + +// Frees one dynamically discovered partition list. +static void IopFreePartitionList(IOP_PARTITION_RECORD* head) { + while (head) { + IOP_PARTITION_RECORD* next = head->Next; + MmFreePool(head); + head = next; + } +} + +// Updates one CRC32 accumulator using the GPT/IEEE polynomial. +static uint32_t IopCrc32Update(uint32_t crc, const uint8_t* data, size_t length) { + for (size_t i = 0; i < length; ++i) { + crc ^= data[i]; + for (uint32_t bit = 0; bit < 8; ++bit) + crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int32_t)(crc & 1u)); + } + return crc; +} + +// Computes a complete GPT CRC32 value. +static uint32_t IopCrc32(const void* data, size_t length) { + uint32_t crc = IopCrc32Update(0xffffffffu, (const uint8_t*)data, length); + return crc ^ 0xffffffffu; +} + +// Reads an arbitrary byte span through the kernel's 512-byte block-device API. +static KSTATUS IopReadDiskBytes(uint8_t diskIndex, + uint64_t diskSectors, + uint64_t byteOffset, + void* buffer, + size_t length) { + if (!buffer || length == 0) + return NTLSTATUS_INVALID_PARAMETER; + if (diskSectors > UINT64_MAX / IOP_SECTOR_SIZE) + return NTLSTATUS_INVALID_PARAMETER; + + uint64_t diskBytes = diskSectors * IOP_SECTOR_SIZE; + if (byteOffset > diskBytes || (uint64_t)length > diskBytes - byteOffset) + return NTLSTATUS_INVALID_PARAMETER; + + uint8_t sector[IOP_SECTOR_SIZE]; + uint8_t* out = (uint8_t*)buffer; + size_t remaining = length; + while (remaining) { + uint64_t lba = byteOffset / IOP_SECTOR_SIZE; + uint32_t inSector = (uint32_t)(byteOffset % IOP_SECTOR_SIZE); + size_t chunk = IOP_SECTOR_SIZE - inSector; + if (chunk > remaining) + chunk = remaining; + + KSTATUS status = IoReadDiskSectors(diskIndex, lba, 1, sector); + if (!KSUCCESS(status)) + return status; + memcpy(out, sector + inSector, chunk); + out += chunk; + byteOffset += chunk; + remaining -= chunk; + } + return NTLSTATUS_SUCCESS; +} + +// Returns whether one MBR type is an extended-partition container we intentionally do not enumerate. +static bool IopMbrTypeIsExtended(uint8_t type) { + return type == 0x05u || type == 0x0fu || type == 0x85u; +} + +// Allocates one immutable partition record. +static IOP_PARTITION_RECORD* IopAllocatePartition(uint8_t diskIndex, + uint64_t startLba, + uint64_t sectorCount, + const BOOT_VOLUME_SELECTOR* identity) { + if (!sectorCount || !identity) + return NULL; + + IOP_PARTITION_RECORD* record = MmAllocatePool(sizeof(*record)); + if (!record) + return NULL; + memset(record, 0, sizeof(*record)); + record->Info.DiskIndex = diskIndex; + record->Info.StartLba = startLba; + record->Info.SectorCount = sectorCount; + record->Identity = *identity; + return record; +} + +// Inserts one discovered partition in ascending LBA order and rejects overlap or duplicate identity. +static KSTATUS IopInsertDiscoveredPartition(IOP_PARTITION_RECORD** head, + IOP_PARTITION_RECORD* record) { + if (!head || !record || record->Info.SectorCount == 0) + return NTLSTATUS_INVALID_PARAMETER; + + if (record->Info.StartLba > UINT64_MAX - record->Info.SectorCount) + return NTLSTATUS_INVALID_PARAMETER; + uint64_t recordEnd = record->Info.StartLba + record->Info.SectorCount; + + IOP_PARTITION_RECORD* previous = NULL; + IOP_PARTITION_RECORD* current = *head; + while (current && current->Info.StartLba < record->Info.StartLba) { + if (IopIdentityEquals(¤t->Identity, &record->Identity)) + return NTLSTATUS_OBJECT_NAME_COLLISION; + previous = current; + current = current->Next; + } + for (IOP_PARTITION_RECORD* scan = current; scan; scan = scan->Next) { + if (IopIdentityEquals(&scan->Identity, &record->Identity)) + return NTLSTATUS_OBJECT_NAME_COLLISION; + } + + if (previous) { + uint64_t previousEnd = previous->Info.StartLba + previous->Info.SectorCount; + if (previousEnd > record->Info.StartLba) + return NTLSTATUS_INVALID_PARAMETER; + } + if (current && recordEnd > current->Info.StartLba) + return NTLSTATUS_INVALID_PARAMETER; + + record->Next = current; + if (previous) + previous->Next = record; + else + *head = record; + return NTLSTATUS_SUCCESS; +} + +// Validates the exact CRC32 of one GPT partition-entry array without imposing an entry-count cap. +static KSTATUS IopValidateGptEntryArrayCrc(uint8_t diskIndex, + const IOP_GPT_VALIDATED_HEADER* validated) { + uint8_t sector[IOP_SECTOR_SIZE]; + uint64_t remaining = validated->EntryBytes; + uint64_t lba = validated->Header.PartitionEntryLba; + uint32_t crc = 0xffffffffu; + while (remaining) { + KSTATUS status = IoReadDiskSectors(diskIndex, lba++, 1, sector); + if (!KSUCCESS(status)) + return status; + size_t chunk = remaining < IOP_SECTOR_SIZE ? (size_t)remaining : IOP_SECTOR_SIZE; + crc = IopCrc32Update(crc, sector, chunk); + remaining -= chunk; + } + crc ^= 0xffffffffu; + return crc == validated->Header.PartitionEntryArrayCrc32 + ? NTLSTATUS_SUCCESS + : NTLSTATUS_CHECKSUM_MISMATCH; +} + +// Validates one primary or backup GPT header and its partition-entry-array CRC. +static KSTATUS IopValidateGptHeader(uint8_t diskIndex, + uint64_t diskSectors, + uint64_t headerLba, + uint64_t expectedAlternateLba, + IOP_GPT_VALIDATED_HEADER* validatedOut) { + if (!validatedOut || diskSectors < 3 || headerLba >= diskSectors || + expectedAlternateLba >= diskSectors || headerLba == expectedAlternateLba) { + return NTLSTATUS_INVALID_PARAMETER; + } + + uint8_t sector[IOP_SECTOR_SIZE]; + KSTATUS status = IoReadDiskSectors(diskIndex, headerLba, 1, sector); + if (!KSUCCESS(status)) + return status; + + IOP_GPT_HEADER header; + memcpy(&header, sector, sizeof(header)); + if (memcmp(header.Signature, IOP_GPT_SIGNATURE, 8) != 0 || + header.Revision < IOP_GPT_REVISION_1_0 || + header.HeaderSize < sizeof(IOP_GPT_HEADER) || + header.HeaderSize > IOP_SECTOR_SIZE || + header.Reserved != 0 || + header.MyLba != headerLba || + header.AlternateLba != expectedAlternateLba || + header.FirstUsableLba > header.LastUsableLba || + header.LastUsableLba >= diskSectors || + IopGuidIsZero(&header.DiskGuid) || + header.NumberOfPartitionEntries == 0 || + header.SizeOfPartitionEntry < IOP_GPT_MIN_ENTRY_SIZE || + (header.SizeOfPartitionEntry & 7u) != 0) { + return NTLSTATUS_INVALID_PARAMETER; + } + + uint32_t expectedHeaderCrc = header.HeaderCrc32; + memset(sector + offsetof(IOP_GPT_HEADER, HeaderCrc32), 0, sizeof(header.HeaderCrc32)); + if (IopCrc32(sector, header.HeaderSize) != expectedHeaderCrc) + return NTLSTATUS_CHECKSUM_MISMATCH; + + if ((uint64_t)header.NumberOfPartitionEntries > UINT64_MAX / header.SizeOfPartitionEntry) + return NTLSTATUS_INVALID_PARAMETER; + uint64_t entryBytes = (uint64_t)header.NumberOfPartitionEntries * header.SizeOfPartitionEntry; + if (entryBytes > UINT64_MAX - (IOP_SECTOR_SIZE - 1u)) + return NTLSTATUS_INVALID_PARAMETER; + uint64_t entrySectors = (entryBytes + IOP_SECTOR_SIZE - 1u) / IOP_SECTOR_SIZE; + if (entrySectors == 0 || header.PartitionEntryLba >= diskSectors || + entrySectors > diskSectors - header.PartitionEntryLba) { + return NTLSTATUS_INVALID_PARAMETER; + } + + uint64_t entryLastLba = header.PartitionEntryLba + entrySectors - 1u; + if (headerLba == 1u) { + if (header.PartitionEntryLba <= headerLba || entryLastLba >= header.FirstUsableLba) + return NTLSTATUS_INVALID_PARAMETER; + } else { + if (header.PartitionEntryLba <= header.LastUsableLba || entryLastLba >= headerLba) + return NTLSTATUS_INVALID_PARAMETER; + } + + IOP_GPT_VALIDATED_HEADER validated = {0}; + validated.Header = header; + validated.EntryBytes = entryBytes; + validated.EntrySectors = entrySectors; + status = IopValidateGptEntryArrayCrc(diskIndex, &validated); + if (!KSUCCESS(status)) + return status; + + *validatedOut = validated; + return NTLSTATUS_SUCCESS; +} + +// Returns whether independently valid primary and backup GPT headers describe the same table. +static bool IopGptHeadersAgree(const IOP_GPT_VALIDATED_HEADER* primary, + const IOP_GPT_VALIDATED_HEADER* backup) { + if (!primary || !backup) + return false; + return IopGuidEquals(&primary->Header.DiskGuid, &backup->Header.DiskGuid) && + primary->Header.FirstUsableLba == backup->Header.FirstUsableLba && + primary->Header.LastUsableLba == backup->Header.LastUsableLba && + primary->Header.NumberOfPartitionEntries == backup->Header.NumberOfPartitionEntries && + primary->Header.SizeOfPartitionEntry == backup->Header.SizeOfPartitionEntry && + primary->Header.PartitionEntryArrayCrc32 == backup->Header.PartitionEntryArrayCrc32; +} + +// Parses all populated GPT entries from one already validated table. +static KSTATUS IopParseGptEntries(uint8_t diskIndex, + uint64_t diskSectors, + const IOP_GPT_VALIDATED_HEADER* validated, + IOP_PARTITION_RECORD** partitionsOut) { + if (!validated || !partitionsOut) + return NTLSTATUS_INVALID_PARAMETER; + + IOP_PARTITION_RECORD* partitions = NULL; + if (validated->Header.PartitionEntryLba > UINT64_MAX / IOP_SECTOR_SIZE) + return NTLSTATUS_INVALID_PARAMETER; + uint64_t arrayByteBase = validated->Header.PartitionEntryLba * IOP_SECTOR_SIZE; + + for (uint32_t index = 0; index < validated->Header.NumberOfPartitionEntries; ++index) { + uint64_t entryOffset = (uint64_t)index * validated->Header.SizeOfPartitionEntry; + if (arrayByteBase > UINT64_MAX - entryOffset) { + IopFreePartitionList(partitions); + return NTLSTATUS_INVALID_PARAMETER; + } + + IOP_GPT_ENTRY_PREFIX entry; + KSTATUS status = IopReadDiskBytes(diskIndex, + diskSectors, + arrayByteBase + entryOffset, + &entry, + sizeof(entry)); + if (!KSUCCESS(status)) { + IopFreePartitionList(partitions); + return status; + } + if (IopGuidIsZero(&entry.PartitionTypeGuid)) + continue; + if (IopGuidIsZero(&entry.UniquePartitionGuid) || + entry.EndingLba < entry.StartingLba || + entry.StartingLba < validated->Header.FirstUsableLba || + entry.EndingLba > validated->Header.LastUsableLba) { + IopFreePartitionList(partitions); + return NTLSTATUS_INVALID_PARAMETER; + } + + uint64_t sectorCount = entry.EndingLba - entry.StartingLba + 1u; + BOOT_VOLUME_SELECTOR identity = {0}; + identity.Kind = BootVolumeSelectorGpt; + identity.Identity.Gpt.PartitionGuid = entry.UniquePartitionGuid; + IOP_PARTITION_RECORD* record = IopAllocatePartition(diskIndex, + entry.StartingLba, + sectorCount, + &identity); + if (!record) { + IopFreePartitionList(partitions); + return NTLSTATUS_NO_MEMORY; + } + status = IopInsertDiscoveredPartition(&partitions, record); + if (!KSUCCESS(status)) { + MmFreePool(record); + IopFreePartitionList(partitions); + return status; + } + } + + *partitionsOut = partitions; + return NTLSTATUS_SUCCESS; +} + +// Discovers one GPT disk from primary/backup metadata and rejects inconsistent valid copies. +static KSTATUS IopDiscoverGptDisk(uint8_t diskIndex, + uint64_t diskSectors, + IOP_PARTITION_RECORD** partitionsOut) { + IOP_GPT_VALIDATED_HEADER primary = {0}; + IOP_GPT_VALIDATED_HEADER backup = {0}; + KSTATUS primaryStatus = IopValidateGptHeader(diskIndex, diskSectors, 1u, + diskSectors - 1u, &primary); + KSTATUS backupStatus = IopValidateGptHeader(diskIndex, diskSectors, diskSectors - 1u, + 1u, &backup); + bool primaryValid = KSUCCESS(primaryStatus); + bool backupValid = KSUCCESS(backupStatus); + if (!primaryValid && !backupValid) { + DbgPrint("partition: disk %u GPT primary=0x%x backup=0x%x\n", + diskIndex, primaryStatus, backupStatus); + return primaryStatus == NTLSTATUS_CHECKSUM_MISMATCH || backupStatus == NTLSTATUS_CHECKSUM_MISMATCH + ? NTLSTATUS_CHECKSUM_MISMATCH + : NTLSTATUS_INVALID_PARAMETER; + } + if (primaryValid && backupValid && !IopGptHeadersAgree(&primary, &backup)) { + DbgPrint("partition: disk %u GPT primary/backup metadata disagree\n", diskIndex); + return NTLSTATUS_INVALID_PARAMETER; + } + + const IOP_GPT_VALIDATED_HEADER* selected = primaryValid ? &primary : &backup; + if (!primaryValid) + DbgPrint("partition: disk %u using valid backup GPT\n", diskIndex); + return IopParseGptEntries(diskIndex, diskSectors, selected, partitionsOut); +} + +// Discovers either an MBR primary-partition map or a protective-MBR GPT map. +static KSTATUS IopDiscoverDiskPartitions(uint8_t diskIndex, + DEVICE_OBJECT* device, + IOP_PARTITION_RECORD** partitionsOut) { + if (!device || !partitionsOut) + return NTLSTATUS_INVALID_PARAMETER; + *partitionsOut = NULL; + + if (device->u.Disk.SectorSize != IOP_SECTOR_SIZE || device->u.Disk.SectorCount == 0) { + DbgPrint("partition: disk %u unsupported sector geometry size=%u sectors=%llu\n", + diskIndex, device->u.Disk.SectorSize, + (unsigned long long)device->u.Disk.SectorCount); + return NTLSTATUS_NOT_SUPPORTED; + } + uint64_t diskSectors = device->u.Disk.SectorCount; + + uint8_t sector[IOP_SECTOR_SIZE]; + KSTATUS status = IoReadDiskSectors(diskIndex, 0, 1, sector); + if (!KSUCCESS(status)) + return status; + if (sector[510] != 0x55u || sector[511] != 0xaau) + return NTLSTATUS_NOT_FOUND; + + uint32_t diskSignature = 0; + memcpy(&diskSignature, sector + 440u, sizeof(diskSignature)); + IOP_MBR_PARTITION_ENTRY entries[4]; + memcpy(entries, sector + 446u, sizeof(entries)); + + bool protective = false; + for (uint32_t i = 0; i < 4; ++i) { + if (entries[i].Type == 0xeeu && entries[i].SectorCount != 0) + protective = true; + } + if (protective) + return IopDiscoverGptDisk(diskIndex, diskSectors, partitionsOut); + + // Validate the complete primary table before emitting supported partitions. Extended containers remain + // unsupported, but malformed container extents must not make a partially trusted MBR map acceptable. + for (uint32_t i = 0; i < 4; ++i) { + const IOP_MBR_PARTITION_ENTRY* entry = &entries[i]; + if (entry->BootIndicator != 0x00u && entry->BootIndicator != 0x80u) + return NTLSTATUS_INVALID_PARAMETER; + if (entry->Type == 0 && entry->SectorCount == 0) + continue; + if (entry->Type == 0 || entry->SectorCount == 0 || + entry->StartingLba == 0 || entry->StartingLba >= diskSectors || + (uint64_t)entry->SectorCount > diskSectors - entry->StartingLba) { + return NTLSTATUS_INVALID_PARAMETER; + } + + uint64_t entryEnd = (uint64_t)entry->StartingLba + entry->SectorCount; + for (uint32_t previous = 0; previous < i; ++previous) { + const IOP_MBR_PARTITION_ENTRY* prior = &entries[previous]; + if (prior->Type == 0 && prior->SectorCount == 0) + continue; + uint64_t priorEnd = (uint64_t)prior->StartingLba + prior->SectorCount; + if ((uint64_t)entry->StartingLba < priorEnd && + (uint64_t)prior->StartingLba < entryEnd) { + return NTLSTATUS_INVALID_PARAMETER; + } + } + } + + IOP_PARTITION_RECORD* partitions = NULL; + for (uint32_t i = 0; i < 4; ++i) { + const IOP_MBR_PARTITION_ENTRY* entry = &entries[i]; + if (entry->Type == 0 || IopMbrTypeIsExtended(entry->Type)) + continue; + + BOOT_VOLUME_SELECTOR identity = {0}; + identity.Kind = BootVolumeSelectorMbr; + identity.Identity.Mbr.DiskSignature = diskSignature; + identity.Identity.Mbr.PartitionNumber = i + 1u; + IOP_PARTITION_RECORD* record = IopAllocatePartition(diskIndex, + entry->StartingLba, + entry->SectorCount, + &identity); + if (!record) { + IopFreePartitionList(partitions); + return NTLSTATUS_NO_MEMORY; + } + status = IopInsertDiscoveredPartition(&partitions, record); + if (!KSUCCESS(status)) { + MmFreePool(record); + IopFreePartitionList(partitions); + return status; + } + } + + *partitionsOut = partitions; + return NTLSTATUS_SUCCESS; +} + +// Returns whether the registry already contains at least one partition for this disk index. +static bool IopDiskAlreadyDiscovered(uint8_t diskIndex) { + bool found = false; + if (!IopAcquirePartitionMutex()) + return false; + for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) { + if (record->Info.DiskIndex == diskIndex) { + found = true; + break; + } + } + KeReleaseMutex(gPartitionMutex); + return found; +} + +// Appends one discovered list to the live registry and synchronously notifies every subscriber. +static KSTATUS IopPublishPartitions(uint8_t diskIndex, IOP_PARTITION_RECORD* discovered) { + if (!IopAcquirePartitionMutex()) { + IopFreePartitionList(discovered); + return NTLSTATUS_FAILURE; + } + + for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) { + if (record->Info.DiskIndex == diskIndex) { + KeReleaseMutex(gPartitionMutex); + IopFreePartitionList(discovered); + return NTLSTATUS_SUCCESS; + } + } + + IOP_PARTITION_RECORD** tail = &gPartitions; + while (*tail) + tail = &(*tail)->Next; + *tail = discovered; + + for (IOP_PARTITION_RECORD* record = discovered; record; record = record->Next) { + if (record->Identity.Kind == BootVolumeSelectorMbr) { + DbgPrint("partition: disk=%u mbr/%08X/%u start=%llu sectors=%llu\n", + record->Info.DiskIndex, + record->Identity.Identity.Mbr.DiskSignature, + record->Identity.Identity.Mbr.PartitionNumber, + (unsigned long long)record->Info.StartLba, + (unsigned long long)record->Info.SectorCount); + } else { + const BOOT_VOLUME_GUID* guid = &record->Identity.Identity.Gpt.PartitionGuid; + DbgPrint("partition: disk=%u gpt/%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X start=%llu sectors=%llu\n", + record->Info.DiskIndex, + guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7], + (unsigned long long)record->Info.StartLba, + (unsigned long long)record->Info.SectorCount); + } + if (IopIdentityEquals(&record->Identity, &gSystemVolumeSelector)) { + DbgPrint("partition: configured system_volume matches disk=%u start=%llu sectors=%llu\n", + record->Info.DiskIndex, + (unsigned long long)record->Info.StartLba, + (unsigned long long)record->Info.SectorCount); + } + for (IOP_PARTITION_CALLBACK_ENTRY* entry = gPartitionCallbacks; entry; entry = entry->Next) + entry->Callback(&record->Info, IoPartitionArrived, entry->Context); + } + + KeReleaseMutex(gPartitionMutex); + return NTLSTATUS_SUCCESS; +} + +// Discovers and publishes all partitions for one newly registered disk. +static void IopDiskArrived(uint8_t diskIndex, DEVICE_OBJECT* device) { + if (!device || IopDiskAlreadyDiscovered(diskIndex)) + return; + + IOP_PARTITION_RECORD* discovered = NULL; + KSTATUS status = IopDiscoverDiskPartitions(diskIndex, device, &discovered); + if (!KSUCCESS(status)) { + if (status != NTLSTATUS_NOT_FOUND) { + DbgPrint("partition: disk %u discovery failed status=0x%x\n", diskIndex, status); + } else { + DbgPrint("partition: disk %u has no partition table\n", diskIndex); + } + return; + } + if (!discovered) { + DbgPrint("partition: disk %u contains no supported partitions\n", diskIndex); + return; + } + + status = IopPublishPartitions(diskIndex, discovered); + if (!KSUCCESS(status)) + DbgPrint("partition: disk %u publish failed status=0x%x\n", diskIndex, status); +} + +// Removes all partitions belonging to one disk and notifies consumers before freeing their snapshots. +static void IopDiskRemoved(uint8_t diskIndex, IO_PARTITION_CHANGE change) { + if (!IopAcquirePartitionMutex()) + return; + + IOP_PARTITION_RECORD** link = &gPartitions; + while (*link) { + IOP_PARTITION_RECORD* record = *link; + if (record->Info.DiskIndex != diskIndex) { + link = &record->Next; + continue; + } + + *link = record->Next; + for (IOP_PARTITION_CALLBACK_ENTRY* entry = gPartitionCallbacks; entry; entry = entry->Next) + entry->Callback(&record->Info, change, entry->Context); + MmFreePool(record); + } + + KeReleaseMutex(gPartitionMutex); +} + +// Receives the underlying disk namespace and translates it into partition changes. +static void IopDiskChange(uint8_t diskIndex, + DEVICE_OBJECT* device, + IO_DEVICE_CHANGE change, + void* context) { + (void)context; + switch (change) { + case IoDeviceArrived: + IopDiskArrived(diskIndex, device); + break; + case IoDeviceSurpriseRemoved: + case IoDeviceRemoved: + IopDiskRemoved(diskIndex, + change == IoDeviceSurpriseRemoved + ? IoPartitionSurpriseRemoved + : IoPartitionRemoved); + break; + case IoDeviceQueryRemove: + default: + break; + } +} + +// Replays one disk that predates partition-manager initialization. +static void IopReplayDisk(uint8_t diskIndex, DEVICE_OBJECT* device, void* context) { + (void)context; + IopDiskArrived(diskIndex, device); +} + +// Initializes dynamic partition discovery and the mandatory structural system-volume selector. +KSTATUS IoInitializePartitionManager(const char* systemVolumeSelector) { + if (!systemVolumeSelector || !*systemVolumeSelector) + return NTLSTATUS_INVALID_PARAMETER; + if (gPartitionManagerInitialized) + return NTLSTATUS_ALREADY_EXISTS; + + BOOT_VOLUME_SELECTOR selector = {0}; + if (!BootVolumeSelectorParse(systemVolumeSelector, strlen(systemVolumeSelector), &selector)) + return NTLSTATUS_INVALID_PARAMETER; + + MUTEX* mutex = NULL; + KSTATUS status = KeMutexCreate(&mutex); + if (!KSUCCESS(status)) + return status; + + gPartitionMutex = mutex; + gSystemVolumeSelector = selector; + + status = IoRegisterDiskChangeCallback(IopDiskChange, NULL); + if (!KSUCCESS(status)) { + gPartitionMutex = NULL; + memset(&gSystemVolumeSelector, 0, sizeof(gSystemVolumeSelector)); + ObDereferenceObject(&mutex->Header); + return status; + } + + // The disk-change subscription is permanent, so the manager becomes live before replaying existing disks. + gPartitionManagerInitialized = true; + status = IoEnumerateDiskDevices(IopReplayDisk, NULL); + if (!KSUCCESS(status)) + return status; + + DbgPrint("partition: dynamic discovery initialized for system_volume=%s\n", systemVolumeSelector); + return NTLSTATUS_SUCCESS; +} + +// Registers one partition-change consumer and replays every currently discovered partition before returning. +__declspec(dllexport) KSTATUS IoRegisterPartitionChangeCallback(IO_PARTITION_CHANGE_CALLBACK callback, + void* context) { + if (!callback || !gPartitionManagerInitialized) + return NTLSTATUS_INVALID_PARAMETER; + + IOP_PARTITION_CALLBACK_ENTRY* entry = MmAllocatePool(sizeof(*entry)); + if (!entry) + return NTLSTATUS_NO_MEMORY; + entry->Next = NULL; + entry->Callback = callback; + entry->Context = context; + + if (!IopAcquirePartitionMutex()) { + MmFreePool(entry); + return NTLSTATUS_FAILURE; + } + + IOP_PARTITION_CALLBACK_ENTRY** tail = &gPartitionCallbacks; + while (*tail) + tail = &(*tail)->Next; + *tail = entry; + + for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) + callback(&record->Info, IoPartitionArrived, context); + + KeReleaseMutex(gPartitionMutex); + return NTLSTATUS_SUCCESS; +} + +// Returns the uniquely selected system partition, or NOT_FOUND/COLLISION while discovery is incomplete or ambiguous. +KSTATUS IoQuerySystemPartition(IO_PARTITION_INFO* partitionOut) { + if (!partitionOut || !gPartitionManagerInitialized) + return NTLSTATUS_INVALID_PARAMETER; + + if (!IopAcquirePartitionMutex()) + return NTLSTATUS_FAILURE; + + const IOP_PARTITION_RECORD* match = NULL; + uint32_t matchCount = 0; + for (IOP_PARTITION_RECORD* record = gPartitions; record; record = record->Next) { + if (!IopIdentityEquals(&record->Identity, &gSystemVolumeSelector)) + continue; + match = record; + matchCount++; + } + + KSTATUS status; + if (matchCount == 0) { + status = NTLSTATUS_NOT_FOUND; + } else if (matchCount != 1) { + status = NTLSTATUS_OBJECT_NAME_COLLISION; + } else { + *partitionOut = match->Info; + status = NTLSTATUS_SUCCESS; + } + + KeReleaseMutex(gPartitionMutex); + return status; +} diff --git a/code/kernel/io_volume.c b/code/kernel/io_volume.c new file mode 100644 index 0000000..191d808 --- /dev/null +++ b/code/kernel/io_volume.c @@ -0,0 +1,634 @@ +// Mounted-volume core. This file owns volume identity, provider lifetime, and dead-volume dispatch semantics. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static KE_ID_ALLOCATOR gVolumeIds = KE_ID_ALLOCATOR_INIT(1); +static SPINLOCK gVolumeRegistryLock = SPINLOCK_INIT; +static LIST_ENTRY gVolumeRegistry; +static uint8_t gVolumeRegistryInitialized; +static SPINLOCK gVolumeStatsLock = SPINLOCK_INIT; +static IO_VOLUME_STATS gVolumeStats; +static IO_VOLUME_MANAGER_STATS gVolumeManagerStats; +static EVENT* gVolumeChangeEvent; +static SPINLOCK gVolumeChangeLock = SPINLOCK_INIT; +static uint64_t gVolumeGeneration; +static IO_VOLUME_MANAGER gVolumeManagerObject; +static uint8_t gVolumeManagerObjectInitialized; + +static void KiIoVolumeAddStat(uint64_t* field, int64_t delta); + +// Initializes the dynamic mounted-volume registry on first use. +static void KiIoEnsureVolumeRegistry(void) { + uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock); + if (!gVolumeRegistryInitialized) { + LIST_INIT(&gVolumeRegistry); + gVolumeRegistryInitialized = 1; + } + KeReleaseSpinlock(&gVolumeRegistryLock, flags); +} + + +// Publishes one mounted-volume registry change to discovery waiters. +static void KiIoNotifyVolumeChange(void) { + uintptr_t flags = KeAcquireSpinlock(&gVolumeChangeLock); + gVolumeGeneration++; + KeReleaseSpinlock(&gVolumeChangeLock, flags); + if (gVolumeChangeEvent) + KeSignalEvent(gVolumeChangeEvent); +} + +// Initializes the waitable mounted-volume registry notification before providers start. +KSTATUS IoInitializeVolumeManager(void) { + KiIoEnsureVolumeRegistry(); + if (!gVolumeManagerObjectInitialized) { + ObInitializeObjectHeader(&gVolumeManagerObject.Header, ObjectTypeVolumeManager); + gVolumeManagerObjectInitialized = 1; + } + if (gVolumeChangeEvent) + return NTLSTATUS_SUCCESS; + return KeEventCreate(EventAutoReset, 0, &gVolumeChangeEvent); +} + +// References the persistent manager object used to delegate mounted-volume control-plane authority. +KSTATUS IoReferenceVolumeManager(IO_VOLUME_MANAGER** managerOut) { + if (!managerOut) + return NTLSTATUS_INVALID_PARAMETER; + *managerOut = (IO_VOLUME_MANAGER*)0; + if (!gVolumeManagerObjectInitialized || !ObTryReferenceObject(&gVolumeManagerObject.Header)) + return NTLSTATUS_OBJECT_DEAD; + *managerOut = &gVolumeManagerObject; + return NTLSTATUS_SUCCESS; +} + +// Captures one stable value snapshot. A generation change during capture causes a retry instead of returning stale metadata as current. +KSTATUS IoSnapshotVolumes(IO_VOLUME_SNAPSHOT_ENTRY** entriesOut, uint32_t* countOut, uint64_t* generationOut) { + if (!entriesOut || !countOut || !generationOut) + return NTLSTATUS_INVALID_PARAMETER; + *entriesOut = (IO_VOLUME_SNAPSHOT_ENTRY*)0; + *countOut = 0; + *generationOut = 0; + + KiIoEnsureVolumeRegistry(); + for (;;) { + uint64_t generationBefore = IoGetVolumeGeneration(); + uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock); + size_t count = 0; + LIST_ENTRY* head = &gVolumeRegistry; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) + count++; + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + if (count > UINT32_MAX) + return NTLSTATUS_LIMIT_REACHED; + + IO_VOLUME_SNAPSHOT_ENTRY* entries = (IO_VOLUME_SNAPSHOT_ENTRY*)0; + if (count) { + entries = (IO_VOLUME_SNAPSHOT_ENTRY*)MmAllocatePool(count * sizeof(*entries)); + if (!entries) + return NTLSTATUS_NO_MEMORY; + memset(entries, 0, count * sizeof(*entries)); + } + + size_t used = 0; + bool overflowed = false; + flags = KeAcquireSpinlock(&gVolumeRegistryLock); + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { + if (used >= count) { + overflowed = true; + break; + } + IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink); + entries[used].InstanceId = volume->InstanceId; + used++; + } + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + + uint64_t generationAfter = IoGetVolumeGeneration(); + if (!overflowed && generationBefore == generationAfter) { + *entriesOut = entries; + *countOut = (uint32_t)used; + *generationOut = generationAfter; + KiIoVolumeAddStat(&gVolumeManagerStats.Snapshots, 1); + return NTLSTATUS_SUCCESS; + } + if (entries) + MmFreePool(entries); + } +} + +// References one currently mounted volume by its runtime instance identity. +KSTATUS IoReferenceVolumeByInstanceId(uint32_t instanceId, IO_VOLUME** volumeOut) { + if (!instanceId || !volumeOut) + return NTLSTATUS_INVALID_PARAMETER; + *volumeOut = (IO_VOLUME*)0; + + KiIoEnsureVolumeRegistry(); + uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock); + LIST_ENTRY* head = &gVolumeRegistry; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { + IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink); + if (volume->InstanceId != instanceId) + continue; + if (!ObTryReferenceObject(&volume->Header)) { + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + return NTLSTATUS_OBJECT_DEAD; + } + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + if (!IoVolumeIsMounted(volume)) { + ObDereferenceObject(&volume->Header); + return NTLSTATUS_OBJECT_DEAD; + } + *volumeOut = volume; + KiIoVolumeAddStat(&gVolumeManagerStats.VolumeOpens, 1); + return NTLSTATUS_SUCCESS; + } + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + return NTLSTATUS_NOT_FOUND; +} + +// Returns the current dynamic mounted-volume registry generation. +uint64_t IoGetVolumeGeneration(void) { + uintptr_t flags = KeAcquireSpinlock(&gVolumeChangeLock); + uint64_t generation = gVolumeGeneration; + KeReleaseSpinlock(&gVolumeChangeLock, flags); + return generation; +} + +// Waits until the mounted-volume registry changes or the timeout expires. +KSTATUS IoWaitForVolumeChange(uint64_t observedGeneration, uint64_t timeoutMilliseconds) { + KiIoVolumeAddStat(&gVolumeManagerStats.Waits, 1); + if (IoGetVolumeGeneration() != observedGeneration) { + KiIoVolumeAddStat(&gVolumeManagerStats.Wakeups, 1); + return NTLSTATUS_SUCCESS; + } + if (!gVolumeChangeEvent) + return NTLSTATUS_OBJECT_DEAD; + + EVENT* events[1] = { gVolumeChangeEvent }; + uint32_t selected = 0; + KSTATUS status = KeWaitForMultipleEvents(events, 1, 0, timeoutMilliseconds, &selected); + if (KSUCCESS(status)) + KiIoVolumeAddStat(&gVolumeManagerStats.Wakeups, 1); + return status; +} + +// Adds one value to a mounted-volume statistic. +static void KiIoVolumeAddStat(uint64_t* field, int64_t delta) { + uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock); + if (delta >= 0) + *field += (uint64_t)delta; + else { + uint64_t amount = (uint64_t)(-delta); + *field = *field > amount ? *field - amount : 0; + } + KeReleaseSpinlock(&gVolumeStatsLock, flags); +} + +void IoQueryVolumeStats(IO_VOLUME_STATS* out) { + if (!out) return; + uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock); + *out = gVolumeStats; + KeReleaseSpinlock(&gVolumeStatsLock, flags); +} + +void IoQueryVolumeManagerStats(IO_VOLUME_MANAGER_STATS* out) { + if (!out) return; + uintptr_t flags = KeAcquireSpinlock(&gVolumeStatsLock); + *out = gVolumeManagerStats; + KeReleaseSpinlock(&gVolumeStatsLock, flags); +} + +// References the unique mounted volume backed by one exact disk extent start. +KSTATUS IoReferenceVolumeByBacking(uint8_t diskIndex, uint64_t startLba, IO_VOLUME** volumeOut) { + if (!volumeOut) + return NTLSTATUS_INVALID_PARAMETER; + *volumeOut = (IO_VOLUME*)0; + + KiIoEnsureVolumeRegistry(); + IO_VOLUME* match = (IO_VOLUME*)0; + uint32_t matches = 0; + uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock); + LIST_ENTRY* head = &gVolumeRegistry; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { + IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink); + if (volume->Source.DiskIndex != diskIndex || volume->Source.StartLba != startLba) + continue; + matches++; + if (!match && ObTryReferenceObject(&volume->Header)) + match = volume; + } + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + + if (matches == 0) + return NTLSTATUS_NOT_FOUND; + if (matches != 1) { + if (match) + ObDereferenceObject(&match->Header); + return NTLSTATUS_OBJECT_NAME_COLLISION; + } + if (!match) + return NTLSTATUS_OBJECT_DEAD; + if (!IoVolumeIsMounted(match)) { + ObDereferenceObject(&match->Header); + return NTLSTATUS_OBJECT_DEAD; + } + + *volumeOut = match; + return NTLSTATUS_SUCCESS; +} + +// Returns whether the volume still accepts provider operations. +bool IoVolumeIsMounted(const IO_VOLUME* volume) { + if (!volume) + return false; + IO_VOLUME* mutableVolume = (IO_VOLUME*)volume; + uintptr_t flags = KeAcquireSpinlock(&mutableVolume->Lock); + bool mounted = mutableVolume->State == IoVolumeMounted; + KeReleaseSpinlock(&mutableVolume->Lock, flags); + return mounted; +} + +// Acquires one object reference only while the mounted-volume state remains live. +bool IoVolumeTryReferenceMounted(IO_VOLUME* volume) { + if (!volume || !ObTryReferenceObject(&volume->Header)) + return false; + if (IoVolumeIsMounted(volume)) + return true; + ObDereferenceObject(&volume->Header); + return false; +} + +// Destroys provider state only after namespace, file, directory, and transient references drain. +static void KiIoVolumeDestroy(OBJECT* object) { + IO_VOLUME* volume = CONTAINING_RECORD(object, IO_VOLUME, Header); + if (volume->Provider.Destroy) + volume->Provider.Destroy(&volume->Provider); + if (volume->BackingDevice) { + IoDereferenceDeviceObject(volume->BackingDevice); + volume->BackingDevice = (DEVICE_OBJECT*)0; + } + if (volume->InstanceId) + KiIoVolumeAddStat(&gVolumeStats.ProviderDestroys, 1); +} + +// Creates a dynamic mounted volume and inserts it into the mounted-volume registry. +__declspec(dllexport) KSTATUS IoCreateVolume(const IO_VOLUME_PROVIDER* provider, + const IO_VOLUME_SOURCE* source, + IO_VOLUME** volumeOut) { + if (!provider || !source || !volumeOut || !provider->Open || !provider->Read || + !provider->GetSize || !provider->Close || source->SectorCount == 0 || + (source->Type != IoVolumeSourcePartition && source->Type != IoVolumeSourceBlockDevice)) { + return NTLSTATUS_INVALID_PARAMETER; + } + if (source->Type == IoVolumeSourceBlockDevice && source->StartLba != 0) + return NTLSTATUS_INVALID_PARAMETER; + *volumeOut = (IO_VOLUME*)0; + + DEVICE_OBJECT* backingDevice = IoReferenceDiskDevice(source->DiskIndex); + if (!backingDevice) + return NTLSTATUS_NOT_FOUND; + uint64_t diskSectors = backingDevice->u.Disk.SectorCount; + if (source->StartLba >= diskSectors || source->SectorCount > diskSectors - source->StartLba || + (source->Type == IoVolumeSourceBlockDevice && source->SectorCount != diskSectors)) { + IoDereferenceDeviceObject(backingDevice); + return NTLSTATUS_INVALID_PARAMETER; + } + + IO_VOLUME* volume = (IO_VOLUME*)ObAllocateObject(ObjectTypeVolume, sizeof(IO_VOLUME)); + if (!volume) { + IoDereferenceDeviceObject(backingDevice); + return NTLSTATUS_NO_MEMORY; + } + + uint32_t instanceId = 0; + KSTATUS status = KeAllocateId32(&gVolumeIds, &instanceId); + if (!KSUCCESS(status)) { + IoDereferenceDeviceObject(backingDevice); + ObDereferenceObject(&volume->Header); + return status; + } + + volume->Header.Destroy = KiIoVolumeDestroy; + volume->Lock = (SPINLOCK)SPINLOCK_INIT; + volume->InstanceId = instanceId; + volume->State = IoVolumeMounted; + volume->RemovalReason = 0; + volume->Source = *source; + volume->BackingDevice = backingDevice; + volume->Provider = *provider; + LIST_INIT(&volume->RegistryLink); + + KiIoEnsureVolumeRegistry(); + uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock); + ListInsertTail(&gVolumeRegistry, &volume->RegistryLink); + volume->RegistryLinked = 1; + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + + // The registry owns the allocation reference; the creator receives an independent reference. + ObReferenceObject(&volume->Header); + *volumeOut = volume; + KiIoVolumeAddStat(&gVolumeStats.CreatedVolumes, 1); + KiIoVolumeAddStat(&gVolumeStats.MountedVolumes, 1); + KiIoNotifyVolumeChange(); + + DbgPrint("io: volume %u mounted disk=%u start=%llu sectors=%llu\n", + instanceId, + source->DiskIndex, + (unsigned long long)source->StartLba, + (unsigned long long)source->SectorCount); + return NTLSTATUS_SUCCESS; +} + +// Marks a mounted volume terminally dead while retaining provider state until references drain. +__declspec(dllexport) KSTATUS IoRemoveVolume(IO_VOLUME* volume, IO_VOLUME_REMOVAL_REASON reason) { + if (!volume || reason < IoVolumeUnmounted || reason > IoVolumeProviderDead) + return NTLSTATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&volume->Lock); + if (volume->State != IoVolumeMounted) { + KeReleaseSpinlock(&volume->Lock, flags); + return NTLSTATUS_OBJECT_DEAD; + } + volume->State = IoVolumeDead; + volume->RemovalReason = (uint32_t)reason; + KeReleaseSpinlock(&volume->Lock, flags); + + // No future provider operation can start after this point. The callback may cancel + // provider-specific work, but Context remains valid until KiIoVolumeDestroy. + if (volume->Provider.VolumeDead) + volume->Provider.VolumeDead(&volume->Provider, reason); + MmFileCacheDiscardVolume(volume); + + bool dropRegistryReference = false; + flags = KeAcquireSpinlock(&gVolumeRegistryLock); + if (volume->RegistryLinked) { + ListRemove(&volume->RegistryLink); + volume->RegistryLinked = 0; + dropRegistryReference = true; + } + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + + KiIoVolumeAddStat(&gVolumeStats.MountedVolumes, -1); + KiIoVolumeAddStat(&gVolumeStats.RemovedVolumes, 1); + KiIoNotifyVolumeChange(); + DbgPrint("io: volume %u removed reason=%u\n", volume->InstanceId, (unsigned)reason); + if (dropRegistryReference) + ObDereferenceObject(&volume->Header); + return NTLSTATUS_SUCCESS; +} + +// Releases a creator-owned mounted-volume reference. +__declspec(dllexport) void IoReleaseVolume(IO_VOLUME* volume) { + if (volume) + ObDereferenceObject(&volume->Header); +} + +// Returns the monotonic identity of a mounted-volume instance. +__declspec(dllexport) uint32_t IoGetVolumeInstanceId(const IO_VOLUME* volume) { + return volume ? volume->InstanceId : 0; +} + +// Returns the immutable block extent backing this mounted-volume instance. +__declspec(dllexport) KSTATUS IoGetVolumeSource(const IO_VOLUME* volume, IO_VOLUME_SOURCE* sourceOut) { + if (!volume || !sourceOut) + return NTLSTATUS_INVALID_PARAMETER; + *sourceOut = volume->Source; + return NTLSTATUS_SUCCESS; +} + +// Returns whether native provider async read is available while the volume remains mounted. +bool IoVolumeSupportsReadAsync(const IO_VOLUME* volume) { + return volume && IoVolumeIsMounted(volume) && volume->Provider.ReadAsync != (void*)0; +} + +// Returns whether native provider async write is available while the volume remains mounted. +bool IoVolumeSupportsWriteAsync(const IO_VOLUME* volume) { + return volume && IoVolumeIsMounted(volume) && volume->Provider.WriteAsync != (void*)0; +} + +// Validates mounted state before entering provider code. +static KSTATUS KiIoVolumeCheckMounted(IO_VOLUME* volume) { + if (IoVolumeIsMounted(volume)) + return NTLSTATUS_SUCCESS; + KiIoVolumeAddStat(&gVolumeStats.DeadIoRejects, 1); + return NTLSTATUS_OBJECT_DEAD; +} + +KSTATUS IoVolumeOpen(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + return volume->Provider.Open(&volume->Provider, path, fileCtx); +} + +KSTATUS IoVolumeRead(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + void* buffer, size_t size, size_t* bytesRead) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Read) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.Read(&volume->Provider, fileCtx, offset, buffer, size, bytesRead); +} + +KSTATUS IoVolumeWrite(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + const void* buffer, size_t size, size_t* bytesWritten) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Write) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.Write(&volume->Provider, fileCtx, offset, buffer, size, bytesWritten); +} + +KSTATUS IoVolumeReadAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete, + void* completeContext) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.ReadAsync) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.ReadAsync(&volume->Provider, fileCtx, offset, buffer, size, complete, completeContext); +} + +KSTATUS IoVolumeWriteAsync(IO_VOLUME* volume, void* fileCtx, uint64_t offset, + const void* buffer, size_t size, IO_FS_ASYNC_COMPLETE complete, + void* completeContext) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.WriteAsync) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.WriteAsync(&volume->Provider, fileCtx, offset, buffer, size, complete, completeContext); +} + +KSTATUS IoVolumeCreate(IO_VOLUME* volume, SYS_STRING_VIEW path, void** fileCtx) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Create) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.Create(&volume->Provider, path, fileCtx); +} + +KSTATUS IoVolumeDelete(IO_VOLUME* volume, SYS_STRING_VIEW path) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Delete) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.Delete(&volume->Provider, path); +} + +KSTATUS IoVolumeRename(IO_VOLUME* volume, SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Rename) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.Rename(&volume->Provider, oldPath, newPath); +} + +KSTATUS IoVolumeCreateDir(IO_VOLUME* volume, SYS_STRING_VIEW path) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.CreateDir) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.CreateDir(&volume->Provider, path); +} + +KSTATUS IoVolumeRemoveDir(IO_VOLUME* volume, SYS_STRING_VIEW path) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.RemoveDir) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.RemoveDir(&volume->Provider, path); +} + +KSTATUS IoVolumeTruncate(IO_VOLUME* volume, void* fileCtx, uint64_t newSize, uint32_t fileFlags) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Truncate) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.Truncate(&volume->Provider, fileCtx, newSize, fileFlags); +} + +KSTATUS IoVolumeGetSize(IO_VOLUME* volume, void* fileCtx, uint64_t* sizeOut) { + if (!sizeOut) return NTLSTATUS_INVALID_PARAMETER; + *sizeOut = 0; + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.GetSize) return NTLSTATUS_NOT_SUPPORTED; + *sizeOut = volume->Provider.GetSize(&volume->Provider, fileCtx); + return NTLSTATUS_SUCCESS; +} + +KSTATUS IoVolumeQueryInfo(IO_VOLUME* volume, void* fileCtx, SYS_FILE_STAT* out) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.QueryInfo) return NTLSTATUS_NOT_SUPPORTED; + s = volume->Provider.QueryInfo(&volume->Provider, fileCtx, out); + if (KSUCCESS(s) && out) + out->VolumeId = volume->InstanceId; + return s; +} + +KSTATUS IoVolumeStat(IO_VOLUME* volume, SYS_STRING_VIEW path, SYS_FILE_STAT* out) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Stat) return NTLSTATUS_NOT_SUPPORTED; + s = volume->Provider.Stat(&volume->Provider, path, out); + if (KSUCCESS(s) && out) + out->VolumeId = volume->InstanceId; + return s; +} + +KSTATUS IoVolumeQueryIdentity(IO_VOLUME* volume, void* fileCtx, IO_FILE_IDENTITY* out) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!out || !volume->Provider.QueryIdentity) return NTLSTATUS_NOT_SUPPORTED; + s = volume->Provider.QueryIdentity(&volume->Provider, fileCtx, out); + if (KSUCCESS(s)) + out->VolumeId = volume->InstanceId; + return s; +} + +KSTATUS IoVolumeFlush(IO_VOLUME* volume, void* fileCtx) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.Flush) return NTLSTATUS_SUCCESS; + return volume->Provider.Flush(&volume->Provider, fileCtx); +} + +void IoVolumeClose(IO_VOLUME* volume, void* fileCtx) { + if (volume && volume->Provider.Close && fileCtx) + volume->Provider.Close(&volume->Provider, fileCtx); +} + +void IoVolumeQueryStats(IO_VOLUME* volume, IO_FILESYSTEM_STATS* out) { + if (!out) return; + memset(out, 0, sizeof(*out)); + if (!volume || !IoVolumeIsMounted(volume) || !volume->Provider.QueryStats) + return; + volume->Provider.QueryStats(&volume->Provider, out); +} + +// Aggregates filesystem traversal statistics across every dynamic mounted volume. +__declspec(dllexport) void IoQueryFilesystemStats(IO_FILESYSTEM_STATS* out) { + if (!out) return; + memset(out, 0, sizeof(*out)); + + KiIoEnsureVolumeRegistry(); + uintptr_t flags = KeAcquireSpinlock(&gVolumeRegistryLock); + size_t count = 0; + LIST_ENTRY* head = &gVolumeRegistry; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) + count++; + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + if (!count) return; + + IO_VOLUME** snapshot = (IO_VOLUME**)MmAllocatePool(count * sizeof(IO_VOLUME*)); + if (!snapshot) return; + memset(snapshot, 0, count * sizeof(IO_VOLUME*)); + + size_t used = 0; + flags = KeAcquireSpinlock(&gVolumeRegistryLock); + for (LIST_ENTRY* node = head->Fwd; node != head && used < count; node = node->Fwd) { + IO_VOLUME* volume = CONTAINING_RECORD(node, IO_VOLUME, RegistryLink); + if (ObTryReferenceObject(&volume->Header)) + snapshot[used++] = volume; + } + KeReleaseSpinlock(&gVolumeRegistryLock, flags); + + for (size_t i = 0; i < used; ++i) { + IO_FILESYSTEM_STATS one; + IoVolumeQueryStats(snapshot[i], &one); + ObDereferenceObject(&snapshot[i]->Header); + + out->PathLookups += one.PathLookups; + out->PathCacheHits += one.PathCacheHits; + out->PathCacheMisses += one.PathCacheMisses; + out->PathCacheStores += one.PathCacheStores; + out->PathCacheInvalidations += one.PathCacheInvalidations; + if (one.PathCacheGeneration > out->PathCacheGeneration) + out->PathCacheGeneration = one.PathCacheGeneration; + out->DirectorySearches += one.DirectorySearches; + out->DirectoryBlocksRead += one.DirectoryBlocksRead; + out->DirectoryEntriesScanned += one.DirectoryEntriesScanned; + out->DataReadRuns += one.DataReadRuns; + out->DataReadSectors += one.DataReadSectors; + out->DataReadRunMerges += one.DataReadRunMerges; + if (one.MaxDataReadSpanSectors > out->MaxDataReadSpanSectors) + out->MaxDataReadSpanSectors = one.MaxDataReadSpanSectors; + } + MmFreePool(snapshot); +} + +KSTATUS IoVolumeOpenDir(IO_VOLUME* volume, SYS_STRING_VIEW path, void** dirCtx) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.OpenDir) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.OpenDir(&volume->Provider, path, dirCtx); +} + +KSTATUS IoVolumeReadDir(IO_VOLUME* volume, void* dirCtx, KDIR_ENTRY* out) { + KSTATUS s = KiIoVolumeCheckMounted(volume); + if (!KSUCCESS(s)) return s; + if (!volume->Provider.ReadDir) return NTLSTATUS_NOT_SUPPORTED; + return volume->Provider.ReadDir(&volume->Provider, dirCtx, out); +} + +void IoVolumeCloseDir(IO_VOLUME* volume, void* dirCtx) { + if (volume && volume->Provider.CloseDir && dirCtx) + volume->Provider.CloseDir(&volume->Provider, dirCtx); +} diff --git a/code/kernel/ke_audio.c b/code/kernel/ke_audio.c index 0699cbc..d9ae652 100644 --- a/code/kernel/ke_audio.c +++ b/code/kernel/ke_audio.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -22,7 +23,6 @@ #define KE_AUDIO_ENDPOINT_TAG MM_POOL_TAG('a', 'e', 'p', 't') #define KE_AUDIO_RING_TAG MM_POOL_TAG('a', 'r', 'n', 'g') -#define KE_AUDIO_SELFTEST_TAG MM_POOL_TAG('a', 't', 's', 't') #define KE_AUDIO_PERIOD_STATE_FREE 0u #define KE_AUDIO_PERIOD_STATE_FILLING 1u @@ -31,7 +31,6 @@ #define KE_AUDIO_PERIOD_STATE_REAPING 4u #define KE_AUDIO_TICKS_PER_MILLISECOND 1000ull -#define KE_AUDIO_SELFTEST_WAIT_MILLISECONDS 5000ull typedef struct KE_AUDIO_PERIOD_CONTRIBUTION { KE_AUDIO_STREAM* Stream; @@ -103,7 +102,7 @@ struct IO_AUDIO_ENDPOINT { EVENT* MixerExitEvent; THREAD* MixerThread; SPINLOCK NotifyLock; - uint64_t EndpointId; + uint32_t EndpointId; IO_AUDIO_ENDPOINT_KIND Kind; KE_AUDIO_ENDPOINT_STATE State; uint32_t Capabilities; @@ -174,8 +173,8 @@ typedef struct KE_AUDIO_LIFECYCLE_SNAPSHOT { static SPINLOCK gAudioRegistryLock = SPINLOCK_INIT; static LIST_ENTRY gAudioEndpointList; static uint32_t gAudioEndpointCount; -static uint64_t gAudioNextEndpointId = 1; -static uint64_t gAudioDefaultEndpointId; +static KE_ID_ALLOCATOR gAudioEndpointIds = KE_ID_ALLOCATOR_INIT(1u); +static uint32_t gAudioDefaultEndpointId; static uint8_t gAudioInitialized; static KE_AUDIO_CORE_STATS gAudioCoreStats; @@ -433,7 +432,7 @@ static uint32_t KiAudioEndpointRank(IO_AUDIO_ENDPOINT_KIND kind) { // Recomputes the default endpoint while the registry lock is held. static void KiAudioSelectDefaultEndpointLocked(void) { - uint64_t selectedId = 0; + uint32_t selectedId = 0; uint32_t selectedRank = UINT32_MAX; for (LIST_ENTRY* link = gAudioEndpointList.Fwd; link != &gAudioEndpointList; @@ -452,7 +451,7 @@ static void KiAudioSelectDefaultEndpointLocked(void) { } // References one ready endpoint by stable identifier. -static IO_AUDIO_ENDPOINT* KiAudioReferenceEndpointById(uint64_t endpointId) { +static IO_AUDIO_ENDPOINT* KiAudioReferenceEndpointById(uint32_t endpointId) { IO_AUDIO_ENDPOINT* result = (IO_AUDIO_ENDPOINT*)0; uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock); for (LIST_ENTRY* link = gAudioEndpointList.Fwd; @@ -1421,7 +1420,18 @@ static KSTATUS KiAudioRegisterEndpoint(const IO_AUDIO_ENDPOINT_REGISTRATION* reg ObDereferenceObject(&endpoint->Header); return NTLSTATUS_NO_MEMORY; } - endpoint->EndpointId = gAudioNextEndpointId++; + KSTATUS idStatus = KeAllocateId32(&gAudioEndpointIds, &endpoint->EndpointId); + if (!KSUCCESS(idStatus)) { + KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointStopping); + endpoint->FailureStatus = idStatus; + KeReleaseSpinlock(&gAudioRegistryLock, flags); + KeSignalEvent(endpoint->WorkEvent); + (void)KeWaitForEvent(endpoint->MixerExitEvent, 0); + ObDereferenceObject(&endpoint->MixerThread->Header); + endpoint->MixerThread = (THREAD*)0; + ObDereferenceObject(&endpoint->Header); + return idStatus; + } KiAudioSetEndpointStateLocked(endpoint, KeAudioEndpointReady); endpoint->Registered = 1; ObReferenceObject(&endpoint->Header); @@ -1590,9 +1600,9 @@ const NTL_DDK_AUDIO* KeAudioGetDdk(void) { } // Returns the current default endpoint identifier. -uint64_t KeAudioGetDefaultEndpointId(void) { +uint32_t KeAudioGetDefaultEndpointId(void) { uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock); - uint64_t endpointId = gAudioDefaultEndpointId; + uint32_t endpointId = gAudioDefaultEndpointId; KeReleaseSpinlock(&gAudioRegistryLock, flags); return endpointId; } @@ -1606,7 +1616,7 @@ KSTATUS KeAudioQueryEndpoints(KE_AUDIO_ENDPOINT_INFO* endpoints, IO_AUDIO_ENDPOINT* references[KE_AUDIO_MAX_ENDPOINTS]; uint32_t count = 0; - uint64_t defaultId = 0; + uint32_t defaultId = 0; uintptr_t flags = KeAcquireSpinlock(&gAudioRegistryLock); defaultId = gAudioDefaultEndpointId; for (LIST_ENTRY* link = gAudioEndpointList.Fwd; @@ -1668,7 +1678,7 @@ KSTATUS KeAudioQueryEndpoints(KE_AUDIO_ENDPOINT_INFO* endpoints, } // Creates one logical render stream with a fixed-capacity source ring. -KSTATUS KeAudioCreateStream(uint64_t endpointId, +KSTATUS KeAudioCreateStream(uint32_t endpointId, const IO_AUDIO_FORMAT* format, KE_AUDIO_STREAM** outStream) { if (!outStream || !KiAudioValidateSourceFormat(format)) @@ -2010,7 +2020,7 @@ KSTATUS KeAudioSetStreamVolume(KE_AUDIO_STREAM* stream, } // Updates endpoint master software gain and separate logical mute state. -KSTATUS KeAudioSetMasterVolume(uint64_t endpointId, float volume, bool muted) { +KSTATUS KeAudioSetMasterVolume(uint32_t endpointId, float volume, bool muted) { if (!KiAudioFloatIsFinite(volume) || volume < 0.0f || volume > 1.0f) return NTLSTATUS_INVALID_PARAMETER; IO_AUDIO_ENDPOINT* endpoint = KiAudioReferenceEndpointById(endpointId); @@ -2080,381 +2090,3 @@ KSTATUS KeAudioQueryStream(KE_AUDIO_STREAM* stream, KE_AUDIO_STREAM_INFO* infoOu KiAudioReleaseMutex(stream->Lock); return NTLSTATUS_SUCCESS; } - -// Copies one endpoint snapshot by stable identifier. -static KSTATUS KiAudioSelfTestQueryEndpoint(uint64_t endpointId, - KE_AUDIO_ENDPOINT_INFO* infoOut) { - KE_AUDIO_ENDPOINT_INFO infos[KE_AUDIO_MAX_ENDPOINTS]; - uint32_t count = 0; - KSTATUS status = KeAudioQueryEndpoints(infos, KE_AUDIO_MAX_ENDPOINTS, &count); - if (KFAILED(status)) - return status; - for (uint32_t index = 0; index < count; index++) { - if (infos[index].EndpointId == endpointId) { - *infoOut = infos[index]; - return NTLSTATUS_SUCCESS; - } - } - return NTLSTATUS_NOT_FOUND; -} - -// Generates one bounded run of a ramped 440 Hz stereo sine wave. -static void KiAudioSelfTestGenerateTone(int16_t* frames, - uint32_t frameCount, - uint64_t firstFrame, - double* sineInOut, - double* cosineInOut) { - const uint64_t totalFrames = 3ull * KE_AUDIO_OUTPUT_SAMPLE_RATE; - const uint64_t rampFrames = 384ull; - const double amplitude = 8231.0; - const double sineStep = 0.057564026959567284; - const double cosineStep = 0.9983418166140283; - double sine = *sineInOut; - double cosine = *cosineInOut; - - for (uint32_t index = 0; index < frameCount; index++) { - uint64_t frame = firstFrame + index; - uint64_t framesFromEnd = totalFrames - 1ull - frame; - double envelope = 1.0; - if (frame < rampFrames) - envelope = (double)frame / (double)(rampFrames - 1ull); - if (framesFromEnd < rampFrames) { - double fadeOut = (double)framesFromEnd / (double)(rampFrames - 1ull); - if (fadeOut < envelope) - envelope = fadeOut; - } - - double scaled = sine * amplitude * envelope; - int32_t sample = scaled >= 0.0 ? (int32_t)(scaled + 0.5) - : (int32_t)(scaled - 0.5); - frames[index * 2u] = (int16_t)sample; - frames[index * 2u + 1u] = (int16_t)sample; - - double nextSine = sine * cosineStep + cosine * sineStep; - double nextCosine = cosine * cosineStep - sine * sineStep; - sine = nextSine; - cosine = nextCosine; - } - - *sineInOut = sine; - *cosineInOut = cosine; -} - -// Waits for one selftest stream condition with a finite boot-time deadline. -static KSTATUS KiAudioSelfTestWait(KE_AUDIO_STREAM* stream) { - EVENT* event = KeAudioStreamWaitEvent(stream); - if (!event) - return NTLSTATUS_OBJECT_DEAD; - uint32_t selected = 0; - return KeWaitForMultipleEvents(&event, - 1, - 0, - KE_AUDIO_SELFTEST_WAIT_MILLISECONDS, - &selected); -} - -// Writes one generated run while using the stream event for bounded backpressure. -static KSTATUS KiAudioSelfTestWriteFrames(KE_AUDIO_STREAM* stream, - const int16_t* frames, - uint32_t frameCount, - uint64_t expectedDiscontinuity) { - uint32_t offset = 0; - while (offset < frameCount) { - uint32_t written = 0; - KSTATUS status = KeAudioWriteStream(stream, - frames + offset * 2u, - frameCount - offset, - &written); - if (KSUCCESS(status) && written) { - offset += written; - continue; - } - if (status != NTLSTATUS_WOULD_BLOCK) - return KFAILED(status) ? status : NTLSTATUS_FAILURE; - - KE_AUDIO_STREAM_INFO info; - status = KeAudioQueryStream(stream, &info); - if (KFAILED(status)) - return status; - if (info.State == KeAudioStreamFailed) - return KFAILED(info.FailureStatus) ? info.FailureStatus : NTLSTATUS_DEVICE_ERROR; - if (info.DiscontinuityGeneration != expectedDiscontinuity) - return NTLSTATUS_DEVICE_ERROR; - - KE_AUDIO_STREAM_WAIT_CONDITION condition = { - .Mask = KE_AUDIO_WAIT_WRITABLE | KE_AUDIO_WAIT_FAILED | KE_AUDIO_WAIT_DISCONTINUITY, - .WritableFrames = 1, - .DiscontinuityGeneration = expectedDiscontinuity, - }; - status = KeAudioArmStreamWait(stream, &condition); - if (KFAILED(status)) - return status; - status = KiAudioSelfTestWait(stream); - if (KFAILED(status) || status == NTLSTATUS_TIMEOUT) - return status; - } - return NTLSTATUS_SUCCESS; -} - -// Prints the common endpoint and stream counters for one selftest outcome. -static void KiAudioSelfTestPrintCounters(uint64_t endpointId, - const KE_AUDIO_ENDPOINT_INFO* endpointBefore, - const KE_AUDIO_ENDPOINT_INFO* endpointAfter, - const KE_AUDIO_STREAM_INFO* streamInfo, - uint64_t expectedDiscontinuity) { - uint64_t mixedPeriods = endpointAfter->PeriodsMixed - endpointBefore->PeriodsMixed; - uint64_t completedPeriods = endpointAfter->PeriodsCompleted - endpointBefore->PeriodsCompleted; - uint64_t interruptCount = endpointAfter->InterruptCount - endpointBefore->InterruptCount; - uint64_t endpointUnderruns = endpointAfter->Underruns - endpointBefore->Underruns; - uint64_t endpointDiscontinuities = endpointAfter->DiscontinuityGeneration - - endpointBefore->DiscontinuityGeneration; - uint64_t positionFrames = endpointAfter->HardwarePositionFrames - - endpointBefore->HardwarePositionFrames; - uint64_t streamDiscontinuities = streamInfo->DiscontinuityGeneration >= expectedDiscontinuity - ? streamInfo->DiscontinuityGeneration - expectedDiscontinuity - : 0; - - DbgPrint("AUDIO final selftest: endpoint=%llu submitted=%llu consumed=%llu presented=%llu\n", - (unsigned long long)endpointId, - (unsigned long long)streamInfo->FramesSubmitted, - (unsigned long long)streamInfo->FramesConsumed, - (unsigned long long)streamInfo->FramesPresented); - DbgPrint("AUDIO final selftest: periods mixed=%llu completed=%llu interrupts=%llu\n", - (unsigned long long)mixedPeriods, - (unsigned long long)completedPeriods, - (unsigned long long)interruptCount); - DbgPrint("AUDIO final selftest: underruns endpoint=%llu stream=%llu discontinuities endpoint=%llu stream=%llu\n", - (unsigned long long)endpointUnderruns, - (unsigned long long)streamInfo->Underruns, - (unsigned long long)endpointDiscontinuities, - (unsigned long long)streamDiscontinuities); - DbgPrint("AUDIO final selftest: position start=%llu end=%llu delta=%llu\n", - (unsigned long long)endpointBefore->HardwarePositionFrames, - (unsigned long long)endpointAfter->HardwarePositionFrames, - (unsigned long long)positionFrames); -} - -// Plays the final-path sine test through the selected hardware endpoint and mixer. -void KiRunAudioFinalPathSelfTest(void) { - const uint64_t totalFrames = 3ull * KE_AUDIO_OUTPUT_SAMPLE_RATE; - const uint32_t chunkFrames = 1024u; - const char* failurePhase = (const char*)0; - KSTATUS failureStatus = NTLSTATUS_SUCCESS; - uint64_t failureValue = 0; - uint64_t endpointId = KeAudioGetDefaultEndpointId(); - KE_AUDIO_ENDPOINT_INFO endpointBefore; - KE_AUDIO_ENDPOINT_INFO endpointAfter; - KE_AUDIO_STREAM_INFO startInfo; - KE_AUDIO_STREAM_INFO streamAfter; - KE_AUDIO_STREAM* stream = (KE_AUDIO_STREAM*)0; - int16_t* chunk = (int16_t*)0; - uint64_t expectedDiscontinuity = 0; - int haveEndpointBefore = 0; - int haveEndpointAfter = 0; - int haveStreamAfter = 0; - - memset(&endpointBefore, 0, sizeof(endpointBefore)); - memset(&endpointAfter, 0, sizeof(endpointAfter)); - memset(&startInfo, 0, sizeof(startInfo)); - memset(&streamAfter, 0, sizeof(streamAfter)); - - if (!endpointId) { - DbgPrint("AUDIO final selftest: SKIP no default endpoint\n"); - return; - } - - KSTATUS status = KiAudioSelfTestQueryEndpoint(endpointId, &endpointBefore); - if (KFAILED(status) || !endpointBefore.Available || - endpointBefore.State != KeAudioEndpointReady) { - failurePhase = "endpoint-ready"; - failureStatus = KFAILED(status) ? status : NTLSTATUS_DEVICE_ERROR; - goto cleanup; - } - haveEndpointBefore = 1; - if (endpointBefore.HardwareFormat.SampleRate != KE_AUDIO_OUTPUT_SAMPLE_RATE || - endpointBefore.HardwareFormat.ChannelCount != KE_AUDIO_OUTPUT_CHANNELS || - endpointBefore.HardwareFormat.SampleFormat != IoAudioSampleS16) { - failurePhase = "endpoint-format"; - failureStatus = NTLSTATUS_NOT_SUPPORTED; - failureValue = endpointBefore.HardwareFormat.SampleFormat; - goto cleanup; - } - - DbgPrint("AUDIO final selftest: begin endpoint=%llu kind=%u format=48000/S16/stereo\n", - (unsigned long long)endpointId, - (unsigned)endpointBefore.Kind); - - IO_AUDIO_FORMAT format = { - .SampleRate = KE_AUDIO_OUTPUT_SAMPLE_RATE, - .ChannelCount = KE_AUDIO_OUTPUT_CHANNELS, - .SampleFormat = IoAudioSampleS16, - }; - status = KeAudioCreateStream(endpointId, &format, &stream); - if (KFAILED(status)) { - failurePhase = "stream-create"; - failureStatus = status; - goto cleanup; - } - - chunk = (int16_t*)MmAllocatePoolTag( - (size_t)chunkFrames * KE_AUDIO_OUTPUT_BYTES_PER_FRAME, - KE_AUDIO_SELFTEST_TAG); - if (!chunk) { - failurePhase = "tone-allocation"; - failureStatus = NTLSTATUS_NO_MEMORY; - goto cleanup; - } - - double sine = 0.0; - double cosine = 1.0; - uint64_t submitted = 0; - while (submitted < KE_AUDIO_STREAM_RING_FRAMES) { - uint32_t count = (uint32_t)(KE_AUDIO_STREAM_RING_FRAMES - submitted); - if (count > chunkFrames) - count = chunkFrames; - KiAudioSelfTestGenerateTone(chunk, count, submitted, &sine, &cosine); - uint32_t written = 0; - status = KeAudioWriteStream(stream, chunk, count, &written); - if (KFAILED(status) || written != count) { - failurePhase = "initial-prefill"; - failureStatus = KFAILED(status) ? status : NTLSTATUS_FAILURE; - failureValue = written; - goto cleanup; - } - submitted += written; - } - - status = KeAudioStartStream(stream); - if (KFAILED(status)) { - failurePhase = "stream-start"; - failureStatus = status; - goto cleanup; - } - - status = KeAudioQueryStream(stream, &startInfo); - if (KFAILED(status)) { - failurePhase = "start-query"; - failureStatus = status; - goto cleanup; - } - expectedDiscontinuity = startInfo.DiscontinuityGeneration; - - while (submitted < totalFrames) { - uint64_t remaining = totalFrames - submitted; - uint32_t count = remaining < chunkFrames ? (uint32_t)remaining : chunkFrames; - KiAudioSelfTestGenerateTone(chunk, count, submitted, &sine, &cosine); - status = KiAudioSelfTestWriteFrames(stream, - chunk, - count, - expectedDiscontinuity); - if (KFAILED(status) || status == NTLSTATUS_TIMEOUT) { - failurePhase = "stream-write"; - failureStatus = status; - failureValue = submitted; - goto cleanup; - } - submitted += count; - } - - status = KeAudioDrainStream(stream); - if (KFAILED(status)) { - failurePhase = "stream-drain"; - failureStatus = status; - goto cleanup; - } - KE_AUDIO_STREAM_WAIT_CONDITION drainCondition = { - .Mask = KE_AUDIO_WAIT_DRAINED | KE_AUDIO_WAIT_FAILED | KE_AUDIO_WAIT_DISCONTINUITY, - .DrainedGeneration = startInfo.DrainedGeneration, - .DiscontinuityGeneration = expectedDiscontinuity, - }; - status = KeAudioArmStreamWait(stream, &drainCondition); - if (KFAILED(status)) { - failurePhase = "drain-arm"; - failureStatus = status; - goto cleanup; - } - status = KiAudioSelfTestWait(stream); - if (KFAILED(status) || status == NTLSTATUS_TIMEOUT) { - failurePhase = "drain-wait"; - failureStatus = status; - goto cleanup; - } - - status = KeAudioQueryStream(stream, &streamAfter); - if (KFAILED(status)) { - failurePhase = "final-stream-query"; - failureStatus = status; - goto cleanup; - } - haveStreamAfter = 1; - if (streamAfter.State != KeAudioStreamStopped || - streamAfter.DrainedGeneration != startInfo.DrainedGeneration + 1u || - streamAfter.BufferedFrames != 0 || streamAfter.FramesSubmitted != totalFrames || - streamAfter.FramesConsumed != totalFrames || streamAfter.FramesPresented != totalFrames || - streamAfter.FramesDiscarded != 0 || streamAfter.Underruns != 0 || - streamAfter.DiscontinuityGeneration != expectedDiscontinuity) { - failurePhase = "final-stream-counters"; - failureStatus = NTLSTATUS_DEVICE_ERROR; - failureValue = streamAfter.FramesPresented; - goto cleanup; - } - - status = KiAudioSelfTestQueryEndpoint(endpointId, &endpointAfter); - if (KFAILED(status)) { - failurePhase = "final-endpoint-query"; - failureStatus = status; - goto cleanup; - } - haveEndpointAfter = 1; - uint64_t mixedPeriods = endpointAfter.PeriodsMixed - endpointBefore.PeriodsMixed; - uint64_t completedPeriods = endpointAfter.PeriodsCompleted - endpointBefore.PeriodsCompleted; - uint64_t interruptCount = endpointAfter.InterruptCount - endpointBefore.InterruptCount; - uint64_t endpointUnderruns = endpointAfter.Underruns - endpointBefore.Underruns; - uint64_t endpointDiscontinuities = endpointAfter.DiscontinuityGeneration - - endpointBefore.DiscontinuityGeneration; - uint64_t positionFrames = endpointAfter.HardwarePositionFrames - - endpointBefore.HardwarePositionFrames; - if (!mixedPeriods || !completedPeriods || !interruptCount || - endpointUnderruns || endpointDiscontinuities || - positionFrames + endpointAfter.FramesPerPeriod < totalFrames) { - failurePhase = "final-endpoint-counters"; - failureStatus = NTLSTATUS_DEVICE_ERROR; - failureValue = positionFrames; - goto cleanup; - } - -cleanup: - if (stream && !haveStreamAfter && KSUCCESS(KeAudioQueryStream(stream, &streamAfter))) - haveStreamAfter = 1; - if (haveEndpointBefore && !haveEndpointAfter && - KSUCCESS(KiAudioSelfTestQueryEndpoint(endpointId, &endpointAfter))) - haveEndpointAfter = 1; - if (haveEndpointBefore && haveEndpointAfter && haveStreamAfter) - KiAudioSelfTestPrintCounters(endpointId, - &endpointBefore, - &endpointAfter, - &streamAfter, - expectedDiscontinuity); - - if (stream) { - KE_AUDIO_STREAM_INFO cleanupInfo; - if (KSUCCESS(KeAudioQueryStream(stream, &cleanupInfo)) && - cleanupInfo.State != KeAudioStreamStopped && - cleanupInfo.State != KeAudioStreamFailed && - cleanupInfo.State != KeAudioStreamClosed) - (void)KeAudioStopStream(stream); - } - if (chunk) - MmFreePool(chunk); - if (stream) - ObDereferenceObject(&stream->Header); - - if (failurePhase) { - DbgPrint("AUDIO final selftest: FAIL phase=%s status=0x%x value=%llu\n", - failurePhase, - failureStatus, - (unsigned long long)failureValue); - return; - } - DbgPrint("AUDIO final selftest: PASS\n"); -} diff --git a/code/kernel/ke_boot_config.c b/code/kernel/ke_boot_config.c new file mode 100644 index 0000000..9c9d29b --- /dev/null +++ b/code/kernel/ke_boot_config.c @@ -0,0 +1,154 @@ +// Kernel boot configuration parser. This file intentionally implements only the boot.ini settings the kernel owns. + +#include + +#include + +#include +#include + +// Returns whether one character is horizontal configuration whitespace. +static bool KiBootConfigSpace(char c) { + return c == ' ' || c == '\t' || c == '\r'; +} + +// Trims horizontal whitespace from one immutable line span. +static void KiBootConfigTrim(const char** begin, const char** end) { + while (*begin < *end && KiBootConfigSpace(**begin)) + ++*begin; + while (*end > *begin && KiBootConfigSpace((*end)[-1])) + --*end; +} + +// Returns whether one immutable span exactly matches a NUL-terminated string. +static bool KiBootConfigSpanEquals(const char* begin, const char* end, const char* text) { + while (begin < end && *text) { + if (*begin++ != *text++) + return false; + } + return begin == end && *text == '\0'; +} + +// Parses one unsigned decimal or 0x-prefixed hexadecimal value without accepting trailing junk. +static KSTATUS KiBootConfigParseUInt32(const char* begin, const char* end, uint32_t* valueOut) { + KiBootConfigTrim(&begin, &end); + if (begin == end || !valueOut) + return NTLSTATUS_INVALID_PARAMETER; + + uint32_t base = 10; + if (end - begin >= 2 && begin[0] == '0' && (begin[1] == 'x' || begin[1] == 'X')) { + base = 16; + begin += 2; + if (begin == end) + return NTLSTATUS_INVALID_PARAMETER; + } + + uint32_t value = 0; + for (const char* p = begin; p < end; ++p) { + uint32_t digit; + if (*p >= '0' && *p <= '9') digit = (uint32_t)(*p - '0'); + else if (*p >= 'a' && *p <= 'f') digit = (uint32_t)(10 + *p - 'a'); + else if (*p >= 'A' && *p <= 'F') digit = (uint32_t)(10 + *p - 'A'); + else return NTLSTATUS_INVALID_PARAMETER; + if (digit >= base || value > (UINT32_MAX - digit) / base) + return NTLSTATUS_INVALID_PARAMETER; + value = value * base + digit; + } + + *valueOut = value; + return NTLSTATUS_SUCCESS; +} + +// Copies one trimmed configuration value into a fixed kernel-owned string. +static KSTATUS KiBootConfigCopyString(const char* begin, const char* end, char* buffer, size_t bufferSize) { + KiBootConfigTrim(&begin, &end); + size_t length = (size_t)(end - begin); + if (!buffer || bufferSize == 0 || length >= bufferSize) + return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(buffer, begin, length); + buffer[length] = '\0'; + return NTLSTATUS_SUCCESS; +} + +// Parses the kernel-owned boot.ini settings from the raw bootloader string. +KSTATUS KeParseBootConfiguration(const char* text, size_t length, KE_BOOT_CONFIG* configOut) { + if (!text || !configOut) + return NTLSTATUS_INVALID_PARAMETER; + + KE_BOOT_CONFIG config = {0}; + bool systemVolumeSeen = false; + char section[32] = {0}; + size_t offset = 0; + + while (offset < length) { + size_t lineStart = offset; + while (offset < length && text[offset] != '\n') + ++offset; + size_t lineEnd = offset; + if (offset < length) + ++offset; + + const char* begin = text + lineStart; + const char* end = text + lineEnd; + KiBootConfigTrim(&begin, &end); + if (begin == end || *begin == ';' || *begin == '#') + continue; + + if (*begin == '[') { + if (end[-1] != ']') + return NTLSTATUS_INVALID_PARAMETER; + const char* sectionBegin = begin + 1; + const char* sectionEnd = end - 1; + KiBootConfigTrim(§ionBegin, §ionEnd); + size_t sectionLength = (size_t)(sectionEnd - sectionBegin); + if (sectionLength >= sizeof(section)) + return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(section, sectionBegin, sectionLength); + section[sectionLength] = '\0'; + continue; + } + + const char* equals = NULL; + for (const char* p = begin; p < end; ++p) { + if (*p == '=') { + equals = p; + break; + } + } + if (!equals) + return NTLSTATUS_INVALID_PARAMETER; + + const char* keyBegin = begin; + const char* keyEnd = equals; + const char* valueBegin = equals + 1; + const char* valueEnd = end; + KiBootConfigTrim(&keyBegin, &keyEnd); + + if (strcmp(section, "boot") == 0 && KiBootConfigSpanEquals(keyBegin, keyEnd, "system_volume")) { + if (systemVolumeSeen) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = KiBootConfigCopyString(valueBegin, valueEnd, + config.SystemVolume, sizeof(config.SystemVolume)); + if (!KSUCCESS(status) || config.SystemVolume[0] == '\0') + return KSUCCESS(status) ? NTLSTATUS_INVALID_PARAMETER : status; + systemVolumeSeen = true; + } else if (strcmp(section, "kernel") == 0 && KiBootConfigSpanEquals(keyBegin, keyEnd, "debug")) { + KSTATUS status = KiBootConfigParseUInt32(valueBegin, valueEnd, &config.Debug); + if (!KSUCCESS(status)) + return status; + if (config.Debug & ~KE_DEBUG_VALID_MASK) + return NTLSTATUS_INVALID_PARAMETER; + } else if (strcmp(section, "init") == 0 && KiBootConfigSpanEquals(keyBegin, keyEnd, "commandline")) { + KSTATUS status = KiBootConfigCopyString(valueBegin, valueEnd, + config.CommandLine, sizeof(config.CommandLine)); + if (!KSUCCESS(status)) + return status; + } + } + + if (!systemVolumeSeen) + return NTLSTATUS_NOT_FOUND; + + *configOut = config; + return NTLSTATUS_SUCCESS; +} diff --git a/code/kernel/ke_cpu.c b/code/kernel/ke_cpu.c index 441d2b1..032078d 100644 --- a/code/kernel/ke_cpu.c +++ b/code/kernel/ke_cpu.c @@ -3,9 +3,10 @@ #include #include #include +#include #include #include -#include +#include #define BOOTSTRAP_CPU_STACK_SIZE 0x4000u #define IA32_GS_BASE 0xC0000101u @@ -64,7 +65,7 @@ static uintptr_t KiDisableGlobalTranslations(void) { // nt-lite does not enable PCID. Refuse inherited PCID state rather than // pretending that a CR3 reload flushed every tagged translation. if (cr4 & CR4_PCID_ENABLE) - HalStopProcessor(); + KeStopProcessor(); if (cr4 & CR4_PAGE_GLOBAL_ENABLE) __writecr4(cr4 & ~CR4_PAGE_GLOBAL_ENABLE); @@ -98,7 +99,7 @@ static bool KiMemoryTypePolicyInstalled(uint32_t variableCount) { // Programs the kernel-owned PAT policy over a uniform write-back MTRR baseline. void KeInitializeMemoryTypes(void) { if (!KiMemoryTypeFeaturesAvailable()) - HalStopProcessor(); + KeStopProcessor(); uint32_t variableCount = (uint32_t)(__readmsr(IA32_MTRRCAP) & MTRR_CAP_VARIABLE_COUNT); if (KiMemoryTypePolicyInstalled(variableCount)) { @@ -106,10 +107,10 @@ void KeInitializeMemoryTypes(void) { return; } - uintptr_t interruptFlags = HalDisableInterrupts(); + uintptr_t interruptFlags = KeDisableInterrupts(); uintptr_t cr0 = __readcr0(); - // Every CPU runs this before entering normal kernel execution. The BSP + // Every CPU runs this before entering normal kernel execution. The boot processor // installs the policy while it is alone, and each AP installs it before // publishing its started state. __writecr0((cr0 | CR0_CACHE_DISABLE) & ~CR0_NOT_WRITE_THROUGH); @@ -134,9 +135,9 @@ void KeInitializeMemoryTypes(void) { __writecr4(cr4); bool installed = KiMemoryTypePolicyInstalled(variableCount); - HalRestoreInterrupts(interruptFlags); + KeRestoreInterrupts(interruptFlags); if (!installed) - HalStopProcessor(); + KeStopProcessor(); _InterlockedExchange(&gWriteCombiningAvailable, 1); } @@ -176,9 +177,9 @@ void KeInitializeBootstrapCpu(void) { KCPU* cpu = &gCpus[0]; cpu->Self = cpu; cpu->CpuIndex = 0; - cpu->LapicId = 0; + cpu->HardwareId = 0; cpu->State = KeCpuStarted; - cpu->IsBsp = 1; + cpu->IsBootProcessor = 1; cpu->PackageId = 0; cpu->CoreId = 0; cpu->ThreadId = 0; diff --git a/code/kernel/ke_debug.c b/code/kernel/ke_debug.c index 8359cc7..948a722 100644 --- a/code/kernel/ke_debug.c +++ b/code/kernel/ke_debug.c @@ -4,8 +4,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -104,7 +104,7 @@ static volatile long gPanicStoppedCpuCount; // Permanently stops a secondary processor that entered an active panic. static _Noreturn void KiPanicSecondaryHalt(void) { _InterlockedIncrement(&gPanicStoppedCpuCount); - HalStopProcessor(); + KeStopProcessor(); } // Returns a stable token for the current execution context. @@ -131,7 +131,7 @@ static int KiPanicCurrentCpuOwnsReport(void) { // Claims fatal-report ownership and masks interrupts on this processor. static void KiBeginFatalReport(void) { - (void)HalDisableInterrupts(); + (void)KeDisableInterrupts(); uintptr_t token = KiPanicCurrentToken(); if (_InterlockedCompareExchange((volatile long*)&gPanicInProgress, 1, 0) == 0) { gPanicOwnerToken = token; @@ -243,8 +243,8 @@ static KSTATUS KiDebugLogWriteAll(FILE_OBJECT* file, } // Persists the in-memory debug ring and flushes it through the storage stack. -__declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path) { - if (!gDebugLogBuffer || gDebugLogSize == 0 || !SysSvIsValid(path) || path.Length == 0) +KSTATUS KeFlushDebugLogToSystemRoot(const char* relativePath) { + if (!gDebugLogBuffer || gDebugLogSize == 0 || !relativePath || !relativePath[0]) return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; @@ -259,10 +259,10 @@ __declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path) { start = (gDebugLogUsed == gDebugLogSize) ? gDebugLogWrite : 0; KeReleaseSpinlock(&gDebugPrintLock, flags); - (void)IoDeleteFile(path); + (void)KeDeleteSystemRootFile(relativePath); FILE_OBJECT* file = (FILE_OBJECT*)0; - KSTATUS st = IoCreateFile(path, &file); + KSTATUS st = KeCreateSystemRootFile(relativePath, &file); if (!KSUCCESS(st)) { _InterlockedExchange((volatile long*)&gDebugLogFlushing, 0); return st; @@ -303,13 +303,9 @@ static void KiTryFlushPanicDebugLog(void) { if (!KeCanWait() || !KeSystemRootReady()) return; - char path[1024]; - if (!KeBuildSystemRootPath("kernel-panic.log", path, sizeof(path))) - return; - - KSTATUS st = KeFlushDebugLogToFile(SysSvFromCstr(path)); + KSTATUS st = KeFlushDebugLogToSystemRoot("kernel-panic.log"); if (!KSUCCESS(st)) - DbgPrint("debuglog: panic flush to %s failed st=0x%x\n", path, st); + DbgPrint("debuglog: panic flush failed st=0x%x\n", st); } // Writes one formatted panic record through panic-safe endpoints. @@ -327,7 +323,7 @@ __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) { // Stop every other CPU before writing the final report. Panic-safe endpoints // do not depend on scheduler locks held by processors stopped by this IPI. - HalApicSendPanicIpi(); + KeBroadcastProcessorNmi(); static const char banner[] = "\n*** KERNEL PANIC ***\n"; KiOutputWrite(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY, @@ -337,9 +333,9 @@ __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) { KCPU* cpu = KeGetCurrentCpu(); THREAD* thread = KeGetCurrentThread(); if (cpu) { - KiPanicPrintf("Panic CPU: %u LAPIC=%u", + KiPanicPrintf("Panic CPU: %u hardware=%u", (unsigned)cpu->CpuIndex, - (unsigned)cpu->LapicId); + (unsigned)cpu->HardwareId); if (thread) { KiPanicPrintf(" thread=0x%016llX tid=%llu", thread, @@ -355,7 +351,7 @@ __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) { va_end(args); KiTryFlushPanicDebugLog(); - HalStopProcessor(); + KeStopProcessor(); } // Writes user-visible console text through subscribed output endpoints. @@ -377,9 +373,9 @@ static void KiFaultPrintf(const char* format, ...) { va_end(args); } -// Walk the process module list to find which image owns an address. -// Returns the module name string, or "unknown" if the address is outside all -// known image ranges. Base/size are optional and are useful for offset dumps. +// Resolves the main user image or a registered kernel image that owns an address. +// User-mode DLL bookkeeping belongs to the user-mode loader and is intentionally +// not duplicated in kernel diagnostics. static const char* KiResolveModuleEx(PROCESS* proc, uintptr_t addr, uintptr_t* baseOut, size_t* sizeOut) { if (baseOut) *baseOut = 0; @@ -394,16 +390,6 @@ static const char* KiResolveModuleEx(PROCESS* proc, uintptr_t addr, return proc->Name; } - for (uint32_t i = 0; i < proc->ModuleCount; i++) { - uintptr_t base = proc->Modules[i].Base; - size_t size = proc->Modules[i].Size; - if (base && size && addr >= base && addr < base + size) { - if (baseOut) *baseOut = base; - if (sizeOut) *sizeOut = size; - return proc->Modules[i].Name; - } - } - uintptr_t kernelBase = 0; size_t kernelSize = 0; const char* kernelModule = LdrFindKernelModuleByAddress(addr, &kernelBase, &kernelSize); @@ -595,15 +581,6 @@ static void KiDumpModules(PROCESS* proc) { (unsigned long long)proc->ImageSize); } - for (uint32_t i = 0; i < proc->ModuleCount; i++) { - uintptr_t base = proc->Modules[i].Base; - size_t size = proc->Modules[i].Size; - KiFaultPrintf(" %-31s %016llx-%016llx size=%llx\n", - proc->Modules[i].Name, - (unsigned long long)base, - (unsigned long long)(base + size), - (unsigned long long)size); - } } #if 0 @@ -783,9 +760,9 @@ static _Noreturn void KiFinishFaultedProcess(PROCESS* proc, THREAD* thread, KSTA // dead thread to the scheduler with interrupts enabled so waiters such as // the foreground shell can observe the process exit event. while (true) { - HalEnableInterrupts(); + KeEnableInterrupts(); KeYield(); - HalWaitForInterrupt(); + KeWaitForInterrupt(); } } @@ -931,5 +908,5 @@ __declspec(dllexport) _Noreturn void KeFaultDispatch(int vector, uintptr_t rip, frame.Rip = rip; frame.Cs = cs; (void)KeFaultDispatchFrame(&frame); - HalStopProcessor(); + KeStopProcessor(); } diff --git a/code/kernel/ke_event.c b/code/kernel/ke_event.c index a69a7fb..42ea070 100644 --- a/code/kernel/ke_event.c +++ b/code/kernel/ke_event.c @@ -96,9 +96,9 @@ static void KiRundownMultiWait(THREAD* thread, void* context, KSTATUS status) { } // Creates event. -__declspec(dllexport) KSTATUS KeEventCreate(EVENT_TYPE type, uint32_t initialState, EVENT **out) { +__declspec(dllexport) KSTATUS KeEventCreate(EVENT_TYPE type, uint32_t initialState, EVENT** out) { if (!out) return NTLSTATUS_INVALID_PARAMETER; - EVENT* e = (EVENT *)ObAllocateObject(ObjectTypeEvent, sizeof(EVENT)); + EVENT* e = (EVENT*)ObAllocateObject(ObjectTypeEvent, sizeof(EVENT)); if (!e) return NTLSTATUS_NO_MEMORY; e->Header.Destroy = KiEventDestroy; e->Lock = (SPINLOCK)SPINLOCK_INIT; diff --git a/code/kernel/ke_hal.c b/code/kernel/ke_hal.c new file mode 100644 index 0000000..695653b --- /dev/null +++ b/code/kernel/ke_hal.c @@ -0,0 +1,125 @@ +// Kernel HAL binding and generic platform-operation wrappers. + +#include + +static const HAL_API* gHal; + +// Binds the bootstrap HAL table before kernel code begins using interrupt-state primitives. +KSTATUS KeBindBootstrapHal(void) { + const HAL_API* hal = HalGetApi(); + if (!hal || hal->Size != sizeof(HAL_API) || hal->Version != HAL_API_VERSION || + !hal->Processor.DisableInterrupts || !hal->Processor.RestoreInterrupts || + !hal->Processor.EnableInterrupts || !hal->Processor.WaitForInterrupt || + !hal->Processor.Halt || !hal->Processor.Stop) { + return NTLSTATUS_INVALID_PARAMETER; + } + gHal = hal; + return NTLSTATUS_SUCCESS; +} + +// Returns whether the initialized table contains every mandatory kernel platform service. +static int KiHalRuntimeTableValid(const HAL_API* hal) { + return hal && hal->Size == sizeof(HAL_API) && hal->Version == HAL_API_VERSION && + hal->Processor.DisableInterrupts && hal->Processor.RestoreInterrupts && + hal->Processor.EnableInterrupts && hal->Processor.WaitForInterrupt && + hal->Processor.Halt && hal->Processor.Stop && hal->Processor.QueryTopology && + hal->Processor.Start && + hal->Screen.GetFramebuffer && + hal->Output.RegisterEndpoint && hal->Output.Write && hal->Output.QueryStats && + hal->Timer.QueryTicks && hal->Timer.QueryTickFrequency && hal->Timer.Stall && + hal->Timer.HandleInterrupt && hal->Timer.ConfigureCurrentProcessor && + hal->Rtc.QueryTime && hal->Pci.Read32 && hal->Pci.Write32 && + hal->InterruptController.RouteIrq && hal->InterruptController.MaskIrq && + hal->InterruptController.UnmaskIrq && hal->InterruptController.SendEoi && + hal->InterruptController.InitializeCurrentProcessor && + hal->InterruptController.SendProcessorInterrupt && + hal->InterruptController.SendProcessorNmi && hal->InterruptController.BroadcastNmi && + hal->InterruptController.ComposeMsi && hal->Power.Shutdown; +} + +// Initializes the HAL and validates every mandatory runtime service consumed by the kernel. +KSTATUS KeInitializeHal(const HAL_INIT_PARAMS* params) { + if (!gHal || !params) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = HalInitialize(params); + if (!KSUCCESS(status)) + return status; + + const HAL_API* hal = HalGetApi(); + if (hal != gHal || !KiHalRuntimeTableValid(hal)) + return NTLSTATUS_INVALID_PARAMETER; + return NTLSTATUS_SUCCESS; +} + +// Returns the bound HAL table. +const HAL_API* KeGetHal(void) { + return gHal; +} + +// Masks interrupts and returns the previous processor flags. +__declspec(dllexport) uintptr_t KeDisableInterrupts(void) { + return gHal->Processor.DisableInterrupts(); +} + +// Restores a previously saved processor interrupt state. +__declspec(dllexport) void KeRestoreInterrupts(uintptr_t flags) { + gHal->Processor.RestoreInterrupts(flags); +} + +// Enables maskable interrupts on the current processor. +void KeEnableInterrupts(void) { + gHal->Processor.EnableInterrupts(); +} + +// Enables interrupts and waits for the next interrupt on the current processor. +void KeWaitForInterrupt(void) { + gHal->Processor.WaitForInterrupt(); +} + +// Halts the current processor once without changing interrupt state. +void KeHaltProcessor(void) { + gHal->Processor.Halt(); +} + +// Permanently stops the current processor. +_Noreturn void KeStopProcessor(void) { + gHal->Processor.Stop(); + for (;;) { + } +} + +// Queries platform processor topology without exposing interrupt-controller details. +KSTATUS KeQueryProcessorTopology(HAL_PROCESSOR_TOPOLOGY* topologyOut) { + return gHal->Processor.QueryTopology(topologyOut); +} + +// Starts one processor at a physical bootstrap entry address. +KSTATUS KeStartProcessor(uint32_t hardwareId, uintptr_t startPhysical) { + return gHal->Processor.Start(hardwareId, startPhysical); +} + +// Initializes the current processor's local interrupt-controller state. +void KeInitializeCurrentProcessorInterruptController(void) { + gHal->InterruptController.InitializeCurrentProcessor(); +} + +// Sends one fixed-vector interrupt to a processor hardware ID. +void KeSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector) { + gHal->InterruptController.SendProcessorInterrupt(hardwareId, vector); +} + +// Sends one non-maskable interrupt to a processor hardware ID. +void KeSendProcessorNmi(uint32_t hardwareId) { + gHal->InterruptController.SendProcessorNmi(hardwareId); +} + +// Sends a non-maskable interrupt to every other processor. +void KeBroadcastProcessorNmi(void) { + gHal->InterruptController.BroadcastNmi(); +} + +// Arms the calibrated periodic timer on the current processor. +void KeConfigureCurrentProcessorTimer(void) { + gHal->Timer.ConfigureCurrentProcessor(); +} diff --git a/code/kernel/ke_handle.c b/code/kernel/ke_handle.c new file mode 100644 index 0000000..347867c --- /dev/null +++ b/code/kernel/ke_handle.c @@ -0,0 +1,753 @@ +// Kernel capability handles. This file owns process-local handle identity, grant delegation, and revocation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct CAPABILITY_GRANT { + volatile long RefCount; + volatile long Revoked; + uint32_t Depth; + SYS_RIGHTS MaximumAccess; + OBJECT* Target; + struct CAPABILITY_GRANT* Parent; +} CAPABILITY_GRANT; + +typedef struct KE_HANDLE_ENTRY { + struct KE_HANDLE_ENTRY* Next; + SYS_HANDLE Handle; + SYS_RIGHTS Access; + CAPABILITY_GRANT* Grant; +} KE_HANDLE_ENTRY; + +typedef struct KE_CAPTURED_HANDLE { + CAPABILITY_GRANT* Grant; + SYS_RIGHTS Access; +} KE_CAPTURED_HANDLE; + +_Static_assert((KE_HANDLE_BUCKET_COUNT & (KE_HANDLE_BUCKET_COUNT - 1u)) == 0, + "handle bucket count must be a power of two"); + +// Returns the process-local hash bucket for one opaque handle value. +static uint32_t KiHandleBucket(SYS_HANDLE handle) { + return (handle ^ (handle >> 16) ^ (handle >> 8)) & (KE_HANDLE_BUCKET_COUNT - 1u); +} + +// Returns one entry while the handle-table lock is held. +static KE_HANDLE_ENTRY* KiFindHandleLocked(KE_HANDLE_TABLE* table, SYS_HANDLE handle, KE_HANDLE_ENTRY*** linkOut) { + if (linkOut) *linkOut = (KE_HANDLE_ENTRY**)0; + if (!table || handle == SYS_HANDLE_INVALID) return (KE_HANDLE_ENTRY*)0; + + uint32_t bucket = KiHandleBucket(handle); + KE_HANDLE_ENTRY** link = &table->Buckets[bucket]; + while (*link) { + if ((*link)->Handle == handle) { + if (linkOut) *linkOut = link; + return *link; + } + link = &(*link)->Next; + } + return (KE_HANDLE_ENTRY*)0; +} + +// Acquires one grant reference while its owner still stabilizes the pointer. +static void KiReferenceGrant(CAPABILITY_GRANT* grant) { + if (!grant) return; + long refs = _InterlockedIncrement(&grant->RefCount); + if (refs <= 1) { + KePanic(KE_PANIC_CODE_GENERIC, + "capability: invalid grant reference grant=0x%016llX refs=%ld\n", + (uint64_t)(uintptr_t)grant, + refs); + } +} + +// Releases one grant and recursively drops its immutable parent chain. +static void KiDereferenceGrant(CAPABILITY_GRANT* grant) { + while (grant) { + long refs = _InterlockedDecrement(&grant->RefCount); + if (refs > 0) return; + if (refs < 0) { + KePanic(KE_PANIC_CODE_GENERIC, + "capability: grant reference underflow grant=0x%016llX refs=%ld\n", + (uint64_t)(uintptr_t)grant, + refs); + } + + CAPABILITY_GRANT* parent = grant->Parent; + if (!parent && grant->Target) + ObDereferenceObject(grant->Target); + MmFreePool(grant); + grant = parent; + } +} + +// Returns whether a grant and every ancestor remain unrevoked. +static int KiGrantIsActive(const CAPABILITY_GRANT* grant) { + const CAPABILITY_GRANT* current = grant; + uint32_t depth = 0; + while (current) { + if (_InterlockedCompareExchange((volatile long*)¤t->Revoked, 0, 0) != 0) + return 0; + current = current->Parent; + if (++depth > KE_CAPABILITY_MAX_DEPTH) + return 0; + } + return 1; +} + +// Creates a root grant and takes the target object's lifetime reference. +static KSTATUS KiCreateRootGrant(OBJECT* object, SYS_RIGHTS maximumAccess, CAPABILITY_GRANT** grantOut) { + if (!object || !grantOut || maximumAccess == 0) return NTLSTATUS_INVALID_PARAMETER; + *grantOut = (CAPABILITY_GRANT*)0; + + CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)MmAllocatePoolTag(sizeof(CAPABILITY_GRANT), + MM_POOL_TAG('c', 'a', 'p', 'g')); + if (!grant) return NTLSTATUS_NO_MEMORY; + memset(grant, 0, sizeof(*grant)); + + if (!ObTryReferenceObject(object)) { + MmFreePool(grant); + return NTLSTATUS_OBJECT_DEAD; + } + + grant->RefCount = 1; + grant->Depth = 0; + grant->MaximumAccess = maximumAccess; + grant->Target = object; + grant->Parent = (CAPABILITY_GRANT*)0; + *grantOut = grant; + return NTLSTATUS_SUCCESS; +} + +// Creates one attenuated child grant and retains its parent. +static KSTATUS KiCreateChildGrant(CAPABILITY_GRANT* parent, + SYS_RIGHTS maximumAccess, + CAPABILITY_GRANT** grantOut) { + if (!parent || !grantOut || maximumAccess == 0) return NTLSTATUS_INVALID_PARAMETER; + *grantOut = (CAPABILITY_GRANT*)0; + if (parent->Depth + 1u >= KE_CAPABILITY_MAX_DEPTH) + return NTLSTATUS_LIMIT_REACHED; + if ((maximumAccess & ~parent->MaximumAccess) != 0) + return NTLSTATUS_ACCESS_DENIED; + if (!KiGrantIsActive(parent)) + return NTLSTATUS_CAPABILITY_REVOKED; + + CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)MmAllocatePoolTag(sizeof(CAPABILITY_GRANT), + MM_POOL_TAG('c', 'a', 'p', 'g')); + if (!grant) return NTLSTATUS_NO_MEMORY; + memset(grant, 0, sizeof(*grant)); + + KiReferenceGrant(parent); + grant->RefCount = 1; + grant->Depth = parent->Depth + 1u; + grant->MaximumAccess = maximumAccess; + grant->Target = parent->Target; + grant->Parent = parent; + + if (!KiGrantIsActive(parent)) { + KiDereferenceGrant(grant); + return NTLSTATUS_CAPABILITY_REVOKED; + } + + *grantOut = grant; + return NTLSTATUS_SUCCESS; +} + +// Records type-specific ownership after a process handle becomes visible. +static void KiHandleOpened(OBJECT* object) { + if (object && object->Type == ObjectTypeAudioStream) + KeAudioStreamHandleOpened((KE_AUDIO_STREAM*)object); +} + +// Releases type-specific ownership before a process handle disappears. +static void KiHandleClosed(OBJECT* object) { + if (object && object->Type == ObjectTypeAudioStream) + KeAudioStreamHandleClosed((KE_AUDIO_STREAM*)object); +} + +// Inserts one retained grant into a process and consumes that grant reference on success. +static KSTATUS KiInsertGrantHandle(PROCESS* process, + CAPABILITY_GRANT* grant, + SYS_RIGHTS access, + SYS_HANDLE* handleOut) { + if (!process || !grant || !handleOut || access == 0) return NTLSTATUS_INVALID_PARAMETER; + if ((access & ~grant->MaximumAccess) != 0) return NTLSTATUS_ACCESS_DENIED; + if (!KiGrantIsActive(grant)) return NTLSTATUS_CAPABILITY_REVOKED; + + KE_HANDLE_ENTRY* entry = (KE_HANDLE_ENTRY*)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY), + MM_POOL_TAG('h', 'n', 'd', 'l')); + if (!entry) return NTLSTATUS_NO_MEMORY; + memset(entry, 0, sizeof(*entry)); + + KE_HANDLE_TABLE* table = &process->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + if (table->Rundown) { + KeReleaseSpinlock(&table->Lock, flags); + MmFreePool(entry); + return NTLSTATUS_OBJECT_DEAD; + } + + SYS_HANDLE handle = table->NextHandle; + if (handle == SYS_HANDLE_INVALID) { + KeReleaseSpinlock(&table->Lock, flags); + MmFreePool(entry); + return NTLSTATUS_ID_EXHAUSTED; + } + + table->NextHandle = handle + 1u; + entry->Handle = handle; + entry->Access = access; + entry->Grant = grant; + uint32_t bucket = KiHandleBucket(handle); + entry->Next = table->Buckets[bucket]; + table->Buckets[bucket] = entry; + table->Count++; + KiHandleOpened(grant->Target); + KeReleaseSpinlock(&table->Lock, flags); + + *handleOut = handle; + return NTLSTATUS_SUCCESS; +} + +// Initializes an empty process-local handle namespace. +void KeInitializeHandleTable(KE_HANDLE_TABLE* table) { + if (!table) return; + memset(table, 0, sizeof(*table)); + table->NextHandle = 1u; + table->Lock = (SPINLOCK)SPINLOCK_INIT; +} + +// Returns the object-type-specific rights mask accepted by the handle core. +SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type) { + const SYS_RIGHTS management = SYS_RIGHT_MANAGE; + switch (type) { + case ObjectTypeProcess: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | + SYS_PROCESS_RIGHT_TERMINATE | SYS_PROCESS_RIGHT_INSERT_HANDLE | + SYS_PROCESS_RIGHT_BUILD; + case ObjectTypeThread: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_THREAD_RIGHT_RESUME; + case ObjectTypeMutex: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_MUTEX_RIGHT_RELEASE; + case ObjectTypeSemaphore: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_SEMAPHORE_RIGHT_SIGNAL; + case ObjectTypeEvent: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_EVENT_RIGHT_SIGNAL; + case ObjectTypeQueue: + return management | SYS_RIGHT_QUERY | SYS_QUEUE_RIGHT_SEND | + SYS_QUEUE_RIGHT_RECEIVE | SYS_QUEUE_RIGHT_PUBLISH; + case ObjectTypeFile: + return management | SYS_RIGHT_QUERY | SYS_FILE_RIGHT_READ_DATA | + SYS_FILE_RIGHT_WRITE_DATA | SYS_FILE_RIGHT_LOCK | + SYS_FILE_RIGHT_MAP_READ | SYS_FILE_RIGHT_MAP_WRITE; + case ObjectTypeDir: + return management | SYS_RIGHT_QUERY | SYS_DIR_RIGHT_ENUMERATE | SYS_DIR_RIGHT_OPEN | + SYS_DIR_RIGHT_MODIFY; + case ObjectTypeAsyncIo: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_ASYNC_IO_RIGHT_CANCEL; + case ObjectTypeSocket: + return management | SYS_RIGHT_QUERY | SYS_SOCKET_RIGHT_BIND | + SYS_SOCKET_RIGHT_CONNECT | SYS_SOCKET_RIGHT_LISTEN | + SYS_SOCKET_RIGHT_ACCEPT | SYS_SOCKET_RIGHT_SEND | + SYS_SOCKET_RIGHT_RECEIVE | SYS_SOCKET_RIGHT_SHUTDOWN | + SYS_SOCKET_RIGHT_POLL | SYS_SOCKET_RIGHT_SET_OPTION; + case ObjectTypeFileMapping: + return management | SYS_RIGHT_QUERY | SYS_FILE_MAPPING_RIGHT_MAP; + case ObjectTypeSurface: + return management | SYS_RIGHT_QUERY | SYS_SURFACE_RIGHT_MAP | SYS_SURFACE_RIGHT_PRESENT; + case ObjectTypeInputQueue: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_INPUT_QUEUE_RIGHT_READ; + case ObjectTypeTimer: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_TIMER_RIGHT_SET; + case ObjectTypeDisplayClock: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT; + case ObjectTypeAudioStream: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | + SYS_AUDIO_STREAM_RIGHT_WRITE | SYS_AUDIO_STREAM_RIGHT_CONTROL; + case ObjectTypeSecurityContext: + return management | SYS_RIGHT_QUERY | + SYS_SECURITY_CONTEXT_RIGHT_DERIVE | SYS_SECURITY_CONTEXT_RIGHT_ASSIGN; + case ObjectTypePort: + return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | + SYS_PORT_RIGHT_CONNECT | SYS_PORT_RIGHT_ACCEPT | + SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE | + SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY | + SYS_PORT_RIGHT_DISCONNECT; + case ObjectTypeVolume: + return management | SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY; + case ObjectTypeVolumeManager: + return management | SYS_RIGHT_QUERY | + SYS_VOLUME_MANAGER_RIGHT_ENUMERATE | SYS_VOLUME_MANAGER_RIGHT_OPEN | + SYS_VOLUME_MANAGER_RIGHT_WAIT; + case ObjectTypeDevice: + case ObjectTypeDriver: + case ObjectTypeUsbDevice: + case ObjectTypeUsbPipe: + case ObjectTypeImageSection: + case ObjectTypeAudioEndpoint: + return management | SYS_RIGHT_QUERY; + default: + return 0; + } +} + +// Creates one root capability handle for an object. +KSTATUS KeCreateHandle(PROCESS* process, OBJECT* object, SYS_RIGHTS access, SYS_HANDLE* handleOut) { + if (!process || !object || !handleOut || access == 0) return NTLSTATUS_INVALID_PARAMETER; + SYS_RIGHTS valid = KeGetValidHandleRights(object->Type); + if (!valid || (access & ~valid) != 0) return NTLSTATUS_INVALID_PARAMETER; + + CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)0; + KSTATUS status = KiCreateRootGrant(object, access, &grant); + if (!KSUCCESS(status)) return status; + + status = KiInsertGrantHandle(process, grant, access, handleOut); + if (!KSUCCESS(status)) KiDereferenceGrant(grant); + return status; +} + +// References the target object after validating type, revocation, and rights. +KSTATUS KeReferenceHandle(PROCESS* process, + SYS_HANDLE handle, + OBJECT_TYPE expectedType, + SYS_RIGHTS requiredRights, + OBJECT** objectOut) { + if (!process || !objectOut) return NTLSTATUS_INVALID_PARAMETER; + *objectOut = (OBJECT*)0; + if (handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE; + + KE_HANDLE_TABLE* table = &process->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, (KE_HANDLE_ENTRY***)0); + if (!entry) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_HANDLE; + } + if (requiredRights && (entry->Access & requiredRights) != requiredRights) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_ACCESS_DENIED; + } + if (!KiGrantIsActive(entry->Grant)) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_CAPABILITY_REVOKED; + } + + OBJECT* object = entry->Grant->Target; + if (!object || (expectedType != 0 && object->Type != expectedType)) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_HANDLE; + } + if (!ObTryReferenceObject(object)) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_OBJECT_DEAD; + } + + KeReleaseSpinlock(&table->Lock, flags); + *objectOut = object; + return NTLSTATUS_SUCCESS; +} + +// Closes one process-local handle without reusing its numeric identity. +KSTATUS KeCloseHandle(PROCESS* process, SYS_HANDLE handle) { + if (!process || handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE; + + KE_HANDLE_TABLE* table = &process->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + KE_HANDLE_ENTRY** link = (KE_HANDLE_ENTRY**)0; + KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, &link); + if (!entry || !link) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_HANDLE; + } + + *link = entry->Next; + if (table->Count == 0) { + KeReleaseSpinlock(&table->Lock, flags); + KePanic(KE_PANIC_CODE_GENERIC, "capability: handle count underflow pid=%u\n", process->ProcessId); + } + table->Count--; + KeReleaseSpinlock(&table->Lock, flags); + + KSTATUS closeStatus = NTLSTATUS_SUCCESS; + OBJECT* object = entry->Grant->Target; + if (object && object->Type == ObjectTypeFile) + closeStatus = IoFlushFile((FILE_OBJECT*)object); + KeNamedQueueUnpublishHandle(process, handle, object); + KiHandleClosed(object); + KiDereferenceGrant(entry->Grant); + MmFreePool(entry); + return closeStatus; +} + +// Duplicates one alias of the same capability grant into another process. +KSTATUS KeDuplicateHandle(PROCESS* sourceProcess, + SYS_HANDLE sourceHandle, + PROCESS* targetProcess, + SYS_RIGHTS desiredRights, + uint32_t duplicateFlags, + SYS_HANDLE* targetHandleOut) { + if (!sourceProcess || !targetProcess || !targetHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + if (sourceHandle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE; + + const uint32_t validFlags = SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS; + if ((duplicateFlags & ~validFlags) != 0) return NTLSTATUS_INVALID_PARAMETER; + if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && desiredRights != 0) + return NTLSTATUS_INVALID_PARAMETER; + if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && desiredRights == 0) + return NTLSTATUS_INVALID_PARAMETER; + + CAPABILITY_GRANT* grant = (CAPABILITY_GRANT*)0; + SYS_RIGHTS sourceAccess = 0; + KE_HANDLE_TABLE* table = &sourceProcess->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0); + if (!entry) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_HANDLE; + } + if (!KiGrantIsActive(entry->Grant)) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_CAPABILITY_REVOKED; + } + SYS_RIGHTS requiredManagement = SYS_RIGHT_DUPLICATE; + if (sourceProcess != targetProcess) + requiredManagement |= SYS_RIGHT_TRANSFER; + if ((entry->Access & requiredManagement) != requiredManagement) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_ACCESS_DENIED; + } + + sourceAccess = entry->Access; + SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type); + if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && + (desiredRights & ~validRights) != 0) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_PARAMETER; + } + if (!(duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) && + (desiredRights & ~sourceAccess) != 0) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_ACCESS_DENIED; + } + grant = entry->Grant; + KiReferenceGrant(grant); + KeReleaseSpinlock(&table->Lock, flags); + + SYS_RIGHTS targetAccess = (duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) + ? sourceAccess : desiredRights; + KSTATUS status = KiInsertGrantHandle(targetProcess, grant, targetAccess, targetHandleOut); + if (!KSUCCESS(status)) KiDereferenceGrant(grant); + return status; +} + +// Derives one child grant whose maximum rights are an attenuated subset. +KSTATUS KeDeriveHandle(PROCESS* process, + SYS_HANDLE sourceHandle, + SYS_RIGHTS desiredRights, + SYS_HANDLE* derivedHandleOut) { + if (!process || !derivedHandleOut || desiredRights == 0) + return NTLSTATUS_INVALID_PARAMETER; + if (sourceHandle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE; + + CAPABILITY_GRANT* parent = (CAPABILITY_GRANT*)0; + KE_HANDLE_TABLE* table = &process->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0); + if (!entry) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_HANDLE; + } + if (!KiGrantIsActive(entry->Grant)) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_CAPABILITY_REVOKED; + } + if ((entry->Access & SYS_RIGHT_DERIVE) == 0) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_ACCESS_DENIED; + } + SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type); + if ((desiredRights & ~validRights) != 0) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_PARAMETER; + } + if ((desiredRights & ~entry->Access) != 0) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_ACCESS_DENIED; + } + parent = entry->Grant; + KiReferenceGrant(parent); + KeReleaseSpinlock(&table->Lock, flags); + + CAPABILITY_GRANT* child = (CAPABILITY_GRANT*)0; + KSTATUS status = KiCreateChildGrant(parent, desiredRights, &child); + KiDereferenceGrant(parent); + if (!KSUCCESS(status)) return status; + + status = KiInsertGrantHandle(process, child, desiredRights, derivedHandleOut); + if (!KSUCCESS(status)) KiDereferenceGrant(child); + return status; +} + +// Revokes one grant and all grants derived from it. +KSTATUS KeRevokeHandle(PROCESS* process, SYS_HANDLE handle) { + if (!process) return NTLSTATUS_INVALID_PARAMETER; + if (handle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE; + + KE_HANDLE_TABLE* table = &process->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, handle, (KE_HANDLE_ENTRY***)0); + if (!entry) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_INVALID_HANDLE; + } + if ((entry->Access & SYS_RIGHT_REVOKE) == 0) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_ACCESS_DENIED; + } + if (!KiGrantIsActive(entry->Grant)) { + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_CAPABILITY_REVOKED; + } + + _InterlockedExchange(&entry->Grant->Revoked, 1); + KeReleaseSpinlock(&table->Lock, flags); + return NTLSTATUS_SUCCESS; +} + +// Captures one capability so a later Port delivery does not depend on the source handle staying open. +KSTATUS KeCaptureHandleForTransfer(PROCESS* sourceProcess, + SYS_HANDLE sourceHandle, + SYS_RIGHTS desiredRights, + uint32_t flags, + KE_CAPTURED_HANDLE** capturedOut) { + if (!sourceProcess || !capturedOut) + return NTLSTATUS_INVALID_PARAMETER; + *capturedOut = (KE_CAPTURED_HANDLE*)0; + if (sourceHandle == SYS_HANDLE_INVALID) + return NTLSTATUS_INVALID_HANDLE; + + const uint32_t validFlags = KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS | + KE_HANDLE_TRANSFER_FLAG_DERIVE; + if ((flags & ~validFlags) != 0) + return NTLSTATUS_INVALID_PARAMETER; + if ((flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) && + (flags & KE_HANDLE_TRANSFER_FLAG_DERIVE)) + return NTLSTATUS_INVALID_PARAMETER; + if ((flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) && desiredRights != 0) + return NTLSTATUS_INVALID_PARAMETER; + if (!(flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) && desiredRights == 0) + return NTLSTATUS_INVALID_PARAMETER; + + CAPABILITY_GRANT* sourceGrant = (CAPABILITY_GRANT*)0; + SYS_RIGHTS sourceAccess = 0; + KE_HANDLE_TABLE* table = &sourceProcess->HandleTable; + uintptr_t lockFlags = KeAcquireSpinlock(&table->Lock); + KE_HANDLE_ENTRY* entry = KiFindHandleLocked(table, sourceHandle, (KE_HANDLE_ENTRY***)0); + if (!entry) { + KeReleaseSpinlock(&table->Lock, lockFlags); + return NTLSTATUS_INVALID_HANDLE; + } + if (!KiGrantIsActive(entry->Grant)) { + KeReleaseSpinlock(&table->Lock, lockFlags); + return NTLSTATUS_CAPABILITY_REVOKED; + } + + SYS_RIGHTS required = SYS_RIGHT_TRANSFER; + if (flags & KE_HANDLE_TRANSFER_FLAG_DERIVE) + required |= SYS_RIGHT_DERIVE; + if ((entry->Access & required) != required) { + KeReleaseSpinlock(&table->Lock, lockFlags); + return NTLSTATUS_ACCESS_DENIED; + } + + sourceAccess = entry->Access; + SYS_RIGHTS targetAccess = (flags & KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS) + ? sourceAccess : desiredRights; + SYS_RIGHTS validRights = KeGetValidHandleRights(entry->Grant->Target->Type); + if ((targetAccess & ~validRights) != 0) { + KeReleaseSpinlock(&table->Lock, lockFlags); + return NTLSTATUS_INVALID_PARAMETER; + } + if ((targetAccess & ~sourceAccess) != 0) { + KeReleaseSpinlock(&table->Lock, lockFlags); + return NTLSTATUS_ACCESS_DENIED; + } + + sourceGrant = entry->Grant; + KiReferenceGrant(sourceGrant); + KeReleaseSpinlock(&table->Lock, lockFlags); + + CAPABILITY_GRANT* capturedGrant = sourceGrant; + if (flags & KE_HANDLE_TRANSFER_FLAG_DERIVE) { + KSTATUS status = KiCreateChildGrant(sourceGrant, targetAccess, &capturedGrant); + KiDereferenceGrant(sourceGrant); + if (!KSUCCESS(status)) + return status; + } + + KE_CAPTURED_HANDLE* captured = (KE_CAPTURED_HANDLE*)MmAllocatePoolTag(sizeof(*captured), + MM_POOL_TAG('c', 'a', 'p', 't')); + if (!captured) { + KiDereferenceGrant(capturedGrant); + return NTLSTATUS_NO_MEMORY; + } + captured->Grant = capturedGrant; + captured->Access = targetAccess; + *capturedOut = captured; + return NTLSTATUS_SUCCESS; +} + +// Releases one captured capability that did not become a target-process handle. +void KeReleaseCapturedHandle(KE_CAPTURED_HANDLE* captured) { + if (!captured) + return; + if (captured->Grant) + KiDereferenceGrant(captured->Grant); + MmFreePool(captured); +} + +// Materializes a complete transfer set atomically with respect to target-process handle visibility. +KSTATUS KeMaterializeCapturedHandles(PROCESS* targetProcess, + KE_CAPTURED_HANDLE** captured, + uint32_t count, + SYS_HANDLE* handlesOut) { + if (!targetProcess || (count && (!captured || !handlesOut))) + return NTLSTATUS_INVALID_PARAMETER; + if (count == 0) + return NTLSTATUS_SUCCESS; + + KE_HANDLE_ENTRY** entries = (KE_HANDLE_ENTRY**)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY*) * count, + MM_POOL_TAG('h', 'b', 'a', 't')); + if (!entries) + return NTLSTATUS_NO_MEMORY; + memset(entries, 0, sizeof(KE_HANDLE_ENTRY*) * count); + + KSTATUS status = NTLSTATUS_SUCCESS; + for (uint32_t i = 0; i < count; ++i) { + if (!captured[i] || !captured[i]->Grant || captured[i]->Access == 0) { + status = NTLSTATUS_INVALID_PARAMETER; + break; + } + if (!KiGrantIsActive(captured[i]->Grant)) { + status = NTLSTATUS_CAPABILITY_REVOKED; + break; + } + entries[i] = (KE_HANDLE_ENTRY*)MmAllocatePoolTag(sizeof(KE_HANDLE_ENTRY), + MM_POOL_TAG('h', 'n', 'd', 'l')); + if (!entries[i]) { + status = NTLSTATUS_NO_MEMORY; + break; + } + memset(entries[i], 0, sizeof(*entries[i])); + } + + if (!KSUCCESS(status)) { + for (uint32_t i = 0; i < count; ++i) + if (entries[i]) MmFreePool(entries[i]); + MmFreePool(entries); + return status; + } + + KE_HANDLE_TABLE* table = &targetProcess->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + if (table->Rundown) { + status = NTLSTATUS_OBJECT_DEAD; + } else if (table->NextHandle == SYS_HANDLE_INVALID || + (uint64_t)table->NextHandle + (uint64_t)count - 1ull > UINT32_MAX) { + status = NTLSTATUS_ID_EXHAUSTED; + } else { + for (uint32_t i = 0; i < count; ++i) { + if (!KiGrantIsActive(captured[i]->Grant)) { + status = NTLSTATUS_CAPABILITY_REVOKED; + break; + } + } + } + + if (KSUCCESS(status)) { + for (uint32_t i = 0; i < count; ++i) { + SYS_HANDLE handle = table->NextHandle++; + entries[i]->Handle = handle; + entries[i]->Access = captured[i]->Access; + entries[i]->Grant = captured[i]->Grant; + uint32_t bucket = KiHandleBucket(handle); + entries[i]->Next = table->Buckets[bucket]; + table->Buckets[bucket] = entries[i]; + table->Count++; + KiHandleOpened(captured[i]->Grant->Target); + handlesOut[i] = handle; + captured[i]->Grant = (CAPABILITY_GRANT*)0; + } + } + KeReleaseSpinlock(&table->Lock, flags); + + if (!KSUCCESS(status)) { + for (uint32_t i = 0; i < count; ++i) + MmFreePool(entries[i]); + MmFreePool(entries); + return status; + } + + for (uint32_t i = 0; i < count; ++i) { + MmFreePool(captured[i]); + captured[i] = (KE_CAPTURED_HANDLE*)0; + } + MmFreePool(entries); + return NTLSTATUS_SUCCESS; +} + +// Returns the number of currently live handles in one process. +uint32_t KeGetHandleCount(PROCESS* process) { + if (!process) return 0; + KE_HANDLE_TABLE* table = &process->HandleTable; + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + uint32_t count = table->Count; + KeReleaseSpinlock(&table->Lock, flags); + return count; +} + +// Releases every handle and capability grant owned by one process. +void KeRundownHandleTable(PROCESS* process) { + if (!process) return; + + KE_HANDLE_TABLE* table = &process->HandleTable; + uintptr_t rundownFlags = KeAcquireSpinlock(&table->Lock); + table->Rundown = 1u; + KeReleaseSpinlock(&table->Lock, rundownFlags); + + for (uint32_t bucket = 0; bucket < KE_HANDLE_BUCKET_COUNT; ++bucket) { + while (true) { + uintptr_t flags = KeAcquireSpinlock(&table->Lock); + KE_HANDLE_ENTRY* entry = table->Buckets[bucket]; + if (entry) { + table->Buckets[bucket] = entry->Next; + if (table->Count == 0) { + KeReleaseSpinlock(&table->Lock, flags); + KePanic(KE_PANIC_CODE_GENERIC, + "capability: rundown count underflow pid=%u\n", + process->ProcessId); + } + table->Count--; + } + KeReleaseSpinlock(&table->Lock, flags); + if (!entry) break; + + OBJECT* object = entry->Grant->Target; + KeNamedQueueUnpublishHandle(process, entry->Handle, object); + KiHandleClosed(object); + KiDereferenceGrant(entry->Grant); + MmFreePool(entry); + } + } +} diff --git a/code/kernel/ke_id.c b/code/kernel/ke_id.c new file mode 100644 index 0000000..177b81d --- /dev/null +++ b/code/kernel/ke_id.c @@ -0,0 +1,22 @@ +// Kernel monotonic identifier allocator. Public identities stay 32-bit while the internal cursor detects exhaustion without wrapping. + +#include +#include + +// Allocates one 32-bit identifier and permanently advances the allocator. +KSTATUS KeAllocateId32(KE_ID_ALLOCATOR* allocator, uint32_t* idOut) { + if (!allocator || !idOut) + return NTLSTATUS_INVALID_PARAMETER; + + while (1) { + long long next = _InterlockedCompareExchange64(&allocator->Next, 0, 0); + if ((unsigned long long)next > 0xFFFFFFFFULL) + return NTLSTATUS_ID_EXHAUSTED; + + long long desired = next + 1; + if (_InterlockedCompareExchange64(&allocator->Next, desired, next) == next) { + *idOut = (uint32_t)next; + return NTLSTATUS_SUCCESS; + } + } +} diff --git a/code/kernel/ke_idt.c b/code/kernel/ke_idt.c index ab4fc31..f47e07c 100644 --- a/code/kernel/ke_idt.c +++ b/code/kernel/ke_idt.c @@ -221,7 +221,7 @@ void KeInitializeIdt(void) { KiIdtSetGate(0x40, (uintptr_t)KeSyscallEntry, KI_IDT_TYPE_USER_INTERRUPT_GATE, 0); - static void *irqStubs[NUM_IRQS] = { + static void* irqStubs[NUM_IRQS] = { (void*)KeIrqStub0, (void*)KeIrqStub1, (void*)KeIrqStub2, (void*)KeIrqStub3, (void*)KeIrqStub4, (void*)KeIrqStub5, (void*)KeIrqStub6, (void*)KeIrqStub7, (void*)KeIrqStub8, diff --git a/code/kernel/ke_input.c b/code/kernel/ke_input.c index ca54e56..3ad352e 100644 --- a/code/kernel/ke_input.c +++ b/code/kernel/ke_input.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -19,7 +20,6 @@ #define MOUSE_QUEUE_SIZE 128u #define MOUSE_QUEUE_MASK (MOUSE_QUEUE_SIZE - 1u) #define MAX_INPUT_KEYS 256u -#define MAX_INPUT_DEVICES 16u #define KEY_REPEAT_DELAY_US 500000ull #define KEY_REPEAT_PERIOD_US 33000ull @@ -30,7 +30,7 @@ typedef struct KEY_CHAR_PAIR { } KEY_CHAR_PAIR; static KE_INPUT_CONTEXT gDefaultInputContext; -static volatile uint32_t gNextInputDeviceId = 1; +static KE_ID_ALLOCATOR gInputDeviceIds = KE_ID_ALLOCATOR_INIT(1u); static char KiInputKeyToChar(KE_INPUT_CONTEXT* context, INPUT_KEY key); static void KiInputServiceRepeat(KE_INPUT_CONTEXT* context); @@ -246,8 +246,9 @@ void KeInputInitialize(const HAL_API* hal) { // Implements the input Register Device operation. __declspec(dllexport) KSTATUS KeInputRegisterDevice(const char* name, uint32_t flags, uint32_t* outDeviceId) { (void)flags; - uint32_t id = gNextInputDeviceId++; - if (id >= MAX_INPUT_DEVICES) return NTLSTATUS_NO_MEMORY; + uint32_t id = 0; + KSTATUS status = KeAllocateId32(&gInputDeviceIds, &id); + if (!KSUCCESS(status)) return status; if (outDeviceId) *outDeviceId = id; DbgPrint("Input: registered device %u (%s)\n", id, name ? name : "unnamed"); return NTLSTATUS_SUCCESS; diff --git a/code/kernel/ke_irq.c b/code/kernel/ke_irq.c index e5722ab..ad619c7 100644 --- a/code/kernel/ke_irq.c +++ b/code/kernel/ke_irq.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #define MAX_IRQS 24 @@ -23,7 +23,7 @@ typedef struct IRQ_HANDLER_LIST { static IRQ_HANDLER_LIST gIrqHandlers[MAX_IRQS]; static bool gIrqSoftwareVector[MAX_IRQS]; -static const HAL_API *gHal; +static const HAL_API* gHal; // Implements the irq To Irql operation. static KIRQL KiIrqToIrql(uint8_t irq) { @@ -34,7 +34,9 @@ static KIRQL KiIrqToIrql(uint8_t irq) { } // Implements the irq Initialize operation. -void KeIrqInitialize(const HAL_API *hal) { +void KeIrqInitialize(const HAL_API* hal) { + if (!hal || !hal->InterruptController.RouteIrq || !hal->InterruptController.SendEoi) + KePanic(KE_PANIC_CODE_INIT, "IRQ: invalid interrupt-controller API during initialization\n"); gHal = hal; } @@ -59,6 +61,8 @@ __declspec(dllexport) KSTATUS KeIrqAllocateMsiVector(uint8_t* outIrq) { // Implements the irq Register operation. __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) { if (irq >= MAX_IRQS || !handler) return; + if (!gHal) + KePanic(KE_PANIC_CODE_INIT, "IRQ: registration before interrupt-manager initialization\n"); IRQ_HANDLER_LIST* list = &gIrqHandlers[irq]; for (uint8_t i = 0; i < list->Count; i++) { @@ -73,11 +77,14 @@ __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) { list->Handlers[list->Count++] = handler; if (!gIrqSoftwareVector[irq]) - gHal->Intc.RouteIrq(irq); + gHal->InterruptController.RouteIrq(irq); } // Implements the irq Dispatch operation. void KeIrqDispatch(uint8_t irq) { + if (!gHal) + KePanic(KE_PANIC_CODE_INIT, "IRQ: dispatch before interrupt-manager initialization, irq=%u\n", irq); + KIRQL targetIrql = KiIrqToIrql(irq); KIRQL currentIrql = KeGetCurrentIrql(); @@ -104,7 +111,7 @@ void KeIrqDispatch(uint8_t irq) { // only request rescheduling; KiExitInterrupt performs the switch after this // function returns, so the EOI is guaranteed to be sent first. Keep the // software level at DIRQL until after the controller has been acknowledged. - gHal->Intc.SendEoi(); + gHal->InterruptController.SendEoi(); KeLowerIrql(oldIrql); } diff --git a/code/kernel/ke_port.c b/code/kernel/ke_port.c new file mode 100644 index 0000000..8bc7b21 --- /dev/null +++ b/code/kernel/ke_port.c @@ -0,0 +1,1636 @@ +// Kernel Port IPC mechanism. Ports provide copied messaging, synchronous calls, waitability, and capability transfer. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT_KIND_LISTENER 1u +#define PORT_KIND_ENDPOINT 2u + +#define PORT_CALL_PENDING 1u +#define PORT_CALL_DELIVERED 2u +#define PORT_CALL_REPLYING 3u +#define PORT_CALL_COMPLETED 4u +#define PORT_CALL_CANCELLED 5u + +#define PORT_PROCESS_MESSAGE_LIMIT 256u +#define PORT_PROCESS_BYTE_LIMIT (256u * 1024u) +#define PORT_PROCESS_HANDLE_LIMIT 512u +#define PORT_PROCESS_CALL_LIMIT 128u + +typedef struct PORT_CONNECTION PORT_CONNECTION; +typedef struct PORT_MESSAGE PORT_MESSAGE; +typedef struct PORT_CALL PORT_CALL; +typedef struct PORT_RECEIVE_WAIT PORT_RECEIVE_WAIT; + +typedef struct PORT_PENDING_CONNECTION { + LIST_ENTRY Link; + struct PORT* ServerEndpoint; + SYS_PORT_CONNECTION_INFO Info; +} PORT_PENDING_CONNECTION; + +struct PORT_MESSAGE { + LIST_ENTRY Link; + SYS_PORT_MESSAGE_INFO Info; + void* Payload; + struct PROCESS* AccountingProcess; + uint32_t AccountedBytes; + uint32_t AccountedHandles; + struct KE_CAPTURED_HANDLE* Captured[SYS_PORT_MAX_TRANSFER_HANDLES]; + uint32_t CapturedCount; + PORT_CALL* Call; +}; + +struct PORT_RECEIVE_WAIT { + LIST_ENTRY Link; + struct PROCESS* Process; + void* Payload; + uint32_t PayloadCapacity; + uint32_t HandleCapacity; + MM_LOCKED_USER_BUFFER* LockedPayload; + EVENT* CompletionEvent; + SYS_PORT_MESSAGE_INFO Info; + SYS_HANDLE Handles[SYS_PORT_MAX_TRANSFER_HANDLES]; + uint32_t HandleCount; + KSTATUS Status; + volatile long Claimed; + volatile long Completed; +}; + +struct PORT_CALL { + LIST_ENTRY Link; + volatile long RefCount; + PORT_CONNECTION* Connection; + uint32_t MessageId; + uint32_t CallerSide; + volatile long State; + struct PROCESS* CallerProcess; + EVENT* CompletionEvent; + MM_LOCKED_USER_BUFFER* LockedReplyPayload; + void* ReplyPayload; + uint32_t ReplyCapacity; + uint32_t ReplyHandleCapacity; + SYS_PORT_MESSAGE_INFO ReplyInfo; + SYS_HANDLE ReplyHandles[SYS_PORT_MAX_TRANSFER_HANDLES]; + uint32_t ReplyHandleCount; + KSTATUS Status; +}; + +struct PORT_CONNECTION { + volatile long RefCount; + SPINLOCK Lock; + struct PORT* Endpoints[2]; + uint8_t Disconnected[2]; + LIST_ENTRY Calls; +}; + +typedef struct PORT_LISTENER_STATE { + SPINLOCK Lock; + LIST_ENTRY Pending; + uint32_t PendingCount; + uint32_t Backlog; + EVENT* WaitEvent; +} PORT_LISTENER_STATE; + +typedef struct PORT_ENDPOINT_STATE { + PORT_CONNECTION* Connection; + uint32_t Side; + EVENT* WaitEvent; + EVENT* SpaceEvent; + LIST_ENTRY Incoming; + uint32_t IncomingCount; + uint32_t IncomingBytes; + uint32_t IncomingHandles; + LIST_ENTRY WaitingReceivers; +} PORT_ENDPOINT_STATE; + +struct PORT { + OBJECT Header; + uint32_t Kind; + union { + PORT_LISTENER_STATE Listener; + PORT_ENDPOINT_STATE Endpoint; + } u; +}; + +static KE_ID_ALLOCATOR gPortMessageIds = KE_ID_ALLOCATOR_INIT(1u); + +typedef struct PORT_COUNTERS { + volatile long long ConnectionsCreated; + volatile long long DirectDeliveries; + volatile long long DirectBytes; + volatile long long QueuedMessages; + volatile long long QueuedBytes; + volatile long long QueuedDeliveries; + volatile long long RepliesCompleted; + volatile long long Disconnects; + volatile long long ProcessQuotaRejects; +} PORT_COUNTERS; + +static PORT_COUNTERS gPortCounters; + +// Reads one 64-bit diagnostic counter atomically without changing it. +static uint64_t KiPortReadCounter(volatile long long* counter) { + return (uint64_t)_InterlockedCompareExchange64(counter, 0, 0); +} + +// Copies current Port transport diagnostics for the kernel stats syscall. +void KePortQueryStats(KE_PORT_STATS* statsOut) { + if (!statsOut) + return; + statsOut->ConnectionsCreated = KiPortReadCounter(&gPortCounters.ConnectionsCreated); + statsOut->DirectDeliveries = KiPortReadCounter(&gPortCounters.DirectDeliveries); + statsOut->DirectBytes = KiPortReadCounter(&gPortCounters.DirectBytes); + statsOut->QueuedMessages = KiPortReadCounter(&gPortCounters.QueuedMessages); + statsOut->QueuedBytes = KiPortReadCounter(&gPortCounters.QueuedBytes); + statsOut->QueuedDeliveries = KiPortReadCounter(&gPortCounters.QueuedDeliveries); + statsOut->RepliesCompleted = KiPortReadCounter(&gPortCounters.RepliesCompleted); + statsOut->Disconnects = KiPortReadCounter(&gPortCounters.Disconnects); + statsOut->ProcessQuotaRejects = KiPortReadCounter(&gPortCounters.ProcessQuotaRejects); +} + +// Returns the complete capability rights granted to a newly created endpoint handle. +static SYS_RIGHTS KiPortEndpointRights(void) { + return SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | + SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE | + SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY | + SYS_PORT_RIGHT_DISCONNECT; +} + +// Returns one process security identity for kernel-stamped IPC metadata. +static uint32_t KiPortSecurityId(const PROCESS* process) { + return process && process->SecurityContext ? process->SecurityContext->SecurityId : 0u; +} + +// Charges one process for the kernel resources retained by a submitted Port message. +static KSTATUS KiPortChargeProcess(PROCESS* process, uint32_t payloadBytes, uint32_t handleCount) { + if (!process || payloadBytes > SYS_PORT_MAX_INLINE_BYTES || handleCount > SYS_PORT_MAX_TRANSFER_HANDLES) + return NTLSTATUS_INVALID_PARAMETER; + if (!ObTryReferenceObject(&process->Header)) + return NTLSTATUS_OBJECT_DEAD; + + uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock); + if (process->PortOutstandingMessages >= PORT_PROCESS_MESSAGE_LIMIT || + payloadBytes > PORT_PROCESS_BYTE_LIMIT - process->PortOutstandingBytes || + handleCount > PORT_PROCESS_HANDLE_LIMIT - process->PortOutstandingHandles) { + KeReleaseSpinlock(&process->PortQuotaLock, flags); + ObDereferenceObject(&process->Header); + _InterlockedIncrement64(&gPortCounters.ProcessQuotaRejects); + return NTLSTATUS_LIMIT_REACHED; + } + process->PortOutstandingMessages++; + process->PortOutstandingBytes += payloadBytes; + process->PortOutstandingHandles += handleCount; + KeReleaseSpinlock(&process->PortQuotaLock, flags); + return NTLSTATUS_SUCCESS; +} + +// Releases one process Port quota charge after delivery, cancellation, or disconnect. +static void KiPortReleaseProcessCharge(PROCESS* process, uint32_t payloadBytes, uint32_t handleCount) { + if (!process) + return; + + uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock); + if (process->PortOutstandingMessages == 0 || + process->PortOutstandingBytes < payloadBytes || + process->PortOutstandingHandles < handleCount) { + KeReleaseSpinlock(&process->PortQuotaLock, flags); + KePanic(KE_PANIC_CODE_GENERIC, + "port: process quota accounting underflow pid=%u\n", + process->ProcessId); + } + process->PortOutstandingMessages--; + process->PortOutstandingBytes -= payloadBytes; + process->PortOutstandingHandles -= handleCount; + KeReleaseSpinlock(&process->PortQuotaLock, flags); + ObDereferenceObject(&process->Header); +} + +// Charges one caller for a retained synchronous call context and locked reply buffer. +static KSTATUS KiPortChargeCall(PROCESS* process) { + if (!process) + return NTLSTATUS_INVALID_PARAMETER; + if (!ObTryReferenceObject(&process->Header)) + return NTLSTATUS_OBJECT_DEAD; + + uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock); + if (process->PortOutstandingCalls >= PORT_PROCESS_CALL_LIMIT) { + KeReleaseSpinlock(&process->PortQuotaLock, flags); + ObDereferenceObject(&process->Header); + _InterlockedIncrement64(&gPortCounters.ProcessQuotaRejects); + return NTLSTATUS_LIMIT_REACHED; + } + process->PortOutstandingCalls++; + KeReleaseSpinlock(&process->PortQuotaLock, flags); + return NTLSTATUS_SUCCESS; +} + +// Releases a retained synchronous call quota charge and its process reference. +static void KiPortReleaseCallCharge(PROCESS* process) { + if (!process) + return; + uintptr_t flags = KeAcquireSpinlock(&process->PortQuotaLock); + if (process->PortOutstandingCalls == 0) { + KeReleaseSpinlock(&process->PortQuotaLock, flags); + KePanic(KE_PANIC_CODE_GENERIC, + "port: process call quota underflow pid=%u\n", + process->ProcessId); + } + process->PortOutstandingCalls--; + KeReleaseSpinlock(&process->PortQuotaLock, flags); + ObDereferenceObject(&process->Header); +} + +// Drops one shared connection reference after an endpoint object releases it. +static void KiPortDereferenceConnection(PORT_CONNECTION* connection) { + if (!connection) + return; + long refs = _InterlockedDecrement(&connection->RefCount); + if (refs > 0) + return; + if (refs < 0) { + KePanic(KE_PANIC_CODE_GENERIC, + "port: connection ref underflow connection=0x%016llX refs=%ld\n", + (uint64_t)(uintptr_t)connection, + refs); + } + if (!LIST_IS_EMPTY(&connection->Calls)) { + KePanic(KE_PANIC_CODE_GENERIC, + "port: destroying connection with pending calls connection=0x%016llX\n", + (uint64_t)(uintptr_t)connection); + } + MmFreePool(connection); +} + +// Releases one synchronous-call reference and destroys the context after the last owner leaves. +static void KiPortReleaseCall(PORT_CALL* call); + +// Releases every capability capture and payload buffer owned by one queued message. +static void KiPortFreeMessage(PORT_MESSAGE* message) { + if (!message) + return; + for (uint32_t i = 0; i < message->CapturedCount; ++i) { + if (message->Captured[i]) { + KeReleaseCapturedHandle(message->Captured[i]); + message->Captured[i] = (struct KE_CAPTURED_HANDLE*)0; + } + } + if (message->Payload) + MmFreePool(message->Payload); + if (message->Call) { + KiPortReleaseCall(message->Call); + message->Call = (PORT_CALL*)0; + } + if (message->AccountingProcess) { + KiPortReleaseProcessCharge(message->AccountingProcess, + message->AccountedBytes, + message->AccountedHandles); + message->AccountingProcess = (PROCESS*)0; + } + MmFreePool(message); +} + +// Closes a batch of handles that were materialized but could not be returned to the receiver. +static void KiPortCloseHandles(PROCESS* process, SYS_HANDLE* handles, uint32_t count) { + if (!process || !handles) + return; + for (uint32_t i = 0; i < count; ++i) { + if (handles[i] != SYS_HANDLE_INVALID) { + (void)KeCloseHandle(process, handles[i]); + handles[i] = SYS_HANDLE_INVALID; + } + } +} + +// Captures user-specified handles as kernel capability grants for later delivery. +static KSTATUS KiPortCaptureHandles(PROCESS* sourceProcess, + const SYS_PORT_HANDLE_TRANSFER* transfers, + uint32_t count, + struct KE_CAPTURED_HANDLE** captured) { + if (count == 0) + return NTLSTATUS_SUCCESS; + if (!sourceProcess || !transfers || !captured || count > SYS_PORT_MAX_TRANSFER_HANDLES) + return NTLSTATUS_INVALID_PARAMETER; + + for (uint32_t i = 0; i < count; ++i) + captured[i] = (struct KE_CAPTURED_HANDLE*)0; + + for (uint32_t i = 0; i < count; ++i) { + uint32_t flags = 0; + if (transfers[i].Flags & SYS_PORT_TRANSFER_FLAG_SAME_RIGHTS) + flags |= KE_HANDLE_TRANSFER_FLAG_SAME_RIGHTS; + if (transfers[i].Flags & SYS_PORT_TRANSFER_FLAG_DERIVE) + flags |= KE_HANDLE_TRANSFER_FLAG_DERIVE; + if (transfers[i].Flags & ~(SYS_PORT_TRANSFER_FLAG_SAME_RIGHTS | + SYS_PORT_TRANSFER_FLAG_DERIVE)) { + for (uint32_t j = 0; j < i; ++j) + KeReleaseCapturedHandle(captured[j]); + return NTLSTATUS_INVALID_PARAMETER; + } + + KSTATUS status = KeCaptureHandleForTransfer(sourceProcess, + transfers[i].Handle, + transfers[i].DesiredRights, + flags, + &captured[i]); + if (!KSUCCESS(status)) { + for (uint32_t j = 0; j < i; ++j) + KeReleaseCapturedHandle(captured[j]); + return status; + } + } + return NTLSTATUS_SUCCESS; +} + +// Allocates one message header and captures its monotonic identity and sender metadata. +static KSTATUS KiPortCreateMessage(PROCESS* senderProcess, + THREAD* senderThread, + uint32_t type, + uint32_t messageId, + uint32_t payloadLength, + const SYS_PORT_HANDLE_TRANSFER* transfers, + uint32_t transferCount, + PORT_CALL* call, + PORT_MESSAGE** messageOut) { + if (!senderProcess || !senderThread || !messageOut) + return NTLSTATUS_INVALID_PARAMETER; + *messageOut = (PORT_MESSAGE*)0; + if (payloadLength > SYS_PORT_MAX_INLINE_BYTES || transferCount > SYS_PORT_MAX_TRANSFER_HANDLES) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS quotaStatus = KiPortChargeProcess(senderProcess, payloadLength, transferCount); + if (!KSUCCESS(quotaStatus)) + return quotaStatus; + + PORT_MESSAGE* message = (PORT_MESSAGE*)MmAllocatePoolTag(sizeof(PORT_MESSAGE), + MM_POOL_TAG('p', 'm', 's', 'g')); + if (!message) { + KiPortReleaseProcessCharge(senderProcess, payloadLength, transferCount); + return NTLSTATUS_NO_MEMORY; + } + memset(message, 0, sizeof(*message)); + LIST_INIT(&message->Link); + message->AccountingProcess = senderProcess; + message->AccountedBytes = payloadLength; + message->AccountedHandles = transferCount; + + if (messageId == 0) { + KSTATUS status = KeAllocateId32(&gPortMessageIds, &messageId); + if (!KSUCCESS(status)) { + KiPortFreeMessage(message); + return status; + } + } + + message->Info.Size = sizeof(message->Info); + message->Info.MessageId = messageId; + message->Info.Type = type; + message->Info.PayloadLength = payloadLength; + message->Info.SenderProcessId = senderProcess->ProcessId; + message->Info.SenderThreadId = senderThread->ThreadId; + message->Info.SenderSecurityId = KiPortSecurityId(senderProcess); + message->CapturedCount = transferCount; + message->Info.HandleCount = transferCount; + message->Call = call; + if (call) + _InterlockedIncrement(&call->RefCount); + + KSTATUS status = KiPortCaptureHandles(senderProcess, + transfers, + transferCount, + message->Captured); + if (!KSUCCESS(status)) { + KiPortFreeMessage(message); + return status; + } + + *messageOut = message; + return NTLSTATUS_SUCCESS; +} + +// Copies the sender payload into kernel-owned storage when immediate rendezvous is unavailable. +static KSTATUS KiPortSnapshotPayload(PORT_MESSAGE* message, const void* payload, uint32_t payloadLength) { + if (!message) + return NTLSTATUS_INVALID_PARAMETER; + if (message->Payload) + return NTLSTATUS_SUCCESS; + if (payloadLength == 0) { + message->Info.PayloadLength = 0; + return NTLSTATUS_SUCCESS; + } + if (!payload || payloadLength > SYS_PORT_MAX_INLINE_BYTES) + return NTLSTATUS_INVALID_PARAMETER; + + void* copy = MmAllocatePoolTag(payloadLength, MM_POOL_TAG('p', 'd', 'a', 't')); + if (!copy) + return NTLSTATUS_NO_MEMORY; + KSTATUS status = CopyFromUser(copy, payload, payloadLength); + if (!KSUCCESS(status)) { + MmFreePool(copy); + return status; + } + message->Payload = copy; + message->Info.PayloadLength = payloadLength; + return NTLSTATUS_SUCCESS; +} + +// Updates endpoint queue events after one message is removed. +static void KiPortNoteDequeuedLocked(PORT* endpoint, PORT_MESSAGE* message) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !message) + return; + PORT_ENDPOINT_STATE* state = &endpoint->u.Endpoint; + if (state->IncomingCount == 0 || state->IncomingBytes < message->Info.PayloadLength || + state->IncomingHandles < message->CapturedCount) { + KePanic(KE_PANIC_CODE_GENERIC, "port: queue accounting underflow\n"); + } + state->IncomingCount--; + state->IncomingBytes -= message->Info.PayloadLength; + state->IncomingHandles -= message->CapturedCount; + KeSignalEvent(state->SpaceEvent); + if (state->IncomingCount == 0 && + !state->Connection->Disconnected[1u - state->Side]) + KeClearEvent(state->WaitEvent); +} + +// Returns whether one queued message can fit within the peer endpoint quotas. +static bool KiPortMessageFitsLocked(const PORT* endpoint, const PORT_MESSAGE* message) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !message) + return false; + const PORT_ENDPOINT_STATE* state = &endpoint->u.Endpoint; + if (state->IncomingCount >= SYS_PORT_QUEUE_MESSAGE_LIMIT) + return false; + if (message->Info.PayloadLength > SYS_PORT_QUEUE_BYTE_LIMIT - state->IncomingBytes) + return false; + if (message->CapturedCount > SYS_PORT_QUEUE_HANDLE_LIMIT - state->IncomingHandles) + return false; + return true; +} + +// Completes a direct receive rendezvous after the connection lock has claimed the waiting receiver. +static KSTATUS KiPortDeliverDirect(PORT* targetEndpoint, + PORT_RECEIVE_WAIT* receiver, + PORT_MESSAGE* message, + PROCESS* senderProcess, + const void* sourcePayload) { + if (!targetEndpoint || !receiver || !message || !senderProcess) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = NTLSTATUS_SUCCESS; + SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0}; + uint32_t handleCount = message->CapturedCount; + + if (handleCount) { + status = KeMaterializeCapturedHandles(receiver->Process, + message->Captured, + handleCount, + handles); + } + if (KSUCCESS(status) && message->Info.PayloadLength) { + status = MmCopyUserToUser(senderProcess, + sourcePayload, + receiver->Process, + receiver->Payload, + message->Info.PayloadLength); + } + + if (!KSUCCESS(status)) { + KiPortCloseHandles(receiver->Process, handles, handleCount); + receiver->HandleCount = 0; + } else { + memcpy(receiver->Handles, handles, sizeof(SYS_HANDLE) * handleCount); + receiver->HandleCount = handleCount; + } + + receiver->Info = message->Info; + receiver->Status = status; + if (KSUCCESS(status)) { + _InterlockedIncrement64(&gPortCounters.DirectDeliveries); + _InterlockedExchangeAdd64(&gPortCounters.DirectBytes, (long long)message->Info.PayloadLength); + } + _InterlockedExchange(&receiver->Completed, 1); + KeSignalEvent(receiver->CompletionEvent); + return status; +} + +// Completes one synchronous call with an error that does not include a reply payload. +static void KiPortCompleteCallError(PORT_CALL* call, KSTATUS status) { + if (!call) + return; + call->Status = status; + call->ReplyHandleCount = 0; + _InterlockedExchange(&call->State, PORT_CALL_COMPLETED); + KeSignalEvent(call->CompletionEvent); +} + +// Removes one queued request belonging to a call while the connection lock is held. +static PORT_MESSAGE* KiPortRemoveQueuedCallLocked(PORT_CONNECTION* connection, PORT_CALL* call) { + if (!connection || !call) + return (PORT_MESSAGE*)0; + uint32_t targetSide = 1u - call->CallerSide; + PORT* endpoint = connection->Endpoints[targetSide]; + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT) + return (PORT_MESSAGE*)0; + + LIST_ENTRY* head = &endpoint->u.Endpoint.Incoming; + for (LIST_ENTRY* link = head->Fwd; link != head; link = link->Fwd) { + PORT_MESSAGE* message = CONTAINING_RECORD(link, PORT_MESSAGE, Link); + if (message->Call == call) { + KeListRemoveChecked(&message->Link); + KiPortNoteDequeuedLocked(endpoint, message); + return message; + } + } + return (PORT_MESSAGE*)0; +} + +// Disconnects one endpoint side, drains undelivered messages, and completes pending calls. +static void KiPortMarkDisconnected(PORT* endpoint, bool removeEndpointPointer) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT) + return; + + PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection; + if (!connection) + return; + + LIST_ENTRY abandonedMessages; + LIST_ENTRY completedCalls; + LIST_INIT(&abandonedMessages); + LIST_INIT(&completedCalls); + + uint32_t side = endpoint->u.Endpoint.Side; + uintptr_t flags = KeAcquireSpinlock(&connection->Lock); + if (!connection->Disconnected[side]) { + connection->Disconnected[side] = 1; + _InterlockedIncrement64(&gPortCounters.Disconnects); + } + if (removeEndpointPointer && connection->Endpoints[side] == endpoint) + connection->Endpoints[side] = (PORT*)0; + + while (!LIST_IS_EMPTY(&endpoint->u.Endpoint.WaitingReceivers)) { + LIST_ENTRY* link = KeListPopHeadChecked(&endpoint->u.Endpoint.WaitingReceivers); + PORT_RECEIVE_WAIT* waiter = CONTAINING_RECORD(link, PORT_RECEIVE_WAIT, Link); + if (_InterlockedCompareExchange(&waiter->Claimed, 1, 0) == 0) { + waiter->Status = NTLSTATUS_PORT_DISCONNECTED; + _InterlockedExchange(&waiter->Completed, 1); + KeSignalEvent(waiter->CompletionEvent); + } + } + + while (!LIST_IS_EMPTY(&endpoint->u.Endpoint.Incoming)) { + LIST_ENTRY* link = KeListPopHeadChecked(&endpoint->u.Endpoint.Incoming); + PORT_MESSAGE* message = CONTAINING_RECORD(link, PORT_MESSAGE, Link); + if (endpoint->u.Endpoint.IncomingCount) + endpoint->u.Endpoint.IncomingCount--; + if (endpoint->u.Endpoint.IncomingBytes >= message->Info.PayloadLength) + endpoint->u.Endpoint.IncomingBytes -= message->Info.PayloadLength; + if (endpoint->u.Endpoint.IncomingHandles >= message->CapturedCount) + endpoint->u.Endpoint.IncomingHandles -= message->CapturedCount; + KeListInsertTailChecked(&abandonedMessages, &message->Link); + } + + LIST_ENTRY* callHead = &connection->Calls; + LIST_ENTRY* callLink = callHead->Fwd; + while (callLink != callHead) { + PORT_CALL* call = CONTAINING_RECORD(callLink, PORT_CALL, Link); + callLink = callLink->Fwd; + long state = _InterlockedCompareExchange(&call->State, 0, 0); + if (state == PORT_CALL_REPLYING || state == PORT_CALL_COMPLETED || state == PORT_CALL_CANCELLED) + continue; + PORT_MESSAGE* queuedMessage = KiPortRemoveQueuedCallLocked(connection, call); + if (queuedMessage) + KeListInsertTailChecked(&abandonedMessages, &queuedMessage->Link); + KeListRemoveChecked(&call->Link); + call->Status = NTLSTATUS_PORT_DISCONNECTED; + _InterlockedExchange(&call->State, PORT_CALL_COMPLETED); + KeListInsertTailChecked(&completedCalls, &call->Link); + } + + PORT* peer = connection->Endpoints[1u - side]; + if (peer) { + KeSignalEvent(peer->u.Endpoint.WaitEvent); + KeSignalEvent(peer->u.Endpoint.SpaceEvent); + } + KeSignalEvent(endpoint->u.Endpoint.WaitEvent); + KeSignalEvent(endpoint->u.Endpoint.SpaceEvent); + KeReleaseSpinlock(&connection->Lock, flags); + + while (!LIST_IS_EMPTY(&abandonedMessages)) { + LIST_ENTRY* link = KeListPopHeadChecked(&abandonedMessages); + PORT_MESSAGE* message = CONTAINING_RECORD(link, PORT_MESSAGE, Link); + KiPortFreeMessage(message); + } + while (!LIST_IS_EMPTY(&completedCalls)) { + LIST_ENTRY* link = KeListPopHeadChecked(&completedCalls); + PORT_CALL* call = CONTAINING_RECORD(link, PORT_CALL, Link); + KeSignalEvent(call->CompletionEvent); + } +} + +// Destroys either a listener or endpoint Port object. +static void KiPortDestroy(OBJECT* object) { + PORT* port = (PORT*)object; + if (!port) + return; + + if (port->Kind == PORT_KIND_LISTENER) { + KeSystemServiceUnregisterListener(port); + while (true) { + PORT_PENDING_CONNECTION* pending = (PORT_PENDING_CONNECTION*)0; + uintptr_t flags = KeAcquireSpinlock(&port->u.Listener.Lock); + LIST_ENTRY* link = KeListPopHeadChecked(&port->u.Listener.Pending); + if (link) { + pending = CONTAINING_RECORD(link, PORT_PENDING_CONNECTION, Link); + if (port->u.Listener.PendingCount) + port->u.Listener.PendingCount--; + } + KeReleaseSpinlock(&port->u.Listener.Lock, flags); + if (!pending) + break; + ObDereferenceObject((OBJECT*)pending->ServerEndpoint); + MmFreePool(pending); + } + if (port->u.Listener.WaitEvent) + ObDereferenceObject(&port->u.Listener.WaitEvent->Header); + return; + } + + if (port->Kind == PORT_KIND_ENDPOINT) { + KiPortMarkDisconnected(port, true); + if (port->u.Endpoint.WaitEvent) + ObDereferenceObject(&port->u.Endpoint.WaitEvent->Header); + if (port->u.Endpoint.SpaceEvent) + ObDereferenceObject(&port->u.Endpoint.SpaceEvent->Header); + KiPortDereferenceConnection(port->u.Endpoint.Connection); + port->u.Endpoint.Connection = (PORT_CONNECTION*)0; + } +} + +// Allocates one endpoint object attached to a shared connection side. +static KSTATUS KiPortCreateEndpoint(PORT_CONNECTION* connection, uint32_t side, PORT** endpointOut) { + if (!connection || !endpointOut || side > 1u) + return NTLSTATUS_INVALID_PARAMETER; + *endpointOut = (PORT*)0; + + PORT* endpoint = (PORT*)ObAllocateObject(ObjectTypePort, sizeof(PORT)); + if (!endpoint) + return NTLSTATUS_NO_MEMORY; + endpoint->Header.Destroy = KiPortDestroy; + endpoint->Kind = PORT_KIND_ENDPOINT; + endpoint->u.Endpoint.Connection = connection; + endpoint->u.Endpoint.Side = side; + LIST_INIT(&endpoint->u.Endpoint.Incoming); + LIST_INIT(&endpoint->u.Endpoint.WaitingReceivers); + + KSTATUS status = KeEventCreate(EventManualReset, 0, &endpoint->u.Endpoint.WaitEvent); + if (!KSUCCESS(status)) { + endpoint->u.Endpoint.Connection = (PORT_CONNECTION*)0; + ObDereferenceObject(&endpoint->Header); + return status; + } + status = KeEventCreate(EventManualReset, 1, &endpoint->u.Endpoint.SpaceEvent); + if (!KSUCCESS(status)) { + endpoint->u.Endpoint.Connection = (PORT_CONNECTION*)0; + ObDereferenceObject(&endpoint->Header); + return status; + } + + *endpointOut = endpoint; + return NTLSTATUS_SUCCESS; +} + +// Allocates one paired connection and its two endpoint objects. +static KSTATUS KiPortCreateConnection(PORT** clientOut, PORT** serverOut) { + if (!clientOut || !serverOut) + return NTLSTATUS_INVALID_PARAMETER; + *clientOut = (PORT*)0; + *serverOut = (PORT*)0; + + PORT_CONNECTION* connection = (PORT_CONNECTION*)MmAllocatePoolTag(sizeof(PORT_CONNECTION), + MM_POOL_TAG('p', 'c', 'o', 'n')); + if (!connection) + return NTLSTATUS_NO_MEMORY; + memset(connection, 0, sizeof(*connection)); + connection->RefCount = 2; + connection->Lock = (SPINLOCK)SPINLOCK_INIT; + LIST_INIT(&connection->Calls); + + PORT* client = (PORT*)0; + PORT* server = (PORT*)0; + KSTATUS status = KiPortCreateEndpoint(connection, 0, &client); + if (!KSUCCESS(status)) { + connection->RefCount = 1; + KiPortDereferenceConnection(connection); + return status; + } + status = KiPortCreateEndpoint(connection, 1, &server); + if (!KSUCCESS(status)) { + connection->RefCount = 1; + ObDereferenceObject(&client->Header); + return status; + } + + uintptr_t flags = KeAcquireSpinlock(&connection->Lock); + connection->Endpoints[0] = client; + connection->Endpoints[1] = server; + KeReleaseSpinlock(&connection->Lock, flags); + + *clientOut = client; + *serverOut = server; + return NTLSTATUS_SUCCESS; +} + +// Creates one bounded listener Port with a waitable pending-connection event. +KSTATUS KePortCreate(uint32_t backlog, PORT** portOut) { + if (!portOut) + return NTLSTATUS_INVALID_PARAMETER; + *portOut = (PORT*)0; + if (backlog == 0) + backlog = SYS_PORT_DEFAULT_BACKLOG; + if (backlog > SYS_PORT_MAX_BACKLOG) + return NTLSTATUS_INVALID_PARAMETER; + + PORT* port = (PORT*)ObAllocateObject(ObjectTypePort, sizeof(PORT)); + if (!port) + return NTLSTATUS_NO_MEMORY; + port->Header.Destroy = KiPortDestroy; + port->Kind = PORT_KIND_LISTENER; + port->u.Listener.Lock = (SPINLOCK)SPINLOCK_INIT; + port->u.Listener.Backlog = backlog; + LIST_INIT(&port->u.Listener.Pending); + + KSTATUS status = KeEventCreate(EventManualReset, 0, &port->u.Listener.WaitEvent); + if (!KSUCCESS(status)) { + ObDereferenceObject(&port->Header); + return status; + } + + *portOut = port; + return NTLSTATUS_SUCCESS; +} + +// Returns whether one Port object is a listener. +bool KePortIsListener(const PORT* port) { + return port && port->Kind == PORT_KIND_LISTENER; +} + +// Returns the event whose signaled state represents listener or endpoint readiness. +EVENT* KePortWaitEvent(PORT* port) { + if (!port) + return (EVENT*)0; + if (port->Kind == PORT_KIND_LISTENER) + return port->u.Listener.WaitEvent; + if (port->Kind == PORT_KIND_ENDPOINT) + return port->u.Endpoint.WaitEvent; + return (EVENT*)0; +} + +// Connects one client process to a listener by queueing the server endpoint for later acceptance. +KSTATUS KePortConnect(PORT* listener, + PROCESS* clientProcess, + THREAD* clientThread, + SYS_HANDLE* endpointHandleOut) { + if (!listener || listener->Kind != PORT_KIND_LISTENER || !clientProcess || !clientThread || !endpointHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + *endpointHandleOut = SYS_HANDLE_INVALID; + + uintptr_t flags = KeAcquireSpinlock(&listener->u.Listener.Lock); + if (listener->u.Listener.PendingCount >= listener->u.Listener.Backlog) { + KeReleaseSpinlock(&listener->u.Listener.Lock, flags); + return NTLSTATUS_LIMIT_REACHED; + } + KeReleaseSpinlock(&listener->u.Listener.Lock, flags); + + PORT* clientEndpoint = (PORT*)0; + PORT* serverEndpoint = (PORT*)0; + KSTATUS status = KiPortCreateConnection(&clientEndpoint, &serverEndpoint); + if (!KSUCCESS(status)) + return status; + + SYS_HANDLE clientHandle = SYS_HANDLE_INVALID; + status = KeCreateHandle(clientProcess, + &clientEndpoint->Header, + KiPortEndpointRights(), + &clientHandle); + if (!KSUCCESS(status)) { + ObDereferenceObject(&serverEndpoint->Header); + ObDereferenceObject(&clientEndpoint->Header); + return status; + } + ObDereferenceObject(&clientEndpoint->Header); + + PORT_PENDING_CONNECTION* pending = (PORT_PENDING_CONNECTION*)MmAllocatePoolTag(sizeof(*pending), + MM_POOL_TAG('p', 'c', 'n', 'q')); + if (!pending) { + (void)KeCloseHandle(clientProcess, clientHandle); + ObDereferenceObject(&serverEndpoint->Header); + return NTLSTATUS_NO_MEMORY; + } + memset(pending, 0, sizeof(*pending)); + LIST_INIT(&pending->Link); + pending->ServerEndpoint = serverEndpoint; + pending->Info.Size = sizeof(pending->Info); + pending->Info.ClientProcessId = clientProcess->ProcessId; + pending->Info.ClientThreadId = clientThread->ThreadId; + pending->Info.ClientSecurityId = KiPortSecurityId(clientProcess); + + flags = KeAcquireSpinlock(&listener->u.Listener.Lock); + if (listener->u.Listener.PendingCount >= listener->u.Listener.Backlog) { + KeReleaseSpinlock(&listener->u.Listener.Lock, flags); + MmFreePool(pending); + ObDereferenceObject(&serverEndpoint->Header); + (void)KeCloseHandle(clientProcess, clientHandle); + return NTLSTATUS_LIMIT_REACHED; + } + KeListInsertTailChecked(&listener->u.Listener.Pending, &pending->Link); + listener->u.Listener.PendingCount++; + _InterlockedIncrement64(&gPortCounters.ConnectionsCreated); + KeSignalEvent(listener->u.Listener.WaitEvent); + KeReleaseSpinlock(&listener->u.Listener.Lock, flags); + + *endpointHandleOut = clientHandle; + return NTLSTATUS_SUCCESS; +} + +// Accepts the oldest queued connection, waiting on the listener event when requested. +KSTATUS KePortAccept(PORT* listener, + PROCESS* serverProcess, + uint64_t timeoutMilliseconds, + SYS_HANDLE* endpointHandleOut, + SYS_PORT_CONNECTION_INFO* infoOut) { + if (!listener || listener->Kind != PORT_KIND_LISTENER || !serverProcess || !endpointHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + *endpointHandleOut = SYS_HANDLE_INVALID; + + uint64_t deadline = 0; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) + deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds); + + while (true) { + PORT_PENDING_CONNECTION* pending = (PORT_PENDING_CONNECTION*)0; + uintptr_t flags = KeAcquireSpinlock(&listener->u.Listener.Lock); + LIST_ENTRY* link = KeListPopHeadChecked(&listener->u.Listener.Pending); + if (link) { + pending = CONTAINING_RECORD(link, PORT_PENDING_CONNECTION, Link); + if (listener->u.Listener.PendingCount == 0) { + KeReleaseSpinlock(&listener->u.Listener.Lock, flags); + KePanic(KE_PANIC_CODE_GENERIC, "port: listener pending count underflow\n"); + } + listener->u.Listener.PendingCount--; + } + if (listener->u.Listener.PendingCount == 0) + KeClearEvent(listener->u.Listener.WaitEvent); + KeReleaseSpinlock(&listener->u.Listener.Lock, flags); + + if (pending) { + SYS_HANDLE handle = SYS_HANDLE_INVALID; + KSTATUS status = KeCreateHandle(serverProcess, + &pending->ServerEndpoint->Header, + KiPortEndpointRights(), + &handle); + if (KSUCCESS(status)) { + if (infoOut) + *infoOut = pending->Info; + *endpointHandleOut = handle; + } + ObDereferenceObject(&pending->ServerEndpoint->Header); + MmFreePool(pending); + return status; + } + + if (timeoutMilliseconds == 0) + return NTLSTATUS_WOULD_BLOCK; + + uint64_t wait = timeoutMilliseconds; + if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) { + wait = KeMonotonicMillisecondsUntil(deadline); + if (wait == 0) + return NTLSTATUS_TIMEOUT; + } + EVENT* events[1] = { listener->u.Listener.WaitEvent }; + KSTATUS status = KeWaitForMultipleEvents(events, 1, 0, wait, (uint32_t*)0); + if (!KSUCCESS(status)) + return status; + } +} + +// Submits a prepared message to the peer, using direct rendezvous when a receiver is already waiting. +// This function consumes the message on every return path; queued success transfers ownership to the peer queue. +static KSTATUS KiPortSubmitMessage(PORT* endpoint, + PROCESS* senderProcess, + const void* sourcePayload, + uint32_t payloadLength, + PORT_MESSAGE* message, + uint64_t timeoutMilliseconds) { + if (!message) + return NTLSTATUS_INVALID_PARAMETER; + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !senderProcess || + payloadLength > SYS_PORT_MAX_INLINE_BYTES || (payloadLength && !sourcePayload)) { + KiPortFreeMessage(message); + return NTLSTATUS_INVALID_PARAMETER; + } + message->Info.PayloadLength = payloadLength; + + PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection; + uint32_t peerSide = 1u - endpoint->u.Endpoint.Side; + uint64_t deadline = 0; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) + deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds); + + bool snapshotted = false; + while (true) { + PORT* peer = (PORT*)0; + PORT_RECEIVE_WAIT* receiver = (PORT_RECEIVE_WAIT*)0; + uintptr_t flags = KeAcquireSpinlock(&connection->Lock); + if (connection->Disconnected[endpoint->u.Endpoint.Side] || + connection->Disconnected[peerSide] || + !(peer = connection->Endpoints[peerSide])) { + KeReleaseSpinlock(&connection->Lock, flags); + KiPortFreeMessage(message); + return NTLSTATUS_PORT_DISCONNECTED; + } + + if (!snapshotted && peer->u.Endpoint.IncomingCount == 0) { + LIST_ENTRY* waitHead = &peer->u.Endpoint.WaitingReceivers; + for (LIST_ENTRY* link = waitHead->Fwd; link != waitHead; link = link->Fwd) { + PORT_RECEIVE_WAIT* candidate = CONTAINING_RECORD(link, PORT_RECEIVE_WAIT, Link); + if (payloadLength > candidate->PayloadCapacity || + message->CapturedCount > candidate->HandleCapacity) + continue; + if (_InterlockedCompareExchange(&candidate->Claimed, 1, 0) != 0) + continue; + receiver = candidate; + KeListRemoveChecked(&receiver->Link); + break; + } + if (receiver) { + if (message->Call) + _InterlockedExchange(&message->Call->State, PORT_CALL_DELIVERED); + if (!ObTryReferenceObject(&peer->Header)) { + receiver->Claimed = 0; + KeListInsertHeadChecked(&peer->u.Endpoint.WaitingReceivers, &receiver->Link); + KeReleaseSpinlock(&connection->Lock, flags); + KiPortFreeMessage(message); + return NTLSTATUS_OBJECT_DEAD; + } + KeReleaseSpinlock(&connection->Lock, flags); + + KSTATUS status = KiPortDeliverDirect(peer, + receiver, + message, + senderProcess, + sourcePayload); + ObDereferenceObject(&peer->Header); + KiPortFreeMessage(message); + return status; + } + } + KeReleaseSpinlock(&connection->Lock, flags); + + if (!snapshotted) { + KSTATUS status = KiPortSnapshotPayload(message, sourcePayload, payloadLength); + if (!KSUCCESS(status)) { + KiPortFreeMessage(message); + return status; + } + snapshotted = true; + } + + flags = KeAcquireSpinlock(&connection->Lock); + if (connection->Disconnected[endpoint->u.Endpoint.Side] || + connection->Disconnected[peerSide] || + !(peer = connection->Endpoints[peerSide])) { + KeReleaseSpinlock(&connection->Lock, flags); + KiPortFreeMessage(message); + return NTLSTATUS_PORT_DISCONNECTED; + } + + if (KiPortMessageFitsLocked(peer, message)) { + KeListInsertTailChecked(&peer->u.Endpoint.Incoming, &message->Link); + peer->u.Endpoint.IncomingCount++; + peer->u.Endpoint.IncomingBytes += message->Info.PayloadLength; + peer->u.Endpoint.IncomingHandles += message->CapturedCount; + _InterlockedIncrement64(&gPortCounters.QueuedMessages); + _InterlockedExchangeAdd64(&gPortCounters.QueuedBytes, (long long)message->Info.PayloadLength); + if (message->Call) + _InterlockedExchange(&message->Call->State, PORT_CALL_PENDING); + KeSignalEvent(peer->u.Endpoint.WaitEvent); + if (peer->u.Endpoint.IncomingCount >= SYS_PORT_QUEUE_MESSAGE_LIMIT || + peer->u.Endpoint.IncomingBytes >= SYS_PORT_QUEUE_BYTE_LIMIT || + peer->u.Endpoint.IncomingHandles >= SYS_PORT_QUEUE_HANDLE_LIMIT) + KeClearEvent(peer->u.Endpoint.SpaceEvent); + KeReleaseSpinlock(&connection->Lock, flags); + return NTLSTATUS_SUCCESS; + } + + KeClearEvent(peer->u.Endpoint.SpaceEvent); + EVENT* spaceEvent = peer->u.Endpoint.SpaceEvent; + if (!ObTryReferenceObject(&spaceEvent->Header)) { + KeReleaseSpinlock(&connection->Lock, flags); + KiPortFreeMessage(message); + return NTLSTATUS_OBJECT_DEAD; + } + KeReleaseSpinlock(&connection->Lock, flags); + + if (timeoutMilliseconds == 0) { + ObDereferenceObject(&spaceEvent->Header); + KiPortFreeMessage(message); + return NTLSTATUS_WOULD_BLOCK; + } + + uint64_t wait = timeoutMilliseconds; + if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) { + wait = KeMonotonicMillisecondsUntil(deadline); + if (wait == 0) { + ObDereferenceObject(&spaceEvent->Header); + KiPortFreeMessage(message); + return NTLSTATUS_TIMEOUT; + } + } + EVENT* events[1] = { spaceEvent }; + KSTATUS status = KeWaitForMultipleEvents(events, 1, 0, wait, (uint32_t*)0); + ObDereferenceObject(&spaceEvent->Header); + if (!KSUCCESS(status)) { + KiPortFreeMessage(message); + return status; + } + } +} + +// Sends one one-way copied notification to the peer endpoint. +KSTATUS KePortSend(PORT* endpoint, + PROCESS* senderProcess, + THREAD* senderThread, + const void* payload, + uint32_t payloadLength, + const SYS_PORT_HANDLE_TRANSFER* transfers, + uint32_t transferCount, + uint64_t timeoutMilliseconds) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !senderProcess || !senderThread) + return NTLSTATUS_INVALID_PARAMETER; + + PORT_MESSAGE* message = (PORT_MESSAGE*)0; + KSTATUS status = KiPortCreateMessage(senderProcess, + senderThread, + SYS_PORT_MESSAGE_NOTIFICATION, + 0, + payloadLength, + transfers, + transferCount, + (PORT_CALL*)0, + &message); + if (!KSUCCESS(status)) + return status; + + return KiPortSubmitMessage(endpoint, + senderProcess, + payload, + payloadLength, + message, + timeoutMilliseconds); +} + +// Delivers one already-dequeued kernel-buffered message to the current receiver. +static KSTATUS KiPortDeliverQueued(PORT* endpoint, + PROCESS* receiverProcess, + PORT_MESSAGE* message, + void* payload, + SYS_HANDLE* handlesOut, + SYS_PORT_MESSAGE_INFO* infoOut) { + if (!endpoint || !receiverProcess || !message || !infoOut) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0}; + KSTATUS status = NTLSTATUS_SUCCESS; + if (message->CapturedCount) { + status = KeMaterializeCapturedHandles(receiverProcess, + message->Captured, + message->CapturedCount, + handles); + } + if (KSUCCESS(status) && message->Info.PayloadLength) + status = CopyToUser(payload, message->Payload, message->Info.PayloadLength); + + if (!KSUCCESS(status)) { + KiPortCloseHandles(receiverProcess, handles, message->CapturedCount); + if (message->Call) { + PORT_CALL* call = message->Call; + PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection; + bool completeCall = false; + uintptr_t flags = KeAcquireSpinlock(&connection->Lock); + long callState = _InterlockedCompareExchange(&call->State, 0, 0); + if (callState != PORT_CALL_CANCELLED && callState != PORT_CALL_COMPLETED) { + if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link) + KeListRemoveChecked(&call->Link); + completeCall = true; + } + KeReleaseSpinlock(&connection->Lock, flags); + if (completeCall) + KiPortCompleteCallError(call, status); + } + return status; + } + + if (message->CapturedCount) + memcpy(handlesOut, handles, sizeof(SYS_HANDLE) * message->CapturedCount); + *infoOut = message->Info; + _InterlockedIncrement64(&gPortCounters.QueuedDeliveries); + return NTLSTATUS_SUCCESS; +} + +// Receives one copied message or registers one direct-delivery rendezvous while blocked. +KSTATUS KePortReceive(PORT* endpoint, + PROCESS* receiverProcess, + void* payload, + uint32_t payloadCapacity, + SYS_HANDLE* handlesOut, + uint32_t handleCapacity, + SYS_PORT_MESSAGE_INFO* infoOut, + uint64_t timeoutMilliseconds) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !receiverProcess || !infoOut) + return NTLSTATUS_INVALID_PARAMETER; + if (payloadCapacity > SYS_PORT_MAX_INLINE_BYTES || + handleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES || + (payloadCapacity && !payload) || (handleCapacity && !handlesOut)) + return NTLSTATUS_INVALID_PARAMETER; + + PORT_RECEIVE_WAIT wait; + memset(&wait, 0, sizeof(wait)); + LIST_INIT(&wait.Link); + wait.Process = receiverProcess; + wait.Payload = payload; + wait.PayloadCapacity = payloadCapacity; + wait.HandleCapacity = handleCapacity; + wait.Status = NTLSTATUS_PENDING; + + if (payloadCapacity) { + KSTATUS status = MmLockUserBuffer(receiverProcess, + payload, + payloadCapacity, + 1, + &wait.LockedPayload); + if (!KSUCCESS(status)) + return status; + } + if (timeoutMilliseconds != 0) { + KSTATUS status = KeEventCreate(EventAutoReset, 0, &wait.CompletionEvent); + if (!KSUCCESS(status)) { + if (wait.LockedPayload) + MmUnlockUserBuffer(wait.LockedPayload); + return status; + } + } + + PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection; + uint64_t deadline = 0; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) + deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds); + + KSTATUS result = NTLSTATUS_FAILURE; + while (true) { + PORT_MESSAGE* message = (PORT_MESSAGE*)0; + uintptr_t flags = KeAcquireSpinlock(&connection->Lock); + if (!LIST_IS_EMPTY(&endpoint->u.Endpoint.Incoming)) { + message = CONTAINING_RECORD(endpoint->u.Endpoint.Incoming.Fwd, + PORT_MESSAGE, + Link); + if (message->Info.PayloadLength > payloadCapacity || + message->CapturedCount > handleCapacity) { + *infoOut = message->Info; + KeReleaseSpinlock(&connection->Lock, flags); + result = NTLSTATUS_BUFFER_TOO_SMALL; + break; + } + KeListRemoveChecked(&message->Link); + KiPortNoteDequeuedLocked(endpoint, message); + if (message->Call) + _InterlockedExchange(&message->Call->State, PORT_CALL_DELIVERED); + KeReleaseSpinlock(&connection->Lock, flags); + + result = KiPortDeliverQueued(endpoint, + receiverProcess, + message, + payload, + handlesOut, + infoOut); + KiPortFreeMessage(message); + break; + } + + uint32_t peerSide = 1u - endpoint->u.Endpoint.Side; + if (connection->Disconnected[endpoint->u.Endpoint.Side] || + connection->Disconnected[peerSide]) { + KeReleaseSpinlock(&connection->Lock, flags); + result = NTLSTATUS_PORT_DISCONNECTED; + break; + } + + if (timeoutMilliseconds == 0) { + KeReleaseSpinlock(&connection->Lock, flags); + result = NTLSTATUS_WOULD_BLOCK; + break; + } + + if (wait.Link.Fwd == &wait.Link && wait.Link.Bwd == &wait.Link) + KeListInsertTailChecked(&endpoint->u.Endpoint.WaitingReceivers, &wait.Link); + KeClearEvent(endpoint->u.Endpoint.WaitEvent); + KeReleaseSpinlock(&connection->Lock, flags); + + uint64_t waitMilliseconds = timeoutMilliseconds; + if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) { + waitMilliseconds = KeMonotonicMillisecondsUntil(deadline); + if (waitMilliseconds == 0) + waitMilliseconds = 1; + } + EVENT* events[2] = { wait.CompletionEvent, endpoint->u.Endpoint.WaitEvent }; + KSTATUS waitStatus = KeWaitForMultipleEvents(events, + 2, + 0, + waitMilliseconds, + (uint32_t*)0); + + flags = KeAcquireSpinlock(&connection->Lock); + if (_InterlockedCompareExchange(&wait.Completed, 0, 0) != 0) { + if (wait.Link.Fwd != &wait.Link || wait.Link.Bwd != &wait.Link) + KeListRemoveChecked(&wait.Link); + if (endpoint->u.Endpoint.IncomingCount == 0 && + !connection->Disconnected[1u - endpoint->u.Endpoint.Side]) + KeClearEvent(endpoint->u.Endpoint.WaitEvent); + KeReleaseSpinlock(&connection->Lock, flags); + result = wait.Status; + if (KSUCCESS(result)) { + *infoOut = wait.Info; + if (wait.HandleCount) + memcpy(handlesOut, wait.Handles, sizeof(SYS_HANDLE) * wait.HandleCount); + } + break; + } + + if (_InterlockedCompareExchange(&wait.Claimed, 0, 0) != 0) { + KeReleaseSpinlock(&connection->Lock, flags); + EVENT* claimedEvents[1] = { wait.CompletionEvent }; + (void)KeWaitForMultipleEvents(claimedEvents, + 1, + 0, + SYS_PORT_TIMEOUT_INFINITE, + (uint32_t*)0); + continue; + } + + if (wait.Link.Fwd != &wait.Link || wait.Link.Bwd != &wait.Link) + KeListRemoveChecked(&wait.Link); + KeReleaseSpinlock(&connection->Lock, flags); + + if (!KSUCCESS(waitStatus)) { + result = waitStatus; + break; + } + if (timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE && + KeMonotonicMillisecondsUntil(deadline) == 0) { + result = NTLSTATUS_TIMEOUT; + break; + } + } + + if (wait.CompletionEvent) + ObDereferenceObject(&wait.CompletionEvent->Header); + if (wait.LockedPayload) + MmUnlockUserBuffer(wait.LockedPayload); + return result; +} + +// Creates one synchronous call context and holds caller resources until reply, timeout, or disconnect. +static KSTATUS KiPortCreateCall(PORT* endpoint, + PROCESS* callerProcess, + void* replyPayload, + uint32_t replyCapacity, + uint32_t replyHandleCapacity, + PORT_CALL** callOut) { + if (!endpoint || !callerProcess || !callOut) + return NTLSTATUS_INVALID_PARAMETER; + *callOut = (PORT_CALL*)0; + + KSTATUS quotaStatus = KiPortChargeCall(callerProcess); + if (!KSUCCESS(quotaStatus)) + return quotaStatus; + + PORT_CALL* call = (PORT_CALL*)MmAllocatePoolTag(sizeof(PORT_CALL), MM_POOL_TAG('p', 'c', 'a', 'l')); + if (!call) { + KiPortReleaseCallCharge(callerProcess); + return NTLSTATUS_NO_MEMORY; + } + memset(call, 0, sizeof(*call)); + LIST_INIT(&call->Link); + call->RefCount = 1; + call->Connection = endpoint->u.Endpoint.Connection; + call->CallerSide = endpoint->u.Endpoint.Side; + call->State = PORT_CALL_PENDING; + call->CallerProcess = callerProcess; + call->ReplyPayload = replyPayload; + call->ReplyCapacity = replyCapacity; + call->ReplyHandleCapacity = replyHandleCapacity; + call->Status = NTLSTATUS_PENDING; + + KSTATUS status = KeAllocateId32(&gPortMessageIds, &call->MessageId); + if (!KSUCCESS(status)) { + KiPortReleaseCallCharge(callerProcess); + MmFreePool(call); + return status; + } + + status = KeEventCreate(EventManualReset, 0, &call->CompletionEvent); + if (!KSUCCESS(status)) { + KiPortReleaseCallCharge(callerProcess); + MmFreePool(call); + return status; + } + + if (replyCapacity) { + status = MmLockUserBuffer(callerProcess, + replyPayload, + replyCapacity, + 1, + &call->LockedReplyPayload); + if (!KSUCCESS(status)) { + ObDereferenceObject(&call->CompletionEvent->Header); + KiPortReleaseCallCharge(callerProcess); + MmFreePool(call); + return status; + } + } + + uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock); + if (call->Connection->Disconnected[call->CallerSide] || + call->Connection->Disconnected[1u - call->CallerSide]) { + KeReleaseSpinlock(&call->Connection->Lock, flags); + if (call->LockedReplyPayload) MmUnlockUserBuffer(call->LockedReplyPayload); + ObDereferenceObject(&call->CompletionEvent->Header); + KiPortReleaseCallCharge(callerProcess); + MmFreePool(call); + return NTLSTATUS_PORT_DISCONNECTED; + } + KeListInsertTailChecked(&call->Connection->Calls, &call->Link); + KeReleaseSpinlock(&call->Connection->Lock, flags); + + *callOut = call; + return NTLSTATUS_SUCCESS; +} + +// Releases one synchronous-call reference and frees its retained resources after the final owner leaves. +static void KiPortReleaseCall(PORT_CALL* call) { + if (!call) + return; + long refs = _InterlockedDecrement(&call->RefCount); + if (refs > 0) + return; + if (refs < 0) { + KePanic(KE_PANIC_CODE_GENERIC, + "port: call ref underflow message=%u refs=%ld\n", + call->MessageId, + refs); + } + if (call->LockedReplyPayload) + MmUnlockUserBuffer(call->LockedReplyPayload); + if (call->CompletionEvent) + ObDereferenceObject(&call->CompletionEvent->Header); + if (call->CallerProcess) + KiPortReleaseCallCharge(call->CallerProcess); + MmFreePool(call); +} + +// Performs one synchronous Port request and waits for exactly one matching reply. +KSTATUS KePortCall(PORT* endpoint, + PROCESS* callerProcess, + THREAD* callerThread, + const void* requestPayload, + uint32_t requestLength, + const SYS_PORT_HANDLE_TRANSFER* requestTransfers, + uint32_t requestTransferCount, + void* replyPayload, + uint32_t replyCapacity, + SYS_HANDLE* replyHandlesOut, + uint32_t replyHandleCapacity, + SYS_PORT_MESSAGE_INFO* replyInfoOut, + uint64_t timeoutMilliseconds) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !callerProcess || !callerThread) + return NTLSTATUS_INVALID_PARAMETER; + if (timeoutMilliseconds == 0) + return NTLSTATUS_WOULD_BLOCK; + if (requestLength > SYS_PORT_MAX_INLINE_BYTES || replyCapacity > SYS_PORT_MAX_INLINE_BYTES || + requestTransferCount > SYS_PORT_MAX_TRANSFER_HANDLES || + replyHandleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES || + (requestLength && !requestPayload) || (replyCapacity && !replyPayload) || + (replyHandleCapacity && !replyHandlesOut)) + return NTLSTATUS_INVALID_PARAMETER; + + uint64_t deadline = 0; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) + deadline = KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds); + + PORT_CALL* call = (PORT_CALL*)0; + KSTATUS status = KiPortCreateCall(endpoint, + callerProcess, + replyPayload, + replyCapacity, + replyHandleCapacity, + &call); + if (!KSUCCESS(status)) + return status; + + PORT_MESSAGE* message = (PORT_MESSAGE*)0; + status = KiPortCreateMessage(callerProcess, + callerThread, + SYS_PORT_MESSAGE_REQUEST, + call->MessageId, + requestLength, + requestTransfers, + requestTransferCount, + call, + &message); + if (!KSUCCESS(status)) { + uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock); + if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link) + KeListRemoveChecked(&call->Link); + KeReleaseSpinlock(&call->Connection->Lock, flags); + KiPortReleaseCall(call); + return status; + } + + uint64_t submitTimeout = timeoutMilliseconds; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) + submitTimeout = KeMonotonicMillisecondsUntil(deadline); + + status = KiPortSubmitMessage(endpoint, + callerProcess, + requestPayload, + requestLength, + message, + submitTimeout); + if (!KSUCCESS(status)) { + uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock); + if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link) + KeListRemoveChecked(&call->Link); + KeReleaseSpinlock(&call->Connection->Lock, flags); + KiPortReleaseCall(call); + return status; + } + + uint64_t replyWait = timeoutMilliseconds; + if (timeoutMilliseconds != 0 && timeoutMilliseconds != SYS_PORT_TIMEOUT_INFINITE) + replyWait = KeMonotonicMillisecondsUntil(deadline); + + EVENT* events[1] = { call->CompletionEvent }; + KSTATUS waitStatus = KeWaitForMultipleEvents(events, + 1, + 0, + replyWait, + (uint32_t*)0); + if (!KSUCCESS(waitStatus)) { + PORT_MESSAGE* queuedMessage = (PORT_MESSAGE*)0; + uintptr_t flags = KeAcquireSpinlock(&call->Connection->Lock); + long state = _InterlockedCompareExchange(&call->State, 0, 0); + if (state == PORT_CALL_REPLYING) { + KeReleaseSpinlock(&call->Connection->Lock, flags); + (void)KeWaitForMultipleEvents(events, 1, 0, SYS_PORT_TIMEOUT_INFINITE, (uint32_t*)0); + } else if (state == PORT_CALL_COMPLETED) { + KeReleaseSpinlock(&call->Connection->Lock, flags); + } else { + if (call->Link.Fwd != &call->Link && call->Link.Bwd != &call->Link) + KeListRemoveChecked(&call->Link); + _InterlockedExchange(&call->State, PORT_CALL_CANCELLED); + queuedMessage = KiPortRemoveQueuedCallLocked(call->Connection, call); + KeReleaseSpinlock(&call->Connection->Lock, flags); + if (queuedMessage) + KiPortFreeMessage(queuedMessage); + KiPortReleaseCall(call); + return waitStatus; + } + } + + status = call->Status; + if (KSUCCESS(status)) { + if (replyInfoOut) + *replyInfoOut = call->ReplyInfo; + if (call->ReplyHandleCount && replyHandlesOut) + memcpy(replyHandlesOut, + call->ReplyHandles, + sizeof(SYS_HANDLE) * call->ReplyHandleCount); + } else if (call->ReplyHandleCount) { + KiPortCloseHandles(callerProcess, call->ReplyHandles, call->ReplyHandleCount); + call->ReplyHandleCount = 0; + } + + KiPortReleaseCall(call); + return status; +} + +// Replies to one delivered synchronous request and directly copies the reply into the blocked caller buffer. +KSTATUS KePortReply(PORT* endpoint, + PROCESS* senderProcess, + THREAD* senderThread, + uint32_t messageId, + const void* payload, + uint32_t payloadLength, + const SYS_PORT_HANDLE_TRANSFER* transfers, + uint32_t transferCount) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !senderProcess || !senderThread || messageId == 0) + return NTLSTATUS_INVALID_PARAMETER; + if (payloadLength > SYS_PORT_MAX_INLINE_BYTES || transferCount > SYS_PORT_MAX_TRANSFER_HANDLES || + (payloadLength && !payload)) + return NTLSTATUS_INVALID_PARAMETER; + + struct KE_CAPTURED_HANDLE* captured[SYS_PORT_MAX_TRANSFER_HANDLES] = {0}; + KSTATUS status = KiPortCaptureHandles(senderProcess, + transfers, + transferCount, + captured); + if (!KSUCCESS(status)) + return status; + + PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection; + PORT_CALL* call = (PORT_CALL*)0; + uintptr_t flags = KeAcquireSpinlock(&connection->Lock); + LIST_ENTRY* head = &connection->Calls; + for (LIST_ENTRY* link = head->Fwd; link != head; link = link->Fwd) { + PORT_CALL* candidate = CONTAINING_RECORD(link, PORT_CALL, Link); + if (candidate->MessageId == messageId && + candidate->CallerSide != endpoint->u.Endpoint.Side) { + call = candidate; + break; + } + } + + if (!call || _InterlockedCompareExchange(&call->State, 0, 0) != PORT_CALL_DELIVERED) { + KeReleaseSpinlock(&connection->Lock, flags); + for (uint32_t i = 0; i < transferCount; ++i) + KeReleaseCapturedHandle(captured[i]); + return NTLSTATUS_NOT_FOUND; + } + if (payloadLength > call->ReplyCapacity || transferCount > call->ReplyHandleCapacity) { + KeReleaseSpinlock(&connection->Lock, flags); + for (uint32_t i = 0; i < transferCount; ++i) + KeReleaseCapturedHandle(captured[i]); + return NTLSTATUS_BUFFER_TOO_SMALL; + } + + _InterlockedExchange(&call->State, PORT_CALL_REPLYING); + KeListRemoveChecked(&call->Link); + KeReleaseSpinlock(&connection->Lock, flags); + + SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0}; + if (transferCount) + status = KeMaterializeCapturedHandles(call->CallerProcess, captured, transferCount, handles); + if (KSUCCESS(status) && payloadLength) { + status = MmCopyUserToUser(senderProcess, + payload, + call->CallerProcess, + call->ReplyPayload, + payloadLength); + } + + if (!KSUCCESS(status)) { + KiPortCloseHandles(call->CallerProcess, handles, transferCount); + for (uint32_t i = 0; i < transferCount; ++i) + if (captured[i]) KeReleaseCapturedHandle(captured[i]); + call->ReplyHandleCount = 0; + } else { + memcpy(call->ReplyHandles, handles, sizeof(SYS_HANDLE) * transferCount); + call->ReplyHandleCount = transferCount; + call->ReplyInfo.Size = sizeof(call->ReplyInfo); + call->ReplyInfo.MessageId = messageId; + call->ReplyInfo.Type = SYS_PORT_MESSAGE_REPLY; + call->ReplyInfo.PayloadLength = payloadLength; + call->ReplyInfo.HandleCount = transferCount; + call->ReplyInfo.SenderProcessId = senderProcess->ProcessId; + call->ReplyInfo.SenderThreadId = senderThread->ThreadId; + call->ReplyInfo.SenderSecurityId = KiPortSecurityId(senderProcess); + } + + if (KSUCCESS(status)) + _InterlockedIncrement64(&gPortCounters.RepliesCompleted); + call->Status = status; + _InterlockedExchange(&call->State, PORT_CALL_COMPLETED); + KeSignalEvent(call->CompletionEvent); + return status; +} + +// Explicitly disconnects one endpoint capability without requiring its last handle to close. +KSTATUS KePortDisconnect(PORT* endpoint) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT) + return NTLSTATUS_INVALID_PARAMETER; + KiPortMarkDisconnected(endpoint, false); + return NTLSTATUS_SUCCESS; +} diff --git a/code/kernel/ke_process.c b/code/kernel/ke_process.c index 5670a48..613517b 100644 --- a/code/kernel/ke_process.c +++ b/code/kernel/ke_process.c @@ -1,6 +1,8 @@ -// Kernel process model. This file owns address-space metadata, handle tables, and process lifetime transitions. +// Kernel process model. This file owns process lifetime, thread membership, and address-space metadata. #include +#include +#include #include #include #include @@ -8,21 +10,19 @@ #include #include #include -#include #include #include #include #include -#include #include -#include -#include +#include +#include #include // Global process list. static LIST_ENTRY gProcessList; static SPINLOCK gProcessListLock; -static uint32_t gNextProcessId = 0; +static KE_ID_ALLOCATOR gProcessIds = KE_ID_ALLOCATOR_INIT(1u); static PROCESS* gSystemProcess = (PROCESS*)0; // Processes init. @@ -64,7 +64,7 @@ KSTATUS KeCreateSystemProcess(PROCESS** out) { } PROCESS* proc = (PROCESS*)0; - KSTATUS s = KeCreateProcess("system", &proc); + KSTATUS s = KeCreateProcess("system", SeGetRootSecurityContext(), &proc); if (!KSUCCESS(s)) { *out = (PROCESS*)0; return s; @@ -76,17 +76,6 @@ KSTATUS KeCreateSystemProcess(PROCESS** out) { return NTLSTATUS_SUCCESS; } -// Implements the count Process Handles Locked operation. -static uint32_t KiCountProcessHandlesLocked(PROCESS* process) { - uint32_t count = 0; - if (!process) return 0; - - for (uint32_t i = 1; i < HANDLE_TABLE_SIZE; i++) { - if (process->Handles[i].Object) count++; - } - return count; -} - // Copies truncated String. static void KiCopyTruncatedString(char* dst, size_t dstSize, const char* src, size_t srcLen) { if (!dst || dstSize == 0) return; @@ -147,20 +136,17 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { memset(info, 0, sizeof(*info)); info->Size = sizeof(*info); info->ProcessId = p->ProcessId; + info->SecurityId = p->SecurityContext ? p->SecurityContext->SecurityId : 0u; 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); + uintptr_t threadFlags = KeAcquireSpinlock(&p->ThreadLock); uint32_t threadCount = p->ThreadCount; - uint32_t handleCount = KiCountProcessHandlesLocked(p); + KeReleaseSpinlock(&p->ThreadLock, threadFlags); + uint32_t handleCount = KeGetHandleCount(p); int32_t exitCode = p->ExitCode; long terminating = p->Terminating; long exited = p->Exited; - KeReleaseSpinlock(&p->HandleLock, handleFlags); info->ThreadCount = threadCount; info->HandleCount = handleCount; @@ -184,6 +170,9 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { } else if (terminating) { info->State = SYS_PROCESS_STATE_TERMINATING; info->Flags |= SYS_PROCESS_FLAG_TERMINATING; + } else if (_InterlockedCompareExchange(&p->BuildState, 0, 0) != KE_PROCESS_BUILD_STARTED) { + info->State = SYS_PROCESS_STATE_CONSTRUCTING; + info->Flags |= SYS_PROCESS_FLAG_CONSTRUCTING; } else { info->State = SYS_PROCESS_STATE_RUNNING; } @@ -200,11 +189,6 @@ size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { KiCopyTruncatedString(info->Name, sizeof(info->Name), p->Name, sizeof(p->Name)); - uintptr_t cwdFlags = KeAcquireSpinlock(&p->CwdLock); - info->CurrentDrive = p->CurrentDrive; - KiCopyTruncatedString(info->CurrentDirectory, sizeof(info->CurrentDirectory), - p->CurrentDirectory, p->CurrentDirectoryLength); - KeReleaseSpinlock(&p->CwdLock, cwdFlags); } count++; } @@ -260,7 +244,7 @@ KSTATUS KeCopyProcessThreadInfo(uint32_t pid, struct SYS_THREAD_INFO* infoOut, s THREAD* current = KeGetCurrentThread(); size_t count = 0; - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock); LIST_ENTRY* head = &process->ThreadList; for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { THREAD* thread = CONTAINING_RECORD(node, THREAD, ProcessLink); @@ -300,55 +284,13 @@ KSTATUS KeCopyProcessThreadInfo(uint32_t pid, struct SYS_THREAD_INFO* infoOut, s } count++; } - KeReleaseSpinlock(&process->HandleLock, flags); + KeReleaseSpinlock(&process->ThreadLock, flags); *countOut = count; ObDereferenceObject(&process->Header); return NTLSTATUS_SUCCESS; } -// Records type-specific ownership after a handle slot becomes visible. -static void KiProcessHandleOpened(OBJECT* object) { - if (!object) - return; - - if (object->Type == ObjectTypeAudioStream) - KeAudioStreamHandleOpened((KE_AUDIO_STREAM*)object); -} - -// Releases type-specific ownership before the handle-table reference drops. -static void KiProcessReleaseHandleObject(OBJECT* object) { - if (!object) - return; - - if (object->Type == ObjectTypeAudioStream) - KeAudioStreamHandleClosed((KE_AUDIO_STREAM*)object); - ObDereferenceObject(object); -} - -// Releases every handle-table reference owned by one process. -static void KiProcessReleaseAllHandles(PROCESS* proc) { - if (!proc) return; - - for (uint32_t i = 1; i < HANDLE_TABLE_SIZE; ++i) { - OBJECT* obj = (OBJECT*)0; - - uintptr_t flags = KeAcquireSpinlock(&proc->HandleLock); - obj = proc->Handles[i].Object; - if (obj) { - proc->Handles[i].Object = (OBJECT*)0; - proc->Handles[i].Rights = 0; - proc->Handles[i].Flags = 0; - } - KeReleaseSpinlock(&proc->HandleLock, flags); - - if (obj) { - KeNamedQueueUnpublishHandle(proc, (uint32_t)i, obj); - KiProcessReleaseHandleObject(obj); - } - } -} - // Releases the process-owned address space and per-process kernel resources. // Idempotent because the normal exit path and the final OBJECT destructor can // both observe a process whose threads are already gone. @@ -358,7 +300,7 @@ static void KiProcessRundownResources(PROCESS* proc) { return; IoReleaseFileLocksForProcess(proc); - KiProcessReleaseAllHandles(proc); + KeRundownHandleTable(proc); // Release every user VA region owned by this process. This includes the // main image, DLLs, TEBs/stacks that survived thread teardown, process @@ -366,17 +308,14 @@ 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; proc->UserImagePathSize = 0; proc->UserCmdlineBase = 0; proc->UserCmdlineSize = 0; - proc->UserCwdBase = 0; - proc->UserCwdSize = 0; + proc->UserStartupCapabilitiesBase = 0; + proc->UserStartupCapabilitiesSize = 0; uintptr_t exceptionFlags = KeAcquireSpinlock(&proc->ExceptionLock); proc->ExceptionDispatcher = 0; proc->ExceptionDispatcherContext = 0; @@ -397,11 +336,29 @@ static void KiProcessDestroy(OBJECT* obj) { // ran, make sure its own handles and VADs are still reclaimed. KiProcessRundownResources(proc); + if (proc->PortOutstandingMessages != 0 || + proc->PortOutstandingBytes != 0 || + proc->PortOutstandingHandles != 0 || + proc->PortOutstandingCalls != 0) { + KePanic(KE_PANIC_CODE_GENERIC, + "process: destroying with live Port quota pid=%u messages=%u bytes=%u handles=%u calls=%u\n", + proc->ProcessId, + proc->PortOutstandingMessages, + proc->PortOutstandingBytes, + proc->PortOutstandingHandles, + proc->PortOutstandingCalls); + } + // Release the exit event only when the process object itself dies. External // process handles may still wait/query an exited process after rundown. if (proc->ExitEvent) { ObDereferenceObject(&proc->ExitEvent->Header); - proc->ExitEvent = (EVENT *)0; + proc->ExitEvent = (EVENT*)0; + } + + if (proc->SecurityContext) { + ObDereferenceObject(&proc->SecurityContext->Header); + proc->SecurityContext = (SECURITY_CONTEXT*)0; } // Remove from global list. @@ -414,28 +371,36 @@ static void KiProcessDestroy(OBJECT* obj) { } // KeCreateProcess. -KSTATUS KeCreateProcess(const char* name, PROCESS **out) { +KSTATUS KeCreateProcess(const char* name, SECURITY_CONTEXT* securityContext, PROCESS** out) { KiProcessInit(); - if (!name || !out) return NTLSTATUS_INVALID_PARAMETER; + if (!name || !securityContext || !out) return NTLSTATUS_INVALID_PARAMETER; - PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS)); + uint32_t processId = 0; + KSTATUS idStatus = KeAllocateId32(&gProcessIds, &processId); + if (!KSUCCESS(idStatus)) + return idStatus; + + PROCESS* proc = (PROCESS*)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS)); if (!proc) return NTLSTATUS_NO_MEMORY; proc->Header.Destroy = KiProcessDestroy; - - proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1); - proc->Flags = 0; + proc->ProcessId = processId; + proc->Flags = 0; + proc->SecurityContext = securityContext; + ObReferenceObject(&securityContext->Header); strncpy(proc->Name, name, PROCESS_NAME_MAX - 1); proc->Name[PROCESS_NAME_MAX - 1] = '\0'; LIST_INIT(&proc->ThreadList); proc->ThreadCount = 0; - proc->HandleLock = (SPINLOCK)SPINLOCK_INIT; + proc->ThreadLock = (SPINLOCK)SPINLOCK_INIT; proc->ExitCode = 0; proc->Terminating = 0; proc->Exited = 0; proc->ResourcesReleased = 0; + proc->BuildState = KE_PROCESS_BUILD_STARTED; + proc->BuildOperations = 0; proc->ImageBase = 0; proc->ImageSize = 0; proc->UserPebBase = 0; @@ -444,10 +409,8 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { proc->UserImagePathSize = 0; proc->UserCmdlineBase = 0; proc->UserCmdlineSize = 0; - proc->UserCwdBase = 0; - proc->UserCwdSize = 0; - proc->ModuleCount = 0; - proc->ModuleLock = (SPINLOCK)SPINLOCK_INIT; + proc->UserStartupCapabilitiesBase = 0; + proc->UserStartupCapabilitiesSize = 0; proc->ExceptionDispatcher = 0; proc->ExceptionDispatcherContext = 0; proc->ExceptionStackBase = 0; @@ -463,15 +426,15 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { proc->LastExceptionAddress = 0; proc->LastExceptionFaultAddress = 0; proc->ExceptionLock = (SPINLOCK)SPINLOCK_INIT; - proc->CurrentDirectory[0] = '\0'; - proc->CurrentDirectoryLength = 0; - proc->CurrentDrive = KeGetSystemRootDrive(); - proc->CwdLock = (SPINLOCK)SPINLOCK_INIT; LIST_INIT(&proc->VadList); proc->VadLock = (SPINLOCK)SPINLOCK_INIT; - // Slot 0 is the invalid handle - leave it NULL. - memset(proc->Handles, 0, sizeof(proc->Handles)); + KeInitializeHandleTable(&proc->HandleTable); + proc->PortQuotaLock = (SPINLOCK)SPINLOCK_INIT; + proc->PortOutstandingMessages = 0; + proc->PortOutstandingBytes = 0; + proc->PortOutstandingHandles = 0; + proc->PortOutstandingCalls = 0; LIST_INIT(&proc->GlobalLink); @@ -514,32 +477,7 @@ static KSTATUS KiCopyEnvironmentStringView(PROCESS* proc, return NTLSTATUS_SUCCESS; } -static KSTATUS KiCopyEnvironmentStringViewFixed(PROCESS* proc, - SYS_STRING_VIEW src, - size_t capacity, - uintptr_t* baseOut, - size_t* sizeOut) { - if (!baseOut || !sizeOut || capacity == 0) return NTLSTATUS_INVALID_PARAMETER; - *baseOut = 0; - *sizeOut = 0; - - if (!SysSvIsValid(src)) return NTLSTATUS_INVALID_PARAMETER; - if (SysSvContainsByte(src, '\0')) return NTLSTATUS_INVALID_PARAMETER; - if (src.Length + 1 > capacity) return NTLSTATUS_BUFFER_TOO_SMALL; - - char* dst = (char*)MmAllocateUserMemoryForProcess(proc, capacity); - if (!dst) return NTLSTATUS_NO_MEMORY; - memset(dst, 0, capacity); - - if (src.Length) memcpy(dst, src.Data, src.Length); - dst[src.Length] = '\0'; - - *baseOut = (uintptr_t)dst; - *sizeOut = capacity; - return NTLSTATUS_SUCCESS; -} - -// Releases partial Process Environment. +// Releases a partially constructed process environment. static void KiFreePartialProcessEnvironment(PROCESS* proc) { if (!proc) return; @@ -558,78 +496,31 @@ static void KiFreePartialProcessEnvironment(PROCESS* proc) { proc->UserCmdlineBase = 0; proc->UserCmdlineSize = 0; } - if (proc->UserCwdBase && proc->UserCwdSize) { - MmFreeUserMemoryForProcess(proc, (void*)proc->UserCwdBase); - proc->UserCwdBase = 0; - proc->UserCwdSize = 0; + if (proc->UserStartupCapabilitiesBase && proc->UserStartupCapabilitiesSize) { + MmFreeUserMemoryForProcess(proc, (void*)proc->UserStartupCapabilitiesBase); + proc->UserStartupCapabilitiesBase = 0; + proc->UserStartupCapabilitiesSize = 0; } } -static KSTATUS KiFormatNativeCwdV(const PROCESS* proc, - SYS_STRING_VIEW currentDirectory, - char* out, - size_t outSize, - size_t* lengthOut) { - if (lengthOut) *lengthOut = 0; - if (!out || outSize == 0) return NTLSTATUS_INVALID_PARAMETER; - out[0] = '\0'; - - if (!SysSvIsValid(currentDirectory)) return NTLSTATUS_INVALID_PARAMETER; - if (SysSvContainsByte(currentDirectory, '\0')) return NTLSTATUS_INVALID_PARAMETER; - - uint8_t drive = proc ? proc->CurrentDrive : 0; - if (drive > 7) drive = 0; - - if (currentDirectory.Length + 4 > outSize) return NTLSTATUS_BUFFER_TOO_SMALL; - - out[0] = (char)('0' + drive); - out[1] = ':'; - out[2] = '/'; - if (currentDirectory.Length) - memcpy(out + 3, currentDirectory.Data, currentDirectory.Length); - out[3 + currentDirectory.Length] = '\0'; - - if (lengthOut) *lengthOut = 3 + currentDirectory.Length; - return NTLSTATUS_SUCCESS; -} - KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc, SYS_STRING_VIEW imagePath, - SYS_STRING_VIEW commandLine, - SYS_STRING_VIEW currentDirectory) { + SYS_STRING_VIEW commandLine) { if (!proc || imagePath.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - if (!SysSvIsValid(imagePath) || !SysSvIsValid(commandLine) || !SysSvIsValid(currentDirectory)) + if (!SysSvIsValid(imagePath) || !SysSvIsValid(commandLine)) return NTLSTATUS_INVALID_PARAMETER; - if (SysSvContainsByte(imagePath, '\0') || - SysSvContainsByte(commandLine, '\0') || - SysSvContainsByte(currentDirectory, '\0')) + if (SysSvContainsByte(imagePath, '\0') || SysSvContainsByte(commandLine, '\0')) return NTLSTATUS_INVALID_PARAMETER; SYS_STRING_VIEW finalCommandLine = commandLine.Length ? commandLine : imagePath; - SYS_STRING_VIEW finalCwdRel = currentDirectory; - if (finalCwdRel.Length == 0 && proc->CurrentDirectoryLength) - finalCwdRel = SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength); - - char finalCwd[SYS_CWD_MAX]; - size_t finalCwdLength = 0; - KSTATUS s = KiFormatNativeCwdV(proc, finalCwdRel, finalCwd, sizeof(finalCwd), &finalCwdLength); - if (!KSUCCESS(s)) return s; - KiFreePartialProcessEnvironment(proc); - s = KiCopyEnvironmentStringView(proc, imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize); + KSTATUS s = KiCopyEnvironmentStringView(proc, imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize); if (!KSUCCESS(s)) goto fail; s = KiCopyEnvironmentStringView(proc, finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize); if (!KSUCCESS(s)) goto fail; - s = KiCopyEnvironmentStringViewFixed(proc, - SysSvFromParts(finalCwd, finalCwdLength), - SYS_CWD_MAX, - &proc->UserCwdBase, - &proc->UserCwdSize); - if (!KSUCCESS(s)) goto fail; - SYS_PEB* peb = (SYS_PEB*)MmAllocateUserMemoryForProcess(proc, sizeof(SYS_PEB)); if (!peb) { s = NTLSTATUS_NO_MEMORY; goto fail; } memset(peb, 0, sizeof(SYS_PEB)); @@ -639,7 +530,10 @@ KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc, peb->ProcessId = proc->ProcessId; peb->ImagePath = SysSvFromParts((const char*)proc->UserImagePathBase, imagePath.Length); peb->CommandLine = SysSvFromParts((const char*)proc->UserCmdlineBase, finalCommandLine.Length); - peb->CurrentDirectory = SysSvFromParts((const char*)proc->UserCwdBase, finalCwdLength); + peb->StartupCapabilities = (const SYS_STARTUP_CAPABILITY*)0; + peb->StartupCapabilityCount = 0; + peb->Reserved = 0; + peb->LoaderData = (const void*)0; proc->UserPebBase = (uintptr_t)peb; proc->UserPebSize = sizeof(SYS_PEB); @@ -650,13 +544,65 @@ fail: return s; } +// Copies the immutable startup-capability descriptor table into child-owned user memory. +KSTATUS KeSetProcessStartupCapabilities(PROCESS* proc, + const SYS_STARTUP_CAPABILITY* capabilities, + uint32_t count) { + if (!proc || !proc->UserPebBase) return NTLSTATUS_INVALID_PARAMETER; + if (count > SYS_STARTUP_CAPABILITY_MAX) return NTLSTATUS_LIMIT_REACHED; + if (count && !capabilities) return NTLSTATUS_INVALID_PARAMETER; + + if (proc->UserStartupCapabilitiesBase && proc->UserStartupCapabilitiesSize) { + MmFreeUserMemoryForProcess(proc, (void*)proc->UserStartupCapabilitiesBase); + proc->UserStartupCapabilitiesBase = 0; + proc->UserStartupCapabilitiesSize = 0; + } + + SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase; + peb->StartupCapabilities = (const SYS_STARTUP_CAPABILITY*)0; + peb->StartupCapabilityCount = 0; + + if (!count) return NTLSTATUS_SUCCESS; + size_t bytes = (size_t)count * sizeof(SYS_STARTUP_CAPABILITY); + SYS_STARTUP_CAPABILITY* dst = (SYS_STARTUP_CAPABILITY*)MmAllocateUserMemoryForProcess(proc, bytes); + if (!dst) return NTLSTATUS_NO_MEMORY; + memcpy(dst, capabilities, bytes); + + KSTATUS protectStatus = MmVadProtect(proc, dst, bytes, PAGE_PRESENT | PAGE_USER | PAGE_NX); + if (!KSUCCESS(protectStatus)) { + MmFreeUserMemoryForProcess(proc, dst); + return protectStatus; + } + + proc->UserStartupCapabilitiesBase = (uintptr_t)dst; + proc->UserStartupCapabilitiesSize = bytes; + peb->StartupCapabilities = dst; + peb->StartupCapabilityCount = count; + return NTLSTATUS_SUCCESS; +} + +// Installs the opaque userspace loader-data pointer in the child PEB. +KSTATUS KeSetProcessLoaderData(PROCESS* proc, const void* loaderData) { + if (!proc || !proc->UserPebBase) return NTLSTATUS_INVALID_PARAMETER; + SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase; + peb->LoaderData = loaderData; + return NTLSTATUS_SUCCESS; +} + +// Stops new process-construction operations. Active builders defer final teardown until they leave. +void KeStopProcessConstruction(PROCESS* process) { + if (!process) return; + _InterlockedExchange(&process->BuildState, KE_PROCESS_BUILD_DEAD); +} + // Process exit / termination. void KeCompleteProcessExitIfLastThread(PROCESS* process) { if (!process) return; + if (_InterlockedCompareExchange(&process->BuildOperations, 0, 0) != 0) return; - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock); uint32_t threadCount = process->ThreadCount; - KeReleaseSpinlock(&process->HandleLock, flags); + KeReleaseSpinlock(&process->ThreadLock, flags); if (threadCount == 0 && _InterlockedExchange(&process->Exited, 1) == 0) { KiProcessRundownResources(process); @@ -664,30 +610,24 @@ void KeCompleteProcessExitIfLastThread(PROCESS* process) { } } -// Implements the exit Process operation. +// Exits one process by terminating every thread in it. +// SysExitThread remains the operation for ending only the calling thread. KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode) { - if (!process) return NTLSTATUS_INVALID_PARAMETER; - if (KeIsProcessTerminationProtected(process)) return NTLSTATUS_ACCESS_DENIED; - - process->ExitCode = exitCode; - _InterlockedExchange(&process->Terminating, 1); - - // The calling thread exits immediately after this in SysExitProcess. Do - // not drop the process object here: each thread owns one process reference, - // and KiThreadDestroy releases that reference after unlinking the thread. - KeCompleteProcessExitIfLastThread(process); - return NTLSTATUS_SUCCESS; + return KeTerminateProcess(process, exitCode); } -// Implements the terminate Process operation. +// Begins termination of one process and wakes every sibling thread. KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode) { if (!process) return NTLSTATUS_INVALID_PARAMETER; if (KeIsProcessTerminationProtected(process)) return NTLSTATUS_ACCESS_DENIED; process->ExitCode = exitCode; _InterlockedExchange(&process->Terminating, 1); + KeStopProcessConstruction(process); + if (_InterlockedCompareExchange(&process->BuildOperations, 0, 0) != 0) + return NTLSTATUS_SUCCESS; - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock); LIST_ENTRY* head = &process->ThreadList; THREAD* self = KeGetCurrentThread(); KCPU* selfCpu = KeGetCurrentCpu(); @@ -714,11 +654,11 @@ KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode) { KCPU* cpu = &gCpus[t->RunningCpu]; if (cpu->State == KeCpuOnline) { _InterlockedExchange(&cpu->ReschedulePending, 1); - HalApicSendRescheduleIpi(cpu->LapicId); + KeSendProcessorInterrupt(cpu->HardwareId, KE_VECTOR_RESCHEDULE_IPI); } } } - KeReleaseSpinlock(&process->HandleLock, flags); + KeReleaseSpinlock(&process->ThreadLock, flags); KeCompleteProcessExitIfLastThread(process); return NTLSTATUS_SUCCESS; @@ -741,140 +681,6 @@ KSTATUS KeGetProcessExitCode(PROCESS* process, int32_t* exitCodeOut) { return NTLSTATUS_SUCCESS; } -// Handle table. -KSTATUS KeAllocateHandleWithRights(PROCESS* process, OBJECT* object, uint32_t rights, uint32_t* handleOut) { - if (!process || !object || !handleOut || rights == 0) return NTLSTATUS_INVALID_PARAMETER; - - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - - for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { - if (!process->Handles[i].Object) { - if (!ObTryReferenceObject(object)) { - KeReleaseSpinlock(&process->HandleLock, flags); - return NTLSTATUS_OBJECT_DEAD; - } - process->Handles[i].Object = object; - process->Handles[i].Rights = rights; - process->Handles[i].Flags = 0; - KiProcessHandleOpened(object); - *handleOut = (uint32_t)i; - KeReleaseSpinlock(&process->HandleLock, flags); - return NTLSTATUS_SUCCESS; - } - } - - KeReleaseSpinlock(&process->HandleLock, flags); - return NTLSTATUS_NO_MEMORY; -} - -// Creates handle. -KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) { - return KeAllocateHandleWithRights(process, object, HANDLE_RIGHT_ALL, handleOut); -} - -// Releases handle. -void KeCloseHandle(PROCESS* process, uint32_t handle) { - if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return; - - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - OBJECT* obj = process->Handles[handle].Object; - if (obj) { - process->Handles[handle].Object = (OBJECT*)0; - process->Handles[handle].Rights = 0; - process->Handles[handle].Flags = 0; - KeReleaseSpinlock(&process->HandleLock, flags); - if (obj->Type == ObjectTypeFile) - (void)IoFlushFile((FILE_OBJECT*)obj); - KeNamedQueueUnpublishHandle(process, handle, obj); - KiProcessReleaseHandleObject(obj); - return; - } - KeReleaseSpinlock(&process->HandleLock, flags); -} - -// Returns handle With Rights. -OBJECT* KeLookupHandleWithRights(PROCESS* process, uint32_t handle, uint32_t requiredRights) { - if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) - return (OBJECT*)0; - - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - HANDLE_ENTRY* entry = &process->Handles[handle]; - OBJECT* obj = entry->Object; - if (obj && requiredRights && ((entry->Rights & requiredRights) != requiredRights)) - obj = (OBJECT*)0; - if (obj && !ObTryReferenceObject(obj)) - obj = (OBJECT*)0; - KeReleaseSpinlock(&process->HandleLock, flags); - return obj; -} - -// Returns handle. -OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) { - return KeLookupHandleWithRights(process, handle, 0); -} - -// Returns handle Rights. -uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle) { - if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return 0; - - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - uint32_t rights = process->Handles[handle].Object ? process->Handles[handle].Rights : 0; - KeReleaseSpinlock(&process->HandleLock, flags); - return rights; -} - -// Duplicates a handle from one process table into another. -KSTATUS KeDuplicateHandleToProcess(PROCESS* sourceProcess, uint32_t sourceHandle, PROCESS* targetProcess, uint32_t desiredRights, uint32_t duplicateFlags, uint32_t* targetHandleOut) { - if (!sourceProcess || !targetProcess || !targetHandleOut) return NTLSTATUS_INVALID_PARAMETER; - if (sourceHandle == 0 || sourceHandle >= HANDLE_TABLE_SIZE) return NTLSTATUS_INVALID_HANDLE; - - const uint32_t validDuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS; - const uint32_t publicHandleRights = SYS_HANDLE_RIGHT_ALL; - - if ((duplicateFlags & ~validDuplicateFlags) != 0) return NTLSTATUS_INVALID_PARAMETER; - if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) != 0 && desiredRights != 0) - return NTLSTATUS_INVALID_PARAMETER; - if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) == 0) { - if (desiredRights == 0) return NTLSTATUS_INVALID_PARAMETER; - if ((desiredRights & ~publicHandleRights) != 0) return NTLSTATUS_INVALID_PARAMETER; - } - - OBJECT* obj = NULL; - uint32_t sourceRights = 0; - - uintptr_t savedFlags = KeAcquireSpinlock(&sourceProcess->HandleLock); - HANDLE_ENTRY* entry = &sourceProcess->Handles[sourceHandle]; - if (!entry->Object) { - KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags); - return NTLSTATUS_INVALID_HANDLE; - } - if ((entry->Rights & HANDLE_RIGHT_DUPLICATE) == 0) { - KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags); - return NTLSTATUS_ACCESS_DENIED; - } - - obj = entry->Object; - sourceRights = entry->Rights; - if (!ObTryReferenceObject(obj)) { - KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags); - return NTLSTATUS_OBJECT_DEAD; - } - KeReleaseSpinlock(&sourceProcess->HandleLock, savedFlags); - - uint32_t targetRights = sourceRights; - if ((duplicateFlags & SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS) == 0) { - if ((desiredRights & ~sourceRights) != 0) { - ObDereferenceObject(obj); - return NTLSTATUS_ACCESS_DENIED; - } - targetRights = desiredRights; - } - - KSTATUS s = KeAllocateHandleWithRights(targetProcess, obj, targetRights, targetHandleOut); - ObDereferenceObject(obj); - return s; -} - // Returns process By Id. PROCESS* KeGetProcessById(uint32_t pid) { KiProcessInit(); diff --git a/code/kernel/ke_queue.c b/code/kernel/ke_queue.c index b11601d..9c596f2 100644 --- a/code/kernel/ke_queue.c +++ b/code/kernel/ke_queue.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -46,49 +47,24 @@ static void KiQueueNoteRemovedLocked(QUEUE* queue, uint32_t previousCount) { KeClearEvent(queue->NotEmptyEvent); } -// Returns the scheduler tick value used by queue finite-timeout accounting. -static uint64_t KiQueueReadSchedulerTick(void) { - KE_SCHED_STATS stats; - KeGetSchedulerStats(&stats); - return stats.Ticks; -} - -// Converts a millisecond timeout to at least one scheduler tick. -static uint64_t KiQueueTimeoutTicks(uint64_t timeoutMilliseconds) { - uint64_t ticks = timeoutMilliseconds / SCHED_TICK_MS; - if ((timeoutMilliseconds % SCHED_TICK_MS) != 0) - ticks++; - return ticks ? ticks : 1; -} - -// Computes an absolute scheduler-tick deadline for a finite timeout. +// Computes an absolute monotonic deadline for a finite timeout. static uint64_t KiQueueTimeoutDeadline(uint64_t timeoutMilliseconds) { - uint64_t now = KiQueueReadSchedulerTick(); - uint64_t ticks = KiQueueTimeoutTicks(timeoutMilliseconds); - uint64_t deadline = now + ticks; - return deadline < now ? UINT64_MAX : deadline; + return KeMonotonicDeadlineAfterMilliseconds(timeoutMilliseconds); } // Computes the remaining wait time for a finite timeout deadline. -static uint64_t KiQueueRemainingMilliseconds(uint64_t deadlineTick) { - uint64_t now = KiQueueReadSchedulerTick(); - if (deadlineTick <= now) - return 0; - - uint64_t ticks = deadlineTick - now; - if (ticks > UINT64_MAX / SCHED_TICK_MS) - return UINT64_MAX; - return ticks * SCHED_TICK_MS; +static uint64_t KiQueueRemainingMilliseconds(uint64_t deadlineMicroseconds) { + return KeMonotonicMillisecondsUntil(deadlineMicroseconds); } // Waits for a queue state event using either an infinite or finite timeout. -static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineTick) { +static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutMilliseconds, uint64_t deadlineMicroseconds) { if (!queue || !event) return NTLSTATUS_INVALID_PARAMETER; uint64_t waitMilliseconds = timeoutMilliseconds; if (timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) { - waitMilliseconds = KiQueueRemainingMilliseconds(deadlineTick); + waitMilliseconds = KiQueueRemainingMilliseconds(deadlineMicroseconds); if (waitMilliseconds == 0) return NTLSTATUS_TIMEOUT; } @@ -98,11 +74,11 @@ static KSTATUS KiQueueWaitForEvent(QUEUE* queue, EVENT* event, uint64_t timeoutM } // Creates a fixed-depth queue with waitable occupancy and capacity events. -KSTATUS KeQueueCreate(QUEUE **out) { +KSTATUS KeQueueCreate(QUEUE** out) { if (!out) return NTLSTATUS_INVALID_PARAMETER; *out = (QUEUE*)0; - QUEUE* q = (QUEUE *)ObAllocateObject(ObjectTypeQueue, sizeof(QUEUE)); + QUEUE* q = (QUEUE*)ObAllocateObject(ObjectTypeQueue, sizeof(QUEUE)); if (!q) return NTLSTATUS_NO_MEMORY; q->Header.Destroy = KiQueueDestroy; q->Lock = (SPINLOCK)SPINLOCK_INIT; @@ -129,9 +105,9 @@ KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMi if (!queue || !msg) return NTLSTATUS_INVALID_PARAMETER; if (timeoutMilliseconds != 0 && !KeCanWait()) return NTLSTATUS_INVALID_IRQL; - uint64_t deadlineTick = 0; + uint64_t deadlineMicroseconds = 0; if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) - deadlineTick = KiQueueTimeoutDeadline(timeoutMilliseconds); + deadlineMicroseconds = KiQueueTimeoutDeadline(timeoutMilliseconds); while (true) { uintptr_t flags = KeAcquireSpinlock(&queue->Lock); @@ -150,7 +126,7 @@ KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, uint64_t timeoutMi if (timeoutMilliseconds == 0) return NTLSTATUS_WOULD_BLOCK; - KSTATUS s = KiQueueWaitForEvent(queue, queue->NotFullEvent, timeoutMilliseconds, deadlineTick); + KSTATUS s = KiQueueWaitForEvent(queue, queue->NotFullEvent, timeoutMilliseconds, deadlineMicroseconds); if (s == NTLSTATUS_TIMEOUT || KFAILED(s)) return s; } @@ -161,9 +137,9 @@ KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilli if (!queue || !msg) return NTLSTATUS_INVALID_PARAMETER; if (timeoutMilliseconds != 0 && !KeCanWait()) return NTLSTATUS_INVALID_IRQL; - uint64_t deadlineTick = 0; + uint64_t deadlineMicroseconds = 0; if (timeoutMilliseconds != 0 && timeoutMilliseconds != QUEUE_TIMEOUT_INFINITE) - deadlineTick = KiQueueTimeoutDeadline(timeoutMilliseconds); + deadlineMicroseconds = KiQueueTimeoutDeadline(timeoutMilliseconds); while (true) { uintptr_t flags = KeAcquireSpinlock(&queue->Lock); @@ -182,7 +158,7 @@ KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, uint64_t timeoutMilli if (timeoutMilliseconds == 0) return NTLSTATUS_WOULD_BLOCK; - KSTATUS s = KiQueueWaitForEvent(queue, queue->NotEmptyEvent, timeoutMilliseconds, deadlineTick); + KSTATUS s = KiQueueWaitForEvent(queue, queue->NotEmptyEvent, timeoutMilliseconds, deadlineMicroseconds); if (s == NTLSTATUS_TIMEOUT || KFAILED(s)) return s; } diff --git a/code/kernel/ke_sched.c b/code/kernel/ke_sched.c index 67afa95..90175a6 100644 --- a/code/kernel/ke_sched.c +++ b/code/kernel/ke_sched.c @@ -3,10 +3,13 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -18,7 +21,6 @@ #include #include #include -#include static LIST_ENTRY gNormalRunQueue[PriorityCount]; static LIST_ENTRY gRealtimeRunQueue[KE_REALTIME_PRIORITY_COUNT]; @@ -129,7 +131,7 @@ int KiSchedulerRefreshMutexDonation(MUTEX* mutex) { // Implements the default Thread Affinity Mask operation. static uint64_t KiDefaultThreadAffinityMask(void) { - // Normal kernel, user, and driver-created threads are BSP-affine by + // Normal kernel, user, and driver-created threads are boot-processor-affine by // default. Code that is explicitly audited for AP execution can widen its // affinity before the thread enters the ready queue. return 1ULL; @@ -200,7 +202,7 @@ static void KiRequestRescheduleCpuLocked(KCPU* target) { KCPU* self = KeGetCurrentCpu(); if (self && target->CpuIndex != self->CpuIndex) { - HalApicSendRescheduleIpi(target->LapicId); + KeSendProcessorInterrupt(target->HardwareId, KE_VECTOR_RESCHEDULE_IPI); } } @@ -707,8 +709,8 @@ static void KiIdleLoop(void) { KCPU* cpu = KeGetCurrentCpu(); cpu->SchedulerTicks++; - // AP idle threads sleep until an interrupt or reschedule IPI wakes the - // CPU. Ready paths set ReschedulePending and target the CPU's LAPIC; + // Secondary-processor idle threads sleep until an interrupt or reschedule IPI wakes + // the CPU. Ready paths set ReschedulePending and target that processor; // KiExitInterrupt consumes the request after EOI. if (KiAnyRunnableForCurrentCpu()) { KeYield(); @@ -719,7 +721,7 @@ static void KiIdleLoop(void) { // the idle thread through an unusual switch path must not sleep with // interrupts disabled, otherwise reschedule/TLB shootdown IPIs can // be delayed forever. - HalWaitForInterrupt(); + KeWaitForInterrupt(); } } @@ -731,7 +733,7 @@ void KiInitializeSchedulerGlobal(void) { KeInitializeWorkItem(&gTimeoutWorkItem, KiTimeoutWorkRoutine, (void*)0); gSchedLock = (SPINLOCK)SPINLOCK_INIT; - KSTATUS s = KeCreateProcess("idle", &gIdleProcess); + KSTATUS s = KeCreateProcess("idle", SeGetRootSecurityContext(), &gIdleProcess); if (!KSUCCESS(s)) KePanic(KE_PANIC_CODE_INIT, "scheduler: failed to create idle process, status=%u\n", s); gIdleProcess->Flags |= KE_PROCESS_FLAG_IDLE | KE_PROCESS_FLAG_TERMINATION_PROTECTED; @@ -750,12 +752,16 @@ KSTATUS KiCreateIdleThreadForCpu(KCPU* cpu) { if (cpu->IdleThread) return NTLSTATUS_SUCCESS; - THREAD* idle = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); + THREAD* idle = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); if (!idle) return NTLSTATUS_NO_MEMORY; - idle->Header.Destroy = (void *)0; - idle->ThreadId = cpu->CpuIndex ? (0x80000000u | cpu->CpuIndex) : 0; + idle->Header.Destroy = (void*)0; + KSTATUS idStatus = KeAllocateThreadId(&idle->ThreadId); + if (!KSUCCESS(idStatus)) { + ObDereferenceObject(&idle->Header); + return idStatus; + } idle->State = ThreadRunning; idle->BaseSchedulingClass = KeSchedulingNormal; idle->BasePriority = PriorityLow; @@ -776,7 +782,7 @@ KSTATUS KiCreateIdleThreadForCpu(KCPU* cpu) { idle->LastCpu = cpu->CpuIndex; idle->AffinityMask = (cpu->CpuIndex < 64) ? (1ULL << cpu->CpuIndex) : 0ULL; idle->Owner = gIdleProcess; - idle->WaitObject = (OBJECT *)0; + idle->WaitObject = (OBJECT*)0; idle->WaitStatus = NTLSTATUS_SUCCESS; idle->WaitListHead = (LIST_ENTRY*)0; idle->AlertableWait = 0; @@ -822,13 +828,13 @@ KSTATUS KiCreateIdleThreadForCpu(KCPU* cpu) { KeInitThreadFpuContext(idle); // The hand-built CPU idle thread is not created through KeCreateThread(), - // so register it manually with the idle process. This keeps PID 0 visible - // as the real idle process while kernel worker threads live under PID 1. + // so register it manually with the dedicated idle process while kernel worker + // threads remain grouped under the system process. ObReferenceObject(&gIdleProcess->Header); - uintptr_t idleFlags = KeAcquireSpinlock(&gIdleProcess->HandleLock); + uintptr_t idleFlags = KeAcquireSpinlock(&gIdleProcess->ThreadLock); KeListInsertTailChecked(&gIdleProcess->ThreadList, &idle->ProcessLink); gIdleProcess->ThreadCount++; - KeReleaseSpinlock(&gIdleProcess->HandleLock, idleFlags); + KeReleaseSpinlock(&gIdleProcess->ThreadLock, idleFlags); cpu->IdleThread = idle; cpu->CurrentThread = idle; @@ -1321,11 +1327,11 @@ static void KiTimeoutWorkRoutine(void* context) { // Checks whether this CPU owns global scheduler tick duties. static int KiCpuOwnsGlobalSchedulerTick(const KCPU* cpu) { - // LAPIC timer interrupts are per-CPU, but sleep deadlines, waitable timers, + // Periodic timer interrupts are per-CPU, but sleep deadlines, waitable timers, // input repeat, and priority aging are global scheduler services. Keep the - // global clock single-sourced on the BSP so enabling AP timers does not + // global clock single-sourced on the boot processor so enabling AP timers does not // make those services run once per online CPU. - return cpu && cpu->IsBsp; + return cpu && cpu->IsBootProcessor; } // Ages ready normal-class threads after enough global ticks have elapsed. @@ -1451,6 +1457,29 @@ void KeSchedulerTick(void) { KiRequestReschedule(); } +// Converts one requested delay to scheduler ticks while reserving one phase guard tick. +static uint64_t KiTimeoutTicksForMilliseconds(uint64_t milliseconds) { + uint64_t ticks = milliseconds / SCHED_TICK_MS; + if ((milliseconds % SCHED_TICK_MS) != 0) + ticks++; + + // The scheduler tick count identifies completed quanta, not the current + // position inside one quantum. One additional tick guarantees that a + // timeout armed immediately before an interrupt cannot expire early. + if (ticks < UINT64_MAX) + ticks++; + return ticks ? ticks : 1; +} + +// Returns the saturated scheduler tick at which one finite timeout may expire. +static uint64_t KiTimeoutWakeTickLocked(uint64_t milliseconds) { + uint64_t ticks = KiTimeoutTicksForMilliseconds(milliseconds); + uint64_t now = gSchedStats.Ticks; + if (ticks == UINT64_MAX || ticks > UINT64_MAX - now) + return UINT64_MAX; + return now + ticks; +} + // Arms a timeout for the current thread after a wait primitive has prepared it to block. KSTATUS KeArmCurrentThreadTimeout(uint64_t milliseconds, KSTATUS timeoutStatus) { if (milliseconds == 0) @@ -1462,12 +1491,6 @@ KSTATUS KeArmCurrentThreadTimeout(uint64_t milliseconds, KSTATUS timeoutStatus) if (!self || self->IsIdleThread) return NTLSTATUS_INVALID_PARAMETER; - uint64_t ticks = milliseconds / SCHED_TICK_MS; - if ((milliseconds % SCHED_TICK_MS) != 0) - ticks++; - if (ticks == 0) - ticks = 1; - uintptr_t flags = KeAcquireSpinlock(&gSchedLock); if (self->State != ThreadWaiting || self->RunningCpu != KeGetCurrentCpu()->CpuIndex || @@ -1478,12 +1501,7 @@ KSTATUS KeArmCurrentThreadTimeout(uint64_t milliseconds, KSTATUS timeoutStatus) return NTLSTATUS_INVALID_PARAMETER; } - uint64_t now = gSchedStats.Ticks; - uint64_t wakeTick = now + ticks; - if (wakeTick < now) - wakeTick = UINT64_MAX; - - self->TimeoutTick = wakeTick; + self->TimeoutTick = KiTimeoutWakeTickLocked(milliseconds); self->TimeoutStatus = timeoutStatus; self->TimeoutArmed = 1; KeListInsertTailChecked(&gTimeoutList, &self->TimeoutLink); @@ -1505,12 +1523,6 @@ __declspec(dllexport) KSTATUS KeDelayThread(uint64_t milliseconds) { if (!self || self->IsIdleThread) return NTLSTATUS_INVALID_PARAMETER; - uint64_t ticks = milliseconds / SCHED_TICK_MS; - if ((milliseconds % SCHED_TICK_MS) != 0) - ticks++; - if (ticks == 0) - ticks = 1; - uintptr_t flags = KeAcquireSpinlock(&gSchedLock); if (self->Queued || self->State != ThreadRunning || @@ -1522,10 +1534,7 @@ __declspec(dllexport) KSTATUS KeDelayThread(uint64_t milliseconds) { return NTLSTATUS_INVALID_PARAMETER; } - uint64_t now = gSchedStats.Ticks; - uint64_t wakeTick = now + ticks; - if (wakeTick < now) - wakeTick = UINT64_MAX; + uint64_t wakeTick = KiTimeoutWakeTickLocked(milliseconds); self->State = ThreadWaiting; self->WaitObject = (OBJECT*)0; diff --git a/code/kernel/ke_selftest.c b/code/kernel/ke_selftest.c index f745cdf..a81ba64 100644 --- a/code/kernel/ke_selftest.c +++ b/code/kernel/ke_selftest.c @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include #include @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1152,10 +1153,15 @@ static void KiRunStringViewSelfTest(void) { if (SysSvCopyZ(copy, 4, emb)) KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: undersized copy unexpectedly succeeded\n"); - // Paths are string views at the ABI boundary, but FAT/VFS path names do not + // Paths are string views at the ABI boundary, but provider path names do not // accept embedded NUL bytes. This must fail before reaching any filesystem. + IO_VOLUME* volume = (IO_VOLUME*)0; + KSTATUS s = KeReferenceSystemRootVolume(&volume); + if (!KSUCCESS(s)) + KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: system volume reference status=%u\n", s); FILE_OBJECT* f = (FILE_OBJECT*)0; - KSTATUS s = IoOpenFile(SysSvFromParts("system/init.exe\0junk", 20), &f); + s = IoOpenFileAtVolume(volume, SysSvFromParts("system/init.exe\0junk", 20), &f); + IoReleaseVolume(volume); if (s != NTLSTATUS_INVALID_PARAMETER) KePanic(KE_PANIC_CODE_SELFTEST, "STRINGVIEW selftest: embedded-NUL path status=%u\n", s); @@ -1164,6 +1170,59 @@ static void KiRunStringViewSelfTest(void) { DbgPrint("STRINGVIEW selftest: PASS\n"); } +// Capability-relative native-path self-test. +static void KiRunVolumeRelativePathSelfTest(void) { + DbgPrint("VOLUME/PATH selftest: begin\n"); + + IO_VOLUME* volume = (IO_VOLUME*)0; + KSTATUS status = KeReferenceSystemRootVolume(&volume); + if (!KSUCCESS(status)) + KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: system volume reference failed %u\n", status); + + FILE_OBJECT* file = (FILE_OBJECT*)0; + status = IoOpenFileAtVolume(volume, SYS_SV_LITERAL("system/init.exe"), &file); + if (!KSUCCESS(status) || !file) + KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: direct open failed %u\n", status); + IoCloseFile(file); + + const SYS_STRING_VIEW invalidPaths[] = { + SYS_SV_LITERAL("/system/init.exe"), + SYS_SV_LITERAL("system\\init.exe"), + SYS_SV_LITERAL("system//init.exe"), + SYS_SV_LITERAL("system/./init.exe"), + SYS_SV_LITERAL("system/../init.exe"), + SYS_SV_LITERAL("system/init.exe/"), + }; + for (size_t i = 0; i < sizeof(invalidPaths) / sizeof(invalidPaths[0]); ++i) { + file = (FILE_OBJECT*)0; + status = IoOpenFileAtVolume(volume, invalidPaths[i], &file); + if (status != NTLSTATUS_INVALID_PARAMETER || file) + KePanic(KE_PANIC_CODE_SELFTEST, + "VOLUME/PATH selftest: invalid path %llu status=%u file=0x%016llX\n", + (uint64_t)i, status, (uint64_t)(uintptr_t)file); + } + + DIR_OBJECT* directory = (DIR_OBJECT*)0; + status = IoOpenDirAtVolume(volume, SYS_SV_LITERAL("system"), &directory); + if (!KSUCCESS(status) || !directory) + KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: directory open failed %u\n", status); + + file = (FILE_OBJECT*)0; + status = IoOpenFileAtDirectory(directory, SYS_SV_LITERAL("init.exe"), &file); + if (!KSUCCESS(status) || !file) + KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: directory-relative open failed %u\n", status); + IoCloseFile(file); + + file = (FILE_OBJECT*)0; + status = IoOpenFileAtDirectory(directory, SYS_SV_LITERAL("../init.exe"), &file); + if (status != NTLSTATUS_INVALID_PARAMETER || file) + KePanic(KE_PANIC_CODE_SELFTEST, "VOLUME/PATH selftest: parent traversal status=%u\n", status); + + IoCloseDir(directory); + IoReleaseVolume(volume); + DbgPrint("VOLUME/PATH selftest: PASS\n"); +} + // Async file I/O self-test. typedef struct KI_FILE_ASYNC_SELFTEST_STATE { @@ -1221,7 +1280,7 @@ static void KiRunAsyncFileIoSelfTest(void) { memset(&st, 0, sizeof(st)); st.Iosb.Status = NTLSTATUS_FAILURE; - KSTATUS s = IoOpenFile(SYS_SV_LITERAL("system/init.exe"), &st.File); + KSTATUS s = KeOpenSystemRootFile("system/init.exe", &st.File); if (!KSUCCESS(s)) { DbgPrint("FILE/ASYNC selftest: SKIP, system/init.exe open failed %u\n", s); return; @@ -1303,8 +1362,8 @@ static void KiRunAsyncFileIoSelfTest(void) { wiosb.Status = NTLSTATUS_FAILURE; wiosb.Information = 0; - (void)IoDeleteFile(SYS_SV_LITERAL("asyncio.tmp")); - s = IoCreateFile(SYS_SV_LITERAL("asyncio.tmp"), &wf); + (void)KeDeleteSystemRootFile("asyncio.tmp"); + s = KeCreateSystemRootFile("asyncio.tmp", &wf); if (!KSUCCESS(s)) KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: create write temp failed %u\n", s); @@ -1343,7 +1402,7 @@ static void KiRunAsyncFileIoSelfTest(void) { IoCloseFile(wf); wf = (FILE_OBJECT*)0; - s = IoOpenFile(SYS_SV_LITERAL("asyncio.tmp"), &rf); + s = KeOpenSystemRootFile("asyncio.tmp", &rf); if (!KSUCCESS(s)) KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: reopen write temp failed %u\n", s); @@ -1357,7 +1416,7 @@ static void KiRunAsyncFileIoSelfTest(void) { IoCloseFile(rf); ObDereferenceObject(&wev->Header); - s = IoDeleteFile(SYS_SV_LITERAL("asyncio.tmp")); + s = KeDeleteSystemRootFile("asyncio.tmp"); if (!KSUCCESS(s)) KePanic(KE_PANIC_CODE_SELFTEST, "FILE/ASYNC selftest: delete temp failed %u\n", s); @@ -1774,16 +1833,16 @@ void KiRunCoreHardeningSelfTests(void) { KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: GS/KCPU self pointer mismatch self=0x%016llX cpu=0x%016llX\n", (uint64_t)cpu->Self, (uint64_t)cpu); if (KeGetCurrentCpuIndex() != 0) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: current CPU index is not BSP/0\n"); - if (cpu->State != KeCpuOnline || !cpu->IsBsp) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: BSP lifecycle invalid state=%ld bsp=%u\n", - cpu->State, cpu->IsBsp); + KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: current CPU index is not boot processor/0\n"); + if (cpu->State != KeCpuOnline || !cpu->IsBootProcessor) + KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: boot-processor lifecycle invalid state=%ld boot=%u\n", + cpu->State, cpu->IsBootProcessor); if (KeGetCurrentIrql() != PASSIVE_LEVEL) KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: expected PASSIVE_LEVEL, got %u\n", KeGetCurrentIrql()); if (KeGetCurrentTss() != &cpu->Tss) KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: current TSS is not CPU-local\n"); if (!cpu->Tss.RSP0 || cpu->Tss.IoMapBase != sizeof(TSS64)) - KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: invalid BSP TSS rsp0=0x%016llX iomap=%u\n", + KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: invalid boot-processor TSS rsp0=0x%016llX iomap=%u\n", cpu->Tss.RSP0, cpu->Tss.IoMapBase); if (!cpu->Gdtr.Base || cpu->Gdtr.Base != (uint64_t)cpu->Gdt) KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: GDT is not CPU-local base=0x%016llX gdt=0x%016llX\n", @@ -1793,10 +1852,10 @@ void KiRunCoreHardeningSelfTests(void) { KCPU* ap = &gCpus[i]; if (ap->State == KeCpuAbsent) continue; - if (ap->State != KeCpuOnline || ap->IsBsp) + if (ap->State != KeCpuOnline || ap->IsBootProcessor) KePanic(KE_PANIC_CODE_SELFTEST, - "CORE selftest: AP%u lifecycle invalid state=%ld bsp=%u\n", - i, ap->State, ap->IsBsp); + "CORE selftest: AP%u lifecycle invalid state=%ld boot=%u\n", + i, ap->State, ap->IsBootProcessor); if (!ap->KernelStack || !ap->KernelStackTop) KePanic(KE_PANIC_CODE_SELFTEST, "CORE selftest: AP%u missing kernel stack\n", i); if (!ap->Tss.RSP0 || ap->Tss.IoMapBase != sizeof(TSS64)) @@ -1847,6 +1906,7 @@ void KiRunKernelSelfTests(const HAL_API* hal) { KiRunVadCopyOnWriteSelfTest(); KiRunVadSwapSelfTest(); KiRunStringViewSelfTest(); + KiRunVolumeRelativePathSelfTest(); KiRunIrpKernelApcSelfTest(hal); KiRunAsyncFileIoSelfTest(); KiRunTrapFrameSelfTest(); diff --git a/code/kernel/ke_semaphore.c b/code/kernel/ke_semaphore.c index 73ceb70..801dce1 100644 --- a/code/kernel/ke_semaphore.c +++ b/code/kernel/ke_semaphore.c @@ -12,10 +12,10 @@ static void KiSemaphoreDestroy(OBJECT* obj) { (void)obj; } // Implements the semaphore Create operation. -__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE **out) { +__declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE** out) { if (!out || initial < 0 || max < 1 || initial > max) return NTLSTATUS_INVALID_PARAMETER; - SEMAPHORE* s = (SEMAPHORE *)ObAllocateObject(ObjectTypeSemaphore, sizeof(SEMAPHORE)); + SEMAPHORE* s = (SEMAPHORE*)ObAllocateObject(ObjectTypeSemaphore, sizeof(SEMAPHORE)); if (!s) return NTLSTATUS_NO_MEMORY; s->Header.Destroy = KiSemaphoreDestroy; s->Lock = (SPINLOCK)SPINLOCK_INIT; diff --git a/code/kernel/ke_settings.c b/code/kernel/ke_settings.c index f2099e4..2c14249 100644 --- a/code/kernel/ke_settings.c +++ b/code/kernel/ke_settings.c @@ -1,7 +1,4 @@ -// Kernel settings reader. This file loads simple runtime settings from the system configuration file. - -// This intentionally exposes generic string settings only. Subsystems and -// drivers own the policy for interpreting their sections. +// Kernel settings reader. This file performs a direct lookup in the simple system settings file. #include #include @@ -9,41 +6,102 @@ #include #include +#include #include -#include #define KE_SETTINGS_MAX_FILE_BYTES (64u * 1024u) -typedef struct KE_FIND_SETTING_CTX { - const char* Section; - const char* Key; - char* Buffer; - size_t BufferSize; - KSTATUS Status; -} KE_FIND_SETTING_CTX; - -static void KeFindSettingCallback(const char* section, const char* key, - const char* value, void* userdata) { - KE_FIND_SETTING_CTX* ctx = (KE_FIND_SETTING_CTX*)userdata; - if (!ctx || ctx->Status != NTLSTATUS_NOT_FOUND) - return; - if (!section || !key || !value) - return; - if (strcmp(section, ctx->Section) != 0 || strcmp(key, ctx->Key) != 0) - return; - - size_t len = strlen(value); - if (len + 1 > ctx->BufferSize) { - if (ctx->BufferSize) - ctx->Buffer[0] = '\0'; - ctx->Status = NTLSTATUS_BUFFER_TOO_SMALL; - return; - } - - memcpy(ctx->Buffer, value, len + 1); - ctx->Status = NTLSTATUS_SUCCESS; +// Returns whether one settings.ini character is horizontal whitespace. +static int KeSettingSpace(char c) { + return c == ' ' || c == '\t' || c == '\r'; } +// Trims horizontal whitespace from one immutable settings.ini span. +static void KeSettingTrim(const char** begin, const char** end) { + while (*begin < *end && KeSettingSpace(**begin)) + ++*begin; + while (*end > *begin && KeSettingSpace((*end)[-1])) + --*end; +} + +// Returns whether one immutable span exactly matches a NUL-terminated string. +static int KeSettingSpanEquals(const char* begin, const char* end, const char* text) { + while (begin < end && *text) { + if (*begin++ != *text++) + return 0; + } + return begin == end && *text == '\0'; +} + +// Looks up one [section] key=value entry in a read-only settings buffer. +static KSTATUS KeFindSetting(const char* text, size_t length, + const char* wantedSection, const char* wantedKey, + char* buffer, size_t bufferSize) { + char section[64] = {0}; + size_t offset = 0; + + while (offset < length) { + size_t lineStart = offset; + while (offset < length && text[offset] != '\n') + ++offset; + size_t lineEnd = offset; + if (offset < length) + ++offset; + + const char* begin = text + lineStart; + const char* end = text + lineEnd; + KeSettingTrim(&begin, &end); + if (begin == end || *begin == ';' || *begin == '#') + continue; + + if (*begin == '[') { + if (end[-1] != ']') + return NTLSTATUS_INVALID_PARAMETER; + const char* sectionBegin = begin + 1; + const char* sectionEnd = end - 1; + KeSettingTrim(§ionBegin, §ionEnd); + size_t sectionLength = (size_t)(sectionEnd - sectionBegin); + if (sectionLength >= sizeof(section)) + return NTLSTATUS_INVALID_PARAMETER; + memcpy(section, sectionBegin, sectionLength); + section[sectionLength] = '\0'; + continue; + } + + const char* equals = NULL; + for (const char* p = begin; p < end; ++p) { + if (*p == '=') { + equals = p; + break; + } + } + if (!equals) + continue; + + const char* keyBegin = begin; + const char* keyEnd = equals; + const char* valueBegin = equals + 1; + const char* valueEnd = end; + KeSettingTrim(&keyBegin, &keyEnd); + KeSettingTrim(&valueBegin, &valueEnd); + + if (strcmp(section, wantedSection) != 0 || + !KeSettingSpanEquals(keyBegin, keyEnd, wantedKey)) { + continue; + } + + size_t valueLength = (size_t)(valueEnd - valueBegin); + if (valueLength + 1u > bufferSize) + return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(buffer, valueBegin, valueLength); + buffer[valueLength] = '\0'; + return NTLSTATUS_SUCCESS; + } + + return NTLSTATUS_NOT_FOUND; +} + +// Reads one string setting from the system settings file. __declspec(dllexport) KSTATUS KeGetSettingString(const char* section, const char* key, char* buffer, @@ -54,9 +112,9 @@ __declspec(dllexport) KSTATUS KeGetSettingString(const char* section, buffer[0] = '\0'; FILE_OBJECT* file = NULL; - KSTATUS st = IoOpenFile(SYS_SV_LITERAL(KE_SETTINGS_PATH), &file); - if (!KSUCCESS(st)) - return st; + KSTATUS status = KeOpenSystemRootFile(KE_SETTINGS_PATH, &file); + if (!KSUCCESS(status)) + return status; uint64_t fileSize64 = IoGetFileSize(file); if (fileSize64 >= KE_SETTINGS_MAX_FILE_BYTES) { @@ -65,30 +123,22 @@ __declspec(dllexport) KSTATUS KeGetSettingString(const char* section, } size_t fileSize = (size_t)fileSize64; - char* text = (char*)MmAllocatePool(fileSize + 1); + char* text = (char*)MmAllocatePool(fileSize + 1u); if (!text) { IoCloseFile(file); return NTLSTATUS_NO_MEMORY; } size_t got = 0; - st = IoReadFile(file, text, fileSize, &got); + status = IoReadFile(file, text, fileSize, &got); IoCloseFile(file); - if (!KSUCCESS(st)) { + if (!KSUCCESS(status) || got != fileSize) { MmFreePool(text); - return st; + return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; } text[got] = '\0'; - - KE_FIND_SETTING_CTX ctx; - ctx.Section = section; - ctx.Key = key; - ctx.Buffer = buffer; - ctx.BufferSize = bufferSize; - ctx.Status = NTLSTATUS_NOT_FOUND; - - IniParse(text, KeFindSettingCallback, &ctx); + status = KeFindSetting(text, got, section, key, buffer, bufferSize); MmFreePool(text); - return ctx.Status; + return status; } diff --git a/code/kernel/ke_smp.c b/code/kernel/ke_smp.c index 44f0321..1929180 100644 --- a/code/kernel/ke_smp.c +++ b/code/kernel/ke_smp.c @@ -1,15 +1,16 @@ -// SMP bring-up and CPU topology management. This file owns AP startup, CPU admission, and topology-derived scheduling metadata. +// SMP bring-up and CPU topology management. This file owns secondary-processor admission and topology-derived scheduling metadata. -// The bootloader is intentionally not involved in AP ownership. The kernel -// asks the HAL for MADT-discovered LAPIC IDs, copies a small low-memory x86 AP -// startup blob, sends INIT/SIPI through the local APIC, and waits for each AP -// to enter KiApKernelEntry() and park in kernel-owned state. +// The bootloader is intentionally not involved in secondary-processor ownership. +// The kernel asks the HAL for platform processor identities, prepares the x86 +// bootstrap state, asks the HAL to start each processor, and waits for it to +// enter KiApKernelEntry() and park in kernel-owned state. #include #include #include #include #include #include +#include #include #include #include @@ -21,7 +22,6 @@ #include #include #include -#include #define AP_TRAMPOLINE_MAX_PHYS 0x000A0000ULL #define AP_TRAMPOLINE_PREFERRED_MIN 0x00070000ULL @@ -276,13 +276,13 @@ static KSTATUS KiBuildApTrampoline(void) { } _Noreturn void KiApKernelEntry(KCPU* cpu) { - (void)HalDisableInterrupts(); + (void)KeDisableInterrupts(); if (!cpu) - HalStopProcessor(); + KeStopProcessor(); - // APs start from the SIPI reset state, so do not assume the firmware/BSP - // CR4 feature bits are present. The scheduler and context-switch paths + // Secondary processors enter through the x86 bootstrap path, so do not assume + // firmware CR4 feature bits are present. The scheduler and context-switch paths // use FXSAVE/FXRSTOR on every CPU; without OSFXSR, those instructions // raise #UD as soon as an AP enters its idle thread. uintptr_t cr4 = __readcr4(); @@ -302,7 +302,7 @@ _Noreturn void KiApKernelEntry(KCPU* cpu) { KeSetUserGsBase(0); if (gApBootBlock) gApBootBlock->Stage = AP_STAGE_GS; - HalApicEnableCurrentCpu(); + KeInitializeCurrentProcessorInterruptController(); KeInitializeGdtForCpu(cpu); KeLoadGdtForCurrentCpu(cpu); @@ -332,41 +332,41 @@ _Noreturn void KiApKernelEntry(KCPU* cpu) { idle->State = ThreadRunning; idle->RunningCpu = cpu->CpuIndex; idle->Queued = 0; - // Do not arm the AP's periodic LAPIC timer while it is still in - // the raw park loop. Timer IRQs are scheduler-owned events; - // the AP may take them only after it has a current idle thread. - HalTimerConfigureCurrentCpu(); + // Do not arm this processor's periodic timer while it is still in the raw + // park loop. Timer IRQs are scheduler-owned events and require + // a current idle thread before they may be delivered. + KeConfigureCurrentProcessorTimer(); _InterlockedExchange(&cpu->State, KeCpuSchedulerActive); - HalEnableInterrupts(); + KeEnableInterrupts(); KeStartInitialThread(idle); } } - // The BSP admits parked APs by publishing AdmissionRequested and + // The boot processor admits parked secondary processors by publishing AdmissionRequested and // sending a wake IPI. Avoid the classic lost wakeup where the state // changes between the poll and HLT: check once with interrupts // disabled, then use STI;HLT as a single sleep transition. - uintptr_t interruptFlags = HalDisableInterrupts(); + uintptr_t interruptFlags = KeDisableInterrupts(); if (_InterlockedCompareExchange(&cpu->State, 0, 0) == KeCpuAdmissionRequested) { - HalRestoreInterrupts(interruptFlags); + KeRestoreInterrupts(interruptFlags); continue; } - HalWaitForInterrupt(); + KeWaitForInterrupt(); } } // Prepares cpu Record. -static void KiPrepareCpuRecord(uint32_t cpuIndex, const HAL_CPU_INFO* info) { +static void KiPrepareCpuRecord(uint32_t cpuIndex, const HAL_PROCESSOR_INFO* info) { KCPU* cpu = &gCpus[cpuIndex]; memset(cpu, 0, sizeof(*cpu)); cpu->Self = cpu; cpu->CpuIndex = cpuIndex; - cpu->LapicId = info->LapicId; - cpu->AcpiProcessorId = info->ProcessorUid; + cpu->HardwareId = info->HardwareId; + cpu->FirmwareId = info->FirmwareId; cpu->BootProcessorIndex = cpuIndex; cpu->State = info->Enabled ? KeCpuPresent : KeCpuAbsent; - cpu->IsBsp = info->IsBsp ? 1 : 0; + cpu->IsBootProcessor = info->IsBootProcessor ? 1 : 0; cpu->PackageId = 0; cpu->CoreId = cpuIndex; cpu->ThreadId = 0; @@ -517,11 +517,11 @@ void KeBuildProcessorTopology(void) { if (cpu->State == KeCpuAbsent) continue; - uint32_t apic = cpu->LapicId; - cpu->ThreadId = smtMask ? (apic & smtMask) : 0u; - cpu->CoreId = coreBits ? ((apic >> topo.SmtShift) & coreMask) : - (apic >> topo.SmtShift); - cpu->PackageId = (topo.CoreShift < 32u) ? (apic >> topo.CoreShift) : 0u; + uint32_t hardwareId = cpu->HardwareId; + cpu->ThreadId = smtMask ? (hardwareId & smtMask) : 0u; + cpu->CoreId = coreBits ? ((hardwareId >> topo.SmtShift) & coreMask) : + (hardwareId >> topo.SmtShift); + cpu->PackageId = (topo.CoreShift < 32u) ? (hardwareId >> topo.CoreShift) : 0u; cpu->PhysicalCoreIndex = KE_INVALID_CPU; cpu->CoreSiblingMask = 0; cpu->PackageMask = 0; @@ -591,9 +591,9 @@ void KeBuildProcessorTopology(void) { KCPU* cpu = &gCpus[i]; if (cpu->State == KeCpuAbsent) continue; - DbgPrint("SMP: cpu%u lapic=%u pkg=%u core=%u thread=%u primary=%u siblings=0x%llx package=0x%llx\n", + DbgPrint("SMP: cpu%u hardware=%u pkg=%u core=%u thread=%u primary=%u siblings=0x%llx package=0x%llx\n", i, - cpu->LapicId, + cpu->HardwareId, cpu->PackageId, cpu->CoreId, cpu->ThreadId, @@ -656,40 +656,39 @@ static void KiFillBootBlockForCpu(KCPU* cpu, uint32_t apIndex) { // Starts application Processors. KSTATUS KeStartApplicationProcessors(void) { - HAL_CPU_TOPOLOGY topo; - KSTATUS st = HalGetProcessorTopology(&topo); + HAL_PROCESSOR_TOPOLOGY topo; + KSTATUS st = KeQueryProcessorTopology(&topo); if (!KSUCCESS(st)) return st; - if (topo.CpuCount == 0 || topo.CpuCount > KE_MAX_CPUS) + if (topo.Count == 0 || topo.Count > KE_MAX_CPUS) return NTLSTATUS_DEVICE_ERROR; - uint32_t bspLapic = HalGetBspLapicId(); + uint32_t bootHardwareId = topo.BootProcessorHardwareId; KCPU* bsp = KeGetCurrentCpu(); - bsp->LapicId = bspLapic; - bsp->IsBsp = 1; + bsp->HardwareId = bootHardwareId; + bsp->IsBootProcessor = 1; bsp->State = KeCpuStarted; - DbgPrint("SMP: APIC mode=%s BSP LAPIC=%u MADT CPUs=%u\n", - HalApicUsingX2Apic() ? "x2APIC" : "xAPIC", - bspLapic, topo.CpuCount); + DbgPrint("SMP: platform processors boot-id=%u firmware-count=%u\n", + bootHardwareId, topo.Count); uint32_t count = 1; - for (uint32_t i = 0; i < topo.CpuCount && count < KE_MAX_CPUS; i++) { - HAL_CPU_INFO* info = &topo.Cpus[i]; + for (uint32_t i = 0; i < topo.Count && count < KE_MAX_CPUS; i++) { + HAL_PROCESSOR_INFO* info = &topo.Processors[i]; if (KiSmpDiagnosticsEnabled()) { - DbgPrint("SMP: topology[%u] uid=%u lapic=%u flags=0x%x enabled=%u bsp=%u addressable=%u\n", - i, info->ProcessorUid, info->LapicId, info->Flags, - info->Enabled, info->IsBsp, HalApicCanAddressId(info->LapicId)); + DbgPrint("SMP: topology[%u] firmware=%u hardware=%u flags=0x%x enabled=%u boot=%u startable=%u\n", + i, info->FirmwareId, info->HardwareId, info->Flags, + info->Enabled, info->IsBootProcessor, info->Startable); } if (!info->Enabled) continue; - if (info->LapicId == bspLapic) { - bsp->AcpiProcessorId = info->ProcessorUid; + if (info->HardwareId == bootHardwareId) { + bsp->FirmwareId = info->FirmwareId; continue; } - if (!HalApicCanAddressId(info->LapicId)) { - DbgPrint("SMP: skipping AP uid=%u lapic=%u; current APIC mode cannot address it\n", - info->ProcessorUid, info->LapicId); + if (!info->Startable) { + DbgPrint("SMP: skipping processor firmware=%u hardware=%u; HAL cannot start it\n", + info->FirmwareId, info->HardwareId); continue; } KiPrepareCpuRecord(count, info); @@ -697,7 +696,7 @@ KSTATUS KeStartApplicationProcessors(void) { } gCpuCount = count; - DbgPrint("SMP: discovered %u addressable CPU(s), BSP LAPIC=%u\n", gCpuCount, bspLapic); + DbgPrint("SMP: discovered %u usable processor(s), boot hardware ID=%u\n", gCpuCount, bootHardwareId); if (gCpuCount <= 1) { DbgPrint("SMP: no application processors present\n"); return NTLSTATUS_SUCCESS; @@ -721,7 +720,6 @@ KSTATUS KeStartApplicationProcessors(void) { return NTLSTATUS_SUCCESS; } - uint8_t sipiVector = (uint8_t)(gApTrampolinePhys >> 12); uint32_t claimed = 0; for (uint32_t i = 1; i < gCpuCount; i++) { @@ -730,20 +728,21 @@ KSTATUS KeStartApplicationProcessors(void) { st = KiAllocateApStack(cpu); if (!KSUCCESS(st)) { - DbgPrint("SMP: AP%u LAPIC=%u stack allocation failed status=0x%x; disabling AP and continuing\n", - i, cpu->LapicId, st); + DbgPrint("SMP: AP%u hardware=%u stack allocation failed status=0x%x; disabling AP and continuing\n", + i, cpu->HardwareId, st); _InterlockedExchange(&cpu->State, KeCpuAbsent); continue; } KiFillBootBlockForCpu(cpu, i); if (KiSmpDiagnosticsEnabled()) - DbgPrint("SMP: starting AP%u LAPIC=%u vector=0x%x\n", i, cpu->LapicId, sipiVector); + DbgPrint("SMP: starting AP%u hardware=%u entry=0x%016llX\n", + i, cpu->HardwareId, (uint64_t)gApTrampolinePhys); - st = HalStartApplicationProcessor(cpu->LapicId, sipiVector); + st = KeStartProcessor(cpu->HardwareId, gApTrampolinePhys); if (!KSUCCESS(st)) { - DbgPrint("SMP: AP%u LAPIC=%u startup IPI failed status=0x%x; disabling AP and continuing\n", - i, cpu->LapicId, st); + DbgPrint("SMP: AP%u hardware=%u startup IPI failed status=0x%x; disabling AP and continuing\n", + i, cpu->HardwareId, st); _InterlockedExchange(&cpu->State, KeCpuAbsent); continue; } @@ -763,8 +762,8 @@ KSTATUS KeStartApplicationProcessors(void) { } if (!parked) { - DbgPrint("SMP: AP%u failed to reach kernel park state lapic=%u state=%u stage=0x%x(%s) heartbeat=%llu last=%llu; disabling AP and continuing\n", - i, cpu->LapicId, gApBootBlock->State, gApBootBlock->Stage, + DbgPrint("SMP: AP%u failed to reach kernel park state hardware=%u state=%u stage=0x%x(%s) heartbeat=%llu last=%llu; disabling AP and continuing\n", + i, cpu->HardwareId, gApBootBlock->State, gApBootBlock->Stage, KiApStageName(gApBootBlock->Stage), gApBootBlock->Heartbeat, lastHeartbeat); _InterlockedExchange(&cpu->State, KeCpuAbsent); continue; @@ -781,7 +780,7 @@ KSTATUS KeStartApplicationProcessors(void) { } if (claimed == 0 && gCpuCount > 1) - DbgPrint("SMP: no APs reached kernel-owned state; continuing BSP-only\n"); + DbgPrint("SMP: no APs reached kernel-owned state; continuing boot-processor-only\n"); KeBuildProcessorTopology(); DbgPrint("SMP: kernel claimed %u APs\n", claimed); @@ -816,12 +815,12 @@ KSTATUS KeAdmitApplicationProcessorsToScheduler(void) { _InterlockedExchange(&cpu->State, KeCpuAdmissionRequested); KiSmpPoolCheckpoint("smp before AP wake", i); - // Parked APs sleep with HLT and have no LAPIC timer armed yet. The + // Parked secondary processors sleep with no periodic timer armed yet. The // state transition alone is not sufficient; send a fixed-vector IPI // only to wake the parked loop. ReschedulePending is intentionally left // clear, so the interrupt exit path will not try to schedule before // the AP has installed its idle thread. - HalApicSendRescheduleIpi(cpu->LapicId); + KeSendProcessorInterrupt(cpu->HardwareId, KE_VECTOR_RESCHEDULE_IPI); int active = 0; for (uint32_t spin = 0; spin < AP_SCHED_TIMEOUT_SPINS; spin++) { @@ -834,8 +833,8 @@ KSTATUS KeAdmitApplicationProcessorsToScheduler(void) { if (!active) { KePanic(KE_PANIC_CODE_INIT, - "SMP: AP%u failed scheduler admission lapic=%u state=%ld ticks=%llu\n", - i, cpu->LapicId, cpu->State, cpu->SchedulerTicks); + "SMP: AP%u failed scheduler admission hardware=%u state=%ld ticks=%llu\n", + i, cpu->HardwareId, cpu->State, cpu->SchedulerTicks); } _InterlockedExchange(&cpu->State, KeCpuOnline); diff --git a/code/kernel/ke_switch.asm b/code/kernel/ke_switch.asm index 09709fb..4fff0be 100644 --- a/code/kernel/ke_switch.asm +++ b/code/kernel/ke_switch.asm @@ -38,7 +38,7 @@ KeSwitchContext PROC mov [rcx + THREAD_SavedContext], rsp ; Update this CPU's TSS.RSP0 before switching to the incoming stack. - ; The helper hides whether current-CPU storage is still CPU0-only, LAPIC-ID + ; The helper hides whether current-CPU storage is still CPU0-only, hardware-ID ; indexed, or eventually reached through a SWAPGS-backed per-CPU base. mov rbx, rdx mov rcx, [rbx + THREAD_TssRsp0] diff --git a/code/kernel/ke_syscall.asm b/code/kernel/ke_syscall.asm index d2c9da9..f699770 100644 --- a/code/kernel/ke_syscall.asm +++ b/code/kernel/ke_syscall.asm @@ -112,22 +112,18 @@ KeSyscallEntry PROC call KiEnterTrapFrame add rsp, 32 - ; Re-order original user arguments into KeSyscallDispatch(nr,a0..a5). + ; Re-order original user arguments into KeSyscallDispatch(nr,a0..a3). ; Original registers were saved in the trap frame before any C calls. - sub rsp, 64 + sub rsp, 48 mov rax, [rbx + TF_R9] mov [rsp + 32], rax ; a3 - mov rax, [rbx + TF_R10] - mov [rsp + 40], rax ; a4 - mov rax, [rbx + TF_R11] - mov [rsp + 48], rax ; a5 mov r9, [rbx + TF_R8] ; a2 mov r8, [rbx + TF_Rdx] ; a1 mov rdx, [rbx + TF_Rcx] ; a0 mov rcx, [rbx + TF_Rax] ; nr call KeSyscallDispatch - add rsp, 64 + add rsp, 48 mov [rbx + TF_Rax], rax @@ -199,7 +195,22 @@ KF_R13 EQU 0A8h KF_R14 EQU 0B0h KF_R15 EQU 0B8h -FAULT_CALL_SHADOW EQU 28h +; Asynchronous kernel entry must preserve the full interrupted x87/SSE state. +; MS x64 calls may freely clobber XMM0-XMM5, and an interrupt or fault can +; arrive between any two user or kernel instructions rather than at a call ABI. +; Reserve one 512-byte FXSAVE image plus alignment slack on the interrupted stack. +ASYNC_FPU_SCRATCH EQU 528 + +ASYNC_FPU_SAVE MACRO + sub rsp, ASYNC_FPU_SCRATCH + lea r12, [rsp + 15] + and r12, -16 + fxsave64 [r12] +ENDM + +ASYNC_FPU_RESTORE MACRO + fxrstor64 [r12] +ENDM FAULT_RESUME_NOERR MACRO LOCAL noerr_skip_user_slots, noerr_no_swapgs, noerr_unhandled_halt @@ -372,11 +383,13 @@ de_kernel_frame: mov qword ptr [rax + KF_Ss], 0 de_call: - and rsp, -16 - sub rsp, FAULT_CALL_SHADOW mov rbx, rax - mov rcx, rax + ASYNC_FPU_SAVE + and rsp, -16 + sub rsp, 32 + mov rcx, rbx call KeFaultDispatchFrame + ASYNC_FPU_RESTORE FAULT_RESUME_NOERR KeFaultDE ENDP @@ -440,11 +453,13 @@ gp_kernel_frame: mov qword ptr [rax + KF_Ss], 0 gp_call: - and rsp, -16 - sub rsp, FAULT_CALL_SHADOW mov rbx, rax - mov rcx, rax + ASYNC_FPU_SAVE + and rsp, -16 + sub rsp, 32 + mov rcx, rbx call KeFaultDispatchFrame + ASYNC_FPU_RESTORE FAULT_RESUME_ERR KeFaultGP ENDP @@ -509,11 +524,13 @@ pf_kernel_frame: mov qword ptr [rax + KF_Ss], 0 pf_call: - and rsp, -16 - sub rsp, FAULT_CALL_SHADOW mov rbx, rax ; Keep the KFAULT_FRAME pointer across C. - mov rcx, rax + ASYNC_FPU_SAVE + and rsp, -16 + sub rsp, 32 + mov rcx, rbx call KeFaultDispatchFrame + ASYNC_FPU_RESTORE FAULT_RESUME_ERR KeFaultPF ENDP @@ -579,11 +596,13 @@ noerr_kernel_frame: mov qword ptr [rax + KF_Ss], 0 noerr_call: - and rsp, -16 - sub rsp, FAULT_CALL_SHADOW mov rbx, rax - mov rcx, rax + ASYNC_FPU_SAVE + and rsp, -16 + sub rsp, 32 + mov rcx, rbx call KeFaultDispatchFrame + ASYNC_FPU_RESTORE FAULT_RESUME_NOERR StubName ENDP ENDM @@ -646,11 +665,13 @@ err_kernel_frame: mov qword ptr [rax + KF_Ss], 0 err_call: - and rsp, -16 - sub rsp, FAULT_CALL_SHADOW mov rbx, rax - mov rcx, rax + ASYNC_FPU_SAVE + and rsp, -16 + sub rsp, 32 + mov rcx, rbx call KeFaultDispatchFrame + ASYNC_FPU_RESTORE FAULT_RESUME_ERR StubName ENDP ENDM @@ -669,14 +690,22 @@ KeNmiIpiStub PROC push r9 push r10 push r11 + push r12 + push r13 + mov r13, rsp + ASYNC_FPU_SAVE + and rsp, -16 sub rsp, 32 call KeHandleNmiIpi - add rsp, 32 + ASYNC_FPU_RESTORE + mov rsp, r13 test eax, eax jz short NmiUnhandled + pop r13 + pop r12 pop r11 pop r10 pop r9 @@ -687,6 +716,8 @@ KeNmiIpiStub PROC iretq NmiUnhandled: + pop r13 + pop r12 pop r11 pop r10 pop r9 @@ -857,7 +888,9 @@ IrqKernelModeFrame: mov qword ptr [rbx + TF_SsSlot], 0 IrqFrameReady: - sub rsp, 40 ; 32-byte shadow space + alignment/local slack + ASYNC_FPU_SAVE + and rsp, -16 + sub rsp, 32 mov rcx, rbx call KiEnterTrapFrame call KiEnterInterrupt @@ -866,14 +899,15 @@ IrqFrameReady: call KiExitInterrupt mov rcx, rbx call KiExitTrapFrame - add rsp, 40 + ASYNC_FPU_RESTORE mov eax, [rbx + TF_PreviousMode] cmp eax, KTRAP_MODE_USER jne short IrqNoSwapGsOut swapgs ; returning to ring 3: restore user GS/TEB IrqNoSwapGsOut: - add rsp, KTRAP_FRAME_SIZE + mov r12, [rbx + TF_R12] + lea rsp, [rbx + KTRAP_FRAME_SIZE] pop rbx pop r11 pop r10 diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index 969376f..2dd5dd5 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -2,13 +2,9 @@ // KeSyscallEntry (ke_syscall.asm) handles the INT 0x40 ring-3 -> ring-0 // transition, re-orders the arguments to match MS x64, and calls -// KeSyscallDispatch with: - -// nr = original RAX (syscall number) -// a0 .. a5 = RCX, RDX, R8, R9, R10, R11 (user arguments) - -// Every handler receives the same (a0..a5) signature even if it uses fewer -// arguments; unused parameters are silently ignored. +// KeSyscallDispatch with the syscall number plus at most four register arguments. +// Compound operations use size-tagged request structures instead of extending +// the register ABI. // Return value (RAX) is a KSTATUS code *or* an arbitrary 64-bit datum // (pointer, size, tick count, etc.) depending on the call; conventions are @@ -35,11 +31,14 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -62,8 +61,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -79,6 +79,7 @@ // HAL pointer - set once at boot by KeSyscallSetHal(). static const HAL_API* gHal; + typedef struct SYS_IO_STATUS_BLOCK_K { volatile uint32_t Status; volatile uint64_t Information; @@ -89,18 +90,22 @@ typedef void (*SYS_USER_APC_ROUTINE_K)(void* normalContext, uint64_t information); typedef struct SYS_ASYNC_FILE_REQUEST_K { - uint32_t FileHandle; - uint32_t EventHandle; + uint32_t Size; + SYS_HANDLE FileHandle; + SYS_HANDLE EventHandle; uint32_t Flags; uint64_t Offset; void* Buffer; - size_t Size; + size_t Length; SYS_IO_STATUS_BLOCK_K* Iosb; SYS_USER_APC_ROUTINE_K ApcRoutine; void* ApcContext; - uint32_t* RequestHandleOut; // optional cancellation/query handle output + SYS_HANDLE* RequestHandleOut; // optional cancellation/query handle output } SYS_ASYNC_FILE_REQUEST_K; +_Static_assert(sizeof(SYS_ASYNC_FILE_REQUEST_K) == sizeof(SYS_ASYNC_FILE_REQUEST), + "async file request ABI mismatch"); + typedef struct SYS_USER_ASYNC_APC_CONTEXT { KAPC KernelApc; THREAD* TargetThread; @@ -126,44 +131,6 @@ 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 NTLSTATUS_INVALID_PARAMETER; - - MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)ObAllocateObject(ObjectTypeModule, sizeof(MODULE_HANDLE_OBJECT)); - if (!module) return NTLSTATUS_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; @@ -317,56 +284,88 @@ void KeSyscallInit(const HAL_API* hal) { gHal = hal; } -// Helper: look up a handle and type-check it in one call. -// Bumps the refcount on success; caller must ObDereferenceObject when done. -// Returns NULL if the handle is invalid or the type does not match. -static OBJECT* KiHandleToObject(uint32_t handle, OBJECT_TYPE expected, uint32_t requiredRights) { - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return NULL; - OBJECT* obj = KeLookupHandleWithRights(proc, handle, requiredRights); - if (!obj) return NULL; - if (obj->Type != expected) { - ObDereferenceObject(obj); - return NULL; - } - return obj; +// Requires one ambient privilege from the calling process's immutable security context. +static KSTATUS KiRequirePrivilege(SYS_PRIVILEGES privilege) { + PROCESS* process = KeGetCurrentProcess(); + if (!process) return NTLSTATUS_FAILURE; + return SeProcessHasPrivilege(process, privilege) ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED; +} + +// Resolves one current-process handle while preserving the exact lookup status. +static OBJECT* KiHandleToObject(SYS_HANDLE handle, + OBJECT_TYPE expected, + SYS_RIGHTS requiredRights, + KSTATUS* statusOut) { + if (!statusOut) return (OBJECT*)0; + *statusOut = NTLSTATUS_FAILURE; + + PROCESS* process = KeGetCurrentProcess(); + if (!process) return (OBJECT*)0; + + OBJECT* object = (OBJECT*)0; + *statusOut = KeReferenceHandle(process, handle, expected, requiredRights, &object); + return KSUCCESS(*statusOut) ? object : (OBJECT*)0; } // Helper: allocate a handle in the current process's table for 'obj'. // Takes one reference on behalf of the handle slot. // On failure, drops the reference it would have stored. -static KSTATUS KiAllocHandleForCurrentWithRights(OBJECT* obj, uint32_t rights, uint32_t* handleOut) { +static KSTATUS KiAllocHandleForCurrentWithRights(OBJECT* obj, SYS_RIGHTS rights, SYS_HANDLE* handleOut) { PROCESS* proc = KeGetCurrentProcess(); if (!proc) { ObDereferenceObject(obj); return NTLSTATUS_FAILURE; } - KSTATUS s = KeAllocateHandleWithRights(proc, obj, rights, handleOut); + KSTATUS s = KeCreateHandle(proc, obj, rights, handleOut); // Most syscall create/open paths receive a fresh creator reference from the - // subsystem. The handle table takes its own reference; this helper always + // subsystem. The root capability grant takes its own reference; this helper always // drops the creator reference so the object is owned only by the returned // handle (and any subsystem-internal refs). ObDereferenceObject(obj); return s; } -// Creates handle For Current. -static KSTATUS KiAllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { - return KiAllocHandleForCurrentWithRights(obj, HANDLE_RIGHT_ALL, handleOut); -} - #define SYSCALL_STRING_MAX SYS_PATH_MAX #define SYSCALL_TEXT_MAX_BYTES (64ULL * 1024ULL) #define SYSCALL_IO_MAX_BYTES (16ULL * 1024ULL * 1024ULL) #define SYSCALL_FILE_IO_CHUNK_BYTES (64ULL * 1024ULL) #define SYSCALL_BLIT_MAX_BYTES (16ULL * 1024ULL * 1024ULL) -// Copies user Request. -static KSTATUS KiCopyUserRequest(uint64_t arg, void* dst, size_t size) { - if (!arg || !dst || size == 0) return NTLSTATUS_INVALID_PARAMETER; - return CopyFromUser(dst, (const void*)(uintptr_t)arg, size); +// Copies a request only after its leading size field matches the exact ABI layout. +static KSTATUS KiCopySizedUserRequest(const void* userRequest, void* dst, size_t size) { + if (!userRequest || !dst || size < sizeof(uint32_t) || size > UINT32_MAX) + return NTLSTATUS_INVALID_PARAMETER; + + uint32_t userSize = 0; + KSTATUS status = CopyFromUser(&userSize, userRequest, sizeof(userSize)); + if (!KSUCCESS(status)) return status; + if (userSize != (uint32_t)size) return NTLSTATUS_INVALID_PARAMETER; + + return CopyFromUser(dst, userRequest, size); +} + +// Copies one bounded Port capability-transfer array from user memory. +static KSTATUS KiImportPortTransfers(const SYS_PORT_HANDLE_TRANSFER* userTransfers, + uint32_t count, + SYS_PORT_HANDLE_TRANSFER* transfers) { + if (count == 0) + return NTLSTATUS_SUCCESS; + if (!userTransfers || !transfers || count > SYS_PORT_MAX_TRANSFER_HANDLES) + return NTLSTATUS_INVALID_PARAMETER; + return CopyFromUser(transfers, userTransfers, sizeof(*transfers) * count); +} + +// Validates the leading Size of a caller-owned output structure before writing it. +static KSTATUS KiProbeSizedUserOutput(void* userOutput, size_t size) { + if (!userOutput || size < sizeof(uint32_t) || size > UINT32_MAX) + return NTLSTATUS_INVALID_PARAMETER; + uint32_t userSize = 0; + KSTATUS status = CopyFromUser(&userSize, userOutput, sizeof(userSize)); + if (!KSUCCESS(status)) return status; + if (userSize != (uint32_t)size) return NTLSTATUS_INVALID_PARAMETER; + return MmProbeUserWrite(userOutput, size); } static KSTATUS KiImportUserStringView(SYS_STRING_VIEW userView, @@ -411,39 +410,39 @@ static KSTATUS KiImportUserTextView(SYS_STRING_VIEW userView, } // Copies handle To User. -static KSTATUS KiCopyHandleToUser(uint64_t arg, uint32_t value) { - if (!arg) return NTLSTATUS_INVALID_PARAMETER; - return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +static KSTATUS KiCopyHandleToUser(SYS_HANDLE* handleOut, SYS_HANDLE value) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; + return CopyToUser(handleOut, &value, sizeof(value)); } // Copies u32 To User. -static KSTATUS KiCopyU32ToUser(uint64_t arg, uint32_t value) { - if (!arg) return NTLSTATUS_INVALID_PARAMETER; - return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +static KSTATUS KiCopyU32ToUser(uint32_t* valueOut, uint32_t value) { + if (!valueOut) return NTLSTATUS_INVALID_PARAMETER; + return CopyToUser(valueOut, &value, sizeof(value)); } // Copies size To User. -static KSTATUS KiCopySizeToUser(uint64_t arg, size_t value) { - if (!arg) return NTLSTATUS_INVALID_PARAMETER; - return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +static KSTATUS KiCopySizeToUser(size_t* valueOut, size_t value) { + if (!valueOut) return NTLSTATUS_INVALID_PARAMETER; + return CopyToUser(valueOut, &value, sizeof(value)); } // Copies u64 To User. -static KSTATUS KiCopyU64ToUser(uint64_t arg, uint64_t value) { - if (!arg) return NTLSTATUS_INVALID_PARAMETER; - return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +static KSTATUS KiCopyU64ToUser(uint64_t* valueOut, uint64_t value) { + if (!valueOut) return NTLSTATUS_INVALID_PARAMETER; + return CopyToUser(valueOut, &value, sizeof(value)); } // Copies int To User. -static KSTATUS KiCopyIntToUser(uint64_t arg, int value) { - if (!arg) return NTLSTATUS_INVALID_PARAMETER; - return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +static KSTATUS KiCopyIntToUser(int* valueOut, int value) { + if (!valueOut) return NTLSTATUS_INVALID_PARAMETER; + return CopyToUser(valueOut, &value, sizeof(value)); } // Copies one signed 32-bit integer to user memory. -static KSTATUS KiCopyInt32ToUser(uint64_t arg, int32_t value) { - if (!arg) return NTLSTATUS_INVALID_PARAMETER; - return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value)); +static KSTATUS KiCopyInt32ToUser(int32_t* valueOut, int32_t value) { + if (!valueOut) return NTLSTATUS_INVALID_PARAMETER; + return CopyToUser(valueOut, &value, sizeof(value)); } // Implements the mul Size operation. @@ -463,13 +462,10 @@ static size_t KiAlignUpSize(size_t value, size_t alignment) { // Individual syscall handlers. -// SYS_EXIT_PROCESS (0) a0 = exitCode. -static uint64_t SyscallExitProcess(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; +// Implements SYS_EXIT_PROCESS. +static uint64_t SyscallExitProcess(int32_t exitCode) { THREAD* t = KeGetCurrentThread(); PROCESS* proc = t ? t->Owner : NULL; - int32_t exitCode = (int32_t)a0; if (!proc || !t) return NTLSTATUS_FAILURE; KSTATUS s = KeExitProcess(proc, exitCode); @@ -479,25 +475,20 @@ static uint64_t SyscallExitProcess(uint64_t a0, uint64_t a1, uint64_t a2, while (true) ; // unreachable - KeExitThread does not return } -// SYS_EXIT_THREAD (1) a0 = exitCode. -static uint64_t SyscallExitThread(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; +// Implements SYS_EXIT_THREAD. +static uint64_t SyscallExitThread(int32_t exitCode) { THREAD* t = KeGetCurrentThread(); - if (t) KeExitThread(t, (int)a0); + if (t) KeExitThread(t, (int)exitCode); while (true); } -// SYS_CREATE_THREAD a0=*SYS_CREATE_THREAD_REQUEST. +// Implements SYS_CREATE_THREAD. // Creates a user thread and optionally returns both its waitable handle and // diagnostic TID. Suspended creation leaves the thread off every ready queue // until SYS_RESUME_THREAD decrements its initial suspend count. -static uint64_t SyscallCreateThread(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; - +static uint64_t SyscallCreateThread(const SYS_CREATE_THREAD_REQUEST* request) { SYS_CREATE_THREAD_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; PROCESS* proc = KeGetCurrentProcess(); @@ -529,12 +520,12 @@ static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, &threadParameters, &newt); if (!KSUCCESS(s)) return s; - uint32_t threadHandle = 0; + SYS_HANDLE threadHandle = SYS_HANDLE_INVALID; if (req.ThreadHandleOut) { - s = KeAllocateHandleWithRights(proc, &newt->Header, - HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | - HANDLE_RIGHT_RESUME, - &threadHandle); + s = KeCreateHandle(proc, + &newt->Header, + KeGetValidHandleRights(ObjectTypeThread), + &threadHandle); if (!KSUCCESS(s)) { if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED) (void)KeCancelSuspendedThread(newt); @@ -544,7 +535,7 @@ static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, } if (req.ThreadHandleOut) { - s = KiCopyU32ToUser((uint64_t)(uintptr_t)req.ThreadHandleOut, threadHandle); + s = KiCopyHandleToUser(req.ThreadHandleOut, threadHandle); if (!KSUCCESS(s)) { KeCloseHandle(proc, threadHandle); if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED) @@ -555,7 +546,7 @@ static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, } if (req.ThreadIdOut) { - s = KiCopyU32ToUser((uint64_t)(uintptr_t)req.ThreadIdOut, newt->ThreadId); + s = KiCopyU32ToUser(req.ThreadIdOut, newt->ThreadId); if (!KSUCCESS(s)) { if (threadHandle) KeCloseHandle(proc, threadHandle); if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED) @@ -569,25 +560,20 @@ static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -// SYS_RESUME_THREAD a0=threadHandle, a1=previousSuspendCountOut. -static uint64_t SyscallResumeThread(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 (!a0 || !a1) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_RESUME_THREAD. +static uint64_t SyscallResumeThread(SYS_HANDLE threadHandle, uint32_t* previousSuspendCountOut) { + if (!threadHandle || !previousSuspendCountOut) return NTLSTATUS_INVALID_PARAMETER; - KSTATUS status = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(uint32_t)); + KSTATUS status = MmProbeUserWrite(previousSuspendCountOut, sizeof(uint32_t)); if (!KSUCCESS(status)) return status; PROCESS* process = KeGetCurrentProcess(); if (!process) return NTLSTATUS_FAILURE; - OBJECT* object = KeLookupHandleWithRights(process, (uint32_t)a0, - HANDLE_RIGHT_RESUME); - if (!object) return NTLSTATUS_INVALID_HANDLE; - if (object->Type != ObjectTypeThread) { - ObDereferenceObject(object); - return NTLSTATUS_INVALID_HANDLE; - } + OBJECT* object = (OBJECT*)0; + KSTATUS handleStatus = KeReferenceHandle(process, threadHandle, ObjectTypeThread, + SYS_THREAD_RIGHT_RESUME, &object); + if (!KSUCCESS(handleStatus)) return handleStatus; THREAD* thread = CONTAINING_RECORD(object, THREAD, Header); uint32_t previousSuspendCount = 0; @@ -595,7 +581,7 @@ static uint64_t SyscallResumeThread(uint64_t a0, uint64_t a1, uint64_t a2, ObDereferenceObject(object); if (!KSUCCESS(status)) return status; - return KiCopyU32ToUser(a1, previousSuspendCount); + return KiCopyU32ToUser(previousSuspendCountOut, previousSuspendCount); } static void KiFinishAlertableUserWait(uint8_t alertable) { @@ -607,13 +593,14 @@ static void KiFinishAlertableUserWait(uint8_t alertable) { } // Maps a waitable handle to its passive event backing object. -static KSTATUS KiHandleToWaitEvent(PROCESS* proc, uint32_t handle, EVENT** eventOut, OBJECT** objectOut) { +static KSTATUS KiHandleToWaitEvent(PROCESS* proc, SYS_HANDLE handle, EVENT** eventOut, OBJECT** objectOut) { if (!proc || !eventOut || !objectOut) return NTLSTATUS_INVALID_PARAMETER; *eventOut = (EVENT*)0; *objectOut = (OBJECT*)0; - OBJECT* obj = KeLookupHandleWithRights(proc, handle, HANDLE_RIGHT_WAIT); - if (!obj) return NTLSTATUS_INVALID_HANDLE; + OBJECT* obj = (OBJECT*)0; + KSTATUS handleStatus = KeReferenceHandle(proc, handle, 0, SYS_RIGHT_WAIT, &obj); + if (!KSUCCESS(handleStatus)) return handleStatus; EVENT* event = (EVENT*)0; switch (obj->Type) { @@ -641,6 +628,9 @@ static KSTATUS KiHandleToWaitEvent(PROCESS* proc, uint32_t handle, EVENT** event case ObjectTypeAudioStream: event = KeAudioStreamWaitEvent((KE_AUDIO_STREAM*)obj); break; + case ObjectTypePort: + event = KePortWaitEvent((PORT*)obj); + break; default: ObDereferenceObject(obj); return NTLSTATUS_NOT_SUPPORTED; @@ -656,22 +646,19 @@ static KSTATUS KiHandleToWaitEvent(PROCESS* proc, uint32_t handle, EVENT** event return NTLSTATUS_SUCCESS; } -// SYS_WAIT_SINGLE a0=handle a1=SYS_WAIT_FLAG_* a2=timeoutMs. +// Implements SYS_WAIT_SINGLE. // Waits on one handle. Passive objects use the wait-many engine; mutexes and // semaphores keep their native consuming semantics for single-object waits. -static uint64_t SyscallWaitSingle(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - - uint32_t flags = (uint32_t)a1; - uint64_t timeoutMs = a2; +static uint64_t SyscallWaitSingle(SYS_HANDLE handle, uint32_t flags, uint64_t timeoutMilliseconds) { + uint64_t timeoutMs = timeoutMilliseconds; if (flags & ~SYS_WAIT_FLAG_ALERTABLE) return NTLSTATUS_INVALID_PARAMETER; PROCESS* proc = KeGetCurrentProcess(); if (!proc) return NTLSTATUS_FAILURE; - OBJECT* obj = KeLookupHandleWithRights(proc, (uint32_t)a0, HANDLE_RIGHT_WAIT); - if (!obj) return NTLSTATUS_INVALID_HANDLE; + OBJECT* obj = (OBJECT*)0; + KSTATUS handleStatus = KeReferenceHandle(proc, handle, 0, SYS_RIGHT_WAIT, &obj); + if (!KSUCCESS(handleStatus)) return handleStatus; uint8_t alertable = (flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0; KSTATUS s = NTLSTATUS_NOT_SUPPORTED; @@ -694,7 +681,7 @@ static uint64_t SyscallWaitSingle(uint64_t a0, uint64_t a1, uint64_t a2, EVENT* event = (EVENT*)0; OBJECT* ref = (OBJECT*)0; - s = KiHandleToWaitEvent(proc, (uint32_t)a0, &event, &ref); + s = KiHandleToWaitEvent(proc, handle, &event, &ref); if (!KSUCCESS(s)) return s; uint32_t selected = 0; @@ -705,21 +692,17 @@ static uint64_t SyscallWaitSingle(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_WAIT_MANY a0=*SYS_WAIT_MANY_REQUEST. +// Implements SYS_WAIT_MANY. // Waits until any passive waitable handle becomes signaled. -static uint64_t SyscallWaitMany(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; - +static uint64_t SyscallWaitMany(const SYS_WAIT_MANY_REQUEST* request) { SYS_WAIT_MANY_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req)) return NTLSTATUS_INVALID_PARAMETER; if (req.Flags & ~SYS_WAIT_FLAG_ALERTABLE) return NTLSTATUS_INVALID_PARAMETER; if (!req.Handles || req.Count == 0 || req.Count > SYS_WAIT_MANY_MAX_HANDLES) return NTLSTATUS_INVALID_PARAMETER; - uint32_t handles[SYS_WAIT_MANY_MAX_HANDLES]; + SYS_HANDLE handles[SYS_WAIT_MANY_MAX_HANDLES]; size_t handleBytes = (size_t)req.Count * sizeof(handles[0]); s = CopyFromUser(handles, req.Handles, handleBytes); if (!KSUCCESS(s)) return s; @@ -751,7 +734,7 @@ static uint64_t SyscallWaitMany(uint64_t a0, uint64_t a1, uint64_t a2, ObDereferenceObject(refs[i]); if (KSUCCESS(s) && req.SignaledIndexOut) { - KSTATUS cs = KiCopyU32ToUser((uint64_t)(uintptr_t)req.SignaledIndexOut, selected); + KSTATUS cs = KiCopyU32ToUser(req.SignaledIndexOut, selected); if (!KSUCCESS(cs)) return cs; } @@ -760,22 +743,22 @@ static uint64_t SyscallWaitMany(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_OPEN_INPUT_QUEUE a0=flags a1=*handleOut. -static uint64_t SyscallOpenInputQueue(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 (a0 != 0) return NTLSTATUS_INVALID_PARAMETER; - if (!a1) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_OPEN_INPUT_QUEUE. +static uint64_t SyscallOpenInputQueue(uint32_t flags, SYS_HANDLE* handleOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (flags != 0) return NTLSTATUS_INVALID_PARAMETER; + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; INPUT_QUEUE* queue = (INPUT_QUEUE*)0; KSTATUS s = KeInputOpenQueue(&queue); if (!KSUCCESS(s)) return s; - uint32_t handle = 0; - s = KiAllocHandleForCurrentWithRights(&queue->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY, &handle); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&queue->Header, KeGetValidHandleRights(ObjectTypeInputQueue), &handle); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a1, handle); + s = KiCopyHandleToUser(handleOut, handle); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, handle); @@ -783,48 +766,40 @@ static uint64_t SyscallOpenInputQueue(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_GET_CURRENT_TID (3). -static uint64_t SyscallGetCurrentTid(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; +// Returns the current thread ID. +static uint64_t SyscallGetCurrentTid(void) { THREAD* t = KeGetCurrentThread(); return t ? t->ThreadId : 0; } -// SYS_GET_CURRENT_PID (4). -static uint64_t SyscallGetCurrentPid(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; +// Returns the current process ID. +static uint64_t SyscallGetCurrentPid(void) { PROCESS* p = KeGetCurrentProcess(); return p ? p->ProcessId : 0; } -// SYS_YIELD (5). -static uint64_t SyscallYield(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; +// Yields the current thread. +static uint64_t SyscallYield(void) { KeYield(); return NTLSTATUS_SUCCESS; } -// SYS_CREATE_TIMER a0=*handleOut. -static uint64_t SyscallCreateTimer(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_CREATE_TIMER. +static uint64_t SyscallCreateTimer(SYS_HANDLE* handleOut) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; KTIMER* timer = (KTIMER*)0; KSTATUS s = KeTimerCreate(&timer); if (!KSUCCESS(s)) return s; - uint32_t handle = 0; + SYS_HANDLE handle = SYS_HANDLE_INVALID; s = KiAllocHandleForCurrentWithRights(&timer->Header, - HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, + KeGetValidHandleRights(ObjectTypeTimer), &handle); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a0, handle); + s = KiCopyHandleToUser(handleOut, handle); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, handle); @@ -832,44 +807,39 @@ static uint64_t SyscallCreateTimer(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SET_TIMER a0=*SYS_SET_TIMER_REQUEST. -static uint64_t SyscallSetTimer(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; - +// Implements SYS_SET_TIMER. +static uint64_t SyscallSetTimer(const SYS_SET_TIMER_REQUEST* request) { SYS_SET_TIMER_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req) || req.Flags != SYS_TIMER_FLAG_NONE) + if (req.Flags != SYS_TIMER_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; - KTIMER* timer = (KTIMER*)KiHandleToObject(req.TimerHandle, ObjectTypeTimer, HANDLE_RIGHT_SIGNAL); - if (!timer) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + KTIMER* timer = (KTIMER*)KiHandleToObject(req.TimerHandle, ObjectTypeTimer, + SYS_TIMER_RIGHT_SET, &handleStatus); + if (!timer) return handleStatus; s = KeTimerSet(timer, req.DueTimeMilliseconds, req.PeriodMilliseconds); ObDereferenceObject(&timer->Header); return s; } -// SYS_CANCEL_TIMER a0=timerHandle. -static uint64_t SyscallCancelTimer(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; - - KTIMER* timer = (KTIMER*)KiHandleToObject((uint32_t)a0, ObjectTypeTimer, HANDLE_RIGHT_SIGNAL); - if (!timer) return NTLSTATUS_INVALID_HANDLE; +// Implements SYS_CANCEL_TIMER. +static uint64_t SyscallCancelTimer(SYS_HANDLE timerHandle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + KTIMER* timer = (KTIMER*)KiHandleToObject(timerHandle, ObjectTypeTimer, + SYS_TIMER_RIGHT_SET, &handleStatus); + if (!timer) return handleStatus; KSTATUS s = KeTimerCancel(timer); ObDereferenceObject(&timer->Header); return s; } -// SYS_SLEEP a0=milliseconds. +// Implements SYS_SLEEP. // Sleep is a convenience wrapper over the same waitable timer backend exposed // by timer handles. A zero-length sleep remains a yield. -static uint64_t SyscallSleep(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 == 0) { +static uint64_t SyscallSleep(uint64_t ms) { + if (ms == 0) { KeYield(); return NTLSTATUS_SUCCESS; } @@ -878,7 +848,7 @@ static uint64_t SyscallSleep(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = KeTimerCreate(&timer); if (!KSUCCESS(s)) return s; - s = KeTimerSet(timer, a0, 0); + s = KeTimerSet(timer, ms, 0); if (KSUCCESS(s)) { EVENT* event = KeTimerWaitEvent(timer); uint32_t selected = 0; @@ -889,31 +859,24 @@ static uint64_t SyscallSleep(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SET_USER_APC_DISPATCHER a0 = dispatcher entry in syscall.dll/user image. -static uint64_t SyscallSetUserApcDispatcher(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 NTLSTATUS_INVALID_PARAMETER; - KSTATUS s = MmProbeUserRead((const void*)(uintptr_t)a0, 1); +// Implements SYS_SET_USER_APC_DISPATCHER. +static uint64_t SyscallSetUserApcDispatcher(void* dispatcher) { + if (!dispatcher) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS s = MmProbeUserRead(dispatcher, 1); if (!KSUCCESS(s)) return s; - return KeSetUserApcDispatcher((void*)(uintptr_t)a0); + return KeSetUserApcDispatcher(dispatcher); } -// SYS_APC_RETURN a0 = SYS_USER_APC_FRAME* on the user stack. -static uint64_t SyscallApcReturn(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; - return KeUserApcReturn((const SYS_USER_APC_FRAME_K*)(uintptr_t)a0); +// Implements SYS_APC_RETURN. +static uint64_t SyscallApcReturn(const SYS_USER_APC_FRAME* frame) { + return KeUserApcReturn((const SYS_USER_APC_FRAME_K*)(uintptr_t)frame); } -// SYS_SET_EXCEPTION_DISPATCHER a0 = SYS_EXCEPTION_DISPATCHER_REQUEST*. -static uint64_t SyscallSetExceptionDispatcher(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; - +// Implements SYS_SET_EXCEPTION_DISPATCHER. +static uint64_t SyscallSetExceptionDispatcher(const SYS_EXCEPTION_DISPATCHER_REQUEST* userRequest) { SYS_EXCEPTION_DISPATCHER_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; @@ -924,15 +887,13 @@ static uint64_t SyscallSetExceptionDispatcher(uint64_t a0, uint64_t a1, uint64_t return KeSetExceptionDispatcher(process, &request); } -// SYS_GET_EXCEPTION_DISPATCHER a0 = SYS_EXCEPTION_DISPATCHER_REQUEST*. -static uint64_t SyscallGetExceptionDispatcher(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) +// Implements SYS_GET_EXCEPTION_DISPATCHER. +static uint64_t SyscallGetExceptionDispatcher(SYS_EXCEPTION_DISPATCHER_REQUEST* userRequest) { + if (!userRequest) return NTLSTATUS_INVALID_PARAMETER; uint32_t userSize = 0; - KSTATUS status = CopyFromUser(&userSize, (const void*)(uintptr_t)a0, sizeof(userSize)); + KSTATUS status = CopyFromUser(&userSize, userRequest, sizeof(userSize)); if (!KSUCCESS(status)) return status; if (userSize != sizeof(SYS_EXCEPTION_DISPATCHER_REQUEST)) @@ -947,16 +908,13 @@ static uint64_t SyscallGetExceptionDispatcher(uint64_t a0, uint64_t a1, uint64_t if (!KSUCCESS(status)) return status; - return CopyToUser((void*)(uintptr_t)a0, &request, sizeof(request)); + return CopyToUser(userRequest, &request, sizeof(request)); } -// SYS_CONTINUE_EXCEPTION a0 = SYS_CONTINUE_EXCEPTION_REQUEST*. -static uint64_t SyscallContinueException(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; - +// Implements SYS_CONTINUE_EXCEPTION. +static uint64_t SyscallContinueException(const SYS_CONTINUE_EXCEPTION_REQUEST* userRequest) { SYS_CONTINUE_EXCEPTION_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; @@ -964,18 +922,13 @@ static uint64_t SyscallContinueException(uint64_t a0, uint64_t a1, uint64_t a2, } -// SYS_RAISE_EXCEPTION a0 = SYS_RAISE_EXCEPTION_REQUEST*. -static uint64_t SyscallRaiseException(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; - +// Implements SYS_RAISE_EXCEPTION. +static uint64_t SyscallRaiseException(const SYS_RAISE_EXCEPTION_REQUEST* userRequest) { SYS_RAISE_EXCEPTION_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; - if (request.Size != sizeof(request)) - return NTLSTATUS_INVALID_PARAMETER; if (request.ParameterCount > SYS_EXCEPTION_MAXIMUM_PARAMETERS) return NTLSTATUS_INVALID_PARAMETER; @@ -993,14 +946,12 @@ static uint64_t SyscallRaiseException(uint64_t a0, uint64_t a1, uint64_t a2, return KeRaiseUserException(&request, parameters); } -// SYS_SET_THREAD_PRIORITY (7) a0 = priority. -static uint64_t SyscallSetThreadPriority(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 >= PriorityCount) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_SET_THREAD_PRIORITY. +static uint64_t SyscallSetThreadPriority(uint32_t priority) { + if (priority >= PriorityCount) return NTLSTATUS_INVALID_PARAMETER; THREAD* t = KeGetCurrentThread(); if (!t) return NTLSTATUS_FAILURE; - return KiSchedulerSetNormalPriority(t, (THREAD_PRIORITY)a0); + return KiSchedulerSetNormalPriority(t, (THREAD_PRIORITY)priority); } // SYS_VM_* page-granular user virtual memory. @@ -1039,15 +990,12 @@ static uint32_t KiPageFlagsToVmProtect(uint64_t flags) { return SYS_VM_READ; } -// SYS_ALLOC_VM a0=*SYS_ALLOC_VM_REQUEST. -static uint64_t SyscallVmAlloc(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; - +// Implements SYS_ALLOC_VM. +static uint64_t SyscallVmAlloc(const SYS_ALLOC_VM_REQUEST* request) { SYS_ALLOC_VM_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req) || req.Reserved != 0) return NTLSTATUS_INVALID_PARAMETER; + if (req.Reserved != 0) return NTLSTATUS_INVALID_PARAMETER; void* preferredBase = req.PreferredBase; size_t pageCount = req.PageCount; @@ -1082,47 +1030,44 @@ static uint64_t SyscallVmAlloc(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -static uint64_t SyscallVmFree(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; +// Releases one user virtual-memory allocation. +static uint64_t SyscallVmFree(void* base) { PROCESS* process = KeGetCurrentProcess(); - if (!process || !a0) return NTLSTATUS_INVALID_PARAMETER; - return MmVadDestroy(process, (void*)(uintptr_t)a0); + if (!process || !base) return NTLSTATUS_INVALID_PARAMETER; + return MmVadDestroy(process, base); } -static uint64_t SyscallVmProtect(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - if (!a0 || !a1) return NTLSTATUS_INVALID_PARAMETER; - if ((size_t)a1 > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY; +// Changes page protection for one user virtual-memory range. +static uint64_t SyscallVmProtect(void* base, size_t pageCount, uint32_t protect, uint32_t* oldProtectOut) { + if (!base || !pageCount) return NTLSTATUS_INVALID_PARAMETER; + if (pageCount > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY; uint64_t pageFlags = 0; - KSTATUS s = KiVmProtectToPageFlags((uint32_t)a2, &pageFlags); + KSTATUS s = KiVmProtectToPageFlags(protect, &pageFlags); if (!KSUCCESS(s)) return s; PROCESS* process = KeGetCurrentProcess(); if (!process) return NTLSTATUS_FAILURE; - if (a3) { + if (oldProtectOut) { void* regionBase = (void*)0; size_t regionSize = 0; uint64_t oldFlags = 0; MM_VAD_TYPE type = MmVadGeneric; uint32_t regionFlags = 0; - s = MmVadQuery(process, (void*)(uintptr_t)a0, ®ionBase, ®ionSize, &oldFlags, &type, ®ionFlags); + s = MmVadQuery(process, base, ®ionBase, ®ionSize, &oldFlags, &type, ®ionFlags); if (!KSUCCESS(s)) return s; uint32_t oldProtect = KiPageFlagsToVmProtect(oldFlags); - s = CopyToUser((void*)(uintptr_t)a3, &oldProtect, sizeof(oldProtect)); + s = CopyToUser(oldProtectOut, &oldProtect, sizeof(oldProtect)); if (!KSUCCESS(s)) return s; } - return MmVadProtect(process, (void*)(uintptr_t)a0, (size_t)a1 * PAGE_SIZE, pageFlags); + return MmVadProtect(process, base, pageCount * PAGE_SIZE, pageFlags); } -static uint64_t SyscallVmQuery(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 (!a0 || !a1) return NTLSTATUS_INVALID_PARAMETER; +// Queries the user virtual-memory region containing one address. +static uint64_t SyscallVmQuery(void* address, SYS_VM_REGION_INFO* out) { + if (!address || !out) return NTLSTATUS_INVALID_PARAMETER; PROCESS* process = KeGetCurrentProcess(); if (!process) return NTLSTATUS_FAILURE; @@ -1132,7 +1077,7 @@ static uint64_t SyscallVmQuery(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t pageFlags = 0; MM_VAD_TYPE type = MmVadGeneric; uint32_t regionFlags = 0; - KSTATUS s = MmVadQuery(process, (void*)(uintptr_t)a0, &base, &size, &pageFlags, &type, ®ionFlags); + KSTATUS s = MmVadQuery(process, address, &base, &size, &pageFlags, &type, ®ionFlags); if (!KSUCCESS(s)) return s; SYS_VM_REGION_INFO info; @@ -1142,21 +1087,19 @@ static uint64_t SyscallVmQuery(uint64_t a0, uint64_t a1, uint64_t a2, info.Flags = regionFlags; info.Type = (uint32_t)type; info.OwnerPid = process->ProcessId; - return CopyToUser((void*)(uintptr_t)a1, &info, sizeof(info)); + return CopyToUser(out, &info, sizeof(info)); } -// SYS_CREATE_MUTEX (11) a0 = *handleOut. -static uint64_t SyscallMutexCreate(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_CREATE_MUTEX. +static uint64_t SyscallMutexCreate(SYS_HANDLE* handleOut) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; MUTEX* mx = NULL; KSTATUS s = KeMutexCreate(&mx); if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&mx->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h); // takes ownership of the ref + SYS_HANDLE h = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&mx->Header, KeGetValidHandleRights(ObjectTypeMutex), &h); // takes ownership of the ref if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a0, h); + s = KiCopyHandleToUser(handleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); @@ -1164,41 +1107,37 @@ static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_TRY_ACQUIRE_MUTEX (13) a0 = handle. -static uint64_t SyscallMutexTryAcquire(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; - MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex, HANDLE_RIGHT_WAIT); - if (!mx) return NTLSTATUS_NOT_FOUND; +// Implements SYS_TRY_ACQUIRE_MUTEX. +static uint64_t SyscallMutexTryAcquire(SYS_HANDLE handle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + MUTEX* mx = (MUTEX*)KiHandleToObject(handle, ObjectTypeMutex, SYS_RIGHT_WAIT, &handleStatus); + if (!mx) return handleStatus; KSTATUS s = KeAcquireMutex(mx, 0); if (s == NTLSTATUS_TIMEOUT) s = NTLSTATUS_FAILURE; ObDereferenceObject(&mx->Header); return s; } -// SYS_RELEASE_MUTEX (14) a0 = handle. -static uint64_t SyscallMutexRelease(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; - MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex, HANDLE_RIGHT_SIGNAL); - if (!mx) return NTLSTATUS_NOT_FOUND; +// Implements SYS_RELEASE_MUTEX. +static uint64_t SyscallMutexRelease(SYS_HANDLE handle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + MUTEX* mx = (MUTEX*)KiHandleToObject(handle, ObjectTypeMutex, SYS_MUTEX_RIGHT_RELEASE, &handleStatus); + if (!mx) return handleStatus; KeReleaseMutex(mx); ObDereferenceObject(&mx->Header); return NTLSTATUS_SUCCESS; } -// SYS_CREATE_SEM (15) a0=initial a1=max a2=*handleOut. -static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a2) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_CREATE_SEM. +static uint64_t SyscallSemCreate(int32_t initial, int32_t max, SYS_HANDLE* handleOut) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; SEMAPHORE* sem = NULL; - KSTATUS s = KeSemaphoreCreate((int32_t)a0, (int32_t)a1, &sem); + KSTATUS s = KeSemaphoreCreate(initial, max, &sem); if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&sem->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h); + SYS_HANDLE h = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&sem->Header, KeGetValidHandleRights(ObjectTypeSemaphore), &h); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a2, h); + s = KiCopyHandleToUser(handleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); @@ -1206,41 +1145,38 @@ static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_TRY_WAIT_SEM (17) a0 = handle. -static uint64_t SyscallSemTryWait(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; - SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore, HANDLE_RIGHT_WAIT); - if (!sem) return NTLSTATUS_NOT_FOUND; +// Implements SYS_TRY_WAIT_SEM. +static uint64_t SyscallSemTryWait(SYS_HANDLE handle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject(handle, ObjectTypeSemaphore, SYS_RIGHT_WAIT, &handleStatus); + if (!sem) return handleStatus; KSTATUS s = KeWaitSemaphore(sem, 0); if (s == NTLSTATUS_TIMEOUT) s = NTLSTATUS_FAILURE; ObDereferenceObject(&sem->Header); return s; } -// SYS_SIGNAL_SEM (18) a0 = handle. -static uint64_t SyscallSemSignal(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; - SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore, HANDLE_RIGHT_SIGNAL); - if (!sem) return NTLSTATUS_NOT_FOUND; +// Implements SYS_SIGNAL_SEM. +static uint64_t SyscallSemSignal(SYS_HANDLE handle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject(handle, ObjectTypeSemaphore, + SYS_SEMAPHORE_RIGHT_SIGNAL, &handleStatus); + if (!sem) return handleStatus; KeSignalSemaphore(sem); ObDereferenceObject(&sem->Header); return NTLSTATUS_SUCCESS; } -// SYS_CREATE_EVENT (19) a0=type a1=initialState a2=*handleOut. -static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a2 || a0 > EventManualReset) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_CREATE_EVENT. +static uint64_t SyscallEventCreate(uint32_t type, uint32_t initialState, SYS_HANDLE* handleOut) { + if (!handleOut || type > EventManualReset) return NTLSTATUS_INVALID_PARAMETER; EVENT* ev = NULL; - KSTATUS s = KeEventCreate((EVENT_TYPE)a0, (uint32_t)a1, &ev); + KSTATUS s = KeEventCreate((EVENT_TYPE)type, initialState, &ev); if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&ev->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h); + SYS_HANDLE h = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&ev->Header, KeGetValidHandleRights(ObjectTypeEvent), &h); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a2, h); + s = KiCopyHandleToUser(handleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); @@ -1248,40 +1184,36 @@ static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SIGNAL_EVENT (20) a0 = handle. -static uint64_t SyscallEventSignal(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; - EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); - if (!ev) return NTLSTATUS_NOT_FOUND; +// Implements SYS_SIGNAL_EVENT. +static uint64_t SyscallEventSignal(SYS_HANDLE handle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + EVENT* ev = (EVENT*)KiHandleToObject(handle, ObjectTypeEvent, SYS_EVENT_RIGHT_SIGNAL, &handleStatus); + if (!ev) return handleStatus; KeSignalEvent(ev); ObDereferenceObject(&ev->Header); return NTLSTATUS_SUCCESS; } -// SYS_CLEAR_EVENT (21) a0 = handle. -static uint64_t SyscallEventClear(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; - EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); - if (!ev) return NTLSTATUS_NOT_FOUND; +// Implements SYS_CLEAR_EVENT. +static uint64_t SyscallEventClear(SYS_HANDLE handle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + EVENT* ev = (EVENT*)KiHandleToObject(handle, ObjectTypeEvent, SYS_EVENT_RIGHT_SIGNAL, &handleStatus); + if (!ev) return handleStatus; KeClearEvent(ev); ObDereferenceObject(&ev->Header); return NTLSTATUS_SUCCESS; } -// SYS_CREATE_QUEUE (23) a0 = *handleOut. -static uint64_t SyscallQueueCreate(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_CREATE_QUEUE. +static uint64_t SyscallQueueCreate(SYS_HANDLE* handleOut) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; QUEUE* q = NULL; KSTATUS s = KeQueueCreate(&q); if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &h); + SYS_HANDLE h = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&q->Header, KeGetValidHandleRights(ObjectTypeQueue), &h); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a0, h); + s = KiCopyHandleToUser(handleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); @@ -1289,41 +1221,37 @@ static uint64_t SyscallQueueCreate(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_SEND_QUEUE a0=*SYS_QUEUE_SEND_REQUEST. -static uint64_t SyscallQueueSend(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; - +// Implements SYS_SEND_QUEUE. +static uint64_t SyscallQueueSend(const SYS_QUEUE_SEND_REQUEST* request) { SYS_QUEUE_SEND_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req) || req.Flags != SYS_QUEUE_FLAG_NONE || !req.Message) + if (req.Flags != SYS_QUEUE_FLAG_NONE || !req.Message) return NTLSTATUS_INVALID_PARAMETER; QUEUE_MESSAGE msg; s = CopyFromUser(&msg, req.Message, sizeof(msg)); if (!KSUCCESS(s)) return s; - QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_WRITE); - if (!q) return NTLSTATUS_NOT_FOUND; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, SYS_QUEUE_RIGHT_SEND, &handleStatus); + if (!q) return handleStatus; s = KeSendMessage(q, &msg, req.TimeoutMilliseconds); ObDereferenceObject(&q->Header); return s; } -// SYS_RECV_QUEUE a0=*SYS_QUEUE_RECV_REQUEST. -static uint64_t SyscallQueueRecv(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; - +// Implements SYS_RECV_QUEUE. +static uint64_t SyscallQueueRecv(const SYS_QUEUE_RECV_REQUEST* request) { SYS_QUEUE_RECV_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req) || req.Flags != SYS_QUEUE_FLAG_NONE || !req.MessageOut) + if (req.Flags != SYS_QUEUE_FLAG_NONE || !req.MessageOut) return NTLSTATUS_INVALID_PARAMETER; - QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_READ); - if (!q) return NTLSTATUS_NOT_FOUND; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, SYS_QUEUE_RIGHT_RECEIVE, &handleStatus); + if (!q) return handleStatus; QUEUE_MESSAGE msg; s = KeReceiveMessage(q, &msg, req.TimeoutMilliseconds); @@ -1333,37 +1261,35 @@ static uint64_t SyscallQueueRecv(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser(req.MessageOut, &msg, sizeof(msg)); } -// SYS_CLOSE_HANDLE (26) a0 = handle +// Implements SYS_CLOSE_HANDLE. // The one and only close path. KeCloseHandle drops the handle-table reference // on 'obj'. If that was the last reference, ObDereferenceObject runs the // type-specific Destroy callback (KiFileDestroy, etc.) and frees the pool memory. // No per-type close syscall needed or wanted. -static uint64_t SyscallCreateFileMapping(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - uint32_t fileHandle = (uint32_t)a0; - uint32_t protect = (uint32_t)a1; - uint32_t flags = (uint32_t)a2; - uint64_t maximumSize = a3; - uint32_t* handleOut = (uint32_t*)(uintptr_t)a4; - - if (!fileHandle || !handleOut) +static uint64_t SyscallCreateFileMapping(const SYS_CREATE_FILE_MAPPING_REQUEST* request) { + SYS_CREATE_FILE_MAPPING_REQUEST req; + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.FileHandle || !req.MappingHandleOut) return NTLSTATUS_INVALID_PARAMETER; uint64_t pageFlags = 0; - KSTATUS s = KiVmProtectToPageFlags(protect, &pageFlags); + s = KiVmProtectToPageFlags(req.Protect, &pageFlags); if (!KSUCCESS(s)) return s; if (!pageFlags) return NTLSTATUS_ACCESS_DENIED; - uint32_t neededRights = HANDLE_RIGHT_READ | HANDLE_RIGHT_QUERY; + SYS_RIGHTS neededRights = SYS_FILE_RIGHT_MAP_READ | SYS_RIGHT_QUERY; if (pageFlags & PAGE_WRITE) - neededRights |= HANDLE_RIGHT_WRITE; + neededRights |= SYS_FILE_RIGHT_MAP_WRITE; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(fileHandle, ObjectTypeFile, neededRights); - if (!file) return NTLSTATUS_NOT_FOUND; + KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, + neededRights, &fileHandleStatus); + if (!file) return fileHandleStatus; + uint64_t maximumSize = req.MaximumSize; if (maximumSize == 0) maximumSize = IoGetFileSize(file); @@ -1375,23 +1301,23 @@ static uint64_t SyscallCreateFileMapping(uint64_t a0, uint64_t a1, uint64_t a2, } mapping->Header.Destroy = KiFileMappingDestroy; - mapping->File = file; // KiHandleToObject returned the mapping object's file reference. + mapping->File = file; mapping->MaximumSize = maximumSize; mapping->PageFlags = pageFlags; - mapping->Protect = protect; - mapping->Flags = flags; + mapping->Protect = req.Protect; + mapping->Flags = req.Flags; - uint32_t h = 0; + SYS_HANDLE handle = SYS_HANDLE_INVALID; s = KiAllocHandleForCurrentWithRights(&mapping->Header, - HANDLE_RIGHT_READ | HANDLE_RIGHT_QUERY, - &h); + KeGetValidHandleRights(ObjectTypeFileMapping), + &handle); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser((uint64_t)(uintptr_t)handleOut, h); + s = KiCopyHandleToUser(req.MappingHandleOut, handle); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); - if (proc) KeCloseHandle(proc, h); + if (proc) KeCloseHandle(proc, handle); } return s; } @@ -1431,53 +1357,49 @@ static KSTATUS KiMapAccessToPageFlags(const FILE_MAPPING_OBJECT* mapping, return NTLSTATUS_SUCCESS; } -static uint64_t SyscallMapFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - uint32_t mappingHandle = (uint32_t)a0; - uint64_t offset = a1; - size_t size = (size_t)a2; - uint32_t access = (uint32_t)a3; - uint32_t flags = (uint32_t)a4; - void** baseOut = (void**)(uintptr_t)a5; - - if (!mappingHandle || !baseOut) +// Maps a file-mapping object into the current process. +static uint64_t SyscallMapFile(const SYS_MAP_FILE_REQUEST* request) { + SYS_MAP_FILE_REQUEST req; + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.MappingHandle || !req.BaseOut) return NTLSTATUS_INVALID_PARAMETER; - if (offset & (PAGE_SIZE - 1)) + if (req.Offset & (PAGE_SIZE - 1)) return NTLSTATUS_INVALID_PARAMETER; - FILE_MAPPING_OBJECT* mapping = (FILE_MAPPING_OBJECT*)KiHandleToObject(mappingHandle, + KSTATUS mappingHandleStatus = NTLSTATUS_SUCCESS; + FILE_MAPPING_OBJECT* mapping = (FILE_MAPPING_OBJECT*)KiHandleToObject(req.MappingHandle, ObjectTypeFileMapping, - HANDLE_RIGHT_READ); + SYS_FILE_MAPPING_RIGHT_MAP, + &mappingHandleStatus); if (!mapping) - return NTLSTATUS_NOT_FOUND; + return mappingHandleStatus; uint64_t maximumSize = mapping->MaximumSize; if (maximumSize == 0) maximumSize = IoGetFileSize(mapping->File); - if (offset >= maximumSize) { + if (req.Offset >= maximumSize) { ObDereferenceObject(&mapping->Header); return NTLSTATUS_INVALID_PARAMETER; } - if (size == 0) { - uint64_t tail = maximumSize - offset; + + size_t length = req.Length; + if (length == 0) { + uint64_t tail = maximumSize - req.Offset; if (tail > (uint64_t)((size_t)-1)) { ObDereferenceObject(&mapping->Header); return NTLSTATUS_NO_MEMORY; } - size = (size_t)tail; + length = (size_t)tail; } - if (size == 0) { - ObDereferenceObject(&mapping->Header); - return NTLSTATUS_INVALID_PARAMETER; - } - if ((uint64_t)size > maximumSize - offset) { + if (length == 0 || (uint64_t)length > maximumSize - req.Offset) { ObDereferenceObject(&mapping->Header); return NTLSTATUS_INVALID_PARAMETER; } uint64_t pageFlags = 0; - KSTATUS s = KiMapAccessToPageFlags(mapping, access, &pageFlags); + s = KiMapAccessToPageFlags(mapping, req.Access, &pageFlags); if (!KSUCCESS(s)) { ObDereferenceObject(&mapping->Header); return s; @@ -1489,18 +1411,18 @@ static uint64_t SyscallMapFile(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_FAILURE; } - uint32_t vadFlags = flags; - if (access & SYS_MAP_ACCESS_WRITE) + uint32_t vadFlags = req.Flags; + if (req.Access & SYS_MAP_ACCESS_WRITE) vadFlags |= MM_VAD_SHARED_WRITE; void* base = (void*)0; - s = MmVadCreateFileMapping(process, mapping->File, offset, size, + s = MmVadCreateFileMapping(process, mapping->File, req.Offset, length, pageFlags, vadFlags, &base); ObDereferenceObject(&mapping->Header); if (!KSUCCESS(s)) return s; - s = CopyToUser(baseOut, &base, sizeof(base)); + s = CopyToUser(req.BaseOut, &base, sizeof(base)); if (!KSUCCESS(s)) { MmVadDestroy(process, base); return s; @@ -1508,30 +1430,15 @@ static uint64_t SyscallMapFile(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -static uint64_t SyscallCloseHandle(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; +// Closes one handle through the unified object handle path. +static uint64_t SyscallCloseHandle(SYS_HANDLE handle) { PROCESS* proc = KeGetCurrentProcess(); if (!proc) return NTLSTATUS_FAILURE; - - OBJECT* obj = KeLookupHandleWithRights(proc, (uint32_t)a0, 0); - if (obj) { - if (obj->Type == ObjectTypeFile) { - KSTATUS fs = IoFlushFile((FILE_OBJECT*)obj); - ObDereferenceObject(obj); - if (!KSUCCESS(fs)) - return fs; - } else { - ObDereferenceObject(obj); - } - } - - KeCloseHandle(proc, (uint32_t)a0); - return NTLSTATUS_SUCCESS; + return KeCloseHandle(proc, handle); } // Converts public file access flags to handle rights and file object flags. -static KSTATUS KiFileAccessToHandleRights(uint32_t access, uint32_t* rightsOut, uint32_t* fileFlagsOut) { +static KSTATUS KiFileAccessToHandleRights(uint32_t access, SYS_RIGHTS* rightsOut, uint32_t* fileFlagsOut) { if (!rightsOut || !fileFlagsOut) return NTLSTATUS_INVALID_PARAMETER; @@ -1543,117 +1450,201 @@ static KSTATUS KiFileAccessToHandleRights(uint32_t access, uint32_t* rightsOut, if ((access & ~valid) != 0 || access == 0) return NTLSTATUS_INVALID_PARAMETER; - uint32_t rights = 0; + SYS_RIGHTS rights = SYS_RIGHT_MANAGE; uint32_t fileFlags = 0; if (access & SYS_FILE_ACCESS_READ) - rights |= HANDLE_RIGHT_READ; + rights |= SYS_FILE_RIGHT_READ_DATA | SYS_FILE_RIGHT_MAP_READ; if (access & SYS_FILE_ACCESS_WRITE) - rights |= HANDLE_RIGHT_WRITE; + rights |= SYS_FILE_RIGHT_WRITE_DATA | SYS_FILE_RIGHT_MAP_WRITE; if (access & SYS_FILE_ACCESS_APPEND) { - rights |= HANDLE_RIGHT_WRITE; + rights |= SYS_FILE_RIGHT_WRITE_DATA | SYS_FILE_RIGHT_MAP_WRITE; fileFlags |= IO_FILE_FLAG_APPEND; } if (access & SYS_FILE_ACCESS_QUERY) - rights |= HANDLE_RIGHT_QUERY; + rights |= SYS_RIGHT_QUERY; if (access & SYS_FILE_ACCESS_LOCK) - rights |= HANDLE_RIGHT_LOCK; + rights |= SYS_FILE_RIGHT_LOCK; *rightsOut = rights; *fileFlagsOut = fileFlags; return NTLSTATUS_SUCCESS; } -// Opens a file using the public disposition contract. -static KSTATUS KiOpenFileByDisposition(SYS_STRING_VIEW path, - uint32_t disposition, - uint32_t access, - FILE_OBJECT** fileOut) { - if (!fileOut) +// Converts public directory access flags to directory capability rights. +static KSTATUS KiDirAccessToHandleRights(uint32_t access, SYS_RIGHTS* rightsOut) { + if (!rightsOut) return NTLSTATUS_INVALID_PARAMETER; - *fileOut = NULL; + + uint32_t valid = SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN | SYS_DIR_ACCESS_MODIFY; + if ((access & ~valid) != 0 || access == 0) + return NTLSTATUS_INVALID_PARAMETER; + if ((access & SYS_DIR_ACCESS_MODIFY) && !(access & SYS_DIR_ACCESS_OPEN)) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_RIGHTS rights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY; + if (access & SYS_DIR_ACCESS_ENUMERATE) + rights |= SYS_DIR_RIGHT_ENUMERATE; + if (access & SYS_DIR_ACCESS_OPEN) + rights |= SYS_DIR_RIGHT_OPEN; + if (access & SYS_DIR_ACCESS_MODIFY) + rights |= SYS_DIR_RIGHT_MODIFY; + *rightsOut = rights; + return NTLSTATUS_SUCCESS; +} + +// References one capability-relative path root with type-specific traversal authority. +static OBJECT* KiReferencePathRoot(SYS_HANDLE handle, bool requireModify, KSTATUS* statusOut) { + if (!statusOut) return (OBJECT*)0; + + KSTATUS status = NTLSTATUS_SUCCESS; + OBJECT* probe = KiHandleToObject(handle, 0, 0, &status); + if (!probe) { + *statusOut = status; + return (OBJECT*)0; + } + + OBJECT_TYPE type = probe->Type; + ObDereferenceObject(probe); + + SYS_RIGHTS required = 0; + if (type == ObjectTypeVolume) + required = SYS_VOLUME_RIGHT_OPEN | (requireModify ? SYS_VOLUME_RIGHT_MODIFY : 0); + else if (type == ObjectTypeDir) + required = SYS_DIR_RIGHT_OPEN | (requireModify ? SYS_DIR_RIGHT_MODIFY : 0); + else { + *statusOut = NTLSTATUS_INVALID_HANDLE; + return (OBJECT*)0; + } + + return KiHandleToObject(handle, type, required, statusOut); +} + +// Opens or creates one file beneath a referenced volume/directory root. +static KSTATUS KiOpenFileAtRoot(OBJECT* root, + SYS_STRING_VIEW path, + bool create, + FILE_OBJECT** fileOut) { + if (!root || !fileOut) + return NTLSTATUS_INVALID_PARAMETER; + if (root->Type == ObjectTypeVolume) { + return create + ? IoCreateFileAtVolume((IO_VOLUME*)root, path, fileOut) + : IoOpenFileAtVolume((IO_VOLUME*)root, path, fileOut); + } + if (root->Type == ObjectTypeDir) { + return create + ? IoCreateFileAtDirectory((DIR_OBJECT*)root, path, fileOut) + : IoOpenFileAtDirectory((DIR_OBJECT*)root, path, fileOut); + } + return NTLSTATUS_INVALID_HANDLE; +} + +// Opens one directory beneath a referenced volume/directory root. +static KSTATUS KiOpenDirAtRoot(OBJECT* root, SYS_STRING_VIEW path, DIR_OBJECT** dirOut) { + if (!root || !dirOut) + return NTLSTATUS_INVALID_PARAMETER; + if (root->Type == ObjectTypeVolume) + return IoOpenDirAtVolume((IO_VOLUME*)root, path, dirOut); + if (root->Type == ObjectTypeDir) + return IoOpenDirAtDirectory((DIR_OBJECT*)root, path, dirOut); + return NTLSTATUS_INVALID_HANDLE; +} + +// Opens a file beneath a capability root using the public disposition contract. +static KSTATUS KiOpenFileAtRootByDisposition(OBJECT* root, + SYS_STRING_VIEW path, + uint32_t disposition, + uint32_t access, + FILE_OBJECT** fileOut) { + if (!root || !fileOut) + return NTLSTATUS_INVALID_PARAMETER; + *fileOut = (FILE_OBJECT*)0; bool writes = (access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND)) != 0; - KSTATUS s; + KSTATUS status; switch (disposition) { case SYS_FILE_OPEN_EXISTING: - return IoOpenFile(path, fileOut); + return KiOpenFileAtRoot(root, path, false, fileOut); case SYS_FILE_CREATE_NEW: if (!writes) return NTLSTATUS_ACCESS_DENIED; - return IoCreateFile(path, fileOut); + return KiOpenFileAtRoot(root, path, true, fileOut); case SYS_FILE_CREATE_ALWAYS: if (!writes) return NTLSTATUS_ACCESS_DENIED; - s = IoCreateFile(path, fileOut); - if (s == NTLSTATUS_OBJECT_NAME_COLLISION) { - s = IoOpenFile(path, fileOut); - if (KSUCCESS(s)) { - KSTATUS ts = IoTruncateFile(*fileOut, 0); - if (!KSUCCESS(ts)) { + status = KiOpenFileAtRoot(root, path, true, fileOut); + if (status == NTLSTATUS_OBJECT_NAME_COLLISION) { + status = KiOpenFileAtRoot(root, path, false, fileOut); + if (KSUCCESS(status)) { + KSTATUS truncateStatus = IoTruncateFile(*fileOut, 0); + if (!KSUCCESS(truncateStatus)) { IoCloseFile(*fileOut); - *fileOut = NULL; - return ts; + *fileOut = (FILE_OBJECT*)0; + return truncateStatus; } } } - return s; + return status; case SYS_FILE_OPEN_ALWAYS: - s = IoOpenFile(path, fileOut); - if (s == NTLSTATUS_NOT_FOUND) { + status = KiOpenFileAtRoot(root, path, false, fileOut); + if (status == NTLSTATUS_NOT_FOUND) { if (!writes) return NTLSTATUS_ACCESS_DENIED; - s = IoCreateFile(path, fileOut); + status = KiOpenFileAtRoot(root, path, true, fileOut); } - return s; + return status; case SYS_FILE_TRUNCATE_EXISTING: if (!writes) return NTLSTATUS_ACCESS_DENIED; - s = IoOpenFile(path, fileOut); - if (KSUCCESS(s)) { - KSTATUS ts = IoTruncateFile(*fileOut, 0); - if (!KSUCCESS(ts)) { + status = KiOpenFileAtRoot(root, path, false, fileOut); + if (KSUCCESS(status)) { + KSTATUS truncateStatus = IoTruncateFile(*fileOut, 0); + if (!KSUCCESS(truncateStatus)) { IoCloseFile(*fileOut); - *fileOut = NULL; - return ts; + *fileOut = (FILE_OBJECT*)0; + return truncateStatus; } } - return s; + return status; default: return NTLSTATUS_INVALID_PARAMETER; } } -// Implements SYS_OPEN_FILE. -static uint64_t SyscallOpenFile(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_OPEN_FILE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - if (!KSUCCESS(s)) return s; +// Implements SYS_OPEN_FILE_AT. Authority comes only from the supplied root capability. +static uint64_t SyscallOpenFileAt(const SYS_OPEN_FILE_AT_REQUEST* request) { + SYS_OPEN_FILE_AT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER; if (req.Flags != SYS_FILE_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; - uint32_t rights = 0; + SYS_RIGHTS rights = 0; uint32_t fileFlags = 0; - s = KiFileAccessToHandleRights(req.Access, &rights, &fileFlags); - if (!KSUCCESS(s)) return s; + status = KiFileAccessToHandleRights(req.Access, &rights, &fileFlags); + if (!KSUCCESS(status)) return status; - char pathBuf[SYSCALL_STRING_MAX]; + char pathBuffer[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 NTLSTATUS_INVALID_PARAMETER; + status = KiImportUserTextView(req.Path, pathBuffer, sizeof(pathBuffer), &path, true); + if (!KSUCCESS(status)) return status; - FILE_OBJECT* file = NULL; - s = KiOpenFileByDisposition(path, req.Disposition, req.Access, &file); - if (!KSUCCESS(s)) return s; + bool requireModify = (req.Access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND | SYS_FILE_ACCESS_LOCK)) != 0 || + req.Disposition != SYS_FILE_OPEN_EXISTING; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + OBJECT* root = KiReferencePathRoot(req.RootHandle, requireModify, &handleStatus); + if (!root) return handleStatus; + + FILE_OBJECT* file = (FILE_OBJECT*)0; + status = KiOpenFileAtRootByDisposition(root, path, req.Disposition, req.Access, &file); + ObDereferenceObject(root); + if (!KSUCCESS(status)) return status; if (fileFlags != 0) { uintptr_t flags = KeAcquireSpinlock(&file->Lock); @@ -1661,19 +1652,176 @@ static uint64_t SyscallOpenFile(uint64_t a0, uint64_t a1, uint64_t a2, KeReleaseSpinlock(&file->Lock, flags); } - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&file->Header, rights, &h); - if (!KSUCCESS(s)) { - IoCloseFile(file); - return s; + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KiAllocHandleForCurrentWithRights(&file->Header, rights, &handle); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(req.HandleOut, handle); + if (!KSUCCESS(status)) { + PROCESS* process = KeGetCurrentProcess(); + if (process) KeCloseHandle(process, handle); + } + return status; +} + +// Implements SYS_OPEN_DIR_AT. Authority comes only from the supplied root capability. +static uint64_t SyscallOpenDirAt(const SYS_OPEN_DIR_AT_REQUEST* request) { + SYS_OPEN_DIR_AT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER; + if (req.Flags != SYS_DIR_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; + + SYS_RIGHTS dirRights = 0; + status = KiDirAccessToHandleRights(req.Access, &dirRights); + if (!KSUCCESS(status)) return status; + + char pathBuffer[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + status = KiImportUserTextView(req.Path, pathBuffer, sizeof(pathBuffer), &path, true); + if (!KSUCCESS(status)) return status; + + bool requireModify = (req.Access & SYS_DIR_ACCESS_MODIFY) != 0; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + OBJECT* root = KiReferencePathRoot(req.RootHandle, requireModify, &handleStatus); + if (!root) return handleStatus; + + DIR_OBJECT* dir = (DIR_OBJECT*)0; + status = KiOpenDirAtRoot(root, path, &dir); + ObDereferenceObject(root); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KiAllocHandleForCurrentWithRights(&dir->Header, dirRights, &handle); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(req.HandleOut, handle); + if (!KSUCCESS(status)) { + PROCESS* process = KeGetCurrentProcess(); + if (process) KeCloseHandle(process, handle); + } + return status; +} + +// Imports one capability-relative path from userspace. +static KSTATUS KiImportAtPath(SYS_STRING_VIEW userPath, char* buffer, size_t bufferSize, + bool allowEmpty, SYS_STRING_VIEW* pathOut) { + KSTATUS status = KiImportUserTextView(userPath, buffer, bufferSize, pathOut, true); + if (!KSUCCESS(status)) return status; + return (allowEmpty || pathOut->Length) ? NTLSTATUS_SUCCESS : NTLSTATUS_INVALID_PARAMETER; +} + +// Implements SYS_STAT_AT. Authority comes only from the supplied root capability. +static uint64_t SyscallStatAt(const SYS_STAT_AT_REQUEST* request) { + SYS_STAT_AT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (!req.StatOut) return NTLSTATUS_INVALID_PARAMETER; + status = MmProbeUserWrite(req.StatOut, sizeof(*req.StatOut)); + if (!KSUCCESS(status)) return status; + + char pathBuffer[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), true, &path); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + OBJECT* root = KiReferencePathRoot(req.RootHandle, false, &handleStatus); + if (!root) return handleStatus; + + SYS_FILE_STAT stat; + status = IoStatAtRoot(root, path, &stat); + ObDereferenceObject(root); + if (!KSUCCESS(status)) return status; + return CopyToUser(req.StatOut, &stat, sizeof(stat)); +} + +// Implements SYS_DELETE_AT. Authority comes only from the supplied root capability. +static uint64_t SyscallDeleteAt(const SYS_PATH_AT_REQUEST* request) { + SYS_PATH_AT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + + char pathBuffer[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), false, &path); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + OBJECT* root = KiReferencePathRoot(req.RootHandle, true, &handleStatus); + if (!root) return handleStatus; + status = IoDeleteFileAtRoot(root, path); + ObDereferenceObject(root); + return status; +} + +// Implements SYS_RENAME_AT. Both roots must grant modify authority and resolve to the same volume. +static uint64_t SyscallRenameAt(const SYS_RENAME_AT_REQUEST* request) { + SYS_RENAME_AT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + + char oldBuffer[SYSCALL_STRING_MAX]; + char newBuffer[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW oldPath; + SYS_STRING_VIEW newPath; + status = KiImportAtPath(req.OldPath, oldBuffer, sizeof(oldBuffer), false, &oldPath); + if (!KSUCCESS(status)) return status; + status = KiImportAtPath(req.NewPath, newBuffer, sizeof(newBuffer), false, &newPath); + if (!KSUCCESS(status)) return status; + + KSTATUS oldStatus = NTLSTATUS_SUCCESS; + OBJECT* oldRoot = KiReferencePathRoot(req.OldRootHandle, true, &oldStatus); + if (!oldRoot) return oldStatus; + KSTATUS newStatus = NTLSTATUS_SUCCESS; + OBJECT* newRoot = KiReferencePathRoot(req.NewRootHandle, true, &newStatus); + if (!newRoot) { + ObDereferenceObject(oldRoot); + return newStatus; } - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, h); - if (!KSUCCESS(s)) { - PROCESS* proc = KeGetCurrentProcess(); - if (proc) KeCloseHandle(proc, h); - } - return s; + status = IoRenameFileAtRoots(oldRoot, oldPath, newRoot, newPath); + ObDereferenceObject(newRoot); + ObDereferenceObject(oldRoot); + return status; +} + +// Implements SYS_CREATE_DIR_AT. Authority comes only from the supplied root capability. +static uint64_t SyscallCreateDirAt(const SYS_PATH_AT_REQUEST* request) { + SYS_PATH_AT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + + char pathBuffer[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), false, &path); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + OBJECT* root = KiReferencePathRoot(req.RootHandle, true, &handleStatus); + if (!root) return handleStatus; + status = IoCreateDirAtRoot(root, path); + ObDereferenceObject(root); + return status; +} + +// Implements SYS_REMOVE_DIR_AT. Authority comes only from the supplied root capability. +static uint64_t SyscallRemoveDirAt(const SYS_PATH_AT_REQUEST* request) { + SYS_PATH_AT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + + char pathBuffer[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), false, &path); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + OBJECT* root = KiReferencePathRoot(req.RootHandle, true, &handleStatus); + if (!root) return handleStatus; + status = IoRemoveDirAtRoot(root, path); + ObDereferenceObject(root); + return status; } static void* KiAllocateSyscallIoBuffer(size_t requested, size_t* actualOut) { @@ -1697,42 +1845,41 @@ static void* KiAllocateSyscallIoBuffer(size_t requested, size_t* actualOut) { } // Imports and validates a synchronous file I/O request. -static KSTATUS KiImportSyncFileIoRequest(uint64_t arg, SYS_FILE_IO_REQUEST* req) { - KSTATUS s = KiCopyUserRequest(arg, req, sizeof(*req)); +static KSTATUS KiImportSyncFileIoRequest(const SYS_FILE_IO_REQUEST* userRequest, SYS_FILE_IO_REQUEST* req) { + KSTATUS s = KiCopySizedUserRequest(userRequest, req, sizeof(*req)); if (!KSUCCESS(s)) return s; if (req->Flags != 0) return NTLSTATUS_INVALID_PARAMETER; - if (req->Size > SYSCALL_IO_MAX_BYTES) + if (req->Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; - if (req->Size && !req->Buffer) + if (req->Length && !req->Buffer) return NTLSTATUS_INVALID_PARAMETER; return NTLSTATUS_SUCCESS; } -// SYS_READ_FILE a0=*SYS_FILE_IO_REQUEST. -static uint64_t SyscallReadFile(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; - +// Implements SYS_READ_FILE. +static uint64_t SyscallReadFile(const SYS_FILE_IO_REQUEST* request) { SYS_FILE_IO_REQUEST req; - KSTATUS s = KiImportSyncFileIoRequest(a0, &req); + KSTATUS s = KiImportSyncFileIoRequest(request, &req); if (!KSUCCESS(s)) return s; - if (req.Size == 0) { + if (req.Length == 0) { if (req.TransferredOut) - return KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, 0); + return KiCopySizeToUser(req.TransferredOut, 0); return NTLSTATUS_SUCCESS; } - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_READ); - if (!file) return NTLSTATUS_NOT_FOUND; + KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, + SYS_FILE_RIGHT_READ_DATA, &fileHandleStatus); + if (!file) return fileHandleStatus; size_t chunkSize = 0; - void* kbuf = KiAllocateSyscallIoBuffer(req.Size, &chunkSize); + void* kbuf = KiAllocateSyscallIoBuffer(req.Length, &chunkSize); if (!kbuf) { ObDereferenceObject(&file->Header); return NTLSTATUS_NO_MEMORY; @@ -1742,8 +1889,8 @@ static uint64_t SyscallReadFile(uint64_t a0, uint64_t a1, uint64_t a2, size_t total = 0; s = NTLSTATUS_SUCCESS; - while (total < req.Size) { - size_t todo = req.Size - total; + while (total < req.Length) { + size_t todo = req.Length - total; if (todo > chunkSize) todo = chunkSize; if (user > (uintptr_t)-1 - total) { @@ -1773,33 +1920,32 @@ static uint64_t SyscallReadFile(uint64_t a0, uint64_t a1, uint64_t a2, ObDereferenceObject(&file->Header); if (req.TransferredOut) { - KSTATUS cs = KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, total); + KSTATUS cs = KiCopySizeToUser(req.TransferredOut, total); if (KSUCCESS(s)) s = cs; } return s; } -// SYS_WRITE_FILE a0=*SYS_FILE_IO_REQUEST. -static uint64_t SyscallWriteFile(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; - +// Implements SYS_WRITE_FILE. +static uint64_t SyscallWriteFile(const SYS_FILE_IO_REQUEST* request) { SYS_FILE_IO_REQUEST req; - KSTATUS s = KiImportSyncFileIoRequest(a0, &req); + KSTATUS s = KiImportSyncFileIoRequest(request, &req); if (!KSUCCESS(s)) return s; - if (req.Size == 0) { + if (req.Length == 0) { if (req.TransferredOut) - return KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, 0); + return KiCopySizeToUser(req.TransferredOut, 0); return NTLSTATUS_SUCCESS; } - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_WRITE); - if (!file) return NTLSTATUS_NOT_FOUND; + KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, + SYS_FILE_RIGHT_WRITE_DATA, &fileHandleStatus); + if (!file) return fileHandleStatus; size_t chunkSize = 0; - void* kbuf = KiAllocateSyscallIoBuffer(req.Size, &chunkSize); + void* kbuf = KiAllocateSyscallIoBuffer(req.Length, &chunkSize); if (!kbuf) { ObDereferenceObject(&file->Header); return NTLSTATUS_NO_MEMORY; @@ -1809,8 +1955,8 @@ static uint64_t SyscallWriteFile(uint64_t a0, uint64_t a1, uint64_t a2, size_t total = 0; s = NTLSTATUS_SUCCESS; - while (total < req.Size) { - size_t todo = req.Size - total; + while (total < req.Length) { + size_t todo = req.Length - total; if (todo > chunkSize) todo = chunkSize; if (user > (uintptr_t)-1 - total) { @@ -1834,7 +1980,7 @@ static uint64_t SyscallWriteFile(uint64_t a0, uint64_t a1, uint64_t a2, ObDereferenceObject(&file->Header); if (req.TransferredOut) { - KSTATUS cs = KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, total); + KSTATUS cs = KiCopySizeToUser(req.TransferredOut, total); if (KSUCCESS(s)) s = cs; } return s; @@ -1899,17 +2045,17 @@ static void SyscallUserAsyncNormalRoutine(void* normalContext, } // Implements the async File Common operation. -static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { - if (!a0) return NTLSTATUS_INVALID_PARAMETER; +static uint64_t SyscallAsyncFileCommon(const SYS_ASYNC_FILE_REQUEST* request, bool write) { + if (!request) return NTLSTATUS_INVALID_PARAMETER; SYS_ASYNC_FILE_REQUEST_K req; - KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Flags) return NTLSTATUS_INVALID_PARAMETER; if (!req.Iosb) return NTLSTATUS_INVALID_PARAMETER; - if (!req.Buffer && req.Size) return NTLSTATUS_INVALID_PARAMETER; - if (req.Size > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; + if (!req.Buffer && req.Length) return NTLSTATUS_INVALID_PARAMETER; + if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; PROCESS* proc = KeGetCurrentProcess(); if (!proc) return NTLSTATUS_FAILURE; @@ -1919,16 +2065,21 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { if (!KSUCCESS(s)) return s; } - FILE_OBJECT* file = - (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, write ? HANDLE_RIGHT_WRITE : HANDLE_RIGHT_READ); - if (!file) return NTLSTATUS_NOT_FOUND; + KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject( + req.FileHandle, ObjectTypeFile, + write ? SYS_FILE_RIGHT_WRITE_DATA : SYS_FILE_RIGHT_READ_DATA, + &fileHandleStatus); + if (!file) return fileHandleStatus; EVENT* event = (EVENT*)0; if (req.EventHandle) { - event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); + KSTATUS eventHandleStatus = NTLSTATUS_SUCCESS; + event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, + SYS_EVENT_RIGHT_SIGNAL, &eventHandleStatus); if (!event) { ObDereferenceObject(&file->Header); - return NTLSTATUS_NOT_FOUND; + return eventHandleStatus; } } @@ -1975,8 +2126,8 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { return s; } - if (req.Size) { - s = MmLockUserBuffer(proc, req.Buffer, req.Size, write ? 0 : 1, &lockedBuffer); + if (req.Length) { + s = MmLockUserBuffer(proc, req.Buffer, req.Length, write ? 0 : 1, &lockedBuffer); if (!KSUCCESS(s)) { MmUnlockUserBuffer(lockedIosb); if (apcCtx) @@ -1989,14 +2140,14 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { IO_ASYNC_FILE_REQUEST_OBJECT* asyncReq = (IO_ASYNC_FILE_REQUEST_OBJECT*)0; if (write) { - s = IoWriteFileAsyncKernel(file, req.Buffer, req.Size, req.Offset, + s = IoWriteFileAsyncKernel(file, req.Buffer, req.Length, req.Offset, (IO_STATUS_BLOCK*)req.Iosb, event, completionApc, lockedBuffer, lockedIosb, &asyncReq); } else { - s = IoReadFileAsyncKernel(file, req.Buffer, req.Size, req.Offset, + s = IoReadFileAsyncKernel(file, req.Buffer, req.Length, req.Offset, (IO_STATUS_BLOCK*)req.Iosb, event, completionApc, lockedBuffer, @@ -2007,10 +2158,11 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { lockedIosb = (MM_LOCKED_USER_BUFFER*)0; if (s == NTLSTATUS_PENDING && req.RequestHandleOut && asyncReq) { - uint32_t requestHandle = 0; - KSTATUS hs = KeAllocateHandleWithRights(proc, IoAsyncFileRequestObject(asyncReq), - HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE, - &requestHandle); + SYS_HANDLE requestHandle = SYS_HANDLE_INVALID; + KSTATUS hs = KeCreateHandle(proc, + IoAsyncFileRequestObject(asyncReq), + KeGetValidHandleRights(ObjectTypeAsyncIo), + &requestHandle); if (!KSUCCESS(hs)) { (void)IoCancelAsyncFileRequest(asyncReq); ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); @@ -2029,7 +2181,7 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { return cs; } } else if (req.RequestHandleOut) { - uint32_t zeroHandle = 0; + SYS_HANDLE zeroHandle = SYS_HANDLE_INVALID; KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle)); if (!KSUCCESS(cs)) { if (asyncReq) ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); @@ -2052,164 +2204,113 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { return s; } -// SYS_READ_FILE_ASYNC (43) a0=*SYS_ASYNC_FILE_REQUEST. -static uint64_t SyscallReadFileAsync(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; - return SyscallAsyncFileCommon(a0, false); +// Implements SYS_READ_FILE_ASYNC. +static uint64_t SyscallReadFileAsync(const SYS_ASYNC_FILE_REQUEST* request) { + return SyscallAsyncFileCommon(request, false); } -// SYS_WRITE_FILE_ASYNC (44) a0=*SYS_ASYNC_FILE_REQUEST. -static uint64_t SyscallWriteFileAsync(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; - return SyscallAsyncFileCommon(a0, true); +// Implements SYS_WRITE_FILE_ASYNC. +static uint64_t SyscallWriteFileAsync(const SYS_ASYNC_FILE_REQUEST* request) { + return SyscallAsyncFileCommon(request, true); } -// SYS_CANCEL_IO a0 = async request handle. -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_REQUEST_OBJECT* req = - (IO_ASYNC_REQUEST_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeAsyncIo, HANDLE_RIGHT_TERMINATE); - if (!req) return NTLSTATUS_INVALID_HANDLE; +// Implements SYS_CANCEL_IO. +static uint64_t SyscallCancelIo(SYS_HANDLE asyncRequestHandle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + IO_ASYNC_REQUEST_OBJECT* req = (IO_ASYNC_REQUEST_OBJECT*)KiHandleToObject( + asyncRequestHandle, ObjectTypeAsyncIo, SYS_ASYNC_IO_RIGHT_CANCEL, &handleStatus); + if (!req) return handleStatus; KSTATUS s = IoCancelAsyncRequest(req); ObDereferenceObject(IoAsyncRequestObject(req)); return s; } -// SYS_DELETE_FILE (31) a0=*path. -static uint64_t SyscallDeleteFile(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_PATH_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - 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 NTLSTATUS_INVALID_PARAMETER; - - return IoDeleteFile(path); -} - -// SYS_TRUNCATE_FILE (32) a0=handle a1=newSize. -static uint64_t SyscallTruncateFile(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; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_WRITE); - if (!file) return NTLSTATUS_NOT_FOUND; - KSTATUS s = IoTruncateFile(file, (uint64_t)a1); +// Implements SYS_TRUNCATE_FILE. +static uint64_t SyscallTruncateFile(SYS_HANDLE handle, uint64_t newSize) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, + SYS_FILE_RIGHT_WRITE_DATA, &handleStatus); + if (!file) return handleStatus; + KSTATUS s = IoTruncateFile(file, newSize); ObDereferenceObject(&file->Header); return s; } -// SYS_FLUSH_FILE a0=handle. -static uint64_t SyscallFlushFile(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; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_WRITE); - if (!file) return NTLSTATUS_NOT_FOUND; +// Implements SYS_FLUSH_FILE. +static uint64_t SyscallFlushFile(SYS_HANDLE handle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, + SYS_FILE_RIGHT_WRITE_DATA, &handleStatus); + if (!file) return handleStatus; KSTATUS s = IoFlushFile(file); ObDereferenceObject(&file->Header); return s; } -// SYS_GET_FILE_POS (33) a0=handle a1=*positionOut. -static uint64_t SyscallGetFilePos(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_GET_FILE_POS. +static uint64_t SyscallGetFilePos(SYS_HANDLE handle, uint64_t* positionOut) { + if (!positionOut) return NTLSTATUS_INVALID_PARAMETER; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); - if (!file) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus); + if (!file) return handleStatus; uintptr_t flags = KeAcquireSpinlock(&file->Lock); uint64_t pos = file->Offset; KeReleaseSpinlock(&file->Lock, flags); ObDereferenceObject(&file->Header); - return KiCopyU64ToUser(a1, pos); + return KiCopyU64ToUser(positionOut, pos); } -// SYS_SEEK_FILE (34) a0=handle a1=offset. -static uint64_t SyscallSeekFile(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; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); - if (!file) return NTLSTATUS_NOT_FOUND; - KSTATUS s = IoSeekFile(file, (uint64_t)a1); +// Implements SYS_SEEK_FILE. +static uint64_t SyscallSeekFile(SYS_HANDLE handle, uint64_t offset) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus); + if (!file) return handleStatus; + KSTATUS s = IoSeekFile(file, offset); ObDereferenceObject(&file->Header); return s; } -// SYS_GET_FILE_SIZE (35) a0=handle a1=*sizeOut. -static uint64_t SyscallGetFileSize(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_GET_FILE_SIZE. +static uint64_t SyscallGetFileSize(SYS_HANDLE handle, uint64_t* sizeOut) { + if (!sizeOut) return NTLSTATUS_INVALID_PARAMETER; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); - if (!file) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus); + if (!file) return handleStatus; uint64_t sz = IoGetFileSize(file); ObDereferenceObject(&file->Header); - return KiCopyU64ToUser(a1, sz); + return KiCopyU64ToUser(sizeOut, sz); } -// SYS_GET_FILE_STAT a0=handle a1=*SYS_FILE_STAT. -static uint64_t SyscallGetFileStat(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_GET_FILE_STAT. +static uint64_t SyscallGetFileStat(SYS_HANDLE handle, SYS_FILE_STAT* statOut) { + if (!statOut) return NTLSTATUS_INVALID_PARAMETER; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); - if (!file) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus); + if (!file) return handleStatus; SYS_FILE_STAT stat; KSTATUS s = IoGetFileStat(file, &stat); ObDereferenceObject(&file->Header); if (!KSUCCESS(s)) return s; - return CopyToUser((void*)(uintptr_t)a1, &stat, sizeof(stat)); + return CopyToUser(statOut, &stat, sizeof(stat)); } -// SYS_STAT_FILE a0=*SYS_STAT_FILE_REQUEST. -static uint64_t SyscallStatFile(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_STAT_FILE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - if (!KSUCCESS(s)) return s; - if (!req.StatOut) return NTLSTATUS_INVALID_PARAMETER; - - 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 NTLSTATUS_INVALID_PARAMETER; - - SYS_FILE_STAT stat; - s = IoStatFile(path, &stat); - if (!KSUCCESS(s)) return s; - - return CopyToUser(req.StatOut, &stat, sizeof(stat)); -} - -// SYS_OPEN_DISPLAY_CLOCK a0=*SYS_OPEN_DISPLAY_CLOCK_REQUEST. -static uint64_t SyscallOpenDisplayClock(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; - +// Implements SYS_OPEN_DISPLAY_CLOCK. +static uint64_t SyscallOpenDisplayClock(const SYS_OPEN_DISPLAY_CLOCK_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_OPEN_DISPLAY_CLOCK_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req) || req.Flags != SYS_DISPLAY_CLOCK_FLAG_NONE || !req.HandleOut) + if (req.Flags != SYS_DISPLAY_CLOCK_FLAG_NONE || !req.HandleOut) return NTLSTATUS_INVALID_PARAMETER; DISPLAY_CLOCK_OBJECT* clock = (DISPLAY_CLOCK_OBJECT*)0; @@ -2217,12 +2318,12 @@ static uint64_t SyscallOpenDisplayClock(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; - uint32_t handle = 0; - s = KiAllocHandleForCurrentWithRights(&clock->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY, &handle); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&clock->Header, KeGetValidHandleRights(ObjectTypeDisplayClock), &handle); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle); + s = KiCopyHandleToUser(req.HandleOut, handle); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, handle); @@ -2230,15 +2331,11 @@ static uint64_t SyscallOpenDisplayClock(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_CREATE_SURFACE a0=*SYS_CREATE_SURFACE_REQUEST. -static uint64_t SyscallCreateSurface(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; - +// Implements SYS_CREATE_SURFACE. +static uint64_t SyscallCreateSurface(const SYS_CREATE_SURFACE_REQUEST* request) { SYS_CREATE_SURFACE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req)) return NTLSTATUS_INVALID_PARAMETER; uint32_t width = req.Width; uint32_t height = req.Height; @@ -2311,8 +2408,8 @@ static uint64_t SyscallCreateSurface(uint64_t a0, uint64_t a1, uint64_t a2, } surface->KernelBase = MmKernelViewBase(surface->KernelView); - uint32_t handle = 0; - s = KiAllocHandleForCurrentWithRights(&surface->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &handle); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&surface->Header, KeGetValidHandleRights(ObjectTypeSurface), &handle); if (!KSUCCESS(s)) return s; @@ -2326,16 +2423,16 @@ static uint64_t SyscallCreateSurface(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -// SYS_MAP_SURFACE (100) a0=surfaceHandle a1=*baseOut a2=*pitchBytesOut. -static uint64_t SyscallMapSurface(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a1 || !a2) +// Implements SYS_MAP_SURFACE. +static uint64_t SyscallMapSurface(SYS_HANDLE surfaceHandle, void** baseOut, uint32_t* pitchBytesOut) { + if (!baseOut || !pitchBytesOut) return NTLSTATUS_INVALID_PARAMETER; - SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSurface, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject(surfaceHandle, ObjectTypeSurface, + SYS_SURFACE_RIGHT_MAP, &handleStatus); if (!surface) - return NTLSTATUS_INVALID_PARAMETER; + return handleStatus; PROCESS* current = KeGetCurrentProcess(); if (surface->Owner != current) { @@ -2356,9 +2453,9 @@ static uint64_t SyscallMapSurface(uint64_t a0, uint64_t a1, uint64_t a2, KeReleaseMutex(surface->Lock); if (KSUCCESS(s)) - s = CopyToUser((void*)(uintptr_t)a1, &userBase, sizeof(userBase)); + s = CopyToUser(baseOut, &userBase, sizeof(userBase)); if (KSUCCESS(s)) - s = CopyToUser((void*)(uintptr_t)a2, &pitchBytes, sizeof(pitchBytes)); + s = CopyToUser(pitchBytesOut, &pitchBytes, sizeof(pitchBytes)); if (KSUCCESS(s)) KiSurfaceStatsRecordMap(surface); @@ -2366,16 +2463,16 @@ static uint64_t SyscallMapSurface(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_QUERY_SURFACE (101) a0=surfaceHandle a1=*SYS_SURFACE_DESC. -static uint64_t SyscallQuerySurface(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) +// Implements SYS_QUERY_SURFACE. +static uint64_t SyscallQuerySurface(SYS_HANDLE surfaceHandle, SYS_SURFACE_DESC* descOut) { + if (!descOut) return NTLSTATUS_INVALID_PARAMETER; - SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSurface, HANDLE_RIGHT_QUERY); + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject(surfaceHandle, ObjectTypeSurface, + SYS_RIGHT_QUERY, &handleStatus); if (!surface) - return NTLSTATUS_INVALID_PARAMETER; + return handleStatus; if (surface->Owner != KeGetCurrentProcess()) { ObDereferenceObject(&surface->Header); @@ -2406,7 +2503,7 @@ static uint64_t SyscallQuerySurface(uint64_t a0, uint64_t a1, uint64_t a2, desc.PresentBytesCopied = surface->PresentBytesCopied; KeReleaseSpinlock(&gSurfaceStatsLock, statsFlags); - s = CopyToUser((void*)(uintptr_t)a1, &desc, sizeof(desc)); + s = CopyToUser(descOut, &desc, sizeof(desc)); } ObDereferenceObject(&surface->Header); @@ -2446,20 +2543,21 @@ static int KiClipSurfaceRect(const SURFACE_OBJECT* surface, const DISPLAY_RECT* return 1; } -// SYS_PRESENT_SURFACE (102) a0=surfaceHandle a1=*DISPLAY_RECT[] a2=rectCount. -static uint64_t SyscallPresentSurface(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - uint32_t rectCount = (uint32_t)a2; +// Implements SYS_PRESENT_SURFACE. +static uint64_t SyscallPresentSurface(SYS_HANDLE surfaceHandle, const DISPLAY_RECT* userRects, uint32_t rectCount) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; if (rectCount > SYS_SURFACE_MAX_RECTS) return NTLSTATUS_INVALID_PARAMETER; - if (rectCount != 0 && !a1) + if (rectCount != 0 && !userRects) return NTLSTATUS_INVALID_PARAMETER; - SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSurface, HANDLE_RIGHT_READ); + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject(surfaceHandle, ObjectTypeSurface, + SYS_SURFACE_RIGHT_PRESENT, &handleStatus); if (!surface) - return NTLSTATUS_INVALID_PARAMETER; + return handleStatus; if (surface->Owner != KeGetCurrentProcess()) { ObDereferenceObject(&surface->Header); return NTLSTATUS_ACCESS_DENIED; @@ -2473,14 +2571,14 @@ static uint64_t SyscallPresentSurface(uint64_t a0, uint64_t a1, uint64_t a2, rects[0].H = (int32_t)surface->Height; rectCount = 1; } else { - KSTATUS cs = CopyFromUser(rects, (const void*)(uintptr_t)a1, (size_t)rectCount * sizeof(DISPLAY_RECT)); + KSTATUS cs = CopyFromUser(rects, userRects, (size_t)rectCount * sizeof(DISPLAY_RECT)); if (!KSUCCESS(cs)) { ObDereferenceObject(&surface->Header); return cs; } } - uint64_t inputRects = (uint64_t)a2; + uint64_t inputRects = (uint64_t)rectCount; uint64_t clippedRects = 0; uint64_t emptyClips = 0; uint64_t rowsCopied = 0; @@ -2516,44 +2614,51 @@ static uint64_t SyscallPresentSurface(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_CLEAR_SCREEN (67) a0=color. -static uint64_t SyscallClearScreen(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; - return KeDisplayClear((uint32_t)a0); +// Implements SYS_CLEAR_SCREEN. +static uint64_t SyscallClearScreen(uint32_t color) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + return KeDisplayClear(color); } -// SYS_GET_SCREEN_SIZE (68) a0=*widthOut a1=*heightOut. -static uint64_t SyscallGetScreenSize(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; +// Implements SYS_GET_SCREEN_SIZE. +static uint64_t SyscallGetScreenSize(int* widthOut, int* heightOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; int w = 0, h = 0; KSTATUS gs = KeDisplayGetSize(&w, &h); if (!KSUCCESS(gs)) return gs; - if (a0) { - KSTATUS s = KiCopyIntToUser(a0, w); + if (widthOut) { + KSTATUS s = KiCopyIntToUser(widthOut, w); if (!KSUCCESS(s)) return s; } - if (a1) { - KSTATUS s = KiCopyIntToUser(a1, h); + if (heightOut) { + KSTATUS s = KiCopyIntToUser(heightOut, h); if (!KSUCCESS(s)) return s; } return NTLSTATUS_SUCCESS; } -// SYS_PUT_SCREEN_CHAR (65) a0=c a1=x a2=y a3=fg a4=bg. -static uint64_t SyscallPutScreenChar(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - return KeDisplayPutChar((char)a0, (int)a1, (int)a2, - (uint32_t)a3, (uint32_t)a4); +// Implements SYS_PUT_SCREEN_CHAR. +static uint64_t SyscallPutScreenChar(const SYS_PUT_SCREEN_CHAR_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + SYS_PUT_SCREEN_CHAR_REQUEST req; + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2]) + return NTLSTATUS_INVALID_PARAMETER; + if (req.X < 0 || req.Y < 0) + return NTLSTATUS_INVALID_PARAMETER; + return KeDisplayPutChar(req.Character, req.X, req.Y, + req.Foreground, req.Background); } -// SYS_GET_CONSOLE_INFO (61) a0=*SYS_CONSOLE_INFO. -static uint64_t SyscallGetConsoleInfo(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 NTLSTATUS_INVALID_PARAMETER; +// Returns the current console geometry and cursor state. +static uint64_t SyscallGetConsoleInfo(SYS_CONSOLE_INFO* infoOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!infoOut) return NTLSTATUS_INVALID_PARAMETER; KE_CONSOLE_INFO ki; KSTATUS s = KeDisplayGetConsoleInfo(&ki); @@ -2566,61 +2671,70 @@ static uint64_t SyscallGetConsoleInfo(uint64_t a0, uint64_t a1, uint64_t a2, out.CursorX = ki.CursorX; out.CursorY = ki.CursorY; out.Attribute = ki.Attribute; - return CopyToUser((void*)(uintptr_t)a0, &out, sizeof(out)); + return CopyToUser(infoOut, &out, sizeof(out)); } -// SYS_SET_CONSOLE_CURSOR (62) a0=column a1=row. -static uint64_t SyscallSetConsoleCursor(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; - int col = (int)(int32_t)a0; - int row = (int)(int32_t)a1; +// Implements SYS_SET_CONSOLE_CURSOR. +static uint64_t SyscallSetConsoleCursor(int x, int y) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + int col = (int)x; + int row = (int)y; if (col < 0 || row < 0) return NTLSTATUS_INVALID_PARAMETER; return KeDisplaySetConsoleCursor(col, row); } -// SYS_FILL_CONSOLE_CHARACTER (63) a0=char a1=column a2=row a3=count a4=*writtenOut. -static uint64_t SyscallFillConsoleCharacter(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - int col = (int)(int32_t)a1; - int row = (int)(int32_t)a2; - if (col < 0 || row < 0) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_FILL_CONSOLE_CHARACTER. +static uint64_t SyscallFillConsoleCharacter(const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + SYS_FILL_CONSOLE_CHARACTER_REQUEST req; + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2]) + return NTLSTATUS_INVALID_PARAMETER; + if (req.X < 0 || req.Y < 0) + return NTLSTATUS_INVALID_PARAMETER; size_t written = 0; - KSTATUS s = KeDisplayFillConsoleChars((char)a0, col, row, (size_t)a3, &written); - if (a4) { - KSTATUS cs = KiCopySizeToUser(a4, written); - if (KSUCCESS(s) && !KSUCCESS(cs)) s = cs; + s = KeDisplayFillConsoleChars(req.Character, req.X, req.Y, req.Length, &written); + if (req.WrittenOut) { + KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written); + if (KSUCCESS(s) && !KSUCCESS(copyStatus)) s = copyStatus; } return s; } -// SYS_FILL_CONSOLE_ATTRIBUTE (64) a0=attribute a1=column a2=row a3=count a4=*writtenOut. -static uint64_t SyscallFillConsoleAttribute(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - int col = (int)(int32_t)a1; - int row = (int)(int32_t)a2; - if (col < 0 || row < 0) return NTLSTATUS_INVALID_PARAMETER; +// Fills console attributes over one linear character range. +static uint64_t SyscallFillConsoleAttribute(const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST req; + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Reserved != 0) + return NTLSTATUS_INVALID_PARAMETER; + if (req.X < 0 || req.Y < 0) + return NTLSTATUS_INVALID_PARAMETER; size_t written = 0; - KSTATUS s = KeDisplayFillConsoleAttrs((uint16_t)a0, col, row, (size_t)a3, &written); - if (a4) { - KSTATUS cs = KiCopySizeToUser(a4, written); - if (KSUCCESS(s) && !KSUCCESS(cs)) s = cs; + s = KeDisplayFillConsoleAttrs(req.Attribute, req.X, req.Y, req.Length, &written); + if (req.WrittenOut) { + KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written); + if (KSUCCESS(s) && !KSUCCESS(copyStatus)) s = copyStatus; } return s; } -// References a user input queue handle. Handle zero selects the default context. -static KSTATUS KiReferenceInputQueueHandle(uint32_t handle, INPUT_QUEUE** queueOut) { +static KSTATUS KiReferenceInputQueueHandle(SYS_HANDLE handle, INPUT_QUEUE** queueOut) { if (!queueOut) return NTLSTATUS_INVALID_PARAMETER; *queueOut = (INPUT_QUEUE*)0; if (handle == 0) return NTLSTATUS_SUCCESS; - OBJECT* object = KiHandleToObject(handle, ObjectTypeInputQueue, HANDLE_RIGHT_READ); - if (!object) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + OBJECT* object = KiHandleToObject(handle, ObjectTypeInputQueue, + SYS_INPUT_QUEUE_RIGHT_READ, &handleStatus); + if (!object) return handleStatus; *queueOut = CONTAINING_RECORD(object, INPUT_QUEUE, Header); return NTLSTATUS_SUCCESS; } @@ -2632,47 +2746,46 @@ static KSTATUS KiCopyInputRecordToUser(INPUT_KEY_RECORD* userRecord, const INPUT } // Imports and validates a keyboard input event request. -static KSTATUS KiCopyInputEventRequest(uint64_t arg, SYS_INPUT_EVENT_REQUEST* req) { - KSTATUS s = KiCopyUserRequest(arg, req, sizeof(*req)); +static KSTATUS KiCopyInputEventRequest(const SYS_INPUT_EVENT_REQUEST* userRequest, SYS_INPUT_EVENT_REQUEST* req) { + KSTATUS s = KiCopySizedUserRequest(userRequest, req, sizeof(*req)); if (!KSUCCESS(s)) return s; - if (req->Size != sizeof(*req)) return NTLSTATUS_INVALID_PARAMETER; if (req->Flags != SYS_INPUT_EVENT_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; if (!req->RecordOut) return NTLSTATUS_INVALID_PARAMETER; return NTLSTATUS_SUCCESS; } -// SYS_POLL_KEYBOARD a0=*INPUT_KEY -> KSTATUS. -static uint64_t SyscallKbPoll(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_POLL_KEYBOARD. +static uint64_t SyscallKbPoll(INPUT_KEY* keyOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!keyOut) return NTLSTATUS_INVALID_PARAMETER; INPUT_KEY key = KeInputKbPoll(); - KSTATUS s = CopyToUser((void*)(uintptr_t)a0, &key, sizeof(key)); + KSTATUS s = CopyToUser(keyOut, &key, sizeof(key)); if (!KSUCCESS(s)) return s; return (key == INPUT_KEY_NONE) ? NTLSTATUS_WOULD_BLOCK : NTLSTATUS_SUCCESS; } -// SYS_IS_KEY_DOWN a0=INPUT_KEY a1=*bool -> KSTATUS. -static uint64_t SyscallKbIsKeyDown(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_IS_KEY_DOWN. +static uint64_t SyscallKbIsKeyDown(INPUT_KEY key, bool* downOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!downOut) return NTLSTATUS_INVALID_PARAMETER; - bool down = KeInputIsKeyDown((INPUT_KEY)a0); - return CopyToUser((void*)(uintptr_t)a1, &down, sizeof(down)); + bool down = KeInputIsKeyDown((INPUT_KEY)key); + return CopyToUser(downOut, &down, sizeof(down)); } -// SYS_READ_KEYBOARD_CHAR a0=*char -> KSTATUS. -static uint64_t SyscallKbReadChar(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_READ_KEYBOARD_CHAR. +static uint64_t SyscallKbReadChar(char* charOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!charOut) return NTLSTATUS_INVALID_PARAMETER; while (true) { char ch = KeInputReadChar(); if (ch) - return CopyToUser((void*)(uintptr_t)a0, &ch, sizeof(ch)); + return CopyToUser(charOut, &ch, sizeof(ch)); KSTATUS s = KeInputWaitKey(0, SYS_WAIT_TIMEOUT_INFINITE); if (!KSUCCESS(s)) @@ -2680,16 +2793,16 @@ static uint64_t SyscallKbReadChar(uint64_t a0, uint64_t a1, uint64_t a2, } } -// SYS_READ_KEY a0=*INPUT_KEY -> KSTATUS. -static uint64_t SyscallKbReadKey(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_READ_KEY. +static uint64_t SyscallKbReadKey(INPUT_KEY* keyOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!keyOut) return NTLSTATUS_INVALID_PARAMETER; while (true) { INPUT_KEY key = KeInputReadKey(); if (key != INPUT_KEY_NONE) - return CopyToUser((void*)(uintptr_t)a0, &key, sizeof(key)); + return CopyToUser(keyOut, &key, sizeof(key)); KSTATUS s = KeInputWaitKey(0, SYS_WAIT_TIMEOUT_INFINITE); if (!KSUCCESS(s)) @@ -2698,36 +2811,35 @@ static uint64_t SyscallKbReadKey(uint64_t a0, uint64_t a1, uint64_t a2, } // SYS_GET_KEY_MODIFIERS -> uint32_t INPUT_MOD_* flags. -static uint64_t SyscallKbGetModifiers(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; +static uint64_t SyscallKbGetModifiers(void) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; return (uint64_t)KeInputGetModifiers(); } -// SYS_QUERY_GLOBAL_INPUT_STATE a0=*SYS_GLOBAL_INPUT_STATE -> KSTATUS. -static uint64_t SyscallQueryGlobalInputState(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; - +// Implements SYS_QUERY_GLOBAL_INPUT_STATE. +static uint64_t SyscallQueryGlobalInputState(SYS_GLOBAL_INPUT_STATE* stateOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_GLOBAL_INPUT_STATE state; - KSTATUS s = KiCopyUserRequest(a0, &state, sizeof(state)); + KSTATUS s = KiCopySizedUserRequest(stateOut, &state, sizeof(state)); if (!KSUCCESS(s)) return s; - if (state.Size != sizeof(state)) return NTLSTATUS_INVALID_PARAMETER; if (state.Flags != SYS_GLOBAL_INPUT_STATE_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; s = KeInputQueryGlobalState(&state); if (!KSUCCESS(s)) return s; - return CopyToUser((void*)(uintptr_t)a0, &state, sizeof(state)); + return CopyToUser(stateOut, &state, sizeof(state)); } -// SYS_READ_INPUT_EVENT a0=*SYS_INPUT_EVENT_REQUEST -> KSTATUS. -static uint64_t SyscallKbReadInputEvent(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; - +// Implements SYS_READ_INPUT_EVENT. +static uint64_t SyscallKbReadInputEvent(const SYS_INPUT_EVENT_REQUEST* request) { SYS_INPUT_EVENT_REQUEST req; - KSTATUS s = KiCopyInputEventRequest(a0, &req); + KSTATUS s = KiCopyInputEventRequest(request, &req); if (!KSUCCESS(s)) return s; + if (req.QueueHandle == SYS_HANDLE_INVALID) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + } INPUT_QUEUE* queue = (INPUT_QUEUE*)0; s = KiReferenceInputQueueHandle(req.QueueHandle, &queue); @@ -2757,14 +2869,15 @@ static uint64_t SyscallKbReadInputEvent(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_PEEK_INPUT_EVENT a0=*SYS_INPUT_EVENT_REQUEST -> KSTATUS. -static uint64_t SyscallKbPeekInputEvent(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; - +// Implements SYS_PEEK_INPUT_EVENT. +static uint64_t SyscallKbPeekInputEvent(const SYS_INPUT_EVENT_REQUEST* request) { SYS_INPUT_EVENT_REQUEST req; - KSTATUS s = KiCopyInputEventRequest(a0, &req); + KSTATUS s = KiCopyInputEventRequest(request, &req); if (!KSUCCESS(s)) return s; + if (req.QueueHandle == SYS_HANDLE_INVALID) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + } INPUT_QUEUE* queue = (INPUT_QUEUE*)0; s = KiReferenceInputQueueHandle(req.QueueHandle, &queue); @@ -2794,78 +2907,71 @@ static uint64_t SyscallKbPeekInputEvent(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_PEEK_MOUSE a0=*INPUT_MOUSE_EVENT -> KSTATUS. -static uint64_t SyscallMousePeek(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_PEEK_MOUSE. +static uint64_t SyscallMousePeek(INPUT_MOUSE_EVENT* eventOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!eventOut) return NTLSTATUS_INVALID_PARAMETER; INPUT_MOUSE_EVENT event; if (!KeInputMousePeek(&event)) return NTLSTATUS_WOULD_BLOCK; - return CopyToUser((void*)(uintptr_t)a0, &event, sizeof(event)); + return CopyToUser(eventOut, &event, sizeof(event)); } -// SYS_READ_MOUSE a0=*INPUT_MOUSE_EVENT -> KSTATUS. -static uint64_t SyscallMouseRead(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_READ_MOUSE. +static uint64_t SyscallMouseRead(INPUT_MOUSE_EVENT* eventOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!eventOut) return NTLSTATUS_INVALID_PARAMETER; INPUT_MOUSE_EVENT event; if (!KeInputMouseRead(&event)) return NTLSTATUS_WOULD_BLOCK; - return CopyToUser((void*)(uintptr_t)a0, &event, sizeof(event)); + return CopyToUser(eventOut, &event, sizeof(event)); } -// SYS_GET_MOUSE_POS a0=*x a1=*y -> KSTATUS. -static uint64_t SyscallMouseGetPos(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 (!a0 || !a1) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_GET_MOUSE_POS. +static uint64_t SyscallMouseGetPos(int* xOut, int* yOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!xOut || !yOut) return NTLSTATUS_INVALID_PARAMETER; int x = 0, y = 0; KeInputMouseGetPosition(&x, &y); - KSTATUS s = KiCopyIntToUser(a0, x); + KSTATUS s = KiCopyIntToUser(xOut, x); if (!KSUCCESS(s)) return s; - return KiCopyIntToUser(a1, y); + return KiCopyIntToUser(yOut, y); } -// SYS_GET_TICK (75) returns monotonic ticks since boot. -static uint64_t SyscallGetTick(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; +// Returns monotonic ticks since boot. +static uint64_t SyscallGetTick(void) { return gHal ? gHal->Timer.QueryTicks() : 0; } -// SYS_GET_TICK_FREQUENCY (110) copies SysGetTick ticks per second. -static uint64_t SyscallGetTickFrequency(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; +// Copies the frequency used by SysGetTick. +static uint64_t SyscallGetTickFrequency(uint64_t* frequencyOut) { if (!gHal || !gHal->Timer.QueryTickFrequency) return NTLSTATUS_DEVICE_ERROR; uint64_t frequency = gHal->Timer.QueryTickFrequency(); if (!frequency) return NTLSTATUS_DEVICE_ERROR; - return KiCopyU64ToUser(a0, frequency); + return KiCopyU64ToUser(frequencyOut, frequency); } -// SYS_GET_SYSTEM_TIME a0=*SYS_SYSTEM_TIME. -static uint64_t SyscallGetSystemTime(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_GET_SYSTEM_TIME. +static uint64_t SyscallGetSystemTime(SYS_SYSTEM_TIME* timeOut) { + if (!timeOut) return NTLSTATUS_INVALID_PARAMETER; SYS_SYSTEM_TIME t; KSTATUS s = KeQuerySystemTime(&t); if (!KSUCCESS(s)) return s; - return CopyToUser((void*)(uintptr_t)a0, &t, sizeof(t)); + return CopyToUser(timeOut, &t, sizeof(t)); } -// SYS_PRINT_DEBUG (79) a0=*str. -static uint64_t SyscallDebugPrint(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; - +// Implements SYS_PRINT_DEBUG. +static uint64_t SyscallDebugPrint(const SYS_PRINT_DEBUG_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DEBUG_OUTPUT); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_PRINT_DEBUG_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Text.Length == 0) return NTLSTATUS_SUCCESS; if (!req.Text.Data) return NTLSTATUS_INVALID_PARAMETER; @@ -2884,181 +2990,347 @@ static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -// SYS_QUERY_TRAP_FRAME a0=*SYS_TRAP_FRAME_INFO. -static uint64_t SyscallQueryTrapFrame(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_QUERY_TRAP_FRAME. +static uint64_t SyscallQueryTrapFrame(SYS_TRAP_FRAME_INFO* infoOut) { + if (!infoOut) return NTLSTATUS_INVALID_PARAMETER; uint32_t userSize = 0; - KSTATUS s = CopyFromUser(&userSize, (const void*)(uintptr_t)a0, sizeof(userSize)); + KSTATUS s = CopyFromUser(&userSize, infoOut, sizeof(userSize)); if (!KSUCCESS(s)) return s; - SYS_TRAP_FRAME_INFO_K info; - memset(&info, 0, sizeof(info)); - info.Size = userSize; - s = KeQueryCurrentTrapFrame(&info); + SYS_TRAP_FRAME_INFO_K trapInfo; + memset(&trapInfo, 0, sizeof(trapInfo)); + trapInfo.Size = userSize; + s = KeQueryCurrentTrapFrame(&trapInfo); - size_t copySize = (s == NTLSTATUS_BUFFER_TOO_SMALL) ? sizeof(info.Size) : sizeof(info); - KSTATUS cs = CopyToUser((void*)(uintptr_t)a0, &info, copySize); + size_t copySize = (s == NTLSTATUS_BUFFER_TOO_SMALL) ? sizeof(trapInfo.Size) : sizeof(trapInfo); + KSTATUS cs = CopyToUser(infoOut, &trapInfo, copySize); if (!KSUCCESS(cs)) return cs; return s; } -// SYS_CREATE_PROCESS (50) a0=*path a1=*handleOut a2=*cmdline (optional). -static uint64_t SyscallCreateProcess(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; +// Begins one process-construction memory operation without racing the start transition. +static KSTATUS KiBeginProcessBuildOperation(PROCESS* process) { + if (!process) return NTLSTATUS_INVALID_PARAMETER; + for (;;) { + if (_InterlockedCompareExchange(&process->BuildState, 0, 0) != KE_PROCESS_BUILD_CONSTRUCTING) + return NTLSTATUS_ACCESS_DENIED; + _InterlockedIncrement(&process->BuildOperations); + if (_InterlockedCompareExchange(&process->BuildState, 0, 0) == KE_PROCESS_BUILD_CONSTRUCTING) + return NTLSTATUS_SUCCESS; + _InterlockedDecrement(&process->BuildOperations); + } +} - SYS_CREATE_PROCESS_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - if (!KSUCCESS(s)) return s; - if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER; +// Ends one process-construction memory operation. +static void KiEndProcessBuildOperation(PROCESS* process) { + if (!process) return; + long remaining = _InterlockedDecrement(&process->BuildOperations); + if (remaining == 0 && KeIsProcessTerminating(process)) + (void)KeTerminateProcess(process, process->ExitCode); +} - char pathBuf[SYSCALL_STRING_MAX]; - char cmdlineBuf[SYSCALL_STRING_MAX]; - SYS_STRING_VIEW path; - SYS_STRING_VIEW cmdlineView; - - s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); - if (!KSUCCESS(s)) return s; - if (path.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - - s = KiImportUserTextView(req.CommandLine, cmdlineBuf, sizeof(cmdlineBuf), &cmdlineView, true); - if (!KSUCCESS(s)) return s; - s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t)); - if (!KSUCCESS(s)) return s; - - // Read the executable from disk. - FILE_OBJECT* f; - ProcDbg("CreateProcess: path='%v' cmd='%v' callerPid=%u\n", path, cmdlineView, KeGetCurrentProcess() ? KeGetCurrentProcess()->ProcessId : 0); - s = IoOpenFile(path, &f); - if (!KSUCCESS(s)) { ProcDbg("CreateProcess: IoOpenFile failed st=%d\n", s); return s; } - - uint64_t size = IoGetFileSize(f); - ProcDbg("CreateProcess: fileSize=%llu\n", size); - - // Create the process object. - PROCESS* proc; - s = KeCreateProcess(pathBuf, &proc); - if (!KSUCCESS(s)) { ProcDbg("CreateProcess: KeCreateProcess failed st=%d\n", s); IoCloseFile(f); return s; } - ProcDbg("CreateProcess: created pid=%u name=%s\n", proc->ProcessId, proc->Name); - - // Inherit CWD from parent. +// Copies the caller-selected security context for one child process. +static KSTATUS KiReferenceChildSecurityContext(SYS_HANDLE contextHandle, SECURITY_CONTEXT** contextOut) { + if (!contextOut) return NTLSTATUS_INVALID_PARAMETER; + *contextOut = (SECURITY_CONTEXT*)0; PROCESS* parent = KeGetCurrentProcess(); - if (parent) { - uintptr_t flags = KeAcquireSpinlock(&parent->CwdLock); - size_t cwdLen = parent->CurrentDirectoryLength; - if (cwdLen >= sizeof(proc->CurrentDirectory)) cwdLen = sizeof(proc->CurrentDirectory) - 1; - memcpy(proc->CurrentDirectory, parent->CurrentDirectory, cwdLen); - proc->CurrentDirectory[cwdLen] = '\0'; - proc->CurrentDirectoryLength = cwdLen; - proc->CurrentDrive = parent->CurrentDrive; - KeReleaseSpinlock(&parent->CwdLock, flags); + if (!parent || !parent->SecurityContext) return NTLSTATUS_FAILURE; + if (contextHandle == SYS_SECURITY_CONTEXT_CURRENT) { + *contextOut = parent->SecurityContext; + ObReferenceObject(&(*contextOut)->Header); + return NTLSTATUS_SUCCESS; } - - s = KeSetupUserProcessEnvironment(proc, - path, - cmdlineView, - SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength)); - if (!KSUCCESS(s)) { ProcDbg("CreateProcess: env setup failed st=%d\n", s); IoCloseFile(f); ObDereferenceObject(&proc->Header); return s; } - - // Map the PE image into memory. - uintptr_t imageBase = 0; - s = LdrLoadImageFileForProcess(proc, f, &imageBase); - IoCloseFile(f); - if (!KSUCCESS(s)) { ProcDbg("CreateProcess: LdrLoadImageFileForProcess failed st=%d\n", s); ObDereferenceObject(&proc->Header); return s; } - ProcDbg("CreateProcess: image mapped pid=%u base=%016llx size=%llx\n", proc->ProcessId, (unsigned long long)imageBase, (unsigned long long)LdrGetMappedImageSize(proc, imageBase)); - - s = LdrResolveImportsForProcess(proc, imageBase); - if (!KSUCCESS(s)) { - ProcDbg("CreateProcess: resolve imports failed st=%d pid=%u image=%016llx\n", s, proc->ProcessId, (unsigned long long)imageBase); - MmVadDebugDumpProcess(proc); - ObDereferenceObject(&proc->Header); - return s; - } - ProcDbg("CreateProcess: imports resolved pid=%u modules=%u\n", proc->ProcessId, proc->ModuleCount); - - s = LdrProtectImageForProcess(proc, imageBase, true); - if (!KSUCCESS(s)) { - ProcDbg("CreateProcess: protect image failed st=%d pid=%u\n", s, proc->ProcessId); - MmVadDebugDumpProcess(proc); - ObDereferenceObject(&proc->Header); - return s; - } - - proc->ImageBase = imageBase; - proc->ImageSize = LdrGetMappedImageSize(proc, imageBase); - - PROCESS* caller = KeGetCurrentProcess(); - if (!caller) { - ObDereferenceObject(&proc->Header); - return NTLSTATUS_FAILURE; - } - - uint32_t handle = 0; - s = KeAllocateHandleWithRights(caller, &proc->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE, &handle); - if (!KSUCCESS(s)) { - proc->ImageBase = 0; - proc->ImageSize = 0; - ObDereferenceObject(&proc->Header); - return s; - } - - THREAD* thread; - uintptr_t entryPoint = LdrGetEntryPoint(imageBase); - const KE_THREAD_CREATE_PARAMETERS threadParameters = KE_THREAD_CREATE_NORMAL(PriorityNormal); - s = KeCreateThread(proc, entryPoint, 0, &threadParameters, &thread); - if (!KSUCCESS(s)) { - ProcDbg("CreateProcess: KeCreateThread failed st=%d pid=%u entry=%016llx\n", s, proc->ProcessId, (unsigned long long)entryPoint); - MmVadDebugDumpProcess(proc); - KeCloseHandle(caller, handle); - ObDereferenceObject(&proc->Header); - return s; - } - uint32_t tid = thread->ThreadId; - ProcDbg("CreateProcess: thread created pid=%u tid=%llu entry=%016llx handle=%u\n", proc->ProcessId, (unsigned long long)tid, (unsigned long long)entryPoint, handle); - ObDereferenceObject(&thread->Header); - - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle); - if (!KSUCCESS(s)) { - KeTerminateProcess(proc, (int)NTLSTATUS_PROCESS_PAGE_FAULT); - KeCloseHandle(caller, handle); - ObDereferenceObject(&proc->Header); - return s; - } - - // Drop the creator's local reference. The returned handle and live thread - // references now own the process lifetime. - ObDereferenceObject(&proc->Header); + KSTATUS status = NTLSTATUS_SUCCESS; + SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)KiHandleToObject(contextHandle, + ObjectTypeSecurityContext, + SYS_SECURITY_CONTEXT_RIGHT_ASSIGN, + &status); + if (!context) return status; + *contextOut = context; return NTLSTATUS_SUCCESS; } -// SYS_OPEN_PROCESS a0=pid a1=*handleOut. -static uint64_t SyscallOpenProcess(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 NTLSTATUS_INVALID_PARAMETER; - KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(uint32_t)); +// Implements SYS_CREATE_PROCESS_OBJECT. The returned process has no runnable thread. +static uint64_t SyscallCreateProcessObject(const SYS_CREATE_PROCESS_OBJECT_REQUEST* request) { + SYS_CREATE_PROCESS_OBJECT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_CREATE_PROCESS_OBJECT_FLAG_NONE || !req.ProcessHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + status = MmProbeUserWrite(req.ProcessHandleOut, sizeof(*req.ProcessHandleOut)); + if (!KSUCCESS(status)) return status; + + char imagePathBuf[SYSCALL_STRING_MAX]; + char commandLineBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW imagePath; + SYS_STRING_VIEW commandLine; + status = KiImportUserTextView(req.ImagePath, imagePathBuf, sizeof(imagePathBuf), &imagePath, true); + if (!KSUCCESS(status)) return status; + if (imagePath.Length == 0) return NTLSTATUS_INVALID_PARAMETER; + status = KiImportUserTextView(req.CommandLine, commandLineBuf, sizeof(commandLineBuf), &commandLine, true); + if (!KSUCCESS(status)) return status; + + SECURITY_CONTEXT* childContext = (SECURITY_CONTEXT*)0; + status = KiReferenceChildSecurityContext(req.SecurityContext, &childContext); + if (!KSUCCESS(status)) return status; + + PROCESS* child = (PROCESS*)0; + status = KeCreateProcess(imagePathBuf, childContext, &child); + ObDereferenceObject(&childContext->Header); + if (!KSUCCESS(status)) return status; + child->BuildState = KE_PROCESS_BUILD_CONSTRUCTING; + child->BuildOperations = 0; + + PROCESS* parent = KeGetCurrentProcess(); + status = KeSetupUserProcessEnvironment(child, imagePath, commandLine); + if (!KSUCCESS(status)) { + ObDereferenceObject(&child->Header); + return status; + } + + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KeCreateHandle(parent, &child->Header, KeGetValidHandleRights(ObjectTypeProcess), &handle); + if (KSUCCESS(status)) { + status = KiCopyHandleToUser(req.ProcessHandleOut, handle); + if (!KSUCCESS(status)) KeCloseHandle(parent, handle); + } + ObDereferenceObject(&child->Header); + return status; +} + +// Implements SYS_ALLOC_PROCESS_VM. The shared user VA is reachable by the builder but owned by the child VAD. +static uint64_t SyscallAllocProcessVm(const SYS_ALLOC_PROCESS_VM_REQUEST* request) { + SYS_ALLOC_PROCESS_VM_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (!req.PageCount || !req.BaseOut || req.Flags != SYS_VM_FLAG_NONE) + return NTLSTATUS_INVALID_PARAMETER; + if (req.PageCount > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY; + status = MmProbeUserWrite(req.BaseOut, sizeof(*req.BaseOut)); + if (!KSUCCESS(status)) return status; + + uint64_t pageFlags = 0; + status = KiVmProtectToPageFlags(req.Protect, &pageFlags); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PROCESS* child = (PROCESS*)KiHandleToObject(req.ProcessHandle, ObjectTypeProcess, + SYS_PROCESS_RIGHT_BUILD, &handleStatus); + if (!child) return handleStatus; + status = KiBeginProcessBuildOperation(child); + if (!KSUCCESS(status)) { + ObDereferenceObject(&child->Header); + return status; + } + + void* base = (void*)0; + status = MmVadCreateCommitted(child, req.PageCount * PAGE_SIZE, PAGE_SIZE, + pageFlags, MM_VAD_ZERO, MmVadGeneric, &base); + KiEndProcessBuildOperation(child); + if (KSUCCESS(status)) { + KSTATUS copyStatus = CopyToUser(req.BaseOut, &base, sizeof(base)); + if (!KSUCCESS(copyStatus)) { + (void)MmVadDestroy(child, base); + status = copyStatus; + } + } + ObDereferenceObject(&child->Header); + return status; +} + +// Implements SYS_PROTECT_PROCESS_VM. +static uint64_t SyscallProtectProcessVm(SYS_HANDLE processHandle, void* base, size_t pageCount, uint32_t protect) { + if (!base || ((uintptr_t)base & (PAGE_SIZE - 1u)) != 0 || + !pageCount || pageCount > ((size_t)-1) / PAGE_SIZE) + return NTLSTATUS_INVALID_PARAMETER; + uint64_t pageFlags = 0; + KSTATUS status = KiVmProtectToPageFlags(protect, &pageFlags); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PROCESS* child = (PROCESS*)KiHandleToObject(processHandle, ObjectTypeProcess, + SYS_PROCESS_RIGHT_BUILD, &handleStatus); + if (!child) return handleStatus; + status = KiBeginProcessBuildOperation(child); + if (KSUCCESS(status)) { + status = MmVadProtect(child, base, pageCount * PAGE_SIZE, pageFlags); + KiEndProcessBuildOperation(child); + } + ObDereferenceObject(&child->Header); + return status; +} + +// Implements SYS_START_PROCESS. Start is a one-way transition from Constructing to Started. +static uint64_t SyscallStartProcess(const SYS_START_PROCESS_REQUEST* request) { + SYS_START_PROCESS_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_START_PROCESS_FLAG_NONE || + !req.MainImageBase || !req.MainImageSize || !req.EntryPoint || + req.StartupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX || + (req.StartupCapabilityCount && !req.StartupCapabilities)) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_STARTUP_CAPABILITY capabilities[SYS_STARTUP_CAPABILITY_MAX]; + memset(capabilities, 0, sizeof(capabilities)); + if (req.StartupCapabilityCount) { + status = CopyFromUser(capabilities, req.StartupCapabilities, + (size_t)req.StartupCapabilityCount * sizeof(capabilities[0])); + if (!KSUCCESS(status)) return status; + } + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PROCESS* child = (PROCESS*)KiHandleToObject(req.ProcessHandle, ObjectTypeProcess, + SYS_PROCESS_RIGHT_BUILD, &handleStatus); + if (!child) return handleStatus; + + int buildActive = 0; + status = KiBeginProcessBuildOperation(child); + if (!KSUCCESS(status)) { + int terminating = KeIsProcessTerminating(child); + ObDereferenceObject(&child->Header); + return terminating ? NTLSTATUS_OBJECT_DEAD : status; + } + buildActive = 1; + THREAD* thread = (THREAD*)0; + + if (_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_STARTING, + KE_PROCESS_BUILD_CONSTRUCTING) != KE_PROCESS_BUILD_CONSTRUCTING) { + status = KeIsProcessTerminating(child) ? NTLSTATUS_OBJECT_DEAD : NTLSTATUS_ACCESS_DENIED; + goto done; + } + if (_InterlockedCompareExchange(&child->BuildOperations, 0, 0) != 1) { + (void)_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_CONSTRUCTING, + KE_PROCESS_BUILD_STARTING); + status = NTLSTATUS_WOULD_BLOCK; + goto done; + } + + for (uint32_t i = 0; i < req.StartupCapabilityCount && KSUCCESS(status); ++i) { + if (!capabilities[i].Tag || capabilities[i].Handle == SYS_HANDLE_INVALID) { + status = NTLSTATUS_INVALID_PARAMETER; + break; + } + for (uint32_t j = 0; j < i; ++j) { + if (capabilities[j].Tag == capabilities[i].Tag) { + status = NTLSTATUS_OBJECT_NAME_COLLISION; + break; + } + } + if (!KSUCCESS(status)) break; + OBJECT* object = (OBJECT*)0; + status = KeReferenceHandle(child, capabilities[i].Handle, 0, 0, &object); + if (KSUCCESS(status)) ObDereferenceObject(object); + } + + void* regionBase = (void*)0; + size_t regionSize = 0; + uint64_t regionFlags = 0; + MM_VAD_TYPE regionType = MmVadGeneric; + uint32_t vadFlags = 0; + if (KSUCCESS(status)) { + size_t imageVadSize = MmVadGetSize(child, (const void*)req.MainImageBase); + if (!imageVadSize || req.MainImageSize > imageVadSize || + req.MainImageBase > UINTPTR_MAX - req.MainImageSize || + req.EntryPoint < req.MainImageBase || + req.EntryPoint >= req.MainImageBase + req.MainImageSize) + status = NTLSTATUS_INVALID_PARAMETER; + } + if (KSUCCESS(status)) { + status = MmVadQuery(child, (const void*)req.EntryPoint, + ®ionBase, ®ionSize, ®ionFlags, ®ionType, &vadFlags); + if (KSUCCESS(status) && (!(regionFlags & PAGE_PRESENT) || (regionFlags & PAGE_NX))) + status = NTLSTATUS_ACCESS_DENIED; + } + if (KSUCCESS(status) && req.LoaderData) { + status = MmVadQuery(child, req.LoaderData, + ®ionBase, ®ionSize, ®ionFlags, ®ionType, &vadFlags); + if (KSUCCESS(status) && (!(regionFlags & PAGE_PRESENT) || !(regionFlags & PAGE_USER))) + status = NTLSTATUS_INVALID_PARAMETER; + } + + if (KSUCCESS(status)) + status = KeSetProcessStartupCapabilities(child, capabilities, req.StartupCapabilityCount); + if (KSUCCESS(status)) + status = KeSetProcessLoaderData(child, req.LoaderData); + + if (KSUCCESS(status) && !KeIsProcessTerminating(child)) { + child->ImageBase = req.MainImageBase; + child->ImageSize = req.MainImageSize; + KE_THREAD_CREATE_PARAMETERS parameters = KE_THREAD_CREATE_NORMAL(PriorityNormal); + parameters.Flags = KE_THREAD_CREATE_FLAG_SUSPENDED; + status = KeCreateThread(child, req.EntryPoint, 0, ¶meters, &thread); + if (KSUCCESS(status)) { + if (_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_STARTED, + KE_PROCESS_BUILD_STARTING) != KE_PROCESS_BUILD_STARTING) { + (void)KeCancelSuspendedThread(thread); + status = NTLSTATUS_OBJECT_DEAD; + } + } + } else if (KSUCCESS(status)) { + status = NTLSTATUS_OBJECT_DEAD; + } + + if (!KSUCCESS(status)) { + child->ImageBase = 0; + child->ImageSize = 0; + (void)KeSetProcessStartupCapabilities(child, (const SYS_STARTUP_CAPABILITY*)0, 0); + (void)KeSetProcessLoaderData(child, (const void*)0); + (void)_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_CONSTRUCTING, + KE_PROCESS_BUILD_STARTING); + goto done; + } + + KiEndProcessBuildOperation(child); + buildActive = 0; + + if (KeIsProcessTerminating(child)) { + status = NTLSTATUS_OBJECT_DEAD; + } else { + status = KeResumeThread(thread, (uint32_t*)0); + if (!KSUCCESS(status) && !KeIsProcessTerminating(child)) + (void)KeCancelSuspendedThread(thread); + } + ObDereferenceObject(&thread->Header); + ObDereferenceObject(&child->Header); + return status; + +done: + if (thread) ObDereferenceObject(&thread->Header); + if (buildActive) KiEndProcessBuildOperation(child); + ObDereferenceObject(&child->Header); + return status; +} + +// Implements SYS_OPEN_PROCESS. +static uint64_t SyscallOpenProcess(uint32_t pid, SYS_RIGHTS desiredAccess, SYS_HANDLE* handleOut) { + if (!handleOut || desiredAccess == 0) return NTLSTATUS_INVALID_PARAMETER; + SYS_RIGHTS validAccess = KeGetValidHandleRights(ObjectTypeProcess); + if ((desiredAccess & ~validAccess) != 0) return NTLSTATUS_INVALID_PARAMETER; + + PROCESS* caller = KeGetCurrentProcess(); + if (!caller) return NTLSTATUS_FAILURE; + if (pid != caller->ProcessId) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_OPEN_ANY); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + } + + KSTATUS s = MmProbeUserWrite(handleOut, sizeof(SYS_HANDLE)); if (!KSUCCESS(s)) return s; - PROCESS* proc = KeGetProcessById((uint32_t)a0); + PROCESS* proc = KeGetProcessById(pid); if (!proc) return NTLSTATUS_NOT_FOUND; - PROCESS* caller = KeGetCurrentProcess(); - if (!caller) { - ObDereferenceObject(&proc->Header); - return NTLSTATUS_FAILURE; - } - - uint32_t handle = 0; - s = KeAllocateHandleWithRights(caller, &proc->Header, - HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE, - &handle); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + s = KeCreateHandle(caller, + &proc->Header, + desiredAccess, + &handle); ObDereferenceObject(&proc->Header); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a1, handle); + s = KiCopyHandleToUser(handleOut, handle); if (!KSUCCESS(s)) { KeCloseHandle(caller, handle); return s; @@ -3066,30 +3338,28 @@ static uint64_t SyscallOpenProcess(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -// SYS_GET_PROCESS_EXIT_CODE (4) copies the process exit code. -static uint64_t SyscallGetProcessExitCode(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 NTLSTATUS_INVALID_PARAMETER; +// Copies the process exit code. +static uint64_t SyscallGetProcessExitCode(SYS_HANDLE handle, int32_t* exitCodeOut) { + if (!exitCodeOut) return NTLSTATUS_INVALID_PARAMETER; - PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_QUERY); - if (!proc) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PROCESS* proc = (PROCESS*)KiHandleToObject(handle, ObjectTypeProcess, SYS_RIGHT_QUERY, &handleStatus); + if (!proc) return handleStatus; int32_t exitCode = 0; KSTATUS s = KeGetProcessExitCode(proc, &exitCode); ObDereferenceObject(&proc->Header); if (!KSUCCESS(s)) return s; - return KiCopyInt32ToUser(a1, exitCode); + return KiCopyInt32ToUser(exitCodeOut, exitCode); } -// SYS_WRITE_CONSOLE (60) a0=*buf a1=len +// Implements SYS_WRITE_CONSOLE. // Writes user console text and completes display delivery before returning. -static uint64_t SyscallWriteConsole(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; - +static uint64_t SyscallWriteConsole(const SYS_WRITE_CONSOLE_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_WRITE_CONSOLE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Text.Length == 0) return NTLSTATUS_SUCCESS; if (!req.Text.Data) return NTLSTATUS_INVALID_PARAMETER; @@ -3108,136 +3378,523 @@ static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2, return KeDisplayFlushConsoleOutput(); } -// SYS_OPEN_DIR (54) a0=*dirPath a1=*handleOut. -static uint64_t SyscallOpenDir(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_PATH_HANDLE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - if (!KSUCCESS(s)) return s; - if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER; - - char dirPathBuf[SYSCALL_STRING_MAX]; - SYS_STRING_VIEW dirPath; - s = KiImportUserTextView(req.Path, dirPathBuf, sizeof(dirPathBuf), &dirPath, true); - if (!KSUCCESS(s)) return s; - - s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t)); - if (!KSUCCESS(s)) return s; - - DIR_OBJECT* dir = NULL; - s = IoOpenDir(dirPath, &dir); - if (!KSUCCESS(s)) return s; - - uint32_t handle = 0; - s = KiAllocHandleForCurrentWithRights(&dir->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_QUERY, &handle); - if (!KSUCCESS(s)) return s; - - // KiAllocHandleForCurrentWithRights consumed IoOpenDir's creator reference; - // the returned process handle now owns the directory object. - - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle); - if (!KSUCCESS(s)) { - PROCESS* proc = KeGetCurrentProcess(); - if (proc) KeCloseHandle(proc, handle); - return s; - } - return NTLSTATUS_SUCCESS; -} - -// SYS_READ_DIR (55) a0=handle a1=*KDIR_ENTRY +// Implements SYS_READ_DIR. // Returns NTLSTATUS_NO_MORE_FILES when the directory is exhausted. -static uint64_t SyscallReadDir(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 NTLSTATUS_INVALID_PARAMETER; +static uint64_t SyscallReadDir(SYS_HANDLE handle, SYS_DIR_ENTRY* entryOut) { + if (!entryOut) return NTLSTATUS_INVALID_PARAMETER; - DIR_OBJECT* dir = (DIR_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeDir, HANDLE_RIGHT_READ); - if (!dir) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + DIR_OBJECT* dir = (DIR_OBJECT*)KiHandleToObject(handle, ObjectTypeDir, + SYS_DIR_RIGHT_ENUMERATE, &handleStatus); + if (!dir) return handleStatus; KDIR_ENTRY entry; KSTATUS s = IoReadDir(dir, &entry); ObDereferenceObject(&dir->Header); if (!KSUCCESS(s)) return s; - return CopyToUser((void*)(uintptr_t)a1, &entry, sizeof(entry)); + return CopyToUser(entryOut, &entry, sizeof(entry)); } -// SYS_SET_CWD (56) a0=*path. -static uint64_t SyscallSetCwd(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_PATH_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - 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 NTLSTATUS_INVALID_PARAMETER; - - return IoSetCwd(path); -} - -// SYS_REAL_PATH a0=*SYS_REAL_PATH_REQUEST. -static uint64_t SyscallRealPath(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_REAL_PATH_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req) || req.Flags != SYS_REAL_PATH_FLAG_NONE) - return NTLSTATUS_INVALID_PARAMETER; - if (!req.Buffer && req.BufferSize != 0) return NTLSTATUS_INVALID_PARAMETER; - if (!req.Buffer && !req.RequiredSizeOut) return NTLSTATUS_INVALID_PARAMETER; - - if (req.RequiredSizeOut) { - s = MmProbeUserWrite(req.RequiredSizeOut, sizeof(size_t)); - 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 NTLSTATUS_INVALID_PARAMETER; - - char resolved[SYS_CWD_MAX]; - size_t required = 0; - s = IoRealPath(path, resolved, sizeof(resolved), &required); - if (!KSUCCESS(s)) return s; - - if (req.RequiredSizeOut) { - KSTATUS cs = KiCopySizeToUser((uint64_t)(uintptr_t)req.RequiredSizeOut, required); - if (!KSUCCESS(cs)) return cs; - } - - if (!req.Buffer) - return NTLSTATUS_SUCCESS; - if (req.BufferSize < required) - return NTLSTATUS_BUFFER_TOO_SMALL; - - return CopyToUser(req.Buffer, resolved, required); -} - -// SYS_GET_PROCESS_PID (59) a0=procHandle a1=*pidOut +// Implements SYS_GET_PROCESS_PID. // Returns the numeric PID of a process identified by handle. // Used by posix_spawn to map the kernel handle back to a pid_t for waitpid. -static uint64_t SyscallGetProcessPid(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 NTLSTATUS_INVALID_PARAMETER; +static uint64_t SyscallGetProcessPid(SYS_HANDLE handle, uint32_t* pidOut) { + if (!pidOut) return NTLSTATUS_INVALID_PARAMETER; - PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_QUERY); - if (!proc) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PROCESS* proc = (PROCESS*)KiHandleToObject(handle, ObjectTypeProcess, SYS_RIGHT_QUERY, &handleStatus); + if (!proc) return handleStatus; uint32_t pid = proc->ProcessId; ObDereferenceObject(&proc->Header); - return KiCopyU32ToUser(a1, pid); + return KiCopyU32ToUser(pidOut, pid); +} + +// Implements SYS_CREATE_PORT. +static uint64_t SyscallCreatePort(const SYS_CREATE_PORT_REQUEST* request) { + SYS_CREATE_PORT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_FLAG_NONE || !req.HandleOut) + return NTLSTATUS_INVALID_PARAMETER; + status = MmProbeUserWrite(req.HandleOut, sizeof(*req.HandleOut)); + if (!KSUCCESS(status)) return status; + + PORT* port = (PORT*)0; + status = KePortCreate(req.Backlog, &port); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KiAllocHandleForCurrentWithRights((OBJECT*)port, + SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | + SYS_PORT_RIGHT_CONNECT | SYS_PORT_RIGHT_ACCEPT, + &handle); + if (!KSUCCESS(status)) return status; + status = KiCopyHandleToUser(req.HandleOut, handle); + if (!KSUCCESS(status)) { + PROCESS* process = KeGetCurrentProcess(); + if (process) (void)KeCloseHandle(process, handle); + } + return status; +} + +// Implements SYS_CONNECT_PORT. +static uint64_t SyscallConnectPort(const SYS_CONNECT_PORT_REQUEST* request) { + SYS_CONNECT_PORT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_CONNECT_FLAG_NONE || !req.EndpointHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + status = MmProbeUserWrite(req.EndpointHandleOut, sizeof(*req.EndpointHandleOut)); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* listener = (PORT*)KiHandleToObject(req.ListenerHandle, + ObjectTypePort, + SYS_PORT_RIGHT_CONNECT, + &handleStatus); + if (!listener) return handleStatus; + + PROCESS* process = KeGetCurrentProcess(); + THREAD* thread = KeGetCurrentThread(); + SYS_HANDLE endpoint = SYS_HANDLE_INVALID; + status = process && thread + ? KePortConnect(listener, process, thread, &endpoint) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)listener); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(req.EndpointHandleOut, endpoint); + if (!KSUCCESS(status) && process) + (void)KeCloseHandle(process, endpoint); + return status; +} + +// Implements SYS_ACCEPT_PORT. +static uint64_t SyscallAcceptPort(const SYS_ACCEPT_PORT_REQUEST* request) { + SYS_ACCEPT_PORT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_ACCEPT_FLAG_NONE || !req.EndpointHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + status = MmProbeUserWrite(req.EndpointHandleOut, sizeof(*req.EndpointHandleOut)); + if (!KSUCCESS(status)) return status; + if (req.ConnectionInfoOut) { + status = KiProbeSizedUserOutput(req.ConnectionInfoOut, sizeof(*req.ConnectionInfoOut)); + if (!KSUCCESS(status)) return status; + } + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* listener = (PORT*)KiHandleToObject(req.ListenerHandle, + ObjectTypePort, + SYS_PORT_RIGHT_ACCEPT, + &handleStatus); + if (!listener) return handleStatus; + + PROCESS* process = KeGetCurrentProcess(); + SYS_HANDLE endpoint = SYS_HANDLE_INVALID; + SYS_PORT_CONNECTION_INFO info; + memset(&info, 0, sizeof(info)); + status = process + ? KePortAccept(listener, process, req.TimeoutMilliseconds, &endpoint, &info) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)listener); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(req.EndpointHandleOut, endpoint); + if (KSUCCESS(status) && req.ConnectionInfoOut) + status = CopyToUser(req.ConnectionInfoOut, &info, sizeof(info)); + if (!KSUCCESS(status) && process) + (void)KeCloseHandle(process, endpoint); + return status; +} + +// Implements SYS_SEND_PORT. +static uint64_t SyscallSendPort(const SYS_SEND_PORT_REQUEST* request) { + SYS_SEND_PORT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_FLAG_NONE || req.PayloadLength > SYS_PORT_MAX_INLINE_BYTES || + req.HandleCount > SYS_PORT_MAX_TRANSFER_HANDLES || + (req.PayloadLength && !req.Payload)) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_PORT_HANDLE_TRANSFER transfers[SYS_PORT_MAX_TRANSFER_HANDLES]; + status = KiImportPortTransfers(req.Handles, req.HandleCount, transfers); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle, + ObjectTypePort, + SYS_PORT_RIGHT_SEND, + &handleStatus); + if (!endpoint) return handleStatus; + + PROCESS* process = KeGetCurrentProcess(); + THREAD* thread = KeGetCurrentThread(); + status = process && thread + ? KePortSend(endpoint, process, thread, + req.Payload, req.PayloadLength, + transfers, req.HandleCount, + req.TimeoutMilliseconds) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)endpoint); + return status; +} + +// Implements SYS_RECEIVE_PORT. +static uint64_t SyscallReceivePort(const SYS_RECEIVE_PORT_REQUEST* request) { + SYS_RECEIVE_PORT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_FLAG_NONE || !req.InfoOut || + req.PayloadCapacity > SYS_PORT_MAX_INLINE_BYTES || + req.HandleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES || + (req.PayloadCapacity && !req.Payload) || + (req.HandleCapacity && !req.HandlesOut)) + return NTLSTATUS_INVALID_PARAMETER; + + status = KiProbeSizedUserOutput(req.InfoOut, sizeof(*req.InfoOut)); + if (!KSUCCESS(status)) return status; + if (req.HandleCapacity) { + status = MmProbeUserWrite(req.HandlesOut, sizeof(SYS_HANDLE) * req.HandleCapacity); + if (!KSUCCESS(status)) return status; + } + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle, + ObjectTypePort, + SYS_PORT_RIGHT_RECEIVE, + &handleStatus); + if (!endpoint) return handleStatus; + + PROCESS* process = KeGetCurrentProcess(); + SYS_PORT_MESSAGE_INFO info; + SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0}; + memset(&info, 0, sizeof(info)); + status = process + ? KePortReceive(endpoint, process, + req.Payload, req.PayloadCapacity, + handles, req.HandleCapacity, + &info, req.TimeoutMilliseconds) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)endpoint); + + if (status == NTLSTATUS_BUFFER_TOO_SMALL) { + KSTATUS copyStatus = CopyToUser(req.InfoOut, &info, sizeof(info)); + return KSUCCESS(copyStatus) ? status : copyStatus; + } + if (!KSUCCESS(status)) return status; + + status = CopyToUser(req.InfoOut, &info, sizeof(info)); + if (KSUCCESS(status) && info.HandleCount) + status = CopyToUser(req.HandlesOut, handles, sizeof(SYS_HANDLE) * info.HandleCount); + if (!KSUCCESS(status) && process) + for (uint32_t i = 0; i < info.HandleCount; ++i) + (void)KeCloseHandle(process, handles[i]); + return status; +} + +// Implements SYS_CALL_PORT. +static uint64_t SyscallCallPort(const SYS_CALL_PORT_REQUEST* request) { + SYS_CALL_PORT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_FLAG_NONE || + req.RequestLength > SYS_PORT_MAX_INLINE_BYTES || + req.ReplyCapacity > SYS_PORT_MAX_INLINE_BYTES || + req.RequestHandleCount > SYS_PORT_MAX_TRANSFER_HANDLES || + req.ReplyHandleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES || + (req.RequestLength && !req.RequestPayload) || + (req.ReplyCapacity && !req.ReplyPayload) || + (req.ReplyHandleCapacity && !req.ReplyHandlesOut)) + return NTLSTATUS_INVALID_PARAMETER; + + if (req.ReplyInfoOut) { + status = KiProbeSizedUserOutput(req.ReplyInfoOut, sizeof(*req.ReplyInfoOut)); + if (!KSUCCESS(status)) return status; + } + if (req.ReplyHandleCapacity) { + status = MmProbeUserWrite(req.ReplyHandlesOut, sizeof(SYS_HANDLE) * req.ReplyHandleCapacity); + if (!KSUCCESS(status)) return status; + } + + SYS_PORT_HANDLE_TRANSFER transfers[SYS_PORT_MAX_TRANSFER_HANDLES]; + status = KiImportPortTransfers(req.RequestHandles, req.RequestHandleCount, transfers); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle, + ObjectTypePort, + SYS_PORT_RIGHT_CALL, + &handleStatus); + if (!endpoint) return handleStatus; + + PROCESS* process = KeGetCurrentProcess(); + THREAD* thread = KeGetCurrentThread(); + SYS_PORT_MESSAGE_INFO info; + SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0}; + memset(&info, 0, sizeof(info)); + status = process && thread + ? KePortCall(endpoint, process, thread, + req.RequestPayload, req.RequestLength, + transfers, req.RequestHandleCount, + req.ReplyPayload, req.ReplyCapacity, + handles, req.ReplyHandleCapacity, + &info, req.TimeoutMilliseconds) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)endpoint); + if (!KSUCCESS(status)) return status; + + if (req.ReplyInfoOut) { + status = CopyToUser(req.ReplyInfoOut, &info, sizeof(info)); + if (!KSUCCESS(status)) goto cleanup_call_handles; + } + if (info.HandleCount) { + status = CopyToUser(req.ReplyHandlesOut, handles, sizeof(SYS_HANDLE) * info.HandleCount); + if (!KSUCCESS(status)) goto cleanup_call_handles; + } + return NTLSTATUS_SUCCESS; + +cleanup_call_handles: + if (process) + for (uint32_t i = 0; i < info.HandleCount; ++i) + (void)KeCloseHandle(process, handles[i]); + return status; +} + +// Implements SYS_REPLY_PORT. +static uint64_t SyscallReplyPort(const SYS_REPLY_PORT_REQUEST* request) { + SYS_REPLY_PORT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_FLAG_NONE || req.MessageId == 0 || + req.PayloadLength > SYS_PORT_MAX_INLINE_BYTES || + req.HandleCount > SYS_PORT_MAX_TRANSFER_HANDLES || + (req.PayloadLength && !req.Payload)) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_PORT_HANDLE_TRANSFER transfers[SYS_PORT_MAX_TRANSFER_HANDLES]; + status = KiImportPortTransfers(req.Handles, req.HandleCount, transfers); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle, + ObjectTypePort, + SYS_PORT_RIGHT_REPLY, + &handleStatus); + if (!endpoint) return handleStatus; + + PROCESS* process = KeGetCurrentProcess(); + THREAD* thread = KeGetCurrentThread(); + status = process && thread + ? KePortReply(endpoint, process, thread, + req.MessageId, + req.Payload, req.PayloadLength, + transfers, req.HandleCount) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)endpoint); + return status; +} + +// Implements SYS_DISCONNECT_PORT. +static uint64_t SyscallDisconnectPort(SYS_HANDLE endpointHandle) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* endpoint = (PORT*)KiHandleToObject(endpointHandle, + ObjectTypePort, + SYS_PORT_RIGHT_DISCONNECT, + &handleStatus); + if (!endpoint) return handleStatus; + KSTATUS status = KePortDisconnect(endpoint); + ObDereferenceObject((OBJECT*)endpoint); + return status; +} + +// Implements SYS_REGISTER_SYSTEM_SERVICE. +static uint64_t SyscallRegisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle) { + if (serviceId == SYS_SERVICE_INVALID || serviceId > SYS_SERVICE_MAX) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = KiRequirePrivilege(SYS_PRIV_SYSTEM_SERVICE_REGISTER); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + SYS_SERVICE_CLAIMS claim = KeSystemServiceRequiredClaim(serviceId); + if (!process || !process->SecurityContext || claim == 0 || + !SeContextHasServiceClaims(process->SecurityContext, claim)) + return NTLSTATUS_ACCESS_DENIED; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* listener = (PORT*)KiHandleToObject(listenerHandle, + ObjectTypePort, + SYS_PORT_RIGHT_ACCEPT, + &handleStatus); + if (!listener) return handleStatus; + status = KeSystemServiceRegister(serviceId, listener); + ObDereferenceObject((OBJECT*)listener); + return status; +} + +// Implements SYS_UNREGISTER_SYSTEM_SERVICE. +static uint64_t SyscallUnregisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle) { + if (serviceId == SYS_SERVICE_INVALID || serviceId > SYS_SERVICE_MAX) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = KiRequirePrivilege(SYS_PRIV_SYSTEM_SERVICE_REGISTER); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + SYS_SERVICE_CLAIMS claim = KeSystemServiceRequiredClaim(serviceId); + if (!process || !process->SecurityContext || claim == 0 || + !SeContextHasServiceClaims(process->SecurityContext, claim)) + return NTLSTATUS_ACCESS_DENIED; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* listener = (PORT*)KiHandleToObject(listenerHandle, + ObjectTypePort, + SYS_PORT_RIGHT_ACCEPT, + &handleStatus); + if (!listener) return handleStatus; + status = KeSystemServiceUnregister(serviceId, listener); + ObDereferenceObject((OBJECT*)listener); + return status; +} + +// Implements SYS_CONNECT_SYSTEM_SERVICE. +static uint64_t SyscallConnectSystemService(const SYS_CONNECT_SYSTEM_SERVICE_REQUEST* request) { + SYS_CONNECT_SYSTEM_SERVICE_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_PORT_CONNECT_FLAG_NONE || !req.EndpointHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + status = MmProbeUserWrite(req.EndpointHandleOut, sizeof(*req.EndpointHandleOut)); + if (!KSUCCESS(status)) return status; + + PORT* listener = (PORT*)0; + status = KeSystemServiceReference(req.ServiceId, &listener); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + THREAD* thread = KeGetCurrentThread(); + SYS_HANDLE endpoint = SYS_HANDLE_INVALID; + status = process && thread + ? KePortConnect(listener, process, thread, &endpoint) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)listener); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(req.EndpointHandleOut, endpoint); + if (!KSUCCESS(status) && process) + (void)KeCloseHandle(process, endpoint); + return status; +} + +// Implements SYS_QUERY_VOLUMES. The manager capability is the authority to observe mounted-volume lifecycle. +static uint64_t SyscallQueryVolumes(const SYS_QUERY_VOLUMES_REQUEST* request) { + SYS_QUERY_VOLUMES_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_VOLUME_QUERY_FLAG_NONE || !req.CountOut || !req.GenerationOut || + (req.Capacity != 0 && !req.Volumes)) + return NTLSTATUS_INVALID_PARAMETER; + + status = MmProbeUserWrite(req.CountOut, sizeof(*req.CountOut)); + if (!KSUCCESS(status)) return status; + status = MmProbeUserWrite(req.GenerationOut, sizeof(*req.GenerationOut)); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + IO_VOLUME_MANAGER* manager = (IO_VOLUME_MANAGER*)KiHandleToObject( + req.ManagerHandle, ObjectTypeVolumeManager, SYS_VOLUME_MANAGER_RIGHT_ENUMERATE, &handleStatus); + if (!manager) return handleStatus; + ObDereferenceObject(&manager->Header); + + IO_VOLUME_SNAPSHOT_ENTRY* snapshot = (IO_VOLUME_SNAPSHOT_ENTRY*)0; + uint32_t count = 0; + uint64_t generation = 0; + status = IoSnapshotVolumes(&snapshot, &count, &generation); + if (!KSUCCESS(status)) return status; + + KSTATUS countStatus = CopyToUser(req.CountOut, &count, sizeof(count)); + KSTATUS generationStatus = CopyToUser(req.GenerationOut, &generation, sizeof(generation)); + if (!KSUCCESS(countStatus) || !KSUCCESS(generationStatus)) { + if (snapshot) MmFreePool(snapshot); + return !KSUCCESS(countStatus) ? countStatus : generationStatus; + } + if (req.Capacity < count) { + if (snapshot) MmFreePool(snapshot); + return NTLSTATUS_BUFFER_TOO_SMALL; + } + if (count) { + status = MmProbeUserWrite(req.Volumes, (size_t)count * sizeof(*req.Volumes)); + if (!KSUCCESS(status)) { + MmFreePool(snapshot); + return status; + } + } + + for (uint32_t i = 0; i < count; ++i) { + SYS_VOLUME_INFO info; + memset(&info, 0, sizeof(info)); + info.Size = sizeof(info); + info.Version = SYS_VOLUME_ENUM_VERSION; + info.InstanceId = snapshot[i].InstanceId; + info.Flags = 0; + status = CopyToUser(&req.Volumes[i], &info, sizeof(info)); + if (!KSUCCESS(status)) { + MmFreePool(snapshot); + return status; + } + } + + if (snapshot) MmFreePool(snapshot); + return NTLSTATUS_SUCCESS; +} + +// Implements SYS_OPEN_VOLUME. The manager can mint only traversal/data authority, never capability-management authority. +static uint64_t SyscallOpenVolume(SYS_HANDLE managerHandle, + uint32_t instanceId, + SYS_RIGHTS desiredRights, + SYS_HANDLE* volumeHandleOut) { + const SYS_RIGHTS exportable = SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | + SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY; + if (!instanceId || !desiredRights || (desiredRights & ~exportable) != 0 || !volumeHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = MmProbeUserWrite(volumeHandleOut, sizeof(*volumeHandleOut)); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + IO_VOLUME_MANAGER* manager = (IO_VOLUME_MANAGER*)KiHandleToObject( + managerHandle, ObjectTypeVolumeManager, SYS_VOLUME_MANAGER_RIGHT_OPEN, &handleStatus); + if (!manager) return handleStatus; + ObDereferenceObject(&manager->Header); + + IO_VOLUME* volume = (IO_VOLUME*)0; + status = IoReferenceVolumeByInstanceId(instanceId, &volume); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = process ? KeCreateHandle(process, &volume->Header, desiredRights, &handle) + : NTLSTATUS_FAILURE; + IoReleaseVolume(volume); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(volumeHandleOut, handle); + if (!KSUCCESS(status) && process) + (void)KeCloseHandle(process, handle); + return status; +} + +// Implements SYS_WAIT_VOLUME_CHANGE. Generation comparison closes the query-to-wait race. +static uint64_t SyscallWaitVolumeChange(SYS_HANDLE managerHandle, + uint64_t observedGeneration, + uint64_t timeoutMilliseconds) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + IO_VOLUME_MANAGER* manager = (IO_VOLUME_MANAGER*)KiHandleToObject( + managerHandle, ObjectTypeVolumeManager, SYS_VOLUME_MANAGER_RIGHT_WAIT, &handleStatus); + if (!manager) return handleStatus; + ObDereferenceObject(&manager->Header); + return IoWaitForVolumeChange(observedGeneration, timeoutMilliseconds); } // Named Queue Registry @@ -3258,7 +3915,7 @@ typedef struct { size_t NameLength; QUEUE* Queue; // NULL = slot free; non-NULL = occupied uint32_t PublisherPid; // ProcessId of whoever called SysPublishQueue - uint32_t PublisherHandle; // Publisher-owned handle that keeps the name live + SYS_HANDLE PublisherHandle; // Publisher-owned handle that keeps the name live } NAMED_QUEUE_ENTRY; static NAMED_QUEUE_ENTRY gNamedQueues[NAMED_QUEUE_MAX]; @@ -3272,7 +3929,7 @@ static int KiNamedQueueNameEquals(const NAMED_QUEUE_ENTRY* entry, static int KiNamedQueueOwnedByHandle(const NAMED_QUEUE_ENTRY* entry, const PROCESS* process, - uint32_t handle, + SYS_HANDLE handle, const OBJECT* object) { if (!entry || !entry->Queue || !process || !object) return 0; @@ -3318,7 +3975,7 @@ static KSTATUS KiNamedQueueUnpublishByName(SYS_STRING_VIEW nameView) { } // Removes published names whose owner handle is being closed. -void KeNamedQueueUnpublishHandle(PROCESS* process, uint32_t handle, OBJECT* object) { +void KeNamedQueueUnpublishHandle(PROCESS* process, SYS_HANDLE handle, OBJECT* object) { if (!process || !object || object->Type != ObjectTypeQueue) return; @@ -3339,17 +3996,16 @@ void KeNamedQueueUnpublishHandle(PROCESS* process, uint32_t handle, OBJECT* obje } } -// SYS_PUBLISH_QUEUE (60) a0=*name a1=queueHandle +// Implements SYS_PUBLISH_QUEUE. // Registers the queue identified by QueueHandle under Name. // The published name is owned by the publishing process and handle. Closing that // handle or exiting the process withdraws every name published through it. -static uint64_t SyscallPublishQueue(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; - +static uint64_t SyscallPublishQueue(const SYS_PUBLISH_QUEUE_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NAMED_QUEUE_PUBLISH); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_PUBLISH_QUEUE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; char name[NAMED_QUEUE_NAME_MAX]; @@ -3358,8 +4014,9 @@ static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; if (nameView.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_DUPLICATE); - if (!q) return NTLSTATUS_INVALID_HANDLE; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, SYS_QUEUE_RIGHT_PUBLISH, &handleStatus); + if (!q) return handleStatus; uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); @@ -3388,14 +4045,13 @@ static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_NO_MEMORY; } -// SYS_UNPUBLISH_QUEUE a0=*SYS_UNPUBLISH_QUEUE_REQUEST. +// Implements SYS_UNPUBLISH_QUEUE. // Withdraws a published queue name owned by the calling process. -static uint64_t SyscallUnpublishQueue(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; - +static uint64_t SyscallUnpublishQueue(const SYS_UNPUBLISH_QUEUE_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NAMED_QUEUE_PUBLISH); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_UNPUBLISH_QUEUE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; char name[NAMED_QUEUE_NAME_MAX]; @@ -3407,17 +4063,14 @@ static uint64_t SyscallUnpublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, return KiNamedQueueUnpublishByName(nameView); } -// SYS_OPEN_NAMED_QUEUE (61) a0=*name a1=*handleOut +// Implements SYS_OPEN_NAMED_QUEUE. // Looks up 'name' in the registry and allocates a handle to the queue in the // calling process's handle table. Returns NTLSTATUS_NOT_FOUND if no match. // Callers that race against the publisher should retry on NOT_FOUND. -static uint64_t SyscallOpenNamedQueue(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; - +static uint64_t SyscallOpenNamedQueue(const SYS_OPEN_NAMED_QUEUE_REQUEST* request) { SYS_OPEN_NAMED_QUEUE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER; @@ -3427,7 +4080,7 @@ static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; if (nameView.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t)); + s = MmProbeUserWrite(req.HandleOut, sizeof(SYS_HANDLE)); if (!KSUCCESS(s)) return s; uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); @@ -3446,11 +4099,11 @@ static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, if (!q) return NTLSTATUS_NOT_FOUND; - uint32_t handle = 0; - s = KiAllocHandleForCurrentWithRights(&q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &handle); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&q->Header, KeGetValidHandleRights(ObjectTypeQueue), &handle); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle); + s = KiCopyHandleToUser(req.HandleOut, handle); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, handle); @@ -3459,36 +4112,35 @@ static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -// SYS_DUPLICATE_HANDLE a0=*request. +// Implements SYS_DUPLICATE_HANDLE. // Duplicates a handle from the caller into the target process table. -static uint64_t SyscallDuplicateHandle(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; - +static uint64_t SyscallDuplicateHandle(const SYS_DUPLICATE_HANDLE_REQUEST* request) { SYS_DUPLICATE_HANDLE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (!req.SourceHandle || !req.TargetProcessHandle || !req.TargetHandleOut) + if (!req.TargetHandleOut) return NTLSTATUS_INVALID_PARAMETER; - s = MmProbeUserWrite(req.TargetHandleOut, sizeof(uint32_t)); + s = MmProbeUserWrite(req.TargetHandleOut, sizeof(SYS_HANDLE)); if (!KSUCCESS(s)) return s; PROCESS* caller = KeGetCurrentProcess(); if (!caller) return NTLSTATUS_FAILURE; - PROCESS* target = (PROCESS*)KiHandleToObject(req.TargetProcessHandle, ObjectTypeProcess, HANDLE_RIGHT_DUPLICATE); - if (!target) return NTLSTATUS_INVALID_HANDLE; + KSTATUS targetStatus = NTLSTATUS_SUCCESS; + PROCESS* target = (PROCESS*)KiHandleToObject(req.TargetProcessHandle, ObjectTypeProcess, + SYS_PROCESS_RIGHT_INSERT_HANDLE, &targetStatus); + if (!target) return targetStatus; - uint32_t targetHandle = 0; - s = KeDuplicateHandleToProcess(caller, req.SourceHandle, target, req.DesiredRights, req.Flags, &targetHandle); + SYS_HANDLE targetHandle = SYS_HANDLE_INVALID; + s = KeDuplicateHandle(caller, req.SourceHandle, target, req.DesiredRights, req.Flags, &targetHandle); if (!KSUCCESS(s)) { ObDereferenceObject(&target->Header); return s; } - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.TargetHandleOut, targetHandle); + s = KiCopyHandleToUser(req.TargetHandleOut, targetHandle); if (!KSUCCESS(s)) { KeCloseHandle(target, targetHandle); ObDereferenceObject(&target->Header); @@ -3499,17 +4151,130 @@ static uint64_t SyscallDuplicateHandle(uint64_t a0, uint64_t a1, uint64_t a2, return NTLSTATUS_SUCCESS; } -// SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS a0=*request. +// Implements SYS_DERIVE_HANDLE. +static uint64_t SyscallDeriveHandle(const SYS_DERIVE_HANDLE_REQUEST* request) { + SYS_DERIVE_HANDLE_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (!req.DesiredRights || !req.DerivedHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + + status = MmProbeUserWrite(req.DerivedHandleOut, sizeof(SYS_HANDLE)); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + if (!process) return NTLSTATUS_FAILURE; + + SYS_HANDLE derived = SYS_HANDLE_INVALID; + status = KeDeriveHandle(process, req.SourceHandle, req.DesiredRights, &derived); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(req.DerivedHandleOut, derived); + if (!KSUCCESS(status)) + (void)KeCloseHandle(process, derived); + return status; +} + +// Implements SYS_REVOKE_HANDLE. +static uint64_t SyscallRevokeHandle(SYS_HANDLE handle) { + PROCESS* process = KeGetCurrentProcess(); + if (!process) return NTLSTATUS_FAILURE; + return KeRevokeHandle(process, handle); +} + +// Implements SYS_DERIVE_SECURITY_CONTEXT. +static uint64_t SyscallDeriveSecurityContext(const SYS_DERIVE_SECURITY_CONTEXT_REQUEST* request) { + SYS_DERIVE_SECURITY_CONTEXT_REQUEST req; + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(status)) return status; + if (req.Flags != SYS_SECURITY_CONTEXT_FLAG_NONE || !req.ContextHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + + status = MmProbeUserWrite(req.ContextHandleOut, sizeof(SYS_HANDLE)); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + if (!process || !process->SecurityContext) return NTLSTATUS_FAILURE; + + SECURITY_CONTEXT* source = (SECURITY_CONTEXT*)0; + if (req.SourceContext == SYS_SECURITY_CONTEXT_CURRENT) { + source = process->SecurityContext; + ObReferenceObject(&source->Header); + } else { + source = (SECURITY_CONTEXT*)KiHandleToObject(req.SourceContext, + ObjectTypeSecurityContext, + SYS_SECURITY_CONTEXT_RIGHT_DERIVE, + &status); + if (!source) return status; + } + + SECURITY_CONTEXT* derived = (SECURITY_CONTEXT*)0; + status = SeDeriveSecurityContext(source, + req.EffectivePrivileges, + req.DelegablePrivileges, + req.ServiceClaims, + req.DelegableServiceClaims, + &derived); + ObDereferenceObject(&source->Header); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KiAllocHandleForCurrentWithRights(&derived->Header, + KeGetValidHandleRights(ObjectTypeSecurityContext), + &handle); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(req.ContextHandleOut, handle); + if (!KSUCCESS(status)) { + KeCloseHandle(process, handle); + return status; + } + return NTLSTATUS_SUCCESS; +} + +// Implements SYS_QUERY_SECURITY_CONTEXT. +static uint64_t SyscallQuerySecurityContext(SYS_HANDLE contextHandle, SYS_SECURITY_CONTEXT_INFO* infoOut) { + if (!infoOut) return NTLSTATUS_INVALID_PARAMETER; + + uint32_t userSize = 0; + KSTATUS status = CopyFromUser(&userSize, infoOut, sizeof(userSize)); + if (!KSUCCESS(status)) return status; + if (userSize != sizeof(SYS_SECURITY_CONTEXT_INFO)) return NTLSTATUS_INVALID_PARAMETER; + + PROCESS* process = KeGetCurrentProcess(); + if (!process || !process->SecurityContext) return NTLSTATUS_FAILURE; + + SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)0; + if (contextHandle == SYS_SECURITY_CONTEXT_CURRENT) { + context = process->SecurityContext; + ObReferenceObject(&context->Header); + } else { + context = (SECURITY_CONTEXT*)KiHandleToObject(contextHandle, + ObjectTypeSecurityContext, + SYS_RIGHT_QUERY, + &status); + if (!context) return status; + } + + SYS_SECURITY_CONTEXT_INFO info; + SeQuerySecurityContext(context, &info); + ObDereferenceObject(&context->Header); + return CopyToUser(infoOut, &info, sizeof(info)); +} + +// Implements SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS. // Opens a process handle for the process that published a named queue. -static uint64_t SyscallOpenNamedQueueOwnerProcess(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; - +static uint64_t SyscallOpenNamedQueueOwnerProcess(const SYS_NAMED_QUEUE_OWNER_REQUEST* request) { SYS_NAMED_QUEUE_OWNER_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (!req.ProcessHandleOut) return NTLSTATUS_INVALID_PARAMETER; + if (!req.ProcessHandleOut || req.DesiredAccess == 0) return NTLSTATUS_INVALID_PARAMETER; + SYS_RIGHTS validAccess = KeGetValidHandleRights(ObjectTypeProcess); + if ((req.DesiredAccess & ~validAccess) != 0) return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_OPEN_ANY); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; char name[NAMED_QUEUE_NAME_MAX]; SYS_STRING_VIEW nameView; @@ -3517,7 +4282,7 @@ static uint64_t SyscallOpenNamedQueueOwnerProcess(uint64_t a0, uint64_t a1, uint if (!KSUCCESS(s)) return s; if (nameView.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - s = MmProbeUserWrite(req.ProcessHandleOut, sizeof(uint32_t)); + s = MmProbeUserWrite(req.ProcessHandleOut, sizeof(SYS_HANDLE)); if (!KSUCCESS(s)) return s; uint32_t pid = 0; @@ -3540,14 +4305,15 @@ static uint64_t SyscallOpenNamedQueueOwnerProcess(uint64_t a0, uint64_t a1, uint return NTLSTATUS_FAILURE; } - uint32_t handle = 0; - s = KeAllocateHandleWithRights(caller, &owner->Header, - HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_DUPLICATE, - &handle); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + s = KeCreateHandle(caller, + &owner->Header, + req.DesiredAccess, + &handle); ObDereferenceObject(&owner->Header); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.ProcessHandleOut, handle); + s = KiCopyHandleToUser(req.ProcessHandleOut, handle); if (!KSUCCESS(s)) { KeCloseHandle(caller, handle); return s; @@ -3555,14 +4321,14 @@ static uint64_t SyscallOpenNamedQueueOwnerProcess(uint64_t a0, uint64_t a1, uint return NTLSTATUS_SUCCESS; } -static uint64_t SyscallKillProcess(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; - PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_TERMINATE); - if (!proc) return NTLSTATUS_INVALID_HANDLE; +// Terminates the process referenced by one process handle. +static uint64_t SyscallKillProcess(SYS_HANDLE processHandle, int32_t exitCode) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PROCESS* proc = (PROCESS*)KiHandleToObject(processHandle, ObjectTypeProcess, + SYS_PROCESS_RIGHT_TERMINATE, &handleStatus); + if (!proc) return handleStatus; THREAD* self = KeGetCurrentThread(); - int32_t exitCode = (int32_t)a1; if (self && self->Owner == proc) { KSTATUS s = KeExitProcess(proc, exitCode); ObDereferenceObject(&proc->Header); @@ -3576,15 +4342,14 @@ static uint64_t SyscallKillProcess(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_GET_PROCESS_LIST (6) a0=*SYS_PROCESS_INFO[] a1=maxCount a2=*countOut. -static uint64_t SyscallGetProcessList(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; +// Implements SYS_GET_PROCESS_LIST. +static uint64_t SyscallGetProcessList(SYS_PROCESS_INFO* processesOut, size_t maxCount, size_t* countOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_ENUMERATE); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; - if (maxCount && !a0) return NTLSTATUS_INVALID_PARAMETER; - if (a2) { - KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a2, sizeof(size_t)); + if (maxCount && !processesOut) return NTLSTATUS_INVALID_PARAMETER; + if (countOut) { + KSTATUS s = MmProbeUserWrite(countOut, sizeof(size_t)); if (!KSUCCESS(s)) return s; } @@ -3601,25 +4366,27 @@ static uint64_t SyscallGetProcessList(uint64_t a0, uint64_t a1, uint64_t a2, size_t copied = KeCopyProcessInfo(tmp, toCopy); if (copied < toCopy) toCopy = copied; - KSTATUS s = CopyToUser((void*)(uintptr_t)a0, tmp, toCopy * sizeof(SYS_PROCESS_INFO)); + KSTATUS s = CopyToUser(processesOut, tmp, toCopy * sizeof(SYS_PROCESS_INFO)); MmFreePool(tmp); if (!KSUCCESS(s)) return s; } - if (a2) return KiCopySizeToUser(a2, total); + if (countOut) return KiCopySizeToUser(countOut, total); return NTLSTATUS_SUCCESS; } -// SYS_GET_THREAD_LIST (103) a0=pid a1=*SYS_THREAD_INFO[] a2=maxCount a3=*countOut. -static uint64_t SyscallGetThreadList(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - uint32_t pid = (uint32_t)a0; - size_t maxCount = (size_t)a2; +// Implements SYS_GET_THREAD_LIST. +static uint64_t SyscallGetThreadList(uint32_t pid, SYS_THREAD_INFO* threadsOut, size_t maxCount, size_t* countOut) { + PROCESS* caller = KeGetCurrentProcess(); + if (!caller) return NTLSTATUS_FAILURE; + if (pid != caller->ProcessId) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_THREAD_ENUMERATE_ANY); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + } - if (maxCount && !a1) return NTLSTATUS_INVALID_PARAMETER; - if (a3) { - KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a3, sizeof(size_t)); + if (maxCount && !threadsOut) return NTLSTATUS_INVALID_PARAMETER; + if (countOut) { + KSTATUS s = MmProbeUserWrite(countOut, sizeof(size_t)); if (!KSUCCESS(s)) return s; } @@ -3639,221 +4406,22 @@ static uint64_t SyscallGetThreadList(uint64_t a0, uint64_t a1, uint64_t a2, s = KeCopyProcessThreadInfo(pid, tmp, toCopy, &copied); if (KSUCCESS(s)) { if (copied < toCopy) toCopy = copied; - s = CopyToUser((void*)(uintptr_t)a1, tmp, toCopy * sizeof(SYS_THREAD_INFO)); + s = CopyToUser(threadsOut, tmp, toCopy * sizeof(SYS_THREAD_INFO)); } MmFreePool(tmp); if (!KSUCCESS(s)) return s; } - if (a3) return KiCopySizeToUser(a3, total); + if (countOut) return KiCopySizeToUser(countOut, total); return NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; - if (req.Flags != SYS_LOAD_MODULE_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; - if (!req.ModuleHandleOut) return NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; - - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_FAILURE; - - uint32_t handle = (uint32_t)a0; - MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject(handle, ObjectTypeModule, HANDLE_RIGHT_TERMINATE); - if (!module) return NTLSTATUS_INVALID_HANDLE; - - KeCloseHandle(proc, handle); - ObDereferenceObject(&module->Header); - return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; - if (!req.AddressOut) return NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; - - MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject(req.ModuleHandle, ObjectTypeModule, HANDLE_RIGHT_QUERY); - if (!module) return NTLSTATUS_INVALID_HANDLE; - - void* address = LdrGetModuleExportForProcess(module->Owner, module->Base, nameBuf); - ObDereferenceObject(&module->Header); - if (!address) return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_FAILURE; - - size_t total = 0; - KSTATUS s = LdrCopyProcessModuleInfo(proc, NULL, 0, &total); - if (s != NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; - - SYS_MODULE_INFO* tmp = (SYS_MODULE_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('m', 'o', 'd', 'l')); - if (!tmp) return NTLSTATUS_NO_MEMORY; - - size_t copiedTotal = 0; - s = LdrCopyProcessModuleInfo(proc, tmp, toCopy, &copiedTotal); - if (s == NTLSTATUS_BUFFER_TOO_SMALL) - s = NTLSTATUS_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 NTLSTATUS_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) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - - SYS_RENAME_FILE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - if (!KSUCCESS(s)) return s; - - char oldPathBuf[SYSCALL_STRING_MAX]; - char newPathBuf[SYSCALL_STRING_MAX]; - SYS_STRING_VIEW oldPath; - SYS_STRING_VIEW newPath; - s = KiImportUserTextView(req.OldPath, oldPathBuf, sizeof(oldPathBuf), &oldPath, true); - if (!KSUCCESS(s)) return s; - s = KiImportUserTextView(req.NewPath, newPathBuf, sizeof(newPathBuf), &newPath, true); - if (!KSUCCESS(s)) return s; - if (oldPath.Length == 0 || newPath.Length == 0) return NTLSTATUS_INVALID_PARAMETER; - return IoRenameFile(oldPath, newPath); -} - -// SYS_LOCK_FILE a0=*SYS_LOCK_FILE_REQUEST. -static uint64_t SyscallLockFile(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; - +// Implements SYS_LOCK_FILE. +static uint64_t SyscallLockFile(const SYS_LOCK_FILE_REQUEST* request) { SYS_LOCK_FILE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req)) return NTLSTATUS_INVALID_PARAMETER; if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; if (req.Mode != SYS_FILE_LOCK_SHARED && req.Mode != SYS_FILE_LOCK_EXCLUSIVE) return NTLSTATUS_INVALID_PARAMETER; if (req.Length == 0) return NTLSTATUS_INVALID_PARAMETER; @@ -3861,72 +4429,39 @@ static uint64_t SyscallLockFile(uint64_t a0, uint64_t a1, uint64_t a2, PROCESS* proc = KeGetCurrentProcess(); if (!proc) return NTLSTATUS_FAILURE; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_LOCK); - if (!file) return NTLSTATUS_INVALID_HANDLE; + KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, + SYS_FILE_RIGHT_LOCK, &fileHandleStatus); + if (!file) return fileHandleStatus; s = IoLockFile(file, proc, req.Mode, req.Offset, req.Length, req.TimeoutMilliseconds); ObDereferenceObject(&file->Header); return s; } -// SYS_UNLOCK_FILE a0=*SYS_UNLOCK_FILE_REQUEST. -static uint64_t SyscallUnlockFile(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; - +// Implements SYS_UNLOCK_FILE. +static uint64_t SyscallUnlockFile(const SYS_UNLOCK_FILE_REQUEST* request) { SYS_UNLOCK_FILE_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req)) return NTLSTATUS_INVALID_PARAMETER; if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER; if (req.Length == 0) return NTLSTATUS_INVALID_PARAMETER; PROCESS* proc = KeGetCurrentProcess(); if (!proc) return NTLSTATUS_FAILURE; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_LOCK); - if (!file) return NTLSTATUS_INVALID_HANDLE; + KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS; + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, + SYS_FILE_RIGHT_LOCK, &fileHandleStatus); + if (!file) return fileHandleStatus; s = IoUnlockFile(file, proc, req.Offset, req.Length); ObDereferenceObject(&file->Header); return s; } -// SYS_CREATE_DIR a0=*path. -static uint64_t SyscallCreateDir(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_PATH_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - 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 NTLSTATUS_INVALID_PARAMETER; - return IoCreateDir(path); -} - -// SYS_REMOVE_DIR (68) a0=*path. -static uint64_t SyscallRemoveDir(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_PATH_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); - 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 NTLSTATUS_INVALID_PARAMETER; - return IoRemoveDir(path); -} - typedef struct SYS_SOCKET_SEND_TO_REQUEST_K { + uint32_t Size; const void* Buffer; size_t Length; uint32_t Flags; @@ -3936,6 +4471,7 @@ typedef struct SYS_SOCKET_SEND_TO_REQUEST_K { } SYS_SOCKET_SEND_TO_REQUEST_K; typedef struct SYS_SOCKET_RECV_FROM_REQUEST_K { + uint32_t Size; void* Buffer; size_t Length; uint32_t Flags; @@ -3945,6 +4481,7 @@ typedef struct SYS_SOCKET_RECV_FROM_REQUEST_K { } SYS_SOCKET_RECV_FROM_REQUEST_K; typedef struct SYS_RESOLVE_HOST_REQUEST_K { + uint32_t Size; SYS_STRING_VIEW Name; uint32_t Family; uint32_t Flags; @@ -3953,6 +4490,7 @@ typedef struct SYS_RESOLVE_HOST_REQUEST_K { } SYS_RESOLVE_HOST_REQUEST_K; typedef struct SYS_SOCKET_OPTION_REQUEST_K { + uint32_t Size; uint32_t Option; void* Value; size_t ValueSize; @@ -3961,8 +4499,8 @@ typedef struct SYS_SOCKET_OPTION_REQUEST_K { typedef struct SYS_ASYNC_SOCKET_REQUEST_K { uint32_t Size; - uint32_t SocketHandle; - uint32_t EventHandle; + SYS_HANDLE SocketHandle; + SYS_HANDLE EventHandle; uint32_t Operation; uint32_t Flags; uint32_t SocketFlags; @@ -3983,15 +4521,26 @@ typedef struct SYS_ASYNC_SOCKET_REQUEST_K { size_t OptionValueSize; size_t* OptionActualSizeOut; - uint32_t* HandleOut; + SYS_HANDLE* HandleOut; uint32_t* PollReturnedEventsOut; SYS_IO_STATUS_BLOCK_K* Iosb; SYS_USER_APC_ROUTINE_K ApcRoutine; void* ApcContext; - uint32_t* RequestHandleOut; + SYS_HANDLE* RequestHandleOut; } SYS_ASYNC_SOCKET_REQUEST_K; +_Static_assert(sizeof(SYS_SOCKET_SEND_TO_REQUEST_K) == sizeof(SYS_SOCKET_SEND_TO_REQUEST), + "send-to request ABI mismatch"); +_Static_assert(sizeof(SYS_SOCKET_RECV_FROM_REQUEST_K) == sizeof(SYS_SOCKET_RECV_FROM_REQUEST), + "recv-from request ABI mismatch"); +_Static_assert(sizeof(SYS_RESOLVE_HOST_REQUEST_K) == sizeof(SYS_RESOLVE_HOST_REQUEST), + "resolve-host request ABI mismatch"); +_Static_assert(sizeof(SYS_SOCKET_OPTION_REQUEST_K) == sizeof(SYS_SOCKET_OPTION_REQUEST), + "socket option request ABI mismatch"); +_Static_assert(sizeof(SYS_ASYNC_SOCKET_REQUEST_K) == sizeof(SYS_ASYNC_SOCKET_REQUEST), + "async socket request ABI mismatch"); + typedef struct SYS_MEMORY_STATUS_K { uint64_t FreePhysicalBytes; uint64_t KernelPoolBytes; @@ -4006,24 +4555,24 @@ typedef struct SYS_MEMORY_STATUS_K { #define SYS_POLL_MAX_FDS 64u -// SYS_CREATE_SOCKET a0=domain a1=type a2=protocol a3=*handleOut. -static uint64_t SyscallSocket(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - if (!a3) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_CREATE_SOCKET. +static uint64_t SyscallSocket(uint32_t domain, uint32_t type, uint32_t protocol, SYS_HANDLE* handleOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NETWORK_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; SOCKET_OBJECT* socket = NULL; - KSTATUS s = NetSocketCreate((uint32_t)a0, (uint32_t)a1, (uint32_t)a2, &socket); + KSTATUS s = NetSocketCreate(domain, type, protocol, &socket); if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&socket->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); + SYS_HANDLE h = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&socket->Header, KeGetValidHandleRights(ObjectTypeSocket), &h); if (!KSUCCESS(s)) return s; // KiAllocHandleForCurrentWithRights consumed NetSocketCreate's creator // reference; the returned process handle now owns the socket object. - s = KiCopyHandleToUser(a3, h); + s = KiCopyHandleToUser(handleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); @@ -4049,65 +4598,64 @@ static KSTATUS SyscallValidateSocketAddressInput(const SYS_SOCKET_ADDRESS_K* add return NTLSTATUS_SUCCESS; } -// SYS_BIND_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS a2=addressSize. -static uint64_t SyscallBind(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a1) return NTLSTATUS_INVALID_PARAMETER; - KSTATUS s = SyscallValidateSocketAddressBufferSize((size_t)a2); +// Implements SYS_BIND_SOCKET. +static uint64_t SyscallBind(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* userAddress, size_t addressSize) { + if (!userAddress) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS s = SyscallValidateSocketAddressBufferSize(addressSize); if (!KSUCCESS(s)) return s; SYS_SOCKET_ADDRESS_K address; - s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address)); + s = CopyFromUser(&address, userAddress, sizeof(address)); if (!KSUCCESS(s)) return s; s = SyscallValidateSocketAddressInput(&address); if (!KSUCCESS(s)) return s; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) return NTLSTATUS_NOT_FOUND; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_BIND, &handleStatus); + if (!socketObject) return handleStatus; - s = NetSocketBind(socket, &address, (size_t)a2); - ObDereferenceObject(&socket->Header); + s = NetSocketBind(socketObject, &address, addressSize); + ObDereferenceObject(&socketObject->Header); return s; } -// SYS_LISTEN_SOCKET a0=socketHandle a1=backlog. -static uint64_t SyscallListen(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; +// Implements SYS_LISTEN_SOCKET. +static uint64_t SyscallListen(SYS_HANDLE socket, uint32_t backlog) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_LISTEN, &handleStatus); + if (!socketObject) return handleStatus; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) return NTLSTATUS_NOT_FOUND; - - KSTATUS s = NetSocketListen(socket, (uint32_t)a1); - ObDereferenceObject(&socket->Header); + KSTATUS s = NetSocketListen(socketObject, backlog); + ObDereferenceObject(&socketObject->Header); return s; } -// SYS_ACCEPT_SOCKET a0=listenSocket a1=*remoteAddressOut(optional) +// Implements SYS_ACCEPT_SOCKET. // a2=*remoteAddressSizeInOut(optional) a3=*acceptedHandleOut. -static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - if (!a3) return NTLSTATUS_INVALID_PARAMETER; +static uint64_t SyscallAccept(SYS_HANDLE socket, SYS_SOCKET_ADDRESS* remoteAddressOut, size_t* remoteAddressSizeInOut, SYS_HANDLE* acceptedHandleOut) { + if (!acceptedHandleOut) return NTLSTATUS_INVALID_PARAMETER; - bool wantRemote = (a1 != 0); + bool wantRemote = (remoteAddressOut != 0); size_t remoteCapacity = 0; if (wantRemote) { - if (!a2) return NTLSTATUS_INVALID_PARAMETER; - KSTATUS cs = CopyFromUser(&remoteCapacity, (const void*)(uintptr_t)a2, sizeof(remoteCapacity)); + if (!remoteAddressSizeInOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS cs = CopyFromUser(&remoteCapacity, remoteAddressSizeInOut, sizeof(remoteCapacity)); if (!KSUCCESS(cs)) return cs; if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) { size_t required = sizeof(SYS_SOCKET_ADDRESS_K); - (void)CopyToUser((void*)(uintptr_t)a2, &required, sizeof(required)); + (void)CopyToUser(remoteAddressSizeInOut, &required, sizeof(required)); return NTLSTATUS_BUFFER_TOO_SMALL; } - cs = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(SYS_SOCKET_ADDRESS_K)); + cs = MmProbeUserWrite(remoteAddressOut, sizeof(SYS_SOCKET_ADDRESS_K)); if (!KSUCCESS(cs)) return cs; } - SOCKET_OBJECT* listener = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!listener) return NTLSTATUS_NOT_FOUND; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* listener = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_ACCEPT, &handleStatus); + if (!listener) return handleStatus; SOCKET_OBJECT* accepted = NULL; SYS_SOCKET_ADDRESS_K remote; @@ -4115,8 +4663,8 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, ObDereferenceObject(&listener->Header); if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = KiAllocHandleForCurrentWithRights(&accepted->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); + SYS_HANDLE h = SYS_HANDLE_INVALID; + s = KiAllocHandleForCurrentWithRights(&accepted->Header, KeGetValidHandleRights(ObjectTypeSocket), &h); if (!KSUCCESS(s)) return s; // KiAllocHandleForCurrentWithRights consumed the accept creator reference; @@ -4124,13 +4672,13 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, if (wantRemote) { size_t actual = sizeof(SYS_SOCKET_ADDRESS_K); - s = CopyToUser((void*)(uintptr_t)a1, &remote, sizeof(remote)); + s = CopyToUser(remoteAddressOut, &remote, sizeof(remote)); if (KSUCCESS(s)) - s = CopyToUser((void*)(uintptr_t)a2, &actual, sizeof(actual)); + s = CopyToUser(remoteAddressSizeInOut, &actual, sizeof(actual)); } if (KSUCCESS(s)) - s = KiCopyHandleToUser(a3, h); + s = KiCopyHandleToUser(acceptedHandleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); @@ -4139,118 +4687,120 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_CONNECT_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS a2=addressSize. -static uint64_t SyscallConnect(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a1) return NTLSTATUS_INVALID_PARAMETER; - KSTATUS s = SyscallValidateSocketAddressBufferSize((size_t)a2); +// Implements SYS_CONNECT_SOCKET. +static uint64_t SyscallConnect(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* userAddress, size_t addressSize) { + if (!userAddress) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS s = SyscallValidateSocketAddressBufferSize(addressSize); if (!KSUCCESS(s)) return s; SYS_SOCKET_ADDRESS_K address; - s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address)); + s = CopyFromUser(&address, userAddress, sizeof(address)); if (!KSUCCESS(s)) return s; s = SyscallValidateSocketAddressInput(&address); if (!KSUCCESS(s)) return s; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) return NTLSTATUS_NOT_FOUND; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_CONNECT, &handleStatus); + if (!socketObject) return handleStatus; - s = NetSocketConnect(socket, &address, (size_t)a2, (IO_ASYNC_REQUEST_OBJECT*)0); - ObDereferenceObject(&socket->Header); + s = NetSocketConnect(socketObject, &address, addressSize, (IO_ASYNC_REQUEST_OBJECT*)0); + ObDereferenceObject(&socketObject->Header); return s; } -// SYS_SEND_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*sentOut(optional). -static uint64_t SyscallSend(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - size_t length = (size_t)a2; - if (length && !a1) return NTLSTATUS_INVALID_PARAMETER; - if (length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_SEND_SOCKET. +static uint64_t SyscallSend(const SYS_SOCKET_SEND_REQUEST* request) { + SYS_SOCKET_SEND_REQUEST req; + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER; + if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; - if (length == 0) { - if (a4) return KiCopySizeToUser(a4, 0); + if (req.Length == 0) { + if (req.SentOut) return KiCopySizeToUser(req.SentOut, 0); return NTLSTATUS_SUCCESS; } - void* kbuf = MmAllocatePoolTag(length, MM_POOL_TAG('s', 'y', 's', ' ')); + void* kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' ')); if (!kbuf) return NTLSTATUS_NO_MEMORY; - KSTATUS s = CopyFromUser(kbuf, (const void*)(uintptr_t)a1, length); + s = CopyFromUser(kbuf, req.Buffer, req.Length); if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; } - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) { MmFreePool(kbuf); return NTLSTATUS_NOT_FOUND; } + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.Socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_SEND, &handleStatus); + if (!socket) { MmFreePool(kbuf); return handleStatus; } size_t sent = 0; - s = NetSocketSend(socket, kbuf, length, (uint32_t)a3, &sent); + s = NetSocketSend(socket, kbuf, req.Length, req.Flags, &sent); ObDereferenceObject(&socket->Header); MmFreePool(kbuf); - if (a4) { - KSTATUS cs = KiCopySizeToUser(a4, sent); - if (KSUCCESS(s)) s = cs; + if (req.SentOut) { + KSTATUS copyStatus = KiCopySizeToUser(req.SentOut, sent); + if (KSUCCESS(s)) s = copyStatus; } return s; } -// SYS_RECV_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*receivedOut(optional). -static uint64_t SyscallRecv(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - size_t length = (size_t)a2; - if (length && !a1) return NTLSTATUS_INVALID_PARAMETER; - if (length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; +// Receives bytes from a connected socket. +static uint64_t SyscallRecv(const SYS_SOCKET_RECV_REQUEST* request) { + SYS_SOCKET_RECV_REQUEST req; + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER; + if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; - if (length == 0) { - if (a4) return KiCopySizeToUser(a4, 0); + if (req.Length == 0) { + if (req.ReceivedOut) return KiCopySizeToUser(req.ReceivedOut, 0); return NTLSTATUS_SUCCESS; } - KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a1, length); + s = MmProbeUserWrite(req.Buffer, req.Length); if (!KSUCCESS(s)) return s; - void* kbuf = MmAllocatePoolTag(length, MM_POOL_TAG('s', 'y', 's', ' ')); + void* kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' ')); if (!kbuf) return NTLSTATUS_NO_MEMORY; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) { MmFreePool(kbuf); return NTLSTATUS_NOT_FOUND; } + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.Socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_RECEIVE, &handleStatus); + if (!socket) { MmFreePool(kbuf); return handleStatus; } size_t received = 0; - s = NetSocketReceive(socket, kbuf, length, (uint32_t)a3, &received, (IO_ASYNC_REQUEST_OBJECT*)0); + s = NetSocketReceive(socket, kbuf, req.Length, req.Flags, &received, + (IO_ASYNC_REQUEST_OBJECT*)0); ObDereferenceObject(&socket->Header); if (KSUCCESS(s) && received) - s = CopyToUser((void*)(uintptr_t)a1, kbuf, received); + s = CopyToUser(req.Buffer, kbuf, received); MmFreePool(kbuf); - if (KSUCCESS(s) && a4) - s = KiCopySizeToUser(a4, received); + if (KSUCCESS(s) && req.ReceivedOut) + s = KiCopySizeToUser(req.ReceivedOut, received); return s; } -// SYS_SHUTDOWN_SOCKET a0=socketHandle a1=SYS_SHUTDOWN_SOCKET_*. -static uint64_t SyscallShutdown(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; +// Shuts down one or both directions of a socket. +static uint64_t SyscallShutdown(SYS_HANDLE socket, uint32_t how) { + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_SHUTDOWN, &handleStatus); + if (!socketObject) return handleStatus; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) return NTLSTATUS_NOT_FOUND; - - KSTATUS s = NetSocketShutdown(socket, (uint32_t)a1); - ObDereferenceObject(&socket->Header); + KSTATUS s = NetSocketShutdown(socketObject, how); + ObDereferenceObject(&socketObject->Header); return s; } -// SYS_SEND_TO_SOCKET a0=socketHandle a1=*SYS_SOCKET_SEND_TO_REQUEST. -static uint64_t SyscallSendTo(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_SEND_TO_SOCKET. +static uint64_t SyscallSendTo(SYS_HANDLE socket, const SYS_SOCKET_SEND_TO_REQUEST* request) { + if (!request) return NTLSTATUS_INVALID_PARAMETER; SYS_SOCKET_SEND_TO_REQUEST_K req; - KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER; if (!req.RemoteAddress) return NTLSTATUS_INVALID_PARAMETER; @@ -4272,12 +4822,14 @@ static uint64_t SyscallSendTo(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; } } - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) { if (kbuf) MmFreePool(kbuf); return NTLSTATUS_NOT_FOUND; } + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_SEND, &handleStatus); + if (!socketObject) { if (kbuf) MmFreePool(kbuf); return handleStatus; } size_t sent = 0; - s = NetSocketSendTo(socket, kbuf, req.Length, req.Flags, &remote, req.RemoteAddressSize, &sent); - ObDereferenceObject(&socket->Header); + s = NetSocketSendTo(socketObject, kbuf, req.Length, req.Flags, &remote, req.RemoteAddressSize, &sent); + ObDereferenceObject(&socketObject->Header); if (kbuf) MmFreePool(kbuf); if (req.SentOut) { @@ -4287,14 +4839,12 @@ static uint64_t SyscallSendTo(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_RECV_FROM_SOCKET a0=socketHandle a1=*SYS_SOCKET_RECV_FROM_REQUEST. -static uint64_t SyscallRecvFrom(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_RECV_FROM_SOCKET. +static uint64_t SyscallRecvFrom(SYS_HANDLE socket, const SYS_SOCKET_RECV_FROM_REQUEST* request) { + if (!request) return NTLSTATUS_INVALID_PARAMETER; SYS_SOCKET_RECV_FROM_REQUEST_K req; - KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER; if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER; @@ -4326,14 +4876,16 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, if (!kbuf) return NTLSTATUS_NO_MEMORY; } - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) { if (kbuf) MmFreePool(kbuf); return NTLSTATUS_NOT_FOUND; } + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_RECEIVE, &handleStatus); + if (!socketObject) { if (kbuf) MmFreePool(kbuf); return handleStatus; } 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, (IO_ASYNC_REQUEST_OBJECT*)0); - ObDereferenceObject(&socket->Header); + s = NetSocketReceiveFrom(socketObject, kbuf, req.Length, req.Flags, wantRemote ? &remote : NULL, &received, (IO_ASYNC_REQUEST_OBJECT*)0); + ObDereferenceObject(&socketObject->Header); if (KSUCCESS(s) && received) s = CopyToUser(req.Buffer, kbuf, received); @@ -4351,14 +4903,14 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, return s; } -// SYS_RESOLVE_HOST a0=*SYS_RESOLVE_HOST_REQUEST. -static uint64_t SyscallResolveHost(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_RESOLVE_HOST. +static uint64_t SyscallResolveHost(const SYS_RESOLVE_HOST_REQUEST* request) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NETWORK_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!request) return NTLSTATUS_INVALID_PARAMETER; SYS_RESOLVE_HOST_REQUEST_K req; - KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (!req.Name.Data || req.Name.Length == 0 || req.Name.Length > SYS_RESOLVE_HOST_NAME_MAX) @@ -4394,14 +4946,12 @@ static uint64_t SyscallResolveHost(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser(req.AddressSizeInOut, &actual, sizeof(actual)); } -// SYS_SET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST. -static uint64_t SyscallSetSocketOption(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_SET_SOCKET_OPTION. +static uint64_t SyscallSetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request) { + if (!request) return NTLSTATUS_INVALID_PARAMETER; SYS_SOCKET_OPTION_REQUEST_K req; - KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (!req.Value) return NTLSTATUS_INVALID_PARAMETER; @@ -4410,34 +4960,36 @@ static uint64_t SyscallSetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2, s = CopyFromUser(value, req.Value, req.ValueSize); if (!KSUCCESS(s)) return s; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) return NTLSTATUS_NOT_FOUND; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_SOCKET_RIGHT_SET_OPTION, &handleStatus); + if (!socketObject) return handleStatus; - s = NetSocketSetOption(socket, req.Option, value, req.ValueSize); - ObDereferenceObject(&socket->Header); + s = NetSocketSetOption(socketObject, req.Option, value, req.ValueSize); + ObDereferenceObject(&socketObject->Header); return s; } -// SYS_GET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST. -static uint64_t SyscallGetSocketOption(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 NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_GET_SOCKET_OPTION. +static uint64_t SyscallGetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request) { + if (!request) return NTLSTATUS_INVALID_PARAMETER; SYS_SOCKET_OPTION_REQUEST_K req; - KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (!req.Value) return NTLSTATUS_INVALID_PARAMETER; uint8_t value[sizeof(uint64_t)]; if (req.ValueSize > sizeof(value)) return NTLSTATUS_BUFFER_TOO_SMALL; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); - if (!socket) return NTLSTATUS_NOT_FOUND; + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket, + SYS_RIGHT_QUERY, &handleStatus); + if (!socketObject) return handleStatus; size_t actual = 0; - s = NetSocketGetOption(socket, req.Option, value, req.ValueSize, &actual); - ObDereferenceObject(&socket->Header); + s = NetSocketGetOption(socketObject, req.Option, value, req.ValueSize, &actual); + ObDereferenceObject(&socketObject->Header); if (!KSUCCESS(s)) { if (s == NTLSTATUS_BUFFER_TOO_SMALL && req.ActualSizeOut) (void)CopyToUser(req.ActualSizeOut, &actual, sizeof(actual)); @@ -4461,15 +5013,12 @@ static int KiPollDeadlineExpired(uint64_t start, uint32_t timeoutMs) { return (gHal->Timer.QueryTicks() - start) >= ((uint64_t)timeoutMs * 1000u); } -// SYS_POLL a0=*SYS_POLL_REQUEST. -static uint64_t SyscallPoll(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; - +// Implements SYS_POLL. +static uint64_t SyscallPoll(const SYS_POLL_REQUEST* request) { SYS_POLL_REQUEST req; - KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - if (req.Size != sizeof(req) || !req.Fds) return NTLSTATUS_INVALID_PARAMETER; + if (!req.Fds) return NTLSTATUS_INVALID_PARAMETER; size_t count = req.Count; if (count == 0 || count > SYS_POLL_MAX_FDS) @@ -4490,7 +5039,9 @@ static uint64_t SyscallPoll(uint64_t a0, uint64_t a1, uint64_t a2, ready = 0; for (size_t i = 0; i < count; i++) { fds[i].ReturnedEvents = 0; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(fds[i].Handle, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject( + fds[i].Handle, ObjectTypeSocket, SYS_SOCKET_RIGHT_POLL, &handleStatus); if (!socket) { fds[i].ReturnedEvents = SYS_POLL_INVALID; ready++; @@ -4663,7 +5214,7 @@ static KSTATUS SyscallAsyncSocketPrepareOperation(PROCESS* proc, case SYS_ASYNC_SOCKET_OP_ACCEPT: { if (!req->HandleOut) return NTLSTATUS_INVALID_PARAMETER; - uint32_t zero = 0; + SYS_HANDLE zero = SYS_HANDLE_INVALID; KSTATUS status = MmLockUserBuffer(proc, req->HandleOut, sizeof(*req->HandleOut), 1, &submit->LockedHandleOut); if (!KSUCCESS(status)) @@ -4797,18 +5348,36 @@ static KSTATUS SyscallAsyncSocketPrepareOperation(PROCESS* proc, } } -// 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) + +// Returns the capability right required by one asynchronous socket operation. +static SYS_RIGHTS KiAsyncSocketRequiredRights(uint32_t operation) { + switch (operation) { + case SYS_ASYNC_SOCKET_OP_BIND: return SYS_SOCKET_RIGHT_BIND; + case SYS_ASYNC_SOCKET_OP_LISTEN: return SYS_SOCKET_RIGHT_LISTEN; + case SYS_ASYNC_SOCKET_OP_ACCEPT: return SYS_SOCKET_RIGHT_ACCEPT; + case SYS_ASYNC_SOCKET_OP_CONNECT: return SYS_SOCKET_RIGHT_CONNECT; + case SYS_ASYNC_SOCKET_OP_SEND: + case SYS_ASYNC_SOCKET_OP_SEND_TO: return SYS_SOCKET_RIGHT_SEND; + case SYS_ASYNC_SOCKET_OP_RECV: + case SYS_ASYNC_SOCKET_OP_RECV_FROM: return SYS_SOCKET_RIGHT_RECEIVE; + case SYS_ASYNC_SOCKET_OP_SHUTDOWN: return SYS_SOCKET_RIGHT_SHUTDOWN; + case SYS_ASYNC_SOCKET_OP_SET_OPT: return SYS_SOCKET_RIGHT_SET_OPTION; + case SYS_ASYNC_SOCKET_OP_GET_OPT: return SYS_RIGHT_QUERY; + case SYS_ASYNC_SOCKET_OP_POLL: return SYS_SOCKET_RIGHT_POLL; + default: return 0; + } +} + +// Implements SYS_SOCKET_IO_ASYNC. +static uint64_t SyscallSocketIoAsync(const SYS_ASYNC_SOCKET_REQUEST* request) { + if (!request) return NTLSTATUS_INVALID_PARAMETER; SYS_ASYNC_SOCKET_REQUEST_K req; - KSTATUS status = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req)); + KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(status)) return status; - if (req.Size != sizeof(req) || req.Flags != SYS_ASYNC_SOCKET_FLAG_NONE || !req.Iosb) + if (req.Flags != SYS_ASYNC_SOCKET_FLAG_NONE || !req.Iosb) return NTLSTATUS_INVALID_PARAMETER; if (req.RequestHandleOut) { status = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut)); @@ -4820,18 +5389,26 @@ static uint64_t SyscallSocketIoAsync(uint64_t a0, uint64_t a1, uint64_t a2, if (!proc) return NTLSTATUS_FAILURE; + SYS_RIGHTS socketRights = KiAsyncSocketRequiredRights(req.Operation); + if (!socketRights) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS socketHandleStatus = NTLSTATUS_SUCCESS; SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.SocketHandle, ObjectTypeSocket, - HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); + socketRights, + &socketHandleStatus); if (!socket) - return NTLSTATUS_NOT_FOUND; + return socketHandleStatus; EVENT* event = (EVENT*)0; if (req.EventHandle) { - event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); + KSTATUS eventHandleStatus = NTLSTATUS_SUCCESS; + event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, + SYS_EVENT_RIGHT_SIGNAL, &eventHandleStatus); if (!event) { ObDereferenceObject(&socket->Header); - return NTLSTATUS_NOT_FOUND; + return eventHandleStatus; } } @@ -4891,11 +5468,11 @@ static uint64_t SyscallSocketIoAsync(uint64_t a0, uint64_t a1, uint64_t a2, 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); + SYS_HANDLE requestHandle = SYS_HANDLE_INVALID; + KSTATUS hs = KeCreateHandle(proc, + NetAsyncSocketRequestObject(asyncReq), + KeGetValidHandleRights(ObjectTypeAsyncIo), + &requestHandle); if (!KSUCCESS(hs)) { (void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq); ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq)); @@ -4912,7 +5489,7 @@ static uint64_t SyscallSocketIoAsync(uint64_t a0, uint64_t a1, uint64_t a2, goto fail_after_submit; } } else if (req.RequestHandleOut) { - uint32_t zeroHandle = 0; + SYS_HANDLE zeroHandle = SYS_HANDLE_INVALID; KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle)); if (!KSUCCESS(cs)) { status = cs; @@ -4938,11 +5515,9 @@ fail_after_submit: 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) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_GET_MEMORY_STATUS. +static uint64_t SyscallGetMemoryStatus(SYS_MEMORY_STATUS* statusOut) { + if (!statusOut) return NTLSTATUS_INVALID_PARAMETER; MM_PFN_STATS pfn; MmPfnQueryStats(&pfn); @@ -4959,31 +5534,32 @@ static uint64_t SyscallGetMemoryStatus(uint64_t a0, uint64_t a1, uint64_t a2, status.UserPhysicalBytes = pfn.UserFrames * PAGE_SIZE; status.PageTablePhysicalBytes = pfn.PageTableFrames * PAGE_SIZE; - return CopyToUser((void*)(uintptr_t)a0, &status, sizeof(status)); + return CopyToUser(statusOut, &status, sizeof(status)); } -// SYS_QUERY_KERNEL_STATS a0=classId a1=buffer a2=bufferSize a3=*requiredSizeOut. -static KSTATUS KiReturnKernelStats(uint64_t bufferArg, size_t bufferSize, - uint64_t requiredArg, +// Implements SYS_QUERY_KERNEL_STATS. +static KSTATUS KiReturnKernelStats(void* buffer, size_t bufferSize, + size_t* requiredSizeOut, const void* src, size_t size) { - if (requiredArg) { - KSTATUS s = KiCopySizeToUser(requiredArg, size); + if (requiredSizeOut) { + KSTATUS s = KiCopySizeToUser(requiredSizeOut, size); if (!KSUCCESS(s)) return s; } - if (!bufferArg) + if (!buffer) return NTLSTATUS_SUCCESS; if (bufferSize < size) return NTLSTATUS_BUFFER_TOO_SMALL; - return CopyToUser((void*)(uintptr_t)bufferArg, src, size); + return CopyToUser(buffer, src, size); } -static uint64_t SyscallQueryDisplayInfo(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 NTLSTATUS_INVALID_PARAMETER; +// Returns the current display mode and framebuffer metadata. +static uint64_t SyscallQueryDisplayInfo(SYS_DISPLAY_INFO* infoOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + if (!infoOut) return NTLSTATUS_INVALID_PARAMETER; KE_DISPLAY_INFO ki; KSTATUS s = KeDisplayQueryInfo(&ki); @@ -4999,14 +5575,14 @@ static uint64_t SyscallQueryDisplayInfo(uint64_t a0, uint64_t a1, uint64_t a2, out.ManagerStats = ki.ManagerStats; out.DeviceStats = ki.DeviceStats; - return CopyToUser((void*)(uintptr_t)a0, &out, sizeof(out)); + return CopyToUser(infoOut, &out, sizeof(out)); } -static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - - switch ((uint32_t)a0) { +// Queries one class of kernel diagnostic statistics. +static uint64_t SyscallQueryKernelStats(uint32_t classId, void* buffer, size_t bufferSize, size_t* requiredSizeOut) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_KERNEL_DIAGNOSTICS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; + switch (classId) { case SYS_KERNEL_STATS_SCHEDULER: { KE_SCHED_STATS ks; SYS_SCHEDULER_STATS us; @@ -5032,7 +5608,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, us.PriorityInheritanceDrops = ks.PriorityInheritanceDrops; us.PriorityInheritancePropagations = ks.PriorityInheritancePropagations; us.PriorityInheritanceMaxDepth = ks.PriorityInheritanceMaxDepth; - return KiReturnKernelStats(a1, (size_t)a2, a3, &us, sizeof(us)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &us, sizeof(us)); } case SYS_KERNEL_STATS_PMM: { @@ -5049,7 +5625,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.FreeRunCount = km.FreeRunCount; for (size_t i = 0; i < 8; i++) um.FreeRunHistogram[i] = km.FreeRunHistogram[i]; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_PFN: { @@ -5111,7 +5687,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.ReclaimPagesSucceeded = km.ReclaimPagesSucceeded; um.ReclaimPagesFailed = km.ReclaimPagesFailed; um.ReclaimReentrantSkips = km.ReclaimReentrantSkips; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_PTE: { @@ -5145,7 +5721,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.ReplacedMappingFlushes = km.ReplacedMappingFlushes; um.PageTablePagesAllocated = km.PageTablePagesAllocated; um.CurrentLeafMappings = km.CurrentLeafMappings; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_VAD: { @@ -5186,7 +5762,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.ImageFixupPrivatePages = km.ImageFixupPrivatePages; um.SwapPageOuts = km.SwapPageOuts; um.SwapPageIns = km.SwapPageIns; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_FILE_CACHE: { @@ -5223,14 +5799,14 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.ActivePages = km.ActivePages; um.StandbyPages = km.StandbyPages; um.MaxResidentPages = km.MaxResidentPages; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_SURFACE: { SYS_SURFACE_STATS stats; memset(&stats, 0, sizeof(stats)); KiSurfaceStatsQuery(&stats); - return KiReturnKernelStats(a1, (size_t)a2, a3, &stats, sizeof(stats)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats)); } case SYS_KERNEL_STATS_SWAP: { @@ -5252,7 +5828,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.SlotAllocations = km.SlotAllocations; um.SlotFrees = km.SlotFrees; um.SlotAllocationFailures = km.SlotAllocationFailures; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_FILESYSTEM: { @@ -5275,7 +5851,36 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.DataReadSectors = km.DataReadSectors; um.DataReadRunMerges = km.DataReadRunMerges; um.MaxDataReadSpanSectors = km.MaxDataReadSpanSectors; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); + } + + case SYS_KERNEL_STATS_VOLUME: { + IO_VOLUME_STATS km; + SYS_VOLUME_STATS um; + IoQueryVolumeStats(&km); + memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; + um.CreatedVolumes = km.CreatedVolumes; + um.MountedVolumes = km.MountedVolumes; + um.RemovedVolumes = km.RemovedVolumes; + um.DeadIoRejects = km.DeadIoRejects; + um.ProviderDestroys = km.ProviderDestroys; + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); + } + + case SYS_KERNEL_STATS_VOLUME_MANAGER: { + IO_VOLUME_MANAGER_STATS km; + SYS_VOLUME_MANAGER_STATS um; + IoQueryVolumeManagerStats(&km); + memset(&um, 0, sizeof(um)); + um.Size = sizeof(um); + um.Version = SYS_STATS_VERSION; + um.Snapshots = km.Snapshots; + um.VolumeOpens = km.VolumeOpens; + um.Waits = km.Waits; + um.Wakeups = km.Wakeups; + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_DISK: { @@ -5292,7 +5897,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, um.WriteSectors = km.WriteSectors; um.MaxReadSectors = km.MaxReadSectors; um.MaxWriteSectors = km.MaxWriteSectors; - return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } @@ -5315,14 +5920,34 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, 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)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um)); } case SYS_KERNEL_STATS_EXCEPTION: { SYS_EXCEPTION_STATS stats; KeQueryExceptionStats(&stats); - return KiReturnKernelStats(a1, (size_t)a2, a3, &stats, sizeof(stats)); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats)); + } + + case SYS_KERNEL_STATS_PORT: { + KE_PORT_STATS kernelStats; + SYS_PORT_STATS stats; + memset(&kernelStats, 0, sizeof(kernelStats)); + memset(&stats, 0, sizeof(stats)); + KePortQueryStats(&kernelStats); + stats.Size = sizeof(stats); + stats.Version = SYS_STATS_VERSION; + stats.ConnectionsCreated = kernelStats.ConnectionsCreated; + stats.DirectDeliveries = kernelStats.DirectDeliveries; + stats.DirectBytes = kernelStats.DirectBytes; + stats.QueuedMessages = kernelStats.QueuedMessages; + stats.QueuedBytes = kernelStats.QueuedBytes; + stats.QueuedDeliveries = kernelStats.QueuedDeliveries; + stats.RepliesCompleted = kernelStats.RepliesCompleted; + stats.Disconnects = kernelStats.Disconnects; + stats.ProcessQuotaRejects = kernelStats.ProcessQuotaRejects; + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats)); } case SYS_KERNEL_STATS_CPU: { @@ -5348,13 +5973,13 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, KCPU* cpu = &gCpus[i]; SYS_CPU_STAT* out = &stats.Records[i]; out->CpuIndex = cpu->CpuIndex; - out->LapicId = cpu->LapicId; + out->HardwareId = cpu->HardwareId; KE_CPU_STATE state = (KE_CPU_STATE)cpu->State; if (state >= KeCpuPresent) out->Flags |= SYS_CPU_FLAG_PRESENT; if (state >= KeCpuStarted) out->Flags |= SYS_CPU_FLAG_STARTED; if (state == KeCpuParked) out->Flags |= SYS_CPU_FLAG_PARKED; if (state == KeCpuOnline) out->Flags |= SYS_CPU_FLAG_ONLINE; - if (cpu->IsBsp) out->Flags |= SYS_CPU_FLAG_BSP; + if (cpu->IsBootProcessor) out->Flags |= SYS_CPU_FLAG_BOOT_PROCESSOR; if (cpu->IsPrimaryThread) out->Flags |= SYS_CPU_FLAG_PRIMARY; out->CurrentTid = cpu->CurrentThread ? cpu->CurrentThread->ThreadId : 0; if (cpu->CurrentThread && cpu->CurrentThread->IsIdleThread) out->CurrentThreadFlags |= SYS_CPU_THREAD_FLAG_IDLE; @@ -5362,7 +5987,7 @@ static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2, out->ContextSwitches = cpu->ContextSwitches; } - return KiReturnKernelStats(a1, (size_t)a2, a3, &stats, size); + return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, size); } default: @@ -5483,20 +6108,19 @@ static uint32_t KiImportAudioWaitMask(uint32_t mask) { } // Looks up one audio stream handle with the requested operation rights. -static KE_AUDIO_STREAM* KiLookupAudioStream(uint32_t handle, uint32_t rights) { - OBJECT* object = KiHandleToObject(handle, ObjectTypeAudioStream, rights); +static KE_AUDIO_STREAM* KiLookupAudioStream(SYS_HANDLE handle, SYS_RIGHTS rights, KSTATUS* statusOut) { + OBJECT* object = KiHandleToObject(handle, ObjectTypeAudioStream, rights, statusOut); return object ? (KE_AUDIO_STREAM*)object : (KE_AUDIO_STREAM*)0; } -// SYS_AUDIO_ENUMERATE_ENDPOINTS a0=*SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST. -static uint64_t SyscallAudioEnumerateEndpoints(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; - +// Implements SYS_AUDIO_ENUMERATE_ENDPOINTS. +static uint64_t SyscallAudioEnumerateEndpoints(const SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST* userRequest) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; - if (request.Size != sizeof(request) || request.Flags != 0 || request.Reserved != 0 || + if (request.Flags != 0 || request.Reserved != 0 || !request.CountOut || (request.Capacity && !request.Endpoints)) return NTLSTATUS_INVALID_PARAMETER; @@ -5549,15 +6173,14 @@ static uint64_t SyscallAudioEnumerateEndpoints(uint64_t a0, uint64_t a1, uint64_ return request.Capacity < count ? NTLSTATUS_BUFFER_TOO_SMALL : NTLSTATUS_SUCCESS; } -// SYS_AUDIO_OPEN_STREAM a0=*SYS_AUDIO_OPEN_STREAM_REQUEST. -static uint64_t SyscallAudioOpenStream(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; - +// Implements SYS_AUDIO_OPEN_STREAM. +static uint64_t SyscallAudioOpenStream(const SYS_AUDIO_OPEN_STREAM_REQUEST* userRequest) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_ACCESS); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_AUDIO_OPEN_STREAM_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; - if (request.Size != sizeof(request) || request.Flags != 0 || request.Reserved != 0 || + if (request.Flags != 0 || request.Reserved != 0 || !request.EndpointId || !request.StreamHandleOut) return NTLSTATUS_INVALID_PARAMETER; @@ -5572,11 +6195,9 @@ static uint64_t SyscallAudioOpenStream(uint64_t a0, uint64_t a1, uint64_t a2, status = KeAudioCreateStream(request.EndpointId, &format, &stream); if (!KSUCCESS(status)) return status; - uint32_t handle = 0; + SYS_HANDLE handle = SYS_HANDLE_INVALID; status = KiAllocHandleForCurrentWithRights((OBJECT*)stream, - HANDLE_RIGHT_WRITE | - HANDLE_RIGHT_WAIT | - HANDLE_RIGHT_QUERY, + KeGetValidHandleRights(ObjectTypeAudioStream), &handle); if (!KSUCCESS(status)) return status; @@ -5588,15 +6209,12 @@ static uint64_t SyscallAudioOpenStream(uint64_t a0, uint64_t a1, uint64_t a2, return status; } -// SYS_AUDIO_WRITE a0=*SYS_AUDIO_WRITE_REQUEST. -static uint64_t SyscallAudioWrite(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; - +// Implements SYS_AUDIO_WRITE. +static uint64_t SyscallAudioWrite(const SYS_AUDIO_WRITE_REQUEST* userRequest) { SYS_AUDIO_WRITE_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; - if (request.Size != sizeof(request) || request.Flags != 0 || !request.StreamHandle || + if (request.Flags != 0 || !request.StreamHandle || !request.WrittenFramesOut || (request.FrameCount && !request.Frames)) return NTLSTATUS_INVALID_PARAMETER; @@ -5604,8 +6222,8 @@ static uint64_t SyscallAudioWrite(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(status)) return status; uint32_t written = 0; - KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, HANDLE_RIGHT_WRITE); - if (!stream) return NTLSTATUS_INVALID_HANDLE; + KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_AUDIO_STREAM_RIGHT_WRITE, &status); + if (!stream) return status; if (!request.FrameCount) { ObDereferenceObject((OBJECT*)stream); return CopyToUser(request.WrittenFramesOut, &written, sizeof(written)); @@ -5661,15 +6279,12 @@ static uint64_t SyscallAudioWrite(uint64_t a0, uint64_t a1, uint64_t a2, return CopyToUser(request.WrittenFramesOut, &written, sizeof(written)); } -// SYS_AUDIO_ARM_WAIT a0=*SYS_AUDIO_ARM_WAIT_REQUEST. -static uint64_t SyscallAudioArmWait(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; - +// Implements SYS_AUDIO_ARM_WAIT. +static uint64_t SyscallAudioArmWait(const SYS_AUDIO_ARM_WAIT_REQUEST* userRequest) { SYS_AUDIO_ARM_WAIT_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; - if (request.Size != sizeof(request) || request.Flags != 0 || request.Reserved != 0 || + if (request.Flags != 0 || request.Reserved != 0 || !request.StreamHandle || !request.Mask || (request.Mask & ~SYS_AUDIO_WAIT_VALID_MASK) != 0) return NTLSTATUS_INVALID_PARAMETER; @@ -5682,29 +6297,27 @@ static uint64_t SyscallAudioArmWait(uint64_t a0, uint64_t a1, uint64_t a2, condition.DrainedGeneration = request.DrainedGeneration; condition.DiscontinuityGeneration = request.DiscontinuityGeneration; - KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, HANDLE_RIGHT_WAIT); - if (!stream) return NTLSTATUS_INVALID_HANDLE; + KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_RIGHT_WAIT, &status); + if (!stream) return status; status = KeAudioArmStreamWait(stream, &condition); ObDereferenceObject((OBJECT*)stream); return status; } -// SYS_AUDIO_QUERY_STREAM a0=streamHandle a1=*SYS_AUDIO_STREAM_INFO. -static uint64_t SyscallAudioQueryStream(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 (!a0 || !a1) return NTLSTATUS_INVALID_PARAMETER; +// Implements SYS_AUDIO_QUERY_STREAM. +static uint64_t SyscallAudioQueryStream(SYS_HANDLE streamHandle, SYS_AUDIO_STREAM_INFO* infoOut) { + if (!streamHandle || !infoOut) return NTLSTATUS_INVALID_PARAMETER; uint32_t userSize = 0; - KSTATUS status = CopyFromUser(&userSize, (const void*)(uintptr_t)a1, sizeof(userSize)); + KSTATUS status = CopyFromUser(&userSize, infoOut, sizeof(userSize)); if (!KSUCCESS(status)) return status; if (userSize != sizeof(SYS_AUDIO_STREAM_INFO)) return NTLSTATUS_INVALID_PARAMETER; - status = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(SYS_AUDIO_STREAM_INFO)); + status = MmProbeUserWrite(infoOut, sizeof(SYS_AUDIO_STREAM_INFO)); if (!KSUCCESS(status)) return status; - KE_AUDIO_STREAM* stream = KiLookupAudioStream((uint32_t)a0, HANDLE_RIGHT_QUERY); - if (!stream) return NTLSTATUS_INVALID_HANDLE; + KE_AUDIO_STREAM* stream = KiLookupAudioStream(streamHandle, SYS_RIGHT_QUERY, &status); + if (!stream) return status; KE_AUDIO_STREAM_INFO kernelInfo; status = KeAudioQueryStream(stream, &kernelInfo); ObDereferenceObject((OBJECT*)stream); @@ -5732,79 +6345,65 @@ static uint64_t SyscallAudioQueryStream(uint64_t a0, uint64_t a1, uint64_t a2, publicInfo.VolumeQ16 = KiExportAudioGainQ16(kernelInfo.Volume); publicInfo.PanQ16 = KiExportAudioPanQ16(kernelInfo.Pan); publicInfo.Muted = kernelInfo.Muted; - return CopyToUser((void*)(uintptr_t)a1, &publicInfo, sizeof(publicInfo)); + return CopyToUser(infoOut, &publicInfo, sizeof(publicInfo)); } typedef KSTATUS (*KI_AUDIO_STREAM_OPERATION)(KE_AUDIO_STREAM* stream); // Invokes one existing kernel stream operation through a write-capable handle. -static KSTATUS KiCallAudioStreamOperation(uint32_t handle, KI_AUDIO_STREAM_OPERATION operation) { +static KSTATUS KiCallAudioStreamOperation(SYS_HANDLE handle, KI_AUDIO_STREAM_OPERATION operation) { if (!handle || !operation) return NTLSTATUS_INVALID_PARAMETER; - KE_AUDIO_STREAM* stream = KiLookupAudioStream(handle, HANDLE_RIGHT_WRITE); - if (!stream) return NTLSTATUS_INVALID_HANDLE; - KSTATUS status = operation(stream); + KSTATUS status = NTLSTATUS_SUCCESS; + KE_AUDIO_STREAM* stream = KiLookupAudioStream(handle, SYS_AUDIO_STREAM_RIGHT_CONTROL, &status); + if (!stream) return status; + status = operation(stream); ObDereferenceObject((OBJECT*)stream); return status; } -// SYS_AUDIO_START a0=streamHandle. -static uint64_t SyscallAudioStart(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; - return KiCallAudioStreamOperation((uint32_t)a0, KeAudioStartStream); +// Implements SYS_AUDIO_START. +static uint64_t SyscallAudioStart(SYS_HANDLE streamHandle) { + return KiCallAudioStreamOperation(streamHandle, KeAudioStartStream); } -// SYS_AUDIO_PAUSE a0=streamHandle. -static uint64_t SyscallAudioPause(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; - return KiCallAudioStreamOperation((uint32_t)a0, KeAudioPauseStream); +// Implements SYS_AUDIO_PAUSE. +static uint64_t SyscallAudioPause(SYS_HANDLE streamHandle) { + return KiCallAudioStreamOperation(streamHandle, KeAudioPauseStream); } -// SYS_AUDIO_RESUME a0=streamHandle. -static uint64_t SyscallAudioResume(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; - return KiCallAudioStreamOperation((uint32_t)a0, KeAudioResumeStream); +// Implements SYS_AUDIO_RESUME. +static uint64_t SyscallAudioResume(SYS_HANDLE streamHandle) { + return KiCallAudioStreamOperation(streamHandle, KeAudioResumeStream); } -// SYS_AUDIO_DRAIN a0=streamHandle. -static uint64_t SyscallAudioDrain(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; - return KiCallAudioStreamOperation((uint32_t)a0, KeAudioDrainStream); +// Implements SYS_AUDIO_DRAIN. +static uint64_t SyscallAudioDrain(SYS_HANDLE streamHandle) { + return KiCallAudioStreamOperation(streamHandle, KeAudioDrainStream); } -// SYS_AUDIO_STOP a0=streamHandle. -static uint64_t SyscallAudioStop(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; - return KiCallAudioStreamOperation((uint32_t)a0, KeAudioStopStream); +// Implements SYS_AUDIO_STOP. +static uint64_t SyscallAudioStop(SYS_HANDLE streamHandle) { + return KiCallAudioStreamOperation(streamHandle, KeAudioStopStream); } -// SYS_AUDIO_RESET a0=streamHandle. -static uint64_t SyscallAudioReset(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; - return KiCallAudioStreamOperation((uint32_t)a0, KeAudioResetStream); +// Implements SYS_AUDIO_RESET. +static uint64_t SyscallAudioReset(SYS_HANDLE streamHandle) { + return KiCallAudioStreamOperation(streamHandle, KeAudioResetStream); } -// SYS_AUDIO_SET_VOLUME a0=*SYS_AUDIO_SET_VOLUME_REQUEST. -static uint64_t SyscallAudioSetVolume(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; - +// Implements SYS_AUDIO_SET_VOLUME. +static uint64_t SyscallAudioSetVolume(const SYS_AUDIO_SET_VOLUME_REQUEST* userRequest) { SYS_AUDIO_SET_VOLUME_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; - if (request.Size != sizeof(request) || request.Flags != 0 || !request.StreamHandle || + if (request.Flags != 0 || !request.StreamHandle || request.Muted > 1u || request.VolumeQ16 > SYS_AUDIO_GAIN_Q16_UNITY || request.PanQ16 < SYS_AUDIO_PAN_Q16_LEFT || request.PanQ16 > SYS_AUDIO_PAN_Q16_RIGHT) return NTLSTATUS_INVALID_PARAMETER; - KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, HANDLE_RIGHT_WRITE); - if (!stream) return NTLSTATUS_INVALID_HANDLE; + KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_AUDIO_STREAM_RIGHT_CONTROL, &status); + if (!stream) return status; status = KeAudioSetStreamVolume(stream, KiImportAudioGainQ16(request.VolumeQ16), KiImportAudioPanQ16(request.PanQ16), @@ -5813,15 +6412,14 @@ static uint64_t SyscallAudioSetVolume(uint64_t a0, uint64_t a1, uint64_t a2, return status; } -// SYS_AUDIO_SET_MASTER_VOLUME a0=*SYS_AUDIO_SET_MASTER_VOLUME_REQUEST. -static uint64_t SyscallAudioSetMasterVolume(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; - +// Implements SYS_AUDIO_SET_MASTER_VOLUME. +static uint64_t SyscallAudioSetMasterVolume(const SYS_AUDIO_SET_MASTER_VOLUME_REQUEST* userRequest) { + KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_MASTER_CONTROL); + if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_AUDIO_SET_MASTER_VOLUME_REQUEST request; - KSTATUS status = KiCopyUserRequest(a0, &request, sizeof(request)); + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); if (!KSUCCESS(status)) return status; - if (request.Size != sizeof(request) || request.Flags != 0 || !request.EndpointId || + if (request.Flags != 0 || !request.EndpointId || request.Muted > 1u || request.VolumeQ16 > SYS_AUDIO_GAIN_Q16_UNITY) return NTLSTATUS_INVALID_PARAMETER; @@ -5831,175 +6429,223 @@ static uint64_t SyscallAudioSetMasterVolume(uint64_t a0, uint64_t a1, uint64_t a } // Dispatch table keyed by SYS_* constants. -typedef uint64_t (*SYSCALL_HANDLER)(uint64_t, uint64_t, uint64_t, - uint64_t, uint64_t, uint64_t); +typedef uint64_t (*SYSCALL_HANDLER0)(void); +typedef uint64_t (*SYSCALL_HANDLER1)(uint64_t); +typedef uint64_t (*SYSCALL_HANDLER2)(uint64_t, uint64_t); +typedef uint64_t (*SYSCALL_HANDLER3)(uint64_t, uint64_t, uint64_t); +typedef uint64_t (*SYSCALL_HANDLER4)(uint64_t, uint64_t, uint64_t, uint64_t); -static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { - // Process, threads, modules, waits, APC, and exceptions. - [SYS_EXIT_PROCESS] = SyscallExitProcess, - [SYS_EXIT_THREAD] = SyscallExitThread, - [SYS_CREATE_PROCESS] = SyscallCreateProcess, - [SYS_OPEN_PROCESS] = SyscallOpenProcess, - [SYS_GET_PROCESS_EXIT_CODE] = SyscallGetProcessExitCode, - [SYS_KILL_PROCESS] = SyscallKillProcess, - [SYS_GET_PROCESS_LIST] = SyscallGetProcessList, - [SYS_GET_PROCESS_PID] = SyscallGetProcessPid, - [SYS_CREATE_THREAD] = SyscallCreateThread, - [SYS_RESUME_THREAD] = SyscallResumeThread, - [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, - [SYS_YIELD] = SyscallYield, - [SYS_SLEEP] = SyscallSleep, - [SYS_WAIT_SINGLE] = SyscallWaitSingle, - [SYS_WAIT_MANY] = SyscallWaitMany, - [SYS_SET_USER_APC_DISPATCHER] = SyscallSetUserApcDispatcher, - [SYS_APC_RETURN] = SyscallApcReturn, - [SYS_SET_EXCEPTION_DISPATCHER] = SyscallSetExceptionDispatcher, - [SYS_GET_EXCEPTION_DISPATCHER] = SyscallGetExceptionDispatcher, - [SYS_CONTINUE_EXCEPTION] = SyscallContinueException, - [SYS_RAISE_EXCEPTION] = SyscallRaiseException, +typedef struct SYSCALL_ENTRY { + void* Handler; + uint8_t ArgumentCount; +} SYSCALL_ENTRY; + +#define SYSCALL_ENTRY0(handler) { handler, 0 } +#define SYSCALL_ENTRY1(handler) { handler, 1 } +#define SYSCALL_ENTRY2(handler) { handler, 2 } +#define SYSCALL_ENTRY3(handler) { handler, 3 } +#define SYSCALL_ENTRY4(handler) { handler, 4 } + +static const SYSCALL_ENTRY gSyscallTable[SYS_MAX] = { + // Process, threads, waits, APC, and exceptions. + [SYS_EXIT_PROCESS] = SYSCALL_ENTRY1(SyscallExitProcess), + [SYS_EXIT_THREAD] = SYSCALL_ENTRY1(SyscallExitThread), + [SYS_CREATE_PROCESS_OBJECT] = SYSCALL_ENTRY1(SyscallCreateProcessObject), + [SYS_ALLOC_PROCESS_VM] = SYSCALL_ENTRY1(SyscallAllocProcessVm), + [SYS_PROTECT_PROCESS_VM] = SYSCALL_ENTRY4(SyscallProtectProcessVm), + [SYS_START_PROCESS] = SYSCALL_ENTRY1(SyscallStartProcess), + [SYS_OPEN_PROCESS] = SYSCALL_ENTRY3(SyscallOpenProcess), + [SYS_GET_PROCESS_EXIT_CODE] = SYSCALL_ENTRY2(SyscallGetProcessExitCode), + [SYS_KILL_PROCESS] = SYSCALL_ENTRY2(SyscallKillProcess), + [SYS_GET_PROCESS_LIST] = SYSCALL_ENTRY3(SyscallGetProcessList), + [SYS_GET_PROCESS_PID] = SYSCALL_ENTRY2(SyscallGetProcessPid), + [SYS_CREATE_THREAD] = SYSCALL_ENTRY1(SyscallCreateThread), + [SYS_RESUME_THREAD] = SYSCALL_ENTRY2(SyscallResumeThread), + [SYS_GET_THREAD_LIST] = SYSCALL_ENTRY4(SyscallGetThreadList), + [SYS_GET_CURRENT_TID] = SYSCALL_ENTRY0(SyscallGetCurrentTid), + [SYS_GET_CURRENT_PID] = SYSCALL_ENTRY0(SyscallGetCurrentPid), + [SYS_SET_THREAD_PRIORITY] = SYSCALL_ENTRY1(SyscallSetThreadPriority), + [SYS_YIELD] = SYSCALL_ENTRY0(SyscallYield), + [SYS_SLEEP] = SYSCALL_ENTRY1(SyscallSleep), + [SYS_WAIT_SINGLE] = SYSCALL_ENTRY3(SyscallWaitSingle), + [SYS_WAIT_MANY] = SYSCALL_ENTRY1(SyscallWaitMany), + [SYS_SET_USER_APC_DISPATCHER] = SYSCALL_ENTRY1(SyscallSetUserApcDispatcher), + [SYS_APC_RETURN] = SYSCALL_ENTRY1(SyscallApcReturn), + [SYS_SET_EXCEPTION_DISPATCHER] = SYSCALL_ENTRY1(SyscallSetExceptionDispatcher), + [SYS_GET_EXCEPTION_DISPATCHER] = SYSCALL_ENTRY1(SyscallGetExceptionDispatcher), + [SYS_CONTINUE_EXCEPTION] = SYSCALL_ENTRY1(SyscallContinueException), + [SYS_RAISE_EXCEPTION] = SYSCALL_ENTRY1(SyscallRaiseException), // Virtual memory, mappings, and handles. - [SYS_ALLOC_VM] = SyscallVmAlloc, - [SYS_FREE_VM] = SyscallVmFree, - [SYS_PROTECT_VM] = SyscallVmProtect, - [SYS_QUERY_VM] = SyscallVmQuery, - [SYS_CLOSE_HANDLE] = SyscallCloseHandle, - [SYS_DUPLICATE_HANDLE] = SyscallDuplicateHandle, - [SYS_CREATE_FILE_MAPPING] = SyscallCreateFileMapping, - [SYS_MAP_FILE] = SyscallMapFile, + [SYS_ALLOC_VM] = SYSCALL_ENTRY1(SyscallVmAlloc), + [SYS_FREE_VM] = SYSCALL_ENTRY1(SyscallVmFree), + [SYS_PROTECT_VM] = SYSCALL_ENTRY4(SyscallVmProtect), + [SYS_QUERY_VM] = SYSCALL_ENTRY2(SyscallVmQuery), + [SYS_CLOSE_HANDLE] = SYSCALL_ENTRY1(SyscallCloseHandle), + [SYS_DUPLICATE_HANDLE] = SYSCALL_ENTRY1(SyscallDuplicateHandle), + [SYS_DERIVE_HANDLE] = SYSCALL_ENTRY1(SyscallDeriveHandle), + [SYS_REVOKE_HANDLE] = SYSCALL_ENTRY1(SyscallRevokeHandle), + [SYS_DERIVE_SECURITY_CONTEXT] = SYSCALL_ENTRY1(SyscallDeriveSecurityContext), + [SYS_QUERY_SECURITY_CONTEXT] = SYSCALL_ENTRY2(SyscallQuerySecurityContext), + [SYS_CREATE_FILE_MAPPING] = SYSCALL_ENTRY1(SyscallCreateFileMapping), + [SYS_MAP_FILE] = SYSCALL_ENTRY1(SyscallMapFile), // Synchronization objects. - [SYS_CREATE_MUTEX] = SyscallMutexCreate, - [SYS_TRY_ACQUIRE_MUTEX] = SyscallMutexTryAcquire, - [SYS_RELEASE_MUTEX] = SyscallMutexRelease, - [SYS_CREATE_SEM] = SyscallSemCreate, - [SYS_TRY_WAIT_SEM] = SyscallSemTryWait, - [SYS_SIGNAL_SEM] = SyscallSemSignal, - [SYS_CREATE_EVENT] = SyscallEventCreate, - [SYS_SIGNAL_EVENT] = SyscallEventSignal, - [SYS_CLEAR_EVENT] = SyscallEventClear, + [SYS_CREATE_MUTEX] = SYSCALL_ENTRY1(SyscallMutexCreate), + [SYS_TRY_ACQUIRE_MUTEX] = SYSCALL_ENTRY1(SyscallMutexTryAcquire), + [SYS_RELEASE_MUTEX] = SYSCALL_ENTRY1(SyscallMutexRelease), + [SYS_CREATE_SEM] = SYSCALL_ENTRY3(SyscallSemCreate), + [SYS_TRY_WAIT_SEM] = SYSCALL_ENTRY1(SyscallSemTryWait), + [SYS_SIGNAL_SEM] = SYSCALL_ENTRY1(SyscallSemSignal), + [SYS_CREATE_EVENT] = SYSCALL_ENTRY3(SyscallEventCreate), + [SYS_SIGNAL_EVENT] = SYSCALL_ENTRY1(SyscallEventSignal), + [SYS_CLEAR_EVENT] = SYSCALL_ENTRY1(SyscallEventClear), // Queues and named queues. - [SYS_CREATE_QUEUE] = SyscallQueueCreate, - [SYS_SEND_QUEUE] = SyscallQueueSend, - [SYS_RECV_QUEUE] = SyscallQueueRecv, - [SYS_PUBLISH_QUEUE] = SyscallPublishQueue, - [SYS_UNPUBLISH_QUEUE] = SyscallUnpublishQueue, - [SYS_OPEN_NAMED_QUEUE] = SyscallOpenNamedQueue, - [SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS] = SyscallOpenNamedQueueOwnerProcess, + [SYS_CREATE_QUEUE] = SYSCALL_ENTRY1(SyscallQueueCreate), + [SYS_SEND_QUEUE] = SYSCALL_ENTRY1(SyscallQueueSend), + [SYS_RECV_QUEUE] = SYSCALL_ENTRY1(SyscallQueueRecv), + [SYS_PUBLISH_QUEUE] = SYSCALL_ENTRY1(SyscallPublishQueue), + [SYS_UNPUBLISH_QUEUE] = SYSCALL_ENTRY1(SyscallUnpublishQueue), + [SYS_OPEN_NAMED_QUEUE] = SYSCALL_ENTRY1(SyscallOpenNamedQueue), + [SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS] = SYSCALL_ENTRY1(SyscallOpenNamedQueueOwnerProcess), - // Files, directories, and current directory. - [SYS_OPEN_FILE] = SyscallOpenFile, - [SYS_READ_FILE] = SyscallReadFile, - [SYS_WRITE_FILE] = SyscallWriteFile, - [SYS_READ_FILE_ASYNC] = SyscallReadFileAsync, - [SYS_WRITE_FILE_ASYNC] = SyscallWriteFileAsync, - [SYS_CANCEL_IO] = SyscallCancelIo, - [SYS_SEEK_FILE] = SyscallSeekFile, - [SYS_GET_FILE_POS] = SyscallGetFilePos, - [SYS_GET_FILE_SIZE] = SyscallGetFileSize, - [SYS_GET_FILE_STAT] = SyscallGetFileStat, - [SYS_STAT_FILE] = SyscallStatFile, - [SYS_TRUNCATE_FILE] = SyscallTruncateFile, - [SYS_FLUSH_FILE] = SyscallFlushFile, - [SYS_DELETE_FILE] = SyscallDeleteFile, - [SYS_RENAME_FILE] = SyscallRenameFile, - [SYS_LOCK_FILE] = SyscallLockFile, - [SYS_UNLOCK_FILE] = SyscallUnlockFile, - [SYS_OPEN_DIR] = SyscallOpenDir, - [SYS_READ_DIR] = SyscallReadDir, - [SYS_CREATE_DIR] = SyscallCreateDir, - [SYS_REMOVE_DIR] = SyscallRemoveDir, - [SYS_SET_CWD] = SyscallSetCwd, - [SYS_REAL_PATH] = SyscallRealPath, + // LPC-like Ports and well-known system services. + [SYS_CREATE_PORT] = SYSCALL_ENTRY1(SyscallCreatePort), + [SYS_CONNECT_PORT] = SYSCALL_ENTRY1(SyscallConnectPort), + [SYS_ACCEPT_PORT] = SYSCALL_ENTRY1(SyscallAcceptPort), + [SYS_SEND_PORT] = SYSCALL_ENTRY1(SyscallSendPort), + [SYS_RECEIVE_PORT] = SYSCALL_ENTRY1(SyscallReceivePort), + [SYS_CALL_PORT] = SYSCALL_ENTRY1(SyscallCallPort), + [SYS_REPLY_PORT] = SYSCALL_ENTRY1(SyscallReplyPort), + [SYS_DISCONNECT_PORT] = SYSCALL_ENTRY1(SyscallDisconnectPort), + [SYS_REGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallRegisterSystemService), + [SYS_UNREGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallUnregisterSystemService), + [SYS_CONNECT_SYSTEM_SERVICE] = SYSCALL_ENTRY1(SyscallConnectSystemService), + + // Mounted-volume manager control plane. + [SYS_QUERY_VOLUMES] = SYSCALL_ENTRY1(SyscallQueryVolumes), + [SYS_OPEN_VOLUME] = SYSCALL_ENTRY4(SyscallOpenVolume), + [SYS_WAIT_VOLUME_CHANGE] = SYSCALL_ENTRY3(SyscallWaitVolumeChange), + + // Capability-relative file and directory operations. + [SYS_OPEN_FILE_AT] = SYSCALL_ENTRY1(SyscallOpenFileAt), + [SYS_OPEN_DIR_AT] = SYSCALL_ENTRY1(SyscallOpenDirAt), + [SYS_STAT_AT] = SYSCALL_ENTRY1(SyscallStatAt), + [SYS_DELETE_AT] = SYSCALL_ENTRY1(SyscallDeleteAt), + [SYS_RENAME_AT] = SYSCALL_ENTRY1(SyscallRenameAt), + [SYS_CREATE_DIR_AT] = SYSCALL_ENTRY1(SyscallCreateDirAt), + [SYS_REMOVE_DIR_AT] = SYSCALL_ENTRY1(SyscallRemoveDirAt), + [SYS_READ_FILE] = SYSCALL_ENTRY1(SyscallReadFile), + [SYS_WRITE_FILE] = SYSCALL_ENTRY1(SyscallWriteFile), + [SYS_READ_FILE_ASYNC] = SYSCALL_ENTRY1(SyscallReadFileAsync), + [SYS_WRITE_FILE_ASYNC] = SYSCALL_ENTRY1(SyscallWriteFileAsync), + [SYS_CANCEL_IO] = SYSCALL_ENTRY1(SyscallCancelIo), + [SYS_SEEK_FILE] = SYSCALL_ENTRY2(SyscallSeekFile), + [SYS_GET_FILE_POS] = SYSCALL_ENTRY2(SyscallGetFilePos), + [SYS_GET_FILE_SIZE] = SYSCALL_ENTRY2(SyscallGetFileSize), + [SYS_GET_FILE_STAT] = SYSCALL_ENTRY2(SyscallGetFileStat), + [SYS_TRUNCATE_FILE] = SYSCALL_ENTRY2(SyscallTruncateFile), + [SYS_FLUSH_FILE] = SYSCALL_ENTRY1(SyscallFlushFile), + [SYS_LOCK_FILE] = SYSCALL_ENTRY1(SyscallLockFile), + [SYS_UNLOCK_FILE] = SYSCALL_ENTRY1(SyscallUnlockFile), + [SYS_READ_DIR] = SYSCALL_ENTRY2(SyscallReadDir), // Console, display, and surfaces. - [SYS_WRITE_CONSOLE] = SyscallWriteConsole, - [SYS_GET_CONSOLE_INFO] = SyscallGetConsoleInfo, - [SYS_SET_CONSOLE_CURSOR] = SyscallSetConsoleCursor, - [SYS_FILL_CONSOLE_CHARACTER] = SyscallFillConsoleCharacter, - [SYS_FILL_CONSOLE_ATTRIBUTE] = SyscallFillConsoleAttribute, - [SYS_PUT_SCREEN_CHAR] = SyscallPutScreenChar, - [SYS_CLEAR_SCREEN] = SyscallClearScreen, - [SYS_GET_SCREEN_SIZE] = SyscallGetScreenSize, - [SYS_QUERY_DISPLAY_INFO] = SyscallQueryDisplayInfo, - [SYS_OPEN_DISPLAY_CLOCK] = SyscallOpenDisplayClock, - [SYS_CREATE_SURFACE] = SyscallCreateSurface, - [SYS_MAP_SURFACE] = SyscallMapSurface, - [SYS_QUERY_SURFACE] = SyscallQuerySurface, - [SYS_PRESENT_SURFACE] = SyscallPresentSurface, + [SYS_WRITE_CONSOLE] = SYSCALL_ENTRY1(SyscallWriteConsole), + [SYS_GET_CONSOLE_INFO] = SYSCALL_ENTRY1(SyscallGetConsoleInfo), + [SYS_SET_CONSOLE_CURSOR] = SYSCALL_ENTRY2(SyscallSetConsoleCursor), + [SYS_FILL_CONSOLE_CHARACTER] = SYSCALL_ENTRY1(SyscallFillConsoleCharacter), + [SYS_FILL_CONSOLE_ATTRIBUTE] = SYSCALL_ENTRY1(SyscallFillConsoleAttribute), + [SYS_PUT_SCREEN_CHAR] = SYSCALL_ENTRY1(SyscallPutScreenChar), + [SYS_CLEAR_SCREEN] = SYSCALL_ENTRY1(SyscallClearScreen), + [SYS_GET_SCREEN_SIZE] = SYSCALL_ENTRY2(SyscallGetScreenSize), + [SYS_QUERY_DISPLAY_INFO] = SYSCALL_ENTRY1(SyscallQueryDisplayInfo), + [SYS_OPEN_DISPLAY_CLOCK] = SYSCALL_ENTRY1(SyscallOpenDisplayClock), + [SYS_CREATE_SURFACE] = SYSCALL_ENTRY1(SyscallCreateSurface), + [SYS_MAP_SURFACE] = SYSCALL_ENTRY3(SyscallMapSurface), + [SYS_QUERY_SURFACE] = SYSCALL_ENTRY2(SyscallQuerySurface), + [SYS_PRESENT_SURFACE] = SYSCALL_ENTRY3(SyscallPresentSurface), // Native render audio. - [SYS_AUDIO_ENUMERATE_ENDPOINTS] = SyscallAudioEnumerateEndpoints, - [SYS_AUDIO_OPEN_STREAM] = SyscallAudioOpenStream, - [SYS_AUDIO_WRITE] = SyscallAudioWrite, - [SYS_AUDIO_ARM_WAIT] = SyscallAudioArmWait, - [SYS_AUDIO_QUERY_STREAM] = SyscallAudioQueryStream, - [SYS_AUDIO_START] = SyscallAudioStart, - [SYS_AUDIO_PAUSE] = SyscallAudioPause, - [SYS_AUDIO_RESUME] = SyscallAudioResume, - [SYS_AUDIO_DRAIN] = SyscallAudioDrain, - [SYS_AUDIO_STOP] = SyscallAudioStop, - [SYS_AUDIO_RESET] = SyscallAudioReset, - [SYS_AUDIO_SET_VOLUME] = SyscallAudioSetVolume, - [SYS_AUDIO_SET_MASTER_VOLUME] = SyscallAudioSetMasterVolume, + [SYS_AUDIO_ENUMERATE_ENDPOINTS] = SYSCALL_ENTRY1(SyscallAudioEnumerateEndpoints), + [SYS_AUDIO_OPEN_STREAM] = SYSCALL_ENTRY1(SyscallAudioOpenStream), + [SYS_AUDIO_WRITE] = SYSCALL_ENTRY1(SyscallAudioWrite), + [SYS_AUDIO_ARM_WAIT] = SYSCALL_ENTRY1(SyscallAudioArmWait), + [SYS_AUDIO_QUERY_STREAM] = SYSCALL_ENTRY2(SyscallAudioQueryStream), + [SYS_AUDIO_START] = SYSCALL_ENTRY1(SyscallAudioStart), + [SYS_AUDIO_PAUSE] = SYSCALL_ENTRY1(SyscallAudioPause), + [SYS_AUDIO_RESUME] = SYSCALL_ENTRY1(SyscallAudioResume), + [SYS_AUDIO_DRAIN] = SYSCALL_ENTRY1(SyscallAudioDrain), + [SYS_AUDIO_STOP] = SYSCALL_ENTRY1(SyscallAudioStop), + [SYS_AUDIO_RESET] = SYSCALL_ENTRY1(SyscallAudioReset), + [SYS_AUDIO_SET_VOLUME] = SYSCALL_ENTRY1(SyscallAudioSetVolume), + [SYS_AUDIO_SET_MASTER_VOLUME] = SYSCALL_ENTRY1(SyscallAudioSetMasterVolume), // Input. - [SYS_POLL_KEYBOARD] = SyscallKbPoll, - [SYS_IS_KEY_DOWN] = SyscallKbIsKeyDown, - [SYS_READ_KEYBOARD_CHAR] = SyscallKbReadChar, - [SYS_READ_KEY] = SyscallKbReadKey, - [SYS_GET_KEY_MODIFIERS] = SyscallKbGetModifiers, - [SYS_QUERY_GLOBAL_INPUT_STATE] = SyscallQueryGlobalInputState, - [SYS_OPEN_INPUT_QUEUE] = SyscallOpenInputQueue, - [SYS_READ_INPUT_EVENT] = SyscallKbReadInputEvent, - [SYS_PEEK_INPUT_EVENT] = SyscallKbPeekInputEvent, - [SYS_PEEK_MOUSE] = SyscallMousePeek, - [SYS_READ_MOUSE] = SyscallMouseRead, - [SYS_GET_MOUSE_POS] = SyscallMouseGetPos, + [SYS_POLL_KEYBOARD] = SYSCALL_ENTRY1(SyscallKbPoll), + [SYS_IS_KEY_DOWN] = SYSCALL_ENTRY2(SyscallKbIsKeyDown), + [SYS_READ_KEYBOARD_CHAR] = SYSCALL_ENTRY1(SyscallKbReadChar), + [SYS_READ_KEY] = SYSCALL_ENTRY1(SyscallKbReadKey), + [SYS_GET_KEY_MODIFIERS] = SYSCALL_ENTRY0(SyscallKbGetModifiers), + [SYS_QUERY_GLOBAL_INPUT_STATE] = SYSCALL_ENTRY1(SyscallQueryGlobalInputState), + [SYS_OPEN_INPUT_QUEUE] = SYSCALL_ENTRY2(SyscallOpenInputQueue), + [SYS_READ_INPUT_EVENT] = SYSCALL_ENTRY1(SyscallKbReadInputEvent), + [SYS_PEEK_INPUT_EVENT] = SYSCALL_ENTRY1(SyscallKbPeekInputEvent), + [SYS_PEEK_MOUSE] = SYSCALL_ENTRY1(SyscallMousePeek), + [SYS_READ_MOUSE] = SYSCALL_ENTRY1(SyscallMouseRead), + [SYS_GET_MOUSE_POS] = SYSCALL_ENTRY2(SyscallMouseGetPos), // Time, debug, and system information. - [SYS_CREATE_TIMER] = SyscallCreateTimer, - [SYS_SET_TIMER] = SyscallSetTimer, - [SYS_CANCEL_TIMER] = SyscallCancelTimer, - [SYS_GET_TICK] = SyscallGetTick, - [SYS_GET_TICK_FREQUENCY] = SyscallGetTickFrequency, - [SYS_GET_SYSTEM_TIME] = SyscallGetSystemTime, - [SYS_PRINT_DEBUG] = SyscallDebugPrint, - [SYS_QUERY_TRAP_FRAME] = SyscallQueryTrapFrame, - [SYS_GET_MEMORY_STATUS] = SyscallGetMemoryStatus, - [SYS_QUERY_KERNEL_STATS] = SyscallQueryKernelStats, + [SYS_CREATE_TIMER] = SYSCALL_ENTRY1(SyscallCreateTimer), + [SYS_SET_TIMER] = SYSCALL_ENTRY1(SyscallSetTimer), + [SYS_CANCEL_TIMER] = SYSCALL_ENTRY1(SyscallCancelTimer), + [SYS_GET_TICK] = SYSCALL_ENTRY0(SyscallGetTick), + [SYS_GET_TICK_FREQUENCY] = SYSCALL_ENTRY1(SyscallGetTickFrequency), + [SYS_GET_SYSTEM_TIME] = SYSCALL_ENTRY1(SyscallGetSystemTime), + [SYS_PRINT_DEBUG] = SYSCALL_ENTRY1(SyscallDebugPrint), + [SYS_QUERY_TRAP_FRAME] = SYSCALL_ENTRY1(SyscallQueryTrapFrame), + [SYS_GET_MEMORY_STATUS] = SYSCALL_ENTRY1(SyscallGetMemoryStatus), + [SYS_QUERY_KERNEL_STATS] = SYSCALL_ENTRY4(SyscallQueryKernelStats), // 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, - [SYS_CONNECT_SOCKET] = SyscallConnect, - [SYS_SEND_SOCKET] = SyscallSend, - [SYS_RECV_SOCKET] = SyscallRecv, - [SYS_SHUTDOWN_SOCKET] = SyscallShutdown, - [SYS_SEND_TO_SOCKET] = SyscallSendTo, - [SYS_RECV_FROM_SOCKET] = SyscallRecvFrom, - [SYS_RESOLVE_HOST] = SyscallResolveHost, - [SYS_SET_SOCKET_OPTION] = SyscallSetSocketOption, - [SYS_GET_SOCKET_OPTION] = SyscallGetSocketOption, - [SYS_POLL] = SyscallPoll, + [SYS_CREATE_SOCKET] = SYSCALL_ENTRY4(SyscallSocket), + [SYS_SOCKET_IO_ASYNC] = SYSCALL_ENTRY1(SyscallSocketIoAsync), + [SYS_BIND_SOCKET] = SYSCALL_ENTRY3(SyscallBind), + [SYS_LISTEN_SOCKET] = SYSCALL_ENTRY2(SyscallListen), + [SYS_ACCEPT_SOCKET] = SYSCALL_ENTRY4(SyscallAccept), + [SYS_CONNECT_SOCKET] = SYSCALL_ENTRY3(SyscallConnect), + [SYS_SEND_SOCKET] = SYSCALL_ENTRY1(SyscallSend), + [SYS_RECV_SOCKET] = SYSCALL_ENTRY1(SyscallRecv), + [SYS_SHUTDOWN_SOCKET] = SYSCALL_ENTRY2(SyscallShutdown), + [SYS_SEND_TO_SOCKET] = SYSCALL_ENTRY2(SyscallSendTo), + [SYS_RECV_FROM_SOCKET] = SYSCALL_ENTRY2(SyscallRecvFrom), + [SYS_RESOLVE_HOST] = SYSCALL_ENTRY1(SyscallResolveHost), + [SYS_SET_SOCKET_OPTION] = SYSCALL_ENTRY2(SyscallSetSocketOption), + [SYS_GET_SOCKET_OPTION] = SYSCALL_ENTRY2(SyscallGetSocketOption), + [SYS_POLL] = SYSCALL_ENTRY1(SyscallPoll), }; -// KeSyscallDispatch is entered from ke_syscall.asm after the assembly stub has reordered registers into the C calling convention. +// Invokes one syscall handler through the MS x64 register ABI shape selected by its table entry. +static uint64_t KiInvokeSyscall(const SYSCALL_ENTRY* entry, + uint64_t a0, + uint64_t a1, + uint64_t a2, + uint64_t a3) { + switch (entry->ArgumentCount) { + case 0: return ((SYSCALL_HANDLER0)entry->Handler)(); + case 1: return ((SYSCALL_HANDLER1)entry->Handler)(a0); + case 2: return ((SYSCALL_HANDLER2)entry->Handler)(a0, a1); + case 3: return ((SYSCALL_HANDLER3)entry->Handler)(a0, a1, a2); + case 4: return ((SYSCALL_HANDLER4)entry->Handler)(a0, a1, a2, a3); + default: return NTLSTATUS_NOT_SUPPORTED; + } +} + +// Dispatches one user syscall after the assembly entry has restored its four ABI arguments. uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, - uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - KiValidateSyscallTrapFrame(nr, a0, a1, a2, a3, a4, a5); + uint64_t a2, uint64_t a3) { + KiValidateSyscallTrapFrame(nr, a0, a1, a2, a3); THREAD* self = KeGetCurrentThread(); PROCESS* owner = self ? self->Owner : (PROCESS*)0; @@ -6011,9 +6657,10 @@ uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, while (true) ; } - if (nr >= SYS_MAX || !gSyscallTable[nr]) return NTLSTATUS_NOT_SUPPORTED; + if (nr >= SYS_MAX || !gSyscallTable[nr].Handler) + return NTLSTATUS_NOT_SUPPORTED; - uint64_t result = gSyscallTable[nr](a0, a1, a2, a3, a4, a5); + uint64_t result = KiInvokeSyscall(&gSyscallTable[nr], a0, a1, a2, a3); // A process may be terminated while one of its threads is already inside a // syscall. Do not allow that thread to return to userspace after the owner diff --git a/code/kernel/ke_system_root.c b/code/kernel/ke_system_root.c index ab9c2ad..a2a28e5 100644 --- a/code/kernel/ke_system_root.c +++ b/code/kernel/ke_system_root.c @@ -1,60 +1,21 @@ -// System root volume selection. This file keeps boot-volume identity in one place so loaders do not guess. +// System root volume selection. This file retains the structurally selected mounted volume for bootstrap capability-relative I/O. #include #include #include -#include #include #include -#include +#include +#include #include -static uint8_t gSystemRootDrive; +static IO_VOLUME* gSystemRootVolume; static bool gSystemRootReady; #define KE_SYSTEM_ROOT_DISCOVERY_TIMEOUT_MS 30000ull #define KE_SYSTEM_ROOT_WAIT_SLICE_MS 1000ull #define KE_SYSTEM_ROOT_LOG_INTERVAL_MS 5000ull -// Builds a drive-qualified path without depending on any process CWD. -static bool KiBuildDrivePath(uint8_t drive, const char* relative, char* out, size_t outSize) { - if (!relative || !out || drive >= KE_SYSTEM_ROOT_MAX_DRIVES) - return false; - - size_t relLen = strlen(relative); - if (relLen + 4 > outSize) - return false; - - out[0] = (char)('0' + drive); - out[1] = ':'; - out[2] = '/'; - memcpy(out + 3, relative, relLen + 1); - return true; -} - -// Returns true when the candidate volume looks like the volume that booted the kernel. -static bool KiDriveLooksLikeSystemRoot(uint8_t drive) { - static const char* required[] = { - "system/colonel.exe", - "system/hal.dll", - "drivers/drivers.ini", - }; - - char path[1024]; - for (size_t i = 0; i < sizeof(required) / sizeof(required[0]); i++) { - if (!KiBuildDrivePath(drive, required[i], path, sizeof(path))) - return false; - - FILE_OBJECT* file = NULL; - KSTATUS s = IoOpenFile(SysSvFromCstr(path), &file); - if (!KSUCCESS(s)) - return false; - IoCloseFile(file); - } - - return true; -} - // Returns the current scheduler time in milliseconds. static uint64_t KiSystemRootNowMilliseconds(void) { KE_SCHED_STATS stats; @@ -62,54 +23,68 @@ static uint64_t KiSystemRootNowMilliseconds(void) { return stats.Ticks * SCHED_TICK_MS; } -// Scans the currently mounted volumes for the system tree. -static KSTATUS KiTrySelectSystemRootVolume(uint32_t* mountedOut) { - uint32_t mounted = 0; - for (uint8_t drive = 0; drive < KE_SYSTEM_ROOT_MAX_DRIVES; drive++) { - if (!IoFilesystemPresent(drive)) - continue; - mounted++; - if (!KiDriveLooksLikeSystemRoot(drive)) - continue; - - gSystemRootDrive = drive; - gSystemRootReady = true; - if (mountedOut) - *mountedOut = mounted; - DbgPrint("System root: drive=%u\n", drive); - return NTLSTATUS_SUCCESS; - } - +// Resolves the unique configured partition identity to its mounted filesystem volume. +static KSTATUS KiTrySelectSystemRootVolume(uint64_t* mountedOut) { + IO_VOLUME_STATS stats; + IoQueryVolumeStats(&stats); if (mountedOut) - *mountedOut = mounted; - return NTLSTATUS_NOT_FOUND; + *mountedOut = stats.MountedVolumes; + + IO_PARTITION_INFO systemPartition; + KSTATUS status = IoQuerySystemPartition(&systemPartition); + if (!KSUCCESS(status)) + return status; + + IO_VOLUME* volume = (IO_VOLUME*)0; + status = IoReferenceVolumeByBacking(systemPartition.DiskIndex, + systemPartition.StartLba, + &volume); + if (!KSUCCESS(status)) + return status; + + gSystemRootVolume = volume; // Retain the mounted-volume reference for the system lifetime. + gSystemRootReady = true; + DbgPrint("System root: volume=%u disk=%u start_lba=%llu sectors=%llu\n", + IoGetVolumeInstanceId(volume), systemPartition.DiskIndex, + (unsigned long long)systemPartition.StartLba, + (unsigned long long)systemPartition.SectorCount); + return NTLSTATUS_SUCCESS; } -// Waits for the mounted boot volume instead of racing asynchronous bus discovery. +// Waits for the configured structural system partition to be discovered and mounted. KSTATUS KeInitializeSystemRootVolume(void) { + if (gSystemRootReady) + return NTLSTATUS_SUCCESS; + uint64_t start = KiSystemRootNowMilliseconds(); uint64_t deadline = start + KE_SYSTEM_ROOT_DISCOVERY_TIMEOUT_MS; uint64_t nextLog = start; for (;;) { - uint64_t generation = IoGetVolumeNamespaceGeneration(); - uint32_t mounted = 0; + uint64_t generation = IoGetVolumeGeneration(); + uint64_t mounted = 0; KSTATUS status = KiTrySelectSystemRootVolume(&mounted); if (KSUCCESS(status)) return status; + if (status == NTLSTATUS_OBJECT_NAME_COLLISION) { + DbgPrint("System root: configured system_volume matches multiple mounted volumes\n"); + return status; + } + if (status != NTLSTATUS_NOT_FOUND) + return status; uint64_t now = KiSystemRootNowMilliseconds(); if (now >= deadline) { - DbgPrint("System root: discovery timed out after %llu ms; mounted=%u generation=%llu\n", + DbgPrint("System root: discovery timed out after %llu ms; mounted=%llu generation=%llu\n", (unsigned long long)KE_SYSTEM_ROOT_DISCOVERY_TIMEOUT_MS, - mounted, + (unsigned long long)mounted, (unsigned long long)generation); return NTLSTATUS_TIMEOUT; } if (now >= nextLog) { - DbgPrint("System root: waiting for boot volume; mounted=%u generation=%llu remaining=%llu ms\n", - mounted, + DbgPrint("System root: waiting for configured partition; mounted=%llu generation=%llu remaining=%llu ms\n", + (unsigned long long)mounted, (unsigned long long)generation, (unsigned long long)(deadline - now)); nextLog = now + KE_SYSTEM_ROOT_LOG_INTERVAL_MS; @@ -119,7 +94,7 @@ KSTATUS KeInitializeSystemRootVolume(void) { uint64_t wait = remaining < KE_SYSTEM_ROOT_WAIT_SLICE_MS ? remaining : KE_SYSTEM_ROOT_WAIT_SLICE_MS; - KSTATUS waitStatus = IoWaitForVolumeNamespaceChange(generation, wait); + KSTATUS waitStatus = IoWaitForVolumeChange(generation, wait); if (waitStatus != NTLSTATUS_SUCCESS && waitStatus != NTLSTATUS_TIMEOUT) return waitStatus; } @@ -130,26 +105,44 @@ bool KeSystemRootReady(void) { return gSystemRootReady; } -// Returns the selected system root drive, or zero before discovery. -uint8_t KeGetSystemRootDrive(void) { - return gSystemRootReady ? gSystemRootDrive : 0; +// References the selected system volume for capability transfer or kernel-relative I/O. +KSTATUS KeReferenceSystemRootVolume(IO_VOLUME** volumeOut) { + if (!volumeOut) + return NTLSTATUS_INVALID_PARAMETER; + *volumeOut = (IO_VOLUME*)0; + if (!gSystemRootReady || !gSystemRootVolume) + return NTLSTATUS_NOT_FOUND; + if (!IoVolumeTryReferenceMounted(gSystemRootVolume)) + return NTLSTATUS_OBJECT_DEAD; + *volumeOut = gSystemRootVolume; + return NTLSTATUS_SUCCESS; } -// Builds an absolute path rooted at the selected system volume. -bool KeBuildSystemRootPath(const char* relative, char* out, size_t outSize) { - if (!gSystemRootReady) - return false; - return KiBuildDrivePath(gSystemRootDrive, relative, out, outSize); -} - -// Opens a file rooted at the selected system volume. +// Opens a canonical native path rooted directly at the selected system volume. KSTATUS KeOpenSystemRootFile(const char* relative, FILE_OBJECT** out) { - if (!out) + if (!relative || !out) return NTLSTATUS_INVALID_PARAMETER; *out = NULL; - - char path[1024]; - if (!KeBuildSystemRootPath(relative, path, sizeof(path))) + if (!gSystemRootReady || !gSystemRootVolume) return NTLSTATUS_NOT_FOUND; - return IoOpenFile(SysSvFromCstr(path), out); + return IoOpenFileAtVolume(gSystemRootVolume, SysSvFromCstr(relative), out); +} + +// Creates a canonical native path rooted directly at the selected system volume. +KSTATUS KeCreateSystemRootFile(const char* relative, FILE_OBJECT** out) { + if (!relative || !out) + return NTLSTATUS_INVALID_PARAMETER; + *out = NULL; + if (!gSystemRootReady || !gSystemRootVolume) + return NTLSTATUS_NOT_FOUND; + return IoCreateFileAtVolume(gSystemRootVolume, SysSvFromCstr(relative), out); +} + +// Deletes a canonical native path rooted directly at the selected system volume. +KSTATUS KeDeleteSystemRootFile(const char* relative) { + if (!relative) + return NTLSTATUS_INVALID_PARAMETER; + if (!gSystemRootReady || !gSystemRootVolume) + return NTLSTATUS_NOT_FOUND; + return IoDeleteFileAtRoot(&gSystemRootVolume->Header, SysSvFromCstr(relative)); } diff --git a/code/kernel/ke_system_service.c b/code/kernel/ke_system_service.c new file mode 100644 index 0000000..65ade33 --- /dev/null +++ b/code/kernel/ke_system_service.c @@ -0,0 +1,92 @@ +// Kernel well-known system-service registry. This file intentionally owns only numeric service discovery. + +#include +#include +#include +#include +#include + +static SPINLOCK gSystemServiceLock = SPINLOCK_INIT; +static PORT* gSystemServices[SYS_SERVICE_MAX + 1u]; + +// Returns whether one numeric service identifier is valid for the current ABI. +static bool KiSystemServiceIdValid(SYS_SERVICE_ID serviceId) { + return serviceId > SYS_SERVICE_INVALID && serviceId <= SYS_SERVICE_MAX; +} + +// Returns the security claim that authorizes publication of one well-known service. +SYS_SERVICE_CLAIMS KeSystemServiceRequiredClaim(SYS_SERVICE_ID serviceId) { + switch (serviceId) { + case SYS_SERVICE_RESOURCE: + return SYS_SERVICE_CLAIM_RESOURCE; + default: + return 0; + } +} + +// Registers one weak listener reference without creating a string or pathname namespace. +KSTATUS KeSystemServiceRegister(SYS_SERVICE_ID serviceId, PORT* listener) { + if (!KiSystemServiceIdValid(serviceId) || !listener || !KePortIsListener(listener)) + return NTLSTATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&gSystemServiceLock); + if (gSystemServices[serviceId]) { + KeReleaseSpinlock(&gSystemServiceLock, flags); + return NTLSTATUS_ALREADY_EXISTS; + } + gSystemServices[serviceId] = listener; + KeReleaseSpinlock(&gSystemServiceLock, flags); + return NTLSTATUS_SUCCESS; +} + +// Removes one registration only when the caller still names the registered listener object. +KSTATUS KeSystemServiceUnregister(SYS_SERVICE_ID serviceId, PORT* listener) { + if (!KiSystemServiceIdValid(serviceId) || !listener || !KePortIsListener(listener)) + return NTLSTATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&gSystemServiceLock); + if (!gSystemServices[serviceId]) { + KeReleaseSpinlock(&gSystemServiceLock, flags); + return NTLSTATUS_NOT_FOUND; + } + if (gSystemServices[serviceId] != listener) { + KeReleaseSpinlock(&gSystemServiceLock, flags); + return NTLSTATUS_ACCESS_DENIED; + } + gSystemServices[serviceId] = (PORT*)0; + KeReleaseSpinlock(&gSystemServiceLock, flags); + return NTLSTATUS_SUCCESS; +} + +// References one currently registered listener for a connect operation. +KSTATUS KeSystemServiceReference(SYS_SERVICE_ID serviceId, PORT** listenerOut) { + if (!listenerOut) + return NTLSTATUS_INVALID_PARAMETER; + *listenerOut = (PORT*)0; + if (!KiSystemServiceIdValid(serviceId)) + return NTLSTATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&gSystemServiceLock); + PORT* listener = gSystemServices[serviceId]; + if (!listener || !ObTryReferenceObject((OBJECT*)listener)) { + KeReleaseSpinlock(&gSystemServiceLock, flags); + return NTLSTATUS_NOT_FOUND; + } + KeReleaseSpinlock(&gSystemServiceLock, flags); + + *listenerOut = listener; + return NTLSTATUS_SUCCESS; +} + +// Removes any weak registration whose listener is entering terminal object destruction. +void KeSystemServiceUnregisterListener(PORT* listener) { + if (!listener) + return; + + uintptr_t flags = KeAcquireSpinlock(&gSystemServiceLock); + for (SYS_SERVICE_ID id = 1; id <= SYS_SERVICE_MAX; ++id) { + if (gSystemServices[id] == listener) + gSystemServices[id] = (PORT*)0; + } + KeReleaseSpinlock(&gSystemServiceLock, flags); +} diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index a5f7a2a..ffd9771 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -16,6 +16,7 @@ #include #include #include +#include #define FPU_FCW_DEFAULT 0x037Fu #define FPU_MXCSR_DEFAULT 0x1F80u @@ -32,9 +33,14 @@ STATIC_ASSERT_OFFSET(THREAD, TssRsp0, 128); STATIC_ASSERT_OFFSET(THREAD, Teb, 136); STATIC_ASSERT_OFFSET(THREAD, FpuContext, 192); -static uint32_t gNextThreadId = 1; +static KE_ID_ALLOCATOR gThreadIds = KE_ID_ALLOCATOR_INIT(1u); extern void KeThreadEntry(void); +// Allocates one boot-unique thread identifier for normal and hand-built idle threads. +KSTATUS KeAllocateThreadId(uint32_t* threadIdOut) { + return KeAllocateId32(&gThreadIds, threadIdOut); +} + // Prevents a newly scheduled user thread from entering a terminating process. void KePrepareUserThreadStart(void) { THREAD* thread = KeGetCurrentThread(); @@ -85,11 +91,11 @@ void KeFinalizeThreadExit(THREAD* thread) { uint32_t remaining = 0; int detached = 0; - uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); + uintptr_t flags = KeAcquireSpinlock(&owner->ThreadLock); int fwdDetached = thread->ProcessLink.Fwd == &thread->ProcessLink; int bwdDetached = thread->ProcessLink.Bwd == &thread->ProcessLink; if (fwdDetached != bwdDetached) { - KeReleaseSpinlock(&owner->HandleLock, flags); + KeReleaseSpinlock(&owner->ThreadLock, flags); KePanic(KE_PANIC_CODE_SCHEDULER, "thread process linkage corrupt: thread=0x%016llX tid=%u fwd=0x%016llX bwd=0x%016llX\n", (uint64_t)thread, @@ -100,7 +106,7 @@ void KeFinalizeThreadExit(THREAD* thread) { if (!fwdDetached) { if (owner->ThreadCount == 0) { - KeReleaseSpinlock(&owner->HandleLock, flags); + KeReleaseSpinlock(&owner->ThreadLock, flags); KePanic(KE_PANIC_CODE_SCHEDULER, "thread process count underflow: thread=0x%016llX tid=%u pid=%u\n", (uint64_t)thread, @@ -113,7 +119,7 @@ void KeFinalizeThreadExit(THREAD* thread) { detached = 1; } remaining = owner->ThreadCount; - KeReleaseSpinlock(&owner->HandleLock, flags); + KeReleaseSpinlock(&owner->ThreadLock, flags); if (!detached) return; @@ -122,6 +128,7 @@ void KeFinalizeThreadExit(THREAD* thread) { // ThreadCount represents live process membership, not retained thread // handles. Dead THREAD objects may remain waitable after process exit. _InterlockedExchange(&owner->Terminating, 1); + KeStopProcessConstruction(owner); KeCompleteProcessExitIfLastThread(owner); } } @@ -184,12 +191,12 @@ static void KiThreadDestroy(OBJECT* obj) { if (thread->Owner) { ObDereferenceObject(&thread->Owner->Header); - thread->Owner = (PROCESS *)0; + thread->Owner = (PROCESS*)0; } if (thread->KernelStackBase) { MmFreePool(thread->KernelStackBase); - thread->KernelStackBase = (void *)0; + thread->KernelStackBase = (void*)0; } } @@ -204,11 +211,15 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui uint8_t initiallySuspended = (parameters->Flags & KE_THREAD_CREATE_FLAG_SUSPENDED) ? 1u : 0u; - THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); + uint32_t threadId = 0; + KSTATUS idStatus = KeAllocateThreadId(&threadId); + if (!KSUCCESS(idStatus)) return idStatus; + + THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); if (!thread) return NTLSTATUS_NO_MEMORY; thread->Header.Destroy = KiThreadDestroy; - thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1); + thread->ThreadId = threadId; thread->State = initiallySuspended ? ThreadSuspended : ThreadReady; thread->BaseSchedulingClass = parameters->SchedulingClass; thread->BasePriority = parameters->Priority; @@ -232,7 +243,7 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui : KeGetDefaultThreadAffinityMask(); LIST_INIT(&thread->ProcessLink); LIST_INIT(&thread->SchedulerLink); - thread->WaitObject = (OBJECT *)0; + thread->WaitObject = (OBJECT*)0; thread->WaitStatus = NTLSTATUS_SUCCESS; thread->WaitListHead = (LIST_ENTRY*)0; thread->AlertableWait = 0; @@ -310,7 +321,7 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui thread->UserEntry = entry; thread->UserArg = arg; - uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop; + uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop; *(--ksp) = userStackTop; *(--ksp) = 0x23ULL; *(--ksp) = userStackTop; @@ -320,7 +331,7 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui *(--ksp) = arg; ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); - KCONTEXT* ctx = (KCONTEXT *)ksp; + KCONTEXT* ctx = (KCONTEXT*)ksp; memset(ctx, 0, sizeof(KCONTEXT)); ctx->Rip = (uint64_t)KeThreadEntry; thread->SavedContext = ctx; @@ -347,10 +358,10 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui } thread->ExitEvent = exitEvent; - uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); + uintptr_t flags = KeAcquireSpinlock(&owner->ThreadLock); KeListInsertTailChecked(&owner->ThreadList, &thread->ProcessLink); owner->ThreadCount++; - KeReleaseSpinlock(&owner->HandleLock, flags); + KeReleaseSpinlock(&owner->ThreadLock, flags); KeInitThreadFpuContext(thread); @@ -432,8 +443,8 @@ static KSTATUS KiCreateKernelThread(uintptr_t fn, uintptr_t arg, const KE_THREAD_CREATE_PARAMETERS* parameters, THREAD** out) { // Kernel worker threads are owned by the system process. The scheduler's - // hand-built CPU idle thread stays attached to PID 0 (idle), while driver, - // async I/O, and init worker threads are grouped under PID 1 (system). + // hand-built CPU idle thread stays attached to the dedicated idle process, while + // driver, async I/O, and init worker threads use the system process. PROCESS* owner = KeGetSystemProcess(); if (!owner) { THREAD* currentThread = KeGetCurrentThread(); @@ -455,11 +466,15 @@ static KSTATUS KiCreateKernelThread(uintptr_t fn, uintptr_t arg, uint8_t initiallySuspended = (parameters->Flags & KE_THREAD_CREATE_FLAG_SUSPENDED) ? 1u : 0u; + uint32_t threadId = 0; + KSTATUS idStatus = KeAllocateThreadId(&threadId); + if (!KSUCCESS(idStatus)) return idStatus; + THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); if (!thread) return NTLSTATUS_NO_MEMORY; thread->Header.Destroy = KiThreadDestroy; - thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1); + thread->ThreadId = threadId; thread->State = initiallySuspended ? ThreadSuspended : ThreadReady; thread->BaseSchedulingClass = parameters->SchedulingClass; thread->BasePriority = parameters->Priority; @@ -528,10 +543,10 @@ static KSTATUS KiCreateKernelThread(uintptr_t fn, uintptr_t arg, ObReferenceObject(&owner->Header); thread->Owner = owner; - uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); + uintptr_t flags = KeAcquireSpinlock(&owner->ThreadLock); KeListInsertTailChecked(&owner->ThreadList, &thread->ProcessLink); owner->ThreadCount++; - KeReleaseSpinlock(&owner->HandleLock, flags); + KeReleaseSpinlock(&owner->ThreadLock, flags); // Hand the creator a short-lived reference before making the kernel // thread runnable. Kernel workers can run, signal their completion event, diff --git a/code/kernel/ke_time.c b/code/kernel/ke_time.c index fe069ed..bd03e9c 100644 --- a/code/kernel/ke_time.c +++ b/code/kernel/ke_time.c @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include @@ -526,6 +526,27 @@ uint64_t KeQueryMonotonicTicks(void) { : 0; } +// Returns a saturated monotonic deadline for a relative millisecond interval. +uint64_t KeMonotonicDeadlineAfterMilliseconds(uint64_t milliseconds) { + uint64_t now = KeQueryMonotonicTicks(); + if (milliseconds > (UINT64_MAX - now) / 1000ull) + return UINT64_MAX; + return now + (milliseconds * 1000ull); +} + +// Returns the remaining whole milliseconds, rounded up, until a monotonic deadline. +uint64_t KeMonotonicMillisecondsUntil(uint64_t deadlineMicroseconds) { + uint64_t now = KeQueryMonotonicTicks(); + if (deadlineMicroseconds <= now) + return 0; + + uint64_t remaining = deadlineMicroseconds - now; + uint64_t milliseconds = remaining / 1000ull; + if ((remaining % 1000ull) != 0) + milliseconds++; + return milliseconds; +} + // Returns unix Time Seconds. __declspec(dllexport) uint64_t KeQueryUnixTimeSeconds(void) { SYS_SYSTEM_TIME t; diff --git a/code/kernel/ke_timer.c b/code/kernel/ke_timer.c index ff64419..429a7e8 100644 --- a/code/kernel/ke_timer.c +++ b/code/kernel/ke_timer.c @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #define TIMER_SIGNAL_BATCH 32u @@ -23,29 +23,19 @@ static void KiTimerEnsureInitialized(void) { gTimerInitialized = 1; } -// Reads the scheduler tick value used by timer deadlines. -static uint64_t KiTimerReadSchedulerTick(void) { - KE_SCHED_STATS stats; - KeGetSchedulerStats(&stats); - return stats.Ticks; +// Converts a millisecond interval to microseconds with saturation. +static uint64_t KiTimerMillisecondsToMicroseconds(uint64_t milliseconds) { + if (milliseconds > UINT64_MAX / 1000ull) + return UINT64_MAX; + return milliseconds * 1000ull; } -// Converts a millisecond interval to at least one scheduler tick. -static uint64_t KiTimerMillisecondsToTicks(uint64_t milliseconds) { - if (milliseconds == 0) - return 0; - - uint64_t ticks = milliseconds / SCHED_TICK_MS; - if ((milliseconds % SCHED_TICK_MS) != 0) - ticks++; - return ticks ? ticks : 1; -} - -// Adds a tick interval to the current scheduler tick with saturation. -static uint64_t KiTimerDeadlineFromNow(uint64_t intervalTicks) { - uint64_t now = KiTimerReadSchedulerTick(); - uint64_t due = now + intervalTicks; - return due < now ? UINT64_MAX : due; +// Adds a monotonic interval to the current time with saturation. +static uint64_t KiTimerDeadlineFromNow(uint64_t intervalMicroseconds) { + uint64_t now = KeQueryMonotonicTicks(); + if (intervalMicroseconds > UINT64_MAX - now) + return UINT64_MAX; + return now + intervalMicroseconds; } // Removes an active timer from the global deadline list while the timer lock is held. @@ -56,8 +46,8 @@ static uint8_t KiTimerRemoveLocked(KTIMER* timer) { KeListRemoveChecked(&timer->Link); timer->Linked = 0; timer->Active = 0; - timer->DueTick = 0; - timer->PeriodTicks = 0; + timer->DueMicroseconds = 0; + timer->PeriodMicroseconds = 0; return 1; } @@ -110,27 +100,27 @@ KSTATUS KeTimerSet(KTIMER* timer, uint64_t dueTimeMilliseconds, uint64_t periodM KiTimerEnsureInitialized(); - uint64_t dueTicks = KiTimerMillisecondsToTicks(dueTimeMilliseconds); - uint64_t periodTicks = KiTimerMillisecondsToTicks(periodMilliseconds); + uint64_t dueMicroseconds = KiTimerMillisecondsToMicroseconds(dueTimeMilliseconds); + uint64_t periodMicroseconds = KiTimerMillisecondsToMicroseconds(periodMilliseconds); uint8_t dropListReference = 0; uint8_t addListReference = 0; - uint8_t signalNow = dueTicks == 0; + uint8_t signalNow = dueMicroseconds == 0; EVENT* event = timer->Event; ObReferenceObject(&event->Header); uintptr_t flags = KeAcquireSpinlock(&gTimerLock); if (timer->Linked) { - if (signalNow && periodTicks == 0) { + if (signalNow && periodMicroseconds == 0) { dropListReference = KiTimerRemoveLocked(timer); } else { - timer->DueTick = KiTimerDeadlineFromNow(signalNow ? periodTicks : dueTicks); - timer->PeriodTicks = periodTicks; + timer->DueMicroseconds = KiTimerDeadlineFromNow(signalNow ? periodMicroseconds : dueMicroseconds); + timer->PeriodMicroseconds = periodMicroseconds; timer->Active = 1; } - } else if (!signalNow || periodTicks != 0) { - timer->DueTick = KiTimerDeadlineFromNow(signalNow ? periodTicks : dueTicks); - timer->PeriodTicks = periodTicks; + } else if (!signalNow || periodMicroseconds != 0) { + timer->DueMicroseconds = KiTimerDeadlineFromNow(signalNow ? periodMicroseconds : dueMicroseconds); + timer->PeriodMicroseconds = periodMicroseconds; timer->Active = 1; timer->Linked = 1; KeListInsertTailChecked(&gTimerList, &timer->Link); @@ -183,7 +173,7 @@ void KeTimerTick(void) { KTIMER* listReferencesToDrop[TIMER_SIGNAL_BATCH]; uint32_t count = 0; uint32_t dropCount = 0; - uint64_t now = KiTimerReadSchedulerTick(); + uint64_t now = KeQueryMonotonicTicks(); uintptr_t flags = KeAcquireSpinlock(&gTimerLock); LIST_ENTRY* node = gTimerList.Fwd; @@ -191,15 +181,15 @@ void KeTimerTick(void) { LIST_ENTRY* next = node->Fwd; KTIMER* timer = CONTAINING_RECORD(node, KTIMER, Link); - if (timer->Active && timer->DueTick <= now) { + if (timer->Active && timer->DueMicroseconds <= now) { if (timer->Event) { ObReferenceObject(&timer->Event->Header); events[count++] = timer->Event; } - if (timer->PeriodTicks != 0) { - uint64_t due = now + timer->PeriodTicks; - timer->DueTick = due < now ? UINT64_MAX : due; + if (timer->PeriodMicroseconds != 0) { + uint64_t due = now + timer->PeriodMicroseconds; + timer->DueMicroseconds = due < now ? UINT64_MAX : due; } else { if (KiTimerRemoveLocked(timer)) listReferencesToDrop[dropCount++] = timer; diff --git a/code/kernel/ke_trap.c b/code/kernel/ke_trap.c index 9dc0107..8971f51 100644 --- a/code/kernel/ke_trap.c +++ b/code/kernel/ke_trap.c @@ -97,8 +97,7 @@ void KiExitTrapFrame(KTRAP_FRAME* frame) { } void KiValidateSyscallTrapFrame(uint64_t nr, uint64_t a0, uint64_t a1, - uint64_t a2, uint64_t a3, uint64_t a4, - uint64_t a5) { + uint64_t a2, uint64_t a3) { KTRAP_FRAME* f = KeGetCurrentTrapFrame(); if (!f) KePanic(KE_PANIC_CODE_TRAP, "syscall trap validation: no current trap frame for nr=%llu\n", nr); @@ -110,11 +109,10 @@ void KiValidateSyscallTrapFrame(uint64_t nr, uint64_t a0, uint64_t a1, KePanic(KE_PANIC_CODE_TRAP, "syscall trap validation: syscall did not come from user cs=0x%016llX mode=%u\n", f->Cs, f->PreviousMode); if (f->Rax != nr || f->Rcx != a0 || f->Rdx != a1 || f->R8 != a2 || - f->R9 != a3 || f->R10 != a4 || f->R11 != a5) { - KePanic(KE_PANIC_CODE_TRAP, "syscall trap validation: arg mismatch nr=%llu tf=%llu/%llX/%llX/%llX/%llX/%llX/%llX call=%llu/%llX/%llX/%llX/%llX/%llX/%llX\n", - nr, - f->Rax, f->Rcx, f->Rdx, f->R8, f->R9, f->R10, f->R11, - nr, a0, a1, a2, a3, a4, a5); + f->R9 != a3) { + KePanic(KE_PANIC_CODE_TRAP, "syscall trap validation: arg mismatch nr=%llu tf=%llu/%llX/%llX/%llX/%llX call=%llu/%llX/%llX/%llX/%llX\n", + nr, f->Rax, f->Rcx, f->Rdx, f->R8, f->R9, + nr, a0, a1, a2, a3); } } diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index 13dbb1d..0bd0e6f 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -3,7 +3,9 @@ #include #include #include +#pragma intrinsic(_disable) #include +#include #include #include #include @@ -19,10 +21,11 @@ #include #include #include -#include #include -#include +#include #include +#include +#include #include #include #include @@ -33,11 +36,13 @@ #include #include #include -#include +#include +#include typedef struct { - BOOT_INFO* bootInfo; - const HAL_API* hal; + BOOT_INFO* BootInfo; + const HAL_API* Hal; + KE_BOOT_CONFIG BootConfig; } SYSTEM_INIT_ARGS; #define KI_SWAP_MIN_MB 4ull @@ -107,19 +112,12 @@ static void KiInitializeSwapFromSettings(void) { (unsigned long long)KI_SWAP_MAX_MB); } - char pagefilePath[1024]; - if (!KeBuildSystemRootPath("pagefile.sys", pagefilePath, sizeof(pagefilePath))) { - KePanic(KE_PANIC_CODE_INIT, - "swap: failed to build system-root pagefile path\n"); - } - uint64_t swapBytes = swapMb * 1024ull * 1024ull; - KSTATUS s = MmSwapInitialize(SysSvFromCstr(pagefilePath), swapBytes); + KSTATUS s = MmSwapInitialize("pagefile.sys", swapBytes); if (!KSUCCESS(s)) { KePanic(KE_PANIC_CODE_INIT, - "swap: failed to initialize %llu MB pagefile at %s, status=0x%x\n", + "swap: failed to initialize %llu MB system-root pagefile, status=0x%x\n", (unsigned long long)swapMb, - pagefilePath, s); } } @@ -144,50 +142,162 @@ static int KiPageInBootloaderRegion(const BOOT_INFO* bootInfo, PHYSICAL_ADDRESS return 0; } -// Implements the reclaim Bootloader Memory operation. +// Reclaims one exact page-backed blob after its boot consumer has copied or materialized it. +static void KiReclaimBootBlob(const BOOT_INFO* bootInfo, uintptr_t physicalBase, uint32_t pageCount) { + for (uint32_t index = 0; index < pageCount; ++index) { + PHYSICAL_ADDRESS page = (PHYSICAL_ADDRESS)(physicalBase + (uintptr_t)index * PAGE_SIZE); + if (KiPageInBootloaderRegion(bootInfo, page)) + MmPfnFree(page); + } +} + +// Reclaims raw boot-driver/configuration pages while leaving mapped kernel/HAL/page-table memory owned by bootstrap state. static void KiReclaimBootloaderMemory(const BOOT_INFO* bootInfo) { - // Reclaim only the raw buffers backing boot-loaded .sys modules. - // The kernel/HAL images, page tables, and bootloader code remain live. - for (uint32_t i = 2; i < bootInfo->ModuleCount; i++) { - const BOOT_MODULE* mod = &bootInfo->Modules[i]; - if (!mod->PhysBase || !mod->ImageSize) continue; + const BOOT_FILE_LIST* list = &bootInfo->BootDrivers; + const BOOT_FILE* files = list->FilesPhysical + ? (const BOOT_FILE*)(bootInfo->PhysicalMapBase + list->FilesPhysical) + : NULL; - uintptr_t base = (uintptr_t)(mod->PhysBase & PAGE_MASK); - uintptr_t end = (uintptr_t)((mod->PhysBase + mod->ImageSize + PAGE_SIZE - 1) & PAGE_MASK); - - for (uintptr_t page = base; page < end; page += PAGE_SIZE) { - if (!KiPageInBootloaderRegion(bootInfo, (PHYSICAL_ADDRESS)page)) continue; - MmPfnFree((PHYSICAL_ADDRESS)page); - } + for (uint32_t index = 0; files && index < list->Count; ++index) { + if (files[index].PhysicalBase && files[index].PageCount) + KiReclaimBootBlob(bootInfo, files[index].PhysicalBase, files[index].PageCount); } + + if (list->FilesPhysical && list->PageCount) + KiReclaimBootBlob(bootInfo, list->FilesPhysical, list->PageCount); + if (bootInfo->BootIni.PhysicalBase && bootInfo->BootIni.PageCount) + KiReclaimBootBlob(bootInfo, bootInfo->BootIni.PhysicalBase, bootInfo->BootIni.PageCount); } -// Phase Helper: Resolve Imports for core modules. -static void KiResolveSystemImports(BOOT_INFO* bootInfo) { - // Only resolve imports for kernel.exe and hal.dll (typically indices 0 and 1). - // The bootloader maps these, but drivers are now passed as raw physical files. - uint32_t coreModuleCount = (bootInfo->ModuleCount > 2) ? 2 : bootInfo->ModuleCount; - - for (uint32_t i = 0; i < coreModuleCount; i++) { - if (bootInfo->Modules[i].VirtBase != 0) { - LdrResolveImports(bootInfo->Modules[i].VirtBase); - } - } +// Returns whether one drivers.ini line character is horizontal whitespace. +static int KiDriverConfigSpace(char c) { + return c == ' ' || c == '\t' || c == '\r'; } -// Phase Helper: Initialize dynamically loaded .sys drivers. -static void KiDriversIniCb(const char* section, const char* key, - const char* val, void* ud) { - (void)val; - if (!strcmp(section, "runtime")) { - char*** pArray = (char***)ud; - size_t len = strlen(key) + 1; - char* keyCopy = MmAllocatePool(len); - if (keyCopy) { - memcpy(keyCopy, key, len); - arr_push(*pArray, keyCopy); - } +// Trims horizontal whitespace from one immutable drivers.ini line span. +static void KiDriverConfigTrim(const char** begin, const char** end) { + while (*begin < *end && KiDriverConfigSpace(**begin)) + ++*begin; + while (*end > *begin && KiDriverConfigSpace((*end)[-1])) + --*end; +} + +// Returns whether one immutable drivers.ini span exactly matches an ASCII string. +static int KiDriverConfigSpanEquals(const char* begin, const char* end, const char* text) { + while (begin < end && *text) { + if (*begin++ != *text++) + return 0; } + return begin == end && *text == '\0'; +} + +// Loads one runtime driver filename from the system-root drivers directory. +static KSTATUS KiLoadRuntimeDriverByName(const char* name, const NTL_DDK* ddk) { + char relative[512]; + size_t length = strlen(name); + if (length + 9u > sizeof(relative)) + return NTLSTATUS_BUFFER_TOO_SMALL; + + memcpy(relative, "drivers/", 8); + memcpy(relative + 8, name, length + 1); + FILE_OBJECT* file = NULL; + KSTATUS status = KeOpenSystemRootFile(relative, &file); + if (!KSUCCESS(status)) + return status; + + uint64_t driverSize = IoGetFileSize(file); + if (driverSize == 0 || driverSize > SIZE_MAX) { + IoCloseFile(file); + return NTLSTATUS_INVALID_PARAMETER; + } + + void* driverMemory = MmAllocatePool((size_t)driverSize); + if (!driverMemory) { + IoCloseFile(file); + return NTLSTATUS_NO_MEMORY; + } + + size_t got = 0; + status = IoReadFile(file, driverMemory, (size_t)driverSize, &got); + IoCloseFile(file); + if (!KSUCCESS(status) || got != (size_t)driverSize) { + MmFreePool(driverMemory); + return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; + } + + uintptr_t mappedBase = 0; + status = LdrLoadKernelImage(driverMemory, (size_t)driverSize, &mappedBase); + MmFreePool(driverMemory); + if (!KSUCCESS(status)) + return status; + + status = LdrResolveImports(mappedBase); + if (!KSUCCESS(status)) + return status; + + status = LdrRegisterModule(name, mappedBase); + if (!KSUCCESS(status)) + return status; + + DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)LdrGetEntryPoint(mappedBase); + if (!entry) + return NTLSTATUS_FAILURE; + + NTL_DRIVER_INIT init = {0}; + return entry(ddk, &init); +} + +// Scans only the [runtime] section of drivers.ini and loads each filename immediately. +static KSTATUS KiLoadRuntimeDriversFromConfig(const char* text, size_t length, const NTL_DDK* ddk) { + int inRuntime = 0; + size_t offset = 0; + + while (offset < length) { + size_t lineStart = offset; + while (offset < length && text[offset] != '\n') + ++offset; + size_t lineEnd = offset; + if (offset < length) + ++offset; + + const char* begin = text + lineStart; + const char* end = text + lineEnd; + KiDriverConfigTrim(&begin, &end); + if (begin == end || *begin == ';' || *begin == '#') + continue; + + if (*begin == '[') { + if (end[-1] != ']') + return NTLSTATUS_INVALID_PARAMETER; + const char* sectionBegin = begin + 1; + const char* sectionEnd = end - 1; + KiDriverConfigTrim(§ionBegin, §ionEnd); + inRuntime = KiDriverConfigSpanEquals(sectionBegin, sectionEnd, "runtime"); + continue; + } + + if (!inRuntime) + continue; + + size_t nameLength = (size_t)(end - begin); + if (nameLength == 0 || nameLength >= BOOT_FILE_NAME_MAX) + return NTLSTATUS_INVALID_PARAMETER; + for (const char* p = begin; p < end; ++p) { + if (*p == '=' || *p == '/' || *p == '\\') + return NTLSTATUS_INVALID_PARAMETER; + } + + char name[BOOT_FILE_NAME_MAX]; + memcpy(name, begin, nameLength); + name[nameLength] = '\0'; + DbgPrint("Found runtime driver: %s\n", name); + + KSTATUS status = KiLoadRuntimeDriverByName(name, ddk); + if (!KSUCCESS(status)) + DbgPrint("KiInitializeDrivers: runtime driver %s failed, status=0x%x\n", name, status); + } + + return NTLSTATUS_SUCCESS; } // Initializes drivers. @@ -196,49 +306,45 @@ static void KiInitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { const NTL_DDK* ddk = IoGetDdk(); DbgPrint("KiInitializeDrivers: Initializing drivers...\n"); - // boot drivers (provided by the bootloader). - for (uint32_t i = 0; i < bootInfo->ModuleCount; i++) { - BOOT_MODULE* mod = &bootInfo->Modules[i]; - - // Skip modules that are already mapped (Kernel/HAL) - // or have no physical data (invalid). - if (mod->VirtBase != 0 || mod->PhysBase == 0) continue; + // Boot drivers are page-backed raw files supplied by the bootloader. + const BOOT_FILE* bootFiles = bootInfo->BootDrivers.FilesPhysical + ? (const BOOT_FILE*)(bootInfo->PhysicalMapBase + bootInfo->BootDrivers.FilesPhysical) + : NULL; - DbgPrint("KiInitializeDrivers: Loading boot driver %s\n", mod->Name); + for (uint32_t i = 0; bootFiles && i < bootInfo->BootDrivers.Count; ++i) { + const BOOT_FILE* file = &bootFiles[i]; + if (!file->PhysicalBase || !file->Length) { + DbgPrint("KiInitializeDrivers: invalid boot driver descriptor at index %u\n", i); + continue; + } - // Calculate virtual address of the raw file in the physical window. - void* rawPeBuffer = (void*)(bootInfo->PhysicalMapBase + mod->PhysBase); + DbgPrint("KiInitializeDrivers: Loading boot driver %s\n", file->Name); + void* rawPeBuffer = (void*)(bootInfo->PhysicalMapBase + file->PhysicalBase); - // Map the PE image into kernel space. uintptr_t mappedBase = 0; - KSTATUS st = LdrLoadKernelImage(rawPeBuffer, mod->ImageSize, &mappedBase); + KSTATUS st = LdrLoadKernelImage(rawPeBuffer, file->Length, &mappedBase); if (!KSUCCESS(st)) { - DbgPrint("ERROR: Failed to map driver %s (0x%X)\n", mod->Name, st); + DbgPrint("ERROR: Failed to map driver %s (0x%X)\n", file->Name, st); continue; } - // Update the struct so other modules can find it. - mod->VirtBase = mappedBase; - - // Resolve imports while the image is still writable. Kernel driver images - // stay writable until the loader grows real section-backed mappings; the - // loader still clears NX for the whole image before returning it. if (!KSUCCESS(LdrResolveImports(mappedBase))) { - DbgPrint("ERROR: Failed to resolve imports for %s\n", mod->Name); + DbgPrint("ERROR: Failed to resolve imports for %s\n", file->Name); continue; } - // Register with the global module list so subsequent drivers can import from this one. - LdrRegisterModule(mod->Name, mod->VirtBase); + KSTATUS registerStatus = LdrRegisterModule(file->Name, mappedBase); + if (!KSUCCESS(registerStatus)) { + DbgPrint("ERROR: Failed to register driver %s (0x%X)\n", file->Name, registerStatus); + continue; + } - // Execute Entry Point. DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)LdrGetEntryPoint(mappedBase); if (entry) { NTL_DRIVER_INIT init = {0}; KSTATUS ds = entry(ddk, &init); - if (!KSUCCESS(ds)) { - DbgPrint("WARNING: Driver %s returned failure status 0x%X\n", mod->Name, ds); - } + if (!KSUCCESS(ds)) + DbgPrint("WARNING: Driver %s returned failure status 0x%X\n", file->Name, ds); } } @@ -251,106 +357,41 @@ static void KiInitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to find system root volume (status %d)\n", s); } - char root[4]; - root[0] = (char)('0' + KeGetSystemRootDrive()); - root[1] = ':'; - root[2] = '/'; - root[3] = '\0'; - s = IoSetCwd(SysSvFromCstr(root)); - if (s != NTLSTATUS_SUCCESS) { - KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to set system CWD to %s (status %d)\n", root, s); - } - s = KeOpenSystemRootFile("drivers\\drivers.ini", &f); + s = KeOpenSystemRootFile("drivers/drivers.ini", &f); if (s != NTLSTATUS_SUCCESS) { KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to open drivers.ini on system root (status %d)\n", s); } - uint64_t size = IoGetFileSize(f) + 1; - char* mem = MmAllocatePool(size); - size_t got; - if (!KSUCCESS(IoReadFile(f, mem, size, &got))) { + uint64_t fileSize = IoGetFileSize(f); + if (fileSize == 0 || fileSize >= SIZE_MAX) { IoCloseFile(f); - KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to read drivers.ini\n"); + KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: drivers.ini has an invalid size.\n"); + } + + size_t size = (size_t)fileSize; + char* mem = (char*)MmAllocatePool(size + 1u); + if (!mem) { + IoCloseFile(f); + KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to allocate drivers.ini buffer.\n"); + } + + size_t got = 0; + KSTATUS readStatus = IoReadFile(f, mem, size, &got); + IoCloseFile(f); + if (!KSUCCESS(readStatus) || got != size) { + MmFreePool(mem); + KePanic(KE_PANIC_CODE_INIT, "KiInitializeDrivers: Failed to read drivers.ini.\n"); } mem[got] = '\0'; - IoCloseFile(f); - - char** runtimeKeys = NULL; - IniParse(mem, KiDriversIniCb, &runtimeKeys); - - for (uint32_t i = 0; i < arr_len(runtimeKeys); i++) { - DbgPrint("Found runtime driver: %s\n", runtimeKeys[i]); - - char path[1024]; - char relative[512]; - size_t len = strlen(runtimeKeys[i]); - if (len + 9 > sizeof(relative)) { - DbgPrint("KiInitializeDrivers: runtime driver path too long: %s\n", runtimeKeys[i]); - continue; - } - memcpy(relative, "drivers\\", 8); - memcpy(relative + 8, runtimeKeys[i], len + 1); - - if (!KeBuildSystemRootPath(relative, path, sizeof(path))) { - DbgPrint("KiInitializeDrivers: failed to build runtime driver path: %s\n", runtimeKeys[i]); - continue; - } - - s = IoOpenFile(SysSvFromCstr(path), &f); - if (s != NTLSTATUS_SUCCESS) { - DbgPrint("KiInitializeDrivers: Failed to open runtime driver: %s (%s)\n", runtimeKeys[i], path); - continue; - } - - uint64_t drvSize = IoGetFileSize(f); - void* drvMem = MmAllocatePool(drvSize); - if (!KSUCCESS(IoReadFile(f, drvMem, drvSize, &got))) { - DbgPrint("KiInitializeDrivers: Failed to read runtime driver: %s\n", runtimeKeys[i]); - IoCloseFile(f); - continue; - } - IoCloseFile(f); - - uintptr_t mappedBase = 0; - if (!KSUCCESS(LdrLoadKernelImage(drvMem, drvSize, &mappedBase))) { // Load into kernel space - DbgPrint("KiInitializeDrivers: Failed to load runtime driver: %s\n", runtimeKeys[i]); - MmFreePool(drvMem); - continue; - } - MmFreePool(drvMem); - - if (!KSUCCESS(LdrResolveImports(mappedBase))) { - DbgPrint("KiInitializeDrivers: Failed to resolve imports for runtime driver: %s\n", runtimeKeys[i]); - continue; - } - // Runtime modules may also export DDIs to drivers loaded later. Boot - // drivers were already registered above; keep the module model uniform. - LdrRegisterModule(runtimeKeys[i], mappedBase); - - DRIVER_ENTRY_FN entry = (DRIVER_ENTRY_FN)(LdrGetEntryPoint(mappedBase)); - - NTL_DRIVER_INIT init = {0}; - KSTATUS ds = entry(ddk, &init); - if (!KSUCCESS(ds)) { - DbgPrint("KiInitializeDrivers: Driver %s failed to init!\n", runtimeKeys[i]); - } - } - - // Free the individual strings allocated via strdup in the callback. - if (runtimeKeys) { - for (uint32_t i = 0; i < arr_len(runtimeKeys); i++) { - if (runtimeKeys[i]) { - MmFreePool(runtimeKeys[i]); - } - } - // Free the dynamic array structure itself. - arr_free(runtimeKeys); - } - - // Free the file buffer. + KSTATUS runtimeStatus = KiLoadRuntimeDriversFromConfig(mem, got, ddk); MmFreePool(mem); + if (!KSUCCESS(runtimeStatus)) { + KePanic(KE_PANIC_CODE_INIT, + "KiInitializeDrivers: drivers.ini [runtime] is malformed, status=0x%x\n", + runtimeStatus); + } } // Phase Helper: Load and launch a process from the filesystem. @@ -374,7 +415,7 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { PROCESS* proc; THREAD* thread; - s = KeCreateProcess(filePath, &proc); + s = KeCreateProcess(filePath, SeGetRootSecurityContext(), &proc); if (!KSUCCESS(s)) { DbgPrint("Failed to create process. System halted.\n"); IoCloseFile(f); @@ -384,8 +425,7 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { s = KeSetupUserProcessEnvironment(proc, SysSvFromCstr(filePath), - SysSvFromCstr(args), - SysSvFromCstr(proc->CurrentDirectory)); + SysSvFromCstr(args)); if (!KSUCCESS(s)) { DbgPrint("KiLaunchUserProcess: Failed to build user process environment. Status: %d\n", s); IoCloseFile(f); @@ -393,6 +433,56 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { return 0; } + // Bootstrap init receives read-only system-volume authority, separate namespace-seeding + // authority, and the volume-manager capability used to supervise RSSRV publication. + IO_VOLUME* systemVolume = (IO_VOLUME*)0; + SYS_HANDLE systemVolumeHandle = SYS_HANDLE_INVALID; + SYS_HANDLE namespaceVolumeHandle = SYS_HANDLE_INVALID; + SYS_HANDLE volumeManagerHandle = SYS_HANDLE_INVALID; + s = KeReferenceSystemRootVolume(&systemVolume); + if (KSUCCESS(s)) { + s = KeCreateHandle(proc, &systemVolume->Header, + SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, + &systemVolumeHandle); + if (KSUCCESS(s)) { + s = KeCreateHandle(proc, &systemVolume->Header, + SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | + SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY, + &namespaceVolumeHandle); + } + IoReleaseVolume(systemVolume); + } + if (KSUCCESS(s)) { + IO_VOLUME_MANAGER* volumeManager = (IO_VOLUME_MANAGER*)0; + s = IoReferenceVolumeManager(&volumeManager); + if (KSUCCESS(s)) { + s = KeCreateHandle(proc, &volumeManager->Header, + SYS_RIGHT_DUPLICATE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | + SYS_VOLUME_MANAGER_RIGHT_ENUMERATE | SYS_VOLUME_MANAGER_RIGHT_OPEN | + SYS_VOLUME_MANAGER_RIGHT_WAIT, + &volumeManagerHandle); + ObDereferenceObject(&volumeManager->Header); + } + } + if (!KSUCCESS(s)) { + DbgPrint("KiLaunchUserProcess: Failed to create bootstrap startup capabilities. Status: %d\n", s); + IoCloseFile(f); + ObDereferenceObject(&proc->Header); + return 0; + } + SYS_STARTUP_CAPABILITY startupCapabilities[3] = { + { SYS_STARTUP_CAP_SYSTEM_VOLUME, systemVolumeHandle }, + { SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE, namespaceVolumeHandle }, + { SYS_STARTUP_CAP_VOLUME_MANAGER, volumeManagerHandle }, + }; + s = KeSetProcessStartupCapabilities(proc, startupCapabilities, 3); + if (!KSUCCESS(s)) { + DbgPrint("KiLaunchUserProcess: Failed to install startup capabilities. Status: %d\n", s); + IoCloseFile(f); + ObDereferenceObject(&proc->Header); + return 0; + } + // Map the executable through an image section backed by the open file. uintptr_t imageBase = 0; s = LdrLoadImageFileForProcess(proc, f, &imageBase); @@ -403,9 +493,9 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { return 0; } - s = LdrResolveImportsForProcess(proc, imageBase); + s = LdrRequireNoImports(imageBase); if (!KSUCCESS(s)) { - DbgPrint("KiLaunchUserProcess: Failed to resolve EXE imports for %s (status %d)\n", filePath, s); + DbgPrint("KiLaunchUserProcess: bootstrap image %s must be import-free (status %d)\n", filePath, s); ObDereferenceObject(&proc->Header); return 0; } @@ -448,22 +538,19 @@ void SystemInitThread(void* arg) { KiRunEarlyKernelSelfTests(); - KiInitializeDrivers(args->bootInfo, args->hal); + KiInitializeDrivers(args->BootInfo, args->Hal); - if (args->bootInfo->Debug & KE_DEBUG_AUDIO_SELFTEST) - KiRunAudioFinalPathSelfTest(); KeTimeReloadSettings(); KiInitializeSwapFromSettings(); - KiResolveSystemImports(args->bootInfo); - KiReclaimBootloaderMemory(args->bootInfo); + KiReclaimBootloaderMemory(args->BootInfo); - if (args->bootInfo->Debug & KE_DEBUG_SELFTEST) - KiRunKernelSelfTests(args->hal); + if (args->BootConfig.Debug & KE_DEBUG_SELFTEST) + KiRunKernelSelfTests(args->Hal); // Launch User-Mode init.exe. - PROCESS* shellProc = KiLaunchUserProcess("system/init.exe", args->bootInfo->CommandLine); + PROCESS* shellProc = KiLaunchUserProcess("system/init.exe", args->BootConfig.CommandLine); if (!shellProc) { DbgPrint("SystemInitThread: Failed to start system/init.exe.\n"); KePanic(KE_PANIC_CODE_INIT, "SystemInitThread: Failed to start system/init.exe.\n"); @@ -477,28 +564,114 @@ void SystemInitThread(void* arg) { DbgPrint("SystemInitThread: init.exe exited with code %d.\n", exitCode); ObDereferenceObject(&shellProc->Header); - if (args->bootInfo->Debug & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) { - char logPath[1024]; - KSTATUS logStatus = NTLSTATUS_NOT_FOUND; - if (KeBuildSystemRootPath("kernel.log", logPath, sizeof(logPath))) { - logStatus = KeFlushDebugLogToFile(SysSvFromCstr(logPath)); - if (!KSUCCESS(logStatus)) - DbgPrint("debuglog: shutdown flush to %s failed st=0x%x\n", logPath, logStatus); - } else { - DbgPrint("debuglog: shutdown flush skipped; system root path unavailable\n"); - } + if (args->BootConfig.Debug & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) { + KSTATUS logStatus = KeFlushDebugLogToSystemRoot("kernel.log"); + if (!KSUCCESS(logStatus)) + DbgPrint("debuglog: shutdown flush failed st=0x%x\n", logStatus); } gKmainHal->Power.Shutdown(); MmFreePool(args); } +// Returns whether one page-backed boot allocation is aligned, nonempty, and does not overflow. +static int KiValidateBootPageRange(uintptr_t physicalBase, uint32_t pageCount) { + if (!physicalBase || (physicalBase & (PAGE_SIZE - 1u)) != 0 || pageCount == 0) + return 0; + + uint64_t byteCount = (uint64_t)pageCount * PAGE_SIZE; + return byteCount != 0 && physicalBase <= UINTPTR_MAX - byteCount; +} + +// Returns whether one page-backed raw boot file is fully contained in its recorded allocation. +static int KiValidateBootFile(const BOOT_FILE* file) { + if (!file || !KiValidateBootPageRange(file->PhysicalBase, file->PageCount) || file->Length == 0) + return 0; + + uint64_t capacity = (uint64_t)file->PageCount * PAGE_SIZE; + if ((uint64_t)file->Length + 1u > capacity || file->Name[0] == '\0') + return 0; + return memchr(file->Name, '\0', sizeof(file->Name)) != NULL; +} + +// Returns whether one fixed core-image descriptor fits inside its page-backed physical allocation. +static int KiValidateBootImage(const BOOT_IMAGE* image, int requireEntry) { + if (!image || !image->VirtualBase || (image->VirtualBase & (PAGE_SIZE - 1u)) != 0 || + !KiValidateBootPageRange(image->PhysicalBase, image->PageCount) || !image->ImageSize) { + return 0; + } + + uint64_t capacity = (uint64_t)image->PageCount * PAGE_SIZE; + if (image->ImageSize > capacity || image->VirtualBase > UINTPTR_MAX - image->ImageSize) + return 0; + if (requireEntry && image->EntryRva >= image->ImageSize) + return 0; + return 1; +} + +// Validates the exact bootloader/kernel handoff contract before dereferencing variable physical blobs. +static int KiValidateBootInfo(const BOOT_INFO* bootInfo) { + if (!bootInfo || bootInfo->Magic != BOOT_INFO_MAGIC || + bootInfo->Version != BOOT_INFO_VERSION || bootInfo->Size != sizeof(BOOT_INFO) || + bootInfo->MemoryMap.Magic != MEMORY_MAP_MAGIC || + bootInfo->MemoryMap.RegionCount > MEMORY_MAX_REGIONS || + bootInfo->PhysicalMapBase == 0) { + return 0; + } + + uint64_t bootIniCapacity = (uint64_t)bootInfo->BootIni.PageCount * PAGE_SIZE; + if (!KiValidateBootPageRange(bootInfo->BootIni.PhysicalBase, bootInfo->BootIni.PageCount) || + bootInfo->BootIni.Length == 0 || (uint64_t)bootInfo->BootIni.Length + 1u > bootIniCapacity || + bootInfo->BootIni.PhysicalBase > UINTPTR_MAX - bootInfo->PhysicalMapBase) { + return 0; + } + + uint64_t driverListCapacity = (uint64_t)bootInfo->BootDrivers.PageCount * PAGE_SIZE; + uint64_t driverListBytes = (uint64_t)bootInfo->BootDrivers.Count * sizeof(BOOT_FILE); + if (!KiValidateBootPageRange(bootInfo->BootDrivers.FilesPhysical, bootInfo->BootDrivers.PageCount) || + bootInfo->BootDrivers.Count == 0 || driverListBytes > driverListCapacity || + bootInfo->BootDrivers.FilesPhysical > UINTPTR_MAX - bootInfo->PhysicalMapBase) { + return 0; + } + + const BOOT_FILE* bootFiles = + (const BOOT_FILE*)(bootInfo->PhysicalMapBase + bootInfo->BootDrivers.FilesPhysical); + for (uint32_t index = 0; index < bootInfo->BootDrivers.Count; ++index) { + if (!KiValidateBootFile(&bootFiles[index]) || + bootFiles[index].PhysicalBase > UINTPTR_MAX - bootInfo->PhysicalMapBase) { + return 0; + } + } + + if (!KiValidateBootImage(&bootInfo->KernelImage, 1) || + !KiValidateBootImage(&bootInfo->HalImage, 0)) { + return 0; + } + + return 1; +} + // Main Kernel Entry. extern void KernelMain(BOOT_INFO* bootInfo) { + // Firmware is not required to transfer control with IF clear. Keep the + // bootstrap CPU masked until the interrupt manager and scheduler are ready. + _disable(); + + if (!KiValidateBootInfo(bootInfo)) { + for (;;) + __halt(); + } + + if (!KSUCCESS(KeBindBootstrapHal())) { + for (;;) + __halt(); + } + const HAL_API* hal = KeGetHal(); + // Physical & Virtual Memory initialization. if (!MmInitializePhysical(&bootInfo->MemoryMap, bootInfo->PhysicalMapBase)) - HalStopProcessor(); + KeStopProcessor(); PHYSICAL_ADDRESS pml4 = (PHYSICAL_ADDRESS)__readcr3(); // The early virtual allocator starts small because it only needs bootstrap page tables before the PMM is online. @@ -513,38 +686,56 @@ extern void KernelMain(BOOT_INFO* bootInfo) { // Register boot modules (Allows LdrGetExport to work for drivers and the HAL) // Note: Kept to a single call here to avoid the duplicate in your original code. - LdrRegisterModulesFromBootInfo(bootInfo); + KSTATUS moduleRegistryStatus = LdrRegisterModulesFromBootInfo(bootInfo); + if (!KSUCCESS(moduleRegistryStatus)) + KeStopProcessor(); + + // Parse the raw mandatory boot.ini handoff once before HAL policy consumes debug flags. + KE_BOOT_CONFIG bootConfig = {0}; + const char* bootIniText = (const char*)(bootInfo->PhysicalMapBase + bootInfo->BootIni.PhysicalBase); + KSTATUS bootConfigStatus = KeParseBootConfiguration(bootIniText, bootInfo->BootIni.Length, &bootConfig); // HAL Initialization. HAL_INIT_PARAMS halParams = { + .Size = sizeof(HAL_INIT_PARAMS), + .Flags = 0, .PhysicalWindowBase = bootInfo->PhysicalMapBase, .FramebufferBase = bootInfo->FramebufferBase, .FramebufferWidth = bootInfo->FramebufferWidth, .FramebufferHeight = bootInfo->FramebufferHeight, .FramebufferStride = bootInfo->FramebufferStride, .RsdpPhysical = bootInfo->RsdpPhysical, - .Debug = bootInfo->Debug & 1, // bitmask 1 debug prints + .Debug = KSUCCESS(bootConfigStatus) ? (int)(bootConfig.Debug & 1u) : 0, .AllocatePool = MmAllocatePool }; - KSTATUS s = HalInitialize(&halParams); + KSTATUS s = KeInitializeHal(&halParams); if (!KSUCCESS(s)) - HalStopProcessor(); + KeStopProcessor(); - const HAL_API* hal = HalGetApi(); - IoInitializeDdk(hal); + IoInitializeDdk(hal, bootInfo->PhysicalMapBase); gKmainHal = hal; - KeInitDebug(hal, bootInfo->Debug); + KeInitDebug(hal, KSUCCESS(bootConfigStatus) ? bootConfig.Debug : 0); + if (!KSUCCESS(bootConfigStatus)) { + KePanic(KE_PANIC_CODE_INIT, + "boot.ini: malformed or missing mandatory configuration, status=0x%x\n", + bootConfigStatus); + } + DbgPrint("Boot: system volume selector=%s\n", bootConfig.SystemVolume); + + // Bind IRQ dispatch while IF remains clear. Interrupt sources are armed + // later, after their kernel consumers have registered. + KeIrqInitialize(hal); DbgPrint("CPU: PAT owns WB/WC/UC policy; MTRR default=WB fixed=off variable=off.\n"); KeTimeInitialize(bootInfo, hal); KeDisplayInitialize(hal); // CPU startup intentionally lives after HAL initialization but before IRQs - // and scheduler startup. HAL owns ACPI/MADT discovery and LAPIC INIT/SIPI; - // the kernel owns KCPU records, per-CPU stacks, GDT/TSS/IDT state, and the - // AP park loop. External device IRQs remain routed to the BSP during this + // and scheduler startup. HAL owns firmware processor discovery and platform + // startup signaling; the kernel owns KCPU records, per-CPU stacks, GDT/TSS/IDT state, and the + // AP park loop. External device IRQs remain routed to the boot processor during this // step; APs only become runnable after scheduler admission below. KeSetApplicationProcessorTrampoline((uint64_t)bootInfo->ApTrampolinePhys); KSTATUS smpStatus = KeStartApplicationProcessors(); @@ -552,31 +743,42 @@ extern void KernelMain(BOOT_INFO* bootInfo) { KePanic(KE_PANIC_CODE_INIT, "SMP: failed to start application processors, status=0x%x\n", smpStatus); } - KeIrqInitialize(hal); KeInitializeDpcSystem(); KeInputInitialize(hal); - KeIrqRegister(hal->Timer.IrqNumber, hal->Timer.Isr); - - HalRegisterScheduler(KeSchedulerTick); + KeIrqRegister(hal->Timer.IrqNumber, hal->Timer.HandleInterrupt); KeSyscallInit(hal); + KSTATUS securityStatus = SeInitializeSecurity(); + if (!KSUCCESS(securityStatus)) { + KePanic(KE_PANIC_CODE_INIT, + "security: failed to initialize root context, status=0x%x\n", + securityStatus); + } + // Dynamic Linking & Driver Execution. - KiResolveSystemImports(bootInfo); - //KiInitializeDrivers(bootInfo, hal);. // Scheduler Prep & Status Update. KeInitializeScheduler(); + KeIrqRegister(hal->Timer.IrqNumber, KeSchedulerTick); + KeConfigureCurrentProcessorTimer(); - KSTATUS volumeNamespaceStatus = IoInitializeVolumeNamespace(); - if (!KSUCCESS(volumeNamespaceStatus)) { + KSTATUS volumeManagerStatus = IoInitializeVolumeManager(); + if (!KSUCCESS(volumeManagerStatus)) { KePanic(KE_PANIC_CODE_INIT, - "I/O: failed to initialize volume namespace notifications, status=0x%x\n", - volumeNamespaceStatus); + "I/O: failed to initialize volume manager notifications, status=0x%x\n", + volumeManagerStatus); + } + + KSTATUS partitionStatus = IoInitializePartitionManager(bootConfig.SystemVolume); + if (!KSUCCESS(partitionStatus)) { + KePanic(KE_PANIC_CODE_INIT, + "I/O: failed to initialize partition discovery, status=0x%x\n", + partitionStatus); } KeAudioInitialize(); @@ -595,8 +797,11 @@ extern void KernelMain(BOOT_INFO* bootInfo) { // Pack the arguments for the init thread. SYSTEM_INIT_ARGS* initArgs = MmAllocatePool(sizeof(SYSTEM_INIT_ARGS)); - initArgs->bootInfo = bootInfo; - initArgs->hal = hal; + if (!initArgs) + KePanic(KE_PANIC_CODE_INIT, "SystemInitThread: failed to allocate initialization arguments.\n"); + initArgs->BootInfo = bootInfo; + initArgs->Hal = hal; + initArgs->BootConfig = bootConfig; // Spawn the Initialization Thread. No creator reference is needed; the // scheduler owns the live thread lifetime. @@ -613,5 +818,5 @@ extern void KernelMain(BOOT_INFO* bootInfo) { // The bootstrap CPU becomes the scheduler idle context once interrupts are enabled. while (true) - HalWaitForInterrupt(); + KeWaitForInterrupt(); } diff --git a/code/kernel/ldr_image.c b/code/kernel/ldr_image.c index 1b7dce8..6675743 100644 --- a/code/kernel/ldr_image.c +++ b/code/kernel/ldr_image.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #ifndef LDR_DEBUG_VERBOSE @@ -164,17 +163,17 @@ typedef struct { #define IMAGE_SCN_MEM_READ 0x40000000u #define IMAGE_SCN_MEM_WRITE 0x80000000u -// Module registry. -#define MAX_MODULES 32 +// Global kernel-module registry. Entries are allocated as modules arrive so boot-driver count is not an ABI limit. +#define LDR_KERNEL_MODULE_NAME_MAX 64u -typedef struct { - char Name[32]; +typedef struct MODULE_ENTRY { + char Name[LDR_KERNEL_MODULE_NAME_MAX]; uintptr_t Base; - size_t Size; + size_t Size; + struct MODULE_ENTRY* Next; } MODULE_ENTRY; -static MODULE_ENTRY gModules[MAX_MODULES]; -static uint32_t gModuleCount = 0; +static MODULE_ENTRY* gModules; // Ensures a user image page is privately writable before the kernel patches it. static KSTATUS KiEnsureUserImageWrite(PROCESS* process, bool user, uintptr_t address, size_t size) { @@ -182,62 +181,50 @@ static KSTATUS KiEnsureUserImageWrite(PROCESS* process, bool user, uintptr_t add return MmVadMakeImageRangePrivate(process, (void*)address, size); } -// Implements the canonical Module Name operation. -static const char* KiCanonicalModuleName(const char* name) { - if (!name) return name; +// Allocates and inserts one global kernel-module registry entry. +static KSTATUS KiRegisterGlobalModule(const char* name, uintptr_t base, size_t size) { + if (!name || !*name || !base || !size) + return NTLSTATUS_INVALID_PARAMETER; - if (!_stricmp(name, "kernel32.dll") || - !_stricmp(name, "user32.dll") || - !_stricmp(name, "gdi32.dll") || - !_stricmp(name, "ole32.dll") || - !_stricmp(name, "winmm.dll")) { - return "win32.dll"; - } + size_t nameLength = 0; + while (nameLength < LDR_KERNEL_MODULE_NAME_MAX && name[nameLength]) nameLength++; + if (nameLength == 0 || nameLength >= LDR_KERNEL_MODULE_NAME_MAX) + return NTLSTATUS_INVALID_PARAMETER; - if (!_stricmp(name, "msvcrt.dll")) { - return "posix.dll"; - } + MODULE_ENTRY* entry = (MODULE_ENTRY*)MmAllocatePool(sizeof(MODULE_ENTRY)); + if (!entry) + return NTLSTATUS_NO_MEMORY; - return name; -} - -// Returns module Base In Table. -static uintptr_t KiFindModuleBaseInTable(const MODULE_ENTRY* modules, uint32_t count, const char* name) { - for (uint32_t i = 0; i < count; i++) { - if (_stricmp(modules[i].Name, name) == 0) return modules[i].Base; - } - return 0; -} - -// Registers module Entry. -static void KiRegisterModuleEntry(MODULE_ENTRY* entry, const char* name, uintptr_t base, size_t size) { - strncpy(entry->Name, name, 31); - entry->Name[31] = '\0'; + memset(entry, 0, sizeof(*entry)); + memcpy(entry->Name, name, nameLength); + entry->Name[nameLength] = '\0'; entry->Base = base; entry->Size = size; + entry->Next = gModules; + gModules = entry; + return NTLSTATUS_SUCCESS; } -// Implements the ldr Register Modules From Boot Info operation. -void LdrRegisterModulesFromBootInfo(const BOOT_INFO* bootInfo) { - for (uint32_t i = 0; i < bootInfo->ModuleCount && gModuleCount < MAX_MODULES; i++) { - // Only register modules that have a virtual base (Kernel, HAL). - if (bootInfo->Modules[i].VirtBase != 0) { - KiRegisterModuleEntry(&gModules[gModuleCount], - bootInfo->Modules[i].Name, - bootInfo->Modules[i].VirtBase, - bootInfo->Modules[i].ImageSize); - gModuleCount++; - } - } +// Registers the two fixed bootstrap images described explicitly by BOOT_INFO. +KSTATUS LdrRegisterModulesFromBootInfo(const BOOT_INFO* bootInfo) { + if (!bootInfo) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = KiRegisterGlobalModule("kernel.exe", + bootInfo->KernelImage.VirtualBase, + bootInfo->KernelImage.ImageSize); + if (!KSUCCESS(status)) + return status; + + return KiRegisterGlobalModule("hal.dll", + bootInfo->HalImage.VirtualBase, + bootInfo->HalImage.ImageSize); } -// Implements the ldr Register Module operation. -void LdrRegisterModule(const char* name, uintptr_t base) { - if (gModuleCount < MAX_MODULES) { - size_t size = base ? LdrGetImageSize(base) : 0; - KiRegisterModuleEntry(&gModules[gModuleCount], name, base, size); - gModuleCount++; - } +// Registers one loaded kernel module without imposing a fixed registry capacity. +KSTATUS LdrRegisterModule(const char* name, uintptr_t base) { + size_t size = base ? LdrGetImageSize(base) : 0; + return KiRegisterGlobalModule(name, base, size); } // Finds the loaded kernel module that owns an address. @@ -245,111 +232,70 @@ const char* LdrFindKernelModuleByAddress(uintptr_t address, uintptr_t* baseOut, if (baseOut) *baseOut = 0; if (sizeOut) *sizeOut = 0; - for (uint32_t i = 0; i < gModuleCount; i++) { - uintptr_t base = gModules[i].Base; - size_t size = gModules[i].Size; - if (!base || !size) + for (MODULE_ENTRY* module = gModules; module; module = module->Next) { + uintptr_t base = module->Base; + size_t size = module->Size; + if (!base || !size || base > UINTPTR_MAX - size) continue; if (address >= base && address < base + size) { if (baseOut) *baseOut = base; if (sizeOut) *sizeOut = size; - return gModules[i].Name; + return module->Name; } } return "unknown"; } -// Returns global Module Base. +// Returns the loaded global kernel module base for one exact module name. static uintptr_t KiFindGlobalModuleBase(const char* name) { - return KiFindModuleBaseInTable(gModules, gModuleCount, KiCanonicalModuleName(name)); -} - -static uintptr_t KiFindProcessModuleBaseInTable( - const PROCESS_MODULE* modules, - uint32_t count, - const char* name -) { - for (uint32_t i = 0; i < count; i++) { - if (_stricmp(modules[i].Name, KiCanonicalModuleName(name)) == 0) { - return modules[i].Base; - } + if (!name) return 0; + for (MODULE_ENTRY* module = gModules; module; module = module->Next) { + if (_stricmp(module->Name, name) == 0) + return module->Base; } return 0; } -// Returns process Or Global Module Base. -static uintptr_t KiFindProcessOrGlobalModuleBase(PROCESS* process, const char* name) { - if (process) { - uintptr_t base = KiFindProcessModuleBaseInTable(process->Modules, process->ModuleCount, name); - if (base) return base; - } - return KiFindGlobalModuleBase(name); -} - // Returns export At Base. static void* KiGetExportAtBase(uintptr_t base, const char* functionName) { - if (!base) return (void *)0; + if (!base) return (void*)0; - uint32_t peOffset = *(uint32_t *)(base + 0x3C); - PE_HEADER* pe = (PE_HEADER *)(base + peOffset); - if (pe->Signature != PE_SIGNATURE) return (void *)0; + uint32_t peOffset = *(uint32_t*)(base + 0x3C); + PE_HEADER* pe = (PE_HEADER*)(base + peOffset); + if (pe->Signature != PE_SIGNATURE) return (void*)0; OPT_HEADER64* opt = &pe->OptionalHeader; - if (opt->NumberOfRvaAndSizes < 1) return (void *)0; + if (opt->NumberOfRvaAndSizes < 1) return (void*)0; uint32_t expRva = opt->DataDirectory[0].VirtualAddress; - if (!expRva) return (void *)0; + if (!expRva) return (void*)0; - EXPORT_DIR* exp = (EXPORT_DIR *)(base + expRva); - uint32_t* names = (uint32_t *)(base + exp->AddressOfNames); - uint16_t* ordinals = (uint16_t *)(base + exp->AddressOfNameOrdinals); - uint32_t* functions = (uint32_t *)(base + exp->AddressOfFunctions); + EXPORT_DIR* exp = (EXPORT_DIR*)(base + expRva); + uint32_t* names = (uint32_t*)(base + exp->AddressOfNames); + uint16_t* ordinals = (uint16_t*)(base + exp->AddressOfNameOrdinals); + uint32_t* functions = (uint32_t*)(base + exp->AddressOfFunctions); for (uint32_t i = 0; i < exp->NumberOfNames; i++) { - const char* name = (const char *)(base + names[i]); + const char* name = (const char*)(base + names[i]); if (strcmp(name, functionName) == 0) { uint16_t ord = ordinals[i]; - return (void *)(base + functions[ord]); + return (void*)(base + functions[ord]); } } - return (void *)0; + return (void*)0; } // Export lookup. void* LdrGetExport(const char* moduleName, const char* functionName) { - uintptr_t base = KiFindGlobalModuleBase(KiCanonicalModuleName(moduleName)); + uintptr_t base = KiFindGlobalModuleBase(moduleName); return KiGetExportAtBase(base, functionName); } -// Implements the ldr Get Export For Process operation. -void* LdrGetExportForProcess(PROCESS* process, const char* moduleName, const char* functionName) { - uintptr_t base = KiFindProcessOrGlobalModuleBase(process, KiCanonicalModuleName(moduleName)); - 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; - uint32_t peOffset = *(uint32_t *)(base + 0x3C); - PE_HEADER* pe = (PE_HEADER *)(base + peOffset); + uint32_t peOffset = *(uint32_t*)(base + 0x3C); + PE_HEADER* pe = (PE_HEADER*)(base + peOffset); if (pe->Signature != PE_SIGNATURE) return 0; return (size_t)pe->OptionalHeader.SizeOfImage; } @@ -401,11 +347,11 @@ KSTATUS LdrProtectImageForProcess(PROCESS* process, uintptr_t imageBase, bool us size_t mappedSize = user ? LdrGetMappedImageSize(process, imageBase) : LdrGetImageSize(imageBase); if (!mappedSize) return NTLSTATUS_FAILURE; - uint32_t peOffset = *(uint32_t *)(imageBase + 0x3C); + uint32_t peOffset = *(uint32_t*)(imageBase + 0x3C); if ((size_t)peOffset > mappedSize || mappedSize - (size_t)peOffset < sizeof(uint32_t) + 20) return NTLSTATUS_FAILURE; - PE_HEADER* pe = (PE_HEADER *)(imageBase + peOffset); + PE_HEADER* pe = (PE_HEADER*)(imageBase + peOffset); if (pe->Signature != PE_SIGNATURE) return NTLSTATUS_FAILURE; size_t sectionTableOffset = (size_t)peOffset + 24u + (size_t)pe->SizeOfOptionalHeader; @@ -421,7 +367,7 @@ KSTATUS LdrProtectImageForProcess(PROCESS* process, uintptr_t imageBase, bool us for (size_t i = 0; i < pageCount; ++i) pageProt[i] = headerProt; - SECTION_HEADER* sec = (SECTION_HEADER *)(imageBase + sectionTableOffset); + SECTION_HEADER* sec = (SECTION_HEADER*)(imageBase + sectionTableOffset); for (int i = 0; i < pe->NumberOfSections; i++) { size_t protectSize = sec[i].VirtualSize ? sec[i].VirtualSize : sec[i].SizeOfRawData; if (!protectSize) continue; @@ -497,8 +443,8 @@ uintptr_t LdrGetEntryPoint(uintptr_t base) { // Base Relocation. static KSTATUS LdrApplyRelocationsForProcess(PROCESS* process, uintptr_t base, uintptr_t delta, bool user) { - uint32_t peOffset = *(uint32_t *)(base + 0x3C); - PE_HEADER* pe = (PE_HEADER *)(base + peOffset); + uint32_t peOffset = *(uint32_t*)(base + 0x3C); + PE_HEADER* pe = (PE_HEADER*)(base + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; if (opt->NumberOfRvaAndSizes < 6) return NTLSTATUS_SUCCESS; @@ -506,12 +452,12 @@ static KSTATUS LdrApplyRelocationsForProcess(PROCESS* process, uintptr_t base, u uint32_t relocSize = opt->DataDirectory[5].Size; if (!relocRva || !relocSize) return NTLSTATUS_SUCCESS; - BASE_RELOC* block = (BASE_RELOC *)(base + relocRva); + BASE_RELOC* block = (BASE_RELOC*)(base + relocRva); uintptr_t end = (uintptr_t)block + relocSize; while ((uintptr_t)block < end && block->SizeOfBlock > 0) { uint32_t count = (block->SizeOfBlock - sizeof(BASE_RELOC)) / sizeof(uint16_t); - uint16_t* entries = (uint16_t *)(block + 1); + uint16_t* entries = (uint16_t*)(block + 1); for (uint32_t i = 0; i < count; i++) { int type = entries[i] >> 12; @@ -519,39 +465,21 @@ static KSTATUS LdrApplyRelocationsForProcess(PROCESS* process, uintptr_t base, u // For 64-bit PEs, we mostly care about DIR64 (Type 10). if (type == IMAGE_REL_BASED_DIR64) { - uint64_t* ptr = (uint64_t *)(base + block->VirtualAddress + offset); + uint64_t* ptr = (uint64_t*)(base + block->VirtualAddress + offset); KSTATUS ws = KiEnsureUserImageWrite(process, user, (uintptr_t)ptr, sizeof(*ptr)); if (!KSUCCESS(ws)) return ws; *ptr += delta; } } - block = (BASE_RELOC *)((uint8_t *)block + block->SizeOfBlock); + block = (BASE_RELOC*)((uint8_t*)block + block->SizeOfBlock); } return NTLSTATUS_SUCCESS; } -// Import resolution. -typedef void* (*EXPORT_LOOKUP_FN)(void* ctx, const char* moduleName, const char* functionName); - -// Implements the global Export Lookup operation. -static void* KiGlobalExportLookup(void* ctx, const char* moduleName, const char* functionName) { - (void)ctx; - return LdrGetExport(moduleName, functionName); -} - -// Processes export Lookup. -static void* KiProcessExportLookup(void* ctx, const char* moduleName, const char* functionName) { - return LdrGetExportForProcess((PROCESS*)ctx, moduleName, functionName); -} - -static KSTATUS LdrResolveImportsCommon( - PROCESS* process, - uintptr_t imageBase, - EXPORT_LOOKUP_FN lookup, - void* ctx -) { - uint32_t peOffset = *(uint32_t *)(imageBase + 0x3C); - PE_HEADER* pe = (PE_HEADER *)(imageBase + peOffset); +// Resolves imports for kernel images against the kernel module registry. +KSTATUS LdrResolveImports(uintptr_t imageBase) { + uint32_t peOffset = *(uint32_t*)(imageBase + 0x3C); + PE_HEADER* pe = (PE_HEADER*)(imageBase + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; if (opt->NumberOfRvaAndSizes < 2) @@ -561,101 +489,57 @@ static KSTATUS LdrResolveImportsCommon( if (!impRva) return NTLSTATUS_SUCCESS; - IMPORT_DESC* imp = (IMPORT_DESC *)(imageBase + impRva); - + IMPORT_DESC* imp = (IMPORT_DESC*)(imageBase + impRva); while (imp->Name) { - const char* dllName = (const char *)(imageBase + imp->Name); + const char* dllName = (const char*)(imageBase + imp->Name); + LdrDbg("LDR: resolve kernel import dll=%s image=%016llx\n", + dllName, (unsigned long long)imageBase); - //Compatibility remap. - dllName = KiCanonicalModuleName(dllName); - LdrDbg("LDR: resolve import dll=%s image=%016llx pid=%u\n", dllName, (unsigned long long)imageBase, process ? process->ProcessId : 0); - - // Ensure dependency is loaded into the process. - - if (process) { - uintptr_t modBase = - KiFindProcessOrGlobalModuleBase(process, dllName); - - if (!modBase) { - KSTATUS s = - LdrLoadModuleForProcess(process, dllName, LDR_MODULE_LOAD_IMPORT, &modBase); - - if (!KSUCCESS(s)) { - DbgPrint( - "LdrResolveImports: Failed to load dependency %s (%d)\n", - dllName, - s - ); - return s; - } - } - } - - uint64_t* thunk = - (uint64_t *)(imageBase + imp->FirstThunk); - - uint64_t* origThunk = - (uint64_t *)(imageBase + - (imp->OriginalFirstThunk - ? imp->OriginalFirstThunk - : imp->FirstThunk)); + uint64_t* thunk = (uint64_t*)(imageBase + imp->FirstThunk); + uint64_t* origThunk = (uint64_t*)(imageBase + + (imp->OriginalFirstThunk ? imp->OriginalFirstThunk : imp->FirstThunk)); while (*origThunk) { if (*origThunk & (1ULL << 63)) { - DbgPrint( - "LdrResolveImports: Ordinal imports not supported! (%s)\n", - dllName - ); + DbgPrint("LdrResolveImports: Ordinal imports not supported! (%s)\n", dllName); return NTLSTATUS_NOT_SUPPORTED; } const char* funcName = - (const char *)(imageBase + - (*origThunk & 0xFFFFFFFF) + 2); - - void* addr = lookup(ctx, dllName, funcName); - + (const char*)(imageBase + (*origThunk & 0xFFFFFFFF) + 2); + void* addr = LdrGetExport(dllName, funcName); if (!addr) { - DbgPrint( - "LdrResolveImports: Function not found! (%s!%s)\n", - dllName, - funcName - ); + DbgPrint("LdrResolveImports: Function not found! (%s!%s)\n", dllName, funcName); return NTLSTATUS_NOT_FOUND; } - KSTATUS ws = KiEnsureUserImageWrite(process, process != (PROCESS*)0, (uintptr_t)thunk, sizeof(*thunk)); - if (!KSUCCESS(ws)) return ws; *thunk = (uint64_t)addr; - thunk++; origThunk++; } - imp++; } - return NTLSTATUS_SUCCESS; } -// Implements the ldr Resolve Imports operation. -KSTATUS LdrResolveImports(uintptr_t imageBase) { - return LdrResolveImportsCommon( - (PROCESS*)0, - imageBase, - KiGlobalExportLookup, - (void*)0 - ); -} - -// Implements the ldr Resolve Imports For Process operation. -KSTATUS LdrResolveImportsForProcess(PROCESS* process, uintptr_t imageBase) { - return LdrResolveImportsCommon( - process, - imageBase, - KiProcessExportLookup, - process - ); +// Requires an already-mapped bootstrap image to have no import directory. +KSTATUS LdrRequireNoImports(uintptr_t imageBase) { + if (!imageBase) return NTLSTATUS_INVALID_PARAMETER; + uint32_t peOffset = *(uint32_t*)(imageBase + 0x3C); + PE_HEADER* pe = (PE_HEADER*)(imageBase + peOffset); + if (pe->Signature != PE_SIGNATURE || + pe->Machine != IMAGE_FILE_MACHINE_AMD64 || + pe->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return NTLSTATUS_FAILURE; + if (pe->OptionalHeader.NumberOfRvaAndSizes >= 2 && + (pe->OptionalHeader.DataDirectory[1].VirtualAddress != 0 || + pe->OptionalHeader.DataDirectory[1].Size != 0)) + return NTLSTATUS_NOT_SUPPORTED; + if (pe->OptionalHeader.NumberOfRvaAndSizes >= 14 && + (pe->OptionalHeader.DataDirectory[13].VirtualAddress != 0 || + pe->OptionalHeader.DataDirectory[13].Size != 0)) + return NTLSTATUS_NOT_SUPPORTED; + return NTLSTATUS_SUCCESS; } // Loads a user image from an open file through the image-section object model. @@ -693,261 +577,26 @@ KSTATUS LdrLoadImageFileForProcess(PROCESS* process, FILE_OBJECT* file, uintptr_ return NTLSTATUS_SUCCESS; } -// 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 &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 NTLSTATUS_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 NTLSTATUS_ALREADY_EXISTS; - } - - if (process->ModuleCount >= PROCESS_MODULE_MAX) { - KeReleaseSpinlock(&process->ModuleLock, flags); - return NTLSTATUS_NO_MEMORY; - } - - PROCESS_MODULE* mod = &process->Modules[process->ModuleCount++]; - 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 NTLSTATUS_SUCCESS; -} - -// 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 NTLSTATUS_INVALID_PARAMETER; - - filename = KiCanonicalModuleName(filename); - - 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 NTLSTATUS_SUCCESS; - } - KeReleaseSpinlock(&process->ModuleLock, flags); - - FILE_OBJECT* f; - KSTATUS s = IoOpenFile(SysSvFromCstr(filename), &f); - if (!KSUCCESS(s)) { - char relative[260]; - size_t nameLen = strnlen(filename, sizeof(relative) - 8); - if (nameLen + 8 >= sizeof(relative)) - return NTLSTATUS_BUFFER_TOO_SMALL; - memcpy(relative, "system/", 7); - memcpy(relative + 7, filename, nameLen); - relative[7 + nameLen] = '\0'; - - s = KeOpenSystemRootFile(relative, &f); - if (!KSUCCESS(s)) { - DbgPrint("LdrLoadModuleForProcess: Failed to open %s on system root (%d)\n", filename, s); - return s; - } - } - - uint64_t fileSize = IoGetFileSize(f); - LdrDbg("LDR: load module pid=%u file=%s size=%llu\n", process->ProcessId, filename, fileSize); - - uintptr_t imageBase = 0; - s = LdrLoadImageFileForProcess(process, f, &imageBase); - IoCloseFile(f); - if (!KSUCCESS(s)) { - DbgPrint("LDR: load image for module %s failed st=%d\n", filename, s); - return s; - } - LdrDbg("LDR: module mapped pid=%u file=%s base=%016llx size=%llx\n", - process->ProcessId, filename, (unsigned long long)imageBase, (unsigned long long)LdrGetMappedImageSize(process, imageBase)); - - s = LdrResolveImportsForProcess(process, imageBase); - if (!KSUCCESS(s)) { - KiFreeLoadedImageForProcess(process, imageBase, true); - return s; - } - - s = LdrProtectImageForProcess(process, imageBase, true); - if (!KSUCCESS(s)) { - KiFreeLoadedImageForProcess(process, imageBase, true); - return s; - } - - uintptr_t registeredBase = 0; - s = LdrRegisterProcessModule(process, filename, imageBase, loadFlags, ®isteredBase); - if (s == NTLSTATUS_ALREADY_EXISTS) { - KiFreeLoadedImageForProcess(process, imageBase, true); - imageBase = registeredBase; - s = NTLSTATUS_SUCCESS; - } - if (!KSUCCESS(s)) { - KiFreeLoadedImageForProcess(process, imageBase, true); - return s; - } - - LdrDbg("LDR: module registered pid=%u name=%s base=%016llx modules=%u\n", - process->ProcessId, filename, (unsigned long long)imageBase, process->ModuleCount); - if (baseOut) *baseOut = imageBase; - return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_NOT_FOUND; - } - if (mod->PublicRefCount == 0) { - KeReleaseSpinlock(&process->ModuleLock, flags); - return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; - - uintptr_t flags = KeAcquireSpinlock(&process->ModuleLock); - PROCESS_MODULE* mod = KiFindProcessModuleByBaseLocked(process, imageBase); - if (!mod) { - KeReleaseSpinlock(&process->ModuleLock, flags); - return NTLSTATUS_NOT_FOUND; - } - KiFillProcessModuleInfo(mod, infoOut); - KeReleaseSpinlock(&process->ModuleLock, flags); - return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; - } - for (size_t i = 0; i < toCopy; i++) - KiFillProcessModuleInfo(&process->Modules[i], &infoOut[i]); - KeReleaseSpinlock(&process->ModuleLock, flags); - return (maxCount < count) ? NTLSTATUS_BUFFER_TOO_SMALL : NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER; if (size < 0x40) return NTLSTATUS_FAILURE; const uint8_t* bytes = (const uint8_t*)buffer; - uint32_t peOffset = *(const uint32_t *)(bytes + 0x3C); + uint32_t peOffset = *(const uint32_t*)(bytes + 0x3C); if ((size_t)peOffset > size || size - (size_t)peOffset < 24) return NTLSTATUS_FAILURE; - uint32_t signature = *(const uint32_t *)(bytes + peOffset); + uint32_t signature = *(const uint32_t*)(bytes + peOffset); if (signature != PE_SIGNATURE) return NTLSTATUS_FAILURE; - uint16_t machine = *(const uint16_t *)(bytes + peOffset + 4); - uint16_t optionalSize = *(const uint16_t *)(bytes + peOffset + 20); + uint16_t machine = *(const uint16_t*)(bytes + peOffset + 4); + uint16_t optionalSize = *(const uint16_t*)(bytes + peOffset + 20); if (optionalSize < sizeof(OPT_HEADER64) || (size_t)peOffset + 24 + optionalSize > size) { return NTLSTATUS_FAILURE; } - uint16_t optionalMagic = *(const uint16_t *)(bytes + peOffset + 24); + uint16_t optionalMagic = *(const uint16_t*)(bytes + peOffset + 24); if (machine == IMAGE_FILE_MACHINE_I386 || optionalMagic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { DbgPrint("LdrLoadKernelImage: hard error: 32-bit PE images are not supported (machine=0x%X magic=0x%X)\n", machine, optionalMagic); @@ -960,7 +609,7 @@ static KSTATUS KiLdrLoadKernelImageFromBuffer(const void* buffer, size_t size, u return NTLSTATUS_NOT_SUPPORTED; } - PE_HEADER* pe = (PE_HEADER *)(bytes + peOffset); + PE_HEADER* pe = (PE_HEADER*)(bytes + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; size_t imageSize = 0; KSTATUS sizeStatus = KiComputeRequiredImageSize(pe, size, &imageSize); @@ -981,10 +630,10 @@ static KSTATUS KiLdrLoadKernelImageFromBuffer(const void* buffer, size_t size, u memset(mem, 0, imageSize); memcpy(mem, buffer, opt->SizeOfHeaders); - SECTION_HEADER* sec = (SECTION_HEADER *)((uint8_t *)&pe->OptionalHeader + pe->SizeOfOptionalHeader); + SECTION_HEADER* sec = (SECTION_HEADER*)((uint8_t*)&pe->OptionalHeader + pe->SizeOfOptionalHeader); for (int i = 0; i < pe->NumberOfSections; i++) { - void* dst = (uint8_t *)mem + sec[i].VirtualAddress; - const void* src = (const uint8_t *)buffer + sec[i].PointerToRawData; + void* dst = (uint8_t*)mem + sec[i].VirtualAddress; + const void* src = (const uint8_t*)buffer + sec[i].PointerToRawData; if (sec[i].SizeOfRawData) { size_t copySize = sec[i].SizeOfRawData; if (sec[i].VirtualSize && sec[i].VirtualSize < copySize) copySize = sec[i].VirtualSize; diff --git a/code/kernel/ldr_section.c b/code/kernel/ldr_section.c index b105a68..68cda02 100644 --- a/code/kernel/ldr_section.c +++ b/code/kernel/ldr_section.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -329,16 +330,16 @@ static KSTATUS KiReadImageSectionFileBytes(IMAGE_SECTION_OBJECT* section, // Image prototype pages are the canonical cache for executable image bytes. // Read the backing filesystem directly so the fault path has one cache owner. - if (!section->BackingFile->Fs || !section->BackingFile->Fs->Read) - return NTLSTATUS_NOT_SUPPORTED; + if (!IoVolumeIsMounted(section->BackingFile->Volume)) + return NTLSTATUS_OBJECT_DEAD; size_t got = 0; - KSTATUS s = section->BackingFile->Fs->Read(section->BackingFile->Fs, - section->BackingFile->FileCtx, - fileOffset, - dst, - size, - &got); + KSTATUS s = IoVolumeRead(section->BackingFile->Volume, + section->BackingFile->FileCtx, + fileOffset, + dst, + size, + &got); if (!KSUCCESS(s)) return s; if (got != size) diff --git a/code/kernel/mm_file_cache.c b/code/kernel/mm_file_cache.c index b22e50d..0022114 100644 --- a/code/kernel/mm_file_cache.c +++ b/code/kernel/mm_file_cache.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,8 @@ static uint64_t gFileCacheMaxResidentPages; static MM_FILE_CACHE_STATS gFileCacheStats; +static int KiFileCacheUnlinkEntryLocked(MM_FILE_CACHE_ENTRY* entry); + static SPINLOCK gWritebackLock = SPINLOCK_INIT; static SEMAPHORE* gWritebackSemaphore; static int gWritebackStartState; @@ -291,27 +294,35 @@ static KSTATUS KiFileCacheFlushOneDirtyPage(FILE_OBJECT* file, int* didWork) { void* page = MmPfnToVirtual(frame); size_t written = 0; KSTATUS s = NTLSTATUS_FAILURE; - if (page && target->Fs && target->Fs->Write) - s = target->Fs->Write(target->Fs, target->FileCtx, offset, page, validBytes, &written); + if (page) + s = IoVolumeWrite(target->Volume, target->FileCtx, offset, page, validBytes, &written); if (KSUCCESS(s) && written != validBytes) s = NTLSTATUS_FAILURE; + MM_FILE_CACHE_ENTRY* invalidatedVictim = (MM_FILE_CACHE_ENTRY*)0; flags = KeAcquireSpinlock(&gFileCacheLock); uint32_t bucket = KiFileCacheHash(target, offset); MM_FILE_CACHE_ENTRY* cur = KiFileCacheFindLocked(target, offset, bucket); if (cur && cur->Frame == frame) { - if (KSUCCESS(s) && cur->Dirty && cur->DirtySequence == sequence) { + cur->Writeback = 0; + if (cur->Invalidated) { + if (cur->Dirty) + KiFileCacheSetDirtyLocked(cur, 0); + if (KiFileCacheUnlinkEntryLocked(cur)) + invalidatedVictim = cur; + } else if (KSUCCESS(s) && cur->Dirty && cur->DirtySequence == sequence) { KiFileCacheSetDirtyLocked(cur, 0); gFileCacheStats.WritebackPages++; gFileCacheStats.WritebackBytes += validBytes; } else if (!KSUCCESS(s)) { gFileCacheStats.WritebackFailures++; } - cur->Writeback = 0; } KeReleaseSpinlock(&gFileCacheLock, flags); (void)MmPfnRelease(frame); + if (invalidatedVictim) + KiFileCacheFreeEntry(invalidatedVictim); ObDereferenceObject(&target->Header); if (didWork) *didWork = 1; return s; @@ -619,9 +630,7 @@ static KSTATUS KiFileCacheLoadPage(FILE_OBJECT* file, toRead = (size_t)remaining; size_t got = 0; - s = NTLSTATUS_NOT_SUPPORTED; - if (file->Fs && file->Fs->Read) - s = file->Fs->Read(file->Fs, file->FileCtx, aligned, page, toRead, &got); + s = IoVolumeRead(file->Volume, file->FileCtx, aligned, page, toRead, &got); if (!KSUCCESS(s)) { (void)MmPfnRelease(frame); KiFileCacheCancelLoadingPage(entry); @@ -747,8 +756,8 @@ KSTATUS MmFileCacheWrite(FILE_OBJECT* file, uint64_t offset, const void* buf, return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; - if (!file->Fs || !file->Fs->Write) - return NTLSTATUS_NOT_SUPPORTED; + if (!IoVolumeIsMounted(file->Volume)) + return NTLSTATUS_OBJECT_DEAD; if (size == 0) return NTLSTATUS_SUCCESS; @@ -925,6 +934,46 @@ void MmFileCacheInvalidateAll(FILE_OBJECT* file) { } } +// Discards all cache state owned by one dead volume without attempting writeback. +void MmFileCacheDiscardVolume(IO_VOLUME* volume) { + if (!volume) return; + + for (uint32_t b = 0; b < MM_FILE_CACHE_BUCKETS; ++b) { + while (true) { + MM_FILE_CACHE_ENTRY* victim = (MM_FILE_CACHE_ENTRY*)0; + bool pending = false; + uintptr_t flags = KeAcquireSpinlock(&gFileCacheLock); + MM_FILE_CACHE_ENTRY* prev = (MM_FILE_CACHE_ENTRY*)0; + for (MM_FILE_CACHE_ENTRY* e = gFileCacheBuckets[b]; e; prev = e, e = e->Next) { + if (!e->File || e->File->Volume != volume) + continue; + + if (e->Loading || e->Writeback) { + e->Invalidated = 1; + if (e->Dirty) + KiFileCacheSetDirtyLocked(e, 0); + pending = true; + continue; + } + + victim = e; + KiFileCacheRemoveEntryLocked(b, prev, e); + gFileCacheStats.Invalidations++; + break; + } + KeReleaseSpinlock(&gFileCacheLock, flags); + if (victim) { + KiFileCacheFreeEntry(victim); + continue; + } + if (!pending) + break; + // Loading/writeback entries unlink themselves when their in-flight operation completes. + break; + } + } +} + KSTATUS MmFileCacheDropOnePageForPressure(void) { return KiFileCacheReclaimOnePage((FILE_OBJECT*)0, 0); } diff --git a/code/kernel/mm_pool.c b/code/kernel/mm_pool.c index a3fd1dc..dc8e3dd 100644 --- a/code/kernel/mm_pool.c +++ b/code/kernel/mm_pool.c @@ -68,19 +68,19 @@ _Static_assert((sizeof(BLOCK_FTR) % POOL_ALIGNMENT) == 0, "pool footer must pres // Implements the hdr To Ftr operation. static inline BLOCK_FTR* KiHdrToFtr(BLOCK_HDR* h) { - return (BLOCK_FTR *)((char *)h + HDR_SIZE + h->Size); + return (BLOCK_FTR*)((char*)h + HDR_SIZE + h->Size); } // Implements the ftr To Hdr operation. static inline BLOCK_HDR* KiFtrToHdr(BLOCK_FTR* f) { - return (BLOCK_HDR *)((char *)f - HDR_SIZE - f->Size); + return (BLOCK_HDR*)((char*)f - HDR_SIZE - f->Size); } // Implements the next Block operation. static inline BLOCK_HDR* KiNextBlock(BLOCK_HDR* h) { - return (BLOCK_HDR *)((char *)h + HDR_SIZE + h->Size + FTR_SIZE); + return (BLOCK_HDR*)((char*)h + HDR_SIZE + h->Size + FTR_SIZE); } // Implements the prev Ftr operation. static inline BLOCK_FTR* KiPrevFtr(BLOCK_HDR* h) { - return (BLOCK_FTR *)((char *)h - FTR_SIZE); + return (BLOCK_FTR*)((char*)h - FTR_SIZE); } typedef struct HEAP HEAP; @@ -268,7 +268,7 @@ static int KiHeapGrow(HEAP* h, size_t need, uintptr_t caller) { return 0; } mappedEnd = va + PAGE_SIZE; - memset((void *)va, 0, PAGE_SIZE); + memset((void*)va, 0, PAGE_SIZE); } // The grow path owns the only transition from reserved heap VA to mapped heap @@ -300,12 +300,12 @@ static int KiHeapGrow(HEAP* h, size_t need, uintptr_t caller) { } // Make the new pages a single free block. - BLOCK_HDR* blk = (BLOCK_HDR *)oldBrk; + BLOCK_HDR* blk = (BLOCK_HDR*)oldBrk; blk->Magic = BLOCK_MAGIC; blk->Free = BLOCK_STATE_FREE; blk->Size = grow - OVERHEAD; - blk->PrevFree = (BLOCK_HDR *)0; - blk->NextFree = (BLOCK_HDR *)0; + blk->PrevFree = (BLOCK_HDR*)0; + blk->NextFree = (BLOCK_HDR*)0; blk->RequestedSize = 0; blk->Tag = 0; blk->Flags = 0; @@ -381,7 +381,7 @@ static void KiFreeListRemoveLocked(HEAP* h, BLOCK_HDR* blk, uintptr_t caller) { if (blk->PrevFree) blk->PrevFree->NextFree = blk->NextFree; else h->FreeList = blk->NextFree; if (blk->NextFree) blk->NextFree->PrevFree = blk->PrevFree; - blk->PrevFree = blk->NextFree = (BLOCK_HDR *)0; + blk->PrevFree = blk->NextFree = (BLOCK_HDR*)0; } // Implements the heap Try Decommit Tail operation. @@ -548,7 +548,7 @@ static void KiHeapQuarantineBlockLocked(HEAP* h, BLOCK_HDR* blk, uintptr_t calle // Allocates a block from the heap. static void* KiHeapAlloc(HEAP* h, size_t requestedSize, uint32_t tag, uintptr_t caller) { - if (!requestedSize) return (void *)0; + if (!requestedSize) return (void*)0; uint32_t dbg = KiPoolDebugFlags(); size_t size = requestedSize; @@ -564,7 +564,7 @@ static void* KiHeapAlloc(HEAP* h, size_t requestedSize, uint32_t tag, uintptr_t uintptr_t flags = KeAcquireSpinlock(&h->Lock); // Best-fit search. - BLOCK_HDR* best = (BLOCK_HDR *)0; + BLOCK_HDR* best = (BLOCK_HDR*)0; for (BLOCK_HDR* cur = h->FreeList; cur; cur = cur->NextFree) { KiPoolValidateFooterLocked(h, cur, "free list block corrupt"); if (cur->Free != BLOCK_STATE_FREE) @@ -589,14 +589,14 @@ static void* KiHeapAlloc(HEAP* h, size_t requestedSize, uint32_t tag, uintptr_t if (!best) { if (!KiHeapGrow(h, size + OVERHEAD, caller)) { KeReleaseSpinlock(&h->Lock, flags); - return (void *)0; + return (void*)0; } // Re-search; KiHeapGrow inserted at the front. best = h->FreeList; while (best && best->Size < size) best = best->NextFree; if (!best) { KeReleaseSpinlock(&h->Lock, flags); - return (void *)0; + return (void*)0; } KiPoolValidateFooterLocked(h, best, "grown free block corrupt"); } @@ -605,12 +605,12 @@ static void* KiHeapAlloc(HEAP* h, size_t requestedSize, uint32_t tag, uintptr_t // Split the block if the remainder is big enough. if (best->Size >= size + OVERHEAD + POOL_ALIGNMENT) { - BLOCK_HDR* rem = (BLOCK_HDR *)((char *)best + HDR_SIZE + size + FTR_SIZE); + BLOCK_HDR* rem = (BLOCK_HDR*)((char*)best + HDR_SIZE + size + FTR_SIZE); rem->Magic = BLOCK_MAGIC; rem->Free = BLOCK_STATE_FREE; rem->Size = best->Size - size - OVERHEAD; - rem->PrevFree = (BLOCK_HDR *)0; - rem->NextFree = (BLOCK_HDR *)0; + rem->PrevFree = (BLOCK_HDR*)0; + rem->NextFree = (BLOCK_HDR*)0; rem->RequestedSize = 0; rem->Tag = 0; rem->Flags = 0; @@ -661,7 +661,7 @@ static void KiHeapFree(HEAP* h, void* ptr, uintptr_t caller) { if (!h || u < h->Start + HDR_SIZE || u >= h->Brk) KiPoolPanic("invalid free pointer outside heap", h, (BLOCK_HDR*)0, ptr); - BLOCK_HDR* blk = (BLOCK_HDR *)(u - HDR_SIZE); + BLOCK_HDR* blk = (BLOCK_HDR*)(u - HDR_SIZE); uintptr_t flags = KeAcquireSpinlock(&h->Lock); @@ -708,7 +708,7 @@ static void* KiHeapRealloc(HEAP* h, void* ptr, size_t newSize, uintptr_t caller) if (!h || u < h->Start + HDR_SIZE || u >= h->Brk) KiPoolPanic("invalid realloc pointer outside heap", h, (BLOCK_HDR*)0, ptr); - BLOCK_HDR* blk = (BLOCK_HDR *)(u - HDR_SIZE); + BLOCK_HDR* blk = (BLOCK_HDR*)(u - HDR_SIZE); uintptr_t flags = KeAcquireSpinlock(&h->Lock); if (blk->Magic != BLOCK_MAGIC) diff --git a/code/kernel/mm_pte.c b/code/kernel/mm_pte.c index 2a71465..04c105e 100644 --- a/code/kernel/mm_pte.c +++ b/code/kernel/mm_pte.c @@ -4,23 +4,23 @@ #include #include +#include #include #include #include #include #include -#include // Page-table frames are reached through the physical direct map while the kernel still owns one shared CR3. static uint64_t gPhysBase = PHYS_MAP_BASE; // Converts a physical address to the kernel physical-window alias used for page-table editing. static inline void* KiPhysToVirt(PHYSICAL_ADDRESS p) { - return (void *)(p + gPhysBase); + return (void*)(p + gPhysBase); } // Returns the entry pointer for a page-table frame that is already mapped in the physical window. static inline uint64_t* KiPtePtr(PHYSICAL_ADDRESS tablePhys, unsigned idx) { - return (uint64_t *)KiPhysToVirt(tablePhys) + idx; + return (uint64_t*)KiPhysToVirt(tablePhys) + idx; } // The bootloader builds the initial PML4, and this layer owns all later edits to it. @@ -154,7 +154,7 @@ void MmPteFlushKernelAddress(uintptr_t virt) { continue; targets++; KiPteStatInc(&gPteStats.ShootdownTargetCpus); - HalApicSendTlbShootdownIpi(cpu->LapicId); + KeSendProcessorNmi(cpu->HardwareId); } if (!targets) { diff --git a/code/kernel/mm_swap.c b/code/kernel/mm_swap.c index f239766..130a060 100644 --- a/code/kernel/mm_swap.c +++ b/code/kernel/mm_swap.c @@ -1,5 +1,5 @@ // Kernel swap manager. -// The first backend is a fixed-size pagefile accessed through VFS offset I/O. +// The first backend is a fixed-size pagefile accessed through system-volume file I/O. // Kernel memory and filesystem code remain non-pageable, so this layer is safe // to use before the VMM has direct block-extents for pageout. @@ -12,6 +12,7 @@ #include #include #include +#include typedef struct MM_SWAP_STATE { SPINLOCK Lock; @@ -70,8 +71,8 @@ static KSTATUS KiSwapAllocateSlot(uint32_t* slotOut) { } // Initializes the fixed pagefile backend. -KSTATUS MmSwapInitialize(SYS_STRING_VIEW path, uint64_t sizeBytes) { - if (!path.Data || !path.Length || sizeBytes < PAGE_SIZE) +KSTATUS MmSwapInitialize(const char* relativePath, uint64_t sizeBytes) { + if (!relativePath || !relativePath[0] || sizeBytes < PAGE_SIZE) return NTLSTATUS_INVALID_PARAMETER; if (sizeBytes & (PAGE_SIZE - 1)) sizeBytes = (sizeBytes + PAGE_SIZE - 1) & ~(uint64_t)(PAGE_SIZE - 1); @@ -84,9 +85,9 @@ KSTATUS MmSwapInitialize(SYS_STRING_VIEW path, uint64_t sizeBytes) { KeReleaseSpinlock(&gSwap.Lock, flags); FILE_OBJECT* file = (FILE_OBJECT*)0; - KSTATUS s = IoOpenFile(path, &file); + KSTATUS s = KeOpenSystemRootFile(relativePath, &file); if (!KSUCCESS(s)) { - s = IoCreateFile(path, &file); + s = KeCreateSystemRootFile(relativePath, &file); if (!KSUCCESS(s)) return s; } @@ -137,10 +138,10 @@ KSTATUS MmSwapInitialize(SYS_STRING_VIEW path, uint64_t sizeBytes) { gSwap.Stats.FreeSlots = slotCount; KeReleaseSpinlock(&gSwap.Lock, flags); - DbgPrint("swap: initialized %u slots (%llu KiB) at %v\n", + DbgPrint("swap: initialized %u slots (%llu KiB) at system-root/%s\n", slotCount, (unsigned long long)(sizeBytes / 1024), - path); + relativePath); return NTLSTATUS_SUCCESS; } diff --git a/code/kernel/mm_virt.c b/code/kernel/mm_virt.c index 218cced..0a40266 100644 --- a/code/kernel/mm_virt.c +++ b/code/kernel/mm_virt.c @@ -443,7 +443,12 @@ static void KiVadSyncSharedFilePageBeforeRelease(MM_VAD* vad, size_t index) { if ((page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) == 0) return; uintptr_t va = vad->Base + index * PAGE_SIZE; - if (page->Resident && page->Frame && MmPteTestAndClearDirty(va)) + if (!page->Resident || !page->Frame) + return; + + int wasDirty = MmPteTestAndClearDirty(va); + int wasWriteEnabled = (page->Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK) == 0; + if (wasDirty || wasWriteEnabled) (void)KiVadMarkSharedFilePageDirty(vad, index, 0); } @@ -1361,6 +1366,58 @@ static KSTATUS KiCopyToUserPaged(void* userDst, const void* kernelSrc, size_t si return KiCopyUserPaged(userDst, kernelSrc, size, 1); } +// Copies between process-owned user ranges without bouncing through a kernel payload buffer. +__declspec(dllexport) KSTATUS MmCopyUserToUser(PROCESS* sourceProcess, const void* source, + PROCESS* targetProcess, void* target, size_t size) { + if (size == 0) + return NTLSTATUS_SUCCESS; + if (!sourceProcess || !targetProcess || !source || !target) + return NTLSTATUS_INVALID_PARAMETER; + + uintptr_t sourceAddress = (uintptr_t)source; + uintptr_t targetAddress = (uintptr_t)target; + if (!KiUserRangeValid(sourceAddress, size) || !KiUserRangeValid(targetAddress, size)) + return NTLSTATUS_ACCESS_DENIED; + + size_t done = 0; + while (done < size) { + MM_PINNED_USER_PAGE sourcePage; + MM_PINNED_USER_PAGE targetPage; + memset(&sourcePage, 0, sizeof(sourcePage)); + memset(&targetPage, 0, sizeof(targetPage)); + + KSTATUS status = MmPinUserPageForRead(sourceProcess, + (const void*)(sourceAddress + done), + &sourcePage); + if (!KSUCCESS(status)) + return status; + + status = MmPinUserPageForWrite(targetProcess, + (void*)(targetAddress + done), + &targetPage); + if (!KSUCCESS(status)) { + MmUnpinUserPage(&sourcePage); + return status; + } + + size_t sourceRemaining = PAGE_SIZE - sourcePage.PageOffset; + size_t targetRemaining = PAGE_SIZE - targetPage.PageOffset; + size_t chunk = size - done; + if (chunk > sourceRemaining) chunk = sourceRemaining; + if (chunk > targetRemaining) chunk = targetRemaining; + + memcpy((uint8_t*)targetPage.KernelAddress + targetPage.PageOffset, + (const uint8_t*)sourcePage.KernelAddress + sourcePage.PageOffset, + chunk); + + MmUnpinUserPage(&targetPage); + MmUnpinUserPage(&sourcePage); + done += chunk; + } + + return NTLSTATUS_SUCCESS; +} + // Reserves a VAD in the global user VA arena without committing physical pages. KSTATUS MmVadReserve(PROCESS* process, size_t size, size_t alignment, uint64_t pageFlags, uint32_t vadFlags, @@ -2158,10 +2215,11 @@ KSTATUS MmVadSyncFileMappings(FILE_OBJECT* file) { uintptr_t va = vad->Base + i * PAGE_SIZE; int wasDirty = MmPteTestAndClearDirty(va); - if (wasDirty) + int wasWriteEnabled = (page->Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK) == 0; + if (wasDirty || wasWriteEnabled) (void)KiVadMarkSharedFilePageDirty(vad, i, 0); - if ((page->Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK) == 0) { + if (wasWriteEnabled) { page->Flags |= MM_VAD_PAGE_FILE_DIRTY_TRACK; (void)MmPteProtectRange(va, PAGE_SIZE, KiVadHardwarePageFlags(page)); } diff --git a/code/kernel/net_socket.c b/code/kernel/net_socket.c index 2638d8d..48945ab 100644 --- a/code/kernel/net_socket.c +++ b/code/kernel/net_socket.c @@ -106,7 +106,7 @@ struct NET_ASYNC_SOCKET_REQUEST_OBJECT { size_t OptionValueSize; size_t* OptionActualSizeOut; - uint32_t* HandleOut; + SYS_HANDLE* HandleOut; uint32_t* PollReturnedEventsOut; MM_LOCKED_USER_BUFFER* LockedAddressOut; @@ -277,11 +277,11 @@ static void NetAsyncSocketExecute(NET_ASYNC_SOCKET_REQUEST_OBJECT* req, 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); + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KeCreateHandle(req->OwnerProcess, + &accepted->Header, + KeGetValidHandleRights(ObjectTypeSocket), + &handle); ObDereferenceObject(&accepted->Header); if (KSUCCESS(status)) { if (req->HandleOut) diff --git a/code/kernel/ob_object.c b/code/kernel/ob_object.c index 3e59ab7..758e607 100644 --- a/code/kernel/ob_object.c +++ b/code/kernel/ob_object.c @@ -29,10 +29,13 @@ 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'); case ObjectTypeDisplayClock: return MM_POOL_TAG('d', 'c', 'l', 'k'); case ObjectTypeAudioEndpoint: return MM_POOL_TAG('a', 'e', 'p', 't'); case ObjectTypeAudioStream: return MM_POOL_TAG('a', 's', 't', 'r'); + case ObjectTypeSecurityContext: return MM_POOL_TAG('s', 'e', 'c', 'x'); + case ObjectTypePort: return MM_POOL_TAG('p', 'o', 'r', 't'); + case ObjectTypeVolume: return MM_POOL_TAG('v', 'o', 'l', 'm'); + case ObjectTypeVolumeManager: return MM_POOL_TAG('v', 'm', 'g', 'r'); default: return MM_POOL_TAG('o', 'b', 'j', ' '); } } diff --git a/code/kernel/se_security.c b/code/kernel/se_security.c new file mode 100644 index 0000000..7250fb7 --- /dev/null +++ b/code/kernel/se_security.c @@ -0,0 +1,130 @@ +// Kernel security contexts. Contexts are immutable authority sets carried by processes and delegated only by attenuation. + +#include +#include +#include +#include +#include + +static KE_ID_ALLOCATOR gSecurityIds = KE_ID_ALLOCATOR_INIT(1u); +static SECURITY_CONTEXT* gRootSecurityContext; + +// Destroys one non-root security context after its last object reference disappears. +static void KiSecurityContextDestroy(OBJECT* object) { + (void)object; +} + +// Allocates and initializes one immutable security context object. +static KSTATUS KiCreateSecurityContext(SYS_PRIVILEGES effectivePrivileges, + SYS_PRIVILEGES delegablePrivileges, + SYS_SERVICE_CLAIMS serviceClaims, + SYS_SERVICE_CLAIMS delegableServiceClaims, + SECURITY_CONTEXT** contextOut) { + if (!contextOut) + return NTLSTATUS_INVALID_PARAMETER; + *contextOut = (SECURITY_CONTEXT*)0; + + if ((effectivePrivileges & ~SYS_PRIV_ALL) != 0 || + (delegablePrivileges & ~SYS_PRIV_ALL) != 0 || + (serviceClaims & ~SYS_SERVICE_CLAIM_ALL) != 0 || + (delegableServiceClaims & ~SYS_SERVICE_CLAIM_ALL) != 0) + return NTLSTATUS_INVALID_PARAMETER; + if ((delegablePrivileges & ~effectivePrivileges) != 0 || + (delegableServiceClaims & ~serviceClaims) != 0) + return NTLSTATUS_ACCESS_DENIED; + + SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)ObAllocateObject(ObjectTypeSecurityContext, + sizeof(SECURITY_CONTEXT)); + if (!context) + return NTLSTATUS_NO_MEMORY; + context->Header.Destroy = KiSecurityContextDestroy; + + KSTATUS status = KeAllocateId32(&gSecurityIds, &context->SecurityId); + if (!KSUCCESS(status)) { + ObDereferenceObject(&context->Header); + return status; + } + + context->EffectivePrivileges = effectivePrivileges; + context->DelegablePrivileges = delegablePrivileges; + context->ServiceClaims = serviceClaims; + context->DelegableServiceClaims = delegableServiceClaims; + context->Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; + *contextOut = context; + return NTLSTATUS_SUCCESS; +} + +// Initializes the root context exactly once before any process is created. +KSTATUS SeInitializeSecurity(void) { + if (gRootSecurityContext) + return NTLSTATUS_SUCCESS; + + SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)0; + KSTATUS status = KiCreateSecurityContext(SYS_PRIV_ALL, + SYS_PRIV_ALL, + SYS_SERVICE_CLAIM_ALL, + SYS_SERVICE_CLAIM_ALL, + &context); + if (!KSUCCESS(status)) + return status; + + gRootSecurityContext = context; + return NTLSTATUS_SUCCESS; +} + +// Returns the permanently referenced bootstrap root context. +SECURITY_CONTEXT* SeGetRootSecurityContext(void) { + return gRootSecurityContext; +} + +// Derives one child context whose effective and delegable authority are subsets of the source delegable sets. +KSTATUS SeDeriveSecurityContext(SECURITY_CONTEXT* source, + SYS_PRIVILEGES effectivePrivileges, + SYS_PRIVILEGES delegablePrivileges, + SYS_SERVICE_CLAIMS serviceClaims, + SYS_SERVICE_CLAIMS delegableServiceClaims, + SECURITY_CONTEXT** contextOut) { + if (!source || !contextOut) + return NTLSTATUS_INVALID_PARAMETER; + + if ((effectivePrivileges & ~source->DelegablePrivileges) != 0 || + (delegablePrivileges & ~source->DelegablePrivileges) != 0 || + (serviceClaims & ~source->DelegableServiceClaims) != 0 || + (delegableServiceClaims & ~source->DelegableServiceClaims) != 0) + return NTLSTATUS_ACCESS_DENIED; + + return KiCreateSecurityContext(effectivePrivileges, + delegablePrivileges, + serviceClaims, + delegableServiceClaims, + contextOut); +} + +// Tests one process's immutable effective privilege mask. +bool SeProcessHasPrivilege(const struct PROCESS* process, SYS_PRIVILEGES privileges) { + if (!process || !process->SecurityContext) + return false; + return (process->SecurityContext->EffectivePrivileges & privileges) == privileges; +} + +// Tests one security context's effective service-claim mask. +bool SeContextHasServiceClaims(const SECURITY_CONTEXT* context, SYS_SERVICE_CLAIMS claims) { + if (!context) + return false; + return (context->ServiceClaims & claims) == claims; +} + +// Exports immutable context metadata to one public structure. +void SeQuerySecurityContext(const SECURITY_CONTEXT* context, SYS_SECURITY_CONTEXT_INFO* infoOut) { + if (!context || !infoOut) + return; + + memset(infoOut, 0, sizeof(*infoOut)); + infoOut->Size = sizeof(*infoOut); + infoOut->SecurityId = context->SecurityId; + infoOut->EffectivePrivileges = context->EffectivePrivileges; + infoOut->DelegablePrivileges = context->DelegablePrivileges; + infoOut->ServiceClaims = context->ServiceClaims; + infoOut->DelegableServiceClaims = context->DelegableServiceClaims; + infoOut->Flags = context->Flags; +} diff --git a/code/public/ntlite/audio.h b/code/public/ntlite/audio.h index 5c93c48..a7f5b0f 100644 --- a/code/public/ntlite/audio.h +++ b/code/public/ntlite/audio.h @@ -4,8 +4,9 @@ #include #include +#include -#define SYS_AUDIO_ABI_VERSION 3u +#define SYS_AUDIO_ABI_VERSION 4u #define SYS_AUDIO_MAX_ENDPOINTS 32u #define SYS_AUDIO_STREAM_CAPACITY_FRAMES 4096u @@ -54,7 +55,7 @@ typedef enum SYS_AUDIO_ENDPOINT_STATE { typedef struct SYS_AUDIO_ENDPOINT_INFO { uint32_t Size; uint32_t Version; - uint64_t EndpointId; + uint32_t EndpointId; uint32_t Kind; uint32_t Flags; uint32_t Capabilities; @@ -82,17 +83,17 @@ typedef struct SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST { typedef struct SYS_AUDIO_OPEN_STREAM_REQUEST { uint32_t Size; uint32_t Flags; - uint64_t EndpointId; + uint32_t EndpointId; SYS_AUDIO_FORMAT Format; uint32_t Reserved; - uint32_t* StreamHandleOut; + SYS_HANDLE* StreamHandleOut; } SYS_AUDIO_OPEN_STREAM_REQUEST; // Describes one bounded nonblocking stream write. typedef struct SYS_AUDIO_WRITE_REQUEST { uint32_t Size; uint32_t Flags; - uint32_t StreamHandle; + SYS_HANDLE StreamHandle; uint32_t FrameCount; const void* Frames; uint32_t* WrittenFramesOut; @@ -111,7 +112,7 @@ typedef struct SYS_AUDIO_WRITE_REQUEST { typedef struct SYS_AUDIO_ARM_WAIT_REQUEST { uint32_t Size; uint32_t Flags; - uint32_t StreamHandle; + SYS_HANDLE StreamHandle; uint32_t Mask; // Frame counters are inclusive absolute targets and generations are change baselines. uint32_t WritableFrames; @@ -165,7 +166,7 @@ typedef struct SYS_AUDIO_STREAM_INFO { typedef struct SYS_AUDIO_SET_VOLUME_REQUEST { uint32_t Size; uint32_t Flags; - uint32_t StreamHandle; + SYS_HANDLE StreamHandle; uint32_t Muted; uint32_t VolumeQ16; int32_t PanQ16; @@ -175,7 +176,7 @@ typedef struct SYS_AUDIO_SET_VOLUME_REQUEST { typedef struct SYS_AUDIO_SET_MASTER_VOLUME_REQUEST { uint32_t Size; uint32_t Flags; - uint64_t EndpointId; + uint32_t EndpointId; uint32_t Muted; uint32_t VolumeQ16; } SYS_AUDIO_SET_MASTER_VOLUME_REQUEST; @@ -193,25 +194,25 @@ KSTATUS SysAudioWrite(const SYS_AUDIO_WRITE_REQUEST* request); KSTATUS SysAudioArmWait(const SYS_AUDIO_ARM_WAIT_REQUEST* request); // Copies one coherent stream snapshot after the caller initializes infoOut->Size. -KSTATUS SysAudioQueryStream(uint32_t streamHandle, SYS_AUDIO_STREAM_INFO* infoOut); +KSTATUS SysAudioQueryStream(SYS_HANDLE streamHandle, SYS_AUDIO_STREAM_INFO* infoOut); // Starts one created or stopped stream. -KSTATUS SysAudioStart(uint32_t streamHandle); +KSTATUS SysAudioStart(SYS_HANDLE streamHandle); // Pauses future mixing while preserving unconsumed source frames. -KSTATUS SysAudioPause(uint32_t streamHandle); +KSTATUS SysAudioPause(SYS_HANDLE streamHandle); // Resumes one paused stream. -KSTATUS SysAudioResume(uint32_t streamHandle); +KSTATUS SysAudioResume(SYS_HANDLE streamHandle); // Rejects new writes and stops after all submitted frames reach a terminal disposition. -KSTATUS SysAudioDrain(uint32_t streamHandle); +KSTATUS SysAudioDrain(SYS_HANDLE streamHandle); // Stops future mixing and discards source frames not yet committed to hardware periods. -KSTATUS SysAudioStop(uint32_t streamHandle); +KSTATUS SysAudioStop(SYS_HANDLE streamHandle); // Returns one stopped stream to an empty position-zero state. -KSTATUS SysAudioReset(uint32_t streamHandle); +KSTATUS SysAudioReset(SYS_HANDLE streamHandle); // Updates one stream's software volume, pan, and mute state. KSTATUS SysAudioSetVolume(const SYS_AUDIO_SET_VOLUME_REQUEST* request); diff --git a/code/public/ntlite/boot_info.h b/code/public/ntlite/boot_info.h index 7a0a2fd..b5c598f 100644 --- a/code/public/ntlite/boot_info.h +++ b/code/public/ntlite/boot_info.h @@ -1,13 +1,15 @@ -// Public NTLite boot info ABI declarations shared between kernel, drivers, and user mode. +// Public NTLite boot info ABI declarations shared between the bootloader and kernel. #pragma once -// Boot information ABI shared between bootx64.efi and kernel.exe. -// The bootloader fills this structure immediately before ExitBootServices so the kernel starts with a firmware-free view of memory and devices. + #include #include -#define BOOT_INFO_MAGIC 0x424F4F54494E464FULL // 'BOOTINFO' -#define MAX_BOOT_MODULES 32 +// Encodes the ASCII tag 'BOOTINFO'. +#define BOOT_INFO_MAGIC 0x424F4F54494E464FULL +#define BOOT_INFO_VERSION 3u + +#define BOOT_FILE_NAME_MAX 64u #define BOOT_TIME_FLAG_VALID 0x0001u #define BOOT_TIME_FLAG_TZ_KNOWN 0x0002u @@ -18,56 +20,80 @@ typedef struct BOOT_TIME { uint16_t Year; - uint8_t Month; - uint8_t Day; - uint8_t Hour; - uint8_t Minute; - uint8_t Second; - uint8_t Daylight; + uint8_t Month; + uint8_t Day; + uint8_t Hour; + uint8_t Minute; + uint8_t Second; + uint8_t Daylight; uint32_t Nanosecond; - int16_t TimeZoneMinutes; + int16_t TimeZoneMinutes; uint16_t Flags; uint32_t Reserved; } BOOT_TIME; -typedef struct BOOT_MODULE { - char Name[32]; // e.g. "hal.dll", "ata.sys" - uintptr_t VirtBase; // Virtual address (mapped) - uintptr_t PhysBase; // Raw physical address (file) - size_t ImageSize; // File size / Image size -} BOOT_MODULE; +// Describes a page-backed raw file that remains owned by the boot handoff. +typedef struct BOOT_BLOB { + uintptr_t PhysicalBase; + uint32_t Length; + uint32_t PageCount; +} BOOT_BLOB; + +// Describes a boot driver raw file passed to the kernel without mapping it as an image. +typedef struct BOOT_FILE { + char Name[BOOT_FILE_NAME_MAX]; + uintptr_t PhysicalBase; + uint32_t Length; + uint32_t PageCount; +} BOOT_FILE; + +// Describes the page-backed BOOT_FILE array produced from drivers.ini [boot]. +typedef struct BOOT_FILE_LIST { + uintptr_t FilesPhysical; + uint32_t Count; + uint32_t PageCount; +} BOOT_FILE_LIST; + +// Describes one fixed-base PE image materialized by the bootloader. +typedef struct BOOT_IMAGE { + uintptr_t VirtualBase; + uintptr_t PhysicalBase; + uint32_t ImageSize; + uint32_t PageCount; + uint32_t EntryRva; + uint32_t Reserved; +} BOOT_IMAGE; typedef struct BOOT_INFO { - uint64_t Magic; + uint64_t Magic; + uint32_t Size; + uint32_t Version; - // Physical memory. - MEMORY_MAP MemoryMap; + // Physical memory translated from the final firmware memory map. + MEMORY_MAP MemoryMap; // GOP framebuffer. - uintptr_t FramebufferBase; - uint32_t FramebufferWidth; - uint32_t FramebufferHeight; - uint32_t FramebufferStride; // pixels per row + uintptr_t FramebufferBase; + uint32_t FramebufferWidth; + uint32_t FramebufferHeight; + uint32_t FramebufferStride; + uint32_t Reserved0; - // Loaded modules. - BOOT_MODULE Modules[MAX_BOOT_MODULES]; - uint32_t ModuleCount; - uint32_t _pad1; + // Fixed core images and raw boot inputs. + BOOT_IMAGE KernelImage; + BOOT_IMAGE HalImage; + BOOT_BLOB BootIni; + BOOT_FILE_LIST BootDrivers; - char CommandLine[64]; - - // ACPI RSDP physical address (for APIC, HPET). - uintptr_t RsdpPhysical; + // ACPI RSDP physical address. + uintptr_t RsdpPhysical; // Direct physical window base - always 0xFFFF880000000000. - uintptr_t PhysicalMapBase; + uintptr_t PhysicalMapBase; // Reserved low-memory page for the AP startup trampoline, or zero. - uintptr_t ApTrampolinePhys; - - // From boot.ini. - uint32_t Debug; + uintptr_t ApTrampolinePhys; // Firmware wall-clock snapshot taken before ExitBootServices. - BOOT_TIME BootTime; + BOOT_TIME BootTime; } BOOT_INFO; diff --git a/code/public/ntlite/ddi/io_driver.h b/code/public/ntlite/ddi/io_driver.h index 6d7ccf4..175d783 100644 --- a/code/public/ntlite/ddi/io_driver.h +++ b/code/public/ntlite/ddi/io_driver.h @@ -203,6 +203,27 @@ __declspec(dllexport) KSTATUS IoRegisterDiskChangeCallback(IO_DISK_CHANGE_CALLBA __declspec(dllexport) KSTATUS IoEnumerateDiskDevices(IO_DISK_ARRIVAL_CALLBACK callback, void* context); +// Dynamic partition discovery above registered block devices. Filesystem providers receive only immutable extents. +typedef enum IO_PARTITION_CHANGE { + IoPartitionArrived = 0, + IoPartitionSurpriseRemoved, + IoPartitionRemoved, +} IO_PARTITION_CHANGE; + +typedef struct IO_PARTITION_INFO { + uint8_t DiskIndex; + uint64_t StartLba; + uint64_t SectorCount; +} IO_PARTITION_INFO; + +typedef void (*IO_PARTITION_CHANGE_CALLBACK)(const IO_PARTITION_INFO* partition, + IO_PARTITION_CHANGE change, + void* context); + +// Registers one partition-change consumer and replays every currently discovered partition before returning. +__declspec(dllexport) KSTATUS IoRegisterPartitionChangeCallback(IO_PARTITION_CHANGE_CALLBACK callback, + void* context); + __declspec(dllexport) KSTATUS IoReadDiskSectors(uint8_t driveIndex, uint64_t lba, uint32_t count, diff --git a/code/public/ntlite/ddi/net_abi.h b/code/public/ntlite/ddi/net_abi.h index f6332f1..c162a1f 100644 --- a/code/public/ntlite/ddi/net_abi.h +++ b/code/public/ntlite/ddi/net_abi.h @@ -6,6 +6,7 @@ #include #include +#include #define SYS_AF_INET 2u #define SYS_AF_INET6 10u @@ -48,7 +49,7 @@ typedef struct SYS_SOCKET_ADDRESS_K { } SYS_SOCKET_ADDRESS_K; typedef struct SYS_POLLFD_K { - uint32_t Handle; + SYS_HANDLE Handle; uint32_t Events; uint32_t ReturnedEvents; } SYS_POLLFD_K; diff --git a/code/public/ntlite/ddk/debug.h b/code/public/ntlite/ddk/debug.h index b6863ee..1a87988 100644 --- a/code/public/ntlite/ddk/debug.h +++ b/code/public/ntlite/ddk/debug.h @@ -5,21 +5,21 @@ #include #include -#define KE_DEBUG_CONSOLE 0x00000001u -#define KE_DEBUG_SELFTEST 0x00000002u -#define KE_DEBUG_FILE_LOG 0x00000004u -#define KE_DEBUG_POOL_CANARY 0x00000008u -#define KE_DEBUG_POOL_POISON 0x00000010u -#define KE_DEBUG_POOL_QUARANTINE 0x00000020u -#define KE_DEBUG_POOL_CALLERS 0x00000040u -#define KE_DEBUG_DISPLAY_ASSERTS 0x00000080u -#define KE_DEBUG_DISPLAY_TRACE 0x00000100u -#define KE_DEBUG_INPUT_TRACE 0x00000200u -#define KE_DEBUG_VM_ASSERTS 0x00000400u -#define KE_DEBUG_SYSCALL_ASSERTS 0x00000800u -#define KE_DEBUG_LIST_CHECKS 0x00001000u -#define KE_DEBUG_AUDIO 0x00002000u -#define KE_DEBUG_AUDIO_SELFTEST 0x00004000u +// Kernel boot debug flags. Keep these contiguous and document every bit in boot.ini. +#define KE_DEBUG_CONSOLE 0x00000001u // Mirror debug output to the firmware/debug console. +#define KE_DEBUG_SELFTEST 0x00000002u // Run kernel and SMP boot self-tests. +#define KE_DEBUG_FILE_LOG 0x00000004u // Retain debug output for kernel.log persistence. +#define KE_DEBUG_POOL_CANARY 0x00000008u // Validate pool block canaries. +#define KE_DEBUG_POOL_POISON 0x00000010u // Poison released pool blocks. +#define KE_DEBUG_POOL_QUARANTINE 0x00000020u // Delay reuse of released pool blocks. +#define KE_DEBUG_POOL_CALLERS 0x00000040u // Track pool allocation/free callers. +#define KE_DEBUG_DISPLAY_ASSERTS 0x00000080u // Enable display-path consistency assertions. +#define KE_DEBUG_LIST_CHECKS 0x00000100u // Validate intrusive-list links on mutation. + +#define KE_DEBUG_VALID_MASK (KE_DEBUG_CONSOLE | KE_DEBUG_SELFTEST | KE_DEBUG_FILE_LOG | \ + KE_DEBUG_POOL_CANARY | KE_DEBUG_POOL_POISON | \ + KE_DEBUG_POOL_QUARANTINE | KE_DEBUG_POOL_CALLERS | \ + KE_DEBUG_DISPLAY_ASSERTS | KE_DEBUG_LIST_CHECKS) // Driver-visible debug output service table passed through NTL_DDK. typedef struct NTL_DDK_DEBUG { diff --git a/code/public/ntlite/ddk/file.h b/code/public/ntlite/ddk/file.h index ad27a7c..7285a6c 100644 --- a/code/public/ntlite/ddk/file.h +++ b/code/public/ntlite/ddk/file.h @@ -1,4 +1,4 @@ -// Public nt-lite DDK filesystem declarations. +// Public nt-lite DDK filesystem and mounted-volume declarations. #pragma once @@ -43,30 +43,46 @@ typedef struct IO_FILE_IDENTITY { uint64_t Size; } IO_FILE_IDENTITY; -typedef struct IO_FILESYSTEM { - KSTATUS (*Open)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx); - KSTATUS (*Read)(struct IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead); - KSTATUS (*Write)(struct IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten); - KSTATUS (*ReadAsync)(struct IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext); - KSTATUS (*WriteAsync)(struct IO_FILESYSTEM* fs, void* fileCtx, uint64_t offset, const void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext); - KSTATUS (*Create)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx); - KSTATUS (*Delete)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path); - KSTATUS (*Rename)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath); - KSTATUS (*CreateDir)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path); - KSTATUS (*RemoveDir)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path); - KSTATUS (*Truncate)(struct IO_FILESYSTEM* fs, void* fileCtx, uint64_t newSize, uint32_t fileFlags); - uint64_t (*GetSize)(struct IO_FILESYSTEM* fs, void* fileCtx); - KSTATUS (*QueryInfo)(struct IO_FILESYSTEM* fs, void* fileCtx, SYS_FILE_STAT* out); - KSTATUS (*Stat)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path, SYS_FILE_STAT* out); - KSTATUS (*QueryIdentity)(struct IO_FILESYSTEM* fs, void* fileCtx, IO_FILE_IDENTITY* out); - KSTATUS (*Flush)(struct IO_FILESYSTEM* fs, void* fileCtx); - void (*Close)(struct IO_FILESYSTEM* fs, void* fileCtx); - void (*QueryStats)(struct IO_FILESYSTEM* fs, IO_FILESYSTEM_STATS* out); - void* DriverCtx; - KSTATUS (*OpenDir)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW dirPath, void** dirCtx); - KSTATUS (*ReadDir)(struct IO_FILESYSTEM* fs, void* dirCtx, KDIR_ENTRY* out); - void (*CloseDir)(struct IO_FILESYSTEM* fs, void* dirCtx); -} IO_FILESYSTEM; +typedef enum IO_VOLUME_REMOVAL_REASON { + IoVolumeUnmounted = 1, + IoVolumeBackingRemoved, + IoVolumeProviderDead, +} IO_VOLUME_REMOVAL_REASON; + +// Kernel filesystem providers implement this backend. IO_VOLUME owns a copy of +// the dispatch and keeps Context alive until all file and directory references drain. +typedef struct IO_VOLUME_PROVIDER { + KSTATUS (*Open)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path, void** fileCtx); + KSTATUS (*Read)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx, uint64_t offset, void* buf, size_t size, size_t* bytesRead); + KSTATUS (*Write)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten); + KSTATUS (*ReadAsync)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx, uint64_t offset, void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext); + KSTATUS (*WriteAsync)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx, uint64_t offset, const void* buf, size_t size, IO_FS_ASYNC_COMPLETE complete, void* completeContext); + KSTATUS (*Create)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path, void** fileCtx); + KSTATUS (*Delete)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path); + KSTATUS (*Rename)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath); + KSTATUS (*CreateDir)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path); + KSTATUS (*RemoveDir)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path); + KSTATUS (*Truncate)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx, uint64_t newSize, uint32_t fileFlags); + uint64_t (*GetSize)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx); + KSTATUS (*QueryInfo)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx, SYS_FILE_STAT* out); + KSTATUS (*Stat)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW path, SYS_FILE_STAT* out); + KSTATUS (*QueryIdentity)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx, IO_FILE_IDENTITY* out); + KSTATUS (*Flush)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx); + void (*Close)(struct IO_VOLUME_PROVIDER* provider, void* fileCtx); + void (*QueryStats)(struct IO_VOLUME_PROVIDER* provider, IO_FILESYSTEM_STATS* out); + KSTATUS (*OpenDir)(struct IO_VOLUME_PROVIDER* provider, SYS_STRING_VIEW dirPath, void** dirCtx); + KSTATUS (*ReadDir)(struct IO_VOLUME_PROVIDER* provider, void* dirCtx, KDIR_ENTRY* out); + void (*CloseDir)(struct IO_VOLUME_PROVIDER* provider, void* dirCtx); + + // Called once when the mounted volume becomes unusable. It must not free Context. + void (*VolumeDead)(struct IO_VOLUME_PROVIDER* provider, IO_VOLUME_REMOVAL_REASON reason); + + // Called once after the final IO_VOLUME/file/directory reference drains. + void (*Destroy)(struct IO_VOLUME_PROVIDER* provider); + void* Context; +} IO_VOLUME_PROVIDER; + +typedef struct IO_VOLUME IO_VOLUME; struct EVENT; struct KAPC; @@ -79,7 +95,7 @@ typedef struct IO_ASYNC_FILE_REQUEST_OBJECT IO_ASYNC_FILE_REQUEST_OBJECT; typedef struct FILE_OBJECT { OBJECT Header; - IO_FILESYSTEM* Fs; + IO_VOLUME* Volume; void* FileCtx; SPINLOCK Lock; uint64_t Size; @@ -89,41 +105,58 @@ typedef struct FILE_OBJECT { typedef struct DIR_OBJECT { OBJECT Header; - IO_FILESYSTEM* Fs; + IO_VOLUME* Volume; void* DirCtx; + char* Path; + size_t PathLength; } DIR_OBJECT; -typedef struct IO_VOLUME_PARAMS { - uint8_t DiskIndex; - uint64_t PartitionLba; - uint64_t PartitionSize; -} IO_VOLUME_PARAMS; +typedef enum IO_VOLUME_SOURCE_TYPE { + IoVolumeSourcePartition = 1, + IoVolumeSourceBlockDevice, +} IO_VOLUME_SOURCE_TYPE; + +typedef struct IO_VOLUME_SOURCE { + uint32_t Type; + uint8_t DiskIndex; + uint8_t Reserved[3]; + uint64_t StartLba; + uint64_t SectorCount; +} IO_VOLUME_SOURCE; + +// Creates a mounted, refcounted volume and returns one caller reference. +__declspec(dllexport) KSTATUS IoCreateVolume(const IO_VOLUME_PROVIDER* provider, + const IO_VOLUME_SOURCE* source, + IO_VOLUME** volumeOut); + +// Marks a volume dead, removes it from the mounted-volume registry, and rejects future provider I/O. +__declspec(dllexport) KSTATUS IoRemoveVolume(IO_VOLUME* volume, IO_VOLUME_REMOVAL_REASON reason); + +// Releases a caller-owned volume reference returned by IoCreateVolume. +__declspec(dllexport) void IoReleaseVolume(IO_VOLUME* volume); + +// Returns the monotonic runtime instance identifier assigned to a mounted volume. +__declspec(dllexport) uint32_t IoGetVolumeInstanceId(const IO_VOLUME* volume); + +// Returns the immutable block source associated with a mounted volume. +__declspec(dllexport) KSTATUS IoGetVolumeSource(const IO_VOLUME* volume, IO_VOLUME_SOURCE* sourceOut); -__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t driveIndex, IO_FILESYSTEM* fs, const IO_VOLUME_PARAMS* vol); -__declspec(dllexport) KSTATUS IoUnregisterFilesystem(uint8_t driveIndex); -__declspec(dllexport) KSTATUS IoUnregisterFilesystemsForDisk(uint8_t diskIndex); -__declspec(dllexport) bool IoFilesystemPresent(uint8_t driveIndex); __declspec(dllexport) void IoQueryFilesystemStats(IO_FILESYSTEM_STATS* out); -__declspec(dllexport) KSTATUS IoOpenFile(SYS_STRING_VIEW path, FILE_OBJECT** out); +// Capability-relative native opens. Paths are canonical relative paths: '/' separators only, +// no leading/trailing separator, empty components, '.', '..', backslash, or embedded NUL. +__declspec(dllexport) KSTATUS IoOpenFileAtVolume(IO_VOLUME* volume, SYS_STRING_VIEW path, FILE_OBJECT** out); +__declspec(dllexport) KSTATUS IoCreateFileAtVolume(IO_VOLUME* volume, SYS_STRING_VIEW path, FILE_OBJECT** out); +__declspec(dllexport) KSTATUS IoOpenDirAtVolume(IO_VOLUME* volume, SYS_STRING_VIEW path, DIR_OBJECT** out); __declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead); __declspec(dllexport) KSTATUS IoReadFileAt(FILE_OBJECT* file, uint64_t offset, void* buf, size_t size, size_t* bytesRead); __declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf, size_t size, size_t* bytesWritten); __declspec(dllexport) KSTATUS IoWriteFileAt(FILE_OBJECT* file, uint64_t offset, const void* buf, size_t size, size_t* bytesWritten); __declspec(dllexport) KSTATUS IoFlushFile(FILE_OBJECT* file); -__declspec(dllexport) KSTATUS IoCreateFile(SYS_STRING_VIEW path, FILE_OBJECT** out); -__declspec(dllexport) KSTATUS IoDeleteFile(SYS_STRING_VIEW path); -__declspec(dllexport) KSTATUS IoRenameFile(SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath); -__declspec(dllexport) KSTATUS IoCreateDir(SYS_STRING_VIEW path); -__declspec(dllexport) KSTATUS IoRemoveDir(SYS_STRING_VIEW path); __declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize); __declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset); __declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file); __declspec(dllexport) KSTATUS IoGetFileStat(FILE_OBJECT* file, SYS_FILE_STAT* out); -__declspec(dllexport) KSTATUS IoStatFile(SYS_STRING_VIEW path, SYS_FILE_STAT* out); __declspec(dllexport) KSTATUS IoQueryFileIdentity(FILE_OBJECT* file, IO_FILE_IDENTITY* out); __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file); -__declspec(dllexport) KSTATUS IoOpenDir(SYS_STRING_VIEW dirPath, DIR_OBJECT** out); __declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut); __declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir); -__declspec(dllexport) KSTATUS IoSetCwd(SYS_STRING_VIEW path); -__declspec(dllexport) KSTATUS IoRealPath(SYS_STRING_VIEW path, char* buffer, size_t bufferSize, size_t* requiredSizeOut); diff --git a/code/public/ntlite/ddk/interrupt.h b/code/public/ntlite/ddk/interrupt.h index 96ca5ed..cc3c581 100644 --- a/code/public/ntlite/ddk/interrupt.h +++ b/code/public/ntlite/ddk/interrupt.h @@ -5,9 +5,16 @@ #include #include +typedef struct NTL_DDK_MSI_MESSAGE { + uint64_t Address; + uint32_t Data; + uint32_t Reserved; +} NTL_DDK_MSI_MESSAGE; + // Driver-visible interrupt services. typedef struct NTL_DDK_INTERRUPT { KSTATUS (*AllocateMsiVector)(uint8_t* outIrq); + KSTATUS (*ComposeMsiMessage)(uint8_t irq, NTL_DDK_MSI_MESSAGE* messageOut); } NTL_DDK_INTERRUPT; // Allocates one kernel IRQ slot for MSI delivery. diff --git a/code/public/ntlite/ddk/irql.h b/code/public/ntlite/ddk/irql.h index b07fdb5..17386d8 100644 --- a/code/public/ntlite/ddk/irql.h +++ b/code/public/ntlite/ddk/irql.h @@ -13,6 +13,12 @@ typedef uint8_t KIRQL; #define DIRQL_BASE ((KIRQL)16) #define HIGH_LEVEL ((KIRQL)31) +// Masks interrupts and returns an opaque state restored by KeRestoreInterrupts. +__declspec(dllexport) uintptr_t KeDisableInterrupts(void); + +// Restores the interrupt state returned by KeDisableInterrupts. +__declspec(dllexport) void KeRestoreInterrupts(uintptr_t flags); + // Returns the current processor's software IRQL. __declspec(dllexport) KIRQL KeGetCurrentIrql(void); diff --git a/code/public/ntlite/ddk/list.h b/code/public/ntlite/ddk/list.h index 0d682c6..39a0fbb 100644 --- a/code/public/ntlite/ddk/list.h +++ b/code/public/ntlite/ddk/list.h @@ -15,7 +15,7 @@ typedef struct LIST_ENTRY { // Recover the containing struct pointer from an embedded list node. #define CONTAINING_RECORD(ptr, type, member) \ - ((type *)((char *)(ptr) - offsetof(type, member))) + ((type*)((char*)(ptr) - offsetof(type, member))) // Inserts a node at the tail of a list. static inline void ListInsertTail(LIST_ENTRY* head, LIST_ENTRY* node) { diff --git a/code/public/ntlite/ddk/object.h b/code/public/ntlite/ddk/object.h index 17a61b9..c6d17dd 100644 --- a/code/public/ntlite/ddk/object.h +++ b/code/public/ntlite/ddk/object.h @@ -27,10 +27,13 @@ typedef enum OBJECT_TYPE { ObjectTypeInputQueue = 17, ObjectTypeImageSection = 18, ObjectTypeTimer = 19, - ObjectTypeModule = 20, - ObjectTypeDisplayClock = 21, - ObjectTypeAudioEndpoint = 22, - ObjectTypeAudioStream = 23, + ObjectTypeDisplayClock = 20, + ObjectTypeAudioEndpoint = 21, + ObjectTypeAudioStream = 22, + ObjectTypeSecurityContext = 23, + ObjectTypePort = 24, + ObjectTypeVolume = 25, + ObjectTypeVolumeManager = 26, } OBJECT_TYPE; typedef enum OBJECT_LIFETIME_STATE { diff --git a/code/public/ntlite/ddk/platform.h b/code/public/ntlite/ddk/platform.h index 5854b22..c0ac030 100644 --- a/code/public/ntlite/ddk/platform.h +++ b/code/public/ntlite/ddk/platform.h @@ -18,6 +18,4 @@ typedef struct NTL_DDK_PLATFORM { uint8_t function, uint16_t offset, uint32_t value); - uint32_t (*GetBspLapicId)(void); - uint8_t (*IsX2ApicEnabled)(void); } NTL_DDK_PLATFORM; diff --git a/code/public/ntlite/ddk/settings.h b/code/public/ntlite/ddk/settings.h index 79d7900..4b2939a 100644 --- a/code/public/ntlite/ddk/settings.h +++ b/code/public/ntlite/ddk/settings.h @@ -5,7 +5,7 @@ #include #include -#define KE_SETTINGS_PATH "system\\settings.ini" +#define KE_SETTINGS_PATH "system/settings.ini" // Reads one string setting from the system settings file. __declspec(dllexport) KSTATUS KeGetSettingString(const char* section, diff --git a/code/public/ntlite/ddk/sync.h b/code/public/ntlite/ddk/sync.h index 56c548d..c1d6a6c 100644 --- a/code/public/ntlite/ddk/sync.h +++ b/code/public/ntlite/ddk/sync.h @@ -4,7 +4,6 @@ #include #include -#include #include // Legacy spinlock storage used by current driver-visible objects. @@ -38,7 +37,7 @@ static __forceinline KIRQL KiSpinlockCookieIrql(uintptr_t cookie) { // Acquires a spinlock and raises to at least DISPATCH_LEVEL. static __forceinline uintptr_t KeAcquireSpinlock(SPINLOCK* sl) { - uintptr_t flags = HalDisableInterrupts(); + uintptr_t flags = KeDisableInterrupts(); KIRQL oldIrql = KeGetCurrentIrql(); if (oldIrql < DISPATCH_LEVEL) @@ -57,12 +56,12 @@ static __forceinline void KeReleaseSpinlock(SPINLOCK* sl, uintptr_t cookie) { _InterlockedExchange(&sl->Locked, 0); KeLowerIrql(oldIrql); - HalRestoreInterrupts(flags); + KeRestoreInterrupts(flags); } // Attempts to acquire a spinlock without blocking. static __forceinline int KeTryAcquireSpinlock(SPINLOCK* sl, uintptr_t* outCookie) { - uintptr_t flags = HalDisableInterrupts(); + uintptr_t flags = KeDisableInterrupts(); KIRQL oldIrql = KeGetCurrentIrql(); if (oldIrql < DISPATCH_LEVEL) @@ -74,7 +73,7 @@ static __forceinline int KeTryAcquireSpinlock(SPINLOCK* sl, uintptr_t* outCookie } KeLowerIrql(oldIrql); - HalRestoreInterrupts(flags); + KeRestoreInterrupts(flags); return 0; } diff --git a/code/public/ntlite/handle.h b/code/public/ntlite/handle.h new file mode 100644 index 0000000..c2f69e8 --- /dev/null +++ b/code/public/ntlite/handle.h @@ -0,0 +1,100 @@ +// Public nt-lite capability handle declarations. + +#pragma once + +#include + +// Handle values are process-local monotonic identities. Zero is invalid, and +// an issued value is never reused during the lifetime of its owning process. +typedef uint32_t SYS_HANDLE; +typedef uint32_t SYS_RIGHTS; + +#define SYS_HANDLE_INVALID ((SYS_HANDLE)0u) + +// Capability-management rights shared by every handle type. Rights may only attenuate during delegation. +#define SYS_RIGHT_DUPLICATE 0x00000001u +#define SYS_RIGHT_DERIVE 0x00000002u +#define SYS_RIGHT_REVOKE 0x00000004u +#define SYS_RIGHT_TRANSFER 0x00000008u +#define SYS_RIGHT_QUERY 0x00000010u +#define SYS_RIGHT_WAIT 0x00000020u +#define SYS_RIGHT_COMMON_MASK 0x0000003Fu + +// Process-specific rights. +#define SYS_PROCESS_RIGHT_TERMINATE 0x00000100u +#define SYS_PROCESS_RIGHT_INSERT_HANDLE 0x00000200u +#define SYS_PROCESS_RIGHT_BUILD 0x00000400u + +// Thread-specific rights. +#define SYS_THREAD_RIGHT_RESUME 0x00000100u + +// Mutex-specific rights. +#define SYS_MUTEX_RIGHT_RELEASE 0x00000100u + +// Semaphore-specific rights. +#define SYS_SEMAPHORE_RIGHT_SIGNAL 0x00000100u + +// Event-specific rights. +#define SYS_EVENT_RIGHT_SIGNAL 0x00000100u + +// Queue-specific rights. +#define SYS_QUEUE_RIGHT_SEND 0x00000100u +#define SYS_QUEUE_RIGHT_RECEIVE 0x00000200u +#define SYS_QUEUE_RIGHT_PUBLISH 0x00000400u + +// File-specific rights. +#define SYS_FILE_RIGHT_READ_DATA 0x00000100u +#define SYS_FILE_RIGHT_WRITE_DATA 0x00000200u +#define SYS_FILE_RIGHT_LOCK 0x00000400u +#define SYS_FILE_RIGHT_MAP_READ 0x00000800u +#define SYS_FILE_RIGHT_MAP_WRITE 0x00001000u + +// Mounted-volume rights. +#define SYS_VOLUME_RIGHT_OPEN 0x00000100u +#define SYS_VOLUME_RIGHT_MODIFY 0x00000200u + +// Volume-manager rights. The manager capability is delegated only to the userspace storage control plane. +#define SYS_VOLUME_MANAGER_RIGHT_ENUMERATE 0x00000100u +#define SYS_VOLUME_MANAGER_RIGHT_OPEN 0x00000200u +#define SYS_VOLUME_MANAGER_RIGHT_WAIT 0x00000400u + +// Directory-specific rights. +#define SYS_DIR_RIGHT_ENUMERATE 0x00000100u +#define SYS_DIR_RIGHT_OPEN 0x00000200u +#define SYS_DIR_RIGHT_MODIFY 0x00000400u + +// Asynchronous I/O request rights. +#define SYS_ASYNC_IO_RIGHT_CANCEL 0x00000100u + +// Socket-specific rights. +#define SYS_SOCKET_RIGHT_BIND 0x00000100u +#define SYS_SOCKET_RIGHT_CONNECT 0x00000200u +#define SYS_SOCKET_RIGHT_LISTEN 0x00000400u +#define SYS_SOCKET_RIGHT_ACCEPT 0x00000800u +#define SYS_SOCKET_RIGHT_SEND 0x00001000u +#define SYS_SOCKET_RIGHT_RECEIVE 0x00002000u +#define SYS_SOCKET_RIGHT_SHUTDOWN 0x00004000u +#define SYS_SOCKET_RIGHT_POLL 0x00008000u +#define SYS_SOCKET_RIGHT_SET_OPTION 0x00010000u + +// File-mapping rights. +#define SYS_FILE_MAPPING_RIGHT_MAP 0x00000100u + +// Surface rights. +#define SYS_SURFACE_RIGHT_MAP 0x00000100u +#define SYS_SURFACE_RIGHT_PRESENT 0x00000200u + +// Input-queue rights. +#define SYS_INPUT_QUEUE_RIGHT_READ 0x00000100u + +// Timer rights. +#define SYS_TIMER_RIGHT_SET 0x00000100u + +// Audio-stream rights. +#define SYS_AUDIO_STREAM_RIGHT_WRITE 0x00000100u +#define SYS_AUDIO_STREAM_RIGHT_CONTROL 0x00000200u + +// Security-context-specific rights are declared in . + +// Rights normally retained by a newly created root capability. +#define SYS_RIGHT_MANAGE (SYS_RIGHT_DUPLICATE | SYS_RIGHT_DERIVE | SYS_RIGHT_REVOKE | SYS_RIGHT_TRANSFER) diff --git a/code/public/ntlite/module_loader.h b/code/public/ntlite/module_loader.h new file mode 100644 index 0000000..bbf70cf --- /dev/null +++ b/code/public/ntlite/module_loader.h @@ -0,0 +1,76 @@ +// Native userspace module-loader declarations. + +#pragma once + +#include +#include +#include +#include +#include + +#define NTL_MODULE_NAME_MAX 64u +#define NTL_MODULE_LOAD_FLAG_NONE 0x00000000u + +// Opaque process-local userspace module identity. It is never a kernel capability. +typedef uintptr_t NTL_MODULE_HANDLE; +#define NTL_MODULE_HANDLE_INVALID ((NTL_MODULE_HANDLE)0u) + +// Maps one PE import name to the canonical module filename used by policy. +// canonicalNameOut may refer to requestedName storage or to caller-owned static storage. +typedef KSTATUS (*NTL_MODULE_NAME_RESOLVER)(void* context, + SYS_STRING_VIEW requestedName, + SYS_STRING_VIEW* canonicalNameOut); + +typedef struct NTL_MODULE_INFO { + uintptr_t Base; + size_t Size; + uintptr_t EntryPoint; + uint32_t PublicRefCount; + uint32_t ImportPinned; + char Name[NTL_MODULE_NAME_MAX]; +} NTL_MODULE_INFO; + +typedef struct NTL_LOAD_MODULE_AT_REQUEST { + uint32_t Size; + uint32_t Flags; + SYS_HANDLE RootHandle; + SYS_STRING_VIEW Path; + SYS_HANDLE ModuleRootHandle; + SYS_STRING_VIEW ModuleDirectory; + NTL_MODULE_NAME_RESOLVER ResolveModuleName; + void* ResolveContext; + NTL_MODULE_HANDLE* ModuleHandleOut; + NTL_MODULE_INFO* InfoOut; +} NTL_LOAD_MODULE_AT_REQUEST; + +#if defined(NTL_SYSCALL_STATIC) +#define NTL_MODULE_LOADER_API +#elif defined(NTL_SYSCALL_BUILD) +#define NTL_MODULE_LOADER_API __declspec(dllexport) +#else +#define NTL_MODULE_LOADER_API +#endif + +// Identity module-name policy for native images whose import names are already canonical. +NTL_MODULE_LOADER_API KSTATUS NtlResolveModuleNameIdentity(void* context, + SYS_STRING_VIEW requestedName, + SYS_STRING_VIEW* canonicalNameOut); + +// Resolves the installed Win32/POSIX compatibility import aliases for generic userspace launchers. +NTL_MODULE_LOADER_API KSTATUS NtlResolveSystemModuleName(void* context, + SYS_STRING_VIEW requestedName, + SYS_STRING_VIEW* canonicalNameOut); + +// Loads one DLL-like image into the current process. Dependency images are resolved +// beneath ModuleRootHandle/ModuleDirectory and remain import-pinned for process lifetime. +NTL_MODULE_LOADER_API KSTATUS NtlLoadModuleAt(const NTL_LOAD_MODULE_AT_REQUEST* request); +NTL_MODULE_LOADER_API KSTATUS NtlUnloadModule(NTL_MODULE_HANDLE module); +NTL_MODULE_LOADER_API KSTATUS NtlGetModuleInfo(NTL_MODULE_HANDLE module, NTL_MODULE_INFO* infoOut); +NTL_MODULE_LOADER_API KSTATUS NtlGetModuleExport(NTL_MODULE_HANDLE module, + SYS_STRING_VIEW name, + uintptr_t* addressOut); +NTL_MODULE_LOADER_API KSTATUS NtlGetModuleList(NTL_MODULE_INFO* modulesOut, + size_t maxCount, + size_t* countOut); + +#undef NTL_MODULE_LOADER_API diff --git a/code/public/ntlite/path.h b/code/public/ntlite/path.h new file mode 100644 index 0000000..6d91921 --- /dev/null +++ b/code/public/ntlite/path.h @@ -0,0 +1,49 @@ +// Native userspace path and filesystem base services. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#if defined(NTL_SYSCALL_STATIC) +#define NTL_PATH_API +#elif defined(NTL_SYSCALL_BUILD) +#define NTL_PATH_API __declspec(dllexport) +#else +#define NTL_PATH_API +#endif + +// Native global paths are canonical resource paths such as /SystemRoot/system/init.exe. +// Relative paths are resolved against the process-local userspace CWD. Parent traversal is +// clamped at the CWD's owning RSSRV resource binding. Backslash is never accepted; repeated '/', +// '.', and '..' are normalized before RSSRV sees the path. +NTL_PATH_API KSTATUS NtlRealPath(SYS_STRING_VIEW path, + char* buffer, + size_t bufferSize, + size_t* requiredSizeOut); +NTL_PATH_API KSTATUS NtlSetCurrentDirectory(SYS_STRING_VIEW path); +NTL_PATH_API KSTATUS NtlGetCurrentDirectory(char* buffer, + size_t bufferSize, + size_t* requiredSizeOut); + +NTL_PATH_API KSTATUS NtlOpenFile(SYS_STRING_VIEW path, + uint32_t access, + uint32_t disposition, + uint32_t flags, + SYS_HANDLE* handleOut); +NTL_PATH_API KSTATUS NtlOpenDir(SYS_STRING_VIEW path, + uint32_t access, + uint32_t flags, + SYS_HANDLE* handleOut); +NTL_PATH_API KSTATUS NtlStatPath(SYS_STRING_VIEW path, SYS_FILE_STAT* statOut); +NTL_PATH_API KSTATUS NtlDeleteFile(SYS_STRING_VIEW path); +NTL_PATH_API KSTATUS NtlRenameFile(SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath); +NTL_PATH_API KSTATUS NtlCreateDirectory(SYS_STRING_VIEW path); +NTL_PATH_API KSTATUS NtlRemoveDirectory(SYS_STRING_VIEW path); + +#undef NTL_PATH_API diff --git a/code/public/ntlite/pe_loader.h b/code/public/ntlite/pe_loader.h new file mode 100644 index 0000000..331bf35 --- /dev/null +++ b/code/public/ntlite/pe_loader.h @@ -0,0 +1,56 @@ +// Native userspace PE32+ image-loader declarations. + +#pragma once + +#include +#include +#include +#include +#include + +#define NTL_PE_LOAD_FLAG_NONE 0x00000000u + +typedef KSTATUS (*NTL_PE_IMPORT_RESOLVER)(void* context, + SYS_STRING_VIEW moduleName, + SYS_STRING_VIEW symbolName, + uintptr_t* addressOut); + +typedef struct NTL_PE_IMAGE { + uintptr_t Base; + size_t Size; + uintptr_t EntryPoint; + uint64_t PreferredBase; +} NTL_PE_IMAGE; + +typedef KSTATUS (*NTL_PE_PREPARED_CALLBACK)(void* context, const NTL_PE_IMAGE* image); + +typedef struct NTL_PE_LOAD_REQUEST { + uint32_t Size; + SYS_HANDLE ProcessHandle; + SYS_HANDLE FileHandle; + uint32_t Flags; + NTL_PE_IMPORT_RESOLVER ResolveImport; + void* ResolveContext; + NTL_PE_PREPARED_CALLBACK Prepared; + void* PreparedContext; + NTL_PE_IMAGE* ImageOut; +} NTL_PE_LOAD_REQUEST; + +#if defined(NTL_SYSCALL_STATIC) +#define NTL_SYSCALL_API +#elif defined(NTL_SYSCALL_BUILD) +#define NTL_SYSCALL_API __declspec(dllexport) +#else +#define NTL_SYSCALL_API +#endif + +// Reads, validates, materializes, relocates, binds, and protects one AMD64 PE32+ image. +// ProcessHandle may be SYS_HANDLE_INVALID to target the current process. Dependency policy +// remains entirely in ResolveImport. Prepared runs after relocation and before import binding. +NTL_SYSCALL_API KSTATUS NtlLoadPeImage(const NTL_PE_LOAD_REQUEST* request); + +// Resolves one named export directly from an already-materialized PE image. Forwarders and +// ordinal lookups are intentionally unsupported. +NTL_SYSCALL_API KSTATUS NtlGetPeExport(uintptr_t imageBase, SYS_STRING_VIEW name, uintptr_t* addressOut); + +#undef NTL_SYSCALL_API diff --git a/code/public/ntlite/port.h b/code/public/ntlite/port.h new file mode 100644 index 0000000..8ff3261 --- /dev/null +++ b/code/public/ntlite/port.h @@ -0,0 +1,150 @@ +// Public nt-lite Port IPC declarations. Payload pointers are syscall-local only; queued messages never retain them. + +#pragma once + +#include +#include +#include + +// Larger transfers should pass a mapping/file-mapping capability instead of enlarging inline messages. +#define SYS_PORT_MAX_INLINE_BYTES 4096u +#define SYS_PORT_MAX_TRANSFER_HANDLES 8u +#define SYS_PORT_DEFAULT_BACKLOG 16u +#define SYS_PORT_MAX_BACKLOG 64u +#define SYS_PORT_QUEUE_MESSAGE_LIMIT 64u +#define SYS_PORT_QUEUE_BYTE_LIMIT (64u * 1024u) +#define SYS_PORT_QUEUE_HANDLE_LIMIT 128u +#define SYS_PORT_TIMEOUT_INFINITE UINT64_MAX + +#define SYS_PORT_FLAG_NONE 0u + +#define SYS_PORT_MESSAGE_NOTIFICATION 1u +#define SYS_PORT_MESSAGE_REQUEST 2u +#define SYS_PORT_MESSAGE_REPLY 3u + +// With no flag, DesiredRights creates an attenuated alias of the source grant. +// SAME_RIGHTS requires DesiredRights=0. DERIVE creates a revocable child grant. +#define SYS_PORT_TRANSFER_FLAG_NONE 0u +#define SYS_PORT_TRANSFER_FLAG_SAME_RIGHTS 0x00000001u +#define SYS_PORT_TRANSFER_FLAG_DERIVE 0x00000002u + +#define SYS_PORT_ACCEPT_FLAG_NONE 0u +#define SYS_PORT_CONNECT_FLAG_NONE 0u + +// Port-specific capability rights. +#define SYS_PORT_RIGHT_CONNECT 0x00000100u +#define SYS_PORT_RIGHT_ACCEPT 0x00000200u +#define SYS_PORT_RIGHT_SEND 0x00000400u +#define SYS_PORT_RIGHT_RECEIVE 0x00000800u +#define SYS_PORT_RIGHT_CALL 0x00001000u +#define SYS_PORT_RIGHT_REPLY 0x00002000u +#define SYS_PORT_RIGHT_DISCONNECT 0x00004000u + +// Describes one capability that accompanies a Port message. +typedef struct SYS_PORT_HANDLE_TRANSFER { + SYS_HANDLE Handle; + SYS_RIGHTS DesiredRights; + uint32_t Flags; +} SYS_PORT_HANDLE_TRANSFER; + +// Describes one delivered Port message. Sender identity is always kernel-stamped. +typedef struct SYS_PORT_MESSAGE_INFO { + uint32_t Size; + uint32_t Flags; + uint32_t MessageId; + uint32_t Type; + uint32_t PayloadLength; + uint32_t HandleCount; + uint32_t SenderProcessId; + uint32_t SenderThreadId; + uint32_t SenderSecurityId; +} SYS_PORT_MESSAGE_INFO; + +// Describes the client that created one accepted Port connection. +typedef struct SYS_PORT_CONNECTION_INFO { + uint32_t Size; + uint32_t Flags; + uint32_t ClientProcessId; + uint32_t ClientThreadId; + uint32_t ClientSecurityId; +} SYS_PORT_CONNECTION_INFO; + +// Creates one connection-listener Port. +typedef struct SYS_CREATE_PORT_REQUEST { + uint32_t Size; + uint32_t Flags; + uint32_t Backlog; + SYS_HANDLE* HandleOut; +} SYS_CREATE_PORT_REQUEST; + +// Connects to one listener capability directly. +typedef struct SYS_CONNECT_PORT_REQUEST { + uint32_t Size; + SYS_HANDLE ListenerHandle; + uint32_t Flags; + SYS_HANDLE* EndpointHandleOut; +} SYS_CONNECT_PORT_REQUEST; + +// Accepts one queued connection from a listener. +typedef struct SYS_ACCEPT_PORT_REQUEST { + uint32_t Size; + SYS_HANDLE ListenerHandle; + uint32_t Flags; + uint64_t TimeoutMilliseconds; + SYS_HANDLE* EndpointHandleOut; + SYS_PORT_CONNECTION_INFO* ConnectionInfoOut; +} SYS_ACCEPT_PORT_REQUEST; + +// Sends one one-way notification to the peer endpoint. +typedef struct SYS_SEND_PORT_REQUEST { + uint32_t Size; + SYS_HANDLE EndpointHandle; + uint32_t Flags; + const void* Payload; + uint32_t PayloadLength; + const SYS_PORT_HANDLE_TRANSFER* Handles; + uint32_t HandleCount; + uint64_t TimeoutMilliseconds; +} SYS_SEND_PORT_REQUEST; + +// Receives one request or notification from the endpoint. +typedef struct SYS_RECEIVE_PORT_REQUEST { + uint32_t Size; + SYS_HANDLE EndpointHandle; + uint32_t Flags; + void* Payload; + uint32_t PayloadCapacity; + SYS_HANDLE* HandlesOut; + uint32_t HandleCapacity; + SYS_PORT_MESSAGE_INFO* InfoOut; + uint64_t TimeoutMilliseconds; +} SYS_RECEIVE_PORT_REQUEST; + +// Sends a synchronous request and receives its reply. Timeout zero returns WOULD_BLOCK without sending. +typedef struct SYS_CALL_PORT_REQUEST { + uint32_t Size; + SYS_HANDLE EndpointHandle; + uint32_t Flags; + const void* RequestPayload; + uint32_t RequestLength; + const SYS_PORT_HANDLE_TRANSFER* RequestHandles; + uint32_t RequestHandleCount; + void* ReplyPayload; + uint32_t ReplyCapacity; + SYS_HANDLE* ReplyHandlesOut; + uint32_t ReplyHandleCapacity; + SYS_PORT_MESSAGE_INFO* ReplyInfoOut; + uint64_t TimeoutMilliseconds; +} SYS_CALL_PORT_REQUEST; + +// Replies to one synchronous request identified by MessageId. +typedef struct SYS_REPLY_PORT_REQUEST { + uint32_t Size; + SYS_HANDLE EndpointHandle; + uint32_t MessageId; + uint32_t Flags; + const void* Payload; + uint32_t PayloadLength; + const SYS_PORT_HANDLE_TRANSFER* Handles; + uint32_t HandleCount; +} SYS_REPLY_PORT_REQUEST; diff --git a/code/public/ntlite/process_loader.h b/code/public/ntlite/process_loader.h new file mode 100644 index 0000000..5b8804d --- /dev/null +++ b/code/public/ntlite/process_loader.h @@ -0,0 +1,71 @@ +// Native userspace process-loader declarations. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#define NTL_PROCESS_LOAD_FLAG_NONE 0x00000000u + +// Describes one source capability to duplicate into a constructing child. +typedef struct NTL_PROCESS_STARTUP_CAPABILITY { + uint32_t Tag; + SYS_HANDLE SourceHandle; + SYS_RIGHTS DesiredRights; + uint32_t DuplicateFlags; +} NTL_PROCESS_STARTUP_CAPABILITY; + +// Creates and starts one PE32+ process entirely through the userspace image loader. +typedef struct NTL_CREATE_PROCESS_AT_REQUEST { + uint32_t Size; + uint32_t Flags; + SYS_HANDLE RootHandle; + SYS_STRING_VIEW Path; + SYS_STRING_VIEW CommandLine; + // Empty inherits the caller's userspace CWD; non-empty is normalized as a native resource path. + SYS_STRING_VIEW CurrentDirectory; + SYS_HANDLE SecurityContext; + SYS_HANDLE ModuleRootHandle; + SYS_STRING_VIEW ModuleDirectory; + NTL_MODULE_NAME_RESOLVER ResolveModuleName; + void* ResolveContext; + const NTL_PROCESS_STARTUP_CAPABILITY* StartupCapabilities; + uint32_t StartupCapabilityCount; + SYS_HANDLE* ProcessHandleOut; +} NTL_CREATE_PROCESS_AT_REQUEST; + +// Creates one process beneath the globally published /SystemRoot volume. Path remains a +// canonical volume-relative native path; RSSRV is used only to obtain the volume capability. +typedef struct NTL_CREATE_SYSTEM_PROCESS_REQUEST { + uint32_t Size; + uint32_t Flags; + // Optional pre-opened executable file. When supplied, Path is diagnostic/PEB identity only. + SYS_HANDLE ImageFile; + SYS_STRING_VIEW Path; + SYS_STRING_VIEW CommandLine; + // Empty inherits the caller's userspace CWD; non-empty is normalized as a native resource path. + SYS_STRING_VIEW CurrentDirectory; + SYS_HANDLE SecurityContext; + NTL_MODULE_NAME_RESOLVER ResolveModuleName; + void* ResolveContext; + const NTL_PROCESS_STARTUP_CAPABILITY* StartupCapabilities; + uint32_t StartupCapabilityCount; + SYS_HANDLE* ProcessHandleOut; +} NTL_CREATE_SYSTEM_PROCESS_REQUEST; + +#if defined(NTL_SYSCALL_STATIC) +#define NTL_PROCESS_LOADER_API +#elif defined(NTL_SYSCALL_BUILD) +#define NTL_PROCESS_LOADER_API __declspec(dllexport) +#else +#define NTL_PROCESS_LOADER_API +#endif + +NTL_PROCESS_LOADER_API KSTATUS NtlCreateProcessAt(const NTL_CREATE_PROCESS_AT_REQUEST* request); +NTL_PROCESS_LOADER_API KSTATUS NtlCreateSystemProcess(const NTL_CREATE_SYSTEM_PROCESS_REQUEST* request); + +#undef NTL_PROCESS_LOADER_API diff --git a/code/public/ntlite/resource.h b/code/public/ntlite/resource.h new file mode 100644 index 0000000..339d8f6 --- /dev/null +++ b/code/public/ntlite/resource.h @@ -0,0 +1,96 @@ +// Native resource-service protocol and client helpers. + +#pragma once + +#include +#include +#include +#include + +#define NTL_RESOURCE_PROTOCOL_VERSION 2u +#define NTL_RESOURCE_PATH_MAX 1024u + +#define NTL_RESOURCE_FLAG_NONE 0x00000000u +#define NTL_RESOURCE_RESOLVE_FLAG_MODIFY 0x00000001u + +#define NTL_RESOURCE_OP_LOOKUP 1u +#define NTL_RESOURCE_OP_QUERY 2u +#define NTL_RESOURCE_OP_BIND 3u +#define NTL_RESOURCE_OP_UNBIND 4u +#define NTL_RESOURCE_OP_RESOLVE 5u + +#define NTL_RESOURCE_KIND_INVALID 0u +#define NTL_RESOURCE_KIND_CAPABILITY 1u +#define NTL_RESOURCE_KIND_VOLUME 2u +#define NTL_RESOURCE_KIND_DIRECTORY 3u +#define NTL_RESOURCE_KIND_DEVICE 4u + +// Request header followed immediately by PathLength bytes of canonical native resource path. +typedef struct NTL_RESOURCE_REQUEST { + uint32_t Size; + uint32_t Version; + uint32_t Operation; + uint32_t Flags; + SYS_RIGHTS Rights; + SYS_RIGHTS ExportRights; + uint32_t Kind; + uint32_t PathLength; + uint32_t Reserved; +} NTL_RESOURCE_REQUEST; + +// Every resource request receives one fixed-size reply. Successful lookup additionally transfers one handle. +typedef struct NTL_RESOURCE_REPLY { + uint32_t Size; + uint32_t Version; + uint32_t Operation; + uint32_t Flags; + KSTATUS Status; + uint32_t Kind; + uint32_t ResolvedLength; + uint32_t Reserved; +} NTL_RESOURCE_REPLY; + +#if defined(NTL_SYSCALL_STATIC) +#define NTL_RESOURCE_API +#elif defined(NTL_SYSCALL_BUILD) +#define NTL_RESOURCE_API __declspec(dllexport) +#else +#define NTL_RESOURCE_API +#endif + +// Connects one session to the globally registered resource service. +NTL_RESOURCE_API KSTATUS NtlResourceConnect(SYS_HANDLE* sessionOut); +NTL_RESOURCE_API KSTATUS NtlResourceOpenSystemRoot(SYS_RIGHTS desiredRights, SYS_HANDLE* handleOut); +// Connects one session through an explicitly delegated resource-listener capability. +NTL_RESOURCE_API KSTATUS NtlResourceConnectListener(SYS_HANDLE listenerHandle, SYS_HANDLE* sessionOut); +// Resolves one canonical global resource name to an attenuated capability. +NTL_RESOURCE_API KSTATUS NtlResourceLookup(SYS_HANDLE session, + SYS_STRING_VIEW path, + SYS_RIGHTS desiredRights, + SYS_HANDLE* handleOut); +// Queries resource metadata without transferring the resource capability. +NTL_RESOURCE_API KSTATUS NtlResourceQuery(SYS_HANDLE session, + SYS_STRING_VIEW path, + uint32_t* kindOut); +// Resolves the longest bound resource prefix of one canonical absolute path. +// ResolvedLength receives the byte length of the matched resource name; the remaining +// path, after one optional '/', is relative to the returned volume/directory capability. +NTL_RESOURCE_API KSTATUS NtlResourceResolve(SYS_HANDLE session, + SYS_STRING_VIEW path, + uint32_t flags, + SYS_HANDLE* rootHandleOut, + uint32_t* kindOut, + uint32_t* resolvedLengthOut); +// Binds one capability into an administrative session with an explicit namespace-rights grant and export ceiling. +NTL_RESOURCE_API KSTATUS NtlResourceBind(SYS_HANDLE session, + SYS_STRING_VIEW path, + uint32_t kind, + SYS_HANDLE resourceHandle, + SYS_RIGHTS namespaceRights, + SYS_RIGHTS exportRights); +// Removes one name from an administrative resource session. Existing looked-up capabilities remain valid. +NTL_RESOURCE_API KSTATUS NtlResourceUnbind(SYS_HANDLE session, SYS_STRING_VIEW path); +// Disconnects and closes one resource session endpoint. +NTL_RESOURCE_API KSTATUS NtlResourceClose(SYS_HANDLE session); + +#undef NTL_RESOURCE_API diff --git a/code/public/ntlite/security.h b/code/public/ntlite/security.h new file mode 100644 index 0000000..73ca1a9 --- /dev/null +++ b/code/public/ntlite/security.h @@ -0,0 +1,66 @@ +// Public nt-lite security context and privilege declarations. + +#pragma once + +#include +#include +#include + +typedef uint32_t SYS_PRIVILEGES; +typedef uint32_t SYS_SERVICE_CLAIMS; + +// Ambient privileges are reserved for kernel-mediated authority that is not represented by an object capability yet. +#define SYS_PRIV_PROCESS_OPEN_ANY 0x00000001u +#define SYS_PRIV_PROCESS_ENUMERATE 0x00000002u +#define SYS_PRIV_THREAD_ENUMERATE_ANY 0x00000004u +#define SYS_PRIV_KERNEL_DIAGNOSTICS 0x00000008u +#define SYS_PRIV_GLOBAL_INPUT 0x00000010u +#define SYS_PRIV_AUDIO_ACCESS 0x00000020u +#define SYS_PRIV_AUDIO_MASTER_CONTROL 0x00000040u +#define SYS_PRIV_CONSOLE_ACCESS 0x00000080u +#define SYS_PRIV_DISPLAY_ACCESS 0x00000100u +#define SYS_PRIV_DEBUG_OUTPUT 0x00000200u +#define SYS_PRIV_SYSTEM_SERVICE_REGISTER 0x00000400u +#define SYS_PRIV_RAW_STORAGE 0x00000800u +#define SYS_PRIV_FILESYSTEM_PROVIDER 0x00001000u +#define SYS_PRIV_NETWORK_ACCESS 0x00002000u +// Allows publication into the legacy global named-queue registry; opening an explicitly published queue stays public. +#define SYS_PRIV_NAMED_QUEUE_PUBLISH 0x00004000u +#define SYS_PRIV_ALL 0x00007FFFu + +// Service claims constrain which well-known service identities a privileged context may register. +#define SYS_SERVICE_CLAIM_RESOURCE 0x00000001u +#define SYS_SERVICE_CLAIM_ALL SYS_SERVICE_CLAIM_RESOURCE + +// Security-context-specific capability rights. +#define SYS_SECURITY_CONTEXT_RIGHT_DERIVE 0x00000100u +#define SYS_SECURITY_CONTEXT_RIGHT_ASSIGN 0x00000200u + +// Zero is a per-API sentinel meaning the calling process's current security context. +#define SYS_SECURITY_CONTEXT_CURRENT SYS_HANDLE_INVALID + +typedef struct SYS_SECURITY_CONTEXT_INFO { + uint32_t Size; + uint32_t SecurityId; + SYS_PRIVILEGES EffectivePrivileges; + SYS_PRIVILEGES DelegablePrivileges; + SYS_SERVICE_CLAIMS ServiceClaims; + SYS_SERVICE_CLAIMS DelegableServiceClaims; + uint32_t Flags; +} SYS_SECURITY_CONTEXT_INFO; + +typedef struct SYS_DERIVE_SECURITY_CONTEXT_REQUEST { + uint32_t Size; + SYS_HANDLE SourceContext; + SYS_PRIVILEGES EffectivePrivileges; + SYS_PRIVILEGES DelegablePrivileges; + SYS_SERVICE_CLAIMS ServiceClaims; + SYS_SERVICE_CLAIMS DelegableServiceClaims; + uint32_t Flags; + SYS_HANDLE* ContextHandleOut; +} SYS_DERIVE_SECURITY_CONTEXT_REQUEST; + +#define SYS_SECURITY_CONTEXT_FLAG_NONE 0u + +KSTATUS SysDeriveSecurityContext(const SYS_DERIVE_SECURITY_CONTEXT_REQUEST* request); +KSTATUS SysQuerySecurityContext(SYS_HANDLE contextHandle, SYS_SECURITY_CONTEXT_INFO* infoOut); diff --git a/code/public/ntlite/service.h b/code/public/ntlite/service.h new file mode 100644 index 0000000..f0e9088 --- /dev/null +++ b/code/public/ntlite/service.h @@ -0,0 +1,21 @@ +// Public nt-lite well-known system-service identifiers. + +#pragma once + +#include +#include + +// System-service identities are numeric kernel registry keys, not pathnames. +typedef uint32_t SYS_SERVICE_ID; + +#define SYS_SERVICE_INVALID 0u +#define SYS_SERVICE_RESOURCE 1u +#define SYS_SERVICE_MAX 1u + +// Connects to one well-known system service through its registered Port listener. +typedef struct SYS_CONNECT_SYSTEM_SERVICE_REQUEST { + uint32_t Size; + SYS_SERVICE_ID ServiceId; + uint32_t Flags; + SYS_HANDLE* EndpointHandleOut; +} SYS_CONNECT_SYSTEM_SERVICE_REQUEST; diff --git a/code/public/ntlite/service_control.h b/code/public/ntlite/service_control.h new file mode 100644 index 0000000..70963ef --- /dev/null +++ b/code/public/ntlite/service_control.h @@ -0,0 +1,23 @@ +// Native service-manager control protocol. + +#pragma once + +#include +#include + +#define NTL_SERVICE_CONTROL_VERSION 1u + +#define NTL_SERVICE_CONTROL_READY 1u +#define NTL_SERVICE_CONTROL_STOP 2u + +#define NTL_SERVICE_READY_FLAG_RESOURCE_ADMIN_LISTENER 0x00000001u + +// Service-control messages are notifications over a private Port connection. +typedef struct NTL_SERVICE_CONTROL_MESSAGE { + uint32_t Size; + uint32_t Version; + uint32_t Operation; + uint32_t Flags; + KSTATUS Status; + uint32_t Reserved[3]; +} NTL_SERVICE_CONTROL_MESSAGE; diff --git a/code/public/ntlite/status.h b/code/public/ntlite/status.h index 5362e37..3e191f0 100644 --- a/code/public/ntlite/status.h +++ b/code/public/ntlite/status.h @@ -62,6 +62,10 @@ typedef uint32_t KSTATUS; #define NTLSTATUS_WOULD_BLOCK KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_SYSTEM, 0x000013u) #define NTLSTATUS_DEADLOCK KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_SYSTEM, 0x000014u) #define NTLSTATUS_DEVICE_REMOVED KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_SYSTEM, 0x000015u) +#define NTLSTATUS_ID_EXHAUSTED KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_SYSTEM, 0x000016u) +#define NTLSTATUS_CAPABILITY_REVOKED KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_SYSTEM, 0x000017u) +#define NTLSTATUS_LIMIT_REACHED KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_SYSTEM, 0x000018u) +#define NTLSTATUS_PORT_DISCONNECTED KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_SYSTEM, 0x000019u) // Process termination and fault statuses use the process facility. #define NTLSTATUS_PROCESS_BASE KSTATUS_MAKE_ERROR(NTLSTATUS_FACILITY_PROCESS, 0x000000u) diff --git a/code/public/ntlite/syscall.h b/code/public/ntlite/syscall.h index f181621..faed35b 100644 --- a/code/public/ntlite/syscall.h +++ b/code/public/ntlite/syscall.h @@ -12,23 +12,39 @@ #include #include #include +#include +#include +#include +#include #include +#include -// Syscall ABI convention: status-returning calls use KSTATUS. New or revised -// operations use a request struct when they need five or more logical arguments -// or when a pointer/length pair would otherwise be split across flat arguments. +// Syscall ABI convention: status-returning calls use KSTATUS. Syscalls use at most +// four register arguments; larger operations use request structures. Every +// SYS_*_REQUEST begins with Size, which must equal the exact structure size for +// the current ABI. Payload byte counts use Length or another semantic name. // User process/thread environment. // GS base points at SYS_TEB. GS:[0] is the process PEB pointer. #define SYS_PEB_MAGIC 0x30424550u // "PEB0" #define SYS_TEB_MAGIC 0x30424554u // "TEB0" -#define SYS_ENV_VERSION 1u -#define SYS_CWD_MAX 520u +#define SYS_ENV_VERSION 4u #define SYS_PATH_MAX 1024u #ifndef SYS_USER_ENV_TYPES_DEFINED #define SYS_USER_ENV_TYPES_DEFINED 1 +#define SYS_STARTUP_CAP_SYSTEM_VOLUME 1u +#define SYS_STARTUP_CAP_SERVICE_CONTROL 2u +#define SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE 3u +#define SYS_STARTUP_CAP_VOLUME_MANAGER 4u +#define SYS_STARTUP_CAPABILITY_MAX 32u + +typedef struct SYS_STARTUP_CAPABILITY { + uint32_t Tag; + SYS_HANDLE Handle; +} SYS_STARTUP_CAPABILITY; + typedef struct SYS_PEB { uint32_t Magic; uint32_t Version; @@ -36,7 +52,11 @@ typedef struct SYS_PEB { SYS_STRING_VIEW ImagePath; SYS_STRING_VIEW CommandLine; - SYS_STRING_VIEW CurrentDirectory; + const SYS_STARTUP_CAPABILITY* StartupCapabilities; + uint32_t StartupCapabilityCount; + uint32_t Reserved; + + const void* LoaderData; } SYS_PEB; typedef struct SYS_TEB { @@ -58,14 +78,21 @@ static inline SYS_PEB* SysGetPeb(void) { return (SYS_PEB*)(uintptr_t)__readgsqword(0); } -// Returns the kernel-maintained current-directory cache from the PEB. -static inline SYS_STRING_VIEW SysGetCurrentDirectoryView(void) { +// Returns one tagged startup capability from the immutable PEB descriptor table. +static inline SYS_HANDLE SysGetStartupCapability(uint32_t tag) { SYS_PEB* peb = SysGetPeb(); - if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) - return SysSvFromParts("", 0); - return peb->CurrentDirectory; + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || !tag || + peb->StartupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX || + (peb->StartupCapabilityCount && !peb->StartupCapabilities)) + return SYS_HANDLE_INVALID; + for (uint32_t i = 0; i < peb->StartupCapabilityCount; ++i) { + if (peb->StartupCapabilities[i].Tag == tag) + return peb->StartupCapabilities[i].Handle; + } + return SYS_HANDLE_INVALID; } + // Returns the current thread environment block. static inline SYS_TEB* SysGetTeb(void) { return (SYS_TEB*)(uintptr_t)__readgsqword(offsetof(SYS_TEB, Self)); @@ -82,37 +109,40 @@ typedef void (*SYS_USER_APC_ROUTINE)(void* normalContext, uint64_t information); typedef struct SYS_FILE_IO_REQUEST { - uint32_t FileHandle; + uint32_t Size; + SYS_HANDLE FileHandle; uint32_t Flags; void* Buffer; - size_t Size; + size_t Length; size_t* TransferredOut; } SYS_FILE_IO_REQUEST; -static inline SYS_FILE_IO_REQUEST SysMakeFileIoRequest(uint32_t fileHandle, +static inline SYS_FILE_IO_REQUEST SysMakeFileIoRequest(SYS_HANDLE fileHandle, const void* buffer, size_t size, size_t* transferredOut) { SYS_FILE_IO_REQUEST request; + request.Size = sizeof(request); request.FileHandle = fileHandle; request.Flags = 0; request.Buffer = (void*)(uintptr_t)buffer; - request.Size = size; + request.Length = size; request.TransferredOut = transferredOut; return request; } typedef struct SYS_ASYNC_FILE_REQUEST { - uint32_t FileHandle; - uint32_t EventHandle; // optional; 0 means no event signal + uint32_t Size; + SYS_HANDLE FileHandle; + SYS_HANDLE EventHandle; // optional; 0 means no event signal uint32_t Flags; uint64_t Offset; // explicit byte offset; does not touch file pointer void* Buffer; - size_t Size; + size_t Length; SYS_IO_STATUS_BLOCK* Iosb; // required; initialized to NTLSTATUS_PENDING SYS_USER_APC_ROUTINE ApcRoutine; // optional; requires SysSetUserApcDispatcher void* ApcContext; - uint32_t* RequestHandleOut; // optional; receives cancel/query handle + SYS_HANDLE* RequestHandleOut; // optional; receives cancel/query handle } SYS_ASYNC_FILE_REQUEST; // User APC frames are kernel-issued control records, not resume contexts. @@ -303,16 +333,38 @@ typedef struct SYS_RAISE_EXCEPTION_REQUEST { } SYS_RAISE_EXCEPTION_REQUEST; // String-view based syscall request structures. -typedef struct SYS_CREATE_PROCESS_REQUEST { - SYS_STRING_VIEW Path; - SYS_STRING_VIEW CommandLine; - uint32_t* HandleOut; -} SYS_CREATE_PROCESS_REQUEST; +#define SYS_CREATE_PROCESS_OBJECT_FLAG_NONE 0x00000000u -typedef struct SYS_PATH_HANDLE_REQUEST { - SYS_STRING_VIEW Path; - uint32_t* HandleOut; -} SYS_PATH_HANDLE_REQUEST; +typedef struct SYS_CREATE_PROCESS_OBJECT_REQUEST { + uint32_t Size; + uint32_t Flags; + SYS_STRING_VIEW ImagePath; + SYS_STRING_VIEW CommandLine; + SYS_HANDLE SecurityContext; + SYS_HANDLE* ProcessHandleOut; +} SYS_CREATE_PROCESS_OBJECT_REQUEST; + +typedef struct SYS_ALLOC_PROCESS_VM_REQUEST { + uint32_t Size; + SYS_HANDLE ProcessHandle; + uint32_t Protect; + uint32_t Flags; + size_t PageCount; + void** BaseOut; +} SYS_ALLOC_PROCESS_VM_REQUEST; + +#define SYS_START_PROCESS_FLAG_NONE 0x00000000u +typedef struct SYS_START_PROCESS_REQUEST { + uint32_t Size; + SYS_HANDLE ProcessHandle; + uint32_t Flags; + uintptr_t MainImageBase; + size_t MainImageSize; + uintptr_t EntryPoint; + const SYS_STARTUP_CAPABILITY* StartupCapabilities; + uint32_t StartupCapabilityCount; + const void* LoaderData; +} SYS_START_PROCESS_REQUEST; #define SYS_FILE_ACCESS_READ 0x00000001u #define SYS_FILE_ACCESS_WRITE 0x00000002u @@ -328,21 +380,69 @@ typedef struct SYS_PATH_HANDLE_REQUEST { #define SYS_FILE_FLAG_NONE 0x00000000u -typedef struct SYS_OPEN_FILE_REQUEST { +// Opens or creates a file beneath a mounted-volume or directory capability. +// Path is a canonical native relative path: '/' separators only, with no leading/trailing +// separator, empty component, '.', '..', backslash, or embedded NUL. +typedef struct SYS_OPEN_FILE_AT_REQUEST { + uint32_t Size; + SYS_HANDLE RootHandle; SYS_STRING_VIEW Path; uint32_t Access; uint32_t Disposition; uint32_t Flags; - uint32_t* HandleOut; -} SYS_OPEN_FILE_REQUEST; + SYS_HANDLE* HandleOut; +} SYS_OPEN_FILE_AT_REQUEST; -// Builds a flags-based file-open request. -static inline SYS_OPEN_FILE_REQUEST SysMakeOpenFileRequest(SYS_STRING_VIEW path, - uint32_t access, - uint32_t disposition, - uint32_t flags, - uint32_t* handleOut) { - SYS_OPEN_FILE_REQUEST request; +#define SYS_DIR_ACCESS_ENUMERATE 0x00000001u +#define SYS_DIR_ACCESS_OPEN 0x00000002u +#define SYS_DIR_ACCESS_MODIFY 0x00000004u +#define SYS_DIR_FLAG_NONE 0x00000000u + +// Opens a directory beneath a mounted-volume or directory capability. An empty Path +// opens the root itself; non-empty paths use the same canonical relative-path contract. +typedef struct SYS_OPEN_DIR_AT_REQUEST { + uint32_t Size; + SYS_HANDLE RootHandle; + SYS_STRING_VIEW Path; + uint32_t Access; + uint32_t Flags; + SYS_HANDLE* HandleOut; +} SYS_OPEN_DIR_AT_REQUEST; + +// Performs one path-only operation beneath a mounted-volume or directory capability. +typedef struct SYS_PATH_AT_REQUEST { + uint32_t Size; + SYS_HANDLE RootHandle; + SYS_STRING_VIEW Path; +} SYS_PATH_AT_REQUEST; + +// Queries metadata beneath a mounted-volume or directory capability. +typedef struct SYS_STAT_AT_REQUEST { + uint32_t Size; + SYS_HANDLE RootHandle; + SYS_STRING_VIEW Path; + SYS_FILE_STAT* StatOut; +} SYS_STAT_AT_REQUEST; + +// Renames one path between capability-relative roots. Cross-volume rename is rejected. +typedef struct SYS_RENAME_AT_REQUEST { + uint32_t Size; + SYS_HANDLE OldRootHandle; + SYS_STRING_VIEW OldPath; + SYS_HANDLE NewRootHandle; + SYS_STRING_VIEW NewPath; +} SYS_RENAME_AT_REQUEST; + +// Builds a capability-relative file-open request. +static inline SYS_OPEN_FILE_AT_REQUEST SysMakeOpenFileAtRequest(SYS_HANDLE rootHandle, + SYS_STRING_VIEW path, + uint32_t access, + uint32_t disposition, + uint32_t flags, + SYS_HANDLE* handleOut) { + SYS_OPEN_FILE_AT_REQUEST request; + request.Size = sizeof(request); + request.RootHandle = rootHandle; request.Path = path; request.Access = access; request.Disposition = disposition; @@ -351,36 +451,21 @@ static inline SYS_OPEN_FILE_REQUEST SysMakeOpenFileRequest(SYS_STRING_VIEW path, return request; } -typedef struct SYS_PATH_REQUEST { - SYS_STRING_VIEW Path; -} SYS_PATH_REQUEST; - -#define SYS_REAL_PATH_FLAG_NONE 0x00000000u - -// SysRealPath returns a canonical native path of the form "N:/path". It uses -// the kernel-owned process CWD for relative input, collapses '.', '..', and -// repeated separators, and clamps parent traversal at the volume root. The -// current filesystem model has no symlinks, so resolution is lexical and does -// not require the final path to exist. RequiredSizeOut receives the byte count -// including the terminating NUL; a NULL buffer with size zero only queries it. -typedef struct SYS_REAL_PATH_REQUEST { - uint32_t Size; - uint32_t Flags; - SYS_STRING_VIEW Path; - char* Buffer; - size_t BufferSize; - size_t* RequiredSizeOut; -} SYS_REAL_PATH_REQUEST; - -typedef struct SYS_STAT_FILE_REQUEST { - SYS_STRING_VIEW Path; - SYS_FILE_STAT* StatOut; -} SYS_STAT_FILE_REQUEST; - -typedef struct SYS_RENAME_FILE_REQUEST { - SYS_STRING_VIEW OldPath; - SYS_STRING_VIEW NewPath; -} SYS_RENAME_FILE_REQUEST; +// Builds a capability-relative directory-open request. +static inline SYS_OPEN_DIR_AT_REQUEST SysMakeOpenDirAtRequest(SYS_HANDLE rootHandle, + SYS_STRING_VIEW path, + uint32_t access, + uint32_t flags, + SYS_HANDLE* handleOut) { + SYS_OPEN_DIR_AT_REQUEST request; + request.Size = sizeof(request); + request.RootHandle = rootHandle; + request.Path = path; + request.Access = access; + request.Flags = flags; + request.HandleOut = handleOut; + return request; +} #define SYS_FILE_LOCK_SHARED 1u #define SYS_FILE_LOCK_EXCLUSIVE 2u @@ -395,7 +480,7 @@ typedef struct SYS_RENAME_FILE_REQUEST { // Unlock removes this process's overlapping locks for the file identity. typedef struct SYS_LOCK_FILE_REQUEST { uint32_t Size; - uint32_t FileHandle; + SYS_HANDLE FileHandle; uint32_t Mode; uint32_t Flags; uint64_t Offset; @@ -405,17 +490,19 @@ typedef struct SYS_LOCK_FILE_REQUEST { typedef struct SYS_UNLOCK_FILE_REQUEST { uint32_t Size; - uint32_t FileHandle; + SYS_HANDLE FileHandle; uint32_t Flags; uint64_t Offset; uint64_t Length; } SYS_UNLOCK_FILE_REQUEST; typedef struct SYS_WRITE_CONSOLE_REQUEST { + uint32_t Size; SYS_STRING_VIEW Text; } SYS_WRITE_CONSOLE_REQUEST; typedef struct SYS_PRINT_DEBUG_REQUEST { + uint32_t Size; SYS_STRING_VIEW Text; } SYS_PRINT_DEBUG_REQUEST; @@ -423,22 +510,27 @@ typedef struct SYS_PRINT_DEBUG_REQUEST { // SysUnpublishQueue withdraws a name explicitly. Closing the publishing handle // or exiting the process withdraws every name published through that handle. typedef struct SYS_PUBLISH_QUEUE_REQUEST { + uint32_t Size; SYS_STRING_VIEW Name; - uint32_t QueueHandle; + SYS_HANDLE QueueHandle; } SYS_PUBLISH_QUEUE_REQUEST; typedef struct SYS_UNPUBLISH_QUEUE_REQUEST { + uint32_t Size; SYS_STRING_VIEW Name; } SYS_UNPUBLISH_QUEUE_REQUEST; typedef struct SYS_OPEN_NAMED_QUEUE_REQUEST { + uint32_t Size; SYS_STRING_VIEW Name; - uint32_t* HandleOut; + SYS_HANDLE* HandleOut; } SYS_OPEN_NAMED_QUEUE_REQUEST; typedef struct SYS_NAMED_QUEUE_OWNER_REQUEST { + uint32_t Size; SYS_STRING_VIEW Name; - uint32_t* ProcessHandleOut; + SYS_RIGHTS DesiredAccess; + SYS_HANDLE* ProcessHandleOut; } SYS_NAMED_QUEUE_OWNER_REQUEST; #define SYS_QUEUE_FLAG_NONE 0x00000000u @@ -455,7 +547,7 @@ typedef struct SYS_NAMED_QUEUE_OWNER_REQUEST { // not copy storage behind it. typedef struct SYS_QUEUE_SEND_REQUEST { uint32_t Size; - uint32_t QueueHandle; + SYS_HANDLE QueueHandle; uint32_t Flags; uint64_t TimeoutMilliseconds; const void* Message; @@ -463,7 +555,7 @@ typedef struct SYS_QUEUE_SEND_REQUEST { typedef struct SYS_QUEUE_RECV_REQUEST { uint32_t Size; - uint32_t QueueHandle; + SYS_HANDLE QueueHandle; uint32_t Flags; uint64_t TimeoutMilliseconds; void* MessageOut; @@ -480,18 +572,19 @@ typedef struct SYS_QUEUE_RECV_REQUEST { typedef struct SYS_WAIT_MANY_REQUEST { uint32_t Size; uint32_t Flags; - const uint32_t* Handles; + const SYS_HANDLE* Handles; uint32_t Count; uint32_t* SignaledIndexOut; uint64_t TimeoutMilliseconds; } SYS_WAIT_MANY_REQUEST; typedef struct SYS_CREATE_THREAD_REQUEST { + uint32_t Size; uintptr_t Entry; uintptr_t Argument; uint32_t Priority; uint32_t Flags; - uint32_t* ThreadHandleOut; + SYS_HANDLE* ThreadHandleOut; uint32_t* ThreadIdOut; } SYS_CREATE_THREAD_REQUEST; @@ -500,31 +593,34 @@ typedef struct SYS_CREATE_THREAD_REQUEST { void SysExitProcess(int32_t exitCode); void SysExitThread(int32_t exitCode); -KSTATUS SysCreateProcess(const SYS_CREATE_PROCESS_REQUEST* request); -KSTATUS SysWaitSingle(uint32_t handle, uint32_t flags, uint64_t timeoutMilliseconds); +KSTATUS SysCreateProcessObject(const SYS_CREATE_PROCESS_OBJECT_REQUEST* request); +KSTATUS SysAllocProcessVm(const SYS_ALLOC_PROCESS_VM_REQUEST* request); +KSTATUS SysProtectProcessVm(SYS_HANDLE processHandle, void* base, size_t pageCount, uint32_t protect); +KSTATUS SysStartProcess(const SYS_START_PROCESS_REQUEST* request); +KSTATUS SysWaitSingle(SYS_HANDLE handle, uint32_t flags, uint64_t timeoutMilliseconds); KSTATUS SysWaitMany(const SYS_WAIT_MANY_REQUEST* request); // SysOpenInputQueue returns a readable and waitable handle to the default // keyboard input queue. The handle does not create a second event stream; // SysReadInputEvent, SysPeekInputEvent, SysReadKey, and SysReadKeyboardChar // consume the same queue. SysWaitSingle succeeds for this handle when a // keyboard input event can be read. -KSTATUS SysOpenInputQueue(uint32_t flags, uint32_t* handleOut); +KSTATUS SysOpenInputQueue(uint32_t flags, SYS_HANDLE* handleOut); -static inline KSTATUS SysWait(uint32_t handle, uint32_t flags) { +static inline KSTATUS SysWait(SYS_HANDLE handle, uint32_t flags) { return SysWaitSingle(handle, flags, SYS_WAIT_TIMEOUT_INFINITE); } -KSTATUS SysGetProcessExitCode(uint32_t handle, int32_t* exitCodeOut); -KSTATUS SysOpenProcess(uint32_t pid, uint32_t* handleOut); +KSTATUS SysGetProcessExitCode(SYS_HANDLE handle, int32_t* exitCodeOut); +KSTATUS SysOpenProcess(uint32_t pid, SYS_RIGHTS desiredAccess, SYS_HANDLE* handleOut); // Protected kernel processes remain openable for query and wait operations, // but SysKillProcess returns NTLSTATUS_ACCESS_DENIED for them. -KSTATUS SysKillProcess(uint32_t processHandle, int32_t exitCode); +KSTATUS SysKillProcess(SYS_HANDLE processHandle, int32_t exitCode); #define SYS_PROCESS_NAME_MAX 64u -#define SYS_PROCESS_CWD_MAX 128u #define SYS_PROCESS_STATE_RUNNING 0u #define SYS_PROCESS_STATE_TERMINATING 1u #define SYS_PROCESS_STATE_EXITED 2u +#define SYS_PROCESS_STATE_CONSTRUCTING 3u #define SYS_PROCESS_FLAG_SYSTEM 0x00000001u #define SYS_PROCESS_FLAG_CURRENT 0x00000002u @@ -532,6 +628,7 @@ KSTATUS SysKillProcess(uint32_t processHandle, int32_t exitCode); #define SYS_PROCESS_FLAG_EXITED 0x00000008u #define SYS_PROCESS_FLAG_IDLE 0x00000010u #define SYS_PROCESS_FLAG_TERMINATION_PROTECTED 0x00000020u +#define SYS_PROCESS_FLAG_CONSTRUCTING 0x00000040u #define SYS_THREAD_STATE_READY 0u #define SYS_THREAD_STATE_RUNNING 1u @@ -599,16 +696,12 @@ typedef struct SYS_THREAD_INFO { typedef struct SYS_PROCESS_INFO { uint32_t Size; uint32_t ProcessId; + uint32_t SecurityId; uint32_t State; uint32_t Flags; 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; - int32_t ExitCode; uintptr_t ImageBase; @@ -630,63 +723,14 @@ typedef struct SYS_PROCESS_INFO { uintptr_t LastExceptionFaultAddress; char Name[SYS_PROCESS_NAME_MAX]; - char CurrentDirectory[SYS_PROCESS_CWD_MAX]; } SYS_PROCESS_INFO; KSTATUS SysGetProcessList(SYS_PROCESS_INFO* processesOut, size_t maxCount, size_t* countOut); -KSTATUS SysGetProcessPid(uint32_t handle, uint32_t* pidOut); +KSTATUS SysGetProcessPid(SYS_HANDLE handle, uint32_t* pidOut); KSTATUS 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; - -KSTATUS SysLoadModule(const SYS_LOAD_MODULE_REQUEST* request); -KSTATUS SysUnloadModule(uint32_t moduleHandle); -KSTATUS SysGetModuleInfo(uint32_t moduleHandle, SYS_MODULE_INFO* infoOut); -KSTATUS SysGetModuleExport(const SYS_GET_MODULE_EXPORT_REQUEST* request); -KSTATUS SysGetModuleList(SYS_MODULE_INFO* modulesOut, size_t maxCount, size_t* countOut); - KSTATUS SysCreateThread(const SYS_CREATE_THREAD_REQUEST* request); -KSTATUS SysResumeThread(uint32_t threadHandle, uint32_t* previousSuspendCountOut); +KSTATUS SysResumeThread(SYS_HANDLE threadHandle, uint32_t* previousSuspendCountOut); uint32_t SysGetCurrentTid(void); uint32_t SysGetCurrentPid(void); KSTATUS SysSetThreadPriority(uint32_t priority); @@ -700,15 +744,15 @@ KSTATUS SysYield(void); // state before applying the new schedule. typedef struct SYS_SET_TIMER_REQUEST { uint32_t Size; - uint32_t TimerHandle; + SYS_HANDLE TimerHandle; uint32_t Flags; uint64_t DueTimeMilliseconds; uint64_t PeriodMilliseconds; } SYS_SET_TIMER_REQUEST; -KSTATUS SysCreateTimer(uint32_t* handleOut); +KSTATUS SysCreateTimer(SYS_HANDLE* handleOut); KSTATUS SysSetTimer(const SYS_SET_TIMER_REQUEST* request); -KSTATUS SysCancelTimer(uint32_t timerHandle); +KSTATUS SysCancelTimer(SYS_HANDLE timerHandle); // SysSleep blocks the current thread through the same timer backend used by // waitable timer handles. A zero-length sleep yields the processor. @@ -770,49 +814,72 @@ KSTATUS SysQueryVm(void* address, SYS_VM_REGION_INFO* out); #define SYS_MAP_ACCESS_EXECUTE 0x00000008u #define SYS_MAP_FLAG_NONE 0x00000000u -KSTATUS SysCreateFileMapping(uint32_t fileHandle, uint32_t protect, uint32_t flags, - uint64_t maximumSize, uint32_t* mappingHandleOut); -KSTATUS SysMapFile(uint32_t mappingHandle, uint64_t offset, size_t size, - uint32_t access, uint32_t flags, void** baseOut); -KSTATUS SysCloseHandle(uint32_t handle); +typedef struct SYS_CREATE_FILE_MAPPING_REQUEST { + uint32_t Size; + SYS_HANDLE FileHandle; + uint32_t Protect; + uint32_t Flags; + uint64_t MaximumSize; + SYS_HANDLE* MappingHandleOut; +} SYS_CREATE_FILE_MAPPING_REQUEST; -#define SYS_HANDLE_RIGHT_READ 0x00000001u -#define SYS_HANDLE_RIGHT_WRITE 0x00000002u -#define SYS_HANDLE_RIGHT_WAIT 0x00000004u -#define SYS_HANDLE_RIGHT_SIGNAL 0x00000008u -#define SYS_HANDLE_RIGHT_DUPLICATE 0x00000010u -#define SYS_HANDLE_RIGHT_QUERY 0x00000020u -#define SYS_HANDLE_RIGHT_TERMINATE 0x00000040u -#define SYS_HANDLE_RIGHT_LOCK 0x00000080u -#define SYS_HANDLE_RIGHT_RESUME 0x00000100u -#define SYS_HANDLE_RIGHT_ALL 0x000001FFu +typedef struct SYS_MAP_FILE_REQUEST { + uint32_t Size; + SYS_HANDLE MappingHandle; + uint32_t Access; + uint32_t Flags; + uint64_t Offset; + size_t Length; + void** BaseOut; +} SYS_MAP_FILE_REQUEST; + +KSTATUS SysCreateFileMapping(const SYS_CREATE_FILE_MAPPING_REQUEST* request); +KSTATUS SysMapFile(const SYS_MAP_FILE_REQUEST* request); +KSTATUS SysCloseHandle(SYS_HANDLE handle); #define SYS_DUPLICATE_HANDLE_FLAG_NONE 0x00000000u #define SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS 0x00000001u typedef struct SYS_DUPLICATE_HANDLE_REQUEST { - uint32_t SourceHandle; - uint32_t TargetProcessHandle; - uint32_t DesiredRights; + uint32_t Size; + SYS_HANDLE SourceHandle; + SYS_HANDLE TargetProcessHandle; + SYS_RIGHTS DesiredRights; uint32_t Flags; - uint32_t* TargetHandleOut; + SYS_HANDLE* TargetHandleOut; } SYS_DUPLICATE_HANDLE_REQUEST; +// Duplicate creates another handle to the same revocation grant. DesiredRights +// may attenuate the alias but can never exceed the source handle's rights. +// Cross-process duplication also requires transfer authority on the source. KSTATUS SysDuplicateHandle(const SYS_DUPLICATE_HANDLE_REQUEST* request); -KSTATUS SysCreateMutex(uint32_t* handleOut); -KSTATUS SysTryAcquireMutex(uint32_t handle); -KSTATUS SysReleaseMutex(uint32_t handle); +typedef struct SYS_DERIVE_HANDLE_REQUEST { + uint32_t Size; + SYS_HANDLE SourceHandle; + SYS_RIGHTS DesiredRights; + SYS_HANDLE* DerivedHandleOut; +} SYS_DERIVE_HANDLE_REQUEST; -KSTATUS SysCreateSem(int32_t initial, int32_t max, uint32_t* handleOut); -KSTATUS SysTryWaitSem(uint32_t handle); -KSTATUS SysSignalSem(uint32_t handle); +// Derive creates a child grant. Revoking that child invalidates its aliases and +// descendants without revoking the parent grant. +KSTATUS SysDeriveHandle(const SYS_DERIVE_HANDLE_REQUEST* request); +// Revoke invalidates the selected grant, every alias of it, and all descendants. +KSTATUS SysRevokeHandle(SYS_HANDLE handle); -KSTATUS SysCreateEvent(uint32_t type, uint32_t initialState, uint32_t* handleOut); -KSTATUS SysSignalEvent(uint32_t handle); -KSTATUS SysClearEvent(uint32_t handle); +KSTATUS SysCreateMutex(SYS_HANDLE* handleOut); +KSTATUS SysTryAcquireMutex(SYS_HANDLE handle); +KSTATUS SysReleaseMutex(SYS_HANDLE handle); -KSTATUS SysCreateQueue(uint32_t* handleOut); +KSTATUS SysCreateSem(int32_t initial, int32_t max, SYS_HANDLE* handleOut); +KSTATUS SysTryWaitSem(SYS_HANDLE handle); +KSTATUS SysSignalSem(SYS_HANDLE handle); + +KSTATUS SysCreateEvent(uint32_t type, uint32_t initialState, SYS_HANDLE* handleOut); +KSTATUS SysSignalEvent(SYS_HANDLE handle); +KSTATUS SysClearEvent(SYS_HANDLE handle); + +KSTATUS SysCreateQueue(SYS_HANDLE* handleOut); KSTATUS SysSendQueue(const SYS_QUEUE_SEND_REQUEST* request); KSTATUS SysRecvQueue(const SYS_QUEUE_RECV_REQUEST* request); KSTATUS SysPublishQueue(const SYS_PUBLISH_QUEUE_REQUEST* request); @@ -820,30 +887,44 @@ KSTATUS SysUnpublishQueue(const SYS_UNPUBLISH_QUEUE_REQUEST* request); KSTATUS SysOpenNamedQueue(const SYS_OPEN_NAMED_QUEUE_REQUEST* request); KSTATUS SysOpenNamedQueueOwnerProcess(const SYS_NAMED_QUEUE_OWNER_REQUEST* request); -KSTATUS SysOpenFile(const SYS_OPEN_FILE_REQUEST* request); +KSTATUS SysCreatePort(const SYS_CREATE_PORT_REQUEST* request); +KSTATUS SysConnectPort(const SYS_CONNECT_PORT_REQUEST* request); +KSTATUS SysAcceptPort(const SYS_ACCEPT_PORT_REQUEST* request); +KSTATUS SysSendPort(const SYS_SEND_PORT_REQUEST* request); +KSTATUS SysReceivePort(const SYS_RECEIVE_PORT_REQUEST* request); +KSTATUS SysCallPort(const SYS_CALL_PORT_REQUEST* request); +KSTATUS SysReplyPort(const SYS_REPLY_PORT_REQUEST* request); +KSTATUS SysDisconnectPort(SYS_HANDLE endpointHandle); +KSTATUS SysRegisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle); +KSTATUS SysUnregisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle); +KSTATUS SysConnectSystemService(const SYS_CONNECT_SYSTEM_SERVICE_REQUEST* request); + +KSTATUS SysQueryVolumes(const SYS_QUERY_VOLUMES_REQUEST* request); +KSTATUS SysOpenVolume(SYS_HANDLE managerHandle, uint32_t instanceId, SYS_RIGHTS desiredRights, SYS_HANDLE* volumeHandleOut); +KSTATUS SysWaitVolumeChange(SYS_HANDLE managerHandle, uint64_t observedGeneration, uint64_t timeoutMilliseconds); + +KSTATUS SysOpenFileAt(const SYS_OPEN_FILE_AT_REQUEST* request); +KSTATUS SysOpenDirAt(const SYS_OPEN_DIR_AT_REQUEST* request); +KSTATUS SysStatAt(const SYS_STAT_AT_REQUEST* request); +KSTATUS SysDeleteAt(const SYS_PATH_AT_REQUEST* request); +KSTATUS SysRenameAt(const SYS_RENAME_AT_REQUEST* request); +KSTATUS SysCreateDirAt(const SYS_PATH_AT_REQUEST* request); +KSTATUS SysRemoveDirAt(const SYS_PATH_AT_REQUEST* request); KSTATUS SysReadFile(const SYS_FILE_IO_REQUEST* request); KSTATUS SysWriteFile(const SYS_FILE_IO_REQUEST* request); KSTATUS SysReadFileAsync(const SYS_ASYNC_FILE_REQUEST* request); KSTATUS SysWriteFileAsync(const SYS_ASYNC_FILE_REQUEST* request); -KSTATUS SysCancelIo(uint32_t asyncRequestHandle); -KSTATUS SysSeekFile(uint32_t handle, uint64_t offset); -KSTATUS SysGetFilePos(uint32_t handle, uint64_t* positionOut); -KSTATUS SysGetFileSize(uint32_t handle, uint64_t* sizeOut); -KSTATUS SysGetFileStat(uint32_t handle, SYS_FILE_STAT* statOut); -KSTATUS SysStatFile(const SYS_STAT_FILE_REQUEST* request); -KSTATUS SysTruncateFile(uint32_t handle, uint64_t newSize); -KSTATUS SysFlushFile(uint32_t handle); -KSTATUS SysDeleteFile(const SYS_PATH_REQUEST* request); -KSTATUS SysRenameFile(const SYS_RENAME_FILE_REQUEST* request); +KSTATUS SysCancelIo(SYS_HANDLE asyncRequestHandle); +KSTATUS SysSeekFile(SYS_HANDLE handle, uint64_t offset); +KSTATUS SysGetFilePos(SYS_HANDLE handle, uint64_t* positionOut); +KSTATUS SysGetFileSize(SYS_HANDLE handle, uint64_t* sizeOut); +KSTATUS SysGetFileStat(SYS_HANDLE handle, SYS_FILE_STAT* statOut); +KSTATUS SysTruncateFile(SYS_HANDLE handle, uint64_t newSize); +KSTATUS SysFlushFile(SYS_HANDLE handle); KSTATUS SysLockFile(const SYS_LOCK_FILE_REQUEST* request); KSTATUS SysUnlockFile(const SYS_UNLOCK_FILE_REQUEST* request); -KSTATUS SysOpenDir(const SYS_PATH_HANDLE_REQUEST* request); -KSTATUS SysReadDir(uint32_t handle, SYS_DIR_ENTRY* entryOut); -KSTATUS SysCreateDir(const SYS_PATH_REQUEST* request); -KSTATUS SysRemoveDir(const SYS_PATH_REQUEST* request); -KSTATUS SysSetCwd(const SYS_PATH_REQUEST* request); -KSTATUS SysRealPath(const SYS_REAL_PATH_REQUEST* request); +KSTATUS SysReadDir(SYS_HANDLE handle, SYS_DIR_ENTRY* entryOut); // Console and graphics. typedef struct SYS_CONSOLE_INFO { @@ -857,9 +938,40 @@ typedef struct SYS_CONSOLE_INFO { KSTATUS SysWriteConsole(const SYS_WRITE_CONSOLE_REQUEST* request); KSTATUS SysGetConsoleInfo(SYS_CONSOLE_INFO* infoOut); KSTATUS SysSetConsoleCursor(int x, int y); -KSTATUS SysFillConsoleCharacter(char c, int x, int y, size_t length, size_t* writtenOut); -KSTATUS SysFillConsoleAttribute(uint16_t attr, int x, int y, size_t length, size_t* writtenOut); -KSTATUS SysPutScreenChar(char c, int x, int y, uint32_t fg, uint32_t bg); + +typedef struct SYS_FILL_CONSOLE_CHARACTER_REQUEST { + uint32_t Size; + char Character; + uint8_t Reserved[3]; + int32_t X; + int32_t Y; + size_t Length; + size_t* WrittenOut; +} SYS_FILL_CONSOLE_CHARACTER_REQUEST; + +typedef struct SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST { + uint32_t Size; + uint16_t Attribute; + uint16_t Reserved; + int32_t X; + int32_t Y; + size_t Length; + size_t* WrittenOut; +} SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST; + +typedef struct SYS_PUT_SCREEN_CHAR_REQUEST { + uint32_t Size; + char Character; + uint8_t Reserved[3]; + int32_t X; + int32_t Y; + uint32_t Foreground; + uint32_t Background; +} SYS_PUT_SCREEN_CHAR_REQUEST; + +KSTATUS SysFillConsoleCharacter(const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request); +KSTATUS SysFillConsoleAttribute(const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request); +KSTATUS SysPutScreenChar(const SYS_PUT_SCREEN_CHAR_REQUEST* request); typedef struct SYS_CREATE_SURFACE_REQUEST { uint32_t Size; @@ -867,19 +979,19 @@ typedef struct SYS_CREATE_SURFACE_REQUEST { uint32_t Height; uint32_t Format; uint32_t Flags; - uint32_t* HandleOut; + SYS_HANDLE* HandleOut; } SYS_CREATE_SURFACE_REQUEST; KSTATUS SysCreateSurface(const SYS_CREATE_SURFACE_REQUEST* request); -KSTATUS SysMapSurface(uint32_t surfaceHandle, void** baseOut, uint32_t* pitchBytesOut); -KSTATUS SysQuerySurface(uint32_t surfaceHandle, SYS_SURFACE_DESC* descOut); -KSTATUS SysPresentSurface(uint32_t surfaceHandle, const DISPLAY_RECT* rects, uint32_t rectCount); +KSTATUS SysMapSurface(SYS_HANDLE surfaceHandle, void** baseOut, uint32_t* pitchBytesOut); +KSTATUS SysQuerySurface(SYS_HANDLE surfaceHandle, SYS_SURFACE_DESC* descOut); +KSTATUS SysPresentSurface(SYS_HANDLE surfaceHandle, const DISPLAY_RECT* rects, uint32_t rectCount); KSTATUS SysClearScreen(uint32_t color); KSTATUS SysGetScreenSize(int* widthOut, int* heightOut); KSTATUS SysQueryDisplayInfo(SYS_DISPLAY_INFO* infoOut); // Display clocks are waitable pacing objects. Providers with frame-event -// support signal them once per submitted frame. Other providers use the BSP +// support signal them once per submitted frame. Other providers use the boot processor // scheduler tick, with PeriodMilliseconds rounded up to one or more ticks. #define SYS_DISPLAY_CLOCK_FLAG_NONE 0u #define SYS_DISPLAY_CLOCK_DEFAULT_PERIOD_MS 1ull @@ -888,7 +1000,7 @@ typedef struct SYS_OPEN_DISPLAY_CLOCK_REQUEST { uint32_t Size; uint32_t Flags; uint64_t PeriodMilliseconds; - uint32_t* HandleOut; + SYS_HANDLE* HandleOut; } SYS_OPEN_DISPLAY_CLOCK_REQUEST; KSTATUS SysOpenDisplayClock(const SYS_OPEN_DISPLAY_CLOCK_REQUEST* request); @@ -920,7 +1032,7 @@ typedef struct SYS_GLOBAL_INPUT_STATE { // empty. A finite timeout returns NTLSTATUS_TIMEOUT only when its deadline expires. typedef struct SYS_INPUT_EVENT_REQUEST { uint32_t Size; - uint32_t QueueHandle; + SYS_HANDLE QueueHandle; uint32_t Flags; uint64_t TimeoutMilliseconds; INPUT_KEY_RECORD* RecordOut; @@ -984,6 +1096,7 @@ typedef struct SYS_SOCKET_ADDRESS { } SYS_SOCKET_ADDRESS; typedef struct SYS_SOCKET_SEND_TO_REQUEST { + uint32_t Size; const void* Buffer; size_t Length; uint32_t Flags; @@ -993,6 +1106,7 @@ typedef struct SYS_SOCKET_SEND_TO_REQUEST { } SYS_SOCKET_SEND_TO_REQUEST; typedef struct SYS_SOCKET_RECV_FROM_REQUEST { + uint32_t Size; void* Buffer; size_t Length; uint32_t Flags; @@ -1002,6 +1116,7 @@ typedef struct SYS_SOCKET_RECV_FROM_REQUEST { } SYS_SOCKET_RECV_FROM_REQUEST; typedef struct SYS_RESOLVE_HOST_REQUEST { + uint32_t Size; SYS_STRING_VIEW Name; uint32_t Family; uint32_t Flags; @@ -1010,6 +1125,7 @@ typedef struct SYS_RESOLVE_HOST_REQUEST { } SYS_RESOLVE_HOST_REQUEST; typedef struct SYS_SOCKET_OPTION_REQUEST { + uint32_t Size; uint32_t Option; void* Value; size_t ValueSize; @@ -1017,7 +1133,7 @@ typedef struct SYS_SOCKET_OPTION_REQUEST { } SYS_SOCKET_OPTION_REQUEST; typedef struct SYS_POLLFD { - uint32_t Handle; + SYS_HANDLE Handle; uint32_t Events; uint32_t ReturnedEvents; } SYS_POLLFD; @@ -1053,8 +1169,8 @@ typedef struct SYS_POLL_REQUEST { // queueing. typedef struct SYS_ASYNC_SOCKET_REQUEST { uint32_t Size; - uint32_t SocketHandle; - uint32_t EventHandle; + SYS_HANDLE SocketHandle; + SYS_HANDLE EventHandle; uint32_t Operation; uint32_t Flags; uint32_t SocketFlags; @@ -1075,28 +1191,47 @@ typedef struct SYS_ASYNC_SOCKET_REQUEST { size_t OptionValueSize; size_t* OptionActualSizeOut; - uint32_t* HandleOut; + SYS_HANDLE* HandleOut; uint32_t* PollReturnedEventsOut; SYS_IO_STATUS_BLOCK* Iosb; SYS_USER_APC_ROUTINE ApcRoutine; void* ApcContext; - uint32_t* RequestHandleOut; + SYS_HANDLE* RequestHandleOut; } SYS_ASYNC_SOCKET_REQUEST; -KSTATUS SysCreateSocket(uint32_t domain, uint32_t type, uint32_t protocol, uint32_t* handleOut); -KSTATUS SysBindSocket(uint32_t socket, const SYS_SOCKET_ADDRESS* address, size_t addressSize); -KSTATUS SysListenSocket(uint32_t socket, uint32_t backlog); -KSTATUS SysAcceptSocket(uint32_t socket, SYS_SOCKET_ADDRESS* remoteAddressOut, size_t* remoteAddressSizeInOut, uint32_t* acceptedHandleOut); -KSTATUS SysConnectSocket(uint32_t socket, const SYS_SOCKET_ADDRESS* address, size_t addressSize); -KSTATUS SysSendSocket(uint32_t socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut); -KSTATUS SysRecvSocket(uint32_t socket, void* buffer, size_t length, uint32_t flags, size_t* receivedOut); -KSTATUS SysShutdownSocket(uint32_t socket, uint32_t how); -KSTATUS SysSendToSocket(uint32_t socket, const SYS_SOCKET_SEND_TO_REQUEST* request); -KSTATUS SysRecvFromSocket(uint32_t socket, const SYS_SOCKET_RECV_FROM_REQUEST* request); +KSTATUS SysCreateSocket(uint32_t domain, uint32_t type, uint32_t protocol, SYS_HANDLE* handleOut); +KSTATUS SysBindSocket(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* address, size_t addressSize); +KSTATUS SysListenSocket(SYS_HANDLE socket, uint32_t backlog); +KSTATUS SysAcceptSocket(SYS_HANDLE socket, SYS_SOCKET_ADDRESS* remoteAddressOut, size_t* remoteAddressSizeInOut, SYS_HANDLE* acceptedHandleOut); +KSTATUS SysConnectSocket(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* address, size_t addressSize); + +typedef struct SYS_SOCKET_SEND_REQUEST { + uint32_t Size; + SYS_HANDLE Socket; + uint32_t Flags; + const void* Buffer; + size_t Length; + size_t* SentOut; +} SYS_SOCKET_SEND_REQUEST; + +typedef struct SYS_SOCKET_RECV_REQUEST { + uint32_t Size; + SYS_HANDLE Socket; + uint32_t Flags; + void* Buffer; + size_t Length; + size_t* ReceivedOut; +} SYS_SOCKET_RECV_REQUEST; + +KSTATUS SysSendSocket(const SYS_SOCKET_SEND_REQUEST* request); +KSTATUS SysRecvSocket(const SYS_SOCKET_RECV_REQUEST* request); +KSTATUS SysShutdownSocket(SYS_HANDLE socket, uint32_t how); +KSTATUS SysSendToSocket(SYS_HANDLE socket, const SYS_SOCKET_SEND_TO_REQUEST* request); +KSTATUS SysRecvFromSocket(SYS_HANDLE socket, const SYS_SOCKET_RECV_FROM_REQUEST* request); KSTATUS SysResolveHost(const SYS_RESOLVE_HOST_REQUEST* request); // Cry about it. -KSTATUS SysSetSocketOption(uint32_t socket, const SYS_SOCKET_OPTION_REQUEST* request); -KSTATUS SysGetSocketOption(uint32_t socket, const SYS_SOCKET_OPTION_REQUEST* request); +KSTATUS SysSetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request); +KSTATUS SysGetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request); KSTATUS SysPoll(const SYS_POLL_REQUEST* request); KSTATUS SysSocketIoAsync(const SYS_ASYNC_SOCKET_REQUEST* request); @@ -1125,6 +1260,9 @@ typedef struct SYS_MEMORY_STATUS { #define SYS_KERNEL_STATS_DISK 11u #define SYS_KERNEL_STATS_NET_IRP 12u #define SYS_KERNEL_STATS_EXCEPTION 13u +#define SYS_KERNEL_STATS_PORT 14u +#define SYS_KERNEL_STATS_VOLUME 15u +#define SYS_KERNEL_STATS_VOLUME_MANAGER 16u // Public stats payloads start with Size and Version. Size is the byte count // returned for that payload, including any trailing records. Version describes @@ -1146,6 +1284,20 @@ typedef struct SYS_EXCEPTION_STATS { uintptr_t LastFaultAddress; } SYS_EXCEPTION_STATS; +typedef struct SYS_PORT_STATS { + uint32_t Size; + uint32_t Version; + uint64_t ConnectionsCreated; + uint64_t DirectDeliveries; + uint64_t DirectBytes; + uint64_t QueuedMessages; + uint64_t QueuedBytes; + uint64_t QueuedDeliveries; + uint64_t RepliesCompleted; + uint64_t Disconnects; + uint64_t ProcessQuotaRejects; +} SYS_PORT_STATS; + typedef struct SYS_SCHEDULER_STATS { uint32_t Size; uint32_t Version; @@ -1184,7 +1336,7 @@ typedef struct SYS_PMM_STATS { typedef struct SYS_CPU_STAT { uint32_t CpuIndex; - uint32_t LapicId; + uint32_t HardwareId; uint32_t Flags; uint32_t CurrentTid; uint32_t CurrentThreadFlags; @@ -1336,7 +1488,7 @@ typedef struct SYS_FILE_CACHE_STATS { uint64_t MaxResidentPages; } SYS_FILE_CACHE_STATS; -// Filesystem stats are generic VFS/driver telemetry. Drivers should map +// Filesystem stats are generic I/O/provider telemetry. Drivers should map // private metadata work onto these counters instead of exposing filesystem- // specific cache names through the public ABI. typedef struct SYS_FILESYSTEM_STATS { @@ -1357,6 +1509,25 @@ typedef struct SYS_FILESYSTEM_STATS { uint64_t MaxDataReadSpanSectors; } SYS_FILESYSTEM_STATS; +typedef struct SYS_VOLUME_STATS { + uint32_t Size; + uint32_t Version; + uint64_t CreatedVolumes; + uint64_t MountedVolumes; + uint64_t RemovedVolumes; + uint64_t DeadIoRejects; + uint64_t ProviderDestroys; +} SYS_VOLUME_STATS; + +typedef struct SYS_VOLUME_MANAGER_STATS { + uint32_t Size; + uint32_t Version; + uint64_t Snapshots; + uint64_t VolumeOpens; + uint64_t Waits; + uint64_t Wakeups; +} SYS_VOLUME_MANAGER_STATS; + typedef struct SYS_DISK_STATS { uint32_t Size; uint32_t Version; @@ -1459,7 +1630,7 @@ typedef struct SYS_PTE_STATS { #define SYS_CPU_FLAG_STARTED 0x00000002u #define SYS_CPU_FLAG_PARKED 0x00000004u #define SYS_CPU_FLAG_ONLINE 0x00000008u -#define SYS_CPU_FLAG_BSP 0x00000010u +#define SYS_CPU_FLAG_BOOT_PROCESSOR 0x00000010u #define SYS_CPU_FLAG_PRIMARY 0x00000020u // Time and diagnostics. diff --git a/code/public/ntlite/volume_manager.h b/code/public/ntlite/volume_manager.h new file mode 100644 index 0000000..38c11e2 --- /dev/null +++ b/code/public/ntlite/volume_manager.h @@ -0,0 +1,27 @@ +// Native mounted-volume manager ABI. The manager exposes kernel volume lifecycle to a delegated userspace control plane. + +#pragma once + +#include +#include + +#define SYS_VOLUME_ENUM_VERSION 1u +#define SYS_VOLUME_QUERY_FLAG_NONE 0x00000000u + +// InstanceId is kernel correlation metadata only. RSSRV owns all global volume names independently. +typedef struct SYS_VOLUME_INFO { + uint32_t Size; + uint32_t Version; + uint32_t InstanceId; + uint32_t Flags; +} SYS_VOLUME_INFO; + +typedef struct SYS_QUERY_VOLUMES_REQUEST { + uint32_t Size; + SYS_HANDLE ManagerHandle; + uint32_t Flags; + uint32_t Capacity; + SYS_VOLUME_INFO* Volumes; + uint32_t* CountOut; + uint64_t* GenerationOut; +} SYS_QUERY_VOLUMES_REQUEST; diff --git a/code/user/apps/doom/doom.c b/code/user/apps/doom/doom.c index 07924ea..806a6e6 100644 --- a/code/user/apps/doom/doom.c +++ b/code/user/apps/doom/doom.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "doom_audio.h" @@ -22,7 +23,7 @@ int _fltused; // --------------------------------------------------------------------------- static void doom_print_wrapper(const char* msg) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(msg) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(msg) }; SysWriteConsole(&req); } @@ -66,12 +67,8 @@ static void* doom_open_wrapper(const char* path, const char* mode) access |= SYS_FILE_ACCESS_READ; } - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), - access, - disposition, - SYS_FILE_FLAG_NONE, - &handle); - result = SysOpenFile(&req); + result = NtlOpenFile(SysSvFromCstr(path), access, disposition, + SYS_FILE_FLAG_NONE, &handle); if (result != 0) return 0; DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)malloc(sizeof(DOOM_FILE_HANDLE)); @@ -468,7 +465,7 @@ int main(void) { const char* cmdline = SysGetPeb()->CommandLine.Data; - do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting\n") }; SysWriteConsole(&req); } while (0); + do { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SYS_SV_LITERAL("doom: starting\n") }; SysWriteConsole(&req); } while (0); // Wire up PureDOOM core mechanics doom_set_print(doom_print_wrapper); @@ -506,12 +503,12 @@ int main(void) doom_print_wrapper("doom: parsed argc <= 0\n"); init_surface(); init_display_clock(); - do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting doom\n") }; SysWriteConsole(&req); } while (0); + do { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SYS_SV_LITERAL("doom: starting doom\n") }; SysWriteConsole(&req); } while (0); doom_init(argc, argv, 0); - do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting audio\n") }; SysWriteConsole(&req); } while (0); + do { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SYS_SV_LITERAL("doom: starting audio\n") }; SysWriteConsole(&req); } while (0); DoomAudioStart(); - do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: entering game loop\n") }; SysWriteConsole(&req); } while (0); + do { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SYS_SV_LITERAL("doom: entering game loop\n") }; SysWriteConsole(&req); } while (0); // Core execution loop. Doom advances at TICRATE, so rendering duplicate // frames as fast as possible only amplifies scheduler and input jitter. diff --git a/code/user/apps/doom/doom_audio.c b/code/user/apps/doom/doom_audio.c index b9e8932..0756a0e 100644 --- a/code/user/apps/doom/doom_audio.c +++ b/code/user/apps/doom/doom_audio.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "doom_audio.h" #include "PureDOOM.h" @@ -212,12 +213,12 @@ static DOOM_AUDIO_STATE g_doomAudio; // Writes one complete diagnostic line to the process console. static void DoomAudioPrint(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } // Opens the default available native render endpoint. -static KSTATUS DoomAudioFindEndpoint(uint64_t* endpointIdOut) { +static KSTATUS DoomAudioFindEndpoint(uint32_t* endpointIdOut) { SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS]; SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request; uint32_t count = 0; @@ -278,13 +279,9 @@ static KSTATUS DoomAudioMapSoundFont(void** dataOut, uint32_t* sizeOut) { *dataOut = NULL; *sizeOut = 0; - SYS_OPEN_FILE_REQUEST open = SysMakeOpenFileRequest( - SysSvFromCstr(DOOM_AUDIO_SOUNDFONT_PATH), - SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_READ, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &fileHandle); - status = SysOpenFile(&open); + status = NtlOpenFile(SysSvFromCstr(DOOM_AUDIO_SOUNDFONT_PATH), + SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_READ, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &fileHandle); if (status != NTLSTATUS_SUCCESS) return status; @@ -296,15 +293,21 @@ static KSTATUS DoomAudioMapSoundFont(void** dataOut, uint32_t* sizeOut) { return status == NTLSTATUS_SUCCESS ? NTLSTATUS_INVALID_PARAMETER : status; } - status = SysCreateFileMapping(fileHandle, SYS_VM_READ, SYS_MAP_FLAG_NONE, - fileSize, &mappingHandle); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), fileHandle, SYS_VM_READ, + SYS_MAP_FLAG_NONE, fileSize, &mappingHandle + }; + status = SysCreateFileMapping(&createMapping); if (status != NTLSTATUS_SUCCESS || !mappingHandle) { (void)SysCloseHandle(fileHandle); return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status; } - status = SysMapFile(mappingHandle, 0, (size_t)fileSize, - SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mappingHandle, SYS_MAP_ACCESS_READ, + SYS_MAP_FLAG_NONE, 0, (size_t)fileSize, &base + }; + status = SysMapFile(&mapFile); (void)SysCloseHandle(mappingHandle); (void)SysCloseHandle(fileHandle); if (status != NTLSTATUS_SUCCESS || !base) @@ -695,7 +698,7 @@ static void DoomAudioReleaseResources(void) { // Starts one process-owned audio stream and its exclusive producer thread. void DoomAudioStart(void) { - uint64_t endpointId = 0; + uint32_t endpointId = 0; uint32_t streamHandle = 0; KSTATUS status; @@ -739,6 +742,7 @@ void DoomAudioStart(void) { SYS_CREATE_THREAD_REQUEST thread; memset(&thread, 0, sizeof(thread)); + thread.Size = sizeof(thread); thread.Entry = (uintptr_t)DoomAudioWorker; thread.Priority = DOOM_AUDIO_WORKER_PRIORITY; thread.ThreadHandleOut = &g_doomAudio.WorkerHandle; diff --git a/code/user/apps/edit/edit.c b/code/user/apps/edit/edit.c index 7ba0191..8aa9542 100644 --- a/code/user/apps/edit/edit.c +++ b/code/user/apps/edit/edit.c @@ -5,6 +5,7 @@ #include #include #include +#include #define GLYPH_W 8 #define GLYPH_H 16 @@ -109,7 +110,11 @@ static void put_cell(int col, int row, char c, uint32_t fg, uint32_t bg) { cell->bg = bg; } - SysPutScreenChar(c, col * GLYPH_W, row * GLYPH_H, fg, bg); + SYS_PUT_SCREEN_CHAR_REQUEST request = { + sizeof(SYS_PUT_SCREEN_CHAR_REQUEST), c, { 0, 0, 0 }, + col * GLYPH_W, row * GLYPH_H, fg, bg + }; + SysPutScreenChar(&request); } // Clears row. @@ -369,12 +374,9 @@ static void move_down(void) { // Updates file. static void save_file(void) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST pathReq = SysMakeOpenFileRequest(SysSvFromCstr(gFilename), - SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, - SYS_FILE_CREATE_ALWAYS, - SYS_FILE_FLAG_NONE, - &h); - uint32_t st = SysOpenFile(&pathReq); + uint32_t st = NtlOpenFile(SysSvFromCstr(gFilename), + SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_ALWAYS, SYS_FILE_FLAG_NONE, &h); if (st != 0) { msg_set("save failed: cannot open file"); return; } SysSeekFile(h, 0); @@ -413,12 +415,8 @@ static void load_file(void) { reset_buffer(); uint32_t h = 0; - SYS_OPEN_FILE_REQUEST pathReq = SysMakeOpenFileRequest(SysSvFromCstr(gFilename), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - if (SysOpenFile(&pathReq) != 0) { + if (NtlOpenFile(SysSvFromCstr(gFilename), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &h) != 0) { msg_set("new file"); return; } @@ -564,7 +562,7 @@ static void editor_loop(void) { // Prints the requested operation. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } diff --git a/code/user/apps/httpget/httpget.c b/code/user/apps/httpget/httpget.c index 3eeed2f..220894c 100644 --- a/code/user/apps/httpget/httpget.c +++ b/code/user/apps/httpget/httpget.c @@ -8,12 +8,12 @@ // Prints the requested operation. static void print(const char* s) { - if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } } // Prints char. static void print_char(char c) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } @@ -167,6 +167,7 @@ static uint32_t resolve_host(const char* host, SYS_SOCKET_ADDRESS* address) { SYS_RESOLVE_HOST_REQUEST req; memset(address, 0, sizeof(*address)); memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(host); req.Family = SYS_AF_INET6; req.AddressOut = address; @@ -268,7 +269,11 @@ int main(void) { size_t totalSent = 0; while (totalSent < off) { size_t sent = 0; - st = SysSendSocket(sock, request + totalSent, off - totalSent, 0, &sent); + SYS_SOCKET_SEND_REQUEST sendRequest = { + sizeof(SYS_SOCKET_SEND_REQUEST), sock, 0, + request + totalSent, off - totalSent, &sent + }; + st = SysSendSocket(&sendRequest); if (st != NTLSTATUS_SUCCESS || sent == 0) { print("httpget: SysSendSocket failed "); print_u64(st); print(" sent="); print_u64(sent); print("\n"); SysCloseHandle(sock); @@ -281,7 +286,11 @@ int main(void) { size_t total = 0; while (true) { size_t got = 0; - st = SysRecvSocket(sock, buf, sizeof(buf) - 1, 0, &got); + SYS_SOCKET_RECV_REQUEST recvRequest = { + sizeof(SYS_SOCKET_RECV_REQUEST), sock, 0, + buf, sizeof(buf) - 1, &got + }; + st = SysRecvSocket(&recvRequest); if (st != NTLSTATUS_SUCCESS) { print("\nhttpget: SysRecvSocket failed "); print_u64(st); print(" after "); print_u64(total); print(" bytes\n"); SysCloseHandle(sock); diff --git a/code/user/apps/memtop/memtop.c b/code/user/apps/memtop/memtop.c index c45f049..447a72e 100644 --- a/code/user/apps/memtop/memtop.c +++ b/code/user/apps/memtop/memtop.c @@ -60,7 +60,11 @@ int main(void) { // Loop through the string and use the console's dedicated text filling API // This utilizes the console's native font configuration instead of drawing raw screen bits for (size_t i = 0; i < textLen; i++) { - SysFillConsoleCharacter(metricsBuf[i], targetX + (int)i, targetY, 1, &written); + SYS_FILL_CONSOLE_CHARACTER_REQUEST request = { + sizeof(SYS_FILL_CONSOLE_CHARACTER_REQUEST), metricsBuf[i], { 0, 0, 0 }, + targetX + (int)i, targetY, 1, &written + }; + SysFillConsoleCharacter(&request); } } } diff --git a/code/user/apps/ping/ping.c b/code/user/apps/ping/ping.c index 9c71a46..b77f982 100644 --- a/code/user/apps/ping/ping.c +++ b/code/user/apps/ping/ping.c @@ -15,12 +15,12 @@ // Prints the requested operation. static void print(const char* s) { - if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } } // Prints char. static void print_char(char c) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } @@ -140,6 +140,7 @@ static uint32_t resolve_host(const char* host, uint8_t ip[4]) { SYS_RESOLVE_HOST_REQUEST req; memset(&resolved, 0, sizeof(resolved)); memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(host); req.Family = SYS_AF_INET; req.AddressOut = &resolved; @@ -276,6 +277,7 @@ int main(void) { uint32_t timeout = PING_TIMEOUT_MS; SYS_SOCKET_OPTION_REQUEST opt; memset(&opt, 0, sizeof(opt)); + opt.Size = sizeof(opt); opt.Option = SYS_SOCKET_OPT_RECV_TIMEOUT_MS; opt.Value = &timeout; opt.ValueSize = sizeof(timeout); @@ -315,6 +317,7 @@ int main(void) { size_t sent = 0; SYS_SOCKET_SEND_TO_REQUEST sendReq; memset(&sendReq, 0, sizeof(sendReq)); + sendReq.Size = sizeof(sendReq); sendReq.Buffer = packet; sendReq.Length = sizeof(packet); sendReq.RemoteAddress = &remote; @@ -339,6 +342,7 @@ int main(void) { size_t got = 0; SYS_SOCKET_RECV_FROM_REQUEST recvReq; memset(&recvReq, 0, sizeof(recvReq)); + recvReq.Size = sizeof(recvReq); recvReq.Buffer = recvBuf; recvReq.Length = sizeof(recvBuf); recvReq.RemoteAddressOut = &from; diff --git a/code/user/apps/resolve/resolve.c b/code/user/apps/resolve/resolve.c index d33cbad..b6e6c42 100644 --- a/code/user/apps/resolve/resolve.c +++ b/code/user/apps/resolve/resolve.c @@ -7,12 +7,12 @@ // Prints the requested operation. static void print(const char* s) { - if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } } // Prints char. static void print_char(char c) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } @@ -103,6 +103,7 @@ int main(void) { SYS_RESOLVE_HOST_REQUEST req; memset(&addr, 0, sizeof(addr)); memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(name); req.Family = family ? family : SYS_AF_INET6; req.AddressOut = &addr; diff --git a/code/user/apps/shell_native/shell_native.c b/code/user/apps/shell_native/shell_native.c index caefe96..0229f93 100644 --- a/code/user/apps/shell_native/shell_native.c +++ b/code/user/apps/shell_native/shell_native.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #define MAX_CMD 256 @@ -20,50 +22,55 @@ #define SHELL_CURSOR_BACKGROUND 0x00E0E0E0u // Output helpers. -static void print(const char* s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } +static void print(const char* s) { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } // Opens file. static uint32_t open_file(const char* path, uint32_t* handleOut) { - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - handleOut); - return SysOpenFile(&req); + return NtlOpenFile(SysSvFromCstr(path), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, handleOut); } // Creates file. static uint32_t create_file(const char* path, uint32_t* handleOut) { - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), - SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, - SYS_FILE_CREATE_ALWAYS, - SYS_FILE_FLAG_NONE, - handleOut); - return SysOpenFile(&req); + return NtlOpenFile(SysSvFromCstr(path), SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_ALWAYS, SYS_FILE_FLAG_NONE, handleOut); } // Opens dir. static uint32_t open_dir(const char* path, uint32_t* handleOut) { - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; - return SysOpenDir(&req); + return NtlOpenDir(SysSvFromCstr(path), SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN, + SYS_DIR_FLAG_NONE, handleOut); } // Updates cwd. static uint32_t set_cwd(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - return SysSetCwd(&req); + return NtlSetCurrentDirectory(SysSvFromCstr(path)); } // Releases file. static uint32_t delete_file(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - return SysDeleteFile(&req); + return NtlDeleteFile(SysSvFromCstr(path)); } // Creates process. static uint32_t create_process(const char* path, uint32_t* handleOut, const char* cmdline) { - SYS_CREATE_PROCESS_REQUEST req = { SysSvFromCstr(path), SysSvFromCstr(cmdline), handleOut }; - return SysCreateProcess(&req); + uint32_t imageFile = SYS_HANDLE_INVALID; + uint32_t status = open_file(path, &imageFile); + if (status != NTLSTATUS_SUCCESS) return status; + + NTL_CREATE_SYSTEM_PROCESS_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + req.ImageFile = imageFile; + req.Path = SysSvFromCstr(path); + req.CommandLine = SysSvFromCstr(cmdline); + req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + req.ResolveModuleName = NtlResolveSystemModuleName; + req.ProcessHandleOut = handleOut; + status = NtlCreateSystemProcess(&req); + (void)SysCloseHandle(imageFile); + return status; } // Prints u Int. @@ -306,11 +313,13 @@ static void shellHistoryAdd(const char* cmd) { static void shellEditorHideVisibleCursor(SHELL_VISIBLE_CURSOR* cursor) { if (!cursor || !cursor->Visible) return; - (void)SysPutScreenChar(cursor->Character, - cursor->Column * SHELL_GLYPH_WIDTH, - cursor->Row * SHELL_GLYPH_HEIGHT, - SHELL_TEXT_FOREGROUND, - SHELL_TEXT_BACKGROUND); + SYS_PUT_SCREEN_CHAR_REQUEST request = { + sizeof(SYS_PUT_SCREEN_CHAR_REQUEST), cursor->Character, { 0, 0, 0 }, + cursor->Column * SHELL_GLYPH_WIDTH, + cursor->Row * SHELL_GLYPH_HEIGHT, + SHELL_TEXT_FOREGROUND, SHELL_TEXT_BACKGROUND + }; + (void)SysPutScreenChar(&request); cursor->Visible = false; } @@ -325,11 +334,13 @@ static void shellEditorShowVisibleCursor(SHELL_VISIBLE_CURSOR* cursor, const SHE char character = editor->Cursor < editor->Length ? editor->Buffer[editor->Cursor] : ' '; - if (SysPutScreenChar(character, - (int)info.CursorX * SHELL_GLYPH_WIDTH, - (int)info.CursorY * SHELL_GLYPH_HEIGHT, - SHELL_CURSOR_FOREGROUND, - SHELL_CURSOR_BACKGROUND) != NTLSTATUS_SUCCESS) + SYS_PUT_SCREEN_CHAR_REQUEST request = { + sizeof(SYS_PUT_SCREEN_CHAR_REQUEST), character, { 0, 0, 0 }, + (int)info.CursorX * SHELL_GLYPH_WIDTH, + (int)info.CursorY * SHELL_GLYPH_HEIGHT, + SHELL_CURSOR_FOREGROUND, SHELL_CURSOR_BACKGROUND + }; + if (SysPutScreenChar(&request) != NTLSTATUS_SUCCESS) return; cursor->Column = (int)info.CursorX; @@ -775,7 +786,7 @@ static void printPagesKiB(uint64_t pages) { // Prints cpu Flags. static void printCpuFlags(uint32_t flags) { - if (flags & SYS_CPU_FLAG_BSP) print("bsp "); + if (flags & SYS_CPU_FLAG_BOOT_PROCESSOR) print("boot "); if (flags & SYS_CPU_FLAG_ONLINE) print("online "); if (flags & SYS_CPU_FLAG_PARKED) print("parked "); if (flags & SYS_CPU_FLAG_PRIMARY) print("primary "); @@ -1078,7 +1089,7 @@ static void cmd_stats(void) { for (uint32_t i = 0; i < n; i++) { const SYS_CPU_STAT* c = &cpuRecords[i]; print(" cpu"); printUInt(c->CpuIndex); - print(" lapic="); printUInt(c->LapicId); + print(" hardware="); printUInt(c->HardwareId); print(" current="); if (c->CurrentThreadFlags & SYS_CPU_THREAD_FLAG_IDLE) { print("idle/"); printUInt(c->CpuIndex); @@ -1621,6 +1632,7 @@ static const char* processStateName(uint32_t state) { case SYS_PROCESS_STATE_RUNNING: return "run"; case SYS_PROCESS_STATE_TERMINATING: return "term"; case SYS_PROCESS_STATE_EXITED: return "exit"; + case SYS_PROCESS_STATE_CONSTRUCTING: return "build"; default: return "?"; } } @@ -1691,7 +1703,7 @@ static void cmd_ps(void) { print("Processes (Count: "); printUInt((uint32_t)count); print("):\n"); - print(" PID State Thr Hnd Mod UserKiB ImageKiB Flags Name\n"); + print(" PID State Thr Hnd UserKiB ImageKiB Flags Name\n"); print(" Flags: S=system I=idle P=protected *=current T=terminating X=exited.\n"); size_t displayCount = count < MAX_PROCS ? count : MAX_PROCS; @@ -1718,8 +1730,6 @@ static void cmd_ps(void) { print(" "); printUIntWidth(p->HandleCount, 3); print(" "); - printUIntWidth(p->ModuleCount, 3); - print(" "); printUInt64Width((uint64_t)p->UserCommittedBytes / 1024u, 7); print(" "); printUInt64Width((uint64_t)p->ImageSize / 1024u, 8); @@ -1727,10 +1737,6 @@ static void cmd_ps(void) { printStringWidth(flags, 5); print(" "); print(p->Name); - if (p->CurrentDirectory[0]) { - print(" cwd="); - print(p->CurrentDirectory); - } print("\n"); } @@ -1813,7 +1819,7 @@ static void cmd_kill(const char* args) { if (!parseUInt(args, &pid)) { print("kill: usage: kill \n"); return; } uint32_t hProc = 0; - uint32_t status = SysOpenProcess(pid, &hProc); + uint32_t status = SysOpenProcess(pid, SYS_PROCESS_RIGHT_TERMINATE, &hProc); if (status == 0) { status = SysKillProcess(hProc, (int32_t)NTLSTATUS_PROCESS_KILLED); SysCloseHandle(hProc); @@ -2094,7 +2100,7 @@ static int runCommand(const char* cmd) { } else if (strcmp(cmd + 6, "noncanonical") == 0) { // Non-canonical pointer dereference. - volatile int *ptr = (volatile int*)0x4242424242424242ULL; + volatile int* ptr = (volatile int*)0x4242424242424242ULL; *ptr = 1337; return 0; } diff --git a/code/user/apps/shell_posix/shell_posix.c b/code/user/apps/shell_posix/shell_posix.c index 49f8b9b..3429c20 100644 --- a/code/user/apps/shell_posix/shell_posix.c +++ b/code/user/apps/shell_posix/shell_posix.c @@ -12,17 +12,17 @@ typedef struct dirent { char d_name[NAME_MAX + 1]; unsigned char d_type; } dirent; typedef struct DIR { uint32_t KHandle; dirent Current; bool Valid; } DIR; -extern int read(int fd, void *buf, size_t count); -extern int write(int fd, const void *buf, size_t count); -extern int open(const char *pathname, int flags, ...); +extern int read(int fd, void* buf, size_t count); +extern int write(int fd, const void* buf, size_t count); +extern int open(const char* pathname, int flags, ...); extern int close(int fd); -extern int chdir(const char *path); +extern int chdir(const char* path); extern DIR* opendir(const char* path); extern dirent* readdir(DIR* d); extern int closedir(DIR* d); -extern int unlink(const char *pathname); -extern int posix_spawn(int *pid, const char *path, const void *act, const void *attr, char *const argv[], char *const envp[]); -extern int waitpid(int pid, int *status, int options); +extern int unlink(const char* pathname); +extern int posix_spawn(int* pid, const char* path, const void* act, const void* attr, char* const argv[], char* const envp[]); +extern int waitpid(int pid, int* status, int options); extern void* malloc(size_t size); extern void* realloc(void* ptr, size_t size); extern void free(void* ptr); diff --git a/code/user/apps/tcpserve/tcpserve.c b/code/user/apps/tcpserve/tcpserve.c index a480091..5d67af7 100644 --- a/code/user/apps/tcpserve/tcpserve.c +++ b/code/user/apps/tcpserve/tcpserve.c @@ -8,12 +8,12 @@ // Prints the requested operation. static void print(const char* s) { - if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } } // Prints char. static void print_char(char c) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } @@ -177,7 +177,11 @@ int main(void) { char request[1025]; size_t got = 0; - st = SysRecvSocket(client, request, sizeof(request) - 1, 0, &got); + SYS_SOCKET_RECV_REQUEST recvRequest = { + sizeof(SYS_SOCKET_RECV_REQUEST), client, 0, + request, sizeof(request) - 1, &got + }; + st = SysRecvSocket(&recvRequest); if (st != NTLSTATUS_SUCCESS) { print("tcpserve: SysRecvSocket failed "); print_u64(st); print("\n"); SysCloseHandle(client); @@ -216,9 +220,17 @@ int main(void) { #undef APPEND_LITERAL size_t sent = 0; - st = SysSendSocket(client, response, off, 0, &sent); - if (st == NTLSTATUS_SUCCESS) - st = SysSendSocket(client, body, sizeof(body) - 1, 0, &sent); + SYS_SOCKET_SEND_REQUEST sendResponse = { + sizeof(SYS_SOCKET_SEND_REQUEST), client, 0, response, off, &sent + }; + st = SysSendSocket(&sendResponse); + if (st == NTLSTATUS_SUCCESS) { + SYS_SOCKET_SEND_REQUEST sendBody = { + sizeof(SYS_SOCKET_SEND_REQUEST), client, 0, + body, sizeof(body) - 1, &sent + }; + st = SysSendSocket(&sendBody); + } if (st != NTLSTATUS_SUCCESS) print("tcpserve: SysSendSocket failed "), print_u64(st), print("\n"); diff --git a/code/user/apps/udp/udp.c b/code/user/apps/udp/udp.c index d0eaa85..c750eb3 100644 --- a/code/user/apps/udp/udp.c +++ b/code/user/apps/udp/udp.c @@ -8,12 +8,12 @@ // Prints the requested operation. static void print(const char* s) { - if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } } // Prints char. static void print_char(char c) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } @@ -155,6 +155,7 @@ static uint32_t resolve_address(const char* text, SYS_SOCKET_ADDRESS* out) { size_t addressSize = sizeof(*out); memset(out, 0, sizeof(*out)); memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(text); req.Family = SYS_AF_INET6; req.AddressOut = out; @@ -207,6 +208,7 @@ static int run_send(const char* cmd) { size_t sent = 0; SYS_SOCKET_SEND_TO_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Buffer = msg; req.Length = strlen(msg); req.RemoteAddress = &remote; @@ -270,6 +272,7 @@ static int run_listen(const char* cmd) { size_t got = 0; SYS_SOCKET_RECV_FROM_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Buffer = buf; req.Length = sizeof(buf) - 1; req.RemoteAddressOut = &remote; diff --git a/code/user/apps/video/video.c b/code/user/apps/video/video.c index 70b57ad..3c9d338 100644 --- a/code/user/apps/video/video.c +++ b/code/user/apps/video/video.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "bink.h" #define VIDEO_USAGE "usage: video \n" \ @@ -17,13 +18,13 @@ static void print_span(const char* s, size_t len) { if (!s || !len) return; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(s, len) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(s, len) }; SysWriteConsole(&req); } static void print(const char* s) { if (!s) return; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -158,12 +159,8 @@ static const char* path_to_cstr(SYS_STRING_VIEW path, char* out, size_t outSize) } static uint32_t open_file_view(SYS_STRING_VIEW path, uint32_t* handleOut) { - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(path, - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - handleOut); - return SysOpenFile(&req); + return NtlOpenFile(path, SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, handleOut); } int main(void) { @@ -188,7 +185,7 @@ int main(void) { print("video: failed to open input file: "); print(path_to_cstr(pathView, pathBuf, sizeof(pathBuf))); print("\n"); - fail_status("SysOpenFile", status); + fail_status("NtlOpenFile", status); } uint64_t fileSize = 0; @@ -204,15 +201,22 @@ int main(void) { } uint32_t mapping = 0; - status = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, fileSize, &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ, + SYS_MAP_FLAG_NONE, fileSize, &mapping + }; + status = SysCreateFileMapping(&createMapping); if (status != NTLSTATUS_SUCCESS || !mapping) { SysCloseHandle(file); fail_status("SysCreateFileMapping", status); } void* buf = 0; - status = SysMapFile(mapping, 0, (size_t)fileSize, - SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &buf); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ, + SYS_MAP_FLAG_NONE, 0, (size_t)fileSize, &buf + }; + status = SysMapFile(&mapFile); if (status != NTLSTATUS_SUCCESS || !buf) { SysCloseHandle(mapping); SysCloseHandle(file); diff --git a/code/user/personalities/posix/posix_dir.c b/code/user/personalities/posix/posix_dir.c index 24e3da5..bd72e9b 100644 --- a/code/user/personalities/posix/posix_dir.c +++ b/code/user/personalities/posix/posix_dir.c @@ -2,6 +2,7 @@ #include #include +#include #include #include "posix.h" #include "posix_fd.h" @@ -15,8 +16,7 @@ typedef SYS_DIR_ENTRY KDIR_ENTRY; __declspec(dllexport) int chdir(const char* path) { char native[512]; PosixTranslatePath(path, native, sizeof(native)); - SYS_PATH_REQUEST req = { SysSvFromCstr(native) }; - uint32_t s = SysSetCwd(&req); + uint32_t s = NtlSetCurrentDirectory(SysSvFromCstr(native)); if (s != 0) { errno = ENOENT; return -1; } return 0; } @@ -25,17 +25,15 @@ __declspec(dllexport) int chdir(const char* path) { __declspec(dllexport) char* getcwd(char* buf, size_t size) { if (!buf || size == 0) { errno = EINVAL; return (char*)0; } - SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView(); - if ((cwd.Length && !cwd.Data) || cwd.Length > SYS_CWD_MAX) { + char native[SYS_PATH_MAX + 1u]; + size_t required = 0; + uint32_t status = NtlGetCurrentDirectory(native, sizeof(native), &required); + if (status != NTLSTATUS_SUCCESS) { errno = ERANGE; return (char*)0; } - char native[SYS_CWD_MAX + 1]; - if (cwd.Length) memcpy(native, cwd.Data, cwd.Length); - native[cwd.Length] = '\0'; - - char posix[SYS_CWD_MAX + 1]; + char posix[SYS_PATH_MAX + 1]; NativeToPosix(native, posix, sizeof(posix)); size_t needed = strlen(posix) + 1; @@ -73,31 +71,15 @@ __declspec(dllexport) char* realpath(const char* path, char* resolved_path) { return PosixCopyResolvedPath(path, resolved_path); size_t required = 0; - SYS_REAL_PATH_REQUEST query = { - sizeof(query), - SYS_REAL_PATH_FLAG_NONE, - SysSvFromCstr(translated), - (char*)0, - 0, - &required, - }; - uint32_t status = SysRealPath(&query); + uint32_t status = NtlRealPath(SysSvFromCstr(translated), (char*)0, 0, &required); if (status != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(status); return (char*)0; } - if (required == 0 || required > SYS_CWD_MAX) { errno = ERANGE; return (char*)0; } + if (required == 0 || required > SYS_PATH_MAX + 1u) { errno = ERANGE; return (char*)0; } - char nativeResolved[SYS_CWD_MAX]; - SYS_REAL_PATH_REQUEST request = { - sizeof(request), - SYS_REAL_PATH_FLAG_NONE, - SysSvFromCstr(translated), - nativeResolved, - sizeof(nativeResolved), - &required, - }; - status = SysRealPath(&request); + char nativeResolved[SYS_PATH_MAX + 1u]; + status = NtlRealPath(SysSvFromCstr(translated), nativeResolved, sizeof(nativeResolved), &required); if (status != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(status); return (char*)0; } - char posix[SYS_CWD_MAX]; + char posix[SYS_PATH_MAX]; NativeToPosix(nativeResolved, posix, sizeof(posix)); return PosixCopyResolvedPath(posix, resolved_path); } @@ -107,8 +89,9 @@ __declspec(dllexport) DIR* opendir(const char* path) { char native[512]; PosixTranslatePath(path, native, sizeof(native)); uint32_t kh; - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(native), &kh }; - uint32_t s = SysOpenDir(&req); + uint32_t s = NtlOpenDir(SysSvFromCstr(native), + SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN, + SYS_DIR_FLAG_NONE, &kh); if (s != 0) { errno = ENOENT; return (DIR*)0; } // Allocate DIR on the kernel heap (malloc) so it outlives the stack. @@ -188,8 +171,7 @@ __declspec(dllexport) int stat(const char* path, struct stat* out) { return PosixFillSyntheticStat(S_IFCHR, out); SYS_FILE_STAT nativeStat; - SYS_STAT_FILE_REQUEST req = { SysSvFromCstr(p), &nativeStat }; - uint32_t s = SysStatFile(&req); + uint32_t s = NtlStatPath(SysSvFromCstr(p), &nativeStat); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } return PosixFillStat(&nativeStat, out); } diff --git a/code/user/personalities/posix/posix_dlfcn.c b/code/user/personalities/posix/posix_dlfcn.c index 7130bd0..537e347 100644 --- a/code/user/personalities/posix/posix_dlfcn.c +++ b/code/user/personalities/posix/posix_dlfcn.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "posix.h" #include "posix_path.h" #include "posix_fd.h" @@ -43,22 +45,45 @@ __declspec(dllexport) void* dlopen(const char* filename, int flags) { } char native[512]; - const char* path = PosixTranslatePath(filename, native, sizeof(native)); - if (!path || strcmp(path, SYNTH_DEVNULL) == 0 || strcmp(path, SYNTH_DEVZERO) == 0) { + uint32_t status = PosixProcessPath(filename, native, sizeof(native)); + if (status != NTLSTATUS_SUCCESS) { errno = EINVAL; DlSetError("dlopen path is not a loadable module"); return 0; } + if (!strchr(native, '/')) { + size_t length = strlen(native); + if (length + sizeof("system/") > sizeof(native)) { + errno = ERANGE; + DlSetError("dlopen path is too long"); + return 0; + } + memmove(native + 7, native, length + 1u); + memcpy(native, "system/", 7); + } - uint32_t handle = 0; - SYS_LOAD_MODULE_REQUEST req; + SYS_HANDLE root = SYS_HANDLE_INVALID; + status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &root); + if (status != NTLSTATUS_SUCCESS) { + DlSetStatusError("dlopen system root failed", status); + return 0; + } + + NTL_MODULE_HANDLE handle = NTL_MODULE_HANDLE_INVALID; + NTL_LOAD_MODULE_AT_REQUEST req; memset(&req, 0, sizeof(req)); req.Size = sizeof(req); - req.Path = SysSvFromCstr(path); + req.Flags = NTL_MODULE_LOAD_FLAG_NONE; + req.RootHandle = root; + req.Path = SysSvFromCstr(native); + req.ModuleRootHandle = root; + req.ModuleDirectory = SYS_SV_LITERAL("system"); + req.ResolveModuleName = PosixResolveModuleName; req.ModuleHandleOut = &handle; - uint32_t status = SysLoadModule(&req); - if (status != NTLSTATUS_SUCCESS || handle == 0) { + status = NtlLoadModuleAt(&req); + (void)SysCloseHandle(root); + if (status != NTLSTATUS_SUCCESS || handle == NTL_MODULE_HANDLE_INVALID) { DlSetStatusError("dlopen failed", status); return 0; } @@ -76,14 +101,8 @@ __declspec(dllexport) void* dlsym(void* handle, const char* symbol) { } 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); + uint32_t status = NtlGetModuleExport((NTL_MODULE_HANDLE)(uintptr_t)handle, + SysSvFromCstr(symbol), &address); if (status != NTLSTATUS_SUCCESS || address == 0) { DlSetStatusError("dlsym failed", status); return 0; @@ -101,7 +120,7 @@ __declspec(dllexport) int dlclose(void* handle) { return -1; } - uint32_t status = SysUnloadModule((uint32_t)(uintptr_t)handle); + uint32_t status = NtlUnloadModule((NTL_MODULE_HANDLE)(uintptr_t)handle); if (status != NTLSTATUS_SUCCESS) { DlSetStatusError("dlclose failed", status); return -1; diff --git a/code/user/personalities/posix/posix_io.c b/code/user/personalities/posix/posix_io.c index aa19c8d..f4f4466 100644 --- a/code/user/personalities/posix/posix_io.c +++ b/code/user/personalities/posix/posix_io.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "posix.h" #include "posix_fd.h" #include "posix_path.h" @@ -61,12 +62,8 @@ __declspec(dllexport) int open(const char* path, int flags, ...) { else if (flags & O_TRUNC) disposition = SYS_FILE_TRUNCATE_EXISTING; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(p), - access, - disposition, - SYS_FILE_FLAG_NONE, - &kh); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(p), access, disposition, + SYS_FILE_FLAG_NONE, &kh); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } int fd = FdAlloc(pid, type, kh); @@ -109,7 +106,11 @@ __declspec(dllexport) ssize_t read(int fd, void* buf, size_t count) { } case FD_SOCKET: { size_t got = 0; - uint32_t s = SysRecvSocket(e->KHandle, buf, count, 0, &got); + SYS_SOCKET_RECV_REQUEST request = { + sizeof(SYS_SOCKET_RECV_REQUEST), e->KHandle, 0, + buf, count, &got + }; + uint32_t s = SysRecvSocket(&request); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } return (ssize_t)got; } @@ -128,7 +129,7 @@ __declspec(dllexport) ssize_t write(int fd, const void* buf, size_t count) { switch (e->Type) { case FD_CONSOLE_W: { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts((const char*)buf, count) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts((const char*)buf, count) }; SysWriteConsole(&req); return (ssize_t)count; } @@ -144,7 +145,11 @@ __declspec(dllexport) ssize_t write(int fd, const void* buf, size_t count) { } case FD_SOCKET: { size_t sent = 0; - uint32_t s = SysSendSocket(e->KHandle, buf, count, 0, &sent); + SYS_SOCKET_SEND_REQUEST request = { + sizeof(SYS_SOCKET_SEND_REQUEST), e->KHandle, 0, + buf, count, &sent + }; + uint32_t s = SysSendSocket(&request); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } return (ssize_t)sent; } @@ -177,8 +182,7 @@ __declspec(dllexport) int unlink(const char* path) { return -1; } - SYS_PATH_REQUEST req = { SysSvFromCstr(p) }; - uint32_t s = SysDeleteFile(&req); + uint32_t s = NtlDeleteFile(SysSvFromCstr(p)); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } return 0; } diff --git a/code/user/personalities/posix/posix_mman.c b/code/user/personalities/posix/posix_mman.c index 6692cf4..1fafff4 100644 --- a/code/user/personalities/posix/posix_mman.c +++ b/code/user/personalities/posix/posix_mman.c @@ -50,17 +50,23 @@ __declspec(dllexport) void* mmap(void* addr, size_t length, int prot, protect = (prot & PROT_WRITE) ? SYS_VM_EXECUTE_READWRITE : SYS_VM_EXECUTE_READ; uint32_t mapping = 0; - uint32_t s = SysCreateFileMapping(e->KHandle, protect, SYS_MAP_FLAG_NONE, - maxSize, &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), e->KHandle, protect, + SYS_MAP_FLAG_NONE, maxSize, &mapping + }; + uint32_t s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(s); return MAP_FAILED; } void* base = (void*)0; - s = SysMapFile(mapping, (uint64_t)offset, length, - PosixProtToSysMapAccess(prot, flags), - SYS_MAP_FLAG_NONE, &base); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mapping, + PosixProtToSysMapAccess(prot, flags), SYS_MAP_FLAG_NONE, + (uint64_t)offset, length, &base + }; + s = SysMapFile(&mapFile); SysCloseHandle(mapping); if (s != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(s); diff --git a/code/user/personalities/posix/posix_path.c b/code/user/personalities/posix/posix_path.c index 3fa666d..60ef292 100644 --- a/code/user/personalities/posix/posix_path.c +++ b/code/user/personalities/posix/posix_path.c @@ -1,76 +1,90 @@ -// POSIX compatibility support for posix path. +// POSIX compatibility support for path translation. #include #include +#include #include "posix_path.h" -// Checks whether drive Mount. -static int IsDriveMount(const char* p) { - return p && p[0] == '/' && p[1] >= '0' && p[1] <= '7' && - (p[2] == '\0' || p[2] == '/'); -} +#define POSIX_SYSTEM_ROOT "/SystemRoot" +// Translates POSIX filesystem syntax into the native resource namespace. const char* PosixTranslatePath(const char* in, char* out, size_t outSize) { - if (!in || !out || outSize < 4) return out; + if (!in || !out || outSize == 0) return out; if (strcmp(in, "/dev/null") == 0) return SYNTH_DEVNULL; if (strcmp(in, "/dev/zero") == 0) return SYNTH_DEVZERO; - if (IsDriveMount(in)) { - out[0] = in[1]; - out[1] = ':'; - out[2] = '/'; - const char* rest = (in[2] == '/') ? in + 3 : ""; - size_t restLen = strlen(rest); - if (restLen + 4 > outSize) restLen = outSize - 4; - memcpy(out + 3, rest, restLen); - out[3 + restLen] = '\0'; + if (in[0] == '/') { + size_t prefixLength = sizeof(POSIX_SYSTEM_ROOT) - 1u; + size_t restLength = strlen(in); + if (prefixLength + restLength + 1u > outSize) { + out[0] = '\0'; + return out; + } + memcpy(out, POSIX_SYSTEM_ROOT, prefixLength); + memcpy(out + prefixLength, in, restLength + 1u); return out; } - if (in[0] == '/') { - out[0] = '0'; - out[1] = ':'; - out[2] = '/'; - size_t rest = strlen(in + 1); - if (rest + 4 > outSize) rest = outSize - 4; - memcpy(out + 3, in + 1, rest); - out[3 + rest] = '\0'; - } else { - size_t len = strlen(in); - if (len + 1 > outSize) len = outSize - 1; - memcpy(out, in, len); - out[len] = '\0'; + size_t length = strlen(in); + if (length + 1u > outSize) { + out[0] = '\0'; + return out; } - + memcpy(out, in, length + 1u); return out; } -// Implements the native To Posix operation. +// Translates a canonical native SystemRoot path back to POSIX syntax. void NativeToPosix(const char* in, char* out, size_t outSize) { if (!in || !out || outSize == 0) return; + size_t prefixLength = sizeof(POSIX_SYSTEM_ROOT) - 1u; const char* src = in; - char drive = 0; - if (src[0] >= '0' && src[0] <= '7' && src[1] == ':' && - (src[2] == '\\' || src[2] == '/')) { - drive = src[0]; - src += 3; + if (strncmp(src, POSIX_SYSTEM_ROOT, prefixLength) == 0 && + (src[prefixLength] == '\0' || src[prefixLength] == '/')) { + src += prefixLength; + if (*src == '\0') src = "/"; } - size_t pos = 0; - if (drive && drive != '0') { - if (outSize < 4) { out[0] = '\0'; return; } - out[pos++] = '/'; - out[pos++] = drive; - if (*src) out[pos++] = '/'; - } else { - out[pos++] = '/'; + size_t length = strlen(src); + if (length + 1u > outSize) { + out[0] = '\0'; + return; } - - while (*src && pos + 1 < outSize) { - out[pos++] = (*src == '\\') ? '/' : *src; - src++; - } - out[pos] = '\0'; + memcpy(out, src, length + 1u); +} + +// Maps POSIX executable syntax into native resource/CWD syntax. +KSTATUS PosixProcessPath(const char* in, char* out, size_t outSize) { + if (!in || !out || outSize == 0) return NTLSTATUS_INVALID_PARAMETER; + const char* translated = PosixTranslatePath(in, out, outSize); + if (translated != out || out[0] == '\0') return NTLSTATUS_INVALID_PARAMETER; + return NTLSTATUS_SUCCESS; +} + +static int PosixViewEqualsI(SYS_STRING_VIEW view, const char* text) { + size_t length = strlen(text); + if (view.Length != length) return 0; + for (size_t i = 0; i < length; ++i) { + char a = view.Data[i]; + char b = text[i]; + if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a'); + if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a'); + if (a != b) return 0; + } + return 1; +} + +// POSIX owns the msvcrt compatibility alias; all other import names are native. +KSTATUS PosixResolveModuleName(void* context, SYS_STRING_VIEW requested, SYS_STRING_VIEW* canonicalOut) { + (void)context; + if (!canonicalOut || !SysSvIsValid(requested) || requested.Length == 0) + return NTLSTATUS_INVALID_PARAMETER; + if (PosixViewEqualsI(requested, "msvcrt.dll")) { + *canonicalOut = SYS_SV_LITERAL("posix.dll"); + return NTLSTATUS_SUCCESS; + } + *canonicalOut = requested; + return NTLSTATUS_SUCCESS; } diff --git a/code/user/personalities/posix/posix_path.h b/code/user/personalities/posix/posix_path.h index 795b7e0..0394421 100644 --- a/code/user/personalities/posix/posix_path.h +++ b/code/user/personalities/posix/posix_path.h @@ -1,6 +1,7 @@ // POSIX compatibility support for posix path. #pragma once +#include // posix_path.h - POSIX <-> native path translation. // SYNTHETIC sentinel returned from PosixTranslatePath for /dev/null and @@ -16,3 +17,9 @@ const char* PosixTranslatePath(const char* in, char* out, size_t outSize); // Translate a native kernel path back to a POSIX-style path. // Strips the drive letter prefix and flips backslashes. void NativeToPosix(const char* in, char* out, size_t outSize); + +// Maps POSIX executable syntax to a canonical path relative to /SystemRoot. +KSTATUS PosixProcessPath(const char* in, char* out, size_t outSize); + +// Maps POSIX/CRT import aliases to their canonical system personality module. +KSTATUS PosixResolveModuleName(void* context, SYS_STRING_VIEW requested, SYS_STRING_VIEW* canonicalOut); diff --git a/code/user/personalities/posix/posix_proc.c b/code/user/personalities/posix/posix_proc.c index 91ecbd7..75d7e17 100644 --- a/code/user/personalities/posix/posix_proc.c +++ b/code/user/personalities/posix/posix_proc.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "posix.h" #include "posix_path.h" @@ -93,7 +95,13 @@ __declspec(dllexport) int posix_spawn(pid_t* pidOut, (void)file_actions; (void)attrp; (void)envp; char native[512]; - PosixTranslatePath(path, native, sizeof(native)); + uint32_t pathStatus = PosixProcessPath(path, native, sizeof(native)); + if (pathStatus != NTLSTATUS_SUCCESS) { errno = ENOENT; return -1; } + + SYS_HANDLE imageFile = SYS_HANDLE_INVALID; + pathStatus = NtlOpenFile(SysSvFromCstr(native), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &imageFile); + if (pathStatus != NTLSTATUS_SUCCESS) { errno = ENOENT; return -1; } char cmdline[512]; BuildCmdline(argv, cmdline, sizeof(cmdline)); @@ -106,9 +114,19 @@ __declspec(dllexport) int posix_spawn(pid_t* pidOut, cmdline[len] = '\0'; } - uint32_t procHandle; - SYS_CREATE_PROCESS_REQUEST req = { SysSvFromCstr(native), SysSvFromCstr(cmdline), &procHandle }; - uint32_t s = SysCreateProcess(&req); + uint32_t procHandle = 0; + NTL_CREATE_SYSTEM_PROCESS_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + req.ImageFile = imageFile; + req.Path = SysSvFromCstr(native); + req.CommandLine = SysSvFromCstr(cmdline); + req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + req.ResolveModuleName = PosixResolveModuleName; + req.ProcessHandleOut = &procHandle; + uint32_t s = NtlCreateSystemProcess(&req); + if (imageFile != SYS_HANDLE_INVALID) (void)SysCloseHandle(imageFile); if (s != 0) { errno = ENOENT; return -1; } uint32_t kpid = 0; diff --git a/code/user/personalities/posix/posix_socket.c b/code/user/personalities/posix/posix_socket.c index 2b4c1ec..c8d1a08 100644 --- a/code/user/personalities/posix/posix_socket.c +++ b/code/user/personalities/posix/posix_socket.c @@ -363,7 +363,11 @@ __declspec(dllexport) ssize_t send(int fd, const void* buf, size_t len, int flag FD_ENTRY* e = SocketFdGet(fd); if (!e) return -1; size_t sent = 0; - uint32_t s = SysSendSocket(e->KHandle, buf, len, (uint32_t)flags, &sent); + SYS_SOCKET_SEND_REQUEST request = { + sizeof(SYS_SOCKET_SEND_REQUEST), e->KHandle, (uint32_t)flags, + buf, len, &sent + }; + uint32_t s = SysSendSocket(&request); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } return (ssize_t)sent; } @@ -373,7 +377,11 @@ __declspec(dllexport) ssize_t recv(int fd, void* buf, size_t len, int flags) { FD_ENTRY* e = SocketFdGet(fd); if (!e) return -1; size_t got = 0; - uint32_t s = SysRecvSocket(e->KHandle, buf, len, (uint32_t)flags, &got); + SYS_SOCKET_RECV_REQUEST request = { + sizeof(SYS_SOCKET_RECV_REQUEST), e->KHandle, (uint32_t)flags, + buf, len, &got + }; + uint32_t s = SysRecvSocket(&request); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } return (ssize_t)got; } @@ -388,6 +396,7 @@ __declspec(dllexport) ssize_t sendto(int fd, const void* buf, size_t len, int fl size_t sent = 0; SYS_SOCKET_SEND_TO_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Buffer = buf; req.Length = len; req.Flags = (uint32_t)flags; @@ -410,6 +419,7 @@ __declspec(dllexport) ssize_t recvfrom(int fd, void* buf, size_t len, int flags, size_t got = 0; SYS_SOCKET_RECV_FROM_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Buffer = buf; req.Length = len; req.Flags = (uint32_t)flags; @@ -463,6 +473,7 @@ __declspec(dllexport) int setsockopt(int fd, int level, int optname, const void* SYS_SOCKET_OPTION_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Option = option; req.Value = &value; req.ValueSize = sizeof(value); @@ -487,6 +498,7 @@ __declspec(dllexport) int getsockopt(int fd, int level, int optname, void* optva size_t actual = sizeof(value); SYS_SOCKET_OPTION_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Option = option; req.Value = &value; req.ValueSize = sizeof(value); @@ -605,6 +617,7 @@ static int ResolveNameFamily(const char* name, int family, SYS_SOCKET_ADDRESS* o size_t size = sizeof(*out); SYS_RESOLVE_HOST_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(name); req.Family = family == AF_INET6 ? SYS_AF_INET6 : SYS_AF_INET; req.AddressOut = out; diff --git a/code/user/personalities/posix/posix_thread.c b/code/user/personalities/posix/posix_thread.c index 6334509..2e49d5c 100644 --- a/code/user/personalities/posix/posix_thread.c +++ b/code/user/personalities/posix/posix_thread.c @@ -86,7 +86,7 @@ __declspec(dllexport) int pthread_create(pthread_t* t, const pthread_attr_t* att uint32_t tid = 0; uint32_t handle = 0; SYS_CREATE_THREAD_REQUEST req = { - (uintptr_t)PthreadTrampoline, + sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)PthreadTrampoline, (uintptr_t)tr, 1, 0, diff --git a/code/user/personalities/win32/gdi32.c b/code/user/personalities/win32/gdi32.c index 77b9a3d..1bfd3ad 100644 --- a/code/user/personalities/win32/gdi32.c +++ b/code/user/personalities/win32/gdi32.c @@ -64,7 +64,7 @@ static int currentXMapCapacity = 0; __declspec(dllexport) int StretchDIBits(HDC hdc, int xDest, int yDest, int DestWidth, int DestHeight, int xSrc, int ySrc, int SrcWidth, int SrcHeight, - const void *lpBits, const BITMAPINFO *lpbmi, + const void* lpBits, const BITMAPINFO* lpbmi, UINT iUsage, DWORD rop) { (void)hdc; (void)xDest; (void)yDest; (void)DestWidth; (void)DestHeight; diff --git a/code/user/personalities/win32/kernel32.c b/code/user/personalities/win32/kernel32.c index c87d8d4..ef6002d 100644 --- a/code/user/personalities/win32/kernel32.c +++ b/code/user/personalities/win32/kernel32.c @@ -13,8 +13,8 @@ // multiple heaps, and NULL is a legal (no-op) heap argument under Win32. // - Win32 per-thread state lives behind SYS_TEB::SubsystemData so the native // TEB ABI stays subsystem-neutral. -// - File I/O uses the flags-based SYS_OPEN_FILE request plus the common -// read/write/seek/query/close syscalls. +// - File I/O resolves Win32 paths in userspace, then uses capability-relative +// opens plus the common read/write/seek/query/close syscalls. #include #include @@ -24,6 +24,10 @@ #include #include #include +#include +#include +#include +#include extern size_t _malloc_usable_size(const void* ptr); @@ -235,37 +239,33 @@ static inline BOOL IS_VALID_KERNEL_HANDLE(HANDLE h) { return v != 0 && v != (uintptr_t)INVALID_HANDLE_VALUE; } -// Path translator. +// Translates Win32 path syntax into native resource/CWD syntax. static void TranslatePath(const char* input, char* output) { if (!output) return; if (!input) { output[0] = '\0'; return; } - if (input[0] != '\0' && input[1] == ':' && (input[2] == '\\' || input[2] == '/')) { - // Translate "C:\" -> "0:/". Native nt-lite paths such as - // "0:/system/foo.dll" are already drive-indexed and pass through. - char drive = input[0]; - if (drive >= 'a' && drive <= 'z') drive -= ('a' - 'A'); // To uppercase - - output[0] = (drive >= '0' && drive <= '7') ? drive : (char)('0' + (drive - 'C')); - output[1] = ':'; - output[2] = '/'; - - // Copy the rest, swapping backslashes to forward slashes. - const char* src = input + 3; - char* dst = output + 3; - while (*src) { - *dst = (*src == '\\') ? '/' : *src; - src++; dst++; - } - *dst = '\0'; - } else { - // Fallback for relative paths: just swap slashes. - while (*input) { - *output = (*input == '\\') ? '/' : *input; - input++; output++; - } - *output = '\0'; + const char* src = input; + char* dst = output; + if (src[0] != '\0' && src[1] == ':' && (src[2] == '\\' || src[2] == '/')) { + char drive = src[0]; + if (drive >= 'a' && drive <= 'z') drive = (char)(drive - ('a' - 'A')); + if (drive != 'C') { output[0] = '\0'; return; } + memcpy(dst, "/SystemRoot", 11u); + dst += 11u; + src += 3; + if (*src) *dst++ = '/'; + } else if (*src == '\\' || *src == '/') { + memcpy(dst, "/SystemRoot", 11u); + dst += 11u; + while (*src == '\\' || *src == '/') src++; + if (*src) *dst++ = '/'; } + + while (*src) { + *dst++ = (*src == '\\') ? '/' : *src; + src++; + } + *dst = '\0'; } // Implements the k32 Open File Native operation. @@ -273,60 +273,79 @@ static uint32_t K32OpenFileNative(const char* path, uint32_t access, uint32_t disposition, uint32_t* handleOut) { - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), - access, - disposition, - SYS_FILE_FLAG_NONE, - handleOut); - return SysOpenFile(&req); + return NtlOpenFile(SysSvFromCstr(path), access, disposition, SYS_FILE_FLAG_NONE, handleOut); } // Implements the k32 Delete File Native operation. static uint32_t K32DeleteFileNative(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - return SysDeleteFile(&req); + return NtlDeleteFile(SysSvFromCstr(path)); } // Implements the k32 Open Dir Native operation. static uint32_t K32OpenDirNative(const char* path, uint32_t* handleOut) { - SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; - return SysOpenDir(&req); + return NtlOpenDir(SysSvFromCstr(path), SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN, + SYS_DIR_FLAG_NONE, handleOut); } // Implements the k32 Set Cwd Native operation. static uint32_t K32SetCwdNative(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - return SysSetCwd(&req); + return NtlSetCurrentDirectory(SysSvFromCstr(path)); } // Implements the k32 Get Cwd Native operation. static uint32_t K32GetCwdNative(char* buffer, size_t capacity, size_t* lengthOut) { - SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView(); - if ((cwd.Length && !cwd.Data) || cwd.Length > SYS_CWD_MAX) return NTLSTATUS_INVALID_PARAMETER; - if (lengthOut) *lengthOut = cwd.Length; - if (!buffer || cwd.Length + 1 > capacity) return NTLSTATUS_BUFFER_TOO_SMALL; + size_t required = 0; + uint32_t status = NtlGetCurrentDirectory(buffer, capacity, &required); + if (lengthOut) *lengthOut = required ? required - 1u : 0; + return status; +} - if (cwd.Length) memcpy(buffer, cwd.Data, cwd.Length); - buffer[cwd.Length] = '\0'; +// Resolves packaged personality import aliases entirely in userspace. +static KSTATUS K32ResolveModuleName(void* context, SYS_STRING_VIEW requested, SYS_STRING_VIEW* canonicalOut) { + return NtlResolveSystemModuleName(context, requested, canonicalOut); +} + +// Translates Win32 process syntax into native resource/CWD syntax. +static uint32_t K32ProcessPath(const char* input, char* output, size_t outputSize) { + if (!input || !output || outputSize == 0) return NTLSTATUS_INVALID_PARAMETER; + char translated[MAX_PATH]; + TranslatePath(input, translated); + if (!translated[0]) return NTLSTATUS_NOT_FOUND; + size_t length = strlen(translated); + if (length + 1u > outputSize) return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(output, translated, length + 1u); return NTLSTATUS_SUCCESS; } -// Implements the k32 Create Process Native operation. -static uint32_t K32CreateProcessNative(const char* path, uint32_t* handleOut, const char* commandLine) { - SYS_CREATE_PROCESS_REQUEST req = { SysSvFromCstr(path), SysSvFromCstr(commandLine), handleOut }; - return SysCreateProcess(&req); +// Creates one Win32 child through RSSRV lookup plus the userspace PE loader. +static uint32_t K32CreateProcessNative(const char* path, uint32_t imageFile, uint32_t* handleOut, const char* commandLine, const char* currentDirectory) { + char relative[MAX_PATH]; + uint32_t status = K32ProcessPath(path, relative, sizeof(relative)); + if (status != NTLSTATUS_SUCCESS) return status; + NTL_CREATE_SYSTEM_PROCESS_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + req.ImageFile = imageFile; + req.Path = SysSvFromCstr(relative); + req.CommandLine = SysSvFromCstr(commandLine); + req.CurrentDirectory = currentDirectory ? SysSvFromCstr(currentDirectory) : SysSvFromParts((const char*)0, 0); + req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + req.ResolveModuleName = K32ResolveModuleName; + req.ProcessHandleOut = handleOut; + return NtlCreateSystemProcess(&req); } // Implements the k32 Write Console Span operation. static void K32WriteConsoleSpan(const char* data, size_t length) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(data, length) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(data, length) }; SysWriteConsole(&req); } // Implements the k32 Debug Print A operation. static void K32DebugPrintA(const char* s) { if (!s) return; - SYS_PRINT_DEBUG_REQUEST req = { SysSvFromCstr(s) }; + SYS_PRINT_DEBUG_REQUEST req = { sizeof(SYS_PRINT_DEBUG_REQUEST), SysSvFromCstr(s) }; SysPrintDebug(&req); } @@ -657,11 +676,12 @@ __declspec(dllexport) HANDLE CreateFileMappingA(HANDLE hFile, uint64_t maximumSize = ((uint64_t)dwMaximumSizeHigh << 32) | (uint64_t)dwMaximumSizeLow; uint32_t kh = 0; - uint32_t s = SysCreateFileMapping(HANDLE_TO_KH(hFile), - WinFileMappingProtectToSysVm(flProtect), - SYS_MAP_FLAG_NONE, - maximumSize, - &kh); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), HANDLE_TO_KH(hFile), + WinFileMappingProtectToSysVm(flProtect), SYS_MAP_FLAG_NONE, + maximumSize, &kh + }; + uint32_t s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) { gLastError = (s == NTLSTATUS_NOT_SUPPORTED) ? 120 : ((s == NTLSTATUS_NO_MEMORY) ? 8 : 87); return NULL; @@ -692,12 +712,12 @@ __declspec(dllexport) void* MapViewOfFile(HANDLE hFileMappingObject, uint64_t offset = ((uint64_t)dwFileOffsetHigh << 32) | (uint64_t)dwFileOffsetLow; void* base = NULL; - uint32_t s = SysMapFile(HANDLE_TO_KH(hFileMappingObject), - offset, - dwNumberOfBytesToMap, - WinFileMapAccessToSys(dwDesiredAccess), - SYS_MAP_FLAG_NONE, - &base); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), HANDLE_TO_KH(hFileMappingObject), + WinFileMapAccessToSys(dwDesiredAccess), SYS_MAP_FLAG_NONE, + offset, dwNumberOfBytesToMap, &base + }; + uint32_t s = SysMapFile(&mapFile); if (s != NTLSTATUS_SUCCESS) { gLastError = (s == NTLSTATUS_NOT_SUPPORTED) ? 120 : ((s == NTLSTATUS_NO_MEMORY) ? 8 : 87); return NULL; @@ -869,7 +889,7 @@ __declspec(dllexport) HANDLE CreateFileA(const char* name, DWORD flags, void* templ) { (void)share; (void)sa; (void)flags; (void)templ; - // Translate the path from Windows format to custom 0:/ format. + // Translate Win32 path syntax into the native resource/CWD form. char translatedName[260]; TranslatePath(name, translatedName); @@ -1364,24 +1384,46 @@ __declspec(dllexport) HANDLE GetModuleHandleW(const unsigned short* lpModuleName return lpModuleName ? NULL : WIN32_HMODULE_EXE; } -// Loads a user-mode DLL through the native module loader. +// Loads one DLL entirely through the process-local userspace module loader. __declspec(dllexport) HANDLE LoadLibraryA(const char* lpLibFileName) { if (KiIsWin32DllNameA(lpLibFileName)) return WIN32_HMODULE_WIN32; - if (!lpLibFileName) { gLastError = 87; return NULL; } + if (!lpLibFileName || !lpLibFileName[0]) { 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; + SYS_HANDLE root = SYS_HANDLE_INVALID; + uint32_t status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &root); + if (status != NTLSTATUS_SUCCESS) { gLastError = 126; return NULL; } - uint32_t status = SysLoadModule(&req); - if (status != NTLSTATUS_SUCCESS) { + char relative[MAX_PATH]; + status = K32ProcessPath(lpLibFileName, relative, sizeof(relative)); + if (status == NTLSTATUS_SUCCESS && !strchr(relative, '/')) { + size_t length = strlen(relative); + if (length + sizeof("system/") > sizeof(relative)) status = NTLSTATUS_BUFFER_TOO_SMALL; + else { + memmove(relative + 7, relative, length + 1u); + memcpy(relative, "system/", 7); + } + } + + NTL_MODULE_HANDLE module = NTL_MODULE_HANDLE_INVALID; + if (status == NTLSTATUS_SUCCESS) { + NTL_LOAD_MODULE_AT_REQUEST load; + memset(&load, 0, sizeof(load)); + load.Size = sizeof(load); + load.Flags = NTL_MODULE_LOAD_FLAG_NONE; + load.RootHandle = root; + load.Path = SysSvFromCstr(relative); + load.ModuleRootHandle = root; + load.ModuleDirectory = SYS_SV_LITERAL("system"); + load.ResolveModuleName = K32ResolveModuleName; + load.ModuleHandleOut = &module; + status = NtlLoadModuleAt(&load); + } + (void)SysCloseHandle(root); + if (status != NTLSTATUS_SUCCESS || module == NTL_MODULE_HANDLE_INVALID) { gLastError = 126; return NULL; } - return (HANDLE)(uintptr_t)handle; + return (HANDLE)(uintptr_t)module; } // GetProcAddress handles both built-in win32 exports and native module handles. @@ -1415,13 +1457,8 @@ __declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcNam 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); + uint32_t status = NtlGetModuleExport((NTL_MODULE_HANDLE)(uintptr_t)hModule, + SysSvFromCstr(lpProcName), &address); if (status != NTLSTATUS_SUCCESS || !address) { gLastError = 127; return NULL; } return (void*)address; } @@ -1463,7 +1500,7 @@ __declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcNam __declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule) { if (!hLibModule || hLibModule == WIN32_HMODULE_WIN32 || hLibModule == WIN32_HMODULE_EXE) return TRUE; - uint32_t status = SysUnloadModule((uint32_t)(uintptr_t)hLibModule); + uint32_t status = NtlUnloadModule((NTL_MODULE_HANDLE)(uintptr_t)hLibModule); if (status != NTLSTATUS_SUCCESS) { gLastError = 126; return FALSE; @@ -1593,7 +1630,7 @@ __declspec(dllexport) HANDLE CreateThread(void* lpThreadAttributes, uint32_t tid = 0; uint32_t kh = 0; SYS_CREATE_THREAD_REQUEST req = { - (uintptr_t)K32ThreadStart, + sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)K32ThreadStart, (uintptr_t)context, 0, (dwCreationFlags & CREATE_SUSPENDED) @@ -1767,52 +1804,49 @@ __declspec(dllexport) BOOL CreateProcessA( const char* cmdline = (lpCommandLine && lpCommandLine[0]) ? lpCommandLine : targetPath; char translatedPath[MAX_PATH]; - TranslatePath(targetPath, translatedPath); - - char savedCwd[SYS_CWD_MAX]; - bool changedCwd = false; - savedCwd[0] = '\0'; - - if (lpCurrentDirectory && lpCurrentDirectory[0]) { - char translatedCwd[MAX_PATH]; - uint32_t gs = K32GetCwdNative(savedCwd, sizeof(savedCwd), NULL); - if (gs != 0) { gLastError = 3; return FALSE; } - - TranslatePath(lpCurrentDirectory, translatedCwd); - gs = K32SetCwdNative(translatedCwd); - if (gs != 0) { gLastError = 3; return FALSE; } - changedCwd = true; - } - - uint32_t kh = 0; - // Call the syscall.dll stub. The kernel snapshots the current CWD into - // the child process, so the temporary lpCurrentDirectory switch can be - // restored immediately after creation. - uint32_t s = K32CreateProcessNative(translatedPath, &kh, cmdline); - - if (changedCwd) { - if (savedCwd[0]) { - char restoreCwd[SYS_CWD_MAX + 3]; - restoreCwd[0] = '0'; restoreCwd[1] = ':'; restoreCwd[2] = '/'; - size_t rlen = strlen(savedCwd); - if (rlen + 4 > sizeof(restoreCwd)) rlen = sizeof(restoreCwd) - 4; - memcpy(restoreCwd + 3, savedCwd, rlen); - restoreCwd[3 + rlen] = '\0'; - K32SetCwdNative(restoreCwd); - } else { - K32SetCwdNative("0:/"); + uint32_t imageFile = SYS_HANDLE_INVALID; + bool relativeImage = targetPath[0] != '/' && targetPath[0] != '\\' && + !(targetPath[0] && targetPath[1] == ':' && + (targetPath[2] == '\\' || targetPath[2] == '/')); + if (relativeImage) { + TranslatePath(targetPath, translatedPath); + uint32_t openStatus = K32OpenFileNative(translatedPath, + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + &imageFile); + if (openStatus != NTLSTATUS_SUCCESS) { + gLastError = 2; + return FALSE; } - } - - if (s != 0 || kh == 0) { - gLastError = 2; // ERROR_FILE_NOT_FOUND + } else if (K32ProcessPath(targetPath, translatedPath, sizeof(translatedPath)) != NTLSTATUS_SUCCESS) { + gLastError = 3; return FALSE; } - lpProcessInformation->hProcess = KH_TO_HANDLE(kh); - lpProcessInformation->hThread = INVALID_HANDLE_VALUE; - lpProcessInformation->dwProcessId = 0; - lpProcessInformation->dwThreadId = 0; + char translatedCwd[MAX_PATH]; + const char* childCwd = (const char*)0; + if (lpCurrentDirectory && lpCurrentDirectory[0]) { + TranslatePath(lpCurrentDirectory, translatedCwd); + if (!translatedCwd[0]) { + if (imageFile != SYS_HANDLE_INVALID) (void)SysCloseHandle(imageFile); + gLastError = 3; + return FALSE; + } + childCwd = translatedCwd; + } + + uint32_t kh = 0; + uint32_t s = K32CreateProcessNative(translatedPath, imageFile, &kh, cmdline, childCwd); + if (imageFile != SYS_HANDLE_INVALID) (void)SysCloseHandle(imageFile); + if (s != 0 || kh == 0) { + gLastError = 2; + return FALSE; + } + + lpProcessInformation->hProcess = KH_TO_HANDLE(kh); + lpProcessInformation->hThread = INVALID_HANDLE_VALUE; + lpProcessInformation->dwProcessId = 0; + lpProcessInformation->dwThreadId = 0; return TRUE; } @@ -1820,7 +1854,7 @@ __declspec(dllexport) BOOL CreateProcessA( __declspec(dllexport) BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode) { if (!lpExitCode) { gLastError = 87; return FALSE; } uint32_t kh = HANDLE_TO_KH(hProcess); - if (!kh) { gLastError = 6; return FALSE; } // ERROR_INVALID_HANDLE + if (!kh) { gLastError = 6; return FALSE; } int32_t code = 0; uint32_t s = SysGetProcessExitCode(kh, &code); if (s != 0) { gLastError = 6; return FALSE; } @@ -2037,9 +2071,11 @@ __declspec(dllexport) BOOL FillConsoleOutputCharacterA(HANDLE hConsoleOutput, return TRUE; size_t written = 0; - uint32_t s = SysFillConsoleCharacter(cCharacter, (int)dwWriteCoord.X, - (int)dwWriteCoord.Y, (size_t)nLength, - &written); + SYS_FILL_CONSOLE_CHARACTER_REQUEST request = { + sizeof(SYS_FILL_CONSOLE_CHARACTER_REQUEST), cCharacter, { 0, 0, 0 }, + (int)dwWriteCoord.X, (int)dwWriteCoord.Y, (size_t)nLength, &written + }; + uint32_t s = SysFillConsoleCharacter(&request); if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = (DWORD)written; if (s != NTLSTATUS_SUCCESS) { K32SetConsoleLastError(s); @@ -2066,9 +2102,11 @@ __declspec(dllexport) BOOL FillConsoleOutputAttribute(HANDLE hConsoleOutput, return TRUE; size_t written = 0; - uint32_t s = SysFillConsoleAttribute(wAttribute, (int)dwWriteCoord.X, - (int)dwWriteCoord.Y, (size_t)nLength, - &written); + SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST request = { + sizeof(SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST), wAttribute, 0, + (int)dwWriteCoord.X, (int)dwWriteCoord.Y, (size_t)nLength, &written + }; + uint32_t s = SysFillConsoleAttribute(&request); if (lpNumberOfAttrsWritten) *lpNumberOfAttrsWritten = (DWORD)written; if (s != NTLSTATUS_SUCCESS) { K32SetConsoleLastError(s); @@ -2335,24 +2373,25 @@ __declspec(dllexport) BOOL SetCurrentDirectoryA(const char* lpPathName) { __declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBuffer) { if (!lpBuffer || nBufferLength == 0) { // Return a conservative required size including NUL. - return SYS_CWD_MAX + 1; + return SYS_PATH_MAX + 1u; } - // Kernel returns native "N:/path". Present it as a Win32 drive letter. - char raw[SYS_CWD_MAX + 1]; + char raw[SYS_PATH_MAX + 1u]; uint32_t s = K32GetCwdNative(raw, sizeof(raw), NULL); if (s != 0) { gLastError = 1; return 0; } - char full[SYS_CWD_MAX + 1]; - char drive = 'C'; - const char* rel = raw; - if (raw[0] >= '0' && raw[0] <= '7' && raw[1] == ':' && - (raw[2] == '\\' || raw[2] == '/')) { - drive = (char)('C' + (raw[0] - '0')); - rel = raw + 3; + static const char prefix[] = "/SystemRoot"; + size_t prefixLength = sizeof(prefix) - 1u; + if (strncmp(raw, prefix, prefixLength) != 0 || + (raw[prefixLength] != '\0' && raw[prefixLength] != '/')) { + gLastError = 3; + return 0; } + const char* rel = raw + prefixLength; + if (*rel == '/') rel++; - full[0] = drive; + char full[SYS_PATH_MAX + 1u]; + full[0] = 'C'; full[1] = ':'; full[2] = '\\'; size_t rawLen = strlen(rel); diff --git a/code/user/personalities/win32/user32.c b/code/user/personalities/win32/user32.c index 487d1f6..4fe7eee 100644 --- a/code/user/personalities/win32/user32.c +++ b/code/user/personalities/win32/user32.c @@ -17,7 +17,7 @@ typedef struct tagPAINTSTRUCT { // Implements the user32 Debug Print A operation. static void User32DebugPrintA(const char* s) { if (!s) return; - SYS_PRINT_DEBUG_REQUEST req = { SysSvFromCstr(s) }; + SYS_PRINT_DEBUG_REQUEST req = { sizeof(SYS_PRINT_DEBUG_REQUEST), SysSvFromCstr(s) }; SysPrintDebug(&req); } diff --git a/code/user/personalities/win32/winmm_waveout.c b/code/user/personalities/win32/winmm_waveout.c index a709585..ecc07ee 100644 --- a/code/user/personalities/win32/winmm_waveout.c +++ b/code/user/personalities/win32/winmm_waveout.c @@ -173,7 +173,7 @@ typedef struct WINMM_WAVEOUT { uint32_t ReadyEvent; uint32_t WorkerHandle; uint32_t WorkerTid; - uint64_t EndpointId; + uint32_t EndpointId; WAVEFORMATEX SourceFormat; uintptr_t Callback; uintptr_t CallbackInstance; @@ -248,7 +248,7 @@ static void WinmmDebugCommit(WINMM_DEBUG_LINE* line) { if (line->Length + 1 < sizeof(line->Buffer)) line->Buffer[line->Length++] = '\n'; line->Buffer[line->Length] = '\0'; - SYS_PRINT_DEBUG_REQUEST request = { SysSvFromParts(line->Buffer, line->Length) }; + SYS_PRINT_DEBUG_REQUEST request = { sizeof(SYS_PRINT_DEBUG_REQUEST), SysSvFromParts(line->Buffer, line->Length) }; (void)SysPrintDebug(&request); } @@ -379,7 +379,7 @@ static uint32_t WinmmAvailableEndpointCount(void) { // Resolves WAVE_MAPPER or one available-device index to a stable endpoint ID. static MMRESULT WinmmResolveEndpoint(uint32_t deviceId, - uint64_t* endpointIdOut, + uint32_t* endpointIdOut, uint32_t* resolvedDeviceIdOut) { SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS]; uint32_t count = 0; @@ -952,7 +952,7 @@ __declspec(dllexport) MMRESULT waveOutOpen(HWAVEOUT* handleOut, if ((callbackMode == CALLBACK_FUNCTION || callbackMode == CALLBACK_EVENT) && !callback) return MMSYSERR_INVALPARAM; - uint64_t endpointId = 0; + uint32_t endpointId = 0; uint32_t resolvedDeviceId = 0; result = WinmmResolveEndpoint(deviceId, &endpointId, &resolvedDeviceId); if (result != MMSYSERR_NOERROR) { @@ -1026,6 +1026,7 @@ __declspec(dllexport) MMRESULT waveOutOpen(HWAVEOUT* handleOut, WinmmRegisterWaveOut(waveOut); SYS_CREATE_THREAD_REQUEST thread; memset(&thread, 0, sizeof(thread)); + thread.Size = sizeof(thread); thread.Entry = (uintptr_t)WinmmWaveWorker; thread.Argument = (uintptr_t)waveOut; thread.Priority = WINMM_WORKER_PRIORITY; @@ -1391,7 +1392,7 @@ __declspec(dllexport) MMRESULT waveOutGetID(HWAVEOUT handle, UINT* deviceIdOut) __declspec(dllexport) MMRESULT waveOutGetDevCapsA(UINT deviceId, WAVEOUTCAPSA* caps, UINT size) { - uint64_t endpointId = 0; + uint32_t endpointId = 0; uint32_t resolvedDeviceId = 0; MMRESULT result = WinmmResolveEndpoint(deviceId, &endpointId, &resolvedDeviceId); if (result != MMSYSERR_NOERROR) { diff --git a/code/user/syscall/module_loader.c b/code/user/syscall/module_loader.c new file mode 100644 index 0000000..e7bfe5e --- /dev/null +++ b/code/user/syscall/module_loader.c @@ -0,0 +1,796 @@ +// Userspace PE module registry and dependency loader. Kernel process objects know only VADs. + +#include +#include +#include +#include +#include +#include +#include +#include "module_loader_internal.h" + +#define NTL_MODULE_CONTEXT_MAGIC 0x4D4F444Cu +#define NTL_MODULE_PAGE_SIZE 4096u +#define NTL_LOADER_DATA_MAGIC 0x30444C4Eu +#define NTL_LOADER_DATA_VERSION 2u + +typedef struct NTL_LOADER_IMAGE_RECORD { + uintptr_t Base; + size_t Size; + uintptr_t EntryPoint; + char Name[NTL_MODULE_NAME_MAX]; +} NTL_LOADER_IMAGE_RECORD; + +typedef struct NTL_LOADER_DATA { + uint32_t Magic; + uint32_t Version; + uint32_t Size; + uint32_t ImageCount; + uint32_t CurrentDirectoryOffset; + uint32_t CurrentDirectoryLength; + uint32_t Reserved[2]; + NTL_LOADER_IMAGE_RECORD Images[]; +} NTL_LOADER_DATA; + +typedef struct NTL_MODULE_ENTRY { + struct NTL_MODULE_ENTRY* Next; + uintptr_t Base; + size_t Size; + uintptr_t EntryPoint; + uint32_t PublicRefCount; + uint32_t ImportPinned; + uint32_t Initial; + char Name[NTL_MODULE_NAME_MAX]; +} NTL_MODULE_ENTRY; + +struct NTL_MODULE_CONTEXT { + uint32_t Magic; + SYS_HANDLE ProcessHandle; + SYS_HANDLE ModuleRootHandle; + SYS_STRING_VIEW ModuleDirectory; + NTL_MODULE_NAME_RESOLVER Resolver; + void* ResolverContext; + NTL_MODULE_ENTRY* Head; + uint32_t Count; + uint32_t OwnsStorage; +}; + +static NTL_MODULE_CONTEXT gCurrentContext; +static volatile long gCurrentContextState; +static volatile long gCurrentContextBusy; + +// Converts one ASCII character to lowercase for module-name comparisons. +static char NtlpLower(char c) { + return (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c; +} + +// Compares one string view with a module name without case sensitivity. +static int NtlpViewEqualsNameI(SYS_STRING_VIEW view, const char* name) { + if (!name) return 0; + size_t n = strlen(name); + if (view.Length != n) return 0; + for (size_t i = 0; i < n; ++i) { + if (NtlpLower(view.Data[i]) != NtlpLower(name[i])) return 0; + } + return 1; +} + +// Allocates page-backed userspace metadata and clears the complete allocation. +static KSTATUS NtlpAllocZero(size_t bytes, void** out) { + if (!bytes || !out) return NTLSTATUS_INVALID_PARAMETER; + *out = 0; + if (bytes > SIZE_MAX - (NTL_MODULE_PAGE_SIZE - 1u)) return NTLSTATUS_NO_MEMORY; + size_t pages = (bytes + NTL_MODULE_PAGE_SIZE - 1u) / NTL_MODULE_PAGE_SIZE; + SYS_ALLOC_VM_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Protect = SYS_VM_READWRITE; + request.Flags = SYS_VM_FLAG_NONE; + request.PageCount = pages; + request.BaseOut = out; + KSTATUS status = SysAllocVm(&request); + if (KSUCCESS(status)) memset(*out, 0, pages * NTL_MODULE_PAGE_SIZE); + return status; +} + +// Releases page-backed userspace metadata. +static void NtlpFree(void* p) { + if (p) (void)SysFreeVm(p); +} + +// Copies and validates one basename-only module identity. +static KSTATUS NtlpCopyName(SYS_STRING_VIEW name, char out[NTL_MODULE_NAME_MAX]) { + if (!SysSvIsValid(name) || name.Length == 0 || name.Length >= NTL_MODULE_NAME_MAX || + SysSvContainsByte(name, '\0')) + return NTLSTATUS_INVALID_PARAMETER; + for (size_t i = 0; i < name.Length; ++i) { + if (name.Data[i] == '/' || name.Data[i] == '\\') return NTLSTATUS_INVALID_PARAMETER; + } + memcpy(out, name.Data, name.Length); + out[name.Length] = '\0'; + return NTLSTATUS_SUCCESS; +} + +// Returns the final native path component without modifying the source view. +static SYS_STRING_VIEW NtlpBasename(SYS_STRING_VIEW path) { + if (!SysSvIsValid(path)) return SysSvFromParts((const char*)0, 0); + size_t start = 0; + for (size_t i = 0; i < path.Length; ++i) { + if (path.Data[i] == '/') start = i + 1u; + } + return SysSvFromParts(path.Data + start, path.Length - start); +} + +// Finds one process-local module by canonical case-insensitive name. +static NTL_MODULE_ENTRY* NtlpFindName(NTL_MODULE_CONTEXT* context, SYS_STRING_VIEW name) { + for (NTL_MODULE_ENTRY* entry = context ? context->Head : 0; entry; entry = entry->Next) { + if (NtlpViewEqualsNameI(name, entry->Name)) return entry; + } + return 0; +} + +// Resolves one opaque userspace module handle against the live registry. +static NTL_MODULE_ENTRY* NtlpFindHandle(NTL_MODULE_CONTEXT* context, NTL_MODULE_HANDLE handle) { + if (!context || handle == NTL_MODULE_HANDLE_INVALID) return 0; + NTL_MODULE_ENTRY* wanted = (NTL_MODULE_ENTRY*)(uintptr_t)handle; + for (NTL_MODULE_ENTRY* entry = context->Head; entry; entry = entry->Next) { + if (entry == wanted) return entry; + } + return 0; +} + +// Copies one private module entry into the public userspace information shape. +static void NtlpFillInfo(const NTL_MODULE_ENTRY* entry, NTL_MODULE_INFO* info) { + if (!entry || !info) return; + memset(info, 0, sizeof(*info)); + info->Base = entry->Base; + info->Size = entry->Size; + info->EntryPoint = entry->EntryPoint; + info->PublicRefCount = entry->PublicRefCount; + info->ImportPinned = entry->ImportPinned; + strncpy(info->Name, entry->Name, sizeof(info->Name) - 1u); +} + +// Preserves an import name when the caller already uses canonical module names. +KSTATUS NtlResolveModuleNameIdentity(void* context, + SYS_STRING_VIEW requestedName, + SYS_STRING_VIEW* canonicalNameOut) { + (void)context; + if (!canonicalNameOut || !SysSvIsValid(requestedName) || requestedName.Length == 0) + return NTLSTATUS_INVALID_PARAMETER; + *canonicalNameOut = requestedName; + return NTLSTATUS_SUCCESS; +} + + +// Resolves installed personality compatibility aliases for generic launchers. +KSTATUS NtlResolveSystemModuleName(void* context, + SYS_STRING_VIEW requestedName, + SYS_STRING_VIEW* canonicalNameOut) { + (void)context; + if (!canonicalNameOut || !SysSvIsValid(requestedName) || requestedName.Length == 0) + return NTLSTATUS_INVALID_PARAMETER; +#define NTL_SYSTEM_ALIAS(name, target) do { \ + if (NtlpViewEqualsNameI(requestedName, name)) { \ + *canonicalNameOut = SYS_SV_LITERAL(target); \ + return NTLSTATUS_SUCCESS; \ + } \ +} while (0) + NTL_SYSTEM_ALIAS("kernel32.dll", "win32.dll"); + NTL_SYSTEM_ALIAS("user32.dll", "win32.dll"); + NTL_SYSTEM_ALIAS("gdi32.dll", "win32.dll"); + NTL_SYSTEM_ALIAS("ole32.dll", "win32.dll"); + NTL_SYSTEM_ALIAS("winmm.dll", "win32.dll"); + NTL_SYSTEM_ALIAS("msvcrt.dll", "posix.dll"); +#undef NTL_SYSTEM_ALIAS + *canonicalNameOut = requestedName; + return NTLSTATUS_SUCCESS; +} + +// Applies caller-owned import policy and validates the resulting module basename. +static KSTATUS NtlpCanonicalName(NTL_MODULE_CONTEXT* context, + SYS_STRING_VIEW requested, + SYS_STRING_VIEW* canonicalOut) { + if (!context || !canonicalOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = context->Resolver + ? context->Resolver(context->ResolverContext, requested, canonicalOut) + : NtlResolveModuleNameIdentity(0, requested, canonicalOut); + if (!KSUCCESS(status)) return status; + char scratch[NTL_MODULE_NAME_MAX]; + return NtlpCopyName(*canonicalOut, scratch); +} + +// Builds the capability-relative dependency path beneath the configured module directory. +static KSTATUS NtlpBuildModulePath(NTL_MODULE_CONTEXT* context, + SYS_STRING_VIEW canonical, + char out[SYS_PATH_MAX]) { + if (!context || !out) return NTLSTATUS_INVALID_PARAMETER; + char name[NTL_MODULE_NAME_MAX]; + KSTATUS status = NtlpCopyName(canonical, name); + if (!KSUCCESS(status)) return status; + size_t dirLength = context->ModuleDirectory.Length; + if (!SysSvIsValid(context->ModuleDirectory) || SysSvContainsByte(context->ModuleDirectory, '\0')) + return NTLSTATUS_INVALID_PARAMETER; + size_t total = dirLength + (dirLength ? 1u : 0u) + canonical.Length; + if (total + 1u > SYS_PATH_MAX) return NTLSTATUS_BUFFER_TOO_SMALL; + if (dirLength) memcpy(out, context->ModuleDirectory.Data, dirLength); + size_t pos = dirLength; + if (dirLength) out[pos++] = '/'; + memcpy(out + pos, canonical.Data, canonical.Length); + out[total] = '\0'; + return NTLSTATUS_SUCCESS; +} + +// Replaces the final component of an explicit module path with its canonical policy name. +static KSTATUS NtlpBuildCanonicalPath(SYS_STRING_VIEW path, + SYS_STRING_VIEW canonical, + char out[SYS_PATH_MAX], + SYS_STRING_VIEW* pathOut) { + if (!pathOut || !out || !SysSvIsValid(path) || path.Length == 0) + return NTLSTATUS_INVALID_PARAMETER; + char name[NTL_MODULE_NAME_MAX]; + KSTATUS status = NtlpCopyName(canonical, name); + if (!KSUCCESS(status)) return status; + SYS_STRING_VIEW base = NtlpBasename(path); + if (!base.Data || base.Length == 0) return NTLSTATUS_INVALID_PARAMETER; + size_t prefix = (size_t)(base.Data - path.Data); + if (prefix > path.Length || canonical.Length > SYS_PATH_MAX - 1u - prefix) + return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(out, path.Data, prefix); + memcpy(out + prefix, canonical.Data, canonical.Length); + size_t length = prefix + canonical.Length; + out[length] = '\0'; + *pathOut = SysSvFromParts(out, length); + return NTLSTATUS_SUCCESS; +} + +// Opens one canonical dependency through the caller-supplied module-root capability. +static KSTATUS NtlpOpenDependency(NTL_MODULE_CONTEXT* context, + SYS_STRING_VIEW canonical, + SYS_HANDLE* fileOut) { + if (!context || context->ModuleRootHandle == SYS_HANDLE_INVALID || !fileOut) + return NTLSTATUS_INVALID_PARAMETER; + *fileOut = SYS_HANDLE_INVALID; + char path[SYS_PATH_MAX]; + KSTATUS status = NtlpBuildModulePath(context, canonical, path); + if (!KSUCCESS(status)) return status; + SYS_OPEN_FILE_AT_REQUEST open = SysMakeOpenFileAtRequest(context->ModuleRootHandle, + SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + fileOut); + return SysOpenFileAt(&open); +} + +// Publishes a mapped image before import binding so cyclic dependencies can resolve it. +static KSTATUS NtlpPreparedModule(void* opaque, const NTL_PE_IMAGE* image) { + NTL_MODULE_ENTRY* entry = (NTL_MODULE_ENTRY*)opaque; + if (!entry || !image || !image->Base || !image->Size) return NTLSTATUS_INVALID_PARAMETER; + entry->Base = image->Base; + entry->Size = image->Size; + entry->EntryPoint = image->EntryPoint; + return NTLSTATUS_SUCCESS; +} + +// Resolves one imported symbol through the recursive userspace dependency registry. +static KSTATUS NtlpResolveImport(void* opaque, + SYS_STRING_VIEW moduleName, + SYS_STRING_VIEW symbolName, + uintptr_t* addressOut); + +// Loads or reuses one dependency and import-pins it for the process lifetime. +static KSTATUS NtlpLoadDependency(NTL_MODULE_CONTEXT* context, + SYS_STRING_VIEW requested, + NTL_MODULE_ENTRY** entryOut) { + if (!context || !entryOut) return NTLSTATUS_INVALID_PARAMETER; + *entryOut = 0; + SYS_STRING_VIEW canonical; + KSTATUS status = NtlpCanonicalName(context, requested, &canonical); + if (!KSUCCESS(status)) return status; + + NTL_MODULE_ENTRY* existing = NtlpFindName(context, canonical); + if (existing) { + existing->ImportPinned = 1u; + *entryOut = existing; + return NTLSTATUS_SUCCESS; + } + + NTL_MODULE_ENTRY* entry = 0; + status = NtlpAllocZero(sizeof(*entry), (void**)&entry); + if (!KSUCCESS(status)) return status; + status = NtlpCopyName(canonical, entry->Name); + if (!KSUCCESS(status)) { NtlpFree(entry); return status; } + entry->ImportPinned = 1u; + entry->Next = context->Head; + context->Head = entry; + context->Count++; + + SYS_HANDLE file = SYS_HANDLE_INVALID; + status = NtlpOpenDependency(context, canonical, &file); + if (KSUCCESS(status)) { + NTL_PE_IMAGE image; + memset(&image, 0, sizeof(image)); + NTL_PE_LOAD_REQUEST load; + memset(&load, 0, sizeof(load)); + load.Size = sizeof(load); + load.ProcessHandle = context->ProcessHandle; + load.FileHandle = file; + load.Flags = NTL_PE_LOAD_FLAG_NONE; + load.ResolveImport = NtlpResolveImport; + load.ResolveContext = context; + load.Prepared = NtlpPreparedModule; + load.PreparedContext = entry; + load.ImageOut = ℑ + status = NtlLoadPeImage(&load); + (void)SysCloseHandle(file); + } + + if (!KSUCCESS(status)) { + NTL_MODULE_ENTRY** link = &context->Head; + while (*link && *link != entry) link = &(*link)->Next; + if (*link == entry) *link = entry->Next; + if (context->Count) context->Count--; + NtlpFree(entry); + return status; + } + + *entryOut = entry; + return NTLSTATUS_SUCCESS; +} + +static KSTATUS NtlpResolveImport(void* opaque, + SYS_STRING_VIEW moduleName, + SYS_STRING_VIEW symbolName, + uintptr_t* addressOut) { + if (!opaque || !addressOut) return NTLSTATUS_INVALID_PARAMETER; + *addressOut = 0; + NTL_MODULE_ENTRY* entry = 0; + KSTATUS status = NtlpLoadDependency((NTL_MODULE_CONTEXT*)opaque, moduleName, &entry); + if (!KSUCCESS(status)) return status; + if (!entry->Base) return NTLSTATUS_WOULD_BLOCK; + return NtlGetPeExport(entry->Base, symbolName, addressOut); +} + +// Creates loader state for a child process or seeds the current process from PEB loader data. +KSTATUS NtlpCreateModuleContext(SYS_HANDLE processHandle, + SYS_HANDLE moduleRootHandle, + SYS_STRING_VIEW moduleDirectory, + NTL_MODULE_NAME_RESOLVER resolver, + void* resolverContext, + int seedCurrentProcess, + NTL_MODULE_CONTEXT** contextOut) { + if (!contextOut || moduleRootHandle == SYS_HANDLE_INVALID || !SysSvIsValid(moduleDirectory) || + SysSvContainsByte(moduleDirectory, '\0')) + return NTLSTATUS_INVALID_PARAMETER; + *contextOut = 0; + + NTL_MODULE_CONTEXT* context = 0; + KSTATUS status = NtlpAllocZero(sizeof(*context), (void**)&context); + if (!KSUCCESS(status)) return status; + context->Magic = NTL_MODULE_CONTEXT_MAGIC; + context->ProcessHandle = processHandle; + context->ModuleRootHandle = moduleRootHandle; + context->ModuleDirectory = moduleDirectory; + context->Resolver = resolver; + context->ResolverContext = resolverContext; + context->OwnsStorage = 1u; + + if (seedCurrentProcess) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) { + NtlpFree(context); + return NTLSTATUS_FAILURE; + } + if (peb->LoaderData) { + SYS_VM_REGION_INFO region; + memset(®ion, 0, sizeof(region)); + status = SysQueryVm((void*)peb->LoaderData, ®ion); + if (!KSUCCESS(status) || !region.Base || !region.PageCount) { + NtlpFree(context); + return NTLSTATUS_FAILURE; + } + const NTL_LOADER_DATA* data = (const NTL_LOADER_DATA*)peb->LoaderData; + if (region.PageCount > SIZE_MAX / NTL_MODULE_PAGE_SIZE) { + NtlpFree(context); + return NTLSTATUS_FAILURE; + } + size_t regionBytes = region.PageCount * NTL_MODULE_PAGE_SIZE; + uintptr_t regionBase = (uintptr_t)region.Base; + uintptr_t dataBase = (uintptr_t)data; + if (dataBase < regionBase || dataBase - regionBase > regionBytes || + regionBytes - (dataBase - regionBase) < sizeof(*data) || + data->Magic != NTL_LOADER_DATA_MAGIC || data->Version != NTL_LOADER_DATA_VERSION || + data->Size < sizeof(*data)) { + NtlpFree(context); + return NTLSTATUS_FAILURE; + } + size_t recordsBytes = (size_t)data->ImageCount * sizeof(NTL_LOADER_IMAGE_RECORD); + if (data->ImageCount && recordsBytes / sizeof(NTL_LOADER_IMAGE_RECORD) != data->ImageCount) { + NtlpFree(context); + return NTLSTATUS_FAILURE; + } + size_t recordsEnd = sizeof(*data) + recordsBytes; + if (recordsEnd < sizeof(*data) || data->Size < recordsEnd || + data->Size > regionBytes - (dataBase - regionBase) || + data->CurrentDirectoryOffset < recordsEnd || + data->CurrentDirectoryLength == 0 || + data->CurrentDirectoryOffset > data->Size || + data->CurrentDirectoryLength > data->Size - data->CurrentDirectoryOffset || + data->CurrentDirectoryLength == data->Size - data->CurrentDirectoryOffset || + ((const char*)data)[data->CurrentDirectoryOffset + data->CurrentDirectoryLength] != '\0') { + NtlpFree(context); + return NTLSTATUS_FAILURE; + } + for (uint32_t i = 0; i < data->ImageCount; ++i) { + const NTL_LOADER_IMAGE_RECORD* image = &data->Images[i]; + size_t nameLength = 0; + while (nameLength < NTL_MODULE_NAME_MAX && image->Name[nameLength]) nameLength++; + if (!image->Base || !image->Size || nameLength == 0 || nameLength >= NTL_MODULE_NAME_MAX) { + NtlpDestroyModuleContext(context); + return NTLSTATUS_FAILURE; + } + NTL_MODULE_ENTRY* entry = 0; + status = NtlpAllocZero(sizeof(*entry), (void**)&entry); + if (!KSUCCESS(status)) { NtlpDestroyModuleContext(context); return status; } + entry->Base = image->Base; + entry->Size = image->Size; + entry->EntryPoint = image->EntryPoint; + entry->ImportPinned = 1u; + entry->Initial = 1u; + memcpy(entry->Name, image->Name, nameLength + 1u); + if (NtlpFindName(context, SysSvFromParts(entry->Name, nameLength))) { + NtlpFree(entry); + NtlpDestroyModuleContext(context); + return NTLSTATUS_OBJECT_NAME_COLLISION; + } + entry->Next = context->Head; + context->Head = entry; + context->Count++; + } + } + } + + *contextOut = context; + return NTLSTATUS_SUCCESS; +} + +// Releases loader metadata without unmapping images owned by the target process. +void NtlpDestroyModuleContext(NTL_MODULE_CONTEXT* context) { + if (!context || context->Magic != NTL_MODULE_CONTEXT_MAGIC) return; + NTL_MODULE_ENTRY* entry = context->Head; + while (entry) { + NTL_MODULE_ENTRY* next = entry->Next; + NtlpFree(entry); + entry = next; + } + context->Head = 0; + context->Magic = 0; + if (context->OwnsStorage) NtlpFree(context); +} + +// Materializes the main PE while resolving all startup dependencies through this context. +KSTATUS NtlpLoadMainImage(NTL_MODULE_CONTEXT* context, + SYS_HANDLE fileHandle, + NTL_PE_IMAGE* imageOut) { + if (!context || context->Magic != NTL_MODULE_CONTEXT_MAGIC || + fileHandle == SYS_HANDLE_INVALID || !imageOut) + return NTLSTATUS_INVALID_PARAMETER; + NTL_PE_LOAD_REQUEST load; + memset(&load, 0, sizeof(load)); + load.Size = sizeof(load); + load.ProcessHandle = context->ProcessHandle; + load.FileHandle = fileHandle; + load.Flags = NTL_PE_LOAD_FLAG_NONE; + load.ResolveImport = NtlpResolveImport; + load.ResolveContext = context; + load.ImageOut = imageOut; + return NtlLoadPeImage(&load); +} + +// Serializes startup dependency metadata into a read-only child-owned VAD. +KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, + SYS_STRING_VIEW currentDirectory, + void** loaderDataOut) { + if (!context || context->Magic != NTL_MODULE_CONTEXT_MAGIC || !loaderDataOut || + context->ProcessHandle == SYS_HANDLE_INVALID || !SysSvIsValid(currentDirectory) || + currentDirectory.Length == 0 || currentDirectory.Length > SYS_PATH_MAX || + currentDirectory.Data[0] != '/' || SysSvContainsByte(currentDirectory, '\\') || + SysSvContainsByte(currentDirectory, '\0')) + return NTLSTATUS_INVALID_PARAMETER; + *loaderDataOut = 0; + + size_t recordsBytes = (size_t)context->Count * sizeof(NTL_LOADER_IMAGE_RECORD); + if (context->Count && recordsBytes / sizeof(NTL_LOADER_IMAGE_RECORD) != context->Count) + return NTLSTATUS_NO_MEMORY; + size_t bytes = sizeof(NTL_LOADER_DATA) + recordsBytes; + if (bytes < sizeof(NTL_LOADER_DATA) || currentDirectory.Length > SIZE_MAX - bytes - 1u) + return NTLSTATUS_NO_MEMORY; + size_t currentDirectoryOffset = bytes; + bytes += currentDirectory.Length + 1u; + if (bytes > UINT32_MAX) return NTLSTATUS_NO_MEMORY; + if (bytes > SIZE_MAX - (NTL_MODULE_PAGE_SIZE - 1u)) return NTLSTATUS_NO_MEMORY; + size_t pages = (bytes + NTL_MODULE_PAGE_SIZE - 1u) / NTL_MODULE_PAGE_SIZE; + + void* base = 0; + SYS_ALLOC_PROCESS_VM_REQUEST alloc; + memset(&alloc, 0, sizeof(alloc)); + alloc.Size = sizeof(alloc); + alloc.ProcessHandle = context->ProcessHandle; + alloc.Protect = SYS_VM_READWRITE; + alloc.Flags = SYS_VM_FLAG_NONE; + alloc.PageCount = pages; + alloc.BaseOut = &base; + KSTATUS status = SysAllocProcessVm(&alloc); + if (!KSUCCESS(status)) return status; + + NTL_LOADER_DATA* data = (NTL_LOADER_DATA*)base; + memset(data, 0, pages * NTL_MODULE_PAGE_SIZE); + data->Magic = NTL_LOADER_DATA_MAGIC; + data->Version = NTL_LOADER_DATA_VERSION; + data->Size = (uint32_t)bytes; + data->ImageCount = context->Count; + data->CurrentDirectoryOffset = (uint32_t)currentDirectoryOffset; + data->CurrentDirectoryLength = (uint32_t)currentDirectory.Length; + uint32_t i = 0; + for (NTL_MODULE_ENTRY* entry = context->Head; entry; entry = entry->Next) { + NTL_LOADER_IMAGE_RECORD* image = &data->Images[i++]; + image->Base = entry->Base; + image->Size = entry->Size; + image->EntryPoint = entry->EntryPoint; + strncpy(image->Name, entry->Name, sizeof(image->Name) - 1u); + } + char* cwd = (char*)data + currentDirectoryOffset; + memcpy(cwd, currentDirectory.Data, currentDirectory.Length); + cwd[currentDirectory.Length] = '\0'; + + status = SysProtectProcessVm(context->ProcessHandle, base, pages, SYS_VM_READ); + if (!KSUCCESS(status)) return status; + *loaderDataOut = base; + return NTLSTATUS_SUCCESS; +} + +// Returns the canonical inherited resource CWD stored in this process's loader data. +KSTATUS NtlpGetLoaderCurrentDirectory(SYS_STRING_VIEW* currentDirectoryOut) { + if (!currentDirectoryOut) return NTLSTATUS_INVALID_PARAMETER; + *currentDirectoryOut = SysSvFromParts((const char*)0, 0); + + SYS_PEB* peb = SysGetPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || !peb->LoaderData) + return NTLSTATUS_NOT_FOUND; + + SYS_VM_REGION_INFO region; + memset(®ion, 0, sizeof(region)); + KSTATUS status = SysQueryVm((void*)peb->LoaderData, ®ion); + if (!KSUCCESS(status) || !region.Base || !region.PageCount || + region.PageCount > SIZE_MAX / NTL_MODULE_PAGE_SIZE) + return NTLSTATUS_FAILURE; + + size_t regionBytes = region.PageCount * NTL_MODULE_PAGE_SIZE; + uintptr_t regionBase = (uintptr_t)region.Base; + uintptr_t dataBase = (uintptr_t)peb->LoaderData; + if (dataBase < regionBase || dataBase - regionBase > regionBytes || + regionBytes - (dataBase - regionBase) < sizeof(NTL_LOADER_DATA)) + return NTLSTATUS_FAILURE; + + const NTL_LOADER_DATA* data = (const NTL_LOADER_DATA*)peb->LoaderData; + if (data->Magic != NTL_LOADER_DATA_MAGIC || data->Version != NTL_LOADER_DATA_VERSION || + data->Size < sizeof(*data) || data->Size > regionBytes - (dataBase - regionBase) || + data->CurrentDirectoryOffset < sizeof(*data) || + data->CurrentDirectoryLength == 0 || + data->CurrentDirectoryOffset > data->Size || + data->CurrentDirectoryLength > data->Size - data->CurrentDirectoryOffset || + data->CurrentDirectoryLength == data->Size - data->CurrentDirectoryOffset) + return NTLSTATUS_FAILURE; + + const char* cwd = (const char*)data + data->CurrentDirectoryOffset; + SYS_STRING_VIEW view = SysSvFromParts(cwd, data->CurrentDirectoryLength); + if (cwd[data->CurrentDirectoryLength] != '\0' || cwd[0] != '/' || + SysSvContainsByte(view, '\\') || SysSvContainsByte(view, '\0')) + return NTLSTATUS_FAILURE; + *currentDirectoryOut = view; + return NTLSTATUS_SUCCESS; +} + +// Lazily seeds the current-process registry and refreshes per-call resolution authority. +static KSTATUS NtlpEnsureCurrentContext(SYS_HANDLE moduleRoot, + SYS_STRING_VIEW moduleDirectory, + NTL_MODULE_NAME_RESOLVER resolver, + void* resolverContext, + NTL_MODULE_CONTEXT** contextOut) { + if (!contextOut) return NTLSTATUS_INVALID_PARAMETER; + if (_InterlockedCompareExchange(&gCurrentContextState, 1, 0) == 0) { + NTL_MODULE_CONTEXT* temporary = 0; + KSTATUS status = NtlpCreateModuleContext(SYS_HANDLE_INVALID, moduleRoot, moduleDirectory, + resolver, resolverContext, 1, &temporary); + if (!KSUCCESS(status)) { + _InterlockedExchange(&gCurrentContextState, 0); + return status; + } + gCurrentContext = *temporary; + gCurrentContext.OwnsStorage = 0u; + temporary->Head = 0; + temporary->Magic = 0; + NtlpFree(temporary); + _InterlockedExchange(&gCurrentContextState, 2); + } else { + while (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) == 1) SysYield(); + if (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) != 2) + return NTLSTATUS_FAILURE; + gCurrentContext.ModuleRootHandle = moduleRoot; + gCurrentContext.ModuleDirectory = moduleDirectory; + gCurrentContext.Resolver = resolver; + gCurrentContext.ResolverContext = resolverContext; + } + *contextOut = &gCurrentContext; + return NTLSTATUS_SUCCESS; +} + +// Serializes current-process module mutations because loader metadata is process-global. +static KSTATUS NtlpEnterCurrent(void) { + while (_InterlockedCompareExchange(&gCurrentContextBusy, 1, 0) != 0) SysYield(); + return NTLSTATUS_SUCCESS; +} + +// Releases the current-process loader serialization guard. +static void NtlpLeaveCurrent(void) { + _InterlockedExchange(&gCurrentContextBusy, 0); +} + +// Loads or references one DLL-like PE in the current process through capability-relative I/O. +KSTATUS NtlLoadModuleAt(const NTL_LOAD_MODULE_AT_REQUEST* request) { + if (!request || request->Size != sizeof(*request) || request->Flags != NTL_MODULE_LOAD_FLAG_NONE || + request->RootHandle == SYS_HANDLE_INVALID || request->ModuleRootHandle == SYS_HANDLE_INVALID || + !SysSvIsValid(request->Path) || request->Path.Length == 0 || !request->ModuleHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + *request->ModuleHandleOut = NTL_MODULE_HANDLE_INVALID; + + KSTATUS status = NtlpEnterCurrent(); + if (!KSUCCESS(status)) return status; + NTL_MODULE_CONTEXT* context = 0; + status = NtlpEnsureCurrentContext(request->ModuleRootHandle, request->ModuleDirectory, + request->ResolveModuleName, request->ResolveContext, &context); + if (!KSUCCESS(status)) { NtlpLeaveCurrent(); return status; } + + SYS_STRING_VIEW baseName = NtlpBasename(request->Path); + SYS_STRING_VIEW canonical = baseName; + status = NtlpCanonicalName(context, baseName, &canonical); + if (!KSUCCESS(status)) { NtlpLeaveCurrent(); return status; } + NTL_MODULE_ENTRY* existing = NtlpFindName(context, canonical); + if (existing) { + existing->PublicRefCount++; + *request->ModuleHandleOut = (NTL_MODULE_HANDLE)(uintptr_t)existing; + if (request->InfoOut) NtlpFillInfo(existing, request->InfoOut); + NtlpLeaveCurrent(); + return NTLSTATUS_SUCCESS; + } + + char canonicalPath[SYS_PATH_MAX]; + SYS_STRING_VIEW openPath; + status = NtlpBuildCanonicalPath(request->Path, canonical, canonicalPath, &openPath); + if (!KSUCCESS(status)) { NtlpLeaveCurrent(); return status; } + + SYS_HANDLE file = SYS_HANDLE_INVALID; + SYS_OPEN_FILE_AT_REQUEST open = SysMakeOpenFileAtRequest(request->RootHandle, openPath, + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &file); + status = SysOpenFileAt(&open); + if (!KSUCCESS(status)) { NtlpLeaveCurrent(); return status; } + + NTL_MODULE_ENTRY* entry = 0; + status = NtlpAllocZero(sizeof(*entry), (void**)&entry); + if (KSUCCESS(status)) status = NtlpCopyName(canonical, entry->Name); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(file); + NtlpFree(entry); + NtlpLeaveCurrent(); + return status; + } + entry->PublicRefCount = 1u; + entry->Next = context->Head; + context->Head = entry; + context->Count++; + + NTL_PE_IMAGE image; + memset(&image, 0, sizeof(image)); + NTL_PE_LOAD_REQUEST load; + memset(&load, 0, sizeof(load)); + load.Size = sizeof(load); + load.ProcessHandle = SYS_HANDLE_INVALID; + load.FileHandle = file; + load.Flags = NTL_PE_LOAD_FLAG_NONE; + load.ResolveImport = NtlpResolveImport; + load.ResolveContext = context; + load.Prepared = NtlpPreparedModule; + load.PreparedContext = entry; + load.ImageOut = ℑ + status = NtlLoadPeImage(&load); + (void)SysCloseHandle(file); + if (!KSUCCESS(status)) { + context->Head = entry->Next; + context->Count--; + NtlpFree(entry); + NtlpLeaveCurrent(); + return status; + } + + *request->ModuleHandleOut = (NTL_MODULE_HANDLE)(uintptr_t)entry; + if (request->InfoOut) NtlpFillInfo(entry, request->InfoOut); + NtlpLeaveCurrent(); + return NTLSTATUS_SUCCESS; +} + +// Releases one public module reference and unmaps modules that are no longer pinned. +KSTATUS NtlUnloadModule(NTL_MODULE_HANDLE module) { + KSTATUS status = NtlpEnterCurrent(); + if (!KSUCCESS(status)) return status; + if (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) != 2) { + NtlpLeaveCurrent(); + return NTLSTATUS_INVALID_HANDLE; + } + NTL_MODULE_ENTRY* entry = NtlpFindHandle(&gCurrentContext, module); + if (!entry || entry->PublicRefCount == 0) { NtlpLeaveCurrent(); return NTLSTATUS_INVALID_HANDLE; } + entry->PublicRefCount--; + if (entry->PublicRefCount == 0 && !entry->ImportPinned && !entry->Initial) { + NTL_MODULE_ENTRY** link = &gCurrentContext.Head; + while (*link && *link != entry) link = &(*link)->Next; + if (*link == entry) *link = entry->Next; + gCurrentContext.Count--; + if (entry->Base) (void)SysFreeVm((void*)entry->Base); + NtlpFree(entry); + } + NtlpLeaveCurrent(); + return NTLSTATUS_SUCCESS; +} + +// Queries one live userspace module handle. +KSTATUS NtlGetModuleInfo(NTL_MODULE_HANDLE module, NTL_MODULE_INFO* infoOut) { + if (!infoOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = NtlpEnterCurrent(); + if (!KSUCCESS(status)) return status; + if (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) != 2) { + NtlpLeaveCurrent(); + return NTLSTATUS_INVALID_HANDLE; + } + NTL_MODULE_ENTRY* entry = NtlpFindHandle(&gCurrentContext, module); + if (!entry) { NtlpLeaveCurrent(); return NTLSTATUS_INVALID_HANDLE; } + NtlpFillInfo(entry, infoOut); + NtlpLeaveCurrent(); + return NTLSTATUS_SUCCESS; +} + +// Resolves one named export from a live userspace module handle. +KSTATUS NtlGetModuleExport(NTL_MODULE_HANDLE module, SYS_STRING_VIEW name, uintptr_t* addressOut) { + if (!addressOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = NtlpEnterCurrent(); + if (!KSUCCESS(status)) return status; + if (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) != 2) { + NtlpLeaveCurrent(); + return NTLSTATUS_INVALID_HANDLE; + } + NTL_MODULE_ENTRY* entry = NtlpFindHandle(&gCurrentContext, module); + if (!entry) { NtlpLeaveCurrent(); return NTLSTATUS_INVALID_HANDLE; } + status = NtlGetPeExport(entry->Base, name, addressOut); + NtlpLeaveCurrent(); + return status; +} + +// Snapshots the current process-local userspace module registry. +KSTATUS NtlGetModuleList(NTL_MODULE_INFO* modulesOut, size_t maxCount, size_t* countOut) { + if (!countOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = NtlpEnterCurrent(); + if (!KSUCCESS(status)) return status; + if (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) != 2) { + *countOut = 0; + NtlpLeaveCurrent(); + return NTLSTATUS_SUCCESS; + } + *countOut = gCurrentContext.Count; + if (gCurrentContext.Count > maxCount) { NtlpLeaveCurrent(); return NTLSTATUS_BUFFER_TOO_SMALL; } + if (gCurrentContext.Count && !modulesOut) { NtlpLeaveCurrent(); return NTLSTATUS_INVALID_PARAMETER; } + size_t i = 0; + for (NTL_MODULE_ENTRY* entry = gCurrentContext.Head; entry; entry = entry->Next) + NtlpFillInfo(entry, &modulesOut[i++]); + NtlpLeaveCurrent(); + return NTLSTATUS_SUCCESS; +} diff --git a/code/user/syscall/module_loader_internal.h b/code/user/syscall/module_loader_internal.h new file mode 100644 index 0000000..fd930f0 --- /dev/null +++ b/code/user/syscall/module_loader_internal.h @@ -0,0 +1,30 @@ +// Internal shared state for userspace PE dependency loading. + +#pragma once + +#include +#include +#include + +typedef struct NTL_MODULE_CONTEXT NTL_MODULE_CONTEXT; + +// Creates dependency-loader state for one constructing child or the current process. +KSTATUS NtlpCreateModuleContext(SYS_HANDLE processHandle, + SYS_HANDLE moduleRootHandle, + SYS_STRING_VIEW moduleDirectory, + NTL_MODULE_NAME_RESOLVER resolver, + void* resolverContext, + int seedCurrentProcess, + NTL_MODULE_CONTEXT** contextOut); +// Releases loader metadata without releasing image mappings owned elsewhere. +void NtlpDestroyModuleContext(NTL_MODULE_CONTEXT* context); +// Materializes the main image while resolving startup dependencies through the context. +KSTATUS NtlpLoadMainImage(NTL_MODULE_CONTEXT* context, + SYS_HANDLE fileHandle, + NTL_PE_IMAGE* imageOut); +// Builds the opaque read-only loader-data block consumed later by userspace base services. +KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, + SYS_STRING_VIEW currentDirectory, + void** loaderDataOut); +// Returns the inherited canonical resource CWD stored in the current process loader data. +KSTATUS NtlpGetLoaderCurrentDirectory(SYS_STRING_VIEW* currentDirectoryOut); diff --git a/code/user/syscall/path.c b/code/user/syscall/path.c new file mode 100644 index 0000000..1815d77 --- /dev/null +++ b/code/user/syscall/path.c @@ -0,0 +1,499 @@ +// Native userspace path policy. RSSRV resolves global resource prefixes; kernel I/O sees only capabilities and relative paths. + +#include +#include +#include +#include "module_loader_internal.h" + +#define NTL_PATH_COMPONENT_MAX 255u + +typedef struct NTL_RESOLVED_ROOT { + SYS_HANDLE Handle; + SYS_STRING_VIEW RelativePath; + uint32_t Kind; + size_t ResolvedLength; +} NTL_RESOLVED_ROOT; + +static volatile long gPathState; +static volatile long gPathLock; +static char gCurrentDirectory[SYS_PATH_MAX + 1u]; +static size_t gCurrentDirectoryLength; +static size_t gCurrentDirectoryRootLength; + +// Acquires the tiny userspace CWD lock without introducing a kernel object. +static void NtlpLockPathState(void) { + while (_InterlockedCompareExchange(&gPathLock, 1, 0) != 0) + (void)SysYield(); +} + +// Releases the userspace CWD lock. +static void NtlpUnlockPathState(void) { + _InterlockedExchange(&gPathLock, 0); +} + +// Initializes the process-local CWD from opaque loader data, or /SystemRoot for bootstrap init. +static KSTATUS NtlpEnsurePathState(void) { + if (_InterlockedCompareExchange(&gPathState, 2, 2) == 2) + return NTLSTATUS_SUCCESS; + + if (_InterlockedCompareExchange(&gPathState, 1, 0) == 0) { + SYS_STRING_VIEW inherited; + KSTATUS status = NtlpGetLoaderCurrentDirectory(&inherited); + if (!KSUCCESS(status)) + inherited = SYS_SV_LITERAL("/SystemRoot"); + if (!SysSvIsValid(inherited) || inherited.Length == 0 || inherited.Length > SYS_PATH_MAX || + inherited.Data[0] != '/' || SysSvContainsByte(inherited, '\\') || SysSvContainsByte(inherited, '\0')) { + _InterlockedExchange(&gPathState, 0); + return NTLSTATUS_FAILURE; + } + memcpy(gCurrentDirectory, inherited.Data, inherited.Length); + gCurrentDirectory[inherited.Length] = '\0'; + gCurrentDirectoryLength = inherited.Length; + gCurrentDirectoryRootLength = 0; + _InterlockedExchange(&gPathState, 2); + return NTLSTATUS_SUCCESS; + } + + while (_InterlockedCompareExchange(&gPathState, 2, 2) == 1) + (void)SysYield(); + return _InterlockedCompareExchange(&gPathState, 2, 2) == 2 + ? NTLSTATUS_SUCCESS : NTLSTATUS_FAILURE; +} + +// Appends one normalized path component while enforcing the owning resource boundary. +static KSTATUS NtlpAppendComponent(char* output, + size_t outputSize, + size_t* lengthInOut, + size_t componentStart, + const char* component, + size_t componentLength, + uint32_t clampAtBoundary) { + if (!output || !lengthInOut || !component || componentLength == 0 || + componentLength > NTL_PATH_COMPONENT_MAX) + return NTLSTATUS_INVALID_PARAMETER; + + size_t length = *lengthInOut; + if (componentLength == 1 && component[0] == '.') + return NTLSTATUS_SUCCESS; + if (componentLength == 2 && component[0] == '.' && component[1] == '.') { + if (length <= componentStart) + return clampAtBoundary ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED; + size_t slash = length; + while (slash > componentStart && output[slash - 1u] != '/') slash--; + if (slash <= componentStart) + return clampAtBoundary ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED; + length = slash - 1u; + *lengthInOut = length; + output[length] = '\0'; + return NTLSTATUS_SUCCESS; + } + + if (length && output[length - 1u] != '/') { + if (length + 1u >= outputSize) return NTLSTATUS_BUFFER_TOO_SMALL; + output[length++] = '/'; + } + if (componentLength > outputSize - length - 1u) + return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(output + length, component, componentLength); + length += componentLength; + output[length] = '\0'; + *lengthInOut = length; + return NTLSTATUS_SUCCESS; +} + +// Returns whether one relative path contains a parent-traversal component. +static uint32_t NtlpContainsParentComponent(SYS_STRING_VIEW path) { + size_t componentStart = 0; + for (size_t i = 0; i <= path.Length; ++i) { + if (i != path.Length && path.Data[i] != '/') + continue; + size_t componentLength = i - componentStart; + if (componentLength == 2 && path.Data[componentStart] == '.' && + path.Data[componentStart + 1u] == '.') + return 1; + componentStart = i + 1u; + } + return 0; +} + +// Builds the canonical absolute prefix preceding the first parent component. +static KSTATUS NtlpBuildAbsoluteParentPrefix(SYS_STRING_VIEW path, + char output[SYS_PATH_MAX + 1u], + size_t* lengthOut) { + if (!output || !lengthOut || !SysSvIsValid(path) || path.Length == 0 || path.Data[0] != '/') + return NTLSTATUS_INVALID_PARAMETER; + + size_t length = 1u; + output[0] = '/'; + output[1] = '\0'; + + size_t componentStart = 1u; + for (size_t i = 1u; i <= path.Length; ++i) { + if (i != path.Length && path.Data[i] != '/') + continue; + size_t componentLength = i - componentStart; + if (componentLength == 1u && path.Data[componentStart] == '.') { + componentStart = i + 1u; + continue; + } + if (componentLength == 2u && path.Data[componentStart] == '.' && + path.Data[componentStart + 1u] == '.') { + if (length <= 1u) return NTLSTATUS_ACCESS_DENIED; + *lengthOut = length; + return NTLSTATUS_SUCCESS; + } + if (componentLength != 0) { + KSTATUS status = NtlpAppendComponent(output, SYS_PATH_MAX + 1u, &length, 1u, + path.Data + componentStart, componentLength, 0); + if (!KSUCCESS(status)) return status; + } + componentStart = i + 1u; + } + + return NTLSTATUS_INVALID_PARAMETER; +} + +// Resolves one canonical path solely to discover the owning RSSRV resource-prefix boundary. +static KSTATUS NtlpResolveResourceBoundary(const char* canonical, + size_t canonicalLength, + size_t* boundaryOut) { + if (!canonical || canonicalLength < 2u || !boundaryOut) + return NTLSTATUS_INVALID_PARAMETER; + *boundaryOut = 0; + + SYS_HANDLE session = SYS_HANDLE_INVALID; + KSTATUS status = NtlResourceConnect(&session); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE root = SYS_HANDLE_INVALID; + uint32_t kind = NTL_RESOURCE_KIND_INVALID; + uint32_t resolvedLength = 0; + status = NtlResourceResolve(session, SysSvFromParts(canonical, canonicalLength), + NTL_RESOURCE_FLAG_NONE, &root, &kind, &resolvedLength); + (void)NtlResourceClose(session); + if (root != SYS_HANDLE_INVALID) (void)SysCloseHandle(root); + if (!KSUCCESS(status)) return status; + if (kind == NTL_RESOURCE_KIND_INVALID || resolvedLength < 2u || resolvedLength > canonicalLength || + (resolvedLength != canonicalLength && canonical[resolvedLength] != '/')) + return NTLSTATUS_FAILURE; + + *boundaryOut = resolvedLength; + return NTLSTATUS_SUCCESS; +} + +// Normalizes one absolute or CWD-relative native path into a canonical resource path. +static KSTATUS NtlpNormalizePath(SYS_STRING_VIEW path, char output[SYS_PATH_MAX + 1u], size_t* lengthOut) { + if (!output || !lengthOut || !SysSvIsValid(path) || SysSvContainsByte(path, '\0') || + SysSvContainsByte(path, '\\')) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = NtlpEnsurePathState(); + if (!KSUCCESS(status)) return status; + + size_t outputLength = 0; + size_t rootBoundary = 1u; + size_t sourceOffset = 0; + uint32_t absolutePath = path.Length && path.Data[0] == '/'; + if (absolutePath) { + output[0] = '/'; + output[1] = '\0'; + outputLength = 1u; + sourceOffset = 1u; + + if (NtlpContainsParentComponent(path)) { + char prefix[SYS_PATH_MAX + 1u]; + size_t prefixLength = 0; + status = NtlpBuildAbsoluteParentPrefix(path, prefix, &prefixLength); + if (!KSUCCESS(status)) return status; + status = NtlpResolveResourceBoundary(prefix, prefixLength, &rootBoundary); + if (!KSUCCESS(status)) return status; + } + } else { + NtlpLockPathState(); + if (gCurrentDirectoryLength > SYS_PATH_MAX || + gCurrentDirectoryRootLength > gCurrentDirectoryLength) { + NtlpUnlockPathState(); + return NTLSTATUS_FAILURE; + } + memcpy(output, gCurrentDirectory, gCurrentDirectoryLength + 1u); + outputLength = gCurrentDirectoryLength; + rootBoundary = gCurrentDirectoryRootLength; + NtlpUnlockPathState(); + sourceOffset = 0; + + if (rootBoundary == 0 && NtlpContainsParentComponent(path)) { + status = NtlpResolveResourceBoundary(output, outputLength, &rootBoundary); + if (!KSUCCESS(status)) return status; + + NtlpLockPathState(); + if (gCurrentDirectoryLength == outputLength && + memcmp(gCurrentDirectory, output, outputLength + 1u) == 0) + gCurrentDirectoryRootLength = rootBoundary; + NtlpUnlockPathState(); + } + } + + size_t componentStart = sourceOffset; + for (size_t i = sourceOffset; i <= path.Length; ++i) { + if (i != path.Length && path.Data[i] != '/') + continue; + size_t componentLength = i - componentStart; + if (componentLength != 0) { + status = NtlpAppendComponent(output, SYS_PATH_MAX + 1u, &outputLength, + rootBoundary, path.Data + componentStart, componentLength, + absolutePath ? 0u : 1u); + if (!KSUCCESS(status)) return status; + } + componentStart = i + 1u; + } + + while (outputLength > 1u && output[outputLength - 1u] == '/') outputLength--; + output[outputLength] = '\0'; + if (outputLength < 2u || output[0] != '/') return NTLSTATUS_INVALID_PARAMETER; + *lengthOut = outputLength; + return NTLSTATUS_SUCCESS; +} + +// Resolves a canonical resource path to one volume/directory capability and a relative tail. +static KSTATUS NtlpResolveRoot(const char* canonical, + size_t canonicalLength, + uint32_t modify, + NTL_RESOLVED_ROOT* rootOut) { + if (!canonical || canonicalLength < 2u || !rootOut) + return NTLSTATUS_INVALID_PARAMETER; + memset(rootOut, 0, sizeof(*rootOut)); + + SYS_HANDLE session = SYS_HANDLE_INVALID; + KSTATUS status = NtlResourceConnect(&session); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE root = SYS_HANDLE_INVALID; + uint32_t kind = NTL_RESOURCE_KIND_INVALID; + uint32_t resolvedLength = 0; + status = NtlResourceResolve(session, + SysSvFromParts(canonical, canonicalLength), + modify ? NTL_RESOURCE_RESOLVE_FLAG_MODIFY : NTL_RESOURCE_FLAG_NONE, + &root, &kind, &resolvedLength); + (void)NtlResourceClose(session); + if (!KSUCCESS(status)) return status; + + size_t relativeOffset = resolvedLength; + if (relativeOffset < canonicalLength) relativeOffset++; + if (relativeOffset > canonicalLength) { + (void)SysCloseHandle(root); + return NTLSTATUS_FAILURE; + } + rootOut->Handle = root; + rootOut->Kind = kind; + rootOut->ResolvedLength = resolvedLength; + rootOut->RelativePath = SysSvFromParts(canonical + relativeOffset, canonicalLength - relativeOffset); + return NTLSTATUS_SUCCESS; +} + +// Returns one canonical absolute resource path. +KSTATUS NtlRealPath(SYS_STRING_VIEW path, + char* buffer, + size_t bufferSize, + size_t* requiredSizeOut) { + if (requiredSizeOut) *requiredSizeOut = 0; + if (!buffer && bufferSize) return NTLSTATUS_INVALID_PARAMETER; + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + + size_t required = length + 1u; + if (requiredSizeOut) *requiredSizeOut = required; + if (!buffer) return NTLSTATUS_SUCCESS; + if (bufferSize < required) return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(buffer, canonical, required); + return NTLSTATUS_SUCCESS; +} + +// Returns the process-local userspace current directory. +KSTATUS NtlGetCurrentDirectory(char* buffer, size_t bufferSize, size_t* requiredSizeOut) { + if (requiredSizeOut) *requiredSizeOut = 0; + if (!buffer && bufferSize) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = NtlpEnsurePathState(); + if (!KSUCCESS(status)) return status; + + NtlpLockPathState(); + size_t required = gCurrentDirectoryLength + 1u; + if (requiredSizeOut) *requiredSizeOut = required; + if (buffer && bufferSize >= required) + memcpy(buffer, gCurrentDirectory, required); + NtlpUnlockPathState(); + if (!buffer) return NTLSTATUS_SUCCESS; + return bufferSize >= required ? NTLSTATUS_SUCCESS : NTLSTATUS_BUFFER_TOO_SMALL; +} + +// Validates and installs one process-local userspace current directory. +KSTATUS NtlSetCurrentDirectory(SYS_STRING_VIEW path) { + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + + NTL_RESOLVED_ROOT root; + status = NtlpResolveRoot(canonical, length, 0, &root); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE directory = SYS_HANDLE_INVALID; + SYS_OPEN_DIR_AT_REQUEST open = SysMakeOpenDirAtRequest(root.Handle, root.RelativePath, + SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN, + SYS_DIR_FLAG_NONE, &directory); + status = SysOpenDirAt(&open); + (void)SysCloseHandle(root.Handle); + if (!KSUCCESS(status)) return status; + (void)SysCloseHandle(directory); + + NtlpLockPathState(); + memcpy(gCurrentDirectory, canonical, length + 1u); + gCurrentDirectoryLength = length; + gCurrentDirectoryRootLength = root.ResolvedLength; + NtlpUnlockPathState(); + return NTLSTATUS_SUCCESS; +} + +// Opens or creates one file through RSSRV resource resolution and capability-relative kernel I/O. +KSTATUS NtlOpenFile(SYS_STRING_VIEW path, + uint32_t access, + uint32_t disposition, + uint32_t flags, + SYS_HANDLE* handleOut) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; + *handleOut = SYS_HANDLE_INVALID; + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + + uint32_t modify = ((access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND | SYS_FILE_ACCESS_LOCK)) != 0 || + disposition != SYS_FILE_OPEN_EXISTING); + NTL_RESOLVED_ROOT root; + status = NtlpResolveRoot(canonical, length, modify, &root); + if (!KSUCCESS(status)) return status; + if (root.RelativePath.Length == 0) { + (void)SysCloseHandle(root.Handle); + return NTLSTATUS_INVALID_PARAMETER; + } + + SYS_OPEN_FILE_AT_REQUEST request = SysMakeOpenFileAtRequest(root.Handle, root.RelativePath, + access, disposition, flags, handleOut); + status = SysOpenFileAt(&request); + (void)SysCloseHandle(root.Handle); + return status; +} + +// Opens one directory through RSSRV resource resolution and capability-relative kernel I/O. +KSTATUS NtlOpenDir(SYS_STRING_VIEW path, uint32_t access, uint32_t flags, SYS_HANDLE* handleOut) { + if (!handleOut) return NTLSTATUS_INVALID_PARAMETER; + *handleOut = SYS_HANDLE_INVALID; + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + + NTL_RESOLVED_ROOT root; + status = NtlpResolveRoot(canonical, length, (access & SYS_DIR_ACCESS_MODIFY) != 0, &root); + if (!KSUCCESS(status)) return status; + SYS_OPEN_DIR_AT_REQUEST request = SysMakeOpenDirAtRequest(root.Handle, root.RelativePath, + access, flags, handleOut); + status = SysOpenDirAt(&request); + (void)SysCloseHandle(root.Handle); + return status; +} + +// Queries file metadata through one resolved root capability. +KSTATUS NtlStatPath(SYS_STRING_VIEW path, SYS_FILE_STAT* statOut) { + if (!statOut) return NTLSTATUS_INVALID_PARAMETER; + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + NTL_RESOLVED_ROOT root; + status = NtlpResolveRoot(canonical, length, 0, &root); + if (!KSUCCESS(status)) return status; + SYS_STAT_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath, statOut }; + status = SysStatAt(&request); + (void)SysCloseHandle(root.Handle); + return status; +} + +// Deletes one file through one resolved writable root capability. +KSTATUS NtlDeleteFile(SYS_STRING_VIEW path) { + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + NTL_RESOLVED_ROOT root; + status = NtlpResolveRoot(canonical, length, 1, &root); + if (!KSUCCESS(status)) return status; + SYS_PATH_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath }; + status = root.RelativePath.Length ? SysDeleteAt(&request) : NTLSTATUS_INVALID_PARAMETER; + (void)SysCloseHandle(root.Handle); + return status; +} + +// Renames one file using independently resolved writable roots. +KSTATUS NtlRenameFile(SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) { + char oldCanonical[SYS_PATH_MAX + 1u]; + char newCanonical[SYS_PATH_MAX + 1u]; + size_t oldLength = 0, newLength = 0; + KSTATUS status = NtlpNormalizePath(oldPath, oldCanonical, &oldLength); + if (!KSUCCESS(status)) return status; + status = NtlpNormalizePath(newPath, newCanonical, &newLength); + if (!KSUCCESS(status)) return status; + + NTL_RESOLVED_ROOT oldRoot, newRoot; + status = NtlpResolveRoot(oldCanonical, oldLength, 1, &oldRoot); + if (!KSUCCESS(status)) return status; + status = NtlpResolveRoot(newCanonical, newLength, 1, &newRoot); + if (!KSUCCESS(status)) { (void)SysCloseHandle(oldRoot.Handle); return status; } + if (oldRoot.RelativePath.Length == 0 || newRoot.RelativePath.Length == 0) { + (void)SysCloseHandle(newRoot.Handle); + (void)SysCloseHandle(oldRoot.Handle); + return NTLSTATUS_INVALID_PARAMETER; + } + + SYS_RENAME_AT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.OldRootHandle = oldRoot.Handle; + request.OldPath = oldRoot.RelativePath; + request.NewRootHandle = newRoot.Handle; + request.NewPath = newRoot.RelativePath; + status = SysRenameAt(&request); + (void)SysCloseHandle(newRoot.Handle); + (void)SysCloseHandle(oldRoot.Handle); + return status; +} + +// Creates one directory through one resolved writable root capability. +KSTATUS NtlCreateDirectory(SYS_STRING_VIEW path) { + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + NTL_RESOLVED_ROOT root; + status = NtlpResolveRoot(canonical, length, 1, &root); + if (!KSUCCESS(status)) return status; + SYS_PATH_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath }; + status = root.RelativePath.Length ? SysCreateDirAt(&request) : NTLSTATUS_INVALID_PARAMETER; + (void)SysCloseHandle(root.Handle); + return status; +} + +// Removes one directory through one resolved writable root capability. +KSTATUS NtlRemoveDirectory(SYS_STRING_VIEW path) { + char canonical[SYS_PATH_MAX + 1u]; + size_t length = 0; + KSTATUS status = NtlpNormalizePath(path, canonical, &length); + if (!KSUCCESS(status)) return status; + NTL_RESOLVED_ROOT root; + status = NtlpResolveRoot(canonical, length, 1, &root); + if (!KSUCCESS(status)) return status; + SYS_PATH_AT_REQUEST request = { sizeof(request), root.Handle, root.RelativePath }; + status = root.RelativePath.Length ? SysRemoveDirAt(&request) : NTLSTATUS_INVALID_PARAMETER; + (void)SysCloseHandle(root.Handle); + return status; +} diff --git a/code/user/syscall/pe_loader.c b/code/user/syscall/pe_loader.c new file mode 100644 index 0000000..ae655d0 --- /dev/null +++ b/code/user/syscall/pe_loader.c @@ -0,0 +1,408 @@ +// Userspace PE32+ image loader. The kernel sees only generic process memory and execution state. + +#include +#include +#include +#include +#include +#include + +#define PE_DOS_MAGIC 0x5A4Du +#define PE_SIGNATURE 0x00004550u +#define PE_MACHINE_AMD64 0x8664u +#define PE_FILE_RELOCS_STRIPPED 0x0001u +#define PE_OPTIONAL_MAGIC_PE32_PLUS 0x020Bu +#define PE_OPTIONAL_MIN_BYTES 112u +#define PE_SECTION_BYTES 40u +#define PE_DIRECTORY_IMPORT 1u +#define PE_DIRECTORY_RELOC 5u +#define PE_DIRECTORY_DELAY_IMPORT 13u +#define PE_ORDINAL_FLAG64 0x8000000000000000ull +#define PE_RELOC_DIR64 10u +#define PE_PAGE_SIZE 4096u +#define PE_SCN_MEM_EXECUTE 0x20000000u +#define PE_SCN_MEM_READ 0x40000000u +#define PE_SCN_MEM_WRITE 0x80000000u + +typedef struct PE_VIEW { + const uint8_t* File; + size_t FileSize; + size_t OptionalOffset; + size_t SectionOffset; + uint16_t SectionCount; + uint16_t OptionalSize; + uint16_t FileCharacteristics; + uint32_t DirectoryCount; + uint64_t ImageBase; + uint32_t ImageSize; + uint32_t HeaderSize; + uint32_t EntryRva; +} PE_VIEW; + +static bool PeRange(size_t offset, size_t length, size_t size) { + return offset <= size && length <= size - offset; +} + +static uint16_t PeU16(const void* p) { uint16_t v; memcpy(&v, p, sizeof(v)); return v; } +static uint32_t PeU32(const void* p) { uint32_t v; memcpy(&v, p, sizeof(v)); return v; } +static uint64_t PeU64(const void* p) { uint64_t v; memcpy(&v, p, sizeof(v)); return v; } +static void PeW64(void* p, uint64_t v) { memcpy(p, &v, sizeof(v)); } + +static bool PeImageRange(uint32_t rva, size_t length, size_t imageSize) { + return (size_t)rva <= imageSize && length <= imageSize - (size_t)rva; +} + +static bool PeImageString(const uint8_t* image, size_t imageSize, uint32_t rva, size_t* lengthOut) { + if ((size_t)rva >= imageSize) return false; + for (size_t i = rva; i < imageSize; ++i) { + if (image[i] == 0) { + if (lengthOut) *lengthOut = i - rva; + return true; + } + } + return false; +} + +static KSTATUS PeParse(const void* file, size_t fileSize, PE_VIEW* out) { + if (!file || !out || fileSize < 64u) return NTLSTATUS_FAILURE; + const uint8_t* b = (const uint8_t*)file; + if (PeU16(b) != PE_DOS_MAGIC) return NTLSTATUS_FAILURE; + size_t nt = PeU32(b + 0x3cu); + if (!PeRange(nt, 24u, fileSize) || PeU32(b + nt) != PE_SIGNATURE) return NTLSTATUS_FAILURE; + if (PeU16(b + nt + 4u) != PE_MACHINE_AMD64) return NTLSTATUS_NOT_SUPPORTED; + uint16_t sections = PeU16(b + nt + 6u); + uint16_t optionalSize = PeU16(b + nt + 20u); + uint16_t fileCharacteristics = PeU16(b + nt + 22u); + size_t optional = nt + 24u; + if (optionalSize < PE_OPTIONAL_MIN_BYTES || !PeRange(optional, optionalSize, fileSize)) return NTLSTATUS_FAILURE; + if (PeU16(b + optional) != PE_OPTIONAL_MAGIC_PE32_PLUS) return NTLSTATUS_NOT_SUPPORTED; + + uint64_t imageBase = PeU64(b + optional + 24u); + uint32_t imageSize = PeU32(b + optional + 56u); + uint32_t headerSize = PeU32(b + optional + 60u); + uint32_t entryRva = PeU32(b + optional + 16u); + uint32_t directories = PeU32(b + optional + 108u); + uint32_t available = (uint32_t)((optionalSize - PE_OPTIONAL_MIN_BYTES) / 8u); + if (!imageBase || !imageSize || !headerSize || headerSize > imageSize || entryRva >= imageSize || directories > available) + return NTLSTATUS_FAILURE; + if (headerSize > fileSize) return NTLSTATUS_FAILURE; + if (directories > PE_DIRECTORY_DELAY_IMPORT) { + const uint8_t* delay = b + optional + PE_OPTIONAL_MIN_BYTES + PE_DIRECTORY_DELAY_IMPORT * 8u; + if (PeU32(delay) || PeU32(delay + 4u)) return NTLSTATUS_NOT_SUPPORTED; + } + + size_t sectionOffset = optional + optionalSize; + size_t sectionBytes = (size_t)sections * PE_SECTION_BYTES; + if (sections && sectionBytes / PE_SECTION_BYTES != sections) return NTLSTATUS_FAILURE; + if (!PeRange(sectionOffset, sectionBytes, fileSize)) return NTLSTATUS_FAILURE; + + for (uint16_t i = 0; i < sections; ++i) { + const uint8_t* sec = b + sectionOffset + (size_t)i * PE_SECTION_BYTES; + uint32_t va = PeU32(sec + 12u); + uint32_t rawSize = PeU32(sec + 16u); + uint32_t raw = PeU32(sec + 20u); + uint32_t virtualSize = PeU32(sec + 8u); + uint32_t span = virtualSize > rawSize ? virtualSize : rawSize; + if ((va & (PE_PAGE_SIZE - 1u)) != 0) return NTLSTATUS_NOT_SUPPORTED; + if (!PeImageRange(va, span, imageSize)) return NTLSTATUS_FAILURE; + if (rawSize && !PeRange(raw, rawSize, fileSize)) return NTLSTATUS_FAILURE; + } + + memset(out, 0, sizeof(*out)); + out->File = b; + out->FileSize = fileSize; + out->OptionalOffset = optional; + out->SectionOffset = sectionOffset; + out->SectionCount = sections; + out->OptionalSize = optionalSize; + out->FileCharacteristics = fileCharacteristics; + out->DirectoryCount = directories; + out->ImageBase = imageBase; + out->ImageSize = imageSize; + out->HeaderSize = headerSize; + out->EntryRva = entryRva; + return NTLSTATUS_SUCCESS; +} + +static bool PeDirectory(const PE_VIEW* view, uint32_t index, uint32_t* rva, uint32_t* size) { + if (!view || index >= view->DirectoryCount) return false; + const uint8_t* d = view->File + view->OptionalOffset + PE_OPTIONAL_MIN_BYTES + (size_t)index * 8u; + if (rva) *rva = PeU32(d); + if (size) *size = PeU32(d + 4u); + return true; +} + +static KSTATUS PeAllocCurrent(size_t bytes, void** out) { + if (!out || !bytes) return NTLSTATUS_INVALID_PARAMETER; + size_t pages = (bytes + PE_PAGE_SIZE - 1u) / PE_PAGE_SIZE; + SYS_ALLOC_VM_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Protect = SYS_VM_READWRITE; + req.Flags = SYS_VM_FLAG_NONE; + req.PageCount = pages; + req.BaseOut = out; + return SysAllocVm(&req); +} + +static KSTATUS PeReadFile(SYS_HANDLE file, void** bufferOut, size_t* sizeOut) { + if (!bufferOut || !sizeOut) return NTLSTATUS_INVALID_PARAMETER; + *bufferOut = 0; *sizeOut = 0; + uint64_t fileSize64 = 0; + KSTATUS s = SysGetFileSize(file, &fileSize64); + if (!KSUCCESS(s)) return s; + if (!fileSize64 || fileSize64 > (uint64_t)SIZE_MAX) return NTLSTATUS_BUFFER_TOO_SMALL; + size_t fileSize = (size_t)fileSize64; + void* buffer = 0; + s = PeAllocCurrent(fileSize, &buffer); + if (!KSUCCESS(s)) return s; + s = SysSeekFile(file, 0); + if (!KSUCCESS(s)) { SysFreeVm(buffer); return s; } + size_t got = 0; + SYS_FILE_IO_REQUEST read = SysMakeFileIoRequest(file, buffer, fileSize, &got); + s = SysReadFile(&read); + if (!KSUCCESS(s) || got != fileSize) { + SysFreeVm(buffer); + return KSUCCESS(s) ? NTLSTATUS_DEVICE_ERROR : s; + } + *bufferOut = buffer; *sizeOut = fileSize; + return NTLSTATUS_SUCCESS; +} + +static KSTATUS PeRelocate(uint8_t* image, size_t imageSize, const PE_VIEW* view, uintptr_t actualBase) { + uintptr_t delta = actualBase - (uintptr_t)view->ImageBase; + if (!delta) return NTLSTATUS_SUCCESS; + uint32_t rva = 0, size = 0; + if (!PeDirectory(view, PE_DIRECTORY_RELOC, &rva, &size) || !rva || !size) { + // An image with no base fixups can still be position-independent. Only the + // COFF RELOCS_STRIPPED bit says missing relocation data makes rebasing invalid. + return (view->FileCharacteristics & PE_FILE_RELOCS_STRIPPED) + ? NTLSTATUS_NOT_SUPPORTED + : NTLSTATUS_SUCCESS; + } + if (!PeImageRange(rva, size, imageSize)) return NTLSTATUS_FAILURE; + size_t offset = rva; + size_t end = (size_t)rva + size; + while (offset < end) { + if (end - offset < 8u) return NTLSTATUS_FAILURE; + uint32_t pageRva = PeU32(image + offset); + uint32_t blockSize = PeU32(image + offset + 4u); + if (blockSize < 8u || blockSize > end - offset || ((blockSize - 8u) & 1u)) return NTLSTATUS_FAILURE; + uint32_t count = (blockSize - 8u) / 2u; + for (uint32_t i = 0; i < count; ++i) { + uint16_t e = PeU16(image + offset + 8u + (size_t)i * 2u); + uint16_t type = e >> 12; + uint16_t pageOffset = e & 0x0fffu; + if (type == 0) continue; + if (type != PE_RELOC_DIR64) return NTLSTATUS_NOT_SUPPORTED; + uint32_t fixRva = pageRva + pageOffset; + if (fixRva < pageRva || !PeImageRange(fixRva, 8u, imageSize)) return NTLSTATUS_FAILURE; + PeW64(image + fixRva, PeU64(image + fixRva) + delta); + } + offset += blockSize; + } + return offset == end ? NTLSTATUS_SUCCESS : NTLSTATUS_FAILURE; +} + +static KSTATUS PeBindImports(uint8_t* image, size_t imageSize, const PE_VIEW* view, + NTL_PE_IMPORT_RESOLVER resolver, void* context) { + uint32_t rva = 0, size = 0; + if (!PeDirectory(view, PE_DIRECTORY_IMPORT, &rva, &size) || !rva || !size) + return NTLSTATUS_SUCCESS; + if (!resolver) return NTLSTATUS_NOT_FOUND; + if (!PeImageRange(rva, size, imageSize)) return NTLSTATUS_FAILURE; + + size_t offset = rva; + size_t end = (size_t)rva + size; + while (offset + 20u <= end) { + uint8_t* d = image + offset; + uint32_t originalThunk = PeU32(d + 0u); + uint32_t nameRva = PeU32(d + 12u); + uint32_t firstThunk = PeU32(d + 16u); + if (!originalThunk && !nameRva && !firstThunk) return NTLSTATUS_SUCCESS; + size_t moduleLen = 0; + if (!nameRva || !PeImageString(image, imageSize, nameRva, &moduleLen)) return NTLSTATUS_FAILURE; + uint32_t sourceThunk = originalThunk ? originalThunk : firstThunk; + if (!sourceThunk || !firstThunk) return NTLSTATUS_FAILURE; + + for (uint32_t index = 0;; ++index) { + size_t sourceRva = (size_t)sourceThunk + (size_t)index * 8u; + size_t targetRva = (size_t)firstThunk + (size_t)index * 8u; + if (sourceRva + 8u > imageSize || targetRva + 8u > imageSize) return NTLSTATUS_FAILURE; + uint64_t thunk = PeU64(image + sourceRva); + if (!thunk) break; + if (thunk & PE_ORDINAL_FLAG64) return NTLSTATUS_NOT_SUPPORTED; + uint32_t hintNameRva = (uint32_t)thunk; + if (!PeImageRange(hintNameRva, 3u, imageSize)) return NTLSTATUS_FAILURE; + size_t symbolLen = 0; + if (!PeImageString(image, imageSize, hintNameRva + 2u, &symbolLen)) return NTLSTATUS_FAILURE; + uintptr_t address = 0; + KSTATUS s = resolver(context, + SysSvFromParts((const char*)image + nameRva, moduleLen), + SysSvFromParts((const char*)image + hintNameRva + 2u, symbolLen), + &address); + if (!KSUCCESS(s)) return s; + if (!address) return NTLSTATUS_NOT_FOUND; + PeW64(image + targetRva, address); + } + offset += 20u; + } + return NTLSTATUS_FAILURE; +} + +static uint32_t PeSectionProtect(uint32_t characteristics) { + bool r = (characteristics & PE_SCN_MEM_READ) != 0; + bool w = (characteristics & PE_SCN_MEM_WRITE) != 0; + bool x = (characteristics & PE_SCN_MEM_EXECUTE) != 0; + if (x && w) return SYS_VM_EXECUTE_READWRITE; + if (x && r) return SYS_VM_EXECUTE_READ; + if (x) return SYS_VM_EXECUTE; + if (w) return SYS_VM_READWRITE; + if (r) return SYS_VM_READ; + return SYS_VM_NOACCESS; +} + +static KSTATUS PeProtectRange(SYS_HANDLE process, void* base, size_t pages, uint32_t protect) { + if (process != SYS_HANDLE_INVALID) + return SysProtectProcessVm(process, base, pages, protect); + return SysProtectVm(base, pages, protect, (uint32_t*)0); +} + +static KSTATUS PeProtectImage(SYS_HANDLE process, uint8_t* image, const PE_VIEW* view) { + size_t pages = ((size_t)view->ImageSize + PE_PAGE_SIZE - 1u) / PE_PAGE_SIZE; + KSTATUS s = PeProtectRange(process, image, pages, SYS_VM_READ); + if (!KSUCCESS(s)) return s; + for (uint16_t i = 0; i < view->SectionCount; ++i) { + const uint8_t* sec = view->File + view->SectionOffset + (size_t)i * PE_SECTION_BYTES; + uint32_t va = PeU32(sec + 12u); + uint32_t virtualSize = PeU32(sec + 8u); + uint32_t rawSize = PeU32(sec + 16u); + uint32_t span = virtualSize > rawSize ? virtualSize : rawSize; + if (!span) continue; + size_t sectionPages = ((size_t)span + PE_PAGE_SIZE - 1u) / PE_PAGE_SIZE; + s = PeProtectRange(process, image + va, sectionPages, PeSectionProtect(PeU32(sec + 36u))); + if (!KSUCCESS(s)) return s; + } + return NTLSTATUS_SUCCESS; +} + +static KSTATUS PeAllocateImage(SYS_HANDLE process, size_t pages, void** baseOut) { + if (process == SYS_HANDLE_INVALID) { + SYS_ALLOC_VM_REQUEST alloc; + memset(&alloc, 0, sizeof(alloc)); + alloc.Size = sizeof(alloc); + alloc.Protect = SYS_VM_READWRITE; + alloc.Flags = SYS_VM_FLAG_NONE; + alloc.PageCount = pages; + alloc.BaseOut = baseOut; + return SysAllocVm(&alloc); + } + + SYS_ALLOC_PROCESS_VM_REQUEST alloc; + memset(&alloc, 0, sizeof(alloc)); + alloc.Size = sizeof(alloc); + alloc.ProcessHandle = process; + alloc.Protect = SYS_VM_READWRITE; + alloc.Flags = SYS_VM_FLAG_NONE; + alloc.PageCount = pages; + alloc.BaseOut = baseOut; + return SysAllocProcessVm(&alloc); +} + +KSTATUS NtlLoadPeImage(const NTL_PE_LOAD_REQUEST* request) { + if (!request || request->Size != sizeof(*request) || request->Flags != NTL_PE_LOAD_FLAG_NONE || + request->FileHandle == SYS_HANDLE_INVALID || !request->ImageOut) + return NTLSTATUS_INVALID_PARAMETER; + + void* file = 0; + size_t fileSize = 0; + KSTATUS s = PeReadFile(request->FileHandle, &file, &fileSize); + if (!KSUCCESS(s)) return s; + + PE_VIEW view; + s = PeParse(file, fileSize, &view); + if (!KSUCCESS(s)) { SysFreeVm(file); return s; } + + size_t pages = ((size_t)view.ImageSize + PE_PAGE_SIZE - 1u) / PE_PAGE_SIZE; + void* imageBase = 0; + s = PeAllocateImage(request->ProcessHandle, pages, &imageBase); + if (!KSUCCESS(s)) { SysFreeVm(file); return s; } + + uint8_t* image = (uint8_t*)imageBase; + memset(image, 0, pages * PE_PAGE_SIZE); + memcpy(image, file, view.HeaderSize); + for (uint16_t i = 0; i < view.SectionCount; ++i) { + const uint8_t* sec = view.File + view.SectionOffset + (size_t)i * PE_SECTION_BYTES; + uint32_t va = PeU32(sec + 12u); + uint32_t rawSize = PeU32(sec + 16u); + uint32_t raw = PeU32(sec + 20u); + if (rawSize) memcpy(image + va, (const uint8_t*)file + raw, rawSize); + } + + s = PeRelocate(image, view.ImageSize, &view, (uintptr_t)image); + if (KSUCCESS(s)) { + request->ImageOut->Base = (uintptr_t)image; + request->ImageOut->Size = view.ImageSize; + request->ImageOut->EntryPoint = (uintptr_t)image + view.EntryRva; + request->ImageOut->PreferredBase = view.ImageBase; + if (request->Prepared) + s = request->Prepared(request->PreparedContext, request->ImageOut); + } + if (KSUCCESS(s)) s = PeBindImports(image, view.ImageSize, &view, request->ResolveImport, request->ResolveContext); + if (KSUCCESS(s)) s = PeProtectImage(request->ProcessHandle, image, &view); + if (!KSUCCESS(s) && request->ProcessHandle == SYS_HANDLE_INVALID) + (void)SysFreeVm(image); + SysFreeVm(file); + return s; +} + +KSTATUS NtlGetPeExport(uintptr_t imageBase, SYS_STRING_VIEW name, uintptr_t* addressOut) { + if (!imageBase || !addressOut || !SysSvIsValid(name) || name.Length == 0 || + SysSvContainsByte(name, '\0')) + return NTLSTATUS_INVALID_PARAMETER; + *addressOut = 0; + + const uint8_t* image = (const uint8_t*)imageBase; + if (PeU16(image) != PE_DOS_MAGIC) return NTLSTATUS_FAILURE; + uint32_t nt = PeU32(image + 0x3cu); + if (PeU32(image + nt) != PE_SIGNATURE || PeU16(image + nt + 4u) != PE_MACHINE_AMD64) + return NTLSTATUS_FAILURE; + const uint8_t* optional = image + nt + 24u; + if (PeU16(optional) != PE_OPTIONAL_MAGIC_PE32_PLUS) return NTLSTATUS_FAILURE; + uint32_t imageSize = PeU32(optional + 56u); + uint32_t directories = PeU32(optional + 108u); + if (directories < 1u) return NTLSTATUS_NOT_FOUND; + uint32_t exportRva = PeU32(optional + PE_OPTIONAL_MIN_BYTES); + uint32_t exportSize = PeU32(optional + PE_OPTIONAL_MIN_BYTES + 4u); + if (!exportRva || !exportSize || !PeImageRange(exportRva, exportSize, imageSize) || + !PeImageRange(exportRva, 40u, imageSize)) + return NTLSTATUS_NOT_FOUND; + + const uint8_t* exp = image + exportRva; + uint32_t functionCount = PeU32(exp + 20u); + uint32_t nameCount = PeU32(exp + 24u); + uint32_t functionsRva = PeU32(exp + 28u); + uint32_t namesRva = PeU32(exp + 32u); + uint32_t ordinalsRva = PeU32(exp + 36u); + if (!PeImageRange(functionsRva, (size_t)functionCount * 4u, imageSize) || + !PeImageRange(namesRva, (size_t)nameCount * 4u, imageSize) || + !PeImageRange(ordinalsRva, (size_t)nameCount * 2u, imageSize)) + return NTLSTATUS_FAILURE; + + for (uint32_t i = 0; i < nameCount; ++i) { + uint32_t nameRva = PeU32(image + namesRva + (size_t)i * 4u); + size_t exportedLength = 0; + if (!PeImageString(image, imageSize, nameRva, &exportedLength)) return NTLSTATUS_FAILURE; + if (exportedLength != name.Length || memcmp(image + nameRva, name.Data, name.Length) != 0) + continue; + uint16_t ordinal = PeU16(image + ordinalsRva + (size_t)i * 2u); + if (ordinal >= functionCount) return NTLSTATUS_FAILURE; + uint32_t functionRva = PeU32(image + functionsRva + (size_t)ordinal * 4u); + if (!functionRva || !PeImageRange(functionRva, 1u, imageSize)) return NTLSTATUS_NOT_FOUND; + if (functionRva >= exportRva && functionRva < exportRva + exportSize) + return NTLSTATUS_NOT_SUPPORTED; + *addressOut = imageBase + functionRva; + return NTLSTATUS_SUCCESS; + } + return NTLSTATUS_NOT_FOUND; +} diff --git a/code/user/syscall/process_loader.c b/code/user/syscall/process_loader.c new file mode 100644 index 0000000..5733e3d --- /dev/null +++ b/code/user/syscall/process_loader.c @@ -0,0 +1,232 @@ +// Native userspace process construction built on capability-relative file open and PE materialization. + +#include +#include +#include +#include +#include +#include "module_loader_internal.h" + +// Terminates one failed constructing child and releases the parent process handle. +static void NtlDiscardChild(SYS_HANDLE processHandle) { + if (processHandle == SYS_HANDLE_INVALID) + return; + (void)SysKillProcess(processHandle, (int32_t)NTLSTATUS_PROCESS_KILLED); + (void)SysCloseHandle(processHandle); +} + +// Creates and starts one child from an already-open PE file capability. +static KSTATUS NtlCreateProcessFile(const NTL_CREATE_PROCESS_AT_REQUEST* request, SYS_HANDLE fileHandle) { + if (!request || fileHandle == SYS_HANDLE_INVALID) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_HANDLE processHandle = SYS_HANDLE_INVALID; + SYS_CREATE_PROCESS_OBJECT_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Flags = SYS_CREATE_PROCESS_OBJECT_FLAG_NONE; + create.ImagePath = request->Path; + create.CommandLine = request->CommandLine; + create.SecurityContext = request->SecurityContext; + create.ProcessHandleOut = &processHandle; + KSTATUS status = SysCreateProcessObject(&create); + if (!KSUCCESS(status)) + return status; + + NTL_MODULE_CONTEXT* modules = 0; + status = NtlpCreateModuleContext(processHandle, + request->ModuleRootHandle, + request->ModuleDirectory, + request->ResolveModuleName, + request->ResolveContext, + 0, + &modules); + if (!KSUCCESS(status)) { + NtlDiscardChild(processHandle); + return status; + } + + NTL_PE_IMAGE image; + memset(&image, 0, sizeof(image)); + status = NtlpLoadMainImage(modules, fileHandle, &image); + if (!KSUCCESS(status)) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return status; + } + + SYS_STARTUP_CAPABILITY childCapabilities[SYS_STARTUP_CAPABILITY_MAX]; + memset(childCapabilities, 0, sizeof(childCapabilities)); + for (uint32_t i = 0; i < request->StartupCapabilityCount; ++i) { + const NTL_PROCESS_STARTUP_CAPABILITY* source = &request->StartupCapabilities[i]; + if (!source->Tag || source->SourceHandle == SYS_HANDLE_INVALID) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return NTLSTATUS_INVALID_PARAMETER; + } + for (uint32_t j = 0; j < i; ++j) { + if (childCapabilities[j].Tag == source->Tag) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return NTLSTATUS_INVALID_PARAMETER; + } + } + + SYS_HANDLE childHandle = SYS_HANDLE_INVALID; + SYS_DUPLICATE_HANDLE_REQUEST duplicate; + memset(&duplicate, 0, sizeof(duplicate)); + duplicate.Size = sizeof(duplicate); + duplicate.SourceHandle = source->SourceHandle; + duplicate.TargetProcessHandle = processHandle; + duplicate.DesiredRights = source->DesiredRights; + duplicate.Flags = source->DuplicateFlags; + duplicate.TargetHandleOut = &childHandle; + status = SysDuplicateHandle(&duplicate); + if (!KSUCCESS(status)) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return status; + } + childCapabilities[i].Tag = source->Tag; + childCapabilities[i].Handle = childHandle; + } + + char currentDirectoryBuffer[SYS_PATH_MAX + 1u]; + SYS_STRING_VIEW currentDirectory; + if (request->CurrentDirectory.Length != 0) { + size_t required = 0; + status = NtlRealPath(request->CurrentDirectory, currentDirectoryBuffer, + sizeof(currentDirectoryBuffer), &required); + if (!KSUCCESS(status)) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return status; + } + currentDirectory = SysSvFromParts(currentDirectoryBuffer, required - 1u); + + SYS_HANDLE cwdHandle = SYS_HANDLE_INVALID; + status = NtlOpenDir(currentDirectory, SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN, + SYS_DIR_FLAG_NONE, &cwdHandle); + if (!KSUCCESS(status)) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return status; + } + (void)SysCloseHandle(cwdHandle); + } else { + size_t required = 0; + status = NtlGetCurrentDirectory(currentDirectoryBuffer, sizeof(currentDirectoryBuffer), &required); + if (!KSUCCESS(status)) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return status; + } + currentDirectory = SysSvFromParts(currentDirectoryBuffer, required - 1u); + } + + void* loaderData = 0; + status = NtlpBuildLoaderData(modules, currentDirectory, &loaderData); + if (!KSUCCESS(status)) { + NtlpDestroyModuleContext(modules); + NtlDiscardChild(processHandle); + return status; + } + + SYS_START_PROCESS_REQUEST startRequest; + memset(&startRequest, 0, sizeof(startRequest)); + startRequest.Size = sizeof(startRequest); + startRequest.ProcessHandle = processHandle; + startRequest.Flags = SYS_START_PROCESS_FLAG_NONE; + startRequest.MainImageBase = image.Base; + startRequest.MainImageSize = image.Size; + startRequest.EntryPoint = image.EntryPoint; + startRequest.StartupCapabilities = childCapabilities; + startRequest.StartupCapabilityCount = request->StartupCapabilityCount; + startRequest.LoaderData = loaderData; + status = SysStartProcess(&startRequest); + NtlpDestroyModuleContext(modules); + if (!KSUCCESS(status)) { + NtlDiscardChild(processHandle); + return status; + } + + *request->ProcessHandleOut = processHandle; + return NTLSTATUS_SUCCESS; +} + +// Creates and starts one child from a PE file beneath a capability-relative root. +KSTATUS NtlCreateProcessAt(const NTL_CREATE_PROCESS_AT_REQUEST* request) { + if (!request || request->Size != sizeof(*request) || + request->Flags != NTL_PROCESS_LOAD_FLAG_NONE || + request->RootHandle == SYS_HANDLE_INVALID || + request->ModuleRootHandle == SYS_HANDLE_INVALID || + !request->Path.Data || request->Path.Length == 0 || + !SysSvIsValid(request->ModuleDirectory) || + request->StartupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX || + (request->StartupCapabilityCount && !request->StartupCapabilities) || + !request->ProcessHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + + *request->ProcessHandleOut = SYS_HANDLE_INVALID; + + SYS_HANDLE fileHandle = SYS_HANDLE_INVALID; + SYS_OPEN_FILE_AT_REQUEST open; + memset(&open, 0, sizeof(open)); + open.Size = sizeof(open); + open.RootHandle = request->RootHandle; + open.Path = request->Path; + open.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY; + open.Disposition = SYS_FILE_OPEN_EXISTING; + open.Flags = SYS_FILE_FLAG_NONE; + open.HandleOut = &fileHandle; + KSTATUS status = SysOpenFileAt(&open); + if (!KSUCCESS(status)) + return status; + + status = NtlCreateProcessFile(request, fileHandle); + (void)SysCloseHandle(fileHandle); + return status; +} + +KSTATUS NtlCreateSystemProcess(const NTL_CREATE_SYSTEM_PROCESS_REQUEST* request) { + if (!request || request->Size != sizeof(*request) || + request->Flags != NTL_PROCESS_LOAD_FLAG_NONE || !request->Path.Data || + request->Path.Length == 0 || !request->ProcessHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + + *request->ProcessHandleOut = SYS_HANDLE_INVALID; + + SYS_HANDLE systemRoot = SYS_HANDLE_INVALID; + KSTATUS status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &systemRoot); + if (!KSUCCESS(status)) return status; + + NTL_CREATE_PROCESS_AT_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Flags = request->Flags; + create.RootHandle = systemRoot; + create.Path = request->Path; + create.CommandLine = request->CommandLine; + create.CurrentDirectory = request->CurrentDirectory; + create.SecurityContext = request->SecurityContext; + create.ModuleRootHandle = systemRoot; + create.ModuleDirectory = SysSvFromCstr("system"); + create.ResolveModuleName = request->ResolveModuleName; + create.ResolveContext = request->ResolveContext; + create.StartupCapabilities = request->StartupCapabilities; + create.StartupCapabilityCount = request->StartupCapabilityCount; + create.ProcessHandleOut = request->ProcessHandleOut; + if (request->ImageFile != SYS_HANDLE_INVALID) { + status = NtlCreateProcessFile(&create, request->ImageFile); + } else { + SYS_HANDLE imageFile = SYS_HANDLE_INVALID; + status = NtlOpenFile(request->Path, SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &imageFile); + if (KSUCCESS(status)) { + status = NtlCreateProcessFile(&create, imageFile); + (void)SysCloseHandle(imageFile); + } + } + (void)SysCloseHandle(systemRoot); + return status; +} diff --git a/code/user/syscall/resource.c b/code/user/syscall/resource.c new file mode 100644 index 0000000..4d32613 --- /dev/null +++ b/code/user/syscall/resource.c @@ -0,0 +1,308 @@ +// Native resource-service client. Resource naming policy stays in rssrv; this file only speaks its Port protocol. + +#include +#include +#include +#include + +// Validates the canonical global resource-path syntax understood by rssrv. +static KSTATUS NtlValidateResourcePath(SYS_STRING_VIEW path) { + if (!path.Data || path.Length < 2 || path.Length > NTL_RESOURCE_PATH_MAX || path.Data[0] != '/') + return NTLSTATUS_INVALID_PARAMETER; + if (path.Data[path.Length - 1] == '/') + return NTLSTATUS_INVALID_PARAMETER; + + size_t componentStart = 1; + for (size_t i = 1; i <= path.Length; ++i) { + if (i < path.Length) { + char c = path.Data[i]; + if (c == '\\' || c == '\0') + return NTLSTATUS_INVALID_PARAMETER; + if (c != '/') + continue; + } + + size_t componentLength = i - componentStart; + if (componentLength == 0 || + (componentLength == 1 && path.Data[componentStart] == '.') || + (componentLength == 2 && path.Data[componentStart] == '.' && path.Data[componentStart + 1] == '.')) + return NTLSTATUS_INVALID_PARAMETER; + componentStart = i + 1; + } + return NTLSTATUS_SUCCESS; +} + +// Builds one inline request header and path payload. +static KSTATUS NtlBuildResourceRequest(uint8_t* buffer, + size_t bufferSize, + uint32_t operation, + SYS_STRING_VIEW path, + SYS_RIGHTS rights, + SYS_RIGHTS exportRights, + uint32_t kind, + uint32_t* lengthOut) { + KSTATUS status = NtlValidateResourcePath(path); + if (!KSUCCESS(status) || !buffer || !lengthOut) + return KSUCCESS(status) ? NTLSTATUS_INVALID_PARAMETER : status; + size_t total = sizeof(NTL_RESOURCE_REQUEST) + path.Length; + if (total > bufferSize || total > SYS_PORT_MAX_INLINE_BYTES) + return NTLSTATUS_BUFFER_TOO_SMALL; + + NTL_RESOURCE_REQUEST header; + memset(&header, 0, sizeof(header)); + header.Size = sizeof(header); + header.Version = NTL_RESOURCE_PROTOCOL_VERSION; + header.Operation = operation; + header.Flags = NTL_RESOURCE_FLAG_NONE; + header.Rights = rights; + header.ExportRights = exportRights; + header.Kind = kind; + header.PathLength = (uint32_t)path.Length; + memcpy(buffer, &header, sizeof(header)); + memcpy(buffer + sizeof(header), path.Data, path.Length); + *lengthOut = (uint32_t)total; + return NTLSTATUS_SUCCESS; +} + +// Executes one resource request and validates its fixed protocol reply. +static KSTATUS NtlCallResource(SYS_HANDLE session, + const void* requestPayload, + uint32_t requestLength, + const SYS_PORT_HANDLE_TRANSFER* requestHandles, + uint32_t requestHandleCount, + NTL_RESOURCE_REPLY* replyOut, + SYS_HANDLE* replyHandleOut) { + if (session == SYS_HANDLE_INVALID || !requestPayload || !requestLength || !replyOut) + return NTLSTATUS_INVALID_PARAMETER; + + NTL_RESOURCE_REPLY reply; + memset(&reply, 0, sizeof(reply)); + SYS_PORT_MESSAGE_INFO info; + memset(&info, 0, sizeof(info)); + info.Size = sizeof(info); + SYS_HANDLE replyHandle = SYS_HANDLE_INVALID; + + SYS_CALL_PORT_REQUEST call; + memset(&call, 0, sizeof(call)); + call.Size = sizeof(call); + call.EndpointHandle = session; + call.Flags = SYS_PORT_FLAG_NONE; + call.RequestPayload = requestPayload; + call.RequestLength = requestLength; + call.RequestHandles = requestHandles; + call.RequestHandleCount = requestHandleCount; + call.ReplyPayload = &reply; + call.ReplyCapacity = sizeof(reply); + call.ReplyHandlesOut = replyHandleOut ? &replyHandle : (SYS_HANDLE*)0; + call.ReplyHandleCapacity = replyHandleOut ? 1u : 0u; + call.ReplyInfoOut = &info; + call.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE; + KSTATUS status = SysCallPort(&call); + if (!KSUCCESS(status)) + return status; + + if (info.Type != SYS_PORT_MESSAGE_REPLY || info.PayloadLength != sizeof(reply) || + reply.Size != sizeof(reply) || reply.Version != NTL_RESOURCE_PROTOCOL_VERSION) { + if (replyHandle != SYS_HANDLE_INVALID) (void)SysCloseHandle(replyHandle); + return NTLSTATUS_FAILURE; + } + if ((!replyHandleOut && info.HandleCount != 0) || (replyHandleOut && info.HandleCount > 1)) { + if (replyHandle != SYS_HANDLE_INVALID) (void)SysCloseHandle(replyHandle); + return NTLSTATUS_FAILURE; + } + + *replyOut = reply; + if (replyHandleOut) *replyHandleOut = replyHandle; + return NTLSTATUS_SUCCESS; +} + +// Connects to the registered resource listener. +KSTATUS NtlResourceConnect(SYS_HANDLE* sessionOut) { + if (!sessionOut) return NTLSTATUS_INVALID_PARAMETER; + *sessionOut = SYS_HANDLE_INVALID; + SYS_CONNECT_SYSTEM_SERVICE_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.ServiceId = SYS_SERVICE_RESOURCE; + request.EndpointHandleOut = sessionOut; + return SysConnectSystemService(&request); +} + +// Resolves the canonical /SystemRoot resource and closes the transient resource session. +KSTATUS NtlResourceOpenSystemRoot(SYS_RIGHTS desiredRights, SYS_HANDLE* handleOut) { + if (!desiredRights || !handleOut) return NTLSTATUS_INVALID_PARAMETER; + *handleOut = SYS_HANDLE_INVALID; + SYS_HANDLE session = SYS_HANDLE_INVALID; + KSTATUS status = NtlResourceConnect(&session); + if (!KSUCCESS(status)) return status; + status = NtlResourceLookup(session, SysSvFromCstr("/SystemRoot"), desiredRights, handleOut); + (void)NtlResourceClose(session); + return status; +} + +// Connects through one delegated resource-listener capability. +KSTATUS NtlResourceConnectListener(SYS_HANDLE listenerHandle, SYS_HANDLE* sessionOut) { + if (listenerHandle == SYS_HANDLE_INVALID || !sessionOut) + return NTLSTATUS_INVALID_PARAMETER; + *sessionOut = SYS_HANDLE_INVALID; + SYS_CONNECT_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.ListenerHandle = listenerHandle; + request.Flags = SYS_PORT_CONNECT_FLAG_NONE; + request.EndpointHandleOut = sessionOut; + return SysConnectPort(&request); +} + +// Looks up one resource and requests an attenuated alias of its bound capability. +KSTATUS NtlResourceLookup(SYS_HANDLE session, + SYS_STRING_VIEW path, + SYS_RIGHTS desiredRights, + SYS_HANDLE* handleOut) { + if (!desiredRights || !handleOut) return NTLSTATUS_INVALID_PARAMETER; + *handleOut = SYS_HANDLE_INVALID; + uint8_t payload[sizeof(NTL_RESOURCE_REQUEST) + NTL_RESOURCE_PATH_MAX]; + uint32_t payloadLength = 0; + KSTATUS status = NtlBuildResourceRequest(payload, sizeof(payload), NTL_RESOURCE_OP_LOOKUP, + path, desiredRights, 0, NTL_RESOURCE_KIND_INVALID, + &payloadLength); + if (!KSUCCESS(status)) return status; + + NTL_RESOURCE_REPLY reply; + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = NtlCallResource(session, payload, payloadLength, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, + &reply, &handle); + if (!KSUCCESS(status)) return status; + if (reply.Operation != NTL_RESOURCE_OP_LOOKUP) { + if (handle != SYS_HANDLE_INVALID) (void)SysCloseHandle(handle); + return NTLSTATUS_FAILURE; + } + if (!KSUCCESS(reply.Status)) { + if (handle != SYS_HANDLE_INVALID) (void)SysCloseHandle(handle); + return reply.Status; + } + if (handle == SYS_HANDLE_INVALID) return NTLSTATUS_FAILURE; + *handleOut = handle; + return NTLSTATUS_SUCCESS; +} + +// Queries one bound resource kind without transferring authority. +KSTATUS NtlResourceQuery(SYS_HANDLE session, SYS_STRING_VIEW path, uint32_t* kindOut) { + if (!kindOut) return NTLSTATUS_INVALID_PARAMETER; + *kindOut = NTL_RESOURCE_KIND_INVALID; + uint8_t payload[sizeof(NTL_RESOURCE_REQUEST) + NTL_RESOURCE_PATH_MAX]; + uint32_t payloadLength = 0; + KSTATUS status = NtlBuildResourceRequest(payload, sizeof(payload), NTL_RESOURCE_OP_QUERY, + path, 0, 0, NTL_RESOURCE_KIND_INVALID, &payloadLength); + if (!KSUCCESS(status)) return status; + + NTL_RESOURCE_REPLY reply; + status = NtlCallResource(session, payload, payloadLength, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, + &reply, (SYS_HANDLE*)0); + if (!KSUCCESS(status)) return status; + if (reply.Operation != NTL_RESOURCE_OP_QUERY) return NTLSTATUS_FAILURE; + if (!KSUCCESS(reply.Status)) return reply.Status; + *kindOut = reply.Kind; + return NTLSTATUS_SUCCESS; +} + +// Resolves the longest bound resource prefix into one traversal capability. +KSTATUS NtlResourceResolve(SYS_HANDLE session, + SYS_STRING_VIEW path, + uint32_t flags, + SYS_HANDLE* rootHandleOut, + uint32_t* kindOut, + uint32_t* resolvedLengthOut) { + if (!rootHandleOut || !kindOut || !resolvedLengthOut || + (flags & ~NTL_RESOURCE_RESOLVE_FLAG_MODIFY) != 0) + return NTLSTATUS_INVALID_PARAMETER; + *rootHandleOut = SYS_HANDLE_INVALID; + *kindOut = NTL_RESOURCE_KIND_INVALID; + *resolvedLengthOut = 0; + + uint8_t payload[sizeof(NTL_RESOURCE_REQUEST) + NTL_RESOURCE_PATH_MAX]; + uint32_t payloadLength = 0; + KSTATUS status = NtlBuildResourceRequest(payload, sizeof(payload), NTL_RESOURCE_OP_RESOLVE, + path, 0, 0, NTL_RESOURCE_KIND_INVALID, &payloadLength); + if (!KSUCCESS(status)) return status; + ((NTL_RESOURCE_REQUEST*)payload)->Flags = flags; + + NTL_RESOURCE_REPLY reply; + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = NtlCallResource(session, payload, payloadLength, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, + &reply, &handle); + if (!KSUCCESS(status)) return status; + if (reply.Operation != NTL_RESOURCE_OP_RESOLVE || reply.Flags != flags) { + if (handle != SYS_HANDLE_INVALID) (void)SysCloseHandle(handle); + return NTLSTATUS_FAILURE; + } + if (!KSUCCESS(reply.Status)) { + if (handle != SYS_HANDLE_INVALID) (void)SysCloseHandle(handle); + return reply.Status; + } + if (handle == SYS_HANDLE_INVALID || reply.Kind == NTL_RESOURCE_KIND_INVALID || + reply.ResolvedLength < 2 || reply.ResolvedLength > path.Length || + (reply.ResolvedLength != path.Length && path.Data[reply.ResolvedLength] != '/')) { + if (handle != SYS_HANDLE_INVALID) (void)SysCloseHandle(handle); + return NTLSTATUS_FAILURE; + } + + *rootHandleOut = handle; + *kindOut = reply.Kind; + *resolvedLengthOut = reply.ResolvedLength; + return NTLSTATUS_SUCCESS; +} + +// Binds an explicitly attenuated capability into an administrative resource session. +KSTATUS NtlResourceBind(SYS_HANDLE session, + SYS_STRING_VIEW path, + uint32_t kind, + SYS_HANDLE resourceHandle, + SYS_RIGHTS namespaceRights, + SYS_RIGHTS exportRights) { + if (kind == NTL_RESOURCE_KIND_INVALID || resourceHandle == SYS_HANDLE_INVALID || + !(namespaceRights & SYS_RIGHT_TRANSFER) || !exportRights || + (exportRights & ~namespaceRights) != 0) + return NTLSTATUS_INVALID_PARAMETER; + uint8_t payload[sizeof(NTL_RESOURCE_REQUEST) + NTL_RESOURCE_PATH_MAX]; + uint32_t payloadLength = 0; + KSTATUS status = NtlBuildResourceRequest(payload, sizeof(payload), NTL_RESOURCE_OP_BIND, + path, namespaceRights, exportRights, kind, &payloadLength); + if (!KSUCCESS(status)) return status; + + SYS_PORT_HANDLE_TRANSFER transfer; + transfer.Handle = resourceHandle; + transfer.DesiredRights = namespaceRights; + transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; + NTL_RESOURCE_REPLY reply; + status = NtlCallResource(session, payload, payloadLength, &transfer, 1, &reply, (SYS_HANDLE*)0); + if (!KSUCCESS(status)) return status; + if (reply.Operation != NTL_RESOURCE_OP_BIND) return NTLSTATUS_FAILURE; + return reply.Status; +} + +// Removes one resource name through an administrative session. +KSTATUS NtlResourceUnbind(SYS_HANDLE session, SYS_STRING_VIEW path) { + uint8_t payload[sizeof(NTL_RESOURCE_REQUEST) + NTL_RESOURCE_PATH_MAX]; + uint32_t payloadLength = 0; + KSTATUS status = NtlBuildResourceRequest(payload, sizeof(payload), NTL_RESOURCE_OP_UNBIND, + path, 0, 0, NTL_RESOURCE_KIND_INVALID, &payloadLength); + if (!KSUCCESS(status)) return status; + + NTL_RESOURCE_REPLY reply; + status = NtlCallResource(session, payload, payloadLength, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, + &reply, (SYS_HANDLE*)0); + if (!KSUCCESS(status)) return status; + if (reply.Operation != NTL_RESOURCE_OP_UNBIND) return NTLSTATUS_FAILURE; + return reply.Status; +} + +// Disconnects and closes one resource session. +KSTATUS NtlResourceClose(SYS_HANDLE session) { + if (session == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_HANDLE; + KSTATUS disconnect = SysDisconnectPort(session); + KSTATUS close = SysCloseHandle(session); + if (!KSUCCESS(disconnect) && disconnect != NTLSTATUS_PORT_DISCONNECTED) + return disconnect; + return close; +} diff --git a/code/user/syscall/syscall_nr.inc b/code/user/syscall/syscall_nr.inc index 4d7599a..54dc21d 100644 --- a/code/user/syscall/syscall_nr.inc +++ b/code/user/syscall/syscall_nr.inc @@ -1,163 +1,181 @@ ; Syscall numbers shared by MASM user-mode stubs. -; Process, threads, modules, waits, APC, and exceptions. +; Process, threads, waits, APC, and exceptions. 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_RESUME_THREAD EQU 9 -SYS_GET_THREAD_LIST EQU 10 -SYS_LOAD_MODULE EQU 11 -SYS_UNLOAD_MODULE EQU 12 -SYS_GET_MODULE_INFO EQU 13 -SYS_GET_MODULE_EXPORT EQU 14 -SYS_GET_MODULE_LIST EQU 15 -SYS_GET_CURRENT_TID EQU 16 -SYS_GET_CURRENT_PID EQU 17 -SYS_SET_THREAD_PRIORITY EQU 18 -SYS_YIELD EQU 19 -SYS_SLEEP EQU 20 -SYS_WAIT_SINGLE EQU 21 -SYS_WAIT_MANY EQU 22 -SYS_SET_USER_APC_DISPATCHER EQU 23 -SYS_APC_RETURN EQU 24 -SYS_SET_EXCEPTION_DISPATCHER EQU 25 -SYS_GET_EXCEPTION_DISPATCHER EQU 26 -SYS_CONTINUE_EXCEPTION EQU 27 -SYS_RAISE_EXCEPTION EQU 28 +SYS_CREATE_PROCESS_OBJECT EQU 2 +SYS_ALLOC_PROCESS_VM EQU 3 +SYS_PROTECT_PROCESS_VM EQU 4 +SYS_START_PROCESS EQU 5 +SYS_OPEN_PROCESS EQU 6 +SYS_GET_PROCESS_EXIT_CODE EQU 7 +SYS_KILL_PROCESS EQU 8 +SYS_GET_PROCESS_LIST EQU 9 +SYS_GET_PROCESS_PID EQU 10 +SYS_CREATE_THREAD EQU 11 +SYS_RESUME_THREAD EQU 12 +SYS_GET_THREAD_LIST EQU 13 +SYS_GET_CURRENT_TID EQU 14 +SYS_GET_CURRENT_PID EQU 15 +SYS_SET_THREAD_PRIORITY EQU 16 +SYS_YIELD EQU 17 +SYS_SLEEP EQU 18 +SYS_WAIT_SINGLE EQU 19 +SYS_WAIT_MANY EQU 20 +SYS_SET_USER_APC_DISPATCHER EQU 21 +SYS_APC_RETURN EQU 22 +SYS_SET_EXCEPTION_DISPATCHER EQU 23 +SYS_GET_EXCEPTION_DISPATCHER EQU 24 +SYS_CONTINUE_EXCEPTION EQU 25 +SYS_RAISE_EXCEPTION EQU 26 ; Virtual memory, mappings, and handles. -SYS_ALLOC_VM EQU 29 -SYS_FREE_VM EQU 30 -SYS_PROTECT_VM EQU 31 -SYS_QUERY_VM EQU 32 -SYS_CLOSE_HANDLE EQU 33 -SYS_DUPLICATE_HANDLE EQU 34 -SYS_CREATE_FILE_MAPPING EQU 35 -SYS_MAP_FILE EQU 36 +SYS_ALLOC_VM EQU 27 +SYS_FREE_VM EQU 28 +SYS_PROTECT_VM EQU 29 +SYS_QUERY_VM EQU 30 +SYS_CLOSE_HANDLE EQU 31 +SYS_DUPLICATE_HANDLE EQU 32 +SYS_DERIVE_HANDLE EQU 33 +SYS_REVOKE_HANDLE EQU 34 +SYS_DERIVE_SECURITY_CONTEXT EQU 35 +SYS_QUERY_SECURITY_CONTEXT EQU 36 +SYS_CREATE_FILE_MAPPING EQU 37 +SYS_MAP_FILE EQU 38 ; Synchronization objects. -SYS_CREATE_MUTEX EQU 37 -SYS_TRY_ACQUIRE_MUTEX EQU 38 -SYS_RELEASE_MUTEX EQU 39 -SYS_CREATE_SEM EQU 40 -SYS_TRY_WAIT_SEM EQU 41 -SYS_SIGNAL_SEM EQU 42 -SYS_CREATE_EVENT EQU 43 -SYS_SIGNAL_EVENT EQU 44 -SYS_CLEAR_EVENT EQU 45 +SYS_CREATE_MUTEX EQU 39 +SYS_TRY_ACQUIRE_MUTEX EQU 40 +SYS_RELEASE_MUTEX EQU 41 +SYS_CREATE_SEM EQU 42 +SYS_TRY_WAIT_SEM EQU 43 +SYS_SIGNAL_SEM EQU 44 +SYS_CREATE_EVENT EQU 45 +SYS_SIGNAL_EVENT EQU 46 +SYS_CLEAR_EVENT EQU 47 ; Queues and named queues. -SYS_CREATE_QUEUE EQU 46 -SYS_SEND_QUEUE EQU 47 -SYS_RECV_QUEUE EQU 48 -SYS_PUBLISH_QUEUE EQU 49 -SYS_OPEN_NAMED_QUEUE EQU 50 -SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 51 +SYS_CREATE_QUEUE EQU 48 +SYS_SEND_QUEUE EQU 49 +SYS_RECV_QUEUE EQU 50 +SYS_PUBLISH_QUEUE EQU 51 +SYS_OPEN_NAMED_QUEUE EQU 52 +SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 53 +SYS_UNPUBLISH_QUEUE EQU 54 -; Files, directories, and current directory. -SYS_OPEN_FILE EQU 52 -SYS_READ_FILE EQU 53 -SYS_WRITE_FILE EQU 54 -SYS_READ_FILE_ASYNC EQU 55 -SYS_WRITE_FILE_ASYNC EQU 56 -SYS_CANCEL_IO EQU 57 -SYS_SEEK_FILE EQU 58 -SYS_GET_FILE_POS EQU 59 -SYS_GET_FILE_SIZE EQU 60 -SYS_GET_FILE_STAT EQU 61 -SYS_STAT_FILE EQU 62 -SYS_TRUNCATE_FILE EQU 63 -SYS_FLUSH_FILE EQU 64 -SYS_DELETE_FILE EQU 65 -SYS_RENAME_FILE EQU 66 -SYS_LOCK_FILE EQU 67 -SYS_UNLOCK_FILE EQU 68 -SYS_OPEN_DIR EQU 69 -SYS_READ_DIR EQU 70 -SYS_CREATE_DIR EQU 71 -SYS_REMOVE_DIR EQU 72 -SYS_SET_CWD EQU 73 -SYS_REAL_PATH EQU 74 +; LPC-like Ports and well-known system services. +SYS_CREATE_PORT EQU 55 +SYS_CONNECT_PORT EQU 56 +SYS_ACCEPT_PORT EQU 57 +SYS_SEND_PORT EQU 58 +SYS_RECEIVE_PORT EQU 59 +SYS_CALL_PORT EQU 60 +SYS_REPLY_PORT EQU 61 +SYS_DISCONNECT_PORT EQU 62 +SYS_REGISTER_SYSTEM_SERVICE EQU 63 +SYS_UNREGISTER_SYSTEM_SERVICE EQU 64 +SYS_CONNECT_SYSTEM_SERVICE EQU 65 + +; Mounted-volume manager control plane. +SYS_QUERY_VOLUMES EQU 66 +SYS_OPEN_VOLUME EQU 67 +SYS_WAIT_VOLUME_CHANGE EQU 68 + +; Files and directories. +SYS_OPEN_FILE_AT EQU 69 +SYS_OPEN_DIR_AT EQU 70 +SYS_STAT_AT EQU 71 +SYS_DELETE_AT EQU 72 +SYS_RENAME_AT EQU 73 +SYS_CREATE_DIR_AT EQU 74 +SYS_REMOVE_DIR_AT EQU 75 +SYS_READ_FILE EQU 76 +SYS_WRITE_FILE EQU 77 +SYS_READ_FILE_ASYNC EQU 78 +SYS_WRITE_FILE_ASYNC EQU 79 +SYS_CANCEL_IO EQU 80 +SYS_SEEK_FILE EQU 81 +SYS_GET_FILE_POS EQU 82 +SYS_GET_FILE_SIZE EQU 83 +SYS_GET_FILE_STAT EQU 84 +SYS_TRUNCATE_FILE EQU 85 +SYS_FLUSH_FILE EQU 86 +SYS_LOCK_FILE EQU 87 +SYS_UNLOCK_FILE EQU 88 +SYS_READ_DIR EQU 89 ; Console, display, and surfaces. -SYS_WRITE_CONSOLE EQU 75 -SYS_GET_CONSOLE_INFO EQU 76 -SYS_SET_CONSOLE_CURSOR EQU 77 -SYS_FILL_CONSOLE_CHARACTER EQU 78 -SYS_FILL_CONSOLE_ATTRIBUTE EQU 79 -SYS_PUT_SCREEN_CHAR EQU 80 -SYS_CLEAR_SCREEN EQU 81 -SYS_GET_SCREEN_SIZE EQU 82 -SYS_QUERY_DISPLAY_INFO EQU 83 -SYS_OPEN_DISPLAY_CLOCK EQU 84 -SYS_CREATE_SURFACE EQU 85 -SYS_MAP_SURFACE EQU 86 -SYS_QUERY_SURFACE EQU 87 -SYS_PRESENT_SURFACE EQU 88 +SYS_WRITE_CONSOLE EQU 90 +SYS_GET_CONSOLE_INFO EQU 91 +SYS_SET_CONSOLE_CURSOR EQU 92 +SYS_FILL_CONSOLE_CHARACTER EQU 93 +SYS_FILL_CONSOLE_ATTRIBUTE EQU 94 +SYS_PUT_SCREEN_CHAR EQU 95 +SYS_CLEAR_SCREEN EQU 96 +SYS_GET_SCREEN_SIZE EQU 97 +SYS_QUERY_DISPLAY_INFO EQU 98 +SYS_OPEN_DISPLAY_CLOCK EQU 99 +SYS_CREATE_SURFACE EQU 100 +SYS_MAP_SURFACE EQU 101 +SYS_QUERY_SURFACE EQU 102 +SYS_PRESENT_SURFACE EQU 103 ; Native render audio. -SYS_AUDIO_ENUMERATE_ENDPOINTS EQU 89 -SYS_AUDIO_OPEN_STREAM EQU 90 -SYS_AUDIO_WRITE EQU 91 -SYS_AUDIO_ARM_WAIT EQU 92 -SYS_AUDIO_QUERY_STREAM EQU 93 -SYS_AUDIO_START EQU 94 -SYS_AUDIO_PAUSE EQU 95 -SYS_AUDIO_RESUME EQU 96 -SYS_AUDIO_DRAIN EQU 97 -SYS_AUDIO_STOP EQU 98 -SYS_AUDIO_RESET EQU 99 -SYS_AUDIO_SET_VOLUME EQU 100 -SYS_AUDIO_SET_MASTER_VOLUME EQU 101 +SYS_AUDIO_ENUMERATE_ENDPOINTS EQU 104 +SYS_AUDIO_OPEN_STREAM EQU 105 +SYS_AUDIO_WRITE EQU 106 +SYS_AUDIO_ARM_WAIT EQU 107 +SYS_AUDIO_QUERY_STREAM EQU 108 +SYS_AUDIO_START EQU 109 +SYS_AUDIO_PAUSE EQU 110 +SYS_AUDIO_RESUME EQU 111 +SYS_AUDIO_DRAIN EQU 112 +SYS_AUDIO_STOP EQU 113 +SYS_AUDIO_RESET EQU 114 +SYS_AUDIO_SET_VOLUME EQU 115 +SYS_AUDIO_SET_MASTER_VOLUME EQU 116 ; Input. -SYS_POLL_KEYBOARD EQU 102 -SYS_IS_KEY_DOWN EQU 103 -SYS_READ_KEYBOARD_CHAR EQU 104 -SYS_READ_KEY EQU 105 -SYS_GET_KEY_MODIFIERS EQU 106 -SYS_QUERY_GLOBAL_INPUT_STATE EQU 107 -SYS_OPEN_INPUT_QUEUE EQU 108 -SYS_READ_INPUT_EVENT EQU 109 -SYS_PEEK_INPUT_EVENT EQU 110 -SYS_PEEK_MOUSE EQU 111 -SYS_READ_MOUSE EQU 112 -SYS_GET_MOUSE_POS EQU 113 +SYS_POLL_KEYBOARD EQU 117 +SYS_IS_KEY_DOWN EQU 118 +SYS_READ_KEYBOARD_CHAR EQU 119 +SYS_READ_KEY EQU 120 +SYS_GET_KEY_MODIFIERS EQU 121 +SYS_QUERY_GLOBAL_INPUT_STATE EQU 122 +SYS_OPEN_INPUT_QUEUE EQU 123 +SYS_READ_INPUT_EVENT EQU 124 +SYS_PEEK_INPUT_EVENT EQU 125 +SYS_PEEK_MOUSE EQU 126 +SYS_READ_MOUSE EQU 127 +SYS_GET_MOUSE_POS EQU 128 ; Time, debug, and system information. -SYS_CREATE_TIMER EQU 114 -SYS_SET_TIMER EQU 115 -SYS_CANCEL_TIMER EQU 116 -SYS_GET_TICK EQU 117 -SYS_GET_TICK_FREQUENCY EQU 118 -SYS_GET_SYSTEM_TIME EQU 119 -SYS_PRINT_DEBUG EQU 120 -SYS_QUERY_TRAP_FRAME EQU 121 -SYS_GET_MEMORY_STATUS EQU 122 -SYS_QUERY_KERNEL_STATS EQU 123 +SYS_CREATE_TIMER EQU 129 +SYS_SET_TIMER EQU 130 +SYS_CANCEL_TIMER EQU 131 +SYS_GET_TICK EQU 132 +SYS_GET_TICK_FREQUENCY EQU 133 +SYS_GET_SYSTEM_TIME EQU 134 +SYS_PRINT_DEBUG EQU 135 +SYS_QUERY_TRAP_FRAME EQU 136 +SYS_GET_MEMORY_STATUS EQU 137 +SYS_QUERY_KERNEL_STATS EQU 138 ; Native sockets and polling. -SYS_CREATE_SOCKET EQU 124 -SYS_SOCKET_IO_ASYNC EQU 125 -SYS_BIND_SOCKET EQU 126 -SYS_LISTEN_SOCKET EQU 127 -SYS_ACCEPT_SOCKET EQU 128 -SYS_CONNECT_SOCKET EQU 129 -SYS_SEND_SOCKET EQU 130 -SYS_RECV_SOCKET EQU 131 -SYS_SHUTDOWN_SOCKET EQU 132 -SYS_SEND_TO_SOCKET EQU 133 -SYS_RECV_FROM_SOCKET EQU 134 -SYS_RESOLVE_HOST EQU 135 -SYS_SET_SOCKET_OPTION EQU 136 -SYS_GET_SOCKET_OPTION EQU 137 -SYS_POLL EQU 138 -SYS_UNPUBLISH_QUEUE EQU 139 +SYS_CREATE_SOCKET EQU 139 +SYS_SOCKET_IO_ASYNC EQU 140 +SYS_BIND_SOCKET EQU 141 +SYS_LISTEN_SOCKET EQU 142 +SYS_ACCEPT_SOCKET EQU 143 +SYS_CONNECT_SOCKET EQU 144 +SYS_SEND_SOCKET EQU 145 +SYS_RECV_SOCKET EQU 146 +SYS_SHUTDOWN_SOCKET EQU 147 +SYS_SEND_TO_SOCKET EQU 148 +SYS_RECV_FROM_SOCKET EQU 149 +SYS_RESOLVE_HOST EQU 150 +SYS_SET_SOCKET_OPTION EQU 151 +SYS_GET_SOCKET_OPTION EQU 152 +SYS_POLL EQU 153 -SYS_MAX EQU 140 +SYS_MAX EQU 154 diff --git a/code/user/syscall/syscall_stubs.asm b/code/user/syscall/syscall_stubs.asm index b0983ca..6763cd9 100644 --- a/code/user/syscall/syscall_stubs.asm +++ b/code/user/syscall/syscall_stubs.asm @@ -1,20 +1,14 @@ -; syscall_stubs.asm - user-mode INT 0x40 stubs exported by syscall.dll (MASM64) +; syscall_stubs.asm - user-mode INT 0x40 stubs for syscall.dll and syscall_s.lib (MASM64) ; -; Calling convention (kernel side): -; RAX = syscall number -; RCX = arg 0 -; RDX = arg 1 -; R8 = arg 2 -; R9 = arg 3 -; R10 = arg 4 (our extension - loaded from the stack by SYSCALL_STUB6) -; R11 = arg 5 (our extension - loaded from the stack by SYSCALL_STUB6) +; Calling convention: +; RAX = syscall number. +; RCX = arg 0. +; RDX = arg 1. +; R8 = arg 2. +; R9 = arg 3. ; -; User-mode callers follow standard MS x64: args 1-4 in RCX/RDX/R8/R9, -; args 5+ on the stack above the home area ([rsp+28h], [rsp+30h], ...). -; -; SYSCALL_STUB2/3 - 4 or fewer args: pure register passing, no stack read. -; SYSCALL_STUB5 - exactly 5 args: loads [rsp+28h] -> R10 only. -; SYSCALL_STUB6 - 5 or 6 args: loads [rsp+28h] -> R10 and [rsp+30h] -> R11. +; Syscalls never consume stack arguments. Operations that need more than four +; logical arguments use a request structure. INCLUDE syscall_nr.inc @@ -28,42 +22,13 @@ SYS_USER_APC_FRAME_SystemArgument2 EQU 40 .CODE ; --------------------------------------------------------------------------- -SYSCALL_STUB2 MACRO name, nr +SYSCALL_STUB MACRO name, nr PUBLIC Sys&name +IFDEF NTL_SYSCALL_STATIC +Sys&name PROC +ELSE Sys&name PROC EXPORT - mov rax, nr - int 40h - ret -Sys&name ENDP -ENDM - -; --------------------------------------------------------------------------- -SYSCALL_STUB3 MACRO name, nr -PUBLIC Sys&name -Sys&name PROC EXPORT - mov rax, nr - int 40h - ret -Sys&name ENDP -ENDM - -; --------------------------------------------------------------------------- -SYSCALL_STUB5 MACRO name, nr -PUBLIC Sys&name -Sys&name PROC EXPORT - mov r10, [rsp+28h] ; 5th argument - mov rax, nr - int 40h - ret -Sys&name ENDP -ENDM - -; --------------------------------------------------------------------------- -SYSCALL_STUB6 MACRO name, nr -PUBLIC Sys&name -Sys&name PROC EXPORT - mov r10, [rsp+28h] ; 5th argument - mov r11, [rsp+30h] ; 6th argument +ENDIF mov rax, nr int 40h ret @@ -73,43 +38,45 @@ ENDM ; --------------------------------------------------------------------------- ; Process & thread lifecycle ; --------------------------------------------------------------------------- -SYSCALL_STUB2 ExitProcess, SYS_EXIT_PROCESS -SYSCALL_STUB2 ExitThread, SYS_EXIT_THREAD -SYSCALL_STUB2 CreateProcess, SYS_CREATE_PROCESS -SYSCALL_STUB3 WaitSingle, SYS_WAIT_SINGLE -SYSCALL_STUB2 WaitMany, SYS_WAIT_MANY -SYSCALL_STUB2 OpenInputQueue, SYS_OPEN_INPUT_QUEUE -SYSCALL_STUB2 GetProcessExitCode, SYS_GET_PROCESS_EXIT_CODE -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 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_STUB ExitProcess, SYS_EXIT_PROCESS +SYSCALL_STUB ExitThread, SYS_EXIT_THREAD +SYSCALL_STUB CreateProcessObject, SYS_CREATE_PROCESS_OBJECT +SYSCALL_STUB AllocProcessVm, SYS_ALLOC_PROCESS_VM +SYSCALL_STUB ProtectProcessVm, SYS_PROTECT_PROCESS_VM +SYSCALL_STUB StartProcess, SYS_START_PROCESS +SYSCALL_STUB WaitSingle, SYS_WAIT_SINGLE +SYSCALL_STUB WaitMany, SYS_WAIT_MANY +SYSCALL_STUB OpenInputQueue, SYS_OPEN_INPUT_QUEUE +SYSCALL_STUB GetProcessExitCode, SYS_GET_PROCESS_EXIT_CODE +SYSCALL_STUB OpenProcess, SYS_OPEN_PROCESS +SYSCALL_STUB KillProcess, SYS_KILL_PROCESS +SYSCALL_STUB GetProcessList, SYS_GET_PROCESS_LIST +SYSCALL_STUB GetProcessPid, SYS_GET_PROCESS_PID +SYSCALL_STUB GetThreadList, SYS_GET_THREAD_LIST -SYSCALL_STUB2 CreateThread, SYS_CREATE_THREAD -SYSCALL_STUB2 ResumeThread, SYS_RESUME_THREAD -SYSCALL_STUB2 GetCurrentTid, SYS_GET_CURRENT_TID -SYSCALL_STUB2 GetCurrentPid, SYS_GET_CURRENT_PID -SYSCALL_STUB2 SetThreadPriority, SYS_SET_THREAD_PRIORITY -SYSCALL_STUB2 Yield, SYS_YIELD -SYSCALL_STUB2 Sleep, SYS_SLEEP -SYSCALL_STUB2 SetUserApcDispatcher, SYS_SET_USER_APC_DISPATCHER -SYSCALL_STUB2 ApcReturn, SYS_APC_RETURN -SYSCALL_STUB2 SetExceptionDispatcher, SYS_SET_EXCEPTION_DISPATCHER -SYSCALL_STUB2 GetExceptionDispatcher, SYS_GET_EXCEPTION_DISPATCHER -SYSCALL_STUB2 ContinueException, SYS_CONTINUE_EXCEPTION -SYSCALL_STUB2 RaiseException, SYS_RAISE_EXCEPTION +SYSCALL_STUB CreateThread, SYS_CREATE_THREAD +SYSCALL_STUB ResumeThread, SYS_RESUME_THREAD +SYSCALL_STUB GetCurrentTid, SYS_GET_CURRENT_TID +SYSCALL_STUB GetCurrentPid, SYS_GET_CURRENT_PID +SYSCALL_STUB SetThreadPriority, SYS_SET_THREAD_PRIORITY +SYSCALL_STUB Yield, SYS_YIELD +SYSCALL_STUB Sleep, SYS_SLEEP +SYSCALL_STUB SetUserApcDispatcher, SYS_SET_USER_APC_DISPATCHER +SYSCALL_STUB ApcReturn, SYS_APC_RETURN +SYSCALL_STUB SetExceptionDispatcher, SYS_SET_EXCEPTION_DISPATCHER +SYSCALL_STUB GetExceptionDispatcher, SYS_GET_EXCEPTION_DISPATCHER +SYSCALL_STUB ContinueException, SYS_CONTINUE_EXCEPTION +SYSCALL_STUB RaiseException, SYS_RAISE_EXCEPTION ; --------------------------------------------------------------------------- ; User APC dispatcher ; --------------------------------------------------------------------------- PUBLIC SysUserApcDispatcher +IFDEF NTL_SYSCALL_STATIC +SysUserApcDispatcher PROC +ELSE SysUserApcDispatcher PROC EXPORT +ENDIF ; Kernel enters here with RSP pointing at SYS_USER_APC_FRAME. There is no ; return address because this is an IRET redirection. Preserve RBX because ; the original user context resumes after SysApcReturn. @@ -137,152 +104,176 @@ SysUserApcDispatcher ENDP ; --------------------------------------------------------------------------- ; Virtual memory and handles ; --------------------------------------------------------------------------- -SYSCALL_STUB2 AllocVm, SYS_ALLOC_VM -SYSCALL_STUB2 FreeVm, SYS_FREE_VM -SYSCALL_STUB2 ProtectVm, SYS_PROTECT_VM -SYSCALL_STUB2 QueryVm, SYS_QUERY_VM -SYSCALL_STUB2 CloseHandle, SYS_CLOSE_HANDLE -SYSCALL_STUB2 DuplicateHandle, SYS_DUPLICATE_HANDLE +SYSCALL_STUB AllocVm, SYS_ALLOC_VM +SYSCALL_STUB FreeVm, SYS_FREE_VM +SYSCALL_STUB ProtectVm, SYS_PROTECT_VM +SYSCALL_STUB QueryVm, SYS_QUERY_VM +SYSCALL_STUB CloseHandle, SYS_CLOSE_HANDLE +SYSCALL_STUB DuplicateHandle, SYS_DUPLICATE_HANDLE +SYSCALL_STUB DeriveHandle, SYS_DERIVE_HANDLE +SYSCALL_STUB RevokeHandle, SYS_REVOKE_HANDLE +SYSCALL_STUB DeriveSecurityContext, SYS_DERIVE_SECURITY_CONTEXT +SYSCALL_STUB QuerySecurityContext, SYS_QUERY_SECURITY_CONTEXT ; --------------------------------------------------------------------------- ; Synchronization objects ; --------------------------------------------------------------------------- -SYSCALL_STUB2 CreateMutex, SYS_CREATE_MUTEX -SYSCALL_STUB2 TryAcquireMutex, SYS_TRY_ACQUIRE_MUTEX -SYSCALL_STUB2 ReleaseMutex, SYS_RELEASE_MUTEX +SYSCALL_STUB CreateMutex, SYS_CREATE_MUTEX +SYSCALL_STUB TryAcquireMutex, SYS_TRY_ACQUIRE_MUTEX +SYSCALL_STUB ReleaseMutex, SYS_RELEASE_MUTEX -SYSCALL_STUB2 CreateSem, SYS_CREATE_SEM -SYSCALL_STUB2 TryWaitSem, SYS_TRY_WAIT_SEM -SYSCALL_STUB2 SignalSem, SYS_SIGNAL_SEM +SYSCALL_STUB CreateSem, SYS_CREATE_SEM +SYSCALL_STUB TryWaitSem, SYS_TRY_WAIT_SEM +SYSCALL_STUB SignalSem, SYS_SIGNAL_SEM -SYSCALL_STUB2 CreateEvent, SYS_CREATE_EVENT -SYSCALL_STUB2 SignalEvent, SYS_SIGNAL_EVENT -SYSCALL_STUB2 ClearEvent, SYS_CLEAR_EVENT +SYSCALL_STUB CreateEvent, SYS_CREATE_EVENT +SYSCALL_STUB SignalEvent, SYS_SIGNAL_EVENT +SYSCALL_STUB ClearEvent, SYS_CLEAR_EVENT ; --------------------------------------------------------------------------- ; Queues and named queues ; --------------------------------------------------------------------------- -SYSCALL_STUB2 CreateQueue, SYS_CREATE_QUEUE -SYSCALL_STUB2 SendQueue, SYS_SEND_QUEUE -SYSCALL_STUB2 RecvQueue, SYS_RECV_QUEUE -SYSCALL_STUB2 PublishQueue, SYS_PUBLISH_QUEUE -SYSCALL_STUB2 UnpublishQueue, SYS_UNPUBLISH_QUEUE -SYSCALL_STUB2 OpenNamedQueue, SYS_OPEN_NAMED_QUEUE -SYSCALL_STUB2 OpenNamedQueueOwnerProcess, SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS +SYSCALL_STUB CreateQueue, SYS_CREATE_QUEUE +SYSCALL_STUB SendQueue, SYS_SEND_QUEUE +SYSCALL_STUB RecvQueue, SYS_RECV_QUEUE +SYSCALL_STUB PublishQueue, SYS_PUBLISH_QUEUE +SYSCALL_STUB UnpublishQueue, SYS_UNPUBLISH_QUEUE +SYSCALL_STUB OpenNamedQueue, SYS_OPEN_NAMED_QUEUE +SYSCALL_STUB OpenNamedQueueOwnerProcess, SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS + ; --------------------------------------------------------------------------- -; Files, directories, and current directory +; LPC-like Ports and well-known system services ; --------------------------------------------------------------------------- -SYSCALL_STUB2 OpenFile, SYS_OPEN_FILE -SYSCALL_STUB2 ReadFile, SYS_READ_FILE -SYSCALL_STUB2 WriteFile, SYS_WRITE_FILE -SYSCALL_STUB2 ReadFileAsync, SYS_READ_FILE_ASYNC -SYSCALL_STUB2 WriteFileAsync, SYS_WRITE_FILE_ASYNC -SYSCALL_STUB2 CancelIo, SYS_CANCEL_IO -SYSCALL_STUB2 SeekFile, SYS_SEEK_FILE -SYSCALL_STUB2 GetFilePos, SYS_GET_FILE_POS -SYSCALL_STUB2 GetFileSize, SYS_GET_FILE_SIZE -SYSCALL_STUB2 GetFileStat, SYS_GET_FILE_STAT -SYSCALL_STUB2 StatFile, SYS_STAT_FILE -SYSCALL_STUB2 TruncateFile, SYS_TRUNCATE_FILE -SYSCALL_STUB2 FlushFile, SYS_FLUSH_FILE -SYSCALL_STUB2 DeleteFile, SYS_DELETE_FILE -SYSCALL_STUB2 RenameFile, SYS_RENAME_FILE -SYSCALL_STUB2 LockFile, SYS_LOCK_FILE -SYSCALL_STUB2 UnlockFile, SYS_UNLOCK_FILE +SYSCALL_STUB CreatePort, SYS_CREATE_PORT +SYSCALL_STUB ConnectPort, SYS_CONNECT_PORT +SYSCALL_STUB AcceptPort, SYS_ACCEPT_PORT +SYSCALL_STUB SendPort, SYS_SEND_PORT +SYSCALL_STUB ReceivePort, SYS_RECEIVE_PORT +SYSCALL_STUB CallPort, SYS_CALL_PORT +SYSCALL_STUB ReplyPort, SYS_REPLY_PORT +SYSCALL_STUB DisconnectPort, SYS_DISCONNECT_PORT +SYSCALL_STUB RegisterSystemService, SYS_REGISTER_SYSTEM_SERVICE +SYSCALL_STUB UnregisterSystemService, SYS_UNREGISTER_SYSTEM_SERVICE +SYSCALL_STUB ConnectSystemService, SYS_CONNECT_SYSTEM_SERVICE -SYSCALL_STUB2 OpenDir, SYS_OPEN_DIR -SYSCALL_STUB2 ReadDir, SYS_READ_DIR -SYSCALL_STUB2 CreateDir, SYS_CREATE_DIR -SYSCALL_STUB2 RemoveDir, SYS_REMOVE_DIR -SYSCALL_STUB2 SetCwd, SYS_SET_CWD -SYSCALL_STUB2 RealPath, SYS_REAL_PATH +; --------------------------------------------------------------------------- +; Mounted-volume manager control plane +; --------------------------------------------------------------------------- +SYSCALL_STUB QueryVolumes, SYS_QUERY_VOLUMES +SYSCALL_STUB OpenVolume, SYS_OPEN_VOLUME +SYSCALL_STUB WaitVolumeChange, SYS_WAIT_VOLUME_CHANGE +; --------------------------------------------------------------------------- +; Files and directories +; --------------------------------------------------------------------------- +SYSCALL_STUB OpenFileAt, SYS_OPEN_FILE_AT +SYSCALL_STUB OpenDirAt, SYS_OPEN_DIR_AT +SYSCALL_STUB StatAt, SYS_STAT_AT +SYSCALL_STUB DeleteAt, SYS_DELETE_AT +SYSCALL_STUB RenameAt, SYS_RENAME_AT +SYSCALL_STUB CreateDirAt, SYS_CREATE_DIR_AT +SYSCALL_STUB RemoveDirAt, SYS_REMOVE_DIR_AT +SYSCALL_STUB ReadFile, SYS_READ_FILE +SYSCALL_STUB WriteFile, SYS_WRITE_FILE +SYSCALL_STUB ReadFileAsync, SYS_READ_FILE_ASYNC +SYSCALL_STUB WriteFileAsync, SYS_WRITE_FILE_ASYNC +SYSCALL_STUB CancelIo, SYS_CANCEL_IO +SYSCALL_STUB SeekFile, SYS_SEEK_FILE +SYSCALL_STUB GetFilePos, SYS_GET_FILE_POS +SYSCALL_STUB GetFileSize, SYS_GET_FILE_SIZE +SYSCALL_STUB GetFileStat, SYS_GET_FILE_STAT +SYSCALL_STUB TruncateFile, SYS_TRUNCATE_FILE +SYSCALL_STUB FlushFile, SYS_FLUSH_FILE +SYSCALL_STUB LockFile, SYS_LOCK_FILE +SYSCALL_STUB UnlockFile, SYS_UNLOCK_FILE + +SYSCALL_STUB ReadDir, SYS_READ_DIR ; --------------------------------------------------------------------------- ; Console and graphics ; --------------------------------------------------------------------------- -SYSCALL_STUB2 WriteConsole, SYS_WRITE_CONSOLE -SYSCALL_STUB2 GetConsoleInfo, SYS_GET_CONSOLE_INFO -SYSCALL_STUB2 SetConsoleCursor, SYS_SET_CONSOLE_CURSOR -SYSCALL_STUB6 FillConsoleCharacter, SYS_FILL_CONSOLE_CHARACTER -SYSCALL_STUB6 FillConsoleAttribute, SYS_FILL_CONSOLE_ATTRIBUTE -SYSCALL_STUB6 PutScreenChar, SYS_PUT_SCREEN_CHAR -SYSCALL_STUB2 CreateSurface, SYS_CREATE_SURFACE -SYSCALL_STUB2 MapSurface, SYS_MAP_SURFACE -SYSCALL_STUB2 QuerySurface, SYS_QUERY_SURFACE -SYSCALL_STUB2 PresentSurface, SYS_PRESENT_SURFACE -SYSCALL_STUB2 ClearScreen, SYS_CLEAR_SCREEN -SYSCALL_STUB2 GetScreenSize, SYS_GET_SCREEN_SIZE -SYSCALL_STUB2 QueryDisplayInfo, SYS_QUERY_DISPLAY_INFO -SYSCALL_STUB2 OpenDisplayClock, SYS_OPEN_DISPLAY_CLOCK +SYSCALL_STUB WriteConsole, SYS_WRITE_CONSOLE +SYSCALL_STUB GetConsoleInfo, SYS_GET_CONSOLE_INFO +SYSCALL_STUB SetConsoleCursor, SYS_SET_CONSOLE_CURSOR +SYSCALL_STUB FillConsoleCharacter, SYS_FILL_CONSOLE_CHARACTER +SYSCALL_STUB FillConsoleAttribute, SYS_FILL_CONSOLE_ATTRIBUTE +SYSCALL_STUB PutScreenChar, SYS_PUT_SCREEN_CHAR +SYSCALL_STUB CreateSurface, SYS_CREATE_SURFACE +SYSCALL_STUB MapSurface, SYS_MAP_SURFACE +SYSCALL_STUB QuerySurface, SYS_QUERY_SURFACE +SYSCALL_STUB PresentSurface, SYS_PRESENT_SURFACE +SYSCALL_STUB ClearScreen, SYS_CLEAR_SCREEN +SYSCALL_STUB GetScreenSize, SYS_GET_SCREEN_SIZE +SYSCALL_STUB QueryDisplayInfo, SYS_QUERY_DISPLAY_INFO +SYSCALL_STUB OpenDisplayClock, SYS_OPEN_DISPLAY_CLOCK ; --------------------------------------------------------------------------- ; Native render audio ; --------------------------------------------------------------------------- -SYSCALL_STUB2 AudioEnumerateEndpoints, SYS_AUDIO_ENUMERATE_ENDPOINTS -SYSCALL_STUB2 AudioOpenStream, SYS_AUDIO_OPEN_STREAM -SYSCALL_STUB2 AudioWrite, SYS_AUDIO_WRITE -SYSCALL_STUB2 AudioArmWait, SYS_AUDIO_ARM_WAIT -SYSCALL_STUB2 AudioQueryStream, SYS_AUDIO_QUERY_STREAM -SYSCALL_STUB2 AudioStart, SYS_AUDIO_START -SYSCALL_STUB2 AudioPause, SYS_AUDIO_PAUSE -SYSCALL_STUB2 AudioResume, SYS_AUDIO_RESUME -SYSCALL_STUB2 AudioDrain, SYS_AUDIO_DRAIN -SYSCALL_STUB2 AudioStop, SYS_AUDIO_STOP -SYSCALL_STUB2 AudioReset, SYS_AUDIO_RESET -SYSCALL_STUB2 AudioSetVolume, SYS_AUDIO_SET_VOLUME -SYSCALL_STUB2 AudioSetMasterVolume, SYS_AUDIO_SET_MASTER_VOLUME +SYSCALL_STUB AudioEnumerateEndpoints, SYS_AUDIO_ENUMERATE_ENDPOINTS +SYSCALL_STUB AudioOpenStream, SYS_AUDIO_OPEN_STREAM +SYSCALL_STUB AudioWrite, SYS_AUDIO_WRITE +SYSCALL_STUB AudioArmWait, SYS_AUDIO_ARM_WAIT +SYSCALL_STUB AudioQueryStream, SYS_AUDIO_QUERY_STREAM +SYSCALL_STUB AudioStart, SYS_AUDIO_START +SYSCALL_STUB AudioPause, SYS_AUDIO_PAUSE +SYSCALL_STUB AudioResume, SYS_AUDIO_RESUME +SYSCALL_STUB AudioDrain, SYS_AUDIO_DRAIN +SYSCALL_STUB AudioStop, SYS_AUDIO_STOP +SYSCALL_STUB AudioReset, SYS_AUDIO_RESET +SYSCALL_STUB AudioSetVolume, SYS_AUDIO_SET_VOLUME +SYSCALL_STUB AudioSetMasterVolume, SYS_AUDIO_SET_MASTER_VOLUME ; --------------------------------------------------------------------------- ; Input ; --------------------------------------------------------------------------- -SYSCALL_STUB2 PollKeyboard, SYS_POLL_KEYBOARD -SYSCALL_STUB2 IsKeyDown, SYS_IS_KEY_DOWN -SYSCALL_STUB2 ReadKeyboardChar, SYS_READ_KEYBOARD_CHAR -SYSCALL_STUB2 ReadKey, SYS_READ_KEY -SYSCALL_STUB2 GetKeyModifiers, SYS_GET_KEY_MODIFIERS -SYSCALL_STUB2 QueryGlobalInputState, SYS_QUERY_GLOBAL_INPUT_STATE -SYSCALL_STUB2 ReadInputEvent, SYS_READ_INPUT_EVENT -SYSCALL_STUB2 PeekInputEvent, SYS_PEEK_INPUT_EVENT +SYSCALL_STUB PollKeyboard, SYS_POLL_KEYBOARD +SYSCALL_STUB IsKeyDown, SYS_IS_KEY_DOWN +SYSCALL_STUB ReadKeyboardChar, SYS_READ_KEYBOARD_CHAR +SYSCALL_STUB ReadKey, SYS_READ_KEY +SYSCALL_STUB GetKeyModifiers, SYS_GET_KEY_MODIFIERS +SYSCALL_STUB QueryGlobalInputState, SYS_QUERY_GLOBAL_INPUT_STATE +SYSCALL_STUB ReadInputEvent, SYS_READ_INPUT_EVENT +SYSCALL_STUB PeekInputEvent, SYS_PEEK_INPUT_EVENT -SYSCALL_STUB2 PeekMouse, SYS_PEEK_MOUSE -SYSCALL_STUB2 ReadMouse, SYS_READ_MOUSE -SYSCALL_STUB2 GetMousePos, SYS_GET_MOUSE_POS +SYSCALL_STUB PeekMouse, SYS_PEEK_MOUSE +SYSCALL_STUB ReadMouse, SYS_READ_MOUSE +SYSCALL_STUB GetMousePos, SYS_GET_MOUSE_POS ; --------------------------------------------------------------------------- ; Time and debug ; --------------------------------------------------------------------------- -SYSCALL_STUB2 CreateTimer, SYS_CREATE_TIMER -SYSCALL_STUB2 SetTimer, SYS_SET_TIMER -SYSCALL_STUB2 CancelTimer, SYS_CANCEL_TIMER -SYSCALL_STUB2 GetTick, SYS_GET_TICK -SYSCALL_STUB2 GetTickFrequency, SYS_GET_TICK_FREQUENCY -SYSCALL_STUB2 PrintDebug, SYS_PRINT_DEBUG -SYSCALL_STUB2 QueryTrapFrame, SYS_QUERY_TRAP_FRAME +SYSCALL_STUB CreateTimer, SYS_CREATE_TIMER +SYSCALL_STUB SetTimer, SYS_SET_TIMER +SYSCALL_STUB CancelTimer, SYS_CANCEL_TIMER +SYSCALL_STUB GetTick, SYS_GET_TICK +SYSCALL_STUB GetTickFrequency, SYS_GET_TICK_FREQUENCY +SYSCALL_STUB PrintDebug, SYS_PRINT_DEBUG +SYSCALL_STUB 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 -SYSCALL_STUB2 ConnectSocket, SYS_CONNECT_SOCKET -SYSCALL_STUB6 SendSocket, SYS_SEND_SOCKET -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_STUB CreateSocket, SYS_CREATE_SOCKET +SYSCALL_STUB SocketIoAsync, SYS_SOCKET_IO_ASYNC +SYSCALL_STUB BindSocket, SYS_BIND_SOCKET +SYSCALL_STUB ListenSocket, SYS_LISTEN_SOCKET +SYSCALL_STUB AcceptSocket, SYS_ACCEPT_SOCKET +SYSCALL_STUB ConnectSocket, SYS_CONNECT_SOCKET +SYSCALL_STUB SendSocket, SYS_SEND_SOCKET +SYSCALL_STUB RecvSocket, SYS_RECV_SOCKET +SYSCALL_STUB ShutdownSocket, SYS_SHUTDOWN_SOCKET +SYSCALL_STUB SendToSocket, SYS_SEND_TO_SOCKET +SYSCALL_STUB RecvFromSocket, SYS_RECV_FROM_SOCKET +SYSCALL_STUB ResolveHost, SYS_RESOLVE_HOST +SYSCALL_STUB SetSocketOption, SYS_SET_SOCKET_OPTION +SYSCALL_STUB GetSocketOption, SYS_GET_SOCKET_OPTION +SYSCALL_STUB Poll, SYS_POLL -SYSCALL_STUB2 GetMemoryStatus, SYS_GET_MEMORY_STATUS -SYSCALL_STUB2 QueryKernelStats, SYS_QUERY_KERNEL_STATS -SYSCALL_STUB2 GetSystemTime, SYS_GET_SYSTEM_TIME -SYSCALL_STUB5 CreateFileMapping, SYS_CREATE_FILE_MAPPING -SYSCALL_STUB6 MapFile, SYS_MAP_FILE +SYSCALL_STUB GetMemoryStatus, SYS_GET_MEMORY_STATUS +SYSCALL_STUB QueryKernelStats, SYS_QUERY_KERNEL_STATS +SYSCALL_STUB GetSystemTime, SYS_GET_SYSTEM_TIME +SYSCALL_STUB CreateFileMapping, SYS_CREATE_FILE_MAPPING +SYSCALL_STUB MapFile, SYS_MAP_FILE END diff --git a/code/user/system/init/init.c b/code/user/system/init/init.c index b8fd743..53ee50e 100644 --- a/code/user/system/init/init.c +++ b/code/user/system/init/init.c @@ -1,103 +1,421 @@ -// Init support. +// Init service manager. The kernel bootstraps only init; init starts and supervises user-mode system services. #include #include #include +#include #include +#include +#include +#include +#include #include -// Implements the itoa operation. -void itoa(int num, char* str) { - int i = 0; - int is_negative = 0; +#define INIT_SERVICE_READY_TIMEOUT_MS 5000u +#define INIT_SERVICE_STOP_TIMEOUT_MS 5000u - // Handle 0 explicitly. - if (num == 0) { - str[i++] = '0'; - str[i] = '\0'; - return; - } +typedef struct INIT_RESOURCE_SERVICE { + SYS_HANDLE Process; + SYS_HANDLE ControlEndpoint; + SYS_HANDLE AdminListener; +} INIT_RESOURCE_SERVICE; - // Handle negative numbers. - if (num < 0) { - is_negative = 1; - num = -num; - } - - // Process individual digits (result is in reverse). - while (num != 0) { - int rem = num % 10; - str[i++] = rem + '0'; - num = num / 10; - } - - if (is_negative) { - str[i++] = '-'; - } - - str[i] = '\0'; // Null terminate - - // Reverse the string. - int start = 0; - int end = i - 1; - while (start < end) { - char temp = str[start]; - str[start] = str[end]; - str[end] = temp; - start++; - end--; - } +// Writes one string through the privileged kernel debug channel. +static void debug_print(const char* text) { + SYS_PRINT_DEBUG_REQUEST request = { sizeof(request), SysSvFromCstr(text) }; + (void)SysPrintDebug(&request); } -// Implements the debug print operation. -static void debug_print(const char* s) { - SYS_PRINT_DEBUG_REQUEST req = { SysSvFromCstr(s) }; - (void)SysPrintDebug(&req); +// Writes one status value in hexadecimal without pulling in a formatting runtime. +static void debug_status(KSTATUS status) { + static const char digits[] = "0123456789ABCDEF"; + char buffer[11] = "0x00000000"; + for (uint32_t i = 0; i < 8; ++i) + buffer[2 + i] = digits[(status >> ((7u - i) * 4u)) & 0xFu]; + debug_print(buffer); } -// Initializes init process state and supervises the configured child process. -void _start(void) -{ - uint32_t runtimeStatus = NtlInitializeExceptionRuntime(); - if (runtimeStatus != NTLSTATUS_SUCCESS) - SysExitProcess((int32_t)runtimeStatus); +// Exits init without relying on a compiler runtime return path. +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} - SYS_STRING_VIEW args = SysGetPeb()->CommandLine; - uint32_t h = 0; - uint32_t s = 0; +// Derives the least-privileged security context required by RSSRV. +static KSTATUS derive_resource_service_context(SYS_HANDLE* contextOut) { + if (!contextOut) return NTLSTATUS_INVALID_PARAMETER; + *contextOut = SYS_HANDLE_INVALID; + SYS_DERIVE_SECURITY_CONTEXT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; + request.EffectivePrivileges = SYS_PRIV_SYSTEM_SERVICE_REGISTER; + request.DelegablePrivileges = 0; + request.ServiceClaims = SYS_SERVICE_CLAIM_RESOURCE; + request.DelegableServiceClaims = 0; + request.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; + request.ContextHandleOut = contextOut; + return SysDeriveSecurityContext(&request); +} - while (true) { - debug_print("[init] starting\n"); - SYS_CREATE_PROCESS_REQUEST cp = { args, SysSvFromParts(0, 0), &h }; - s = SysCreateProcess(&cp); - if (s != NTLSTATUS_SUCCESS) { - debug_print("[init] FATAL: failed to start!\n"); - char buffer[12]; - itoa(s, buffer); - debug_print(buffer); +// Creates one private Port listener used only between init and a managed service. +static KSTATUS create_control_listener(SYS_HANDLE* listenerOut) { + SYS_CREATE_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Flags = SYS_PORT_FLAG_NONE; + request.Backlog = 1; + request.HandleOut = listenerOut; + return SysCreatePort(&request); +} + +// Connects init to the private control listener delegated to the child. +static KSTATUS connect_control_listener(SYS_HANDLE listener, SYS_HANDLE* endpointOut) { + SYS_CONNECT_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.ListenerHandle = listener; + request.Flags = SYS_PORT_CONNECT_FLAG_NONE; + request.EndpointHandleOut = endpointOut; + return SysConnectPort(&request); +} + +// Waits for RSSRV readiness and receives its private administrative namespace listener. +static KSTATUS wait_resource_service_ready(SYS_HANDLE controlEndpoint, SYS_HANDLE* adminListenerOut) { + if (!adminListenerOut) return NTLSTATUS_INVALID_PARAMETER; + *adminListenerOut = SYS_HANDLE_INVALID; + + NTL_SERVICE_CONTROL_MESSAGE message; + SYS_PORT_MESSAGE_INFO info; + SYS_HANDLE adminListener = SYS_HANDLE_INVALID; + memset(&message, 0, sizeof(message)); + memset(&info, 0, sizeof(info)); + info.Size = sizeof(info); + + SYS_RECEIVE_PORT_REQUEST receive; + memset(&receive, 0, sizeof(receive)); + receive.Size = sizeof(receive); + receive.EndpointHandle = controlEndpoint; + receive.Flags = SYS_PORT_FLAG_NONE; + receive.Payload = &message; + receive.PayloadCapacity = sizeof(message); + receive.HandlesOut = &adminListener; + receive.HandleCapacity = 1; + receive.InfoOut = &info; + receive.TimeoutMilliseconds = INIT_SERVICE_READY_TIMEOUT_MS; + KSTATUS status = SysReceivePort(&receive); + if (!KSUCCESS(status)) return status; + if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.PayloadLength != sizeof(message) || + info.HandleCount != 1 || adminListener == SYS_HANDLE_INVALID || + message.Size != sizeof(message) || message.Version != NTL_SERVICE_CONTROL_VERSION || + message.Operation != NTL_SERVICE_CONTROL_READY || + message.Flags != NTL_SERVICE_READY_FLAG_RESOURCE_ADMIN_LISTENER || + !KSUCCESS(message.Status)) { + if (adminListener != SYS_HANDLE_INVALID) (void)SysCloseHandle(adminListener); + return NTLSTATUS_FAILURE; + } + + *adminListenerOut = adminListener; + return NTLSTATUS_SUCCESS; +} + +// Verifies private namespace administration and public /SystemRoot lookup before declaring RSSRV usable. +static KSTATUS verify_resource_service(SYS_HANDLE adminListener, SYS_HANDLE namespaceVolume) { + SYS_HANDLE admin = SYS_HANDLE_INVALID; + KSTATUS status = NtlResourceConnectListener(adminListener, &admin); + if (!KSUCCESS(status)) return status; + + uint32_t kind = NTL_RESOURCE_KIND_INVALID; + status = NtlResourceQuery(admin, SYS_SV_LITERAL("/SystemRoot"), &kind); + if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME) { + (void)NtlResourceClose(admin); + return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; + } + + // Exercise capability-based administration without leaving a bootstrap-only namespace entry behind. + status = NtlResourceBind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe"), + NTL_RESOURCE_KIND_VOLUME, namespaceVolume, + SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, + SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN); + if (!KSUCCESS(status)) { + (void)NtlResourceClose(admin); + return status; + } + + SYS_HANDLE publicSession = SYS_HANDLE_INVALID; + status = NtlResourceConnect(&publicSession); + if (!KSUCCESS(status)) { + (void)NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe")); + (void)NtlResourceClose(admin); + return status; + } + + SYS_HANDLE volume = SYS_HANDLE_INVALID; + status = NtlResourceLookup(publicSession, SYS_SV_LITERAL("/SystemRoot"), + SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &volume); + if (KSUCCESS(status)) (void)SysCloseHandle(volume); + if (!KSUCCESS(status)) { + (void)NtlResourceClose(publicSession); + (void)NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe")); + (void)NtlResourceClose(admin); + return status; + } + + kind = NTL_RESOURCE_KIND_INVALID; + status = NtlResourceQuery(publicSession, SYS_SV_LITERAL("/Init/BootstrapProbe"), &kind); + if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME) { + (void)NtlResourceClose(publicSession); + (void)NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe")); + (void)NtlResourceClose(admin); + return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; + } + + status = NtlResourceUnbind(admin, SYS_SV_LITERAL("/Init/BootstrapProbe")); + if (KSUCCESS(status)) { + kind = NTL_RESOURCE_KIND_INVALID; + KSTATUS queryStatus = NtlResourceQuery(publicSession, SYS_SV_LITERAL("/Init/BootstrapProbe"), &kind); + if (queryStatus != NTLSTATUS_NOT_FOUND) status = NTLSTATUS_FAILURE; + } + + (void)NtlResourceClose(publicSession); + (void)NtlResourceClose(admin); + return status; +} + +// Tears down retained parent-side service handles without sending a stop message. +static void close_resource_service(INIT_RESOURCE_SERVICE* service) { + if (!service) return; + if (service->ControlEndpoint != SYS_HANDLE_INVALID) { + (void)SysDisconnectPort(service->ControlEndpoint); + (void)SysCloseHandle(service->ControlEndpoint); + } + if (service->AdminListener != SYS_HANDLE_INVALID) + (void)SysCloseHandle(service->AdminListener); + if (service->Process != SYS_HANDLE_INVALID) + (void)SysCloseHandle(service->Process); + service->Process = SYS_HANDLE_INVALID; + service->ControlEndpoint = SYS_HANDLE_INVALID; + service->AdminListener = SYS_HANDLE_INVALID; +} + +// Starts one restricted, import-free RSSRV instance through the userspace process loader. +static KSTATUS start_resource_service(SYS_HANDLE systemVolume, + SYS_HANDLE namespaceVolume, + SYS_HANDLE volumeManager, + SYS_HANDLE resourceContext, + INIT_RESOURCE_SERVICE* serviceOut) { + if (!serviceOut) return NTLSTATUS_INVALID_PARAMETER; + memset(serviceOut, 0, sizeof(*serviceOut)); + + SYS_HANDLE controlListener = SYS_HANDLE_INVALID; + KSTATUS status = create_control_listener(&controlListener); + if (!KSUCCESS(status)) return status; + + NTL_PROCESS_STARTUP_CAPABILITY startup[3]; + memset(startup, 0, sizeof(startup)); + startup[0].Tag = SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE; + startup[0].SourceHandle = namespaceVolume; + startup[0].DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | + SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY; + startup[0].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; + startup[1].Tag = SYS_STARTUP_CAP_SERVICE_CONTROL; + startup[1].SourceHandle = controlListener; + startup[1].DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_ACCEPT; + startup[1].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; + startup[2].Tag = SYS_STARTUP_CAP_VOLUME_MANAGER; + startup[2].SourceHandle = volumeManager; + startup[2].DesiredRights = SYS_RIGHT_QUERY | + SYS_VOLUME_MANAGER_RIGHT_ENUMERATE | SYS_VOLUME_MANAGER_RIGHT_OPEN | + SYS_VOLUME_MANAGER_RIGHT_WAIT; + startup[2].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; + + SYS_HANDLE process = SYS_HANDLE_INVALID; + NTL_CREATE_PROCESS_AT_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + create.RootHandle = systemVolume; + create.Path = SYS_SV_LITERAL("system/rssrv.exe"); + create.CommandLine = SYS_SV_LITERAL("system/rssrv.exe"); + create.SecurityContext = resourceContext; + create.ModuleRootHandle = systemVolume; + create.ModuleDirectory = SYS_SV_LITERAL("system"); + create.ResolveModuleName = NtlResolveModuleNameIdentity; + create.ResolveContext = (void*)0; + create.StartupCapabilities = startup; + create.StartupCapabilityCount = 3; + create.ProcessHandleOut = &process; + status = NtlCreateProcessAt(&create); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(controlListener); + return status; + } + + SYS_HANDLE controlEndpoint = SYS_HANDLE_INVALID; + status = connect_control_listener(controlListener, &controlEndpoint); + (void)SysCloseHandle(controlListener); + if (!KSUCCESS(status)) { + (void)SysKillProcess(process, 1); + (void)SysCloseHandle(process); + return status; + } + + SYS_HANDLE adminListener = SYS_HANDLE_INVALID; + status = wait_resource_service_ready(controlEndpoint, &adminListener); + if (KSUCCESS(status)) + status = verify_resource_service(adminListener, namespaceVolume); + if (!KSUCCESS(status)) { + if (adminListener != SYS_HANDLE_INVALID) (void)SysCloseHandle(adminListener); + (void)SysDisconnectPort(controlEndpoint); + (void)SysCloseHandle(controlEndpoint); + (void)SysKillProcess(process, 1); + (void)SysCloseHandle(process); + return status; + } + + serviceOut->Process = process; + serviceOut->ControlEndpoint = controlEndpoint; + serviceOut->AdminListener = adminListener; + return NTLSTATUS_SUCCESS; +} + +// Requests graceful RSSRV shutdown, falling back to process termination if it stops responding. +static void stop_resource_service(INIT_RESOURCE_SERVICE* service) { + if (!service || service->Process == SYS_HANDLE_INVALID) return; + + NTL_SERVICE_CONTROL_MESSAGE message; + memset(&message, 0, sizeof(message)); + message.Size = sizeof(message); + message.Version = NTL_SERVICE_CONTROL_VERSION; + message.Operation = NTL_SERVICE_CONTROL_STOP; + + SYS_SEND_PORT_REQUEST send; + memset(&send, 0, sizeof(send)); + send.Size = sizeof(send); + send.EndpointHandle = service->ControlEndpoint; + send.Flags = SYS_PORT_FLAG_NONE; + send.Payload = &message; + send.PayloadLength = sizeof(message); + send.TimeoutMilliseconds = INIT_SERVICE_STOP_TIMEOUT_MS; + (void)SysSendPort(&send); + + KSTATUS status = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE, INIT_SERVICE_STOP_TIMEOUT_MS); + if (!KSUCCESS(status)) { + (void)SysKillProcess(service->Process, 1); + (void)SysWait(service->Process, SYS_WAIT_FLAG_NONE); + } + close_resource_service(service); +} + +// Starts the configured native session child directly beneath the system-volume capability. +static KSTATUS start_configured_child(SYS_HANDLE systemVolume, SYS_STRING_VIEW path, SYS_HANDLE* processOut) { + NTL_CREATE_PROCESS_AT_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + create.RootHandle = systemVolume; + create.Path = path; + create.CommandLine = path; + create.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + create.ModuleRootHandle = systemVolume; + create.ModuleDirectory = SYS_SV_LITERAL("system"); + create.ResolveModuleName = NtlResolveSystemModuleName; + create.ProcessHandleOut = processOut; + return NtlCreateProcessAt(&create); +} + +// Initializes init state and supervises RSSRV plus the configured session process. +void _start(void) { + KSTATUS status = NtlInitializeExceptionRuntime(); + if (!KSUCCESS(status)) exit_now((int)status); + + SYS_HANDLE systemVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME); + SYS_HANDLE namespaceVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE); + SYS_HANDLE volumeManager = SysGetStartupCapability(SYS_STARTUP_CAP_VOLUME_MANAGER); + if (systemVolume == SYS_HANDLE_INVALID || namespaceVolume == SYS_HANDLE_INVALID || + volumeManager == SYS_HANDLE_INVALID) { + debug_print("[init] FATAL: bootstrap storage capability missing\n"); + exit_now(1); + } + + SYS_HANDLE resourceContext = SYS_HANDLE_INVALID; + status = derive_resource_service_context(&resourceContext); + if (!KSUCCESS(status)) { + debug_print("[init] FATAL: cannot derive RSSRV security context: "); + debug_status(status); + debug_print("\n"); + exit_now(1); + } + + SYS_STRING_VIEW childPath = SysGetPeb()->CommandLine; + if (!childPath.Data || childPath.Length == 0) { + debug_print("[init] FATAL: no configured session process\n"); + exit_now(1); + } + + for (;;) { + INIT_RESOURCE_SERVICE resourceService; + status = start_resource_service(systemVolume, namespaceVolume, volumeManager, resourceContext, &resourceService); + if (!KSUCCESS(status)) { + debug_print("[init] FATAL: RSSRV startup failed: "); + debug_status(status); debug_print("\n"); - SysExitProcess(1); + exit_now(1); } + debug_print("[init] RSSRV ready\n"); - debug_print("[init] waiting\n"); - s = SysWait(h, SYS_WAIT_FLAG_NONE); - if (s != NTLSTATUS_SUCCESS) - debug_print("[init] WARNING: SysWait returned error\n"); + for (;;) { + SYS_HANDLE child = SYS_HANDLE_INVALID; + status = start_configured_child(systemVolume, childPath, &child); + if (!KSUCCESS(status)) { + debug_print("[init] FATAL: failed to start configured child: "); + debug_status(status); + debug_print("\n"); + stop_resource_service(&resourceService); + exit_now(1); + } + debug_print("[init] session process started\n"); - int32_t exitCode = 0; - if (SysGetProcessExitCode(h, &exitCode) != NTLSTATUS_SUCCESS) { - debug_print("[init] WARNING: failed to read exit code\n"); - exitCode = 1; + SYS_HANDLE waits[2] = { resourceService.Process, child }; + uint32_t signaled = 0; + SYS_WAIT_MANY_REQUEST wait; + memset(&wait, 0, sizeof(wait)); + wait.Size = sizeof(wait); + wait.Flags = SYS_WAIT_FLAG_NONE; + wait.Handles = waits; + wait.Count = 2; + wait.SignaledIndexOut = &signaled; + wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE; + status = SysWaitMany(&wait); + if (!KSUCCESS(status)) { + (void)SysKillProcess(child, 1); + (void)SysCloseHandle(child); + stop_resource_service(&resourceService); + debug_print("[init] FATAL: service wait failed\n"); + exit_now(1); + } + + if (signaled == 0) { + int32_t serviceExit = -1; + (void)SysGetProcessExitCode(resourceService.Process, &serviceExit); + debug_print("[init] RSSRV exited unexpectedly; restarting service plane\n"); + (void)SysKillProcess(child, 1); + (void)SysWait(child, SYS_WAIT_FLAG_NONE); + (void)SysCloseHandle(child); + close_resource_service(&resourceService); + break; + } + + int32_t exitCode = -1; + status = SysGetProcessExitCode(child, &exitCode); + (void)SysCloseHandle(child); + if (!KSUCCESS(status)) exitCode = 1; + if (exitCode == 0) { + stop_resource_service(&resourceService); + debug_print("[init] done\n"); + (void)SysCloseHandle(resourceContext); + exit_now(0); + } + debug_print("[init] session process crashed; restarting\n"); } - - SysCloseHandle(h); - - if (exitCode == 0) - break; - - debug_print("[init] child crashed, restarting\n"); } - - debug_print("[init] done\n"); - SysExitProcess(0); } diff --git a/code/user/system/rssrv/rssrv.c b/code/user/system/rssrv/rssrv.c new file mode 100644 index 0000000..ff598ff --- /dev/null +++ b/code/user/system/rssrv/rssrv.c @@ -0,0 +1,947 @@ +// Native resource namespace service. RSSRV owns global names and transfers capabilities; it is not a file data path. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RSSRV_LISTENER_BACKLOG 16u + +// One namespace binding. Path storage follows the fixed header in the same allocation. +typedef struct RSSRV_ENTRY { + struct RSSRV_ENTRY* Next; + SYS_HANDLE Handle; + uint32_t Kind; + SYS_RIGHTS ExportRights; + uint32_t PathLength; + char Path[1]; +} RSSRV_ENTRY; + +typedef struct RSSRV_SESSION { + SYS_HANDLE Endpoint; + uint32_t Administrative; +} RSSRV_SESSION; + +typedef struct RSSRV_LISTENER_WORKER { + SYS_HANDLE Listener; + uint32_t Administrative; +} RSSRV_LISTENER_WORKER; + +typedef struct RSSRV_VOLUME_PUBLICATION { + struct RSSRV_VOLUME_PUBLICATION* Next; + uint32_t InstanceId; + uint32_t NamespaceId; + uint32_t PathLength; + char Path[48]; +} RSSRV_VOLUME_PUBLICATION; + +static SYS_HANDLE gNamespaceMutex = SYS_HANDLE_INVALID; +static RSSRV_ENTRY* gNamespace = (RSSRV_ENTRY*)0; +static SYS_HANDLE gPublicListener = SYS_HANDLE_INVALID; +static SYS_HANDLE gAdminListener = SYS_HANDLE_INVALID; +static RSSRV_LISTENER_WORKER gPublicWorker; +static RSSRV_LISTENER_WORKER gAdminWorker; +static RSSRV_VOLUME_PUBLICATION* gPublishedVolumes = (RSSRV_VOLUME_PUBLICATION*)0; +static uint32_t gNextHarddiskVolumeId = 1u; + +// Exits without depending on a compiler runtime return path. +static void RssrvExit(int code) { + for (;;) SysExitProcess(code); +} + +// Acquires the namespace mutex. +static KSTATUS RssrvLockNamespace(void) { + return SysWait(gNamespaceMutex, SYS_WAIT_FLAG_NONE); +} + +// Releases the namespace mutex. +static void RssrvUnlockNamespace(void) { + (void)SysReleaseMutex(gNamespaceMutex); +} + +// Validates one canonical absolute native resource path. +static KSTATUS RssrvValidatePath(const char* path, uint32_t length) { + if (!path || length < 2 || length > NTL_RESOURCE_PATH_MAX || path[0] != '/' || path[length - 1] == '/') + return NTLSTATUS_INVALID_PARAMETER; + + uint32_t componentStart = 1; + for (uint32_t i = 1; i <= length; ++i) { + if (i < length) { + char c = path[i]; + if (c == '\\' || c == '\0') + return NTLSTATUS_INVALID_PARAMETER; + if (c != '/') + continue; + } + uint32_t componentLength = i - componentStart; + if (componentLength == 0 || + (componentLength == 1 && path[componentStart] == '.') || + (componentLength == 2 && path[componentStart] == '.' && path[componentStart + 1] == '.')) + return NTLSTATUS_INVALID_PARAMETER; + componentStart = i + 1; + } + return NTLSTATUS_SUCCESS; +} + +// Returns whether a path belongs to the RSSRV-owned automatic mounted-volume naming family. +static int RssrvIsManagedVolumePath(const char* path, uint32_t length) { + static const char prefix[] = "/Device/HarddiskVolume"; + uint32_t prefixLength = (uint32_t)(sizeof(prefix) - 1u); + return path && length >= prefixLength && memcmp(path, prefix, prefixLength) == 0; +} + +// Finds one exact binding while the namespace mutex is held. +static RSSRV_ENTRY* RssrvFindLocked(const char* path, uint32_t length, RSSRV_ENTRY*** linkOut) { + RSSRV_ENTRY** link = &gNamespace; + while (*link) { + RSSRV_ENTRY* entry = *link; + if (entry->PathLength == length && memcmp(entry->Path, path, length) == 0) { + if (linkOut) *linkOut = link; + return entry; + } + link = &entry->Next; + } + if (linkOut) *linkOut = link; + return (RSSRV_ENTRY*)0; +} + +// Finds the longest namespace binding that is an exact component prefix of path. +static RSSRV_ENTRY* RssrvFindPrefixLocked(const char* path, uint32_t length) { + RSSRV_ENTRY* best = (RSSRV_ENTRY*)0; + for (RSSRV_ENTRY* entry = gNamespace; entry; entry = entry->Next) { + if (entry->PathLength > length) + continue; + if (memcmp(entry->Path, path, entry->PathLength) != 0) + continue; + if (entry->PathLength != length && path[entry->PathLength] != '/') + continue; + if (!best || entry->PathLength > best->PathLength) + best = entry; + } + return best; +} + +// Takes ownership of one handle and binds it to a new exact resource name. +static KSTATUS RssrvBindOwned(const char* path, + uint32_t length, + uint32_t kind, + SYS_HANDLE handle, + SYS_RIGHTS exportRights) { + if (kind == NTL_RESOURCE_KIND_INVALID || handle == SYS_HANDLE_INVALID || !exportRights) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = RssrvValidatePath(path, length); + if (!KSUCCESS(status)) return status; + + size_t bytes = offsetof(RSSRV_ENTRY, Path) + (size_t)length + 1u; + RSSRV_ENTRY* entry = (RSSRV_ENTRY*)malloc(bytes); + if (!entry) return NTLSTATUS_NO_MEMORY; + memset(entry, 0, bytes); + entry->Handle = handle; + entry->Kind = kind; + entry->ExportRights = exportRights; + entry->PathLength = length; + memcpy(entry->Path, path, length); + entry->Path[length] = '\0'; + + status = RssrvLockNamespace(); + if (!KSUCCESS(status)) { + free(entry); + return status; + } + if (RssrvFindLocked(path, length, (RSSRV_ENTRY***)0)) { + RssrvUnlockNamespace(); + free(entry); + return NTLSTATUS_OBJECT_NAME_COLLISION; + } + entry->Next = gNamespace; + gNamespace = entry; + RssrvUnlockNamespace(); + return NTLSTATUS_SUCCESS; +} + +// Removes one binding and closes the namespace's retained handle. +static KSTATUS RssrvUnbind(const char* path, uint32_t length) { + KSTATUS status = RssrvValidatePath(path, length); + if (!KSUCCESS(status)) return status; + status = RssrvLockNamespace(); + if (!KSUCCESS(status)) return status; + + RSSRV_ENTRY** link = (RSSRV_ENTRY**)0; + RSSRV_ENTRY* entry = RssrvFindLocked(path, length, &link); + if (!entry) { + RssrvUnlockNamespace(); + return NTLSTATUS_NOT_FOUND; + } + *link = entry->Next; + RssrvUnlockNamespace(); + (void)SysCloseHandle(entry->Handle); + free(entry); + return NTLSTATUS_SUCCESS; +} + +// Finds one published mounted volume by kernel instance identity. +static RSSRV_VOLUME_PUBLICATION* RssrvFindPublishedVolume(uint32_t instanceId) { + for (RSSRV_VOLUME_PUBLICATION* publication = gPublishedVolumes; publication; publication = publication->Next) { + if (publication->InstanceId == instanceId) + return publication; + } + return (RSSRV_VOLUME_PUBLICATION*)0; +} + +// Returns whether one kernel volume instance is present in the current manager snapshot. +static int RssrvSnapshotContainsVolume(const SYS_VOLUME_INFO* volumes, uint32_t count, uint32_t instanceId) { + for (uint32_t i = 0; i < count; ++i) { + if (volumes[i].InstanceId == instanceId) + return 1; + } + return 0; +} + +// Builds one RSSRV-local /Device/HarddiskVolumeN name without introducing a formatting-runtime dependency. +static KSTATUS RssrvBuildHarddiskVolumePath(uint32_t namespaceId, char* path, uint32_t capacity, uint32_t* lengthOut) { + static const char prefix[] = "/Device/HarddiskVolume"; + if (!namespaceId || !path || !lengthOut) + return NTLSTATUS_INVALID_PARAMETER; + + uint32_t prefixLength = (uint32_t)(sizeof(prefix) - 1u); + char digits[10]; + uint32_t digitCount = 0; + uint32_t value = namespaceId; + do { + digits[digitCount++] = (char)('0' + (value % 10u)); + value /= 10u; + } while (value && digitCount < (uint32_t)sizeof(digits)); + + if (prefixLength + digitCount + 1u > capacity) + return NTLSTATUS_BUFFER_TOO_SMALL; + memcpy(path, prefix, prefixLength); + for (uint32_t i = 0; i < digitCount; ++i) + path[prefixLength + i] = digits[digitCount - 1u - i]; + path[prefixLength + digitCount] = '\0'; + *lengthOut = prefixLength + digitCount; + return NTLSTATUS_SUCCESS; +} + +// Takes a stable manager snapshot, growing the userspace buffer if the mounted-volume count changes. +static KSTATUS RssrvQueryVolumes(SYS_HANDLE manager, + SYS_VOLUME_INFO** volumesOut, + uint32_t* countOut, + uint64_t* generationOut) { + if (!volumesOut || !countOut || !generationOut) + return NTLSTATUS_INVALID_PARAMETER; + *volumesOut = (SYS_VOLUME_INFO*)0; + *countOut = 0; + *generationOut = 0; + + SYS_VOLUME_INFO* volumes = (SYS_VOLUME_INFO*)0; + uint32_t capacity = 0; + for (;;) { + uint32_t count = 0; + uint64_t generation = 0; + SYS_QUERY_VOLUMES_REQUEST query; + memset(&query, 0, sizeof(query)); + query.Size = sizeof(query); + query.ManagerHandle = manager; + query.Flags = SYS_VOLUME_QUERY_FLAG_NONE; + query.Capacity = capacity; + query.Volumes = volumes; + query.CountOut = &count; + query.GenerationOut = &generation; + KSTATUS status = SysQueryVolumes(&query); + if (status == NTLSTATUS_BUFFER_TOO_SMALL) { + if (volumes) free(volumes); + volumes = (SYS_VOLUME_INFO*)0; + capacity = count; + if (capacity) { + volumes = (SYS_VOLUME_INFO*)malloc((size_t)capacity * sizeof(*volumes)); + if (!volumes) + return NTLSTATUS_NO_MEMORY; + memset(volumes, 0, (size_t)capacity * sizeof(*volumes)); + } + continue; + } + if (!KSUCCESS(status)) { + if (volumes) free(volumes); + return status; + } + for (uint32_t i = 0; i < count; ++i) { + if (volumes[i].Size != sizeof(SYS_VOLUME_INFO) || + volumes[i].Version != SYS_VOLUME_ENUM_VERSION || !volumes[i].InstanceId || + volumes[i].Flags != 0) { + if (volumes) free(volumes); + return NTLSTATUS_INVALID_PARAMETER; + } + } + *volumesOut = volumes; + *countOut = count; + *generationOut = generation; + return NTLSTATUS_SUCCESS; + } +} + +// Reconciles the RSSRV object namespace with the kernel's mounted-volume snapshot. +static KSTATUS RssrvSyncVolumes(SYS_HANDLE manager, uint64_t* generationOut) { + if (!generationOut) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_VOLUME_INFO* volumes = (SYS_VOLUME_INFO*)0; + uint32_t count = 0; + uint64_t generation = 0; + KSTATUS status = RssrvQueryVolumes(manager, &volumes, &count, &generation); + if (!KSUCCESS(status)) + return status; + + RSSRV_VOLUME_PUBLICATION** link = &gPublishedVolumes; + while (*link) { + RSSRV_VOLUME_PUBLICATION* publication = *link; + if (RssrvSnapshotContainsVolume(volumes, count, publication->InstanceId)) { + link = &publication->Next; + continue; + } + status = RssrvUnbind(publication->Path, publication->PathLength); + if (!KSUCCESS(status) && status != NTLSTATUS_NOT_FOUND) { + free(volumes); + return status; + } + *link = publication->Next; + free(publication); + } + + for (uint32_t i = 0; i < count; ++i) { + if (RssrvFindPublishedVolume(volumes[i].InstanceId)) + continue; + + SYS_HANDLE volume = SYS_HANDLE_INVALID; + status = SysOpenVolume(manager, volumes[i].InstanceId, + SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | + SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY, + &volume); + if (status == NTLSTATUS_NOT_FOUND || status == NTLSTATUS_OBJECT_DEAD) + continue; + if (!KSUCCESS(status)) { + free(volumes); + return status; + } + + RSSRV_VOLUME_PUBLICATION* publication = + (RSSRV_VOLUME_PUBLICATION*)malloc(sizeof(*publication)); + if (!publication) { + (void)SysCloseHandle(volume); + free(volumes); + return NTLSTATUS_NO_MEMORY; + } + memset(publication, 0, sizeof(*publication)); + publication->InstanceId = volumes[i].InstanceId; + publication->NamespaceId = gNextHarddiskVolumeId; + status = RssrvBuildHarddiskVolumePath(publication->NamespaceId, + publication->Path, + (uint32_t)sizeof(publication->Path), + &publication->PathLength); + if (!KSUCCESS(status)) { + free(publication); + (void)SysCloseHandle(volume); + free(volumes); + return status; + } + + status = RssrvBindOwned(publication->Path, publication->PathLength, + NTL_RESOURCE_KIND_VOLUME, volume, + SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY); + if (!KSUCCESS(status)) { + free(publication); + (void)SysCloseHandle(volume); + free(volumes); + return status; + } + publication->Next = gPublishedVolumes; + gPublishedVolumes = publication; + gNextHarddiskVolumeId++; + if (!gNextHarddiskVolumeId) { + free(volumes); + return NTLSTATUS_LIMIT_REACHED; + } + } + + if (volumes) free(volumes); + *generationOut = generation; + return NTLSTATUS_SUCCESS; +} + +// Keeps automatic /Device/HarddiskVolumeN bindings synchronized with mounted IO_VOLUME instances. +static void RssrvVolumeWorker(uintptr_t argument) { + SYS_HANDLE manager = (SYS_HANDLE)argument; + for (;;) { + uint64_t generation = 0; + KSTATUS status = RssrvSyncVolumes(manager, &generation); + if (!KSUCCESS(status)) + RssrvExit(12); + status = SysWaitVolumeChange(manager, generation, SYS_WAIT_TIMEOUT_INFINITE); + if (!KSUCCESS(status)) + RssrvExit(12); + } +} + +// Starts the mounted-volume namespace publication worker. +static KSTATUS RssrvStartVolumeWorker(SYS_HANDLE manager) { + uint32_t threadId = 0; + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)RssrvVolumeWorker; + create.Argument = (uintptr_t)manager; + create.Priority = 1; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + create.ThreadIdOut = &threadId; + return SysCreateThread(&create); +} + +// Replies with one fixed resource-protocol reply and no handle transfer. +static KSTATUS RssrvReplyStatus(SYS_HANDLE endpoint, + uint32_t messageId, + uint32_t operation, + KSTATUS status, + uint32_t kind) { + NTL_RESOURCE_REPLY reply; + memset(&reply, 0, sizeof(reply)); + reply.Size = sizeof(reply); + reply.Version = NTL_RESOURCE_PROTOCOL_VERSION; + reply.Operation = operation; + reply.Flags = NTL_RESOURCE_FLAG_NONE; + reply.Status = status; + reply.Kind = kind; + + SYS_REPLY_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.EndpointHandle = endpoint; + request.MessageId = messageId; + request.Flags = SYS_PORT_FLAG_NONE; + request.Payload = &reply; + request.PayloadLength = sizeof(reply); + return SysReplyPort(&request); +} + +// Handles one lookup while retaining the namespace mutex through capability transfer. +static KSTATUS RssrvHandleLookup(SYS_HANDLE endpoint, + uint32_t messageId, + const NTL_RESOURCE_REQUEST* request, + const char* path) { + if (!request->Rights) + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + + KSTATUS status = RssrvLockNamespace(); + if (!KSUCCESS(status)) + return RssrvReplyStatus(endpoint, messageId, request->Operation, status, NTL_RESOURCE_KIND_INVALID); + RSSRV_ENTRY* entry = RssrvFindLocked(path, request->PathLength, (RSSRV_ENTRY***)0); + if (!entry) { + RssrvUnlockNamespace(); + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_NOT_FOUND, NTL_RESOURCE_KIND_INVALID); + } + + uint32_t kind = entry->Kind; + NTL_RESOURCE_REPLY reply; + memset(&reply, 0, sizeof(reply)); + reply.Size = sizeof(reply); + reply.Version = NTL_RESOURCE_PROTOCOL_VERSION; + reply.Operation = NTL_RESOURCE_OP_LOOKUP; + reply.Status = NTLSTATUS_SUCCESS; + reply.Kind = kind; + + SYS_PORT_HANDLE_TRANSFER transfer; + transfer.Handle = entry->Handle; + if ((request->Rights & ~entry->ExportRights) != 0) { + RssrvUnlockNamespace(); + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_ACCESS_DENIED, kind); + } + + transfer.DesiredRights = request->Rights; + transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; + + SYS_REPLY_PORT_REQUEST out; + memset(&out, 0, sizeof(out)); + out.Size = sizeof(out); + out.EndpointHandle = endpoint; + out.MessageId = messageId; + out.Flags = SYS_PORT_FLAG_NONE; + out.Payload = &reply; + out.PayloadLength = sizeof(reply); + out.Handles = &transfer; + out.HandleCount = 1; + status = SysReplyPort(&out); + RssrvUnlockNamespace(); + + // A rights mismatch leaves the synchronous call pending, so return the precise error without a handle. + if (!KSUCCESS(status)) + return RssrvReplyStatus(endpoint, messageId, request->Operation, status, kind); + return NTLSTATUS_SUCCESS; +} + +// Resolves the longest bound resource prefix and transfers traversal authority. +static KSTATUS RssrvHandleResolve(SYS_HANDLE endpoint, + uint32_t messageId, + const NTL_RESOURCE_REQUEST* request, + const char* path) { + if ((request->Flags & ~NTL_RESOURCE_RESOLVE_FLAG_MODIFY) != 0) + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + + KSTATUS status = RssrvLockNamespace(); + if (!KSUCCESS(status)) + return RssrvReplyStatus(endpoint, messageId, request->Operation, status, NTL_RESOURCE_KIND_INVALID); + + RSSRV_ENTRY* entry = RssrvFindPrefixLocked(path, request->PathLength); + if (!entry) { + RssrvUnlockNamespace(); + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_NOT_FOUND, NTL_RESOURCE_KIND_INVALID); + } + + SYS_RIGHTS desiredRights = 0; + if (entry->Kind == NTL_RESOURCE_KIND_VOLUME) { + desiredRights = SYS_VOLUME_RIGHT_OPEN; + if (request->Flags & NTL_RESOURCE_RESOLVE_FLAG_MODIFY) + desiredRights |= SYS_VOLUME_RIGHT_MODIFY; + } else if (entry->Kind == NTL_RESOURCE_KIND_DIRECTORY) { + desiredRights = SYS_DIR_RIGHT_OPEN; + if (request->Flags & NTL_RESOURCE_RESOLVE_FLAG_MODIFY) + desiredRights |= SYS_DIR_RIGHT_MODIFY; + } else { + uint32_t kind = entry->Kind; + RssrvUnlockNamespace(); + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_NOT_SUPPORTED, kind); + } + + if ((desiredRights & ~entry->ExportRights) != 0) { + uint32_t kind = entry->Kind; + RssrvUnlockNamespace(); + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_ACCESS_DENIED, kind); + } + + uint32_t kind = entry->Kind; + NTL_RESOURCE_REPLY reply; + memset(&reply, 0, sizeof(reply)); + reply.Size = sizeof(reply); + reply.Version = NTL_RESOURCE_PROTOCOL_VERSION; + reply.Operation = NTL_RESOURCE_OP_RESOLVE; + reply.Flags = request->Flags; + reply.Status = NTLSTATUS_SUCCESS; + reply.Kind = kind; + reply.ResolvedLength = entry->PathLength; + + SYS_PORT_HANDLE_TRANSFER transfer; + transfer.Handle = entry->Handle; + transfer.DesiredRights = desiredRights; + transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; + + SYS_REPLY_PORT_REQUEST out; + memset(&out, 0, sizeof(out)); + out.Size = sizeof(out); + out.EndpointHandle = endpoint; + out.MessageId = messageId; + out.Flags = SYS_PORT_FLAG_NONE; + out.Payload = &reply; + out.PayloadLength = sizeof(reply); + out.Handles = &transfer; + out.HandleCount = 1; + status = SysReplyPort(&out); + RssrvUnlockNamespace(); + if (!KSUCCESS(status)) + return RssrvReplyStatus(endpoint, messageId, request->Operation, status, kind); + return NTLSTATUS_SUCCESS; +} + +// Handles one metadata query. +static KSTATUS RssrvHandleQuery(SYS_HANDLE endpoint, + uint32_t messageId, + const NTL_RESOURCE_REQUEST* request, + const char* path) { + KSTATUS status = RssrvLockNamespace(); + if (!KSUCCESS(status)) + return RssrvReplyStatus(endpoint, messageId, request->Operation, status, NTL_RESOURCE_KIND_INVALID); + RSSRV_ENTRY* entry = RssrvFindLocked(path, request->PathLength, (RSSRV_ENTRY***)0); + uint32_t found = entry != (RSSRV_ENTRY*)0; + uint32_t kind = found ? entry->Kind : NTL_RESOURCE_KIND_INVALID; + RssrvUnlockNamespace(); + return RssrvReplyStatus(endpoint, messageId, request->Operation, + found ? NTLSTATUS_SUCCESS : NTLSTATUS_NOT_FOUND, kind); +} + +// Handles one administrative bind. On success the namespace takes ownership of receivedHandle. +static KSTATUS RssrvHandleBind(SYS_HANDLE endpoint, + uint32_t messageId, + const NTL_RESOURCE_REQUEST* request, + const char* path, + uint32_t administrative, + SYS_HANDLE receivedHandle, + uint32_t* consumedOut) { + if (!consumedOut) return NTLSTATUS_INVALID_PARAMETER; + *consumedOut = 0; + if (!administrative || RssrvIsManagedVolumePath(path, request->PathLength)) + return RssrvReplyStatus(endpoint, messageId, request->Operation, + NTLSTATUS_ACCESS_DENIED, NTL_RESOURCE_KIND_INVALID); + KSTATUS bindStatus = RssrvBindOwned(path, request->PathLength, request->Kind, receivedHandle, request->ExportRights); + if (KSUCCESS(bindStatus)) *consumedOut = 1; + KSTATUS replyStatus = RssrvReplyStatus(endpoint, messageId, request->Operation, bindStatus, + KSUCCESS(bindStatus) ? request->Kind : NTL_RESOURCE_KIND_INVALID); + return KSUCCESS(replyStatus) ? bindStatus : replyStatus; +} + +// Dispatches one received resource request and returns whether a received handle was consumed. +static uint32_t RssrvDispatchRequest(SYS_HANDLE endpoint, + uint32_t administrative, + const uint8_t* payload, + uint32_t payloadLength, + const SYS_PORT_MESSAGE_INFO* info, + SYS_HANDLE receivedHandle) { + uint32_t operation = 0; + if (payloadLength < sizeof(NTL_RESOURCE_REQUEST)) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + return 0; + } + + NTL_RESOURCE_REQUEST request; + memcpy(&request, payload, sizeof(request)); + operation = request.Operation; + if (request.Size != sizeof(request) || request.Version != NTL_RESOURCE_PROTOCOL_VERSION || + request.PathLength == 0 || payloadLength != sizeof(request) + request.PathLength) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + return 0; + } + const char* path = (const char*)(payload + sizeof(request)); + KSTATUS status = RssrvValidatePath(path, request.PathLength); + if (!KSUCCESS(status)) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, status, NTL_RESOURCE_KIND_INVALID); + return 0; + } + + switch (operation) { + case NTL_RESOURCE_OP_LOOKUP: + if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 0 || + request.ExportRights != 0 || request.Kind != NTL_RESOURCE_KIND_INVALID) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + return 0; + } + (void)RssrvHandleLookup(endpoint, info->MessageId, &request, path); + return 0; + case NTL_RESOURCE_OP_QUERY: + if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 0 || request.Rights != 0 || + request.ExportRights != 0 || request.Kind != NTL_RESOURCE_KIND_INVALID) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + return 0; + } + (void)RssrvHandleQuery(endpoint, info->MessageId, &request, path); + return 0; + case NTL_RESOURCE_OP_RESOLVE: + if (info->HandleCount != 0 || request.Rights != 0 || request.ExportRights != 0 || + request.Kind != NTL_RESOURCE_KIND_INVALID || + (request.Flags & ~NTL_RESOURCE_RESOLVE_FLAG_MODIFY) != 0) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + return 0; + } + (void)RssrvHandleResolve(endpoint, info->MessageId, &request, path); + return 0; + case NTL_RESOURCE_OP_BIND: + if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 1 || + !(request.Rights & SYS_RIGHT_TRANSFER) || + request.ExportRights == 0 || (request.ExportRights & ~request.Rights) != 0 || + request.Kind == NTL_RESOURCE_KIND_INVALID || receivedHandle == SYS_HANDLE_INVALID) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + return 0; + } + { + uint32_t consumed = 0; + (void)RssrvHandleBind(endpoint, info->MessageId, &request, path, administrative, + receivedHandle, &consumed); + return consumed; + } + case NTL_RESOURCE_OP_UNBIND: + if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 0 || request.Rights != 0 || + request.ExportRights != 0 || request.Kind != NTL_RESOURCE_KIND_INVALID) { + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID); + return 0; + } + status = administrative && !RssrvIsManagedVolumePath(path, request.PathLength) + ? RssrvUnbind(path, request.PathLength) + : NTLSTATUS_ACCESS_DENIED; + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, status, NTL_RESOURCE_KIND_INVALID); + return 0; + default: + (void)RssrvReplyStatus(endpoint, info->MessageId, operation, + NTLSTATUS_NOT_SUPPORTED, NTL_RESOURCE_KIND_INVALID); + return 0; + } +} + +// Serves one connected resource session until the peer disconnects. +static void RssrvSessionThread(uintptr_t argument) { + RSSRV_SESSION* session = (RSSRV_SESSION*)argument; + if (!session) { + SysExitThread(1); + return; + } + SYS_HANDLE endpoint = session->Endpoint; + uint32_t administrative = session->Administrative; + free(session); + + uint8_t payload[SYS_PORT_MAX_INLINE_BYTES]; + SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES]; + for (;;) { + memset(handles, 0, sizeof(handles)); + SYS_PORT_MESSAGE_INFO info; + memset(&info, 0, sizeof(info)); + info.Size = sizeof(info); + SYS_RECEIVE_PORT_REQUEST receive; + memset(&receive, 0, sizeof(receive)); + receive.Size = sizeof(receive); + receive.EndpointHandle = endpoint; + receive.Flags = SYS_PORT_FLAG_NONE; + receive.Payload = payload; + receive.PayloadCapacity = sizeof(payload); + receive.HandlesOut = handles; + receive.HandleCapacity = SYS_PORT_MAX_TRANSFER_HANDLES; + receive.InfoOut = &info; + receive.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE; + KSTATUS status = SysReceivePort(&receive); + if (status == NTLSTATUS_PORT_DISCONNECTED) + break; + if (!KSUCCESS(status)) + break; + + uint32_t consumed = 0; + if (info.Type == SYS_PORT_MESSAGE_REQUEST) + consumed = RssrvDispatchRequest(endpoint, administrative, payload, info.PayloadLength, + &info, info.HandleCount == 1 ? handles[0] : SYS_HANDLE_INVALID); + for (uint32_t i = consumed ? 1u : 0u; i < info.HandleCount; ++i) { + if (handles[i] != SYS_HANDLE_INVALID) (void)SysCloseHandle(handles[i]); + } + } + + (void)SysCloseHandle(endpoint); + SysExitThread(0); +} + +// Accepts sessions from one listener and gives every connection its own worker thread. +static void RssrvListenerThread(uintptr_t argument) { + RSSRV_LISTENER_WORKER* worker = (RSSRV_LISTENER_WORKER*)argument; + if (!worker) { + SysExitThread(1); + return; + } + for (;;) { + SYS_HANDLE endpoint = SYS_HANDLE_INVALID; + SYS_ACCEPT_PORT_REQUEST accept; + memset(&accept, 0, sizeof(accept)); + accept.Size = sizeof(accept); + accept.ListenerHandle = worker->Listener; + accept.Flags = SYS_PORT_ACCEPT_FLAG_NONE; + accept.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE; + accept.EndpointHandleOut = &endpoint; + KSTATUS status = SysAcceptPort(&accept); + if (!KSUCCESS(status)) + break; + + RSSRV_SESSION* session = (RSSRV_SESSION*)malloc(sizeof(*session)); + if (!session) { + (void)SysDisconnectPort(endpoint); + (void)SysCloseHandle(endpoint); + continue; + } + session->Endpoint = endpoint; + session->Administrative = worker->Administrative; + + uint32_t threadId = 0; + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)RssrvSessionThread; + create.Argument = (uintptr_t)session; + create.Priority = 1; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + create.ThreadIdOut = &threadId; + status = SysCreateThread(&create); + if (!KSUCCESS(status)) { + free(session); + (void)SysDisconnectPort(endpoint); + (void)SysCloseHandle(endpoint); + } + } + SysExitThread(0); +} + +// Starts one resource listener worker thread. +static KSTATUS RssrvStartListenerWorker(RSSRV_LISTENER_WORKER* worker, + SYS_HANDLE listener, + uint32_t administrative) { + worker->Listener = listener; + worker->Administrative = administrative; + uint32_t threadId = 0; + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)RssrvListenerThread; + create.Argument = (uintptr_t)worker; + create.Priority = 1; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + create.ThreadIdOut = &threadId; + return SysCreateThread(&create); +} + +// Creates one listener using the service's bounded default backlog. +static KSTATUS RssrvCreateListener(SYS_HANDLE* listenerOut) { + SYS_CREATE_PORT_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Flags = SYS_PORT_FLAG_NONE; + create.Backlog = RSSRV_LISTENER_BACKLOG; + create.HandleOut = listenerOut; + return SysCreatePort(&create); +} + +// Accepts the one private service-manager control connection. +static KSTATUS RssrvAcceptControl(SYS_HANDLE controlListener, SYS_HANDLE* endpointOut) { + SYS_ACCEPT_PORT_REQUEST accept; + memset(&accept, 0, sizeof(accept)); + accept.Size = sizeof(accept); + accept.ListenerHandle = controlListener; + accept.Flags = SYS_PORT_ACCEPT_FLAG_NONE; + accept.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE; + accept.EndpointHandleOut = endpointOut; + return SysAcceptPort(&accept); +} + +// Sends service readiness and delegates the private administrative resource listener to init. +static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint) { + NTL_SERVICE_CONTROL_MESSAGE message; + memset(&message, 0, sizeof(message)); + message.Size = sizeof(message); + message.Version = NTL_SERVICE_CONTROL_VERSION; + message.Operation = NTL_SERVICE_CONTROL_READY; + message.Flags = NTL_SERVICE_READY_FLAG_RESOURCE_ADMIN_LISTENER; + message.Status = NTLSTATUS_SUCCESS; + + SYS_PORT_HANDLE_TRANSFER transfer; + transfer.Handle = gAdminListener; + transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT; + transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; + + SYS_SEND_PORT_REQUEST send; + memset(&send, 0, sizeof(send)); + send.Size = sizeof(send); + send.EndpointHandle = controlEndpoint; + send.Flags = SYS_PORT_FLAG_NONE; + send.Payload = &message; + send.PayloadLength = sizeof(message); + send.Handles = &transfer; + send.HandleCount = 1; + send.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE; + return SysSendPort(&send); +} + +// Waits for the service manager's stop notification or control-port disconnect. +static void RssrvControlLoop(SYS_HANDLE controlEndpoint) { + for (;;) { + NTL_SERVICE_CONTROL_MESSAGE message; + SYS_PORT_MESSAGE_INFO info; + memset(&message, 0, sizeof(message)); + memset(&info, 0, sizeof(info)); + info.Size = sizeof(info); + SYS_RECEIVE_PORT_REQUEST receive; + memset(&receive, 0, sizeof(receive)); + receive.Size = sizeof(receive); + receive.EndpointHandle = controlEndpoint; + receive.Payload = &message; + receive.PayloadCapacity = sizeof(message); + receive.InfoOut = &info; + receive.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE; + KSTATUS status = SysReceivePort(&receive); + if (status == NTLSTATUS_PORT_DISCONNECTED) + return; + if (!KSUCCESS(status)) + return; + if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 || + info.PayloadLength != sizeof(message) || message.Size != sizeof(message) || + message.Version != NTL_SERVICE_CONTROL_VERSION) + continue; + if (message.Operation == NTL_SERVICE_CONTROL_STOP) + return; + } +} + +// Starts RSSRV and owns the global native resource namespace until init requests shutdown. +void _start(void) { + KSTATUS status = NtlInitializeExceptionRuntime(); + if (!KSUCCESS(status)) RssrvExit(1); + + SYS_SECURITY_CONTEXT_INFO security; + memset(&security, 0, sizeof(security)); + security.Size = sizeof(security); + status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security); + if (!KSUCCESS(status) || security.EffectivePrivileges != SYS_PRIV_SYSTEM_SERVICE_REGISTER || + security.DelegablePrivileges != 0 || security.ServiceClaims != SYS_SERVICE_CLAIM_RESOURCE || + security.DelegableServiceClaims != 0) + RssrvExit(2); + + SYS_HANDLE systemVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE); + SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL); + SYS_HANDLE volumeManager = SysGetStartupCapability(SYS_STARTUP_CAP_VOLUME_MANAGER); + if (systemVolume == SYS_HANDLE_INVALID || controlListener == SYS_HANDLE_INVALID || + volumeManager == SYS_HANDLE_INVALID) + RssrvExit(3); + + status = SysCreateMutex(&gNamespaceMutex); + if (!KSUCCESS(status)) RssrvExit(3); + + // RSSRV takes ownership of its startup system-volume handle as the first namespace binding. + static const char systemRoot[] = "/SystemRoot"; + status = RssrvBindOwned(systemRoot, sizeof(systemRoot) - 1, NTL_RESOURCE_KIND_VOLUME, systemVolume, + SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY); + if (!KSUCCESS(status)) RssrvExit(4); + + SYS_HANDLE controlEndpoint = SYS_HANDLE_INVALID; + status = RssrvAcceptControl(controlListener, &controlEndpoint); + (void)SysCloseHandle(controlListener); + if (!KSUCCESS(status)) RssrvExit(5); + + status = RssrvCreateListener(&gPublicListener); + if (!KSUCCESS(status)) RssrvExit(6); + status = RssrvCreateListener(&gAdminListener); + if (!KSUCCESS(status)) RssrvExit(7); + status = RssrvStartListenerWorker(&gPublicWorker, gPublicListener, 0); + if (!KSUCCESS(status)) RssrvExit(8); + status = RssrvStartListenerWorker(&gAdminWorker, gAdminListener, 1); + if (!KSUCCESS(status)) RssrvExit(9); + + uint64_t volumeGeneration = 0; + status = RssrvSyncVolumes(volumeManager, &volumeGeneration); + if (!KSUCCESS(status)) RssrvExit(10); + status = RssrvStartVolumeWorker(volumeManager); + if (!KSUCCESS(status)) RssrvExit(11); + + status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, gPublicListener); + if (!KSUCCESS(status)) RssrvExit(12); + status = RssrvSendReady(controlEndpoint); + if (!KSUCCESS(status)) RssrvExit(13); + + RssrvControlLoop(controlEndpoint); + (void)SysUnregisterSystemService(SYS_SERVICE_RESOURCE, gPublicListener); + (void)SysDisconnectPort(controlEndpoint); + (void)SysCloseHandle(controlEndpoint); + RssrvExit(0); +} diff --git a/code/user/tests/all_tests/all_tests.c b/code/user/tests/all_tests/all_tests.c index 4c69636..6e2e9b3 100644 --- a/code/user/tests/all_tests/all_tests.c +++ b/code/user/tests/all_tests/all_tests.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #define ALL_TESTS_INITIAL_CAPACITY 32u @@ -14,7 +16,7 @@ typedef struct TEST_ENTRY { // Writes one NUL-terminated string to the console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -199,7 +201,7 @@ static bool build_child_path(const char* dir, const char* name, char* out, size_ memcpy(out, dir, dirLen); size_t pos = dirLen; - if (needSep) out[pos++] = '\\'; + if (needSep) out[pos++] = '/'; memcpy(out + pos, name, nameLen); out[total] = '\0'; return true; @@ -211,8 +213,10 @@ static uint32_t collect_tests(const char* dir, const char* selfName, TEST_ENTRY* *countOut = 0; uint32_t hDir = 0; - SYS_PATH_HANDLE_REQUEST openReq = { SysSvFromCstr(dir), &hDir }; - uint32_t status = SysOpenDir(&openReq); + uint32_t status = NtlOpenDir(SysSvFromCstr(dir), + SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN, + SYS_DIR_FLAG_NONE, + &hDir); if (status != NTLSTATUS_SUCCESS) return status; TEST_ENTRY* entries = 0; @@ -251,13 +255,28 @@ static uint32_t collect_tests(const char* dir, const char* selfName, TEST_ENTRY* // Runs one test process and returns the child's exit code through exitCodeOut. static uint32_t run_test(const char* path, int32_t* exitCodeOut) { + uint32_t imageFile = SYS_HANDLE_INVALID; + uint32_t status = NtlOpenFile(SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &imageFile); + if (status != NTLSTATUS_SUCCESS) return status; + uint32_t process = 0; - SYS_CREATE_PROCESS_REQUEST req; + NTL_CREATE_SYSTEM_PROCESS_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + req.ImageFile = imageFile; req.Path = SysSvFromCstr(path); req.CommandLine = SysSvFromCstr(path); - req.HandleOut = &process; + req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + req.ResolveModuleName = NtlResolveSystemModuleName; + req.ProcessHandleOut = &process; - uint32_t status = SysCreateProcess(&req); + status = NtlCreateSystemProcess(&req); + (void)SysCloseHandle(imageFile); if (status != NTLSTATUS_SUCCESS) return status; status = SysWait(process, SYS_WAIT_FLAG_NONE); diff --git a/code/user/tests/apc_return_smoke/apc_return_smoke.c b/code/user/tests/apc_return_smoke/apc_return_smoke.c index 6ea3432..3677b83 100644 --- a/code/user/tests/apc_return_smoke/apc_return_smoke.c +++ b/code/user/tests/apc_return_smoke/apc_return_smoke.c @@ -6,12 +6,13 @@ #include #include #include +#include #define EVENT_AUTO_RESET 0u // Prints a C string to the native console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; SysWriteConsole(&request); } @@ -37,26 +38,19 @@ static void print_u64(uint64_t value) { static uint32_t open_test_file(uint32_t* handleOut) { static const char* paths[] = { "apc_return_smoke.exe", - "/apps/apc_return_smoke.exe", - "1:/apps/apc_return_smoke.exe", - "0:/apps/apc_return_smoke.exe", + "/SystemRoot/apps/apc_return_smoke.exe", "async_apc.exe", - "/apps/async_apc.exe", - "1:/apps/async_apc.exe", - "0:/apps/async_apc.exe", - "/system/init.exe", - "0:/system/init.exe", - "1:/system/init.exe", + "/SystemRoot/apps/async_apc.exe", + "/SystemRoot/system/init.exe", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t handle = 0; - SYS_OPEN_FILE_REQUEST request = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &handle); - uint32_t status = SysOpenFile(&request); + uint32_t status = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &handle); if (status == NTLSTATUS_SUCCESS) { *handleOut = handle; return NTLSTATUS_SUCCESS; @@ -137,10 +131,11 @@ int main(void) { SYS_ASYNC_FILE_REQUEST request; memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); request.FileHandle = file; request.EventHandle = event; request.Buffer = buffer; - request.Size = 512; + request.Length = 512; request.Iosb = &iosb; request.ApcRoutine = file_apc; request.ApcContext = &state; diff --git a/code/user/tests/async_apc/async_apc.c b/code/user/tests/async_apc/async_apc.c index fd8f522..7009d6a 100644 --- a/code/user/tests/async_apc/async_apc.c +++ b/code/user/tests/async_apc/async_apc.c @@ -6,39 +6,35 @@ #include #include #include +#include #define EVENT_AUTO_RESET 0u // Prints the requested operation. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } // Opens the first available executable payload for async I/O. // Tests are usually launched from /apps, so a bare "system/init.exe" // resolves as /apps/system/init.exe and fails. Prefer the test binary itself, -// then fall back to absolute/drive-qualified paths. +// then fall back to canonical SystemRoot or other relative paths. static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) { static const char* paths[] = { "async_apc.exe", - "/apps/async_apc.exe", - "1:/apps/async_apc.exe", - "0:/apps/async_apc.exe", - "/system/init.exe", - "0:/system/init.exe", - "1:/system/init.exe", + "/SystemRoot/apps/async_apc.exe", + "/SystemRoot/system/init.exe", "system/init.exe", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *handleOut = h; if (pathOut) @@ -87,7 +83,7 @@ static void file_apc(void* context, SYS_IO_STATUS_BLOCK* iosb, uint64_t informat st->Status = iosb ? iosb->Status : 0xFFFFFFFFu; st->Information = information; st->Delivered++; - do { SYS_PRINT_DEBUG_REQUEST req = { SYS_SV_LITERAL("async_apc: user APC callback ran\n") }; SysPrintDebug(&req); } while (0); + do { SYS_PRINT_DEBUG_REQUEST req = { sizeof(SYS_PRINT_DEBUG_REQUEST), SYS_SV_LITERAL("async_apc: user APC callback ran\n") }; SysPrintDebug(&req); } while (0); } // Runs the requested operation. @@ -141,11 +137,12 @@ int main(void) { SYS_ASYNC_FILE_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.FileHandle = file; req.EventHandle = event; req.Offset = 0; req.Buffer = buffer; - req.Size = 512; + req.Length = 512; req.Iosb = &iosb; req.ApcRoutine = file_apc; req.ApcContext = &state; diff --git a/code/user/tests/async_cancel/async_cancel.c b/code/user/tests/async_cancel/async_cancel.c index bb64a51..5162ff0 100644 --- a/code/user/tests/async_cancel/async_cancel.c +++ b/code/user/tests/async_cancel/async_cancel.c @@ -5,39 +5,35 @@ #include #include #include +#include #define EVENT_AUTO_RESET 0u // Prints the requested operation. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } // Opens the first available executable payload for async I/O. // Tests are usually launched from /apps, so a bare "system/init.exe" // resolves as /apps/system/init.exe and fails. Prefer the test binary itself, -// then fall back to absolute/drive-qualified paths. +// then fall back to canonical SystemRoot or other relative paths. static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) { static const char* paths[] = { "async_cancel.exe", - "/apps/async_cancel.exe", - "1:/apps/async_cancel.exe", - "0:/apps/async_cancel.exe", - "/system/init.exe", - "0:/system/init.exe", - "1:/system/init.exe", + "/SystemRoot/apps/async_cancel.exe", + "/SystemRoot/system/init.exe", "system/init.exe", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *handleOut = h; if (pathOut) @@ -63,7 +59,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - do { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } while (0); + do { SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } while (0); } } @@ -105,11 +101,12 @@ int main(void) { SYS_ASYNC_FILE_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.FileHandle = file; req.EventHandle = event; req.Offset = 0; req.Buffer = buffer; - req.Size = 512; + req.Length = 512; req.Iosb = &iosb; req.RequestHandleOut = &request; diff --git a/code/user/tests/audio_stream_smoke/audio_stream_smoke.c b/code/user/tests/audio_stream_smoke/audio_stream_smoke.c index 7c77a15..5847376 100644 --- a/code/user/tests/audio_stream_smoke/audio_stream_smoke.c +++ b/code/user/tests/audio_stream_smoke/audio_stream_smoke.c @@ -23,18 +23,18 @@ static int16_t gAudibleFrames[TEST_AUDIBLE_FRAMES * 2u]; static uint32_t gOpenStream; typedef char TEST_AUDIO_FORMAT_SIZE[(sizeof(SYS_AUDIO_FORMAT) == 8) ? 1 : -1]; -typedef char TEST_AUDIO_ENDPOINT_INFO_SIZE[(sizeof(SYS_AUDIO_ENDPOINT_INFO) == 64) ? 1 : -1]; +typedef char TEST_AUDIO_ENDPOINT_INFO_SIZE[(sizeof(SYS_AUDIO_ENDPOINT_INFO) == 60) ? 1 : -1]; typedef char TEST_AUDIO_ENUMERATE_REQUEST_SIZE[(sizeof(SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST) == 32) ? 1 : -1]; -typedef char TEST_AUDIO_OPEN_REQUEST_SIZE[(sizeof(SYS_AUDIO_OPEN_STREAM_REQUEST) == 40) ? 1 : -1]; +typedef char TEST_AUDIO_OPEN_REQUEST_SIZE[(sizeof(SYS_AUDIO_OPEN_STREAM_REQUEST) == 32) ? 1 : -1]; typedef char TEST_AUDIO_WRITE_REQUEST_SIZE[(sizeof(SYS_AUDIO_WRITE_REQUEST) == 32) ? 1 : -1]; typedef char TEST_AUDIO_ARM_WAIT_REQUEST_SIZE[(sizeof(SYS_AUDIO_ARM_WAIT_REQUEST) == 56) ? 1 : -1]; typedef char TEST_AUDIO_STREAM_INFO_SIZE[(sizeof(SYS_AUDIO_STREAM_INFO) == 128) ? 1 : -1]; typedef char TEST_AUDIO_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_VOLUME_REQUEST) == 24) ? 1 : -1]; -typedef char TEST_AUDIO_MASTER_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_MASTER_VOLUME_REQUEST) == 24) ? 1 : -1]; +typedef char TEST_AUDIO_MASTER_VOLUME_REQUEST_SIZE[(sizeof(SYS_AUDIO_SET_MASTER_VOLUME_REQUEST) == 20) ? 1 : -1]; // Writes one NUL-terminated string to the console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } @@ -52,7 +52,7 @@ static void print_u64(uint64_t value) { } while (count) { char character = digits[--count]; - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromParts(&character, 1) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&character, 1) }; (void)SysWriteConsole(&request); } } @@ -187,7 +187,7 @@ static const SYS_AUDIO_ENDPOINT_INFO* select_endpoint(const SYS_AUDIO_ENDPOINT_I } // Queries one endpoint by its stable identifier. -static SYS_AUDIO_ENDPOINT_INFO query_endpoint(uint64_t endpointId) { +static SYS_AUDIO_ENDPOINT_INFO query_endpoint(uint32_t endpointId) { SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS]; uint32_t count = 0; SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request; @@ -238,7 +238,7 @@ static void test_master_volume(const SYS_AUDIO_ENDPOINT_INFO* endpoint) { } // Opens one stream format and checks the expected status. -static uint32_t open_stream_format(uint64_t endpointId, +static uint32_t open_stream_format(uint32_t endpointId, uint32_t sampleRate, uint16_t channelCount, uint16_t sampleFormat, @@ -263,7 +263,7 @@ static uint32_t open_stream_format(uint64_t endpointId, } // Proves the complete accepted and rejected source-format surface. -static void test_format_contract(uint64_t endpointId) { +static void test_format_contract(uint32_t endpointId) { static const uint32_t rates[] = { 11025u, 22050u, 44100u, 48000u }; for (uint32_t rateIndex = 0; rateIndex < sizeof(rates) / sizeof(rates[0]); rateIndex++) { for (uint16_t channels = 1; channels <= 2; channels++) { @@ -284,7 +284,7 @@ static void test_format_contract(uint64_t endpointId) { } // Opens the canonical test stream. -static uint32_t open_stream(uint64_t endpointId) { +static uint32_t open_stream(uint32_t endpointId) { return open_stream_format(endpointId, 48000u, 2, @@ -357,7 +357,7 @@ static void wait_stream(uint32_t stream, const char* operation) { } // Proves that closing the final handle retires an active stream with queued periods. -static void test_active_close_rundown(uint64_t endpointId) { +static void test_active_close_rundown(uint32_t endpointId) { for (uint32_t iteration = 0; iteration < TEST_CLOSE_RUNDOWN_ITERATIONS; iteration++) { diff --git a/code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c b/code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c deleted file mode 100644 index 6fccb5f..0000000 --- a/code/user/tests/cwd_peb_smoke/cwd_peb_smoke.c +++ /dev/null @@ -1,147 +0,0 @@ -// Current-directory PEB cache smoke test. -// Verifies that SysSetCwd updates the user-visible PEB cache without a getter syscall. - -#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 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 test process. -static void exit_now(int code) { - while (true) SysExitProcess(code); -} - -// Fails the test with one status value. -static void fail_status(const char* what, uint32_t status) { - print("cwd_peb_smoke: "); - print(what); - print(" status="); - print_u64(status); - print("\n"); - exit_now(1); -} - -// Fails the test with a simple message. -static void fail(const char* what) { - print("cwd_peb_smoke: "); - print(what); - print("\n"); - exit_now(1); -} - -// Copies a string view into a NUL-terminated buffer. -static int copy_view(char* dst, size_t cap, SYS_STRING_VIEW view) { - if (!dst || cap == 0 || !view.Data || view.Length + 1 > cap) - return 0; - memcpy(dst, view.Data, view.Length); - dst[view.Length] = '\0'; - return 1; -} - -// Computes the parent of one canonical native current-directory string. -static int parent_of_cwd(const char* cwd, char* out, size_t cap) { - if (!cwd || !out || cap == 0) - return 0; - - size_t len = strlen(cwd); - if (len < 3 || cwd[1] != ':' || (cwd[2] != '/' && cwd[2] != '\\')) - return 0; - if (len + 1 > cap) - return 0; - - memcpy(out, cwd, len + 1); - while (len > 3 && (out[len - 1] == '/' || out[len - 1] == '\\')) - out[--len] = '\0'; - if (len <= 3) { - out[0] = cwd[0]; - out[1] = ':'; - out[2] = '/'; - out[3] = '\0'; - return 1; - } - - size_t pos = len; - while (pos > 3 && out[pos - 1] != '/' && out[pos - 1] != '\\') - pos--; - if (pos <= 3) { - out[0] = cwd[0]; - out[1] = ':'; - out[2] = '/'; - out[3] = '\0'; - } else { - out[pos - 1] = '\0'; - } - return 1; -} - -// Sets the current directory to one native path. -static void set_cwd_or_fail(const char* path, const char* what) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - uint32_t status = SysSetCwd(&req); - if (status != NTLSTATUS_SUCCESS) fail_status(what, status); -} - -// Verifies the current-directory PEB view against one native path. -static void expect_cwd(const char* expected) { - SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView(); - size_t expectedLen = strlen(expected); - if (cwd.Length != expectedLen) fail("cwd length mismatch"); - if (cwd.Length && !cwd.Data) fail("cwd data missing"); - if (cwd.Length && memcmp(cwd.Data, expected, cwd.Length) != 0) fail("cwd value mismatch"); -} - -// Program entry point. -int main(void) { - char original[SYS_CWD_MAX]; - char parent[SYS_CWD_MAX]; - - print("cwd_peb_smoke: start\n"); - - SYS_PEB* peb = SysGetPeb(); - if (!peb) fail("peb missing"); - if (peb->Magic != SYS_PEB_MAGIC) fail("peb magic mismatch"); - if (peb->Version != SYS_ENV_VERSION) fail("peb version mismatch"); - - if (!copy_view(original, sizeof(original), SysGetCurrentDirectoryView())) - fail("initial cwd missing"); - if (!parent_of_cwd(original, parent, sizeof(parent))) - fail("initial cwd malformed"); - - set_cwd_or_fail(".", "set dot cwd"); - expect_cwd(original); - - set_cwd_or_fail("..", "set parent cwd"); - expect_cwd(parent); - - set_cwd_or_fail(original, "restore original cwd"); - expect_cwd(original); - - print("cwd_peb_smoke: PASS\n"); - exit_now(0); - return 0; -} diff --git a/code/user/tests/cwd_userspace_smoke/cwd_userspace_smoke.c b/code/user/tests/cwd_userspace_smoke/cwd_userspace_smoke.c new file mode 100644 index 0000000..12df6ad --- /dev/null +++ b/code/user/tests/cwd_userspace_smoke/cwd_userspace_smoke.c @@ -0,0 +1,117 @@ +// Userspace current-directory smoke test. +// Verifies CWD inheritance and parent traversal clamped at the owning RSSRV resource binding. + +#include +#include +#include +#include +#include +#include + +static void print(const char* s) { + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(req), SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +static void exit_now(int code) { + while (true) SysExitProcess(code); +} + +static void fail_status(const char* what, uint32_t status) { + (void)status; + print("cwd_userspace_smoke: "); print(what); print("\n"); + exit_now(1); +} + +static void fail(const char* what) { + print("cwd_userspace_smoke: "); print(what); print("\n"); + exit_now(1); +} + +static void get_cwd_or_fail(char out[SYS_PATH_MAX + 1u]) { + size_t required = 0; + uint32_t status = NtlGetCurrentDirectory(out, SYS_PATH_MAX + 1u, &required); + if (status != NTLSTATUS_SUCCESS || required == 0 || required > SYS_PATH_MAX + 1u) + fail_status("get cwd", status); +} + +static void set_cwd_or_fail(const char* path, const char* what) { + uint32_t status = NtlSetCurrentDirectory(SysSvFromCstr(path)); + if (status != NTLSTATUS_SUCCESS) fail_status(what, status); +} + +static void expect_cwd(const char* expected) { + char current[SYS_PATH_MAX + 1u]; + get_cwd_or_fail(current); + if (strcmp(current, expected) != 0) fail("cwd value mismatch"); +} + +// Resolves the current absolute resource path and returns the owning namespace-prefix length. +static size_t resource_root_length_or_fail(const char* cwd) { + SYS_HANDLE session = SYS_HANDLE_INVALID; + uint32_t status = NtlResourceConnect(&session); + if (status != NTLSTATUS_SUCCESS) fail_status("resource connect", status); + + SYS_HANDLE root = SYS_HANDLE_INVALID; + uint32_t kind = NTL_RESOURCE_KIND_INVALID; + uint32_t resolvedLength = 0; + status = NtlResourceResolve(session, SysSvFromCstr(cwd), NTL_RESOURCE_FLAG_NONE, + &root, &kind, &resolvedLength); + (void)NtlResourceClose(session); + if (root != SYS_HANDLE_INVALID) (void)SysCloseHandle(root); + if (status != NTLSTATUS_SUCCESS) fail_status("resolve cwd root", status); + + size_t length = strlen(cwd); + if (kind == NTL_RESOURCE_KIND_INVALID || resolvedLength < 2u || resolvedLength > length || + (resolvedLength != length && cwd[resolvedLength] != '/')) + fail("initial cwd malformed"); + return resolvedLength; +} + +static void parent_of_cwd(const char* cwd, size_t rootLength, char* out, size_t cap) { + size_t length = strlen(cwd); + if (rootLength < 2u || rootLength > length || length + 1u > cap || + (rootLength != length && cwd[rootLength] != '/')) + fail("initial cwd malformed"); + + memcpy(out, cwd, length + 1u); + if (length == rootLength) return; + while (length > rootLength && out[length - 1u] == '/') out[--length] = '\0'; + while (length > rootLength && out[length - 1u] != '/') length--; + if (length <= rootLength + 1u) length = rootLength; + else length--; + out[length] = '\0'; +} + +int main(void) { + char original[SYS_PATH_MAX + 1u]; + char parent[SYS_PATH_MAX + 1u]; + char root[SYS_PATH_MAX + 1u]; + + print("cwd_userspace_smoke: start\n"); + get_cwd_or_fail(original); + size_t rootLength = resource_root_length_or_fail(original); + parent_of_cwd(original, rootLength, parent, sizeof(parent)); + memcpy(root, original, rootLength); + root[rootLength] = '\0'; + + // Refresh the cached binding boundary without changing the path. + set_cwd_or_fail(".", "set dot cwd"); + expect_cwd(original); + + set_cwd_or_fail("..", "set parent cwd"); + expect_cwd(parent); + + // Relative traversal stops at the actual bound resource, including multi-component roots. + set_cwd_or_fail(root, "set resource root cwd"); + expect_cwd(root); + set_cwd_or_fail("..", "clamp parent at resource root"); + expect_cwd(root); + + set_cwd_or_fail(original, "restore original cwd"); + expect_cwd(original); + + print("cwd_userspace_smoke: PASS\n"); + exit_now(0); + return 0; +} diff --git a/code/user/tests/dirty_writeback/dirty_writeback.c b/code/user/tests/dirty_writeback/dirty_writeback.c index bab15a1..188fd3c 100644 --- a/code/user/tests/dirty_writeback/dirty_writeback.c +++ b/code/user/tests/dirty_writeback/dirty_writeback.c @@ -5,6 +5,7 @@ #include #include #include +#include #define PAGE_SIZE_BYTES 4096u #define TEST_SIZE (PAGE_SIZE_BYTES * 4u) @@ -16,7 +17,7 @@ static uint8_t gPatternB[TEST_SIZE]; static uint8_t gReadback[TEST_SIZE]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -64,8 +65,7 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) { // Deletes one test path. static uint32_t delete_path(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - return SysDeleteFile(&req); + return NtlDeleteFile(SysSvFromCstr(path)); } static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { @@ -74,20 +74,16 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { // 8.3 form so the test measures writeback rather than the host bridge. static const char* paths[] = { "dwrite.tmp", - "/dwrite.tmp", - "1:/dwrite.tmp", - "0:/dwrite.tmp", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; (void)delete_path(paths[i]); - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, - SYS_FILE_CREATE_NEW, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; @@ -100,12 +96,11 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { static uint32_t open_path(const char* path, uint32_t* fileOut) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) *fileOut = h; return s; diff --git a/code/user/tests/exception_dispatcher_smoke/exception_dispatcher_smoke.c b/code/user/tests/exception_dispatcher_smoke/exception_dispatcher_smoke.c index f640acd..114f410 100644 --- a/code/user/tests/exception_dispatcher_smoke/exception_dispatcher_smoke.c +++ b/code/user/tests/exception_dispatcher_smoke/exception_dispatcher_smoke.c @@ -8,7 +8,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -26,7 +26,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/exception_smoke/exception_smoke.c b/code/user/tests/exception_smoke/exception_smoke.c index 93987b3..ac04455 100644 --- a/code/user/tests/exception_smoke/exception_smoke.c +++ b/code/user/tests/exception_smoke/exception_smoke.c @@ -6,6 +6,7 @@ #include #include #include +#include #define CHILD_DIVIDE_BY_ZERO "child-divide-by-zero" #define CHILD_ACCESS_VIOLATION "child-access-violation" @@ -15,7 +16,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -33,7 +34,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -96,12 +97,16 @@ static void run_child_expect_exit(const char* mode, uint32_t expectedExit) { fail("missing image path"); uint32_t child = 0; - SYS_CREATE_PROCESS_REQUEST request = { - peb->ImagePath, - SysSvFromCstr(mode), - &child, - }; - uint32_t status = SysCreateProcess(&request); + NTL_CREATE_SYSTEM_PROCESS_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + request.Path = peb->ImagePath; + request.CommandLine = SysSvFromCstr(mode); + request.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + request.ResolveModuleName = NtlResolveSystemModuleName; + request.ProcessHandleOut = &child; + uint32_t status = NtlCreateSystemProcess(&request); if (status != NTLSTATUS_SUCCESS) fail_status("create child", status); diff --git a/code/user/tests/file_lock_smoke/file_lock_smoke.c b/code/user/tests/file_lock_smoke/file_lock_smoke.c index 54d5110..35d8785 100644 --- a/code/user/tests/file_lock_smoke/file_lock_smoke.c +++ b/code/user/tests/file_lock_smoke/file_lock_smoke.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #define LOCK_OFFSET 0u #define LOCK_LENGTH 128u @@ -14,7 +16,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -32,7 +34,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -62,19 +64,17 @@ static void fail(const char* what) { // Deletes one path if it already exists. static void delete_path(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - (void)SysDeleteFile(&req); + (void)NtlDeleteFile(SysSvFromCstr(path)); } // Opens one path with lock rights. static uint32_t open_existing_lockable(const char* path, uint32_t* fileOut) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), - SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(path), + SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) *fileOut = h; return s; @@ -84,20 +84,16 @@ static uint32_t open_existing_lockable(const char* path, uint32_t* fileOut) { static uint32_t create_temp_file(uint32_t* fileOut, const char** pathOut) { static const char* paths[] = { "file_lock_smoke.tmp", - "/file_lock_smoke.tmp", - "1:/file_lock_smoke.tmp", - "0:/file_lock_smoke.tmp", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { uint32_t h = 0; delete_path(paths[i]); - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK, - SYS_FILE_CREATE_NEW, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *fileOut = h; *pathOut = paths[i]; @@ -245,12 +241,16 @@ static void run_child_expect_success(const char* mode, const char* path) { build_child_command(command, sizeof(command), mode, path); uint32_t child = 0; - SYS_CREATE_PROCESS_REQUEST req = { - peb->ImagePath, - SysSvFromCstr(command), - &child, - }; - uint32_t s = SysCreateProcess(&req); + NTL_CREATE_SYSTEM_PROCESS_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + req.Path = peb->ImagePath; + req.CommandLine = SysSvFromCstr(command); + req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + req.ResolveModuleName = NtlResolveSystemModuleName; + req.ProcessHandleOut = &child; + uint32_t s = NtlCreateSystemProcess(&req); if (s != NTLSTATUS_SUCCESS) fail_status("create child", s); s = SysWait(child, SYS_WAIT_FLAG_NONE); @@ -348,8 +348,16 @@ int main(void) { char command[CHILD_PREFIX_MAX + CHILD_PATH_MAX]; build_child_command(command, sizeof(command), "excl-wait", path); uint32_t child = 0; - SYS_CREATE_PROCESS_REQUEST createReq = { selfPeb->ImagePath, SysSvFromCstr(command), &child }; - s = SysCreateProcess(&createReq); + NTL_CREATE_SYSTEM_PROCESS_REQUEST createReq; + memset(&createReq, 0, sizeof(createReq)); + createReq.Size = sizeof(createReq); + createReq.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + createReq.Path = selfPeb->ImagePath; + createReq.CommandLine = SysSvFromCstr(command); + createReq.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + createReq.ResolveModuleName = NtlResolveSystemModuleName; + createReq.ProcessHandleOut = &child; + s = NtlCreateSystemProcess(&createReq); if (s != NTLSTATUS_SUCCESS) fail_status("create blocking child", s); SysSleep(30); s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH); diff --git a/code/user/tests/file_stat_smoke/file_stat_smoke.c b/code/user/tests/file_stat_smoke/file_stat_smoke.c index 32a6eee..4d5391c 100644 --- a/code/user/tests/file_stat_smoke/file_stat_smoke.c +++ b/code/user/tests/file_stat_smoke/file_stat_smoke.c @@ -5,13 +5,14 @@ #include #include #include +#include static char gSelfPath[SYS_PATH_MAX]; static char gSelfDir[SYS_PATH_MAX]; // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -29,7 +30,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -105,12 +106,11 @@ static uint32_t open_self_image(uint32_t* fileOut, uint64_t* sizeOut) { build_parent_dir(gSelfDir, sizeof(gSelfDir), gSelfPath); uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(gSelfPath), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(gSelfPath), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s != NTLSTATUS_SUCCESS) return s; @@ -178,8 +178,7 @@ static void test_file_stat(uint32_t file, uint64_t expectedSize) { SYS_FILE_STAT byPath; memset(&byPath, 0x5A, sizeof(byPath)); - SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(gSelfPath), &byPath }; - s = SysStatFile(&statReq); + s = NtlStatPath(SysSvFromCstr(gSelfPath), &byPath); if (s != NTLSTATUS_SUCCESS) fail_status("path stat", s); check_regular_stat(&byPath, expectedSize, "path stat"); @@ -193,8 +192,7 @@ static void test_file_stat(uint32_t file, uint64_t expectedSize) { static void test_directory_stat(void) { SYS_FILE_STAT stat; memset(&stat, 0xCC, sizeof(stat)); - SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(gSelfDir), &stat }; - uint32_t s = SysStatFile(&statReq); + uint32_t s = NtlStatPath(SysSvFromCstr(gSelfDir), &stat); if (s != NTLSTATUS_SUCCESS) fail_status("directory stat", s); check_directory_stat(&stat); } @@ -202,15 +200,13 @@ static void test_directory_stat(void) { // Checks failure status paths that stat callers depend on. static void test_failure_paths(void) { SYS_FILE_STAT stat; - SYS_STAT_FILE_REQUEST missingReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), &stat }; - uint32_t s = SysStatFile(&missingReq); + uint32_t s = NtlStatPath(SYS_SV_LITERAL("file_stat_smoke_missing.nope"), &stat); if (s != NTLSTATUS_NOT_FOUND) fail_status("missing path status", s); s = SysGetFileStat(0xFFFFFFFFu, &stat); if (s != NTLSTATUS_INVALID_HANDLE) fail_status("invalid handle status", s); - SYS_STAT_FILE_REQUEST badReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), 0 }; - s = SysStatFile(&badReq); + s = NtlStatPath(SYS_SV_LITERAL("file_stat_smoke_missing.nope"), (SYS_FILE_STAT*)0); if (s != NTLSTATUS_INVALID_PARAMETER) fail_status("null stat output status", s); } diff --git a/code/user/tests/handle_dup_smoke/handle_dup_smoke.c b/code/user/tests/handle_dup_smoke/handle_dup_smoke.c index 1d95351..98aa3ff 100644 --- a/code/user/tests/handle_dup_smoke/handle_dup_smoke.c +++ b/code/user/tests/handle_dup_smoke/handle_dup_smoke.c @@ -1,10 +1,14 @@ -// Native handle duplication smoke test. -// Verifies explicit rights reduction, same-rights duplication, and invalid duplicate requests. +// Native capability-handle smoke test. +// Verifies monotonic identities, typed rights, derivation, aliasing, and revocation. #include #include #include +#define EVENT_AUTO_RESET 0u +#define HANDLE_STRESS_COUNT 512u +#define DERIVATION_SUCCESS_COUNT 15u + typedef struct TEST_QUEUE_MESSAGE { uint32_t Type; uint32_t Flags; @@ -17,7 +21,7 @@ typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -35,7 +39,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -54,7 +58,7 @@ static void fail(const char* msg) { } // Fails the test with one status value. -static void fail_status(const char* what, uint32_t status) { +static void fail_status(const char* what, KSTATUS status) { print("handle_dup_smoke: "); print(what); print(" status="); @@ -64,7 +68,7 @@ static void fail_status(const char* what, uint32_t status) { } // Requires one exact status value. -static void expect_status(const char* what, uint32_t got, uint32_t expected) { +static void expect_status(const char* what, KSTATUS got, KSTATUS expected) { if (got == expected) return; print("handle_dup_smoke: "); print(what); @@ -77,8 +81,13 @@ static void expect_status(const char* what, uint32_t got, uint32_t expected) { } // Duplicates one handle into the current process. -static uint32_t duplicate_handle(uint32_t source, uint32_t process, uint32_t desiredRights, uint32_t flags, uint32_t* out) { +static KSTATUS duplicate_handle(SYS_HANDLE source, + SYS_HANDLE process, + SYS_RIGHTS desiredRights, + uint32_t flags, + SYS_HANDLE* out) { SYS_DUPLICATE_HANDLE_REQUEST req = { + sizeof(SYS_DUPLICATE_HANDLE_REQUEST), source, process, desiredRights, @@ -88,8 +97,19 @@ static uint32_t duplicate_handle(uint32_t source, uint32_t process, uint32_t des return SysDuplicateHandle(&req); } +// Derives one child capability in the current process. +static KSTATUS derive_handle(SYS_HANDLE source, SYS_RIGHTS desiredRights, SYS_HANDLE* out) { + SYS_DERIVE_HANDLE_REQUEST req = { + sizeof(SYS_DERIVE_HANDLE_REQUEST), + source, + desiredRights, + out, + }; + return SysDeriveHandle(&req); +} + // Sends one queue message. -static uint32_t send_message(uint32_t queue, uint32_t type, uint64_t value) { +static KSTATUS send_message(SYS_HANDLE queue, uint32_t type, uint64_t value) { TEST_QUEUE_MESSAGE msg = {0}; msg.Type = type; msg.Param[0] = value; @@ -104,7 +124,7 @@ static uint32_t send_message(uint32_t queue, uint32_t type, uint64_t value) { } // Receives one queue message. -static uint32_t recv_message(uint32_t queue, TEST_QUEUE_MESSAGE* msg) { +static KSTATUS recv_message(SYS_HANDLE queue, TEST_QUEUE_MESSAGE* msg) { SYS_QUEUE_RECV_REQUEST req = { sizeof(req), queue, @@ -120,84 +140,244 @@ static void check_message(const TEST_QUEUE_MESSAGE* msg, uint32_t type, uint64_t if (msg->Type != type || msg->Param[0] != value) fail("message payload mismatch"); } -// Runs the native handle duplication smoke test. -int main(void) { - print("handle_dup_smoke: start\n"); +// Closes one handle and requires success. +static void close_handle(const char* what, SYS_HANDLE handle) { + KSTATUS status = SysCloseHandle(handle); + if (!KSUCCESS(status)) fail_status(what, status); +} - uint32_t currentPid = SysGetCurrentPid(); - uint32_t process = 0; - uint32_t s = SysOpenProcess(currentPid, &process); - if (s != NTLSTATUS_SUCCESS) fail_status("open current process", s); +// Verifies that closed identities are not reused and stale values stay invalid. +static void test_monotonic_identity(void) { + SYS_HANDLE first = SYS_HANDLE_INVALID; + KSTATUS status = SysCreateEvent(EVENT_AUTO_RESET, 0, &first); + if (!KSUCCESS(status)) fail_status("create first monotonic event", status); + close_handle("close first monotonic event", first); - uint32_t queue = 0; - s = SysCreateQueue(&queue); - if (s != NTLSTATUS_SUCCESS) fail_status("create queue", s); + SYS_HANDLE second = SYS_HANDLE_INVALID; + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &second); + if (!KSUCCESS(status)) fail_status("create second monotonic event", status); + if (second <= first) fail("handle identity was reused or moved backwards"); - uint32_t readOnly = 0; - s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_NONE, &readOnly); - if (s != NTLSTATUS_SUCCESS) fail_status("duplicate read-only", s); + expect_status("close stale handle", SysCloseHandle(first), NTLSTATUS_INVALID_HANDLE); + close_handle("close second monotonic event", second); +} - uint32_t writeOnly = 0; - s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_WRITE, SYS_DUPLICATE_HANDLE_FLAG_NONE, &writeOnly); - if (s != NTLSTATUS_SUCCESS) fail_status("duplicate write-only", s); +// Verifies that live-handle capacity is no longer tied to the old 256-slot table. +static void test_dynamic_handle_capacity(void) { + SYS_HANDLE handles[HANDLE_STRESS_COUNT]; + SYS_HANDLE previous = SYS_HANDLE_INVALID; - uint32_t sameRights = 0; - s = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &sameRights); - if (s != NTLSTATUS_SUCCESS) fail_status("duplicate same rights", s); + for (uint32_t i = 0; i < HANDLE_STRESS_COUNT; ++i) { + handles[i] = SYS_HANDLE_INVALID; + KSTATUS status = SysCreateEvent(EVENT_AUTO_RESET, 0, &handles[i]); + if (!KSUCCESS(status)) fail_status("create stress event", status); + if (previous != SYS_HANDLE_INVALID && handles[i] <= previous) + fail("stress handles were not strictly monotonic"); + previous = handles[i]; + } - s = send_message(readOnly, 10, 100); - if (s == NTLSTATUS_SUCCESS) fail("read-only duplicate allowed send"); + for (uint32_t i = HANDLE_STRESS_COUNT; i != 0; --i) + close_handle("close stress event", handles[i - 1]); +} + +// Verifies explicit rights attenuation on duplicate aliases. +static void test_duplicate_rights(SYS_HANDLE process, SYS_HANDLE queue, + SYS_HANDLE* sameRightsOut, + SYS_HANDLE* receiveOnlyOut, + SYS_HANDLE* sendOnlyOut) { + SYS_HANDLE receiveOnly = SYS_HANDLE_INVALID; + KSTATUS status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE, + SYS_DUPLICATE_HANDLE_FLAG_NONE, &receiveOnly); + if (!KSUCCESS(status)) fail_status("duplicate receive-only", status); + + SYS_HANDLE sendOnly = SYS_HANDLE_INVALID; + status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_SEND, + SYS_DUPLICATE_HANDLE_FLAG_NONE, &sendOnly); + if (!KSUCCESS(status)) fail_status("duplicate send-only", status); + + SYS_HANDLE sameRights = SYS_HANDLE_INVALID; + status = duplicate_handle(queue, process, 0, + SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &sameRights); + if (!KSUCCESS(status)) fail_status("duplicate same rights", status); + + status = send_message(receiveOnly, 10, 100); + expect_status("receive-only send denied", status, NTLSTATUS_ACCESS_DENIED); TEST_QUEUE_MESSAGE msg; - s = recv_message(writeOnly, &msg); - if (s == NTLSTATUS_SUCCESS) fail("write-only duplicate allowed receive"); + status = recv_message(sendOnly, &msg); + expect_status("send-only receive denied", status, NTLSTATUS_ACCESS_DENIED); - s = send_message(queue, 11, 101); - if (s != NTLSTATUS_SUCCESS) fail_status("send original", s); - s = recv_message(readOnly, &msg); - if (s != NTLSTATUS_SUCCESS) fail_status("receive read-only", s); + status = send_message(queue, 11, 101); + if (!KSUCCESS(status)) fail_status("send original", status); + status = recv_message(receiveOnly, &msg); + if (!KSUCCESS(status)) fail_status("receive receive-only", status); check_message(&msg, 11, 101); - s = send_message(writeOnly, 12, 102); - if (s != NTLSTATUS_SUCCESS) fail_status("send write-only", s); - s = recv_message(queue, &msg); - if (s != NTLSTATUS_SUCCESS) fail_status("receive original", s); + status = send_message(sendOnly, 12, 102); + if (!KSUCCESS(status)) fail_status("send send-only", status); + status = recv_message(queue, &msg); + if (!KSUCCESS(status)) fail_status("receive original", status); check_message(&msg, 12, 102); - s = send_message(sameRights, 13, 103); - if (s != NTLSTATUS_SUCCESS) fail_status("send same-rights", s); - s = recv_message(sameRights, &msg); - if (s != NTLSTATUS_SUCCESS) fail_status("receive same-rights", s); + status = send_message(sameRights, 13, 103); + if (!KSUCCESS(status)) fail_status("send same-rights", status); + status = recv_message(sameRights, &msg); + if (!KSUCCESS(status)) fail_status("receive same-rights", status); check_message(&msg, 13, 103); - uint32_t ignored = 0; - s = duplicate_handle(readOnly, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); - expect_status("duplicate non-duplicable handle", s, NTLSTATUS_ACCESS_DENIED); + SYS_HANDLE ignored = SYS_HANDLE_INVALID; + status = duplicate_handle(receiveOnly, process, SYS_QUEUE_RIGHT_RECEIVE, + SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); + expect_status("duplicate non-duplicable handle", status, NTLSTATUS_ACCESS_DENIED); - s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ | 0x80000000u, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); - expect_status("duplicate unknown rights", s, NTLSTATUS_INVALID_PARAMETER); + status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE | 0x80000000u, + SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); + expect_status("duplicate unknown rights", status, NTLSTATUS_INVALID_PARAMETER); - s = duplicate_handle(queue, process, SYS_HANDLE_RIGHT_READ, SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored); - expect_status("duplicate same-rights with desired rights", s, NTLSTATUS_INVALID_PARAMETER); + status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE, + SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored); + expect_status("duplicate same-rights with desired rights", status, NTLSTATUS_INVALID_PARAMETER); - s = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); - expect_status("duplicate zero rights", s, NTLSTATUS_INVALID_PARAMETER); + status = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored); + expect_status("duplicate zero rights", status, NTLSTATUS_INVALID_PARAMETER); SYS_DUPLICATE_HANDLE_REQUEST badOut = { + sizeof(SYS_DUPLICATE_HANDLE_REQUEST), queue, process, - SYS_HANDLE_RIGHT_READ, + SYS_QUEUE_RIGHT_RECEIVE, SYS_DUPLICATE_HANDLE_FLAG_NONE, 0, }; - s = SysDuplicateHandle(&badOut); - expect_status("duplicate null output", s, NTLSTATUS_INVALID_PARAMETER); + status = SysDuplicateHandle(&badOut); + expect_status("duplicate null output", status, NTLSTATUS_INVALID_PARAMETER); - SysCloseHandle(sameRights); - SysCloseHandle(writeOnly); - SysCloseHandle(readOnly); - SysCloseHandle(queue); - SysCloseHandle(process); + *sameRightsOut = sameRights; + *receiveOnlyOut = receiveOnly; + *sendOnlyOut = sendOnly; +} + +// Verifies child grants attenuate authority and can be revoked independently. +static void test_derived_revocation(SYS_HANDLE process, SYS_HANDLE queue) { + const SYS_RIGHTS childRights = SYS_QUEUE_RIGHT_RECEIVE | + SYS_RIGHT_DUPLICATE | + SYS_RIGHT_DERIVE | + SYS_RIGHT_REVOKE; + + SYS_HANDLE child = SYS_HANDLE_INVALID; + KSTATUS status = derive_handle(queue, childRights, &child); + if (!KSUCCESS(status)) fail_status("derive child", status); + + SYS_HANDLE childAlias = SYS_HANDLE_INVALID; + status = duplicate_handle(child, process, 0, + SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &childAlias); + if (!KSUCCESS(status)) fail_status("duplicate child alias", status); + + SYS_HANDLE grandchild = SYS_HANDLE_INVALID; + status = derive_handle(child, SYS_QUEUE_RIGHT_RECEIVE, &grandchild); + if (!KSUCCESS(status)) fail_status("derive grandchild", status); + + SYS_HANDLE ignored = SYS_HANDLE_INVALID; + status = derive_handle(child, SYS_QUEUE_RIGHT_SEND, &ignored); + expect_status("derive cannot amplify rights", status, NTLSTATUS_ACCESS_DENIED); + + status = SysRevokeHandle(grandchild); + expect_status("revoke without revoke right", status, NTLSTATUS_ACCESS_DENIED); + + TEST_QUEUE_MESSAGE msg; + status = send_message(queue, 20, 200); + if (!KSUCCESS(status)) fail_status("send for grandchild", status); + status = recv_message(grandchild, &msg); + if (!KSUCCESS(status)) fail_status("receive grandchild", status); + check_message(&msg, 20, 200); + + status = SysRevokeHandle(child); + if (!KSUCCESS(status)) fail_status("revoke child", status); + + ignored = SYS_HANDLE_INVALID; + status = duplicate_handle(childAlias, process, 0, + SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored); + expect_status("duplicate revoked alias", status, NTLSTATUS_CAPABILITY_REVOKED); + + status = derive_handle(childAlias, SYS_QUEUE_RIGHT_RECEIVE, &ignored); + expect_status("derive revoked alias", status, NTLSTATUS_CAPABILITY_REVOKED); + + status = recv_message(grandchild, &msg); + expect_status("revoked grandchild receive", status, NTLSTATUS_CAPABILITY_REVOKED); + + // Revoking a derived grant must not revoke its parent grant. + status = send_message(queue, 21, 201); + if (!KSUCCESS(status)) fail_status("parent send after child revoke", status); + status = recv_message(queue, &msg); + if (!KSUCCESS(status)) fail_status("parent receive after child revoke", status); + check_message(&msg, 21, 201); + + close_handle("close grandchild", grandchild); + close_handle("close child alias", childAlias); + close_handle("close child", child); +} + +// Verifies the bounded derivation chain used for revocation traversal. +static void test_derivation_depth(SYS_HANDLE queue) { + const SYS_RIGHTS rights = SYS_QUEUE_RIGHT_RECEIVE | + SYS_RIGHT_DERIVE | + SYS_RIGHT_REVOKE; + SYS_HANDLE chain[DERIVATION_SUCCESS_COUNT]; + SYS_HANDLE parent = queue; + + for (uint32_t i = 0; i < DERIVATION_SUCCESS_COUNT; ++i) { + chain[i] = SYS_HANDLE_INVALID; + KSTATUS status = derive_handle(parent, rights, &chain[i]); + if (!KSUCCESS(status)) fail_status("derive bounded chain", status); + parent = chain[i]; + } + + SYS_HANDLE tooDeep = SYS_HANDLE_INVALID; + KSTATUS status = derive_handle(parent, rights, &tooDeep); + expect_status("derive beyond depth bound", status, NTLSTATUS_LIMIT_REACHED); + + for (uint32_t i = DERIVATION_SUCCESS_COUNT; i != 0; --i) + close_handle("close bounded derivation", chain[i - 1]); +} + +// Runs the native capability-handle smoke test. +int main(void) { + print("handle_dup_smoke: start\n"); + + test_monotonic_identity(); + test_dynamic_handle_capacity(); + + uint32_t currentPid = SysGetCurrentPid(); + SYS_HANDLE process = SYS_HANDLE_INVALID; + KSTATUS status = SysOpenProcess(currentPid, SYS_PROCESS_RIGHT_INSERT_HANDLE | SYS_RIGHT_QUERY, &process); + if (!KSUCCESS(status)) fail_status("open current process", status); + + SYS_HANDLE queue = SYS_HANDLE_INVALID; + status = SysCreateQueue(&queue); + if (!KSUCCESS(status)) fail_status("create queue", status); + + SYS_HANDLE sameRights = SYS_HANDLE_INVALID; + SYS_HANDLE receiveOnly = SYS_HANDLE_INVALID; + SYS_HANDLE sendOnly = SYS_HANDLE_INVALID; + test_duplicate_rights(process, queue, &sameRights, &receiveOnly, &sendOnly); + test_derived_revocation(process, queue); + test_derivation_depth(queue); + + // A duplicate is an alias of the same grant, so revocation through either + // alias revokes the original grant as well. + status = SysRevokeHandle(sameRights); + if (!KSUCCESS(status)) fail_status("revoke duplicate alias", status); + + SYS_HANDLE ignored = SYS_HANDLE_INVALID; + status = duplicate_handle(queue, process, 0, + SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored); + expect_status("duplicate root after alias revoke", status, NTLSTATUS_CAPABILITY_REVOKED); + + close_handle("close same-rights", sameRights); + close_handle("close send-only", sendOnly); + close_handle("close receive-only", receiveOnly); + close_handle("close revoked queue", queue); + close_handle("close process", process); print("handle_dup_smoke: PASS\n"); exit_now(0); diff --git a/code/user/tests/input_api_smoke/input_api_smoke.c b/code/user/tests/input_api_smoke/input_api_smoke.c index 9b9fc2e..374bf4f 100644 --- a/code/user/tests/input_api_smoke/input_api_smoke.c +++ b/code/user/tests/input_api_smoke/input_api_smoke.c @@ -7,7 +7,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -25,7 +25,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/input_model_smoke/input_model_smoke.c b/code/user/tests/input_model_smoke/input_model_smoke.c index 8f19d80..ec46fb2 100644 --- a/code/user/tests/input_model_smoke/input_model_smoke.c +++ b/code/user/tests/input_model_smoke/input_model_smoke.c @@ -6,7 +6,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -24,7 +24,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/mmap_smoke/mmap_smoke.c b/code/user/tests/mmap_smoke/mmap_smoke.c index 8830bbb..03c6956 100644 --- a/code/user/tests/mmap_smoke/mmap_smoke.c +++ b/code/user/tests/mmap_smoke/mmap_smoke.c @@ -4,13 +4,14 @@ #include #include #include +#include #define TEST_MAP_SIZE 8192u static uint8_t gExpected[TEST_MAP_SIZE]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -27,7 +28,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -58,24 +59,19 @@ static void fail_msg(const char* msg) { static uint32_t open_first_existing(uint32_t* fileOut, const char** pathOut) { static const char* candidates[] = { "mmap_smoke.exe", - "/apps/mmap_smoke.exe", - "0:/apps/mmap_smoke.exe", - "1:/apps/mmap_smoke.exe", + "/SystemRoot/apps/mmap_smoke.exe", - "/system/init.exe", - "0:/system/init.exe", - "1:/system/init.exe", + "/SystemRoot/system/init.exe", "system/init.exe", }; for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) { uint32_t file = 0; - SYS_OPEN_FILE_REQUEST openReq = SysMakeOpenFileRequest(SysSvFromCstr(candidates[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &file); - uint32_t s = SysOpenFile(&openReq); + uint32_t s = NtlOpenFile(SysSvFromCstr(candidates[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &file); if (s == NTLSTATUS_SUCCESS) { *fileOut = file; if (pathOut) @@ -113,11 +109,11 @@ int main(void) { print("mmap_smoke: baseline read OK\n"); uint32_t mapping = 0; - s = SysCreateFileMapping(file, - SYS_VM_READ, - SYS_MAP_FLAG_NONE, - sizeof(gExpected), - &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ, + SYS_MAP_FLAG_NONE, sizeof(gExpected), &mapping + }; + s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) fail_status("create file mapping", s); @@ -131,12 +127,11 @@ int main(void) { print("mmap_smoke: source handle closed\n"); void* base = 0; - s = SysMapFile(mapping, - 0, - sizeof(gExpected), - SYS_MAP_ACCESS_READ, - SYS_MAP_FLAG_NONE, - &base); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ, + SYS_MAP_FLAG_NONE, 0, sizeof(gExpected), &base + }; + s = SysMapFile(&mapFile); SysCloseHandle(mapping); mapping = 0; diff --git a/code/user/tests/module_load_smoke/module_load_smoke.c b/code/user/tests/module_load_smoke/module_load_smoke.c index f26d51d..fe7d43f 100644 --- a/code/user/tests/module_load_smoke/module_load_smoke.c +++ b/code/user/tests/module_load_smoke/module_load_smoke.c @@ -1,155 +1,127 @@ -// User-mode smoke test for public dynamic module loading. +// User-mode smoke test for process-local userspace module loading. #include #include #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) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), 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; - } + 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) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(req), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } -// Terminates the current process. -static void exit_now(int32_t code) { - while (true) 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 exit_now(int32_t code) { while (true) SysExitProcess(code); } +static void fail(const char* message) { print("module_load_smoke: "); print(message); print("\n"); exit_now(1); } 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); + 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"); +static void check_module_info(const NTL_MODULE_INFO* info) { + if (!info || !info->Base || !info->Size) fail("module has empty mapping"); + if (info->PublicRefCount == 0) fail("module public reference 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 != NTLSTATUS_SUCCESS) fail_status("query module count", status); + uint32_t status = NtlGetModuleList(0, 0, &count); + if (status != NTLSTATUS_BUFFER_TOO_SMALL && status != NTLSTATUS_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 != NTLSTATUS_SUCCESS) fail_status("query module list", status); - - for (size_t i = 0; i < count; i++) { - if (modules[i].Base == base) - return 1; - } - return 0; + if (count > SIZE_MAX / sizeof(NTL_MODULE_INFO)) fail("module count overflow"); + size_t bytes = count * sizeof(NTL_MODULE_INFO); + size_t pages = (bytes + 4095u) / 4096u; + void* memory = 0; + SYS_ALLOC_VM_REQUEST alloc; + memset(&alloc, 0, sizeof(alloc)); + alloc.Size = sizeof(alloc); + alloc.Protect = SYS_VM_READWRITE; + alloc.Flags = SYS_VM_FLAG_NONE; + alloc.PageCount = pages; + alloc.BaseOut = &memory; + status = SysAllocVm(&alloc); + if (status != NTLSTATUS_SUCCESS) fail_status("allocate module list", status); + NTL_MODULE_INFO* modules = (NTL_MODULE_INFO*)memory; + memset(modules, 0, bytes); + size_t copied = count; + status = NtlGetModuleList(modules, count, &copied); + if (status != NTLSTATUS_SUCCESS) { (void)SysFreeVm(memory); fail_status("query module list", status); } + int found = 0; + for (size_t i = 0; i < copied; ++i) if (modules[i].Base == base) { found = 1; break; } + (void)SysFreeVm(memory); + return found; } - -// Verifies export lookup through a module handle. -static void check_export_lookup(void) { - uint32_t handle = 0; - SYS_LOAD_MODULE_REQUEST req; +// Loads one module through the userspace registry with the specified filename policy. +static NTL_MODULE_HANDLE load_module(SYS_HANDLE root, const char* path, + NTL_MODULE_NAME_RESOLVER resolver, NTL_MODULE_INFO* info) { + NTL_MODULE_HANDLE handle = NTL_MODULE_HANDLE_INVALID; + NTL_LOAD_MODULE_AT_REQUEST req; memset(&req, 0, sizeof(req)); req.Size = sizeof(req); - req.Path = SysSvFromCstr("syscall.dll"); + req.Flags = NTL_MODULE_LOAD_FLAG_NONE; + req.RootHandle = root; + req.Path = SysSvFromCstr(path); + req.ModuleRootHandle = root; + req.ModuleDirectory = SYS_SV_LITERAL("system"); + req.ResolveModuleName = resolver; req.ModuleHandleOut = &handle; - - uint32_t status = SysLoadModule(&req); - if (status != NTLSTATUS_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 != NTLSTATUS_SUCCESS) fail_status("lookup SysGetCurrentPid", status); - if (address == 0) fail("export address is zero"); - - status = SysUnloadModule(handle); - if (status != NTLSTATUS_SUCCESS) fail_status("unload syscall.dll handle", status); + req.InfoOut = info; + uint32_t status = NtlLoadModuleAt(&req); + if (status != NTLSTATUS_SUCCESS) fail_status("load module", status); + if (handle == NTL_MODULE_HANDLE_INVALID) fail("load returned null handle"); + return handle; } -// Runs the public dynamic module loader smoke test. +// Runs the userspace process-module loader smoke test. int main(void) { print("module_load_smoke: start\n"); - check_export_lookup(); + SYS_HANDLE root = SYS_HANDLE_INVALID; + uint32_t status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &root); + if (status != NTLSTATUS_SUCCESS) fail_status("open system root", status); - uint32_t handle = 0; - SYS_MODULE_INFO info; + NTL_MODULE_HANDLE syscallModule = load_module(root, "system/syscall.dll", + NtlResolveModuleNameIdentity, 0); + uintptr_t address = 0; + status = NtlGetModuleExport(syscallModule, SysSvFromCstr("SysGetCurrentPid"), &address); + if (status != NTLSTATUS_SUCCESS || !address) fail_status("lookup SysGetCurrentPid", status); + status = NtlUnloadModule(syscallModule); + if (status != NTLSTATUS_SUCCESS) fail_status("unload syscall.dll", status); + + NTL_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 != NTLSTATUS_SUCCESS) fail_status("load posix.dll", status); - if (handle == 0) fail("load returned null handle"); + NTL_MODULE_HANDLE posix = load_module(root, "system/msvcrt.dll", + NtlResolveSystemModuleName, &info); check_module_info(&info); if (!module_list_contains(info.Base)) fail("loaded module absent from list"); - SYS_MODULE_INFO byHandle; + NTL_MODULE_INFO byHandle; memset(&byHandle, 0, sizeof(byHandle)); - status = SysGetModuleInfo(handle, &byHandle); + status = NtlGetModuleInfo(posix, &byHandle); if (status != NTLSTATUS_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 != NTLSTATUS_SUCCESS) fail_status("unload module", status); - - memset(&byHandle, 0, sizeof(byHandle)); - status = SysGetModuleInfo(handle, &byHandle); + status = NtlUnloadModule(posix); + if (status != NTLSTATUS_SUCCESS) fail_status("unload posix.dll", status); + status = NtlGetModuleInfo(posix, &byHandle); if (status != NTLSTATUS_INVALID_HANDLE) fail_status("query unloaded handle", status); + (void)SysCloseHandle(root); print("module_load_smoke: PASS\n"); exit_now(0); return 0; diff --git a/code/user/tests/named_queue_lifetime_smoke/named_queue_lifetime_smoke.c b/code/user/tests/named_queue_lifetime_smoke/named_queue_lifetime_smoke.c index 8bc79d6..d03668e 100644 --- a/code/user/tests/named_queue_lifetime_smoke/named_queue_lifetime_smoke.c +++ b/code/user/tests/named_queue_lifetime_smoke/named_queue_lifetime_smoke.c @@ -20,7 +20,7 @@ typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -38,7 +38,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -83,6 +83,7 @@ static void expect_status(const char* what, uint32_t got, uint32_t expected) { static uint32_t publish_queue(const char* name, uint32_t queue) { SYS_PUBLISH_QUEUE_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(name); req.QueueHandle = queue; return SysPublishQueue(&req); @@ -92,6 +93,7 @@ static uint32_t publish_queue(const char* name, uint32_t queue) { static uint32_t unpublish_queue(const char* name) { SYS_UNPUBLISH_QUEUE_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(name); return SysUnpublishQueue(&req); } @@ -100,6 +102,7 @@ static uint32_t unpublish_queue(const char* name) { static uint32_t open_named_queue(const char* name, uint32_t* out) { SYS_OPEN_NAMED_QUEUE_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr(name); req.HandleOut = out; return SysOpenNamedQueue(&req); diff --git a/code/user/tests/native_exception_runtime_smoke/native_exception_runtime_smoke.c b/code/user/tests/native_exception_runtime_smoke/native_exception_runtime_smoke.c index 9fcda32..8ee774e 100644 --- a/code/user/tests/native_exception_runtime_smoke/native_exception_runtime_smoke.c +++ b/code/user/tests/native_exception_runtime_smoke/native_exception_runtime_smoke.c @@ -10,7 +10,7 @@ static volatile uint32_t gContinueHits; // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -28,7 +28,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/native_raise_exception_smoke/native_raise_exception_smoke.c b/code/user/tests/native_raise_exception_smoke/native_raise_exception_smoke.c index bbfac44..182a407 100644 --- a/code/user/tests/native_raise_exception_smoke/native_raise_exception_smoke.c +++ b/code/user/tests/native_raise_exception_smoke/native_raise_exception_smoke.c @@ -11,7 +11,7 @@ static volatile uint32_t gHandlerHits; // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -29,7 +29,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/net_irp_smoke/net_irp_smoke.c b/code/user/tests/net_irp_smoke/net_irp_smoke.c index 88fb0c3..4ad8b2c 100644 --- a/code/user/tests/net_irp_smoke/net_irp_smoke.c +++ b/code/user/tests/net_irp_smoke/net_irp_smoke.c @@ -11,7 +11,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -29,7 +29,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -89,6 +89,7 @@ static SYS_SOCKET_ADDRESS make_ipv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d, 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.Size = sizeof(req); req.Option = option; req.Value = &value; req.ValueSize = sizeof(value); @@ -135,6 +136,7 @@ static void check_nonblocking_receive(void) { SYS_SOCKET_RECV_FROM_REQUEST req; memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Buffer = buffer; req.Length = sizeof(buffer); req.RemoteAddressOut = &remote; diff --git a/code/user/tests/pcache_invalidate/pcache_invalidate.c b/code/user/tests/pcache_invalidate/pcache_invalidate.c index 4bf2918..dc71ab1 100644 --- a/code/user/tests/pcache_invalidate/pcache_invalidate.c +++ b/code/user/tests/pcache_invalidate/pcache_invalidate.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TEST_SIZE 8192u #define TRUNC_SIZE 1024u @@ -14,7 +15,7 @@ static uint8_t gReplacement[4096]; static uint8_t gReadback[TEST_SIZE]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -58,27 +59,22 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) { } static void delete_path(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - (void)SysDeleteFile(&req); + (void)NtlDeleteFile(SysSvFromCstr(path)); } static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { static const char* paths[] = { "pcache_invalidate.tmp", - "/pcache_invalidate.tmp", - "1:/pcache_invalidate.tmp", - "0:/pcache_invalidate.tmp", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; delete_path(paths[i]); - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, - SYS_FILE_CREATE_NEW, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; @@ -169,11 +165,19 @@ int main(void) { if (afterWrite.DirtyMarks <= afterSecond.DirtyMarks) fail("rewrite did not dirty cached page"); uint32_t mapping = 0; - s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gReplacement), &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ, + SYS_MAP_FLAG_NONE, sizeof(gReplacement), &mapping + }; + s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) fail_status("create mapping after rewrite", s); void* mappedBase = 0; - s = SysMapFile(mapping, 0, sizeof(gReplacement), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &mappedBase); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ, + SYS_MAP_FLAG_NONE, 0, sizeof(gReplacement), &mappedBase + }; + s = SysMapFile(&mapFile); SysCloseHandle(mapping); if (s != NTLSTATUS_SUCCESS) fail_status("map after rewrite", s); if (!mappedBase) fail("map after rewrite returned null"); diff --git a/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c b/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c index d15d711..5cd6a9d 100644 --- a/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c +++ b/code/user/tests/pcache_mmap_read_reuse/pcache_mmap_read_reuse.c @@ -5,13 +5,14 @@ #include #include #include +#include #define TEST_SIZE 8192u static uint8_t gReadback[TEST_SIZE]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -57,27 +58,20 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) { static uint32_t open_test_file(uint32_t* out, const char** pathOut) { static const char* paths[] = { "pcache_mmap_read_reuse.exe", - "/apps/pcache_mmap_read_reuse.exe", - "1:/apps/pcache_mmap_read_reuse.exe", - "0:/apps/pcache_mmap_read_reuse.exe", + "/SystemRoot/apps/pcache_mmap_read_reuse.exe", "pcache_smoke.exe", - "/apps/pcache_smoke.exe", - "1:/apps/pcache_smoke.exe", - "0:/apps/pcache_smoke.exe", - "/system/init.exe", - "0:/system/init.exe", - "1:/system/init.exe", + "/SystemRoot/apps/pcache_smoke.exe", + "/SystemRoot/system/init.exe", "system/init.exe", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *out = h; if (pathOut) *pathOut = paths[i]; @@ -114,11 +108,19 @@ int main(void) { print("\n"); uint32_t mapping = 0; - s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, mapSize, &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ, + SYS_MAP_FLAG_NONE, mapSize, &mapping + }; + s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s); void* base = 0; - s = SysMapFile(mapping, 0, mapSize, SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ, + SYS_MAP_FLAG_NONE, 0, mapSize, &base + }; + s = SysMapFile(&mapFile); SysCloseHandle(mapping); if (s != NTLSTATUS_SUCCESS) fail_status("map file", s); if (!base) fail("map returned null"); diff --git a/code/user/tests/pcache_multithread/pcache_multithread.c b/code/user/tests/pcache_multithread/pcache_multithread.c index 77c6ce8..b37f953 100644 --- a/code/user/tests/pcache_multithread/pcache_multithread.c +++ b/code/user/tests/pcache_multithread/pcache_multithread.c @@ -5,6 +5,7 @@ #include #include #include +#include #define THREAD_COUNT 6u #define ITERATIONS 16u @@ -22,7 +23,7 @@ typedef struct THREAD_CTX { static THREAD_CTX gThreads[THREAD_COUNT]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -66,27 +67,22 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) { } static void delete_path(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - (void)SysDeleteFile(&req); + (void)NtlDeleteFile(SysSvFromCstr(path)); } static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { static const char* paths[] = { "pcache_multithread.tmp", - "/pcache_multithread.tmp", - "1:/pcache_multithread.tmp", - "0:/pcache_multithread.tmp", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; delete_path(paths[i]); - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, - SYS_FILE_CREATE_NEW, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; @@ -116,8 +112,11 @@ static void thread_main(uintptr_t arg) { for (uint32_t iter = 0; iter < ITERATIONS; ++iter) { void* base = 0; - uint32_t s = SysMapFile(ctx->Mapping, 0, TEST_SIZE, - SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), ctx->Mapping, SYS_MAP_ACCESS_READ, + SYS_MAP_FLAG_NONE, 0, TEST_SIZE, &base + }; + uint32_t s = SysMapFile(&mapFile); if (s != NTLSTATUS_SUCCESS || !base) { ctx->Status = s ? s : NTLSTATUS_INVALID_PARAMETER; SysExitThread(1); @@ -161,7 +160,11 @@ int main(void) { if (written != sizeof(gPattern)) fail("short write"); uint32_t mapping = 0; - s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ, + SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping + }; + s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s); SYS_FILE_CACHE_STATS before; @@ -179,7 +182,7 @@ int main(void) { gThreads[i].Mapping = mapping; gThreads[i].Status = NTLSTATUS_PENDING; SYS_CREATE_THREAD_REQUEST req = { - (uintptr_t)thread_main, + sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)thread_main, (uintptr_t)&gThreads[i], 1, 0, diff --git a/code/user/tests/pcache_pressure/pcache_pressure.c b/code/user/tests/pcache_pressure/pcache_pressure.c index 915aeb3..389b182 100644 --- a/code/user/tests/pcache_pressure/pcache_pressure.c +++ b/code/user/tests/pcache_pressure/pcache_pressure.c @@ -6,6 +6,7 @@ #include #include #include +#include #define READ_CHUNK 4096u #define MIN_PRESSURE_BYTES (3u * 1024u * 1024u) @@ -14,7 +15,7 @@ static uint8_t gBuf[READ_CHUNK]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -76,12 +77,11 @@ static uint32_t open_self_image(uint32_t* fileOut, const char** pathOut, uint64_ return NTLSTATUS_INVALID_PARAMETER; uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(gSelfPath), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(gSelfPath), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s != NTLSTATUS_SUCCESS) return s; diff --git a/code/user/tests/pcache_smoke/pcache_smoke.c b/code/user/tests/pcache_smoke/pcache_smoke.c index c88d35d..f466bdf 100644 --- a/code/user/tests/pcache_smoke/pcache_smoke.c +++ b/code/user/tests/pcache_smoke/pcache_smoke.c @@ -5,6 +5,7 @@ #include #include #include +#include #define TEST_SIZE 8192u @@ -12,7 +13,7 @@ static uint8_t gA[TEST_SIZE]; static uint8_t gB[TEST_SIZE]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -26,7 +27,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -55,23 +56,17 @@ static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) { static uint32_t open_self(uint32_t* out) { static const char* paths[] = { "pcache_smoke.exe", - "/apps/pcache_smoke.exe", - "Y:/apps/pcache_smoke.exe", - "0:/apps/pcache_smoke.exe", - "1:/apps/pcache_smoke.exe", - "/apps/shell_native.exe", - "0:/apps/shell_native.exe", - "1:/apps/shell_native.exe", + "/SystemRoot/apps/pcache_smoke.exe", + "/SystemRoot/apps/shell_native.exe", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *out = h; print("pcache_smoke: using "); print(paths[i]); print("\n"); @@ -123,13 +118,21 @@ int main(void) { fail("second read did not hit file cache"); uint32_t mapping = 0; - s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gA), &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READ, + SYS_MAP_FLAG_NONE, sizeof(gA), &mapping + }; + s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s); SysCloseHandle(file); file = 0; void* base = 0; - s = SysMapFile(mapping, 0, sizeof(gA), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mapping, SYS_MAP_ACCESS_READ, + SYS_MAP_FLAG_NONE, 0, sizeof(gA), &base + }; + s = SysMapFile(&mapFile); SysCloseHandle(mapping); if (s != NTLSTATUS_SUCCESS) fail_status("map file", s); if (!base) fail("map returned null"); diff --git a/code/user/tests/port_ipc_smoke/port_ipc_smoke.c b/code/user/tests/port_ipc_smoke/port_ipc_smoke.c new file mode 100644 index 0000000..0a230ef --- /dev/null +++ b/code/user/tests/port_ipc_smoke/port_ipc_smoke.c @@ -0,0 +1,501 @@ +// Port IPC smoke test. This validates copied messaging, service discovery, waitability, and capability transfer. + +#include +#include +#include +#include +#include +#include +#include + +#define EVENT_AUTO_RESET 0u +#define TEST_TIMEOUT_MS 2000u + +// Writes one string to the console. +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) }; + (void)SysWriteConsole(&request); +} + +// Writes one unsigned integer without requiring formatted libc helpers. +static void print_u32(uint32_t value) { + char digits[16]; + uint32_t count = 0; + do { + digits[count++] = (char)('0' + value % 10u); + value /= 10u; + } while (value && count < sizeof(digits)); + while (count) { + char one[2] = { digits[--count], 0 }; + print(one); + } +} + +// Fails the current process with one visible status diagnostic. +static int fail(const char* operation, KSTATUS status) { + print("port_ipc_smoke: "); + print(operation); + print(" status="); + print_u32(status); + print("\n"); + return 1; +} + +// Reads Port transport diagnostics through the privileged kernel-stats interface. +static KSTATUS query_port_stats(SYS_PORT_STATS* stats) { + if (!stats) return NTLSTATUS_INVALID_PARAMETER; + memset(stats, 0, sizeof(*stats)); + return SysQueryKernelStats(SYS_KERNEL_STATS_PORT, stats, sizeof(*stats), (size_t*)0); +} + +// Returns a newly created listener Port. +static KSTATUS create_listener(uint32_t backlog, SYS_HANDLE* handleOut) { + SYS_CREATE_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Backlog = backlog; + request.HandleOut = handleOut; + return SysCreatePort(&request); +} + +// Connects directly to one listener handle. +static KSTATUS connect_listener(SYS_HANDLE listener, SYS_HANDLE* endpointOut) { + SYS_CONNECT_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.ListenerHandle = listener; + request.EndpointHandleOut = endpointOut; + return SysConnectPort(&request); +} + +// Accepts one pending connection from a listener. +static KSTATUS accept_listener(SYS_HANDLE listener, + uint64_t timeoutMilliseconds, + SYS_HANDLE* endpointOut, + SYS_PORT_CONNECTION_INFO* infoOut) { + SYS_ACCEPT_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.ListenerHandle = listener; + request.TimeoutMilliseconds = timeoutMilliseconds; + request.EndpointHandleOut = endpointOut; + request.ConnectionInfoOut = infoOut; + if (infoOut) { + memset(infoOut, 0, sizeof(*infoOut)); + infoOut->Size = sizeof(*infoOut); + } + return SysAcceptPort(&request); +} + +// Sends one notification without transferred handles. +static KSTATUS send_notification(SYS_HANDLE endpoint, + const void* payload, + uint32_t payloadLength, + uint64_t timeoutMilliseconds) { + SYS_SEND_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.EndpointHandle = endpoint; + request.Payload = payload; + request.PayloadLength = payloadLength; + request.TimeoutMilliseconds = timeoutMilliseconds; + return SysSendPort(&request); +} + +// Receives one Port message into caller-provided storage. +static KSTATUS receive_message(SYS_HANDLE endpoint, + void* payload, + uint32_t payloadCapacity, + SYS_HANDLE* handles, + uint32_t handleCapacity, + uint64_t timeoutMilliseconds, + SYS_PORT_MESSAGE_INFO* infoOut) { + memset(infoOut, 0, sizeof(*infoOut)); + infoOut->Size = sizeof(*infoOut); + SYS_RECEIVE_PORT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.EndpointHandle = endpoint; + request.Payload = payload; + request.PayloadCapacity = payloadCapacity; + request.HandlesOut = handles; + request.HandleCapacity = handleCapacity; + request.InfoOut = infoOut; + request.TimeoutMilliseconds = timeoutMilliseconds; + return SysReceivePort(&request); +} + +typedef struct RECEIVE_WORKER_CONTEXT { + SYS_HANDLE Endpoint; + SYS_HANDLE ReadyEvent; + KSTATUS Status; + SYS_PORT_MESSAGE_INFO Info; + char Payload[16]; +} RECEIVE_WORKER_CONTEXT; + +// Blocks one worker on a shared endpoint to validate multiple concurrent receivers. +static void receive_worker(uintptr_t argument) { + RECEIVE_WORKER_CONTEXT* context = (RECEIVE_WORKER_CONTEXT*)argument; + if (!context) SysExitThread(1); + memset(context->Payload, 0, sizeof(context->Payload)); + if (!KSUCCESS(SysSignalEvent(context->ReadyEvent))) + SysExitThread(2); + context->Status = receive_message(context->Endpoint, + context->Payload, + (uint32_t)sizeof(context->Payload), + (SYS_HANDLE*)0, + 0, + TEST_TIMEOUT_MS, + &context->Info); + SysExitThread(KSUCCESS(context->Status) ? 0 : 1); +} + +// Verifies listener backlog and pending-connection teardown semantics. +static int run_connection_lifetime_test(void) { + SYS_HANDLE listener = SYS_HANDLE_INVALID; + KSTATUS status = create_listener(1, &listener); + if (!KSUCCESS(status)) return fail("create lifetime listener", status); + + SYS_HANDLE client = SYS_HANDLE_INVALID; + status = connect_listener(listener, &client); + if (!KSUCCESS(status)) return fail("connect pending client", status); + + SYS_HANDLE overflow = SYS_HANDLE_INVALID; + status = connect_listener(listener, &overflow); + if (status != NTLSTATUS_LIMIT_REACHED) + return fail("listener backlog", status); + + status = SysCloseHandle(listener); + if (!KSUCCESS(status)) return fail("close pending listener", status); + + SYS_PORT_MESSAGE_INFO info; + status = receive_message(client, (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info); + if (status != NTLSTATUS_PORT_DISCONNECTED) + return fail("pending connection disconnect", status); + (void)SysCloseHandle(client); + return 0; +} + +// Exercises same-process connection semantics, waitability, buffering, and queue quotas. +static int run_local_transport_test(void) { + SYS_HANDLE listener = SYS_HANDLE_INVALID; + KSTATUS status = create_listener(4, &listener); + if (!KSUCCESS(status)) return fail("create local listener", status); + + SYS_HANDLE client = SYS_HANDLE_INVALID; + status = connect_listener(listener, &client); + if (!KSUCCESS(status)) return fail("connect local listener", status); + + status = SysWaitSingle(listener, SYS_WAIT_FLAG_NONE, 0); + if (!KSUCCESS(status)) return fail("listener waitability", status); + + SYS_PORT_CONNECTION_INFO connectionInfo; + SYS_HANDLE server = SYS_HANDLE_INVALID; + status = accept_listener(listener, 0, &server, &connectionInfo); + if (!KSUCCESS(status)) return fail("accept local connection", status); + if (connectionInfo.ClientProcessId != SysGetCurrentPid() || + connectionInfo.ClientThreadId != SysGetCurrentTid() || + connectionInfo.ClientSecurityId == 0) + return fail("local connection identity", NTLSTATUS_FAILURE); + + SYS_PORT_STATS directBefore; + status = query_port_stats(&directBefore); + if (!KSUCCESS(status)) return fail("query direct stats before", status); + + RECEIVE_WORKER_CONTEXT workers[2]; + memset(workers, 0, sizeof(workers)); + SYS_HANDLE workerHandles[2] = { SYS_HANDLE_INVALID, SYS_HANDLE_INVALID }; + for (uint32_t i = 0; i < 2; ++i) { + workers[i].Endpoint = server; + workers[i].ReadyEvent = SYS_HANDLE_INVALID; + workers[i].Status = NTLSTATUS_PENDING; + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &workers[i].ReadyEvent); + if (!KSUCCESS(status)) return fail("create receive ready event", status); + uint32_t threadId = 0; + SYS_CREATE_THREAD_REQUEST createThread = { + sizeof(createThread), (uintptr_t)receive_worker, (uintptr_t)&workers[i], + 1, SYS_CREATE_THREAD_FLAG_NONE, &workerHandles[i], &threadId + }; + status = SysCreateThread(&createThread); + if (!KSUCCESS(status)) return fail("create receive worker", status); + } + + for (uint32_t i = 0; i < 2; ++i) { + status = SysWaitSingle(workers[i].ReadyEvent, SYS_WAIT_FLAG_NONE, TEST_TIMEOUT_MS); + if (!KSUCCESS(status)) return fail("wait receive ready event", status); + } + // The ready event is signaled immediately before ReceivePort; give each worker one scheduling turn to block. + (void)SysSleep(20); + static const char workerPayloadA[] = "worker-a"; + static const char workerPayloadB[] = "worker-b"; + status = send_notification(client, workerPayloadA, (uint32_t)sizeof(workerPayloadA), TEST_TIMEOUT_MS); + if (!KSUCCESS(status)) return fail("send worker payload a", status); + status = send_notification(client, workerPayloadB, (uint32_t)sizeof(workerPayloadB), TEST_TIMEOUT_MS); + if (!KSUCCESS(status)) return fail("send worker payload b", status); + + for (uint32_t i = 0; i < 2; ++i) { + status = SysWait(workerHandles[i], SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(status)) return fail("wait receive worker", status); + (void)SysCloseHandle(workerHandles[i]); + (void)SysCloseHandle(workers[i].ReadyEvent); + if (!KSUCCESS(workers[i].Status) || workers[i].Info.Type != SYS_PORT_MESSAGE_NOTIFICATION || + workers[i].Info.SenderProcessId != SysGetCurrentPid()) + return fail("receive worker result", workers[i].Status); + } + bool firstIsA = memcmp(workers[0].Payload, workerPayloadA, sizeof(workerPayloadA)) == 0; + bool firstIsB = memcmp(workers[0].Payload, workerPayloadB, sizeof(workerPayloadB)) == 0; + bool secondIsA = memcmp(workers[1].Payload, workerPayloadA, sizeof(workerPayloadA)) == 0; + bool secondIsB = memcmp(workers[1].Payload, workerPayloadB, sizeof(workerPayloadB)) == 0; + if (!((firstIsA && secondIsB) || (firstIsB && secondIsA))) + return fail("multiple receive payloads", NTLSTATUS_FAILURE); + + SYS_PORT_STATS directAfter; + status = query_port_stats(&directAfter); + if (!KSUCCESS(status)) return fail("query direct stats after", status); + if (directAfter.DirectDeliveries < directBefore.DirectDeliveries + 2u || + directAfter.QueuedMessages != directBefore.QueuedMessages) + return fail("direct rendezvous counters", NTLSTATUS_FAILURE); + + char payload[] = "buffer-too-small"; + static const char expectedPayload[] = "buffer-too-small"; + status = send_notification(client, payload, (uint32_t)sizeof(payload), 0); + if (!KSUCCESS(status)) return fail("queue local notification", status); + payload[0] = 'X'; + + char tiny[4]; + SYS_PORT_MESSAGE_INFO info; + status = receive_message(server, tiny, (uint32_t)sizeof(tiny), (SYS_HANDLE*)0, 0, 0, &info); + if (status != NTLSTATUS_BUFFER_TOO_SMALL || + info.PayloadLength != sizeof(expectedPayload) || info.Type != SYS_PORT_MESSAGE_NOTIFICATION) + return fail("buffer-too-small contract", status); + + char received[32]; + memset(received, 0, sizeof(received)); + status = receive_message(server, received, (uint32_t)sizeof(received), (SYS_HANDLE*)0, 0, 0, &info); + if (!KSUCCESS(status)) return fail("retry local receive", status); + if (info.SenderProcessId != SysGetCurrentPid() || + info.SenderThreadId != SysGetCurrentTid() || + info.SenderSecurityId == 0 || + info.PayloadLength != sizeof(expectedPayload) || + memcmp(received, expectedPayload, sizeof(expectedPayload)) != 0) + return fail("queued message snapshot", NTLSTATUS_FAILURE); + + SYS_HANDLE sourceEvent = SYS_HANDLE_INVALID; + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &sourceEvent); + if (!KSUCCESS(status)) return fail("create queued transfer event", status); + SYS_PORT_HANDLE_TRANSFER queuedTransfer = { + sourceEvent, + SYS_RIGHT_WAIT, + SYS_PORT_TRANSFER_FLAG_DERIVE + }; + SYS_SEND_PORT_REQUEST transferSend; + memset(&transferSend, 0, sizeof(transferSend)); + transferSend.Size = sizeof(transferSend); + transferSend.EndpointHandle = client; + transferSend.Handles = &queuedTransfer; + transferSend.HandleCount = 1; + status = SysSendPort(&transferSend); + if (!KSUCCESS(status)) return fail("queue capability transfer", status); + status = SysCloseHandle(sourceEvent); + if (!KSUCCESS(status)) return fail("close transferred source", status); + + SYS_HANDLE receivedEvent = SYS_HANDLE_INVALID; + status = receive_message(server, + (void*)0, + 0, + &receivedEvent, + 1, + 0, + &info); + if (!KSUCCESS(status) || info.HandleCount != 1) + return fail("receive queued capability transfer", status); + status = SysWaitSingle(receivedEvent, SYS_WAIT_FLAG_NONE, 0); + if (status != NTLSTATUS_TIMEOUT) + return fail("queued transfer wait right", status); + status = SysSignalEvent(receivedEvent); + if (status != NTLSTATUS_ACCESS_DENIED) + return fail("queued transfer attenuation", status); + (void)SysCloseHandle(receivedEvent); + + SYS_SEND_PORT_REQUEST oversized; + memset(&oversized, 0, sizeof(oversized)); + oversized.Size = sizeof(oversized); + oversized.EndpointHandle = client; + oversized.Payload = (const void*)1; + oversized.PayloadLength = SYS_PORT_MAX_INLINE_BYTES + 1u; + status = SysSendPort(&oversized); + if (status != NTLSTATUS_INVALID_PARAMETER) + return fail("inline payload bound", status); + + for (uint32_t i = 0; i < SYS_PORT_QUEUE_MESSAGE_LIMIT; ++i) { + status = send_notification(client, (const void*)0, 0, 0); + if (!KSUCCESS(status)) return fail("fill local queue", status); + } + status = send_notification(client, (const void*)0, 0, 0); + if (status != NTLSTATUS_WOULD_BLOCK) + return fail("local queue backpressure", status); + + for (uint32_t i = 0; i < SYS_PORT_QUEUE_MESSAGE_LIMIT; ++i) { + status = receive_message(server, (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info); + if (!KSUCCESS(status)) return fail("drain local queue", status); + } + + // One process cannot multiply endpoint queue limits into unbounded kernel IPC storage. + SYS_HANDLE quotaClients[5] = { client, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID }; + SYS_HANDLE quotaServers[5] = { server, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID, SYS_HANDLE_INVALID }; + for (uint32_t i = 1; i < 5; ++i) { + status = connect_listener(listener, "aClients[i]); + if (!KSUCCESS(status)) return fail("connect quota endpoint", status); + status = accept_listener(listener, 0, "aServers[i], (SYS_PORT_CONNECTION_INFO*)0); + if (!KSUCCESS(status)) return fail("accept quota endpoint", status); + } + + SYS_PORT_STATS quotaBefore; + status = query_port_stats("aBefore); + if (!KSUCCESS(status)) return fail("query quota stats before", status); + + bool processQuotaHit = false; + for (uint32_t i = 0; i < 5 && !processQuotaHit; ++i) { + for (uint32_t j = 0; j < SYS_PORT_QUEUE_MESSAGE_LIMIT; ++j) { + status = send_notification(quotaClients[i], (const void*)0, 0, 0); + if (status == NTLSTATUS_LIMIT_REACHED) { + processQuotaHit = true; + break; + } + if (!KSUCCESS(status)) return fail("fill process quota", status); + } + } + if (!processQuotaHit) + return fail("process ipc quota", NTLSTATUS_FAILURE); + SYS_PORT_STATS quotaAfter; + status = query_port_stats("aAfter); + if (!KSUCCESS(status)) return fail("query quota stats after", status); + if (quotaAfter.ProcessQuotaRejects <= quotaBefore.ProcessQuotaRejects) + return fail("process quota counter", NTLSTATUS_FAILURE); + + for (uint32_t i = 0; i < 5; ++i) { + while (true) { + status = receive_message(quotaServers[i], (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info); + if (status == NTLSTATUS_WOULD_BLOCK) + break; + if (!KSUCCESS(status)) return fail("drain process quota", status); + } + } + for (uint32_t i = 1; i < 5; ++i) { + (void)SysCloseHandle(quotaServers[i]); + (void)SysCloseHandle(quotaClients[i]); + } + + status = SysDisconnectPort(client); + if (!KSUCCESS(status)) return fail("disconnect local endpoint", status); + status = receive_message(server, (void*)0, 0, (SYS_HANDLE*)0, 0, 0, &info); + if (status != NTLSTATUS_PORT_DISCONNECTED) + return fail("local peer disconnect", status); + + (void)SysCloseHandle(server); + (void)SysCloseHandle(client); + (void)SysCloseHandle(listener); + return 0; +} + +// Verifies that registration privilege alone cannot publish a claimed well-known service. +static int run_claimless_client(void) { + SYS_SECURITY_CONTEXT_INFO contextInfo; + memset(&contextInfo, 0, sizeof(contextInfo)); + contextInfo.Size = sizeof(contextInfo); + KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &contextInfo); + if (!KSUCCESS(status)) return 10; + if (contextInfo.EffectivePrivileges != SYS_PRIV_SYSTEM_SERVICE_REGISTER || + contextInfo.ServiceClaims != 0) + return 11; + + SYS_HANDLE listener = SYS_HANDLE_INVALID; + status = create_listener(1, &listener); + if (!KSUCCESS(status)) return 12; + status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener); + (void)SysCloseHandle(listener); + return status == NTLSTATUS_ACCESS_DENIED ? 0 : 13; +} + +// Verifies that the live resource service owns its well-known registry slot and remains connectable. +static int run_service_test(void) { + SYS_HANDLE listener = SYS_HANDLE_INVALID; + KSTATUS status = create_listener(1, &listener); + if (!KSUCCESS(status)) return fail("create service probe listener", status); + + status = SysRegisterSystemService(SYS_SERVICE_MAX + 1u, listener); + if (status != NTLSTATUS_INVALID_PARAMETER) + return fail("invalid service id", status); + + status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener); + if (status != NTLSTATUS_ALREADY_EXISTS) + return fail("resource service must already be registered", status); + + SYS_HANDLE claimlessContext = SYS_HANDLE_INVALID; + SYS_DERIVE_SECURITY_CONTEXT_REQUEST deriveContext; + memset(&deriveContext, 0, sizeof(deriveContext)); + deriveContext.Size = sizeof(deriveContext); + deriveContext.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; + deriveContext.EffectivePrivileges = SYS_PRIV_SYSTEM_SERVICE_REGISTER; + deriveContext.ContextHandleOut = &claimlessContext; + status = SysDeriveSecurityContext(&deriveContext); + if (!KSUCCESS(status)) return fail("derive claimless service context", status); + + SYS_PEB* peb = SysGetPeb(); + if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) + return fail("missing image path", NTLSTATUS_FAILURE); + + SYS_HANDLE claimlessChild = SYS_HANDLE_INVALID; + NTL_CREATE_SYSTEM_PROCESS_REQUEST claimlessCreate; + memset(&claimlessCreate, 0, sizeof(claimlessCreate)); + claimlessCreate.Size = sizeof(claimlessCreate); + claimlessCreate.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + claimlessCreate.Path = peb->ImagePath; + claimlessCreate.CommandLine = SysSvFromCstr("claimless"); + claimlessCreate.SecurityContext = claimlessContext; + claimlessCreate.ResolveModuleName = NtlResolveSystemModuleName; + claimlessCreate.ProcessHandleOut = &claimlessChild; + status = NtlCreateSystemProcess(&claimlessCreate); + (void)SysCloseHandle(claimlessContext); + if (!KSUCCESS(status)) return fail("create claimless service client", status); + status = SysWait(claimlessChild, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(status)) return fail("wait claimless service client", status); + int32_t claimlessExit = -1; + status = SysGetProcessExitCode(claimlessChild, &claimlessExit); + (void)SysCloseHandle(claimlessChild); + if (!KSUCCESS(status)) return fail("query claimless service client", status); + if (claimlessExit != 0) return fail("service claim enforcement", (KSTATUS)claimlessExit); + + status = SysUnregisterSystemService(SYS_SERVICE_RESOURCE, listener); + if (status != NTLSTATUS_ACCESS_DENIED) + return fail("foreign listener cannot unregister service", status); + + SYS_HANDLE endpoint = SYS_HANDLE_INVALID; + SYS_CONNECT_SYSTEM_SERVICE_REQUEST connect; + memset(&connect, 0, sizeof(connect)); + connect.Size = sizeof(connect); + connect.ServiceId = SYS_SERVICE_RESOURCE; + connect.EndpointHandleOut = &endpoint; + status = SysConnectSystemService(&connect); + if (!KSUCCESS(status)) return fail("connect live resource service", status); + (void)SysDisconnectPort(endpoint); + (void)SysCloseHandle(endpoint); + (void)SysCloseHandle(listener); + return 0; +} + +// Runs either the privileged parent or claimless child half. +int main(void) { + SYS_PEB* peb = SysGetPeb(); + if (!peb) return 2; + if (peb->CommandLine.Length == 9 && + memcmp(peb->CommandLine.Data, "claimless", 9) == 0) + return run_claimless_client(); + + print("port_ipc_smoke: start\n"); + if (run_connection_lifetime_test() != 0) return 1; + if (run_local_transport_test() != 0) return 1; + if (run_service_test() != 0) return 1; + print("port_ipc_smoke: PASS\n"); + return 0; +} diff --git a/code/user/tests/process_builder_smoke/process_builder_smoke.c b/code/user/tests/process_builder_smoke/process_builder_smoke.c new file mode 100644 index 0000000..06c1ac6 --- /dev/null +++ b/code/user/tests/process_builder_smoke/process_builder_smoke.c @@ -0,0 +1,139 @@ +// Process-construction smoke test. The same import-free PE is loaded a second time entirely by userspace. + +#include +#include +#include +#include +#include +#include + +#define TEST_STARTUP_EVENT_TAG 0x50425445u + +static void exit_now(int code) { + for (;;) SysExitProcess(code); +} + +// Writes one string through the native console syscall. +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) }; + (void)SysWriteConsole(&request); +} + +// Writes one 32-bit value as hexadecimal without requiring a formatting runtime. +static void print_hex32(uint32_t value) { + static const char digits[] = "0123456789ABCDEF"; + char buffer[11] = "0x00000000"; + for (uint32_t i = 0; i < 8; ++i) + buffer[2 + i] = digits[(value >> ((7u - i) * 4u)) & 0xFu]; + print(buffer); +} + +static int is_child(void) { + SYS_PEB* peb = SysGetPeb(); + static const char marker[] = "process-builder-child"; + return peb && peb->CommandLine.Length == sizeof(marker) - 1 && + memcmp(peb->CommandLine.Data, marker, sizeof(marker) - 1) == 0; +} + +static SYS_HANDLE find_startup(uint32_t tag) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) + return SYS_HANDLE_INVALID; + for (uint32_t i = 0; i < peb->StartupCapabilityCount; ++i) { + if (peb->StartupCapabilities[i].Tag == tag) + return peb->StartupCapabilities[i].Handle; + } + return SYS_HANDLE_INVALID; +} + +int main(void) { + if (is_child()) { + SYS_HANDLE event = find_startup(TEST_STARTUP_EVENT_TAG); + if (event == SYS_HANDLE_INVALID) exit_now(10); + if (SysSignalEvent(event) != NTLSTATUS_SUCCESS) exit_now(11); + exit_now(0); + } + + SYS_PEB* peb = SysGetPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) + exit_now(1); + + SYS_HANDLE file = SYS_HANDLE_INVALID; + KSTATUS s = NtlOpenFile(peb->ImagePath, + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &file); + if (!KSUCCESS(s)) exit_now(2); + + SYS_HANDLE child = SYS_HANDLE_INVALID; + SYS_CREATE_PROCESS_OBJECT_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Flags = SYS_CREATE_PROCESS_OBJECT_FLAG_NONE; + create.ImagePath = peb->ImagePath; + create.CommandLine = SYS_SV_LITERAL("process-builder-child"); + create.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + create.ProcessHandleOut = &child; + s = SysCreateProcessObject(&create); + if (!KSUCCESS(s)) exit_now(3); + + NTL_PE_IMAGE image; + memset(&image, 0, sizeof(image)); + NTL_PE_LOAD_REQUEST load; + memset(&load, 0, sizeof(load)); + load.Size = sizeof(load); + load.ProcessHandle = child; + load.FileHandle = file; + load.Flags = NTL_PE_LOAD_FLAG_NONE; + load.ImageOut = ℑ + s = NtlLoadPeImage(&load); + SysCloseHandle(file); + if (!KSUCCESS(s) || !image.Base || !image.EntryPoint) { + print("process_builder_smoke: NtlLoadPeImage failed status="); + print_hex32(s); + print("\n"); + exit_now(4); + } + + SYS_HANDLE event = SYS_HANDLE_INVALID; + s = SysCreateEvent(0, 0, &event); + if (!KSUCCESS(s)) exit_now(5); + + SYS_HANDLE childEvent = SYS_HANDLE_INVALID; + SYS_DUPLICATE_HANDLE_REQUEST duplicate; + memset(&duplicate, 0, sizeof(duplicate)); + duplicate.Size = sizeof(duplicate); + duplicate.SourceHandle = event; + duplicate.TargetProcessHandle = child; + duplicate.DesiredRights = SYS_EVENT_RIGHT_SIGNAL | SYS_RIGHT_QUERY; + duplicate.Flags = SYS_DUPLICATE_HANDLE_FLAG_NONE; + duplicate.TargetHandleOut = &childEvent; + s = SysDuplicateHandle(&duplicate); + if (!KSUCCESS(s)) exit_now(6); + + SYS_STARTUP_CAPABILITY startup = { TEST_STARTUP_EVENT_TAG, childEvent }; + SYS_START_PROCESS_REQUEST start; + memset(&start, 0, sizeof(start)); + start.Size = sizeof(start); + start.ProcessHandle = child; + start.Flags = SYS_START_PROCESS_FLAG_NONE; + start.MainImageBase = image.Base; + start.MainImageSize = image.Size; + start.EntryPoint = image.EntryPoint; + start.StartupCapabilities = &startup; + start.StartupCapabilityCount = 1; + s = SysStartProcess(&start); + if (!KSUCCESS(s)) exit_now(7); + + s = SysWait(event, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(s)) exit_now(8); + s = SysWait(child, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(s)) exit_now(9); + int32_t exitCode = -1; + s = SysGetProcessExitCode(child, &exitCode); + SysCloseHandle(event); + SysCloseHandle(child); + if (!KSUCCESS(s) || exitCode != 0) exit_now(12); + return 0; +} diff --git a/code/user/tests/process_exit_multithread_smoke/process_exit_multithread_smoke.c b/code/user/tests/process_exit_multithread_smoke/process_exit_multithread_smoke.c new file mode 100644 index 0000000..1e4858d --- /dev/null +++ b/code/user/tests/process_exit_multithread_smoke/process_exit_multithread_smoke.c @@ -0,0 +1,129 @@ +// Whole-process exit regression test. A blocked sibling must not keep the process alive. + +#include +#include +#include +#include + +#define EVENT_AUTO_RESET 0u +#define READY_TIMEOUT_MS 1000u +#define BLOCK_SETTLE_MS 50u +#define CHILD_EXIT_TIMEOUT_MS 1000u +#define CHILD_EXIT_CODE 37 + +typedef struct WORKER_CONTEXT { + SYS_HANDLE ReadyEvent; + SYS_HANDLE BlockEvent; +} WORKER_CONTEXT; + +static WORKER_CONTEXT gWorker; + +// Writes one test message to the native console. +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; + (void)SysWriteConsole(&request); +} + +// Terminates the current test process after one failure. +static void fail(const char* message) { + print("process_exit_multithread_smoke: "); + print(message); + print("\n"); + while (true) (void)SysExitProcess(1); +} + +// Returns whether one string view exactly matches one NUL-terminated string. +static int view_equals_cstr(SYS_STRING_VIEW view, const char* text) { + size_t length = strlen(text); + return view.Data && view.Length == length && memcmp(view.Data, text, length) == 0; +} + +// Blocks forever so only whole-process termination can remove this sibling promptly. +static void blocked_worker(uintptr_t argument) { + WORKER_CONTEXT* context = (WORKER_CONTEXT*)argument; + if (!context) + while (true) (void)SysExitProcess(2); + + if (SysSignalEvent(context->ReadyEvent) != NTLSTATUS_SUCCESS) + while (true) (void)SysExitProcess(2); + + (void)SysWaitSingle(context->BlockEvent, SYS_WAIT_FLAG_NONE, SYS_WAIT_TIMEOUT_INFINITE); + + // A normal wait return is impossible in the test. If it happens, fail the process. + while (true) (void)SysExitProcess(2); +} + +// Runs the child half that exits while one sibling is blocked indefinitely. +static void run_child(void) { + if (SysCreateEvent(EVENT_AUTO_RESET, 0, &gWorker.ReadyEvent) != NTLSTATUS_SUCCESS) + while (true) (void)SysExitProcess(2); + if (SysCreateEvent(EVENT_AUTO_RESET, 0, &gWorker.BlockEvent) != NTLSTATUS_SUCCESS) + while (true) (void)SysExitProcess(2); + + SYS_HANDLE thread = SYS_HANDLE_INVALID; + SYS_CREATE_THREAD_REQUEST request = { + sizeof(SYS_CREATE_THREAD_REQUEST), + (uintptr_t)blocked_worker, + (uintptr_t)&gWorker, + 1, + SYS_CREATE_THREAD_FLAG_NONE, + &thread, + (uint32_t*)0 + }; + if (SysCreateThread(&request) != NTLSTATUS_SUCCESS) + while (true) (void)SysExitProcess(2); + + if (SysWaitSingle(gWorker.ReadyEvent, SYS_WAIT_FLAG_NONE, READY_TIMEOUT_MS) != NTLSTATUS_SUCCESS) + while (true) (void)SysExitProcess(2); + + // Give the worker a scheduling window to enter its infinite wait before + // exercising whole-process exit. This avoids testing only the syscall gate. + if (SysSleep(BLOCK_SETTLE_MS) != NTLSTATUS_SUCCESS) + while (true) (void)SysExitProcess(2); + + while (true) (void)SysExitProcess(CHILD_EXIT_CODE); +} + +// Launches the child half and requires whole-process exit to finish promptly. +int main(void) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) + fail("invalid PEB"); + + if (view_equals_cstr(peb->CommandLine, "--child")) + run_child(); + + print("process_exit_multithread_smoke: begin\n"); + + SYS_HANDLE process = SYS_HANDLE_INVALID; + NTL_CREATE_SYSTEM_PROCESS_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + request.Path = peb->ImagePath; + request.CommandLine = SysSvFromCstr("--child"); + request.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + request.ResolveModuleName = NtlResolveSystemModuleName; + request.ProcessHandleOut = &process; + + KSTATUS status = NtlCreateSystemProcess(&request); + if (!KSUCCESS(status) || process == SYS_HANDLE_INVALID) + fail("child launch failed"); + + status = SysWaitSingle(process, SYS_WAIT_FLAG_NONE, CHILD_EXIT_TIMEOUT_MS); + if (status != NTLSTATUS_SUCCESS) { + (void)SysKillProcess(process, 1); + (void)SysWait(process, SYS_WAIT_FLAG_NONE); + (void)SysCloseHandle(process); + fail("child did not exit promptly"); + } + + int32_t exitCode = 0; + status = SysGetProcessExitCode(process, &exitCode); + (void)SysCloseHandle(process); + if (status != NTLSTATUS_SUCCESS || exitCode != CHILD_EXIT_CODE) + fail("child exit code mismatch"); + + print("process_exit_multithread_smoke: PASS\n"); + while (true) (void)SysExitProcess(0); +} diff --git a/code/user/tests/process_protection_smoke/process_protection_smoke.c b/code/user/tests/process_protection_smoke/process_protection_smoke.c index aed3ceb..71ecb73 100644 --- a/code/user/tests/process_protection_smoke/process_protection_smoke.c +++ b/code/user/tests/process_protection_smoke/process_protection_smoke.c @@ -9,7 +9,7 @@ // Prints one NUL-terminated string to the console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } @@ -30,7 +30,7 @@ static void print_u32(uint32_t value) { while (length) { char digit = buffer[--length]; - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromParts(&digit, 1) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&digit, 1) }; (void)SysWriteConsole(&request); } } @@ -53,7 +53,7 @@ static void fail_status(const char* operation, uint32_t status) { // Verifies one protected process remains alive after a kill attempt. static void verify_protected_process(const SYS_PROCESS_INFO* process) { uint32_t handle = 0; - uint32_t status = SysOpenProcess(process->ProcessId, &handle); + uint32_t status = SysOpenProcess(process->ProcessId, SYS_PROCESS_RIGHT_TERMINATE | SYS_RIGHT_WAIT | SYS_RIGHT_QUERY, &handle); if (status != NTLSTATUS_SUCCESS) fail_status("open protected process", status); diff --git a/code/user/tests/realpath_smoke/realpath_smoke.c b/code/user/tests/realpath_smoke/realpath_smoke.c index 01973d4..b5218b3 100644 --- a/code/user/tests/realpath_smoke/realpath_smoke.c +++ b/code/user/tests/realpath_smoke/realpath_smoke.c @@ -1,175 +1,75 @@ -// Realpath ABI smoke test. -// Verifies lexical canonicalization, size reporting, and root clamping. +// Userspace realpath smoke test. +// Verifies native resource-path canonicalization and size reporting. #include #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) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(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 test process. static void exit_now(int code) { while (true) SysExitProcess(code); } -// Fails the test with one status value. static void fail_status(const char* what, uint32_t status) { - print("realpath_smoke: "); - print(what); - print(" status="); - print_u64(status); - print("\n"); + (void)status; + print("realpath_smoke: "); print(what); print("\n"); exit_now(1); } -// Fails the test with one plain message. static void fail(const char* what) { - print("realpath_smoke: "); - print(what); - print("\n"); + print("realpath_smoke: "); print(what); print("\n"); exit_now(1); } -// Appends one string to a fixed test buffer. -static void append(char* out, size_t cap, const char* suffix) { - size_t len = strlen(out); - size_t add = strlen(suffix); - if (len + add + 1 > cap) fail("test buffer too small"); - memcpy(out + len, suffix, add + 1); -} - -// Builds the current drive root path. -static void get_current_drive_root(char* out, size_t cap) { - char current[SYS_CWD_MAX]; - size_t required = 0; - SYS_REAL_PATH_REQUEST req = { - sizeof(req), - SYS_REAL_PATH_FLAG_NONE, - SysSvFromCstr("."), - current, - sizeof(current), - &required, - }; - uint32_t status = SysRealPath(&req); - if (status != NTLSTATUS_SUCCESS) fail_status("query current path", status); - if (required < 4 || current[0] < '0' || current[0] > '7' || current[1] != ':' || current[2] != '/') - fail("current path is not native drive-rooted"); - if (cap < 4) fail("drive root output too small"); - out[0] = current[0]; - out[1] = ':'; - out[2] = '/'; - out[3] = '\0'; -} - -// Resolves a path and compares the exact native string. static void expect_realpath(const char* input, const char* expected) { - char out[SYS_CWD_MAX]; + char out[SYS_PATH_MAX + 1u]; size_t required = 0; - SYS_REAL_PATH_REQUEST req = { - sizeof(req), - SYS_REAL_PATH_FLAG_NONE, - SysSvFromCstr(input), - out, - sizeof(out), - &required, - }; - uint32_t status = SysRealPath(&req); + uint32_t status = NtlRealPath(SysSvFromCstr(input), out, sizeof(out), &required); if (status != NTLSTATUS_SUCCESS) fail_status("realpath", status); - if (required != strlen(expected) + 1) fail("required size mismatch"); + if (required != strlen(expected) + 1u) fail("required size mismatch"); if (strcmp(out, expected) != 0) fail("resolved path mismatch"); } -// Verifies size-only and too-small buffer behavior. -static void check_size_reporting(const char* input, const char* expected) { - size_t required = 0; - SYS_REAL_PATH_REQUEST query = { - sizeof(query), - SYS_REAL_PATH_FLAG_NONE, - SysSvFromCstr(input), - (char*)0, - 0, - &required, - }; - uint32_t status = SysRealPath(&query); - if (status != NTLSTATUS_SUCCESS) fail_status("size query", status); - if (required != strlen(expected) + 1) fail("size query required mismatch"); - - char small[4]; - size_t smallRequired = 0; - SYS_REAL_PATH_REQUEST smallReq = { - sizeof(smallReq), - SYS_REAL_PATH_FLAG_NONE, - SysSvFromCstr(input), - small, - sizeof(small), - &smallRequired, - }; - status = SysRealPath(&smallReq); - if (status != NTLSTATUS_BUFFER_TOO_SMALL) fail_status("small buffer", status); - if (smallRequired != required) fail("small required mismatch"); -} - -// Program entry point. int main(void) { print("realpath_smoke: start\n"); - char root[4]; - get_current_drive_root(root, sizeof(root)); + expect_realpath("/SystemRoot//foo///bar/./", "/SystemRoot/foo/bar"); - char input[SYS_CWD_MAX]; - char expected[SYS_CWD_MAX]; + char cwd[SYS_PATH_MAX + 1u]; + size_t cwdRequired = 0; + uint32_t status = NtlGetCurrentDirectory(cwd, sizeof(cwd), &cwdRequired); + if (status != NTLSTATUS_SUCCESS) fail_status("get cwd", status); - memcpy(input, root, sizeof(root)); - append(input, sizeof(input), "//foo///bar\\baz/."); - memcpy(expected, root, sizeof(root)); - append(expected, sizeof(expected), "foo/bar/baz"); - expect_realpath(input, expected); - check_size_reporting(input, expected); + char expected[SYS_PATH_MAX + 1u]; + size_t cwdLength = strlen(cwd); + if (cwdLength + sizeof("/missing_file") > sizeof(expected)) fail("expected buffer too small"); + memcpy(expected, cwd, cwdLength); + memcpy(expected + cwdLength, "/missing_file", sizeof("/missing_file")); + expect_realpath("missing_realpath_dir/../missing_file", expected); - memcpy(input, root, sizeof(root)); - append(input, sizeof(input), "../../root_escape"); - memcpy(expected, root, sizeof(root)); - append(expected, sizeof(expected), "root_escape"); - expect_realpath(input, expected); + size_t required = 0; + status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/system/init.exe"), (char*)0, 0, &required); + if (status != NTLSTATUS_SUCCESS || required != sizeof("/SystemRoot/system/init.exe")) + fail_status("size query", status); + char small[4]; + size_t smallRequired = 0; + status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/system/init.exe"), small, sizeof(small), &smallRequired); + if (status != NTLSTATUS_BUFFER_TOO_SMALL || smallRequired != required) + fail_status("small buffer", status); - SYS_REAL_PATH_REQUEST currentReq = { - sizeof(currentReq), - SYS_REAL_PATH_FLAG_NONE, - SysSvFromCstr("."), - expected, - sizeof(expected), - (size_t*)0, - }; - uint32_t status = SysRealPath(¤tReq); - if (status != NTLSTATUS_SUCCESS) fail_status("current realpath", status); + expect_realpath("/SystemRoot/foo/../bar", "/SystemRoot/bar"); - memcpy(input, "missing_realpath_dir/../missing_file", sizeof("missing_realpath_dir/../missing_file")); - append(expected, sizeof(expected), expected[strlen(expected) - 1] == '/' ? "missing_file" : "/missing_file"); - expect_realpath(input, expected); + status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/../../escape"), expected, sizeof(expected), (size_t*)0); + if (status != NTLSTATUS_ACCESS_DENIED) fail_status("resource-root traversal", status); + + status = NtlRealPath(SYS_SV_LITERAL("/SystemRoot/foo\\bar"), expected, sizeof(expected), (size_t*)0); + if (status != NTLSTATUS_INVALID_PARAMETER) fail_status("native backslash", status); print("realpath_smoke: PASS\n"); exit_now(0); diff --git a/code/user/tests/resource_service_smoke/resource_service_smoke.c b/code/user/tests/resource_service_smoke/resource_service_smoke.c new file mode 100644 index 0000000..c3d8170 --- /dev/null +++ b/code/user/tests/resource_service_smoke/resource_service_smoke.c @@ -0,0 +1,200 @@ +// Resource-service smoke test. Proves public RSSRV lookup is capability-based and namespace administration is private. + +#include +#include +#include +#include +#include + +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) }; + (void)SysWriteConsole(&request); +} + + +// Builds one /Device/HarddiskVolumeN resource path. +static SYS_STRING_VIEW make_volume_path(uint32_t index, char* buffer, size_t capacity) { + static const char prefix[] = "/Device/HarddiskVolume"; + size_t prefixLength = sizeof(prefix) - 1u; + if (!index || !buffer || capacity <= prefixLength) + return SysSvFromParts((const char*)0, 0); + + char digits[10]; + size_t digitCount = 0; + do { + digits[digitCount++] = (char)('0' + (index % 10u)); + index /= 10u; + } while (index && digitCount < sizeof(digits)); + if (prefixLength + digitCount + 1u > capacity) + return SysSvFromParts((const char*)0, 0); + + memcpy(buffer, prefix, prefixLength); + for (size_t i = 0; i < digitCount; ++i) + buffer[prefixLength + i] = digits[digitCount - 1u - i]; + buffer[prefixLength + digitCount] = '\0'; + return SysSvFromParts(buffer, prefixLength + digitCount); +} + +static int fail(const char* what, KSTATUS status) { + (void)status; + print("resource_service_smoke: FAIL "); + print(what); + print("\n"); + return 1; +} + +int main(void) { + SYS_HANDLE session = SYS_HANDLE_INVALID; + KSTATUS status = NtlResourceConnect(&session); + if (!KSUCCESS(status)) return fail("connect", status); + + uint32_t kind = NTL_RESOURCE_KIND_INVALID; + status = NtlResourceQuery(session, SYS_SV_LITERAL("/SystemRoot"), &kind); + if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME) + return fail("query /SystemRoot", status); + + // RSSRV assigns its own monotonic /Device/HarddiskVolumeN names to mounted IO_VOLUME instances. + uint32_t publishedVolumes = 0; + uint32_t systemVolumePublished = 0; + for (uint32_t index = 1; index <= 32; ++index) { + char volumePathBuffer[48]; + SYS_STRING_VIEW volumePath = make_volume_path(index, volumePathBuffer, sizeof(volumePathBuffer)); + if (!volumePath.Data) + return fail("build dynamic volume path", NTLSTATUS_FAILURE); + + SYS_HANDLE published = SYS_HANDLE_INVALID; + status = NtlResourceLookup(session, volumePath, SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &published); + if (status == NTLSTATUS_NOT_FOUND) + continue; + if (!KSUCCESS(status)) + return fail("lookup dynamic volume", status); + publishedVolumes++; + + SYS_HANDLE probe = SYS_HANDLE_INVALID; + SYS_OPEN_FILE_AT_REQUEST probeOpen; + memset(&probeOpen, 0, sizeof(probeOpen)); + probeOpen.Size = sizeof(probeOpen); + probeOpen.RootHandle = published; + probeOpen.Path = SYS_SV_LITERAL("system/init.exe"); + probeOpen.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY; + probeOpen.Disposition = SYS_FILE_OPEN_EXISTING; + probeOpen.Flags = SYS_FILE_FLAG_NONE; + probeOpen.HandleOut = &probe; + KSTATUS probeStatus = SysOpenFileAt(&probeOpen); + if (KSUCCESS(probeStatus)) { + systemVolumePublished = 1; + (void)SysCloseHandle(probe); + } else if (probeStatus != NTLSTATUS_NOT_FOUND) { + (void)SysCloseHandle(published); + return fail("probe dynamic volume", probeStatus); + } + (void)SysCloseHandle(published); + } + if (!publishedVolumes || !systemVolumePublished) + return fail("dynamic volume publication", NTLSTATUS_NOT_FOUND); + + SYS_HANDLE volume = SYS_HANDLE_INVALID; + status = NtlResourceLookup(session, SYS_SV_LITERAL("/SystemRoot"), + SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &volume); + if (!KSUCCESS(status)) return fail("lookup /SystemRoot", status); + + SYS_HANDLE file = SYS_HANDLE_INVALID; + SYS_OPEN_FILE_AT_REQUEST open; + memset(&open, 0, sizeof(open)); + open.Size = sizeof(open); + open.RootHandle = volume; + open.Path = SYS_SV_LITERAL("system/init.exe"); + open.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY; + open.Disposition = SYS_FILE_OPEN_EXISTING; + open.Flags = SYS_FILE_FLAG_NONE; + open.HandleOut = &file; + status = SysOpenFileAt(&open); + if (!KSUCCESS(status)) return fail("open through resource volume", status); + (void)SysCloseHandle(file); + (void)SysCloseHandle(volume); + + // RESOLVE returns the longest bound resource prefix and leaves only a relative tail for I/O. + SYS_HANDLE resolvedRoot = SYS_HANDLE_INVALID; + uint32_t resolvedLength = 0; + status = NtlResourceResolve(session, SYS_SV_LITERAL("/SystemRoot/system/init.exe"), + NTL_RESOURCE_FLAG_NONE, &resolvedRoot, &kind, &resolvedLength); + if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME || + resolvedLength != sizeof("/SystemRoot") - 1u) + return fail("resolve longest prefix", status); + + file = SYS_HANDLE_INVALID; + memset(&open, 0, sizeof(open)); + open.Size = sizeof(open); + open.RootHandle = resolvedRoot; + open.Path = SYS_SV_LITERAL("system/init.exe"); + open.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY; + open.Disposition = SYS_FILE_OPEN_EXISTING; + open.Flags = SYS_FILE_FLAG_NONE; + open.HandleOut = &file; + status = SysOpenFileAt(&open); + if (!KSUCCESS(status)) return fail("open through resolved prefix", status); + (void)SysCloseHandle(file); + (void)SysCloseHandle(resolvedRoot); + + // MODIFY resolution returns write authority without exporting RSSRV's private TRANSFER right. + resolvedRoot = SYS_HANDLE_INVALID; + resolvedLength = 0; + status = NtlResourceResolve(session, SYS_SV_LITERAL("/SystemRoot/tests/resource_f.tmp"), + NTL_RESOURCE_RESOLVE_FLAG_MODIFY, + &resolvedRoot, &kind, &resolvedLength); + if (!KSUCCESS(status) || kind != NTL_RESOURCE_KIND_VOLUME || + resolvedLength != sizeof("/SystemRoot") - 1u) + return fail("resolve modify authority", status); + + file = SYS_HANDLE_INVALID; + memset(&open, 0, sizeof(open)); + open.Size = sizeof(open); + open.RootHandle = resolvedRoot; + open.Path = SYS_SV_LITERAL("tests/resource_f.tmp"); + open.Access = SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY; + open.Disposition = SYS_FILE_CREATE_ALWAYS; + open.Flags = SYS_FILE_FLAG_NONE; + open.HandleOut = &file; + status = SysOpenFileAt(&open); + if (!KSUCCESS(status)) return fail("create through resolved modify root", status); + (void)SysCloseHandle(file); + + SYS_PATH_AT_REQUEST remove = { sizeof(remove), resolvedRoot, SYS_SV_LITERAL("tests/resource_f.tmp") }; + status = SysDeleteAt(&remove); + if (!KSUCCESS(status)) return fail("delete through resolved modify root", status); + (void)SysCloseHandle(resolvedRoot); + + SYS_HANDLE overbroad = SYS_HANDLE_INVALID; + status = NtlResourceLookup(session, SYS_SV_LITERAL("/SystemRoot"), + SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_VOLUME_RIGHT_OPEN, &overbroad); + if (status != NTLSTATUS_ACCESS_DENIED || overbroad != SYS_HANDLE_INVALID) + return fail("export-rights ceiling", status); + + SYS_HANDLE missing = SYS_HANDLE_INVALID; + status = NtlResourceLookup(session, SYS_SV_LITERAL("/Missing"), SYS_RIGHT_QUERY, &missing); + if (status != NTLSTATUS_NOT_FOUND || missing != SYS_HANDLE_INVALID) + return fail("missing lookup", status); + + SYS_HANDLE event = SYS_HANDLE_INVALID; + status = SysCreateEvent(0, 0, &event); + if (!KSUCCESS(status)) return fail("create bind probe", status); + status = NtlResourceBind(session, SYS_SV_LITERAL("/PublicBindMustFail"), + NTL_RESOURCE_KIND_CAPABILITY, event, + SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY, SYS_RIGHT_QUERY); + (void)SysCloseHandle(event); + if (status != NTLSTATUS_ACCESS_DENIED) + return fail("public bind authority", status); + + status = NtlResourceQuery(session, SYS_SV_LITERAL("/PublicBindMustFail"), &kind); + if (status != NTLSTATUS_NOT_FOUND) + return fail("rejected bind visibility", status); + + status = NtlResourceQuery(session, SYS_SV_LITERAL("/SystemRoot/../Bad"), &kind); + if (status != NTLSTATUS_INVALID_PARAMETER) + return fail("canonical path rejection", status); + + status = NtlResourceClose(session); + if (!KSUCCESS(status)) return fail("close session", status); + print("resource_service_smoke: PASS\n"); + return 0; +} diff --git a/code/user/tests/security_smoke/security_smoke.c b/code/user/tests/security_smoke/security_smoke.c new file mode 100644 index 0000000..c582cb6 --- /dev/null +++ b/code/user/tests/security_smoke/security_smoke.c @@ -0,0 +1,319 @@ +// Security-context smoke test. This validates authority attenuation, assignment, and ambient privilege enforcement. + +#include +#include +#include +#include +#include +#include + +// Writes one string from the privileged parent process. +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) }; + (void)SysWriteConsole(&request); +} + +// Writes one unsigned integer from the privileged parent process. +static void print_u32(uint32_t value) { + char digits[16]; + uint32_t count = 0; + if (value == 0) { + print("0"); + return; + } + while (value && count < sizeof(digits)) { + digits[count++] = (char)('0' + value % 10u); + value /= 10u; + } + while (count) { + char one[2] = { digits[--count], 0 }; + print(one); + } +} + +// Exits without relying on a C runtime return path. +static void exit_now(int32_t code) { + while (true) (void)SysExitProcess(code); +} + +// Fails the privileged parent with a visible diagnostic. +static void fail_status(const char* operation, KSTATUS status) { + print("security_smoke: "); + print(operation); + print(" status="); + print_u32(status); + print("\n"); + exit_now(1); +} + +// Parses one unsigned decimal value without requiring formatted libc helpers. +static bool parse_u32(SYS_STRING_VIEW text, size_t start, uint32_t* valueOut) { + if (!valueOut || start >= text.Length) return false; + uint32_t value = 0; + bool any = false; + for (size_t i = start; i < text.Length; i++) { + char c = text.Data[i]; + if (c < '0' || c > '9') return false; + uint32_t digit = (uint32_t)(c - '0'); + if (value > (UINT32_MAX - digit) / 10u) return false; + value = value * 10u + digit; + any = true; + } + if (!any) return false; + *valueOut = value; + return true; +} + +// Appends one unsigned decimal value to a bounded command string. +static bool append_u32(char* buffer, size_t capacity, size_t* lengthInOut, uint32_t value) { + if (!buffer || !lengthInOut || *lengthInOut >= capacity) return false; + char reverse[16]; + size_t count = 0; + do { + reverse[count++] = (char)('0' + value % 10u); + value /= 10u; + } while (value && count < sizeof(reverse)); + if (*lengthInOut + count + 1 > capacity) return false; + while (count) buffer[(*lengthInOut)++] = reverse[--count]; + buffer[*lengthInOut] = '\0'; + return true; +} + +// Runs inside the stripped child and returns a distinct failure code for each leaked ambient authority. +static int run_restricted_child(uint32_t parentPid) { + SYS_SECURITY_CONTEXT_INFO info; + memset(&info, 0, sizeof(info)); + info.Size = sizeof(info); + KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &info); + if (!KSUCCESS(status)) return 10; + if (info.EffectivePrivileges || info.DelegablePrivileges || + info.ServiceClaims || info.DelegableServiceClaims) + return 11; + + SYS_HANDLE process = SYS_HANDLE_INVALID; + status = SysOpenProcess(parentPid, SYS_RIGHT_QUERY, &process); + if (status != NTLSTATUS_ACCESS_DENIED) return 12; + + size_t processCount = 0; + status = SysGetProcessList((SYS_PROCESS_INFO*)0, 0, &processCount); + if (status != NTLSTATUS_ACCESS_DENIED) return 13; + + size_t required = 0; + status = SysQueryKernelStats(SYS_KERNEL_STATS_SCHEDULER, (void*)0, 0, &required); + if (status != NTLSTATUS_ACCESS_DENIED) return 14; + + SYS_HANDLE inputQueue = SYS_HANDLE_INVALID; + status = SysOpenInputQueue(0, &inputQueue); + if (status != NTLSTATUS_ACCESS_DENIED) return 15; + + SYS_DISPLAY_INFO displayInfo; + memset(&displayInfo, 0, sizeof(displayInfo)); + status = SysQueryDisplayInfo(&displayInfo); + if (status != NTLSTATUS_ACCESS_DENIED) return 16; + + SYS_HANDLE socketHandle = SYS_HANDLE_INVALID; + status = SysCreateSocket(0, 0, 0, &socketHandle); + if (status != NTLSTATUS_ACCESS_DENIED) return 17; + + SYS_PRINT_DEBUG_REQUEST debugRequest = { sizeof(debugRequest), SysSvFromParts((const char*)0, 0) }; + status = SysPrintDebug(&debugRequest); + if (status != NTLSTATUS_ACCESS_DENIED) return 18; + + SYS_WRITE_CONSOLE_REQUEST consoleRequest = { sizeof(consoleRequest), SysSvFromParts((const char*)0, 0) }; + status = SysWriteConsole(&consoleRequest); + if (status != NTLSTATUS_ACCESS_DENIED) return 19; + + SYS_PEB* peb = SysGetPeb(); + if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) return 20; + SYS_HANDLE file = SYS_HANDLE_INVALID; + status = NtlOpenFile(peb->ImagePath, + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &file); + if (!KSUCCESS(status)) return 21; + (void)SysCloseHandle(file); + + uint32_t endpointCount = 0; + SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST enumerateAudio; + memset(&enumerateAudio, 0, sizeof(enumerateAudio)); + enumerateAudio.Size = sizeof(enumerateAudio); + enumerateAudio.CountOut = &endpointCount; + status = SysAudioEnumerateEndpoints(&enumerateAudio); + if (status != NTLSTATUS_ACCESS_DENIED) return 22; + + SYS_HANDLE queue = SYS_HANDLE_INVALID; + status = SysCreateQueue(&queue); + if (!KSUCCESS(status)) return 23; + SYS_PUBLISH_QUEUE_REQUEST publishQueue = { + sizeof(publishQueue), SysSvFromCstr("security_smoke.restricted"), queue + }; + status = SysPublishQueue(&publishQueue); + (void)SysCloseHandle(queue); + if (status != NTLSTATUS_ACCESS_DENIED) return 24; + + SYS_HANDLE listener = SYS_HANDLE_INVALID; + SYS_CREATE_PORT_REQUEST createPort; + memset(&createPort, 0, sizeof(createPort)); + createPort.Size = sizeof(createPort); + createPort.HandleOut = &listener; + status = SysCreatePort(&createPort); + if (!KSUCCESS(status)) return 25; + status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, listener); + (void)SysCloseHandle(listener); + if (status != NTLSTATUS_ACCESS_DENIED) return 26; + + uint32_t selfPid = SysGetCurrentPid(); + status = SysOpenProcess(selfPid, SYS_RIGHT_QUERY, &process); + if (!KSUCCESS(status)) return 27; + status = SysCloseHandle(process); + if (!KSUCCESS(status)) return 28; + + return 0; +} + +// Creates one zero-authority security context from the caller's current delegable root. +static SYS_HANDLE derive_stripped_context(void) { + SYS_HANDLE handle = SYS_HANDLE_INVALID; + SYS_DERIVE_SECURITY_CONTEXT_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; + request.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; + request.ContextHandleOut = &handle; + KSTATUS status = SysDeriveSecurityContext(&request); + if (!KSUCCESS(status)) fail_status("derive stripped context", status); + return handle; +} + +// Runs the privileged parent half of the security-context smoke test. +static int run_parent(void) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) + fail_status("missing image path", NTLSTATUS_FAILURE); + + SYS_SECURITY_CONTEXT_INFO rootInfo; + memset(&rootInfo, 0, sizeof(rootInfo)); + rootInfo.Size = sizeof(rootInfo); + KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &rootInfo); + if (!KSUCCESS(status)) fail_status("query current context", status); + if (rootInfo.EffectivePrivileges != SYS_PRIV_ALL || + rootInfo.DelegablePrivileges != SYS_PRIV_ALL || + rootInfo.ServiceClaims != SYS_SERVICE_CLAIM_ALL || + rootInfo.DelegableServiceClaims != SYS_SERVICE_CLAIM_ALL) + fail_status("bootstrap context authority", NTLSTATUS_FAILURE); + + SYS_HANDLE restricted = derive_stripped_context(); + SYS_SECURITY_CONTEXT_INFO restrictedInfo; + memset(&restrictedInfo, 0, sizeof(restrictedInfo)); + restrictedInfo.Size = sizeof(restrictedInfo); + status = SysQuerySecurityContext(restricted, &restrictedInfo); + if (!KSUCCESS(status)) fail_status("query stripped context", status); + if (restrictedInfo.SecurityId <= rootInfo.SecurityId || + restrictedInfo.EffectivePrivileges || restrictedInfo.DelegablePrivileges || + restrictedInfo.ServiceClaims || restrictedInfo.DelegableServiceClaims) + fail_status("stripped context contents", NTLSTATUS_FAILURE); + + SYS_HANDLE illegal = SYS_HANDLE_INVALID; + SYS_DERIVE_SECURITY_CONTEXT_REQUEST amplify; + memset(&lify, 0, sizeof(amplify)); + amplify.Size = sizeof(amplify); + amplify.SourceContext = restricted; + amplify.EffectivePrivileges = SYS_PRIV_PROCESS_OPEN_ANY; + amplify.ContextHandleOut = &illegal; + status = SysDeriveSecurityContext(&lify); + if (status != NTLSTATUS_ACCESS_DENIED) + fail_status("blocked privilege amplification", status); + + memset(&lify, 0, sizeof(amplify)); + amplify.Size = sizeof(amplify); + amplify.SourceContext = restricted; + amplify.ServiceClaims = SYS_SERVICE_CLAIM_RESOURCE; + amplify.ContextHandleOut = &illegal; + status = SysDeriveSecurityContext(&lify); + if (status != NTLSTATUS_ACCESS_DENIED) + fail_status("blocked service-claim amplification", status); + + SYS_HANDLE queryOnly = SYS_HANDLE_INVALID; + SYS_DERIVE_HANDLE_REQUEST deriveHandle = { + sizeof(deriveHandle), restricted, SYS_RIGHT_QUERY, &queryOnly + }; + status = SysDeriveHandle(&deriveHandle); + if (!KSUCCESS(status)) fail_status("derive query-only context handle", status); + + SYS_HANDLE rejectedChild = SYS_HANDLE_INVALID; + NTL_CREATE_SYSTEM_PROCESS_REQUEST rejectedCreate; + memset(&rejectedCreate, 0, sizeof(rejectedCreate)); + rejectedCreate.Size = sizeof(rejectedCreate); + rejectedCreate.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + rejectedCreate.Path = peb->ImagePath; + rejectedCreate.CommandLine = SysSvFromCstr("restricted:1"); + rejectedCreate.SecurityContext = queryOnly; + rejectedCreate.ResolveModuleName = NtlResolveSystemModuleName; + rejectedCreate.ProcessHandleOut = &rejectedChild; + status = NtlCreateSystemProcess(&rejectedCreate); + if (status != NTLSTATUS_ACCESS_DENIED) + fail_status("context assignment right", status); + (void)SysCloseHandle(queryOnly); + + char command[48] = "restricted:"; + size_t commandLength = strlen(command); + if (!append_u32(command, sizeof(command), &commandLength, SysGetCurrentPid())) + fail_status("build child command", NTLSTATUS_BUFFER_TOO_SMALL); + + SYS_HANDLE child = SYS_HANDLE_INVALID; + NTL_CREATE_SYSTEM_PROCESS_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + create.Path = peb->ImagePath; + create.CommandLine = SysSvFromCstr(command); + create.SecurityContext = restricted; + create.ResolveModuleName = NtlResolveSystemModuleName; + create.ProcessHandleOut = &child; + status = NtlCreateSystemProcess(&create); + if (!KSUCCESS(status)) fail_status("create restricted child", status); + + status = SysWait(child, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(status)) fail_status("wait restricted child", status); + int32_t childExit = -1; + status = SysGetProcessExitCode(child, &childExit); + if (!KSUCCESS(status)) fail_status("query restricted child", status); + (void)SysCloseHandle(child); + if (childExit != 0) { + print("security_smoke: restricted child failure="); + print_u32((uint32_t)childExit); + print("\n"); + return 1; + } + + status = SysCloseHandle(restricted); + if (!KSUCCESS(status)) fail_status("close stripped context", status); + + SYS_HANDLE later = derive_stripped_context(); + SYS_SECURITY_CONTEXT_INFO laterInfo; + memset(&laterInfo, 0, sizeof(laterInfo)); + laterInfo.Size = sizeof(laterInfo); + status = SysQuerySecurityContext(later, &laterInfo); + if (!KSUCCESS(status)) fail_status("query later context", status); + if (laterInfo.SecurityId <= restrictedInfo.SecurityId) + fail_status("security id reuse", NTLSTATUS_FAILURE); + (void)SysCloseHandle(later); + + print("security_smoke: PASS\n"); + return 0; +} + +// Runs either the privileged parent or the stripped child based on the process command line. +int main(void) { + SYS_PEB* peb = SysGetPeb(); + if (!peb) return 2; + const char prefix[] = "restricted:"; + if (peb->CommandLine.Length > sizeof(prefix) - 1u && + memcmp(peb->CommandLine.Data, prefix, sizeof(prefix) - 1u) == 0) { + uint32_t parentPid = 0; + if (!parse_u32(peb->CommandLine, sizeof(prefix) - 1u, &parentPid)) return 3; + return run_restricted_child(parentPid); + } + return run_parent(); +} diff --git a/code/user/tests/shared_mmap/shared_mmap.c b/code/user/tests/shared_mmap/shared_mmap.c index eb610d1..682656b 100644 --- a/code/user/tests/shared_mmap/shared_mmap.c +++ b/code/user/tests/shared_mmap/shared_mmap.c @@ -5,6 +5,7 @@ #include #include #include +#include #define PAGE_SIZE_BYTES 4096u #define TEST_SIZE (PAGE_SIZE_BYTES * 3u) @@ -14,7 +15,7 @@ static uint8_t gExpected[TEST_SIZE]; static uint8_t gReadback[TEST_SIZE]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -32,6 +33,45 @@ static void print_u64(uint64_t v) { } } +// Prints one pointer-sized value in hexadecimal for VM alias diagnostics. +static void print_hex_uintptr(uintptr_t value) { + static const char digits[] = "0123456789abcdef"; + char text[2 + sizeof(uintptr_t) * 2 + 1]; + size_t pos = 0; + text[pos++] = '0'; + text[pos++] = 'x'; + for (int shift = (int)(sizeof(uintptr_t) * 8u - 4u); shift >= 0; shift -= 4) + text[pos++] = digits[(value >> (uint32_t)shift) & 0xFu]; + text[pos] = 0; + print(text); +} + +// Prints the VM region containing one address without requiring physical-address disclosure. +static void print_vm_region(const char* name, const void* address) { + SYS_VM_REGION_INFO info; + memset(&info, 0, sizeof(info)); + uint32_t status = SysQueryVm((void*)address, &info); + + print("shared_mmap: vm "); + print(name); + print(" address="); + print_hex_uintptr((uintptr_t)address); + if (status != NTLSTATUS_SUCCESS) { + print(" query-status="); + print_u64(status); + print("\n"); + return; + } + + print(" base="); + print_hex_uintptr((uintptr_t)info.Base); + print(" pages="); + print_u64(info.PageCount); + print(" type="); + print_u64(info.Type); + print("\n"); +} + static void exit_now(int code) { while (true) SysExitProcess(code); } @@ -52,33 +92,48 @@ static void fail_status(const char* what, uint32_t status) { exit_now(1); } +// Reports the first byte mismatch before terminating the regression test. +static void fail_mismatch(const char* what, const uint8_t* actual, const uint8_t* expected, size_t size) { + for (size_t i = 0; i < size; i++) { + uint8_t actualByte = actual[i]; + uint8_t expectedByte = expected[i]; + if (actualByte == expectedByte) continue; + print("shared_mmap: "); + print(what); + print(" offset="); + print_u64(i); + print(" expected="); + print_u64(expectedByte); + print(" actual="); + print_u64(actualByte); + print("\n"); + exit_now(1); + } + fail(what); +} + static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) { memset(out, 0, sizeof(*out)); return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0); } static void delete_path(const char* path) { - SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; - (void)SysDeleteFile(&req); + (void)NtlDeleteFile(SysSvFromCstr(path)); } static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { static const char* paths[] = { "shared_mmap.tmp", - "/shared_mmap.tmp", - "1:/shared_mmap.tmp", - "0:/shared_mmap.tmp", }; for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) { uint32_t h = 0; delete_path(paths[i]); - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, - SYS_FILE_CREATE_NEW, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_CREATE_NEW, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) { *fileOut = h; if (pathOut) *pathOut = paths[i]; @@ -90,12 +145,11 @@ static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) { static uint32_t open_path(const char* path, uint32_t* fileOut) { uint32_t h = 0; - SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &h); - uint32_t s = SysOpenFile(&req); + uint32_t s = NtlOpenFile(SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &h); if (s == NTLSTATUS_SUCCESS) *fileOut = h; return s; @@ -123,9 +177,54 @@ static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOu if (gotOut) *gotOut = got; } +// Returns the deterministic initial byte used to seed the mapped file. +static uint8_t initial_byte(size_t index) { + return (uint8_t)(0x20u + (uint8_t)(index * 7u + (index >> 5))); +} + +// Returns the expected byte after the first shared-mapping write range. +static uint8_t expected_after_first_mapped_write(size_t index) { + if (index >= 128u && index < 128u + 3000u) + return (uint8_t)(0xA0u + (uint8_t)index); + return initial_byte(index); +} + +// Returns the expected byte after both shared-mapping write ranges. +static uint8_t expected_after_mapped_writes(size_t index) { + if (index >= 128u && index < 128u + 3000u) + return (uint8_t)(0xA0u + (uint8_t)index); + if (index >= PAGE_SIZE_BYTES + 17u && index < PAGE_SIZE_BYTES + 17u + 2048u) + return (uint8_t)(0x50u + (uint8_t)(index >> 1)); + return initial_byte(index); +} + +// Verifies the mutable expected buffer against the deterministic write pattern. +static void verify_expected_buffer(const char* stage, int secondRangeWritten) { + for (size_t i = 0; i < TEST_SIZE; ++i) { + uint8_t actualByte = gExpected[i]; + uint8_t expectedByte = secondRangeWritten + ? expected_after_mapped_writes(i) + : expected_after_first_mapped_write(i); + if (actualByte == expectedByte) + continue; + + print("shared_mmap: expected buffer corrupted "); + print(stage); + print(" offset="); + print_u64(i); + print(" expected="); + print_u64(expectedByte); + print(" actual="); + print_u64(actualByte); + print("\n"); + exit_now(1); + } +} + +// Fills the initial file data and its comparison buffer. static void fill_initial(void) { for (size_t i = 0; i < TEST_SIZE; ++i) { - gInitial[i] = (uint8_t)(0x20u + (uint8_t)(i * 7u + (i >> 5))); + gInitial[i] = initial_byte(i); gExpected[i] = gInitial[i]; } } @@ -148,31 +247,53 @@ int main(void) { if (s != NTLSTATUS_SUCCESS) fail_status("initial flush", s); uint32_t mapping = 0; - s = SysCreateFileMapping(file, SYS_VM_READWRITE, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping); + SYS_CREATE_FILE_MAPPING_REQUEST createMapping = { + sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), file, SYS_VM_READWRITE, + SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping + }; + s = SysCreateFileMapping(&createMapping); if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s); uint8_t* view = 0; - s = SysMapFile(mapping, 0, TEST_SIZE, SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_WRITE, - SYS_MAP_FLAG_NONE, (void**)&view); + SYS_MAP_FILE_REQUEST mapFile = { + sizeof(SYS_MAP_FILE_REQUEST), mapping, + SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_WRITE, SYS_MAP_FLAG_NONE, + 0, TEST_SIZE, (void**)&view + }; + s = SysMapFile(&mapFile); if (s != NTLSTATUS_SUCCESS) fail_status("map shared write", s); + print_vm_region("view", view); + print_vm_region("expected", gExpected); + print_vm_region("readback", gReadback); + + // Keep the source value live across the faulting mapped store. This also + // verifies that a repaired page fault preserves the interrupted SIMD/GPR state. for (size_t i = 128; i < 128 + 3000; ++i) { - view[i] = (uint8_t)(0xA0u + (uint8_t)i); - gExpected[i] = view[i]; + uint8_t value = (uint8_t)(0xA0u + (uint8_t)i); + view[i] = value; + gExpected[i] = value; } + verify_expected_buffer("after first mapped write range", 0); + for (size_t i = PAGE_SIZE_BYTES + 17; i < PAGE_SIZE_BYTES + 17 + 2048; ++i) { - view[i] = (uint8_t)(0x50u + (uint8_t)(i >> 1)); - gExpected[i] = view[i]; + uint8_t value = (uint8_t)(0x50u + (uint8_t)(i >> 1)); + view[i] = value; + gExpected[i] = value; } + verify_expected_buffer("after mapped writes", 1); SYS_FILE_CACHE_STATS beforeFlush; SYS_FILE_CACHE_STATS afterFlush; if (query_cache(&beforeFlush) != NTLSTATUS_SUCCESS) fail("pre-flush stats failed"); + verify_expected_buffer("after pre-flush stats", 1); s = SysFlushFile(file); if (s != NTLSTATUS_SUCCESS) fail_status("flush mapped writes", s); + verify_expected_buffer("after mapped flush", 1); if (query_cache(&afterFlush) != NTLSTATUS_SUCCESS) fail("post-flush stats failed"); + verify_expected_buffer("after post-flush stats", 1); if (afterFlush.MappedDirtyPages <= beforeFlush.MappedDirtyPages) fail("mapped dirty pages were not harvested"); if (afterFlush.WritebackBytes <= beforeFlush.WritebackBytes) @@ -181,12 +302,15 @@ int main(void) { checked_seek(file, 0, "seek after mapped flush"); size_t got = 0; checked_read(file, gReadback, TEST_SIZE, &got, "read after mapped flush"); + verify_expected_buffer("after mapped-flush read", 1); if (got != TEST_SIZE) fail("read after mapped flush size mismatch"); - if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) fail("mapped flush data mismatch"); + if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) + fail_mismatch("mapped flush data mismatch", gReadback, gExpected, TEST_SIZE); for (size_t i = PAGE_SIZE_BYTES * 2 + 3; i < PAGE_SIZE_BYTES * 2 + 900; ++i) { - view[i] = (uint8_t)(0xC0u ^ (uint8_t)i); - gExpected[i] = view[i]; + uint8_t value = (uint8_t)(0xC0u ^ (uint8_t)i); + view[i] = value; + gExpected[i] = value; } s = SysFreeVm(view); @@ -206,7 +330,8 @@ int main(void) { checked_read(file, gReadback, TEST_SIZE, &got, "read after close"); if (got != TEST_SIZE) fail("read after close size mismatch"); - if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) fail("unmap/close data mismatch"); + if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) + fail_mismatch("unmap/close data mismatch", gReadback, gExpected, TEST_SIZE); s = SysCloseHandle(file); file = 0; diff --git a/code/user/tests/socket_addr_smoke/socket_addr_smoke.c b/code/user/tests/socket_addr_smoke/socket_addr_smoke.c index 79f136c..83fe3b9 100644 --- a/code/user/tests/socket_addr_smoke/socket_addr_smoke.c +++ b/code/user/tests/socket_addr_smoke/socket_addr_smoke.c @@ -8,7 +8,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -26,7 +26,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -67,6 +67,7 @@ static void check_ipv4_resolve(void) { SYS_RESOLVE_HOST_REQUEST req; memset(&address, 0, sizeof(address)); memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr("127.0.0.1"); req.Family = SYS_AF_INET; req.AddressOut = &address; @@ -87,6 +88,7 @@ static void check_ipv6_resolve(void) { SYS_RESOLVE_HOST_REQUEST req; memset(&address, 0, sizeof(address)); memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); req.Name = SysSvFromCstr("::1"); req.Family = SYS_AF_INET6; req.AddressOut = &address; diff --git a/code/user/tests/stats_abi_smoke/stats_abi_smoke.c b/code/user/tests/stats_abi_smoke/stats_abi_smoke.c index f0e2353..ef1a922 100644 --- a/code/user/tests/stats_abi_smoke/stats_abi_smoke.c +++ b/code/user/tests/stats_abi_smoke/stats_abi_smoke.c @@ -8,7 +8,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -26,7 +26,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -92,11 +92,34 @@ static void check_fixed_stats(void) { if (filesystem.Size != sizeof(filesystem)) fail("filesystem size field mismatch"); if (filesystem.Version != SYS_STATS_VERSION) fail("filesystem version mismatch"); + SYS_VOLUME_STATS volumeStats; + status = SysQueryKernelStats(SYS_KERNEL_STATS_VOLUME, &volumeStats, sizeof(volumeStats), 0); + if (status != NTLSTATUS_SUCCESS) fail_status("volume exact", status); + if (volumeStats.Size != sizeof(volumeStats)) fail("volume size field mismatch"); + if (volumeStats.Version != SYS_STATS_VERSION) fail("volume version mismatch"); + if (volumeStats.CreatedVolumes == 0) fail("volume created count is zero"); + if (volumeStats.MountedVolumes == 0) fail("volume mounted count is zero"); + if (volumeStats.ProviderDestroys > volumeStats.RemovedVolumes) fail("volume destroy count exceeds removals"); + + SYS_VOLUME_MANAGER_STATS volumeManagerStats; + status = SysQueryKernelStats(SYS_KERNEL_STATS_VOLUME_MANAGER, &volumeManagerStats, sizeof(volumeManagerStats), 0); + if (status != NTLSTATUS_SUCCESS) fail_status("volume manager exact", status); + if (volumeManagerStats.Size != sizeof(volumeManagerStats)) fail("volume manager size field mismatch"); + if (volumeManagerStats.Version != SYS_STATS_VERSION) fail("volume manager version mismatch"); + if (volumeManagerStats.Snapshots == 0) fail("volume manager snapshot count is zero"); + if (volumeManagerStats.VolumeOpens == 0) fail("volume manager open count is zero"); + SYS_EXCEPTION_STATS exceptionStats; status = SysQueryKernelStats(SYS_KERNEL_STATS_EXCEPTION, &exceptionStats, sizeof(exceptionStats), 0); if (status != NTLSTATUS_SUCCESS) fail_status("exception exact", status); if (exceptionStats.Size != sizeof(exceptionStats)) fail("exception size field mismatch"); if (exceptionStats.Version != SYS_STATS_VERSION) fail("exception version mismatch"); + + SYS_PORT_STATS portStats; + status = SysQueryKernelStats(SYS_KERNEL_STATS_PORT, &portStats, sizeof(portStats), 0); + if (status != NTLSTATUS_SUCCESS) fail_status("port exact", status); + if (portStats.Size != sizeof(portStats)) fail("port size field mismatch"); + if (portStats.Version != SYS_STATS_VERSION) fail("port version mismatch"); } // Verifies CPU stats use a size-reported trailing-record layout. diff --git a/code/user/tests/surface_invalid_rect/surface_invalid_rect.c b/code/user/tests/surface_invalid_rect/surface_invalid_rect.c index 61f074a..f034115 100644 --- a/code/user/tests/surface_invalid_rect/surface_invalid_rect.c +++ b/code/user/tests/surface_invalid_rect/surface_invalid_rect.c @@ -10,7 +10,7 @@ #define SURFACE_H 24u static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -24,7 +24,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/surface_multithread_present/surface_multithread_present.c b/code/user/tests/surface_multithread_present/surface_multithread_present.c index 91f6725..08d7593 100644 --- a/code/user/tests/surface_multithread_present/surface_multithread_present.c +++ b/code/user/tests/surface_multithread_present/surface_multithread_present.c @@ -23,7 +23,7 @@ typedef struct WORKER_STATE { static WORKER_STATE gWorkers[THREAD_COUNT]; static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -37,7 +37,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -126,7 +126,7 @@ int main(void) { gWorkers[i].Pitch = pitch; gWorkers[i].Status = NTLSTATUS_PENDING; SYS_CREATE_THREAD_REQUEST req = { - (uintptr_t)worker_main, + sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)worker_main, (uintptr_t)&gWorkers[i], 1, 0, diff --git a/code/user/tests/surface_smoke/surface_smoke.c b/code/user/tests/surface_smoke/surface_smoke.c index 14b257a..b68f1bb 100644 --- a/code/user/tests/surface_smoke/surface_smoke.c +++ b/code/user/tests/surface_smoke/surface_smoke.c @@ -10,7 +10,7 @@ #define SURFACE_H 48u static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -24,7 +24,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/syscall_contract_smoke/syscall_contract_smoke.c b/code/user/tests/syscall_contract_smoke/syscall_contract_smoke.c index c7e77c5..0652eaa 100644 --- a/code/user/tests/syscall_contract_smoke/syscall_contract_smoke.c +++ b/code/user/tests/syscall_contract_smoke/syscall_contract_smoke.c @@ -1,15 +1,16 @@ // Native syscall contract smoke test. -// Verifies public tick units and process exit-code round trips. +// Verifies public syscall ABI contracts, tick units, and process exit-code round trips. #include #include #include +#include #define CHILD_EXIT_CODE ((int32_t)-1234567) // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -27,7 +28,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -89,13 +90,17 @@ static void check_exit_code_contract(void) { if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0) fail("missing image path"); uint32_t child = 0; - SYS_CREATE_PROCESS_REQUEST req = { - peb->ImagePath, - SysSvFromCstr("child"), - &child, - }; + NTL_CREATE_SYSTEM_PROCESS_REQUEST req; + memset(&req, 0, sizeof(req)); + req.Size = sizeof(req); + req.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + req.Path = peb->ImagePath; + req.CommandLine = SysSvFromCstr("child"); + req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + req.ResolveModuleName = NtlResolveSystemModuleName; + req.ProcessHandleOut = &child; - uint32_t s = SysCreateProcess(&req); + uint32_t s = NtlCreateSystemProcess(&req); if (s != NTLSTATUS_SUCCESS) fail_status("create child", s); s = SysWait(child, SYS_WAIT_FLAG_NONE); @@ -109,6 +114,22 @@ static void check_exit_code_contract(void) { SysCloseHandle(child); } +// Verifies request structures reject stale layouts instead of accepting compatibility prefixes. +static void check_request_size_contract(void) { + SYS_PRINT_DEBUG_REQUEST request = { + sizeof(SYS_PRINT_DEBUG_REQUEST) - 1u, + SysSvFromCstr("size check must not print"), + }; + uint32_t s = SysPrintDebug(&request); + if (s != NTLSTATUS_INVALID_PARAMETER) + fail_status("undersized request was accepted", s); + + request.Size = sizeof(SYS_PRINT_DEBUG_REQUEST) + 8u; + s = SysPrintDebug(&request); + if (s != NTLSTATUS_INVALID_PARAMETER) + fail_status("oversized request was accepted", s); +} + // Runs the syscall ABI contract smoke test. int main(void) { SYS_PEB* peb = SysGetPeb(); @@ -118,6 +139,7 @@ int main(void) { print("syscall_contract_smoke: start\n"); check_tick_contract(); + check_request_size_contract(); check_exit_code_contract(); print("syscall_contract_smoke: PASS\n"); exit_now(0); diff --git a/code/user/tests/timer_smoke/timer_smoke.c b/code/user/tests/timer_smoke/timer_smoke.c index 51dda9c..2964927 100644 --- a/code/user/tests/timer_smoke/timer_smoke.c +++ b/code/user/tests/timer_smoke/timer_smoke.c @@ -6,7 +6,7 @@ // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -24,7 +24,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/win32_condition_variable_smoke/win32_condition_variable_smoke.c b/code/user/tests/win32_condition_variable_smoke/win32_condition_variable_smoke.c index 20aa869..d2542d4 100644 --- a/code/user/tests/win32_condition_variable_smoke/win32_condition_variable_smoke.c +++ b/code/user/tests/win32_condition_variable_smoke/win32_condition_variable_smoke.c @@ -47,7 +47,7 @@ static uint32_t gWorkerFailure; // Writes one test message to the native console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } diff --git a/code/user/tests/win32_exception_smoke/win32_exception_smoke.c b/code/user/tests/win32_exception_smoke/win32_exception_smoke.c index c2033f4..780700a 100644 --- a/code/user/tests/win32_exception_smoke/win32_exception_smoke.c +++ b/code/user/tests/win32_exception_smoke/win32_exception_smoke.c @@ -16,7 +16,7 @@ static volatile uint32_t gRaisedHits; // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -34,7 +34,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/win32_module_lookup_smoke/win32_module_lookup_smoke.c b/code/user/tests/win32_module_lookup_smoke/win32_module_lookup_smoke.c index f2dfb6b..527b5b2 100644 --- a/code/user/tests/win32_module_lookup_smoke/win32_module_lookup_smoke.c +++ b/code/user/tests/win32_module_lookup_smoke/win32_module_lookup_smoke.c @@ -13,7 +13,7 @@ __declspec(dllimport) void* GetProcAddress(HANDLE module, const char* procedureN // Writes one test message to the native console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } diff --git a/code/user/tests/win32_mutex_smoke/win32_mutex_smoke.c b/code/user/tests/win32_mutex_smoke/win32_mutex_smoke.c index eb0f313..a5ddf75 100644 --- a/code/user/tests/win32_mutex_smoke/win32_mutex_smoke.c +++ b/code/user/tests/win32_mutex_smoke/win32_mutex_smoke.c @@ -36,7 +36,7 @@ static BOOL gWorkerRelease; // Writes one test message to the native console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } diff --git a/code/user/tests/win32_seh_smoke/win32_seh_smoke.c b/code/user/tests/win32_seh_smoke/win32_seh_smoke.c index 11c3f34..543d554 100644 --- a/code/user/tests/win32_seh_smoke/win32_seh_smoke.c +++ b/code/user/tests/win32_seh_smoke/win32_seh_smoke.c @@ -29,7 +29,7 @@ static volatile uint32_t gVehHits; // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -47,7 +47,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } diff --git a/code/user/tests/win32_thread_suspend_smoke/win32_thread_suspend_smoke.c b/code/user/tests/win32_thread_suspend_smoke/win32_thread_suspend_smoke.c index 31d99db..a282453 100644 --- a/code/user/tests/win32_thread_suspend_smoke/win32_thread_suspend_smoke.c +++ b/code/user/tests/win32_thread_suspend_smoke/win32_thread_suspend_smoke.c @@ -30,7 +30,7 @@ static volatile long gRelease; // Writes one test message to the native console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } diff --git a/code/user/tests/winmm_waveout_smoke/winmm_waveout_smoke.c b/code/user/tests/winmm_waveout_smoke/winmm_waveout_smoke.c index 77f8a26..ee94ae6 100644 --- a/code/user/tests/winmm_waveout_smoke/winmm_waveout_smoke.c +++ b/code/user/tests/winmm_waveout_smoke/winmm_waveout_smoke.c @@ -133,7 +133,7 @@ static volatile long gCloseCallbacks; // Writes one NUL-terminated string to the kernel console. static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } diff --git a/code/user/tests/would_block_smoke/would_block_smoke.c b/code/user/tests/would_block_smoke/would_block_smoke.c index 89b3c56..56e817a 100644 --- a/code/user/tests/would_block_smoke/would_block_smoke.c +++ b/code/user/tests/would_block_smoke/would_block_smoke.c @@ -49,7 +49,7 @@ static uint32_t recv_queue(uint32_t queue, TEST_QUEUE_MESSAGE* msg, uint64_t tim // Prints one NUL-terminated string to the kernel console. static void print(const char* s) { - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) }; SysWriteConsole(&req); } @@ -67,7 +67,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } } @@ -172,7 +172,7 @@ static void test_queue_infinite_receive_wakeup(void) { uint32_t threadHandle = 0; uint32_t threadId = 0; SYS_CREATE_THREAD_REQUEST req = { - (uintptr_t)sender_thread, + sizeof(SYS_CREATE_THREAD_REQUEST), (uintptr_t)sender_thread, (uintptr_t)&ctx, 1, 0,