Files
nt-lite/code/kernel/ke_syscall.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

6779 lines
269 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 the syscall number plus at most four register arguments.
// Compound operations use size-tagged request structures instead of extending
// the register ABI.
// Return value (RAX) is a KSTATUS code *or* an arbitrary 64-bit datum
// (pointer, size, tick count, etc.) depending on the call; conventions are
// 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/se_security.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_port.h>
#include <kernel/ke_system_service.h>
#include <kernel/ke_timer.h>
#include <kernel/ke_spinlock.h>
#include <kernel/ke_debug.h>
#include <kernel/ke_exception.h>
#include <kernel/ke_named_queue.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 <kernel/hal/hal_api.h>
#include <kernel/io_file.h>
#include <kernel/io_pipe.h>
#include <kernel/io_volume.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_surface.h>
#include <kernel/ke_audio.h>
#include <kernel/ke_time.h>
#include <kernel/mm_pool.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 Size;
SYS_HANDLE FileHandle;
SYS_HANDLE EventHandle;
uint32_t Flags;
uint64_t Offset;
void* Buffer;
size_t Length;
SYS_IO_STATUS_BLOCK_K* Iosb;
SYS_USER_APC_ROUTINE_K ApcRoutine;
void* ApcContext;
SYS_HANDLE* RequestHandleOut; // optional cancellation/query handle output
} SYS_ASYNC_FILE_REQUEST_K;
_Static_assert(sizeof(SYS_ASYNC_FILE_REQUEST_K) == sizeof(SYS_ASYNC_FILE_REQUEST),
"async file request ABI mismatch");
typedef struct SYS_USER_ASYNC_APC_CONTEXT {
KAPC KernelApc;
THREAD* TargetThread;
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;
}
}
// Implements the syscall Init operation.
void KeSyscallInit(const HAL_API* hal) {
gHal = hal;
}
// Requires one ambient privilege from the calling process's immutable security context.
static KSTATUS KiRequirePrivilege(SYS_PRIVILEGES privilege) {
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
return SeProcessHasPrivilege(process, privilege) ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED;
}
// Resolves one current-process handle while preserving the exact lookup status.
static OBJECT* KiHandleToObject(SYS_HANDLE handle,
OBJECT_TYPE expected,
SYS_RIGHTS requiredRights,
KSTATUS* statusOut) {
if (!statusOut) return (OBJECT*)0;
*statusOut = NTLSTATUS_FAILURE;
PROCESS* process = KeGetCurrentProcess();
if (!process) return (OBJECT*)0;
OBJECT* object = (OBJECT*)0;
*statusOut = KeReferenceHandle(process, handle, expected, requiredRights, &object);
return KSUCCESS(*statusOut) ? object : (OBJECT*)0;
}
// Helper: allocate a handle in the current process's table for 'obj'.
// Takes one reference on behalf of the handle slot.
// On failure, drops the reference it would have stored.
static KSTATUS KiAllocHandleForCurrentWithRights(OBJECT* obj, SYS_RIGHTS rights, SYS_HANDLE* handleOut) {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) {
ObDereferenceObject(obj);
return NTLSTATUS_FAILURE;
}
KSTATUS s = KeCreateHandle(proc, obj, rights, handleOut);
// Most syscall create/open paths receive a fresh creator reference from the
// subsystem. The root capability grant takes its own reference; this helper always
// drops the creator reference so the object is owned only by the returned
// handle (and any subsystem-internal refs).
ObDereferenceObject(obj);
return s;
}
#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 a request only after its leading size field matches the exact ABI layout.
static KSTATUS KiCopySizedUserRequest(const void* userRequest, void* dst, size_t size) {
if (!userRequest || !dst || size < sizeof(uint32_t) || size > UINT32_MAX)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t userSize = 0;
KSTATUS status = CopyFromUser(&userSize, userRequest, sizeof(userSize));
if (!KSUCCESS(status)) return status;
if (userSize != (uint32_t)size) return NTLSTATUS_INVALID_PARAMETER;
return CopyFromUser(dst, userRequest, size);
}
// Copies one bounded Port capability-transfer array from user memory.
static KSTATUS KiImportPortTransfers(const SYS_PORT_HANDLE_TRANSFER* userTransfers,
uint32_t count,
SYS_PORT_HANDLE_TRANSFER* transfers) {
if (count == 0)
return NTLSTATUS_SUCCESS;
if (!userTransfers || !transfers || count > SYS_PORT_MAX_TRANSFER_HANDLES)
return NTLSTATUS_INVALID_PARAMETER;
return CopyFromUser(transfers, userTransfers, sizeof(*transfers) * count);
}
// Validates the leading Size of a caller-owned output structure before writing it.
static KSTATUS KiProbeSizedUserOutput(void* userOutput, size_t size) {
if (!userOutput || size < sizeof(uint32_t) || size > UINT32_MAX)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t userSize = 0;
KSTATUS status = CopyFromUser(&userSize, userOutput, sizeof(userSize));
if (!KSUCCESS(status)) return status;
if (userSize != (uint32_t)size) return NTLSTATUS_INVALID_PARAMETER;
return MmProbeUserWrite(userOutput, size);
}
static KSTATUS KiImportUserStringView(SYS_STRING_VIEW userView,
char* dst,
size_t dstSize,
SYS_STRING_VIEW* out,
bool nulTerminate) {
if (!out) return NTLSTATUS_INVALID_PARAMETER;
*out = SysSvFromParts((const char*)0, 0);
if (userView.Length == 0) {
if (nulTerminate) {
if (!dst || dstSize == 0) return NTLSTATUS_BUFFER_TOO_SMALL;
dst[0] = '\0';
}
return NTLSTATUS_SUCCESS;
}
if (!userView.Data || !dst) return NTLSTATUS_INVALID_PARAMETER;
if (userView.Length > SYSCALL_STRING_MAX) return NTLSTATUS_BUFFER_TOO_SMALL;
size_t needed = userView.Length + (nulTerminate ? 1 : 0);
if (needed < userView.Length || needed > dstSize) return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Copies handle To User.
static KSTATUS KiCopyHandleToUser(SYS_HANDLE* handleOut, SYS_HANDLE value) {
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
return CopyToUser(handleOut, &value, sizeof(value));
}
// Copies u32 To User.
static KSTATUS KiCopyU32ToUser(uint32_t* valueOut, uint32_t value) {
if (!valueOut) return NTLSTATUS_INVALID_PARAMETER;
return CopyToUser(valueOut, &value, sizeof(value));
}
// Copies size To User.
static KSTATUS KiCopySizeToUser(size_t* valueOut, size_t value) {
if (!valueOut) return NTLSTATUS_INVALID_PARAMETER;
return CopyToUser(valueOut, &value, sizeof(value));
}
// Copies u64 To User.
static KSTATUS KiCopyU64ToUser(uint64_t* valueOut, uint64_t value) {
if (!valueOut) return NTLSTATUS_INVALID_PARAMETER;
return CopyToUser(valueOut, &value, sizeof(value));
}
// Copies int To User.
static KSTATUS KiCopyIntToUser(int* valueOut, int value) {
if (!valueOut) return NTLSTATUS_INVALID_PARAMETER;
return CopyToUser(valueOut, &value, sizeof(value));
}
// Copies one signed 32-bit integer to user memory.
static KSTATUS KiCopyInt32ToUser(int32_t* valueOut, int32_t value) {
if (!valueOut) return NTLSTATUS_INVALID_PARAMETER;
return CopyToUser(valueOut, &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.
// Implements SYS_EXIT_PROCESS.
static uint64_t SyscallExitProcess(int32_t exitCode) {
THREAD* t = KeGetCurrentThread();
PROCESS* proc = t ? t->Owner : NULL;
if (!proc || !t) return NTLSTATUS_FAILURE;
KSTATUS s = KeExitProcess(proc, exitCode);
if (!KSUCCESS(s)) return s;
KeExitThread(t, exitCode);
while (true) ; // unreachable - KeExitThread does not return
}
// Implements SYS_EXIT_THREAD.
static uint64_t SyscallExitThread(int32_t exitCode) {
THREAD* t = KeGetCurrentThread();
if (t) KeExitThread(t, (int)exitCode);
while (true);
}
// Implements SYS_CREATE_THREAD.
// Creates a user thread and optionally returns both its waitable handle and
// diagnostic TID. Suspended creation leaves the thread off every ready queue
// until SYS_RESUME_THREAD decrements its initial suspend count.
static uint64_t SyscallCreateThread(const SYS_CREATE_THREAD_REQUEST* request) {
SYS_CREATE_THREAD_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_FAILURE;
if (!req.Entry) return NTLSTATUS_INVALID_PARAMETER;
if (req.Priority >= PriorityCount) return NTLSTATUS_INVALID_PARAMETER;
if (req.Flags & ~SYS_CREATE_THREAD_FLAG_SUSPENDED)
return NTLSTATUS_INVALID_PARAMETER;
// Running threads may be detached: callers that do not need a handle or TID
// can leave both outputs null. A suspended thread still requires a handle so
// there is a capability with which to resume or cancel it.
if ((req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED) && !req.ThreadHandleOut)
return NTLSTATUS_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;
KE_THREAD_CREATE_PARAMETERS threadParameters = KE_THREAD_CREATE_NORMAL(req.Priority);
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
threadParameters.Flags |= KE_THREAD_CREATE_FLAG_SUSPENDED;
s = KeCreateThread(proc, req.Entry, req.Argument,
&threadParameters, &newt);
if (!KSUCCESS(s)) return s;
SYS_HANDLE threadHandle = SYS_HANDLE_INVALID;
if (req.ThreadHandleOut) {
s = KeCreateHandle(proc,
&newt->Header,
KeGetValidHandleRights(ObjectTypeThread),
&threadHandle);
if (!KSUCCESS(s)) {
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
(void)KeCancelSuspendedThread(newt);
ObDereferenceObject(&newt->Header);
return s;
}
}
if (req.ThreadHandleOut) {
s = KiCopyHandleToUser(req.ThreadHandleOut, threadHandle);
if (!KSUCCESS(s)) {
KeCloseHandle(proc, threadHandle);
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
(void)KeCancelSuspendedThread(newt);
ObDereferenceObject(&newt->Header);
return s;
}
}
if (req.ThreadIdOut) {
s = KiCopyU32ToUser(req.ThreadIdOut, newt->ThreadId);
if (!KSUCCESS(s)) {
if (threadHandle) KeCloseHandle(proc, threadHandle);
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
(void)KeCancelSuspendedThread(newt);
ObDereferenceObject(&newt->Header);
return s;
}
}
ObDereferenceObject(&newt->Header);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_RESUME_THREAD.
static uint64_t SyscallResumeThread(SYS_HANDLE threadHandle, uint32_t* previousSuspendCountOut) {
if (!threadHandle || !previousSuspendCountOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = MmProbeUserWrite(previousSuspendCountOut, sizeof(uint32_t));
if (!KSUCCESS(status)) return status;
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
OBJECT* object = (OBJECT*)0;
KSTATUS handleStatus = KeReferenceHandle(process, threadHandle, ObjectTypeThread,
SYS_THREAD_RIGHT_RESUME, &object);
if (!KSUCCESS(handleStatus)) return handleStatus;
THREAD* thread = CONTAINING_RECORD(object, THREAD, Header);
uint32_t previousSuspendCount = 0;
status = KeResumeThread(thread, &previousSuspendCount);
ObDereferenceObject(object);
if (!KSUCCESS(status)) return status;
return KiCopyU32ToUser(previousSuspendCountOut, previousSuspendCount);
}
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, SYS_HANDLE handle, EVENT** eventOut, OBJECT** objectOut) {
if (!proc || !eventOut || !objectOut) return NTLSTATUS_INVALID_PARAMETER;
*eventOut = (EVENT*)0;
*objectOut = (OBJECT*)0;
OBJECT* obj = (OBJECT*)0;
KSTATUS handleStatus = KeReferenceHandle(proc, handle, 0, SYS_RIGHT_WAIT, &obj);
if (!KSUCCESS(handleStatus)) return handleStatus;
EVENT* event = (EVENT*)0;
switch (obj->Type) {
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;
case ObjectTypeSurface:
event = KeSurfaceWaitEvent(obj);
break;
case ObjectTypeDisplayClock:
event = KeDisplayClockWaitEvent((DISPLAY_CLOCK_OBJECT*)obj);
break;
case ObjectTypeAudioStream:
event = KeAudioStreamWaitEvent((KE_AUDIO_STREAM*)obj);
break;
case ObjectTypePort:
event = KePortWaitEvent((PORT*)obj);
break;
case ObjectTypePipeEndpoint:
event = IoPipeWaitEvent((IO_PIPE_ENDPOINT*)obj);
break;
default:
ObDereferenceObject(obj);
return NTLSTATUS_NOT_SUPPORTED;
}
if (!event) {
ObDereferenceObject(obj);
return NTLSTATUS_OBJECT_DEAD;
}
*eventOut = event;
*objectOut = obj;
return NTLSTATUS_SUCCESS;
}
// Implements SYS_WAIT_SINGLE.
// Waits on one handle. Passive objects use the wait-many engine; mutexes and
// semaphores keep their native consuming semantics for single-object waits.
static uint64_t SyscallWaitSingle(SYS_HANDLE handle, uint32_t flags, uint64_t timeoutMilliseconds) {
uint64_t timeoutMs = timeoutMilliseconds;
if (flags & ~SYS_WAIT_FLAG_ALERTABLE) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_FAILURE;
OBJECT* obj = (OBJECT*)0;
KSTATUS handleStatus = KeReferenceHandle(proc, handle, 0, SYS_RIGHT_WAIT, &obj);
if (!KSUCCESS(handleStatus)) return handleStatus;
uint8_t alertable = (flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0;
KSTATUS s = NTLSTATUS_NOT_SUPPORTED;
if (obj->Type == ObjectTypeMutex) {
if (alertable) s = NTLSTATUS_NOT_SUPPORTED;
else s = KeAcquireMutex(CONTAINING_RECORD(obj, MUTEX, Header), timeoutMs);
ObDereferenceObject(obj);
return s;
}
if (obj->Type == ObjectTypeSemaphore) {
if (alertable) s = NTLSTATUS_NOT_SUPPORTED;
else s = KeWaitSemaphore(CONTAINING_RECORD(obj, SEMAPHORE, Header), timeoutMs);
ObDereferenceObject(obj);
return s;
}
ObDereferenceObject(obj);
EVENT* event = (EVENT*)0;
OBJECT* ref = (OBJECT*)0;
s = KiHandleToWaitEvent(proc, handle, &event, &ref);
if (!KSUCCESS(s)) return s;
if (ref->Type == ObjectTypePipeEndpoint)
IoPipeBeginWait((IO_PIPE_ENDPOINT*)ref);
uint32_t selected = 0;
s = KeWaitForMultipleEvents(&event, 1, alertable, timeoutMs, &selected);
if (ref->Type == ObjectTypePipeEndpoint)
IoPipeEndWait((IO_PIPE_ENDPOINT*)ref);
ObDereferenceObject(ref);
if (s != NTLSTATUS_TIMEOUT && s != NTLSTATUS_NOT_SUPPORTED)
KiFinishAlertableUserWait(alertable);
return s;
}
// Implements SYS_WAIT_MANY.
// Waits until any passive waitable handle becomes signaled.
static uint64_t SyscallWaitMany(const SYS_WAIT_MANY_REQUEST* request) {
SYS_WAIT_MANY_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags & ~SYS_WAIT_FLAG_ALERTABLE) return NTLSTATUS_INVALID_PARAMETER;
if (!req.Handles || req.Count == 0 || req.Count > SYS_WAIT_MANY_MAX_HANDLES)
return NTLSTATUS_INVALID_PARAMETER;
SYS_HANDLE handles[SYS_WAIT_MANY_MAX_HANDLES];
size_t handleBytes = (size_t)req.Count * sizeof(handles[0]);
s = CopyFromUser(handles, req.Handles, handleBytes);
if (!KSUCCESS(s)) return s;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_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;
}
}
for (uint32_t i = 0; i < req.Count; i++)
if (refs[i]->Type == ObjectTypePipeEndpoint)
IoPipeBeginWait((IO_PIPE_ENDPOINT*)refs[i]);
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++) {
if (refs[i]->Type == ObjectTypePipeEndpoint)
IoPipeEndWait((IO_PIPE_ENDPOINT*)refs[i]);
ObDereferenceObject(refs[i]);
}
if (KSUCCESS(s) && req.SignaledIndexOut) {
KSTATUS cs = KiCopyU32ToUser(req.SignaledIndexOut, selected);
if (!KSUCCESS(cs)) return cs;
}
if (s != NTLSTATUS_TIMEOUT && s != NTLSTATUS_NOT_SUPPORTED)
KiFinishAlertableUserWait(alertable);
return s;
}
// Implements SYS_OPEN_INPUT_QUEUE.
static uint64_t SyscallOpenInputQueue(uint32_t flags, SYS_HANDLE* handleOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (flags != 0) return NTLSTATUS_INVALID_PARAMETER;
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
INPUT_QUEUE* queue = (INPUT_QUEUE*)0;
KSTATUS s = KeInputOpenQueue(&queue);
if (!KSUCCESS(s)) return s;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&queue->Header, KeGetValidHandleRights(ObjectTypeInputQueue), &handle);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(handleOut, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
}
return s;
}
// Returns the current thread ID.
static uint64_t SyscallGetCurrentTid(void) {
THREAD* t = KeGetCurrentThread();
return t ? t->ThreadId : 0;
}
// Returns the current process ID.
static uint64_t SyscallGetCurrentPid(void) {
PROCESS* p = KeGetCurrentProcess();
return p ? p->ProcessId : 0;
}
// Yields the current thread.
static uint64_t SyscallYield(void) {
KeYield();
return NTLSTATUS_SUCCESS;
}
// Implements SYS_CREATE_TIMER.
static uint64_t SyscallCreateTimer(SYS_HANDLE* handleOut) {
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
KTIMER* timer = (KTIMER*)0;
KSTATUS s = KeTimerCreate(&timer);
if (!KSUCCESS(s)) return s;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&timer->Header,
KeGetValidHandleRights(ObjectTypeTimer),
&handle);
if (!KSUCCESS(s))
return s;
s = KiCopyHandleToUser(handleOut, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
}
return s;
}
// Implements SYS_SET_TIMER.
static uint64_t SyscallSetTimer(const SYS_SET_TIMER_REQUEST* request) {
SYS_SET_TIMER_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags != SYS_TIMER_FLAG_NONE)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KTIMER* timer = (KTIMER*)KiHandleToObject(req.TimerHandle, ObjectTypeTimer,
SYS_TIMER_RIGHT_SET, &handleStatus);
if (!timer) return handleStatus;
s = KeTimerSet(timer, req.DueTimeMilliseconds, req.PeriodMilliseconds);
ObDereferenceObject(&timer->Header);
return s;
}
// Implements SYS_CANCEL_TIMER.
static uint64_t SyscallCancelTimer(SYS_HANDLE timerHandle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KTIMER* timer = (KTIMER*)KiHandleToObject(timerHandle, ObjectTypeTimer,
SYS_TIMER_RIGHT_SET, &handleStatus);
if (!timer) return handleStatus;
KSTATUS s = KeTimerCancel(timer);
ObDereferenceObject(&timer->Header);
return s;
}
// Implements SYS_SLEEP.
// Sleep is a convenience wrapper over the same waitable timer backend exposed
// by timer handles. A zero-length sleep remains a yield.
static uint64_t SyscallSleep(uint64_t ms) {
if (ms == 0) {
KeYield();
return NTLSTATUS_SUCCESS;
}
KTIMER* timer = (KTIMER*)0;
KSTATUS s = KeTimerCreate(&timer);
if (!KSUCCESS(s)) return s;
s = KeTimerSet(timer, ms, 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;
}
// Implements SYS_SET_USER_APC_DISPATCHER.
static uint64_t SyscallSetUserApcDispatcher(void* dispatcher) {
if (!dispatcher) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS s = MmProbeUserRead(dispatcher, 1);
if (!KSUCCESS(s)) return s;
return KeSetUserApcDispatcher(dispatcher);
}
// Implements SYS_APC_RETURN.
static uint64_t SyscallApcReturn(const SYS_USER_APC_FRAME* frame) {
return KeUserApcReturn((const SYS_USER_APC_FRAME_K*)(uintptr_t)frame);
}
// Implements SYS_SET_EXCEPTION_DISPATCHER.
static uint64_t SyscallSetExceptionDispatcher(const SYS_EXCEPTION_DISPATCHER_REQUEST* userRequest) {
SYS_EXCEPTION_DISPATCHER_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status))
return status;
PROCESS* process = KeGetCurrentProcess();
if (!process)
return NTLSTATUS_FAILURE;
return KeSetExceptionDispatcher(process, &request);
}
// Implements SYS_GET_EXCEPTION_DISPATCHER.
static uint64_t SyscallGetExceptionDispatcher(SYS_EXCEPTION_DISPATCHER_REQUEST* userRequest) {
if (!userRequest)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t userSize = 0;
KSTATUS status = CopyFromUser(&userSize, userRequest, sizeof(userSize));
if (!KSUCCESS(status))
return status;
if (userSize != sizeof(SYS_EXCEPTION_DISPATCHER_REQUEST))
return NTLSTATUS_INVALID_PARAMETER;
PROCESS* process = KeGetCurrentProcess();
if (!process)
return NTLSTATUS_FAILURE;
SYS_EXCEPTION_DISPATCHER_REQUEST request;
status = KeGetExceptionDispatcher(process, &request);
if (!KSUCCESS(status))
return status;
return CopyToUser(userRequest, &request, sizeof(request));
}
// Implements SYS_CONTINUE_EXCEPTION.
static uint64_t SyscallContinueException(const SYS_CONTINUE_EXCEPTION_REQUEST* userRequest) {
SYS_CONTINUE_EXCEPTION_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status))
return status;
return KeContinueException(&request);
}
// Implements SYS_RAISE_EXCEPTION.
static uint64_t SyscallRaiseException(const SYS_RAISE_EXCEPTION_REQUEST* userRequest) {
SYS_RAISE_EXCEPTION_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status))
return status;
if (request.ParameterCount > SYS_EXCEPTION_MAXIMUM_PARAMETERS)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t parameters[SYS_EXCEPTION_MAXIMUM_PARAMETERS];
memset(parameters, 0, sizeof(parameters));
if (request.ParameterCount) {
if (!request.Parameters)
return NTLSTATUS_INVALID_PARAMETER;
status = CopyFromUser(parameters, request.Parameters,
(size_t)request.ParameterCount * sizeof(parameters[0]));
if (!KSUCCESS(status))
return status;
}
return KeRaiseUserException(&request, parameters);
}
// Implements SYS_SET_THREAD_PRIORITY.
static uint64_t SyscallSetThreadPriority(uint32_t priority) {
if (priority >= PriorityCount) return NTLSTATUS_INVALID_PARAMETER;
THREAD* t = KeGetCurrentThread();
if (!t) return NTLSTATUS_FAILURE;
return KiSchedulerSetNormalPriority(t, (THREAD_PRIORITY)priority);
}
// Implements SYS_SET_THREAD_NAME.
static uint64_t SyscallSetThreadName(const SYS_SET_THREAD_NAME_REQUEST* userRequest) {
SYS_SET_THREAD_NAME_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status)) return status;
char name[SYS_THREAD_NAME_MAX];
SYS_STRING_VIEW importedName;
status = KiImportUserTextView(request.Name, name, sizeof(name), &importedName, true);
if (!KSUCCESS(status)) return status;
THREAD* thread = (THREAD*)0;
OBJECT* referencedObject = (OBJECT*)0;
if (request.ThreadHandle == SYS_HANDLE_INVALID) {
thread = KeGetCurrentThread();
if (!thread) return NTLSTATUS_FAILURE;
} else {
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
status = KeReferenceHandle(process, request.ThreadHandle, ObjectTypeThread,
SYS_THREAD_RIGHT_SET_INFORMATION, &referencedObject);
if (!KSUCCESS(status)) return status;
thread = CONTAINING_RECORD(referencedObject, THREAD, Header);
}
status = KeSetThreadName(thread, importedName.Data, importedName.Length);
if (referencedObject) ObDereferenceObject(referencedObject);
return status;
}
// Implements SYS_QUERY_THREAD_NAME.
static uint64_t SyscallQueryThreadName(SYS_HANDLE threadHandle,
char* buffer,
size_t bufferSize,
size_t* lengthOut) {
if (!lengthOut || bufferSize > SYS_THREAD_NAME_MAX || (bufferSize && !buffer))
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = MmProbeUserWrite(lengthOut, sizeof(*lengthOut));
if (!KSUCCESS(status)) return status;
if (bufferSize) {
status = MmProbeUserWrite(buffer, bufferSize);
if (!KSUCCESS(status)) return status;
}
THREAD* thread = (THREAD*)0;
OBJECT* referencedObject = (OBJECT*)0;
if (threadHandle == SYS_HANDLE_INVALID) {
thread = KeGetCurrentThread();
if (!thread) return NTLSTATUS_FAILURE;
} else {
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
status = KeReferenceHandle(process, threadHandle, ObjectTypeThread,
SYS_RIGHT_QUERY, &referencedObject);
if (!KSUCCESS(status)) return status;
thread = CONTAINING_RECORD(referencedObject, THREAD, Header);
}
char name[SYS_THREAD_NAME_MAX];
size_t length = KeQueryThreadName(thread, name, sizeof(name));
if (referencedObject) ObDereferenceObject(referencedObject);
status = KiCopySizeToUser(lengthOut, length);
if (!KSUCCESS(status)) return status;
if (bufferSize < length + 1u)
return NTLSTATUS_BUFFER_TOO_SMALL;
return CopyToUser(buffer, name, length + 1u);
}
// SYS_VM_* page-granular user virtual memory.
static KSTATUS KiVmProtectToPageFlags(uint32_t protect, uint64_t* flagsOut) {
if (!flagsOut) return NTLSTATUS_INVALID_PARAMETER;
switch (protect) {
case SYS_VM_NOACCESS:
*flagsOut = 0;
return NTLSTATUS_SUCCESS;
case SYS_VM_READ:
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_NX;
return NTLSTATUS_SUCCESS;
case SYS_VM_READWRITE:
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_WRITE | PAGE_NX;
return NTLSTATUS_SUCCESS;
case SYS_VM_EXECUTE:
case SYS_VM_EXECUTE_READ:
*flagsOut = PAGE_PRESENT | PAGE_USER;
return NTLSTATUS_SUCCESS;
case SYS_VM_EXECUTE_READWRITE:
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_WRITE;
return NTLSTATUS_SUCCESS;
default:
return NTLSTATUS_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;
}
// Implements SYS_ALLOC_VM.
static uint64_t SyscallVmAlloc(const SYS_ALLOC_VM_REQUEST* request) {
SYS_ALLOC_VM_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Reserved != 0) return NTLSTATUS_INVALID_PARAMETER;
void* preferredBase = req.PreferredBase;
size_t pageCount = req.PageCount;
uint32_t protect = req.Protect;
uint32_t flags = req.Flags;
void** baseOut = req.BaseOut;
if (!pageCount || !baseOut) return NTLSTATUS_INVALID_PARAMETER;
if (pageCount > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY;
if (preferredBase || (flags & SYS_VM_FLAG_FIXED)) return NTLSTATUS_NOT_SUPPORTED;
uint64_t pageFlags = 0;
s = KiVmProtectToPageFlags(protect, &pageFlags);
if (!KSUCCESS(s)) return s;
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_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 NTLSTATUS_SUCCESS;
}
// Releases one user virtual-memory allocation.
static uint64_t SyscallVmFree(void* base) {
PROCESS* process = KeGetCurrentProcess();
if (!process || !base) return NTLSTATUS_INVALID_PARAMETER;
return MmVadDestroy(process, base);
}
// Changes page protection for one user virtual-memory range.
static uint64_t SyscallVmProtect(void* base, size_t pageCount, uint32_t protect, uint32_t* oldProtectOut) {
if (!base || !pageCount) return NTLSTATUS_INVALID_PARAMETER;
if (pageCount > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY;
uint64_t pageFlags = 0;
KSTATUS s = KiVmProtectToPageFlags(protect, &pageFlags);
if (!KSUCCESS(s)) return s;
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
if (oldProtectOut) {
void* regionBase = (void*)0;
size_t regionSize = 0;
uint64_t oldFlags = 0;
MM_VAD_TYPE type = MmVadGeneric;
uint32_t regionFlags = 0;
s = MmVadQuery(process, base, &regionBase, &regionSize, &oldFlags, &type, &regionFlags);
if (!KSUCCESS(s)) return s;
uint32_t oldProtect = KiPageFlagsToVmProtect(oldFlags);
s = CopyToUser(oldProtectOut, &oldProtect, sizeof(oldProtect));
if (!KSUCCESS(s)) return s;
}
return MmVadProtect(process, base, pageCount * PAGE_SIZE, pageFlags);
}
// Queries the user virtual-memory region containing one address.
static uint64_t SyscallVmQuery(void* address, SYS_VM_REGION_INFO* out) {
if (!address || !out) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
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, address, &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(out, &info, sizeof(info));
}
// Implements SYS_CREATE_MUTEX.
static uint64_t SyscallMutexCreate(SYS_HANDLE* handleOut) {
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
MUTEX* mx = NULL;
KSTATUS s = KeMutexCreate(&mx);
if (!KSUCCESS(s)) return s;
SYS_HANDLE h = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&mx->Header, KeGetValidHandleRights(ObjectTypeMutex), &h); // takes ownership of the ref
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(handleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// Implements SYS_TRY_ACQUIRE_MUTEX.
static uint64_t SyscallMutexTryAcquire(SYS_HANDLE handle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
MUTEX* mx = (MUTEX*)KiHandleToObject(handle, ObjectTypeMutex, SYS_RIGHT_WAIT, &handleStatus);
if (!mx) return handleStatus;
KSTATUS s = KeAcquireMutex(mx, 0);
if (s == NTLSTATUS_TIMEOUT) s = NTLSTATUS_FAILURE;
ObDereferenceObject(&mx->Header);
return s;
}
// Implements SYS_RELEASE_MUTEX.
static uint64_t SyscallMutexRelease(SYS_HANDLE handle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
MUTEX* mx = (MUTEX*)KiHandleToObject(handle, ObjectTypeMutex, SYS_MUTEX_RIGHT_RELEASE, &handleStatus);
if (!mx) return handleStatus;
KeReleaseMutex(mx);
ObDereferenceObject(&mx->Header);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_CREATE_SEM.
static uint64_t SyscallSemCreate(int32_t initial, int32_t max, SYS_HANDLE* handleOut) {
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
SEMAPHORE* sem = NULL;
KSTATUS s = KeSemaphoreCreate(initial, max, &sem);
if (!KSUCCESS(s)) return s;
SYS_HANDLE h = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&sem->Header, KeGetValidHandleRights(ObjectTypeSemaphore), &h);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(handleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// Implements SYS_TRY_WAIT_SEM.
static uint64_t SyscallSemTryWait(SYS_HANDLE handle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject(handle, ObjectTypeSemaphore, SYS_RIGHT_WAIT, &handleStatus);
if (!sem) return handleStatus;
KSTATUS s = KeWaitSemaphore(sem, 0);
if (s == NTLSTATUS_TIMEOUT) s = NTLSTATUS_FAILURE;
ObDereferenceObject(&sem->Header);
return s;
}
// Implements SYS_SIGNAL_SEM.
static uint64_t SyscallSemSignal(SYS_HANDLE handle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject(handle, ObjectTypeSemaphore,
SYS_SEMAPHORE_RIGHT_SIGNAL, &handleStatus);
if (!sem) return handleStatus;
KeSignalSemaphore(sem);
ObDereferenceObject(&sem->Header);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_CREATE_EVENT.
static uint64_t SyscallEventCreate(uint32_t type, uint32_t initialState, SYS_HANDLE* handleOut) {
if (!handleOut || type > EventManualReset) return NTLSTATUS_INVALID_PARAMETER;
EVENT* ev = NULL;
KSTATUS s = KeEventCreate((EVENT_TYPE)type, initialState, &ev);
if (!KSUCCESS(s)) return s;
SYS_HANDLE h = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&ev->Header, KeGetValidHandleRights(ObjectTypeEvent), &h);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(handleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// Implements SYS_SIGNAL_EVENT.
static uint64_t SyscallEventSignal(SYS_HANDLE handle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
EVENT* ev = (EVENT*)KiHandleToObject(handle, ObjectTypeEvent, SYS_EVENT_RIGHT_SIGNAL, &handleStatus);
if (!ev) return handleStatus;
KeSignalEvent(ev);
ObDereferenceObject(&ev->Header);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_CLEAR_EVENT.
static uint64_t SyscallEventClear(SYS_HANDLE handle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
EVENT* ev = (EVENT*)KiHandleToObject(handle, ObjectTypeEvent, SYS_EVENT_RIGHT_SIGNAL, &handleStatus);
if (!ev) return handleStatus;
KeClearEvent(ev);
ObDereferenceObject(&ev->Header);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_CREATE_QUEUE.
static uint64_t SyscallQueueCreate(SYS_HANDLE* handleOut) {
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
QUEUE* q = NULL;
KSTATUS s = KeQueueCreate(&q);
if (!KSUCCESS(s)) return s;
SYS_HANDLE h = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&q->Header, KeGetValidHandleRights(ObjectTypeQueue), &h);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(handleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// Implements SYS_SEND_QUEUE.
static uint64_t SyscallQueueSend(const SYS_QUEUE_SEND_REQUEST* request) {
SYS_QUEUE_SEND_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags != SYS_QUEUE_FLAG_NONE || !req.Message)
return NTLSTATUS_INVALID_PARAMETER;
QUEUE_MESSAGE msg;
s = CopyFromUser(&msg, req.Message, sizeof(msg));
if (!KSUCCESS(s)) return s;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, SYS_QUEUE_RIGHT_SEND, &handleStatus);
if (!q) return handleStatus;
s = KeSendMessage(q, &msg, req.TimeoutMilliseconds);
ObDereferenceObject(&q->Header);
return s;
}
// Implements SYS_RECV_QUEUE.
static uint64_t SyscallQueueRecv(const SYS_QUEUE_RECV_REQUEST* request) {
SYS_QUEUE_RECV_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags != SYS_QUEUE_FLAG_NONE || !req.MessageOut)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, SYS_QUEUE_RIGHT_RECEIVE, &handleStatus);
if (!q) return handleStatus;
QUEUE_MESSAGE msg;
s = KeReceiveMessage(q, &msg, req.TimeoutMilliseconds);
ObDereferenceObject(&q->Header);
if (!KSUCCESS(s)) return s;
return CopyToUser(req.MessageOut, &msg, sizeof(msg));
}
// Implements SYS_CLOSE_HANDLE.
// The one and only close path. KeCloseHandle drops the handle-table reference
// on 'obj'. If that was the last reference, ObDereferenceObject runs the
// type-specific Destroy callback (KiFileDestroy, etc.) and frees the pool memory.
// No per-type close syscall needed or wanted.
static uint64_t SyscallCreateFileMapping(const SYS_CREATE_FILE_MAPPING_REQUEST* request) {
SYS_CREATE_FILE_MAPPING_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.FileHandle || !req.MappingHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t pageFlags = 0;
s = KiVmProtectToPageFlags(req.Protect, &pageFlags);
if (!KSUCCESS(s)) return s;
if (!pageFlags) return NTLSTATUS_ACCESS_DENIED;
SYS_RIGHTS neededRights = SYS_FILE_RIGHT_MAP_READ | SYS_RIGHT_QUERY;
if (pageFlags & PAGE_WRITE)
neededRights |= SYS_FILE_RIGHT_MAP_WRITE;
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
neededRights, &fileHandleStatus);
if (!file) return fileHandleStatus;
uint64_t maximumSize = req.MaximumSize;
if (maximumSize == 0)
maximumSize = IoGetFileSize(file);
FILE_MAPPING_OBJECT* mapping = (FILE_MAPPING_OBJECT*)ObAllocateObject(ObjectTypeFileMapping,
sizeof(FILE_MAPPING_OBJECT));
if (!mapping) {
ObDereferenceObject(&file->Header);
return NTLSTATUS_NO_MEMORY;
}
mapping->Header.Destroy = KiFileMappingDestroy;
mapping->File = file;
mapping->MaximumSize = maximumSize;
mapping->PageFlags = pageFlags;
mapping->Protect = req.Protect;
mapping->Flags = req.Flags;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&mapping->Header,
KeGetValidHandleRights(ObjectTypeFileMapping),
&handle);
if (!KSUCCESS(s))
return s;
s = KiCopyHandleToUser(req.MappingHandleOut, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
}
return s;
}
static KSTATUS KiMapAccessToPageFlags(const FILE_MAPPING_OBJECT* mapping,
uint32_t access,
uint64_t* pageFlagsOut) {
if (!mapping || !pageFlagsOut)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t mappingFlags = mapping->PageFlags;
if (!mappingFlags)
return NTLSTATUS_ACCESS_DENIED;
if (access == 0)
access = SYS_MAP_ACCESS_READ;
if ((access & SYS_MAP_ACCESS_WRITE) && (access & SYS_MAP_ACCESS_COPY))
return NTLSTATUS_INVALID_PARAMETER;
if (((access & SYS_MAP_ACCESS_COPY) || (access & SYS_MAP_ACCESS_WRITE)) &&
(mappingFlags & PAGE_WRITE) == 0)
return NTLSTATUS_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 NTLSTATUS_ACCESS_DENIED;
} else {
flags |= PAGE_NX;
}
*pageFlagsOut = flags;
return NTLSTATUS_SUCCESS;
}
// Maps a file-mapping object into the current process.
static uint64_t SyscallMapFile(const SYS_MAP_FILE_REQUEST* request) {
SYS_MAP_FILE_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.MappingHandle || !req.BaseOut)
return NTLSTATUS_INVALID_PARAMETER;
if (req.Offset & (PAGE_SIZE - 1))
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS mappingHandleStatus = NTLSTATUS_SUCCESS;
FILE_MAPPING_OBJECT* mapping = (FILE_MAPPING_OBJECT*)KiHandleToObject(req.MappingHandle,
ObjectTypeFileMapping,
SYS_FILE_MAPPING_RIGHT_MAP,
&mappingHandleStatus);
if (!mapping)
return mappingHandleStatus;
uint64_t maximumSize = mapping->MaximumSize;
if (maximumSize == 0)
maximumSize = IoGetFileSize(mapping->File);
if (req.Offset >= maximumSize) {
ObDereferenceObject(&mapping->Header);
return NTLSTATUS_INVALID_PARAMETER;
}
size_t length = req.Length;
if (length == 0) {
uint64_t tail = maximumSize - req.Offset;
if (tail > (uint64_t)((size_t)-1)) {
ObDereferenceObject(&mapping->Header);
return NTLSTATUS_NO_MEMORY;
}
length = (size_t)tail;
}
if (length == 0 || (uint64_t)length > maximumSize - req.Offset) {
ObDereferenceObject(&mapping->Header);
return NTLSTATUS_INVALID_PARAMETER;
}
uint64_t pageFlags = 0;
s = KiMapAccessToPageFlags(mapping, req.Access, &pageFlags);
if (!KSUCCESS(s)) {
ObDereferenceObject(&mapping->Header);
return s;
}
PROCESS* process = KeGetCurrentProcess();
if (!process) {
ObDereferenceObject(&mapping->Header);
return NTLSTATUS_FAILURE;
}
uint32_t vadFlags = req.Flags;
if (req.Access & SYS_MAP_ACCESS_WRITE)
vadFlags |= MM_VAD_SHARED_WRITE;
void* base = (void*)0;
s = MmVadCreateFileMapping(process, mapping->File, req.Offset, length,
pageFlags, vadFlags, &base);
ObDereferenceObject(&mapping->Header);
if (!KSUCCESS(s))
return s;
s = CopyToUser(req.BaseOut, &base, sizeof(base));
if (!KSUCCESS(s)) {
MmVadDestroy(process, base);
return s;
}
return NTLSTATUS_SUCCESS;
}
// Closes one handle through the unified object handle path.
static uint64_t SyscallCloseHandle(SYS_HANDLE handle) {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_FAILURE;
return KeCloseHandle(proc, handle);
}
// Converts public file access flags to handle rights and file object flags.
static KSTATUS KiFileAccessToHandleRights(uint32_t access, SYS_RIGHTS* rightsOut, uint32_t* fileFlagsOut) {
if (!rightsOut || !fileFlagsOut)
return NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS rights = SYS_RIGHT_MANAGE;
uint32_t fileFlags = 0;
if (access & SYS_FILE_ACCESS_READ)
rights |= SYS_FILE_RIGHT_READ_DATA | SYS_FILE_RIGHT_MAP_READ;
if (access & SYS_FILE_ACCESS_WRITE)
rights |= SYS_FILE_RIGHT_WRITE_DATA | SYS_FILE_RIGHT_MAP_WRITE;
if (access & SYS_FILE_ACCESS_APPEND) {
rights |= SYS_FILE_RIGHT_WRITE_DATA | SYS_FILE_RIGHT_MAP_WRITE;
fileFlags |= IO_FILE_FLAG_APPEND;
}
if (access & SYS_FILE_ACCESS_QUERY)
rights |= SYS_RIGHT_QUERY;
if (access & SYS_FILE_ACCESS_LOCK)
rights |= SYS_FILE_RIGHT_LOCK;
*rightsOut = rights;
*fileFlagsOut = fileFlags;
return NTLSTATUS_SUCCESS;
}
// Converts public directory access flags to directory capability rights.
static KSTATUS KiDirAccessToHandleRights(uint32_t access, SYS_RIGHTS* rightsOut) {
if (!rightsOut)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t valid = SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN | SYS_DIR_ACCESS_MODIFY;
if ((access & ~valid) != 0 || access == 0)
return NTLSTATUS_INVALID_PARAMETER;
if ((access & SYS_DIR_ACCESS_MODIFY) && !(access & SYS_DIR_ACCESS_OPEN))
return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS rights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY;
if (access & SYS_DIR_ACCESS_ENUMERATE)
rights |= SYS_DIR_RIGHT_ENUMERATE;
if (access & SYS_DIR_ACCESS_OPEN)
rights |= SYS_DIR_RIGHT_OPEN;
if (access & SYS_DIR_ACCESS_MODIFY)
rights |= SYS_DIR_RIGHT_MODIFY;
*rightsOut = rights;
return NTLSTATUS_SUCCESS;
}
// References one capability-relative path root with type-specific traversal authority.
static OBJECT* KiReferencePathRoot(SYS_HANDLE handle, bool requireModify, KSTATUS* statusOut) {
if (!statusOut) return (OBJECT*)0;
KSTATUS status = NTLSTATUS_SUCCESS;
OBJECT* probe = KiHandleToObject(handle, 0, 0, &status);
if (!probe) {
*statusOut = status;
return (OBJECT*)0;
}
OBJECT_TYPE type = probe->Type;
ObDereferenceObject(probe);
SYS_RIGHTS required = 0;
if (type == ObjectTypeVolume)
required = SYS_VOLUME_RIGHT_OPEN | (requireModify ? SYS_VOLUME_RIGHT_MODIFY : 0);
else if (type == ObjectTypeDir)
required = SYS_DIR_RIGHT_OPEN | (requireModify ? SYS_DIR_RIGHT_MODIFY : 0);
else {
*statusOut = NTLSTATUS_INVALID_HANDLE;
return (OBJECT*)0;
}
return KiHandleToObject(handle, type, required, statusOut);
}
// Opens or creates one file beneath a referenced volume/directory root.
static KSTATUS KiOpenFileAtRoot(OBJECT* root,
SYS_STRING_VIEW path,
bool create,
FILE_OBJECT** fileOut) {
if (!root || !fileOut)
return NTLSTATUS_INVALID_PARAMETER;
if (root->Type == ObjectTypeVolume) {
return create
? IoCreateFileAtVolume((IO_VOLUME*)root, path, fileOut)
: IoOpenFileAtVolume((IO_VOLUME*)root, path, fileOut);
}
if (root->Type == ObjectTypeDir) {
return create
? IoCreateFileAtDirectory((DIR_OBJECT*)root, path, fileOut)
: IoOpenFileAtDirectory((DIR_OBJECT*)root, path, fileOut);
}
return NTLSTATUS_INVALID_HANDLE;
}
// Opens one directory beneath a referenced volume/directory root.
static KSTATUS KiOpenDirAtRoot(OBJECT* root, SYS_STRING_VIEW path, DIR_OBJECT** dirOut) {
if (!root || !dirOut)
return NTLSTATUS_INVALID_PARAMETER;
if (root->Type == ObjectTypeVolume)
return IoOpenDirAtVolume((IO_VOLUME*)root, path, dirOut);
if (root->Type == ObjectTypeDir)
return IoOpenDirAtDirectory((DIR_OBJECT*)root, path, dirOut);
return NTLSTATUS_INVALID_HANDLE;
}
// Opens a file beneath a capability root using the public disposition contract.
static KSTATUS KiOpenFileAtRootByDisposition(OBJECT* root,
SYS_STRING_VIEW path,
uint32_t disposition,
uint32_t access,
FILE_OBJECT** fileOut) {
if (!root || !fileOut)
return NTLSTATUS_INVALID_PARAMETER;
*fileOut = (FILE_OBJECT*)0;
bool writes = (access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND)) != 0;
KSTATUS status;
switch (disposition) {
case SYS_FILE_OPEN_EXISTING:
return KiOpenFileAtRoot(root, path, false, fileOut);
case SYS_FILE_CREATE_NEW:
if (!writes)
return NTLSTATUS_ACCESS_DENIED;
return KiOpenFileAtRoot(root, path, true, fileOut);
case SYS_FILE_CREATE_ALWAYS:
if (!writes)
return NTLSTATUS_ACCESS_DENIED;
status = KiOpenFileAtRoot(root, path, true, fileOut);
if (status == NTLSTATUS_OBJECT_NAME_COLLISION) {
status = KiOpenFileAtRoot(root, path, false, fileOut);
if (KSUCCESS(status)) {
KSTATUS truncateStatus = IoTruncateFile(*fileOut, 0);
if (!KSUCCESS(truncateStatus)) {
IoCloseFile(*fileOut);
*fileOut = (FILE_OBJECT*)0;
return truncateStatus;
}
}
}
return status;
case SYS_FILE_OPEN_ALWAYS:
status = KiOpenFileAtRoot(root, path, false, fileOut);
if (status == NTLSTATUS_NOT_FOUND) {
if (!writes)
return NTLSTATUS_ACCESS_DENIED;
status = KiOpenFileAtRoot(root, path, true, fileOut);
}
return status;
case SYS_FILE_TRUNCATE_EXISTING:
if (!writes)
return NTLSTATUS_ACCESS_DENIED;
status = KiOpenFileAtRoot(root, path, false, fileOut);
if (KSUCCESS(status)) {
KSTATUS truncateStatus = IoTruncateFile(*fileOut, 0);
if (!KSUCCESS(truncateStatus)) {
IoCloseFile(*fileOut);
*fileOut = (FILE_OBJECT*)0;
return truncateStatus;
}
}
return status;
default:
return NTLSTATUS_INVALID_PARAMETER;
}
}
// Implements SYS_OPEN_FILE_AT. Authority comes only from the supplied root capability.
static uint64_t SyscallOpenFileAt(const SYS_OPEN_FILE_AT_REQUEST* request) {
SYS_OPEN_FILE_AT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER;
if (req.Flags != SYS_FILE_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS rights = 0;
uint32_t fileFlags = 0;
status = KiFileAccessToHandleRights(req.Access, &rights, &fileFlags);
if (!KSUCCESS(status)) return status;
char pathBuffer[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
status = KiImportUserTextView(req.Path, pathBuffer, sizeof(pathBuffer), &path, true);
if (!KSUCCESS(status)) return status;
bool requireModify = (req.Access & (SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_APPEND)) != 0 ||
req.Disposition != SYS_FILE_OPEN_EXISTING;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* root = KiReferencePathRoot(req.RootHandle, requireModify, &handleStatus);
if (!root) return handleStatus;
FILE_OBJECT* file = (FILE_OBJECT*)0;
status = KiOpenFileAtRootByDisposition(root, path, req.Disposition, req.Access, &file);
ObDereferenceObject(root);
if (!KSUCCESS(status)) return status;
if (fileFlags != 0) {
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
file->Flags |= fileFlags;
KeReleaseSpinlock(&file->Lock, flags);
}
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = KiAllocHandleForCurrentWithRights(&file->Header, rights, &handle);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.HandleOut, handle);
if (!KSUCCESS(status)) {
PROCESS* process = KeGetCurrentProcess();
if (process) KeCloseHandle(process, handle);
}
return status;
}
// Implements SYS_OPEN_DIR_AT. Authority comes only from the supplied root capability.
static uint64_t SyscallOpenDirAt(const SYS_OPEN_DIR_AT_REQUEST* request) {
SYS_OPEN_DIR_AT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER;
if (req.Flags != SYS_DIR_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS dirRights = 0;
status = KiDirAccessToHandleRights(req.Access, &dirRights);
if (!KSUCCESS(status)) return status;
char pathBuffer[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
status = KiImportUserTextView(req.Path, pathBuffer, sizeof(pathBuffer), &path, true);
if (!KSUCCESS(status)) return status;
bool requireModify = (req.Access & SYS_DIR_ACCESS_MODIFY) != 0;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* root = KiReferencePathRoot(req.RootHandle, requireModify, &handleStatus);
if (!root) return handleStatus;
DIR_OBJECT* dir = (DIR_OBJECT*)0;
status = KiOpenDirAtRoot(root, path, &dir);
ObDereferenceObject(root);
if (!KSUCCESS(status)) return status;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = KiAllocHandleForCurrentWithRights(&dir->Header, dirRights, &handle);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.HandleOut, handle);
if (!KSUCCESS(status)) {
PROCESS* process = KeGetCurrentProcess();
if (process) KeCloseHandle(process, handle);
}
return status;
}
// Imports one capability-relative path from userspace.
static KSTATUS KiImportAtPath(SYS_STRING_VIEW userPath, char* buffer, size_t bufferSize,
bool allowEmpty, SYS_STRING_VIEW* pathOut) {
KSTATUS status = KiImportUserTextView(userPath, buffer, bufferSize, pathOut, true);
if (!KSUCCESS(status)) return status;
return (allowEmpty || pathOut->Length) ? NTLSTATUS_SUCCESS : NTLSTATUS_INVALID_PARAMETER;
}
// Implements SYS_STAT_AT. Authority comes only from the supplied root capability.
static uint64_t SyscallStatAt(const SYS_STAT_AT_REQUEST* request) {
SYS_STAT_AT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (!req.StatOut) return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.StatOut, sizeof(*req.StatOut));
if (!KSUCCESS(status)) return status;
char pathBuffer[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), true, &path);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* root = KiReferencePathRoot(req.RootHandle, false, &handleStatus);
if (!root) return handleStatus;
SYS_FILE_STAT stat;
status = IoStatAtRoot(root, path, &stat);
ObDereferenceObject(root);
if (!KSUCCESS(status)) return status;
return CopyToUser(req.StatOut, &stat, sizeof(stat));
}
// Implements SYS_DELETE_AT. Authority comes only from the supplied root capability.
static uint64_t SyscallDeleteAt(const SYS_PATH_AT_REQUEST* request) {
SYS_PATH_AT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
char pathBuffer[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), false, &path);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* root = KiReferencePathRoot(req.RootHandle, true, &handleStatus);
if (!root) return handleStatus;
status = IoDeleteFileAtRoot(root, path);
ObDereferenceObject(root);
return status;
}
// Implements SYS_RENAME_AT. Both roots must grant modify authority and resolve to the same volume.
static uint64_t SyscallRenameAt(const SYS_RENAME_AT_REQUEST* request) {
SYS_RENAME_AT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
char oldBuffer[SYSCALL_STRING_MAX];
char newBuffer[SYSCALL_STRING_MAX];
SYS_STRING_VIEW oldPath;
SYS_STRING_VIEW newPath;
status = KiImportAtPath(req.OldPath, oldBuffer, sizeof(oldBuffer), false, &oldPath);
if (!KSUCCESS(status)) return status;
status = KiImportAtPath(req.NewPath, newBuffer, sizeof(newBuffer), false, &newPath);
if (!KSUCCESS(status)) return status;
KSTATUS oldStatus = NTLSTATUS_SUCCESS;
OBJECT* oldRoot = KiReferencePathRoot(req.OldRootHandle, true, &oldStatus);
if (!oldRoot) return oldStatus;
KSTATUS newStatus = NTLSTATUS_SUCCESS;
OBJECT* newRoot = KiReferencePathRoot(req.NewRootHandle, true, &newStatus);
if (!newRoot) {
ObDereferenceObject(oldRoot);
return newStatus;
}
status = IoRenameFileAtRoots(oldRoot, oldPath, newRoot, newPath);
ObDereferenceObject(newRoot);
ObDereferenceObject(oldRoot);
return status;
}
// Implements SYS_CREATE_DIR_AT. Authority comes only from the supplied root capability.
static uint64_t SyscallCreateDirAt(const SYS_PATH_AT_REQUEST* request) {
SYS_PATH_AT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
char pathBuffer[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), false, &path);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* root = KiReferencePathRoot(req.RootHandle, true, &handleStatus);
if (!root) return handleStatus;
status = IoCreateDirAtRoot(root, path);
ObDereferenceObject(root);
return status;
}
// Implements SYS_REMOVE_DIR_AT. Authority comes only from the supplied root capability.
static uint64_t SyscallRemoveDirAt(const SYS_PATH_AT_REQUEST* request) {
SYS_PATH_AT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
char pathBuffer[SYSCALL_STRING_MAX];
SYS_STRING_VIEW path;
status = KiImportAtPath(req.Path, pathBuffer, sizeof(pathBuffer), false, &path);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* root = KiReferencePathRoot(req.RootHandle, true, &handleStatus);
if (!root) return handleStatus;
status = IoRemoveDirAtRoot(root, path);
ObDereferenceObject(root);
return status;
}
static void* KiAllocateSyscallIoBuffer(size_t requested, size_t* actualOut) {
if (actualOut) *actualOut = 0;
if (!requested) return NULL;
size_t chunk = requested < SYSCALL_FILE_IO_CHUNK_BYTES ? requested : SYSCALL_FILE_IO_CHUNK_BYTES;
while (true) {
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(const SYS_FILE_IO_REQUEST* userRequest, SYS_FILE_IO_REQUEST* req) {
KSTATUS s = KiCopySizedUserRequest(userRequest, req, sizeof(*req));
if (!KSUCCESS(s))
return s;
if (req->Flags != 0)
return NTLSTATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return NTLSTATUS_INVALID_PARAMETER;
if (req->Length && !req->Buffer)
return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Implements SYS_READ_FILE.
static uint64_t SyscallReadFile(const SYS_FILE_IO_REQUEST* request) {
SYS_FILE_IO_REQUEST req;
KSTATUS s = KiImportSyncFileIoRequest(request, &req);
if (!KSUCCESS(s))
return s;
if (req.Length == 0) {
if (req.TransferredOut)
return KiCopySizeToUser(req.TransferredOut, 0);
return NTLSTATUS_SUCCESS;
}
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
SYS_FILE_RIGHT_READ_DATA, &fileHandleStatus);
if (!file) return fileHandleStatus;
size_t chunkSize = 0;
void* kbuf = KiAllocateSyscallIoBuffer(req.Length, &chunkSize);
if (!kbuf) {
ObDereferenceObject(&file->Header);
return NTLSTATUS_NO_MEMORY;
}
uintptr_t user = (uintptr_t)req.Buffer;
size_t total = 0;
s = NTLSTATUS_SUCCESS;
while (total < req.Length) {
size_t todo = req.Length - total;
if (todo > chunkSize) todo = chunkSize;
if (user > (uintptr_t)-1 - total) {
s = NTLSTATUS_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(req.TransferredOut, total);
if (KSUCCESS(s)) s = cs;
}
return s;
}
// Implements SYS_WRITE_FILE.
static uint64_t SyscallWriteFile(const SYS_FILE_IO_REQUEST* request) {
SYS_FILE_IO_REQUEST req;
KSTATUS s = KiImportSyncFileIoRequest(request, &req);
if (!KSUCCESS(s))
return s;
if (req.Length == 0) {
if (req.TransferredOut)
return KiCopySizeToUser(req.TransferredOut, 0);
return NTLSTATUS_SUCCESS;
}
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
SYS_FILE_RIGHT_WRITE_DATA, &fileHandleStatus);
if (!file) return fileHandleStatus;
size_t chunkSize = 0;
void* kbuf = KiAllocateSyscallIoBuffer(req.Length, &chunkSize);
if (!kbuf) {
ObDereferenceObject(&file->Header);
return NTLSTATUS_NO_MEMORY;
}
uintptr_t user = (uintptr_t)req.Buffer;
size_t total = 0;
s = NTLSTATUS_SUCCESS;
while (total < req.Length) {
size_t todo = req.Length - total;
if (todo > chunkSize) todo = chunkSize;
if (user > (uintptr_t)-1 - total) {
s = NTLSTATUS_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(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(const SYS_ASYNC_FILE_REQUEST* request, bool write) {
if (!request) return NTLSTATUS_INVALID_PARAMETER;
SYS_ASYNC_FILE_REQUEST_K req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags) return NTLSTATUS_INVALID_PARAMETER;
if (!req.Iosb) return NTLSTATUS_INVALID_PARAMETER;
if (!req.Buffer && req.Length) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_FAILURE;
if (req.RequestHandleOut) {
s = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut));
if (!KSUCCESS(s)) return s;
}
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(
req.FileHandle, ObjectTypeFile,
write ? SYS_FILE_RIGHT_WRITE_DATA : SYS_FILE_RIGHT_READ_DATA,
&fileHandleStatus);
if (!file) return fileHandleStatus;
EVENT* event = (EVENT*)0;
if (req.EventHandle) {
KSTATUS eventHandleStatus = NTLSTATUS_SUCCESS;
event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent,
SYS_EVENT_RIGHT_SIGNAL, &eventHandleStatus);
if (!event) {
ObDereferenceObject(&file->Header);
return eventHandleStatus;
}
}
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 NTLSTATUS_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 NTLSTATUS_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.Length) {
s = MmLockUserBuffer(proc, req.Buffer, req.Length, 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.Length, req.Offset,
(IO_STATUS_BLOCK*)req.Iosb, event,
completionApc,
lockedBuffer,
lockedIosb,
&asyncReq);
} else {
s = IoReadFileAsyncKernel(file, req.Buffer, req.Length, 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 == NTLSTATUS_PENDING && req.RequestHandleOut && asyncReq) {
SYS_HANDLE requestHandle = SYS_HANDLE_INVALID;
KSTATUS hs = KeCreateHandle(proc,
IoAsyncFileRequestObject(asyncReq),
KeGetValidHandleRights(ObjectTypeAsyncIo),
&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) {
SYS_HANDLE zeroHandle = SYS_HANDLE_INVALID;
KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle));
if (!KSUCCESS(cs)) {
if (asyncReq) ObDereferenceObject(IoAsyncFileRequestObject(asyncReq));
if (s != NTLSTATUS_PENDING && apcCtx)
SyscallUserAsyncApcContextFree(apcCtx);
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return cs;
}
}
if (asyncReq)
ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); // drop creator reference
if (s != NTLSTATUS_PENDING && apcCtx)
SyscallUserAsyncApcContextFree(apcCtx);
if (event) ObDereferenceObject(&event->Header);
ObDereferenceObject(&file->Header);
return s;
}
// Implements SYS_READ_FILE_ASYNC.
static uint64_t SyscallReadFileAsync(const SYS_ASYNC_FILE_REQUEST* request) {
return SyscallAsyncFileCommon(request, false);
}
// Implements SYS_WRITE_FILE_ASYNC.
static uint64_t SyscallWriteFileAsync(const SYS_ASYNC_FILE_REQUEST* request) {
return SyscallAsyncFileCommon(request, true);
}
// Implements SYS_CANCEL_IO.
static uint64_t SyscallCancelIo(SYS_HANDLE asyncRequestHandle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
IO_ASYNC_REQUEST_OBJECT* req = (IO_ASYNC_REQUEST_OBJECT*)KiHandleToObject(
asyncRequestHandle, ObjectTypeAsyncIo, SYS_ASYNC_IO_RIGHT_CANCEL, &handleStatus);
if (!req) return handleStatus;
KSTATUS s = IoCancelAsyncRequest(req);
ObDereferenceObject(IoAsyncRequestObject(req));
return s;
}
// Implements SYS_TRUNCATE_FILE.
static uint64_t SyscallTruncateFile(SYS_HANDLE handle, uint64_t newSize) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile,
SYS_FILE_RIGHT_WRITE_DATA, &handleStatus);
if (!file) return handleStatus;
KSTATUS s = IoTruncateFile(file, newSize);
ObDereferenceObject(&file->Header);
return s;
}
// Implements SYS_FLUSH_FILE.
static uint64_t SyscallFlushFile(SYS_HANDLE handle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile,
SYS_FILE_RIGHT_WRITE_DATA, &handleStatus);
if (!file) return handleStatus;
KSTATUS s = IoFlushFile(file);
ObDereferenceObject(&file->Header);
return s;
}
// Implements SYS_GET_FILE_POS.
static uint64_t SyscallGetFilePos(SYS_HANDLE handle, uint64_t* positionOut) {
if (!positionOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
if (!file) return handleStatus;
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
uint64_t pos = file->Offset;
KeReleaseSpinlock(&file->Lock, flags);
ObDereferenceObject(&file->Header);
return KiCopyU64ToUser(positionOut, pos);
}
// Implements SYS_SEEK_FILE.
static uint64_t SyscallSeekFile(SYS_HANDLE handle, uint64_t offset) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
if (!file) return handleStatus;
KSTATUS s = IoSeekFile(file, offset);
ObDereferenceObject(&file->Header);
return s;
}
// Implements SYS_GET_FILE_SIZE.
static uint64_t SyscallGetFileSize(SYS_HANDLE handle, uint64_t* sizeOut) {
if (!sizeOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
if (!file) return handleStatus;
uint64_t sz = IoGetFileSize(file);
ObDereferenceObject(&file->Header);
return KiCopyU64ToUser(sizeOut, sz);
}
// Implements SYS_GET_FILE_STAT.
static uint64_t SyscallGetFileStat(SYS_HANDLE handle, SYS_FILE_STAT* statOut) {
if (!statOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
if (!file) return handleStatus;
SYS_FILE_STAT stat;
KSTATUS s = IoGetFileStat(file, &stat);
ObDereferenceObject(&file->Header);
if (!KSUCCESS(s)) return s;
return CopyToUser(statOut, &stat, sizeof(stat));
}
// Implements SYS_OPEN_DISPLAY_CLOCK.
static uint64_t SyscallOpenDisplayClock(const SYS_OPEN_DISPLAY_CLOCK_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_OPEN_DISPLAY_CLOCK_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags != SYS_DISPLAY_CLOCK_FLAG_NONE || !req.HandleOut)
return NTLSTATUS_INVALID_PARAMETER;
DISPLAY_CLOCK_OBJECT* clock = (DISPLAY_CLOCK_OBJECT*)0;
s = KeDisplayOpenClock(req.PeriodMilliseconds, &clock);
if (!KSUCCESS(s))
return s;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&clock->Header, KeGetValidHandleRights(ObjectTypeDisplayClock), &handle);
if (!KSUCCESS(s))
return s;
s = KiCopyHandleToUser(req.HandleOut, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
}
return s;
}
// Copies one optional surface rectangle list from user memory. A zero count means the whole surface.
static KSTATUS KiCaptureUserSurfaceRects(const DISPLAY_RECT* userRects, uint32_t rectCount,
DISPLAY_RECT* rectsOut) {
if (rectCount > SYS_SURFACE_MAX_RECTS || (rectCount && (!userRects || !rectsOut)))
return NTLSTATUS_INVALID_PARAMETER;
if (!rectCount) return NTLSTATUS_SUCCESS;
return CopyFromUser(rectsOut, userRects, (size_t)rectCount * sizeof(DISPLAY_RECT));
}
// Implements SYS_CREATE_SURFACE by creating storage and returning its root capability.
static uint64_t SyscallCreateSurface(const SYS_CREATE_SURFACE_REQUEST* request) {
SYS_CREATE_SURFACE_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER;
OBJECT* surface = (OBJECT*)0;
status = KeSurfaceCreate(req.Width, req.Height, req.Format, req.Flags, &surface);
if (!KSUCCESS(status)) return status;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = KiAllocHandleForCurrentWithRights(surface, KeGetValidHandleRights(ObjectTypeSurface), &handle);
if (!KSUCCESS(status)) return status;
status = CopyToUser(req.HandleOut, &handle, sizeof(handle));
if (!KSUCCESS(status))
KeCloseHandle(KeGetCurrentProcess(), handle);
return status;
}
// Implements SYS_MAP_SURFACE. Each process receives its own VAD over shared backing frames.
static uint64_t SyscallMapSurface(SYS_HANDLE surfaceHandle, uint32_t flags,
void** baseOut, uint32_t* pitchBytesOut) {
if (!baseOut || !pitchBytesOut || !(flags & SYS_SURFACE_MAP_READ) ||
(flags & ~(SYS_SURFACE_MAP_READ | SYS_SURFACE_MAP_WRITE)))
return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS rights = SYS_SURFACE_RIGHT_MAP_READ;
if (flags & SYS_SURFACE_MAP_WRITE) rights |= SYS_SURFACE_RIGHT_MAP_WRITE;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface, rights, &handleStatus);
if (!surface) return handleStatus;
void* userBase = (void*)0;
uint32_t pitchBytes = 0;
KSTATUS status = KeSurfaceMap(KeGetCurrentProcess(), surface, flags, &userBase, &pitchBytes);
if (KSUCCESS(status)) status = CopyToUser(pitchBytesOut, &pitchBytes, sizeof(pitchBytes));
if (KSUCCESS(status)) status = CopyToUser(baseOut, &userBase, sizeof(userBase));
if (!KSUCCESS(status) && userBase)
(void)KeSurfaceUnmap(KeGetCurrentProcess(), userBase);
ObDereferenceObject(surface);
return status;
}
// Implements SYS_UNMAP_SURFACE without accepting arbitrary VADs as surface mappings.
static uint64_t SyscallUnmapSurface(void* base) {
return KeSurfaceUnmap(KeGetCurrentProcess(), base);
}
// Implements SYS_QUERY_SURFACE.
static uint64_t SyscallQuerySurface(SYS_HANDLE surfaceHandle, SYS_SURFACE_DESC* descOut) {
if (!descOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface, SYS_RIGHT_QUERY, &handleStatus);
if (!surface) return handleStatus;
SYS_SURFACE_DESC desc;
KSTATUS status = KeSurfaceQuery(surface, &desc);
ObDereferenceObject(surface);
if (!KSUCCESS(status)) return status;
return CopyToUser(descOut, &desc, sizeof(desc));
}
// Implements SYS_COMMIT_SURFACE. Commit announces new pixels and never touches scanout.
static uint64_t SyscallCommitSurface(SYS_HANDLE surfaceHandle, const DISPLAY_RECT* userRects,
uint32_t rectCount, uint64_t* generationOut) {
DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS];
KSTATUS status = KiCaptureUserSurfaceRects(userRects, rectCount, rects);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface,
SYS_SURFACE_RIGHT_COMMIT, &handleStatus);
if (!surface) return handleStatus;
uint64_t generation = 0;
status = KeSurfaceCommit(surface, rectCount ? rects : (DISPLAY_RECT*)0, rectCount, &generation);
ObDereferenceObject(surface);
if (KSUCCESS(status) && generationOut)
status = CopyToUser(generationOut, &generation, sizeof(generation));
return status;
}
// Clears the legacy text-console display after its caller has already been authorized.
static KSTATUS KiConsoleClear(uint32_t color) {
return KeDisplayClear(color);
}
// Returns the current console geometry and cursor state after authorization.
static KSTATUS KiConsoleQueryInfo(SYS_CONSOLE_INFO* infoOut) {
if (!infoOut) return NTLSTATUS_INVALID_PARAMETER;
KE_CONSOLE_INFO ki;
KSTATUS status = KeDisplayGetConsoleInfo(&ki);
if (!KSUCCESS(status)) return status;
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(infoOut, &out, sizeof(out));
}
// Updates the legacy text-console cursor after authorization.
static KSTATUS KiConsoleSetCursor(int x, int y) {
if (x < 0 || y < 0) return NTLSTATUS_INVALID_PARAMETER;
return KeDisplaySetConsoleCursor(x, y);
}
// Draws one console glyph at explicit pixel coordinates after authorization.
static KSTATUS KiConsolePutCharacter(const SYS_PUT_SCREEN_CHAR_REQUEST* request) {
SYS_PUT_SCREEN_CHAR_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2] || req.X < 0 || req.Y < 0)
return NTLSTATUS_INVALID_PARAMETER;
return KeDisplayPutChar(req.Character, req.X, req.Y, req.Foreground, req.Background);
}
// Fills console characters over one linear range after authorization.
static KSTATUS KiConsoleFillCharacter(const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) {
SYS_FILL_CONSOLE_CHARACTER_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2] || req.X < 0 || req.Y < 0)
return NTLSTATUS_INVALID_PARAMETER;
size_t written = 0;
status = KeDisplayFillConsoleChars(req.Character, req.X, req.Y, req.Length, &written);
if (req.WrittenOut) {
KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written);
if (KSUCCESS(status) && !KSUCCESS(copyStatus)) status = copyStatus;
}
return status;
}
// Fills console attributes over one linear range after authorization.
static KSTATUS KiConsoleFillAttribute(const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) {
SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Reserved != 0 || req.X < 0 || req.Y < 0)
return NTLSTATUS_INVALID_PARAMETER;
size_t written = 0;
status = KeDisplayFillConsoleAttrs(req.Attribute, req.X, req.Y, req.Length, &written);
if (req.WrittenOut) {
KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written);
if (KSUCCESS(status) && !KSUCCESS(copyStatus)) status = copyStatus;
}
return status;
}
// Implements SYS_CLEAR_SCREEN through the legacy ambient console privilege.
static uint64_t SyscallClearScreen(uint32_t color) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiConsoleClear(color);
}
// Implements SYS_GET_SCREEN_SIZE.
static uint64_t SyscallGetScreenSize(int* widthOut, int* heightOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
int w = 0, h = 0;
KSTATUS gs = KeDisplayGetSize(&w, &h);
if (!KSUCCESS(gs)) return gs;
if (widthOut) {
KSTATUS s = KiCopyIntToUser(widthOut, w);
if (!KSUCCESS(s)) return s;
}
if (heightOut) {
KSTATUS s = KiCopyIntToUser(heightOut, h);
if (!KSUCCESS(s)) return s;
}
return NTLSTATUS_SUCCESS;
}
// Implements SYS_PUT_SCREEN_CHAR through the legacy ambient console privilege.
static uint64_t SyscallPutScreenChar(const SYS_PUT_SCREEN_CHAR_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiConsolePutCharacter(request);
}
// Implements SYS_GET_CONSOLE_INFO through the legacy ambient console privilege.
static uint64_t SyscallGetConsoleInfo(SYS_CONSOLE_INFO* infoOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiConsoleQueryInfo(infoOut);
}
// Implements SYS_SET_CONSOLE_CURSOR through the legacy ambient console privilege.
static uint64_t SyscallSetConsoleCursor(int x, int y) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiConsoleSetCursor(x, y);
}
// Implements SYS_FILL_CONSOLE_CHARACTER through the legacy ambient console privilege.
static uint64_t SyscallFillConsoleCharacter(const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiConsoleFillCharacter(request);
}
// Implements SYS_FILL_CONSOLE_ATTRIBUTE through the legacy ambient console privilege.
static uint64_t SyscallFillConsoleAttribute(const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiConsoleFillAttribute(request);
}
static KSTATUS KiReferenceInputQueueHandle(SYS_HANDLE handle, INPUT_QUEUE** queueOut) {
if (!queueOut) return NTLSTATUS_INVALID_PARAMETER;
*queueOut = (INPUT_QUEUE*)0;
if (handle == 0) return NTLSTATUS_SUCCESS;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
OBJECT* object = KiHandleToObject(handle, ObjectTypeInputQueue,
SYS_INPUT_QUEUE_RIGHT_READ, &handleStatus);
if (!object) return handleStatus;
*queueOut = CONTAINING_RECORD(object, INPUT_QUEUE, Header);
return NTLSTATUS_SUCCESS;
}
// Implements the capability-scoped raw input-manager stream used by RSSRV.
static uint64_t SyscallReadInputManagerEvent(const SYS_INPUT_MANAGER_READ_REQUEST* request) {
SYS_INPUT_MANAGER_READ_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_INPUT_MANAGER_EVENT_FLAG_NONE || !req.EventOut)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_INPUT_MANAGER* manager = (KE_INPUT_MANAGER*)KiHandleToObject(
req.ManagerHandle, ObjectTypeInputManager, SYS_INPUT_MANAGER_RIGHT_READ, &handleStatus);
if (!manager) return handleStatus;
SYS_INPUT_MANAGER_EVENT event;
status = KeInputManagerReadEvent(manager, req.TimeoutMilliseconds, &event);
ObDereferenceObject(&manager->Header);
if (!KSUCCESS(status)) return status;
return CopyToUser(req.EventOut, &event, sizeof(event));
}
// Copies one keyboard input record to user mode.
static KSTATUS KiCopyInputRecordToUser(INPUT_KEY_RECORD* userRecord, const INPUT_KEY_RECORD* record) {
if (!userRecord || !record) return NTLSTATUS_INVALID_PARAMETER;
return CopyToUser(userRecord, record, sizeof(*record));
}
// Imports and validates a keyboard input event request.
static KSTATUS KiCopyInputEventRequest(const SYS_INPUT_EVENT_REQUEST* userRequest, SYS_INPUT_EVENT_REQUEST* req) {
KSTATUS s = KiCopySizedUserRequest(userRequest, req, sizeof(*req));
if (!KSUCCESS(s)) return s;
if (req->Flags != SYS_INPUT_EVENT_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
if (!req->RecordOut) return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Implements SYS_POLL_KEYBOARD.
static uint64_t SyscallKbPoll(INPUT_KEY* keyOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!keyOut) return NTLSTATUS_INVALID_PARAMETER;
INPUT_KEY key = KeInputKbPoll();
KSTATUS s = CopyToUser(keyOut, &key, sizeof(key));
if (!KSUCCESS(s)) return s;
return (key == INPUT_KEY_NONE) ? NTLSTATUS_WOULD_BLOCK : NTLSTATUS_SUCCESS;
}
// Implements SYS_IS_KEY_DOWN.
static uint64_t SyscallKbIsKeyDown(INPUT_KEY key, bool* downOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!downOut) return NTLSTATUS_INVALID_PARAMETER;
bool down = KeInputIsKeyDown((INPUT_KEY)key);
return CopyToUser(downOut, &down, sizeof(down));
}
// Implements SYS_READ_KEYBOARD_CHAR.
static uint64_t SyscallKbReadChar(char* charOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!charOut) return NTLSTATUS_INVALID_PARAMETER;
while (true) {
char ch = KeInputReadChar();
if (ch)
return CopyToUser(charOut, &ch, sizeof(ch));
KSTATUS s = KeInputWaitKey(0, SYS_WAIT_TIMEOUT_INFINITE);
if (!KSUCCESS(s))
return s;
}
}
// Implements SYS_READ_KEY.
static uint64_t SyscallKbReadKey(INPUT_KEY* keyOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!keyOut) return NTLSTATUS_INVALID_PARAMETER;
while (true) {
INPUT_KEY key = KeInputReadKey();
if (key != INPUT_KEY_NONE)
return CopyToUser(keyOut, &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(void) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return (uint64_t)KeInputGetModifiers();
}
// Implements SYS_QUERY_GLOBAL_INPUT_STATE.
static uint64_t SyscallQueryGlobalInputState(SYS_GLOBAL_INPUT_STATE* stateOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_GLOBAL_INPUT_STATE state;
KSTATUS s = KiCopySizedUserRequest(stateOut, &state, sizeof(state));
if (!KSUCCESS(s)) return s;
if (state.Flags != SYS_GLOBAL_INPUT_STATE_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
s = KeInputQueryGlobalState(&state);
if (!KSUCCESS(s)) return s;
return CopyToUser(stateOut, &state, sizeof(state));
}
// Implements SYS_READ_INPUT_EVENT.
static uint64_t SyscallKbReadInputEvent(const SYS_INPUT_EVENT_REQUEST* request) {
SYS_INPUT_EVENT_REQUEST req;
KSTATUS s = KiCopyInputEventRequest(request, &req);
if (!KSUCCESS(s)) return s;
if (req.QueueHandle == SYS_HANDLE_INVALID) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
}
INPUT_QUEUE* queue = (INPUT_QUEUE*)0;
s = KiReferenceInputQueueHandle(req.QueueHandle, &queue);
if (!KSUCCESS(s)) return s;
INPUT_KEY_RECORD record;
while (true) {
bool available = queue ? KeInputQueueReadEvent(queue, &record)
: KeInputReadEvent(&record);
if (available) {
s = KiCopyInputRecordToUser(req.RecordOut, &record);
break;
}
if (req.TimeoutMilliseconds == 0) {
s = NTLSTATUS_WOULD_BLOCK;
break;
}
s = queue ? KeInputQueueWaitKey(queue, 0, req.TimeoutMilliseconds)
: KeInputWaitKey(0, req.TimeoutMilliseconds);
if (!KSUCCESS(s))
break;
}
if (queue) ObDereferenceObject(&queue->Header);
return s;
}
// Implements SYS_PEEK_INPUT_EVENT.
static uint64_t SyscallKbPeekInputEvent(const SYS_INPUT_EVENT_REQUEST* request) {
SYS_INPUT_EVENT_REQUEST req;
KSTATUS s = KiCopyInputEventRequest(request, &req);
if (!KSUCCESS(s)) return s;
if (req.QueueHandle == SYS_HANDLE_INVALID) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
}
INPUT_QUEUE* queue = (INPUT_QUEUE*)0;
s = KiReferenceInputQueueHandle(req.QueueHandle, &queue);
if (!KSUCCESS(s)) return s;
INPUT_KEY_RECORD record;
while (true) {
bool available = queue ? KeInputQueuePeekEvent(queue, &record)
: KeInputPeekEvent(&record);
if (available) {
s = KiCopyInputRecordToUser(req.RecordOut, &record);
break;
}
if (req.TimeoutMilliseconds == 0) {
s = NTLSTATUS_WOULD_BLOCK;
break;
}
s = queue ? KeInputQueueWaitKey(queue, 0, req.TimeoutMilliseconds)
: KeInputWaitKey(0, req.TimeoutMilliseconds);
if (!KSUCCESS(s))
break;
}
if (queue) ObDereferenceObject(&queue->Header);
return s;
}
// Implements SYS_PEEK_MOUSE.
static uint64_t SyscallMousePeek(INPUT_MOUSE_EVENT* eventOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!eventOut) return NTLSTATUS_INVALID_PARAMETER;
INPUT_MOUSE_EVENT event;
if (!KeInputMousePeek(&event)) return NTLSTATUS_WOULD_BLOCK;
return CopyToUser(eventOut, &event, sizeof(event));
}
// Implements SYS_READ_MOUSE.
static uint64_t SyscallMouseRead(INPUT_MOUSE_EVENT* eventOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!eventOut) return NTLSTATUS_INVALID_PARAMETER;
INPUT_MOUSE_EVENT event;
if (!KeInputMouseRead(&event)) return NTLSTATUS_WOULD_BLOCK;
return CopyToUser(eventOut, &event, sizeof(event));
}
// Implements SYS_GET_MOUSE_POS.
static uint64_t SyscallMouseGetPos(int* xOut, int* yOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_GLOBAL_INPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!xOut || !yOut) return NTLSTATUS_INVALID_PARAMETER;
int x = 0, y = 0;
KeInputMouseGetPosition(&x, &y);
KSTATUS s = KiCopyIntToUser(xOut, x);
if (!KSUCCESS(s)) return s;
return KiCopyIntToUser(yOut, y);
}
// Returns monotonic ticks since boot.
static uint64_t SyscallGetTick(void) {
return gHal ? gHal->Timer.QueryTicks() : 0;
}
// Copies the frequency used by SysGetTick.
static uint64_t SyscallGetTickFrequency(uint64_t* frequencyOut) {
if (!gHal || !gHal->Timer.QueryTickFrequency) return NTLSTATUS_DEVICE_ERROR;
uint64_t frequency = gHal->Timer.QueryTickFrequency();
if (!frequency) return NTLSTATUS_DEVICE_ERROR;
return KiCopyU64ToUser(frequencyOut, frequency);
}
// Implements SYS_GET_SYSTEM_TIME.
static uint64_t SyscallGetSystemTime(SYS_SYSTEM_TIME* timeOut) {
if (!timeOut) return NTLSTATUS_INVALID_PARAMETER;
SYS_SYSTEM_TIME t;
KSTATUS s = KeQuerySystemTime(&t);
if (!KSUCCESS(s)) return s;
return CopyToUser(timeOut, &t, sizeof(t));
}
// Implements SYS_PRINT_DEBUG.
static uint64_t SyscallDebugPrint(const SYS_PRINT_DEBUG_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DEBUG_OUTPUT);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_PRINT_DEBUG_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Text.Length == 0) return NTLSTATUS_SUCCESS;
if (!req.Text.Data) return NTLSTATUS_INVALID_PARAMETER;
if (req.Text.Length > SYSCALL_TEXT_MAX_BYTES) return NTLSTATUS_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 NTLSTATUS_SUCCESS;
}
// Implements SYS_QUERY_TRAP_FRAME.
static uint64_t SyscallQueryTrapFrame(SYS_TRAP_FRAME_INFO* infoOut) {
if (!infoOut) return NTLSTATUS_INVALID_PARAMETER;
uint32_t userSize = 0;
KSTATUS s = CopyFromUser(&userSize, infoOut, sizeof(userSize));
if (!KSUCCESS(s)) return s;
SYS_TRAP_FRAME_INFO_K trapInfo;
memset(&trapInfo, 0, sizeof(trapInfo));
trapInfo.Size = userSize;
s = KeQueryCurrentTrapFrame(&trapInfo);
size_t copySize = (s == NTLSTATUS_BUFFER_TOO_SMALL) ? sizeof(trapInfo.Size) : sizeof(trapInfo);
KSTATUS cs = CopyToUser(infoOut, &trapInfo, copySize);
if (!KSUCCESS(cs)) return cs;
return s;
}
// Begins one process-construction memory operation without racing the start transition.
static KSTATUS KiBeginProcessBuildOperation(PROCESS* process) {
if (!process) return NTLSTATUS_INVALID_PARAMETER;
for (;;) {
if (_InterlockedCompareExchange(&process->BuildState, 0, 0) != KE_PROCESS_BUILD_CONSTRUCTING)
return NTLSTATUS_ACCESS_DENIED;
_InterlockedIncrement(&process->BuildOperations);
if (_InterlockedCompareExchange(&process->BuildState, 0, 0) == KE_PROCESS_BUILD_CONSTRUCTING)
return NTLSTATUS_SUCCESS;
_InterlockedDecrement(&process->BuildOperations);
}
}
// Ends one process-construction memory operation.
static void KiEndProcessBuildOperation(PROCESS* process) {
if (!process) return;
long remaining = _InterlockedDecrement(&process->BuildOperations);
if (remaining == 0 && KeIsProcessTerminating(process))
(void)KeTerminateProcess(process, process->ExitCode);
}
// Copies the caller-selected security context for one child process.
static KSTATUS KiReferenceChildSecurityContext(SYS_HANDLE contextHandle, SECURITY_CONTEXT** contextOut) {
if (!contextOut) return NTLSTATUS_INVALID_PARAMETER;
*contextOut = (SECURITY_CONTEXT*)0;
PROCESS* parent = KeGetCurrentProcess();
if (!parent || !parent->SecurityContext) return NTLSTATUS_FAILURE;
if (contextHandle == SYS_SECURITY_CONTEXT_CURRENT) {
*contextOut = parent->SecurityContext;
ObReferenceObject(&(*contextOut)->Header);
return NTLSTATUS_SUCCESS;
}
KSTATUS status = NTLSTATUS_SUCCESS;
SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)KiHandleToObject(contextHandle,
ObjectTypeSecurityContext,
SYS_SECURITY_CONTEXT_RIGHT_ASSIGN,
&status);
if (!context) return status;
*contextOut = context;
return NTLSTATUS_SUCCESS;
}
// Implements SYS_CREATE_PROCESS_OBJECT. The returned process has no runnable thread.
static uint64_t SyscallCreateProcessObject(const SYS_CREATE_PROCESS_OBJECT_REQUEST* request) {
SYS_CREATE_PROCESS_OBJECT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_CREATE_PROCESS_OBJECT_FLAG_NONE || !req.ProcessHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.ProcessHandleOut, sizeof(*req.ProcessHandleOut));
if (!KSUCCESS(status)) return status;
char imagePathBuf[SYSCALL_STRING_MAX];
char commandLineBuf[SYSCALL_STRING_MAX];
SYS_STRING_VIEW imagePath;
SYS_STRING_VIEW commandLine;
status = KiImportUserTextView(req.ImagePath, imagePathBuf, sizeof(imagePathBuf), &imagePath, true);
if (!KSUCCESS(status)) return status;
if (imagePath.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
status = KiImportUserTextView(req.CommandLine, commandLineBuf, sizeof(commandLineBuf), &commandLine, true);
if (!KSUCCESS(status)) return status;
SECURITY_CONTEXT* childContext = (SECURITY_CONTEXT*)0;
status = KiReferenceChildSecurityContext(req.SecurityContext, &childContext);
if (!KSUCCESS(status)) return status;
PROCESS* child = (PROCESS*)0;
status = KeCreateProcess(imagePathBuf, childContext, &child);
ObDereferenceObject(&childContext->Header);
if (!KSUCCESS(status)) return status;
child->BuildState = KE_PROCESS_BUILD_CONSTRUCTING;
child->BuildOperations = 0;
PROCESS* parent = KeGetCurrentProcess();
status = KeSetupUserProcessEnvironment(child, imagePath, commandLine);
if (!KSUCCESS(status)) {
ObDereferenceObject(&child->Header);
return status;
}
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = KeCreateHandle(parent, &child->Header, KeGetValidHandleRights(ObjectTypeProcess), &handle);
if (KSUCCESS(status)) {
status = KiCopyHandleToUser(req.ProcessHandleOut, handle);
if (!KSUCCESS(status)) KeCloseHandle(parent, handle);
}
ObDereferenceObject(&child->Header);
return status;
}
// Implements SYS_ALLOC_PROCESS_VM. The shared user VA is reachable by the builder but owned by the child VAD.
static uint64_t SyscallAllocProcessVm(const SYS_ALLOC_PROCESS_VM_REQUEST* request) {
SYS_ALLOC_PROCESS_VM_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (!req.PageCount || !req.BaseOut || req.Flags != SYS_VM_FLAG_NONE)
return NTLSTATUS_INVALID_PARAMETER;
if (req.PageCount > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY;
status = MmProbeUserWrite(req.BaseOut, sizeof(*req.BaseOut));
if (!KSUCCESS(status)) return status;
uint64_t pageFlags = 0;
status = KiVmProtectToPageFlags(req.Protect, &pageFlags);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PROCESS* child = (PROCESS*)KiHandleToObject(req.ProcessHandle, ObjectTypeProcess,
SYS_PROCESS_RIGHT_BUILD, &handleStatus);
if (!child) return handleStatus;
status = KiBeginProcessBuildOperation(child);
if (!KSUCCESS(status)) {
ObDereferenceObject(&child->Header);
return status;
}
void* base = (void*)0;
status = MmVadCreateCommitted(child, req.PageCount * PAGE_SIZE, PAGE_SIZE,
pageFlags, MM_VAD_ZERO, MmVadGeneric, &base);
KiEndProcessBuildOperation(child);
if (KSUCCESS(status)) {
KSTATUS copyStatus = CopyToUser(req.BaseOut, &base, sizeof(base));
if (!KSUCCESS(copyStatus)) {
(void)MmVadDestroy(child, base);
status = copyStatus;
}
}
ObDereferenceObject(&child->Header);
return status;
}
// Implements SYS_PROTECT_PROCESS_VM.
static uint64_t SyscallProtectProcessVm(SYS_HANDLE processHandle, void* base, size_t pageCount, uint32_t protect) {
if (!base || ((uintptr_t)base & (PAGE_SIZE - 1u)) != 0 ||
!pageCount || pageCount > ((size_t)-1) / PAGE_SIZE)
return NTLSTATUS_INVALID_PARAMETER;
uint64_t pageFlags = 0;
KSTATUS status = KiVmProtectToPageFlags(protect, &pageFlags);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PROCESS* child = (PROCESS*)KiHandleToObject(processHandle, ObjectTypeProcess,
SYS_PROCESS_RIGHT_BUILD, &handleStatus);
if (!child) return handleStatus;
status = KiBeginProcessBuildOperation(child);
if (KSUCCESS(status)) {
status = MmVadProtect(child, base, pageCount * PAGE_SIZE, pageFlags);
KiEndProcessBuildOperation(child);
}
ObDereferenceObject(&child->Header);
return status;
}
// Implements SYS_START_PROCESS. Start is a one-way transition from Constructing to Started.
static uint64_t SyscallStartProcess(const SYS_START_PROCESS_REQUEST* request) {
SYS_START_PROCESS_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_START_PROCESS_FLAG_NONE ||
!req.MainImageBase || !req.MainImageSize || !req.EntryPoint ||
req.StartupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX ||
(req.StartupCapabilityCount && !req.StartupCapabilities))
return NTLSTATUS_INVALID_PARAMETER;
SYS_STARTUP_CAPABILITY capabilities[SYS_STARTUP_CAPABILITY_MAX];
memset(capabilities, 0, sizeof(capabilities));
if (req.StartupCapabilityCount) {
status = CopyFromUser(capabilities, req.StartupCapabilities,
(size_t)req.StartupCapabilityCount * sizeof(capabilities[0]));
if (!KSUCCESS(status)) return status;
}
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PROCESS* child = (PROCESS*)KiHandleToObject(req.ProcessHandle, ObjectTypeProcess,
SYS_PROCESS_RIGHT_BUILD, &handleStatus);
if (!child) return handleStatus;
int buildActive = 0;
status = KiBeginProcessBuildOperation(child);
if (!KSUCCESS(status)) {
int terminating = KeIsProcessTerminating(child);
ObDereferenceObject(&child->Header);
return terminating ? NTLSTATUS_OBJECT_DEAD : status;
}
buildActive = 1;
THREAD* thread = (THREAD*)0;
if (_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_STARTING,
KE_PROCESS_BUILD_CONSTRUCTING) != KE_PROCESS_BUILD_CONSTRUCTING) {
status = KeIsProcessTerminating(child) ? NTLSTATUS_OBJECT_DEAD : NTLSTATUS_ACCESS_DENIED;
goto done;
}
if (_InterlockedCompareExchange(&child->BuildOperations, 0, 0) != 1) {
(void)_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_CONSTRUCTING,
KE_PROCESS_BUILD_STARTING);
status = NTLSTATUS_WOULD_BLOCK;
goto done;
}
for (uint32_t i = 0; i < req.StartupCapabilityCount && KSUCCESS(status); ++i) {
if (!capabilities[i].Tag || capabilities[i].Handle == SYS_HANDLE_INVALID) {
status = NTLSTATUS_INVALID_PARAMETER;
break;
}
for (uint32_t j = 0; j < i; ++j) {
if (capabilities[j].Tag == capabilities[i].Tag) {
status = NTLSTATUS_OBJECT_NAME_COLLISION;
break;
}
}
if (!KSUCCESS(status)) break;
OBJECT* object = (OBJECT*)0;
status = KeReferenceHandle(child, capabilities[i].Handle, 0, 0, &object);
if (KSUCCESS(status)) ObDereferenceObject(object);
}
void* regionBase = (void*)0;
size_t regionSize = 0;
uint64_t regionFlags = 0;
MM_VAD_TYPE regionType = MmVadGeneric;
uint32_t vadFlags = 0;
if (KSUCCESS(status)) {
size_t imageVadSize = MmVadGetSize(child, (const void*)req.MainImageBase);
if (!imageVadSize || req.MainImageSize > imageVadSize ||
req.MainImageBase > UINTPTR_MAX - req.MainImageSize ||
req.EntryPoint < req.MainImageBase ||
req.EntryPoint >= req.MainImageBase + req.MainImageSize)
status = NTLSTATUS_INVALID_PARAMETER;
}
if (KSUCCESS(status)) {
status = MmVadQuery(child, (const void*)req.EntryPoint,
&regionBase, &regionSize, &regionFlags, &regionType, &vadFlags);
if (KSUCCESS(status) && (!(regionFlags & PAGE_PRESENT) || (regionFlags & PAGE_NX)))
status = NTLSTATUS_ACCESS_DENIED;
}
if (KSUCCESS(status) && req.LoaderData) {
status = MmVadQuery(child, req.LoaderData,
&regionBase, &regionSize, &regionFlags, &regionType, &vadFlags);
if (KSUCCESS(status) && (!(regionFlags & PAGE_PRESENT) || !(regionFlags & PAGE_USER)))
status = NTLSTATUS_INVALID_PARAMETER;
}
if (KSUCCESS(status))
status = KeSetProcessStartupCapabilities(child, capabilities, req.StartupCapabilityCount);
if (KSUCCESS(status))
status = KeSetProcessLoaderData(child, req.LoaderData);
if (KSUCCESS(status) && !KeIsProcessTerminating(child)) {
child->ImageBase = req.MainImageBase;
child->ImageSize = req.MainImageSize;
KE_THREAD_CREATE_PARAMETERS parameters =
KE_THREAD_CREATE_NAMED_NORMAL(PriorityNormal, child->Name);
parameters.Flags = KE_THREAD_CREATE_FLAG_SUSPENDED;
status = KeCreateThread(child, req.EntryPoint, 0, &parameters, &thread);
if (KSUCCESS(status)) {
if (_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_STARTED,
KE_PROCESS_BUILD_STARTING) != KE_PROCESS_BUILD_STARTING) {
(void)KeCancelSuspendedThread(thread);
status = NTLSTATUS_OBJECT_DEAD;
}
}
} else if (KSUCCESS(status)) {
status = NTLSTATUS_OBJECT_DEAD;
}
if (!KSUCCESS(status)) {
child->ImageBase = 0;
child->ImageSize = 0;
(void)KeSetProcessStartupCapabilities(child, (const SYS_STARTUP_CAPABILITY*)0, 0);
(void)KeSetProcessLoaderData(child, (const void*)0);
(void)_InterlockedCompareExchange(&child->BuildState, KE_PROCESS_BUILD_CONSTRUCTING,
KE_PROCESS_BUILD_STARTING);
goto done;
}
KiEndProcessBuildOperation(child);
buildActive = 0;
if (KeIsProcessTerminating(child)) {
status = NTLSTATUS_OBJECT_DEAD;
} else {
status = KeResumeThread(thread, (uint32_t*)0);
if (!KSUCCESS(status) && !KeIsProcessTerminating(child))
(void)KeCancelSuspendedThread(thread);
}
ObDereferenceObject(&thread->Header);
ObDereferenceObject(&child->Header);
return status;
done:
if (thread) ObDereferenceObject(&thread->Header);
if (buildActive) KiEndProcessBuildOperation(child);
ObDereferenceObject(&child->Header);
return status;
}
// Implements SYS_OPEN_PROCESS.
static uint64_t SyscallOpenProcess(uint32_t pid, SYS_RIGHTS desiredAccess, SYS_HANDLE* handleOut) {
if (!handleOut || desiredAccess == 0) return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS validAccess = KeGetValidHandleRights(ObjectTypeProcess);
if ((desiredAccess & ~validAccess) != 0) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* caller = KeGetCurrentProcess();
if (!caller) return NTLSTATUS_FAILURE;
if (pid != caller->ProcessId) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_OPEN_ANY);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
}
KSTATUS s = MmProbeUserWrite(handleOut, sizeof(SYS_HANDLE));
if (!KSUCCESS(s)) return s;
PROCESS* proc = KeGetProcessById(pid);
if (!proc) return NTLSTATUS_NOT_FOUND;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
s = KeCreateHandle(caller,
&proc->Header,
desiredAccess,
&handle);
ObDereferenceObject(&proc->Header);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(handleOut, handle);
if (!KSUCCESS(s)) {
KeCloseHandle(caller, handle);
return s;
}
return NTLSTATUS_SUCCESS;
}
// Copies the process exit code.
static uint64_t SyscallGetProcessExitCode(SYS_HANDLE handle, int32_t* exitCodeOut) {
if (!exitCodeOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PROCESS* proc = (PROCESS*)KiHandleToObject(handle, ObjectTypeProcess, SYS_RIGHT_QUERY, &handleStatus);
if (!proc) return handleStatus;
int32_t exitCode = 0;
KSTATUS s = KeGetProcessExitCode(proc, &exitCode);
ObDereferenceObject(&proc->Header);
if (!KSUCCESS(s)) return s;
return KiCopyInt32ToUser(exitCodeOut, exitCode);
}
// Writes one user text span to the legacy kernel console after authorization.
static KSTATUS KiConsoleWrite(const SYS_WRITE_CONSOLE_REQUEST* request) {
SYS_WRITE_CONSOLE_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Text.Length == 0) return NTLSTATUS_SUCCESS;
if (!req.Text.Data) return NTLSTATUS_INVALID_PARAMETER;
if (req.Text.Length > SYSCALL_TEXT_MAX_BYTES) return NTLSTATUS_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);
status = CopyFromUser(chunk, req.Text.Data + offset, todo);
if (!KSUCCESS(status)) return status;
KePrintConsole(chunk, todo);
offset += todo;
}
return KeDisplayFlushConsoleOutput();
}
// Implements SYS_WRITE_CONSOLE through the legacy ambient console privilege.
static uint64_t SyscallWriteConsole(const SYS_WRITE_CONSOLE_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiConsoleWrite(request);
}
// Implements SYS_READ_DIR.
// Returns NTLSTATUS_NO_MORE_FILES when the directory is exhausted.
static uint64_t SyscallReadDir(SYS_HANDLE handle, SYS_DIR_ENTRY* entryOut) {
if (!entryOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
DIR_OBJECT* dir = (DIR_OBJECT*)KiHandleToObject(handle, ObjectTypeDir,
SYS_DIR_RIGHT_ENUMERATE, &handleStatus);
if (!dir) return handleStatus;
KDIR_ENTRY entry;
KSTATUS s = IoReadDir(dir, &entry);
ObDereferenceObject(&dir->Header);
if (!KSUCCESS(s)) return s;
return CopyToUser(entryOut, &entry, sizeof(entry));
}
// Implements SYS_GET_PROCESS_PID.
// Returns the numeric PID of a process identified by handle.
// Used by posix_spawn to map the kernel handle back to a pid_t for waitpid.
static uint64_t SyscallGetProcessPid(SYS_HANDLE handle, uint32_t* pidOut) {
if (!pidOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PROCESS* proc = (PROCESS*)KiHandleToObject(handle, ObjectTypeProcess, SYS_RIGHT_QUERY, &handleStatus);
if (!proc) return handleStatus;
uint32_t pid = proc->ProcessId;
ObDereferenceObject(&proc->Header);
return KiCopyU32ToUser(pidOut, pid);
}
// Implements SYS_CREATE_PORT.
static uint64_t SyscallCreatePort(const SYS_CREATE_PORT_REQUEST* request) {
SYS_CREATE_PORT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_FLAG_NONE || !req.HandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.HandleOut, sizeof(*req.HandleOut));
if (!KSUCCESS(status)) return status;
PORT* port = (PORT*)0;
status = KePortCreate(req.Backlog, &port);
if (!KSUCCESS(status)) return status;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = KiAllocHandleForCurrentWithRights((OBJECT*)port,
SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PORT_RIGHT_CONNECT | SYS_PORT_RIGHT_ACCEPT,
&handle);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.HandleOut, handle);
if (!KSUCCESS(status)) {
PROCESS* process = KeGetCurrentProcess();
if (process) (void)KeCloseHandle(process, handle);
}
return status;
}
// Implements SYS_CONNECT_PORT.
static uint64_t SyscallConnectPort(const SYS_CONNECT_PORT_REQUEST* request) {
SYS_CONNECT_PORT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_CONNECT_FLAG_NONE || !req.EndpointHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.EndpointHandleOut, sizeof(*req.EndpointHandleOut));
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* listener = (PORT*)KiHandleToObject(req.ListenerHandle,
ObjectTypePort,
SYS_PORT_RIGHT_CONNECT,
&handleStatus);
if (!listener) return handleStatus;
PROCESS* process = KeGetCurrentProcess();
THREAD* thread = KeGetCurrentThread();
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
status = process && thread
? KePortConnect(listener, process, thread, &endpoint)
: NTLSTATUS_FAILURE;
ObDereferenceObject((OBJECT*)listener);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.EndpointHandleOut, endpoint);
if (!KSUCCESS(status) && process)
(void)KeCloseHandle(process, endpoint);
return status;
}
// Implements SYS_ACCEPT_PORT.
static uint64_t SyscallAcceptPort(const SYS_ACCEPT_PORT_REQUEST* request) {
SYS_ACCEPT_PORT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_ACCEPT_FLAG_NONE || !req.EndpointHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.EndpointHandleOut, sizeof(*req.EndpointHandleOut));
if (!KSUCCESS(status)) return status;
if (req.ConnectionInfoOut) {
status = KiProbeSizedUserOutput(req.ConnectionInfoOut, sizeof(*req.ConnectionInfoOut));
if (!KSUCCESS(status)) return status;
}
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* listener = (PORT*)KiHandleToObject(req.ListenerHandle,
ObjectTypePort,
SYS_PORT_RIGHT_ACCEPT,
&handleStatus);
if (!listener) return handleStatus;
PROCESS* process = KeGetCurrentProcess();
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
SYS_PORT_CONNECTION_INFO info;
memset(&info, 0, sizeof(info));
status = process
? KePortAccept(listener, process, req.TimeoutMilliseconds, &endpoint, &info)
: NTLSTATUS_FAILURE;
ObDereferenceObject((OBJECT*)listener);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.EndpointHandleOut, endpoint);
if (KSUCCESS(status) && req.ConnectionInfoOut)
status = CopyToUser(req.ConnectionInfoOut, &info, sizeof(info));
if (!KSUCCESS(status) && process)
(void)KeCloseHandle(process, endpoint);
return status;
}
// Implements SYS_SEND_PORT.
static uint64_t SyscallSendPort(const SYS_SEND_PORT_REQUEST* request) {
SYS_SEND_PORT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_FLAG_NONE || req.PayloadLength > SYS_PORT_MAX_INLINE_BYTES ||
req.HandleCount > SYS_PORT_MAX_TRANSFER_HANDLES ||
(req.PayloadLength && !req.Payload))
return NTLSTATUS_INVALID_PARAMETER;
SYS_PORT_HANDLE_TRANSFER transfers[SYS_PORT_MAX_TRANSFER_HANDLES];
status = KiImportPortTransfers(req.Handles, req.HandleCount, transfers);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle,
ObjectTypePort,
SYS_PORT_RIGHT_SEND,
&handleStatus);
if (!endpoint) return handleStatus;
PROCESS* process = KeGetCurrentProcess();
THREAD* thread = KeGetCurrentThread();
status = process && thread
? KePortSend(endpoint, process, thread,
req.Payload, req.PayloadLength,
transfers, req.HandleCount,
req.TimeoutMilliseconds)
: NTLSTATUS_FAILURE;
ObDereferenceObject((OBJECT*)endpoint);
return status;
}
// Implements SYS_RECEIVE_PORT.
static uint64_t SyscallReceivePort(const SYS_RECEIVE_PORT_REQUEST* request) {
SYS_RECEIVE_PORT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_FLAG_NONE || !req.InfoOut ||
req.PayloadCapacity > SYS_PORT_MAX_INLINE_BYTES ||
req.HandleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES ||
(req.PayloadCapacity && !req.Payload) ||
(req.HandleCapacity && !req.HandlesOut))
return NTLSTATUS_INVALID_PARAMETER;
status = KiProbeSizedUserOutput(req.InfoOut, sizeof(*req.InfoOut));
if (!KSUCCESS(status)) return status;
if (req.HandleCapacity) {
status = MmProbeUserWrite(req.HandlesOut, sizeof(SYS_HANDLE) * req.HandleCapacity);
if (!KSUCCESS(status)) return status;
}
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle,
ObjectTypePort,
SYS_PORT_RIGHT_RECEIVE,
&handleStatus);
if (!endpoint) return handleStatus;
PROCESS* process = KeGetCurrentProcess();
SYS_PORT_MESSAGE_INFO info;
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0};
memset(&info, 0, sizeof(info));
status = process
? KePortReceive(endpoint, process,
req.Payload, req.PayloadCapacity,
handles, req.HandleCapacity,
&info, req.TimeoutMilliseconds)
: NTLSTATUS_FAILURE;
ObDereferenceObject((OBJECT*)endpoint);
if (status == NTLSTATUS_BUFFER_TOO_SMALL) {
KSTATUS copyStatus = CopyToUser(req.InfoOut, &info, sizeof(info));
return KSUCCESS(copyStatus) ? status : copyStatus;
}
if (!KSUCCESS(status)) return status;
status = CopyToUser(req.InfoOut, &info, sizeof(info));
if (KSUCCESS(status) && info.HandleCount)
status = CopyToUser(req.HandlesOut, handles, sizeof(SYS_HANDLE) * info.HandleCount);
if (!KSUCCESS(status) && process)
for (uint32_t i = 0; i < info.HandleCount; ++i)
(void)KeCloseHandle(process, handles[i]);
return status;
}
// Implements SYS_CALL_PORT.
static uint64_t SyscallCallPort(const SYS_CALL_PORT_REQUEST* request) {
SYS_CALL_PORT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_FLAG_NONE ||
req.RequestLength > SYS_PORT_MAX_INLINE_BYTES ||
req.ReplyCapacity > SYS_PORT_MAX_INLINE_BYTES ||
req.RequestHandleCount > SYS_PORT_MAX_TRANSFER_HANDLES ||
req.ReplyHandleCapacity > SYS_PORT_MAX_TRANSFER_HANDLES ||
(req.RequestLength && !req.RequestPayload) ||
(req.ReplyCapacity && !req.ReplyPayload) ||
(req.ReplyHandleCapacity && !req.ReplyHandlesOut))
return NTLSTATUS_INVALID_PARAMETER;
if (req.ReplyInfoOut) {
status = KiProbeSizedUserOutput(req.ReplyInfoOut, sizeof(*req.ReplyInfoOut));
if (!KSUCCESS(status)) return status;
}
if (req.ReplyHandleCapacity) {
status = MmProbeUserWrite(req.ReplyHandlesOut, sizeof(SYS_HANDLE) * req.ReplyHandleCapacity);
if (!KSUCCESS(status)) return status;
}
SYS_PORT_HANDLE_TRANSFER transfers[SYS_PORT_MAX_TRANSFER_HANDLES];
status = KiImportPortTransfers(req.RequestHandles, req.RequestHandleCount, transfers);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle,
ObjectTypePort,
SYS_PORT_RIGHT_CALL,
&handleStatus);
if (!endpoint) return handleStatus;
PROCESS* process = KeGetCurrentProcess();
THREAD* thread = KeGetCurrentThread();
SYS_PORT_MESSAGE_INFO info;
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES] = {0};
memset(&info, 0, sizeof(info));
status = process && thread
? KePortCall(endpoint, process, thread,
req.RequestPayload, req.RequestLength,
transfers, req.RequestHandleCount,
req.ReplyPayload, req.ReplyCapacity,
handles, req.ReplyHandleCapacity,
&info, req.TimeoutMilliseconds)
: NTLSTATUS_FAILURE;
ObDereferenceObject((OBJECT*)endpoint);
if (!KSUCCESS(status)) return status;
if (req.ReplyInfoOut) {
status = CopyToUser(req.ReplyInfoOut, &info, sizeof(info));
if (!KSUCCESS(status)) goto cleanup_call_handles;
}
if (info.HandleCount) {
status = CopyToUser(req.ReplyHandlesOut, handles, sizeof(SYS_HANDLE) * info.HandleCount);
if (!KSUCCESS(status)) goto cleanup_call_handles;
}
return NTLSTATUS_SUCCESS;
cleanup_call_handles:
if (process)
for (uint32_t i = 0; i < info.HandleCount; ++i)
(void)KeCloseHandle(process, handles[i]);
return status;
}
// Implements SYS_REPLY_PORT.
static uint64_t SyscallReplyPort(const SYS_REPLY_PORT_REQUEST* request) {
SYS_REPLY_PORT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_FLAG_NONE || req.MessageId == 0 ||
req.PayloadLength > SYS_PORT_MAX_INLINE_BYTES ||
req.HandleCount > SYS_PORT_MAX_TRANSFER_HANDLES ||
(req.PayloadLength && !req.Payload))
return NTLSTATUS_INVALID_PARAMETER;
SYS_PORT_HANDLE_TRANSFER transfers[SYS_PORT_MAX_TRANSFER_HANDLES];
status = KiImportPortTransfers(req.Handles, req.HandleCount, transfers);
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* endpoint = (PORT*)KiHandleToObject(req.EndpointHandle,
ObjectTypePort,
SYS_PORT_RIGHT_REPLY,
&handleStatus);
if (!endpoint) return handleStatus;
PROCESS* process = KeGetCurrentProcess();
THREAD* thread = KeGetCurrentThread();
status = process && thread
? KePortReply(endpoint, process, thread,
req.MessageId,
req.Payload, req.PayloadLength,
transfers, req.HandleCount)
: NTLSTATUS_FAILURE;
ObDereferenceObject((OBJECT*)endpoint);
return status;
}
// Implements SYS_DISCONNECT_PORT.
static uint64_t SyscallDisconnectPort(SYS_HANDLE endpointHandle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* endpoint = (PORT*)KiHandleToObject(endpointHandle,
ObjectTypePort,
SYS_PORT_RIGHT_DISCONNECT,
&handleStatus);
if (!endpoint) return handleStatus;
KSTATUS status = KePortDisconnect(endpoint);
ObDereferenceObject((OBJECT*)endpoint);
return status;
}
// Implements SYS_REGISTER_SYSTEM_SERVICE.
static uint64_t SyscallRegisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle) {
if (serviceId == SYS_SERVICE_INVALID || serviceId > SYS_SERVICE_MAX)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KiRequirePrivilege(SYS_PRIV_SYSTEM_SERVICE_REGISTER);
if (!KSUCCESS(status)) return status;
PROCESS* process = KeGetCurrentProcess();
SYS_SERVICE_CLAIMS claim = KeSystemServiceRequiredClaim(serviceId);
if (!process || !process->SecurityContext || claim == 0 ||
!SeContextHasServiceClaims(process->SecurityContext, claim))
return NTLSTATUS_ACCESS_DENIED;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* listener = (PORT*)KiHandleToObject(listenerHandle,
ObjectTypePort,
SYS_PORT_RIGHT_ACCEPT,
&handleStatus);
if (!listener) return handleStatus;
status = KeSystemServiceRegister(serviceId, listener);
ObDereferenceObject((OBJECT*)listener);
return status;
}
// Implements SYS_UNREGISTER_SYSTEM_SERVICE.
static uint64_t SyscallUnregisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle) {
if (serviceId == SYS_SERVICE_INVALID || serviceId > SYS_SERVICE_MAX)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KiRequirePrivilege(SYS_PRIV_SYSTEM_SERVICE_REGISTER);
if (!KSUCCESS(status)) return status;
PROCESS* process = KeGetCurrentProcess();
SYS_SERVICE_CLAIMS claim = KeSystemServiceRequiredClaim(serviceId);
if (!process || !process->SecurityContext || claim == 0 ||
!SeContextHasServiceClaims(process->SecurityContext, claim))
return NTLSTATUS_ACCESS_DENIED;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PORT* listener = (PORT*)KiHandleToObject(listenerHandle,
ObjectTypePort,
SYS_PORT_RIGHT_ACCEPT,
&handleStatus);
if (!listener) return handleStatus;
status = KeSystemServiceUnregister(serviceId, listener);
ObDereferenceObject((OBJECT*)listener);
return status;
}
// Implements SYS_CONNECT_SYSTEM_SERVICE.
static uint64_t SyscallConnectSystemService(const SYS_CONNECT_SYSTEM_SERVICE_REQUEST* request) {
SYS_CONNECT_SYSTEM_SERVICE_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_PORT_CONNECT_FLAG_NONE || !req.EndpointHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.EndpointHandleOut, sizeof(*req.EndpointHandleOut));
if (!KSUCCESS(status)) return status;
PORT* listener = (PORT*)0;
status = KeSystemServiceReference(req.ServiceId, &listener);
if (!KSUCCESS(status)) return status;
PROCESS* process = KeGetCurrentProcess();
THREAD* thread = KeGetCurrentThread();
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
status = process && thread
? KePortConnect(listener, process, thread, &endpoint)
: NTLSTATUS_FAILURE;
ObDereferenceObject((OBJECT*)listener);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.EndpointHandleOut, endpoint);
if (!KSUCCESS(status) && process)
(void)KeCloseHandle(process, endpoint);
return status;
}
// Implements SYS_QUERY_VOLUMES. The manager capability is the authority to observe mounted-volume lifecycle.
static uint64_t SyscallQueryVolumes(const SYS_QUERY_VOLUMES_REQUEST* request) {
SYS_QUERY_VOLUMES_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_VOLUME_QUERY_FLAG_NONE || !req.CountOut || !req.GenerationOut ||
(req.Capacity != 0 && !req.Volumes))
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.CountOut, sizeof(*req.CountOut));
if (!KSUCCESS(status)) return status;
status = MmProbeUserWrite(req.GenerationOut, sizeof(*req.GenerationOut));
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
IO_VOLUME_MANAGER* manager = (IO_VOLUME_MANAGER*)KiHandleToObject(
req.ManagerHandle, ObjectTypeVolumeManager, SYS_VOLUME_MANAGER_RIGHT_ENUMERATE, &handleStatus);
if (!manager) return handleStatus;
ObDereferenceObject(&manager->Header);
IO_VOLUME_SNAPSHOT_ENTRY* snapshot = (IO_VOLUME_SNAPSHOT_ENTRY*)0;
uint32_t count = 0;
uint64_t generation = 0;
status = IoSnapshotVolumes(&snapshot, &count, &generation);
if (!KSUCCESS(status)) return status;
KSTATUS countStatus = CopyToUser(req.CountOut, &count, sizeof(count));
KSTATUS generationStatus = CopyToUser(req.GenerationOut, &generation, sizeof(generation));
if (!KSUCCESS(countStatus) || !KSUCCESS(generationStatus)) {
if (snapshot) MmFreePool(snapshot);
return !KSUCCESS(countStatus) ? countStatus : generationStatus;
}
if (req.Capacity < count) {
if (snapshot) MmFreePool(snapshot);
return NTLSTATUS_BUFFER_TOO_SMALL;
}
if (count) {
status = MmProbeUserWrite(req.Volumes, (size_t)count * sizeof(*req.Volumes));
if (!KSUCCESS(status)) {
MmFreePool(snapshot);
return status;
}
}
for (uint32_t i = 0; i < count; ++i) {
SYS_VOLUME_INFO info;
memset(&info, 0, sizeof(info));
info.Size = sizeof(info);
info.Version = SYS_VOLUME_ENUM_VERSION;
info.InstanceId = snapshot[i].InstanceId;
info.Flags = 0;
status = CopyToUser(&req.Volumes[i], &info, sizeof(info));
if (!KSUCCESS(status)) {
MmFreePool(snapshot);
return status;
}
}
if (snapshot) MmFreePool(snapshot);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_OPEN_VOLUME. The manager can mint only traversal/data authority, never capability-management authority.
static uint64_t SyscallOpenVolume(SYS_HANDLE managerHandle,
uint32_t instanceId,
SYS_RIGHTS desiredRights,
SYS_HANDLE* volumeHandleOut) {
const SYS_RIGHTS exportable = SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER |
SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY;
if (!instanceId || !desiredRights || (desiredRights & ~exportable) != 0 || !volumeHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = MmProbeUserWrite(volumeHandleOut, sizeof(*volumeHandleOut));
if (!KSUCCESS(status)) return status;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
IO_VOLUME_MANAGER* manager = (IO_VOLUME_MANAGER*)KiHandleToObject(
managerHandle, ObjectTypeVolumeManager, SYS_VOLUME_MANAGER_RIGHT_OPEN, &handleStatus);
if (!manager) return handleStatus;
ObDereferenceObject(&manager->Header);
IO_VOLUME* volume = (IO_VOLUME*)0;
status = IoReferenceVolumeByInstanceId(instanceId, &volume);
if (!KSUCCESS(status)) return status;
PROCESS* process = KeGetCurrentProcess();
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = process ? KeCreateHandle(process, &volume->Header, desiredRights, &handle)
: NTLSTATUS_FAILURE;
IoReleaseVolume(volume);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(volumeHandleOut, handle);
if (!KSUCCESS(status) && process)
(void)KeCloseHandle(process, handle);
return status;
}
// Implements SYS_WAIT_VOLUME_CHANGE. Generation comparison closes the query-to-wait race.
static uint64_t SyscallWaitVolumeChange(SYS_HANDLE managerHandle,
uint64_t observedGeneration,
uint64_t timeoutMilliseconds) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
IO_VOLUME_MANAGER* manager = (IO_VOLUME_MANAGER*)KiHandleToObject(
managerHandle, ObjectTypeVolumeManager, SYS_VOLUME_MANAGER_RIGHT_WAIT, &handleStatus);
if (!manager) return handleStatus;
ObDereferenceObject(&manager->Header);
return IoWaitForVolumeChange(observedGeneration, timeoutMilliseconds);
}
// Named Queue Registry
// 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
SYS_HANDLE PublisherHandle; // Publisher-owned handle that keeps the name live
} 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);
}
static int KiNamedQueueOwnedByHandle(const NAMED_QUEUE_ENTRY* entry,
const PROCESS* process,
SYS_HANDLE handle,
const OBJECT* object) {
if (!entry || !entry->Queue || !process || !object)
return 0;
return entry->PublisherPid == process->ProcessId &&
entry->PublisherHandle == handle &&
&entry->Queue->Header == object;
}
// Clears a named queue slot and returns the registry reference for later release.
static QUEUE* KiNamedQueueClearSlotLocked(NAMED_QUEUE_ENTRY* entry) {
if (!entry || !entry->Queue)
return (QUEUE*)0;
QUEUE* queue = entry->Queue;
memset(entry, 0, sizeof(*entry));
return queue;
}
// Removes one published queue name owned by the current process.
static KSTATUS KiNamedQueueUnpublishByName(SYS_STRING_VIEW nameView) {
PROCESS* process = KeGetCurrentProcess();
if (!process)
return NTLSTATUS_FAILURE;
QUEUE* queue = (QUEUE*)0;
uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock);
for (int i = 0; i < NAMED_QUEUE_MAX; i++) {
if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) {
if (gNamedQueues[i].PublisherPid != process->ProcessId) {
KeReleaseSpinlock(&gNamedQueueLock, flags);
return NTLSTATUS_ACCESS_DENIED;
}
queue = KiNamedQueueClearSlotLocked(&gNamedQueues[i]);
break;
}
}
KeReleaseSpinlock(&gNamedQueueLock, flags);
if (!queue)
return NTLSTATUS_NOT_FOUND;
ObDereferenceObject(&queue->Header);
return NTLSTATUS_SUCCESS;
}
// Removes published names whose owner handle is being closed.
void KeNamedQueueUnpublishHandle(PROCESS* process, SYS_HANDLE handle, OBJECT* object) {
if (!process || !object || object->Type != ObjectTypeQueue)
return;
while (true) {
QUEUE* queue = (QUEUE*)0;
uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock);
for (int i = 0; i < NAMED_QUEUE_MAX; i++) {
if (KiNamedQueueOwnedByHandle(&gNamedQueues[i], process, handle, object)) {
queue = KiNamedQueueClearSlotLocked(&gNamedQueues[i]);
break;
}
}
KeReleaseSpinlock(&gNamedQueueLock, flags);
if (!queue)
break;
ObDereferenceObject(&queue->Header);
}
}
// Implements SYS_PUBLISH_QUEUE.
// Registers the queue identified by QueueHandle under Name.
// The published name is owned by the publishing process and handle. Closing that
// handle or exiting the process withdraws every name published through it.
static uint64_t SyscallPublishQueue(const SYS_PUBLISH_QUEUE_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NAMED_QUEUE_PUBLISH);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_PUBLISH_QUEUE_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &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 NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, SYS_QUEUE_RIGHT_PUBLISH, &handleStatus);
if (!q) return handleStatus;
uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock);
for (int i = 0; i < NAMED_QUEUE_MAX; i++) {
if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) {
KeReleaseSpinlock(&gNamedQueueLock, flags);
ObDereferenceObject(&q->Header);
return NTLSTATUS_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;
gNamedQueues[i].PublisherHandle = req.QueueHandle;
KeReleaseSpinlock(&gNamedQueueLock, flags);
return NTLSTATUS_SUCCESS;
}
}
KeReleaseSpinlock(&gNamedQueueLock, flags);
ObDereferenceObject(&q->Header);
return NTLSTATUS_NO_MEMORY;
}
// Implements SYS_UNPUBLISH_QUEUE.
// Withdraws a published queue name owned by the calling process.
static uint64_t SyscallUnpublishQueue(const SYS_UNPUBLISH_QUEUE_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NAMED_QUEUE_PUBLISH);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_UNPUBLISH_QUEUE_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &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 NTLSTATUS_INVALID_PARAMETER;
return KiNamedQueueUnpublishByName(nameView);
}
// Implements SYS_OPEN_NAMED_QUEUE.
// Looks up 'name' in the registry and allocates a handle to the queue in the
// calling process's handle table. Returns NTLSTATUS_NOT_FOUND if no match.
// Callers that race against the publisher should retry on NOT_FOUND.
static uint64_t SyscallOpenNamedQueue(const SYS_OPEN_NAMED_QUEUE_REQUEST* request) {
SYS_OPEN_NAMED_QUEUE_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.HandleOut) return NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.HandleOut, sizeof(SYS_HANDLE));
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;
if (q && !ObTryReferenceObject(&q->Header))
q = (QUEUE*)0;
break;
}
}
KeReleaseSpinlock(&gNamedQueueLock, flags);
if (!q) return NTLSTATUS_NOT_FOUND;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&q->Header, KeGetValidHandleRights(ObjectTypeQueue), &handle);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(req.HandleOut, handle);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, handle);
return s;
}
return NTLSTATUS_SUCCESS;
}
// Implements SYS_DUPLICATE_HANDLE.
// Duplicates a handle from the caller into the target process table.
static uint64_t SyscallDuplicateHandle(const SYS_DUPLICATE_HANDLE_REQUEST* request) {
SYS_DUPLICATE_HANDLE_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.TargetHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.TargetHandleOut, sizeof(SYS_HANDLE));
if (!KSUCCESS(s)) return s;
PROCESS* caller = KeGetCurrentProcess();
if (!caller) return NTLSTATUS_FAILURE;
KSTATUS targetStatus = NTLSTATUS_SUCCESS;
PROCESS* target = (PROCESS*)KiHandleToObject(req.TargetProcessHandle, ObjectTypeProcess,
SYS_PROCESS_RIGHT_INSERT_HANDLE, &targetStatus);
if (!target) return targetStatus;
SYS_HANDLE targetHandle = SYS_HANDLE_INVALID;
s = KeDuplicateHandle(caller, req.SourceHandle, target, req.DesiredRights, req.Flags, &targetHandle);
if (!KSUCCESS(s)) {
ObDereferenceObject(&target->Header);
return s;
}
s = KiCopyHandleToUser(req.TargetHandleOut, targetHandle);
if (!KSUCCESS(s)) {
KeCloseHandle(target, targetHandle);
ObDereferenceObject(&target->Header);
return s;
}
ObDereferenceObject(&target->Header);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_DERIVE_HANDLE.
static uint64_t SyscallDeriveHandle(const SYS_DERIVE_HANDLE_REQUEST* request) {
SYS_DERIVE_HANDLE_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (!req.DesiredRights || !req.DerivedHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.DerivedHandleOut, sizeof(SYS_HANDLE));
if (!KSUCCESS(status)) return status;
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
SYS_HANDLE derived = SYS_HANDLE_INVALID;
status = KeDeriveHandle(process, req.SourceHandle, req.DesiredRights, &derived);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.DerivedHandleOut, derived);
if (!KSUCCESS(status))
(void)KeCloseHandle(process, derived);
return status;
}
// Implements SYS_REVOKE_HANDLE.
static uint64_t SyscallRevokeHandle(SYS_HANDLE handle) {
PROCESS* process = KeGetCurrentProcess();
if (!process) return NTLSTATUS_FAILURE;
return KeRevokeHandle(process, handle);
}
// Implements SYS_DERIVE_SECURITY_CONTEXT.
static uint64_t SyscallDeriveSecurityContext(const SYS_DERIVE_SECURITY_CONTEXT_REQUEST* request) {
SYS_DERIVE_SECURITY_CONTEXT_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_SECURITY_CONTEXT_FLAG_NONE || !req.ContextHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.ContextHandleOut, sizeof(SYS_HANDLE));
if (!KSUCCESS(status)) return status;
PROCESS* process = KeGetCurrentProcess();
if (!process || !process->SecurityContext) return NTLSTATUS_FAILURE;
SECURITY_CONTEXT* source = (SECURITY_CONTEXT*)0;
if (req.SourceContext == SYS_SECURITY_CONTEXT_CURRENT) {
source = process->SecurityContext;
ObReferenceObject(&source->Header);
} else {
source = (SECURITY_CONTEXT*)KiHandleToObject(req.SourceContext,
ObjectTypeSecurityContext,
SYS_SECURITY_CONTEXT_RIGHT_DERIVE,
&status);
if (!source) return status;
}
SECURITY_CONTEXT* derived = (SECURITY_CONTEXT*)0;
status = SeDeriveSecurityContext(source,
req.EffectivePrivileges,
req.DelegablePrivileges,
req.ServiceClaims,
req.DelegableServiceClaims,
&derived);
ObDereferenceObject(&source->Header);
if (!KSUCCESS(status)) return status;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = KiAllocHandleForCurrentWithRights(&derived->Header,
KeGetValidHandleRights(ObjectTypeSecurityContext),
&handle);
if (!KSUCCESS(status)) return status;
status = KiCopyHandleToUser(req.ContextHandleOut, handle);
if (!KSUCCESS(status)) {
KeCloseHandle(process, handle);
return status;
}
return NTLSTATUS_SUCCESS;
}
// Implements SYS_QUERY_SECURITY_CONTEXT.
static uint64_t SyscallQuerySecurityContext(SYS_HANDLE contextHandle, SYS_SECURITY_CONTEXT_INFO* infoOut) {
if (!infoOut) return NTLSTATUS_INVALID_PARAMETER;
uint32_t userSize = 0;
KSTATUS status = CopyFromUser(&userSize, infoOut, sizeof(userSize));
if (!KSUCCESS(status)) return status;
if (userSize != sizeof(SYS_SECURITY_CONTEXT_INFO)) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* process = KeGetCurrentProcess();
if (!process || !process->SecurityContext) return NTLSTATUS_FAILURE;
SECURITY_CONTEXT* context = (SECURITY_CONTEXT*)0;
if (contextHandle == SYS_SECURITY_CONTEXT_CURRENT) {
context = process->SecurityContext;
ObReferenceObject(&context->Header);
} else {
context = (SECURITY_CONTEXT*)KiHandleToObject(contextHandle,
ObjectTypeSecurityContext,
SYS_RIGHT_QUERY,
&status);
if (!context) return status;
}
SYS_SECURITY_CONTEXT_INFO info;
SeQuerySecurityContext(context, &info);
ObDereferenceObject(&context->Header);
return CopyToUser(infoOut, &info, sizeof(info));
}
// Implements SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS.
// Opens a process handle for the process that published a named queue.
static uint64_t SyscallOpenNamedQueueOwnerProcess(const SYS_NAMED_QUEUE_OWNER_REQUEST* request) {
SYS_NAMED_QUEUE_OWNER_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.ProcessHandleOut || req.DesiredAccess == 0) return NTLSTATUS_INVALID_PARAMETER;
SYS_RIGHTS validAccess = KeGetValidHandleRights(ObjectTypeProcess);
if ((req.DesiredAccess & ~validAccess) != 0) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_OPEN_ANY);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
char name[NAMED_QUEUE_NAME_MAX];
SYS_STRING_VIEW nameView;
s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, false);
if (!KSUCCESS(s)) return s;
if (nameView.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
s = MmProbeUserWrite(req.ProcessHandleOut, sizeof(SYS_HANDLE));
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 NTLSTATUS_NOT_FOUND;
PROCESS* owner = KeGetProcessById(pid);
if (!owner) return NTLSTATUS_NOT_FOUND;
PROCESS* caller = KeGetCurrentProcess();
if (!caller) {
ObDereferenceObject(&owner->Header);
return NTLSTATUS_FAILURE;
}
SYS_HANDLE handle = SYS_HANDLE_INVALID;
s = KeCreateHandle(caller,
&owner->Header,
req.DesiredAccess,
&handle);
ObDereferenceObject(&owner->Header);
if (!KSUCCESS(s)) return s;
s = KiCopyHandleToUser(req.ProcessHandleOut, handle);
if (!KSUCCESS(s)) {
KeCloseHandle(caller, handle);
return s;
}
return NTLSTATUS_SUCCESS;
}
// Terminates the process referenced by one process handle.
static uint64_t SyscallKillProcess(SYS_HANDLE processHandle, int32_t exitCode) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
PROCESS* proc = (PROCESS*)KiHandleToObject(processHandle, ObjectTypeProcess,
SYS_PROCESS_RIGHT_TERMINATE, &handleStatus);
if (!proc) return handleStatus;
THREAD* self = KeGetCurrentThread();
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;
}
// Implements SYS_GET_PROCESS_LIST.
static uint64_t SyscallGetProcessList(SYS_PROCESS_INFO* processesOut, size_t maxCount, size_t* countOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_ENUMERATE);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (maxCount && !processesOut) return NTLSTATUS_INVALID_PARAMETER;
if (countOut) {
KSTATUS s = MmProbeUserWrite(countOut, 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 NTLSTATUS_INVALID_PARAMETER;
SYS_PROCESS_INFO* tmp = (SYS_PROCESS_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 'y', 's', ' '));
if (!tmp) return NTLSTATUS_NO_MEMORY;
size_t copied = KeCopyProcessInfo(tmp, toCopy);
if (copied < toCopy) toCopy = copied;
KSTATUS s = CopyToUser(processesOut, tmp, toCopy * sizeof(SYS_PROCESS_INFO));
MmFreePool(tmp);
if (!KSUCCESS(s)) return s;
}
if (countOut) return KiCopySizeToUser(countOut, total);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_GET_THREAD_LIST.
static uint64_t SyscallGetThreadList(uint32_t pid, SYS_THREAD_INFO* threadsOut, size_t maxCount, size_t* countOut) {
PROCESS* caller = KeGetCurrentProcess();
if (!caller) return NTLSTATUS_FAILURE;
if (pid != caller->ProcessId) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_THREAD_ENUMERATE_ANY);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
}
if (maxCount && !threadsOut) return NTLSTATUS_INVALID_PARAMETER;
if (countOut) {
KSTATUS s = MmProbeUserWrite(countOut, 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 NTLSTATUS_INVALID_PARAMETER;
SYS_THREAD_INFO* tmp = (SYS_THREAD_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 't', 'h', 'r'));
if (!tmp) return NTLSTATUS_NO_MEMORY;
size_t copied = 0;
s = KeCopyProcessThreadInfo(pid, tmp, toCopy, &copied);
if (KSUCCESS(s)) {
if (copied < toCopy) toCopy = copied;
s = CopyToUser(threadsOut, tmp, toCopy * sizeof(SYS_THREAD_INFO));
}
MmFreePool(tmp);
if (!KSUCCESS(s)) return s;
}
if (countOut) return KiCopySizeToUser(countOut, total);
return NTLSTATUS_SUCCESS;
}
// Implements SYS_LOCK_FILE.
static uint64_t SyscallLockFile(const SYS_LOCK_FILE_REQUEST* request) {
SYS_LOCK_FILE_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
if (req.Mode != SYS_FILE_LOCK_SHARED && req.Mode != SYS_FILE_LOCK_EXCLUSIVE) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_FAILURE;
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
SYS_FILE_RIGHT_LOCK, &fileHandleStatus);
if (!file) return fileHandleStatus;
s = IoLockFile(file, proc, req.Mode, req.Offset, req.Length, req.TimeoutMilliseconds);
ObDereferenceObject(&file->Header);
return s;
}
// Implements SYS_UNLOCK_FILE.
static uint64_t SyscallUnlockFile(const SYS_UNLOCK_FILE_REQUEST* request) {
SYS_UNLOCK_FILE_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_FAILURE;
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
SYS_FILE_RIGHT_LOCK, &fileHandleStatus);
if (!file) return fileHandleStatus;
s = IoUnlockFile(file, proc, req.Offset, req.Length);
ObDereferenceObject(&file->Header);
return s;
}
// Implements SYS_QUERY_FILE_LOCK.
static uint64_t SyscallQueryFileLock(const SYS_QUERY_FILE_LOCK_REQUEST* request) {
SYS_QUERY_FILE_LOCK_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE || !req.InfoOut)
return NTLSTATUS_INVALID_PARAMETER;
if (req.Mode != SYS_FILE_LOCK_SHARED && req.Mode != SYS_FILE_LOCK_EXCLUSIVE)
return NTLSTATUS_INVALID_PARAMETER;
if (req.Length == 0)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.InfoOut, sizeof(*req.InfoOut));
if (!KSUCCESS(status)) return status;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return NTLSTATUS_FAILURE;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
SYS_FILE_RIGHT_LOCK, &handleStatus);
if (!file) return handleStatus;
SYS_FILE_LOCK_INFO info;
status = IoQueryFileLock(file, proc, req.Mode, req.Offset, req.Length, &info);
ObDereferenceObject(&file->Header);
if (!KSUCCESS(status)) return status;
return CopyToUser(req.InfoOut, &info, sizeof(info));
}
// Implements SYS_SET_FILE_STATUS.
static uint64_t SyscallSetFileStatus(const SYS_SET_FILE_STATUS_REQUEST* request) {
SYS_SET_FILE_STATUS_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Mask & ~SYS_FILE_STATUS_APPEND)
return NTLSTATUS_INVALID_PARAMETER;
if (req.Flags & ~req.Mask)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
SYS_RIGHT_QUERY, &handleStatus);
if (!file) return handleStatus;
status = IoSetFileStatus(file, req.Mask, req.Flags);
ObDereferenceObject(&file->Header);
return status;
}
// Implements SYS_CREATE_PIPE. The syscall creates endpoint capabilities and one
// process-owned shared data-plane view; ordinary reads and writes stay in user mode.
static uint64_t SyscallCreatePipe(const SYS_CREATE_PIPE_REQUEST* request) {
SYS_CREATE_PIPE_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags != 0 || !req.ReadHandleOut || !req.WriteHandleOut || !req.SharedStateOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(req.ReadHandleOut, sizeof(*req.ReadHandleOut));
if (!KSUCCESS(status)) return status;
status = MmProbeUserWrite(req.WriteHandleOut, sizeof(*req.WriteHandleOut));
if (!KSUCCESS(status)) return status;
status = MmProbeUserWrite(req.SharedStateOut, sizeof(*req.SharedStateOut));
if (!KSUCCESS(status)) return status;
IO_PIPE_ENDPOINT* readEnd = (IO_PIPE_ENDPOINT*)0;
IO_PIPE_ENDPOINT* writeEnd = (IO_PIPE_ENDPOINT*)0;
status = IoCreatePipe(&readEnd, &writeEnd);
if (!KSUCCESS(status)) return status;
PROCESS* proc = KeGetCurrentProcess();
void* sharedState = (void*)0;
status = IoMapPipe(readEnd, proc, &sharedState);
if (!KSUCCESS(status)) {
ObDereferenceObject((OBJECT*)readEnd);
ObDereferenceObject((OBJECT*)writeEnd);
return status;
}
SYS_HANDLE readHandle = SYS_HANDLE_INVALID;
SYS_HANDLE writeHandle = SYS_HANDLE_INVALID;
SYS_RIGHTS readRights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PIPE_RIGHT_READ | SYS_PIPE_RIGHT_POLL;
SYS_RIGHTS writeRights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
SYS_PIPE_RIGHT_WRITE | SYS_PIPE_RIGHT_POLL;
status = KiAllocHandleForCurrentWithRights((OBJECT*)readEnd, readRights, &readHandle);
if (!KSUCCESS(status)) {
ObDereferenceObject((OBJECT*)writeEnd);
(void)MmVadDestroy(proc, sharedState);
return status;
}
status = KiAllocHandleForCurrentWithRights((OBJECT*)writeEnd, writeRights, &writeHandle);
if (!KSUCCESS(status)) {
if (proc) KeCloseHandle(proc, readHandle);
(void)MmVadDestroy(proc, sharedState);
return status;
}
status = KiCopyHandleToUser(req.ReadHandleOut, readHandle);
if (KSUCCESS(status)) status = KiCopyHandleToUser(req.WriteHandleOut, writeHandle);
if (KSUCCESS(status)) status = CopyToUser(req.SharedStateOut, &sharedState, sizeof(sharedState));
if (!KSUCCESS(status)) {
if (sharedState && proc)
(void)MmVadDestroy(proc, sharedState);
if (proc) {
KeCloseHandle(proc, readHandle);
KeCloseHandle(proc, writeHandle);
}
}
return status;
}
// Implements SYS_NOTIFY_PIPE. User mode calls this only when a shared waiter
// count says a data-plane transition may need scheduler attention.
static uint64_t SyscallNotifyPipe(SYS_HANDLE pipeHandle) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
IO_PIPE_ENDPOINT* endpoint = (IO_PIPE_ENDPOINT*)KiHandleToObject(pipeHandle,
ObjectTypePipeEndpoint,
SYS_RIGHT_WAIT,
&handleStatus);
if (!endpoint) return handleStatus;
KSTATUS status = IoNotifyPipe(endpoint);
ObDereferenceObject((OBJECT*)endpoint);
return status;
}
typedef struct SYS_SOCKET_SEND_TO_REQUEST_K {
uint32_t Size;
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 {
uint32_t Size;
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 {
uint32_t Size;
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 Size;
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;
SYS_HANDLE SocketHandle;
SYS_HANDLE 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;
SYS_HANDLE* HandleOut;
uint32_t* PollReturnedEventsOut;
SYS_IO_STATUS_BLOCK_K* Iosb;
SYS_USER_APC_ROUTINE_K ApcRoutine;
void* ApcContext;
SYS_HANDLE* RequestHandleOut;
} SYS_ASYNC_SOCKET_REQUEST_K;
_Static_assert(sizeof(SYS_SOCKET_SEND_TO_REQUEST_K) == sizeof(SYS_SOCKET_SEND_TO_REQUEST),
"send-to request ABI mismatch");
_Static_assert(sizeof(SYS_SOCKET_RECV_FROM_REQUEST_K) == sizeof(SYS_SOCKET_RECV_FROM_REQUEST),
"recv-from request ABI mismatch");
_Static_assert(sizeof(SYS_RESOLVE_HOST_REQUEST_K) == sizeof(SYS_RESOLVE_HOST_REQUEST),
"resolve-host request ABI mismatch");
_Static_assert(sizeof(SYS_SOCKET_OPTION_REQUEST_K) == sizeof(SYS_SOCKET_OPTION_REQUEST),
"socket option request ABI mismatch");
_Static_assert(sizeof(SYS_ASYNC_SOCKET_REQUEST_K) == sizeof(SYS_ASYNC_SOCKET_REQUEST),
"async socket request ABI mismatch");
typedef struct SYS_MEMORY_STATUS_K {
uint64_t FreePhysicalBytes;
uint64_t KernelPoolBytes;
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
// Implements SYS_CREATE_SOCKET.
static uint64_t SyscallSocket(uint32_t domain, uint32_t type, uint32_t protocol, SYS_HANDLE* handleOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NETWORK_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
SOCKET_OBJECT* socket = NULL;
KSTATUS s = NetSocketCreate(domain, type, protocol, &socket);
if (!KSUCCESS(s)) return s;
SYS_HANDLE h = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&socket->Header, KeGetValidHandleRights(ObjectTypeSocket), &h);
if (!KSUCCESS(s)) return s;
// KiAllocHandleForCurrentWithRights consumed NetSocketCreate's creator
// reference; the returned process handle now owns the socket object.
s = KiCopyHandleToUser(handleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// Validates the user-supplied socket address buffer size before copying.
static KSTATUS SyscallValidateSocketAddressBufferSize(size_t addressSize) {
if (addressSize < sizeof(SYS_SOCKET_ADDRESS_K))
return NTLSTATUS_BUFFER_TOO_SMALL;
if (addressSize != sizeof(SYS_SOCKET_ADDRESS_K))
return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Validates the fixed-size socket address ABI after a bounded copy.
static KSTATUS SyscallValidateSocketAddressInput(const SYS_SOCKET_ADDRESS_K* address) {
if (!address)
return NTLSTATUS_INVALID_PARAMETER;
if (address->Size != sizeof(SYS_SOCKET_ADDRESS_K))
return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
// Implements SYS_BIND_SOCKET.
static uint64_t SyscallBind(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* userAddress, size_t addressSize) {
if (!userAddress) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS s = SyscallValidateSocketAddressBufferSize(addressSize);
if (!KSUCCESS(s)) return s;
SYS_SOCKET_ADDRESS_K address;
s = CopyFromUser(&address, userAddress, sizeof(address));
if (!KSUCCESS(s)) return s;
s = SyscallValidateSocketAddressInput(&address);
if (!KSUCCESS(s)) return s;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_BIND, &handleStatus);
if (!socketObject) return handleStatus;
s = NetSocketBind(socketObject, &address, addressSize);
ObDereferenceObject(&socketObject->Header);
return s;
}
// Implements SYS_LISTEN_SOCKET.
static uint64_t SyscallListen(SYS_HANDLE socket, uint32_t backlog) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_LISTEN, &handleStatus);
if (!socketObject) return handleStatus;
KSTATUS s = NetSocketListen(socketObject, backlog);
ObDereferenceObject(&socketObject->Header);
return s;
}
// Implements SYS_ACCEPT_SOCKET.
// a2=*remoteAddressSizeInOut(optional) a3=*acceptedHandleOut.
static uint64_t SyscallAccept(SYS_HANDLE socket, SYS_SOCKET_ADDRESS* remoteAddressOut, size_t* remoteAddressSizeInOut, SYS_HANDLE* acceptedHandleOut) {
if (!acceptedHandleOut) return NTLSTATUS_INVALID_PARAMETER;
bool wantRemote = (remoteAddressOut != 0);
size_t remoteCapacity = 0;
if (wantRemote) {
if (!remoteAddressSizeInOut) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS cs = CopyFromUser(&remoteCapacity, remoteAddressSizeInOut, sizeof(remoteCapacity));
if (!KSUCCESS(cs)) return cs;
if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
(void)CopyToUser(remoteAddressSizeInOut, &required, sizeof(required));
return NTLSTATUS_BUFFER_TOO_SMALL;
}
cs = MmProbeUserWrite(remoteAddressOut, sizeof(SYS_SOCKET_ADDRESS_K));
if (!KSUCCESS(cs)) return cs;
}
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* listener = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_ACCEPT, &handleStatus);
if (!listener) return handleStatus;
SOCKET_OBJECT* accepted = NULL;
SYS_SOCKET_ADDRESS_K remote;
KSTATUS s = NetSocketAccept(listener, &accepted, wantRemote ? &remote : NULL, (IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&listener->Header);
if (!KSUCCESS(s)) return s;
SYS_HANDLE h = SYS_HANDLE_INVALID;
s = KiAllocHandleForCurrentWithRights(&accepted->Header, KeGetValidHandleRights(ObjectTypeSocket), &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(remoteAddressOut, &remote, sizeof(remote));
if (KSUCCESS(s))
s = CopyToUser(remoteAddressSizeInOut, &actual, sizeof(actual));
}
if (KSUCCESS(s))
s = KiCopyHandleToUser(acceptedHandleOut, h);
if (!KSUCCESS(s)) {
PROCESS* proc = KeGetCurrentProcess();
if (proc) KeCloseHandle(proc, h);
}
return s;
}
// Implements SYS_CONNECT_SOCKET.
static uint64_t SyscallConnect(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* userAddress, size_t addressSize) {
if (!userAddress) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS s = SyscallValidateSocketAddressBufferSize(addressSize);
if (!KSUCCESS(s)) return s;
SYS_SOCKET_ADDRESS_K address;
s = CopyFromUser(&address, userAddress, sizeof(address));
if (!KSUCCESS(s)) return s;
s = SyscallValidateSocketAddressInput(&address);
if (!KSUCCESS(s)) return s;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_CONNECT, &handleStatus);
if (!socketObject) return handleStatus;
s = NetSocketConnect(socketObject, &address, addressSize, (IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&socketObject->Header);
return s;
}
// Implements SYS_SEND_SOCKET.
static uint64_t SyscallSend(const SYS_SOCKET_SEND_REQUEST* request) {
SYS_SOCKET_SEND_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length == 0) {
if (req.SentOut) return KiCopySizeToUser(req.SentOut, 0);
return NTLSTATUS_SUCCESS;
}
void* kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
if (!kbuf) return NTLSTATUS_NO_MEMORY;
s = CopyFromUser(kbuf, req.Buffer, req.Length);
if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; }
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.Socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_SEND, &handleStatus);
if (!socket) { MmFreePool(kbuf); return handleStatus; }
size_t sent = 0;
s = NetSocketSend(socket, kbuf, req.Length, req.Flags, &sent);
ObDereferenceObject(&socket->Header);
MmFreePool(kbuf);
if (req.SentOut) {
KSTATUS copyStatus = KiCopySizeToUser(req.SentOut, sent);
if (KSUCCESS(s)) s = copyStatus;
}
return s;
}
// Receives bytes from a connected socket.
static uint64_t SyscallRecv(const SYS_SOCKET_RECV_REQUEST* request) {
SYS_SOCKET_RECV_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length == 0) {
if (req.ReceivedOut) return KiCopySizeToUser(req.ReceivedOut, 0);
return NTLSTATUS_SUCCESS;
}
s = MmProbeUserWrite(req.Buffer, req.Length);
if (!KSUCCESS(s)) return s;
void* kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
if (!kbuf) return NTLSTATUS_NO_MEMORY;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.Socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_RECEIVE, &handleStatus);
if (!socket) { MmFreePool(kbuf); return handleStatus; }
size_t received = 0;
s = NetSocketReceive(socket, kbuf, req.Length, req.Flags, &received,
(IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&socket->Header);
if (KSUCCESS(s) && received)
s = CopyToUser(req.Buffer, kbuf, received);
MmFreePool(kbuf);
if (KSUCCESS(s) && req.ReceivedOut)
s = KiCopySizeToUser(req.ReceivedOut, received);
return s;
}
// Shuts down one or both directions of a socket.
static uint64_t SyscallShutdown(SYS_HANDLE socket, uint32_t how) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_SHUTDOWN, &handleStatus);
if (!socketObject) return handleStatus;
KSTATUS s = NetSocketShutdown(socketObject, how);
ObDereferenceObject(&socketObject->Header);
return s;
}
// Implements SYS_SEND_TO_SOCKET.
static uint64_t SyscallSendTo(SYS_HANDLE socket, const SYS_SOCKET_SEND_TO_REQUEST* request) {
if (!request) return NTLSTATUS_INVALID_PARAMETER;
SYS_SOCKET_SEND_TO_REQUEST_K req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
if (!req.RemoteAddress) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
s = SyscallValidateSocketAddressBufferSize(req.RemoteAddressSize);
if (!KSUCCESS(s)) return s;
SYS_SOCKET_ADDRESS_K remote;
s = CopyFromUser(&remote, req.RemoteAddress, sizeof(remote));
if (!KSUCCESS(s)) return s;
s = SyscallValidateSocketAddressInput(&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 NTLSTATUS_NO_MEMORY;
s = CopyFromUser(kbuf, req.Buffer, req.Length);
if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; }
}
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_SEND, &handleStatus);
if (!socketObject) { if (kbuf) MmFreePool(kbuf); return handleStatus; }
size_t sent = 0;
s = NetSocketSendTo(socketObject, kbuf, req.Length, req.Flags, &remote, req.RemoteAddressSize, &sent);
ObDereferenceObject(&socketObject->Header);
if (kbuf) MmFreePool(kbuf);
if (req.SentOut) {
KSTATUS cs = CopyToUser(req.SentOut, &sent, sizeof(sent));
if (KSUCCESS(s)) s = cs;
}
return s;
}
// Implements SYS_RECV_FROM_SOCKET.
static uint64_t SyscallRecvFrom(SYS_HANDLE socket, const SYS_SOCKET_RECV_FROM_REQUEST* request) {
if (!request) return NTLSTATUS_INVALID_PARAMETER;
SYS_SOCKET_RECV_FROM_REQUEST_K req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
bool wantRemote = (req.RemoteAddressOut != NULL);
size_t remoteCapacity = 0;
if (wantRemote) {
if (!req.RemoteAddressSizeInOut) return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_NO_MEMORY;
}
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_RECEIVE, &handleStatus);
if (!socketObject) { if (kbuf) MmFreePool(kbuf); return handleStatus; }
SYS_SOCKET_ADDRESS_K remote;
memset(&remote, 0, sizeof(remote));
size_t received = 0;
s = NetSocketReceiveFrom(socketObject, kbuf, req.Length, req.Flags, wantRemote ? &remote : NULL, &received, (IO_ASYNC_REQUEST_OBJECT*)0);
ObDereferenceObject(&socketObject->Header);
if (KSUCCESS(s) && received)
s = CopyToUser(req.Buffer, kbuf, received);
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;
}
// Implements SYS_RESOLVE_HOST.
static uint64_t SyscallResolveHost(const SYS_RESOLVE_HOST_REQUEST* request) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NETWORK_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
if (!request) return NTLSTATUS_INVALID_PARAMETER;
SYS_RESOLVE_HOST_REQUEST_K req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.Name.Data || req.Name.Length == 0 || req.Name.Length > SYS_RESOLVE_HOST_NAME_MAX)
return NTLSTATUS_INVALID_PARAMETER;
if (req.Family != SYS_AF_INET && req.Family != SYS_AF_INET6)
return NTLSTATUS_NOT_SUPPORTED;
if (req.Flags != 0)
return NTLSTATUS_NOT_SUPPORTED;
if (!req.AddressOut || !req.AddressSizeInOut)
return NTLSTATUS_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 NTLSTATUS_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));
}
// Implements SYS_SET_SOCKET_OPTION.
static uint64_t SyscallSetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request) {
if (!request) return NTLSTATUS_INVALID_PARAMETER;
SYS_SOCKET_OPTION_REQUEST_K req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.Value) return NTLSTATUS_INVALID_PARAMETER;
uint8_t value[sizeof(uint64_t)];
if (req.ValueSize > sizeof(value)) return NTLSTATUS_BUFFER_TOO_SMALL;
s = CopyFromUser(value, req.Value, req.ValueSize);
if (!KSUCCESS(s)) return s;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_SOCKET_RIGHT_SET_OPTION, &handleStatus);
if (!socketObject) return handleStatus;
s = NetSocketSetOption(socketObject, req.Option, value, req.ValueSize);
ObDereferenceObject(&socketObject->Header);
return s;
}
// Implements SYS_GET_SOCKET_OPTION.
static uint64_t SyscallGetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request) {
if (!request) return NTLSTATUS_INVALID_PARAMETER;
SYS_SOCKET_OPTION_REQUEST_K req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.Value) return NTLSTATUS_INVALID_PARAMETER;
uint8_t value[sizeof(uint64_t)];
if (req.ValueSize > sizeof(value)) return NTLSTATUS_BUFFER_TOO_SMALL;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
SYS_RIGHT_QUERY, &handleStatus);
if (!socketObject) return handleStatus;
size_t actual = 0;
s = NetSocketGetOption(socketObject, req.Option, value, req.ValueSize, &actual);
ObDereferenceObject(&socketObject->Header);
if (!KSUCCESS(s)) {
if (s == NTLSTATUS_BUFFER_TOO_SMALL && req.ActualSizeOut)
(void)CopyToUser(req.ActualSizeOut, &actual, sizeof(actual));
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);
}
// Implements SYS_POLL.
static uint64_t SyscallPoll(const SYS_POLL_REQUEST* request) {
SYS_POLL_REQUEST req;
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(s)) return s;
if (!req.Fds) return NTLSTATUS_INVALID_PARAMETER;
size_t count = req.Count;
if (count == 0 || count > SYS_POLL_MAX_FDS)
return NTLSTATUS_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 NTLSTATUS_NO_MEMORY;
s = CopyFromUser(fds, req.Fds, bytes);
if (!KSUCCESS(s)) { MmFreePool(fds); return s; }
uint32_t timeoutMs = req.TimeoutMs;
uint64_t start = gHal ? gHal->Timer.QueryTicks() : 0;
size_t ready = 0;
while (true) {
ready = 0;
for (size_t i = 0; i < count; i++) {
fds[i].ReturnedEvents = 0;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
uint32_t revents = 0;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(
fds[i].Handle, ObjectTypeSocket, SYS_SOCKET_RIGHT_POLL, &handleStatus);
if (socket) {
s = NetSocketPoll(socket, fds[i].Events, &revents);
ObDereferenceObject(&socket->Header);
} else {
IO_PIPE_ENDPOINT* pipe = (IO_PIPE_ENDPOINT*)KiHandleToObject(
fds[i].Handle, ObjectTypePipeEndpoint, SYS_PIPE_RIGHT_POLL, &handleStatus);
if (!pipe) {
fds[i].ReturnedEvents = SYS_POLL_INVALID;
ready++;
continue;
}
s = IoPollPipe(pipe, fds[i].Events, &revents);
ObDereferenceObject((OBJECT*)pipe);
}
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(req.Fds, fds, bytes);
if (KSUCCESS(s) && req.ReadyOut)
s = CopyToUser(req.ReadyOut, &ready, sizeof(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 NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = SyscallValidateSocketAddressBufferSize(req->AddressSize);
if (!KSUCCESS(status))
return status;
status = CopyFromUser(&submit->Address, req->Address, sizeof(submit->Address));
if (!KSUCCESS(status))
return status;
status = SyscallValidateSocketAddressInput(&submit->Address);
if (!KSUCCESS(status))
return status;
submit->AddressSize = req->AddressSize;
return NTLSTATUS_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 NTLSTATUS_INVALID_PARAMETER;
if (!req->AddressOut)
return NTLSTATUS_SUCCESS;
if (!req->AddressSizeInOut)
return NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_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 NTLSTATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_ACCEPT: {
if (!req->HandleOut)
return NTLSTATUS_INVALID_PARAMETER;
SYS_HANDLE zero = SYS_HANDLE_INVALID;
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 NTLSTATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return NTLSTATUS_INVALID_PARAMETER;
if (req->Length) {
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 's', 'n'));
if (!submit->KernelBuffer)
return NTLSTATUS_NO_MEMORY;
return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length);
}
return NTLSTATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_RECV:
if (req->Length && !req->Buffer)
return NTLSTATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return NTLSTATUS_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 NTLSTATUS_NO_MEMORY;
}
submit->UserBuffer = req->Buffer;
return NTLSTATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_SEND_TO: {
if (req->Length && !req->Buffer)
return NTLSTATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return NTLSTATUS_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 NTLSTATUS_NO_MEMORY;
return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length);
}
return NTLSTATUS_SUCCESS;
}
case SYS_ASYNC_SOCKET_OP_RECV_FROM: {
if (req->Length && !req->Buffer)
return NTLSTATUS_INVALID_PARAMETER;
if (req->Length > SYSCALL_IO_MAX_BYTES)
return NTLSTATUS_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 NTLSTATUS_NO_MEMORY;
}
submit->UserBuffer = req->Buffer;
return NTLSTATUS_SUCCESS;
}
case SYS_ASYNC_SOCKET_OP_SET_OPT:
if (!req->OptionValue)
return NTLSTATUS_INVALID_PARAMETER;
if (req->OptionValueSize > sizeof(uint64_t))
return NTLSTATUS_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 NTLSTATUS_NO_MEMORY;
return CopyFromUser(submit->OptionValue, req->OptionValue, req->OptionValueSize);
}
return NTLSTATUS_SUCCESS;
case SYS_ASYNC_SOCKET_OP_GET_OPT: {
if (!req->OptionValue)
return NTLSTATUS_INVALID_PARAMETER;
if (req->OptionValueSize > sizeof(uint64_t))
return NTLSTATUS_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 NTLSTATUS_NO_MEMORY;
}
return NTLSTATUS_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 NTLSTATUS_SUCCESS;
}
default:
return NTLSTATUS_INVALID_PARAMETER;
}
}
// Returns the capability right required by one asynchronous socket operation.
static SYS_RIGHTS KiAsyncSocketRequiredRights(uint32_t operation) {
switch (operation) {
case SYS_ASYNC_SOCKET_OP_BIND: return SYS_SOCKET_RIGHT_BIND;
case SYS_ASYNC_SOCKET_OP_LISTEN: return SYS_SOCKET_RIGHT_LISTEN;
case SYS_ASYNC_SOCKET_OP_ACCEPT: return SYS_SOCKET_RIGHT_ACCEPT;
case SYS_ASYNC_SOCKET_OP_CONNECT: return SYS_SOCKET_RIGHT_CONNECT;
case SYS_ASYNC_SOCKET_OP_SEND:
case SYS_ASYNC_SOCKET_OP_SEND_TO: return SYS_SOCKET_RIGHT_SEND;
case SYS_ASYNC_SOCKET_OP_RECV:
case SYS_ASYNC_SOCKET_OP_RECV_FROM: return SYS_SOCKET_RIGHT_RECEIVE;
case SYS_ASYNC_SOCKET_OP_SHUTDOWN: return SYS_SOCKET_RIGHT_SHUTDOWN;
case SYS_ASYNC_SOCKET_OP_SET_OPT: return SYS_SOCKET_RIGHT_SET_OPTION;
case SYS_ASYNC_SOCKET_OP_GET_OPT: return SYS_RIGHT_QUERY;
case SYS_ASYNC_SOCKET_OP_POLL: return SYS_SOCKET_RIGHT_POLL;
default: return 0;
}
}
// Implements SYS_SOCKET_IO_ASYNC.
static uint64_t SyscallSocketIoAsync(const SYS_ASYNC_SOCKET_REQUEST* request) {
if (!request)
return NTLSTATUS_INVALID_PARAMETER;
SYS_ASYNC_SOCKET_REQUEST_K req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status))
return status;
if (req.Flags != SYS_ASYNC_SOCKET_FLAG_NONE || !req.Iosb)
return NTLSTATUS_INVALID_PARAMETER;
if (req.RequestHandleOut) {
status = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut));
if (!KSUCCESS(status))
return status;
}
PROCESS* proc = KeGetCurrentProcess();
if (!proc)
return NTLSTATUS_FAILURE;
SYS_RIGHTS socketRights = KiAsyncSocketRequiredRights(req.Operation);
if (!socketRights)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS socketHandleStatus = NTLSTATUS_SUCCESS;
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.SocketHandle,
ObjectTypeSocket,
socketRights,
&socketHandleStatus);
if (!socket)
return socketHandleStatus;
EVENT* event = (EVENT*)0;
if (req.EventHandle) {
KSTATUS eventHandleStatus = NTLSTATUS_SUCCESS;
event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent,
SYS_EVENT_RIGHT_SIGNAL, &eventHandleStatus);
if (!event) {
ObDereferenceObject(&socket->Header);
return eventHandleStatus;
}
}
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 NTLSTATUS_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 NTLSTATUS_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 == NTLSTATUS_PENDING) {
memset(&submit, 0, sizeof(submit));
}
if (!KSUCCESS(status) && status != NTLSTATUS_PENDING)
goto fail;
if (req.RequestHandleOut && asyncReq) {
SYS_HANDLE requestHandle = SYS_HANDLE_INVALID;
KSTATUS hs = KeCreateHandle(proc,
NetAsyncSocketRequestObject(asyncReq),
KeGetValidHandleRights(ObjectTypeAsyncIo),
&requestHandle);
if (!KSUCCESS(hs)) {
(void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq);
ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq));
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) {
SYS_HANDLE zeroHandle = SYS_HANDLE_INVALID;
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;
}
// Implements SYS_GET_MEMORY_STATUS.
static uint64_t SyscallGetMemoryStatus(SYS_MEMORY_STATUS* statusOut) {
if (!statusOut) return NTLSTATUS_INVALID_PARAMETER;
MM_PFN_STATS pfn;
MmPfnQueryStats(&pfn);
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(statusOut, &status, sizeof(status));
}
// Implements SYS_QUERY_KERNEL_STATS.
static KSTATUS KiReturnKernelStats(void* buffer, size_t bufferSize,
size_t* requiredSizeOut,
const void* src, size_t size) {
if (requiredSizeOut) {
KSTATUS s = KiCopySizeToUser(requiredSizeOut, size);
if (!KSUCCESS(s)) return s;
}
if (!buffer)
return NTLSTATUS_SUCCESS;
if (bufferSize < size)
return NTLSTATUS_BUFFER_TOO_SMALL;
return CopyToUser(buffer, src, size);
}
// Copies the current primary-display state through either native authority path.
static KSTATUS KiCopyDisplayInfoToUser(SYS_DISPLAY_INFO* infoOut) {
if (!infoOut) return NTLSTATUS_INVALID_PARAMETER;
KE_DISPLAY_INFO ki;
KSTATUS status = KeDisplayQueryInfo(&ki);
if (!KSUCCESS(status)) return status;
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.Flags = ki.Flags;
out.ManagerStats = ki.ManagerStats;
out.DeviceStats = ki.DeviceStats;
return CopyToUser(infoOut, &out, sizeof(out));
}
// Returns display metadata through the legacy ambient display authority.
static uint64_t SyscallQueryDisplayInfo(SYS_DISPLAY_INFO* infoOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
return KiCopyDisplayInfoToUser(infoOut);
}
// Returns display metadata through an explicitly delegated display-manager capability.
static uint64_t SyscallQueryDisplayManagerInfo(SYS_HANDLE managerHandle, SYS_DISPLAY_INFO* infoOut) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = (KE_DISPLAY_MANAGER*)KiHandleToObject(
managerHandle, ObjectTypeDisplayManager, SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiCopyDisplayInfoToUser(infoOut);
ObDereferenceObject(&manager->Header);
return status;
}
// Presents one surface through explicitly delegated physical-display authority.
static uint64_t SyscallPresentDisplayManagerSurface(SYS_HANDLE managerHandle, SYS_HANDLE surfaceHandle,
const DISPLAY_RECT* userRects, uint32_t rectCount) {
KSTATUS managerStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = (KE_DISPLAY_MANAGER*)KiHandleToObject(
managerHandle, ObjectTypeDisplayManager, SYS_DISPLAY_MANAGER_RIGHT_PRESENT, &managerStatus);
if (!manager) return managerStatus;
DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS];
KSTATUS status = KiCaptureUserSurfaceRects(userRects, rectCount, rects);
if (!KSUCCESS(status)) {
ObDereferenceObject(&manager->Header);
return status;
}
KSTATUS surfaceStatus = NTLSTATUS_SUCCESS;
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface,
SYS_SURFACE_RIGHT_MAP_READ, &surfaceStatus);
if (!surface) {
ObDereferenceObject(&manager->Header);
return surfaceStatus;
}
uint64_t bytesCopied = 0;
status = KeSurfacePresent(surface, rectCount ? rects : (DISPLAY_RECT*)0, rectCount, &bytesCopied);
ObDereferenceObject(surface);
ObDereferenceObject(&manager->Header);
return status;
}
// Updates the hardware cursor through an explicitly delegated display-manager capability.
static uint64_t SyscallSetDisplayManagerCursor(const SYS_DISPLAY_MANAGER_CURSOR_REQUEST* request) {
SYS_DISPLAY_MANAGER_CURSOR_REQUEST req;
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
if (!KSUCCESS(status)) return status;
if (req.Flags & ~(SYS_DISPLAY_MANAGER_CURSOR_FLAG_SHOW | SYS_DISPLAY_MANAGER_CURSOR_FLAG_HIDE))
return NTLSTATUS_INVALID_PARAMETER;
if ((req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_SHOW) &&
(req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_HIDE))
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = (KE_DISPLAY_MANAGER*)KiHandleToObject(
req.ManagerHandle, ObjectTypeDisplayManager, SYS_DISPLAY_MANAGER_RIGHT_CURSOR, &handleStatus);
if (!manager) return handleStatus;
status = KeDisplaySetCursorPosition(req.X, req.Y);
if (KSUCCESS(status) && (req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_SHOW))
status = KeDisplayShowCursor(true);
if (KSUCCESS(status) && (req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_HIDE))
status = KeDisplayShowCursor(false);
ObDereferenceObject(&manager->Header);
return status;
}
// Returns a referenced display manager carrying RSSRV's console authority.
static KE_DISPLAY_MANAGER* KiReferenceDisplayManagerConsole(SYS_HANDLE managerHandle, KSTATUS* statusOut) {
return (KE_DISPLAY_MANAGER*)KiHandleToObject(managerHandle, ObjectTypeDisplayManager,
SYS_DISPLAY_MANAGER_RIGHT_CONSOLE, statusOut);
}
// Writes text through an explicitly delegated display-manager console capability.
static uint64_t SyscallWriteDisplayManagerConsole(SYS_HANDLE managerHandle,
const SYS_WRITE_CONSOLE_REQUEST* request) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiConsoleWrite(request);
ObDereferenceObject(&manager->Header);
return status;
}
// Queries console state through an explicitly delegated display-manager capability.
static uint64_t SyscallQueryDisplayManagerConsoleInfo(SYS_HANDLE managerHandle,
SYS_CONSOLE_INFO* infoOut) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiConsoleQueryInfo(infoOut);
ObDereferenceObject(&manager->Header);
return status;
}
// Updates the console cursor through an explicitly delegated display-manager capability.
static uint64_t SyscallSetDisplayManagerConsoleCursor(SYS_HANDLE managerHandle, int x, int y) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiConsoleSetCursor(x, y);
ObDereferenceObject(&manager->Header);
return status;
}
// Fills console characters through an explicitly delegated display-manager capability.
static uint64_t SyscallFillDisplayManagerConsoleCharacter(
SYS_HANDLE managerHandle, const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiConsoleFillCharacter(request);
ObDereferenceObject(&manager->Header);
return status;
}
// Fills console attributes through an explicitly delegated display-manager capability.
static uint64_t SyscallFillDisplayManagerConsoleAttribute(
SYS_HANDLE managerHandle, const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiConsoleFillAttribute(request);
ObDereferenceObject(&manager->Header);
return status;
}
// Draws one explicit console glyph through delegated display-manager authority.
static uint64_t SyscallPutDisplayManagerConsoleCharacter(
SYS_HANDLE managerHandle, const SYS_PUT_SCREEN_CHAR_REQUEST* request) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiConsolePutCharacter(request);
ObDereferenceObject(&manager->Header);
return status;
}
// Clears the console target through delegated display-manager authority.
static uint64_t SyscallClearDisplayManagerConsole(SYS_HANDLE managerHandle, uint32_t color) {
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
if (!manager) return handleStatus;
KSTATUS status = KiConsoleClear(color);
ObDereferenceObject(&manager->Header);
return status;
}
// Queries one class of kernel diagnostic statistics.
static uint64_t SyscallQueryKernelStats(uint32_t classId, void* buffer, size_t bufferSize, size_t* requiredSizeOut) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_KERNEL_DIAGNOSTICS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
switch (classId) {
case SYS_KERNEL_STATS_SCHEDULER: {
KE_SCHED_STATS ks;
SYS_SCHEDULER_STATS us;
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;
us.RealtimeDispatches = ks.RealtimeDispatches;
us.RealtimePreemptions = ks.RealtimePreemptions;
us.PriorityInheritanceBoosts = ks.PriorityInheritanceBoosts;
us.PriorityInheritanceDrops = ks.PriorityInheritanceDrops;
us.PriorityInheritancePropagations = ks.PriorityInheritancePropagations;
us.PriorityInheritanceMaxDepth = ks.PriorityInheritanceMaxDepth;
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &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(buffer, bufferSize, requiredSizeOut, &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(buffer, bufferSize, requiredSizeOut, &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(buffer, bufferSize, requiredSizeOut, &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(buffer, bufferSize, requiredSizeOut, &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.ResidentPages = km.ResidentPages;
um.ActivePages = km.ActivePages;
um.StandbyPages = km.StandbyPages;
um.MaxResidentPages = km.MaxResidentPages;
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
}
case SYS_KERNEL_STATS_SURFACE: {
SYS_SURFACE_STATS stats;
memset(&stats, 0, sizeof(stats));
KeSurfaceQueryStats(&stats);
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &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(buffer, bufferSize, requiredSizeOut, &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(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
}
case SYS_KERNEL_STATS_VOLUME: {
IO_VOLUME_STATS km;
SYS_VOLUME_STATS um;
IoQueryVolumeStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.CreatedVolumes = km.CreatedVolumes;
um.MountedVolumes = km.MountedVolumes;
um.RemovedVolumes = km.RemovedVolumes;
um.DeadIoRejects = km.DeadIoRejects;
um.ProviderDestroys = km.ProviderDestroys;
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
}
case SYS_KERNEL_STATS_VOLUME_MANAGER: {
IO_VOLUME_MANAGER_STATS km;
SYS_VOLUME_MANAGER_STATS um;
IoQueryVolumeManagerStats(&km);
memset(&um, 0, sizeof(um));
um.Size = sizeof(um);
um.Version = SYS_STATS_VERSION;
um.Snapshots = km.Snapshots;
um.VolumeOpens = km.VolumeOpens;
um.Waits = km.Waits;
um.Wakeups = km.Wakeups;
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
}
case SYS_KERNEL_STATS_DISK: {
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;
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &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(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
}
case SYS_KERNEL_STATS_EXCEPTION: {
SYS_EXCEPTION_STATS stats;
KeQueryExceptionStats(&stats);
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats));
}
case SYS_KERNEL_STATS_PORT: {
KE_PORT_STATS kernelStats;
SYS_PORT_STATS stats;
memset(&kernelStats, 0, sizeof(kernelStats));
memset(&stats, 0, sizeof(stats));
KePortQueryStats(&kernelStats);
stats.Size = sizeof(stats);
stats.Version = SYS_STATS_VERSION;
stats.ConnectionsCreated = kernelStats.ConnectionsCreated;
stats.DirectDeliveries = kernelStats.DirectDeliveries;
stats.DirectBytes = kernelStats.DirectBytes;
stats.QueuedMessages = kernelStats.QueuedMessages;
stats.QueuedBytes = kernelStats.QueuedBytes;
stats.QueuedDeliveries = kernelStats.QueuedDeliveries;
stats.RepliesCompleted = kernelStats.RepliesCompleted;
stats.Disconnects = kernelStats.Disconnects;
stats.ProcessQuotaRejects = kernelStats.ProcessQuotaRejects;
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats));
}
case SYS_KERNEL_STATS_CPU: {
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->HardwareId = cpu->HardwareId;
KE_CPU_STATE state = (KE_CPU_STATE)cpu->State;
if (state >= KeCpuPresent) out->Flags |= SYS_CPU_FLAG_PRESENT;
if (state >= KeCpuStarted) out->Flags |= SYS_CPU_FLAG_STARTED;
if (state == KeCpuParked) out->Flags |= SYS_CPU_FLAG_PARKED;
if (state == KeCpuOnline) out->Flags |= SYS_CPU_FLAG_ONLINE;
if (cpu->IsBootProcessor) out->Flags |= SYS_CPU_FLAG_BOOT_PROCESSOR;
if (cpu->IsPrimaryThread) out->Flags |= SYS_CPU_FLAG_PRIMARY;
out->CurrentTid = cpu->CurrentThread ? cpu->CurrentThread->ThreadId : 0;
if (cpu->CurrentThread && cpu->CurrentThread->IsIdleThread) out->CurrentThreadFlags |= SYS_CPU_THREAD_FLAG_IDLE;
out->SchedulerTicks = cpu->SchedulerTicks;
out->ContextSwitches = cpu->ContextSwitches;
}
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, size);
}
default:
return NTLSTATUS_INVALID_PARAMETER;
}
}
// Converts one public audio format into the kernel mixer format.
static KSTATUS KiImportAudioFormat(const SYS_AUDIO_FORMAT* source, IO_AUDIO_FORMAT* destination) {
if (!source || !destination)
return NTLSTATUS_INVALID_PARAMETER;
if (source->SampleFormat != SysAudioSampleS16 ||
(source->ChannelCount != 1 && source->ChannelCount != 2) ||
(source->SampleRate != 11025u && source->SampleRate != 22050u &&
source->SampleRate != 44100u && source->SampleRate != 48000u))
return NTLSTATUS_NOT_SUPPORTED;
destination->SampleRate = source->SampleRate;
destination->ChannelCount = source->ChannelCount;
destination->SampleFormat = IoAudioSampleS16;
return NTLSTATUS_SUCCESS;
}
// Converts one kernel audio format into the public syscall representation.
static SYS_AUDIO_FORMAT KiExportAudioFormat(const IO_AUDIO_FORMAT* source) {
SYS_AUDIO_FORMAT destination;
destination.SampleRate = source->SampleRate;
destination.ChannelCount = source->ChannelCount;
destination.SampleFormat = source->SampleFormat == IoAudioSampleS16
? SysAudioSampleS16
: 0;
return destination;
}
// Converts one normalized kernel gain into unsigned Q16.
static uint32_t KiExportAudioGainQ16(float gain) {
if (gain <= 0.0f) return 0;
if (gain >= 1.0f) return SYS_AUDIO_GAIN_Q16_UNITY;
return (uint32_t)(gain * (float)SYS_AUDIO_GAIN_Q16_UNITY + 0.5f);
}
// Converts one normalized kernel pan into signed Q16.
static int32_t KiExportAudioPanQ16(float pan) {
if (pan <= -1.0f) return SYS_AUDIO_PAN_Q16_LEFT;
if (pan >= 1.0f) return SYS_AUDIO_PAN_Q16_RIGHT;
float scaled = pan * (float)SYS_AUDIO_GAIN_Q16_UNITY;
return (int32_t)(scaled + (scaled >= 0.0f ? 0.5f : -0.5f));
}
// Converts one public unsigned Q16 gain into the kernel representation.
static float KiImportAudioGainQ16(uint32_t gainQ16) {
return (float)gainQ16 / (float)SYS_AUDIO_GAIN_Q16_UNITY;
}
// Converts one public signed Q16 pan into the kernel representation.
static float KiImportAudioPanQ16(int32_t panQ16) {
return (float)panQ16 / (float)SYS_AUDIO_GAIN_Q16_UNITY;
}
// Converts one kernel endpoint kind into its public ABI value.
static uint32_t KiExportAudioEndpointKind(IO_AUDIO_ENDPOINT_KIND kind) {
switch (kind) {
case IoAudioEndpointLineOut: return SysAudioEndpointLineOut;
case IoAudioEndpointSpeakerHeadphone: return SysAudioEndpointSpeakerHeadphone;
case IoAudioEndpointOtherAnalog: return SysAudioEndpointOtherAnalog;
default: return 0;
}
}
// Converts provider endpoint capabilities into public capability bits.
static uint32_t KiExportAudioEndpointCapabilities(uint32_t capabilities) {
uint32_t result = 0;
if (capabilities & IO_AUDIO_ENDPOINT_CAP_JACK_SENSE)
result |= SYS_AUDIO_ENDPOINT_CAP_JACK_SENSE;
if (capabilities & IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN)
result |= SYS_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN;
return result;
}
// Converts one endpoint lifecycle value into its public ABI value.
static uint32_t KiExportAudioEndpointState(KE_AUDIO_ENDPOINT_STATE state) {
switch (state) {
case KeAudioEndpointStarting: return SysAudioEndpointStarting;
case KeAudioEndpointReady: return SysAudioEndpointReady;
case KeAudioEndpointStopping: return SysAudioEndpointStopping;
case KeAudioEndpointFailed: return SysAudioEndpointFailed;
case KeAudioEndpointRemoved: return SysAudioEndpointRemoved;
default: return 0;
}
}
// Converts one stream lifecycle value into its public ABI value.
static uint32_t KiExportAudioStreamState(KE_AUDIO_STREAM_STATE state) {
switch (state) {
case KeAudioStreamCreated: return SysAudioStreamCreated;
case KeAudioStreamRunning: return SysAudioStreamRunning;
case KeAudioStreamPaused: return SysAudioStreamPaused;
case KeAudioStreamDraining: return SysAudioStreamDraining;
case KeAudioStreamStopped: return SysAudioStreamStopped;
case KeAudioStreamFailed: return SysAudioStreamFailed;
case KeAudioStreamClosed:
default:
return 0;
}
}
// Converts public wait flags into the kernel stream condition mask.
static uint32_t KiImportAudioWaitMask(uint32_t mask) {
uint32_t result = 0;
if (mask & SYS_AUDIO_WAIT_WRITABLE) result |= KE_AUDIO_WAIT_WRITABLE;
if (mask & SYS_AUDIO_WAIT_CONSUMED) result |= KE_AUDIO_WAIT_CONSUMED;
if (mask & SYS_AUDIO_WAIT_PRESENTED) result |= KE_AUDIO_WAIT_PRESENTED;
if (mask & SYS_AUDIO_WAIT_STOPPED) result |= KE_AUDIO_WAIT_STOPPED;
if (mask & SYS_AUDIO_WAIT_DRAINED) result |= KE_AUDIO_WAIT_DRAINED;
if (mask & SYS_AUDIO_WAIT_FAILED) result |= KE_AUDIO_WAIT_FAILED;
if (mask & SYS_AUDIO_WAIT_DISCONTINUITY) result |= KE_AUDIO_WAIT_DISCONTINUITY;
return result;
}
// Looks up one audio stream handle with the requested operation rights.
static KE_AUDIO_STREAM* KiLookupAudioStream(SYS_HANDLE handle, SYS_RIGHTS rights, KSTATUS* statusOut) {
OBJECT* object = KiHandleToObject(handle, ObjectTypeAudioStream, rights, statusOut);
return object ? (KE_AUDIO_STREAM*)object : (KE_AUDIO_STREAM*)0;
}
// Implements SYS_AUDIO_ENUMERATE_ENDPOINTS.
static uint64_t SyscallAudioEnumerateEndpoints(const SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST* userRequest) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status)) return status;
if (request.Flags != 0 || request.Reserved != 0 ||
!request.CountOut || (request.Capacity && !request.Endpoints))
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(request.CountOut, sizeof(*request.CountOut));
if (!KSUCCESS(status)) return status;
uint32_t copyCapacity = request.Capacity < SYS_AUDIO_MAX_ENDPOINTS
? request.Capacity
: SYS_AUDIO_MAX_ENDPOINTS;
if (copyCapacity) {
status = MmProbeUserWrite(request.Endpoints,
(size_t)copyCapacity * sizeof(*request.Endpoints));
if (!KSUCCESS(status)) return status;
}
KE_AUDIO_ENDPOINT_INFO kernelInfo[KE_AUDIO_MAX_ENDPOINTS];
uint32_t count = 0;
status = KeAudioQueryEndpoints(kernelInfo, KE_AUDIO_MAX_ENDPOINTS, &count);
if (KFAILED(status)) return status;
SYS_AUDIO_ENDPOINT_INFO publicInfo[SYS_AUDIO_MAX_ENDPOINTS];
uint32_t copyCount = copyCapacity < count ? copyCapacity : count;
for (uint32_t index = 0; index < copyCount; index++) {
const KE_AUDIO_ENDPOINT_INFO* source = &kernelInfo[index];
SYS_AUDIO_ENDPOINT_INFO* destination = &publicInfo[index];
memset(destination, 0, sizeof(*destination));
destination->Size = sizeof(*destination);
destination->Version = SYS_AUDIO_ABI_VERSION;
destination->EndpointId = source->EndpointId;
destination->Kind = KiExportAudioEndpointKind(source->Kind);
if (source->Available) destination->Flags |= SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE;
if (source->IsDefault) destination->Flags |= SYS_AUDIO_ENDPOINT_FLAG_DEFAULT;
destination->Capabilities = KiExportAudioEndpointCapabilities(source->Capabilities);
destination->State = KiExportAudioEndpointState(source->State);
destination->HardwareFormat = KiExportAudioFormat(&source->HardwareFormat);
destination->PeriodCount = source->PeriodCount;
destination->FramesPerPeriod = source->FramesPerPeriod;
destination->MasterVolumeQ16 = KiExportAudioGainQ16(source->MasterRequestedVolume);
destination->MasterMuted = source->MasterMuted ? 1u : 0u;
destination->FailureStatus = source->FailureStatus;
}
if (copyCount) {
status = CopyToUser(request.Endpoints,
publicInfo,
(size_t)copyCount * sizeof(publicInfo[0]));
if (!KSUCCESS(status)) return status;
}
status = CopyToUser(request.CountOut, &count, sizeof(count));
if (!KSUCCESS(status)) return status;
return request.Capacity < count ? NTLSTATUS_BUFFER_TOO_SMALL : NTLSTATUS_SUCCESS;
}
// Implements SYS_AUDIO_OPEN_STREAM.
static uint64_t SyscallAudioOpenStream(const SYS_AUDIO_OPEN_STREAM_REQUEST* userRequest) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_ACCESS);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_AUDIO_OPEN_STREAM_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status)) return status;
if (request.Flags != 0 || request.Reserved != 0 ||
!request.EndpointId || !request.StreamHandleOut)
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(request.StreamHandleOut, sizeof(*request.StreamHandleOut));
if (!KSUCCESS(status)) return status;
IO_AUDIO_FORMAT format;
status = KiImportAudioFormat(&request.Format, &format);
if (!KSUCCESS(status)) return status;
KE_AUDIO_STREAM* stream = (KE_AUDIO_STREAM*)0;
status = KeAudioCreateStream(request.EndpointId, &format, &stream);
if (!KSUCCESS(status)) return status;
SYS_HANDLE handle = SYS_HANDLE_INVALID;
status = KiAllocHandleForCurrentWithRights((OBJECT*)stream,
KeGetValidHandleRights(ObjectTypeAudioStream),
&handle);
if (!KSUCCESS(status)) return status;
status = CopyToUser(request.StreamHandleOut, &handle, sizeof(handle));
if (!KSUCCESS(status)) {
PROCESS* process = KeGetCurrentProcess();
if (process) KeCloseHandle(process, handle);
}
return status;
}
// Implements SYS_AUDIO_WRITE.
static uint64_t SyscallAudioWrite(const SYS_AUDIO_WRITE_REQUEST* userRequest) {
SYS_AUDIO_WRITE_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status)) return status;
if (request.Flags != 0 || !request.StreamHandle ||
!request.WrittenFramesOut || (request.FrameCount && !request.Frames))
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(request.WrittenFramesOut, sizeof(*request.WrittenFramesOut));
if (!KSUCCESS(status)) return status;
uint32_t written = 0;
KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_AUDIO_STREAM_RIGHT_WRITE, &status);
if (!stream) return status;
if (!request.FrameCount) {
ObDereferenceObject((OBJECT*)stream);
return CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
}
KE_AUDIO_STREAM_INFO info;
status = KeAudioQueryStream(stream, &info);
if (!KSUCCESS(status)) {
ObDereferenceObject((OBJECT*)stream);
return status;
}
if (info.State == KeAudioStreamFailed) {
status = KFAILED(info.FailureStatus) ? info.FailureStatus : NTLSTATUS_DEVICE_ERROR;
ObDereferenceObject((OBJECT*)stream);
(void)CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
return status;
}
if (info.State == KeAudioStreamDraining) {
ObDereferenceObject((OBJECT*)stream);
(void)CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
return NTLSTATUS_INVALID_PARAMETER;
}
if (!info.WritableFrames) {
ObDereferenceObject((OBJECT*)stream);
status = CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
return KSUCCESS(status) ? NTLSTATUS_WOULD_BLOCK : status;
}
uint32_t frameCount = request.FrameCount < info.WritableFrames
? request.FrameCount
: info.WritableFrames;
uint32_t frameBytes = (uint32_t)info.Format.ChannelCount * sizeof(int16_t);
size_t byteCount = (size_t)frameCount * frameBytes;
status = MmProbeUserRead(request.Frames, byteCount);
if (!KSUCCESS(status)) {
ObDereferenceObject((OBJECT*)stream);
return status;
}
void* buffer = MmAllocatePoolTag(byteCount, MM_POOL_TAG('a', 'u', 'w', 'r'));
if (!buffer) {
ObDereferenceObject((OBJECT*)stream);
return NTLSTATUS_NO_MEMORY;
}
status = CopyFromUser(buffer, request.Frames, byteCount);
if (KSUCCESS(status))
status = CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
if (KSUCCESS(status))
status = KeAudioWriteStream(stream, buffer, frameCount, &written);
MmFreePool(buffer);
ObDereferenceObject((OBJECT*)stream);
if (!KSUCCESS(status)) return status;
return CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
}
// Implements SYS_AUDIO_ARM_WAIT.
static uint64_t SyscallAudioArmWait(const SYS_AUDIO_ARM_WAIT_REQUEST* userRequest) {
SYS_AUDIO_ARM_WAIT_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status)) return status;
if (request.Flags != 0 || request.Reserved != 0 ||
!request.StreamHandle || !request.Mask ||
(request.Mask & ~SYS_AUDIO_WAIT_VALID_MASK) != 0)
return NTLSTATUS_INVALID_PARAMETER;
KE_AUDIO_STREAM_WAIT_CONDITION condition;
condition.Mask = KiImportAudioWaitMask(request.Mask);
condition.WritableFrames = request.WritableFrames;
condition.ConsumedFrames = request.ConsumedFrames;
condition.PresentedFrames = request.PresentedFrames;
condition.DrainedGeneration = request.DrainedGeneration;
condition.DiscontinuityGeneration = request.DiscontinuityGeneration;
KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_RIGHT_WAIT, &status);
if (!stream) return status;
status = KeAudioArmStreamWait(stream, &condition);
ObDereferenceObject((OBJECT*)stream);
return status;
}
// Implements SYS_AUDIO_QUERY_STREAM.
static uint64_t SyscallAudioQueryStream(SYS_HANDLE streamHandle, SYS_AUDIO_STREAM_INFO* infoOut) {
if (!streamHandle || !infoOut) return NTLSTATUS_INVALID_PARAMETER;
uint32_t userSize = 0;
KSTATUS status = CopyFromUser(&userSize, infoOut, sizeof(userSize));
if (!KSUCCESS(status)) return status;
if (userSize != sizeof(SYS_AUDIO_STREAM_INFO))
return NTLSTATUS_INVALID_PARAMETER;
status = MmProbeUserWrite(infoOut, sizeof(SYS_AUDIO_STREAM_INFO));
if (!KSUCCESS(status)) return status;
KE_AUDIO_STREAM* stream = KiLookupAudioStream(streamHandle, SYS_RIGHT_QUERY, &status);
if (!stream) return status;
KE_AUDIO_STREAM_INFO kernelInfo;
status = KeAudioQueryStream(stream, &kernelInfo);
ObDereferenceObject((OBJECT*)stream);
if (!KSUCCESS(status)) return status;
SYS_AUDIO_STREAM_INFO publicInfo;
memset(&publicInfo, 0, sizeof(publicInfo));
publicInfo.Size = sizeof(publicInfo);
publicInfo.Version = SYS_AUDIO_ABI_VERSION;
publicInfo.State = KiExportAudioStreamState(kernelInfo.State);
publicInfo.Format = KiExportAudioFormat(&kernelInfo.Format);
publicInfo.CapacityFrames = kernelInfo.CapacityFrames;
publicInfo.BufferedFrames = kernelInfo.BufferedFrames;
publicInfo.WritableFrames = kernelInfo.WritableFrames;
publicInfo.FailureStatus = kernelInfo.FailureStatus;
publicInfo.FramesSubmitted = kernelInfo.FramesSubmitted;
publicInfo.FramesConsumed = kernelInfo.FramesConsumed;
publicInfo.FramesPresented = kernelInfo.FramesPresented;
publicInfo.FramesDiscarded = kernelInfo.FramesDiscarded;
publicInfo.OutputFramesMixed = kernelInfo.OutputFramesMixed;
publicInfo.SilenceFramesInserted = kernelInfo.SilenceFramesInserted;
publicInfo.Underruns = kernelInfo.Underruns;
publicInfo.DrainedGeneration = kernelInfo.DrainedGeneration;
publicInfo.DiscontinuityGeneration = kernelInfo.DiscontinuityGeneration;
publicInfo.VolumeQ16 = KiExportAudioGainQ16(kernelInfo.Volume);
publicInfo.PanQ16 = KiExportAudioPanQ16(kernelInfo.Pan);
publicInfo.Muted = kernelInfo.Muted;
return CopyToUser(infoOut, &publicInfo, sizeof(publicInfo));
}
typedef KSTATUS (*KI_AUDIO_STREAM_OPERATION)(KE_AUDIO_STREAM* stream);
// Invokes one existing kernel stream operation through a write-capable handle.
static KSTATUS KiCallAudioStreamOperation(SYS_HANDLE handle, KI_AUDIO_STREAM_OPERATION operation) {
if (!handle || !operation) return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = NTLSTATUS_SUCCESS;
KE_AUDIO_STREAM* stream = KiLookupAudioStream(handle, SYS_AUDIO_STREAM_RIGHT_CONTROL, &status);
if (!stream) return status;
status = operation(stream);
ObDereferenceObject((OBJECT*)stream);
return status;
}
// Implements SYS_AUDIO_START.
static uint64_t SyscallAudioStart(SYS_HANDLE streamHandle) {
return KiCallAudioStreamOperation(streamHandle, KeAudioStartStream);
}
// Implements SYS_AUDIO_PAUSE.
static uint64_t SyscallAudioPause(SYS_HANDLE streamHandle) {
return KiCallAudioStreamOperation(streamHandle, KeAudioPauseStream);
}
// Implements SYS_AUDIO_RESUME.
static uint64_t SyscallAudioResume(SYS_HANDLE streamHandle) {
return KiCallAudioStreamOperation(streamHandle, KeAudioResumeStream);
}
// Implements SYS_AUDIO_DRAIN.
static uint64_t SyscallAudioDrain(SYS_HANDLE streamHandle) {
return KiCallAudioStreamOperation(streamHandle, KeAudioDrainStream);
}
// Implements SYS_AUDIO_STOP.
static uint64_t SyscallAudioStop(SYS_HANDLE streamHandle) {
return KiCallAudioStreamOperation(streamHandle, KeAudioStopStream);
}
// Implements SYS_AUDIO_RESET.
static uint64_t SyscallAudioReset(SYS_HANDLE streamHandle) {
return KiCallAudioStreamOperation(streamHandle, KeAudioResetStream);
}
// Implements SYS_AUDIO_SET_VOLUME.
static uint64_t SyscallAudioSetVolume(const SYS_AUDIO_SET_VOLUME_REQUEST* userRequest) {
SYS_AUDIO_SET_VOLUME_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status)) return status;
if (request.Flags != 0 || !request.StreamHandle ||
request.Muted > 1u || request.VolumeQ16 > SYS_AUDIO_GAIN_Q16_UNITY ||
request.PanQ16 < SYS_AUDIO_PAN_Q16_LEFT ||
request.PanQ16 > SYS_AUDIO_PAN_Q16_RIGHT)
return NTLSTATUS_INVALID_PARAMETER;
KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_AUDIO_STREAM_RIGHT_CONTROL, &status);
if (!stream) return status;
status = KeAudioSetStreamVolume(stream,
KiImportAudioGainQ16(request.VolumeQ16),
KiImportAudioPanQ16(request.PanQ16),
request.Muted != 0);
ObDereferenceObject((OBJECT*)stream);
return status;
}
// Implements SYS_AUDIO_SET_MASTER_VOLUME.
static uint64_t SyscallAudioSetMasterVolume(const SYS_AUDIO_SET_MASTER_VOLUME_REQUEST* userRequest) {
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_MASTER_CONTROL);
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
SYS_AUDIO_SET_MASTER_VOLUME_REQUEST request;
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
if (!KSUCCESS(status)) return status;
if (request.Flags != 0 || !request.EndpointId ||
request.Muted > 1u || request.VolumeQ16 > SYS_AUDIO_GAIN_Q16_UNITY)
return NTLSTATUS_INVALID_PARAMETER;
return KeAudioSetMasterVolume(request.EndpointId,
KiImportAudioGainQ16(request.VolumeQ16),
request.Muted != 0);
}
// Dispatch table keyed by SYS_* constants.
typedef uint64_t (*SYSCALL_HANDLER0)(void);
typedef uint64_t (*SYSCALL_HANDLER1)(uint64_t);
typedef uint64_t (*SYSCALL_HANDLER2)(uint64_t, uint64_t);
typedef uint64_t (*SYSCALL_HANDLER3)(uint64_t, uint64_t, uint64_t);
typedef uint64_t (*SYSCALL_HANDLER4)(uint64_t, uint64_t, uint64_t, uint64_t);
typedef struct SYSCALL_ENTRY {
void* Handler;
uint8_t ArgumentCount;
} SYSCALL_ENTRY;
#define SYSCALL_ENTRY0(handler) { handler, 0 }
#define SYSCALL_ENTRY1(handler) { handler, 1 }
#define SYSCALL_ENTRY2(handler) { handler, 2 }
#define SYSCALL_ENTRY3(handler) { handler, 3 }
#define SYSCALL_ENTRY4(handler) { handler, 4 }
static const SYSCALL_ENTRY gSyscallTable[SYS_MAX] = {
// Process, threads, waits, APC, and exceptions.
[SYS_EXIT_PROCESS] = SYSCALL_ENTRY1(SyscallExitProcess),
[SYS_EXIT_THREAD] = SYSCALL_ENTRY1(SyscallExitThread),
[SYS_CREATE_PROCESS_OBJECT] = SYSCALL_ENTRY1(SyscallCreateProcessObject),
[SYS_ALLOC_PROCESS_VM] = SYSCALL_ENTRY1(SyscallAllocProcessVm),
[SYS_PROTECT_PROCESS_VM] = SYSCALL_ENTRY4(SyscallProtectProcessVm),
[SYS_START_PROCESS] = SYSCALL_ENTRY1(SyscallStartProcess),
[SYS_OPEN_PROCESS] = SYSCALL_ENTRY3(SyscallOpenProcess),
[SYS_GET_PROCESS_EXIT_CODE] = SYSCALL_ENTRY2(SyscallGetProcessExitCode),
[SYS_KILL_PROCESS] = SYSCALL_ENTRY2(SyscallKillProcess),
[SYS_GET_PROCESS_LIST] = SYSCALL_ENTRY3(SyscallGetProcessList),
[SYS_GET_PROCESS_PID] = SYSCALL_ENTRY2(SyscallGetProcessPid),
[SYS_CREATE_THREAD] = SYSCALL_ENTRY1(SyscallCreateThread),
[SYS_RESUME_THREAD] = SYSCALL_ENTRY2(SyscallResumeThread),
[SYS_GET_THREAD_LIST] = SYSCALL_ENTRY4(SyscallGetThreadList),
[SYS_GET_CURRENT_TID] = SYSCALL_ENTRY0(SyscallGetCurrentTid),
[SYS_GET_CURRENT_PID] = SYSCALL_ENTRY0(SyscallGetCurrentPid),
[SYS_SET_THREAD_PRIORITY] = SYSCALL_ENTRY1(SyscallSetThreadPriority),
[SYS_SET_THREAD_NAME] = SYSCALL_ENTRY1(SyscallSetThreadName),
[SYS_QUERY_THREAD_NAME] = SYSCALL_ENTRY4(SyscallQueryThreadName),
[SYS_YIELD] = SYSCALL_ENTRY0(SyscallYield),
[SYS_SLEEP] = SYSCALL_ENTRY1(SyscallSleep),
[SYS_WAIT_SINGLE] = SYSCALL_ENTRY3(SyscallWaitSingle),
[SYS_WAIT_MANY] = SYSCALL_ENTRY1(SyscallWaitMany),
[SYS_SET_USER_APC_DISPATCHER] = SYSCALL_ENTRY1(SyscallSetUserApcDispatcher),
[SYS_APC_RETURN] = SYSCALL_ENTRY1(SyscallApcReturn),
[SYS_SET_EXCEPTION_DISPATCHER] = SYSCALL_ENTRY1(SyscallSetExceptionDispatcher),
[SYS_GET_EXCEPTION_DISPATCHER] = SYSCALL_ENTRY1(SyscallGetExceptionDispatcher),
[SYS_CONTINUE_EXCEPTION] = SYSCALL_ENTRY1(SyscallContinueException),
[SYS_RAISE_EXCEPTION] = SYSCALL_ENTRY1(SyscallRaiseException),
// Virtual memory, mappings, and handles.
[SYS_ALLOC_VM] = SYSCALL_ENTRY1(SyscallVmAlloc),
[SYS_FREE_VM] = SYSCALL_ENTRY1(SyscallVmFree),
[SYS_PROTECT_VM] = SYSCALL_ENTRY4(SyscallVmProtect),
[SYS_QUERY_VM] = SYSCALL_ENTRY2(SyscallVmQuery),
[SYS_CLOSE_HANDLE] = SYSCALL_ENTRY1(SyscallCloseHandle),
[SYS_DUPLICATE_HANDLE] = SYSCALL_ENTRY1(SyscallDuplicateHandle),
[SYS_DERIVE_HANDLE] = SYSCALL_ENTRY1(SyscallDeriveHandle),
[SYS_REVOKE_HANDLE] = SYSCALL_ENTRY1(SyscallRevokeHandle),
[SYS_DERIVE_SECURITY_CONTEXT] = SYSCALL_ENTRY1(SyscallDeriveSecurityContext),
[SYS_QUERY_SECURITY_CONTEXT] = SYSCALL_ENTRY2(SyscallQuerySecurityContext),
[SYS_CREATE_FILE_MAPPING] = SYSCALL_ENTRY1(SyscallCreateFileMapping),
[SYS_MAP_FILE] = SYSCALL_ENTRY1(SyscallMapFile),
// Synchronization objects.
[SYS_CREATE_MUTEX] = SYSCALL_ENTRY1(SyscallMutexCreate),
[SYS_TRY_ACQUIRE_MUTEX] = SYSCALL_ENTRY1(SyscallMutexTryAcquire),
[SYS_RELEASE_MUTEX] = SYSCALL_ENTRY1(SyscallMutexRelease),
[SYS_CREATE_SEM] = SYSCALL_ENTRY3(SyscallSemCreate),
[SYS_TRY_WAIT_SEM] = SYSCALL_ENTRY1(SyscallSemTryWait),
[SYS_SIGNAL_SEM] = SYSCALL_ENTRY1(SyscallSemSignal),
[SYS_CREATE_EVENT] = SYSCALL_ENTRY3(SyscallEventCreate),
[SYS_SIGNAL_EVENT] = SYSCALL_ENTRY1(SyscallEventSignal),
[SYS_CLEAR_EVENT] = SYSCALL_ENTRY1(SyscallEventClear),
// Queues and named queues.
[SYS_CREATE_QUEUE] = SYSCALL_ENTRY1(SyscallQueueCreate),
[SYS_SEND_QUEUE] = SYSCALL_ENTRY1(SyscallQueueSend),
[SYS_RECV_QUEUE] = SYSCALL_ENTRY1(SyscallQueueRecv),
[SYS_PUBLISH_QUEUE] = SYSCALL_ENTRY1(SyscallPublishQueue),
[SYS_UNPUBLISH_QUEUE] = SYSCALL_ENTRY1(SyscallUnpublishQueue),
[SYS_OPEN_NAMED_QUEUE] = SYSCALL_ENTRY1(SyscallOpenNamedQueue),
[SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS] = SYSCALL_ENTRY1(SyscallOpenNamedQueueOwnerProcess),
// LPC-like Ports and well-known system services.
[SYS_CREATE_PORT] = SYSCALL_ENTRY1(SyscallCreatePort),
[SYS_CONNECT_PORT] = SYSCALL_ENTRY1(SyscallConnectPort),
[SYS_ACCEPT_PORT] = SYSCALL_ENTRY1(SyscallAcceptPort),
[SYS_SEND_PORT] = SYSCALL_ENTRY1(SyscallSendPort),
[SYS_RECEIVE_PORT] = SYSCALL_ENTRY1(SyscallReceivePort),
[SYS_CALL_PORT] = SYSCALL_ENTRY1(SyscallCallPort),
[SYS_REPLY_PORT] = SYSCALL_ENTRY1(SyscallReplyPort),
[SYS_DISCONNECT_PORT] = SYSCALL_ENTRY1(SyscallDisconnectPort),
[SYS_REGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallRegisterSystemService),
[SYS_UNREGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallUnregisterSystemService),
[SYS_CONNECT_SYSTEM_SERVICE] = SYSCALL_ENTRY1(SyscallConnectSystemService),
// Mounted-volume manager control plane.
[SYS_QUERY_VOLUMES] = SYSCALL_ENTRY1(SyscallQueryVolumes),
[SYS_OPEN_VOLUME] = SYSCALL_ENTRY4(SyscallOpenVolume),
[SYS_WAIT_VOLUME_CHANGE] = SYSCALL_ENTRY3(SyscallWaitVolumeChange),
// Capability-relative file and directory operations.
[SYS_OPEN_FILE_AT] = SYSCALL_ENTRY1(SyscallOpenFileAt),
[SYS_OPEN_DIR_AT] = SYSCALL_ENTRY1(SyscallOpenDirAt),
[SYS_STAT_AT] = SYSCALL_ENTRY1(SyscallStatAt),
[SYS_DELETE_AT] = SYSCALL_ENTRY1(SyscallDeleteAt),
[SYS_RENAME_AT] = SYSCALL_ENTRY1(SyscallRenameAt),
[SYS_CREATE_DIR_AT] = SYSCALL_ENTRY1(SyscallCreateDirAt),
[SYS_REMOVE_DIR_AT] = SYSCALL_ENTRY1(SyscallRemoveDirAt),
[SYS_READ_FILE] = SYSCALL_ENTRY1(SyscallReadFile),
[SYS_WRITE_FILE] = SYSCALL_ENTRY1(SyscallWriteFile),
[SYS_READ_FILE_ASYNC] = SYSCALL_ENTRY1(SyscallReadFileAsync),
[SYS_WRITE_FILE_ASYNC] = SYSCALL_ENTRY1(SyscallWriteFileAsync),
[SYS_CANCEL_IO] = SYSCALL_ENTRY1(SyscallCancelIo),
[SYS_SEEK_FILE] = SYSCALL_ENTRY2(SyscallSeekFile),
[SYS_GET_FILE_POS] = SYSCALL_ENTRY2(SyscallGetFilePos),
[SYS_GET_FILE_SIZE] = SYSCALL_ENTRY2(SyscallGetFileSize),
[SYS_GET_FILE_STAT] = SYSCALL_ENTRY2(SyscallGetFileStat),
[SYS_TRUNCATE_FILE] = SYSCALL_ENTRY2(SyscallTruncateFile),
[SYS_FLUSH_FILE] = SYSCALL_ENTRY1(SyscallFlushFile),
[SYS_LOCK_FILE] = SYSCALL_ENTRY1(SyscallLockFile),
[SYS_UNLOCK_FILE] = SYSCALL_ENTRY1(SyscallUnlockFile),
[SYS_QUERY_FILE_LOCK] = SYSCALL_ENTRY1(SyscallQueryFileLock),
[SYS_SET_FILE_STATUS] = SYSCALL_ENTRY1(SyscallSetFileStatus),
[SYS_CREATE_PIPE] = SYSCALL_ENTRY1(SyscallCreatePipe),
[SYS_NOTIFY_PIPE] = SYSCALL_ENTRY1(SyscallNotifyPipe),
[SYS_READ_DIR] = SYSCALL_ENTRY2(SyscallReadDir),
// Console, display, and surfaces.
[SYS_WRITE_CONSOLE] = SYSCALL_ENTRY1(SyscallWriteConsole),
[SYS_GET_CONSOLE_INFO] = SYSCALL_ENTRY1(SyscallGetConsoleInfo),
[SYS_SET_CONSOLE_CURSOR] = SYSCALL_ENTRY2(SyscallSetConsoleCursor),
[SYS_FILL_CONSOLE_CHARACTER] = SYSCALL_ENTRY1(SyscallFillConsoleCharacter),
[SYS_FILL_CONSOLE_ATTRIBUTE] = SYSCALL_ENTRY1(SyscallFillConsoleAttribute),
[SYS_PUT_SCREEN_CHAR] = SYSCALL_ENTRY1(SyscallPutScreenChar),
[SYS_CLEAR_SCREEN] = SYSCALL_ENTRY1(SyscallClearScreen),
[SYS_GET_SCREEN_SIZE] = SYSCALL_ENTRY2(SyscallGetScreenSize),
[SYS_QUERY_DISPLAY_INFO] = SYSCALL_ENTRY1(SyscallQueryDisplayInfo),
[SYS_QUERY_DISPLAY_MANAGER_INFO] = SYSCALL_ENTRY2(SyscallQueryDisplayManagerInfo),
[SYS_PRESENT_DISPLAY_MANAGER_SURFACE] = SYSCALL_ENTRY4(SyscallPresentDisplayManagerSurface),
[SYS_SET_DISPLAY_MANAGER_CURSOR] = SYSCALL_ENTRY1(SyscallSetDisplayManagerCursor),
[SYS_WRITE_DISPLAY_MANAGER_CONSOLE] = SYSCALL_ENTRY2(SyscallWriteDisplayManagerConsole),
[SYS_QUERY_DISPLAY_MANAGER_CONSOLE_INFO] = SYSCALL_ENTRY2(SyscallQueryDisplayManagerConsoleInfo),
[SYS_SET_DISPLAY_MANAGER_CONSOLE_CURSOR] = SYSCALL_ENTRY3(SyscallSetDisplayManagerConsoleCursor),
[SYS_FILL_DISPLAY_MANAGER_CONSOLE_CHARACTER] = SYSCALL_ENTRY2(SyscallFillDisplayManagerConsoleCharacter),
[SYS_FILL_DISPLAY_MANAGER_CONSOLE_ATTRIBUTE] = SYSCALL_ENTRY2(SyscallFillDisplayManagerConsoleAttribute),
[SYS_PUT_DISPLAY_MANAGER_CONSOLE_CHARACTER] = SYSCALL_ENTRY2(SyscallPutDisplayManagerConsoleCharacter),
[SYS_CLEAR_DISPLAY_MANAGER_CONSOLE] = SYSCALL_ENTRY2(SyscallClearDisplayManagerConsole),
[SYS_OPEN_DISPLAY_CLOCK] = SYSCALL_ENTRY1(SyscallOpenDisplayClock),
[SYS_CREATE_SURFACE] = SYSCALL_ENTRY1(SyscallCreateSurface),
[SYS_MAP_SURFACE] = SYSCALL_ENTRY4(SyscallMapSurface),
[SYS_UNMAP_SURFACE] = SYSCALL_ENTRY1(SyscallUnmapSurface),
[SYS_QUERY_SURFACE] = SYSCALL_ENTRY2(SyscallQuerySurface),
[SYS_COMMIT_SURFACE] = SYSCALL_ENTRY4(SyscallCommitSurface),
// Native render audio.
[SYS_AUDIO_ENUMERATE_ENDPOINTS] = SYSCALL_ENTRY1(SyscallAudioEnumerateEndpoints),
[SYS_AUDIO_OPEN_STREAM] = SYSCALL_ENTRY1(SyscallAudioOpenStream),
[SYS_AUDIO_WRITE] = SYSCALL_ENTRY1(SyscallAudioWrite),
[SYS_AUDIO_ARM_WAIT] = SYSCALL_ENTRY1(SyscallAudioArmWait),
[SYS_AUDIO_QUERY_STREAM] = SYSCALL_ENTRY2(SyscallAudioQueryStream),
[SYS_AUDIO_START] = SYSCALL_ENTRY1(SyscallAudioStart),
[SYS_AUDIO_PAUSE] = SYSCALL_ENTRY1(SyscallAudioPause),
[SYS_AUDIO_RESUME] = SYSCALL_ENTRY1(SyscallAudioResume),
[SYS_AUDIO_DRAIN] = SYSCALL_ENTRY1(SyscallAudioDrain),
[SYS_AUDIO_STOP] = SYSCALL_ENTRY1(SyscallAudioStop),
[SYS_AUDIO_RESET] = SYSCALL_ENTRY1(SyscallAudioReset),
[SYS_AUDIO_SET_VOLUME] = SYSCALL_ENTRY1(SyscallAudioSetVolume),
[SYS_AUDIO_SET_MASTER_VOLUME] = SYSCALL_ENTRY1(SyscallAudioSetMasterVolume),
// Input.
[SYS_READ_INPUT_MANAGER_EVENT] = SYSCALL_ENTRY1(SyscallReadInputManagerEvent),
[SYS_POLL_KEYBOARD] = SYSCALL_ENTRY1(SyscallKbPoll),
[SYS_IS_KEY_DOWN] = SYSCALL_ENTRY2(SyscallKbIsKeyDown),
[SYS_READ_KEYBOARD_CHAR] = SYSCALL_ENTRY1(SyscallKbReadChar),
[SYS_READ_KEY] = SYSCALL_ENTRY1(SyscallKbReadKey),
[SYS_GET_KEY_MODIFIERS] = SYSCALL_ENTRY0(SyscallKbGetModifiers),
[SYS_QUERY_GLOBAL_INPUT_STATE] = SYSCALL_ENTRY1(SyscallQueryGlobalInputState),
[SYS_OPEN_INPUT_QUEUE] = SYSCALL_ENTRY2(SyscallOpenInputQueue),
[SYS_READ_INPUT_EVENT] = SYSCALL_ENTRY1(SyscallKbReadInputEvent),
[SYS_PEEK_INPUT_EVENT] = SYSCALL_ENTRY1(SyscallKbPeekInputEvent),
[SYS_PEEK_MOUSE] = SYSCALL_ENTRY1(SyscallMousePeek),
[SYS_READ_MOUSE] = SYSCALL_ENTRY1(SyscallMouseRead),
[SYS_GET_MOUSE_POS] = SYSCALL_ENTRY2(SyscallMouseGetPos),
// Time, debug, and system information.
[SYS_CREATE_TIMER] = SYSCALL_ENTRY1(SyscallCreateTimer),
[SYS_SET_TIMER] = SYSCALL_ENTRY1(SyscallSetTimer),
[SYS_CANCEL_TIMER] = SYSCALL_ENTRY1(SyscallCancelTimer),
[SYS_GET_TICK] = SYSCALL_ENTRY0(SyscallGetTick),
[SYS_GET_TICK_FREQUENCY] = SYSCALL_ENTRY1(SyscallGetTickFrequency),
[SYS_GET_SYSTEM_TIME] = SYSCALL_ENTRY1(SyscallGetSystemTime),
[SYS_PRINT_DEBUG] = SYSCALL_ENTRY1(SyscallDebugPrint),
[SYS_QUERY_TRAP_FRAME] = SYSCALL_ENTRY1(SyscallQueryTrapFrame),
[SYS_GET_MEMORY_STATUS] = SYSCALL_ENTRY1(SyscallGetMemoryStatus),
[SYS_QUERY_KERNEL_STATS] = SYSCALL_ENTRY4(SyscallQueryKernelStats),
// Native sockets and polling.
[SYS_CREATE_SOCKET] = SYSCALL_ENTRY4(SyscallSocket),
[SYS_SOCKET_IO_ASYNC] = SYSCALL_ENTRY1(SyscallSocketIoAsync),
[SYS_BIND_SOCKET] = SYSCALL_ENTRY3(SyscallBind),
[SYS_LISTEN_SOCKET] = SYSCALL_ENTRY2(SyscallListen),
[SYS_ACCEPT_SOCKET] = SYSCALL_ENTRY4(SyscallAccept),
[SYS_CONNECT_SOCKET] = SYSCALL_ENTRY3(SyscallConnect),
[SYS_SEND_SOCKET] = SYSCALL_ENTRY1(SyscallSend),
[SYS_RECV_SOCKET] = SYSCALL_ENTRY1(SyscallRecv),
[SYS_SHUTDOWN_SOCKET] = SYSCALL_ENTRY2(SyscallShutdown),
[SYS_SEND_TO_SOCKET] = SYSCALL_ENTRY2(SyscallSendTo),
[SYS_RECV_FROM_SOCKET] = SYSCALL_ENTRY2(SyscallRecvFrom),
[SYS_RESOLVE_HOST] = SYSCALL_ENTRY1(SyscallResolveHost),
[SYS_SET_SOCKET_OPTION] = SYSCALL_ENTRY2(SyscallSetSocketOption),
[SYS_GET_SOCKET_OPTION] = SYSCALL_ENTRY2(SyscallGetSocketOption),
[SYS_POLL] = SYSCALL_ENTRY1(SyscallPoll),
};
// Invokes one syscall handler through the MS x64 register ABI shape selected by its table entry.
static uint64_t KiInvokeSyscall(const SYSCALL_ENTRY* entry,
uint64_t a0,
uint64_t a1,
uint64_t a2,
uint64_t a3) {
switch (entry->ArgumentCount) {
case 0: return ((SYSCALL_HANDLER0)entry->Handler)();
case 1: return ((SYSCALL_HANDLER1)entry->Handler)(a0);
case 2: return ((SYSCALL_HANDLER2)entry->Handler)(a0, a1);
case 3: return ((SYSCALL_HANDLER3)entry->Handler)(a0, a1, a2);
case 4: return ((SYSCALL_HANDLER4)entry->Handler)(a0, a1, a2, a3);
default: return NTLSTATUS_NOT_SUPPORTED;
}
}
// Dispatches one user syscall after the assembly entry has restored its four ABI arguments.
uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1,
uint64_t a2, uint64_t a3) {
KiValidateSyscallTrapFrame(nr, a0, a1, a2, a3);
THREAD* self = KeGetCurrentThread();
PROCESS* owner = self ? self->Owner : (PROCESS*)0;
if (owner && KeIsProcessTerminating(owner) &&
!KeThreadTerminationDeferred(self) &&
nr != SYS_EXIT_PROCESS && nr != SYS_EXIT_THREAD) {
int32_t exitCode = owner->ExitCode;
KeExitThread(self, exitCode);
while (true) ;
}
if (nr >= SYS_MAX || !gSyscallTable[nr].Handler)
return NTLSTATUS_NOT_SUPPORTED;
uint64_t result = KiInvokeSyscall(&gSyscallTable[nr], a0, a1, a2, a3);
// A process may be terminated while one of its threads is already inside a
// syscall. Do not allow that thread to return to userspace after the owner
// has been marked terminating.
owner = self ? self->Owner : (PROCESS*)0;
if (owner && KeIsProcessTerminating(owner) &&
!KeThreadTerminationDeferred(self) &&
nr != SYS_EXIT_PROCESS && nr != SYS_EXIT_THREAD) {
int32_t exitCode = owner->ExitCode;
KeExitThread(self, exitCode);
while (true) ;
}
return result;
}