You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
12 KiB
440 lines
12 KiB
/*++
|
|
|
|
Copyright (c) 20001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
cmhook.c
|
|
|
|
Abstract:
|
|
|
|
Provides routines for implementing callbacks into the registry code.
|
|
Callbacks are to be used by the virus filter drivers and cluster
|
|
replication engine.
|
|
|
|
Author:
|
|
|
|
Dragos C. Sambotin (DragosS) 20-Mar-2001
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
#include "cmp.h"
|
|
|
|
#define CM_MAX_CALLBACKS 100 //TBD
|
|
|
|
typedef struct _CM_CALLBACK_CONTEXT_BLOCK {
|
|
LARGE_INTEGER Cookie; // to identify a specific callback for deregistration purposes
|
|
LIST_ENTRY ThreadListHead; // Active threads inside this callback
|
|
FAST_MUTEX ThreadListLock; // syncronize access to the above
|
|
PVOID CallerContext;
|
|
} CM_CALLBACK_CONTEXT_BLOCK, *PCM_CALLBACK_CONTEXT_BLOCK;
|
|
|
|
typedef struct _CM_ACTIVE_NOTIFY_THREAD {
|
|
LIST_ENTRY ThreadList;
|
|
PETHREAD Thread;
|
|
} CM_ACTIVE_NOTIFY_THREAD, *PCM_ACTIVE_NOTIFY_THREAD;
|
|
|
|
#ifdef ALLOC_DATA_PRAGMA
|
|
#pragma data_seg("PAGEDATA")
|
|
#endif
|
|
|
|
ULONG CmpCallBackCount = 0;
|
|
EX_CALLBACK CmpCallBackVector[CM_MAX_CALLBACKS] = {0};
|
|
|
|
#ifdef ALLOC_DATA_PRAGMA
|
|
#pragma data_seg()
|
|
#endif
|
|
|
|
VOID
|
|
CmpInitCallback(VOID);
|
|
|
|
BOOLEAN
|
|
CmpCheckRecursionAndRecordThreadInfo(
|
|
PCM_CALLBACK_CONTEXT_BLOCK CallbackBlock,
|
|
PCM_ACTIVE_NOTIFY_THREAD ActiveThreadInfo
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE,CmRegisterCallback)
|
|
#pragma alloc_text(PAGE,CmUnRegisterCallback)
|
|
#pragma alloc_text(PAGE,CmpInitCallback)
|
|
#pragma alloc_text(PAGE,CmpCallCallBacks)
|
|
#pragma alloc_text(PAGE,CmpCheckRecursionAndRecordThreadInfo)
|
|
#endif
|
|
|
|
|
|
NTSTATUS
|
|
CmRegisterCallback(IN PEX_CALLBACK_FUNCTION Function,
|
|
IN PVOID Context,
|
|
IN OUT PLARGE_INTEGER Cookie
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Registers a new callback.
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
PEX_CALLBACK_ROUTINE_BLOCK RoutineBlock;
|
|
ULONG i;
|
|
PCM_CALLBACK_CONTEXT_BLOCK CmCallbackContext;
|
|
|
|
PAGED_CODE();
|
|
|
|
CmCallbackContext = (PCM_CALLBACK_CONTEXT_BLOCK)ExAllocatePoolWithTag (PagedPool,
|
|
sizeof (CM_CALLBACK_CONTEXT_BLOCK),
|
|
'bcMC');
|
|
if( CmCallbackContext == NULL ) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RoutineBlock = ExAllocateCallBack (Function,CmCallbackContext);
|
|
if( RoutineBlock == NULL ) {
|
|
ExFreePool(CmCallbackContext);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// init the context
|
|
//
|
|
KeQuerySystemTime(&(CmCallbackContext->Cookie));
|
|
*Cookie = CmCallbackContext->Cookie;
|
|
InitializeListHead(&(CmCallbackContext->ThreadListHead));
|
|
ExInitializeFastMutex(&(CmCallbackContext->ThreadListLock));
|
|
CmCallbackContext->CallerContext = Context;
|
|
|
|
//
|
|
// find a spot where we could add this callback
|
|
//
|
|
for( i=0;i<CM_MAX_CALLBACKS;i++) {
|
|
if( ExCompareExchangeCallBack (&CmpCallBackVector[i],RoutineBlock,NULL) ) {
|
|
InterlockedExchangeAdd ((PLONG) &CmpCallBackCount, 1);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// no more callbacks
|
|
//
|
|
ExFreePool(CmCallbackContext);
|
|
ExFreeCallBack(RoutineBlock);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
CmUnRegisterCallback(IN LARGE_INTEGER Cookie)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Unregisters a callback.
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
ULONG i;
|
|
PCM_CALLBACK_CONTEXT_BLOCK CmCallbackContext;
|
|
PEX_CALLBACK_ROUTINE_BLOCK RoutineBlock;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Search for this cookie
|
|
//
|
|
for( i=0;i<CM_MAX_CALLBACKS;i++) {
|
|
RoutineBlock = ExReferenceCallBackBlock(&(CmpCallBackVector[i]) );
|
|
if( RoutineBlock ) {
|
|
CmCallbackContext = (PCM_CALLBACK_CONTEXT_BLOCK)ExGetCallBackBlockContext(RoutineBlock);
|
|
if( CmCallbackContext && (CmCallbackContext->Cookie.QuadPart == Cookie.QuadPart) ) {
|
|
//
|
|
// found it
|
|
//
|
|
if( ExCompareExchangeCallBack (&CmpCallBackVector[i],NULL,RoutineBlock) ) {
|
|
InterlockedExchangeAdd ((PLONG) &CmpCallBackCount, -1);
|
|
|
|
ExDereferenceCallBackBlock (&(CmpCallBackVector[i]),RoutineBlock);
|
|
//
|
|
// wait for others to release their reference, then tear down the structure
|
|
//
|
|
ExWaitForCallBacks (RoutineBlock);
|
|
|
|
ExFreePool(CmCallbackContext);
|
|
ExFreeCallBack(RoutineBlock);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
} else {
|
|
ExDereferenceCallBackBlock (&(CmpCallBackVector[i]),RoutineBlock);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
NTSTATUS CmpTestCallback(
|
|
IN PVOID CallbackContext,
|
|
IN PVOID Argument1,
|
|
IN PVOID Argument2
|
|
);
|
|
|
|
//
|
|
// Cm internals
|
|
//
|
|
NTSTATUS
|
|
CmpCallCallBacks (
|
|
IN REG_NOTIFY_CLASS Type,
|
|
IN PVOID Argument
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function calls the callback thats inside a callback structure
|
|
|
|
Arguments:
|
|
|
|
Type - Nt call selector
|
|
|
|
Argument - Caller provided argument to pass on (one of the REG_*_INFORMATION )
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_SUCCESS or error status returned by the first callback
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG i;
|
|
PEX_CALLBACK_ROUTINE_BLOCK RoutineBlock;
|
|
PCM_CALLBACK_CONTEXT_BLOCK CmCallbackContext;
|
|
|
|
PAGED_CODE();
|
|
|
|
for(i=0;i<CM_MAX_CALLBACKS;i++) {
|
|
RoutineBlock = ExReferenceCallBackBlock(&(CmpCallBackVector[i]) );
|
|
if( RoutineBlock != NULL ) {
|
|
//
|
|
// we have a safe reference on this block.
|
|
//
|
|
//
|
|
// record thread on a stack struct, so we don't need to allocate pool for it. We unlink
|
|
// it from our lists prior to this function exit, so we are on the safe side.
|
|
//
|
|
CM_ACTIVE_NOTIFY_THREAD ActiveThreadInfo;
|
|
|
|
//
|
|
// get context info
|
|
//
|
|
CmCallbackContext = (PCM_CALLBACK_CONTEXT_BLOCK)ExGetCallBackBlockContext(RoutineBlock);
|
|
ASSERT( CmCallbackContext != NULL );
|
|
|
|
ActiveThreadInfo.Thread = PsGetCurrentThread();
|
|
#if DBG
|
|
InitializeListHead(&(ActiveThreadInfo.ThreadList));
|
|
#endif //DBG
|
|
|
|
if( CmpCheckRecursionAndRecordThreadInfo(CmCallbackContext,&ActiveThreadInfo) ) {
|
|
Status = ExGetCallBackBlockRoutine(RoutineBlock)(CmCallbackContext->CallerContext,(PVOID)(ULONG_PTR)Type,Argument);
|
|
//
|
|
// now that we're down, remove ourselves from the thread list
|
|
//
|
|
ExAcquireFastMutex(&(CmCallbackContext->ThreadListLock));
|
|
RemoveEntryList(&(ActiveThreadInfo.ThreadList));
|
|
ExReleaseFastMutex(&(CmCallbackContext->ThreadListLock));
|
|
} else {
|
|
ASSERT( IsListEmpty(&(ActiveThreadInfo.ThreadList)) );
|
|
}
|
|
|
|
ExDereferenceCallBackBlock (&(CmpCallBackVector[i]),RoutineBlock);
|
|
|
|
if( !NT_SUCCESS(Status) ) {
|
|
//
|
|
// don't bother calling other callbacks if this one vetoed.
|
|
//
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
CmpInitCallback(VOID)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Init the callback module
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
ULONG i;
|
|
|
|
PAGED_CODE();
|
|
|
|
CmpCallBackCount = 0;
|
|
for( i=0;i<CM_MAX_CALLBACKS;i++) {
|
|
ExInitializeCallBack (&(CmpCallBackVector[i]));
|
|
}
|
|
|
|
/*
|
|
{
|
|
LARGE_INTEGER Cookie;
|
|
if( NT_SUCCESS(CmRegisterCallback(CmpTestCallback,NULL,&Cookie) ) ) {
|
|
DbgPrint("Test Hooks installed\n");
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
BOOLEAN
|
|
CmpCheckRecursionAndRecordThreadInfo(
|
|
PCM_CALLBACK_CONTEXT_BLOCK CallbackBlock,
|
|
PCM_ACTIVE_NOTIFY_THREAD ActiveThreadInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks if current thread is already inside the callback (recursion avoidance)
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
|
|
--*/
|
|
{
|
|
PLIST_ENTRY AnchorAddr;
|
|
PCM_ACTIVE_NOTIFY_THREAD CurrentThreadInfo;
|
|
|
|
PAGED_CODE();
|
|
|
|
ExAcquireFastMutex(&(CallbackBlock->ThreadListLock));
|
|
|
|
//
|
|
// walk the ActiveThreadList and see if we are already active
|
|
//
|
|
AnchorAddr = &(CallbackBlock->ThreadListHead);
|
|
CurrentThreadInfo = (PCM_ACTIVE_NOTIFY_THREAD)(CallbackBlock->ThreadListHead.Flink);
|
|
|
|
while ( CurrentThreadInfo != (PCM_ACTIVE_NOTIFY_THREAD)AnchorAddr ) {
|
|
CurrentThreadInfo = CONTAINING_RECORD(
|
|
CurrentThreadInfo,
|
|
CM_ACTIVE_NOTIFY_THREAD,
|
|
ThreadList
|
|
);
|
|
if( CurrentThreadInfo->Thread == ActiveThreadInfo->Thread ) {
|
|
//
|
|
// already there!
|
|
//
|
|
ExReleaseFastMutex(&(CallbackBlock->ThreadListLock));
|
|
return FALSE;
|
|
}
|
|
//
|
|
// skip to the next element
|
|
//
|
|
CurrentThreadInfo = (PCM_ACTIVE_NOTIFY_THREAD)(CurrentThreadInfo->ThreadList.Flink);
|
|
}
|
|
|
|
//
|
|
// add this thread
|
|
//
|
|
InsertTailList(&(CallbackBlock->ThreadListHead), &(ActiveThreadInfo->ThreadList));
|
|
ExReleaseFastMutex(&(CallbackBlock->ThreadListLock));
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// test hook procedure
|
|
//
|
|
|
|
BOOLEAN CmpCallbackSpew = FALSE;
|
|
|
|
NTSTATUS CmpTestCallback(
|
|
IN PVOID CallbackContext,
|
|
IN PVOID Argument1,
|
|
IN PVOID Argument2
|
|
)
|
|
{
|
|
REG_NOTIFY_CLASS Type;
|
|
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER (CallbackContext);
|
|
|
|
if( !CmpCallbackSpew ) return STATUS_SUCCESS;
|
|
|
|
Type = (REG_NOTIFY_CLASS)(ULONG_PTR)Argument1;
|
|
switch( Type ) {
|
|
case RegNtPreDeleteKey:
|
|
{
|
|
PREG_DELETE_KEY_INFORMATION pDelete = (PREG_DELETE_KEY_INFORMATION)Argument2;
|
|
//
|
|
// Code to handle NtDeleteKey
|
|
//
|
|
DbgPrint("Callback(NtDeleteKey) called, arg = %p\n",pDelete);
|
|
}
|
|
break;
|
|
case RegNtPreSetValueKey:
|
|
{
|
|
PREG_SET_VALUE_KEY_INFORMATION pSetValue = (PREG_SET_VALUE_KEY_INFORMATION)Argument2;
|
|
//
|
|
// Code to handle NtSetValueKey
|
|
//
|
|
DbgPrint("Callback(NtSetValueKey) called, arg = %p\n",pSetValue);
|
|
}
|
|
break;
|
|
case RegNtPreDeleteValueKey:
|
|
{
|
|
PREG_DELETE_VALUE_KEY_INFORMATION pDeteteValue = (PREG_DELETE_VALUE_KEY_INFORMATION)Argument2;
|
|
//
|
|
// Code to handle NtDeleteValueKey
|
|
//
|
|
DbgPrint("Callback(NtDeleteValueKey) called, arg = %p\n",pDeteteValue);
|
|
}
|
|
break;
|
|
case RegNtPreSetInformationKey:
|
|
{
|
|
PREG_SET_INFORMATION_KEY_INFORMATION pSetInfo = (PREG_SET_INFORMATION_KEY_INFORMATION)Argument2;
|
|
//
|
|
// Code to handle NtSetInformationKey
|
|
//
|
|
DbgPrint("Callback(NtSetInformationKey) called, arg = %p\n",pSetInfo);
|
|
}
|
|
break;
|
|
default:
|
|
DbgPrint("Callback(%lx) called, arg = %p - We don't handle this call\n",(ULONG)Type,Argument2);
|
|
break;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|