6473 lines
259 KiB
C
6473 lines
259 KiB
C
// Kernel syscall dispatcher. This file validates user requests and bridges user-mode ABI calls into kernel services.
|
|
|
|
// KeSyscallEntry (ke_syscall.asm) handles the INT 0x2F 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 <ntlite/atomic.h>
|
|
#include <intrin.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <kernel/ke_thread.h>
|
|
#include <kernel/ke_cpu.h>
|
|
#include <kernel/ke_sched.h>
|
|
#include <kernel/ke_process.h>
|
|
#include <kernel/ke_power.h>
|
|
#include <kernel/se_security.h>
|
|
#include <kernel/ke_mutex.h>
|
|
#include <kernel/ke_semaphore.h>
|
|
#include <kernel/ke_event.h>
|
|
#include <kernel/ke_apc.h>
|
|
#include <kernel/ke_queue.h>
|
|
#include <kernel/ke_port.h>
|
|
#include <kernel/ke_system_service.h>
|
|
#include <kernel/ke_timer.h>
|
|
#include <kernel/ke_spinlock.h>
|
|
#include <kernel/ke_debug.h>
|
|
#include <kernel/ke_exception.h>
|
|
|
|
#ifndef PROCESS_LAUNCH_DEBUG
|
|
#define PROCESS_LAUNCH_DEBUG 0
|
|
#endif
|
|
|
|
#if PROCESS_LAUNCH_DEBUG
|
|
#define ProcDbg(...) DbgPrint(__VA_ARGS__)
|
|
#else
|
|
#define ProcDbg(...) ((void)0)
|
|
#endif
|
|
#include <kernel/mm_virt.h>
|
|
#include <kernel/mm_swap.h>
|
|
#include <kernel/mm_file_cache.h>
|
|
#include <kernel/mm_pfn.h>
|
|
#include <kernel/mm_pte.h>
|
|
#include <kernel/ldr_image.h>
|
|
#include <kernel/syscall_nr.h>
|
|
#include <kernel/hal/hal_api.h>
|
|
#include <kernel/io_file.h>
|
|
#include <kernel/io_pipe.h>
|
|
#include <kernel/io_volume.h>
|
|
#include <kernel/io_async.h>
|
|
#include <ntlite/ddi/io_driver.h>
|
|
#include <kernel/net_socket.h>
|
|
#include <ntlite/ddi/net_stack.h>
|
|
#include <kernel/ke_trap.h>
|
|
#include <kernel/ke_input.h>
|
|
#include <kernel/ke_display.h>
|
|
#include <kernel/ke_surface.h>
|
|
#include <kernel/ke_audio.h>
|
|
#include <kernel/ke_time.h>
|
|
#include <kernel/mm_pool.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
// HAL pointer - set once at boot by KeSyscallSetHal().
|
|
static const HAL_API* gHal;
|
|
|
|
|
|
typedef struct SYS_IO_STATUS_BLOCK_K {
|
|
volatile KSTATUS Status;
|
|
volatile uint64_t Information;
|
|
} SYS_IO_STATUS_BLOCK_K;
|
|
|
|
typedef void (*SYS_USER_APC_ROUTINE_K)(void* normalContext,
|
|
SYS_IO_STATUS_BLOCK_K* iosb,
|
|
uint64_t information);
|
|
|
|
typedef struct SYS_ASYNC_FILE_REQUEST_K {
|
|
uint32_t Size;
|
|
SYS_HANDLE FileHandle;
|
|
SYS_HANDLE EventHandle;
|
|
uint32_t Flags;
|
|
uint64_t Offset;
|
|
void* Buffer;
|
|
size_t Length;
|
|
SYS_IO_STATUS_BLOCK_K* Iosb;
|
|
SYS_USER_APC_ROUTINE_K ApcRoutine;
|
|
void* ApcContext;
|
|
SYS_HANDLE* RequestHandleOut; // optional cancellation/query handle output
|
|
} SYS_ASYNC_FILE_REQUEST_K;
|
|
|
|
_Static_assert(sizeof(SYS_ASYNC_FILE_REQUEST_K) == sizeof(SYS_ASYNC_FILE_REQUEST),
|
|
"async file request ABI mismatch");
|
|
|
|
typedef struct SYS_USER_ASYNC_APC_CONTEXT {
|
|
KAPC KernelApc;
|
|
THREAD* TargetThread;
|
|
SYS_USER_APC_ROUTINE_K UserRoutine;
|
|
void* UserContext;
|
|
} SYS_USER_ASYNC_APC_CONTEXT;
|
|
|
|
typedef struct FILE_MAPPING_OBJECT {
|
|
OBJECT Header;
|
|
FILE_OBJECT* File;
|
|
uint64_t MaximumSize;
|
|
uint64_t PageFlags;
|
|
uint32_t Protect;
|
|
uint32_t Flags;
|
|
} FILE_MAPPING_OBJECT;
|
|
|
|
static void KiFileMappingDestroy(OBJECT* obj) {
|
|
FILE_MAPPING_OBJECT* mapping = CONTAINING_RECORD(obj, FILE_MAPPING_OBJECT, Header);
|
|
if (mapping->File) {
|
|
ObDereferenceObject(&mapping->File->Header);
|
|
mapping->File = (FILE_OBJECT*)0;
|
|
}
|
|
}
|
|
|
|
|
|
// Implements the syscall Init operation.
|
|
void KeSyscallInit(const HAL_API* hal) {
|
|
gHal = hal;
|
|
}
|
|
|
|
// Requires one ambient privilege from the calling process's immutable security context.
|
|
static KSTATUS KiRequirePrivilege(SYS_PRIVILEGES privilege) {
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
return SeProcessHasPrivilege(process, privilege) ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
// Resolves one current-process handle while preserving the exact lookup status.
|
|
static OBJECT* KiHandleToObject(SYS_HANDLE handle,
|
|
OBJECT_TYPE expected,
|
|
SYS_RIGHTS requiredRights,
|
|
KSTATUS* statusOut) {
|
|
if (!statusOut) return (OBJECT*)0;
|
|
*statusOut = NTLSTATUS_FAILURE;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return (OBJECT*)0;
|
|
|
|
OBJECT* object = (OBJECT*)0;
|
|
*statusOut = KeReferenceHandle(process, handle, expected, requiredRights, &object);
|
|
return KSUCCESS(*statusOut) ? object : (OBJECT*)0;
|
|
}
|
|
|
|
// Helper: allocate a handle in the current process's table for 'obj'.
|
|
// Takes one reference on behalf of the handle slot.
|
|
// On failure, drops the reference it would have stored.
|
|
static KSTATUS KiAllocHandleForCurrentWithRights(OBJECT* obj, SYS_RIGHTS rights, SYS_HANDLE* handleOut) {
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) {
|
|
ObDereferenceObject(obj);
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
|
|
KSTATUS s = KeCreateHandle(proc, obj, rights, handleOut);
|
|
|
|
// Most syscall create/open paths receive a fresh creator reference from the
|
|
// subsystem. The root capability grant takes its own reference; this helper always
|
|
// drops the creator reference so the object is owned only by the returned
|
|
// handle (and any subsystem-internal refs).
|
|
ObDereferenceObject(obj);
|
|
return s;
|
|
}
|
|
|
|
#define SYSCALL_STRING_MAX SYS_PATH_MAX
|
|
#define SYSCALL_TEXT_MAX_BYTES (64ULL * 1024ULL)
|
|
#define SYSCALL_IO_MAX_BYTES (16ULL * 1024ULL * 1024ULL)
|
|
#define SYSCALL_FILE_IO_CHUNK_BYTES (64ULL * 1024ULL)
|
|
#define SYSCALL_BLIT_MAX_BYTES (16ULL * 1024ULL * 1024ULL)
|
|
|
|
// Copies a request only after its leading size field matches the exact ABI layout.
|
|
static KSTATUS KiCopySizedUserRequest(const void* userRequest, void* dst, size_t size) {
|
|
if (!userRequest || !dst || size < sizeof(uint32_t) || size > UINT32_MAX)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint32_t userSize = 0;
|
|
KSTATUS status = CopyFromUser(&userSize, userRequest, sizeof(userSize));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (userSize != (uint32_t)size) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
return CopyFromUser(dst, userRequest, size);
|
|
}
|
|
|
|
// Copies one bounded Port capability-transfer array from user memory.
|
|
static KSTATUS KiImportPortTransfers(const SYS_PORT_HANDLE_TRANSFER* userTransfers,
|
|
uint32_t count,
|
|
SYS_PORT_HANDLE_TRANSFER* transfers) {
|
|
if (count == 0)
|
|
return NTLSTATUS_SUCCESS;
|
|
if (!userTransfers || !transfers || count > SYS_PORT_MAX_TRANSFER_HANDLES)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
return CopyFromUser(transfers, userTransfers, sizeof(*transfers) * count);
|
|
}
|
|
|
|
// Validates the leading Size of a caller-owned output structure before writing it.
|
|
static KSTATUS KiProbeSizedUserOutput(void* userOutput, size_t size) {
|
|
if (!userOutput || size < sizeof(uint32_t) || size > UINT32_MAX)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
uint32_t userSize = 0;
|
|
KSTATUS status = CopyFromUser(&userSize, userOutput, sizeof(userSize));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (userSize != (uint32_t)size) return NTLSTATUS_INVALID_PARAMETER;
|
|
return MmProbeUserWrite(userOutput, size);
|
|
}
|
|
|
|
static KSTATUS KiImportUserStringView(SYS_STRING_VIEW userView,
|
|
char* dst,
|
|
size_t dstSize,
|
|
SYS_STRING_VIEW* out,
|
|
bool nulTerminate) {
|
|
if (!out) return NTLSTATUS_INVALID_PARAMETER;
|
|
*out = SysSvFromParts((const char*)0, 0);
|
|
|
|
if (userView.Length == 0) {
|
|
if (nulTerminate) {
|
|
if (!dst || dstSize == 0) return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
dst[0] = '\0';
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
if (!userView.Data || !dst) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (userView.Length > SYSCALL_STRING_MAX) return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
|
|
size_t needed = userView.Length + (nulTerminate ? 1 : 0);
|
|
if (needed < userView.Length || needed > dstSize) return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
|
|
KSTATUS s = CopyFromUser(dst, userView.Data, userView.Length);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (nulTerminate) dst[userView.Length] = '\0';
|
|
|
|
*out = SysSvFromParts(dst, userView.Length);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static KSTATUS KiImportUserTextView(SYS_STRING_VIEW userView,
|
|
char* dst,
|
|
size_t dstSize,
|
|
SYS_STRING_VIEW* out,
|
|
bool nulTerminate) {
|
|
KSTATUS s = KiImportUserStringView(userView, dst, dstSize, out, nulTerminate);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!out || !SysSvIsUtf8Text(*out)) 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 bool KiMulSize(size_t a, size_t b, size_t* out) {
|
|
if (!out) return false;
|
|
if (a && b > ((size_t)-1) / a) return false;
|
|
*out = a * b;
|
|
return true;
|
|
}
|
|
|
|
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.
|
|
|
|
static uint64_t SyscallOpenPowerEvent(SYS_HANDLE* handleOut) {
|
|
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS st = MmProbeUserWrite(handleOut, sizeof(*handleOut));
|
|
if (!KSUCCESS(st)) return st;
|
|
PROCESS* caller = KeGetCurrentProcess();
|
|
EVENT* event = KePowerRequestEvent(caller);
|
|
if (!event) return NTLSTATUS_ACCESS_DENIED;
|
|
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
|
st = KiAllocHandleForCurrentWithRights(&event->Header,
|
|
SYS_RIGHT_WAIT | SYS_RIGHT_QUERY, &handle);
|
|
if (!KSUCCESS(st)) return st;
|
|
st = KiCopyHandleToUser(handleOut, handle);
|
|
if (!KSUCCESS(st)) (void)KeCloseHandle(caller, handle);
|
|
return st;
|
|
}
|
|
|
|
static uint64_t SyscallRequestSystemPower(uint32_t action) {
|
|
KSTATUS st = KiRequirePrivilege(SYS_PRIV_SYSTEM_POWER);
|
|
return KSUCCESS(st) ? KePowerRequest(action) : st;
|
|
}
|
|
|
|
static uint64_t SyscallQuerySystemPower(uint32_t* actionOut) {
|
|
if (!actionOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS st = MmProbeUserWrite(actionOut, sizeof(*actionOut));
|
|
if (!KSUCCESS(st)) return st;
|
|
uint32_t action = 0;
|
|
st = KePowerGetRequest(KeGetCurrentProcess(), &action);
|
|
return KSUCCESS(st) ? KiCopyU32ToUser(actionOut, action) : st;
|
|
}
|
|
|
|
static uint64_t SyscallCompleteSystemPower(uint32_t action) {
|
|
return KePowerComplete(KeGetCurrentProcess(), action);
|
|
}
|
|
|
|
// Implements SYS_EXIT_PROCESS.
|
|
static uint64_t SyscallExitProcess(int32_t exitCode) {
|
|
THREAD* t = KeGetCurrentThread();
|
|
PROCESS* proc = t ? t->Owner : NULL;
|
|
if (!proc || !t) return NTLSTATUS_FAILURE;
|
|
|
|
KSTATUS s = KeExitProcess(proc, exitCode);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
KeExitThread(t, exitCode);
|
|
while (true) ; // unreachable - KeExitThread does not return
|
|
}
|
|
|
|
// Implements SYS_EXIT_THREAD.
|
|
static uint64_t SyscallExitThread(int32_t exitCode) {
|
|
THREAD* t = KeGetCurrentThread();
|
|
if (t) KeExitThread(t, (int)exitCode);
|
|
while (true);
|
|
}
|
|
|
|
// Implements SYS_CREATE_THREAD.
|
|
// Creates a user thread and optionally returns both its waitable handle and
|
|
// diagnostic TID. Suspended creation leaves the thread off every ready queue
|
|
// until SYS_RESUME_THREAD decrements its initial suspend count.
|
|
static uint64_t SyscallCreateThread(const SYS_CREATE_THREAD_REQUEST* request) {
|
|
SYS_CREATE_THREAD_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) return NTLSTATUS_FAILURE;
|
|
if (!req.Entry) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Priority >= PriorityCount) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Flags & ~SYS_CREATE_THREAD_FLAG_SUSPENDED)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
// Running threads may be detached: callers that do not need a handle or TID
|
|
// can leave both outputs null. A suspended thread still requires a handle so
|
|
// there is a capability with which to resume or cancel it.
|
|
if ((req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED) && !req.ThreadHandleOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
s = MmProbeUserRead((const void*)req.Entry, 1);
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.ThreadHandleOut) {
|
|
s = MmProbeUserWrite(req.ThreadHandleOut, sizeof(*req.ThreadHandleOut));
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
if (req.ThreadIdOut) {
|
|
s = MmProbeUserWrite(req.ThreadIdOut, sizeof(*req.ThreadIdOut));
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
THREAD* newt = NULL;
|
|
KE_THREAD_CREATE_PARAMETERS threadParameters = KE_THREAD_CREATE_NORMAL(req.Priority);
|
|
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
|
|
threadParameters.Flags |= KE_THREAD_CREATE_FLAG_SUSPENDED;
|
|
s = KeCreateThread(proc, req.Entry, req.Argument,
|
|
&threadParameters, &newt);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
SYS_HANDLE threadHandle = SYS_HANDLE_INVALID;
|
|
if (req.ThreadHandleOut) {
|
|
s = KeCreateHandle(proc,
|
|
&newt->Header,
|
|
KeGetValidHandleRights(ObjectTypeThread),
|
|
&threadHandle);
|
|
if (!KSUCCESS(s)) {
|
|
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
|
|
(void)KeCancelSuspendedThread(newt);
|
|
ObDereferenceObject(&newt->Header);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
if (req.ThreadHandleOut) {
|
|
s = KiCopyHandleToUser(req.ThreadHandleOut, threadHandle);
|
|
if (!KSUCCESS(s)) {
|
|
KeCloseHandle(proc, threadHandle);
|
|
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
|
|
(void)KeCancelSuspendedThread(newt);
|
|
ObDereferenceObject(&newt->Header);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
if (req.ThreadIdOut) {
|
|
s = KiCopyU32ToUser(req.ThreadIdOut, newt->ThreadId);
|
|
if (!KSUCCESS(s)) {
|
|
if (threadHandle) KeCloseHandle(proc, threadHandle);
|
|
if (req.Flags & SYS_CREATE_THREAD_FLAG_SUSPENDED)
|
|
(void)KeCancelSuspendedThread(newt);
|
|
ObDereferenceObject(&newt->Header);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
ObDereferenceObject(&newt->Header);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements SYS_OPEN_CURRENT_THREAD.
|
|
static uint64_t SyscallOpenCurrentThread(SYS_RIGHTS desiredAccess, SYS_HANDLE* handleOut) {
|
|
if (!handleOut || desiredAccess == 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_RIGHTS validAccess = KeGetValidHandleRights(ObjectTypeThread);
|
|
if ((desiredAccess & ~validAccess) != 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS status = MmProbeUserWrite(handleOut, sizeof(SYS_HANDLE));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
THREAD* thread = KeGetCurrentThread();
|
|
if (!process || !thread) return NTLSTATUS_FAILURE;
|
|
|
|
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
|
status = KeCreateHandle(process, &thread->Header, desiredAccess, &handle);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
status = KiCopyHandleToUser(handleOut, handle);
|
|
if (!KSUCCESS(status)) {
|
|
KeCloseHandle(process, handle);
|
|
return status;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Implements SYS_RESUME_THREAD_AND_WAIT.
|
|
// Resumes one suspended thread and blocks until that same thread exits without
|
|
// returning to userspace between the resume and wait operations.
|
|
static uint64_t SyscallResumeThreadAndWait(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();
|
|
THREAD* currentThread = KeGetCurrentThread();
|
|
if (!process || !currentThread) return NTLSTATUS_FAILURE;
|
|
|
|
OBJECT* object = (OBJECT*)0;
|
|
status = KeReferenceHandle(process, threadHandle, ObjectTypeThread,
|
|
SYS_THREAD_RIGHT_RESUME | SYS_RIGHT_WAIT, &object);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
THREAD* thread = CONTAINING_RECORD(object, THREAD, Header);
|
|
if (thread == currentThread) {
|
|
ObDereferenceObject(object);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint32_t previousSuspendCount = 0;
|
|
status = KeResumeThread(thread, &previousSuspendCount);
|
|
if (KSUCCESS(status))
|
|
status = KeWaitForEvent(thread->ExitEvent, 0);
|
|
|
|
ObDereferenceObject(object);
|
|
if (!KSUCCESS(status)) return status;
|
|
return KiCopyU32ToUser(previousSuspendCountOut, previousSuspendCount);
|
|
}
|
|
|
|
// Implements SYS_GET_THREAD_TID.
|
|
static uint64_t SyscallGetThreadTid(SYS_HANDLE threadHandle, uint32_t* tidOut) {
|
|
if (!threadHandle || !tidOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS status = MmProbeUserWrite(tidOut, sizeof(uint32_t));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
|
|
OBJECT* object = (OBJECT*)0;
|
|
status = KeReferenceHandle(process, threadHandle, ObjectTypeThread, SYS_RIGHT_QUERY, &object);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
THREAD* thread = CONTAINING_RECORD(object, THREAD, Header);
|
|
uint32_t tid = thread->ThreadId;
|
|
ObDereferenceObject(object);
|
|
return KiCopyU32ToUser(tidOut, tid);
|
|
}
|
|
|
|
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 ObjectTypeTimer:
|
|
event = KeTimerWaitEvent(CONTAINING_RECORD(obj, KTIMER, Header));
|
|
break;
|
|
case ObjectTypeSurface:
|
|
event = KeSurfaceWaitEvent(obj);
|
|
break;
|
|
case ObjectTypeDisplayClock:
|
|
event = KeDisplayClockWaitEvent((DISPLAY_CLOCK_OBJECT*)obj);
|
|
break;
|
|
case ObjectTypeAudioStream:
|
|
event = KeAudioStreamWaitEvent((KE_AUDIO_STREAM*)obj);
|
|
break;
|
|
case ObjectTypePort:
|
|
event = KePortWaitEvent((PORT*)obj);
|
|
break;
|
|
case ObjectTypePipeEndpoint:
|
|
event = IoPipeWaitEvent((IO_PIPE_ENDPOINT*)obj);
|
|
break;
|
|
default:
|
|
ObDereferenceObject(obj);
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (!event) {
|
|
ObDereferenceObject(obj);
|
|
return NTLSTATUS_OBJECT_DEAD;
|
|
}
|
|
|
|
*eventOut = event;
|
|
*objectOut = obj;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements SYS_WAIT_SINGLE.
|
|
// Waits on one handle. Passive objects use the wait-many engine; mutexes and
|
|
// semaphores keep their native consuming semantics for single-object waits.
|
|
static uint64_t SyscallWaitSingle(SYS_HANDLE handle, uint32_t flags, uint64_t timeoutMilliseconds) {
|
|
uint64_t timeoutMs = timeoutMilliseconds;
|
|
if (flags & ~SYS_WAIT_FLAG_ALERTABLE) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) return NTLSTATUS_FAILURE;
|
|
|
|
OBJECT* obj = (OBJECT*)0;
|
|
KSTATUS handleStatus = KeReferenceHandle(proc, handle, 0, SYS_RIGHT_WAIT, &obj);
|
|
if (!KSUCCESS(handleStatus)) return handleStatus;
|
|
|
|
uint8_t alertable = (flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0;
|
|
KSTATUS s = NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
if (obj->Type == ObjectTypeMutex) {
|
|
if (alertable) s = NTLSTATUS_NOT_SUPPORTED;
|
|
else s = KeAcquireMutex(CONTAINING_RECORD(obj, MUTEX, Header), timeoutMs);
|
|
ObDereferenceObject(obj);
|
|
return s;
|
|
}
|
|
|
|
if (obj->Type == ObjectTypeSemaphore) {
|
|
if (alertable) s = NTLSTATUS_NOT_SUPPORTED;
|
|
else s = KeWaitSemaphore(CONTAINING_RECORD(obj, SEMAPHORE, Header), timeoutMs);
|
|
ObDereferenceObject(obj);
|
|
return s;
|
|
}
|
|
|
|
ObDereferenceObject(obj);
|
|
|
|
EVENT* event = (EVENT*)0;
|
|
OBJECT* ref = (OBJECT*)0;
|
|
s = KiHandleToWaitEvent(proc, handle, &event, &ref);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
if (ref->Type == ObjectTypePipeEndpoint)
|
|
IoPipeBeginWait((IO_PIPE_ENDPOINT*)ref);
|
|
uint32_t selected = 0;
|
|
s = KeWaitForMultipleEvents(&event, 1, alertable, timeoutMs, &selected);
|
|
if (ref->Type == ObjectTypePipeEndpoint)
|
|
IoPipeEndWait((IO_PIPE_ENDPOINT*)ref);
|
|
ObDereferenceObject(ref);
|
|
if (s != NTLSTATUS_TIMEOUT && s != NTLSTATUS_NOT_SUPPORTED)
|
|
KiFinishAlertableUserWait(alertable);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_WAIT_MANY.
|
|
// Waits until any passive waitable handle becomes signaled.
|
|
static uint64_t SyscallWaitMany(const SYS_WAIT_MANY_REQUEST* request) {
|
|
SYS_WAIT_MANY_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Flags & ~SYS_WAIT_FLAG_ALERTABLE) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!req.Handles || req.Count == 0 || req.Count > SYS_WAIT_MANY_MAX_HANDLES)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_HANDLE handles[SYS_WAIT_MANY_MAX_HANDLES];
|
|
size_t handleBytes = (size_t)req.Count * sizeof(handles[0]);
|
|
s = CopyFromUser(handles, req.Handles, handleBytes);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) return NTLSTATUS_FAILURE;
|
|
|
|
EVENT* events[SYS_WAIT_MANY_MAX_HANDLES];
|
|
OBJECT* refs[SYS_WAIT_MANY_MAX_HANDLES];
|
|
for (uint32_t i = 0; i < SYS_WAIT_MANY_MAX_HANDLES; i++) {
|
|
events[i] = (EVENT*)0;
|
|
refs[i] = (OBJECT*)0;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < req.Count; i++) {
|
|
s = KiHandleToWaitEvent(proc, handles[i], &events[i], &refs[i]);
|
|
if (!KSUCCESS(s)) {
|
|
for (uint32_t j = 0; j < i; j++)
|
|
ObDereferenceObject(refs[j]);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < req.Count; i++)
|
|
if (refs[i]->Type == ObjectTypePipeEndpoint)
|
|
IoPipeBeginWait((IO_PIPE_ENDPOINT*)refs[i]);
|
|
|
|
uint8_t alertable = (req.Flags & SYS_WAIT_FLAG_ALERTABLE) ? 1 : 0;
|
|
uint32_t selected = 0;
|
|
s = KeWaitForMultipleEvents(events, req.Count, alertable, req.TimeoutMilliseconds, &selected);
|
|
|
|
for (uint32_t i = 0; i < req.Count; i++) {
|
|
if (refs[i]->Type == ObjectTypePipeEndpoint)
|
|
IoPipeEndWait((IO_PIPE_ENDPOINT*)refs[i]);
|
|
ObDereferenceObject(refs[i]);
|
|
}
|
|
|
|
if (KSUCCESS(s) && req.SignaledIndexOut) {
|
|
KSTATUS cs = KiCopyU32ToUser(req.SignaledIndexOut, selected);
|
|
if (!KSUCCESS(cs)) return cs;
|
|
}
|
|
|
|
if (s != NTLSTATUS_TIMEOUT && s != NTLSTATUS_NOT_SUPPORTED)
|
|
KiFinishAlertableUserWait(alertable);
|
|
return s;
|
|
}
|
|
|
|
// 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_QUEUE_USER_APC.
|
|
static uint64_t SyscallQueueUserApc(const SYS_QUEUE_USER_APC_REQUEST* userRequest) {
|
|
SYS_QUEUE_USER_APC_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (!request.ThreadHandle || !request.Routine || request.Reserved != 0 ||
|
|
(request.Flags & ~SYS_QUEUE_USER_APC_FLAG_INTERRUPT) != 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
|
|
OBJECT* object = (OBJECT*)0;
|
|
status = KeReferenceHandle(process, request.ThreadHandle, ObjectTypeThread,
|
|
SYS_THREAD_RIGHT_NOTIFY, &object);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
THREAD* thread = CONTAINING_RECORD(object, THREAD, Header);
|
|
status = KeQueueUserApc(thread,
|
|
(void*)(uintptr_t)request.Routine,
|
|
request.NormalContext,
|
|
request.SystemArgument1,
|
|
request.SystemArgument2,
|
|
(request.Flags & SYS_QUEUE_USER_APC_FLAG_INTERRUPT) != 0
|
|
? KE_USER_APC_FLAG_INTERRUPT
|
|
: KE_USER_APC_FLAG_NONE);
|
|
ObDereferenceObject(object);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_APC_RETURN.
|
|
static uint64_t SyscallApcReturn(const SYS_USER_APC_FRAME* frame) {
|
|
return KeUserApcReturn((const SYS_USER_APC_FRAME_K*)(uintptr_t)frame);
|
|
}
|
|
|
|
|
|
// Implements SYS_SET_EXCEPTION_DISPATCHER.
|
|
static uint64_t SyscallSetExceptionDispatcher(const SYS_EXCEPTION_DISPATCHER_REQUEST* userRequest) {
|
|
SYS_EXCEPTION_DISPATCHER_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process)
|
|
return NTLSTATUS_FAILURE;
|
|
|
|
return KeSetExceptionDispatcher(process, &request);
|
|
}
|
|
|
|
// Implements SYS_GET_EXCEPTION_DISPATCHER.
|
|
static uint64_t SyscallGetExceptionDispatcher(SYS_EXCEPTION_DISPATCHER_REQUEST* userRequest) {
|
|
if (!userRequest)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint32_t userSize = 0;
|
|
KSTATUS status = CopyFromUser(&userSize, userRequest, sizeof(userSize));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (userSize != sizeof(SYS_EXCEPTION_DISPATCHER_REQUEST))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process)
|
|
return NTLSTATUS_FAILURE;
|
|
|
|
SYS_EXCEPTION_DISPATCHER_REQUEST request;
|
|
status = KeGetExceptionDispatcher(process, &request);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
return CopyToUser(userRequest, &request, sizeof(request));
|
|
}
|
|
|
|
// Implements SYS_CONTINUE_EXCEPTION.
|
|
static uint64_t SyscallContinueException(const SYS_CONTINUE_EXCEPTION_REQUEST* userRequest) {
|
|
SYS_CONTINUE_EXCEPTION_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
return KeContinueException(&request);
|
|
}
|
|
|
|
|
|
// Implements SYS_RAISE_EXCEPTION.
|
|
static uint64_t SyscallRaiseException(const SYS_RAISE_EXCEPTION_REQUEST* userRequest) {
|
|
SYS_RAISE_EXCEPTION_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
if (request.ParameterCount > SYS_EXCEPTION_MAXIMUM_PARAMETERS)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint64_t parameters[SYS_EXCEPTION_MAXIMUM_PARAMETERS];
|
|
memset(parameters, 0, sizeof(parameters));
|
|
if (request.ParameterCount) {
|
|
if (!request.Parameters)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
status = CopyFromUser(parameters, request.Parameters,
|
|
(size_t)request.ParameterCount * sizeof(parameters[0]));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
}
|
|
|
|
return KeRaiseUserException(&request, parameters);
|
|
}
|
|
|
|
// Implements SYS_SET_THREAD_PRIORITY.
|
|
static uint64_t SyscallSetThreadPriority(uint32_t priority) {
|
|
if (priority >= PriorityCount) return NTLSTATUS_INVALID_PARAMETER;
|
|
THREAD* t = KeGetCurrentThread();
|
|
if (!t) return NTLSTATUS_FAILURE;
|
|
return KiSchedulerSetNormalPriority(t, (THREAD_PRIORITY)priority);
|
|
}
|
|
|
|
// Implements SYS_SET_THREAD_NAME.
|
|
static uint64_t SyscallSetThreadName(const SYS_SET_THREAD_NAME_REQUEST* userRequest) {
|
|
SYS_SET_THREAD_NAME_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
char name[SYS_THREAD_NAME_MAX];
|
|
SYS_STRING_VIEW importedName;
|
|
status = KiImportUserTextView(request.Name, name, sizeof(name), &importedName, true);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
THREAD* thread = (THREAD*)0;
|
|
OBJECT* referencedObject = (OBJECT*)0;
|
|
if (request.ThreadHandle == SYS_HANDLE_INVALID) {
|
|
thread = KeGetCurrentThread();
|
|
if (!thread) return NTLSTATUS_FAILURE;
|
|
} else {
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
status = KeReferenceHandle(process, request.ThreadHandle, ObjectTypeThread,
|
|
SYS_THREAD_RIGHT_SET_INFORMATION, &referencedObject);
|
|
if (!KSUCCESS(status)) return status;
|
|
thread = CONTAINING_RECORD(referencedObject, THREAD, Header);
|
|
}
|
|
|
|
status = KeSetThreadName(thread, importedName.Data, importedName.Length);
|
|
if (referencedObject) ObDereferenceObject(referencedObject);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_QUERY_THREAD_NAME.
|
|
static uint64_t SyscallQueryThreadName(SYS_HANDLE threadHandle,
|
|
char* buffer,
|
|
size_t bufferSize,
|
|
size_t* lengthOut) {
|
|
if (!lengthOut || bufferSize > SYS_THREAD_NAME_MAX || (bufferSize && !buffer))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS status = MmProbeUserWrite(lengthOut, sizeof(*lengthOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (bufferSize) {
|
|
status = MmProbeUserWrite(buffer, bufferSize);
|
|
if (!KSUCCESS(status)) return status;
|
|
}
|
|
|
|
THREAD* thread = (THREAD*)0;
|
|
OBJECT* referencedObject = (OBJECT*)0;
|
|
if (threadHandle == SYS_HANDLE_INVALID) {
|
|
thread = KeGetCurrentThread();
|
|
if (!thread) return NTLSTATUS_FAILURE;
|
|
} else {
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
status = KeReferenceHandle(process, threadHandle, ObjectTypeThread,
|
|
SYS_RIGHT_QUERY, &referencedObject);
|
|
if (!KSUCCESS(status)) return status;
|
|
thread = CONTAINING_RECORD(referencedObject, THREAD, Header);
|
|
}
|
|
|
|
char name[SYS_THREAD_NAME_MAX];
|
|
size_t length = KeQueryThreadName(thread, name, sizeof(name));
|
|
if (referencedObject) ObDereferenceObject(referencedObject);
|
|
|
|
status = KiCopySizeToUser(lengthOut, length);
|
|
if (!KSUCCESS(status)) return status;
|
|
if (bufferSize < length + 1u)
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
return CopyToUser(buffer, name, length + 1u);
|
|
}
|
|
|
|
// SYS_VM_* page-granular user virtual memory.
|
|
static KSTATUS KiVmProtectToPageFlags(uint32_t protect, uint64_t* flagsOut) {
|
|
if (!flagsOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
switch (protect) {
|
|
case SYS_VM_NOACCESS:
|
|
*flagsOut = 0;
|
|
return NTLSTATUS_SUCCESS;
|
|
case SYS_VM_READ:
|
|
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_NX;
|
|
return NTLSTATUS_SUCCESS;
|
|
case SYS_VM_READWRITE:
|
|
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_WRITE | PAGE_NX;
|
|
return NTLSTATUS_SUCCESS;
|
|
case SYS_VM_EXECUTE:
|
|
case SYS_VM_EXECUTE_READ:
|
|
*flagsOut = PAGE_PRESENT | PAGE_USER;
|
|
return NTLSTATUS_SUCCESS;
|
|
case SYS_VM_EXECUTE_READWRITE:
|
|
*flagsOut = PAGE_PRESENT | PAGE_USER | PAGE_WRITE;
|
|
return NTLSTATUS_SUCCESS;
|
|
default:
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
// Implements the page Flags To Vm Protect operation.
|
|
static uint32_t KiPageFlagsToVmProtect(uint64_t flags) {
|
|
if (!(flags & PAGE_PRESENT)) return SYS_VM_NOACCESS;
|
|
bool writable = (flags & PAGE_WRITE) != 0;
|
|
bool executable = (flags & PAGE_NX) == 0;
|
|
if (executable && writable) return SYS_VM_EXECUTE_READWRITE;
|
|
if (executable) return SYS_VM_EXECUTE_READ;
|
|
if (writable) return SYS_VM_READWRITE;
|
|
return SYS_VM_READ;
|
|
}
|
|
|
|
// Implements SYS_ALLOC_VM.
|
|
static uint64_t SyscallVmAlloc(const SYS_ALLOC_VM_REQUEST* request) {
|
|
SYS_ALLOC_VM_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Reserved != 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
void* preferredBase = req.PreferredBase;
|
|
size_t pageCount = req.PageCount;
|
|
uint32_t protect = req.Protect;
|
|
uint32_t flags = req.Flags;
|
|
void** baseOut = req.BaseOut;
|
|
|
|
if (!pageCount || !baseOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (pageCount > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY;
|
|
if (preferredBase || (flags & SYS_VM_FLAG_FIXED)) return NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
uint64_t pageFlags = 0;
|
|
s = KiVmProtectToPageFlags(protect, &pageFlags);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
|
|
void* base = (void*)0;
|
|
s = MmVadCreateCommitted(process, pageCount * PAGE_SIZE, PAGE_SIZE,
|
|
pageFlags,
|
|
MM_VAD_ZERO,
|
|
MmVadGeneric,
|
|
&base);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
s = CopyToUser(baseOut, &base, sizeof(base));
|
|
if (!KSUCCESS(s)) {
|
|
MmVadDestroy(process, base);
|
|
return s;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Releases one user virtual-memory allocation.
|
|
static uint64_t SyscallVmFree(void* base) {
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process || !base) return NTLSTATUS_INVALID_PARAMETER;
|
|
return MmVadDestroy(process, base);
|
|
}
|
|
|
|
// Changes page protection for one user virtual-memory range.
|
|
static uint64_t SyscallVmProtect(void* base, size_t pageCount, uint32_t protect, uint32_t* oldProtectOut) {
|
|
if (!base || !pageCount) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (pageCount > ((size_t)-1) / PAGE_SIZE) return NTLSTATUS_NO_MEMORY;
|
|
|
|
uint64_t pageFlags = 0;
|
|
KSTATUS s = KiVmProtectToPageFlags(protect, &pageFlags);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
|
|
if (oldProtectOut) {
|
|
void* regionBase = (void*)0;
|
|
size_t regionSize = 0;
|
|
uint64_t oldFlags = 0;
|
|
MM_VAD_TYPE type = MmVadGeneric;
|
|
uint32_t regionFlags = 0;
|
|
s = MmVadQuery(process, base, ®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)) != 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,
|
|
KE_USER_APC_FLAG_NONE);
|
|
if (!KSUCCESS(s)) {
|
|
DbgPrint("USER/APC async: KeQueueUserApc failed status=%u\n", s);
|
|
}
|
|
}
|
|
|
|
SyscallUserAsyncApcContextFree(ctx);
|
|
}
|
|
|
|
// Implements the async File Common operation.
|
|
static uint64_t SyscallAsyncFileCommon(const SYS_ASYNC_FILE_REQUEST* request, bool write) {
|
|
if (!request) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_ASYNC_FILE_REQUEST_K req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
if (req.Flags) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!req.Iosb) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!req.Buffer && req.Length) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) return NTLSTATUS_FAILURE;
|
|
|
|
if (req.RequestHandleOut) {
|
|
s = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut));
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(
|
|
req.FileHandle, ObjectTypeFile,
|
|
write ? SYS_FILE_RIGHT_WRITE_DATA : SYS_FILE_RIGHT_READ_DATA,
|
|
&fileHandleStatus);
|
|
if (!file) return fileHandleStatus;
|
|
|
|
EVENT* event = (EVENT*)0;
|
|
if (req.EventHandle) {
|
|
KSTATUS eventHandleStatus = NTLSTATUS_SUCCESS;
|
|
event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent,
|
|
SYS_EVENT_RIGHT_SIGNAL, &eventHandleStatus);
|
|
if (!event) {
|
|
ObDereferenceObject(&file->Header);
|
|
return eventHandleStatus;
|
|
}
|
|
}
|
|
|
|
KAPC* completionApc = (KAPC*)0;
|
|
SYS_USER_ASYNC_APC_CONTEXT* apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)0;
|
|
if (req.ApcRoutine) {
|
|
THREAD* thread = KeGetCurrentThread();
|
|
if (!thread || !thread->UserApcDispatcher) {
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)MmAllocatePoolTag(sizeof(SYS_USER_ASYNC_APC_CONTEXT), MM_POOL_TAG('u', 'a', 'p', 'c'));
|
|
if (!apcCtx) {
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
|
|
memset(apcCtx, 0, sizeof(*apcCtx));
|
|
ObReferenceObject(&thread->Header);
|
|
apcCtx->TargetThread = thread;
|
|
apcCtx->UserRoutine = req.ApcRoutine;
|
|
apcCtx->UserContext = req.ApcContext;
|
|
KeInitializeApc(&apcCtx->KernelApc,
|
|
thread,
|
|
SyscallUserAsyncKernelRoutine,
|
|
SyscallUserAsyncNormalRoutine,
|
|
apcCtx);
|
|
KeSetApcRundownRoutine(&apcCtx->KernelApc, SyscallUserAsyncApcRundown);
|
|
completionApc = &apcCtx->KernelApc;
|
|
}
|
|
|
|
MM_LOCKED_USER_BUFFER* lockedBuffer = (MM_LOCKED_USER_BUFFER*)0;
|
|
MM_LOCKED_USER_BUFFER* lockedIosb = (MM_LOCKED_USER_BUFFER*)0;
|
|
|
|
s = MmLockUserBuffer(proc, req.Iosb, sizeof(*req.Iosb), 1, &lockedIosb);
|
|
if (!KSUCCESS(s)) {
|
|
if (apcCtx)
|
|
SyscallUserAsyncApcContextFree(apcCtx);
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
|
|
if (req.Length) {
|
|
s = MmLockUserBuffer(proc, req.Buffer, req.Length, write ? 0 : 1, &lockedBuffer);
|
|
if (!KSUCCESS(s)) {
|
|
MmUnlockUserBuffer(lockedIosb);
|
|
if (apcCtx)
|
|
SyscallUserAsyncApcContextFree(apcCtx);
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
IO_ASYNC_FILE_REQUEST_OBJECT* asyncReq = (IO_ASYNC_FILE_REQUEST_OBJECT*)0;
|
|
if (write) {
|
|
s = IoWriteFileAsyncKernel(file, req.Buffer, req.Length, req.Offset,
|
|
(IO_STATUS_BLOCK*)req.Iosb, event,
|
|
completionApc,
|
|
lockedBuffer,
|
|
lockedIosb,
|
|
&asyncReq);
|
|
} else {
|
|
s = IoReadFileAsyncKernel(file, req.Buffer, req.Length, req.Offset,
|
|
(IO_STATUS_BLOCK*)req.Iosb, event,
|
|
completionApc,
|
|
lockedBuffer,
|
|
lockedIosb,
|
|
&asyncReq);
|
|
}
|
|
lockedBuffer = (MM_LOCKED_USER_BUFFER*)0;
|
|
lockedIosb = (MM_LOCKED_USER_BUFFER*)0;
|
|
|
|
if (s == NTLSTATUS_PENDING && req.RequestHandleOut && asyncReq) {
|
|
SYS_HANDLE requestHandle = SYS_HANDLE_INVALID;
|
|
KSTATUS hs = KeCreateHandle(proc,
|
|
IoAsyncFileRequestObject(asyncReq),
|
|
KeGetValidHandleRights(ObjectTypeAsyncIo),
|
|
&requestHandle);
|
|
if (!KSUCCESS(hs)) {
|
|
(void)IoCancelAsyncFileRequest(asyncReq);
|
|
ObDereferenceObject(IoAsyncFileRequestObject(asyncReq));
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return hs;
|
|
}
|
|
|
|
KSTATUS cs = CopyToUser(req.RequestHandleOut, &requestHandle, sizeof(requestHandle));
|
|
if (!KSUCCESS(cs)) {
|
|
(void)IoCancelAsyncFileRequest(asyncReq);
|
|
KeCloseHandle(proc, requestHandle);
|
|
ObDereferenceObject(IoAsyncFileRequestObject(asyncReq));
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return cs;
|
|
}
|
|
} else if (req.RequestHandleOut) {
|
|
SYS_HANDLE zeroHandle = SYS_HANDLE_INVALID;
|
|
KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle));
|
|
if (!KSUCCESS(cs)) {
|
|
if (asyncReq) ObDereferenceObject(IoAsyncFileRequestObject(asyncReq));
|
|
if (s != NTLSTATUS_PENDING && apcCtx)
|
|
SyscallUserAsyncApcContextFree(apcCtx);
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return cs;
|
|
}
|
|
}
|
|
|
|
if (asyncReq)
|
|
ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); // drop creator reference
|
|
|
|
if (s != NTLSTATUS_PENDING && apcCtx)
|
|
SyscallUserAsyncApcContextFree(apcCtx);
|
|
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_READ_FILE_ASYNC.
|
|
static uint64_t SyscallReadFileAsync(const SYS_ASYNC_FILE_REQUEST* request) {
|
|
return SyscallAsyncFileCommon(request, false);
|
|
}
|
|
|
|
// Implements SYS_WRITE_FILE_ASYNC.
|
|
static uint64_t SyscallWriteFileAsync(const SYS_ASYNC_FILE_REQUEST* request) {
|
|
return SyscallAsyncFileCommon(request, true);
|
|
}
|
|
|
|
// Implements SYS_CANCEL_IO.
|
|
static uint64_t SyscallCancelIo(SYS_HANDLE asyncRequestHandle) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
IO_ASYNC_REQUEST_OBJECT* req = (IO_ASYNC_REQUEST_OBJECT*)KiHandleToObject(
|
|
asyncRequestHandle, ObjectTypeAsyncIo, SYS_ASYNC_IO_RIGHT_CANCEL, &handleStatus);
|
|
if (!req) return handleStatus;
|
|
|
|
KSTATUS s = IoCancelAsyncRequest(req);
|
|
ObDereferenceObject(IoAsyncRequestObject(req));
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_TRUNCATE_FILE.
|
|
static uint64_t SyscallTruncateFile(SYS_HANDLE handle, uint64_t newSize) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile,
|
|
SYS_FILE_RIGHT_WRITE_DATA, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
KSTATUS s = IoTruncateFile(file, newSize);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_FLUSH_FILE.
|
|
static uint64_t SyscallFlushFile(SYS_HANDLE handle) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile,
|
|
SYS_FILE_RIGHT_WRITE_DATA, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
KSTATUS s = IoFlushFile(file);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_GET_FILE_POS.
|
|
static uint64_t SyscallGetFilePos(SYS_HANDLE handle, uint64_t* positionOut) {
|
|
if (!positionOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&file->Lock);
|
|
uint64_t pos = file->Offset;
|
|
KeReleaseSpinlock(&file->Lock, flags);
|
|
|
|
ObDereferenceObject(&file->Header);
|
|
return KiCopyU64ToUser(positionOut, pos);
|
|
}
|
|
|
|
// Implements SYS_SEEK_FILE.
|
|
static uint64_t SyscallSeekFile(SYS_HANDLE handle, uint64_t offset) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
KSTATUS s = IoSeekFile(file, offset);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_GET_FILE_SIZE.
|
|
static uint64_t SyscallGetFileSize(SYS_HANDLE handle, uint64_t* sizeOut) {
|
|
if (!sizeOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
|
|
uint64_t sz = IoGetFileSize(file);
|
|
ObDereferenceObject(&file->Header);
|
|
return KiCopyU64ToUser(sizeOut, sz);
|
|
}
|
|
|
|
// Implements SYS_GET_FILE_STAT.
|
|
static uint64_t SyscallGetFileStat(SYS_HANDLE handle, SYS_FILE_STAT* statOut) {
|
|
if (!statOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(handle, ObjectTypeFile, SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
|
|
SYS_FILE_STAT stat;
|
|
KSTATUS s = IoGetFileStat(file, &stat);
|
|
ObDereferenceObject(&file->Header);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
return CopyToUser(statOut, &stat, sizeof(stat));
|
|
}
|
|
|
|
// Implements SYS_OPEN_DISPLAY_CLOCK.
|
|
static uint64_t SyscallOpenDisplayClock(const SYS_OPEN_DISPLAY_CLOCK_REQUEST* request) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
SYS_OPEN_DISPLAY_CLOCK_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Flags != SYS_DISPLAY_CLOCK_FLAG_NONE || !req.HandleOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
DISPLAY_CLOCK_OBJECT* clock = (DISPLAY_CLOCK_OBJECT*)0;
|
|
s = KeDisplayOpenClock(req.PeriodMilliseconds, &clock);
|
|
if (!KSUCCESS(s))
|
|
return s;
|
|
|
|
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
|
s = KiAllocHandleForCurrentWithRights(&clock->Header, KeGetValidHandleRights(ObjectTypeDisplayClock), &handle);
|
|
if (!KSUCCESS(s))
|
|
return s;
|
|
|
|
s = KiCopyHandleToUser(req.HandleOut, handle);
|
|
if (!KSUCCESS(s)) {
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (proc) KeCloseHandle(proc, handle);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Copies one optional surface rectangle list from user memory. A zero count means the whole surface.
|
|
static KSTATUS KiCaptureUserSurfaceRects(const DISPLAY_RECT* userRects, uint32_t rectCount,
|
|
DISPLAY_RECT* rectsOut) {
|
|
if (rectCount > SYS_SURFACE_MAX_RECTS || (rectCount && (!userRects || !rectsOut)))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!rectCount) return NTLSTATUS_SUCCESS;
|
|
return CopyFromUser(rectsOut, userRects, (size_t)rectCount * sizeof(DISPLAY_RECT));
|
|
}
|
|
|
|
// Implements SYS_CREATE_SURFACE by creating storage and returning its root capability.
|
|
static uint64_t SyscallCreateSurface(const SYS_CREATE_SURFACE_REQUEST* request) {
|
|
SYS_CREATE_SURFACE_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (!req.HandleOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
OBJECT* surface = (OBJECT*)0;
|
|
status = KeSurfaceCreate(req.Width, req.Height, req.Format, req.Flags, &surface);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
|
status = KiAllocHandleForCurrentWithRights(surface, KeGetValidHandleRights(ObjectTypeSurface), &handle);
|
|
if (!KSUCCESS(status)) return status;
|
|
status = CopyToUser(req.HandleOut, &handle, sizeof(handle));
|
|
if (!KSUCCESS(status))
|
|
KeCloseHandle(KeGetCurrentProcess(), handle);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_MAP_SURFACE. Each process receives its own VAD over shared backing frames.
|
|
static uint64_t SyscallMapSurface(SYS_HANDLE surfaceHandle, uint32_t flags,
|
|
void** baseOut, uint32_t* pitchBytesOut) {
|
|
if (!baseOut || !pitchBytesOut || !(flags & SYS_SURFACE_MAP_READ) ||
|
|
(flags & ~(SYS_SURFACE_MAP_READ | SYS_SURFACE_MAP_WRITE)))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_RIGHTS rights = SYS_SURFACE_RIGHT_MAP_READ;
|
|
if (flags & SYS_SURFACE_MAP_WRITE) rights |= SYS_SURFACE_RIGHT_MAP_WRITE;
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface, rights, &handleStatus);
|
|
if (!surface) return handleStatus;
|
|
|
|
void* userBase = (void*)0;
|
|
uint32_t pitchBytes = 0;
|
|
KSTATUS status = KeSurfaceMap(KeGetCurrentProcess(), surface, flags, &userBase, &pitchBytes);
|
|
if (KSUCCESS(status)) status = CopyToUser(pitchBytesOut, &pitchBytes, sizeof(pitchBytes));
|
|
if (KSUCCESS(status)) status = CopyToUser(baseOut, &userBase, sizeof(userBase));
|
|
if (!KSUCCESS(status) && userBase)
|
|
(void)KeSurfaceUnmap(KeGetCurrentProcess(), userBase);
|
|
ObDereferenceObject(surface);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_UNMAP_SURFACE without accepting arbitrary VADs as surface mappings.
|
|
static uint64_t SyscallUnmapSurface(void* base) {
|
|
return KeSurfaceUnmap(KeGetCurrentProcess(), base);
|
|
}
|
|
|
|
// Implements SYS_QUERY_SURFACE.
|
|
static uint64_t SyscallQuerySurface(SYS_HANDLE surfaceHandle, SYS_SURFACE_DESC* descOut) {
|
|
if (!descOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface, SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!surface) return handleStatus;
|
|
|
|
SYS_SURFACE_DESC desc;
|
|
KSTATUS status = KeSurfaceQuery(surface, &desc);
|
|
ObDereferenceObject(surface);
|
|
if (!KSUCCESS(status)) return status;
|
|
return CopyToUser(descOut, &desc, sizeof(desc));
|
|
}
|
|
|
|
// Implements SYS_COMMIT_SURFACE. Commit announces new pixels and never touches scanout.
|
|
static uint64_t SyscallCommitSurface(SYS_HANDLE surfaceHandle, const DISPLAY_RECT* userRects,
|
|
uint32_t rectCount, uint64_t* generationOut) {
|
|
DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS];
|
|
KSTATUS status = KiCaptureUserSurfaceRects(userRects, rectCount, rects);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface,
|
|
SYS_SURFACE_RIGHT_COMMIT, &handleStatus);
|
|
if (!surface) return handleStatus;
|
|
|
|
uint64_t generation = 0;
|
|
status = KeSurfaceCommit(surface, rectCount ? rects : (DISPLAY_RECT*)0, rectCount, &generation);
|
|
ObDereferenceObject(surface);
|
|
if (KSUCCESS(status) && generationOut)
|
|
status = CopyToUser(generationOut, &generation, sizeof(generation));
|
|
return status;
|
|
}
|
|
|
|
// Clears the legacy text-console display after its caller has already been authorized.
|
|
static KSTATUS KiConsoleClear(uint32_t color) {
|
|
return KeDisplayClear(color);
|
|
}
|
|
|
|
// Returns the current console geometry and cursor state after authorization.
|
|
static KSTATUS KiConsoleQueryInfo(SYS_CONSOLE_INFO* infoOut) {
|
|
if (!infoOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KE_CONSOLE_INFO ki;
|
|
KSTATUS status = KeDisplayGetConsoleInfo(&ki);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_CONSOLE_INFO out;
|
|
memset(&out, 0, sizeof(out));
|
|
out.Columns = ki.Columns;
|
|
out.Rows = ki.Rows;
|
|
out.CursorX = ki.CursorX;
|
|
out.CursorY = ki.CursorY;
|
|
out.Attribute = ki.Attribute;
|
|
return CopyToUser(infoOut, &out, sizeof(out));
|
|
}
|
|
|
|
// Updates the legacy text-console cursor after authorization.
|
|
static KSTATUS KiConsoleSetCursor(int32_t x, int32_t y) {
|
|
if (x < 0 || y < 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
return KeDisplaySetConsoleCursor(x, y);
|
|
}
|
|
|
|
// Draws one console glyph at explicit pixel coordinates after authorization.
|
|
static KSTATUS KiConsolePutCharacter(const SYS_PUT_SCREEN_CHAR_REQUEST* request) {
|
|
SYS_PUT_SCREEN_CHAR_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2] || req.X < 0 || req.Y < 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
return KeDisplayPutChar(req.Character, req.X, req.Y, req.Foreground, req.Background);
|
|
}
|
|
|
|
// Fills console characters over one linear range after authorization.
|
|
static KSTATUS KiConsoleFillCharacter(const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) {
|
|
SYS_FILL_CONSOLE_CHARACTER_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Reserved[0] || req.Reserved[1] || req.Reserved[2] || req.X < 0 || req.Y < 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
size_t written = 0;
|
|
status = KeDisplayFillConsoleChars(req.Character, req.X, req.Y, req.Length, &written);
|
|
if (req.WrittenOut) {
|
|
KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written);
|
|
if (KSUCCESS(status) && !KSUCCESS(copyStatus)) status = copyStatus;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Fills console attributes over one linear range after authorization.
|
|
static KSTATUS KiConsoleFillAttribute(const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) {
|
|
SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Reserved != 0 || req.X < 0 || req.Y < 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
size_t written = 0;
|
|
status = KeDisplayFillConsoleAttrs(req.Attribute, req.X, req.Y, req.Length, &written);
|
|
if (req.WrittenOut) {
|
|
KSTATUS copyStatus = KiCopySizeToUser(req.WrittenOut, written);
|
|
if (KSUCCESS(status) && !KSUCCESS(copyStatus)) status = copyStatus;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_CLEAR_SCREEN through the legacy ambient console privilege.
|
|
static uint64_t SyscallClearScreen(uint32_t color) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiConsoleClear(color);
|
|
}
|
|
|
|
// Implements SYS_GET_SCREEN_SIZE.
|
|
static uint64_t SyscallGetScreenSize(int32_t* widthOut, int32_t* heightOut) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
int32_t w = 0, h = 0;
|
|
KSTATUS gs = KeDisplayGetSize(&w, &h);
|
|
if (!KSUCCESS(gs)) return gs;
|
|
if (widthOut) {
|
|
KSTATUS s = KiCopyInt32ToUser(widthOut, w);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
if (heightOut) {
|
|
KSTATUS s = KiCopyInt32ToUser(heightOut, h);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements SYS_PUT_SCREEN_CHAR through the legacy ambient console privilege.
|
|
static uint64_t SyscallPutScreenChar(const SYS_PUT_SCREEN_CHAR_REQUEST* request) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiConsolePutCharacter(request);
|
|
}
|
|
|
|
// Implements SYS_GET_CONSOLE_INFO through the legacy ambient console privilege.
|
|
static uint64_t SyscallGetConsoleInfo(SYS_CONSOLE_INFO* infoOut) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiConsoleQueryInfo(infoOut);
|
|
}
|
|
|
|
// Implements SYS_SET_CONSOLE_CURSOR through the legacy ambient console privilege.
|
|
static uint64_t SyscallSetConsoleCursor(int32_t x, int32_t y) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiConsoleSetCursor(x, y);
|
|
}
|
|
|
|
// Implements SYS_FILL_CONSOLE_CHARACTER through the legacy ambient console privilege.
|
|
static uint64_t SyscallFillConsoleCharacter(const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiConsoleFillCharacter(request);
|
|
}
|
|
|
|
// Implements SYS_FILL_CONSOLE_ATTRIBUTE through the legacy ambient console privilege.
|
|
static uint64_t SyscallFillConsoleAttribute(const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiConsoleFillAttribute(request);
|
|
}
|
|
|
|
// Implements the capability-scoped raw input-manager stream used by RSSRV.
|
|
static uint64_t SyscallReadInputManagerEvent(const SYS_INPUT_MANAGER_READ_REQUEST* request) {
|
|
SYS_INPUT_MANAGER_READ_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Flags != SYS_INPUT_MANAGER_EVENT_FLAG_NONE || !req.EventOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_INPUT_MANAGER* manager = (KE_INPUT_MANAGER*)KiHandleToObject(
|
|
req.ManagerHandle, ObjectTypeInputManager, SYS_INPUT_MANAGER_RIGHT_READ, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
|
|
SYS_INPUT_MANAGER_EVENT event;
|
|
status = KeInputManagerReadEvent(manager, req.TimeoutMilliseconds, &event);
|
|
ObDereferenceObject(&manager->Header);
|
|
if (!KSUCCESS(status)) return status;
|
|
return CopyToUser(req.EventOut, &event, sizeof(event));
|
|
}
|
|
|
|
// 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 (NtlAtomicLoad32(&process->BuildState) != KE_PROCESS_BUILD_CONSTRUCTING)
|
|
return NTLSTATUS_ACCESS_DENIED;
|
|
NtlAtomicIncrement32(&process->BuildOperations);
|
|
if (NtlAtomicLoad32(&process->BuildState) == KE_PROCESS_BUILD_CONSTRUCTING)
|
|
return NTLSTATUS_SUCCESS;
|
|
NtlAtomicDecrement32(&process->BuildOperations);
|
|
}
|
|
}
|
|
|
|
// Ends one process-construction memory operation.
|
|
static void KiEndProcessBuildOperation(PROCESS* process) {
|
|
if (!process) return;
|
|
int32_t remaining = NtlAtomicDecrement32(&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)) {
|
|
bool terminating = KeIsProcessTerminating(child);
|
|
ObDereferenceObject(&child->Header);
|
|
return terminating ? NTLSTATUS_OBJECT_DEAD : status;
|
|
}
|
|
buildActive = 1;
|
|
THREAD* thread = (THREAD*)0;
|
|
|
|
if (NtlAtomicCompareExchange32(&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 (NtlAtomicLoad32(&child->BuildOperations) != 1) {
|
|
(void)NtlAtomicCompareExchange32(&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 (!SysStartupCapabilityNameValid(capabilities[i].Name) || capabilities[i].Handle == SYS_HANDLE_INVALID) {
|
|
status = NTLSTATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
for (uint32_t j = 0; j < i; ++j) {
|
|
if (strcmp(capabilities[j].Name, capabilities[i].Name) == 0) {
|
|
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_NAMED_NORMAL(PriorityNormal, child->Name);
|
|
parameters.Flags = KE_THREAD_CREATE_FLAG_SUSPENDED;
|
|
status = KeCreateThread(child, req.EntryPoint, 0, ¶meters, &thread);
|
|
if (KSUCCESS(status)) {
|
|
if (NtlAtomicCompareExchange32(&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)NtlAtomicCompareExchange32(&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 || (desiredAccess & SYS_PROCESS_RIGHT_SECURITY_CONTEXT) != 0) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_OPEN_ANY);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
}
|
|
|
|
KSTATUS s = MmProbeUserWrite(handleOut, sizeof(SYS_HANDLE));
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
PROCESS* proc = KeGetProcessById(pid);
|
|
if (!proc) return NTLSTATUS_NOT_FOUND;
|
|
|
|
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
|
s = KeCreateHandle(caller,
|
|
&proc->Header,
|
|
desiredAccess,
|
|
&handle);
|
|
ObDereferenceObject(&proc->Header);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
s = KiCopyHandleToUser(handleOut, handle);
|
|
if (!KSUCCESS(s)) {
|
|
KeCloseHandle(caller, handle);
|
|
return s;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Copies the process exit code.
|
|
static uint64_t SyscallGetProcessExitCode(SYS_HANDLE handle, int32_t* exitCodeOut) {
|
|
if (!exitCodeOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
PROCESS* proc = (PROCESS*)KiHandleToObject(handle, ObjectTypeProcess, SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!proc) return handleStatus;
|
|
|
|
int32_t exitCode = 0;
|
|
KSTATUS s = KeGetProcessExitCode(proc, &exitCode);
|
|
ObDereferenceObject(&proc->Header);
|
|
if (!KSUCCESS(s)) return s;
|
|
return KiCopyInt32ToUser(exitCodeOut, exitCode);
|
|
}
|
|
|
|
// Writes one user text span to the legacy kernel console after authorization.
|
|
static KSTATUS KiConsoleWrite(const SYS_WRITE_CONSOLE_REQUEST* request) {
|
|
SYS_WRITE_CONSOLE_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Text.Length == 0) return NTLSTATUS_SUCCESS;
|
|
if (!req.Text.Data) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Text.Length > SYSCALL_TEXT_MAX_BYTES) return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
|
|
char chunk[512];
|
|
size_t offset = 0;
|
|
while (offset < req.Text.Length) {
|
|
size_t todo = req.Text.Length - offset;
|
|
if (todo > sizeof(chunk)) todo = sizeof(chunk);
|
|
status = CopyFromUser(chunk, req.Text.Data + offset, todo);
|
|
if (!KSUCCESS(status)) return status;
|
|
KePrintConsole(chunk, todo);
|
|
offset += todo;
|
|
}
|
|
return KeDisplayFlushConsoleOutput();
|
|
}
|
|
|
|
// Implements SYS_WRITE_CONSOLE through the legacy ambient console privilege.
|
|
static uint64_t SyscallWriteConsole(const SYS_WRITE_CONSOLE_REQUEST* request) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_CONSOLE_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiConsoleWrite(request);
|
|
}
|
|
|
|
// Implements SYS_READ_DIR.
|
|
// Returns NTLSTATUS_NO_MORE_FILES when the directory is exhausted.
|
|
static uint64_t SyscallReadDir(SYS_HANDLE handle, SYS_DIR_ENTRY* entryOut) {
|
|
if (!entryOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
DIR_OBJECT* dir = (DIR_OBJECT*)KiHandleToObject(handle, ObjectTypeDir,
|
|
SYS_DIR_RIGHT_ENUMERATE, &handleStatus);
|
|
if (!dir) return handleStatus;
|
|
|
|
KDIR_ENTRY entry;
|
|
KSTATUS s = IoReadDir(dir, &entry);
|
|
ObDereferenceObject(&dir->Header);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
return CopyToUser(entryOut, &entry, sizeof(entry));
|
|
}
|
|
|
|
// Implements SYS_GET_PROCESS_PID.
|
|
// Returns the numeric PID of a process identified by handle.
|
|
// Used by posix_spawn to map the kernel handle back to a pid_t for waitpid.
|
|
static uint64_t SyscallGetProcessPid(SYS_HANDLE handle, uint32_t* pidOut) {
|
|
if (!pidOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
PROCESS* proc = (PROCESS*)KiHandleToObject(handle, ObjectTypeProcess, SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!proc) return handleStatus;
|
|
|
|
uint32_t pid = proc->ProcessId;
|
|
ObDereferenceObject(&proc->Header);
|
|
return KiCopyU32ToUser(pidOut, pid);
|
|
}
|
|
|
|
// Implements SYS_CREATE_PORT.
|
|
static uint64_t SyscallCreatePort(const SYS_CREATE_PORT_REQUEST* request) {
|
|
SYS_CREATE_PORT_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Flags != SYS_PORT_FLAG_NONE || req.Reserved != 0 || !req.HandleOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_RIGHTS validPeerRights = KeGetValidHandleRights(ObjectTypeProcess);
|
|
if ((req.PeerProcessRights & ~validPeerRights) != 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
status = MmProbeUserWrite(req.HandleOut, sizeof(*req.HandleOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
PORT* port = (PORT*)0;
|
|
status = KePortCreate(req.Backlog, req.PeerProcessRights, &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_OPEN_PORT_PEER_PROCESS.
|
|
static uint64_t SyscallOpenPortPeerProcess(SYS_HANDLE endpointHandle,
|
|
SYS_RIGHTS desiredRights,
|
|
SYS_HANDLE* processHandleOut) {
|
|
if (!processHandleOut || desiredRights == 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_RIGHTS validRights = KeGetValidHandleRights(ObjectTypeProcess);
|
|
if ((desiredRights & ~validRights) != 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS status = MmProbeUserWrite(processHandleOut, sizeof(*processHandleOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
PORT* endpoint = (PORT*)KiHandleToObject(endpointHandle,
|
|
ObjectTypePort,
|
|
SYS_PORT_RIGHT_OPEN_PEER_PROCESS,
|
|
&handleStatus);
|
|
if (!endpoint) return handleStatus;
|
|
|
|
PROCESS* caller = KeGetCurrentProcess();
|
|
SYS_HANDLE processHandle = SYS_HANDLE_INVALID;
|
|
status = caller ? KePortOpenPeerProcess(endpoint, caller, desiredRights, &processHandle)
|
|
: NTLSTATUS_FAILURE;
|
|
ObDereferenceObject((OBJECT*)endpoint);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
status = KiCopyHandleToUser(processHandleOut, processHandle);
|
|
if (!KSUCCESS(status) && caller) (void)KeCloseHandle(caller, processHandle);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_REGISTER_SYSTEM_SERVICE using a typed, attenuable registration authority.
|
|
static uint64_t SyscallRegisterSystemService(SYS_HANDLE registrationHandle, SYS_HANDLE listenerHandle) {
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
|
|
OBJECT* authority = (OBJECT*)0;
|
|
KSTATUS status = KeReferenceHandle(process, registrationHandle, ObjectTypeServiceRegistration,
|
|
SYS_SERVICE_REGISTRATION_RIGHT_REGISTER, &authority);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_SERVICE_ID serviceId = SYS_SERVICE_INVALID;
|
|
status = KeSystemServiceRegistrationIdentity(authority, &serviceId);
|
|
ObDereferenceObject(authority);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
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 using the same typed authority.
|
|
static uint64_t SyscallUnregisterSystemService(SYS_HANDLE registrationHandle, SYS_HANDLE listenerHandle) {
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (!process) return NTLSTATUS_FAILURE;
|
|
|
|
OBJECT* authority = (OBJECT*)0;
|
|
KSTATUS status = KeReferenceHandle(process, registrationHandle, ObjectTypeServiceRegistration,
|
|
SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER, &authority);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_SERVICE_ID serviceId = SYS_SERVICE_INVALID;
|
|
status = KeSystemServiceRegistrationIdentity(authority, &serviceId);
|
|
ObDereferenceObject(authority);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
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);
|
|
}
|
|
|
|
// 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,
|
|
&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_PROCESS_SECURITY_CONTEXT.
|
|
static uint64_t SyscallOpenProcessSecurityContext(SYS_HANDLE processHandle,
|
|
SYS_RIGHTS desiredRights,
|
|
SYS_HANDLE* contextHandleOut) {
|
|
if (!contextHandleOut || desiredRights == 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_RIGHTS validRights = KeGetValidHandleRights(ObjectTypeSecurityContext);
|
|
if ((desiredRights & ~validRights) != 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS status = MmProbeUserWrite(contextHandleOut, sizeof(*contextHandleOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
PROCESS* target = (PROCESS*)KiHandleToObject(processHandle,
|
|
ObjectTypeProcess,
|
|
SYS_PROCESS_RIGHT_SECURITY_CONTEXT,
|
|
&status);
|
|
if (!target) return status;
|
|
SECURITY_CONTEXT* context = target->SecurityContext;
|
|
if (!context || !ObTryReferenceObject(&context->Header)) {
|
|
ObDereferenceObject(&target->Header);
|
|
return NTLSTATUS_OBJECT_DEAD;
|
|
}
|
|
ObDereferenceObject(&target->Header);
|
|
|
|
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
|
status = KiAllocHandleForCurrentWithRights(&context->Header, desiredRights, &handle);
|
|
if (!KSUCCESS(status)) return status;
|
|
status = KiCopyHandleToUser(contextHandleOut, handle);
|
|
if (!KSUCCESS(status)) {
|
|
PROCESS* caller = KeGetCurrentProcess();
|
|
if (caller) (void)KeCloseHandle(caller, handle);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Terminates the process referenced by one process handle.
|
|
static uint64_t SyscallKillProcess(SYS_HANDLE processHandle, int32_t exitCode) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
PROCESS* proc = (PROCESS*)KiHandleToObject(processHandle, ObjectTypeProcess,
|
|
SYS_PROCESS_RIGHT_TERMINATE, &handleStatus);
|
|
if (!proc) return handleStatus;
|
|
|
|
THREAD* self = KeGetCurrentThread();
|
|
if (self && self->Owner == proc) {
|
|
KSTATUS s = KeExitProcess(proc, exitCode);
|
|
ObDereferenceObject(&proc->Header);
|
|
if (!KSUCCESS(s)) return s;
|
|
KeExitThread(self, exitCode);
|
|
while (true) ;
|
|
}
|
|
|
|
KSTATUS s = KeTerminateProcess(proc, exitCode);
|
|
ObDereferenceObject(&proc->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_GET_PROCESS_LIST.
|
|
static uint64_t SyscallGetProcessList(SYS_PROCESS_INFO* processesOut, size_t maxCount, size_t* countOut) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_ENUMERATE);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
|
|
if (maxCount && !processesOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (countOut) {
|
|
KSTATUS s = MmProbeUserWrite(countOut, sizeof(size_t));
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
size_t total = KeCopyProcessInfo(NULL, 0);
|
|
size_t toCopy = total < maxCount ? total : maxCount;
|
|
|
|
if (toCopy) {
|
|
size_t bytes = 0;
|
|
if (!KiMulSize(toCopy, sizeof(SYS_PROCESS_INFO), &bytes)) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_PROCESS_INFO* tmp = (SYS_PROCESS_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 'y', 's', ' '));
|
|
if (!tmp) return NTLSTATUS_NO_MEMORY;
|
|
|
|
size_t copied = KeCopyProcessInfo(tmp, toCopy);
|
|
if (copied < toCopy) toCopy = copied;
|
|
|
|
KSTATUS s = CopyToUser(processesOut, tmp, toCopy * sizeof(SYS_PROCESS_INFO));
|
|
MmFreePool(tmp);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
if (countOut) return KiCopySizeToUser(countOut, total);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements SYS_GET_THREAD_LIST.
|
|
static uint64_t SyscallGetThreadList(uint32_t pid, SYS_THREAD_INFO* threadsOut, size_t maxCount, size_t* countOut) {
|
|
PROCESS* caller = KeGetCurrentProcess();
|
|
if (!caller) return NTLSTATUS_FAILURE;
|
|
if (pid != caller->ProcessId) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_THREAD_ENUMERATE_ANY);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
}
|
|
|
|
if (maxCount && !threadsOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (countOut) {
|
|
KSTATUS s = MmProbeUserWrite(countOut, sizeof(size_t));
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
size_t total = 0;
|
|
KSTATUS s = KeCopyProcessThreadInfo(pid, NULL, 0, &total);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
size_t toCopy = total < maxCount ? total : maxCount;
|
|
if (toCopy) {
|
|
size_t bytes = 0;
|
|
if (!KiMulSize(toCopy, sizeof(SYS_THREAD_INFO), &bytes)) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_THREAD_INFO* tmp = (SYS_THREAD_INFO*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 't', 'h', 'r'));
|
|
if (!tmp) return NTLSTATUS_NO_MEMORY;
|
|
|
|
size_t copied = 0;
|
|
s = KeCopyProcessThreadInfo(pid, tmp, toCopy, &copied);
|
|
if (KSUCCESS(s)) {
|
|
if (copied < toCopy) toCopy = copied;
|
|
s = CopyToUser(threadsOut, tmp, toCopy * sizeof(SYS_THREAD_INFO));
|
|
}
|
|
MmFreePool(tmp);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
if (countOut) return KiCopySizeToUser(countOut, total);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
// Implements SYS_LOCK_FILE.
|
|
static uint64_t SyscallLockFile(const SYS_LOCK_FILE_REQUEST* request) {
|
|
SYS_LOCK_FILE_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Mode != SYS_FILE_LOCK_SHARED && req.Mode != SYS_FILE_LOCK_EXCLUSIVE) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) return NTLSTATUS_FAILURE;
|
|
|
|
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
|
|
SYS_FILE_RIGHT_LOCK, &fileHandleStatus);
|
|
if (!file) return fileHandleStatus;
|
|
|
|
s = IoLockFile(file, proc, req.Mode, req.Offset, req.Length, req.TimeoutMilliseconds);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_UNLOCK_FILE.
|
|
static uint64_t SyscallUnlockFile(const SYS_UNLOCK_FILE_REQUEST* request) {
|
|
SYS_UNLOCK_FILE_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length == 0) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) return NTLSTATUS_FAILURE;
|
|
|
|
KSTATUS fileHandleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
|
|
SYS_FILE_RIGHT_LOCK, &fileHandleStatus);
|
|
if (!file) return fileHandleStatus;
|
|
|
|
s = IoUnlockFile(file, proc, req.Offset, req.Length);
|
|
ObDereferenceObject(&file->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_QUERY_FILE_LOCK.
|
|
static uint64_t SyscallQueryFileLock(const SYS_QUERY_FILE_LOCK_REQUEST* request) {
|
|
SYS_QUERY_FILE_LOCK_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Flags != SYS_FILE_LOCK_FLAG_NONE || !req.InfoOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Mode != SYS_FILE_LOCK_SHARED && req.Mode != SYS_FILE_LOCK_EXCLUSIVE)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length == 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
status = MmProbeUserWrite(req.InfoOut, sizeof(*req.InfoOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc) return NTLSTATUS_FAILURE;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
|
|
SYS_FILE_RIGHT_LOCK, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
|
|
SYS_FILE_LOCK_INFO info;
|
|
status = IoQueryFileLock(file, proc, req.Mode, req.Offset, req.Length, &info);
|
|
ObDereferenceObject(&file->Header);
|
|
if (!KSUCCESS(status)) return status;
|
|
return CopyToUser(req.InfoOut, &info, sizeof(info));
|
|
}
|
|
|
|
// Implements SYS_SET_FILE_STATUS.
|
|
static uint64_t SyscallSetFileStatus(const SYS_SET_FILE_STATUS_REQUEST* request) {
|
|
SYS_SET_FILE_STATUS_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Mask & ~SYS_FILE_STATUS_APPEND)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Flags & ~req.Mask)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile,
|
|
SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!file) return handleStatus;
|
|
status = IoSetFileStatus(file, req.Mask, req.Flags);
|
|
ObDereferenceObject(&file->Header);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_CREATE_PIPE. The syscall creates endpoint capabilities and one
|
|
// process-owned shared data-plane view; ordinary reads and writes stay in user mode.
|
|
static uint64_t SyscallCreatePipe(const SYS_CREATE_PIPE_REQUEST* request) {
|
|
SYS_CREATE_PIPE_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Flags != 0 || !req.ReadHandleOut || !req.WriteHandleOut || !req.SharedStateOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
status = MmProbeUserWrite(req.ReadHandleOut, sizeof(*req.ReadHandleOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
status = MmProbeUserWrite(req.WriteHandleOut, sizeof(*req.WriteHandleOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
status = MmProbeUserWrite(req.SharedStateOut, sizeof(*req.SharedStateOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
IO_PIPE_ENDPOINT* readEnd = (IO_PIPE_ENDPOINT*)0;
|
|
IO_PIPE_ENDPOINT* writeEnd = (IO_PIPE_ENDPOINT*)0;
|
|
status = IoCreatePipe(&readEnd, &writeEnd);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
void* sharedState = (void*)0;
|
|
status = IoMapPipe(readEnd, proc, &sharedState);
|
|
if (!KSUCCESS(status)) {
|
|
ObDereferenceObject((OBJECT*)readEnd);
|
|
ObDereferenceObject((OBJECT*)writeEnd);
|
|
return status;
|
|
}
|
|
|
|
SYS_HANDLE readHandle = SYS_HANDLE_INVALID;
|
|
SYS_HANDLE writeHandle = SYS_HANDLE_INVALID;
|
|
SYS_RIGHTS readRights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
|
|
SYS_PIPE_RIGHT_READ | SYS_PIPE_RIGHT_POLL;
|
|
SYS_RIGHTS writeRights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT |
|
|
SYS_PIPE_RIGHT_WRITE | SYS_PIPE_RIGHT_POLL;
|
|
|
|
status = KiAllocHandleForCurrentWithRights((OBJECT*)readEnd, readRights, &readHandle);
|
|
if (!KSUCCESS(status)) {
|
|
ObDereferenceObject((OBJECT*)writeEnd);
|
|
(void)MmVadDestroy(proc, sharedState);
|
|
return status;
|
|
}
|
|
status = KiAllocHandleForCurrentWithRights((OBJECT*)writeEnd, writeRights, &writeHandle);
|
|
if (!KSUCCESS(status)) {
|
|
if (proc) KeCloseHandle(proc, readHandle);
|
|
(void)MmVadDestroy(proc, sharedState);
|
|
return status;
|
|
}
|
|
|
|
status = KiCopyHandleToUser(req.ReadHandleOut, readHandle);
|
|
if (KSUCCESS(status)) status = KiCopyHandleToUser(req.WriteHandleOut, writeHandle);
|
|
if (KSUCCESS(status)) status = CopyToUser(req.SharedStateOut, &sharedState, sizeof(sharedState));
|
|
if (!KSUCCESS(status)) {
|
|
if (sharedState && proc)
|
|
(void)MmVadDestroy(proc, sharedState);
|
|
if (proc) {
|
|
KeCloseHandle(proc, readHandle);
|
|
KeCloseHandle(proc, writeHandle);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_NOTIFY_PIPE. User mode calls this only when a shared waiter
|
|
// count says a data-plane transition may need scheduler attention.
|
|
static uint64_t SyscallNotifyPipe(SYS_HANDLE pipeHandle) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
IO_PIPE_ENDPOINT* endpoint = (IO_PIPE_ENDPOINT*)KiHandleToObject(pipeHandle,
|
|
ObjectTypePipeEndpoint,
|
|
SYS_RIGHT_WAIT,
|
|
&handleStatus);
|
|
if (!endpoint) return handleStatus;
|
|
KSTATUS status = IoNotifyPipe(endpoint);
|
|
ObDereferenceObject((OBJECT*)endpoint);
|
|
return status;
|
|
}
|
|
|
|
typedef struct SYS_SOCKET_SEND_TO_REQUEST_K {
|
|
uint32_t Size;
|
|
const void* Buffer;
|
|
size_t Length;
|
|
uint32_t Flags;
|
|
const SYS_SOCKET_ADDRESS_K* RemoteAddress;
|
|
size_t RemoteAddressSize;
|
|
size_t* SentOut;
|
|
} SYS_SOCKET_SEND_TO_REQUEST_K;
|
|
|
|
typedef struct SYS_SOCKET_RECV_FROM_REQUEST_K {
|
|
uint32_t Size;
|
|
void* Buffer;
|
|
size_t Length;
|
|
uint32_t Flags;
|
|
SYS_SOCKET_ADDRESS_K* RemoteAddressOut;
|
|
size_t* RemoteAddressSizeInOut;
|
|
size_t* ReceivedOut;
|
|
} SYS_SOCKET_RECV_FROM_REQUEST_K;
|
|
|
|
typedef struct SYS_RESOLVE_HOST_REQUEST_K {
|
|
uint32_t Size;
|
|
SYS_STRING_VIEW Name;
|
|
uint32_t Family;
|
|
uint32_t Flags;
|
|
SYS_SOCKET_ADDRESS_K* AddressOut;
|
|
size_t* AddressSizeInOut;
|
|
} SYS_RESOLVE_HOST_REQUEST_K;
|
|
|
|
typedef struct SYS_SOCKET_OPTION_REQUEST_K {
|
|
uint32_t Size;
|
|
uint32_t Option;
|
|
void* Value;
|
|
size_t ValueSize;
|
|
size_t* ActualSizeOut;
|
|
} SYS_SOCKET_OPTION_REQUEST_K;
|
|
|
|
typedef struct SYS_ASYNC_SOCKET_REQUEST_K {
|
|
uint32_t Size;
|
|
SYS_HANDLE SocketHandle;
|
|
SYS_HANDLE EventHandle;
|
|
uint32_t Operation;
|
|
uint32_t Flags;
|
|
uint32_t SocketFlags;
|
|
uint32_t Backlog;
|
|
uint32_t ShutdownHow;
|
|
uint32_t Option;
|
|
uint32_t PollEvents;
|
|
|
|
void* Buffer;
|
|
size_t Length;
|
|
|
|
const SYS_SOCKET_ADDRESS_K* Address;
|
|
size_t AddressSize;
|
|
SYS_SOCKET_ADDRESS_K* AddressOut;
|
|
size_t* AddressSizeInOut;
|
|
|
|
void* OptionValue;
|
|
size_t OptionValueSize;
|
|
size_t* OptionActualSizeOut;
|
|
|
|
SYS_HANDLE* HandleOut;
|
|
uint32_t* PollReturnedEventsOut;
|
|
|
|
SYS_IO_STATUS_BLOCK_K* Iosb;
|
|
SYS_USER_APC_ROUTINE_K ApcRoutine;
|
|
void* ApcContext;
|
|
SYS_HANDLE* RequestHandleOut;
|
|
} SYS_ASYNC_SOCKET_REQUEST_K;
|
|
|
|
_Static_assert(sizeof(SYS_SOCKET_SEND_TO_REQUEST_K) == sizeof(SYS_SOCKET_SEND_TO_REQUEST),
|
|
"send-to request ABI mismatch");
|
|
_Static_assert(sizeof(SYS_SOCKET_RECV_FROM_REQUEST_K) == sizeof(SYS_SOCKET_RECV_FROM_REQUEST),
|
|
"recv-from request ABI mismatch");
|
|
_Static_assert(sizeof(SYS_RESOLVE_HOST_REQUEST_K) == sizeof(SYS_RESOLVE_HOST_REQUEST),
|
|
"resolve-host request ABI mismatch");
|
|
_Static_assert(sizeof(SYS_SOCKET_OPTION_REQUEST_K) == sizeof(SYS_SOCKET_OPTION_REQUEST),
|
|
"socket option request ABI mismatch");
|
|
_Static_assert(sizeof(SYS_ASYNC_SOCKET_REQUEST_K) == sizeof(SYS_ASYNC_SOCKET_REQUEST),
|
|
"async socket request ABI mismatch");
|
|
|
|
typedef struct SYS_MEMORY_STATUS_K {
|
|
uint64_t FreePhysicalBytes;
|
|
uint64_t KernelPoolBytes;
|
|
uint64_t UserHeapBytes;
|
|
uint64_t ReclaimablePhysicalBytes;
|
|
uint64_t AvailablePhysicalBytes;
|
|
uint64_t PinnedPhysicalBytes;
|
|
uint64_t NonEvictablePhysicalBytes;
|
|
uint64_t UserPhysicalBytes;
|
|
uint64_t PageTablePhysicalBytes;
|
|
} SYS_MEMORY_STATUS_K;
|
|
|
|
#define SYS_POLL_MAX_FDS 64u
|
|
|
|
// Implements SYS_CREATE_SOCKET.
|
|
static uint64_t SyscallSocket(uint32_t domain, uint32_t type, uint32_t protocol, SYS_HANDLE* handleOut) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NETWORK_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
if (!handleOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SOCKET_OBJECT* socket = NULL;
|
|
KSTATUS s = NetSocketCreate(domain, type, protocol, &socket);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
SYS_HANDLE h = SYS_HANDLE_INVALID;
|
|
s = KiAllocHandleForCurrentWithRights(&socket->Header, KeGetValidHandleRights(ObjectTypeSocket), &h);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
// KiAllocHandleForCurrentWithRights consumed NetSocketCreate's creator
|
|
// reference; the returned process handle now owns the socket object.
|
|
|
|
s = KiCopyHandleToUser(handleOut, h);
|
|
if (!KSUCCESS(s)) {
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (proc) KeCloseHandle(proc, h);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Validates the user-supplied socket address buffer size before copying.
|
|
static KSTATUS SyscallValidateSocketAddressBufferSize(size_t addressSize) {
|
|
if (addressSize < sizeof(SYS_SOCKET_ADDRESS_K))
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
if (addressSize != sizeof(SYS_SOCKET_ADDRESS_K))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Validates the fixed-size socket address ABI after a bounded copy.
|
|
static KSTATUS SyscallValidateSocketAddressInput(const SYS_SOCKET_ADDRESS_K* address) {
|
|
if (!address)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (address->Size != sizeof(SYS_SOCKET_ADDRESS_K))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements SYS_BIND_SOCKET.
|
|
static uint64_t SyscallBind(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* userAddress, size_t addressSize) {
|
|
if (!userAddress) return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS s = SyscallValidateSocketAddressBufferSize(addressSize);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
SYS_SOCKET_ADDRESS_K address;
|
|
s = CopyFromUser(&address, userAddress, sizeof(address));
|
|
if (!KSUCCESS(s)) return s;
|
|
s = SyscallValidateSocketAddressInput(&address);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_BIND, &handleStatus);
|
|
if (!socketObject) return handleStatus;
|
|
|
|
s = NetSocketBind(socketObject, &address, addressSize);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_LISTEN_SOCKET.
|
|
static uint64_t SyscallListen(SYS_HANDLE socket, uint32_t backlog) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_LISTEN, &handleStatus);
|
|
if (!socketObject) return handleStatus;
|
|
|
|
KSTATUS s = NetSocketListen(socketObject, backlog);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_ACCEPT_SOCKET.
|
|
// a2=*remoteAddressSizeInOut(optional) a3=*acceptedHandleOut.
|
|
static uint64_t SyscallAccept(SYS_HANDLE socket, SYS_SOCKET_ADDRESS* remoteAddressOut, size_t* remoteAddressSizeInOut, SYS_HANDLE* acceptedHandleOut) {
|
|
if (!acceptedHandleOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
bool wantRemote = (remoteAddressOut != 0);
|
|
size_t remoteCapacity = 0;
|
|
if (wantRemote) {
|
|
if (!remoteAddressSizeInOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS cs = CopyFromUser(&remoteCapacity, remoteAddressSizeInOut, sizeof(remoteCapacity));
|
|
if (!KSUCCESS(cs)) return cs;
|
|
if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
|
|
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
|
|
(void)CopyToUser(remoteAddressSizeInOut, &required, sizeof(required));
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
cs = MmProbeUserWrite(remoteAddressOut, sizeof(SYS_SOCKET_ADDRESS_K));
|
|
if (!KSUCCESS(cs)) return cs;
|
|
}
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* listener = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_ACCEPT, &handleStatus);
|
|
if (!listener) return handleStatus;
|
|
|
|
SOCKET_OBJECT* accepted = NULL;
|
|
SYS_SOCKET_ADDRESS_K remote;
|
|
KSTATUS s = NetSocketAccept(listener, &accepted, wantRemote ? &remote : NULL, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
ObDereferenceObject(&listener->Header);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
SYS_HANDLE h = SYS_HANDLE_INVALID;
|
|
s = KiAllocHandleForCurrentWithRights(&accepted->Header, KeGetValidHandleRights(ObjectTypeSocket), &h);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
// KiAllocHandleForCurrentWithRights consumed the accept creator reference;
|
|
// the returned process handle owns the accepted socket from this point.
|
|
|
|
if (wantRemote) {
|
|
size_t actual = sizeof(SYS_SOCKET_ADDRESS_K);
|
|
s = CopyToUser(remoteAddressOut, &remote, sizeof(remote));
|
|
if (KSUCCESS(s))
|
|
s = CopyToUser(remoteAddressSizeInOut, &actual, sizeof(actual));
|
|
}
|
|
|
|
if (KSUCCESS(s))
|
|
s = KiCopyHandleToUser(acceptedHandleOut, h);
|
|
|
|
if (!KSUCCESS(s)) {
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (proc) KeCloseHandle(proc, h);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_CONNECT_SOCKET.
|
|
static uint64_t SyscallConnect(SYS_HANDLE socket, const SYS_SOCKET_ADDRESS* userAddress, size_t addressSize) {
|
|
if (!userAddress) return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS s = SyscallValidateSocketAddressBufferSize(addressSize);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
SYS_SOCKET_ADDRESS_K address;
|
|
s = CopyFromUser(&address, userAddress, sizeof(address));
|
|
if (!KSUCCESS(s)) return s;
|
|
s = SyscallValidateSocketAddressInput(&address);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_CONNECT, &handleStatus);
|
|
if (!socketObject) return handleStatus;
|
|
|
|
s = NetSocketConnect(socketObject, &address, addressSize, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_SEND_SOCKET.
|
|
static uint64_t SyscallSend(const SYS_SOCKET_SEND_REQUEST* request) {
|
|
SYS_SOCKET_SEND_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
if (req.Length == 0) {
|
|
if (req.SentOut) return KiCopySizeToUser(req.SentOut, 0);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
void* kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
|
|
if (!kbuf) return NTLSTATUS_NO_MEMORY;
|
|
|
|
s = CopyFromUser(kbuf, req.Buffer, req.Length);
|
|
if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; }
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.Socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_SEND, &handleStatus);
|
|
if (!socket) { MmFreePool(kbuf); return handleStatus; }
|
|
|
|
size_t sent = 0;
|
|
s = NetSocketSend(socket, kbuf, req.Length, req.Flags, &sent);
|
|
ObDereferenceObject(&socket->Header);
|
|
MmFreePool(kbuf);
|
|
|
|
if (req.SentOut) {
|
|
KSTATUS copyStatus = KiCopySizeToUser(req.SentOut, sent);
|
|
if (KSUCCESS(s)) s = copyStatus;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Receives bytes from a connected socket.
|
|
static uint64_t SyscallRecv(const SYS_SOCKET_RECV_REQUEST* request) {
|
|
SYS_SOCKET_RECV_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
if (req.Length == 0) {
|
|
if (req.ReceivedOut) return KiCopySizeToUser(req.ReceivedOut, 0);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
s = MmProbeUserWrite(req.Buffer, req.Length);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
void* kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
|
|
if (!kbuf) return NTLSTATUS_NO_MEMORY;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.Socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_RECEIVE, &handleStatus);
|
|
if (!socket) { MmFreePool(kbuf); return handleStatus; }
|
|
|
|
size_t received = 0;
|
|
s = NetSocketReceive(socket, kbuf, req.Length, req.Flags, &received,
|
|
(IO_ASYNC_REQUEST_OBJECT*)0);
|
|
ObDereferenceObject(&socket->Header);
|
|
|
|
if (KSUCCESS(s) && received)
|
|
s = CopyToUser(req.Buffer, kbuf, received);
|
|
MmFreePool(kbuf);
|
|
|
|
if (KSUCCESS(s) && req.ReceivedOut)
|
|
s = KiCopySizeToUser(req.ReceivedOut, received);
|
|
return s;
|
|
}
|
|
|
|
// Shuts down one or both directions of a socket.
|
|
static uint64_t SyscallShutdown(SYS_HANDLE socket, uint32_t how) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_SHUTDOWN, &handleStatus);
|
|
if (!socketObject) return handleStatus;
|
|
|
|
KSTATUS s = NetSocketShutdown(socketObject, how);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_SEND_TO_SOCKET.
|
|
static uint64_t SyscallSendTo(SYS_HANDLE socket, const SYS_SOCKET_SEND_TO_REQUEST* request) {
|
|
if (!request) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_SOCKET_SEND_TO_REQUEST_K req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!req.RemoteAddress) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
|
|
s = SyscallValidateSocketAddressBufferSize(req.RemoteAddressSize);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
SYS_SOCKET_ADDRESS_K remote;
|
|
s = CopyFromUser(&remote, req.RemoteAddress, sizeof(remote));
|
|
if (!KSUCCESS(s)) return s;
|
|
s = SyscallValidateSocketAddressInput(&remote);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
void* kbuf = NULL;
|
|
if (req.Length) {
|
|
kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
|
|
if (!kbuf) return NTLSTATUS_NO_MEMORY;
|
|
s = CopyFromUser(kbuf, req.Buffer, req.Length);
|
|
if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; }
|
|
}
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_SEND, &handleStatus);
|
|
if (!socketObject) { if (kbuf) MmFreePool(kbuf); return handleStatus; }
|
|
|
|
size_t sent = 0;
|
|
s = NetSocketSendTo(socketObject, kbuf, req.Length, req.Flags, &remote, req.RemoteAddressSize, &sent);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
if (kbuf) MmFreePool(kbuf);
|
|
|
|
if (req.SentOut) {
|
|
KSTATUS cs = CopyToUser(req.SentOut, &sent, sizeof(sent));
|
|
if (KSUCCESS(s)) s = cs;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_RECV_FROM_SOCKET.
|
|
static uint64_t SyscallRecvFrom(SYS_HANDLE socket, const SYS_SOCKET_RECV_FROM_REQUEST* request) {
|
|
if (!request) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_SOCKET_RECV_FROM_REQUEST_K req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (req.Length && !req.Buffer) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Length > SYSCALL_IO_MAX_BYTES) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
bool wantRemote = (req.RemoteAddressOut != NULL);
|
|
size_t remoteCapacity = 0;
|
|
if (wantRemote) {
|
|
if (!req.RemoteAddressSizeInOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
s = CopyFromUser(&remoteCapacity, req.RemoteAddressSizeInOut, sizeof(remoteCapacity));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (remoteCapacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
|
|
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
|
|
(void)CopyToUser(req.RemoteAddressSizeInOut, &required, sizeof(required));
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
s = MmProbeUserWrite(req.RemoteAddressOut, sizeof(SYS_SOCKET_ADDRESS_K));
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
if (req.Length)
|
|
{
|
|
s = MmProbeUserWrite(req.Buffer, req.Length);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
void* kbuf = NULL;
|
|
if (req.Length) {
|
|
kbuf = MmAllocatePoolTag(req.Length, MM_POOL_TAG('s', 'y', 's', ' '));
|
|
if (!kbuf) return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_RECEIVE, &handleStatus);
|
|
if (!socketObject) { if (kbuf) MmFreePool(kbuf); return handleStatus; }
|
|
|
|
SYS_SOCKET_ADDRESS_K remote;
|
|
memset(&remote, 0, sizeof(remote));
|
|
size_t received = 0;
|
|
s = NetSocketReceiveFrom(socketObject, kbuf, req.Length, req.Flags, wantRemote ? &remote : NULL, &received, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
|
|
if (KSUCCESS(s) && received)
|
|
s = CopyToUser(req.Buffer, kbuf, received);
|
|
if (kbuf) MmFreePool(kbuf);
|
|
|
|
if (KSUCCESS(s) && wantRemote) {
|
|
size_t actual = sizeof(SYS_SOCKET_ADDRESS_K);
|
|
s = CopyToUser(req.RemoteAddressOut, &remote, sizeof(remote));
|
|
if (KSUCCESS(s))
|
|
s = CopyToUser(req.RemoteAddressSizeInOut, &actual, sizeof(actual));
|
|
}
|
|
|
|
if (KSUCCESS(s) && req.ReceivedOut)
|
|
s = CopyToUser(req.ReceivedOut, &received, sizeof(received));
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_RESOLVE_HOST.
|
|
static uint64_t SyscallResolveHost(const SYS_RESOLVE_HOST_REQUEST* request) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_NETWORK_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
if (!request) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_RESOLVE_HOST_REQUEST_K req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
if (!req.Name.Data || req.Name.Length == 0 || req.Name.Length > SYS_RESOLVE_HOST_NAME_MAX)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.Family != SYS_AF_INET && req.Family != SYS_AF_INET6)
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
if (req.Flags != 0)
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
if (!req.AddressOut || !req.AddressSizeInOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
size_t capacity = 0;
|
|
s = CopyFromUser(&capacity, req.AddressSizeInOut, sizeof(capacity));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (capacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
|
|
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
|
|
(void)CopyToUser(req.AddressSizeInOut, &required, sizeof(required));
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
char name[SYS_RESOLVE_HOST_NAME_MAX + 1];
|
|
SYS_STRING_VIEW nameView;
|
|
s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, true);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
SYS_SOCKET_ADDRESS_K address;
|
|
s = NetResolveHost(name, req.Family, &address);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
size_t actual = sizeof(SYS_SOCKET_ADDRESS_K);
|
|
s = CopyToUser(req.AddressOut, &address, sizeof(address));
|
|
if (!KSUCCESS(s)) return s;
|
|
return CopyToUser(req.AddressSizeInOut, &actual, sizeof(actual));
|
|
}
|
|
|
|
// Implements SYS_SET_SOCKET_OPTION.
|
|
static uint64_t SyscallSetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request) {
|
|
if (!request) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_SOCKET_OPTION_REQUEST_K req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!req.Value) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint8_t value[sizeof(uint64_t)];
|
|
if (req.ValueSize > sizeof(value)) return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
s = CopyFromUser(value, req.Value, req.ValueSize);
|
|
if (!KSUCCESS(s)) return s;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_SOCKET_RIGHT_SET_OPTION, &handleStatus);
|
|
if (!socketObject) return handleStatus;
|
|
|
|
s = NetSocketSetOption(socketObject, req.Option, value, req.ValueSize);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
return s;
|
|
}
|
|
|
|
// Implements SYS_GET_SOCKET_OPTION.
|
|
static uint64_t SyscallGetSocketOption(SYS_HANDLE socket, const SYS_SOCKET_OPTION_REQUEST* request) {
|
|
if (!request) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_SOCKET_OPTION_REQUEST_K req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!req.Value) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint8_t value[sizeof(uint64_t)];
|
|
if (req.ValueSize > sizeof(value)) return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socketObject = (SOCKET_OBJECT*)KiHandleToObject(socket, ObjectTypeSocket,
|
|
SYS_RIGHT_QUERY, &handleStatus);
|
|
if (!socketObject) return handleStatus;
|
|
|
|
size_t actual = 0;
|
|
s = NetSocketGetOption(socketObject, req.Option, value, req.ValueSize, &actual);
|
|
ObDereferenceObject(&socketObject->Header);
|
|
if (!KSUCCESS(s)) {
|
|
if (s == NTLSTATUS_BUFFER_TOO_SMALL && req.ActualSizeOut)
|
|
(void)CopyToUser(req.ActualSizeOut, &actual, sizeof(actual));
|
|
return s;
|
|
}
|
|
|
|
s = CopyToUser(req.Value, value, actual);
|
|
if (KSUCCESS(s) && req.ActualSizeOut)
|
|
s = CopyToUser(req.ActualSizeOut, &actual, sizeof(actual));
|
|
return s;
|
|
}
|
|
|
|
// Polls deadline Expired.
|
|
static bool KiPollDeadlineExpired(uint64_t start, uint32_t timeoutMs) {
|
|
if (timeoutMs == 0)
|
|
return true;
|
|
if (timeoutMs == SYS_POLL_TIMEOUT_INFINITE)
|
|
return false;
|
|
if (!gHal)
|
|
return true;
|
|
return (gHal->Timer.QueryTicks() - start) >= ((uint64_t)timeoutMs * 1000u);
|
|
}
|
|
|
|
// Implements SYS_POLL.
|
|
static uint64_t SyscallPoll(const SYS_POLL_REQUEST* request) {
|
|
SYS_POLL_REQUEST req;
|
|
KSTATUS s = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(s)) return s;
|
|
if (!req.Fds) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
size_t count = req.Count;
|
|
if (count == 0 || count > SYS_POLL_MAX_FDS)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
size_t bytes = count * sizeof(SYS_POLLFD_K);
|
|
SYS_POLLFD_K* fds = (SYS_POLLFD_K*)MmAllocatePoolTag(bytes, MM_POOL_TAG('s', 'y', 's', ' '));
|
|
if (!fds) return NTLSTATUS_NO_MEMORY;
|
|
|
|
s = CopyFromUser(fds, req.Fds, bytes);
|
|
if (!KSUCCESS(s)) { MmFreePool(fds); return s; }
|
|
|
|
uint32_t timeoutMs = req.TimeoutMs;
|
|
uint64_t start = gHal ? gHal->Timer.QueryTicks() : 0;
|
|
size_t ready = 0;
|
|
|
|
while (true) {
|
|
ready = 0;
|
|
for (size_t i = 0; i < count; i++) {
|
|
fds[i].ReturnedEvents = 0;
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
uint32_t revents = 0;
|
|
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(
|
|
fds[i].Handle, ObjectTypeSocket, SYS_SOCKET_RIGHT_POLL, &handleStatus);
|
|
if (socket) {
|
|
s = NetSocketPoll(socket, fds[i].Events, &revents);
|
|
ObDereferenceObject(&socket->Header);
|
|
} else {
|
|
IO_PIPE_ENDPOINT* pipe = (IO_PIPE_ENDPOINT*)KiHandleToObject(
|
|
fds[i].Handle, ObjectTypePipeEndpoint, SYS_PIPE_RIGHT_POLL, &handleStatus);
|
|
if (!pipe) {
|
|
fds[i].ReturnedEvents = SYS_POLL_INVALID;
|
|
ready++;
|
|
continue;
|
|
}
|
|
s = IoPollPipe(pipe, fds[i].Events, &revents);
|
|
ObDereferenceObject((OBJECT*)pipe);
|
|
}
|
|
if (!KSUCCESS(s)) {
|
|
fds[i].ReturnedEvents = SYS_POLL_ERROR;
|
|
ready++;
|
|
continue;
|
|
}
|
|
fds[i].ReturnedEvents = revents;
|
|
if (revents != 0)
|
|
ready++;
|
|
}
|
|
|
|
if (ready != 0 || KiPollDeadlineExpired(start, timeoutMs))
|
|
break;
|
|
KeYield();
|
|
}
|
|
|
|
s = CopyToUser(req.Fds, fds, bytes);
|
|
if (KSUCCESS(s) && req.ReadyOut)
|
|
s = CopyToUser(req.ReadyOut, &ready, sizeof(ready));
|
|
MmFreePool(fds);
|
|
return s;
|
|
}
|
|
|
|
// Releases resources prepared for an async socket request before queue ownership starts.
|
|
static void SyscallAsyncSocketReleaseSubmitResources(NET_ASYNC_SOCKET_SUBMIT* submit) {
|
|
if (!submit)
|
|
return;
|
|
|
|
if (submit->KernelBuffer) {
|
|
MmFreePool(submit->KernelBuffer);
|
|
submit->KernelBuffer = (void*)0;
|
|
}
|
|
if (submit->OptionValue) {
|
|
MmFreePool(submit->OptionValue);
|
|
submit->OptionValue = (void*)0;
|
|
}
|
|
if (submit->LockedBuffer) {
|
|
MmUnlockUserBuffer(submit->LockedBuffer);
|
|
submit->LockedBuffer = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (submit->LockedIosb) {
|
|
MmUnlockUserBuffer(submit->LockedIosb);
|
|
submit->LockedIosb = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (submit->LockedAddressOut) {
|
|
MmUnlockUserBuffer(submit->LockedAddressOut);
|
|
submit->LockedAddressOut = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (submit->LockedAddressSize) {
|
|
MmUnlockUserBuffer(submit->LockedAddressSize);
|
|
submit->LockedAddressSize = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (submit->LockedOptionValue) {
|
|
MmUnlockUserBuffer(submit->LockedOptionValue);
|
|
submit->LockedOptionValue = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (submit->LockedOptionActualSize) {
|
|
MmUnlockUserBuffer(submit->LockedOptionActualSize);
|
|
submit->LockedOptionActualSize = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (submit->LockedHandleOut) {
|
|
MmUnlockUserBuffer(submit->LockedHandleOut);
|
|
submit->LockedHandleOut = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (submit->LockedPollReturnedEvents) {
|
|
MmUnlockUserBuffer(submit->LockedPollReturnedEvents);
|
|
submit->LockedPollReturnedEvents = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
}
|
|
|
|
// Locks an optional async socket output pointer for worker-thread completion.
|
|
static KSTATUS SyscallAsyncSocketLockOptional(PROCESS* proc,
|
|
void* ptr,
|
|
size_t size,
|
|
MM_LOCKED_USER_BUFFER** out) {
|
|
if (out)
|
|
*out = (MM_LOCKED_USER_BUFFER*)0;
|
|
if (!ptr || size == 0)
|
|
return NTLSTATUS_SUCCESS;
|
|
return MmLockUserBuffer(proc, ptr, size, 1, out);
|
|
}
|
|
|
|
// Copies an input address into a kernel-owned async socket request.
|
|
static KSTATUS SyscallAsyncSocketCopyInputAddress(const SYS_ASYNC_SOCKET_REQUEST_K* req,
|
|
NET_ASYNC_SOCKET_SUBMIT* submit) {
|
|
if (!req || !submit || !req->Address)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS status = SyscallValidateSocketAddressBufferSize(req->AddressSize);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
status = CopyFromUser(&submit->Address, req->Address, sizeof(submit->Address));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
status = SyscallValidateSocketAddressInput(&submit->Address);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
submit->AddressSize = req->AddressSize;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Validates and locks optional remote-address outputs for async accept/recvfrom.
|
|
static KSTATUS SyscallAsyncSocketPrepareAddressOutput(PROCESS* proc,
|
|
const SYS_ASYNC_SOCKET_REQUEST_K* req,
|
|
NET_ASYNC_SOCKET_SUBMIT* submit) {
|
|
if (!req || !submit)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!req->AddressOut)
|
|
return NTLSTATUS_SUCCESS;
|
|
if (!req->AddressSizeInOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
size_t capacity = 0;
|
|
KSTATUS status = CopyFromUser(&capacity, req->AddressSizeInOut, sizeof(capacity));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (capacity < sizeof(SYS_SOCKET_ADDRESS_K)) {
|
|
size_t required = sizeof(SYS_SOCKET_ADDRESS_K);
|
|
(void)CopyToUser(req->AddressSizeInOut, &required, sizeof(required));
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
status = MmLockUserBuffer(proc, req->AddressOut, sizeof(SYS_SOCKET_ADDRESS_K), 1,
|
|
&submit->LockedAddressOut);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
status = MmLockUserBuffer(proc, req->AddressSizeInOut, sizeof(*req->AddressSizeInOut), 1,
|
|
&submit->LockedAddressSize);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
submit->AddressOut = req->AddressOut;
|
|
submit->AddressSizeInOut = req->AddressSizeInOut;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Prepares kernel-owned buffers and locked outputs for one async socket operation.
|
|
static KSTATUS SyscallAsyncSocketPrepareOperation(PROCESS* proc,
|
|
const SYS_ASYNC_SOCKET_REQUEST_K* req,
|
|
NET_ASYNC_SOCKET_SUBMIT* submit) {
|
|
if (!proc || !req || !submit)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
submit->Operation = req->Operation;
|
|
submit->SocketFlags = req->SocketFlags;
|
|
submit->Backlog = req->Backlog;
|
|
submit->ShutdownHow = req->ShutdownHow;
|
|
submit->Option = req->Option;
|
|
submit->PollEvents = req->PollEvents;
|
|
submit->Length = req->Length;
|
|
|
|
switch (req->Operation) {
|
|
case SYS_ASYNC_SOCKET_OP_BIND:
|
|
case SYS_ASYNC_SOCKET_OP_CONNECT:
|
|
return SyscallAsyncSocketCopyInputAddress(req, submit);
|
|
|
|
case SYS_ASYNC_SOCKET_OP_LISTEN:
|
|
case SYS_ASYNC_SOCKET_OP_SHUTDOWN:
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_ACCEPT: {
|
|
if (!req->HandleOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
SYS_HANDLE zero = SYS_HANDLE_INVALID;
|
|
KSTATUS status = MmLockUserBuffer(proc, req->HandleOut, sizeof(*req->HandleOut), 1,
|
|
&submit->LockedHandleOut);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
*req->HandleOut = zero;
|
|
submit->HandleOut = req->HandleOut;
|
|
return SyscallAsyncSocketPrepareAddressOutput(proc, req, submit);
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_SEND:
|
|
if (req->Length && !req->Buffer)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->Length > SYSCALL_IO_MAX_BYTES)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->Length) {
|
|
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 's', 'n'));
|
|
if (!submit->KernelBuffer)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length);
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_RECV:
|
|
if (req->Length && !req->Buffer)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->Length > SYSCALL_IO_MAX_BYTES)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->Length) {
|
|
KSTATUS status = MmLockUserBuffer(proc, req->Buffer, req->Length, 1, &submit->LockedBuffer);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 'r', 'n'));
|
|
if (!submit->KernelBuffer)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
submit->UserBuffer = req->Buffer;
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_SEND_TO: {
|
|
if (req->Length && !req->Buffer)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->Length > SYSCALL_IO_MAX_BYTES)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS status = SyscallAsyncSocketCopyInputAddress(req, submit);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (req->Length) {
|
|
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 's', 'd'));
|
|
if (!submit->KernelBuffer)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
return CopyFromUser(submit->KernelBuffer, req->Buffer, req->Length);
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_RECV_FROM: {
|
|
if (req->Length && !req->Buffer)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->Length > SYSCALL_IO_MAX_BYTES)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS status = SyscallAsyncSocketPrepareAddressOutput(proc, req, submit);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (req->Length) {
|
|
status = MmLockUserBuffer(proc, req->Buffer, req->Length, 1, &submit->LockedBuffer);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
submit->KernelBuffer = MmAllocatePoolTag(req->Length, MM_POOL_TAG('s', 'a', 'r', 'd'));
|
|
if (!submit->KernelBuffer)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
submit->UserBuffer = req->Buffer;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_SET_OPT:
|
|
if (!req->OptionValue)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->OptionValueSize > sizeof(uint64_t))
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
submit->OptionValueSize = req->OptionValueSize;
|
|
if (req->OptionValueSize) {
|
|
submit->OptionValue = MmAllocatePoolTag(req->OptionValueSize, MM_POOL_TAG('s', 'a', 'o', 's'));
|
|
if (!submit->OptionValue)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
return CopyFromUser(submit->OptionValue, req->OptionValue, req->OptionValueSize);
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_GET_OPT: {
|
|
if (!req->OptionValue)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req->OptionValueSize > sizeof(uint64_t))
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
submit->OptionValueSize = req->OptionValueSize;
|
|
submit->UserOptionValue = req->OptionValue;
|
|
KSTATUS status = MmLockUserBuffer(proc, req->OptionValue, req->OptionValueSize, 1,
|
|
&submit->LockedOptionValue);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (req->OptionActualSizeOut) {
|
|
status = MmLockUserBuffer(proc, req->OptionActualSizeOut, sizeof(*req->OptionActualSizeOut), 1,
|
|
&submit->LockedOptionActualSize);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
submit->OptionActualSizeOut = req->OptionActualSizeOut;
|
|
}
|
|
if (req->OptionValueSize) {
|
|
submit->OptionValue = MmAllocatePoolTag(req->OptionValueSize, MM_POOL_TAG('s', 'a', 'o', 'g'));
|
|
if (!submit->OptionValue)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_POLL: {
|
|
if (req->PollReturnedEventsOut) {
|
|
KSTATUS status = MmLockUserBuffer(proc, req->PollReturnedEventsOut,
|
|
sizeof(*req->PollReturnedEventsOut), 1,
|
|
&submit->LockedPollReturnedEvents);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
*req->PollReturnedEventsOut = 0;
|
|
submit->PollReturnedEventsOut = req->PollReturnedEventsOut;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
default:
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
|
|
// Returns the capability right required by one asynchronous socket operation.
|
|
static SYS_RIGHTS KiAsyncSocketRequiredRights(uint32_t operation) {
|
|
switch (operation) {
|
|
case SYS_ASYNC_SOCKET_OP_BIND: return SYS_SOCKET_RIGHT_BIND;
|
|
case SYS_ASYNC_SOCKET_OP_LISTEN: return SYS_SOCKET_RIGHT_LISTEN;
|
|
case SYS_ASYNC_SOCKET_OP_ACCEPT: return SYS_SOCKET_RIGHT_ACCEPT;
|
|
case SYS_ASYNC_SOCKET_OP_CONNECT: return SYS_SOCKET_RIGHT_CONNECT;
|
|
case SYS_ASYNC_SOCKET_OP_SEND:
|
|
case SYS_ASYNC_SOCKET_OP_SEND_TO: return SYS_SOCKET_RIGHT_SEND;
|
|
case SYS_ASYNC_SOCKET_OP_RECV:
|
|
case SYS_ASYNC_SOCKET_OP_RECV_FROM: return SYS_SOCKET_RIGHT_RECEIVE;
|
|
case SYS_ASYNC_SOCKET_OP_SHUTDOWN: return SYS_SOCKET_RIGHT_SHUTDOWN;
|
|
case SYS_ASYNC_SOCKET_OP_SET_OPT: return SYS_SOCKET_RIGHT_SET_OPTION;
|
|
case SYS_ASYNC_SOCKET_OP_GET_OPT: return SYS_RIGHT_QUERY;
|
|
case SYS_ASYNC_SOCKET_OP_POLL: return SYS_SOCKET_RIGHT_POLL;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// Implements SYS_SOCKET_IO_ASYNC.
|
|
static uint64_t SyscallSocketIoAsync(const SYS_ASYNC_SOCKET_REQUEST* request) {
|
|
if (!request)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_ASYNC_SOCKET_REQUEST_K req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
if (req.Flags != SYS_ASYNC_SOCKET_FLAG_NONE || !req.Iosb)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (req.RequestHandleOut) {
|
|
status = MmProbeUserWrite(req.RequestHandleOut, sizeof(*req.RequestHandleOut));
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
}
|
|
|
|
PROCESS* proc = KeGetCurrentProcess();
|
|
if (!proc)
|
|
return NTLSTATUS_FAILURE;
|
|
|
|
SYS_RIGHTS socketRights = KiAsyncSocketRequiredRights(req.Operation);
|
|
if (!socketRights)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS socketHandleStatus = NTLSTATUS_SUCCESS;
|
|
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(req.SocketHandle,
|
|
ObjectTypeSocket,
|
|
socketRights,
|
|
&socketHandleStatus);
|
|
if (!socket)
|
|
return socketHandleStatus;
|
|
|
|
EVENT* event = (EVENT*)0;
|
|
if (req.EventHandle) {
|
|
KSTATUS eventHandleStatus = NTLSTATUS_SUCCESS;
|
|
event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent,
|
|
SYS_EVENT_RIGHT_SIGNAL, &eventHandleStatus);
|
|
if (!event) {
|
|
ObDereferenceObject(&socket->Header);
|
|
return eventHandleStatus;
|
|
}
|
|
}
|
|
|
|
KAPC* completionApc = (KAPC*)0;
|
|
SYS_USER_ASYNC_APC_CONTEXT* apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)0;
|
|
if (req.ApcRoutine) {
|
|
THREAD* thread = KeGetCurrentThread();
|
|
if (!thread || !thread->UserApcDispatcher) {
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&socket->Header);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
apcCtx = (SYS_USER_ASYNC_APC_CONTEXT*)MmAllocatePoolTag(sizeof(SYS_USER_ASYNC_APC_CONTEXT), MM_POOL_TAG('u', 'a', 'p', 'c'));
|
|
if (!apcCtx) {
|
|
if (event) ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&socket->Header);
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
|
|
memset(apcCtx, 0, sizeof(*apcCtx));
|
|
ObReferenceObject(&thread->Header);
|
|
apcCtx->TargetThread = thread;
|
|
apcCtx->UserRoutine = req.ApcRoutine;
|
|
apcCtx->UserContext = req.ApcContext;
|
|
KeInitializeApc(&apcCtx->KernelApc,
|
|
thread,
|
|
SyscallUserAsyncKernelRoutine,
|
|
SyscallUserAsyncNormalRoutine,
|
|
apcCtx);
|
|
KeSetApcRundownRoutine(&apcCtx->KernelApc, SyscallUserAsyncApcRundown);
|
|
completionApc = &apcCtx->KernelApc;
|
|
}
|
|
|
|
NET_ASYNC_SOCKET_SUBMIT submit;
|
|
memset(&submit, 0, sizeof(submit));
|
|
submit.OwnerProcess = proc;
|
|
submit.Socket = socket;
|
|
submit.CompletionEvent = event;
|
|
submit.CompletionApc = completionApc;
|
|
submit.Iosb = (IO_STATUS_BLOCK*)req.Iosb;
|
|
|
|
status = MmLockUserBuffer(proc, req.Iosb, sizeof(*req.Iosb), 1, &submit.LockedIosb);
|
|
if (!KSUCCESS(status))
|
|
goto fail;
|
|
|
|
status = SyscallAsyncSocketPrepareOperation(proc, &req, &submit);
|
|
if (!KSUCCESS(status))
|
|
goto fail;
|
|
|
|
NET_ASYNC_SOCKET_REQUEST_OBJECT* asyncReq = (NET_ASYNC_SOCKET_REQUEST_OBJECT*)0;
|
|
status = NetSocketSubmitAsyncKernel(&submit, &asyncReq);
|
|
if (status == NTLSTATUS_PENDING) {
|
|
memset(&submit, 0, sizeof(submit));
|
|
}
|
|
if (!KSUCCESS(status) && status != NTLSTATUS_PENDING)
|
|
goto fail;
|
|
|
|
if (req.RequestHandleOut && asyncReq) {
|
|
SYS_HANDLE requestHandle = SYS_HANDLE_INVALID;
|
|
KSTATUS hs = KeCreateHandle(proc,
|
|
NetAsyncSocketRequestObject(asyncReq),
|
|
KeGetValidHandleRights(ObjectTypeAsyncIo),
|
|
&requestHandle);
|
|
if (!KSUCCESS(hs)) {
|
|
(void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq);
|
|
ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq));
|
|
status = hs;
|
|
goto fail_after_submit;
|
|
}
|
|
|
|
KSTATUS cs = CopyToUser(req.RequestHandleOut, &requestHandle, sizeof(requestHandle));
|
|
if (!KSUCCESS(cs)) {
|
|
(void)IoCancelAsyncRequest((IO_ASYNC_REQUEST_OBJECT*)asyncReq);
|
|
KeCloseHandle(proc, requestHandle);
|
|
ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq));
|
|
status = cs;
|
|
goto fail_after_submit;
|
|
}
|
|
} else if (req.RequestHandleOut) {
|
|
SYS_HANDLE zeroHandle = SYS_HANDLE_INVALID;
|
|
KSTATUS cs = CopyToUser(req.RequestHandleOut, &zeroHandle, sizeof(zeroHandle));
|
|
if (!KSUCCESS(cs)) {
|
|
status = cs;
|
|
goto fail_after_submit;
|
|
}
|
|
}
|
|
|
|
if (asyncReq)
|
|
ObDereferenceObject(NetAsyncSocketRequestObject(asyncReq));
|
|
if (event)
|
|
ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&socket->Header);
|
|
return status;
|
|
|
|
fail:
|
|
SyscallAsyncSocketReleaseSubmitResources(&submit);
|
|
if (apcCtx)
|
|
SyscallUserAsyncApcContextFree(apcCtx);
|
|
fail_after_submit:
|
|
if (event)
|
|
ObDereferenceObject(&event->Header);
|
|
ObDereferenceObject(&socket->Header);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_GET_MEMORY_STATUS.
|
|
static uint64_t SyscallGetMemoryStatus(SYS_MEMORY_STATUS* statusOut) {
|
|
if (!statusOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
MM_PFN_STATS pfn;
|
|
MmPfnQueryStats(&pfn);
|
|
|
|
SYS_MEMORY_STATUS_K status;
|
|
memset(&status, 0, sizeof(status));
|
|
status.FreePhysicalBytes = pfn.FreeFrames * PAGE_SIZE;
|
|
status.KernelPoolBytes = MmGetKernelPoolUsage();
|
|
status.UserHeapBytes = MmGetUserHeapUsage();
|
|
status.ReclaimablePhysicalBytes = pfn.ReclaimableFrames * PAGE_SIZE;
|
|
status.AvailablePhysicalBytes = pfn.AvailableFrames * PAGE_SIZE;
|
|
status.PinnedPhysicalBytes = pfn.PinnedFrames * PAGE_SIZE;
|
|
status.NonEvictablePhysicalBytes = pfn.NonEvictableFrames * PAGE_SIZE;
|
|
status.UserPhysicalBytes = pfn.UserFrames * PAGE_SIZE;
|
|
status.PageTablePhysicalBytes = pfn.PageTableFrames * PAGE_SIZE;
|
|
|
|
return CopyToUser(statusOut, &status, sizeof(status));
|
|
}
|
|
|
|
// Implements SYS_QUERY_KERNEL_STATS.
|
|
static KSTATUS KiReturnKernelStats(void* buffer, size_t bufferSize,
|
|
size_t* requiredSizeOut,
|
|
const void* src, size_t size) {
|
|
if (requiredSizeOut) {
|
|
KSTATUS s = KiCopySizeToUser(requiredSizeOut, size);
|
|
if (!KSUCCESS(s)) return s;
|
|
}
|
|
|
|
if (!buffer)
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
if (bufferSize < size)
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
|
|
return CopyToUser(buffer, src, size);
|
|
}
|
|
|
|
// Copies the current primary-display state through either native authority path.
|
|
static KSTATUS KiCopyDisplayInfoToUser(SYS_DISPLAY_INFO* infoOut) {
|
|
if (!infoOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KE_DISPLAY_INFO ki;
|
|
KSTATUS status = KeDisplayQueryInfo(&ki);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_DISPLAY_INFO out;
|
|
memset(&out, 0, sizeof(out));
|
|
memcpy(out.PrimaryName, ki.PrimaryName, sizeof(out.PrimaryName));
|
|
out.Mode = ki.Mode;
|
|
out.Caps = ki.Caps;
|
|
out.DeviceCount = ki.DeviceCount;
|
|
out.Flags = ki.Flags;
|
|
out.ManagerStats = ki.ManagerStats;
|
|
out.DeviceStats = ki.DeviceStats;
|
|
return CopyToUser(infoOut, &out, sizeof(out));
|
|
}
|
|
|
|
// Returns display metadata through the legacy ambient display authority.
|
|
static uint64_t SyscallQueryDisplayInfo(SYS_DISPLAY_INFO* infoOut) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_DISPLAY_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
return KiCopyDisplayInfoToUser(infoOut);
|
|
}
|
|
|
|
// Returns display metadata through an explicitly delegated display-manager capability.
|
|
static uint64_t SyscallQueryDisplayManagerInfo(SYS_HANDLE managerHandle, SYS_DISPLAY_INFO* infoOut) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = (KE_DISPLAY_MANAGER*)KiHandleToObject(
|
|
managerHandle, ObjectTypeDisplayManager, SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiCopyDisplayInfoToUser(infoOut);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Presents one surface through explicitly delegated physical-display authority.
|
|
static uint64_t SyscallPresentDisplayManagerSurface(SYS_HANDLE managerHandle, SYS_HANDLE surfaceHandle,
|
|
const DISPLAY_RECT* userRects, uint32_t rectCount) {
|
|
KSTATUS managerStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = (KE_DISPLAY_MANAGER*)KiHandleToObject(
|
|
managerHandle, ObjectTypeDisplayManager, SYS_DISPLAY_MANAGER_RIGHT_PRESENT, &managerStatus);
|
|
if (!manager) return managerStatus;
|
|
|
|
DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS];
|
|
KSTATUS status = KiCaptureUserSurfaceRects(userRects, rectCount, rects);
|
|
if (!KSUCCESS(status)) {
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
KSTATUS surfaceStatus = NTLSTATUS_SUCCESS;
|
|
OBJECT* surface = KiHandleToObject(surfaceHandle, ObjectTypeSurface,
|
|
SYS_SURFACE_RIGHT_MAP_READ, &surfaceStatus);
|
|
if (!surface) {
|
|
ObDereferenceObject(&manager->Header);
|
|
return surfaceStatus;
|
|
}
|
|
|
|
uint64_t bytesCopied = 0;
|
|
status = KeSurfacePresent(surface, rectCount ? rects : (DISPLAY_RECT*)0, rectCount, &bytesCopied);
|
|
ObDereferenceObject(surface);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Updates the hardware cursor through an explicitly delegated display-manager capability.
|
|
static uint64_t SyscallSetDisplayManagerCursor(const SYS_DISPLAY_MANAGER_CURSOR_REQUEST* request) {
|
|
SYS_DISPLAY_MANAGER_CURSOR_REQUEST req;
|
|
KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (req.Flags & ~(SYS_DISPLAY_MANAGER_CURSOR_FLAG_SHOW | SYS_DISPLAY_MANAGER_CURSOR_FLAG_HIDE))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if ((req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_SHOW) &&
|
|
(req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_HIDE))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = (KE_DISPLAY_MANAGER*)KiHandleToObject(
|
|
req.ManagerHandle, ObjectTypeDisplayManager, SYS_DISPLAY_MANAGER_RIGHT_CURSOR, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
|
|
status = KeDisplaySetCursorPosition(req.X, req.Y);
|
|
if (KSUCCESS(status) && (req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_SHOW))
|
|
status = KeDisplayShowCursor(true);
|
|
if (KSUCCESS(status) && (req.Flags & SYS_DISPLAY_MANAGER_CURSOR_FLAG_HIDE))
|
|
status = KeDisplayShowCursor(false);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Returns a referenced display manager carrying RSSRV's console authority.
|
|
static KE_DISPLAY_MANAGER* KiReferenceDisplayManagerConsole(SYS_HANDLE managerHandle, KSTATUS* statusOut) {
|
|
return (KE_DISPLAY_MANAGER*)KiHandleToObject(managerHandle, ObjectTypeDisplayManager,
|
|
SYS_DISPLAY_MANAGER_RIGHT_CONSOLE, statusOut);
|
|
}
|
|
|
|
// Writes text through an explicitly delegated display-manager console capability.
|
|
static uint64_t SyscallWriteDisplayManagerConsole(SYS_HANDLE managerHandle,
|
|
const SYS_WRITE_CONSOLE_REQUEST* request) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiConsoleWrite(request);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Queries console state through an explicitly delegated display-manager capability.
|
|
static uint64_t SyscallQueryDisplayManagerConsoleInfo(SYS_HANDLE managerHandle,
|
|
SYS_CONSOLE_INFO* infoOut) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiConsoleQueryInfo(infoOut);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Updates the console cursor through an explicitly delegated display-manager capability.
|
|
static uint64_t SyscallSetDisplayManagerConsoleCursor(SYS_HANDLE managerHandle, int32_t x, int32_t y) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiConsoleSetCursor(x, y);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Fills console characters through an explicitly delegated display-manager capability.
|
|
static uint64_t SyscallFillDisplayManagerConsoleCharacter(
|
|
SYS_HANDLE managerHandle, const SYS_FILL_CONSOLE_CHARACTER_REQUEST* request) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiConsoleFillCharacter(request);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Fills console attributes through an explicitly delegated display-manager capability.
|
|
static uint64_t SyscallFillDisplayManagerConsoleAttribute(
|
|
SYS_HANDLE managerHandle, const SYS_FILL_CONSOLE_ATTRIBUTE_REQUEST* request) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiConsoleFillAttribute(request);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Draws one explicit console glyph through delegated display-manager authority.
|
|
static uint64_t SyscallPutDisplayManagerConsoleCharacter(
|
|
SYS_HANDLE managerHandle, const SYS_PUT_SCREEN_CHAR_REQUEST* request) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiConsolePutCharacter(request);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Clears the console target through delegated display-manager authority.
|
|
static uint64_t SyscallClearDisplayManagerConsole(SYS_HANDLE managerHandle, uint32_t color) {
|
|
KSTATUS handleStatus = NTLSTATUS_SUCCESS;
|
|
KE_DISPLAY_MANAGER* manager = KiReferenceDisplayManagerConsole(managerHandle, &handleStatus);
|
|
if (!manager) return handleStatus;
|
|
KSTATUS status = KiConsoleClear(color);
|
|
ObDereferenceObject(&manager->Header);
|
|
return status;
|
|
}
|
|
|
|
// Queries one class of kernel diagnostic statistics.
|
|
static uint64_t SyscallQueryKernelStats(uint32_t classId, void* buffer, size_t bufferSize, size_t* requiredSizeOut) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_KERNEL_DIAGNOSTICS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
switch (classId) {
|
|
case SYS_KERNEL_STATS_SCHEDULER: {
|
|
KE_SCHED_STATS ks;
|
|
SYS_SCHEDULER_STATS us;
|
|
KeGetSchedulerStats(&ks);
|
|
memset(&us, 0, sizeof(us));
|
|
us.Size = sizeof(us);
|
|
us.Version = SYS_STATS_VERSION;
|
|
us.Ticks = ks.Ticks;
|
|
us.ContextSwitches = ks.ContextSwitches;
|
|
us.KeepCurrentDecisions = ks.KeepCurrentDecisions;
|
|
us.IdleSelections = ks.IdleSelections;
|
|
us.Demotions = ks.Demotions;
|
|
us.AgingPromotions = ks.AgingPromotions;
|
|
us.QuantumExpirations = ks.QuantumExpirations;
|
|
us.PriorityPreemptions = ks.PriorityPreemptions;
|
|
us.VoluntaryBlocks = ks.VoluntaryBlocks;
|
|
us.CpuBurstDemotions = ks.CpuBurstDemotions;
|
|
us.SmtSiblingDeferrals = ks.SmtSiblingDeferrals;
|
|
us.SmtPrimaryPokes = ks.SmtPrimaryPokes;
|
|
us.RealtimeDispatches = ks.RealtimeDispatches;
|
|
us.RealtimePreemptions = ks.RealtimePreemptions;
|
|
us.PriorityInheritanceBoosts = ks.PriorityInheritanceBoosts;
|
|
us.PriorityInheritanceDrops = ks.PriorityInheritanceDrops;
|
|
us.PriorityInheritancePropagations = ks.PriorityInheritancePropagations;
|
|
us.PriorityInheritanceMaxDepth = ks.PriorityInheritanceMaxDepth;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &us, sizeof(us));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_PMM: {
|
|
MM_PHYSICAL_STATS km;
|
|
SYS_PMM_STATS um;
|
|
MmQueryPhysicalStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.TotalFrames = km.TotalFrames;
|
|
um.FreeFrames = km.FreeFrames;
|
|
um.UsedFrames = km.UsedFrames;
|
|
um.LargestFreeRun = km.LargestFreeRun;
|
|
um.FreeRunCount = km.FreeRunCount;
|
|
for (size_t i = 0; i < 8; i++)
|
|
um.FreeRunHistogram[i] = km.FreeRunHistogram[i];
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_PFN: {
|
|
MM_PFN_STATS km;
|
|
SYS_PFN_STATS um;
|
|
MmPfnQueryStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.TotalFrames = km.TotalFrames;
|
|
um.FreeFrames = km.FreeFrames;
|
|
um.UsedFrames = km.UsedFrames;
|
|
um.ReservedFrames = km.ReservedFrames;
|
|
um.ZeroedFrames = km.ZeroedFrames;
|
|
um.ActiveFrames = km.ActiveFrames;
|
|
um.StandbyFrames = km.StandbyFrames;
|
|
um.ModifiedFrames = km.ModifiedFrames;
|
|
um.PinnedFrames = km.PinnedFrames;
|
|
um.EvictableFrames = km.EvictableFrames;
|
|
um.NonEvictableFrames = km.NonEvictableFrames;
|
|
um.ReclaimableFrames = km.ReclaimableFrames;
|
|
um.AvailableFrames = km.AvailableFrames;
|
|
um.MappedFrames = km.MappedFrames;
|
|
um.KernelFrames = km.KernelFrames;
|
|
um.PageTableFrames = km.PageTableFrames;
|
|
um.UserFrames = km.UserFrames;
|
|
um.CleanFileCacheFrames = km.CleanFileCacheFrames;
|
|
um.ActiveCleanFileCacheFrames = km.ActiveCleanFileCacheFrames;
|
|
um.StandbyCleanFileCacheFrames = km.StandbyCleanFileCacheFrames;
|
|
um.ImageFrames = km.ImageFrames;
|
|
um.ActiveImageFrames = km.ActiveImageFrames;
|
|
um.StandbyImageFrames = km.StandbyImageFrames;
|
|
um.MappedFileFrames = km.MappedFileFrames;
|
|
um.AnonymousFrames = km.AnonymousFrames;
|
|
um.CowFrames = km.CowFrames;
|
|
um.SurfaceFrames = km.SurfaceFrames;
|
|
um.DriverFrames = km.DriverFrames;
|
|
um.DmaFrames = km.DmaFrames;
|
|
um.BootloaderFrames = km.BootloaderFrames;
|
|
um.PfnDatabaseFrames = km.PfnDatabaseFrames;
|
|
um.MmioFrames = km.MmioFrames;
|
|
um.AllocationCount = km.AllocationCount;
|
|
um.FreeCount = km.FreeCount;
|
|
um.RetainCount = km.RetainCount;
|
|
um.ReleaseCount = km.ReleaseCount;
|
|
um.PinCount = km.PinCount;
|
|
um.UnpinCount = km.UnpinCount;
|
|
um.FailedAllocations = km.FailedAllocations;
|
|
um.FailedContiguousAllocations = km.FailedContiguousAllocations;
|
|
um.FailedInvalidRange = km.FailedInvalidRange;
|
|
um.FailedEmptyRange = km.FailedEmptyRange;
|
|
um.FailedReserveDenied = km.FailedReserveDenied;
|
|
um.FailedNoFrame = km.FailedNoFrame;
|
|
um.FailedContiguousInvalidRequest = km.FailedContiguousInvalidRequest;
|
|
um.FailedContiguousReserveDenied = km.FailedContiguousReserveDenied;
|
|
um.FailedContiguousNoRun = km.FailedContiguousNoRun;
|
|
um.ReclaimAttempts = km.ReclaimAttempts;
|
|
um.ReclaimPagesRequested = km.ReclaimPagesRequested;
|
|
um.ReclaimPagesSucceeded = km.ReclaimPagesSucceeded;
|
|
um.ReclaimPagesFailed = km.ReclaimPagesFailed;
|
|
um.ReclaimReentrantSkips = km.ReclaimReentrantSkips;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_PTE: {
|
|
MM_PTE_STATS km;
|
|
SYS_PTE_STATS um;
|
|
MmPteQueryStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.MapCalls = km.MapCalls;
|
|
um.MapFailures = km.MapFailures;
|
|
um.UnmapCalls = km.UnmapCalls;
|
|
um.UnmapFailures = km.UnmapFailures;
|
|
um.ProtectCalls = km.ProtectCalls;
|
|
um.ProtectFailures = km.ProtectFailures;
|
|
um.QueryCalls = km.QueryCalls;
|
|
um.QueryFailures = km.QueryFailures;
|
|
for (size_t i = 0; i < SYS_PTE_QUERY_PURPOSE_COUNT && i < MmPteQueryPurposeCount; i++) {
|
|
um.QueryByPurpose[i] = km.QueryByPurpose[i];
|
|
um.QueryFailuresByPurpose[i] = km.QueryFailuresByPurpose[i];
|
|
}
|
|
um.TranslateCalls = km.TranslateCalls;
|
|
um.LocalFlushes = km.LocalFlushes;
|
|
um.GlobalFlushes = km.GlobalFlushes;
|
|
um.ShootdownRequests = km.ShootdownRequests;
|
|
um.ShootdownTargetCpus = km.ShootdownTargetCpus;
|
|
um.ShootdownSkippedNoTargets = km.ShootdownSkippedNoTargets;
|
|
um.ShootdownAcks = km.ShootdownAcks;
|
|
um.ShootdownTimeouts = km.ShootdownTimeouts;
|
|
um.NewMappingNoFlushes = km.NewMappingNoFlushes;
|
|
um.ReplacedMappingFlushes = km.ReplacedMappingFlushes;
|
|
um.PageTablePagesAllocated = km.PageTablePagesAllocated;
|
|
um.CurrentLeafMappings = km.CurrentLeafMappings;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_VAD: {
|
|
MM_VAD_STATS km;
|
|
SYS_VAD_STATS um;
|
|
MmVadQueryStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.ResolveFaultCalls = km.ResolveFaultCalls;
|
|
um.ResolvedFaults = km.ResolvedFaults;
|
|
um.FailedFaults = km.FailedFaults;
|
|
um.ProtectionFaults = km.ProtectionFaults;
|
|
um.NoVadFaults = km.NoVadFaults;
|
|
um.UncommittedFaults = km.UncommittedFaults;
|
|
um.NoAccessFaults = km.NoAccessFaults;
|
|
um.DemandZeroPages = km.DemandZeroPages;
|
|
um.ZeroPageMaps = km.ZeroPageMaps;
|
|
um.ZeroPageCowSplits = km.ZeroPageCowSplits;
|
|
um.CowFaults = km.CowFaults;
|
|
um.CowPagesCopied = km.CowPagesCopied;
|
|
um.CowViewsCreated = km.CowViewsCreated;
|
|
um.CowPagesShared = km.CowPagesShared;
|
|
um.EnsureResidentCalls = km.EnsureResidentCalls;
|
|
um.EnsureResidentPages = km.EnsureResidentPages;
|
|
um.AlreadyResidentPages = km.AlreadyResidentPages;
|
|
um.FileBackedFaults = km.FileBackedFaults;
|
|
um.FileBackedReads = km.FileBackedReads;
|
|
um.FileBackedReadFailures = km.FileBackedReadFailures;
|
|
um.FileBackedDiscards = km.FileBackedDiscards;
|
|
um.FileBackedSharedWritableFaults = km.FileBackedSharedWritableFaults;
|
|
um.FileBackedDirtyFaults = km.FileBackedDirtyFaults;
|
|
um.FileBackedDirtySyncs = km.FileBackedDirtySyncs;
|
|
um.ImageBackedFaults = km.ImageBackedFaults;
|
|
um.ImageBackedZeroFaults = km.ImageBackedZeroFaults;
|
|
um.ImageBackedPrivateFaults = km.ImageBackedPrivateFaults;
|
|
um.ImageFixupPrivateCalls = km.ImageFixupPrivateCalls;
|
|
um.ImageFixupPrivatePages = km.ImageFixupPrivatePages;
|
|
um.SwapPageOuts = km.SwapPageOuts;
|
|
um.SwapPageIns = km.SwapPageIns;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_FILE_CACHE: {
|
|
MM_FILE_CACHE_STATS km;
|
|
SYS_FILE_CACHE_STATS um;
|
|
MmFileCacheQueryStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.Lookups = km.Lookups;
|
|
um.Hits = km.Hits;
|
|
um.Misses = km.Misses;
|
|
um.Loads = km.Loads;
|
|
um.LoadFailures = km.LoadFailures;
|
|
um.RaceDiscards = km.RaceDiscards;
|
|
um.InflightWaits = km.InflightWaits;
|
|
um.Invalidations = km.Invalidations;
|
|
um.DirtyPages = km.DirtyPages;
|
|
um.MaxDirtyPages = km.MaxDirtyPages;
|
|
um.DirtyMarks = km.DirtyMarks;
|
|
um.WritebackRequests = km.WritebackRequests;
|
|
um.WritebackPages = km.WritebackPages;
|
|
um.WritebackBytes = km.WritebackBytes;
|
|
um.WritebackFailures = km.WritebackFailures;
|
|
um.FlushFileCalls = km.FlushFileCalls;
|
|
um.FlushFileFailures = km.FlushFileFailures;
|
|
um.MappedDirtySyncs = km.MappedDirtySyncs;
|
|
um.MappedDirtyPages = km.MappedDirtyPages;
|
|
um.Drops = km.Drops;
|
|
um.DropFailures = km.DropFailures;
|
|
um.StandbyTransitions = km.StandbyTransitions;
|
|
um.StandbyHits = km.StandbyHits;
|
|
um.ResidentPages = km.ResidentPages;
|
|
um.ActivePages = km.ActivePages;
|
|
um.StandbyPages = km.StandbyPages;
|
|
um.MaxResidentPages = km.MaxResidentPages;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_SURFACE: {
|
|
SYS_SURFACE_STATS stats;
|
|
memset(&stats, 0, sizeof(stats));
|
|
KeSurfaceQueryStats(&stats);
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_SWAP: {
|
|
MM_SWAP_STATS km;
|
|
SYS_SWAP_STATS um;
|
|
MmSwapQueryStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.Enabled = km.Enabled;
|
|
um.TotalSlots = km.TotalSlots;
|
|
um.UsedSlots = km.UsedSlots;
|
|
um.FreeSlots = km.FreeSlots;
|
|
um.HighWaterSlots = km.HighWaterSlots;
|
|
um.PageOuts = km.PageOuts;
|
|
um.PageIns = km.PageIns;
|
|
um.PageOutFailures = km.PageOutFailures;
|
|
um.PageInFailures = km.PageInFailures;
|
|
um.SlotAllocations = km.SlotAllocations;
|
|
um.SlotFrees = km.SlotFrees;
|
|
um.SlotAllocationFailures = km.SlotAllocationFailures;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_FILESYSTEM: {
|
|
IO_FILESYSTEM_STATS km;
|
|
SYS_FILESYSTEM_STATS um;
|
|
IoQueryFilesystemStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.PathLookups = km.PathLookups;
|
|
um.PathCacheHits = km.PathCacheHits;
|
|
um.PathCacheMisses = km.PathCacheMisses;
|
|
um.PathCacheStores = km.PathCacheStores;
|
|
um.PathCacheInvalidations = km.PathCacheInvalidations;
|
|
um.PathCacheGeneration = km.PathCacheGeneration;
|
|
um.DirectorySearches = km.DirectorySearches;
|
|
um.DirectoryBlocksRead = km.DirectoryBlocksRead;
|
|
um.DirectoryEntriesScanned = km.DirectoryEntriesScanned;
|
|
um.DataReadRuns = km.DataReadRuns;
|
|
um.DataReadSectors = km.DataReadSectors;
|
|
um.DataReadRunMerges = km.DataReadRunMerges;
|
|
um.MaxDataReadSpanSectors = km.MaxDataReadSpanSectors;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_VOLUME: {
|
|
IO_VOLUME_STATS km;
|
|
SYS_VOLUME_STATS um;
|
|
IoQueryVolumeStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.CreatedVolumes = km.CreatedVolumes;
|
|
um.MountedVolumes = km.MountedVolumes;
|
|
um.RemovedVolumes = km.RemovedVolumes;
|
|
um.DeadIoRejects = km.DeadIoRejects;
|
|
um.ProviderDestroys = km.ProviderDestroys;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_VOLUME_MANAGER: {
|
|
IO_VOLUME_MANAGER_STATS km;
|
|
SYS_VOLUME_MANAGER_STATS um;
|
|
IoQueryVolumeManagerStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.Snapshots = km.Snapshots;
|
|
um.VolumeOpens = km.VolumeOpens;
|
|
um.Waits = km.Waits;
|
|
um.Wakeups = km.Wakeups;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_DISK: {
|
|
IO_DISK_STATS km;
|
|
SYS_DISK_STATS um;
|
|
IoQueryDiskStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.RegisteredDisks = km.RegisteredDisks;
|
|
um.ReadRequests = km.ReadRequests;
|
|
um.ReadSectors = km.ReadSectors;
|
|
um.WriteRequests = km.WriteRequests;
|
|
um.WriteSectors = km.WriteSectors;
|
|
um.MaxReadSectors = km.MaxReadSectors;
|
|
um.MaxWriteSectors = km.MaxWriteSectors;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
|
|
case SYS_KERNEL_STATS_NET_IRP: {
|
|
NET_IRP_STATS km;
|
|
SYS_NET_IRP_STATS um;
|
|
NetQueryIrpStats(&km);
|
|
memset(&um, 0, sizeof(um));
|
|
um.Size = sizeof(um);
|
|
um.Version = SYS_STATS_VERSION;
|
|
um.Submitted = km.Submitted;
|
|
um.Completed = km.Completed;
|
|
um.CompletedSynchronously = km.CompletedSynchronously;
|
|
um.CompletedAsynchronously = km.CompletedAsynchronously;
|
|
um.Pending = km.Pending;
|
|
um.CancelRequested = km.CancelRequested;
|
|
um.Cancelled = km.Cancelled;
|
|
um.WouldBlock = km.WouldBlock;
|
|
um.Timeout = km.Timeout;
|
|
um.Failed = km.Failed;
|
|
for (uint32_t i = 0; i < SYS_NET_IRP_OPERATION_COUNT && i < NetSocketIrpOperationCount; ++i)
|
|
um.ByOperation[i] = km.ByOperation[i];
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &um, sizeof(um));
|
|
}
|
|
|
|
|
|
case SYS_KERNEL_STATS_EXCEPTION: {
|
|
SYS_EXCEPTION_STATS stats;
|
|
KeQueryExceptionStats(&stats);
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_PORT: {
|
|
KE_PORT_STATS kernelStats;
|
|
SYS_PORT_STATS stats;
|
|
memset(&kernelStats, 0, sizeof(kernelStats));
|
|
memset(&stats, 0, sizeof(stats));
|
|
KePortQueryStats(&kernelStats);
|
|
stats.Size = sizeof(stats);
|
|
stats.Version = SYS_STATS_VERSION;
|
|
stats.ConnectionsCreated = kernelStats.ConnectionsCreated;
|
|
stats.DirectDeliveries = kernelStats.DirectDeliveries;
|
|
stats.DirectBytes = kernelStats.DirectBytes;
|
|
stats.QueuedMessages = kernelStats.QueuedMessages;
|
|
stats.QueuedBytes = kernelStats.QueuedBytes;
|
|
stats.QueuedDeliveries = kernelStats.QueuedDeliveries;
|
|
stats.RepliesCompleted = kernelStats.RepliesCompleted;
|
|
stats.Disconnects = kernelStats.Disconnects;
|
|
stats.ProcessQuotaRejects = kernelStats.ProcessQuotaRejects;
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, sizeof(stats));
|
|
}
|
|
|
|
case SYS_KERNEL_STATS_CPU: {
|
|
struct {
|
|
SYS_CPU_STATS Header;
|
|
SYS_CPU_STAT Records[KE_MAX_CPUS];
|
|
} stats;
|
|
memset(&stats, 0, sizeof(stats));
|
|
|
|
uint32_t n = gCpuCount;
|
|
if (n > KE_MAX_CPUS)
|
|
n = KE_MAX_CPUS;
|
|
|
|
size_t size = sizeof(SYS_CPU_STATS) + (size_t)n * sizeof(SYS_CPU_STAT);
|
|
stats.Header.Size = (uint32_t)size;
|
|
stats.Header.Version = SYS_STATS_VERSION;
|
|
stats.Header.HeaderSize = sizeof(SYS_CPU_STATS);
|
|
stats.Header.CpuCount = gCpuCount;
|
|
stats.Header.RecordSize = sizeof(SYS_CPU_STAT);
|
|
stats.Header.RecordCount = n;
|
|
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
KCPU* cpu = &gCpus[i];
|
|
SYS_CPU_STAT* out = &stats.Records[i];
|
|
out->CpuIndex = cpu->CpuIndex;
|
|
out->HardwareId = cpu->HardwareId;
|
|
KE_CPU_STATE state = (KE_CPU_STATE)cpu->State;
|
|
if (state >= KeCpuPresent) out->Flags |= SYS_CPU_FLAG_PRESENT;
|
|
if (state >= KeCpuStarted) out->Flags |= SYS_CPU_FLAG_STARTED;
|
|
if (state == KeCpuParked) out->Flags |= SYS_CPU_FLAG_PARKED;
|
|
if (state == KeCpuOnline) out->Flags |= SYS_CPU_FLAG_ONLINE;
|
|
if (cpu->IsBootProcessor) out->Flags |= SYS_CPU_FLAG_BOOT_PROCESSOR;
|
|
if (cpu->IsPrimaryThread) out->Flags |= SYS_CPU_FLAG_PRIMARY;
|
|
out->CurrentTid = cpu->CurrentThread ? cpu->CurrentThread->ThreadId : 0;
|
|
if (cpu->CurrentThread && cpu->CurrentThread->IsIdleThread) out->CurrentThreadFlags |= SYS_CPU_THREAD_FLAG_IDLE;
|
|
out->SchedulerTicks = cpu->SchedulerTicks;
|
|
out->ContextSwitches = cpu->ContextSwitches;
|
|
}
|
|
|
|
return KiReturnKernelStats(buffer, bufferSize, requiredSizeOut, &stats, size);
|
|
}
|
|
|
|
default:
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
// Converts one public audio format into the kernel mixer format.
|
|
static KSTATUS KiImportAudioFormat(const SYS_AUDIO_FORMAT* source, IO_AUDIO_FORMAT* destination) {
|
|
if (!source || !destination)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (source->SampleFormat != SysAudioSampleS16 ||
|
|
(source->ChannelCount != 1 && source->ChannelCount != 2) ||
|
|
(source->SampleRate != 11025u && source->SampleRate != 22050u &&
|
|
source->SampleRate != 44100u && source->SampleRate != 48000u))
|
|
return NTLSTATUS_NOT_SUPPORTED;
|
|
|
|
destination->SampleRate = source->SampleRate;
|
|
destination->ChannelCount = source->ChannelCount;
|
|
destination->SampleFormat = IoAudioSampleS16;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Converts one kernel audio format into the public syscall representation.
|
|
static SYS_AUDIO_FORMAT KiExportAudioFormat(const IO_AUDIO_FORMAT* source) {
|
|
SYS_AUDIO_FORMAT destination;
|
|
destination.SampleRate = source->SampleRate;
|
|
destination.ChannelCount = source->ChannelCount;
|
|
destination.SampleFormat = source->SampleFormat == IoAudioSampleS16
|
|
? SysAudioSampleS16
|
|
: 0;
|
|
return destination;
|
|
}
|
|
|
|
// Converts one normalized kernel gain into unsigned Q16.
|
|
static uint32_t KiExportAudioGainQ16(float gain) {
|
|
if (gain <= 0.0f) return 0;
|
|
if (gain >= 1.0f) return SYS_AUDIO_GAIN_Q16_UNITY;
|
|
return (uint32_t)(gain * (float)SYS_AUDIO_GAIN_Q16_UNITY + 0.5f);
|
|
}
|
|
|
|
// Converts one normalized kernel pan into signed Q16.
|
|
static int32_t KiExportAudioPanQ16(float pan) {
|
|
if (pan <= -1.0f) return SYS_AUDIO_PAN_Q16_LEFT;
|
|
if (pan >= 1.0f) return SYS_AUDIO_PAN_Q16_RIGHT;
|
|
float scaled = pan * (float)SYS_AUDIO_GAIN_Q16_UNITY;
|
|
return (int32_t)(scaled + (scaled >= 0.0f ? 0.5f : -0.5f));
|
|
}
|
|
|
|
// Converts one public unsigned Q16 gain into the kernel representation.
|
|
static float KiImportAudioGainQ16(uint32_t gainQ16) {
|
|
return (float)gainQ16 / (float)SYS_AUDIO_GAIN_Q16_UNITY;
|
|
}
|
|
|
|
// Converts one public signed Q16 pan into the kernel representation.
|
|
static float KiImportAudioPanQ16(int32_t panQ16) {
|
|
return (float)panQ16 / (float)SYS_AUDIO_GAIN_Q16_UNITY;
|
|
}
|
|
|
|
// Converts one kernel endpoint kind into its public ABI value.
|
|
static uint32_t KiExportAudioEndpointKind(IO_AUDIO_ENDPOINT_KIND kind) {
|
|
switch (kind) {
|
|
case IoAudioEndpointLineOut: return SysAudioEndpointLineOut;
|
|
case IoAudioEndpointSpeakerHeadphone: return SysAudioEndpointSpeakerHeadphone;
|
|
case IoAudioEndpointOtherAnalog: return SysAudioEndpointOtherAnalog;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// Converts provider endpoint capabilities into public capability bits.
|
|
static uint32_t KiExportAudioEndpointCapabilities(uint32_t capabilities) {
|
|
uint32_t result = 0;
|
|
if (capabilities & IO_AUDIO_ENDPOINT_CAP_JACK_SENSE)
|
|
result |= SYS_AUDIO_ENDPOINT_CAP_JACK_SENSE;
|
|
if (capabilities & IO_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN)
|
|
result |= SYS_AUDIO_ENDPOINT_CAP_HARDWARE_GAIN;
|
|
return result;
|
|
}
|
|
|
|
// Converts one endpoint lifecycle value into its public ABI value.
|
|
static uint32_t KiExportAudioEndpointState(KE_AUDIO_ENDPOINT_STATE state) {
|
|
switch (state) {
|
|
case KeAudioEndpointStarting: return SysAudioEndpointStarting;
|
|
case KeAudioEndpointReady: return SysAudioEndpointReady;
|
|
case KeAudioEndpointStopping: return SysAudioEndpointStopping;
|
|
case KeAudioEndpointFailed: return SysAudioEndpointFailed;
|
|
case KeAudioEndpointRemoved: return SysAudioEndpointRemoved;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// Converts one stream lifecycle value into its public ABI value.
|
|
static uint32_t KiExportAudioStreamState(KE_AUDIO_STREAM_STATE state) {
|
|
switch (state) {
|
|
case KeAudioStreamCreated: return SysAudioStreamCreated;
|
|
case KeAudioStreamRunning: return SysAudioStreamRunning;
|
|
case KeAudioStreamPaused: return SysAudioStreamPaused;
|
|
case KeAudioStreamDraining: return SysAudioStreamDraining;
|
|
case KeAudioStreamStopped: return SysAudioStreamStopped;
|
|
case KeAudioStreamFailed: return SysAudioStreamFailed;
|
|
case KeAudioStreamClosed:
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Converts public wait flags into the kernel stream condition mask.
|
|
static uint32_t KiImportAudioWaitMask(uint32_t mask) {
|
|
uint32_t result = 0;
|
|
if (mask & SYS_AUDIO_WAIT_WRITABLE) result |= KE_AUDIO_WAIT_WRITABLE;
|
|
if (mask & SYS_AUDIO_WAIT_CONSUMED) result |= KE_AUDIO_WAIT_CONSUMED;
|
|
if (mask & SYS_AUDIO_WAIT_PRESENTED) result |= KE_AUDIO_WAIT_PRESENTED;
|
|
if (mask & SYS_AUDIO_WAIT_STOPPED) result |= KE_AUDIO_WAIT_STOPPED;
|
|
if (mask & SYS_AUDIO_WAIT_DRAINED) result |= KE_AUDIO_WAIT_DRAINED;
|
|
if (mask & SYS_AUDIO_WAIT_FAILED) result |= KE_AUDIO_WAIT_FAILED;
|
|
if (mask & SYS_AUDIO_WAIT_DISCONTINUITY) result |= KE_AUDIO_WAIT_DISCONTINUITY;
|
|
return result;
|
|
}
|
|
|
|
// Looks up one audio stream handle with the requested operation rights.
|
|
static KE_AUDIO_STREAM* KiLookupAudioStream(SYS_HANDLE handle, SYS_RIGHTS rights, KSTATUS* statusOut) {
|
|
OBJECT* object = KiHandleToObject(handle, ObjectTypeAudioStream, rights, statusOut);
|
|
return object ? (KE_AUDIO_STREAM*)object : (KE_AUDIO_STREAM*)0;
|
|
}
|
|
|
|
// Implements SYS_AUDIO_ENUMERATE_ENDPOINTS.
|
|
static uint64_t SyscallAudioEnumerateEndpoints(const SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST* userRequest) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (request.Flags != 0 || request.Reserved != 0 ||
|
|
!request.CountOut || (request.Capacity && !request.Endpoints))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
status = MmProbeUserWrite(request.CountOut, sizeof(*request.CountOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
uint32_t copyCapacity = request.Capacity < SYS_AUDIO_MAX_ENDPOINTS
|
|
? request.Capacity
|
|
: SYS_AUDIO_MAX_ENDPOINTS;
|
|
if (copyCapacity) {
|
|
status = MmProbeUserWrite(request.Endpoints,
|
|
(size_t)copyCapacity * sizeof(*request.Endpoints));
|
|
if (!KSUCCESS(status)) return status;
|
|
}
|
|
|
|
KE_AUDIO_ENDPOINT_INFO kernelInfo[KE_AUDIO_MAX_ENDPOINTS];
|
|
uint32_t count = 0;
|
|
status = KeAudioQueryEndpoints(kernelInfo, KE_AUDIO_MAX_ENDPOINTS, &count);
|
|
if (KFAILED(status)) return status;
|
|
|
|
SYS_AUDIO_ENDPOINT_INFO publicInfo[SYS_AUDIO_MAX_ENDPOINTS];
|
|
uint32_t copyCount = copyCapacity < count ? copyCapacity : count;
|
|
for (uint32_t index = 0; index < copyCount; index++) {
|
|
const KE_AUDIO_ENDPOINT_INFO* source = &kernelInfo[index];
|
|
SYS_AUDIO_ENDPOINT_INFO* destination = &publicInfo[index];
|
|
memset(destination, 0, sizeof(*destination));
|
|
destination->Size = sizeof(*destination);
|
|
destination->Version = SYS_AUDIO_ABI_VERSION;
|
|
destination->EndpointId = source->EndpointId;
|
|
destination->Kind = KiExportAudioEndpointKind(source->Kind);
|
|
if (source->Available) destination->Flags |= SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE;
|
|
if (source->IsDefault) destination->Flags |= SYS_AUDIO_ENDPOINT_FLAG_DEFAULT;
|
|
destination->Capabilities = KiExportAudioEndpointCapabilities(source->Capabilities);
|
|
destination->State = KiExportAudioEndpointState(source->State);
|
|
destination->HardwareFormat = KiExportAudioFormat(&source->HardwareFormat);
|
|
destination->PeriodCount = source->PeriodCount;
|
|
destination->FramesPerPeriod = source->FramesPerPeriod;
|
|
destination->MasterVolumeQ16 = KiExportAudioGainQ16(source->MasterRequestedVolume);
|
|
destination->MasterMuted = source->MasterMuted ? 1u : 0u;
|
|
destination->FailureStatus = source->FailureStatus;
|
|
}
|
|
|
|
if (copyCount) {
|
|
status = CopyToUser(request.Endpoints,
|
|
publicInfo,
|
|
(size_t)copyCount * sizeof(publicInfo[0]));
|
|
if (!KSUCCESS(status)) return status;
|
|
}
|
|
status = CopyToUser(request.CountOut, &count, sizeof(count));
|
|
if (!KSUCCESS(status)) return status;
|
|
return request.Capacity < count ? NTLSTATUS_BUFFER_TOO_SMALL : NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements SYS_AUDIO_OPEN_STREAM.
|
|
static uint64_t SyscallAudioOpenStream(const SYS_AUDIO_OPEN_STREAM_REQUEST* userRequest) {
|
|
KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_AUDIO_ACCESS);
|
|
if (!KSUCCESS(privilegeStatus)) return privilegeStatus;
|
|
SYS_AUDIO_OPEN_STREAM_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (request.Flags != 0 || request.Reserved != 0 ||
|
|
!request.EndpointId || !request.StreamHandleOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
status = MmProbeUserWrite(request.StreamHandleOut, sizeof(*request.StreamHandleOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
IO_AUDIO_FORMAT format;
|
|
status = KiImportAudioFormat(&request.Format, &format);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
KE_AUDIO_STREAM* stream = (KE_AUDIO_STREAM*)0;
|
|
status = KeAudioCreateStream(request.EndpointId, &format, &stream);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_HANDLE handle = SYS_HANDLE_INVALID;
|
|
status = KiAllocHandleForCurrentWithRights((OBJECT*)stream,
|
|
KeGetValidHandleRights(ObjectTypeAudioStream),
|
|
&handle);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
status = CopyToUser(request.StreamHandleOut, &handle, sizeof(handle));
|
|
if (!KSUCCESS(status)) {
|
|
PROCESS* process = KeGetCurrentProcess();
|
|
if (process) KeCloseHandle(process, handle);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_AUDIO_WRITE.
|
|
static uint64_t SyscallAudioWrite(const SYS_AUDIO_WRITE_REQUEST* userRequest) {
|
|
SYS_AUDIO_WRITE_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (request.Flags != 0 || !request.StreamHandle ||
|
|
!request.WrittenFramesOut || (request.FrameCount && !request.Frames))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
status = MmProbeUserWrite(request.WrittenFramesOut, sizeof(*request.WrittenFramesOut));
|
|
if (!KSUCCESS(status)) return status;
|
|
uint32_t written = 0;
|
|
|
|
KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_AUDIO_STREAM_RIGHT_WRITE, &status);
|
|
if (!stream) return status;
|
|
if (!request.FrameCount) {
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
return CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
|
|
}
|
|
|
|
KE_AUDIO_STREAM_INFO info;
|
|
status = KeAudioQueryStream(stream, &info);
|
|
if (!KSUCCESS(status)) {
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
return status;
|
|
}
|
|
if (info.State == KeAudioStreamFailed) {
|
|
status = KFAILED(info.FailureStatus) ? info.FailureStatus : NTLSTATUS_DEVICE_ERROR;
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
(void)CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
|
|
return status;
|
|
}
|
|
if (info.State == KeAudioStreamDraining) {
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
(void)CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
if (!info.WritableFrames) {
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
status = CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
|
|
return KSUCCESS(status) ? NTLSTATUS_WOULD_BLOCK : status;
|
|
}
|
|
|
|
uint32_t frameCount = request.FrameCount < info.WritableFrames
|
|
? request.FrameCount
|
|
: info.WritableFrames;
|
|
uint32_t frameBytes = (uint32_t)info.Format.ChannelCount * sizeof(int16_t);
|
|
size_t byteCount = (size_t)frameCount * frameBytes;
|
|
status = MmProbeUserRead(request.Frames, byteCount);
|
|
if (!KSUCCESS(status)) {
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
return status;
|
|
}
|
|
|
|
void* buffer = MmAllocatePoolTag(byteCount, MM_POOL_TAG('a', 'u', 'w', 'r'));
|
|
if (!buffer) {
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
status = CopyFromUser(buffer, request.Frames, byteCount);
|
|
if (KSUCCESS(status))
|
|
status = CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
|
|
if (KSUCCESS(status))
|
|
status = KeAudioWriteStream(stream, buffer, frameCount, &written);
|
|
MmFreePool(buffer);
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
if (!KSUCCESS(status)) return status;
|
|
return CopyToUser(request.WrittenFramesOut, &written, sizeof(written));
|
|
}
|
|
|
|
// Implements SYS_AUDIO_ARM_WAIT.
|
|
static uint64_t SyscallAudioArmWait(const SYS_AUDIO_ARM_WAIT_REQUEST* userRequest) {
|
|
SYS_AUDIO_ARM_WAIT_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (request.Flags != 0 || request.Reserved != 0 ||
|
|
!request.StreamHandle || !request.Mask ||
|
|
(request.Mask & ~SYS_AUDIO_WAIT_VALID_MASK) != 0)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
KE_AUDIO_STREAM_WAIT_CONDITION condition;
|
|
condition.Mask = KiImportAudioWaitMask(request.Mask);
|
|
condition.WritableFrames = request.WritableFrames;
|
|
condition.ConsumedFrames = request.ConsumedFrames;
|
|
condition.PresentedFrames = request.PresentedFrames;
|
|
condition.DrainedGeneration = request.DrainedGeneration;
|
|
condition.DiscontinuityGeneration = request.DiscontinuityGeneration;
|
|
|
|
KE_AUDIO_STREAM* stream = KiLookupAudioStream(request.StreamHandle, SYS_RIGHT_WAIT, &status);
|
|
if (!stream) return status;
|
|
status = KeAudioArmStreamWait(stream, &condition);
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_AUDIO_QUERY_STREAM.
|
|
static uint64_t SyscallAudioQueryStream(SYS_HANDLE streamHandle, SYS_AUDIO_STREAM_INFO* infoOut) {
|
|
if (!streamHandle || !infoOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint32_t userSize = 0;
|
|
KSTATUS status = CopyFromUser(&userSize, infoOut, sizeof(userSize));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (userSize != sizeof(SYS_AUDIO_STREAM_INFO))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
status = MmProbeUserWrite(infoOut, sizeof(SYS_AUDIO_STREAM_INFO));
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
KE_AUDIO_STREAM* stream = KiLookupAudioStream(streamHandle, SYS_RIGHT_QUERY, &status);
|
|
if (!stream) return status;
|
|
KE_AUDIO_STREAM_INFO kernelInfo;
|
|
status = KeAudioQueryStream(stream, &kernelInfo);
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
SYS_AUDIO_STREAM_INFO publicInfo;
|
|
memset(&publicInfo, 0, sizeof(publicInfo));
|
|
publicInfo.Size = sizeof(publicInfo);
|
|
publicInfo.Version = SYS_AUDIO_ABI_VERSION;
|
|
publicInfo.State = KiExportAudioStreamState(kernelInfo.State);
|
|
publicInfo.Format = KiExportAudioFormat(&kernelInfo.Format);
|
|
publicInfo.CapacityFrames = kernelInfo.CapacityFrames;
|
|
publicInfo.BufferedFrames = kernelInfo.BufferedFrames;
|
|
publicInfo.WritableFrames = kernelInfo.WritableFrames;
|
|
publicInfo.FailureStatus = kernelInfo.FailureStatus;
|
|
publicInfo.FramesSubmitted = kernelInfo.FramesSubmitted;
|
|
publicInfo.FramesConsumed = kernelInfo.FramesConsumed;
|
|
publicInfo.FramesPresented = kernelInfo.FramesPresented;
|
|
publicInfo.FramesDiscarded = kernelInfo.FramesDiscarded;
|
|
publicInfo.OutputFramesMixed = kernelInfo.OutputFramesMixed;
|
|
publicInfo.SilenceFramesInserted = kernelInfo.SilenceFramesInserted;
|
|
publicInfo.Underruns = kernelInfo.Underruns;
|
|
publicInfo.DrainedGeneration = kernelInfo.DrainedGeneration;
|
|
publicInfo.DiscontinuityGeneration = kernelInfo.DiscontinuityGeneration;
|
|
publicInfo.VolumeQ16 = KiExportAudioGainQ16(kernelInfo.Volume);
|
|
publicInfo.PanQ16 = KiExportAudioPanQ16(kernelInfo.Pan);
|
|
publicInfo.Muted = kernelInfo.Muted;
|
|
return CopyToUser(infoOut, &publicInfo, sizeof(publicInfo));
|
|
}
|
|
|
|
typedef KSTATUS (*KI_AUDIO_STREAM_OPERATION)(KE_AUDIO_STREAM* stream);
|
|
|
|
// Invokes one existing kernel stream operation through a write-capable handle.
|
|
static KSTATUS KiCallAudioStreamOperation(SYS_HANDLE handle, KI_AUDIO_STREAM_OPERATION operation) {
|
|
if (!handle || !operation) return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS status = NTLSTATUS_SUCCESS;
|
|
KE_AUDIO_STREAM* stream = KiLookupAudioStream(handle, SYS_AUDIO_STREAM_RIGHT_CONTROL, &status);
|
|
if (!stream) return status;
|
|
status = operation(stream);
|
|
ObDereferenceObject((OBJECT*)stream);
|
|
return status;
|
|
}
|
|
|
|
// Implements SYS_AUDIO_START.
|
|
static uint64_t SyscallAudioStart(SYS_HANDLE streamHandle) {
|
|
return KiCallAudioStreamOperation(streamHandle, KeAudioStartStream);
|
|
}
|
|
|
|
// Implements SYS_AUDIO_PAUSE.
|
|
static uint64_t SyscallAudioPause(SYS_HANDLE streamHandle) {
|
|
return KiCallAudioStreamOperation(streamHandle, KeAudioPauseStream);
|
|
}
|
|
|
|
// Implements SYS_AUDIO_RESUME.
|
|
static uint64_t SyscallAudioResume(SYS_HANDLE streamHandle) {
|
|
return KiCallAudioStreamOperation(streamHandle, KeAudioResumeStream);
|
|
}
|
|
|
|
// Implements SYS_AUDIO_DRAIN.
|
|
static uint64_t SyscallAudioDrain(SYS_HANDLE streamHandle) {
|
|
return KiCallAudioStreamOperation(streamHandle, KeAudioDrainStream);
|
|
}
|
|
|
|
// Implements SYS_AUDIO_STOP.
|
|
static uint64_t SyscallAudioStop(SYS_HANDLE streamHandle) {
|
|
return KiCallAudioStreamOperation(streamHandle, KeAudioStopStream);
|
|
}
|
|
|
|
// Implements SYS_AUDIO_RESET.
|
|
static uint64_t SyscallAudioReset(SYS_HANDLE streamHandle) {
|
|
return KiCallAudioStreamOperation(streamHandle, KeAudioResetStream);
|
|
}
|
|
|
|
// Implements SYS_AUDIO_SET_VOLUME.
|
|
static uint64_t SyscallAudioSetVolume(const SYS_AUDIO_SET_VOLUME_REQUEST* userRequest) {
|
|
SYS_AUDIO_SET_VOLUME_REQUEST request;
|
|
KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request));
|
|
if (!KSUCCESS(status)) return status;
|
|
if (request.Flags != 0 || !request.StreamHandle ||
|
|
((const uint8_t*)&request)[offsetof(SYS_AUDIO_SET_VOLUME_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 ||
|
|
((const uint8_t*)&request)[offsetof(SYS_AUDIO_SET_MASTER_VOLUME_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_OPEN_POWER_EVENT] = SYSCALL_ENTRY1(SyscallOpenPowerEvent),
|
|
[SYS_REQUEST_SYSTEM_POWER] = SYSCALL_ENTRY1(SyscallRequestSystemPower),
|
|
[SYS_QUERY_SYSTEM_POWER] = SYSCALL_ENTRY1(SyscallQuerySystemPower),
|
|
[SYS_COMPLETE_SYSTEM_POWER] = SYSCALL_ENTRY1(SyscallCompleteSystemPower),
|
|
[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_OPEN_CURRENT_THREAD] = SYSCALL_ENTRY2(SyscallOpenCurrentThread),
|
|
[SYS_RESUME_THREAD] = SYSCALL_ENTRY2(SyscallResumeThread),
|
|
[SYS_RESUME_THREAD_AND_WAIT] = SYSCALL_ENTRY2(SyscallResumeThreadAndWait),
|
|
[SYS_GET_THREAD_LIST] = SYSCALL_ENTRY4(SyscallGetThreadList),
|
|
[SYS_GET_THREAD_TID] = SYSCALL_ENTRY2(SyscallGetThreadTid),
|
|
[SYS_GET_CURRENT_TID] = SYSCALL_ENTRY0(SyscallGetCurrentTid),
|
|
[SYS_GET_CURRENT_PID] = SYSCALL_ENTRY0(SyscallGetCurrentPid),
|
|
[SYS_SET_THREAD_PRIORITY] = SYSCALL_ENTRY1(SyscallSetThreadPriority),
|
|
[SYS_SET_THREAD_NAME] = SYSCALL_ENTRY1(SyscallSetThreadName),
|
|
[SYS_QUERY_THREAD_NAME] = SYSCALL_ENTRY4(SyscallQueryThreadName),
|
|
[SYS_YIELD] = SYSCALL_ENTRY0(SyscallYield),
|
|
[SYS_SLEEP] = SYSCALL_ENTRY1(SyscallSleep),
|
|
[SYS_WAIT_SINGLE] = SYSCALL_ENTRY3(SyscallWaitSingle),
|
|
[SYS_WAIT_MANY] = SYSCALL_ENTRY1(SyscallWaitMany),
|
|
[SYS_SET_USER_APC_DISPATCHER] = SYSCALL_ENTRY1(SyscallSetUserApcDispatcher),
|
|
[SYS_QUEUE_USER_APC] = SYSCALL_ENTRY1(SyscallQueueUserApc),
|
|
[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_OPEN_PROCESS_SECURITY_CONTEXT] = SYSCALL_ENTRY3(SyscallOpenProcessSecurityContext),
|
|
[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),
|
|
|
|
// 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_OPEN_PORT_PEER_PROCESS] = SYSCALL_ENTRY3(SyscallOpenPortPeerProcess),
|
|
[SYS_REGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallRegisterSystemService),
|
|
[SYS_UNREGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallUnregisterSystemService),
|
|
[SYS_CONNECT_SYSTEM_SERVICE] = SYSCALL_ENTRY1(SyscallConnectSystemService),
|
|
|
|
// Mounted-volume manager control plane.
|
|
[SYS_QUERY_VOLUMES] = SYSCALL_ENTRY1(SyscallQueryVolumes),
|
|
[SYS_OPEN_VOLUME] = SYSCALL_ENTRY4(SyscallOpenVolume),
|
|
[SYS_WAIT_VOLUME_CHANGE] = SYSCALL_ENTRY3(SyscallWaitVolumeChange),
|
|
|
|
// Capability-relative file and directory operations.
|
|
[SYS_OPEN_FILE_AT] = SYSCALL_ENTRY1(SyscallOpenFileAt),
|
|
[SYS_OPEN_DIR_AT] = SYSCALL_ENTRY1(SyscallOpenDirAt),
|
|
[SYS_STAT_AT] = SYSCALL_ENTRY1(SyscallStatAt),
|
|
[SYS_DELETE_AT] = SYSCALL_ENTRY1(SyscallDeleteAt),
|
|
[SYS_RENAME_AT] = SYSCALL_ENTRY1(SyscallRenameAt),
|
|
[SYS_CREATE_DIR_AT] = SYSCALL_ENTRY1(SyscallCreateDirAt),
|
|
[SYS_REMOVE_DIR_AT] = SYSCALL_ENTRY1(SyscallRemoveDirAt),
|
|
[SYS_READ_FILE] = SYSCALL_ENTRY1(SyscallReadFile),
|
|
[SYS_WRITE_FILE] = SYSCALL_ENTRY1(SyscallWriteFile),
|
|
[SYS_READ_FILE_ASYNC] = SYSCALL_ENTRY1(SyscallReadFileAsync),
|
|
[SYS_WRITE_FILE_ASYNC] = SYSCALL_ENTRY1(SyscallWriteFileAsync),
|
|
[SYS_CANCEL_IO] = SYSCALL_ENTRY1(SyscallCancelIo),
|
|
[SYS_SEEK_FILE] = SYSCALL_ENTRY2(SyscallSeekFile),
|
|
[SYS_GET_FILE_POS] = SYSCALL_ENTRY2(SyscallGetFilePos),
|
|
[SYS_GET_FILE_SIZE] = SYSCALL_ENTRY2(SyscallGetFileSize),
|
|
[SYS_GET_FILE_STAT] = SYSCALL_ENTRY2(SyscallGetFileStat),
|
|
[SYS_TRUNCATE_FILE] = SYSCALL_ENTRY2(SyscallTruncateFile),
|
|
[SYS_FLUSH_FILE] = SYSCALL_ENTRY1(SyscallFlushFile),
|
|
[SYS_LOCK_FILE] = SYSCALL_ENTRY1(SyscallLockFile),
|
|
[SYS_UNLOCK_FILE] = SYSCALL_ENTRY1(SyscallUnlockFile),
|
|
[SYS_QUERY_FILE_LOCK] = SYSCALL_ENTRY1(SyscallQueryFileLock),
|
|
[SYS_SET_FILE_STATUS] = SYSCALL_ENTRY1(SyscallSetFileStatus),
|
|
[SYS_CREATE_PIPE] = SYSCALL_ENTRY1(SyscallCreatePipe),
|
|
[SYS_NOTIFY_PIPE] = SYSCALL_ENTRY1(SyscallNotifyPipe),
|
|
[SYS_READ_DIR] = SYSCALL_ENTRY2(SyscallReadDir),
|
|
|
|
// Console, display, and surfaces.
|
|
[SYS_WRITE_CONSOLE] = SYSCALL_ENTRY1(SyscallWriteConsole),
|
|
[SYS_GET_CONSOLE_INFO] = SYSCALL_ENTRY1(SyscallGetConsoleInfo),
|
|
[SYS_SET_CONSOLE_CURSOR] = SYSCALL_ENTRY2(SyscallSetConsoleCursor),
|
|
[SYS_FILL_CONSOLE_CHARACTER] = SYSCALL_ENTRY1(SyscallFillConsoleCharacter),
|
|
[SYS_FILL_CONSOLE_ATTRIBUTE] = SYSCALL_ENTRY1(SyscallFillConsoleAttribute),
|
|
[SYS_PUT_SCREEN_CHAR] = SYSCALL_ENTRY1(SyscallPutScreenChar),
|
|
[SYS_CLEAR_SCREEN] = SYSCALL_ENTRY1(SyscallClearScreen),
|
|
[SYS_GET_SCREEN_SIZE] = SYSCALL_ENTRY2(SyscallGetScreenSize),
|
|
[SYS_QUERY_DISPLAY_INFO] = SYSCALL_ENTRY1(SyscallQueryDisplayInfo),
|
|
[SYS_QUERY_DISPLAY_MANAGER_INFO] = SYSCALL_ENTRY2(SyscallQueryDisplayManagerInfo),
|
|
[SYS_PRESENT_DISPLAY_MANAGER_SURFACE] = SYSCALL_ENTRY4(SyscallPresentDisplayManagerSurface),
|
|
[SYS_SET_DISPLAY_MANAGER_CURSOR] = SYSCALL_ENTRY1(SyscallSetDisplayManagerCursor),
|
|
[SYS_WRITE_DISPLAY_MANAGER_CONSOLE] = SYSCALL_ENTRY2(SyscallWriteDisplayManagerConsole),
|
|
[SYS_QUERY_DISPLAY_MANAGER_CONSOLE_INFO] = SYSCALL_ENTRY2(SyscallQueryDisplayManagerConsoleInfo),
|
|
[SYS_SET_DISPLAY_MANAGER_CONSOLE_CURSOR] = SYSCALL_ENTRY3(SyscallSetDisplayManagerConsoleCursor),
|
|
[SYS_FILL_DISPLAY_MANAGER_CONSOLE_CHARACTER] = SYSCALL_ENTRY2(SyscallFillDisplayManagerConsoleCharacter),
|
|
[SYS_FILL_DISPLAY_MANAGER_CONSOLE_ATTRIBUTE] = SYSCALL_ENTRY2(SyscallFillDisplayManagerConsoleAttribute),
|
|
[SYS_PUT_DISPLAY_MANAGER_CONSOLE_CHARACTER] = SYSCALL_ENTRY2(SyscallPutDisplayManagerConsoleCharacter),
|
|
[SYS_CLEAR_DISPLAY_MANAGER_CONSOLE] = SYSCALL_ENTRY2(SyscallClearDisplayManagerConsole),
|
|
[SYS_OPEN_DISPLAY_CLOCK] = SYSCALL_ENTRY1(SyscallOpenDisplayClock),
|
|
[SYS_CREATE_SURFACE] = SYSCALL_ENTRY1(SyscallCreateSurface),
|
|
[SYS_MAP_SURFACE] = SYSCALL_ENTRY4(SyscallMapSurface),
|
|
[SYS_UNMAP_SURFACE] = SYSCALL_ENTRY1(SyscallUnmapSurface),
|
|
[SYS_QUERY_SURFACE] = SYSCALL_ENTRY2(SyscallQuerySurface),
|
|
[SYS_COMMIT_SURFACE] = SYSCALL_ENTRY4(SyscallCommitSurface),
|
|
|
|
// Native render audio.
|
|
[SYS_AUDIO_ENUMERATE_ENDPOINTS] = SYSCALL_ENTRY1(SyscallAudioEnumerateEndpoints),
|
|
[SYS_AUDIO_OPEN_STREAM] = SYSCALL_ENTRY1(SyscallAudioOpenStream),
|
|
[SYS_AUDIO_WRITE] = SYSCALL_ENTRY1(SyscallAudioWrite),
|
|
[SYS_AUDIO_ARM_WAIT] = SYSCALL_ENTRY1(SyscallAudioArmWait),
|
|
[SYS_AUDIO_QUERY_STREAM] = SYSCALL_ENTRY2(SyscallAudioQueryStream),
|
|
[SYS_AUDIO_START] = SYSCALL_ENTRY1(SyscallAudioStart),
|
|
[SYS_AUDIO_PAUSE] = SYSCALL_ENTRY1(SyscallAudioPause),
|
|
[SYS_AUDIO_RESUME] = SYSCALL_ENTRY1(SyscallAudioResume),
|
|
[SYS_AUDIO_DRAIN] = SYSCALL_ENTRY1(SyscallAudioDrain),
|
|
[SYS_AUDIO_STOP] = SYSCALL_ENTRY1(SyscallAudioStop),
|
|
[SYS_AUDIO_RESET] = SYSCALL_ENTRY1(SyscallAudioReset),
|
|
[SYS_AUDIO_SET_VOLUME] = SYSCALL_ENTRY1(SyscallAudioSetVolume),
|
|
[SYS_AUDIO_SET_MASTER_VOLUME] = SYSCALL_ENTRY1(SyscallAudioSetMasterVolume),
|
|
|
|
// Input.
|
|
[SYS_READ_INPUT_MANAGER_EVENT] = SYSCALL_ENTRY1(SyscallReadInputManagerEvent),
|
|
|
|
// 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;
|
|
}
|