712 lines
28 KiB
C
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;
|
|
}
|