// Kernel process model. This file owns process lifetime, thread membership, and address-space metadata. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Global process list. static LIST_ENTRY gProcessList; static SPINLOCK gProcessListLock; static KE_ID_ALLOCATOR gProcessIds = KE_ID_ALLOCATOR_INIT(1u); static PROCESS* gSystemProcess = (PROCESS*)0; // Processes init. static void KiProcessInit(void) { static int inited = 0; if (!inited) { LIST_INIT(&gProcessList); gProcessListLock = (SPINLOCK)SPINLOCK_INIT; inited = 1; } } // Returns process List. LIST_ENTRY* KeGetProcessList(void) { KiProcessInit(); return &gProcessList; } // Returns current Process. PROCESS* KeGetCurrentProcess(void) { THREAD* currentThread = KeGetCurrentThread(); if (currentThread) { return currentThread->Owner; } return NULL; } // Returns system Process. PROCESS* KeGetSystemProcess(void) { return gSystemProcess; } // Creates system Process. KSTATUS KeCreateSystemProcess(PROCESS** out) { if (!out) return NTLSTATUS_INVALID_PARAMETER; if (gSystemProcess) { *out = gSystemProcess; return NTLSTATUS_SUCCESS; } PROCESS* proc = (PROCESS*)0; KSTATUS s = KeCreateProcess("system", SeGetRootSecurityContext(), &proc); if (!KSUCCESS(s)) { *out = (PROCESS*)0; return s; } proc->Flags |= KE_PROCESS_FLAG_SYSTEM | KE_PROCESS_FLAG_TERMINATION_PROTECTED; gSystemProcess = proc; *out = proc; return NTLSTATUS_SUCCESS; } // Copies truncated String. static void KiCopyTruncatedString(char* dst, size_t dstSize, const char* src, size_t srcLen) { if (!dst || dstSize == 0) return; size_t n = 0; if (src) { while (n + 1 < dstSize && n < srcLen && src[n]) { dst[n] = src[n]; n++; } } dst[n] = '\0'; } // Aggregates native structured-exception counters across all processes. void KeQueryExceptionStats(SYS_EXCEPTION_STATS* statsOut) { if (!statsOut) return; memset(statsOut, 0, sizeof(*statsOut)); statsOut->Size = sizeof(*statsOut); statsOut->Version = SYS_STATS_VERSION; KiProcessInit(); uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); LIST_ENTRY* head = &gProcessList; for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink); uintptr_t exceptionFlags = KeAcquireSpinlock(&p->ExceptionLock); statsOut->Delivered += p->ExceptionDeliveredCount; statsOut->UserRaised += p->ExceptionUserRaisedCount; statsOut->Continued += p->ExceptionContinuedCount; statsOut->Unhandled += p->ExceptionUnhandledCount; statsOut->InvalidContinue += p->ExceptionInvalidContinueCount; if (p->ExceptionMaxNestedDispatch > statsOut->MaxNestedDispatch) statsOut->MaxNestedDispatch = p->ExceptionMaxNestedDispatch; if (p->LastExceptionCode) { statsOut->LastCode = p->LastExceptionCode; statsOut->LastExceptionAddress = p->LastExceptionAddress; statsOut->LastFaultAddress = p->LastExceptionFaultAddress; } KeReleaseSpinlock(&p->ExceptionLock, exceptionFlags); } KeReleaseSpinlock(&gProcessListLock, flags); } // Copies process Info. size_t KeCopyProcessInfo(struct SYS_PROCESS_INFO* infoOut, size_t maxCount) { KiProcessInit(); PROCESS* current = KeGetCurrentProcess(); uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); size_t count = 0; LIST_ENTRY* head = &gProcessList; for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink); if (infoOut && count < maxCount) { SYS_PROCESS_INFO* info = &infoOut[count]; memset(info, 0, sizeof(*info)); info->Size = sizeof(*info); info->ProcessId = p->ProcessId; info->SecurityId = p->SecurityContext ? p->SecurityContext->SecurityId : 0u; info->ImageBase = p->ImageBase; info->ImageSize = p->ImageSize; info->PebBase = p->UserPebBase; uintptr_t threadFlags = KeAcquireSpinlock(&p->ThreadLock); uint32_t threadCount = p->ThreadCount; KeReleaseSpinlock(&p->ThreadLock, threadFlags); uint32_t handleCount = KeGetHandleCount(p); int32_t exitCode = p->ExitCode; int32_t terminating = p->Terminating; int32_t exited = p->Exited; info->ThreadCount = threadCount; info->HandleCount = handleCount; info->ExitCode = exitCode; uintptr_t exceptionFlags = KeAcquireSpinlock(&p->ExceptionLock); info->ExceptionDelivered = p->ExceptionDeliveredCount; info->ExceptionUserRaised = p->ExceptionUserRaisedCount; info->ExceptionContinued = p->ExceptionContinuedCount; info->ExceptionUnhandled = p->ExceptionUnhandledCount; info->ExceptionInvalidContinue = p->ExceptionInvalidContinueCount; info->ExceptionMaxNestedDispatch = p->ExceptionMaxNestedDispatch; info->LastExceptionCode = p->LastExceptionCode; info->LastExceptionAddress = p->LastExceptionAddress; info->LastExceptionFaultAddress = p->LastExceptionFaultAddress; KeReleaseSpinlock(&p->ExceptionLock, exceptionFlags); if (exited) { info->State = SYS_PROCESS_STATE_EXITED; info->Flags |= SYS_PROCESS_FLAG_EXITED; } else if (terminating) { info->State = SYS_PROCESS_STATE_TERMINATING; info->Flags |= SYS_PROCESS_FLAG_TERMINATING; } else if (NtlAtomicLoad32(&p->BuildState) != KE_PROCESS_BUILD_STARTED) { info->State = SYS_PROCESS_STATE_CONSTRUCTING; info->Flags |= SYS_PROCESS_FLAG_CONSTRUCTING; } else { info->State = SYS_PROCESS_STATE_RUNNING; } if (p->Flags & KE_PROCESS_FLAG_SYSTEM) info->Flags |= SYS_PROCESS_FLAG_SYSTEM; if (p->Flags & KE_PROCESS_FLAG_IDLE) info->Flags |= SYS_PROCESS_FLAG_IDLE; if (p->Flags & KE_PROCESS_FLAG_TERMINATION_PROTECTED) info->Flags |= SYS_PROCESS_FLAG_TERMINATION_PROTECTED; if (p == current) info->Flags |= SYS_PROCESS_FLAG_CURRENT; MmVadQueryProcessMemory(p, &info->VadCount, &info->UserReservedBytes, &info->UserCommittedBytes); KiCopyTruncatedString(info->Name, sizeof(info->Name), p->Name, sizeof(p->Name)); } count++; } KeReleaseSpinlock(&gProcessListLock, flags); return count; } // Converts a kernel thread state to the public ABI state. static uint32_t KiThreadStateToSys(THREAD_STATE state) { switch (state) { case ThreadReady: return SYS_THREAD_STATE_READY; case ThreadRunning: return SYS_THREAD_STATE_RUNNING; case ThreadWaiting: return SYS_THREAD_STATE_WAITING; case ThreadDead: return SYS_THREAD_STATE_DEAD; case ThreadSuspended: return SYS_THREAD_STATE_SUSPENDED; default: return SYS_THREAD_STATE_DEAD; } } // Converts a kernel object type to the public wait-object type. static uint32_t KiWaitObjectTypeToSys(OBJECT_TYPE type) { switch (type) { case ObjectTypeProcess: return SYS_THREAD_WAIT_PROCESS; case ObjectTypeThread: return SYS_THREAD_WAIT_THREAD; case ObjectTypeMutex: return SYS_THREAD_WAIT_MUTEX; case ObjectTypeSemaphore: return SYS_THREAD_WAIT_SEMAPHORE; case ObjectTypeEvent: return SYS_THREAD_WAIT_EVENT; case ObjectTypeQueue: return SYS_THREAD_WAIT_QUEUE; case ObjectTypeFile: return SYS_THREAD_WAIT_FILE; case ObjectTypeDir: return SYS_THREAD_WAIT_DIR; case ObjectTypeAsyncIo: return SYS_THREAD_WAIT_ASYNC_IO; case ObjectTypeSocket: return SYS_THREAD_WAIT_SOCKET; case ObjectTypeDevice: return SYS_THREAD_WAIT_DEVICE; case ObjectTypeDriver: return SYS_THREAD_WAIT_DRIVER; case ObjectTypeUsbDevice: return SYS_THREAD_WAIT_USB_DEVICE; case ObjectTypeUsbPipe: return SYS_THREAD_WAIT_USB_PIPE; case ObjectTypeFileMapping: return SYS_THREAD_WAIT_FILE_MAPPING; case ObjectTypeSurface: return SYS_THREAD_WAIT_SURFACE; case ObjectTypeTimer: return SYS_THREAD_WAIT_TIMER; case ObjectTypeDisplayClock: return SYS_THREAD_WAIT_DISPLAY_CLOCK; case ObjectTypePipeEndpoint: return SYS_THREAD_WAIT_PIPE; default: return SYS_THREAD_WAIT_UNKNOWN; } } // Copies one process's thread telemetry into caller-provided storage. KSTATUS KeCopyProcessThreadInfo(uint32_t pid, struct SYS_THREAD_INFO* infoOut, size_t maxCount, size_t* countOut) { if (!countOut) return NTLSTATUS_INVALID_PARAMETER; if (maxCount && !infoOut) return NTLSTATUS_INVALID_PARAMETER; PROCESS* process = KeGetProcessById(pid); if (!process) return NTLSTATUS_NOT_FOUND; THREAD* current = KeGetCurrentThread(); size_t count = 0; uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock); LIST_ENTRY* head = &process->ThreadList; for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { THREAD* thread = CONTAINING_RECORD(node, THREAD, ProcessLink); if (infoOut && count < maxCount) { SYS_THREAD_INFO* info = &infoOut[count]; memset(info, 0, sizeof(*info)); info->Size = sizeof(*info); KE_THREAD_SCHEDULER_SNAPSHOT snapshot; KiSchedulerSnapshotThread(thread, &snapshot); info->ProcessId = process->ProcessId; info->ThreadId = thread->ThreadId; info->State = KiThreadStateToSys(snapshot.State); info->BaseSchedulingClass = (uint32_t)snapshot.BaseSchedulingClass; info->BasePriority = snapshot.BasePriority; info->EffectiveSchedulingClass = (uint32_t)snapshot.EffectiveSchedulingClass; info->EffectivePriority = snapshot.EffectivePriority; info->RunningCpu = snapshot.RunningCpu; info->LastCpu = snapshot.LastCpu; info->QuantumRemaining = snapshot.QuantumRemaining; info->QuantumTicks = snapshot.QuantumTicks; info->CpuBurstTicks = snapshot.CpuBurstTicks; info->LastRunTicks = snapshot.LastRunTicks; info->WaitObjectType = snapshot.WaitObjectType != 0 ? KiWaitObjectTypeToSys((OBJECT_TYPE)snapshot.WaitObjectType) : SYS_THREAD_WAIT_NONE; info->WaitStatus = (uint32_t)snapshot.WaitStatus; info->KernelApcDisable = thread->KernelApcDisable; (void)KeQueryThreadName(thread, info->Name, sizeof(info->Name)); if (thread == current) info->Flags |= SYS_THREAD_FLAG_CURRENT; if (thread->IsIdleThread) info->Flags |= SYS_THREAD_FLAG_IDLE; if (!thread->Teb) info->Flags |= SYS_THREAD_FLAG_KERNEL; if (snapshot.Queued) info->Flags |= SYS_THREAD_FLAG_QUEUED; if (snapshot.AlertableWait) info->Flags |= SYS_THREAD_FLAG_ALERTABLE_WAIT; if (thread->KernelApcPending) info->Flags |= SYS_THREAD_FLAG_KERNEL_APC_PENDING; if (thread->UserApcPending) info->Flags |= SYS_THREAD_FLAG_USER_APC_PENDING; } count++; } KeReleaseSpinlock(&process->ThreadLock, flags); *countOut = count; ObDereferenceObject(&process->Header); return NTLSTATUS_SUCCESS; } // Releases the process-owned address space and per-process kernel resources. // Idempotent because the normal exit path and the final OBJECT destructor can // both observe a process whose threads are already gone. static void KiProcessRundownResources(PROCESS* proc) { if (!proc) return; if (NtlAtomicExchange32(&proc->ResourcesReleased, 1) != 0) return; IoReleaseFileLocksForProcess(proc); KeRundownHandleTable(proc); // Release every user VA region owned by this process. This includes the // main image, DLLs, TEBs/stacks that survived thread teardown, process // environment strings, mmap views, and leaked user allocations. MmVadDestroyAllForProcess(proc); proc->ImageBase = 0; proc->ImageSize = 0; proc->UserPebBase = 0; proc->UserPebSize = 0; proc->UserImagePathBase = 0; proc->UserImagePathSize = 0; proc->UserCmdlineBase = 0; proc->UserCmdlineSize = 0; proc->UserStartupCapabilitiesBase = 0; proc->UserStartupCapabilitiesSize = 0; uintptr_t exceptionFlags = KeAcquireSpinlock(&proc->ExceptionLock); proc->ExceptionDispatcher = 0; proc->ExceptionDispatcherContext = 0; proc->ExceptionStackBase = 0; proc->ExceptionStackSize = 0; proc->ExceptionDispatchFlags = SYS_EXCEPTION_DISPATCH_FLAG_NONE; KeReleaseSpinlock(&proc->ExceptionLock, exceptionFlags); } // Destructor (called by ObDereferenceObject when RefCount hits zero). static void KiProcessDestroy(OBJECT* obj) { PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header); if (proc == gSystemProcess) gSystemProcess = (PROCESS*)0; // If the process failed during launch, or no normal exit completion path // ran, make sure its own handles and VADs are still reclaimed. KiProcessRundownResources(proc); if (proc->PortOutstandingMessages != 0 || proc->PortOutstandingBytes != 0 || proc->PortOutstandingHandles != 0 || proc->PortOutstandingCalls != 0) { KePanic(KE_PANIC_CODE_GENERIC, "process: destroying with live Port quota pid=%u messages=%u bytes=%u handles=%u calls=%u\n", proc->ProcessId, proc->PortOutstandingMessages, proc->PortOutstandingBytes, proc->PortOutstandingHandles, proc->PortOutstandingCalls); } // Release the exit event only when the process object itself dies. External // process handles may still wait/query an exited process after rundown. if (proc->ExitEvent) { ObDereferenceObject(&proc->ExitEvent->Header); proc->ExitEvent = (EVENT*)0; } if (proc->SecurityContext) { ObDereferenceObject(&proc->SecurityContext->Header); proc->SecurityContext = (SECURITY_CONTEXT*)0; } // Remove from global list. uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); KeListRemoveChecked(&proc->GlobalLink); KeReleaseSpinlock(&gProcessListLock, flags); // Memory for the struct itself is freed by ObDereferenceObject after // Destroy returns. } // KeCreateProcess. KSTATUS KeCreateProcess(const char* name, SECURITY_CONTEXT* securityContext, PROCESS** out) { KiProcessInit(); if (!name || !securityContext || !out) return NTLSTATUS_INVALID_PARAMETER; size_t nameLength = strlen(name); if (!SysSvIsUtf8Text(SysSvFromParts(name, nameLength))) return NTLSTATUS_INVALID_PARAMETER; uint32_t processId = 0; KSTATUS idStatus = KeAllocateId32(&gProcessIds, &processId); if (!KSUCCESS(idStatus)) return idStatus; PROCESS* proc = (PROCESS*)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS)); if (!proc) return NTLSTATUS_NO_MEMORY; proc->Header.Destroy = KiProcessDestroy; proc->ProcessId = processId; proc->Flags = 0; proc->SecurityContext = securityContext; ObReferenceObject(&securityContext->Header); // Truncate the diagnostic name on a scalar boundary, never inside UTF-8. size_t nameCopy = nameLength < PROCESS_NAME_MAX ? nameLength : PROCESS_NAME_MAX - 1u; if (nameCopy < nameLength) { while (nameCopy && (((uint8_t)name[nameCopy] & 0xC0u) == 0x80u)) --nameCopy; } memcpy(proc->Name, name, nameCopy); proc->Name[nameCopy] = '\0'; LIST_INIT(&proc->ThreadList); proc->ThreadCount = 0; proc->ThreadLock = (SPINLOCK)SPINLOCK_INIT; proc->ExitCode = 0; proc->Terminating = 0; proc->Exited = 0; proc->ResourcesReleased = 0; proc->BuildState = KE_PROCESS_BUILD_STARTED; proc->BuildOperations = 0; proc->ImageBase = 0; proc->ImageSize = 0; proc->UserPebBase = 0; proc->UserPebSize = 0; proc->UserImagePathBase = 0; proc->UserImagePathSize = 0; proc->UserCmdlineBase = 0; proc->UserCmdlineSize = 0; proc->UserStartupCapabilitiesBase = 0; proc->UserStartupCapabilitiesSize = 0; proc->ExceptionDispatcher = 0; proc->ExceptionDispatcherContext = 0; proc->ExceptionStackBase = 0; proc->ExceptionStackSize = 0; proc->ExceptionDispatchFlags = SYS_EXCEPTION_DISPATCH_FLAG_NONE; proc->ExceptionDeliveredCount = 0; proc->ExceptionUserRaisedCount = 0; proc->ExceptionContinuedCount = 0; proc->ExceptionUnhandledCount = 0; proc->ExceptionInvalidContinueCount = 0; proc->ExceptionMaxNestedDispatch = 0; proc->LastExceptionCode = 0; proc->LastExceptionAddress = 0; proc->LastExceptionFaultAddress = 0; proc->ExceptionLock = (SPINLOCK)SPINLOCK_INIT; LIST_INIT(&proc->VadList); proc->VadLock = (SPINLOCK)SPINLOCK_INIT; KeInitializeHandleTable(&proc->HandleTable); proc->PortQuotaLock = (SPINLOCK)SPINLOCK_INIT; proc->PortOutstandingMessages = 0; proc->PortOutstandingBytes = 0; proc->PortOutstandingHandles = 0; proc->PortOutstandingCalls = 0; LIST_INIT(&proc->GlobalLink); // Create the manual-reset exit event (initially unsignaled). if (!KSUCCESS(KeEventCreate(EventManualReset, 0, &proc->ExitEvent))) { ObDereferenceObject(&proc->Header); return NTLSTATUS_NO_MEMORY; } uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); KeListInsertTailChecked(&gProcessList, &proc->GlobalLink); KeReleaseSpinlock(&gProcessListLock, flags); *out = proc; return NTLSTATUS_SUCCESS; } // KeSetupUserProcessEnvironment. static KSTATUS KiCopyEnvironmentStringView(PROCESS* proc, SYS_STRING_VIEW src, uintptr_t* baseOut, size_t* sizeOut) { if (!baseOut || !sizeOut) return NTLSTATUS_INVALID_PARAMETER; *baseOut = 0; *sizeOut = 0; if (!SysSvIsUtf8Text(src)) return NTLSTATUS_INVALID_PARAMETER; if (src.Length == (size_t)-1) return NTLSTATUS_BUFFER_TOO_SMALL; size_t len = src.Length + 1; char* dst = (char*)MmAllocateUserMemoryForProcess(proc, len); if (!dst) return NTLSTATUS_NO_MEMORY; if (src.Length) memcpy(dst, src.Data, src.Length); dst[src.Length] = '\0'; *baseOut = (uintptr_t)dst; *sizeOut = len; return NTLSTATUS_SUCCESS; } // Releases a partially constructed process environment. static void KiFreePartialProcessEnvironment(PROCESS* proc) { if (!proc) return; if (proc->UserPebBase && proc->UserPebSize) { MmFreeUserMemoryForProcess(proc, (void*)proc->UserPebBase); proc->UserPebBase = 0; proc->UserPebSize = 0; } if (proc->UserImagePathBase && proc->UserImagePathSize) { MmFreeUserMemoryForProcess(proc, (void*)proc->UserImagePathBase); proc->UserImagePathBase = 0; proc->UserImagePathSize = 0; } if (proc->UserCmdlineBase && proc->UserCmdlineSize) { MmFreeUserMemoryForProcess(proc, (void*)proc->UserCmdlineBase); proc->UserCmdlineBase = 0; proc->UserCmdlineSize = 0; } if (proc->UserStartupCapabilitiesBase && proc->UserStartupCapabilitiesSize) { MmFreeUserMemoryForProcess(proc, (void*)proc->UserStartupCapabilitiesBase); proc->UserStartupCapabilitiesBase = 0; proc->UserStartupCapabilitiesSize = 0; } } KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc, SYS_STRING_VIEW imagePath, SYS_STRING_VIEW commandLine) { if (!proc || imagePath.Length == 0) return NTLSTATUS_INVALID_PARAMETER; if (!SysSvIsUtf8Text(imagePath) || !SysSvIsUtf8Text(commandLine)) return NTLSTATUS_INVALID_PARAMETER; SYS_STRING_VIEW finalCommandLine = commandLine.Length ? commandLine : imagePath; KiFreePartialProcessEnvironment(proc); KSTATUS s = KiCopyEnvironmentStringView(proc, imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize); if (!KSUCCESS(s)) goto fail; s = KiCopyEnvironmentStringView(proc, finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize); if (!KSUCCESS(s)) goto fail; SYS_PEB* peb = (SYS_PEB*)MmAllocateUserMemoryForProcess(proc, sizeof(SYS_PEB)); if (!peb) { s = NTLSTATUS_NO_MEMORY; goto fail; } memset(peb, 0, sizeof(SYS_PEB)); peb->Magic = SYS_PEB_MAGIC; peb->Version = SYS_ENV_VERSION; peb->ProcessId = proc->ProcessId; peb->ImagePath = SysSvFromParts((const char*)proc->UserImagePathBase, imagePath.Length); peb->CommandLine = SysSvFromParts((const char*)proc->UserCmdlineBase, finalCommandLine.Length); peb->StartupCapabilities = (const SYS_STARTUP_CAPABILITY*)0; peb->StartupCapabilityCount = 0; peb->Reserved = 0; peb->LoaderData = (const void*)0; proc->UserPebBase = (uintptr_t)peb; proc->UserPebSize = sizeof(SYS_PEB); return NTLSTATUS_SUCCESS; fail: KiFreePartialProcessEnvironment(proc); return s; } // Copies the immutable startup-capability descriptor table into child-owned user memory. KSTATUS KeSetProcessStartupCapabilities(PROCESS* proc, const SYS_STARTUP_CAPABILITY* capabilities, uint32_t count) { if (!proc || !proc->UserPebBase) return NTLSTATUS_INVALID_PARAMETER; if (count > SYS_STARTUP_CAPABILITY_MAX) return NTLSTATUS_LIMIT_REACHED; if (count && !capabilities) return NTLSTATUS_INVALID_PARAMETER; if (proc->UserStartupCapabilitiesBase && proc->UserStartupCapabilitiesSize) { MmFreeUserMemoryForProcess(proc, (void*)proc->UserStartupCapabilitiesBase); proc->UserStartupCapabilitiesBase = 0; proc->UserStartupCapabilitiesSize = 0; } SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase; peb->StartupCapabilities = (const SYS_STARTUP_CAPABILITY*)0; peb->StartupCapabilityCount = 0; if (!count) return NTLSTATUS_SUCCESS; size_t bytes = (size_t)count * sizeof(SYS_STARTUP_CAPABILITY); SYS_STARTUP_CAPABILITY* dst = (SYS_STARTUP_CAPABILITY*)MmAllocateUserMemoryForProcess(proc, bytes); if (!dst) return NTLSTATUS_NO_MEMORY; memcpy(dst, capabilities, bytes); KSTATUS protectStatus = MmVadProtect(proc, dst, bytes, PAGE_PRESENT | PAGE_USER | PAGE_NX); if (!KSUCCESS(protectStatus)) { MmFreeUserMemoryForProcess(proc, dst); return protectStatus; } proc->UserStartupCapabilitiesBase = (uintptr_t)dst; proc->UserStartupCapabilitiesSize = bytes; peb->StartupCapabilities = dst; peb->StartupCapabilityCount = count; return NTLSTATUS_SUCCESS; } // Installs the opaque userspace loader-data pointer in the child PEB. KSTATUS KeSetProcessLoaderData(PROCESS* proc, const void* loaderData) { if (!proc || !proc->UserPebBase) return NTLSTATUS_INVALID_PARAMETER; SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase; peb->LoaderData = loaderData; return NTLSTATUS_SUCCESS; } // Stops new process-construction operations. Active builders defer final teardown until they leave. void KeStopProcessConstruction(PROCESS* process) { if (!process) return; NtlAtomicExchange32(&process->BuildState, KE_PROCESS_BUILD_DEAD); } // Process exit / termination. void KeCompleteProcessExitIfLastThread(PROCESS* process) { if (!process) return; if (NtlAtomicLoad32(&process->BuildOperations) != 0) return; uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock); uint32_t threadCount = process->ThreadCount; KeReleaseSpinlock(&process->ThreadLock, flags); if (threadCount == 0 && NtlAtomicExchange32(&process->Exited, 1) == 0) { KiProcessRundownResources(process); if (process->ExitEvent) KeSignalEvent(process->ExitEvent); } } // Exits one process by terminating every thread in it. // SysExitThread remains the operation for ending only the calling thread. KSTATUS KeExitProcess(PROCESS* process, int32_t exitCode) { return KeTerminateProcess(process, exitCode); } // Begins termination of one process and wakes every sibling thread. KSTATUS KeTerminateProcess(PROCESS* process, int32_t exitCode) { if (!process) return NTLSTATUS_INVALID_PARAMETER; if (KeIsProcessTerminationProtected(process) && KeGetCurrentProcess() != process) return NTLSTATUS_ACCESS_DENIED; process->ExitCode = exitCode; NtlAtomicExchange32(&process->Terminating, 1); KeStopProcessConstruction(process); if (NtlAtomicLoad32(&process->BuildOperations) != 0) return NTLSTATUS_SUCCESS; uintptr_t flags = KeAcquireSpinlock(&process->ThreadLock); LIST_ENTRY* head = &process->ThreadList; THREAD* self = KeGetCurrentThread(); KCPU* selfCpu = KeGetCurrentCpu(); uint32_t selfIndex = selfCpu ? selfCpu->CpuIndex : KE_INVALID_CPU; for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { THREAD* t = CONTAINING_RECORD(node, THREAD, ProcessLink); if (t == self) continue; // Most user waits must be interrupted immediately. A thread can defer // termination only while synchronous kernel work still owns its stack // or process memory; that thread is allowed to finish the ownership // interval before the normal termination gates reap it. int deferred = KeThreadTerminationDeferred(t); if (t->State == ThreadSuspended && !deferred) (void)KeResumeThread(t, (uint32_t*)0); else if (t->State == ThreadWaiting && !deferred) KeAbortThreadWait(t, NTLSTATUS_ALERTED); else if (!deferred) (void)KeAlertThread(t); if (t->RunningCpu != KE_INVALID_CPU && t->RunningCpu != selfIndex && t->RunningCpu < gCpuCount) { KCPU* cpu = &gCpus[t->RunningCpu]; if (cpu->State == KeCpuOnline) { NtlAtomicExchange32(&cpu->ReschedulePending, 1); KeSendProcessorInterrupt(cpu->HardwareId, KE_VECTOR_RESCHEDULE_IPI); } } } KeReleaseSpinlock(&process->ThreadLock, flags); KeCompleteProcessExitIfLastThread(process); return NTLSTATUS_SUCCESS; } // Checks whether process Terminating. bool KeIsProcessTerminating(PROCESS* process) { return process && process->Terminating != 0; } // Returns whether process termination is forbidden. bool KeIsProcessTerminationProtected(PROCESS* process) { return process && (process->Flags & KE_PROCESS_FLAG_TERMINATION_PROTECTED) != 0; } // Copies the recorded process exit code. KSTATUS KeGetProcessExitCode(PROCESS* process, int32_t* exitCodeOut) { if (!process || !exitCodeOut) return NTLSTATUS_INVALID_PARAMETER; *exitCodeOut = process->ExitCode; return NTLSTATUS_SUCCESS; } // Returns process By Id. PROCESS* KeGetProcessById(uint32_t pid) { KiProcessInit(); uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); LIST_ENTRY* head = &gProcessList; LIST_ENTRY* node = head->Fwd; while (node != head) { PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink); if (p->ProcessId == pid && ObTryReferenceObject(&p->Header)) { KeReleaseSpinlock(&gProcessListLock, flags); return p; } node = node->Fwd; } KeReleaseSpinlock(&gProcessListLock, flags); return (PROCESS*)0; }