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

805 lines
32 KiB
C

// Kernel exception classification and recoverable fault policy.
#include <kernel/ke_exception.h>
#include <kernel/ke_process.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_trap.h>
#include <kernel/mm_virt.h>
#include <string.h>
#define KI_EXCEPTION_MAX_DISPATCH_DEPTH KE_USER_EXCEPTION_MAX_DEPTH
#define KI_EXCEPTION_USER_ENTRY_SHADOW 40u
#define KI_EXCEPTION_FRAME_ALIGNMENT 16u
#define KI_USER_CS 0x1BULL
#define KI_USER_SS 0x23ULL
#define KI_RFLAGS_FIXED 0x2ULL
#define KI_RFLAGS_IOPL 0x3000ULL
#define KI_RFLAGS_NT 0x4000ULL
#define KI_RFLAGS_VM 0x20000ULL
// Returns whether one user address can be used as a control-flow target or pointer.
static bool KiExceptionUserAddressValid(uintptr_t address) {
return address >= USER_BASE && address < USER_LIMIT;
}
// Returns whether one byte range stays wholly inside user address space.
static bool KiExceptionUserRangeValid(uintptr_t base, size_t size) {
if (size == 0)
return false;
if (base < USER_BASE || base >= USER_LIMIT)
return false;
if (size > (size_t)(USER_LIMIT - base))
return false;
return true;
}
// Returns whether a saved user RFLAGS value can be restored safely.
static bool KiExceptionUserRflagsValid(uint64_t rflags) {
if ((rflags & KI_RFLAGS_FIXED) == 0)
return false;
if ((rflags & (KI_RFLAGS_IOPL | KI_RFLAGS_NT | KI_RFLAGS_VM)) != 0)
return false;
return true;
}
// Rounds an address down to the alignment required by the user-mode ABI.
static uintptr_t KiExceptionAlignDown(uintptr_t value, uintptr_t alignment) {
return value & ~(alignment - 1u);
}
// Terminates the current process from exception-return policy code.
static _Noreturn void KiTerminateCurrentExceptionProcess(KSTATUS exitStatus) {
THREAD* thread = KeGetCurrentThread();
PROCESS* process = thread ? thread->Owner : (PROCESS*)0;
int32_t exitCode = (int32_t)exitStatus;
if (process)
(void)KeTerminateProcess(process, exitCode);
if (thread)
KeExitThread(thread, exitCode);
while (true) {
KeYield();
}
}
// Records a delivered exception against the process that owns user policy.
static void KiRecordExceptionDelivery(PROCESS* process, const SYS_EXCEPTION_RECORD* record,
uint32_t nestedDepth, bool userRaised) {
if (!process || !record)
return;
uintptr_t flags = KeAcquireSpinlock(&process->ExceptionLock);
process->ExceptionDeliveredCount++;
if (userRaised)
process->ExceptionUserRaisedCount++;
if (nestedDepth > process->ExceptionMaxNestedDispatch)
process->ExceptionMaxNestedDispatch = nestedDepth;
process->LastExceptionCode = record->Code;
process->LastExceptionAddress = record->ExceptionAddress;
process->LastExceptionFaultAddress = record->FaultAddress;
KeReleaseSpinlock(&process->ExceptionLock, flags);
}
// Records one completed exception continuation.
static void KiRecordExceptionContinued(PROCESS* process) {
if (!process)
return;
uintptr_t flags = KeAcquireSpinlock(&process->ExceptionLock);
process->ExceptionContinuedCount++;
KeReleaseSpinlock(&process->ExceptionLock, flags);
}
// Records that user policy declined or failed to handle an exception.
static void KiRecordExceptionUnhandled(PROCESS* process) {
if (!process)
return;
uintptr_t flags = KeAcquireSpinlock(&process->ExceptionLock);
process->ExceptionUnhandledCount++;
KeReleaseSpinlock(&process->ExceptionLock, flags);
}
// Records a malformed or forbidden exception continuation request.
static void KiRecordExceptionInvalidContinue(PROCESS* process) {
if (!process)
return;
uintptr_t flags = KeAcquireSpinlock(&process->ExceptionLock);
process->ExceptionInvalidContinueCount++;
KeReleaseSpinlock(&process->ExceptionLock, flags);
}
// Returns the process exit status for one unhandled native exception record.
static KSTATUS KiExceptionUnhandledExitStatus(const SYS_EXCEPTION_RECORD* record) {
if (!record)
return NTLSTATUS_PROCESS_UNKNOWN_FAULT;
switch (record->Code) {
case SYS_EXCEPTION_CODE_INT_DIVIDE_BY_ZERO:
return NTLSTATUS_PROCESS_DIVIDE_BY_ZERO;
case SYS_EXCEPTION_CODE_BREAKPOINT:
return NTLSTATUS_PROCESS_BREAKPOINT;
case SYS_EXCEPTION_CODE_ILLEGAL_INSTRUCTION:
return NTLSTATUS_PROCESS_ILLEGAL_OPCODE;
case SYS_EXCEPTION_CODE_PRIVILEGED_INSTRUCTION:
return NTLSTATUS_PROCESS_GENERAL_PROTECTION;
case SYS_EXCEPTION_CODE_ACCESS_VIOLATION:
return NTLSTATUS_PROCESS_PAGE_FAULT;
case SYS_EXCEPTION_CODE_DATATYPE_MISALIGNMENT:
return NTLSTATUS_PROCESS_ALIGNMENT_FAULT;
case SYS_EXCEPTION_CODE_USER_RAISED:
return NTLSTATUS_PROCESS_USER_EXCEPTION;
default:
return NTLSTATUS_PROCESS_UNKNOWN_FAULT;
}
}
// Maps an architectural vector to the native structured-exception code.
static uint32_t KiExceptionCodeFromVector(uint32_t vector) {
switch (vector) {
case 0: return SYS_EXCEPTION_CODE_INT_DIVIDE_BY_ZERO;
case 1: return SYS_EXCEPTION_CODE_SINGLE_STEP;
case 3: return SYS_EXCEPTION_CODE_BREAKPOINT;
case 4: return SYS_EXCEPTION_CODE_INT_OVERFLOW;
case 5: return SYS_EXCEPTION_CODE_ARRAY_BOUNDS_EXCEEDED;
case 6: return SYS_EXCEPTION_CODE_ILLEGAL_INSTRUCTION;
case 13: return SYS_EXCEPTION_CODE_PRIVILEGED_INSTRUCTION;
case 14: return SYS_EXCEPTION_CODE_ACCESS_VIOLATION;
case 17: return SYS_EXCEPTION_CODE_DATATYPE_MISALIGNMENT;
default: return SYS_EXCEPTION_CODE_UNKNOWN;
}
}
// Fills the user-mode context record from a captured CPU fault frame.
static void KiCaptureUserContextFromFault(const KFAULT_FRAME* frame, SYS_USER_CONTEXT* context) {
memset(context, 0, sizeof(*context));
context->Size = sizeof(*context);
context->Version = SYS_EXCEPTION_ABI_VERSION;
context->Flags = SYS_CONTEXT_FLAG_ALL;
context->Rip = frame->Rip;
context->Rsp = frame->Rsp;
context->Rflags = frame->Rflags;
context->Rax = frame->Rax;
context->Rcx = frame->Rcx;
context->Rdx = frame->Rdx;
context->Rbx = frame->Rbx;
context->Rbp = frame->Rbp;
context->Rsi = frame->Rsi;
context->Rdi = frame->Rdi;
context->R8 = frame->R8;
context->R9 = frame->R9;
context->R10 = frame->R10;
context->R11 = frame->R11;
context->R12 = frame->R12;
context->R13 = frame->R13;
context->R14 = frame->R14;
context->R15 = frame->R15;
context->SegCs = (uint32_t)frame->Cs;
context->SegSs = (uint32_t)frame->Ss;
}
// Fills the user-mode context record from the current syscall return frame.
static void KiCaptureUserContextFromTrap(const KTRAP_FRAME* frame, SYS_USER_CONTEXT* context) {
memset(context, 0, sizeof(*context));
context->Size = sizeof(*context);
context->Version = SYS_EXCEPTION_ABI_VERSION;
context->Flags = SYS_CONTEXT_FLAG_ALL;
context->Rip = frame->Rip;
context->Rsp = frame->Rsp;
context->Rflags = frame->Rflags;
context->Rax = NTLSTATUS_SUCCESS;
context->Rcx = frame->Rcx;
context->Rdx = frame->Rdx;
context->Rbx = frame->Rbx;
context->Rbp = frame->Rbp;
context->Rsi = frame->Rsi;
context->Rdi = frame->Rdi;
context->R8 = frame->R8;
context->R9 = frame->R9;
context->R10 = frame->R10;
context->R11 = frame->R11;
context->R12 = frame->R12;
context->R13 = frame->R13;
context->R14 = frame->R14;
context->R15 = frame->R15;
context->SegCs = (uint32_t)frame->Cs;
context->SegSs = (uint32_t)frame->Ss;
}
// Fills the native exception record from an architectural user fault.
static void KiCaptureExceptionRecordFromFault(const KFAULT_FRAME* frame, SYS_EXCEPTION_RECORD* record) {
memset(record, 0, sizeof(*record));
record->Size = sizeof(*record);
record->Version = SYS_EXCEPTION_ABI_VERSION;
record->Code = KiExceptionCodeFromVector((uint32_t)frame->Vector);
record->Flags = SYS_EXCEPTION_FLAG_FIRST_CHANCE;
if ((frame->Vector == 8) || (frame->Vector == 18))
record->Flags |= SYS_EXCEPTION_FLAG_NONCONTINUABLE;
record->ExceptionAddress = (uintptr_t)frame->Rip;
record->FaultAddress = frame->Vector == 14 ? (uintptr_t)frame->Cr2 : (uintptr_t)frame->Rip;
record->ErrorCode = frame->ErrorCode;
if (frame->Vector == 14) {
uint64_t accessKind = 0;
if (frame->ErrorCode & 0x10u)
accessKind = 8;
else if (frame->ErrorCode & 0x2u)
accessKind = 1;
record->ParameterCount = 2;
record->Parameters[0] = accessKind;
record->Parameters[1] = frame->Cr2;
}
}
// Captures a stable process dispatcher snapshot outside the process lock.
static KSTATUS KiSnapshotExceptionDispatcher(PROCESS* process,
SYS_EXCEPTION_DISPATCHER_REQUEST* out) {
if (!process || !out)
return NTLSTATUS_INVALID_PARAMETER;
memset(out, 0, sizeof(*out));
out->Size = sizeof(*out);
uintptr_t flags = KeAcquireSpinlock(&process->ExceptionLock);
out->Flags = process->ExceptionDispatchFlags;
out->DispatcherRoutine = (SYS_USER_EXCEPTION_DISPATCHER)process->ExceptionDispatcher;
out->DispatcherContext = (void*)process->ExceptionDispatcherContext;
out->ExceptionStackBase = (void*)process->ExceptionStackBase;
out->ExceptionStackSize = process->ExceptionStackSize;
KeReleaseSpinlock(&process->ExceptionLock, flags);
if (!out->DispatcherRoutine)
return NTLSTATUS_NOT_FOUND;
return NTLSTATUS_SUCCESS;
}
// Selects the stack top used to inject one exception dispatcher frame.
static KSTATUS KiSelectExceptionStackTop(THREAD* thread,
const SYS_EXCEPTION_DISPATCHER_REQUEST* dispatcher,
const KFAULT_FRAME* fault,
uintptr_t* stackTopOut) {
if (!thread || !dispatcher || !fault || !stackTopOut)
return NTLSTATUS_INVALID_PARAMETER;
if (dispatcher->ExceptionStackBase) {
uintptr_t base = (uintptr_t)dispatcher->ExceptionStackBase;
uintptr_t top = base + dispatcher->ExceptionStackSize;
uintptr_t stride = KiExceptionAlignDown(sizeof(SYS_EXCEPTION_FRAME) +
KI_EXCEPTION_USER_ENTRY_SHADOW +
KI_EXCEPTION_FRAME_ALIGNMENT - 1u,
KI_EXCEPTION_FRAME_ALIGNMENT);
uintptr_t needed = (uintptr_t)(thread->UserExceptionDepth + 1u) * stride;
if (needed > dispatcher->ExceptionStackSize)
return NTLSTATUS_BUFFER_TOO_SMALL;
*stackTopOut = top - (uintptr_t)thread->UserExceptionDepth * stride;
return NTLSTATUS_SUCCESS;
}
if (!KiExceptionUserAddressValid((uintptr_t)fault->Rsp))
return NTLSTATUS_ACCESS_DENIED;
*stackTopOut = (uintptr_t)fault->Rsp;
return NTLSTATUS_SUCCESS;
}
// Selects an exception stack using an explicit user RSP.
static KSTATUS KiSelectExceptionStackTopFromRsp(THREAD* thread,
const SYS_EXCEPTION_DISPATCHER_REQUEST* dispatcher,
uintptr_t rsp,
uintptr_t* stackTopOut) {
if (!thread || !dispatcher || !stackTopOut)
return NTLSTATUS_INVALID_PARAMETER;
if (dispatcher->ExceptionStackBase) {
uintptr_t base = (uintptr_t)dispatcher->ExceptionStackBase;
uintptr_t top = base + dispatcher->ExceptionStackSize;
uintptr_t stride = KiExceptionAlignDown(sizeof(SYS_EXCEPTION_FRAME) +
KI_EXCEPTION_USER_ENTRY_SHADOW +
KI_EXCEPTION_FRAME_ALIGNMENT - 1u,
KI_EXCEPTION_FRAME_ALIGNMENT);
uintptr_t needed = (uintptr_t)(thread->UserExceptionDepth + 1u) * stride;
if (needed > dispatcher->ExceptionStackSize)
return NTLSTATUS_BUFFER_TOO_SMALL;
*stackTopOut = top - (uintptr_t)thread->UserExceptionDepth * stride;
return NTLSTATUS_SUCCESS;
}
if (!KiExceptionUserAddressValid(rsp))
return NTLSTATUS_ACCESS_DENIED;
*stackTopOut = rsp;
return NTLSTATUS_SUCCESS;
}
// Copies the native exception delivery frame to the selected user stack.
static KSTATUS KiBuildUserExceptionFrame(THREAD* thread,
const SYS_EXCEPTION_DISPATCHER_REQUEST* dispatcher,
KFAULT_FRAME* fault,
uintptr_t* frameAddressOut,
uintptr_t* entryRspOut) {
uintptr_t stackTop = 0;
KSTATUS status = KiSelectExceptionStackTop(thread, dispatcher, fault, &stackTop);
if (!KSUCCESS(status))
return status;
uintptr_t frameAddress = KiExceptionAlignDown(stackTop - sizeof(SYS_EXCEPTION_FRAME),
KI_EXCEPTION_FRAME_ALIGNMENT);
uintptr_t entryRsp = frameAddress - KI_EXCEPTION_USER_ENTRY_SHADOW;
if (!KiExceptionUserRangeValid(entryRsp,
KI_EXCEPTION_USER_ENTRY_SHADOW + sizeof(SYS_EXCEPTION_FRAME)))
return NTLSTATUS_ACCESS_DENIED;
SYS_EXCEPTION_FRAME userFrame;
memset(&userFrame, 0, sizeof(userFrame));
userFrame.Size = sizeof(userFrame);
userFrame.Version = SYS_EXCEPTION_ABI_VERSION;
KiCaptureExceptionRecordFromFault(fault, &userFrame.ExceptionRecord);
KiCaptureUserContextFromFault(fault, &userFrame.ContextRecord);
userFrame.DispatcherContext = dispatcher->DispatcherContext;
uint8_t shadow[KI_EXCEPTION_USER_ENTRY_SHADOW];
memset(shadow, 0, sizeof(shadow));
status = CopyToUser((void*)entryRsp, shadow, sizeof(shadow));
if (!KSUCCESS(status))
return status;
status = CopyToUser((void*)frameAddress, &userFrame, sizeof(userFrame));
if (!KSUCCESS(status))
return status;
*frameAddressOut = frameAddress;
*entryRspOut = entryRsp;
return NTLSTATUS_SUCCESS;
}
// Copies an already-classified user exception frame to the selected user stack.
static KSTATUS KiBuildUserRaisedExceptionFrame(THREAD* thread,
const SYS_EXCEPTION_DISPATCHER_REQUEST* dispatcher,
const KTRAP_FRAME* trapFrame,
const SYS_EXCEPTION_RECORD* record,
uintptr_t* frameAddressOut,
uintptr_t* entryRspOut) {
uintptr_t stackTop = 0;
KSTATUS status = KiSelectExceptionStackTopFromRsp(thread, dispatcher, trapFrame->Rsp, &stackTop);
if (!KSUCCESS(status))
return status;
uintptr_t frameAddress = KiExceptionAlignDown(stackTop - sizeof(SYS_EXCEPTION_FRAME),
KI_EXCEPTION_FRAME_ALIGNMENT);
uintptr_t entryRsp = frameAddress - KI_EXCEPTION_USER_ENTRY_SHADOW;
if (!KiExceptionUserRangeValid(entryRsp,
KI_EXCEPTION_USER_ENTRY_SHADOW + sizeof(SYS_EXCEPTION_FRAME)))
return NTLSTATUS_ACCESS_DENIED;
SYS_EXCEPTION_FRAME userFrame;
memset(&userFrame, 0, sizeof(userFrame));
userFrame.Size = sizeof(userFrame);
userFrame.Version = SYS_EXCEPTION_ABI_VERSION;
userFrame.ExceptionRecord = *record;
KiCaptureUserContextFromTrap(trapFrame, &userFrame.ContextRecord);
userFrame.DispatcherContext = dispatcher->DispatcherContext;
uint8_t shadow[KI_EXCEPTION_USER_ENTRY_SHADOW];
memset(shadow, 0, sizeof(shadow));
status = CopyToUser((void*)entryRsp, shadow, sizeof(shadow));
if (!KSUCCESS(status))
return status;
status = CopyToUser((void*)frameAddress, &userFrame, sizeof(userFrame));
if (!KSUCCESS(status))
return status;
*frameAddressOut = frameAddress;
*entryRspOut = entryRsp;
return NTLSTATUS_SUCCESS;
}
// Validates a user context before using it as an exception resume target.
static KSTATUS KiValidateContinueContext(const SYS_USER_CONTEXT* context) {
if (!context)
return NTLSTATUS_INVALID_PARAMETER;
if (context->Size != sizeof(*context) || context->Version != SYS_EXCEPTION_ABI_VERSION)
return NTLSTATUS_INVALID_PARAMETER;
if (context->Flags != SYS_CONTEXT_FLAG_ALL)
return NTLSTATUS_INVALID_PARAMETER;
if (!KiExceptionUserAddressValid((uintptr_t)context->Rip) ||
!KiExceptionUserAddressValid((uintptr_t)context->Rsp))
return NTLSTATUS_ACCESS_DENIED;
if (!KiExceptionUserRflagsValid(context->Rflags))
return NTLSTATUS_ACCESS_DENIED;
if (context->SegCs != KI_USER_CS || context->SegSs != KI_USER_SS)
return NTLSTATUS_ACCESS_DENIED;
return NTLSTATUS_SUCCESS;
}
// Applies a validated user context to the current syscall return trap frame.
static void KiApplyContinueContextToTrapFrame(KTRAP_FRAME* frame,
const SYS_USER_CONTEXT* context) {
frame->Rip = context->Rip;
frame->Rsp = context->Rsp;
frame->Rflags = context->Rflags;
frame->Cs = context->SegCs;
frame->Ss = context->SegSs;
frame->Rax = context->Rax;
frame->Rcx = context->Rcx;
frame->Rdx = context->Rdx;
frame->Rbx = context->Rbx;
frame->Rbp = context->Rbp;
frame->Rsi = context->Rsi;
frame->Rdi = context->Rdi;
frame->R8 = context->R8;
frame->R9 = context->R9;
frame->R10 = context->R10;
frame->R11 = context->R11;
frame->R12 = context->R12;
frame->R13 = context->R13;
frame->R14 = context->R14;
frame->R15 = context->R15;
}
// Redirects a captured user fault to the registered native exception dispatcher.
KSTATUS KeDeliverUserException(KFAULT_FRAME* frame) {
if (!frame || !KeFaultFrameIsValid(frame) || !KeFaultFrameFromUser(frame))
return NTLSTATUS_INVALID_PARAMETER;
THREAD* thread = KeGetCurrentThread();
PROCESS* process = thread ? thread->Owner : (PROCESS*)0;
if (!thread || !process)
return NTLSTATUS_FAILURE;
if (thread->UserExceptionDepth >= KI_EXCEPTION_MAX_DISPATCH_DEPTH)
return NTLSTATUS_PROCESS_UNKNOWN_FAULT;
SYS_EXCEPTION_DISPATCHER_REQUEST dispatcher;
KSTATUS status = KiSnapshotExceptionDispatcher(process, &dispatcher);
if (!KSUCCESS(status))
return status;
uintptr_t frameAddress = 0;
uintptr_t entryRsp = 0;
status = KiBuildUserExceptionFrame(thread, &dispatcher, frame, &frameAddress, &entryRsp);
if (!KSUCCESS(status))
return status;
SYS_EXCEPTION_RECORD deliveredRecord;
KiCaptureExceptionRecordFromFault(frame, &deliveredRecord);
uint32_t depth = thread->UserExceptionDepth;
if ((frame->Vector == 8) || (frame->Vector == 18))
thread->UserExceptionNoncontinuableMask |= (1u << depth);
thread->UserExceptionExitStatus[depth] = KiExceptionUnhandledExitStatus(&deliveredRecord);
KiRecordExceptionDelivery(process, &deliveredRecord, depth + 1u, false);
thread->UserExceptionDepth = depth + 1u;
thread->UserExceptionFrameUserAddress = frameAddress;
frame->Rip = (uint64_t)(uintptr_t)dispatcher.DispatcherRoutine;
frame->Rsp = entryRsp;
frame->Rcx = frameAddress;
frame->Rdx = 0;
frame->R8 = 0;
frame->R9 = 0;
frame->R10 = 0;
frame->R11 = 0;
frame->Rax = NTLSTATUS_SUCCESS;
frame->Cs = KI_USER_CS;
frame->Ss = KI_USER_SS;
frame->Rflags |= KI_RFLAGS_FIXED;
frame->Rflags &= ~(KI_RFLAGS_IOPL | KI_RFLAGS_NT | KI_RFLAGS_VM);
return NTLSTATUS_SUCCESS;
}
// Raises a user-authored structured exception from the current syscall frame.
uint64_t KeRaiseUserException(const SYS_RAISE_EXCEPTION_REQUEST* request, const uint64_t* parameters) {
if (!request)
return NTLSTATUS_INVALID_PARAMETER;
if (request->Size != sizeof(*request))
return NTLSTATUS_INVALID_PARAMETER;
if (request->Flags & ~SYS_RAISE_EXCEPTION_FLAG_NONCONTINUABLE)
return NTLSTATUS_INVALID_PARAMETER;
if (request->ParameterCount > SYS_EXCEPTION_MAXIMUM_PARAMETERS)
return NTLSTATUS_INVALID_PARAMETER;
if (request->ParameterCount && !parameters)
return NTLSTATUS_INVALID_PARAMETER;
THREAD* thread = KeGetCurrentThread();
PROCESS* process = thread ? thread->Owner : (PROCESS*)0;
if (!thread || !process)
return NTLSTATUS_FAILURE;
if (thread->UserExceptionDepth >= KI_EXCEPTION_MAX_DISPATCH_DEPTH)
return NTLSTATUS_PROCESS_UNKNOWN_FAULT;
KTRAP_FRAME* trapFrame = KeGetCurrentTrapFrame();
if (!trapFrame || !KeTrapFrameFromUser(trapFrame) || trapFrame->Kind != KTRAP_KIND_SYSCALL)
return NTLSTATUS_FAILURE;
SYS_EXCEPTION_DISPATCHER_REQUEST dispatcher;
KSTATUS status = KiSnapshotExceptionDispatcher(process, &dispatcher);
if (!KSUCCESS(status))
return status;
SYS_EXCEPTION_RECORD record;
memset(&record, 0, sizeof(record));
record.Size = sizeof(record);
record.Version = SYS_EXCEPTION_ABI_VERSION;
record.Code = SYS_EXCEPTION_CODE_USER_RAISED;
record.UserCode = request->UserCode;
record.Flags = SYS_EXCEPTION_FLAG_FIRST_CHANCE;
if (request->Flags & SYS_RAISE_EXCEPTION_FLAG_NONCONTINUABLE)
record.Flags |= SYS_EXCEPTION_FLAG_NONCONTINUABLE;
record.ExceptionAddress = (uintptr_t)trapFrame->Rip;
record.FaultAddress = 0;
record.ParameterCount = request->ParameterCount;
for (uint32_t i = 0; i < request->ParameterCount; i++)
record.Parameters[i] = parameters[i];
uintptr_t frameAddress = 0;
uintptr_t entryRsp = 0;
status = KiBuildUserRaisedExceptionFrame(thread, &dispatcher, trapFrame, &record,
&frameAddress, &entryRsp);
if (!KSUCCESS(status))
return status;
uint32_t depth = thread->UserExceptionDepth;
if (record.Flags & SYS_EXCEPTION_FLAG_NONCONTINUABLE)
thread->UserExceptionNoncontinuableMask |= (1u << depth);
thread->UserExceptionExitStatus[depth] = KiExceptionUnhandledExitStatus(&record);
KiRecordExceptionDelivery(process, &record, depth + 1u, true);
thread->UserExceptionDepth = depth + 1u;
thread->UserExceptionFrameUserAddress = frameAddress;
trapFrame->Rip = (uint64_t)(uintptr_t)dispatcher.DispatcherRoutine;
trapFrame->Rsp = entryRsp;
trapFrame->Rcx = frameAddress;
trapFrame->Rdx = 0;
trapFrame->R8 = 0;
trapFrame->R9 = 0;
trapFrame->R10 = 0;
trapFrame->R11 = 0;
trapFrame->Rax = NTLSTATUS_SUCCESS;
trapFrame->Cs = KI_USER_CS;
trapFrame->Ss = KI_USER_SS;
trapFrame->Rflags |= KI_RFLAGS_FIXED;
trapFrame->Rflags &= ~(KI_RFLAGS_IOPL | KI_RFLAGS_NT | KI_RFLAGS_VM);
return NTLSTATUS_SUCCESS;
}
// Completes native exception dispatch by applying the user-selected disposition.
uint64_t KeContinueException(const SYS_CONTINUE_EXCEPTION_REQUEST* request) {
if (!request)
return NTLSTATUS_INVALID_PARAMETER;
if (request->Size != sizeof(*request) || request->Flags != SYS_EXCEPTION_CONTINUE_FLAG_NONE)
return NTLSTATUS_INVALID_PARAMETER;
THREAD* thread = KeGetCurrentThread();
PROCESS* process = thread ? thread->Owner : (PROCESS*)0;
if (!thread || thread->UserExceptionDepth == 0) {
KiRecordExceptionInvalidContinue(process);
return NTLSTATUS_INVALID_PARAMETER;
}
uint32_t activeDepth = thread->UserExceptionDepth - 1u;
uint32_t activeBit = 1u << activeDepth;
KSTATUS activeExitStatus = thread->UserExceptionExitStatus[activeDepth];
if (activeExitStatus == NTLSTATUS_SUCCESS)
activeExitStatus = NTLSTATUS_PROCESS_UNKNOWN_FAULT;
if (request->Disposition == SYS_EXCEPTION_DISPOSITION_CONTINUE_SEARCH ||
request->Disposition == SYS_EXCEPTION_DISPOSITION_TERMINATE_PROCESS) {
KiRecordExceptionUnhandled(process);
thread->UserExceptionDepth = activeDepth;
thread->UserExceptionNoncontinuableMask &= ~activeBit;
thread->UserExceptionFrameUserAddress = 0;
thread->UserExceptionExitStatus[activeDepth] = NTLSTATUS_SUCCESS;
KiTerminateCurrentExceptionProcess(activeExitStatus);
}
if (request->Disposition != SYS_EXCEPTION_DISPOSITION_CONTINUE_EXECUTION) {
KiRecordExceptionInvalidContinue(process);
return NTLSTATUS_INVALID_PARAMETER;
}
if (thread->UserExceptionNoncontinuableMask & activeBit) {
KiRecordExceptionInvalidContinue(process);
thread->UserExceptionDepth = activeDepth;
thread->UserExceptionNoncontinuableMask &= ~activeBit;
thread->UserExceptionFrameUserAddress = 0;
thread->UserExceptionExitStatus[activeDepth] = NTLSTATUS_SUCCESS;
KiTerminateCurrentExceptionProcess(NTLSTATUS_PROCESS_UNKNOWN_FAULT);
}
if (!request->Context) {
KiRecordExceptionInvalidContinue(process);
return NTLSTATUS_INVALID_PARAMETER;
}
SYS_USER_CONTEXT context;
KSTATUS status = CopyFromUser(&context, request->Context, sizeof(context));
if (!KSUCCESS(status))
return status;
status = KiValidateContinueContext(&context);
if (!KSUCCESS(status)) {
KiRecordExceptionInvalidContinue(process);
KiTerminateCurrentExceptionProcess(status);
}
KTRAP_FRAME* trapFrame = KeGetCurrentTrapFrame();
if (!trapFrame || !KeTrapFrameFromUser(trapFrame) || trapFrame->Kind != KTRAP_KIND_SYSCALL) {
KiRecordExceptionInvalidContinue(process);
return NTLSTATUS_FAILURE;
}
KiApplyContinueContextToTrapFrame(trapFrame, &context);
KiRecordExceptionContinued(process);
thread->UserExceptionDepth = activeDepth;
thread->UserExceptionNoncontinuableMask &= ~activeBit;
thread->UserExceptionFrameUserAddress = 0;
thread->UserExceptionExitStatus[activeDepth] = NTLSTATUS_SUCCESS;
return context.Rax;
}
// Returns whether a fault frame was initialized by the assembly stubs.
bool KeFaultFrameIsValid(const KFAULT_FRAME* frame) {
return frame && frame->Magic == KFAULT_FRAME_MAGIC_VALUE;
}
// Returns whether a fault frame captured an interrupted user-mode context.
bool KeFaultFrameFromUser(const KFAULT_FRAME* frame) {
return KeFaultFrameIsValid(frame) && ((frame->Cs & 3u) == 3u);
}
// Returns the short architectural name for one CPU exception vector.
const char* KeExceptionVectorName(uint32_t vector) {
switch (vector) {
case 0: return "#DE Divide Error";
case 1: return "#DB Debug";
case 2: return "NMI Interrupt";
case 3: return "#BP Breakpoint";
case 4: return "#OF Overflow";
case 5: return "#BR Bound Range Exceeded";
case 6: return "#UD Invalid Opcode";
case 7: return "#NM Device Not Available";
case 8: return "#DF Double Fault";
case 9: return "Coprocessor Segment Overrun";
case 10: return "#TS Invalid TSS";
case 11: return "#NP Segment Not Present";
case 12: return "#SS Stack-Segment Fault";
case 13: return "#GP General Protection";
case 14: return "#PF Page Fault";
case 15: return "Reserved Exception 15";
case 16: return "#MF x87 Floating-Point Exception";
case 17: return "#AC Alignment Check";
case 18: return "#MC Machine Check";
case 19: return "#XM SIMD Floating-Point Exception";
case 20: return "#VE Virtualization Exception";
case 21: return "#CP Control Protection Exception";
case 22: return "Reserved Exception 22";
case 23: return "Reserved Exception 23";
case 24: return "Reserved Exception 24";
case 25: return "Reserved Exception 25";
case 26: return "Reserved Exception 26";
case 27: return "Reserved Exception 27";
case 28: return "#HV Hypervisor Injection Exception";
case 29: return "#VC VMM Communication Exception";
case 30: return "#SX Security Exception";
case 31: return "Reserved Exception 31";
default: return "Unknown Exception";
}
}
// Returns whether the CPU pushes an error code for one exception vector.
bool KeExceptionVectorHasErrorCode(uint32_t vector) {
switch (vector) {
case 8:
case 10:
case 11:
case 12:
case 13:
case 14:
case 17:
case 21:
case 29:
case 30:
return true;
default:
return false;
}
}
// Maps an unhandled user-mode CPU exception to the legacy process exit status.
KSTATUS KeExceptionProcessExitStatus(uint32_t vector) {
switch (vector) {
case 0: return NTLSTATUS_PROCESS_DIVIDE_BY_ZERO;
case 3: return NTLSTATUS_PROCESS_BREAKPOINT;
case 6: return NTLSTATUS_PROCESS_ILLEGAL_OPCODE;
case 13: return NTLSTATUS_PROCESS_GENERAL_PROTECTION;
case 14: return NTLSTATUS_PROCESS_PAGE_FAULT;
case 17: return NTLSTATUS_PROCESS_ALIGNMENT_FAULT;
default: return NTLSTATUS_PROCESS_UNKNOWN_FAULT;
}
}
// Handles faults that are still kernel-owned repair cases, not user exceptions.
KSTATUS KeExceptionResolveRecoverableFault(const KFAULT_FRAME* frame) {
if (!KeFaultFrameIsValid(frame))
return NTLSTATUS_INVALID_PARAMETER;
if (frame->Vector != 14)
return NTLSTATUS_NOT_SUPPORTED;
return MmVadResolvePageFaultByAddress((uintptr_t)frame->Cr2, frame->ErrorCode);
}
// Validates a user exception dispatcher registration before publishing it.
static KSTATUS KiValidateExceptionDispatcherRequest(const SYS_EXCEPTION_DISPATCHER_REQUEST* request) {
if (!request)
return NTLSTATUS_INVALID_PARAMETER;
if (request->Size != sizeof(*request))
return NTLSTATUS_INVALID_PARAMETER;
if (request->Flags != SYS_EXCEPTION_DISPATCH_FLAG_NONE)
return NTLSTATUS_INVALID_PARAMETER;
if (!request->DispatcherRoutine) {
if (request->ExceptionStackBase || request->ExceptionStackSize)
return NTLSTATUS_INVALID_PARAMETER;
return NTLSTATUS_SUCCESS;
}
if (!KiExceptionUserAddressValid((uintptr_t)request->DispatcherRoutine))
return NTLSTATUS_ACCESS_DENIED;
if ((request->ExceptionStackBase == 0) != (request->ExceptionStackSize == 0))
return NTLSTATUS_INVALID_PARAMETER;
if (request->ExceptionStackBase) {
if (!KiExceptionUserRangeValid((uintptr_t)request->ExceptionStackBase, request->ExceptionStackSize))
return NTLSTATUS_ACCESS_DENIED;
if (request->ExceptionStackSize < sizeof(SYS_EXCEPTION_FRAME) + 64u)
return NTLSTATUS_BUFFER_TOO_SMALL;
}
return NTLSTATUS_SUCCESS;
}
// Registers or clears the current process's native user exception dispatcher.
KSTATUS KeSetExceptionDispatcher(PROCESS* process, const SYS_EXCEPTION_DISPATCHER_REQUEST* request) {
if (!process || !request)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = KiValidateExceptionDispatcherRequest(request);
if (!KSUCCESS(status))
return status;
uintptr_t flags = KeAcquireSpinlock(&process->ExceptionLock);
if (!request->DispatcherRoutine) {
process->ExceptionDispatcher = 0;
process->ExceptionDispatcherContext = 0;
process->ExceptionStackBase = 0;
process->ExceptionStackSize = 0;
process->ExceptionDispatchFlags = SYS_EXCEPTION_DISPATCH_FLAG_NONE;
} else {
process->ExceptionDispatcher = (uintptr_t)request->DispatcherRoutine;
process->ExceptionDispatcherContext = (uintptr_t)request->DispatcherContext;
process->ExceptionStackBase = (uintptr_t)request->ExceptionStackBase;
process->ExceptionStackSize = request->ExceptionStackSize;
process->ExceptionDispatchFlags = request->Flags;
}
KeReleaseSpinlock(&process->ExceptionLock, flags);
return NTLSTATUS_SUCCESS;
}
// Copies a stable snapshot of the current process's exception dispatcher registration.
KSTATUS KeGetExceptionDispatcher(PROCESS* process, SYS_EXCEPTION_DISPATCHER_REQUEST* request) {
if (!process || !request)
return NTLSTATUS_INVALID_PARAMETER;
memset(request, 0, sizeof(*request));
request->Size = sizeof(*request);
uintptr_t flags = KeAcquireSpinlock(&process->ExceptionLock);
request->Flags = process->ExceptionDispatchFlags;
request->DispatcherRoutine = (SYS_USER_EXCEPTION_DISPATCHER)process->ExceptionDispatcher;
request->DispatcherContext = (void*)process->ExceptionDispatcherContext;
request->ExceptionStackBase = (void*)process->ExceptionStackBase;
request->ExceptionStackSize = process->ExceptionStackSize;
KeReleaseSpinlock(&process->ExceptionLock, flags);
return NTLSTATUS_SUCCESS;
}