// Userspace PE module registry and dependency loader. Kernel process objects know only VADs. #include #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 4u 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_STRING_RECORD { uint32_t Offset; uint32_t Length; } NTL_LOADER_STRING_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 StartupDataOffset; uint32_t StartupDataLength; uint32_t ArgumentVectorOffset; uint32_t ArgumentCount; uint32_t EnvironmentVectorOffset; uint32_t EnvironmentCount; 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 PrimaryModuleRootHandle; SYS_STRING_VIEW PrimaryModuleDirectory; SYS_HANDLE SecondaryModuleRootHandle; SYS_STRING_VIEW SecondaryModuleDirectory; SYS_HANDLE FallbackModuleRootHandle; SYS_STRING_VIEW FallbackModuleDirectory; 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); } // 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 from a single capability-relative search location. static KSTATUS NtlpOpenDependencyAt(SYS_HANDLE rootHandle, SYS_STRING_VIEW directory, SYS_STRING_VIEW canonical, SYS_HANDLE* fileOut) { if (rootHandle == SYS_HANDLE_INVALID || !fileOut || !SysSvIsValid(directory) || SysSvContainsByte(directory, '\0')) return NTLSTATUS_INVALID_PARAMETER; *fileOut = SYS_HANDLE_INVALID; char path[SYS_PATH_MAX]; char name[NTL_MODULE_NAME_MAX]; KSTATUS status = NtlpCopyName(canonical, name); if (!KSUCCESS(status)) return status; size_t dirLength = directory.Length; size_t total = dirLength + (dirLength ? 1u : 0u) + canonical.Length; if (total + 1u > sizeof(path)) return NTLSTATUS_BUFFER_TOO_SMALL; if (dirLength) memcpy(path, directory.Data, dirLength); size_t pos = dirLength; if (dirLength) path[pos++] = '/'; memcpy(path + pos, canonical.Data, canonical.Length); path[total] = '\0'; SYS_OPEN_FILE_AT_REQUEST open = SysMakeOpenFileAtRequest(rootHandle, SysSvFromCstr(path), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, fileOut); return SysOpenFileAt(&open); } // Searches the available current-directory root, then image-private and configured fallback roots. static KSTATUS NtlpOpenDependency(NTL_MODULE_CONTEXT* context, SYS_STRING_VIEW canonical, SYS_HANDLE* fileOut) { if (!context || !fileOut) return NTLSTATUS_INVALID_PARAMETER; KSTATUS status = NtlpOpenDependencyAt(context->PrimaryModuleRootHandle, context->PrimaryModuleDirectory, canonical, fileOut); if (status != NTLSTATUS_NOT_FOUND) return status; if (context->SecondaryModuleRootHandle != SYS_HANDLE_INVALID) { status = NtlpOpenDependencyAt(context->SecondaryModuleRootHandle, context->SecondaryModuleDirectory, canonical, fileOut); if (status != NTLSTATUS_NOT_FOUND) return status; } if (context->FallbackModuleRootHandle == SYS_HANDLE_INVALID) return NTLSTATUS_NOT_FOUND; return NtlpOpenDependencyAt(context->FallbackModuleRootHandle, context->FallbackModuleDirectory, canonical, fileOut); } // 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 primaryModuleRootHandle, SYS_STRING_VIEW primaryModuleDirectory, SYS_HANDLE secondaryModuleRootHandle, SYS_STRING_VIEW secondaryModuleDirectory, SYS_HANDLE fallbackModuleRootHandle, SYS_STRING_VIEW fallbackModuleDirectory, NTL_MODULE_NAME_RESOLVER resolver, void* resolverContext, int seedCurrentProcess, NTL_MODULE_CONTEXT** contextOut) { if (!contextOut || primaryModuleRootHandle == SYS_HANDLE_INVALID || !SysSvIsValid(primaryModuleDirectory) || SysSvContainsByte(primaryModuleDirectory, '\0') || (secondaryModuleRootHandle != SYS_HANDLE_INVALID && (!SysSvIsValid(secondaryModuleDirectory) || SysSvContainsByte(secondaryModuleDirectory, '\0'))) || (fallbackModuleRootHandle != SYS_HANDLE_INVALID && (!SysSvIsValid(fallbackModuleDirectory) || SysSvContainsByte(fallbackModuleDirectory, '\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->PrimaryModuleRootHandle = primaryModuleRootHandle; context->PrimaryModuleDirectory = primaryModuleDirectory; context->SecondaryModuleRootHandle = secondaryModuleRootHandle; context->SecondaryModuleDirectory = secondaryModuleDirectory; context->FallbackModuleRootHandle = fallbackModuleRootHandle; context->FallbackModuleDirectory = fallbackModuleDirectory; 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' || (data->StartupDataLength == 0 && data->StartupDataOffset != 0) || (data->StartupDataLength != 0 && (data->StartupDataOffset < data->CurrentDirectoryOffset + data->CurrentDirectoryLength + 1u || data->StartupDataOffset > data->Size || data->StartupDataLength > data->Size - data->StartupDataOffset))) { 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); } // Validates one caller-owned process string vector and returns its serialized string bytes. static KSTATUS NtlpMeasureProcessVector(NTL_PROCESS_STRING_VECTOR vector, size_t* bytesOut) { if (!bytesOut || (vector.Count && !vector.Entries)) return NTLSTATUS_INVALID_PARAMETER; size_t bytes = 0; for (uint32_t i = 0; i < vector.Count; ++i) { SYS_STRING_VIEW entry = vector.Entries[i]; if (!SysSvIsValid(entry) || SysSvContainsByte(entry, '\0') || entry.Length > UINT32_MAX) return NTLSTATUS_INVALID_PARAMETER; if (entry.Length > SIZE_MAX - bytes - 1u) return NTLSTATUS_NO_MEMORY; bytes += entry.Length + 1u; } *bytesOut = bytes; return NTLSTATUS_SUCCESS; } // Serializes startup dependency metadata into a read-only child-owned VAD. KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, SYS_STRING_VIEW currentDirectory, NTL_PROCESS_STRING_VECTOR arguments, NTL_PROCESS_STRING_VECTOR environment, const void* startupData, size_t startupDataSize, 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') || startupDataSize > NTL_PROCESS_STARTUP_DATA_MAX || (startupDataSize && !startupData)) return NTLSTATUS_INVALID_PARAMETER; *loaderDataOut = 0; size_t argumentStringBytes = 0; size_t environmentStringBytes = 0; KSTATUS status = NtlpMeasureProcessVector(arguments, &argumentStringBytes); if (!KSUCCESS(status)) return status; status = NtlpMeasureProcessVector(environment, &environmentStringBytes); if (!KSUCCESS(status)) return status; 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; size_t argumentRecordsBytes = (size_t)arguments.Count * sizeof(NTL_LOADER_STRING_RECORD); size_t environmentRecordsBytes = (size_t)environment.Count * sizeof(NTL_LOADER_STRING_RECORD); if ((arguments.Count && argumentRecordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != arguments.Count) || (environment.Count && environmentRecordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != environment.Count) || argumentRecordsBytes > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; size_t argumentVectorOffset = arguments.Count ? bytes : 0u; bytes += argumentRecordsBytes; if (environmentRecordsBytes > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; size_t environmentVectorOffset = environment.Count ? bytes : 0u; bytes += environmentRecordsBytes; if (bytes < sizeof(NTL_LOADER_DATA) || currentDirectory.Length > SIZE_MAX - bytes - 1u) return NTLSTATUS_NO_MEMORY; size_t currentDirectoryOffset = bytes; bytes += currentDirectory.Length + 1u; size_t startupDataOffset = 0; if (startupDataSize) { if (startupDataSize > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; startupDataOffset = bytes; bytes += startupDataSize; } if (argumentStringBytes > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; size_t argumentStringsOffset = bytes; bytes += argumentStringBytes; if (environmentStringBytes > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; size_t environmentStringsOffset = bytes; bytes += environmentStringBytes; 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; 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; data->StartupDataOffset = startupDataSize ? (uint32_t)startupDataOffset : 0u; data->StartupDataLength = (uint32_t)startupDataSize; data->ArgumentVectorOffset = arguments.Count ? (uint32_t)argumentVectorOffset : 0u; data->ArgumentCount = arguments.Count; data->EnvironmentVectorOffset = environment.Count ? (uint32_t)environmentVectorOffset : 0u; data->EnvironmentCount = environment.Count; 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'; if (startupDataSize) memcpy((char*)data + startupDataOffset, startupData, startupDataSize); NTL_LOADER_STRING_RECORD* argumentRecords = arguments.Count ? (NTL_LOADER_STRING_RECORD*)((char*)data + argumentVectorOffset) : 0; size_t stringOffset = argumentStringsOffset; for (uint32_t argumentIndex = 0; argumentIndex < arguments.Count; ++argumentIndex) { SYS_STRING_VIEW entry = arguments.Entries[argumentIndex]; argumentRecords[argumentIndex].Offset = (uint32_t)stringOffset; argumentRecords[argumentIndex].Length = (uint32_t)entry.Length; if (entry.Length) memcpy((char*)data + stringOffset, entry.Data, entry.Length); ((char*)data)[stringOffset + entry.Length] = '\0'; stringOffset += entry.Length + 1u; } NTL_LOADER_STRING_RECORD* environmentRecords = environment.Count ? (NTL_LOADER_STRING_RECORD*)((char*)data + environmentVectorOffset) : 0; stringOffset = environmentStringsOffset; for (uint32_t environmentIndex = 0; environmentIndex < environment.Count; ++environmentIndex) { SYS_STRING_VIEW entry = environment.Entries[environmentIndex]; environmentRecords[environmentIndex].Offset = (uint32_t)stringOffset; environmentRecords[environmentIndex].Length = (uint32_t)entry.Length; if (entry.Length) memcpy((char*)data + stringOffset, entry.Data, entry.Length); ((char*)data)[stringOffset + entry.Length] = '\0'; stringOffset += entry.Length + 1u; } status = SysProtectProcessVm(context->ProcessHandle, base, pages, SYS_VM_READ); if (!KSUCCESS(status)) return status; *loaderDataOut = base; return NTLSTATUS_SUCCESS; } // Validates and returns the immutable loader block for the current process. static KSTATUS NtlpGetCurrentLoaderData(const NTL_LOADER_DATA** dataOut) { if (!dataOut) return NTLSTATUS_INVALID_PARAMETER; *dataOut = 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)) return NTLSTATUS_FAILURE; *dataOut = data; return NTLSTATUS_SUCCESS; } // Copies the opaque userspace bootstrap payload from the current process loader block. KSTATUS NtlGetProcessStartupData(void* buffer, size_t bufferSize, size_t* sizeOut) { if (!sizeOut || (bufferSize && !buffer)) return NTLSTATUS_INVALID_PARAMETER; *sizeOut = 0; const NTL_LOADER_DATA* data = 0; KSTATUS status = NtlpGetCurrentLoaderData(&data); if (!KSUCCESS(status)) return status; if (data->StartupDataLength > NTL_PROCESS_STARTUP_DATA_MAX || (data->StartupDataLength == 0 && data->StartupDataOffset != 0) || (data->StartupDataLength != 0 && (data->StartupDataOffset < sizeof(*data) || data->StartupDataOffset > data->Size || data->StartupDataLength > data->Size - data->StartupDataOffset))) return NTLSTATUS_FAILURE; if (data->StartupDataLength == 0) return NTLSTATUS_NOT_FOUND; *sizeOut = data->StartupDataLength; if (bufferSize < data->StartupDataLength) return NTLSTATUS_BUFFER_TOO_SMALL; memcpy(buffer, (const char*)data + data->StartupDataOffset, data->StartupDataLength); return NTLSTATUS_SUCCESS; } // Returns one loader string vector count after validating its descriptor table. static KSTATUS NtlpGetProcessVectorCount(int environment, uint32_t* countOut) { if (!countOut) return NTLSTATUS_INVALID_PARAMETER; *countOut = 0; const NTL_LOADER_DATA* data = 0; KSTATUS status = NtlpGetCurrentLoaderData(&data); if (!KSUCCESS(status)) return status; uint32_t count = environment ? data->EnvironmentCount : data->ArgumentCount; uint32_t offset = environment ? data->EnvironmentVectorOffset : data->ArgumentVectorOffset; if ((count == 0 && offset != 0) || (count != 0 && offset == 0)) return NTLSTATUS_FAILURE; size_t recordsBytes = (size_t)count * sizeof(NTL_LOADER_STRING_RECORD); if (count && recordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != count) return NTLSTATUS_FAILURE; if (offset > data->Size || recordsBytes > data->Size - offset) return NTLSTATUS_FAILURE; *countOut = count; return NTLSTATUS_SUCCESS; } // Returns one immutable loader string vector entry. static KSTATUS NtlpGetProcessVectorEntry(int environment, uint32_t index, SYS_STRING_VIEW* entryOut) { if (!entryOut) return NTLSTATUS_INVALID_PARAMETER; *entryOut = SYS_SV_NULL; const NTL_LOADER_DATA* data = 0; KSTATUS status = NtlpGetCurrentLoaderData(&data); if (!KSUCCESS(status)) return status; uint32_t count = environment ? data->EnvironmentCount : data->ArgumentCount; uint32_t offset = environment ? data->EnvironmentVectorOffset : data->ArgumentVectorOffset; if (index >= count || !count || !offset) return NTLSTATUS_NOT_FOUND; size_t recordsBytes = (size_t)count * sizeof(NTL_LOADER_STRING_RECORD); if (recordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != count || offset > data->Size || recordsBytes > data->Size - offset) return NTLSTATUS_FAILURE; const NTL_LOADER_STRING_RECORD* records = (const NTL_LOADER_STRING_RECORD*)((const char*)data + offset); NTL_LOADER_STRING_RECORD record = records[index]; if (record.Offset >= data->Size || record.Length > data->Size - record.Offset || record.Length == data->Size - record.Offset) return NTLSTATUS_FAILURE; const char* string = (const char*)data + record.Offset; SYS_STRING_VIEW view = SysSvFromParts(string, record.Length); if (string[record.Length] != '\0' || SysSvContainsByte(view, '\0')) return NTLSTATUS_FAILURE; *entryOut = view; return NTLSTATUS_SUCCESS; } // Returns the number of serialized process arguments. KSTATUS NtlGetProcessArgumentCount(uint32_t* countOut) { return NtlpGetProcessVectorCount(0, countOut); } // Returns one serialized process argument. KSTATUS NtlGetProcessArgument(uint32_t index, SYS_STRING_VIEW* argumentOut) { return NtlpGetProcessVectorEntry(0, index, argumentOut); } // Returns the number of serialized environment entries. KSTATUS NtlGetProcessEnvironmentCount(uint32_t* countOut) { return NtlpGetProcessVectorCount(1, countOut); } // Returns one serialized environment entry. KSTATUS NtlGetProcessEnvironment(uint32_t index, SYS_STRING_VIEW* entryOut) { return NtlpGetProcessVectorEntry(1, index, entryOut); } // 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 = SYS_SV_NULL; const NTL_LOADER_DATA* data = 0; KSTATUS status = NtlpGetCurrentLoaderData(&data); if (!KSUCCESS(status)) return status; if (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, SYS_HANDLE_INVALID, SysSvFromParts((const char*)0, 0), SYS_HANDLE_INVALID, SysSvFromParts((const char*)0, 0), 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.PrimaryModuleRootHandle = moduleRoot; gCurrentContext.PrimaryModuleDirectory = moduleDirectory; gCurrentContext.SecondaryModuleRootHandle = SYS_HANDLE_INVALID; gCurrentContext.SecondaryModuleDirectory = SysSvFromParts((const char*)0, 0); gCurrentContext.FallbackModuleRootHandle = SYS_HANDLE_INVALID; gCurrentContext.FallbackModuleDirectory = SysSvFromParts((const char*)0, 0); 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; }