// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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; } } typedef struct SURFACE_OBJECT { OBJECT Header; PROCESS* Owner; uint32_t Width; uint32_t Height; uint32_t PitchBytes; uint32_t Format; uint32_t Flags; size_t PayloadSize; size_t PageCount; uint8_t StatsRegistered; uint64_t MapCount; uint64_t PresentCount; uint64_t PresentRectCount; uint64_t PresentClippedRectCount; uint64_t PresentEmptyClipCount; uint64_t PresentRowsCopied; uint64_t PresentBytesCopied; PHYSICAL_ADDRESS* Frames; void* UserBase; MM_KERNEL_VIEW* KernelView; void* KernelBase; MUTEX* Lock; } SURFACE_OBJECT; static SPINLOCK gSurfaceStatsLock = SPINLOCK_INIT; static SYS_SURFACE_STATS gSurfaceStats; // Registers a surface in the global surface telemetry snapshot. static void KiSurfaceStatsRegister(SURFACE_OBJECT* surface) { if (!surface || surface->StatsRegistered) return; uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock); gSurfaceStats.CreatedSurfaces++; gSurfaceStats.LiveSurfaces++; gSurfaceStats.LivePages += surface->PageCount; gSurfaceStats.LiveBytes += surface->PayloadSize; surface->StatsRegistered = 1; KeReleaseSpinlock(&gSurfaceStatsLock, flags); } // Removes a surface from the global surface telemetry snapshot. static void KiSurfaceStatsUnregister(SURFACE_OBJECT* surface) { if (!surface || !surface->StatsRegistered) return; uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock); gSurfaceStats.DestroyedSurfaces++; if (gSurfaceStats.LiveSurfaces) gSurfaceStats.LiveSurfaces--; if (gSurfaceStats.LivePages >= surface->PageCount) gSurfaceStats.LivePages -= surface->PageCount; else gSurfaceStats.LivePages = 0; if (gSurfaceStats.LiveBytes >= surface->PayloadSize) gSurfaceStats.LiveBytes -= surface->PayloadSize; else gSurfaceStats.LiveBytes = 0; surface->StatsRegistered = 0; KeReleaseSpinlock(&gSurfaceStatsLock, flags); } // Records a successful surface map operation. static void KiSurfaceStatsRecordMap(SURFACE_OBJECT* surface) { if (!surface) return; uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock); surface->MapCount++; gSurfaceStats.MapCalls++; KeReleaseSpinlock(&gSurfaceStatsLock, flags); } // Records a successful surface present operation. static void KiSurfaceStatsRecordPresent(SURFACE_OBJECT* surface, uint64_t inputRects, uint64_t clippedRects, uint64_t emptyClips, uint64_t rowsCopied, uint64_t bytesCopied) { if (!surface) return; uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock); surface->PresentCount++; surface->PresentRectCount += inputRects; surface->PresentClippedRectCount += clippedRects; surface->PresentEmptyClipCount += emptyClips; surface->PresentRowsCopied += rowsCopied; surface->PresentBytesCopied += bytesCopied; gSurfaceStats.PresentCalls++; gSurfaceStats.PresentRectInputs += inputRects; gSurfaceStats.PresentClippedRects += clippedRects; gSurfaceStats.PresentEmptyClips += emptyClips; gSurfaceStats.PresentRowsCopied += rowsCopied; gSurfaceStats.PresentBytesCopied += bytesCopied; KeReleaseSpinlock(&gSurfaceStatsLock, flags); } // Copies the global surface telemetry snapshot. static void KiSurfaceStatsQuery(SYS_SURFACE_STATS* out) { if (!out) return; uintptr_t flags = KeAcquireSpinlock(&gSurfaceStatsLock); *out = gSurfaceStats; out->Size = sizeof(*out); out->Version = SYS_STATS_VERSION; KeReleaseSpinlock(&gSurfaceStatsLock, flags); } static void KiSurfaceDestroy(OBJECT* obj) { SURFACE_OBJECT* surface = CONTAINING_RECORD(obj, SURFACE_OBJECT, Header); KiSurfaceStatsUnregister(surface); if (surface->Owner && surface->UserBase) { (void)MmVadDestroy(surface->Owner, surface->UserBase); surface->UserBase = (void*)0; } if (surface->KernelView) { MmDestroyKernelView(surface->KernelView); surface->KernelView = (MM_KERNEL_VIEW*)0; surface->KernelBase = (void*)0; } if (surface->Lock) { ObDereferenceObject(&surface->Lock->Header); surface->Lock = (MUTEX*)0; } if (surface->Frames) { for (size_t i = 0; i < surface->PageCount; ++i) { if (surface->Frames[i]) (void)MmPfnRelease(surface->Frames[i]); } MmFreePool(surface->Frames); surface->Frames = (PHYSICAL_ADDRESS*)0; } } // Creates the process mapping used for user drawing. static KSTATUS KiSurfaceEnsureViewLocked(SURFACE_OBJECT* surface) { if (!surface || !surface->Owner || !surface->Frames || !surface->PageCount) return NTLSTATUS_INVALID_PARAMETER; if (surface->UserBase) return NTLSTATUS_SUCCESS; return MmVadCreateSharedViewFromFrames(surface->Owner, surface->Frames, surface->PageCount, surface->PayloadSize, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, MM_VAD_ZERO, MmVadShared, &surface->UserBase); } // 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; if (!req.ThreadHandleOut && !req.ThreadIdOut) return NTLSTATUS_INVALID_PARAMETER; 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 ObjectTypeDisplayClock: event = KeDisplayClockWaitEvent((DISPLAY_CLOCK_OBJECT*)obj); break; case ObjectTypeAudioStream: event = KeAudioStreamWaitEvent((KE_AUDIO_STREAM*)obj); break; case ObjectTypePort: event = KePortWaitEvent((PORT*)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; uint32_t selected = 0; s = KeWaitForMultipleEvents(&event, 1, alertable, timeoutMs, &selected); 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; } } uint8_t alertable = (req.Flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0; uint32_t selected = 0; s = KeWaitForMultipleEvents(events, req.Count, alertable, req.TimeoutMilliseconds, &selected); for (uint32_t i = 0; i < req.Count; i++) ObDereferenceObject(refs[i]); if (KSUCCESS(s) && req.SignaledIndexOut) { KSTATUS cs = KiCopyU32ToUser(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); } // 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, ®ionBase, ®ionSize, &oldFlags, &type, ®ionFlags); 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, ®ionFlags); 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 | SYS_FILE_ACCESS_LOCK)) != 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; } // Implements SYS_CREATE_SURFACE. static uint64_t SyscallCreateSurface(const SYS_CREATE_SURFACE_REQUEST* request) { SYS_CREATE_SURFACE_REQUEST req; KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; uint32_t width = req.Width; uint32_t height = req.Height; uint32_t format = req.Format; uint32_t flags = req.Flags; if (!width || !height || !req.HandleOut) return NTLSTATUS_INVALID_PARAMETER; if (format != SYS_SURFACE_FORMAT_XRGB8888 || flags != SYS_SURFACE_FLAG_NONE) return NTLSTATUS_NOT_SUPPORTED; DISPLAY_MODE mode; s = KeDisplayGetMode(&mode); if (!KSUCCESS(s)) return s; if (width > mode.Width || height > mode.Height) return NTLSTATUS_INVALID_PARAMETER; size_t rowBytes = 0; if (!KiMulSize((size_t)width, sizeof(uint32_t), &rowBytes)) return NTLSTATUS_INVALID_PARAMETER; size_t pitchBytes = KiAlignUpSize(rowBytes, 64); if (pitchBytes < rowBytes) return NTLSTATUS_INVALID_PARAMETER; size_t payloadSize = 0; if (!KiMulSize(pitchBytes, (size_t)height, &payloadSize)) return NTLSTATUS_INVALID_PARAMETER; size_t pageCount = (payloadSize + PAGE_SIZE - 1) / PAGE_SIZE; if (!pageCount || pageCount > (SIZE_MAX / sizeof(PHYSICAL_ADDRESS))) return NTLSTATUS_INVALID_PARAMETER; SURFACE_OBJECT* surface = (SURFACE_OBJECT*)ObAllocateObject(ObjectTypeSurface, sizeof(SURFACE_OBJECT)); if (!surface) return NTLSTATUS_NO_MEMORY; surface->Header.Destroy = KiSurfaceDestroy; surface->Owner = KeGetCurrentProcess(); surface->Width = width; surface->Height = height; surface->PitchBytes = (uint32_t)pitchBytes; surface->Format = format; surface->Flags = flags; surface->PayloadSize = payloadSize; surface->PageCount = pageCount; surface->Frames = (PHYSICAL_ADDRESS*)MmAllocatePoolTag(pageCount * sizeof(PHYSICAL_ADDRESS), MM_POOL_TAG('s', 'f', 'r', 'm')); s = KeMutexCreate(&surface->Lock); if (!surface->Frames || !KSUCCESS(s)) { ObDereferenceObject(&surface->Header); return NTLSTATUS_NO_MEMORY; } memset(surface->Frames, 0, pageCount * sizeof(PHYSICAL_ADDRESS)); for (size_t i = 0; i < pageCount; ++i) { PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseSurface); if (!frame) { ObDereferenceObject(&surface->Header); return NTLSTATUS_NO_MEMORY; } surface->Frames[i] = frame; void* kva = MmPfnToVirtual(frame); if (kva) memset(kva, 0, PAGE_SIZE); } s = MmCreateKernelViewFromFrames(surface->Frames, surface->PageCount, &surface->KernelView); if (!KSUCCESS(s)) { ObDereferenceObject(&surface->Header); return s; } surface->KernelBase = MmKernelViewBase(surface->KernelView); SYS_HANDLE handle = SYS_HANDLE_INVALID; s = KiAllocHandleForCurrentWithRights(&surface->Header, KeGetValidHandleRights(ObjectTypeSurface), &handle); if (!KSUCCESS(s)) return s; KiSurfaceStatsRegister(surface); s = CopyToUser(req.HandleOut, &handle, sizeof(handle)); if (!KSUCCESS(s)) { KeCloseHandle(KeGetCurrentProcess(), handle); return s; } return NTLSTATUS_SUCCESS; } // Implements SYS_MAP_SURFACE. static uint64_t SyscallMapSurface(SYS_HANDLE surfaceHandle, void** baseOut, uint32_t* pitchBytesOut) { if (!baseOut || !pitchBytesOut) return NTLSTATUS_INVALID_PARAMETER; KSTATUS handleStatus = NTLSTATUS_SUCCESS; SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject(surfaceHandle, ObjectTypeSurface, SYS_SURFACE_RIGHT_MAP, &handleStatus); if (!surface) return handleStatus; PROCESS* current = KeGetCurrentProcess(); if (surface->Owner != current) { ObDereferenceObject(&surface->Header); return NTLSTATUS_ACCESS_DENIED; } KSTATUS s = KeAcquireMutex(surface->Lock, KE_WAIT_INFINITE); if (!KE_MUTEX_ACQUIRE_SUCCEEDED(s)) { ObDereferenceObject(&surface->Header); return s; } s = KiSurfaceEnsureViewLocked(surface); void* userBase = surface->UserBase; uint32_t pitchBytes = surface->PitchBytes; KeReleaseMutex(surface->Lock); if (KSUCCESS(s)) s = CopyToUser(baseOut, &userBase, sizeof(userBase)); if (KSUCCESS(s)) s = CopyToUser(pitchBytesOut, &pitchBytes, sizeof(pitchBytes)); if (KSUCCESS(s)) KiSurfaceStatsRecordMap(surface); ObDereferenceObject(&surface->Header); return s; } // 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; SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject(surfaceHandle, ObjectTypeSurface, SYS_RIGHT_QUERY, &handleStatus); if (!surface) return handleStatus; if (surface->Owner != KeGetCurrentProcess()) { ObDereferenceObject(&surface->Header); return NTLSTATUS_ACCESS_DENIED; } SYS_SURFACE_DESC desc; memset(&desc, 0, sizeof(desc)); KSTATUS s = KeAcquireMutex(surface->Lock, KE_WAIT_INFINITE); if (KE_MUTEX_ACQUIRE_SUCCEEDED(s)) { desc.Width = surface->Width; desc.Height = surface->Height; desc.PitchBytes = surface->PitchBytes; desc.Format = surface->Format; desc.Base = surface->UserBase; desc.PayloadBytes = surface->PayloadSize; desc.PageCount = surface->PageCount; KeReleaseMutex(surface->Lock); uintptr_t statsFlags = KeAcquireSpinlock(&gSurfaceStatsLock); desc.MapCount = surface->MapCount; desc.PresentCount = surface->PresentCount; desc.PresentRectCount = surface->PresentRectCount; desc.PresentClippedRectCount = surface->PresentClippedRectCount; desc.PresentEmptyClipCount = surface->PresentEmptyClipCount; desc.PresentRowsCopied = surface->PresentRowsCopied; desc.PresentBytesCopied = surface->PresentBytesCopied; KeReleaseSpinlock(&gSurfaceStatsLock, statsFlags); s = CopyToUser(descOut, &desc, sizeof(desc)); } ObDereferenceObject(&surface->Header); return s; } static int KiClipSurfaceRect(const SURFACE_OBJECT* surface, const DISPLAY_RECT* in, DISPLAY_RECT* out) { if (!surface || !out) return 0; DISPLAY_RECT r; if (in) { r = *in; } else { r.X = 0; r.Y = 0; r.W = (int32_t)surface->Width; r.H = (int32_t)surface->Height; } if (r.W <= 0 || r.H <= 0) return 0; int64_t x0 = r.X; int64_t y0 = r.Y; int64_t x1 = x0 + r.W; int64_t y1 = y0 + r.H; if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 > surface->Width) x1 = surface->Width; if (y1 > surface->Height) y1 = surface->Height; if (x0 >= x1 || y0 >= y1) return 0; out->X = (int32_t)x0; out->Y = (int32_t)y0; out->W = (int32_t)(x1 - x0); out->H = (int32_t)(y1 - y0); return 1; } // Implements SYS_PRESENT_SURFACE. static uint64_t SyscallPresentSurface(SYS_HANDLE surfaceHandle, const DISPLAY_RECT* userRects, uint32_t rectCount) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; if (rectCount > SYS_SURFACE_MAX_RECTS) return NTLSTATUS_INVALID_PARAMETER; if (rectCount != 0 && !userRects) return NTLSTATUS_INVALID_PARAMETER; KSTATUS handleStatus = NTLSTATUS_SUCCESS; SURFACE_OBJECT* surface = (SURFACE_OBJECT*)KiHandleToObject(surfaceHandle, ObjectTypeSurface, SYS_SURFACE_RIGHT_PRESENT, &handleStatus); if (!surface) return handleStatus; if (surface->Owner != KeGetCurrentProcess()) { ObDereferenceObject(&surface->Header); return NTLSTATUS_ACCESS_DENIED; } DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS]; if (rectCount == 0) { rects[0].X = 0; rects[0].Y = 0; rects[0].W = (int32_t)surface->Width; rects[0].H = (int32_t)surface->Height; rectCount = 1; } else { KSTATUS cs = CopyFromUser(rects, userRects, (size_t)rectCount * sizeof(DISPLAY_RECT)); if (!KSUCCESS(cs)) { ObDereferenceObject(&surface->Header); return cs; } } uint64_t inputRects = (uint64_t)rectCount; uint64_t clippedRects = 0; uint64_t emptyClips = 0; uint64_t rowsCopied = 0; uint64_t bytesCopied = 0; KSTATUS s = surface->KernelBase ? NTLSTATUS_SUCCESS : NTLSTATUS_DEVICE_ERROR; const uint8_t* sourceBase = (const uint8_t*)surface->KernelBase; for (uint32_t i = 0; i < rectCount && KSUCCESS(s); ++i) { DISPLAY_RECT clipped; if (!KiClipSurfaceRect(surface, &rects[i], &clipped)) { emptyClips++; continue; } const uint32_t* source = (const uint32_t*)(sourceBase + (size_t)(uint32_t)clipped.Y * surface->PitchBytes + (size_t)(uint32_t)clipped.X * sizeof(uint32_t)); s = KeDisplayPresentPitched(clipped.X, clipped.Y, clipped.W, clipped.H, source, surface->PitchBytes); if (!KSUCCESS(s)) break; clippedRects++; rowsCopied += (uint64_t)(uint32_t)clipped.H; bytesCopied += (uint64_t)(uint32_t)clipped.W * (uint64_t)(uint32_t)clipped.H * sizeof(uint32_t); } if (KSUCCESS(s)) KiSurfaceStatsRecordPresent(surface, inputRects, clippedRects, emptyClips, rowsCopied, bytesCopied); ObDereferenceObject(&surface->Header); return s; } // Implements SYS_CLEAR_SCREEN. static uint64_t SyscallClearScreen(uint32_t color) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; return KeDisplayClear(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. static uint64_t SyscallPutScreenChar(const SYS_PUT_SCREEN_CHAR_REQUEST* request) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_PUT_SCREEN_CHAR_REQUEST req; KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2]) return NTLSTATUS_INVALID_PARAMETER; if (req.X < 0 || req.Y < 0) return NTLSTATUS_INVALID_PARAMETER; return KeDisplayPutChar(req.Character, req.X, req.Y, req.Foreground, req.Background); } // Returns the current console geometry and cursor state. static uint64_t SyscallGetConsoleInfo(SYS_CONSOLE_INFO* infoOut) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; if (!infoOut) return NTLSTATUS_INVALID_PARAMETER; KE_CONSOLE_INFO ki; KSTATUS s = KeDisplayGetConsoleInfo(&ki); if (!KSUCCESS(s)) return s; SYS_CONSOLE_INFO out; memset(&out, 0, sizeof(out)); out.Columns = ki.Columns; out.Rows = ki.Rows; out.CursorX = ki.CursorX; out.CursorY = ki.CursorY; out.Attribute = ki.Attribute; return CopyToUser(infoOut, &out, sizeof(out)); } // Implements SYS_SET_CONSOLE_CURSOR. static uint64_t SyscallSetConsoleCursor(int x, int y) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; int col = (int)x; int row = (int)y; if (col < 0 || row < 0) return NTLSTATUS_INVALID_PARAMETER; return KeDisplaySetConsoleCursor(col, row); } // Implements SYS_FILL_CONSOLE_CHARACTER. static uint64_t SyscallFillConsoleCharacter(const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_FILL_CONSOLE_CHARACTER_REQUEST req; KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2]) return NTLSTATUS_INVALID_PARAMETER; if (req.X < 0 || req.Y < 0) return NTLSTATUS_INVALID_PARAMETER; size_t written = 0; s = KeDisplayFillConsoleChars(req.Character, req.X, req.Y, req.Length, &written); if (req.WrittenOut) { KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written); if (KSUCCESS(s) && !KSUCCESS(copyStatus)) s = copyStatus; } return s; } // Fills console attributes over one linear character range. static uint64_t SyscallFillConsoleAttribute(const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST req; KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(s)) return s; if (req.Reserved != 0) return NTLSTATUS_INVALID_PARAMETER; if (req.X < 0 || req.Y < 0) return NTLSTATUS_INVALID_PARAMETER; size_t written = 0; s = KeDisplayFillConsoleAttrs(req.Attribute, req.X, req.Y, req.Length, &written); if (req.WrittenOut) { KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written); if (KSUCCESS(s) && !KSUCCESS(copyStatus)) s = copyStatus; } return s; } 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; } // 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, ®ionBase, ®ionSize, ®ionFlags, ®ionType, &vadFlags); if (KSUCCESS(status) && (!(regionFlags & PAGE_PRESENT) || (regionFlags & PAGE_NX))) status = NTLSTATUS_ACCESS_DENIED; } if (KSUCCESS(status) && req.LoaderData) { status = MmVadQuery(child, req.LoaderData, ®ionBase, ®ionSize, ®ionFlags, ®ionType, &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_NORMAL(PriorityNormal); parameters.Flags = KE_THREAD_CREATE_FLAG_SUSPENDED; status = KeCreateThread(child, req.EntryPoint, 0, ¶meters, &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); } // Implements SYS_WRITE_CONSOLE. // Writes user console text and completes display delivery before returning. static uint64_t SyscallWriteConsole(const SYS_WRITE_CONSOLE_REQUEST* request) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; SYS_WRITE_CONSOLE_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; KePrintConsole(chunk, todo); offset += todo; } return KeDisplayFlushConsoleOutput(); } // 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; } 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; SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject( fds[i].Handle, ObjectTypeSocket, SYS_SOCKET_RIGHT_POLL, &handleStatus); if (!socket) { fds[i].ReturnedEvents = SYS_POLL_INVALID; ready++; continue; } uint32_t revents = 0; s = NetSocketPoll(socket, fds[i].Events, &revents); ObDereferenceObject(&socket->Header); if (!KSUCCESS(s)) { fds[i].ReturnedEvents = SYS_POLL_ERROR; ready++; continue; } fds[i].ReturnedEvents = revents; if (revents != 0) ready++; } if (ready != 0 || KiPollDeadlineExpired(start, timeoutMs)) break; KeYield(); } s = CopyToUser(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); } // Returns the current display mode and framebuffer metadata. static uint64_t SyscallQueryDisplayInfo(SYS_DISPLAY_INFO* infoOut) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; if (!infoOut) return NTLSTATUS_INVALID_PARAMETER; KE_DISPLAY_INFO ki; KSTATUS s = KeDisplayQueryInfo(&ki); if (!KSUCCESS(s)) return s; SYS_DISPLAY_INFO out; memset(&out, 0, sizeof(out)); memcpy(out.PrimaryName, ki.PrimaryName, sizeof(out.PrimaryName)); out.Mode = ki.Mode; out.Caps = ki.Caps; out.DeviceCount = ki.DeviceCount; out.Flags = ki.Flags; out.ManagerStats = ki.ManagerStats; out.DeviceStats = ki.DeviceStats; return CopyToUser(infoOut, &out, sizeof(out)); } // 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)); KiSurfaceStatsQuery(&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_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_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_OPEN_DISPLAY_CLOCK] = SYSCALL_ENTRY1(SyscallOpenDisplayClock), [SYS_CREATE_SURFACE] = SYSCALL_ENTRY1(SyscallCreateSurface), [SYS_MAP_SURFACE] = SYSCALL_ENTRY3(SyscallMapSurface), [SYS_QUERY_SURFACE] = SYSCALL_ENTRY2(SyscallQuerySurface), [SYS_PRESENT_SURFACE] = SYSCALL_ENTRY3(SyscallPresentSurface), // 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_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; }