Files
nt-lite/code/kernel/ke_syscall.c
T

5236 lines
193 KiB
C

// Kernel syscall dispatcher. This file validates user requests and bridges user-mode ABI calls into kernel services.
// KeSyscallEntry (ke_syscall.asm) handles the INT 0x40 ring-3 -> ring-0
// transition, re-orders the arguments to match MS x64, and calls
// KeSyscallDispatch with:
// nr = original RAX (syscall number)
// a0 .. a5 = RCX, RDX, R8, R9, R10, R11 (user arguments)
// Every handler receives the same (a0..a5) signature even if it uses fewer
// arguments; unused parameters are silently ignored.
// Return value (RAX) is a KSTATUS code *or* an arbitrary 64-bit datum
// (pointer, size, tick count, etc.) depending on the call; conventions are
// documented in syscall_nr.h.
// Unified handle design
// All kernel objects (FILE_OBJECT, MUTEX, SEMAPHORE, EVENT, QUEUE) are
// reference-counted OBJECTs stored in the per-process handle table.
// SYS_CLOSE_HANDLE is the single close path for every type:
// KeCloseHandle(proc, handle)
// -> ObDereferenceObject(obj)
// -> obj->Destroy(obj) [type-specific cleanup, e.g. KiFileDestroy]
// -> MmFreePool(obj)
// There is no separate SyscallCloseFile. Removing that stub was the right
// call: the object model already handled it; the dedicated syscall was only
// adding a footgun where callers could bypass the unified refcount path.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_cpu.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_process.h>
#include <kernel/ke_mutex.h>
#include <kernel/ke_semaphore.h>
#include <kernel/ke_event.h>
#include <kernel/ke_apc.h>
#include <kernel/ke_queue.h>
#include <kernel/ke_timer.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_debug.h>
#ifndef PROCESS_LAUNCH_DEBUG
#define PROCESS_LAUNCH_DEBUG 0
#endif
#if PROCESS_LAUNCH_DEBUG
#define ProcDbg(...) DbgPrint(__VA_ARGS__)
#else
#define ProcDbg(...) ((void)0)
#endif
#include <kernel/mm_virt.h>
#include <kernel/mm_swap.h>
#include <kernel/mm_file_cache.h>
#include <kernel/mm_pfn.h>
#include <kernel/mm_pte.h>
#include <kernel/ldr_image.h>
#include <kernel/syscall_nr.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/io_file.h>
#include <kernel/io_async.h>
#include <ntlite/ddi/io_driver.h>
#include <kernel/net_socket.h>
#include <ntlite/ddi/net_stack.h>
#include <kernel/ke_trap.h>
#include <kernel/ke_input.h>
#include <kernel/ke_display.h>
#include <kernel/ke_time.h>
#include <ntlite/syscall.h>
// HAL pointer - set once at boot by KeSyscallSetHal().
static const HAL_API* gHal;
typedef struct SYS_IO_STATUS_BLOCK_K {
volatile uint32_t Status;
volatile uint64_t Information;
} SYS_IO_STATUS_BLOCK_K;
typedef void (*SYS_USER_APC_ROUTINE_K)(void* normalContext,
SYS_IO_STATUS_BLOCK_K* iosb,
uint64_t information);
typedef struct SYS_ASYNC_FILE_REQUEST_K {
uint32_t FileHandle;
uint32_t EventHandle;
uint32_t Flags;
uint64_t Offset;
void* Buffer;
size_t Size;
SYS_IO_STATUS_BLOCK_K* Iosb;
SYS_USER_APC_ROUTINE_K ApcRoutine;
void* ApcContext;
uint32_t* RequestHandleOut; // optional cancellation/query handle output
} SYS_ASYNC_FILE_REQUEST_K;
typedef struct SYS_USER_ASYNC_APC_CONTEXT {
KAPC KernelApc;
THREAD* TargetThread;
SYS_USER_APC_ROUTINE_K UserRoutine;
void* UserContext;
} SYS_USER_ASYNC_APC_CONTEXT;
typedef struct FILE_MAPPING_OBJECT {
OBJECT Header;
FILE_OBJECT* File;
uint64_t MaximumSize;
uint64_t PageFlags;
uint32_t Protect;
uint32_t Flags;
} FILE_MAPPING_OBJECT;
static void KiFileMappingDestroy(OBJECT* obj) {
FILE_MAPPING_OBJECT* mapping = CONTAINING_RECORD(obj, FILE_MAPPING_OBJECT, Header);
if (mapping->File) {
ObDereferenceObject(&mapping->File->Header);
mapping->File = (FILE_OBJECT*)0;
}
}
typedef struct MODULE_HANDLE_OBJECT {
OBJECT Header;
PROCESS* Owner;
uintptr_t Base;
} MODULE_HANDLE_OBJECT;
// Releasing the last module handle releases the public load reference it owns.
static void KiModuleHandleDestroy(OBJECT* obj) {
MODULE_HANDLE_OBJECT* module = CONTAINING_RECORD(obj, MODULE_HANDLE_OBJECT, Header);
if (module->Owner && module->Base) {
(void)LdrUnloadModuleForProcess(module->Owner, module->Base);
module->Base = 0;
}
if (module->Owner) {
ObDereferenceObject(&module->Owner->Header);
module->Owner = (PROCESS*)0;
}
}
// Creates a user-visible module handle for one public load reference.
static KSTATUS KiCreateModuleHandle(PROCESS* owner, uintptr_t base, uint32_t* handleOut) {
if (!owner || !base || !handleOut) return STATUS_INVALID_PARAMETER;
MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)ObAllocateObject(ObjectTypeModule, sizeof(MODULE_HANDLE_OBJECT));
if (!module) return STATUS_NO_MEMORY;
module->Header.Destroy = KiModuleHandleDestroy;
module->Owner = owner;
module->Base = base;
ObReferenceObject(&owner->Header);
KSTATUS s = KeAllocateHandleWithRights(owner,
&module->Header,
HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE,
handleOut);
ObDereferenceObject(&module->Header);
return s;
}
typedef struct SURFACE_OBJECT {
OBJECT Header;
PROCESS* Owner;
uint32_t Width;
uint32_t Height;
uint32_t PitchBytes;
uint32_t Format;
uint32_t Flags;
size_t PayloadSize;
size_t PageCount;
uint8_t StatsRegistered;
uint64_t MapCount;
uint64_t PresentCount;
uint64_t PresentRectCount;
uint64_t PresentClippedRectCount;
uint64_t PresentEmptyClipCount;
uint64_t PresentRowsCopied;
uint64_t PresentBytesCopied;
PHYSICAL_ADDRESS* Frames;
void* UserBase;
uint32_t* RowScratch;
MUTEX* Lock;
} SURFACE_OBJECT;
static SPINLOCK gSurfaceStatsLock = SPINLOCK_INIT;
static SYS_SURFACE_STATS gSurfaceStats;
// Registers a surface in the global surface telemetry snapshot.
static void KiSurfaceStatsRegister(SURFACE_OBJECT* surface) {
if (!surface || surface->StatsRegistered)
return;
uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock);
gSurfaceStats.CreatedSurfaces++;
gSurfaceStats.LiveSurfaces++;
gSurfaceStats.LivePages += surface->PageCount;
gSurfaceStats.LiveBytes += surface->PayloadSize;
surface->StatsRegistered = 1;
KeReleaseSpinlock(&gSurfaceStatsLock, flags);
}
// Removes a surface from the global surface telemetry snapshot.
static void KiSurfaceStatsUnregister(SURFACE_OBJECT* surface) {
if (!surface || !surface->StatsRegistered)
return;
uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock);
gSurfaceStats.DestroyedSurfaces++;
if (gSurfaceStats.LiveSurfaces) gSurfaceStats.LiveSurfaces--;
if (gSurfaceStats.LivePages >= surface->PageCount) gSurfaceStats.LivePages -= surface->PageCount;
else gSurfaceStats.LivePages = 0;
if (gSurfaceStats.LiveBytes >= surface->PayloadSize) gSurfaceStats.LiveBytes -= surface->PayloadSize;
else gSurfaceStats.LiveBytes = 0;
surface->StatsRegistered = 0;
KeReleaseSpinlock(&gSurfaceStatsLock, flags);
}
// Records a successful surface map operation.
static void KiSurfaceStatsRecordMap(SURFACE_OBJECT* surface) {
if (!surface)
return;
uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock);
surface->MapCount++;
gSurfaceStats.MapCalls++;
KeReleaseSpinlock(&gSurfaceStatsLock, flags);
}
// Records a successful surface present operation.
static void KiSurfaceStatsRecordPresent(SURFACE_OBJECT* surface, uint64_t inputRects, uint64_t clippedRects,
uint64_t emptyClips, uint64_t rowsCopied, uint64_t bytesCopied) {
if (!surface)
return;
uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock);
surface->PresentCount++;
surface->PresentRectCount += inputRects;
surface->PresentClippedRectCount += clippedRects;
surface->PresentEmptyClipCount += emptyClips;
surface->PresentRowsCopied += rowsCopied;
surface->PresentBytesCopied += bytesCopied;
gSurfaceStats.PresentCalls++;
gSurfaceStats.PresentRectInputs += inputRects;
gSurfaceStats.PresentClippedRects += clippedRects;
gSurfaceStats.PresentEmptyClips += emptyClips;
gSurfaceStats.PresentRowsCopied += rowsCopied;
gSurfaceStats.PresentBytesCopied += bytesCopied;
KeReleaseSpinlock(&gSurfaceStatsLock, flags);
}
// Copies the global surface telemetry snapshot.
static void KiSurfaceStatsQuery(SYS_SURFACE_STATS* out) {
if (!out)
return;
uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock);
*out = gSurfaceStats;
out->Size = sizeof(*out);
out->Version = SYS_STATS_VERSION;
KeReleaseSpinlock(&gSurfaceStatsLock, flags);
}
static void KiSurfaceDestroy(OBJECT* obj) {
SURFACE_OBJECT* surface = CONTAINING_RECORD(obj, SURFACE_OBJECT, Header);
KiSurfaceStatsUnregister(surface);
if (surface->Owner && surface->UserBase) {
(void)MmVadDestroy(surface->Owner, surface->UserBase);
surface->UserBase = (void*)0;
}
if (surface->Lock) {
ObDereferenceObject(&surface->Lock->Header);
surface->Lock = (MUTEX*)0;
}
if (surface->Frames) {
for (size_t i = 0; i < surface->PageCount; ++i) {
if (surface->Frames[i])
(void)MmPfnRelease(surface->Frames[i]);
}
MmFreePool(surface->Frames);
surface->Frames = (PHYSICAL_ADDRESS*)0;
}
if (surface->RowScratch) {
MmFreePool(surface->RowScratch);
surface->RowScratch = (uint32_t*)0;
}
}
typedef struct SURFACE_ROW_CONTEXT {
SURFACE_OBJECT* Surface;
uint32_t SourceX;
uint32_t SourceY;
} SURFACE_ROW_CONTEXT;
static KSTATUS KiSurfaceCopyRow(void* context, int rowIndex, uint32_t* scratch, size_t pixelCount) {
SURFACE_ROW_CONTEXT* ctx = (SURFACE_ROW_CONTEXT*)context;
if (!ctx || !ctx->Surface || !scratch || rowIndex < 0)
return STATUS_INVALID_PARAMETER;
SURFACE_OBJECT* surface = ctx->Surface;
uint64_t row = (uint64_t)ctx->SourceY + (uint64_t)(uint32_t)rowIndex;
uint64_t offset = row * (uint64_t)surface->PitchBytes + (uint64_t)ctx->SourceX * sizeof(uint32_t);
uint64_t bytes = (uint64_t)pixelCount * sizeof(uint32_t);
if (offset > (uint64_t)surface->PayloadSize || bytes > (uint64_t)surface->PayloadSize - offset)
return STATUS_INVALID_PARAMETER;
uint8_t* dst = (uint8_t*)scratch;
while (bytes) {
size_t frameIndex = (size_t)(offset / PAGE_SIZE);
size_t pageOffset = (size_t)(offset & (PAGE_SIZE - 1));
if (frameIndex >= surface->PageCount || !surface->Frames[frameIndex])
return STATUS_INVALID_PARAMETER;
size_t chunk = PAGE_SIZE - pageOffset;
if (chunk > bytes)
chunk = (size_t)bytes;
const uint8_t* src = (const uint8_t*)MmPfnToVirtual(surface->Frames[frameIndex]);
if (!src)
return STATUS_INVALID_PARAMETER;
memcpy(dst, src + pageOffset, chunk);
dst += chunk;
offset += chunk;
bytes -= chunk;
}
return STATUS_SUCCESS;
}
// Implements the syscall Init operation.
void KeSyscallInit(const HAL_API* hal) {
gHal = hal;
}
// Helper: look up a handle and type-check it in one call.
// Bumps the refcount on success; caller must ObDereferenceObject when done.
// Returns NULL if the handle is invalid or the type does not match.
static OBJECT* KiHandleToObject(uint32_t handle, OBJECT_TYPE expected, uint32_t requiredRights) {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NULL;
OBJECT* obj = KeLookupHandleWithRights(proc, handle, requiredRights);
if (!obj) return NULL;
if (obj->Type != expected) {
ObDereferenceObject(obj);
return NULL;
}
return obj;
}
// Helper: allocate a handle in the current process's table for 'obj'.
// Takes one reference on behalf of the handle slot.
// On failure, drops the reference it would have stored.
static KSTATUS KiAllocHandleForCurrentWithRights(OBJECT* obj, uint32_t rights, uint32_t* handleOut) {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) {
ObDereferenceObject(obj);
return STATUS_FAILURE;
}
KSTATUS s = KeAllocateHandleWithRights(proc, obj, rights, handleOut);
// Most syscall create/open paths receive a fresh creator reference from the
// subsystem. The handle table takes its own reference; this helper always
// drops the creator reference so the object is owned only by the returned
// handle (and any subsystem-internal refs).
ObDereferenceObject(obj);
return s;
}
// Creates handle For Current.
static KSTATUS KiAllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) {
return KiAllocHandleForCurrentWithRights(obj, HANDLE_RIGHT_ALL, handleOut);
}
#define SYSCALL_STRING_MAX SYS_PATH_MAX
#define SYSCALL_TEXT_MAX_BYTES (64ULL * 1024ULL)
#define SYSCALL_IO_MAX_BYTES (16ULL * 1024ULL * 1024ULL)
#define SYSCALL_FILE_IO_CHUNK_BYTES (64ULL * 1024ULL)
#define SYSCALL_BLIT_MAX_BYTES (16ULL * 1024ULL * 1024ULL)
// Copies user Request.
static KSTATUS KiCopyUserRequest(uint64_t arg, void* dst, size_t size) {
if (!arg || !dst || size == 0) return STATUS_INVALID_PARAMETER;
return CopyFromUser(dst, (const void*)(uintptr_t)arg, size);
}
static KSTATUS KiImportUserStringView(SYS_STRING_VIEW userView,
char* dst,
size_t dstSize,
SYS_STRING_VIEW* out,
bool nulTerminate) {
if (!out) return STATUS_INVALID_PARAMETER;
*out = SysSvFromParts((const char*)0, 0);
if (userView.Length == 0) {
if (nulTerminate) {
if (!dst || dstSize == 0) return STATUS_BUFFER_TOO_SMALL;
dst[0] = '\0';
}
return STATUS_SUCCESS;
}
if (!userView.Data || !dst) return STATUS_INVALID_PARAMETER;
if (userView.Length > SYSCALL_STRING_MAX) return STATUS_BUFFER_TOO_SMALL;
size_t needed = userView.Length + (nulTerminate ? 1 : 0);
if (needed < userView.Length || needed > dstSize) return STATUS_BUFFER_TOO_SMALL;
KSTATUS s = CopyFromUser(dst, userView.Data, userView.Length);
if (!KSUCCESS(s)) return s;
if (nulTerminate) dst[userView.Length] = '\0';
*out = SysSvFromParts(dst, userView.Length);
return STATUS_SUCCESS;
}
static KSTATUS KiImportUserTextView(SYS_STRING_VIEW userView,
char* dst,
size_t dstSize,
SYS_STRING_VIEW* out,
bool nulTerminate) {
KSTATUS s = KiImportUserStringView(userView, dst, dstSize, out, nulTerminate);
if (!KSUCCESS(s)) return s;
if (out && SysSvContainsByte(*out, '\0')) return STATUS_INVALID_PARAMETER;
return STATUS_SUCCESS;
}
// Copies handle To User.
static KSTATUS KiCopyHandleToUser(uint64_t arg, uint32_t value) {
if (!arg) return STATUS_INVALID_PARAMETER;
return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value));
}
// Copies u32 To User.
static KSTATUS KiCopyU32ToUser(uint64_t arg, uint32_t value) {
if (!arg) return STATUS_INVALID_PARAMETER;
return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value));
}
// Copies size To User.
static KSTATUS KiCopySizeToUser(uint64_t arg, size_t value) {
if (!arg) return STATUS_INVALID_PARAMETER;
return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value));
}
// Copies u64 To User.
static KSTATUS KiCopyU64ToUser(uint64_t arg, uint64_t value) {
if (!arg) return STATUS_INVALID_PARAMETER;
return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value));
}
// Copies int To User.
static KSTATUS KiCopyIntToUser(uint64_t arg, int value) {
if (!arg) return STATUS_INVALID_PARAMETER;
return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value));
}
// Copies one signed 32-bit integer to user memory.
static KSTATUS KiCopyInt32ToUser(uint64_t arg, int32_t value) {
if (!arg) return STATUS_INVALID_PARAMETER;
return CopyToUser((void*)(uintptr_t)arg, &value, sizeof(value));
}
// Implements the mul Size operation.
static int KiMulSize(size_t a, size_t b, size_t* out) {
if (!out) return 0;
if (a && b > ((size_t)-1) / a) return 0;
*out = a * b;
return 1;
}
static size_t KiAlignUpSize(size_t value, size_t alignment) {
if (!alignment)
return value;
size_t mask = alignment - 1;
return (value + mask) & ~mask;
}
// Individual syscall handlers.
// SYS_EXIT_PROCESS (0) a0 = exitCode.
static uint64_t SyscallExitProcess(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
THREAD* t = KeGetCurrentThread();
PROCESS* proc = t ? t->Owner : NULL;
int32_t exitCode = (int32_t)a0;
if (!proc || !t) return STATUS_FAILURE;
KSTATUS s = KeExitProcess(proc, exitCode);
if (!KSUCCESS(s)) return s;
KeExitThread(t, exitCode);
while (true) ; // unreachable - KeExitThread does not return
}
// SYS_EXIT_THREAD (1) a0 = exitCode.
static uint64_t SyscallExitThread(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
THREAD* t = KeGetCurrentThread();
if (t) KeExitThread(t, (int)a0);
while (true);
}
// SYS_CREATE_THREAD a0=*SYS_CREATE_THREAD_REQUEST.
// Creates a runnable user thread and optionally returns both its waitable
// handle and diagnostic TID. The handle is the synchronization identity.
static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_CREATE_THREAD_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
if (!req.Entry) return STATUS_INVALID_PARAMETER;
if (req.Priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
if (req.Flags) return STATUS_INVALID_PARAMETER;
if (!req.ThreadHandleOut && !req.ThreadIdOut) return STATUS_INVALID_PARAMETER;
s = MmProbeUserRead((const void*)req.Entry, 1);
if (!KSUCCESS(s)) return s;
if (req.ThreadHandleOut) {
s = MmProbeUserWrite(req.ThreadHandleOut, sizeof(*req.ThreadHandleOut));
if (!KSUCCESS(s)) return s;
}
if (req.ThreadIdOut) {
s = MmProbeUserWrite(req.ThreadIdOut, sizeof(*req.ThreadIdOut));
if (!KSUCCESS(s)) return s;
}
THREAD* newt = NULL;
s = KeCreateThread(proc, req.Entry, req.Argument,
(THREAD_PRIORITY)req.Priority, &newt);
if (!KSUCCESS(s)) return s;
uint32_t threadHandle = 0;
if (req.ThreadHandleOut) {
s = KeAllocateHandleWithRights(proc, &newt->Header,
HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY,
&threadHandle);
if (!KSUCCESS(s)) {
ObDereferenceObject(&newt->Header);
return s;
}
}
if (req.ThreadHandleOut) {
s = KiCopyU32ToUser((uint64_t)(uintptr_t)req.ThreadHandleOut, threadHandle);
if (!KSUCCESS(s)) {
KeCloseHandle(proc, threadHandle);
ObDereferenceObject(&newt->Header);
return s;
}
}
if (req.ThreadIdOut) {
s = KiCopyU32ToUser((uint64_t)(uintptr_t)req.ThreadIdOut, newt->ThreadId);
if (!KSUCCESS(s)) {
if (threadHandle) KeCloseHandle(proc, threadHandle);
ObDereferenceObject(&newt->Header);
return s;
}
}
ObDereferenceObject(&newt->Header);
return STATUS_SUCCESS;
}
static void KiFinishAlertableUserWait(uint8_t alertable) {
if (!alertable) return;
THREAD* thread = KeGetCurrentThread();
if (thread && thread->UserApcPending)
thread->UserApcDeliverable = 1;
}
// Maps a waitable handle to its passive event backing object.
static KSTATUS KiHandleToWaitEvent(PROCESS* proc, uint32_t handle, EVENT** eventOut, OBJECT** objectOut) {
if (!proc || !eventOut || !objectOut) return STATUS_INVALID_PARAMETER;
*eventOut = (EVENT*)0;
*objectOut = (OBJECT*)0;
OBJECT* obj = KeLookupHandleWithRights(proc, handle, HANDLE_RIGHT_WAIT);
if (!obj) return STATUS_INVALID_HANDLE;
EVENT* event = (EVENT*)0;
switch (obj->Type) {
case ObjectTypeProcess:
event = CONTAINING_RECORD(obj, PROCESS, Header)->ExitEvent;
break;
case ObjectTypeThread:
event = CONTAINING_RECORD(obj, THREAD, Header)->ExitEvent;
break;
case ObjectTypeEvent:
event = CONTAINING_RECORD(obj, EVENT, Header);
break;
case ObjectTypeAsyncIo:
event = IoGetAsyncRequestWaitEvent((IO_ASYNC_REQUEST_OBJECT*)obj);
break;
case ObjectTypeInputQueue:
event = KeInputQueueWaitEvent(CONTAINING_RECORD(obj, INPUT_QUEUE, Header));
break;
case ObjectTypeTimer:
event = KeTimerWaitEvent(CONTAINING_RECORD(obj, KTIMER, Header));
break;
default:
ObDereferenceObject(obj);
return STATUS_NOT_SUPPORTED;
}
if (!event) {
ObDereferenceObject(obj);
return STATUS_OBJECT_DEAD;
}
*eventOut = event;
*objectOut = obj;
return STATUS_SUCCESS;
}
// SYS_WAIT_SINGLE a0=handle a1=SYS_WAIT_FLAG_* a2=timeoutMs.
// Waits on one handle. Passive objects use the wait-many engine; mutexes and
// semaphores keep their native consuming semantics for single-object waits.
static uint64_t SyscallWaitSingle(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
uint32_t flags = (uint32_t)a1;
uint64_t timeoutMs = a2;
if (flags & ~SYS_WAIT_FLAG_ALERTABLE) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
OBJECT* obj = KeLookupHandleWithRights(proc, (uint32_t)a0, HANDLE_RIGHT_WAIT);
if (!obj) return STATUS_INVALID_HANDLE;
uint8_t alertable = (flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0;
KSTATUS s = STATUS_NOT_SUPPORTED;
if (obj->Type == ObjectTypeMutex) {
if (alertable) s = STATUS_NOT_SUPPORTED;
else if (timeoutMs == 0) {
s = KeTryAcquireMutex(CONTAINING_RECORD(obj, MUTEX, Header));
if (s == STATUS_FAILURE) s = STATUS_TIMEOUT;
} else if (timeoutMs == SYS_WAIT_TIMEOUT_INFINITE) {
s = KeAcquireMutex(CONTAINING_RECORD(obj, MUTEX, Header));
} else {
s = STATUS_NOT_SUPPORTED;
}
ObDereferenceObject(obj);
return s;
}
if (obj->Type == ObjectTypeSemaphore) {
if (alertable) s = STATUS_NOT_SUPPORTED;
else if (timeoutMs == 0) {
s = KeTrySemaphore(CONTAINING_RECORD(obj, SEMAPHORE, Header));
if (s == STATUS_FAILURE) s = STATUS_TIMEOUT;
} else if (timeoutMs == SYS_WAIT_TIMEOUT_INFINITE) {
s = KeWaitSemaphore(CONTAINING_RECORD(obj, SEMAPHORE, Header));
} else {
s = STATUS_NOT_SUPPORTED;
}
ObDereferenceObject(obj);
return s;
}
ObDereferenceObject(obj);
EVENT* event = (EVENT*)0;
OBJECT* ref = (OBJECT*)0;
s = KiHandleToWaitEvent(proc, (uint32_t)a0, &event, &ref);
if (!KSUCCESS(s)) return s;
uint32_t selected = 0;
s = KeWaitForMultipleEvents(&event, 1, alertable, timeoutMs, &selected);
ObDereferenceObject(ref);
if (s != STATUS_TIMEOUT && s != STATUS_NOT_SUPPORTED)
KiFinishAlertableUserWait(alertable);
return s;
}
// SYS_WAIT_MANY a0=*SYS_WAIT_MANY_REQUEST.
// Waits until any passive waitable handle becomes signaled.
static uint64_t SyscallWaitMany(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_WAIT_MANY_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER;
if (req.Flags & ~SYS_WAIT_FLAG_ALERTABLE) return STATUS_INVALID_PARAMETER;
if (!req.Handles || req.Count == 0 || req.Count > SYS_WAIT_MANY_MAX_HANDLES)
return STATUS_INVALID_PARAMETER;
uint32_t handles[SYS_WAIT_MANY_MAX_HANDLES];
size_t handleBytes = (size_t)req.Count * sizeof(handles[0]);
s = CopyFromUser(handles, req.Handles, handleBytes);
if (!KSUCCESS(s)) return s;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
EVENT* events[SYS_WAIT_MANY_MAX_HANDLES];
OBJECT* refs[SYS_WAIT_MANY_MAX_HANDLES];
for (uint32_t i = 0; i < SYS_WAIT_MANY_MAX_HANDLES; i++) {
events[i] = (EVENT*)0;
refs[i] = (OBJECT*)0;
}
for (uint32_t i = 0; i < req.Count; i++) {
s = KiHandleToWaitEvent(proc, handles[i], &events[i], &refs[i]);
if (!KSUCCESS(s)) {
for (uint32_t j = 0; j < i; j++)
ObDereferenceObject(refs[j]);
return s;
}
}
uint8_t alertable = (req.Flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0;
uint32_t selected = 0;
s = KeWaitForMultipleEvents(events, req.Count, alertable, req.TimeoutMilliseconds, &selected);
for (uint32_t i = 0; i < req.Count; i++)
ObDereferenceObject(refs[i]);
if (KSUCCESS(s) && req.SignaledIndexOut) {
KSTATUS cs = KiCopyU32ToUser((uint64_t)(uintptr_t)req.SignaledIndexOut, selected);
if (!KSUCCESS(cs)) return cs;
}
if (s != STATUS_TIMEOUT && s != STATUS_NOT_SUPPORTED)
KiFinishAlertableUserWait(alertable);
return s;
}
// SYS_OPEN_INPUT_QUEUE a0=flags a1=*handleOut.
static uint64_t SyscallOpenInputQueue(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (a0 != 0) return STATUS_INVALID_PARAMETER;
if (!a1) return STATUS_INVALID_PARAMETER;
INPUT_QUEUE* queue = (INPUT_QUEUE*)0;
KSTATUS s = KeInputOpenQueue(&queue);
if (!KSUCCESS(s)) return s;
uint32_t handle = 0;
s = KiAllocHandleForCurrentWithRights(&queue->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY, &handle);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(a1, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
}
return s;
}
// SYS_GET_CURRENT_TID (3).
static uint64_t SyscallGetCurrentTid(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
THREAD* t = KeGetCurrentThread();
return t ? t->ThreadId : 0;
}
// SYS_GET_CURRENT_PID (4).
static uint64_t SyscallGetCurrentPid(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
PROCESS* p = KeGetCurrentProcess();
return p ? p->ProcessId : 0;
}
// SYS_YIELD (5).
static uint64_t SyscallYield(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
KeYield();
return STATUS_SUCCESS;
}
// SYS_CREATE_TIMER a0=*handleOut.
static uint64_t SyscallCreateTimer(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
KTIMER* timer = (KTIMER*)0;
KSTATUS s = KeTimerCreate(&timer);
if (!KSUCCESS(s)) return s;
uint32_t handle = 0;
s = KiAllocHandleForCurrentWithRights(&timer->Header,
HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY,
&handle);
if (!KSUCCESS(s))
return s;
s = KiCopyHandleToUser(a0, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
}
return s;
}
// SYS_SET_TIMER a0=*SYS_SET_TIMER_REQUEST.
static uint64_t SyscallSetTimer(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_SET_TIMER_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req) || req.Flags != SYS_TIMER_FLAG_NONE)
return STATUS_INVALID_PARAMETER;
KTIMER* timer = (KTIMER*)KiHandleToObject(req.TimerHandle, ObjectTypeTimer, HANDLE_RIGHT_SIGNAL);
if (!timer) return STATUS_INVALID_HANDLE;
s = KeTimerSet(timer, req.DueTimeMilliseconds, req.PeriodMilliseconds);
ObDereferenceObject(&timer->Header);
return s;
}
// SYS_CANCEL_TIMER a0=timerHandle.
static uint64_t SyscallCancelTimer(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
KTIMER* timer = (KTIMER*)KiHandleToObject((uint32_t)a0, ObjectTypeTimer, HANDLE_RIGHT_SIGNAL);
if (!timer) return STATUS_INVALID_HANDLE;
KSTATUS s = KeTimerCancel(timer);
ObDereferenceObject(&timer->Header);
return s;
}
// SYS_SLEEP a0=milliseconds.
// Sleep is a convenience wrapper over the same waitable timer backend exposed
// by timer handles. A zero-length sleep remains a yield.
static uint64_t SyscallSleep(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (a0 == 0) {
KeYield();
return STATUS_SUCCESS;
}
KTIMER* timer = (KTIMER*)0;
KSTATUS s = KeTimerCreate(&timer);
if (!KSUCCESS(s)) return s;
s = KeTimerSet(timer, a0, 0);
if (KSUCCESS(s)) {
EVENT* event = KeTimerWaitEvent(timer);
uint32_t selected = 0;
s = KeWaitForMultipleEvents(&event, 1, 0, SYS_WAIT_TIMEOUT_INFINITE, &selected);
}
ObDereferenceObject(&timer->Header);
return s;
}
// SYS_SET_USER_APC_DISPATCHER a0 = dispatcher entry in syscall.dll/user image.
static uint64_t SyscallSetUserApcDispatcher(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
KSTATUS s = MmProbeUserRead((const void*)(uintptr_t)a0, 1);
if (!KSUCCESS(s)) return s;
return KeSetUserApcDispatcher((void*)(uintptr_t)a0);
}
// SYS_APC_RETURN a0 = SYS_USER_APC_FRAME* on the user stack.
static uint64_t SyscallApcReturn(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
return KeUserApcReturn((const SYS_USER_APC_FRAME_K*)(uintptr_t)a0);
}
// SYS_SET_THREAD_PRIORITY (7) a0 = priority.
static uint64_t SyscallSetThreadPriority(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (a0 >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* t = KeGetCurrentThread();
if (!t) return STATUS_FAILURE;
t->Priority = (THREAD_PRIORITY)a0;
return STATUS_SUCCESS;
}
// SYS_VM_* page-granular user virtual memory.
static KSTATUS KiVmProtectToPageFlags(uint32_t protect, uint64_t* flagsOut) {
if (!flagsOut) return STATUS_INVALID_PARAMETER;
switch (protect) {
case SYS_VM_NOACCESS:
*flagsOut = 0;
return STATUS_SUCCESS;
case SYS_VM_READ:
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_NX;
return STATUS_SUCCESS;
case SYS_VM_READWRITE:
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_WRITE | PAGE_NX;
return STATUS_SUCCESS;
case SYS_VM_EXECUTE:
case SYS_VM_EXECUTE_READ:
*flagsOut = PAGE_PRESENT | PAGE_USER;
return STATUS_SUCCESS;
case SYS_VM_EXECUTE_READWRITE:
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_WRITE;
return STATUS_SUCCESS;
default:
return STATUS_INVALID_PARAMETER;
}
}
// Implements the page Flags To Vm Protect operation.
static uint32_t KiPageFlagsToVmProtect(uint64_t flags) {
if (!(flags & PAGE_PRESENT)) return SYS_VM_NOACCESS;
bool writable = (flags & PAGE_WRITE) != 0;
bool executable = (flags & PAGE_NX) == 0;
if (executable && writable) return SYS_VM_EXECUTE_READWRITE;
if (executable) return SYS_VM_EXECUTE_READ;
if (writable) return SYS_VM_READWRITE;
return SYS_VM_READ;
}
static uint64_t SyscallVmAlloc(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
void* preferredBase = (void*)(uintptr_t)a0;
size_t pageCount = (size_t)a1;
uint32_t protect = (uint32_t)a2;
uint32_t flags = (uint32_t)a3;
void** baseOut = (void**)(uintptr_t)a4;
if (!pageCount || !baseOut) return STATUS_INVALID_PARAMETER;
if (pageCount > ((size_t)-1) / PAGE_SIZE) return STATUS_NO_MEMORY;
if (preferredBase || (flags & SYS_VM_FLAG_FIXED)) return STATUS_NOT_SUPPORTED;
uint64_t pageFlags = 0;
KSTATUS s = KiVmProtectToPageFlags(protect, &pageFlags);
if (!KSUCCESS(s)) return s;
PROCESS* process = KeGetCurrentProcess();
if (!process) return STATUS_FAILURE;
void* base = (void*)0;
s = MmVadCreateCommitted(process, pageCount * PAGE_SIZE, PAGE_SIZE,
pageFlags,
MM_VAD_ZERO,
MmVadGeneric,
&base);
if (!KSUCCESS(s)) return s;
s = CopyToUser(baseOut, &base, sizeof(base));
if (!KSUCCESS(s)) {
MmVadDestroy(process, base);
return s;
}
return STATUS_SUCCESS;
}
static uint64_t SyscallVmFree(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
PROCESS* process = KeGetCurrentProcess();
if (!process || !a0) return STATUS_INVALID_PARAMETER;
return MmVadDestroy(process, (void*)(uintptr_t)a0);
}
static uint64_t SyscallVmProtect(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a4; (void)a5;
if (!a0 || !a1) return STATUS_INVALID_PARAMETER;
if ((size_t)a1 > ((size_t)-1) / PAGE_SIZE) return STATUS_NO_MEMORY;
uint64_t pageFlags = 0;
KSTATUS s = KiVmProtectToPageFlags((uint32_t)a2, &pageFlags);
if (!KSUCCESS(s)) return s;
PROCESS* process = KeGetCurrentProcess();
if (!process) return STATUS_FAILURE;
if (a3) {
void* regionBase = (void*)0;
size_t regionSize = 0;
uint64_t oldFlags = 0;
MM_VAD_TYPE type = MmVadGeneric;
uint32_t regionFlags = 0;
s = MmVadQuery(process, (void*)(uintptr_t)a0, &regionBase, &regionSize, &oldFlags, &type, &regionFlags);
if (!KSUCCESS(s)) return s;
uint32_t oldProtect = KiPageFlagsToVmProtect(oldFlags);
s = CopyToUser((void*)(uintptr_t)a3, &oldProtect, sizeof(oldProtect));
if (!KSUCCESS(s)) return s;
}
return MmVadProtect(process, (void*)(uintptr_t)a0, (size_t)a1 * PAGE_SIZE, pageFlags);
}
static uint64_t SyscallVmQuery(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a0 || !a1) return STATUS_INVALID_PARAMETER;
PROCESS* process = KeGetCurrentProcess();
if (!process) return STATUS_FAILURE;
void* base = (void*)0;
size_t size = 0;
uint64_t pageFlags = 0;
MM_VAD_TYPE type = MmVadGeneric;
uint32_t regionFlags = 0;
KSTATUS s = MmVadQuery(process, (void*)(uintptr_t)a0, &base, &size, &pageFlags, &type, &regionFlags);
if (!KSUCCESS(s)) return s;
SYS_VM_REGION_INFO info;
info.Base = base;
info.PageCount = size / PAGE_SIZE;
info.Protect = KiPageFlagsToVmProtect(pageFlags);
info.Flags = regionFlags;
info.Type = (uint32_t)type;
info.OwnerPid = process->ProcessId;
return CopyToUser((void*)(uintptr_t)a1, &info, sizeof(info));
}
// SYS_CREATE_MUTEX (11) a0 = *handleOut.
static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
MUTEX* mx = NULL;
KSTATUS s = KeMutexCreate(&mx);
if (!KSUCCESS(s)) return s;
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&mx->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h); // takes ownership of the ref
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(a0, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// SYS_TRY_ACQUIRE_MUTEX (13) a0 = handle.
static uint64_t SyscallMutexTryAcquire(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex, HANDLE_RIGHT_WAIT);
if (!mx) return STATUS_NOT_FOUND;
KSTATUS s = KeTryAcquireMutex(mx);
ObDereferenceObject(&mx->Header);
return s;
}
// SYS_RELEASE_MUTEX (14) a0 = handle.
static uint64_t SyscallMutexRelease(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex, HANDLE_RIGHT_SIGNAL);
if (!mx) return STATUS_NOT_FOUND;
KeReleaseMutex(mx);
ObDereferenceObject(&mx->Header);
return STATUS_SUCCESS;
}
// SYS_CREATE_SEM (15) a0=initial a1=max a2=*handleOut.
static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
if (!a2) return STATUS_INVALID_PARAMETER;
SEMAPHORE* sem = NULL;
KSTATUS s = KeSemaphoreCreate((int32_t)a0, (int32_t)a1, &sem);
if (!KSUCCESS(s)) return s;
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&sem->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(a2, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// SYS_TRY_WAIT_SEM (17) a0 = handle.
static uint64_t SyscallSemTryWait(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore, HANDLE_RIGHT_WAIT);
if (!sem) return STATUS_NOT_FOUND;
KSTATUS s = KeTrySemaphore(sem);
ObDereferenceObject(&sem->Header);
return s;
}
// SYS_SIGNAL_SEM (18) a0 = handle.
static uint64_t SyscallSemSignal(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore, HANDLE_RIGHT_SIGNAL);
if (!sem) return STATUS_NOT_FOUND;
KeSignalSemaphore(sem);
ObDereferenceObject(&sem->Header);
return STATUS_SUCCESS;
}
// SYS_CREATE_EVENT (19) a0=type a1=initialState a2=*handleOut.
static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
if (!a2 || a0 > EventManualReset) return STATUS_INVALID_PARAMETER;
EVENT* ev = NULL;
KSTATUS s = KeEventCreate((EVENT_TYPE)a0, (uint32_t)a1, &ev);
if (!KSUCCESS(s)) return s;
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&ev->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(a2, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// SYS_SIGNAL_EVENT (20) a0 = handle.
static uint64_t SyscallEventSignal(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL);
if (!ev) return STATUS_NOT_FOUND;
KeSignalEvent(ev);
ObDereferenceObject(&ev->Header);
return STATUS_SUCCESS;
}
// SYS_CLEAR_EVENT (21) a0 = handle.
static uint64_t SyscallEventClear(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL);
if (!ev) return STATUS_NOT_FOUND;
KeClearEvent(ev);
ObDereferenceObject(&ev->Header);
return STATUS_SUCCESS;
}
// SYS_CREATE_QUEUE (23) a0 = *handleOut.
static uint64_t SyscallQueueCreate(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
QUEUE* q = NULL;
KSTATUS s = KeQueueCreate(&q);
if (!KSUCCESS(s)) return s;
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &h);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(a0, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// SYS_SEND_QUEUE a0=*SYS_QUEUE_SEND_REQUEST.
static uint64_t SyscallQueueSend(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_QUEUE_SEND_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req) || req.Flags != SYS_QUEUE_FLAG_NONE || !req.Message)
return STATUS_INVALID_PARAMETER;
QUEUE_MESSAGE msg;
s = CopyFromUser(&msg, req.Message, sizeof(msg));
if (!KSUCCESS(s)) return s;
QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_WRITE);
if (!q) return STATUS_NOT_FOUND;
s = KeSendMessage(q, &msg, req.TimeoutMilliseconds);
ObDereferenceObject(&q->Header);
return s;
}
// SYS_RECV_QUEUE a0=*SYS_QUEUE_RECV_REQUEST.
static uint64_t SyscallQueueRecv(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_QUEUE_RECV_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req) || req.Flags != SYS_QUEUE_FLAG_NONE || !req.MessageOut)
return STATUS_INVALID_PARAMETER;
QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_READ);
if (!q) return STATUS_NOT_FOUND;
QUEUE_MESSAGE msg;
s = KeReceiveMessage(q, &msg, req.TimeoutMilliseconds);
ObDereferenceObject(&q->Header);
if (!KSUCCESS(s)) return s;
return CopyToUser(req.MessageOut, &msg, sizeof(msg));
}
// SYS_CLOSE_HANDLE (26) a0 = handle
// The one and only close path. KeCloseHandle drops the handle-table reference
// on 'obj'. If that was the last reference, ObDereferenceObject runs the
// type-specific Destroy callback (KiFileDestroy, etc.) and frees the pool memory.
// No per-type close syscall needed or wanted.
static uint64_t SyscallCreateFileMapping(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
uint32_t fileHandle = (uint32_t)a0;
uint32_t protect = (uint32_t)a1;
uint32_t flags = (uint32_t)a2;
uint64_t maximumSize = a3;
uint32_t* handleOut = (uint32_t*)(uintptr_t)a4;
if (!fileHandle || !handleOut)
return STATUS_INVALID_PARAMETER;
uint64_t pageFlags = 0;
KSTATUS s = KiVmProtectToPageFlags(protect, &pageFlags);
if (!KSUCCESS(s)) return s;
if (!pageFlags) return STATUS_ACCESS_DENIED;
uint32_t neededRights = HANDLE_RIGHT_READ | HANDLE_RIGHT_QUERY;
if (pageFlags & PAGE_WRITE)
neededRights |= HANDLE_RIGHT_WRITE;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(fileHandle, ObjectTypeFile, neededRights);
if (!file) return STATUS_NOT_FOUND;
if (maximumSize == 0)
maximumSize = IoGetFileSize(file);
FILE_MAPPING_OBJECT* mapping = (FILE_MAPPING_OBJECT*)ObAllocateObject(ObjectTypeFileMapping,
sizeof(FILE_MAPPING_OBJECT));
if (!mapping) {
ObDereferenceObject(&file->Header);
return STATUS_NO_MEMORY;
}
mapping->Header.Destroy = KiFileMappingDestroy;
mapping->File = file; // KiHandleToObject returned the mapping object's file reference.
mapping->MaximumSize = maximumSize;
mapping->PageFlags = pageFlags;
mapping->Protect = protect;
mapping->Flags = flags;
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&mapping->Header,
HANDLE_RIGHT_READ | HANDLE_RIGHT_QUERY,
&h);
if (!KSUCCESS(s))
return s;
s = KiCopyHandleToUser((uint64_t)(uintptr_t)handleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
static KSTATUS KiMapAccessToPageFlags(const FILE_MAPPING_OBJECT* mapping,
uint32_t access,
uint64_t* pageFlagsOut) {
if (!mapping || !pageFlagsOut)
return STATUS_INVALID_PARAMETER;
uint64_t mappingFlags = mapping->PageFlags;
if (!mappingFlags)
return STATUS_ACCESS_DENIED;
if (access == 0)
access = SYS_MAP_ACCESS_READ;
if ((access & SYS_MAP_ACCESS_WRITE) && (access & SYS_MAP_ACCESS_COPY))
return STATUS_INVALID_PARAMETER;
if (((access & SYS_MAP_ACCESS_COPY) || (access & SYS_MAP_ACCESS_WRITE)) &&
(mappingFlags & PAGE_WRITE) == 0)
return STATUS_ACCESS_DENIED;
uint64_t flags = PAGE_PRESENT | PAGE_USER;
if ((access & SYS_MAP_ACCESS_COPY) || (access & SYS_MAP_ACCESS_WRITE))
flags |= PAGE_WRITE;
if (access & SYS_MAP_ACCESS_EXECUTE) {
if (mappingFlags & PAGE_NX)
return STATUS_ACCESS_DENIED;
} else {
flags |= PAGE_NX;
}
*pageFlagsOut = flags;
return STATUS_SUCCESS;
}
static uint64_t SyscallMapFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
uint32_t mappingHandle = (uint32_t)a0;
uint64_t offset = a1;
size_t size = (size_t)a2;
uint32_t access = (uint32_t)a3;
uint32_t flags = (uint32_t)a4;
void** baseOut = (void**)(uintptr_t)a5;
if (!mappingHandle || !baseOut)
return STATUS_INVALID_PARAMETER;
if (offset & (PAGE_SIZE - 1))
return STATUS_INVALID_PARAMETER;
FILE_MAPPING_OBJECT* mapping = (FILE_MAPPING_OBJECT*)KiHandleToObject(mappingHandle,
ObjectTypeFileMapping,
HANDLE_RIGHT_READ);
if (!mapping)
return STATUS_NOT_FOUND;
uint64_t maximumSize = mapping->MaximumSize;
if (maximumSize == 0)
maximumSize = IoGetFileSize(mapping->File);
if (offset >= maximumSize) {
ObDereferenceObject(&mapping->Header);
return STATUS_INVALID_PARAMETER;
}
if (size == 0) {
uint64_t tail = maximumSize - offset;
if (tail > (uint64_t)((size_t)-1)) {
ObDereferenceObject(&mapping->Header);
return STATUS_NO_MEMORY;
}
size = (size_t)tail;
}
if (size == 0) {
ObDereferenceObject(&mapping->Header);
return STATUS_INVALID_PARAMETER;
}
if ((uint64_t)size > maximumSize - offset) {
ObDereferenceObject(&mapping->Header);
return STATUS_INVALID_PARAMETER;
}
uint64_t pageFlags = 0;
KSTATUS s = KiMapAccessToPageFlags(mapping, access, &pageFlags);
if (!KSUCCESS(s)) {
ObDereferenceObject(&mapping->Header);
return s;
}
PROCESS* process = KeGetCurrentProcess();
if (!process) {
ObDereferenceObject(&mapping->Header);
return STATUS_FAILURE;
}
uint32_t vadFlags = flags;
if (access & SYS_MAP_ACCESS_WRITE)
vadFlags |= MM_VAD_SHARED_WRITE;
void* base = (void*)0;
s = MmVadCreateFileMapping(process, mapping->File, offset, size,
pageFlags, vadFlags, &base);
ObDereferenceObject(&mapping->Header);
if (!KSUCCESS(s))
return s;
s = CopyToUser(baseOut, &base, sizeof(base));
if (!KSUCCESS(s)) {
MmVadDestroy(process, base);
return s;
}
return STATUS_SUCCESS;
}
static uint64_t SyscallCloseHandle(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
OBJECT* obj = KeLookupHandleWithRights(proc, (uint32_t)a0, 0);
if (obj) {
if (obj->Type == ObjectTypeFile) {
KSTATUS fs = IoFlushFile((FILE_OBJECT*)obj);
ObDereferenceObject(obj);
if (!KSUCCESS(fs))
return fs;
} else {
ObDereferenceObject(obj);
}
}
KeCloseHandle(proc, (uint32_t)a0);
return STATUS_SUCCESS;
}
// Converts public file access flags to handle rights and file object flags.
static KSTATUS KiFileAccessToHandleRights(uint32_t access, uint32_t* rightsOut, uint32_t* fileFlagsOut) {
if (!rightsOut || !fileFlagsOut)
return STATUS_INVALID_PARAMETER;
uint32_t valid = SYS_FILE_ACCESS_READ |
SYS_FILE_ACCESS_WRITE |
SYS_FILE_ACCESS_APPEND |
SYS_FILE_ACCESS_QUERY |
SYS_FILE_ACCESS_LOCK;
if ((access & ~valid) != 0 || access == 0)
return STATUS_INVALID_PARAMETER;
uint32_t rights = 0;
uint32_t fileFlags = 0;
if (access & SYS_FILE_ACCESS_READ)
rights |= HANDLE_RIGHT_READ;
if (access & SYS_FILE_ACCESS_WRITE)
rights |= HANDLE_RIGHT_WRITE;
if (access & SYS_FILE_ACCESS_APPEND) {
rights |= HANDLE_RIGHT_WRITE;
fileFlags |= IO_FILE_FLAG_APPEND;
}
if (access & SYS_FILE_ACCESS_QUERY)
rights |= HANDLE_RIGHT_QUERY;
if (access & SYS_FILE_ACCESS_LOCK)
rights |= HANDLE_RIGHT_LOCK;
*rightsOut = rights;
*fileFlagsOut = fileFlags;
return STATUS_SUCCESS;
}
// Opens a file using the public disposition contract.
static KSTATUS KiOpenFileByDisposition(SYS_STRING_VIEW path,
uint32_t disposition,
uint32_t access,
FILE_OBJECT** fileOut) {
if (!fileOut)
return STATUS_INVALID_PARAMETER;
*fileOut = NULL;
bool writes = (access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND)) != 0;
KSTATUS s;
switch (disposition) {
case SYS_FILE_OPEN_EXISTING:
return IoOpenFile(path, fileOut);
case SYS_FILE_CREATE_NEW:
if (!writes)
return STATUS_ACCESS_DENIED;
return IoCreateFile(path, fileOut);
case SYS_FILE_CREATE_ALWAYS:
if (!writes)
return STATUS_ACCESS_DENIED;
s = IoCreateFile(path, fileOut);
if (s == STATUS_OBJECT_NAME_COLLISION) {
s = IoOpenFile(path, fileOut);
if (KSUCCESS(s)) {
KSTATUS ts = IoTruncateFile(*fileOut, 0);
if (!KSUCCESS(ts)) {
IoCloseFile(*fileOut);
*fileOut = NULL;
return ts;
}
}
}
return s;
case SYS_FILE_OPEN_ALWAYS:
s = IoOpenFile(path, fileOut);
if (s == STATUS_NOT_FOUND) {
if (!writes)
return STATUS_ACCESS_DENIED;
s = IoCreateFile(path, fileOut);
}
return s;
case SYS_FILE_TRUNCATE_EXISTING:
if (!writes)
return STATUS_ACCESS_DENIED;
s = IoOpenFile(path, fileOut);
if (KSUCCESS(s)) {
KSTATUS ts = IoTruncateFile(*fileOut, 0);
if (!KSUCCESS(ts)) {
IoCloseFile(*fileOut);
*fileOut = NULL;
return ts;
}
}
return s;
default:
return STATUS_INVALID_PARAMETER;
}
}
// Implements SYS_OPEN_FILE.
static uint64_t SyscallOpenFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_OPEN_FILE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.HandleOut) return STATUS_INVALID_PARAMETER;
if (req.Flags != SYS_FILE_FLAG_NONE) return STATUS_INVALID_PARAMETER;
uint32_t rights = 0;
uint32_t fileFlags = 0;
s = KiFileAccessToHandleRights(req.Access, &rights, &fileFlags);
if (!KSUCCESS(s)) return s;
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
FILE_OBJECT* file = NULL;
s = KiOpenFileByDisposition(path, req.Disposition, req.Access, &file);
if (!KSUCCESS(s)) return s;
if (fileFlags != 0) {
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
file->Flags |= fileFlags;
KeReleaseSpinlock(&file->Lock, flags);
}
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&file->Header, rights, &h);
if (!KSUCCESS(s)) {
IoCloseFile(file);
return s;
}
s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
static void* KiAllocateSyscallIoBuffer(size_t requested, size_t* actualOut) {
if (actualOut) *actualOut = 0;
if (!requested) return NULL;
size_t chunk = requested < SYSCALL_FILE_IO_CHUNK_BYTES ? requested : SYSCALL_FILE_IO_CHUNK_BYTES;
for (;;) {
void* buf = MmAllocatePoolTag(chunk, MM_POOL_TAG('s', 'i', 'o', ' '));
if (buf) {
if (actualOut) *actualOut = chunk;
return buf;
}
if (chunk <= PAGE_SIZE)
break;
chunk >>= 1;
if (chunk < PAGE_SIZE)
chunk = PAGE_SIZE;
}
return NULL;
}
// Imports and validates a synchronous file I/O request.
static KSTATUS KiImportSyncFileIoRequest(uint64_t arg, SYS_FILE_IO_REQUEST* req) {
KSTATUS s = KiCopyUserRequest(arg, req, sizeof(*req));
if (!KSUCCESS(s))
return s;
if (req->Flags != 0)
return STATUS_INVALID_PARAMETER;
if (req->Size > SYSCALL_IO_MAX_BYTES)
return STATUS_INVALID_PARAMETER;
if (req->Size && !req->Buffer)
return STATUS_INVALID_PARAMETER;
return STATUS_SUCCESS;
}
// SYS_READ_FILE a0=*SYS_FILE_IO_REQUEST.
static uint64_t SyscallReadFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_FILE_IO_REQUEST req;
KSTATUS s = KiImportSyncFileIoRequest(a0, &req);
if (!KSUCCESS(s))
return s;
if (req.Size == 0) {
if (req.TransferredOut)
return KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, 0);
return STATUS_SUCCESS;
}
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_READ);
if (!file) return STATUS_NOT_FOUND;
size_t chunkSize = 0;
void* kbuf = KiAllocateSyscallIoBuffer(req.Size, &chunkSize);
if (!kbuf) {
ObDereferenceObject(&file->Header);
return STATUS_NO_MEMORY;
}
uintptr_t user = (uintptr_t)req.Buffer;
size_t total = 0;
s = STATUS_SUCCESS;
while (total < req.Size) {
size_t todo = req.Size - total;
if (todo > chunkSize) todo = chunkSize;
if (user > (uintptr_t)-1 - total) {
s = STATUS_INVALID_PARAMETER;
break;
}
void* userChunk = (void*)(user + total);
s = MmProbeUserWrite(userChunk, todo);
if (!KSUCCESS(s)) break;
size_t got = 0;
s = IoReadFile(file, kbuf, todo, &got);
if (!KSUCCESS(s)) break;
if (got) {
s = CopyToUser(userChunk, kbuf, got);
if (!KSUCCESS(s)) break;
total += got;
}
if (got < todo)
break;
}
MmFreePool(kbuf);
ObDereferenceObject(&file->Header);
if (req.TransferredOut) {
KSTATUS cs = KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, total);
if (KSUCCESS(s)) s = cs;
}
return s;
}
// SYS_WRITE_FILE a0=*SYS_FILE_IO_REQUEST.
static uint64_t SyscallWriteFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_FILE_IO_REQUEST req;
KSTATUS s = KiImportSyncFileIoRequest(a0, &req);
if (!KSUCCESS(s))
return s;
if (req.Size == 0) {
if (req.TransferredOut)
return KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, 0);
return STATUS_SUCCESS;
}
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_WRITE);
if (!file) return STATUS_NOT_FOUND;
size_t chunkSize = 0;
void* kbuf = KiAllocateSyscallIoBuffer(req.Size, &chunkSize);
if (!kbuf) {
ObDereferenceObject(&file->Header);
return STATUS_NO_MEMORY;
}
uintptr_t user = (uintptr_t)req.Buffer;
size_t total = 0;
s = STATUS_SUCCESS;
while (total < req.Size) {
size_t todo = req.Size - total;
if (todo > chunkSize) todo = chunkSize;
if (user > (uintptr_t)-1 - total) {
s = STATUS_INVALID_PARAMETER;
break;
}
s = CopyFromUser(kbuf, (const void*)(user + total), todo);
if (!KSUCCESS(s)) break;
size_t written = 0;
s = IoWriteFile(file, kbuf, todo, &written);
if (!KSUCCESS(s)) break;
total += written;
if (written < todo)
break;
}
MmFreePool(kbuf);
ObDereferenceObject(&file->Header);
if (req.TransferredOut) {
KSTATUS cs = KiCopySizeToUser((uint64_t)(uintptr_t)req.TransferredOut, total);
if (KSUCCESS(s)) s = cs;
}
return s;
}
static void SyscallUserAsyncKernelRoutine(KAPC* apc,
void** normalRoutine,
void** normalContext,
void** systemArgument1,
void** systemArgument2) {
(void)normalRoutine;
(void)normalContext;
(void)systemArgument1;
(void)systemArgument2;
SYS_USER_ASYNC_APC_CONTEXT* ctx = CONTAINING_RECORD(apc, SYS_USER_ASYNC_APC_CONTEXT, KernelApc);
DbgPrint("USER/APC async: kernel routine ctx=0x%016llX iosb=0x%016llX status=%u info=%llu irql=%u\n",
(uint64_t)ctx,
(uint64_t)(uintptr_t)apc->SystemArgument1,
(uint32_t)(uintptr_t)apc->SystemArgument2,
apc->SystemArgument1 ? ((SYS_IO_STATUS_BLOCK_K*)apc->SystemArgument1)->Information : 0,
KeGetCurrentIrql());
}
// Implements the user Async Apc Context Free operation.
static void SyscallUserAsyncApcContextFree(SYS_USER_ASYNC_APC_CONTEXT* ctx) {
if (!ctx) return;
if (ctx->TargetThread) {
ObDereferenceObject(&ctx->TargetThread->Header);
ctx->TargetThread = (THREAD*)0;
}
MmFreePool(ctx);
}
// Implements the user Async Apc Rundown operation.
static void SyscallUserAsyncApcRundown(KAPC* apc) {
if (!apc) return;
SYS_USER_ASYNC_APC_CONTEXT* ctx = CONTAINING_RECORD(apc, SYS_USER_ASYNC_APC_CONTEXT, KernelApc);
SyscallUserAsyncApcContextFree(ctx);
}
static void SyscallUserAsyncNormalRoutine(void* normalContext,
void* systemArgument1,
void* systemArgument2) {
SYS_USER_ASYNC_APC_CONTEXT* ctx = (SYS_USER_ASYNC_APC_CONTEXT*)normalContext;
SYS_IO_STATUS_BLOCK_K* iosb = (SYS_IO_STATUS_BLOCK_K*)systemArgument1;
uint64_t information = iosb ? iosb->Information : 0;
(void)systemArgument2;
if (ctx && ctx->UserRoutine) {
KSTATUS s = KeQueueUserApc(ctx->TargetThread,
(void*)ctx->UserRoutine,
ctx->UserContext,
iosb,
(void*)(uintptr_t)information);
if (!KSUCCESS(s)) {
DbgPrint("USER/APC async: KeQueueUserApc failed status=%u\n", s);
}
}
SyscallUserAsyncApcContextFree(ctx);
}
// Implements the async File Common operation.
static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) {
if (!a0) return STATUS_INVALID_PARAMETER;
SYS_ASYNC_FILE_REQUEST_K req;
KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags) return STATUS_INVALID_PARAMETER;
if (!req.Iosb) return STATUS_INVALID_PARAMETER;
if (!req.Buffer && req.Size) return STATUS_INVALID_PARAMETER;
if (req.Size > SYSCALL_IO_MAX_BYTES) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
if (req.RequestHandleOut) {
s = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut));
if (!KSUCCESS(s)) return s;
}
FILE_OBJECT* file =
(FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, write ? HANDLE_RIGHT_WRITE : HANDLE_RIGHT_READ);
if (!file) return STATUS_NOT_FOUND;
EVENT* event = (EVENT*)0;
if (req.EventHandle) {
event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL);
if (!event) {
ObDereferenceObject(&file->Header);
return STATUS_NOT_FOUND;
}
}
KAPC* completionApc = (KAPC*)0;
SYS_USER_ASYNC_APC_CONTEXT* apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)0;
if (req.ApcRoutine) {
THREAD* thread = KeGetCurrentThread();
if (!thread || !thread->UserApcDispatcher) {
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return STATUS_INVALID_PARAMETER;
}
apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)MmAllocatePoolTag(sizeof(SYS_USER_ASYNC_APC_CONTEXT), MM_POOL_TAG('u', 'a', 'p', 'c'));
if (!apcCtx) {
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return STATUS_NO_MEMORY;
}
memset(apcCtx, 0, sizeof(*apcCtx));
ObReferenceObject(&thread->Header);
apcCtx->TargetThread = thread;
apcCtx->UserRoutine = req.ApcRoutine;
apcCtx->UserContext = req.ApcContext;
KeInitializeApc(&apcCtx->KernelApc,
thread,
SyscallUserAsyncKernelRoutine,
SyscallUserAsyncNormalRoutine,
apcCtx);
KeSetApcRundownRoutine(&apcCtx->KernelApc, SyscallUserAsyncApcRundown);
completionApc = &apcCtx->KernelApc;
}
MM_LOCKED_USER_BUFFER* lockedBuffer = (MM_LOCKED_USER_BUFFER*)0;
MM_LOCKED_USER_BUFFER* lockedIosb = (MM_LOCKED_USER_BUFFER*)0;
s = MmLockUserBuffer(proc, req.Iosb, sizeof(*req.Iosb), 1, &lockedIosb);
if (!KSUCCESS(s)) {
if (apcCtx)
SyscallUserAsyncApcContextFree(apcCtx);
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return s;
}
if (req.Size) {
s = MmLockUserBuffer(proc, req.Buffer, req.Size, write ? 0 : 1, &lockedBuffer);
if (!KSUCCESS(s)) {
MmUnlockUserBuffer(lockedIosb);
if (apcCtx)
SyscallUserAsyncApcContextFree(apcCtx);
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return s;
}
}
IO_ASYNC_FILE_REQUEST_OBJECT* asyncReq = (IO_ASYNC_FILE_REQUEST_OBJECT*)0;
if (write) {
s = IoWriteFileAsyncKernel(file, req.Buffer, req.Size, req.Offset,
(IO_STATUS_BLOCK*)req.Iosb, event,
completionApc,
lockedBuffer,
lockedIosb,
&asyncReq);
} else {
s = IoReadFileAsyncKernel(file, req.Buffer, req.Size, req.Offset,
(IO_STATUS_BLOCK*)req.Iosb, event,
completionApc,
lockedBuffer,
lockedIosb,
&asyncReq);
}
lockedBuffer = (MM_LOCKED_USER_BUFFER*)0;
lockedIosb = (MM_LOCKED_USER_BUFFER*)0;
if (s == STATUS_PENDING && req.RequestHandleOut && asyncReq) {
uint32_t requestHandle = 0;
KSTATUS hs = KeAllocateHandleWithRights(proc, IoAsyncFileRequestObject(asyncReq),
HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE,
&requestHandle);
if (!KSUCCESS(hs)) {
(void)IoCancelAsyncFileRequest(asyncReq);
ObDereferenceObject(IoAsyncFileRequestObject(asyncReq));
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return hs;
}
KSTATUS cs = CopyToUser(req.RequestHandleOut, &requestHandle, sizeof(requestHandle));
if (!KSUCCESS(cs)) {
(void)IoCancelAsyncFileRequest(asyncReq);
KeCloseHandle(proc, requestHandle);
ObDereferenceObject(IoAsyncFileRequestObject(asyncReq));
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return cs;
}
} else if (req.RequestHandleOut) {
uint32_t zeroHandle = 0;
KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle));
if (!KSUCCESS(cs)) {
if (asyncReq) ObDereferenceObject(IoAsyncFileRequestObject(asyncReq));
if (s != STATUS_PENDING && apcCtx)
SyscallUserAsyncApcContextFree(apcCtx);
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return cs;
}
}
if (asyncReq)
ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); // drop creator reference
if (s != STATUS_PENDING && apcCtx)
SyscallUserAsyncApcContextFree(apcCtx);
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return s;
}
// SYS_READ_FILE_ASYNC (43) a0=*SYS_ASYNC_FILE_REQUEST.
static uint64_t SyscallReadFileAsync(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
return SyscallAsyncFileCommon(a0, false);
}
// SYS_WRITE_FILE_ASYNC (44) a0=*SYS_ASYNC_FILE_REQUEST.
static uint64_t SyscallWriteFileAsync(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
return SyscallAsyncFileCommon(a0, true);
}
// SYS_CANCEL_IO a0 = async request handle.
static uint64_t SyscallCancelIo(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
IO_ASYNC_REQUEST_OBJECT* req =
(IO_ASYNC_REQUEST_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeAsyncIo, HANDLE_RIGHT_TERMINATE);
if (!req) return STATUS_INVALID_HANDLE;
KSTATUS s = IoCancelAsyncRequest(req);
ObDereferenceObject(IoAsyncRequestObject(req));
return s;
}
// SYS_DELETE_FILE (31) a0=*path.
static uint64_t SyscallDeleteFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_PATH_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
return IoDeleteFile(path);
}
// SYS_TRUNCATE_FILE (32) a0=handle a1=newSize.
static uint64_t SyscallTruncateFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_WRITE);
if (!file) return STATUS_NOT_FOUND;
KSTATUS s = IoTruncateFile(file, (uint64_t)a1);
ObDereferenceObject(&file->Header);
return s;
}
// SYS_FLUSH_FILE a0=handle.
static uint64_t SyscallFlushFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_WRITE);
if (!file) return STATUS_NOT_FOUND;
KSTATUS s = IoFlushFile(file);
ObDereferenceObject(&file->Header);
return s;
}
// SYS_GET_FILE_POS (33) a0=handle a1=*positionOut.
static uint64_t SyscallGetFilePos(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY);
if (!file) return STATUS_INVALID_HANDLE;
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
uint64_t pos = file->Offset;
KeReleaseSpinlock(&file->Lock, flags);
ObDereferenceObject(&file->Header);
return KiCopyU64ToUser(a1, pos);
}
// SYS_SEEK_FILE (34) a0=handle a1=offset.
static uint64_t SyscallSeekFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY);
if (!file) return STATUS_NOT_FOUND;
KSTATUS s = IoSeekFile(file, (uint64_t)a1);
ObDereferenceObject(&file->Header);
return s;
}
// SYS_GET_FILE_SIZE (35) a0=handle a1=*sizeOut.
static uint64_t SyscallGetFileSize(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY);
if (!file) return STATUS_INVALID_HANDLE;
uint64_t sz = IoGetFileSize(file);
ObDereferenceObject(&file->Header);
return KiCopyU64ToUser(a1, sz);
}
// SYS_GET_FILE_STAT a0=handle a1=*SYS_FILE_STAT.
static uint64_t SyscallGetFileStat(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY);
if (!file) return STATUS_INVALID_HANDLE;
SYS_FILE_STAT stat;
KSTATUS s = IoGetFileStat(file, &stat);
ObDereferenceObject(&file->Header);
if (!KSUCCESS(s)) return s;
return CopyToUser((void*)(uintptr_t)a1, &stat, sizeof(stat));
}
// SYS_STAT_FILE a0=*SYS_STAT_FILE_REQUEST.
static uint64_t SyscallStatFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_STAT_FILE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.StatOut) return STATUS_INVALID_PARAMETER;
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
SYS_FILE_STAT stat;
s = IoStatFile(path, &stat);
if (!KSUCCESS(s)) return s;
return CopyToUser(req.StatOut, &stat, sizeof(stat));
}
// SYS_CREATE_SURFACE (99) a0=width a1=height a2=format a3=flags a4=*handleOut.
static uint64_t SyscallCreateSurface(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
uint32_t width = (uint32_t)a0;
uint32_t height = (uint32_t)a1;
uint32_t format = (uint32_t)a2;
uint32_t flags = (uint32_t)a3;
if (!width || !height || !a4)
return STATUS_INVALID_PARAMETER;
if (format != SYS_SURFACE_FORMAT_XRGB8888 || flags != SYS_SURFACE_FLAG_NONE)
return STATUS_NOT_SUPPORTED;
DISPLAY_MODE mode;
KSTATUS s = KeDisplayGetMode(&mode);
if (!KSUCCESS(s))
return s;
if (width > mode.Width || height > mode.Height)
return STATUS_INVALID_PARAMETER;
size_t rowBytes = 0;
if (!KiMulSize((size_t)width, sizeof(uint32_t), &rowBytes))
return STATUS_INVALID_PARAMETER;
size_t pitchBytes = KiAlignUpSize(rowBytes, 64);
if (pitchBytes < rowBytes)
return STATUS_INVALID_PARAMETER;
size_t payloadSize = 0;
if (!KiMulSize(pitchBytes, (size_t)height, &payloadSize))
return STATUS_INVALID_PARAMETER;
size_t pageCount = (payloadSize + PAGE_SIZE - 1) / PAGE_SIZE;
if (!pageCount || pageCount > (SIZE_MAX / sizeof(PHYSICAL_ADDRESS)))
return STATUS_INVALID_PARAMETER;
SURFACE_OBJECT* surface = (SURFACE_OBJECT*)ObAllocateObject(ObjectTypeSurface, sizeof(SURFACE_OBJECT));
if (!surface)
return STATUS_NO_MEMORY;
surface->Header.Destroy = KiSurfaceDestroy;
surface->Owner = KeGetCurrentProcess();
surface->Width = width;
surface->Height = height;
surface->PitchBytes = (uint32_t)pitchBytes;
surface->Format = format;
surface->Flags = flags;
surface->PayloadSize = payloadSize;
surface->PageCount = pageCount;
surface->Frames = (PHYSICAL_ADDRESS*)MmAllocatePoolTag(pageCount * sizeof(PHYSICAL_ADDRESS), MM_POOL_TAG('s', 'f', 'r', 'm'));
surface->RowScratch = (uint32_t*)MmAllocatePoolTag(rowBytes, MM_POOL_TAG('s', 'r', 'o', 'w'));
s = KeMutexCreate(&surface->Lock);
if (!surface->Frames || !surface->RowScratch || !KSUCCESS(s)) {
ObDereferenceObject(&surface->Header);
return STATUS_NO_MEMORY;
}
memset(surface->Frames, 0, pageCount * sizeof(PHYSICAL_ADDRESS));
for (size_t i = 0; i < pageCount; ++i) {
PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseSurface);
if (!frame) {
ObDereferenceObject(&surface->Header);
return STATUS_NO_MEMORY;
}
surface->Frames[i] = frame;
void* kva = MmPfnToVirtual(frame);
if (kva)
memset(kva, 0, PAGE_SIZE);
}
uint32_t handle = 0;
s = KiAllocHandleForCurrentWithRights(&surface->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &handle);
if (!KSUCCESS(s))
return s;
KiSurfaceStatsRegister(surface);
s = CopyToUser((void*)(uintptr_t)a4, &handle, sizeof(handle));
if (!KSUCCESS(s)) {
KeCloseHandle(KeGetCurrentProcess(), handle);
return s;
}
return STATUS_SUCCESS;
}
// SYS_MAP_SURFACE (100) a0=surfaceHandle a1=*baseOut a2=*pitchBytesOut.
static uint64_t SyscallMapSurface(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
if (!a1 || !a2)
return STATUS_INVALID_PARAMETER;
SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSurface, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!surface)
return STATUS_INVALID_PARAMETER;
PROCESS* current = KeGetCurrentProcess();
if (surface->Owner != current) {
ObDereferenceObject(&surface->Header);
return STATUS_ACCESS_DENIED;
}
KSTATUS s = KeAcquireMutex(surface->Lock);
if (!KSUCCESS(s)) {
ObDereferenceObject(&surface->Header);
return s;
}
if (!surface->UserBase) {
s = MmVadCreateSharedViewFromFrames(current, surface->Frames, surface->PageCount,
surface->PayloadSize,
PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX,
MM_VAD_ZERO, MmVadShared, &surface->UserBase);
}
void* userBase = surface->UserBase;
uint32_t pitchBytes = surface->PitchBytes;
KeReleaseMutex(surface->Lock);
if (KSUCCESS(s))
s = CopyToUser((void*)(uintptr_t)a1, &userBase, sizeof(userBase));
if (KSUCCESS(s))
s = CopyToUser((void*)(uintptr_t)a2, &pitchBytes, sizeof(pitchBytes));
if (KSUCCESS(s))
KiSurfaceStatsRecordMap(surface);
ObDereferenceObject(&surface->Header);
return s;
}
// SYS_QUERY_SURFACE (101) a0=surfaceHandle a1=*SYS_SURFACE_DESC.
static uint64_t SyscallQuerySurface(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1)
return STATUS_INVALID_PARAMETER;
SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSurface, HANDLE_RIGHT_QUERY);
if (!surface)
return STATUS_INVALID_PARAMETER;
if (surface->Owner != KeGetCurrentProcess()) {
ObDereferenceObject(&surface->Header);
return STATUS_ACCESS_DENIED;
}
SYS_SURFACE_DESC desc;
memset(&desc, 0, sizeof(desc));
KSTATUS s = KeAcquireMutex(surface->Lock);
if (KSUCCESS(s)) {
desc.Width = surface->Width;
desc.Height = surface->Height;
desc.PitchBytes = surface->PitchBytes;
desc.Format = surface->Format;
desc.Base = surface->UserBase;
desc.PayloadBytes = surface->PayloadSize;
desc.PageCount = surface->PageCount;
KeReleaseMutex(surface->Lock);
uintptr_t statsFlags = KeAcquireSpinlock(&gSurfaceStatsLock);
desc.MapCount = surface->MapCount;
desc.PresentCount = surface->PresentCount;
desc.PresentRectCount = surface->PresentRectCount;
desc.PresentClippedRectCount = surface->PresentClippedRectCount;
desc.PresentEmptyClipCount = surface->PresentEmptyClipCount;
desc.PresentRowsCopied = surface->PresentRowsCopied;
desc.PresentBytesCopied = surface->PresentBytesCopied;
KeReleaseSpinlock(&gSurfaceStatsLock, statsFlags);
s = CopyToUser((void*)(uintptr_t)a1, &desc, sizeof(desc));
}
ObDereferenceObject(&surface->Header);
return s;
}
static int KiClipSurfaceRect(const SURFACE_OBJECT* surface, const DISPLAY_RECT* in, DISPLAY_RECT* out) {
if (!surface || !out)
return 0;
DISPLAY_RECT r;
if (in) {
r = *in;
} else {
r.X = 0;
r.Y = 0;
r.W = (int32_t)surface->Width;
r.H = (int32_t)surface->Height;
}
if (r.W <= 0 || r.H <= 0)
return 0;
int64_t x0 = r.X;
int64_t y0 = r.Y;
int64_t x1 = x0 + r.W;
int64_t y1 = y0 + r.H;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 > surface->Width) x1 = surface->Width;
if (y1 > surface->Height) y1 = surface->Height;
if (x0 >= x1 || y0 >= y1)
return 0;
out->X = (int32_t)x0;
out->Y = (int32_t)y0;
out->W = (int32_t)(x1 - x0);
out->H = (int32_t)(y1 - y0);
return 1;
}
// SYS_PRESENT_SURFACE (102) a0=surfaceHandle a1=*DISPLAY_RECT[] a2=rectCount.
static uint64_t SyscallPresentSurface(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
uint32_t rectCount = (uint32_t)a2;
if (rectCount > SYS_SURFACE_MAX_RECTS)
return STATUS_INVALID_PARAMETER;
if (rectCount != 0 && !a1)
return STATUS_INVALID_PARAMETER;
SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSurface, HANDLE_RIGHT_READ);
if (!surface)
return STATUS_INVALID_PARAMETER;
if (surface->Owner != KeGetCurrentProcess()) {
ObDereferenceObject(&surface->Header);
return STATUS_ACCESS_DENIED;
}
DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS];
if (rectCount == 0) {
rects[0].X = 0;
rects[0].Y = 0;
rects[0].W = (int32_t)surface->Width;
rects[0].H = (int32_t)surface->Height;
rectCount = 1;
} else {
KSTATUS cs = CopyFromUser(rects, (const void*)(uintptr_t)a1, (size_t)rectCount * sizeof(DISPLAY_RECT));
if (!KSUCCESS(cs)) {
ObDereferenceObject(&surface->Header);
return cs;
}
}
uint64_t inputRects = (uint64_t)a2;
uint64_t clippedRects = 0;
uint64_t emptyClips = 0;
uint64_t rowsCopied = 0;
uint64_t bytesCopied = 0;
KSTATUS s = STATUS_SUCCESS;
for (uint32_t i = 0; i < rectCount; ++i) {
DISPLAY_RECT clipped;
if (!KiClipSurfaceRect(surface, &rects[i], &clipped)) {
emptyClips++;
continue;
}
SURFACE_ROW_CONTEXT ctx;
ctx.Surface = surface;
ctx.SourceX = (uint32_t)clipped.X;
ctx.SourceY = (uint32_t)clipped.Y;
s = KeDisplayBlitRows(clipped.X, clipped.Y, clipped.W, clipped.H,
KiSurfaceCopyRow, &ctx, surface->RowScratch);
if (!KSUCCESS(s))
break;
clippedRects++;
rowsCopied += (uint64_t)(uint32_t)clipped.H;
bytesCopied += (uint64_t)(uint32_t)clipped.W * (uint64_t)(uint32_t)clipped.H * sizeof(uint32_t);
}
if (KSUCCESS(s))
KiSurfaceStatsRecordPresent(surface, inputRects, clippedRects, emptyClips, rowsCopied, bytesCopied);
ObDereferenceObject(&surface->Header);
return s;
}
// SYS_CLEAR_SCREEN (67) a0=color.
static uint64_t SyscallClearScreen(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
return KeDisplayClear((uint32_t)a0);
}
// SYS_GET_SCREEN_SIZE (68) a0=*widthOut a1=*heightOut.
static uint64_t SyscallGetScreenSize(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
int w = 0, h = 0;
KSTATUS gs = KeDisplayGetSize(&w, &h);
if (!KSUCCESS(gs)) return gs;
if (a0) {
KSTATUS s = KiCopyIntToUser(a0, w);
if (!KSUCCESS(s)) return s;
}
if (a1) {
KSTATUS s = KiCopyIntToUser(a1, h);
if (!KSUCCESS(s)) return s;
}
return STATUS_SUCCESS;
}
// SYS_PUT_SCREEN_CHAR (65) a0=c a1=x a2=y a3=fg a4=bg.
static uint64_t SyscallPutScreenChar(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
return KeDisplayPutChar((char)a0, (int)a1, (int)a2,
(uint32_t)a3, (uint32_t)a4);
}
// SYS_GET_CONSOLE_INFO (61) a0=*SYS_CONSOLE_INFO.
static uint64_t SyscallGetConsoleInfo(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
KE_CONSOLE_INFO ki;
KSTATUS s = KeDisplayGetConsoleInfo(&ki);
if (!KSUCCESS(s)) return s;
SYS_CONSOLE_INFO out;
memset(&out, 0, sizeof(out));
out.Columns = ki.Columns;
out.Rows = ki.Rows;
out.CursorX = ki.CursorX;
out.CursorY = ki.CursorY;
out.Attribute = ki.Attribute;
return CopyToUser((void*)(uintptr_t)a0, &out, sizeof(out));
}
// SYS_SET_CONSOLE_CURSOR (62) a0=column a1=row.
static uint64_t SyscallSetConsoleCursor(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
int col = (int)(int32_t)a0;
int row = (int)(int32_t)a1;
if (col < 0 || row < 0) return STATUS_INVALID_PARAMETER;
return KeDisplaySetConsoleCursor(col, row);
}
// SYS_FILL_CONSOLE_CHARACTER (63) a0=char a1=column a2=row a3=count a4=*writtenOut.
static uint64_t SyscallFillConsoleCharacter(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
int col = (int)(int32_t)a1;
int row = (int)(int32_t)a2;
if (col < 0 || row < 0) return STATUS_INVALID_PARAMETER;
size_t written = 0;
KSTATUS s = KeDisplayFillConsoleChars((char)a0, col, row, (size_t)a3, &written);
if (a4) {
KSTATUS cs = KiCopySizeToUser(a4, written);
if (KSUCCESS(s) && !KSUCCESS(cs)) s = cs;
}
return s;
}
// SYS_FILL_CONSOLE_ATTRIBUTE (64) a0=attribute a1=column a2=row a3=count a4=*writtenOut.
static uint64_t SyscallFillConsoleAttribute(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
int col = (int)(int32_t)a1;
int row = (int)(int32_t)a2;
if (col < 0 || row < 0) return STATUS_INVALID_PARAMETER;
size_t written = 0;
KSTATUS s = KeDisplayFillConsoleAttrs((uint16_t)a0, col, row, (size_t)a3, &written);
if (a4) {
KSTATUS cs = KiCopySizeToUser(a4, written);
if (KSUCCESS(s) && !KSUCCESS(cs)) s = cs;
}
return s;
}
// Validates a user input queue handle. Handle zero means the default queue.
static KSTATUS KiValidateInputQueueHandle(uint32_t handle) {
if (handle == 0) return STATUS_SUCCESS;
OBJECT* obj = KiHandleToObject(handle, ObjectTypeInputQueue, HANDLE_RIGHT_READ);
if (!obj) return STATUS_INVALID_HANDLE;
ObDereferenceObject(obj);
return STATUS_SUCCESS;
}
// Copies one keyboard input record to user mode.
static KSTATUS KiCopyInputRecordToUser(INPUT_KEY_RECORD* userRecord, const INPUT_KEY_RECORD* record) {
if (!userRecord || !record) return STATUS_INVALID_PARAMETER;
return CopyToUser(userRecord, record, sizeof(*record));
}
// Imports and validates a keyboard input event request.
static KSTATUS KiCopyInputEventRequest(uint64_t arg, SYS_INPUT_EVENT_REQUEST* req) {
KSTATUS s = KiCopyUserRequest(arg, req, sizeof(*req));
if (!KSUCCESS(s)) return s;
if (req->Size != sizeof(*req)) return STATUS_INVALID_PARAMETER;
if (req->Flags != SYS_INPUT_EVENT_FLAG_NONE) return STATUS_INVALID_PARAMETER;
if (!req->RecordOut) return STATUS_INVALID_PARAMETER;
return KiValidateInputQueueHandle(req->QueueHandle);
}
// SYS_POLL_KEYBOARD a0=*INPUT_KEY -> KSTATUS.
static uint64_t SyscallKbPoll(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
INPUT_KEY key = KeInputKbPoll();
KSTATUS s = CopyToUser((void*)(uintptr_t)a0, &key, sizeof(key));
if (!KSUCCESS(s)) return s;
return (key == INPUT_KEY_NONE) ? STATUS_WOULD_BLOCK : STATUS_SUCCESS;
}
// SYS_IS_KEY_DOWN a0=INPUT_KEY a1=*bool -> KSTATUS.
static uint64_t SyscallKbIsKeyDown(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
bool down = KeInputIsKeyDown((INPUT_KEY)a0);
return CopyToUser((void*)(uintptr_t)a1, &down, sizeof(down));
}
// SYS_READ_KEYBOARD_CHAR a0=*char -> KSTATUS.
static uint64_t SyscallKbReadChar(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
for (;;) {
char ch = KeInputReadChar();
if (ch)
return CopyToUser((void*)(uintptr_t)a0, &ch, sizeof(ch));
KSTATUS s = KeInputWaitKey(0, SYS_WAIT_TIMEOUT_INFINITE);
if (!KSUCCESS(s))
return s;
}
}
// SYS_READ_KEY a0=*INPUT_KEY -> KSTATUS.
static uint64_t SyscallKbReadKey(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
for (;;) {
INPUT_KEY key = KeInputReadKey();
if (key != INPUT_KEY_NONE)
return CopyToUser((void*)(uintptr_t)a0, &key, sizeof(key));
KSTATUS s = KeInputWaitKey(0, SYS_WAIT_TIMEOUT_INFINITE);
if (!KSUCCESS(s))
return s;
}
}
// SYS_GET_KEY_MODIFIERS -> uint32_t INPUT_MOD_* flags.
static uint64_t SyscallKbGetModifiers(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
return (uint64_t)KeInputGetModifiers();
}
// SYS_READ_INPUT_EVENT a0=*SYS_INPUT_EVENT_REQUEST -> KSTATUS.
static uint64_t SyscallKbReadInputEvent(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_INPUT_EVENT_REQUEST req;
KSTATUS s = KiCopyInputEventRequest(a0, &req);
if (!KSUCCESS(s)) return s;
INPUT_KEY_RECORD record;
for (;;) {
if (KeInputReadEvent(&record))
return KiCopyInputRecordToUser(req.RecordOut, &record);
if (req.TimeoutMilliseconds == 0)
return STATUS_WOULD_BLOCK;
s = KeInputWaitKey(0, req.TimeoutMilliseconds);
if (!KSUCCESS(s))
return s;
}
}
// SYS_PEEK_INPUT_EVENT a0=*SYS_INPUT_EVENT_REQUEST -> KSTATUS.
static uint64_t SyscallKbPeekInputEvent(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_INPUT_EVENT_REQUEST req;
KSTATUS s = KiCopyInputEventRequest(a0, &req);
if (!KSUCCESS(s)) return s;
INPUT_KEY_RECORD record;
for (;;) {
if (KeInputPeekEvent(&record))
return KiCopyInputRecordToUser(req.RecordOut, &record);
if (req.TimeoutMilliseconds == 0)
return STATUS_WOULD_BLOCK;
s = KeInputWaitKey(0, req.TimeoutMilliseconds);
if (!KSUCCESS(s))
return s;
}
}
// SYS_PEEK_MOUSE a0=*INPUT_MOUSE_EVENT -> KSTATUS.
static uint64_t SyscallMousePeek(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
INPUT_MOUSE_EVENT event;
if (!KeInputMousePeek(&event)) return STATUS_WOULD_BLOCK;
return CopyToUser((void*)(uintptr_t)a0, &event, sizeof(event));
}
// SYS_READ_MOUSE a0=*INPUT_MOUSE_EVENT -> KSTATUS.
static uint64_t SyscallMouseRead(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
INPUT_MOUSE_EVENT event;
if (!KeInputMouseRead(&event)) return STATUS_WOULD_BLOCK;
return CopyToUser((void*)(uintptr_t)a0, &event, sizeof(event));
}
// SYS_GET_MOUSE_POS a0=*x a1=*y -> KSTATUS.
static uint64_t SyscallMouseGetPos(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a0 || !a1) return STATUS_INVALID_PARAMETER;
int x = 0, y = 0;
KeInputMouseGetPosition(&x, &y);
KSTATUS s = KiCopyIntToUser(a0, x);
if (!KSUCCESS(s)) return s;
return KiCopyIntToUser(a1, y);
}
// SYS_GET_TICK (75) returns monotonic ticks since boot.
static uint64_t SyscallGetTick(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
return gHal ? gHal->Timer.QueryTicks() : 0;
}
// SYS_GET_TICK_FREQUENCY (110) copies SysGetTick ticks per second.
static uint64_t SyscallGetTickFrequency(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!gHal || !gHal->Timer.QueryTickFrequency) return STATUS_DEVICE_ERROR;
uint64_t frequency = gHal->Timer.QueryTickFrequency();
if (!frequency) return STATUS_DEVICE_ERROR;
return KiCopyU64ToUser(a0, frequency);
}
// SYS_GET_SYSTEM_TIME a0=*SYS_SYSTEM_TIME.
static uint64_t SyscallGetSystemTime(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
SYS_SYSTEM_TIME t;
KSTATUS s = KeQuerySystemTime(&t);
if (!KSUCCESS(s)) return s;
return CopyToUser((void*)(uintptr_t)a0, &t, sizeof(t));
}
// SYS_PRINT_DEBUG (79) a0=*str.
static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_PRINT_DEBUG_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Text.Length == 0) return STATUS_SUCCESS;
if (!req.Text.Data) return STATUS_INVALID_PARAMETER;
if (req.Text.Length > SYSCALL_TEXT_MAX_BYTES) return STATUS_BUFFER_TOO_SMALL;
char chunk[512];
size_t offset = 0;
while (offset < req.Text.Length) {
size_t todo = req.Text.Length - offset;
if (todo > sizeof(chunk)) todo = sizeof(chunk);
s = CopyFromUser(chunk, req.Text.Data + offset, todo);
if (!KSUCCESS(s)) return s;
DbgPrint("%v", SysSvFromParts(chunk, todo));
offset += todo;
}
return STATUS_SUCCESS;
}
// SYS_QUERY_TRAP_FRAME a0=*SYS_TRAP_FRAME_INFO.
static uint64_t SyscallQueryTrapFrame(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
uint32_t userSize = 0;
KSTATUS s = CopyFromUser(&userSize, (const void*)(uintptr_t)a0, sizeof(userSize));
if (!KSUCCESS(s)) return s;
SYS_TRAP_FRAME_INFO_K info;
memset(&info, 0, sizeof(info));
info.Size = userSize;
s = KeQueryCurrentTrapFrame(&info);
size_t copySize = (s == STATUS_BUFFER_TOO_SMALL) ? sizeof(info.Size) : sizeof(info);
KSTATUS cs = CopyToUser((void*)(uintptr_t)a0, &info, copySize);
if (!KSUCCESS(cs)) return cs;
return s;
}
// SYS_CREATE_PROCESS (50) a0=*path a1=*handleOut a2=*cmdline (optional).
static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_CREATE_PROCESS_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.HandleOut) return STATUS_INVALID_PARAMETER;
char pathBuf[SYSCALL_STRING_MAX];
char cmdlineBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
SYS_STRING_VIEW cmdlineView;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
s = KiImportUserTextView(req.CommandLine, cmdlineBuf, sizeof(cmdlineBuf), &cmdlineView, true);
if (!KSUCCESS(s)) return s;
s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t));
if (!KSUCCESS(s)) return s;
// Read the executable from disk.
FILE_OBJECT* f;
ProcDbg("CreateProcess: path='%v' cmd='%v' callerPid=%u\n", path, cmdlineView, KeGetCurrentProcess() ? KeGetCurrentProcess()->ProcessId : 0);
s = IoOpenFile(path, &f);
if (!KSUCCESS(s)) { ProcDbg("CreateProcess: IoOpenFile failed st=%d\n", s); return s; }
uint64_t size = IoGetFileSize(f);
ProcDbg("CreateProcess: fileSize=%llu\n", size);
// Create the process object.
PROCESS* proc;
s = KeCreateProcess(pathBuf, &proc);
if (!KSUCCESS(s)) { ProcDbg("CreateProcess: KeCreateProcess failed st=%d\n", s); IoCloseFile(f); return s; }
ProcDbg("CreateProcess: created pid=%u name=%s\n", proc->ProcessId, proc->Name);
// Inherit CWD from parent.
PROCESS* parent = KeGetCurrentProcess();
if (parent) {
uintptr_t flags = KeAcquireSpinlock(&parent->CwdLock);
size_t cwdLen = parent->CurrentDirectoryLength;
if (cwdLen >= sizeof(proc->CurrentDirectory)) cwdLen = sizeof(proc->CurrentDirectory) - 1;
memcpy(proc->CurrentDirectory, parent->CurrentDirectory, cwdLen);
proc->CurrentDirectory[cwdLen] = '\0';
proc->CurrentDirectoryLength = cwdLen;
proc->CurrentDrive = parent->CurrentDrive;
KeReleaseSpinlock(&parent->CwdLock, flags);
}
s = KeSetupUserProcessEnvironment(proc,
path,
cmdlineView,
SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength));
if (!KSUCCESS(s)) { ProcDbg("CreateProcess: env setup failed st=%d\n", s); IoCloseFile(f); ObDereferenceObject(&proc->Header); return s; }
// Map the PE image into memory.
uintptr_t imageBase = 0;
s = LdrLoadImageFileForProcess(proc, f, &imageBase);
IoCloseFile(f);
if (!KSUCCESS(s)) { ProcDbg("CreateProcess: LdrLoadImageFileForProcess failed st=%d\n", s); ObDereferenceObject(&proc->Header); return s; }
ProcDbg("CreateProcess: image mapped pid=%u base=%016llx size=%llx\n", proc->ProcessId, (unsigned long long)imageBase, (unsigned long long)LdrGetMappedImageSize(proc, imageBase));
s = LdrResolveImportsForProcess(proc, imageBase);
if (!KSUCCESS(s)) {
ProcDbg("CreateProcess: resolve imports failed st=%d pid=%u image=%016llx\n", s, proc->ProcessId, (unsigned long long)imageBase);
MmVadDebugDumpProcess(proc);
ObDereferenceObject(&proc->Header);
return s;
}
ProcDbg("CreateProcess: imports resolved pid=%u modules=%u\n", proc->ProcessId, proc->ModuleCount);
s = LdrProtectImageForProcess(proc, imageBase, true);
if (!KSUCCESS(s)) {
ProcDbg("CreateProcess: protect image failed st=%d pid=%u\n", s, proc->ProcessId);
MmVadDebugDumpProcess(proc);
ObDereferenceObject(&proc->Header);
return s;
}
proc->ImageBase = imageBase;
proc->ImageSize = LdrGetMappedImageSize(proc, imageBase);
PROCESS* caller = KeGetCurrentProcess();
if (!caller) {
ObDereferenceObject(&proc->Header);
return STATUS_FAILURE;
}
uint32_t handle = 0;
s = KeAllocateHandleWithRights(caller, &proc->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE, &handle);
if (!KSUCCESS(s)) {
proc->ImageBase = 0;
proc->ImageSize = 0;
ObDereferenceObject(&proc->Header);
return s;
}
THREAD* thread;
uintptr_t entryPoint = LdrGetEntryPoint(imageBase);
s = KeCreateThread(proc, entryPoint, 0, PriorityNormal, &thread);
if (!KSUCCESS(s)) {
ProcDbg("CreateProcess: KeCreateThread failed st=%d pid=%u entry=%016llx\n", s, proc->ProcessId, (unsigned long long)entryPoint);
MmVadDebugDumpProcess(proc);
KeCloseHandle(caller, handle);
ObDereferenceObject(&proc->Header);
return s;
}
uint32_t tid = thread->ThreadId;
ProcDbg("CreateProcess: thread created pid=%u tid=%llu entry=%016llx handle=%u\n", proc->ProcessId, (unsigned long long)tid, (unsigned long long)entryPoint, handle);
ObDereferenceObject(&thread->Header);
s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle);
if (!KSUCCESS(s)) {
KeTerminateProcess(proc, (int)STATUS_PROCESS_PAGE_FAULT);
KeCloseHandle(caller, handle);
ObDereferenceObject(&proc->Header);
return s;
}
// Drop the creator's local reference. The returned handle and live thread
// references now own the process lifetime.
ObDereferenceObject(&proc->Header);
return STATUS_SUCCESS;
}
// SYS_OPEN_PROCESS a0=pid a1=*handleOut.
static uint64_t SyscallOpenProcess(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(uint32_t));
if (!KSUCCESS(s)) return s;
PROCESS* proc = KeGetProcessById((uint32_t)a0);
if (!proc) return STATUS_NOT_FOUND;
PROCESS* caller = KeGetCurrentProcess();
if (!caller) {
ObDereferenceObject(&proc->Header);
return STATUS_FAILURE;
}
uint32_t handle = 0;
s = KeAllocateHandleWithRights(caller, &proc->Header,
HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE | HANDLE_RIGHT_DUPLICATE,
&handle);
ObDereferenceObject(&proc->Header);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(a1, handle);
if (!KSUCCESS(s)) {
KeCloseHandle(caller, handle);
return s;
}
return STATUS_SUCCESS;
}
// SYS_GET_PROCESS_EXIT_CODE (4) copies the process exit code.
static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_QUERY);
if (!proc) return STATUS_INVALID_HANDLE;
int32_t exitCode = 0;
KSTATUS s = KeGetProcessExitCode(proc, &exitCode);
ObDereferenceObject(&proc->Header);
if (!KSUCCESS(s)) return s;
return KiCopyInt32ToUser(a1, exitCode);
}
// SYS_WRITE_CONSOLE (60) a0=*buf a1=len
// Writes user console text in white; distinct from green SYS_PRINT_DEBUG.
static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_WRITE_CONSOLE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Text.Length == 0) return STATUS_SUCCESS;
if (!req.Text.Data) return STATUS_INVALID_PARAMETER;
if (req.Text.Length > SYSCALL_TEXT_MAX_BYTES) return STATUS_BUFFER_TOO_SMALL;
char chunk[512];
size_t offset = 0;
while (offset < req.Text.Length) {
size_t todo = req.Text.Length - offset;
if (todo > sizeof(chunk)) todo = sizeof(chunk);
s = CopyFromUser(chunk, req.Text.Data + offset, todo);
if (!KSUCCESS(s)) return s;
KePrintConsole(chunk, todo);
offset += todo;
}
return STATUS_SUCCESS;
}
// SYS_OPEN_DIR (54) a0=*dirPath a1=*handleOut.
static uint64_t SyscallOpenDir(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_PATH_HANDLE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.HandleOut) return STATUS_INVALID_PARAMETER;
char dirPathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW dirPath;
s = KiImportUserTextView(req.Path, dirPathBuf, sizeof(dirPathBuf), &dirPath, true);
if (!KSUCCESS(s)) return s;
s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t));
if (!KSUCCESS(s)) return s;
DIR_OBJECT* dir = NULL;
s = IoOpenDir(dirPath, &dir);
if (!KSUCCESS(s)) return s;
uint32_t handle = 0;
s = KiAllocHandleForCurrentWithRights(&dir->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_QUERY, &handle);
if (!KSUCCESS(s)) return s;
// KiAllocHandleForCurrentWithRights consumed IoOpenDir's creator reference;
// the returned process handle now owns the directory object.
s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
return s;
}
return STATUS_SUCCESS;
}
// SYS_READ_DIR (55) a0=handle a1=*KDIR_ENTRY
// Returns STATUS_NO_MORE_FILES when the directory is exhausted.
static uint64_t SyscallReadDir(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
DIR_OBJECT* dir = (DIR_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeDir, HANDLE_RIGHT_READ);
if (!dir) return STATUS_INVALID_PARAMETER;
KDIR_ENTRY entry;
KSTATUS s = IoReadDir(dir, &entry);
ObDereferenceObject(&dir->Header);
if (!KSUCCESS(s)) return s;
return CopyToUser((void*)(uintptr_t)a1, &entry, sizeof(entry));
}
// SYS_SET_CWD (56) a0=*path.
static uint64_t SyscallSetCwd(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_PATH_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
return IoSetCwd(path);
}
// SYS_REAL_PATH a0=*SYS_REAL_PATH_REQUEST.
static uint64_t SyscallRealPath(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_REAL_PATH_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req) || req.Flags != SYS_REAL_PATH_FLAG_NONE)
return STATUS_INVALID_PARAMETER;
if (!req.Buffer && req.BufferSize != 0) return STATUS_INVALID_PARAMETER;
if (!req.Buffer && !req.RequiredSizeOut) return STATUS_INVALID_PARAMETER;
if (req.RequiredSizeOut) {
s = MmProbeUserWrite(req.RequiredSizeOut, sizeof(size_t));
if (!KSUCCESS(s)) return s;
}
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
char resolved[SYS_CWD_MAX];
size_t required = 0;
s = IoRealPath(path, resolved, sizeof(resolved), &required);
if (!KSUCCESS(s)) return s;
if (req.RequiredSizeOut) {
KSTATUS cs = KiCopySizeToUser((uint64_t)(uintptr_t)req.RequiredSizeOut, required);
if (!KSUCCESS(cs)) return cs;
}
if (!req.Buffer)
return STATUS_SUCCESS;
if (req.BufferSize < required)
return STATUS_BUFFER_TOO_SMALL;
return CopyToUser(req.Buffer, resolved, required);
}
// SYS_GET_PROCESS_PID (59) a0=procHandle a1=*pidOut
// Returns the numeric PID of a process identified by handle.
// Used by posix_spawn to map the kernel handle back to a pid_t for waitpid.
static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_QUERY);
if (!proc) return STATUS_INVALID_HANDLE;
uint32_t pid = proc->ProcessId;
ObDereferenceObject(&proc->Header);
return KiCopyU32ToUser(a1, pid);
}
// Named Queue Registry
// A flat, fixed-size table of { name -> QUEUE* } pairs protected by a
// spinlock. The registry holds one reference to each published queue for
// the lifetime of the entry, so the queue object cannot be destroyed while
// it is registered even if every process closes its own handle to it.
// Limits: NAMED_QUEUE_MAX names, each up to NAMED_QUEUE_NAME_MAX bytes.
// These are generous for the intended usage (a handful of system servers).
#define NAMED_QUEUE_MAX 16
#define NAMED_QUEUE_NAME_MAX 64
typedef struct {
char Name[NAMED_QUEUE_NAME_MAX];
size_t NameLength;
QUEUE* Queue; // NULL = slot free; non-NULL = occupied
uint32_t PublisherPid; // ProcessId of whoever called SysPublishQueue
} NAMED_QUEUE_ENTRY;
static NAMED_QUEUE_ENTRY gNamedQueues[NAMED_QUEUE_MAX];
static SPINLOCK gNamedQueueLock;
static int KiNamedQueueNameEquals(const NAMED_QUEUE_ENTRY* entry,
SYS_STRING_VIEW name) {
if (!entry || !entry->Queue) return 0;
return SysSvEqual(SysSvFromParts(entry->Name, entry->NameLength), name);
}
// SYS_PUBLISH_QUEUE (60) a0=*name a1=queueHandle
// Registers the queue identified by 'queueHandle' under 'name'.
// Fails with STATUS_ALREADY_EXISTS if the name is already taken.
// The registry bumps the queue's refcount so it outlives the handle.
static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_PUBLISH_QUEUE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
char name[NAMED_QUEUE_NAME_MAX];
SYS_STRING_VIEW nameView;
s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, false);
if (!KSUCCESS(s)) return s;
if (nameView.Length == 0) return STATUS_INVALID_PARAMETER;
QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_DUPLICATE);
if (!q) return STATUS_INVALID_HANDLE;
uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock);
for (int i = 0; i < NAMED_QUEUE_MAX; i++) {
if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) {
KeReleaseSpinlock(&gNamedQueueLock, flags);
ObDereferenceObject(&q->Header);
return STATUS_ALREADY_EXISTS;
}
}
for (int i = 0; i < NAMED_QUEUE_MAX; i++) {
if (!gNamedQueues[i].Queue) {
memcpy(gNamedQueues[i].Name, name, nameView.Length);
gNamedQueues[i].NameLength = nameView.Length;
gNamedQueues[i].Queue = q;
gNamedQueues[i].PublisherPid = KeGetCurrentProcess()->ProcessId;
KeReleaseSpinlock(&gNamedQueueLock, flags);
return STATUS_SUCCESS;
}
}
KeReleaseSpinlock(&gNamedQueueLock, flags);
ObDereferenceObject(&q->Header);
return STATUS_NO_MEMORY;
}
// SYS_OPEN_NAMED_QUEUE (61) a0=*name a1=*handleOut
// Looks up 'name' in the registry and allocates a handle to the queue in the
// calling process's handle table. Returns STATUS_NOT_FOUND if no match.
// Callers that race against the publisher should retry on NOT_FOUND.
static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_OPEN_NAMED_QUEUE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.HandleOut) return STATUS_INVALID_PARAMETER;
char name[NAMED_QUEUE_NAME_MAX];
SYS_STRING_VIEW nameView;
s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, false);
if (!KSUCCESS(s)) return s;
if (nameView.Length == 0) return STATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t));
if (!KSUCCESS(s)) return s;
uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock);
QUEUE* q = NULL;
for (int i = 0; i < NAMED_QUEUE_MAX; i++) {
if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) {
q = gNamedQueues[i].Queue;
ObReferenceObject(&q->Header);
break;
}
}
KeReleaseSpinlock(&gNamedQueueLock, flags);
if (!q) return STATUS_NOT_FOUND;
uint32_t handle = 0;
s = KiAllocHandleForCurrentWithRights(&q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &handle);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
return s;
}
return STATUS_SUCCESS;
}
// SYS_DUPLICATE_HANDLE a0=*request.
// Duplicates a handle from the caller into the target process table.
static uint64_t SyscallDuplicateHandle(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_DUPLICATE_HANDLE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.SourceHandle || !req.TargetProcessHandle || !req.TargetHandleOut)
return STATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.TargetHandleOut, sizeof(uint32_t));
if (!KSUCCESS(s)) return s;
PROCESS* caller = KeGetCurrentProcess();
if (!caller) return STATUS_FAILURE;
PROCESS* target = (PROCESS*)KiHandleToObject(req.TargetProcessHandle, ObjectTypeProcess, HANDLE_RIGHT_DUPLICATE);
if (!target) return STATUS_INVALID_HANDLE;
uint32_t targetHandle = 0;
s = KeDuplicateHandleToProcess(caller, req.SourceHandle, target, req.DesiredRights, req.Flags, &targetHandle);
if (!KSUCCESS(s)) {
ObDereferenceObject(&target->Header);
return s;
}
s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.TargetHandleOut, targetHandle);
if (!KSUCCESS(s)) {
KeCloseHandle(target, targetHandle);
ObDereferenceObject(&target->Header);
return s;
}
ObDereferenceObject(&target->Header);
return STATUS_SUCCESS;
}
// SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS a0=*request.
// Opens a process handle for the process that published a named queue.
static uint64_t SyscallOpenNamedQueueOwnerProcess(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_NAMED_QUEUE_OWNER_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.ProcessHandleOut) return STATUS_INVALID_PARAMETER;
char name[NAMED_QUEUE_NAME_MAX];
SYS_STRING_VIEW nameView;
s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, false);
if (!KSUCCESS(s)) return s;
if (nameView.Length == 0) return STATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.ProcessHandleOut, sizeof(uint32_t));
if (!KSUCCESS(s)) return s;
uint32_t pid = 0;
uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock);
for (int i = 0; i < NAMED_QUEUE_MAX; i++) {
if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) {
pid = gNamedQueues[i].PublisherPid;
break;
}
}
KeReleaseSpinlock(&gNamedQueueLock, flags);
if (!pid) return STATUS_NOT_FOUND;
PROCESS* owner = KeGetProcessById(pid);
if (!owner) return STATUS_NOT_FOUND;
PROCESS* caller = KeGetCurrentProcess();
if (!caller) {
ObDereferenceObject(&owner->Header);
return STATUS_FAILURE;
}
uint32_t handle = 0;
s = KeAllocateHandleWithRights(caller, &owner->Header,
HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_DUPLICATE,
&handle);
ObDereferenceObject(&owner->Header);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.ProcessHandleOut, handle);
if (!KSUCCESS(s)) {
KeCloseHandle(caller, handle);
return s;
}
return STATUS_SUCCESS;
}
static uint64_t SyscallKillProcess(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_TERMINATE);
if (!proc) return STATUS_INVALID_HANDLE;
THREAD* self = KeGetCurrentThread();
int32_t exitCode = (int32_t)a1;
if (self && self->Owner == proc) {
KSTATUS s = KeExitProcess(proc, exitCode);
ObDereferenceObject(&proc->Header);
if (!KSUCCESS(s)) return s;
KeExitThread(self, exitCode);
while (true) ;
}
KSTATUS s = KeTerminateProcess(proc, exitCode);
ObDereferenceObject(&proc->Header);
return s;
}
// SYS_GET_PROCESS_LIST (6) a0=*SYS_PROCESS_INFO[] a1=maxCount a2=*countOut.
static uint64_t SyscallGetProcessList(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
size_t maxCount = (size_t)a1;
if (maxCount && !a0) return STATUS_INVALID_PARAMETER;
if (a2) {
KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a2, sizeof(size_t));
if (!KSUCCESS(s)) return s;
}
size_t total = KeCopyProcessInfo(NULL, 0);
size_t toCopy = total < maxCount ? total : maxCount;
if (toCopy) {
size_t bytes = 0;
if (!KiMulSize(toCopy, sizeof(SYS_PROCESS_INFO), &bytes)) return STATUS_INVALID_PARAMETER;
SYS_PROCESS_INFO* tmp = (SYS_PROCESS_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 'y', 's', ' '));
if (!tmp) return STATUS_NO_MEMORY;
size_t copied = KeCopyProcessInfo(tmp, toCopy);
if (copied < toCopy) toCopy = copied;
KSTATUS s = CopyToUser((void*)(uintptr_t)a0, tmp, toCopy * sizeof(SYS_PROCESS_INFO));
MmFreePool(tmp);
if (!KSUCCESS(s)) return s;
}
if (a2) return KiCopySizeToUser(a2, total);
return STATUS_SUCCESS;
}
// SYS_GET_THREAD_LIST (103) a0=pid a1=*SYS_THREAD_INFO[] a2=maxCount a3=*countOut.
static uint64_t SyscallGetThreadList(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a4; (void)a5;
uint32_t pid = (uint32_t)a0;
size_t maxCount = (size_t)a2;
if (maxCount && !a1) return STATUS_INVALID_PARAMETER;
if (a3) {
KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a3, sizeof(size_t));
if (!KSUCCESS(s)) return s;
}
size_t total = 0;
KSTATUS s = KeCopyProcessThreadInfo(pid, NULL, 0, &total);
if (!KSUCCESS(s)) return s;
size_t toCopy = total < maxCount ? total : maxCount;
if (toCopy) {
size_t bytes = 0;
if (!KiMulSize(toCopy, sizeof(SYS_THREAD_INFO), &bytes)) return STATUS_INVALID_PARAMETER;
SYS_THREAD_INFO* tmp = (SYS_THREAD_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 't', 'h', 'r'));
if (!tmp) return STATUS_NO_MEMORY;
size_t copied = 0;
s = KeCopyProcessThreadInfo(pid, tmp, toCopy, &copied);
if (KSUCCESS(s)) {
if (copied < toCopy) toCopy = copied;
s = CopyToUser((void*)(uintptr_t)a1, tmp, toCopy * sizeof(SYS_THREAD_INFO));
}
MmFreePool(tmp);
if (!KSUCCESS(s)) return s;
}
if (a3) return KiCopySizeToUser(a3, total);
return STATUS_SUCCESS;
}
// SYS_LOAD_MODULE a0=*SYS_LOAD_MODULE_REQUEST.
static uint64_t SyscallLoadModule(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_LOAD_MODULE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER;
if (req.Flags != SYS_LOAD_MODULE_FLAG_NONE) return STATUS_INVALID_PARAMETER;
if (!req.ModuleHandleOut) return STATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.ModuleHandleOut, sizeof(*req.ModuleHandleOut));
if (!KSUCCESS(s)) return s;
if (req.InfoOut) {
s = MmProbeUserWrite(req.InfoOut, sizeof(*req.InfoOut));
if (!KSUCCESS(s)) return s;
}
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
uintptr_t base = 0;
s = LdrLoadModuleForProcess(proc, pathBuf, LDR_MODULE_LOAD_PUBLIC, &base);
if (!KSUCCESS(s)) return s;
uint32_t handle = 0;
s = KiCreateModuleHandle(proc, base, &handle);
if (!KSUCCESS(s)) {
(void)LdrUnloadModuleForProcess(proc, base);
return s;
}
SYS_MODULE_INFO info;
if (req.InfoOut) {
s = LdrQueryModuleForProcess(proc, base, &info);
if (!KSUCCESS(s)) {
KeCloseHandle(proc, handle);
return s;
}
}
s = KiCopyU32ToUser((uint64_t)(uintptr_t)req.ModuleHandleOut, handle);
if (!KSUCCESS(s)) {
KeCloseHandle(proc, handle);
return s;
}
if (req.InfoOut) {
s = CopyToUser(req.InfoOut, &info, sizeof(info));
if (!KSUCCESS(s)) {
KeCloseHandle(proc, handle);
return s;
}
}
return STATUS_SUCCESS;
}
// SYS_UNLOAD_MODULE a0=moduleHandle.
static uint64_t SyscallUnloadModule(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
uint32_t handle = (uint32_t)a0;
MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject(handle, ObjectTypeModule, HANDLE_RIGHT_TERMINATE);
if (!module) return STATUS_INVALID_HANDLE;
KeCloseHandle(proc, handle);
ObDereferenceObject(&module->Header);
return STATUS_SUCCESS;
}
// SYS_GET_MODULE_INFO a0=moduleHandle a1=*SYS_MODULE_INFO.
static uint64_t SyscallGetModuleInfo(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(SYS_MODULE_INFO));
if (!KSUCCESS(s)) return s;
MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeModule, HANDLE_RIGHT_QUERY);
if (!module) return STATUS_INVALID_HANDLE;
SYS_MODULE_INFO info;
s = LdrQueryModuleForProcess(module->Owner, module->Base, &info);
ObDereferenceObject(&module->Header);
if (!KSUCCESS(s)) return s;
return CopyToUser((void*)(uintptr_t)a1, &info, sizeof(info));
}
// SYS_GET_MODULE_EXPORT a0=*SYS_GET_MODULE_EXPORT_REQUEST.
static uint64_t SyscallGetModuleExport(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_GET_MODULE_EXPORT_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER;
if (!req.AddressOut) return STATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.AddressOut, sizeof(*req.AddressOut));
if (!KSUCCESS(s)) return s;
char nameBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW name;
s = KiImportUserTextView(req.Name, nameBuf, sizeof(nameBuf), &name, true);
if (!KSUCCESS(s)) return s;
if (name.Length == 0) return STATUS_INVALID_PARAMETER;
MODULE_HANDLE_OBJECT* module = (MODULE_HANDLE_OBJECT*)KiHandleToObject(req.ModuleHandle, ObjectTypeModule, HANDLE_RIGHT_QUERY);
if (!module) return STATUS_INVALID_HANDLE;
void* address = LdrGetModuleExportForProcess(module->Owner, module->Base, nameBuf);
ObDereferenceObject(&module->Header);
if (!address) return STATUS_NOT_FOUND;
uintptr_t userAddress = (uintptr_t)address;
return CopyToUser(req.AddressOut, &userAddress, sizeof(userAddress));
}
// SYS_GET_MODULE_LIST a0=*SYS_MODULE_INFO[] a1=maxCount a2=*countOut.
static uint64_t SyscallGetModuleList(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
size_t maxCount = (size_t)a1;
if (maxCount && !a0) return STATUS_INVALID_PARAMETER;
if (a2) {
KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a2, sizeof(size_t));
if (!KSUCCESS(s)) return s;
}
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
size_t total = 0;
KSTATUS s = LdrCopyProcessModuleInfo(proc, NULL, 0, &total);
if (s != STATUS_BUFFER_TOO_SMALL && !KSUCCESS(s)) return s;
size_t toCopy = total < maxCount ? total : maxCount;
if (toCopy) {
size_t bytes = 0;
if (!KiMulSize(toCopy, sizeof(SYS_MODULE_INFO), &bytes)) return STATUS_INVALID_PARAMETER;
SYS_MODULE_INFO* tmp = (SYS_MODULE_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('m', 'o', 'd', 'l'));
if (!tmp) return STATUS_NO_MEMORY;
size_t copiedTotal = 0;
s = LdrCopyProcessModuleInfo(proc, tmp, toCopy, &copiedTotal);
if (s == STATUS_BUFFER_TOO_SMALL)
s = STATUS_SUCCESS;
if (KSUCCESS(s))
s = CopyToUser((void*)(uintptr_t)a0, tmp, toCopy * sizeof(SYS_MODULE_INFO));
MmFreePool(tmp);
if (!KSUCCESS(s)) return s;
}
if (a2) return KiCopySizeToUser(a2, total);
return STATUS_SUCCESS;
}
// SYS_RENAME_FILE (66) a0=*oldPath a1=*newPath.
static uint64_t SyscallRenameFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_RENAME_FILE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
char oldPathBuf[SYSCALL_STRING_MAX];
char newPathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW oldPath;
SYS_STRING_VIEW newPath;
s = KiImportUserTextView(req.OldPath, oldPathBuf, sizeof(oldPathBuf), &oldPath, true);
if (!KSUCCESS(s)) return s;
s = KiImportUserTextView(req.NewPath, newPathBuf, sizeof(newPathBuf), &newPath, true);
if (!KSUCCESS(s)) return s;
if (oldPath.Length == 0 || newPath.Length == 0) return STATUS_INVALID_PARAMETER;
return IoRenameFile(oldPath, newPath);
}
// SYS_LOCK_FILE a0=*SYS_LOCK_FILE_REQUEST.
static uint64_t SyscallLockFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_LOCK_FILE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER;
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return STATUS_INVALID_PARAMETER;
if (req.Mode != SYS_FILE_LOCK_SHARED && req.Mode != SYS_FILE_LOCK_EXCLUSIVE) return STATUS_INVALID_PARAMETER;
if (req.Length == 0) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_LOCK);
if (!file) return STATUS_INVALID_HANDLE;
s = IoLockFile(file, proc, req.Mode, req.Offset, req.Length, req.TimeoutMilliseconds);
ObDereferenceObject(&file->Header);
return s;
}
// SYS_UNLOCK_FILE a0=*SYS_UNLOCK_FILE_REQUEST.
static uint64_t SyscallUnlockFile(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_UNLOCK_FILE_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Size != sizeof(req)) return STATUS_INVALID_PARAMETER;
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return STATUS_INVALID_PARAMETER;
if (req.Length == 0) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_FAILURE;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, HANDLE_RIGHT_LOCK);
if (!file) return STATUS_INVALID_HANDLE;
s = IoUnlockFile(file, proc, req.Offset, req.Length);
ObDereferenceObject(&file->Header);
return s;
}
// SYS_CREATE_DIR a0=*path.
static uint64_t SyscallCreateDir(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_PATH_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
return IoCreateDir(path);
}
// SYS_REMOVE_DIR (68) a0=*path.
static uint64_t SyscallRemoveDir(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
SYS_PATH_REQUEST req;
KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
char pathBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true);
if (!KSUCCESS(s)) return s;
if (path.Length == 0) return STATUS_INVALID_PARAMETER;
return IoRemoveDir(path);
}
typedef struct SYS_SOCKET_SEND_TO_REQUEST_K {
const void* Buffer;
size_t Length;
uint32_t Flags;
const SYS_SOCKET_ADDRESS_K* RemoteAddress;
size_t RemoteAddressSize;
size_t* SentOut;
} SYS_SOCKET_SEND_TO_REQUEST_K;
typedef struct SYS_SOCKET_RECV_FROM_REQUEST_K {
void* Buffer;
size_t Length;
uint32_t Flags;
SYS_SOCKET_ADDRESS_K* RemoteAddressOut;
size_t* RemoteAddressSizeInOut;
size_t* ReceivedOut;
} SYS_SOCKET_RECV_FROM_REQUEST_K;
typedef struct SYS_RESOLVE_HOST_REQUEST_K {
SYS_STRING_VIEW Name;
uint32_t Family;
uint32_t Flags;
SYS_SOCKET_ADDRESS_K* AddressOut;
size_t* AddressSizeInOut;
} SYS_RESOLVE_HOST_REQUEST_K;
typedef struct SYS_SOCKET_OPTION_REQUEST_K {
uint32_t Option;
void* Value;
size_t ValueSize;
size_t* ActualSizeOut;
} SYS_SOCKET_OPTION_REQUEST_K;
typedef struct SYS_ASYNC_SOCKET_REQUEST_K {
uint32_t Size;
uint32_t SocketHandle;
uint32_t EventHandle;
uint32_t Operation;
uint32_t Flags;
uint32_t SocketFlags;
uint32_t Backlog;
uint32_t ShutdownHow;
uint32_t Option;
uint32_t PollEvents;
void* Buffer;
size_t Length;
const SYS_SOCKET_ADDRESS_K* Address;
size_t AddressSize;
SYS_SOCKET_ADDRESS_K* AddressOut;
size_t* AddressSizeInOut;
void* OptionValue;
size_t OptionValueSize;
size_t* OptionActualSizeOut;
uint32_t* HandleOut;
uint32_t* PollReturnedEventsOut;
SYS_IO_STATUS_BLOCK_K* Iosb;
SYS_USER_APC_ROUTINE_K ApcRoutine;
void* ApcContext;
uint32_t* RequestHandleOut;
} SYS_ASYNC_SOCKET_REQUEST_K;
typedef struct SYS_MEMORY_STATUS_K {
uint64_t FreePhysicalBytes;
uint64_t KernelPoolBytes;
uint64_t UserHeapBytes;
uint64_t ReclaimablePhysicalBytes;
uint64_t AvailablePhysicalBytes;
uint64_t PinnedPhysicalBytes;
uint64_t NonEvictablePhysicalBytes;
uint64_t UserPhysicalBytes;
uint64_t PageTablePhysicalBytes;
} SYS_MEMORY_STATUS_K;
#define SYS_POLL_MAX_FDS 64u
// SYS_CREATE_SOCKET a0=domain a1=type a2=protocol a3=*handleOut.
static uint64_t SyscallSocket(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a4; (void)a5;
if (!a3) return STATUS_INVALID_PARAMETER;
SOCKET_OBJECT* socket = NULL;
KSTATUS s = NetSocketCreate((uint32_t)a0, (uint32_t)a1, (uint32_t)a2, &socket);
if (!KSUCCESS(s)) return s;
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&socket->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h);
if (!KSUCCESS(s)) return s;
// KiAllocHandleForCurrentWithRights consumed NetSocketCreate's creator
// reference; the returned process handle now owns the socket object.
s = KiCopyHandleToUser(a3, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// SYS_BIND_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS a2=addressSize.
static uint64_t SyscallBind(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
if ((size_t)a2 < sizeof(SYS_SOCKET_ADDRESS_K)) return STATUS_BUFFER_TOO_SMALL;
SYS_SOCKET_ADDRESS_K address;
KSTATUS s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address));
if (!KSUCCESS(s)) return s;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) return STATUS_NOT_FOUND;
s = NetSocketBind(socket, &address, (size_t)a2);
ObDereferenceObject(&socket->Header);
return s;
}
// SYS_LISTEN_SOCKET a0=socketHandle a1=backlog.
static uint64_t SyscallListen(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) return STATUS_NOT_FOUND;
KSTATUS s = NetSocketListen(socket, (uint32_t)a1);
ObDereferenceObject(&socket->Header);
return s;
}
// SYS_ACCEPT_SOCKET a0=listenSocket a1=*remoteAddressOut(optional)
// a2=*remoteAddressSizeInOut(optional) a3=*acceptedHandleOut.
static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a4; (void)a5;
if (!a3) return STATUS_INVALID_PARAMETER;
bool wantRemote = (a1 != 0);
size_t remoteCapacity = 0;
if (wantRemote) {
if (!a2) return STATUS_INVALID_PARAMETER;
KSTATUS cs = CopyFromUser(&remoteCapacity, (const void*)(uintptr_t)a2, sizeof(remoteCapacity));
if (!KSUCCESS(cs)) return cs;
if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
(void)CopyToUser((void*)(uintptr_t)a2, &required, sizeof(required));
return STATUS_BUFFER_TOO_SMALL;
}
cs = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(SYS_SOCKET_ADDRESS_K));
if (!KSUCCESS(cs)) return cs;
}
SOCKET_OBJECT* listener = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!listener) return STATUS_NOT_FOUND;
SOCKET_OBJECT* accepted = NULL;
SYS_SOCKET_ADDRESS_K remote;
KSTATUS s = NetSocketAccept(listener, &accepted, wantRemote ? &remote : NULL, (IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&listener->Header);
if (!KSUCCESS(s)) return s;
uint32_t h = 0;
s = KiAllocHandleForCurrentWithRights(&accepted->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h);
if (!KSUCCESS(s)) return s;
// KiAllocHandleForCurrentWithRights consumed the accept creator reference;
// the returned process handle owns the accepted socket from this point.
if (wantRemote) {
size_t actual = sizeof(SYS_SOCKET_ADDRESS_K);
s = CopyToUser((void*)(uintptr_t)a1, &remote, sizeof(remote));
if (KSUCCESS(s))
s = CopyToUser((void*)(uintptr_t)a2, &actual, sizeof(actual));
}
if (KSUCCESS(s))
s = KiCopyHandleToUser(a3, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// SYS_CONNECT_SOCKET a0=socketHandle a1=*SYS_SOCKET_ADDRESS a2=addressSize.
static uint64_t SyscallConnect(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
if ((size_t)a2 < sizeof(SYS_SOCKET_ADDRESS_K)) return STATUS_BUFFER_TOO_SMALL;
SYS_SOCKET_ADDRESS_K address;
KSTATUS s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address));
if (!KSUCCESS(s)) return s;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) return STATUS_NOT_FOUND;
s = NetSocketConnect(socket, &address, (size_t)a2, (IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&socket->Header);
return s;
}
// SYS_SEND_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*sentOut(optional).
static uint64_t SyscallSend(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
size_t length = (size_t)a2;
if (length && !a1) return STATUS_INVALID_PARAMETER;
if (length > SYSCALL_IO_MAX_BYTES) return STATUS_INVALID_PARAMETER;
if (length == 0) {
if (a4) return KiCopySizeToUser(a4, 0);
return STATUS_SUCCESS;
}
void* kbuf = MmAllocatePoolTag(length, MM_POOL_TAG('s', 'y', 's', ' '));
if (!kbuf) return STATUS_NO_MEMORY;
KSTATUS s = CopyFromUser(kbuf, (const void*)(uintptr_t)a1, length);
if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; }
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) { MmFreePool(kbuf); return STATUS_NOT_FOUND; }
size_t sent = 0;
s = NetSocketSend(socket, kbuf, length, (uint32_t)a3, &sent);
ObDereferenceObject(&socket->Header);
MmFreePool(kbuf);
if (a4) {
KSTATUS cs = KiCopySizeToUser(a4, sent);
if (KSUCCESS(s)) s = cs;
}
return s;
}
// SYS_RECV_SOCKET a0=socketHandle a1=*buffer a2=length a3=flags a4=*receivedOut(optional).
static uint64_t SyscallRecv(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a5;
size_t length = (size_t)a2;
if (length && !a1) return STATUS_INVALID_PARAMETER;
if (length > SYSCALL_IO_MAX_BYTES) return STATUS_INVALID_PARAMETER;
if (length == 0) {
if (a4) return KiCopySizeToUser(a4, 0);
return STATUS_SUCCESS;
}
KSTATUS s = MmProbeUserWrite((void*)(uintptr_t)a1, length);
if (!KSUCCESS(s)) return s;
void* kbuf = MmAllocatePoolTag(length, MM_POOL_TAG('s', 'y', 's', ' '));
if (!kbuf) return STATUS_NO_MEMORY;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) { MmFreePool(kbuf); return STATUS_NOT_FOUND; }
size_t received = 0;
s = NetSocketReceive(socket, kbuf, length, (uint32_t)a3, &received, (IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&socket->Header);
if (KSUCCESS(s) && received)
s = CopyToUser((void*)(uintptr_t)a1, kbuf, received);
MmFreePool(kbuf);
if (KSUCCESS(s) && a4)
s = KiCopySizeToUser(a4, received);
return s;
}
// SYS_SHUTDOWN_SOCKET a0=socketHandle a1=SYS_SHUTDOWN_SOCKET_*.
static uint64_t SyscallShutdown(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) return STATUS_NOT_FOUND;
KSTATUS s = NetSocketShutdown(socket, (uint32_t)a1);
ObDereferenceObject(&socket->Header);
return s;
}
// SYS_SEND_TO_SOCKET a0=socketHandle a1=*SYS_SOCKET_SEND_TO_REQUEST.
static uint64_t SyscallSendTo(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
SYS_SOCKET_SEND_TO_REQUEST_K req;
KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Length && !req.Buffer) return STATUS_INVALID_PARAMETER;
if (!req.RemoteAddress) return STATUS_INVALID_PARAMETER;
if (req.RemoteAddressSize < sizeof(SYS_SOCKET_ADDRESS_K)) return STATUS_BUFFER_TOO_SMALL;
if (req.Length > SYSCALL_IO_MAX_BYTES) return STATUS_INVALID_PARAMETER;
SYS_SOCKET_ADDRESS_K remote;
s = CopyFromUser(&remote, req.RemoteAddress, sizeof(remote));
if (!KSUCCESS(s)) return s;
void* kbuf = NULL;
if (req.Length) {
kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
if (!kbuf) return STATUS_NO_MEMORY;
s = CopyFromUser(kbuf, req.Buffer, req.Length);
if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; }
}
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) { if (kbuf) MmFreePool(kbuf); return STATUS_NOT_FOUND; }
size_t sent = 0;
s = NetSocketSendTo(socket, kbuf, req.Length, req.Flags, &remote, req.RemoteAddressSize, &sent);
ObDereferenceObject(&socket->Header);
if (kbuf) MmFreePool(kbuf);
if (req.SentOut) {
KSTATUS cs = CopyToUser(req.SentOut, &sent, sizeof(sent));
if (KSUCCESS(s)) s = cs;
}
return s;
}
// SYS_RECV_FROM_SOCKET a0=socketHandle a1=*SYS_SOCKET_RECV_FROM_REQUEST.
static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
SYS_SOCKET_RECV_FROM_REQUEST_K req;
KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Length && !req.Buffer) return STATUS_INVALID_PARAMETER;
if (req.Length > SYSCALL_IO_MAX_BYTES) return STATUS_INVALID_PARAMETER;
bool wantRemote = (req.RemoteAddressOut != NULL);
size_t remoteCapacity = 0;
if (wantRemote) {
if (!req.RemoteAddressSizeInOut) return STATUS_INVALID_PARAMETER;
s = CopyFromUser(&remoteCapacity, req.RemoteAddressSizeInOut, sizeof(remoteCapacity));
if (!KSUCCESS(s)) return s;
if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
(void)CopyToUser(req.RemoteAddressSizeInOut, &required, sizeof(required));
return STATUS_BUFFER_TOO_SMALL;
}
s = MmProbeUserWrite(req.RemoteAddressOut, sizeof(SYS_SOCKET_ADDRESS_K));
if (!KSUCCESS(s)) return s;
}
if (req.Length)
{
s = MmProbeUserWrite(req.Buffer, req.Length);
if (!KSUCCESS(s)) return s;
}
void* kbuf = NULL;
if (req.Length) {
kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
if (!kbuf) return STATUS_NO_MEMORY;
}
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) { if (kbuf) MmFreePool(kbuf); return STATUS_NOT_FOUND; }
SYS_SOCKET_ADDRESS_K remote;
memset(&remote, 0, sizeof(remote));
size_t received = 0;
s = NetSocketReceiveFrom(socket, kbuf, req.Length, req.Flags, wantRemote ? &remote : NULL, &received, (IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&socket->Header);
if (KSUCCESS(s) && received)
s = CopyToUser(req.Buffer, kbuf, received);
if (kbuf) MmFreePool(kbuf);
if (KSUCCESS(s) && wantRemote) {
size_t actual = sizeof(SYS_SOCKET_ADDRESS_K);
s = CopyToUser(req.RemoteAddressOut, &remote, sizeof(remote));
if (KSUCCESS(s))
s = CopyToUser(req.RemoteAddressSizeInOut, &actual, sizeof(actual));
}
if (KSUCCESS(s) && req.ReceivedOut)
s = CopyToUser(req.ReceivedOut, &received, sizeof(received));
return s;
}
// SYS_RESOLVE_HOST a0=*SYS_RESOLVE_HOST_REQUEST.
static uint64_t SyscallResolveHost(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
SYS_RESOLVE_HOST_REQUEST_K req;
KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.Name.Data || req.Name.Length == 0 || req.Name.Length > SYS_RESOLVE_HOST_NAME_MAX)
return STATUS_INVALID_PARAMETER;
if (req.Family != SYS_AF_INET && req.Family != SYS_AF_INET6)
return STATUS_NOT_SUPPORTED;
if (req.Flags != 0)
return STATUS_NOT_SUPPORTED;
if (!req.AddressOut || !req.AddressSizeInOut)
return STATUS_INVALID_PARAMETER;
size_t capacity = 0;
s = CopyFromUser(&capacity, req.AddressSizeInOut, sizeof(capacity));
if (!KSUCCESS(s)) return s;
if (capacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
(void)CopyToUser(req.AddressSizeInOut, &required, sizeof(required));
return STATUS_BUFFER_TOO_SMALL;
}
char name[SYS_RESOLVE_HOST_NAME_MAX + 1];
SYS_STRING_VIEW nameView;
s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, true);
if (!KSUCCESS(s)) return s;
SYS_SOCKET_ADDRESS_K address;
s = NetResolveHost(name, req.Family, &address);
if (!KSUCCESS(s)) return s;
size_t actual = sizeof(SYS_SOCKET_ADDRESS_K);
s = CopyToUser(req.AddressOut, &address, sizeof(address));
if (!KSUCCESS(s)) return s;
return CopyToUser(req.AddressSizeInOut, &actual, sizeof(actual));
}
// SYS_SET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST.
static uint64_t SyscallSetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
SYS_SOCKET_OPTION_REQUEST_K req;
KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.Value) return STATUS_INVALID_PARAMETER;
uint8_t value[sizeof(uint64_t)];
if (req.ValueSize > sizeof(value)) return STATUS_BUFFER_TOO_SMALL;
s = CopyFromUser(value, req.Value, req.ValueSize);
if (!KSUCCESS(s)) return s;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) return STATUS_NOT_FOUND;
s = NetSocketSetOption(socket, req.Option, value, req.ValueSize);
ObDereferenceObject(&socket->Header);
return s;
}
// SYS_GET_SOCKET_OPTION a0=socketHandle a1=*SYS_SOCKET_OPTION_REQUEST.
static uint64_t SyscallGetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a2; (void)a3; (void)a4; (void)a5;
if (!a1) return STATUS_INVALID_PARAMETER;
SYS_SOCKET_OPTION_REQUEST_K req;
KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a1, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.Value) return STATUS_INVALID_PARAMETER;
uint8_t value[sizeof(uint64_t)];
if (req.ValueSize > sizeof(value)) return STATUS_BUFFER_TOO_SMALL;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) return STATUS_NOT_FOUND;
size_t actual = 0;
s = NetSocketGetOption(socket, req.Option, value, req.ValueSize, &actual);
ObDereferenceObject(&socket->Header);
if (!KSUCCESS(s)) {
if (s == STATUS_BUFFER_TOO_SMALL && req.ActualSizeOut)
(void)CopyToUser(req.ActualSizeOut, &actual, sizeof(actual));
return s;
}
s = CopyToUser(req.Value, value, actual);
if (KSUCCESS(s) && req.ActualSizeOut)
s = CopyToUser(req.ActualSizeOut, &actual, sizeof(actual));
return s;
}
// Polls deadline Expired.
static int KiPollDeadlineExpired(uint64_t start, uint32_t timeoutMs) {
if (timeoutMs == 0)
return 1;
if (timeoutMs == SYS_POLL_TIMEOUT_INFINITE)
return 0;
if (!gHal)
return 1;
return (gHal->Timer.QueryTicks() - start) >= ((uint64_t)timeoutMs * 1000u);
}
// SYS_POLL a0=*SYS_POLLFD array a1=count a2=timeoutMs a3=*readyOut(optional).
static uint64_t SyscallPoll(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
size_t count = (size_t)a1;
if (count == 0 || count > SYS_POLL_MAX_FDS)
return STATUS_INVALID_PARAMETER;
size_t bytes = count * sizeof(SYS_POLLFD_K);
SYS_POLLFD_K* fds = (SYS_POLLFD_K*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 'y', 's', ' '));
if (!fds) return STATUS_NO_MEMORY;
KSTATUS s = CopyFromUser(fds, (const void*)(uintptr_t)a0, bytes);
if (!KSUCCESS(s)) { MmFreePool(fds); return s; }
uint32_t timeoutMs = (uint32_t)a2;
uint64_t start = gHal ? gHal->Timer.QueryTicks() : 0;
size_t ready = 0;
for (;;) {
ready = 0;
for (size_t i = 0; i < count; i++) {
fds[i].ReturnedEvents = 0;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(fds[i].Handle, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket) {
fds[i].ReturnedEvents = SYS_POLL_INVALID;
ready++;
continue;
}
uint32_t revents = 0;
s = NetSocketPoll(socket, fds[i].Events, &revents);
ObDereferenceObject(&socket->Header);
if (!KSUCCESS(s)) {
fds[i].ReturnedEvents = SYS_POLL_ERROR;
ready++;
continue;
}
fds[i].ReturnedEvents = revents;
if (revents != 0)
ready++;
}
if (ready != 0 || KiPollDeadlineExpired(start, timeoutMs))
break;
KeYield();
}
s = CopyToUser((void*)(uintptr_t)a0, fds, bytes);
if (KSUCCESS(s) && a3)
s = KiCopySizeToUser(a3, ready);
MmFreePool(fds);
return s;
}
// Releases resources prepared for an async socket request before queue ownership starts.
static void SyscallAsyncSocketReleaseSubmitResources(NET_ASYNC_SOCKET_SUBMIT* submit) {
if (!submit)
return;
if (submit->KernelBuffer) {
MmFreePool(submit->KernelBuffer);
submit->KernelBuffer = (void*)0;
}
if (submit->OptionValue) {
MmFreePool(submit->OptionValue);
submit->OptionValue = (void*)0;
}
if (submit->LockedBuffer) {
MmUnlockUserBuffer(submit->LockedBuffer);
submit->LockedBuffer = (MM_LOCKED_USER_BUFFER*)0;
}
if (submit->LockedIosb) {
MmUnlockUserBuffer(submit->LockedIosb);
submit->LockedIosb = (MM_LOCKED_USER_BUFFER*)0;
}
if (submit->LockedAddressOut) {
MmUnlockUserBuffer(submit->LockedAddressOut);
submit->LockedAddressOut = (MM_LOCKED_USER_BUFFER*)0;
}
if (submit->LockedAddressSize) {
MmUnlockUserBuffer(submit->LockedAddressSize);
submit->LockedAddressSize = (MM_LOCKED_USER_BUFFER*)0;
}
if (submit->LockedOptionValue) {
MmUnlockUserBuffer(submit->LockedOptionValue);
submit->LockedOptionValue = (MM_LOCKED_USER_BUFFER*)0;
}
if (submit->LockedOptionActualSize) {
MmUnlockUserBuffer(submit->LockedOptionActualSize);
submit->LockedOptionActualSize = (MM_LOCKED_USER_BUFFER*)0;
}
if (submit->LockedHandleOut) {
MmUnlockUserBuffer(submit->LockedHandleOut);
submit->LockedHandleOut = (MM_LOCKED_USER_BUFFER*)0;
}
if (submit->LockedPollReturnedEvents) {
MmUnlockUserBuffer(submit->LockedPollReturnedEvents);
submit->LockedPollReturnedEvents = (MM_LOCKED_USER_BUFFER*)0;
}
}
// Locks an optional async socket output pointer for worker-thread completion.
static KSTATUS SyscallAsyncSocketLockOptional(PROCESS* proc,
void* ptr,
size_t size,
MM_LOCKED_USER_BUFFER** out) {
if (out)
*out = (MM_LOCKED_USER_BUFFER*)0;
if (!ptr || size == 0)
return STATUS_SUCCESS;
return MmLockUserBuffer(proc, ptr, size, 1, out);
}
// Copies an input address into a kernel-owned async socket request.
static KSTATUS SyscallAsyncSocketCopyInputAddress(const SYS_ASYNC_SOCKET_REQUEST_K* req,
NET_ASYNC_SOCKET_SUBMIT* submit) {
if (!req || !submit || !req->Address)
return STATUS_INVALID_PARAMETER;
if (req->AddressSize < sizeof(SYS_SOCKET_ADDRESS_K))
return STATUS_BUFFER_TOO_SMALL;
KSTATUS status = CopyFromUser(&submit->Address, req->Address, sizeof(submit->Address));
if (!KSUCCESS(status))
return status;
submit->AddressSize = req->AddressSize;
return STATUS_SUCCESS;
}
// Validates and locks optional remote-address outputs for async accept/recvfrom.
static KSTATUS SyscallAsyncSocketPrepareAddressOutput(PROCESS* proc,
const SYS_ASYNC_SOCKET_REQUEST_K* req,
NET_ASYNC_SOCKET_SUBMIT* submit) {
if (!req || !submit)
return STATUS_INVALID_PARAMETER;
if (!req->AddressOut)
return STATUS_SUCCESS;
if (!req->AddressSizeInOut)
return STATUS_INVALID_PARAMETER;
size_t capacity = 0;
KSTATUS status = CopyFromUser(&capacity, req->AddressSizeInOut, sizeof(capacity));
if (!KSUCCESS(status))
return status;
if (capacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
(void)CopyToUser(req->AddressSizeInOut, &required, sizeof(required));
return STATUS_BUFFER_TOO_SMALL;
}
status = MmLockUserBuffer(proc, req->AddressOut, sizeof(SYS_SOCKET_ADDRESS_K), 1,
&submit->LockedAddressOut);
if (!KSUCCESS(status))
return status;
status = MmLockUserBuffer(proc, req->AddressSizeInOut, sizeof(*req->AddressSizeInOut), 1,
&submit->LockedAddressSize);
if (!KSUCCESS(status))
return status;
submit->AddressOut = req->AddressOut;
submit->AddressSizeInOut = req->AddressSizeInOut;
return STATUS_SUCCESS;
}
// Prepares kernel-owned buffers and locked outputs for one async socket operation.
static KSTATUS SyscallAsyncSocketPrepareOperation(PROCESS* proc,
const SYS_ASYNC_SOCKET_REQUEST_K* req,
NET_ASYNC_SOCKET_SUBMIT* submit) {
if (!proc || !req || !submit)
return STATUS_INVALID_PARAMETER;
submit->Operation = req->Operation;
submit->SocketFlags = req->SocketFlags;
submit->Backlog = req->Backlog;
submit->ShutdownHow = req->ShutdownHow;
submit->Option = req->Option;
submit->PollEvents = req->PollEvents;
submit->Length = req->Length;
switch (req->Operation) {
case SYS_ASYNC_SOCKET_OP_BIND:
case SYS_ASYNC_SOCKET_OP_CONNECT:
return SyscallAsyncSocketCopyInputAddress(req, submit);
case SYS_ASYNC_SOCKET_OP_LISTEN:
case SYS_ASYNC_SOCKET_OP_SHUTDOWN:
return STATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_ACCEPT: {
if (!req->HandleOut)
return STATUS_INVALID_PARAMETER;
uint32_t zero = 0;
KSTATUS status = MmLockUserBuffer(proc, req->HandleOut, sizeof(*req->HandleOut), 1,
&submit->LockedHandleOut);
if (!KSUCCESS(status))
return status;
*req->HandleOut = zero;
submit->HandleOut = req->HandleOut;
return SyscallAsyncSocketPrepareAddressOutput(proc, req, submit);
}
case SYS_ASYNC_SOCKET_OP_SEND:
if (req->Length && !req->Buffer)
return STATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return STATUS_INVALID_PARAMETER;
if (req->Length) {
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 's', 'n'));
if (!submit->KernelBuffer)
return STATUS_NO_MEMORY;
return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length);
}
return STATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_RECV:
if (req->Length && !req->Buffer)
return STATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return STATUS_INVALID_PARAMETER;
if (req->Length) {
KSTATUS status = MmLockUserBuffer(proc, req->Buffer, req->Length, 1, &submit->LockedBuffer);
if (!KSUCCESS(status))
return status;
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 'r', 'n'));
if (!submit->KernelBuffer)
return STATUS_NO_MEMORY;
}
submit->UserBuffer = req->Buffer;
return STATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_SEND_TO: {
if (req->Length && !req->Buffer)
return STATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return STATUS_INVALID_PARAMETER;
KSTATUS status = SyscallAsyncSocketCopyInputAddress(req, submit);
if (!KSUCCESS(status))
return status;
if (req->Length) {
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 's', 'd'));
if (!submit->KernelBuffer)
return STATUS_NO_MEMORY;
return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length);
}
return STATUS_SUCCESS;
}
case SYS_ASYNC_SOCKET_OP_RECV_FROM: {
if (req->Length && !req->Buffer)
return STATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return STATUS_INVALID_PARAMETER;
KSTATUS status = SyscallAsyncSocketPrepareAddressOutput(proc, req, submit);
if (!KSUCCESS(status))
return status;
if (req->Length) {
status = MmLockUserBuffer(proc, req->Buffer, req->Length, 1, &submit->LockedBuffer);
if (!KSUCCESS(status))
return status;
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 'r', 'd'));
if (!submit->KernelBuffer)
return STATUS_NO_MEMORY;
}
submit->UserBuffer = req->Buffer;
return STATUS_SUCCESS;
}
case SYS_ASYNC_SOCKET_OP_SET_OPT:
if (!req->OptionValue)
return STATUS_INVALID_PARAMETER;
if (req->OptionValueSize > sizeof(uint64_t))
return STATUS_BUFFER_TOO_SMALL;
submit->OptionValueSize = req->OptionValueSize;
if (req->OptionValueSize) {
submit->OptionValue = MmAllocatePoolTag(req->OptionValueSize, MM_POOL_TAG('s', 'a', 'o', 's'));
if (!submit->OptionValue)
return STATUS_NO_MEMORY;
return CopyFromUser(submit->OptionValue, req->OptionValue, req->OptionValueSize);
}
return STATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_GET_OPT: {
if (!req->OptionValue)
return STATUS_INVALID_PARAMETER;
if (req->OptionValueSize > sizeof(uint64_t))
return STATUS_BUFFER_TOO_SMALL;
submit->OptionValueSize = req->OptionValueSize;
submit->UserOptionValue = req->OptionValue;
KSTATUS status = MmLockUserBuffer(proc, req->OptionValue, req->OptionValueSize, 1,
&submit->LockedOptionValue);
if (!KSUCCESS(status))
return status;
if (req->OptionActualSizeOut) {
status = MmLockUserBuffer(proc, req->OptionActualSizeOut, sizeof(*req->OptionActualSizeOut), 1,
&submit->LockedOptionActualSize);
if (!KSUCCESS(status))
return status;
submit->OptionActualSizeOut = req->OptionActualSizeOut;
}
if (req->OptionValueSize) {
submit->OptionValue = MmAllocatePoolTag(req->OptionValueSize, MM_POOL_TAG('s', 'a', 'o', 'g'));
if (!submit->OptionValue)
return STATUS_NO_MEMORY;
}
return STATUS_SUCCESS;
}
case SYS_ASYNC_SOCKET_OP_POLL: {
if (req->PollReturnedEventsOut) {
KSTATUS status = MmLockUserBuffer(proc, req->PollReturnedEventsOut,
sizeof(*req->PollReturnedEventsOut), 1,
&submit->LockedPollReturnedEvents);
if (!KSUCCESS(status))
return status;
*req->PollReturnedEventsOut = 0;
submit->PollReturnedEventsOut = req->PollReturnedEventsOut;
}
return STATUS_SUCCESS;
}
default:
return STATUS_INVALID_PARAMETER;
}
}
// SYS_SOCKET_IO_ASYNC a0=*SYS_ASYNC_SOCKET_REQUEST.
static uint64_t SyscallSocketIoAsync(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0)
return STATUS_INVALID_PARAMETER;
SYS_ASYNC_SOCKET_REQUEST_K req;
KSTATUS status = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req));
if (!KSUCCESS(status))
return status;
if (req.Size != sizeof(req) || req.Flags != SYS_ASYNC_SOCKET_FLAG_NONE || !req.Iosb)
return STATUS_INVALID_PARAMETER;
if (req.RequestHandleOut) {
status = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut));
if (!KSUCCESS(status))
return status;
}
PROCESS* proc = KeGetCurrentProcess();
if (!proc)
return STATUS_FAILURE;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.SocketHandle,
ObjectTypeSocket,
HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE);
if (!socket)
return STATUS_NOT_FOUND;
EVENT* event = (EVENT*)0;
if (req.EventHandle) {
event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL);
if (!event) {
ObDereferenceObject(&socket->Header);
return STATUS_NOT_FOUND;
}
}
KAPC* completionApc = (KAPC*)0;
SYS_USER_ASYNC_APC_CONTEXT* apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)0;
if (req.ApcRoutine) {
THREAD* thread = KeGetCurrentThread();
if (!thread || !thread->UserApcDispatcher) {
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&socket->Header);
return STATUS_INVALID_PARAMETER;
}
apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)MmAllocatePoolTag(sizeof(SYS_USER_ASYNC_APC_CONTEXT), MM_POOL_TAG('u', 'a', 'p', 'c'));
if (!apcCtx) {
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&socket->Header);
return STATUS_NO_MEMORY;
}
memset(apcCtx, 0, sizeof(*apcCtx));
ObReferenceObject(&thread->Header);
apcCtx->TargetThread = thread;
apcCtx->UserRoutine = req.ApcRoutine;
apcCtx->UserContext = req.ApcContext;
KeInitializeApc(&apcCtx->KernelApc,
thread,
SyscallUserAsyncKernelRoutine,
SyscallUserAsyncNormalRoutine,
apcCtx);
KeSetApcRundownRoutine(&apcCtx->KernelApc, SyscallUserAsyncApcRundown);
completionApc = &apcCtx->KernelApc;
}
NET_ASYNC_SOCKET_SUBMIT submit;
memset(&submit, 0, sizeof(submit));
submit.OwnerProcess = proc;
submit.Socket = socket;
submit.CompletionEvent = event;
submit.CompletionApc = completionApc;
submit.Iosb = (IO_STATUS_BLOCK*)req.Iosb;
status = MmLockUserBuffer(proc, req.Iosb, sizeof(*req.Iosb), 1, &submit.LockedIosb);
if (!KSUCCESS(status))
goto fail;
status = SyscallAsyncSocketPrepareOperation(proc, &req, &submit);
if (!KSUCCESS(status))
goto fail;
NET_ASYNC_SOCKET_REQUEST_OBJECT* asyncReq = (NET_ASYNC_SOCKET_REQUEST_OBJECT*)0;
status = NetSocketSubmitAsyncKernel(&submit, &asyncReq);
if (status == STATUS_PENDING) {
memset(&submit, 0, sizeof(submit));
}
if (!KSUCCESS(status) && status != STATUS_PENDING)
goto fail;
if (req.RequestHandleOut && asyncReq) {
uint32_t requestHandle = 0;
KSTATUS hs = KeAllocateHandleWithRights(proc,
NetAsyncSocketRequestObject(asyncReq),
HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE,
&requestHandle);
if (!KSUCCESS(hs)) {
(void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq);
ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq));
status = hs;
goto fail_after_submit;
}
KSTATUS cs = CopyToUser(req.RequestHandleOut, &requestHandle, sizeof(requestHandle));
if (!KSUCCESS(cs)) {
(void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq);
KeCloseHandle(proc, requestHandle);
ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq));
status = cs;
goto fail_after_submit;
}
} else if (req.RequestHandleOut) {
uint32_t zeroHandle = 0;
KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle));
if (!KSUCCESS(cs)) {
status = cs;
goto fail_after_submit;
}
}
if (asyncReq)
ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq));
if (event)
ObDereferenceObject(&event->Header);
ObDereferenceObject(&socket->Header);
return status;
fail:
SyscallAsyncSocketReleaseSubmitResources(&submit);
if (apcCtx)
SyscallUserAsyncApcContextFree(apcCtx);
fail_after_submit:
if (event)
ObDereferenceObject(&event->Header);
ObDereferenceObject(&socket->Header);
return status;
}
// SYS_GET_MEMORY_STATUS a0=*SYS_MEMORY_STATUS.
static uint64_t SyscallGetMemoryStatus(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
MM_PFN_STATS pfn;
MmPfnQueryStats(&pfn);
SYS_MEMORY_STATUS_K status;
memset(&status, 0, sizeof(status));
status.FreePhysicalBytes = pfn.FreeFrames * PAGE_SIZE;
status.KernelPoolBytes = MmGetKernelPoolUsage();
status.UserHeapBytes = MmGetUserHeapUsage();
status.ReclaimablePhysicalBytes = pfn.ReclaimableFrames * PAGE_SIZE;
status.AvailablePhysicalBytes = pfn.AvailableFrames * PAGE_SIZE;
status.PinnedPhysicalBytes = pfn.PinnedFrames * PAGE_SIZE;
status.NonEvictablePhysicalBytes = pfn.NonEvictableFrames * PAGE_SIZE;
status.UserPhysicalBytes = pfn.UserFrames * PAGE_SIZE;
status.PageTablePhysicalBytes = pfn.PageTableFrames * PAGE_SIZE;
return CopyToUser((void*)(uintptr_t)a0, &status, sizeof(status));
}
// SYS_QUERY_KERNEL_STATS a0=classId a1=buffer a2=bufferSize a3=*requiredSizeOut.
static KSTATUS KiReturnKernelStats(uint64_t bufferArg, size_t bufferSize,
uint64_t requiredArg,
const void* src, size_t size) {
if (requiredArg) {
KSTATUS s = KiCopySizeToUser(requiredArg, size);
if (!KSUCCESS(s)) return s;
}
if (!bufferArg)
return STATUS_SUCCESS;
if (bufferSize < size)
return STATUS_BUFFER_TOO_SMALL;
return CopyToUser((void*)(uintptr_t)bufferArg, src, size);
}
static uint64_t SyscallQueryDisplayInfo(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
if (!a0) return STATUS_INVALID_PARAMETER;
KE_DISPLAY_INFO ki;
KSTATUS s = KeDisplayQueryInfo(&ki);
if (!KSUCCESS(s)) return s;
SYS_DISPLAY_INFO out;
memset(&out, 0, sizeof(out));
memcpy(out.PrimaryName, ki.PrimaryName, sizeof(out.PrimaryName));
out.Mode = ki.Mode;
out.Caps = ki.Caps;
out.DeviceCount = ki.DeviceCount;
out.ManagerStats = ki.ManagerStats;
out.DeviceStats = ki.DeviceStats;
return CopyToUser((void*)(uintptr_t)a0, &out, sizeof(out));
}
static uint64_t SyscallQueryKernelStats(uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5) {
(void)a4; (void)a5;
switch ((uint32_t)a0) {
case SYS_KERNEL_STATS_SCHEDULER: {
KE_SCHED_STATS ks;
SYS_SCHEDULER_STATS us;
KeGetSchedulerStats(&ks);
memset(&us, 0, sizeof(us));
us.Size = sizeof(us);
us.Version = SYS_STATS_VERSION;
us.Ticks = ks.Ticks;
us.ContextSwitches = ks.ContextSwitches;
us.KeepCurrentDecisions = ks.KeepCurrentDecisions;
us.IdleSelections = ks.IdleSelections;
us.Demotions = ks.Demotions;
us.AgingPromotions = ks.AgingPromotions;
us.QuantumExpirations = ks.QuantumExpirations;
us.PriorityPreemptions = ks.PriorityPreemptions;
us.VoluntaryBlocks = ks.VoluntaryBlocks;
us.CpuBurstDemotions = ks.CpuBurstDemotions;
us.SmtSiblingDeferrals = ks.SmtSiblingDeferrals;
us.SmtPrimaryPokes = ks.SmtPrimaryPokes;
return KiReturnKernelStats(a1, (size_t)a2, a3, &us, sizeof(us));
}
case SYS_KERNEL_STATS_PMM: {
MM_PHYSICAL_STATS km;
SYS_PMM_STATS um;
MmQueryPhysicalStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.TotalFrames = km.TotalFrames;
um.FreeFrames = km.FreeFrames;
um.UsedFrames = km.UsedFrames;
um.LargestFreeRun = km.LargestFreeRun;
um.FreeRunCount = km.FreeRunCount;
for (size_t i = 0; i < 8; i++)
um.FreeRunHistogram[i] = km.FreeRunHistogram[i];
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_PFN: {
MM_PFN_STATS km;
SYS_PFN_STATS um;
MmPfnQueryStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.TotalFrames = km.TotalFrames;
um.FreeFrames = km.FreeFrames;
um.UsedFrames = km.UsedFrames;
um.ReservedFrames = km.ReservedFrames;
um.ZeroedFrames = km.ZeroedFrames;
um.ActiveFrames = km.ActiveFrames;
um.StandbyFrames = km.StandbyFrames;
um.ModifiedFrames = km.ModifiedFrames;
um.PinnedFrames = km.PinnedFrames;
um.EvictableFrames = km.EvictableFrames;
um.NonEvictableFrames = km.NonEvictableFrames;
um.ReclaimableFrames = km.ReclaimableFrames;
um.AvailableFrames = km.AvailableFrames;
um.MappedFrames = km.MappedFrames;
um.KernelFrames = km.KernelFrames;
um.PageTableFrames = km.PageTableFrames;
um.UserFrames = km.UserFrames;
um.CleanFileCacheFrames = km.CleanFileCacheFrames;
um.ActiveCleanFileCacheFrames = km.ActiveCleanFileCacheFrames;
um.StandbyCleanFileCacheFrames = km.StandbyCleanFileCacheFrames;
um.ImageFrames = km.ImageFrames;
um.ActiveImageFrames = km.ActiveImageFrames;
um.StandbyImageFrames = km.StandbyImageFrames;
um.MappedFileFrames = km.MappedFileFrames;
um.AnonymousFrames = km.AnonymousFrames;
um.CowFrames = km.CowFrames;
um.SurfaceFrames = km.SurfaceFrames;
um.DriverFrames = km.DriverFrames;
um.DmaFrames = km.DmaFrames;
um.BootloaderFrames = km.BootloaderFrames;
um.PfnDatabaseFrames = km.PfnDatabaseFrames;
um.MmioFrames = km.MmioFrames;
um.AllocationCount = km.AllocationCount;
um.FreeCount = km.FreeCount;
um.RetainCount = km.RetainCount;
um.ReleaseCount = km.ReleaseCount;
um.PinCount = km.PinCount;
um.UnpinCount = km.UnpinCount;
um.FailedAllocations = km.FailedAllocations;
um.FailedContiguousAllocations = km.FailedContiguousAllocations;
um.FailedInvalidRange = km.FailedInvalidRange;
um.FailedEmptyRange = km.FailedEmptyRange;
um.FailedReserveDenied = km.FailedReserveDenied;
um.FailedNoFrame = km.FailedNoFrame;
um.FailedContiguousInvalidRequest = km.FailedContiguousInvalidRequest;
um.FailedContiguousReserveDenied = km.FailedContiguousReserveDenied;
um.FailedContiguousNoRun = km.FailedContiguousNoRun;
um.ReclaimAttempts = km.ReclaimAttempts;
um.ReclaimPagesRequested = km.ReclaimPagesRequested;
um.ReclaimPagesSucceeded = km.ReclaimPagesSucceeded;
um.ReclaimPagesFailed = km.ReclaimPagesFailed;
um.ReclaimReentrantSkips = km.ReclaimReentrantSkips;
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_PTE: {
MM_PTE_STATS km;
SYS_PTE_STATS um;
MmPteQueryStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.MapCalls = km.MapCalls;
um.MapFailures = km.MapFailures;
um.UnmapCalls = km.UnmapCalls;
um.UnmapFailures = km.UnmapFailures;
um.ProtectCalls = km.ProtectCalls;
um.ProtectFailures = km.ProtectFailures;
um.QueryCalls = km.QueryCalls;
um.QueryFailures = km.QueryFailures;
for (size_t i = 0; i < SYS_PTE_QUERY_PURPOSE_COUNT && i < MmPteQueryPurposeCount; i++) {
um.QueryByPurpose[i] = km.QueryByPurpose[i];
um.QueryFailuresByPurpose[i] = km.QueryFailuresByPurpose[i];
}
um.TranslateCalls = km.TranslateCalls;
um.LocalFlushes = km.LocalFlushes;
um.GlobalFlushes = km.GlobalFlushes;
um.ShootdownRequests = km.ShootdownRequests;
um.ShootdownTargetCpus = km.ShootdownTargetCpus;
um.ShootdownSkippedNoTargets = km.ShootdownSkippedNoTargets;
um.ShootdownAcks = km.ShootdownAcks;
um.ShootdownTimeouts = km.ShootdownTimeouts;
um.NewMappingNoFlushes = km.NewMappingNoFlushes;
um.ReplacedMappingFlushes = km.ReplacedMappingFlushes;
um.PageTablePagesAllocated = km.PageTablePagesAllocated;
um.CurrentLeafMappings = km.CurrentLeafMappings;
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_VAD: {
MM_VAD_STATS km;
SYS_VAD_STATS um;
MmVadQueryStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.ResolveFaultCalls = km.ResolveFaultCalls;
um.ResolvedFaults = km.ResolvedFaults;
um.FailedFaults = km.FailedFaults;
um.ProtectionFaults = km.ProtectionFaults;
um.NoVadFaults = km.NoVadFaults;
um.UncommittedFaults = km.UncommittedFaults;
um.NoAccessFaults = km.NoAccessFaults;
um.DemandZeroPages = km.DemandZeroPages;
um.ZeroPageMaps = km.ZeroPageMaps;
um.ZeroPageCowSplits = km.ZeroPageCowSplits;
um.CowFaults = km.CowFaults;
um.CowPagesCopied = km.CowPagesCopied;
um.CowViewsCreated = km.CowViewsCreated;
um.CowPagesShared = km.CowPagesShared;
um.EnsureResidentCalls = km.EnsureResidentCalls;
um.EnsureResidentPages = km.EnsureResidentPages;
um.AlreadyResidentPages = km.AlreadyResidentPages;
um.FileBackedFaults = km.FileBackedFaults;
um.FileBackedReads = km.FileBackedReads;
um.FileBackedReadFailures = km.FileBackedReadFailures;
um.FileBackedDiscards = km.FileBackedDiscards;
um.FileBackedSharedWritableFaults = km.FileBackedSharedWritableFaults;
um.FileBackedDirtyFaults = km.FileBackedDirtyFaults;
um.FileBackedDirtySyncs = km.FileBackedDirtySyncs;
um.ImageBackedFaults = km.ImageBackedFaults;
um.ImageBackedZeroFaults = km.ImageBackedZeroFaults;
um.ImageBackedPrivateFaults = km.ImageBackedPrivateFaults;
um.ImageFixupPrivateCalls = km.ImageFixupPrivateCalls;
um.ImageFixupPrivatePages = km.ImageFixupPrivatePages;
um.SwapPageOuts = km.SwapPageOuts;
um.SwapPageIns = km.SwapPageIns;
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_FILE_CACHE: {
MM_FILE_CACHE_STATS km;
SYS_FILE_CACHE_STATS um;
MmFileCacheQueryStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.Lookups = km.Lookups;
um.Hits = km.Hits;
um.Misses = km.Misses;
um.Loads = km.Loads;
um.LoadFailures = km.LoadFailures;
um.RaceDiscards = km.RaceDiscards;
um.InflightWaits = km.InflightWaits;
um.Invalidations = km.Invalidations;
um.DirtyPages = km.DirtyPages;
um.MaxDirtyPages = km.MaxDirtyPages;
um.DirtyMarks = km.DirtyMarks;
um.WritebackRequests = km.WritebackRequests;
um.WritebackPages = km.WritebackPages;
um.WritebackBytes = km.WritebackBytes;
um.WritebackFailures = km.WritebackFailures;
um.FlushFileCalls = km.FlushFileCalls;
um.FlushFileFailures = km.FlushFileFailures;
um.MappedDirtySyncs = km.MappedDirtySyncs;
um.MappedDirtyPages = km.MappedDirtyPages;
um.Drops = km.Drops;
um.DropFailures = km.DropFailures;
um.StandbyTransitions = km.StandbyTransitions;
um.StandbyHits = km.StandbyHits;
um.ReadAheadRequests = km.ReadAheadRequests;
um.ReadAheadPagesRequested = km.ReadAheadPagesRequested;
um.ReadAheadPagesQueued = km.ReadAheadPagesQueued;
um.ReadAheadPagesLoaded = km.ReadAheadPagesLoaded;
um.ReadAheadPagesSkipped = km.ReadAheadPagesSkipped;
um.ReadAheadFailures = km.ReadAheadFailures;
um.ReadAheadPagesThrottled = km.ReadAheadPagesThrottled;
um.MaxReadAheadPagesPerRequest = km.MaxReadAheadPagesPerRequest;
um.BatchReads = km.BatchReads;
um.BatchPagesRequested = km.BatchPagesRequested;
um.BatchPagesLoaded = km.BatchPagesLoaded;
um.MaxBatchPagesRequested = km.MaxBatchPagesRequested;
um.MaxBatchPagesLoaded = km.MaxBatchPagesLoaded;
um.ResidentPages = km.ResidentPages;
um.ActivePages = km.ActivePages;
um.StandbyPages = km.StandbyPages;
um.MaxResidentPages = km.MaxResidentPages;
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_SURFACE: {
SYS_SURFACE_STATS stats;
memset(&stats, 0, sizeof(stats));
KiSurfaceStatsQuery(&stats);
return KiReturnKernelStats(a1, (size_t)a2, a3, &stats, sizeof(stats));
}
case SYS_KERNEL_STATS_SWAP: {
MM_SWAP_STATS km;
SYS_SWAP_STATS um;
MmSwapQueryStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.Enabled = km.Enabled;
um.TotalSlots = km.TotalSlots;
um.UsedSlots = km.UsedSlots;
um.FreeSlots = km.FreeSlots;
um.HighWaterSlots = km.HighWaterSlots;
um.PageOuts = km.PageOuts;
um.PageIns = km.PageIns;
um.PageOutFailures = km.PageOutFailures;
um.PageInFailures = km.PageInFailures;
um.SlotAllocations = km.SlotAllocations;
um.SlotFrees = km.SlotFrees;
um.SlotAllocationFailures = km.SlotAllocationFailures;
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_FILESYSTEM: {
IO_FILESYSTEM_STATS km;
SYS_FILESYSTEM_STATS um;
IoQueryFilesystemStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.PathLookups = km.PathLookups;
um.PathCacheHits = km.PathCacheHits;
um.PathCacheMisses = km.PathCacheMisses;
um.PathCacheStores = km.PathCacheStores;
um.PathCacheInvalidations = km.PathCacheInvalidations;
um.PathCacheGeneration = km.PathCacheGeneration;
um.DirectorySearches = km.DirectorySearches;
um.DirectoryBlocksRead = km.DirectoryBlocksRead;
um.DirectoryEntriesScanned = km.DirectoryEntriesScanned;
um.DataReadRuns = km.DataReadRuns;
um.DataReadSectors = km.DataReadSectors;
um.DataReadRunMerges = km.DataReadRunMerges;
um.MaxDataReadSpanSectors = km.MaxDataReadSpanSectors;
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_DISK: {
IO_DISK_STATS km;
SYS_DISK_STATS um;
IoQueryDiskStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.RegisteredDisks = km.RegisteredDisks;
um.ReadRequests = km.ReadRequests;
um.ReadSectors = km.ReadSectors;
um.WriteRequests = km.WriteRequests;
um.WriteSectors = km.WriteSectors;
um.MaxReadSectors = km.MaxReadSectors;
um.MaxWriteSectors = km.MaxWriteSectors;
um.ReadaheadHits = km.ReadaheadHits;
um.ReadaheadMisses = km.ReadaheadMisses;
um.ReadaheadStores = km.ReadaheadStores;
um.ReadaheadInvalidations = km.ReadaheadInvalidations;
um.ReadaheadBytesServed = km.ReadaheadBytesServed;
um.ReadaheadSectorsLoaded = km.ReadaheadSectorsLoaded;
um.ReadaheadSequentialDetections = km.ReadaheadSequentialDetections;
um.ReadaheadSuppressed = km.ReadaheadSuppressed;
um.ReadaheadAllocFailures = km.ReadaheadAllocFailures;
um.ReadaheadReadFailures = km.ReadaheadReadFailures;
um.MaxReadaheadSectors = km.MaxReadaheadSectors;
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_NET_IRP: {
NET_IRP_STATS km;
SYS_NET_IRP_STATS um;
NetQueryIrpStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.Submitted = km.Submitted;
um.Completed = km.Completed;
um.CompletedSynchronously = km.CompletedSynchronously;
um.CompletedAsynchronously = km.CompletedAsynchronously;
um.Pending = km.Pending;
um.CancelRequested = km.CancelRequested;
um.Cancelled = km.Cancelled;
um.WouldBlock = km.WouldBlock;
um.Timeout = km.Timeout;
um.Failed = km.Failed;
for (uint32_t i = 0; i < SYS_NET_IRP_OPERATION_COUNT && i < NetSocketIrpOperationCount; ++i)
um.ByOperation[i] = km.ByOperation[i];
return KiReturnKernelStats(a1, (size_t)a2, a3, &um, sizeof(um));
}
case SYS_KERNEL_STATS_CPU: {
struct {
SYS_CPU_STATS Header;
SYS_CPU_STAT Records[KE_MAX_CPUS];
} stats;
memset(&stats, 0, sizeof(stats));
uint32_t n = gCpuCount;
if (n > KE_MAX_CPUS)
n = KE_MAX_CPUS;
size_t size = sizeof(SYS_CPU_STATS) + (size_t)n * sizeof(SYS_CPU_STAT);
stats.Header.Size = (uint32_t)size;
stats.Header.Version = SYS_STATS_VERSION;
stats.Header.HeaderSize = sizeof(SYS_CPU_STATS);
stats.Header.CpuCount = gCpuCount;
stats.Header.RecordSize = sizeof(SYS_CPU_STAT);
stats.Header.RecordCount = n;
for (uint32_t i = 0; i < n; i++) {
KCPU* cpu = &gCpus[i];
SYS_CPU_STAT* out = &stats.Records[i];
out->CpuIndex = cpu->CpuIndex;
out->LapicId = cpu->LapicId;
if (cpu->Present) out->Flags |= SYS_CPU_FLAG_PRESENT;
if (cpu->Started) out->Flags |= SYS_CPU_FLAG_STARTED;
if (cpu->Parked) out->Flags |= SYS_CPU_FLAG_PARKED;
if (cpu->Online) out->Flags |= SYS_CPU_FLAG_ONLINE;
if (cpu->IsBsp) out->Flags |= SYS_CPU_FLAG_BSP;
if (cpu->IsPrimaryThread) out->Flags |= SYS_CPU_FLAG_PRIMARY;
out->CurrentTid = cpu->CurrentThread ? cpu->CurrentThread->ThreadId : 0;
if (cpu->CurrentThread && cpu->CurrentThread->IsIdleThread) out->CurrentThreadFlags |= SYS_CPU_THREAD_FLAG_IDLE;
out->SchedulerTicks = cpu->SchedulerTicks;
out->ContextSwitches = cpu->ContextSwitches;
}
return KiReturnKernelStats(a1, (size_t)a2, a3, &stats, size);
}
default:
return STATUS_INVALID_PARAMETER;
}
}
// Dispatch table keyed by SYS_* constants.
typedef uint64_t (*SYSCALL_HANDLER)(uint64_t, uint64_t, uint64_t,
uint64_t, uint64_t, uint64_t);
static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = {
// Process, threads, modules, waits, and APC.
[SYS_EXIT_PROCESS] = SyscallExitProcess,
[SYS_EXIT_THREAD] = SyscallExitThread,
[SYS_CREATE_PROCESS] = SyscallCreateProcess,
[SYS_OPEN_PROCESS] = SyscallOpenProcess,
[SYS_GET_PROCESS_EXIT_CODE] = SyscallGetProcessExitCode,
[SYS_KILL_PROCESS] = SyscallKillProcess,
[SYS_GET_PROCESS_LIST] = SyscallGetProcessList,
[SYS_GET_PROCESS_PID] = SyscallGetProcessPid,
[SYS_CREATE_THREAD] = SyscallCreateThread,
[SYS_GET_THREAD_LIST] = SyscallGetThreadList,
[SYS_LOAD_MODULE] = SyscallLoadModule,
[SYS_UNLOAD_MODULE] = SyscallUnloadModule,
[SYS_GET_MODULE_INFO] = SyscallGetModuleInfo,
[SYS_GET_MODULE_EXPORT] = SyscallGetModuleExport,
[SYS_GET_MODULE_LIST] = SyscallGetModuleList,
[SYS_GET_CURRENT_TID] = SyscallGetCurrentTid,
[SYS_GET_CURRENT_PID] = SyscallGetCurrentPid,
[SYS_SET_THREAD_PRIORITY] = SyscallSetThreadPriority,
[SYS_YIELD] = SyscallYield,
[SYS_SLEEP] = SyscallSleep,
[SYS_WAIT_SINGLE] = SyscallWaitSingle,
[SYS_WAIT_MANY] = SyscallWaitMany,
[SYS_SET_USER_APC_DISPATCHER] = SyscallSetUserApcDispatcher,
[SYS_APC_RETURN] = SyscallApcReturn,
// Virtual memory, mappings, and handles.
[SYS_ALLOC_VM] = SyscallVmAlloc,
[SYS_FREE_VM] = SyscallVmFree,
[SYS_PROTECT_VM] = SyscallVmProtect,
[SYS_QUERY_VM] = SyscallVmQuery,
[SYS_CLOSE_HANDLE] = SyscallCloseHandle,
[SYS_DUPLICATE_HANDLE] = SyscallDuplicateHandle,
[SYS_CREATE_FILE_MAPPING] = SyscallCreateFileMapping,
[SYS_MAP_FILE] = SyscallMapFile,
// Synchronization objects.
[SYS_CREATE_MUTEX] = SyscallMutexCreate,
[SYS_TRY_ACQUIRE_MUTEX] = SyscallMutexTryAcquire,
[SYS_RELEASE_MUTEX] = SyscallMutexRelease,
[SYS_CREATE_SEM] = SyscallSemCreate,
[SYS_TRY_WAIT_SEM] = SyscallSemTryWait,
[SYS_SIGNAL_SEM] = SyscallSemSignal,
[SYS_CREATE_EVENT] = SyscallEventCreate,
[SYS_SIGNAL_EVENT] = SyscallEventSignal,
[SYS_CLEAR_EVENT] = SyscallEventClear,
// Queues and named queues.
[SYS_CREATE_QUEUE] = SyscallQueueCreate,
[SYS_SEND_QUEUE] = SyscallQueueSend,
[SYS_RECV_QUEUE] = SyscallQueueRecv,
[SYS_PUBLISH_QUEUE] = SyscallPublishQueue,
[SYS_OPEN_NAMED_QUEUE] = SyscallOpenNamedQueue,
[SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS] = SyscallOpenNamedQueueOwnerProcess,
// Files, directories, and current directory.
[SYS_OPEN_FILE] = SyscallOpenFile,
[SYS_READ_FILE] = SyscallReadFile,
[SYS_WRITE_FILE] = SyscallWriteFile,
[SYS_READ_FILE_ASYNC] = SyscallReadFileAsync,
[SYS_WRITE_FILE_ASYNC] = SyscallWriteFileAsync,
[SYS_CANCEL_IO] = SyscallCancelIo,
[SYS_SEEK_FILE] = SyscallSeekFile,
[SYS_GET_FILE_POS] = SyscallGetFilePos,
[SYS_GET_FILE_SIZE] = SyscallGetFileSize,
[SYS_GET_FILE_STAT] = SyscallGetFileStat,
[SYS_STAT_FILE] = SyscallStatFile,
[SYS_TRUNCATE_FILE] = SyscallTruncateFile,
[SYS_FLUSH_FILE] = SyscallFlushFile,
[SYS_DELETE_FILE] = SyscallDeleteFile,
[SYS_RENAME_FILE] = SyscallRenameFile,
[SYS_LOCK_FILE] = SyscallLockFile,
[SYS_UNLOCK_FILE] = SyscallUnlockFile,
[SYS_OPEN_DIR] = SyscallOpenDir,
[SYS_READ_DIR] = SyscallReadDir,
[SYS_CREATE_DIR] = SyscallCreateDir,
[SYS_REMOVE_DIR] = SyscallRemoveDir,
[SYS_SET_CWD] = SyscallSetCwd,
[SYS_REAL_PATH] = SyscallRealPath,
// Console, display, and surfaces.
[SYS_WRITE_CONSOLE] = SyscallWriteConsole,
[SYS_GET_CONSOLE_INFO] = SyscallGetConsoleInfo,
[SYS_SET_CONSOLE_CURSOR] = SyscallSetConsoleCursor,
[SYS_FILL_CONSOLE_CHARACTER] = SyscallFillConsoleCharacter,
[SYS_FILL_CONSOLE_ATTRIBUTE] = SyscallFillConsoleAttribute,
[SYS_PUT_SCREEN_CHAR] = SyscallPutScreenChar,
[SYS_CLEAR_SCREEN] = SyscallClearScreen,
[SYS_GET_SCREEN_SIZE] = SyscallGetScreenSize,
[SYS_QUERY_DISPLAY_INFO] = SyscallQueryDisplayInfo,
[SYS_CREATE_SURFACE] = SyscallCreateSurface,
[SYS_MAP_SURFACE] = SyscallMapSurface,
[SYS_QUERY_SURFACE] = SyscallQuerySurface,
[SYS_PRESENT_SURFACE] = SyscallPresentSurface,
// Input.
[SYS_POLL_KEYBOARD] = SyscallKbPoll,
[SYS_IS_KEY_DOWN] = SyscallKbIsKeyDown,
[SYS_READ_KEYBOARD_CHAR] = SyscallKbReadChar,
[SYS_READ_KEY] = SyscallKbReadKey,
[SYS_GET_KEY_MODIFIERS] = SyscallKbGetModifiers,
[SYS_OPEN_INPUT_QUEUE] = SyscallOpenInputQueue,
[SYS_READ_INPUT_EVENT] = SyscallKbReadInputEvent,
[SYS_PEEK_INPUT_EVENT] = SyscallKbPeekInputEvent,
[SYS_PEEK_MOUSE] = SyscallMousePeek,
[SYS_READ_MOUSE] = SyscallMouseRead,
[SYS_GET_MOUSE_POS] = SyscallMouseGetPos,
// Time, debug, and system information.
[SYS_CREATE_TIMER] = SyscallCreateTimer,
[SYS_SET_TIMER] = SyscallSetTimer,
[SYS_CANCEL_TIMER] = SyscallCancelTimer,
[SYS_GET_TICK] = SyscallGetTick,
[SYS_GET_TICK_FREQUENCY] = SyscallGetTickFrequency,
[SYS_GET_SYSTEM_TIME] = SyscallGetSystemTime,
[SYS_PRINT_DEBUG] = SyscallDebugPrint,
[SYS_QUERY_TRAP_FRAME] = SyscallQueryTrapFrame,
[SYS_GET_MEMORY_STATUS] = SyscallGetMemoryStatus,
[SYS_QUERY_KERNEL_STATS] = SyscallQueryKernelStats,
// Native sockets and polling.
[SYS_CREATE_SOCKET] = SyscallSocket,
[SYS_SOCKET_IO_ASYNC] = SyscallSocketIoAsync,
[SYS_BIND_SOCKET] = SyscallBind,
[SYS_LISTEN_SOCKET] = SyscallListen,
[SYS_ACCEPT_SOCKET] = SyscallAccept,
[SYS_CONNECT_SOCKET] = SyscallConnect,
[SYS_SEND_SOCKET] = SyscallSend,
[SYS_RECV_SOCKET] = SyscallRecv,
[SYS_SHUTDOWN_SOCKET] = SyscallShutdown,
[SYS_SEND_TO_SOCKET] = SyscallSendTo,
[SYS_RECV_FROM_SOCKET] = SyscallRecvFrom,
[SYS_RESOLVE_HOST] = SyscallResolveHost,
[SYS_SET_SOCKET_OPTION] = SyscallSetSocketOption,
[SYS_GET_SOCKET_OPTION] = SyscallGetSocketOption,
[SYS_POLL] = SyscallPoll,
};
// KeSyscallDispatch is entered from ke_syscall.asm after the assembly stub has reordered registers into the C calling convention.
uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1,
uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) {
KiValidateSyscallTrapFrame(nr, a0, a1, a2, a3, a4, a5);
THREAD* self = KeGetCurrentThread();
PROCESS* owner = self ? self->Owner : (PROCESS*)0;
if (owner && KeIsProcessTerminating(owner) &&
nr != SYS_EXIT_PROCESS && nr != SYS_EXIT_THREAD) {
int32_t exitCode = owner->ExitCode;
KeExitThread(self, exitCode);
while (true) ;
}
if (nr >= SYS_MAX || !gSyscallTable[nr]) return STATUS_NOT_SUPPORTED;
uint64_t result = gSyscallTable[nr](a0, a1, a2, a3, a4, a5);
// A process may be terminated while one of its threads is already inside a
// syscall. Do not allow that thread to return to userspace after the owner
// has been marked terminating.
owner = self ? self->Owner : (PROCESS*)0;
if (owner && KeIsProcessTerminating(owner) &&
nr != SYS_EXIT_PROCESS && nr != SYS_EXIT_THREAD) {
int32_t exitCode = owner->ExitCode;
KeExitThread(self, exitCode);
while (true) ;
}
return result;
}