mirror-ac/driver/queue.c
2023-09-28 23:56:07 +10:00

354 lines
7.2 KiB
C

#include "queue.h"
#include "callbacks.h"
#include "driver.h"
#include "queue.h"
#include "pool.h"
#include "thread.h"
#include "common.h"
/*
* This mutex is to prevent a new item being pushed to the queue
* while the HandlePeriodicCallbackReportQueue is iterating through
* the objects. This can be an issue because the spinlock is released
* after each report is placed in the IRP buffer which means a new report
* can be pushed into the queue before the next iteration can take ownership
* of the spinlock.
*/
typedef struct _REPORT_QUEUE_CONFIGURATION
{
QUEUE_HEAD head;
KGUARDED_MUTEX lock;
}REPORT_QUEUE_CONFIGURATION, *PREPORT_QUEUE_CONFIGURATION;
REPORT_QUEUE_CONFIGURATION report_queue_config = { 0 };
VOID
InitialiseGlobalReportQueue(
_In_ PBOOLEAN Status
)
{
report_queue_config.head.start = NULL;
report_queue_config.head.end = NULL;
report_queue_config.head.entries = 0;
KeInitializeSpinLock( &report_queue_config.head.lock );
KeInitializeGuardedMutex( &report_queue_config.lock );
*Status = TRUE;
}
//PQUEUE_HEAD QueueCreate()
//{
// PQUEUE_HEAD head = ExAllocatePool2( POOL_FLAG_NON_PAGED, sizeof( QUEUE_HEAD ), QUEUE_POOL_TAG );
//
// if ( !head )
// return NULL;
//
// head->end = NULL;
// head->start = NULL;
// head->entries = 0;
//
// KeInitializeSpinLock( &head->lock );
//
// return head;
//}
VOID
QueuePush(
_In_ PQUEUE_HEAD Head,
_In_ PVOID Data
)
{
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock( &Head->lock, &irql );
PQUEUE_NODE temp = ExAllocatePool2( POOL_FLAG_NON_PAGED, sizeof( QUEUE_NODE ), QUEUE_POOL_TAG );
if ( !temp )
goto end;
Head->entries += 1;
temp->data = Data;
if ( Head->end != NULL )
Head->end->next = temp;
Head->end = temp;
if ( Head->start == NULL )
Head->start = temp;
end:
KeReleaseSpinLock( &Head->lock, irql );
}
PVOID
QueuePop(
_In_ PQUEUE_HEAD Head
)
{
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock( &Head->lock, &irql );
PVOID data = NULL;
PQUEUE_NODE temp = Head->start;
if ( temp == NULL )
goto end;
Head->entries = Head->entries - 1;
data = temp->data;
Head->start = temp->next;
if ( Head->end == temp )
Head->end = NULL;
ExFreePoolWithTag( temp, QUEUE_POOL_TAG );
end:
KeReleaseSpinLock( &Head->lock, irql );
return data;
}
VOID
InsertReportToQueue(
_In_ PVOID Report
)
{
KeAcquireGuardedMutex( &report_queue_config.lock );
QueuePush( &report_queue_config.head, Report );
KeReleaseGuardedMutex( &report_queue_config.lock );
}
VOID
FreeGlobalReportQueueObjects()
{
KeAcquireGuardedMutex( &report_queue_config.lock );
PVOID report = QueuePop( &report_queue_config.head );
while ( report != NULL )
{
ExFreePoolWithTag( report, REPORT_POOL_TAG );
report = QueuePop( &report_queue_config.head );
}
end:
KeReleaseGuardedMutex( &report_queue_config.lock );
}
/*
* This function handles sending all the pending reports in the global report
* queue to the usermode application. This function is called periodically by the
* usermode application. The reason I have implemented this is because as this application
* expanded, it became apparent that some of the driver functions will generate multiple
* reports as a result of a single usermode request and hence it makes dealing with
* reports generated from ObRegisterCallbacks for example much easier.
*/
NTSTATUS
HandlePeriodicGlobalReportQueueQuery(
_In_ PIRP Irp
)
{
PVOID report = NULL;
INT count = 0;
GLOBAL_REPORT_QUEUE_HEADER header;
PVOID report_buffer = NULL;
PREPORT_HEADER report_header;
SIZE_T total_size = NULL;
KeAcquireGuardedMutex( &report_queue_config.lock );
report = QueuePop( &report_queue_config.head );
report_buffer = ExAllocatePool2(
POOL_FLAG_NON_PAGED,
sizeof( INVALID_PROCESS_ALLOCATION_REPORT ) * MAX_REPORTS_PER_IRP + sizeof( GLOBAL_REPORT_QUEUE_HEADER ),
REPORT_QUEUE_TEMP_BUFFER_TAG
);
if ( !report_buffer )
{
KeReleaseGuardedMutex( &report_queue_config.lock );
return STATUS_MEMORY_NOT_ALLOCATED;
}
if ( report == NULL )
{
DEBUG_LOG( "callback report queue is empty, returning" );
goto end;
}
while ( report != NULL )
{
if ( count >= MAX_REPORTS_PER_IRP )
goto end;
report_header = ( PREPORT_HEADER )report;
switch ( report_header->report_id )
{
case REPORT_ILLEGAL_HANDLE_OPERATION:
RtlCopyMemory(
( UINT64 )report_buffer + sizeof( GLOBAL_REPORT_QUEUE_HEADER ) + total_size,
report,
sizeof( OPEN_HANDLE_FAILURE_REPORT )
);
total_size += sizeof( OPEN_HANDLE_FAILURE_REPORT );
break;
case REPORT_ILLEGAL_ATTACH_PROCESS:
RtlCopyMemory(
( UINT64 )report_buffer + sizeof( GLOBAL_REPORT_QUEUE_HEADER ) + total_size,
report,
sizeof( ATTACH_PROCESS_REPORT )
);
total_size += sizeof( ATTACH_PROCESS_REPORT );
break;
case REPORT_INVALID_PROCESS_ALLOCATION:
RtlCopyMemory(
( UINT64 )report_buffer + sizeof( GLOBAL_REPORT_QUEUE_HEADER ) + total_size,
report,
sizeof( INVALID_PROCESS_ALLOCATION_REPORT )
);
total_size += sizeof( INVALID_PROCESS_ALLOCATION_REPORT );
break;
case REPORT_APC_STACKWALK:
RtlCopyMemory(
( UINT64 )report_buffer + sizeof( GLOBAL_REPORT_QUEUE_HEADER ) + total_size,
report,
sizeof( APC_STACKWALK_REPORT )
);
total_size += sizeof( APC_STACKWALK_REPORT );
break;
}
/* QueuePop frees the node, but we still need to free the returned data */
ExFreePoolWithTag( report, REPORT_POOL_TAG );
report = QueuePop( &report_queue_config.head );
count += 1;
}
end:
KeReleaseGuardedMutex( &report_queue_config.lock );
Irp->IoStatus.Information = sizeof( GLOBAL_REPORT_QUEUE_HEADER ) + total_size;
header.count = count;
RtlCopyMemory(
report_buffer,
&header,
sizeof( GLOBAL_REPORT_QUEUE_HEADER ) );
RtlCopyMemory(
Irp->AssociatedIrp.SystemBuffer,
report_buffer,
sizeof( GLOBAL_REPORT_QUEUE_HEADER ) + total_size
);
if ( report_buffer )
ExFreePoolWithTag( report_buffer, REPORT_QUEUE_TEMP_BUFFER_TAG );
DEBUG_LOG( "Moved all reports into the IRP, sending !" );
return STATUS_SUCCESS;
}
VOID
ListInit(
_In_ PLIST_HEAD ListHead
)
{
KeInitializeSpinLock( &ListHead->lock );
ListHead->start = NULL;
}
PLIST_ITEM
ListInsert(
_In_ PLIST_HEAD ListHead,
_In_ PLIST_ITEM NewEntry
)
{
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock( &ListHead->lock, &irql );
PLIST_ITEM old_entry = ListHead->start;
ListHead->start = NewEntry;
NewEntry->next = old_entry;
KeReleaseSpinLock( &ListHead->lock, irql );
}
PVOID
ListRemoveFirst(
_In_ PLIST_HEAD ListHead
)
{
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock( &ListHead->lock, &irql );
if ( ListHead->start )
{
PLIST_ITEM entry = ListHead->start;
ListHead->start = ListHead->start->next;
ExFreePoolWithTag( entry, POOL_TAG_APC );
}
KeReleaseSpinLock( &ListHead->lock, irql );
}
PVOID
ListRemoveItem(
_In_ PLIST_HEAD ListHead,
_Inout_ PLIST_ITEM ListItem
)
{
KIRQL irql = KeGetCurrentIrql();
KeAcquireSpinLock( &ListHead->lock, &irql );
PLIST_ITEM entry = ListHead->start;
if ( !entry )
goto unlock;
if ( entry == ListItem )
{
ListHead->start = entry->next;
ExFreePoolWithTag( ListItem, POOL_TAG_APC );
goto unlock;
}
while ( entry->next )
{
if ( entry->next == ListItem )
{
entry->next = ListItem->next;
ExFreePoolWithTag( ListItem, POOL_TAG_APC );
goto unlock;
}
entry = entry->next;
}
unlock:
KeReleaseSpinLock( &ListHead->lock, irql);
}