diff --git a/OVMF/OVMF_VARS_4M.fd b/OVMF/OVMF_VARS_4M.fd index 5f4d30b..28ba5cc 100644 Binary files a/OVMF/OVMF_VARS_4M.fd and b/OVMF/OVMF_VARS_4M.fd differ diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 3b482b3..4548503 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ 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,