redo boot around an explicit system volume and fixed kernel/HAL images, redo volumes and filesystem paths around capabilities instead of drive numbers and kernel CWD state, add a capability/security model with derived and revocable handles and security contexts, add port IPC and rssrv to manage the userspace resource namespace and publish the system volume, move process, dll and path loading to userspace and add process builder syscalls, init now supervises rssrv and the session process, redo the syscall ABI around request structs and four arguments, add a volume manager and proper partition arrival/removal handling so FAT and NTFS mount dynamically, clean up the HAL/DDK boundary so drivers don't depend on HAL internals, save fpu state properly now, the serial driver doesn't hang on a missing port, fix AHCI startup races, fix timeout accounting, fix shared mmap writeback, more smoke tests

This commit is contained in:
maksw committed 2026-08-20 12:33:17 +02:00
1 parent 4add6789ba
commit f882d339fb
232 files changed
+20934 -8898

No files matched your search

+408
View File
@@ -0,0 +1,408 @@
// Userspace PE32+ image loader. The kernel sees only generic process memory and execution state.
#include <ntlite/pe_loader.h>
#include <ntlite/syscall.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#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;
}