Files

712 lines
28 KiB
C

// Kernel process model. This file owns process lifetime, thread membership, and address-space metadata.
#include <ntlite/atomic.h>
#include <stdbool.h>
#include <string.h>
#include <kernel/ke_idt.h>
#include <kernel/ke_hal.h>
#include <kernel/ke_list.h>
#include <intrin.h>
#include <kernel/ke_process.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_event.h>
#include <kernel/ke_debug.h>
#include <kernel/ke_userenv.h>
#include <kernel/io_file.h>
#include <kernel/mm_virt.h>
#include <kernel/ob_object.h>
#include <kernel/ke_cpu.h>
#include <kernel/ke_id.h>
#include <kernel/se_security.h>
#include <ntlite/syscall.h>
// 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;
}