1036 lines
46 KiB
C
1036 lines
46 KiB
C
// Userspace PE module registry and dependency loader. Kernel process objects know only VADs.
|
|
|
|
#include <ntlite/atomic.h>
|
|
#include <intrin.h>
|
|
#include <ntlite/module_loader.h>
|
|
#include <ntlite/pe_loader.h>
|
|
#include <ntlite/process_loader.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#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 int32_t gCurrentContextState;
|
|
static volatile int32_t 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 bool NtlpViewEqualsNameI(SYS_STRING_VIEW view, const char* name) {
|
|
if (!name) return false;
|
|
size_t n = strlen(name);
|
|
if (view.Length != n) return false;
|
|
for (size_t i = 0; i < n; ++i) {
|
|
if (NtlpLower(view.Data[i]) != NtlpLower(name[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 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,
|
|
bool 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 (NtlAtomicCompareExchange32(&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)) {
|
|
NtlAtomicExchange32(&gCurrentContextState, 0);
|
|
return status;
|
|
}
|
|
gCurrentContext = *temporary;
|
|
gCurrentContext.OwnsStorage = 0u;
|
|
temporary->Head = 0;
|
|
temporary->Magic = 0;
|
|
NtlpFree(temporary);
|
|
NtlAtomicExchange32(&gCurrentContextState, 2);
|
|
} else {
|
|
while (NtlAtomicCompareExchange32(&gCurrentContextState, 2, 2) == 1) SysYield();
|
|
if (NtlAtomicCompareExchange32(&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 (NtlAtomicCompareExchange32(&gCurrentContextBusy, 1, 0) != 0) SysYield();
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Releases the current-process loader serialization guard.
|
|
static void NtlpLeaveCurrent(void) {
|
|
NtlAtomicExchange32(&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 (NtlAtomicCompareExchange32(&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 (NtlAtomicCompareExchange32(&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 (NtlAtomicCompareExchange32(&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 (NtlAtomicCompareExchange32(&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;
|
|
}
|