mirror-ac/driver/apc.c

245 lines
7.1 KiB
C
Raw Permalink Normal View History

2024-01-13 22:33:57 +01:00
#include "apc.h"
#include "driver.h"
#include "imports.h"
2024-07-22 12:43:09 +02:00
#include "lib/stdlib.h"
2024-01-13 22:33:57 +01:00
VOID
2024-08-04 08:30:31 +02:00
GetApcContextByIndex(_Out_ PVOID* Context, _In_ UINT32 Index)
2024-01-13 22:33:57 +01:00
{
2024-08-04 08:30:31 +02:00
NT_ASSERT(Index <= MAXIMUM_APC_CONTEXTS);
2024-04-13 10:23:14 +02:00
AcquireDriverConfigLock();
2024-05-11 14:54:58 +02:00
*Context = (PVOID)GetApcContextArray()[Index];
2024-04-13 10:23:14 +02:00
ReleaseDriverConfigLock();
2024-01-13 22:33:57 +01:00
}
VOID
2024-08-04 08:30:31 +02:00
GetApcContext(_Out_ PVOID* Context, _In_ UINT32 ContextIdentifier)
2024-01-13 22:33:57 +01:00
{
2024-08-04 08:30:31 +02:00
NT_ASSERT(ContextIdentifier <= MAXIMUM_APC_CONTEXTS);
PAPC_CONTEXT_HEADER header = NULL;
2024-04-13 10:23:14 +02:00
AcquireDriverConfigLock();
2024-01-13 22:33:57 +01:00
2024-08-04 08:30:31 +02:00
for (UINT32 index = 0; index < MAXIMUM_APC_CONTEXTS; index++) {
header = GetApcContextArray()[index];
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (!header)
continue;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (header->context_id != ContextIdentifier)
continue;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
*Context = header;
goto unlock;
}
2024-01-13 22:33:57 +01:00
unlock:
2024-04-13 10:23:14 +02:00
ReleaseDriverConfigLock();
2024-01-13 22:33:57 +01:00
}
/*
* No need to hold the lock here as the thread freeing the APCs will
* already hold the configuration lock. We also dont want to release and
* reclaim the lock before calling this function since we need to ensure
* we hold the lock during the entire decrement and free process.
*/
BOOLEAN
2024-05-11 14:54:58 +02:00
FreeApcContextStructure(_Inout_ PAPC_CONTEXT_HEADER Context)
2024-01-13 22:33:57 +01:00
{
2024-08-04 08:30:31 +02:00
NT_ASSERT(Context <= MAXIMUM_APC_CONTEXTS);
2024-01-13 22:33:57 +01:00
2024-08-04 08:30:31 +02:00
PUINT64 entry = NULL;
for (UINT32 index = 0; index < MAXIMUM_APC_CONTEXTS; index++) {
entry = GetApcContextArray();
2024-01-13 22:33:57 +01:00
2024-05-11 14:54:58 +02:00
if (entry[index] != (UINT64)Context)
2024-04-13 10:23:14 +02:00
continue;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (Context->count > 0)
return FALSE;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
ImpExFreePoolWithTag(Context, POOL_TAG_APC);
entry[index] = NULL;
return TRUE;
}
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
return FALSE;
2024-01-13 22:33:57 +01:00
}
VOID
2024-08-04 08:30:31 +02:00
IncrementApcCount(_In_ UINT32 ContextId)
2024-01-13 22:33:57 +01:00
{
2024-08-04 08:30:31 +02:00
NT_ASSERT(ContextId <= MAXIMUM_APC_CONTEXTS);
2024-04-13 10:23:14 +02:00
PAPC_CONTEXT_HEADER header = NULL;
2024-08-04 08:30:31 +02:00
2024-04-13 10:23:14 +02:00
GetApcContext(&header, ContextId);
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (!header)
return;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
AcquireDriverConfigLock();
header->count += 1;
ReleaseDriverConfigLock();
2024-01-13 22:33:57 +01:00
}
VOID
2024-08-04 08:30:31 +02:00
FreeApcAndDecrementApcCount(_Inout_ PRKAPC Apc, _In_ UINT32 ContextId)
2024-01-13 22:33:57 +01:00
{
2024-08-04 08:30:31 +02:00
NT_ASSERT(Apc != NULL);
NT_ASSERT(ContextId <= MAXIMUM_APC_CONTEXTS);
2024-04-13 10:23:14 +02:00
PAPC_CONTEXT_HEADER context = NULL;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
ImpExFreePoolWithTag(Apc, POOL_TAG_APC);
GetApcContext(&context, ContextId);
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (!context)
return;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
AcquireDriverConfigLock();
context->count -= 1;
ReleaseDriverConfigLock();
2024-01-13 22:33:57 +01:00
}
/*
* The reason we use a query model rather then checking the count of queued APCs
* after each APC free and decrement is that the lock will be recursively
* acquired by freeing threads (i.e executing APCs) rather then APC allocation
* threads. The reason for this being that freeing threads are executing at a
* higher IRQL then the APC allocation thread, hence they are granted higher
* priority by the scheduler when determining which thread will accquire the
* lock next:
2024-01-13 22:33:57 +01:00
*
* [+] Freeing thread -> ApcKernelRoutine IRQL: 1 (APC_LEVEL)
* [+] Allocation thread -> ValidateThreadViaKernelApcCallback IRQL: 0
* (PASSIVE_LEVEL)
2024-01-13 22:33:57 +01:00
*
* As a result, once an APC is executed and reaches the freeing stage, it will
* acquire the lock and decrement it. Then, if atleast 1 APC execution thread is
* waiting on the lock, it will be prioritised due to its higher IRQL and the
* cycle will continue. Eventually, the count will reach 0 due to recursive
* acquisition by the executing APC threads and then the function will free the
* APC context structure. This will then cause a bug check the next time a
* thread accesses the context structure and hence not good :c.
2024-01-13 22:33:57 +01:00
*
* So to combat this, we add in a flag specifying whether or not an allocation
* of APCs is in progress, and even if the count is 0 we will not free the
* context structure until the count is 0 and allocation_in_progress is 0. We
* can then call this function alongside other query callbacks via IOCTL to
* constantly monitor the status of open APC contexts.
2024-01-13 22:33:57 +01:00
*/
NTSTATUS
QueryActiveApcContextsForCompletion()
{
2024-08-04 08:30:31 +02:00
PAPC_CONTEXT_HEADER entry = NULL;
2024-05-11 14:54:58 +02:00
AcquireDriverConfigLock();
2024-05-05 12:42:22 +02:00
2024-08-04 08:30:31 +02:00
for (UINT32 index = 0; index < MAXIMUM_APC_CONTEXTS; index++) {
2024-04-13 10:23:14 +02:00
GetApcContextByIndex(&entry, index);
if (!entry)
2024-05-11 14:54:58 +02:00
continue;
2024-04-13 10:23:14 +02:00
if (entry->count > 0 || entry->allocation_in_progress == TRUE)
2024-05-11 14:54:58 +02:00
continue;
2024-04-13 10:23:14 +02:00
switch (entry->context_id) {
case APC_CONTEXT_ID_STACKWALK:
2024-08-04 08:30:31 +02:00
FreeApcStackwalkApcContextInformation(entry);
2024-04-13 10:23:14 +02:00
FreeApcContextStructure(entry);
break;
2024-01-13 22:33:57 +01:00
}
2024-04-13 10:23:14 +02:00
}
2024-05-05 12:42:22 +02:00
ReleaseDriverConfigLock();
2024-04-13 10:23:14 +02:00
return STATUS_SUCCESS;
2024-01-13 22:33:57 +01:00
}
VOID
InsertApcContext(_In_ PVOID Context)
{
2024-08-04 08:30:31 +02:00
NT_ASSERT(Context != NULL);
PUINT64 entry = NULL;
2024-04-13 10:23:14 +02:00
if (IsDriverUnloading())
2024-05-11 14:54:58 +02:00
return;
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
AcquireDriverConfigLock();
2024-01-13 22:33:57 +01:00
2024-08-04 08:30:31 +02:00
for (UINT32 index = 0; index < MAXIMUM_APC_CONTEXTS; index++) {
entry = GetApcContextArray();
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (entry[index] == NULL) {
2024-05-11 14:54:58 +02:00
entry[index] = (UINT64)Context;
2024-04-13 10:23:14 +02:00
goto end;
2024-01-13 22:33:57 +01:00
}
2024-04-13 10:23:14 +02:00
}
2024-01-13 22:33:57 +01:00
end:
2024-04-13 10:23:14 +02:00
ReleaseDriverConfigLock();
2024-01-13 22:33:57 +01:00
}
/*
* The driver config structure holds an array of pointers to APC context
* structures. These APC context structures are unique to each APC operation
* that this driver will perform. For example, a single context will manage all
* APCs that are used to stackwalk, whilst another context will be used to
* manage all APCs used to query a threads memory for example.
2024-01-13 22:33:57 +01:00
*
* Due to the nature of APCs, its important to keep a total or count of the
* number of APCs we have allocated and queued to threads. This information is
* stored in the APC_CONTEXT_HEADER which all APC context structures will
* contain as the first entry in their structure. It holds the ContextId which
* is a unique identifier for the type of APC operation it is managing aswell as
* the number of currently queued APCs.
2024-01-13 22:33:57 +01:00
*
* When an APC is allocated a queued, we increment this count. When an APC is
* completed and freed, we decrement this counter and free the APC itself. If
* all APCs have been freed and the counter is 0,the following objects will be
* freed:
2024-01-13 22:33:57 +01:00
*
* 1. Any additional allocations used by the APC stored in the context structure
* 2. The APC context structure for the given APC operation
* 3. The APC context entry in g_DriverConfig->>apc_contexts will be zero'd.
*
* It's important to remember that the driver can unload when pending APC's have
* not been freed due to the limitations windows places on APCs, however I am in
* the process of finding a solution for this.
2024-01-13 22:33:57 +01:00
*/
BOOLEAN
DrvUnloadFreeAllApcContextStructures()
{
2024-08-04 08:30:31 +02:00
PUINT64 entry = NULL;
PAPC_CONTEXT_HEADER context = NULL;
LARGE_INTEGER delay = {.QuadPart = -ABSOLUTE(SECONDS(1))};
2024-04-13 10:23:14 +02:00
AcquireDriverConfigLock();
2024-01-13 22:33:57 +01:00
2024-08-04 08:30:31 +02:00
for (UINT32 index = 0; index < MAXIMUM_APC_CONTEXTS; index++) {
entry = GetApcContextArray();
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (entry[index] == NULL)
continue;
2024-01-13 22:33:57 +01:00
2024-08-04 08:30:31 +02:00
context = entry[index];
2024-01-13 22:33:57 +01:00
2024-04-13 10:23:14 +02:00
if (context->count > 0) {
2024-08-04 08:30:31 +02:00
DEBUG_VERBOSE(
"Still active APCs: Index: %lx, Count: %lx",
index,
context->count);
KeDelayExecutionThread(KernelMode, FALSE, &delay);
2024-04-13 10:23:14 +02:00
ReleaseDriverConfigLock();
return FALSE;
2024-01-13 22:33:57 +01:00
}
2024-04-13 10:23:14 +02:00
2024-05-05 12:42:22 +02:00
ImpExFreePoolWithTag(context, POOL_TAG_APC);
2024-04-13 10:23:14 +02:00
}
2024-05-11 14:54:58 +02:00
2024-04-13 10:23:14 +02:00
ReleaseDriverConfigLock();
return TRUE;
2024-01-13 22:33:57 +01:00
}